This is my first relatively big program. First it shows an askdirectory()
screen to get the path. It gets all subfolders of that path and appends them to a list. It checks every folders' items if they are wanted file types by checking extensions and puts them into a proper sqlite table. After all files written to a database file, a GUI appears to get what user wants to search. After clicking search_button
it gets my_entry
and checks if it's in database. If yes, appends it to a list and after all database is searched, it shows results in a listbox.
There are two lists because I couldn't find the way to get path while only writing file names to a list. So one of the list is shown and only have file names, the other one is hidden and contains whole path for that file.
My code is working relatively slow while writing to a database or reading from it but my real problem is, this program should search 3TB of data which contain lots of folders, files etc.. So when I try to show hundreds (sometimes it is thousands) of items in listBox, GUI freezes. I cannot scroll up/down or open folders by double clicking on their names.
How can I handle that problem? Is there a way to do it with Tkinter or should I use something else?
Any tips on style?
Lastly, about writing to/reading from database performance, should I use threading?
#-*-coding: utf8-*-
import os
import sys
import time
import sqlite3
import tkinter
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
from win32com.shell import shell, shellcon
class Prompt(tkinter.Frame):
def search_button(self):
reading.reader() ##to get data from database tables
self.my_entry = self.ent.get()
if self.my_entry:
for line in reading.lines:
if self.my_entry.lower() in line.split("/")[-1].lower(): ##if entry from textbox in file/path name
self.results.append(line)
self.shown_list()
self.hidden_list()
self.results.clear() #cleared because of next searches
def search_button2(self):
"""i'm destroying first button and adding 2nd one because of UI thing.
Without this UI looks worse than it is now"""
self.lst.destroy()
self.btn2.pack_forget()
self.search_button()
def on_frame_configure(self, event):
self.cvs.configure(scrollregion=self.cvs.bbox("all"))
def __init__(self, den):
self.fp = filepath
self.results = []
self.lbl1 = tkinter.Label(den, text="Select the File Type")
self.lbl2 = tkinter.Label(den, text="Enter the Keyord")
self.ent = tkinter.Entry(den)
self.btn = tkinter.Button(den, text="Search", command=self.search_button)
self.cmb = tkinter.ttk.Combobox(den)
self.cmb["values"] = ["Music","Image","Document","Other","All"]
self.cmb.current("0")
self.lbl1.pack()
self.cmb.pack()
self.lbl2.pack()
self.ent.pack()
self.btn.pack()
##needed to create a canvas to use scrollbar on my Listbox
tkinter.Frame.__init__(self,den)
self.cvs = tkinter.Canvas(den, borderwidth=0, background="#ffffff")
self.frame = tkinter.Frame(self.cvs, background="#ffffff")
self.vsb = tkinter.Scrollbar(den, orient="vertical", command=self.cvs.yview)
self.cvs.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.cvs.pack(fill="both", expand=True)
self.cvs.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.on_frame_configure)
def shown_list(self):
results = self.results
self.lst = tkinter.Listbox(self.frame, selectmode="SINGLE", height = len(results), width = "100")
if self.my_entry and not results:
tkinter.messagebox.showwarning(title = "Can not find",\
message = "There is no result matching with the keyword")
if not self.my_entry:
tkinter.messagebox.showwarning(title = "No keyword to Search" \
,message = "Enter a Keyword")
for index, item in enumerate(results, start=1):
self.lst.insert(index, item.split("\\")[-1][:-4])
if results:
self.lst.bind("<Double-Button-1>", self.open_folder)
self.btn.destroy()
self.btn2 = tkinter.Button(den, text="New Search", command= self.search_button2)
self.btn2.pack()
self.lst.pack(fill="both", expand=True)
def hidden_list(self):
results = self.results
self.lst2 = tkinter.Listbox(self.frame)
for index, item in enumerate(results, start=1):
self.lst2.insert(index, item)
self.lst2.pack()
self.lst2.pack_forget()
self.results = results
def open_folder(self,event): #this opens folder with selected item using shell commands
selected_name = []
self.selection = self.lst2.get(self.lst.curselection()[0])
selected_path = self.selection.split(self.selection.split("\\")[-1])[0][:-1]
selected_name.append(self.selection.split("\\")[-1])
try:
launch_file_explorer(selected_path,selected_name)
except KeyError:
tkinter.messagebox.showwarning(title = "Can not Find the File " \
,message = "Make Sure File Isn't Modified")
def launch_file_explorer(path, files):
folder_pidl = shell.SHILCreateFromPath(path,0)[0]
desktop = shell.SHGetDesktopFolder()
shell_folder = desktop.BindToObject(folder_pidl, None,shell.IID_IShellFolder)
name_to_item_mapping = dict([(desktop.GetDisplayNameOf(item, 0), item) for item in shell_folder])
to_show = []
for file in files:
to_show.append(name_to_item_mapping[file])
shell.SHOpenFolderAndSelectItems(folder_pidl, to_show, 0)
class Reading:
def reader(self):
""" reads items from tables depending on combobox selection"""
if prompt.cmb.get() == "Music":
self.reader_music()
elif prompt.cmb.get() == "Image":
self.reader_image()
elif prompt.cmb.get() == "Document":
self.reader_document()
elif prompt.cmb.get() == "Other":
self.reader_others()
elif prompt.cmb.get() == "All":
self.reader_music()
mlines = self.lines
self.reader_image()
ilines = self.lines
self.reader_document()
dlines = self.lines
self.reader_others()
olines = self.lines
self.lines = mlines + ilines + dlines + olines
def reader_music(self):
self.lines = []
müzikler = data.execute("""select * from müzik""")
for row in müzikler:
self.lines.append(os.path.join(row[0],row[1]))
"""if only row[1] it searches only file names but since my target has lots of
images named like DSC123123 I needed to search in both file name and path."""
def reader_image(self):
self.lines = []
resimler = data.execute("""select * from resim""")
for row in resimler:
self.lines.append(os.path.join(row[0],row[1]))
def reader_document(self):
self.lines = []
dökümanlar = data.execute("""select * from döküman""")
for row in dökümanlar:
self.lines.append(os.path.join(row[0],row[1]))
def reader_others(self):
self.lines = []
diğerleri = data.execute("""select * from diğer""")
for row in diğerleri:
if not os.path.isdir(os.path.join(row[0],row[1])): #to not show folders
self.lines.append(os.path.join(row[0],row[1]))
class Searcher:
def writer(self): ##creates tables and calls the function to insert items
data.execute(""" create table if not exists müzik(path text,
filename text UNIQUE) """)
data.execute(""" create table if not exists resim(path text,
filename text UNIQUE) """)
data.execute(""" create table if not exists döküman(path text,
filename text UNIQUE) """)
data.execute(""" create table if not exists diğer(path text,
filename text UNIQUE) """)
for roots ,dirs ,files in os.walk(searchFolder):
for item in os.listdir(roots):
table_writing(item,roots)
conn.commit()
def table_writing(item,path):
"""
depending on their extensions, inserts items to corresponding tables """
if "."+item.split(".")[-1].lower() in audio_ext:
data.execute(""" INSERT OR IGNORE into müzik
(path, filename) VALUES (?,?)""",(path,item))
elif "."+item.split(".")[-1].lower() in image_ext:
data.execute(""" INSERT OR IGNORE into resim
(path, filename) VALUES (?,?)""",(path,item))
elif "."+item.split(".")[-1].lower() in document_ext:
data.execute(""" INSERT OR IGNORE into döküman
(path, filename) VALUES (?,?)""",(path,item))
else:
data.execute(""" INSERT OR IGNORE into diğer
(path, filename) VALUEs (?,?)""",(path,item))
class FolderSelect:
""" I need to monitor if there are any changes in files. So I needed to store chosen path in database """
def __init__(self):
data.execute("""CREATE TABLE if not EXISTS SearchPath(num INT, path TEXT) """)
data.execute(""" select * from SearchPath""")
b = data.fetchone()
if b is None:
self.searchPath = tkinter.filedialog.askdirectory(parent=den,initialdir="/",title='Please select a directory')
data.execute("""INSERT INTO SearchPath(num,path) VALUES(?,?)""",(1,self.searchPath))
data.execute(""" select * from SearchPath""")
self.searchPath = data.fetchone()[1]
self.searchPath = self.searchPath.replace("/","\\")
if __name__ == "__main__":
audio_ext = [".mp3",".flac",".wma",".wav",".m4a"]
image_ext = [".jpg",".png",".bmp"]
document_ext = [".doc",".txt",".pdf",".docx"] ##these ext. types will be increased
filepath = os.getcwd()
directories = []
wasItThere = True ##checks if there was a database file beforehand.
if not os.path.isfile("test.db"):
wasItThere = False
conn = sqlite3.connect("test.db")
data = conn.cursor()
den = tkinter.Tk()
folder = FolderSelect()
searchFolder = folder.searchPath
reading = Reading()
searcher = Searcher()
if not wasItThere: ##if there is, program doesn't bother to check all folders,items again and again
searcher.writer()
den.title("Arama")
prompt = Prompt(den)
den.mainloop()
conn.close()