I wrote a program to copy files with an progress bar that looks similar to Windows built in function. The copy function is run in a separate thread and the tkinter GUI is run in the main thread. This is my first time using threads and queues.
'''
Copy GUI
An attempt to design a copy program using shutil and tkinter to display the
current progress of a specified copy job
Usage
copyTreeGUI(sourcFolder, destinationFolder)
'''
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import os
import shutil
import threading
import time
import queue as Queue
class CopyGui(ttk.Frame):
def __init__(s, parent, queue, jobTotal=0, jobSize=0):
ttk.Frame.__init__(s, parent)
s.queue = queue
s.parent = parent
s.parent.resizable(width=False, height=False)
s.parent.title(' ')
s.parent.wm_attributes('-topmost', 1)
try:
s.parent.iconbitmap(r'C:\Python34\DLLs\py.ico')
except tk.TclError:
s.parent.iconbitmap(r'')
s.jobSize = jobSize
s.jobTotal = jobTotal
s.statTitleLbl = ttk.Label(s,
text='Copying {:,} item{:.1} ({} {})'.format(jobTotal,
's'*(jobTotal-1),
*sizeNotator(s.jobSize)),
font=(100))
s.statTitleLbl.grid(row=0, column=0, columnspan=4, sticky=tk.W,
padx=30, pady=10)
s.statNameLbl = ttk.Label(s, text='Name:')
s.statNameLbl.grid(row=2, column=0, sticky=tk.W, padx=(30,0))
s.statFromLbl = ttk.Label(s, text='From:')
s.statFromLbl.grid(row=3, column=0, sticky=tk.W, padx=(30,0))
s.statToLbl = ttk.Label(s, text='To:')
s.statToLbl.grid(row=4, column=0, sticky=tk.W, padx=(30,0))
s.statRemainLbl = ttk.Label(s, text='Items remaining:')
s.statRemainLbl.grid(row=5, column=0, sticky=tk.W, padx=(30,0))
s.dynNameLbl = ttk.Label(s)
s.dynNameLbl.grid(row=2, column=1, columnspan=3, sticky=tk.W)
s.dynFromLbl = ttk.Label(s)
s.dynFromLbl.grid(row=3, column=1, columnspan=3, sticky=tk.W)
s.dynToLbl = ttk.Label(s)
s.dynToLbl.grid(row=4, column=1, columnspan=3, sticky=tk.W)
s.dynRemainLbl = ttk.Label(s)
s.dynRemainLbl.grid(row=5, column=1, columnspan=3, sticky=tk.W)
s.dynProBar = ttk.Progressbar(s, length=400, maximum=s.jobTotal+1)
s.dynProBar.grid(row=6, column=0, columnspan=4, sticky=tk.W, padx=30,
pady=15)
ttk.Separator(s).grid(row=1, column=0, columnspan=4,
sticky=tk.EW, pady=(0,10))
ttk.Separator(s).grid(row=7, column=0, columnspan=4,
sticky=tk.EW)
s.cancelBtn = ttk.Button(s, text='Cancel', command=s.Cancel)
s.cancelBtn.grid(row=8, column=3, sticky=tk.E, padx=30, pady=15)
## ttk.Button(s, text='Lie To Me', command=s.lieToMe).grid(row=8,
## column=0,
## padx=30,
## pady=15)
s.grid()
s.parent.update()
s.checkQueue()
def checkQueue(s):
try:
newName, newFold, newTo, newRemain, newSize, keepUp = s.queue.get(block=False)
except Queue.Empty:
pass
else:
if keepUp:
s.upFileName(newName)
s.upFoldFrom(newFold)
s.upFoldTo(newTo)
s.upProBar(1)
s.upItemRemainig(newRemain, newSize)
else:
s.after(1000, s.parent.destroy)
s.after(50, s.checkQueue)
def upFileName(s, newName):
s.dynNameLbl['text'] = newName[:40]
s.parent.update()
def upFoldFrom(s, newFold):
s.dynFromLbl['text'] = folderShortener(newFold, 40)
s.parent.update()
def upFoldTo(s, newTo):
s.dynToLbl['text'] = folderShortener(newTo, 40)
s.parent.update()
def upItemRemainig(s, newRemain, newSize):
s.dynRemainLbl['text'] = '{:,} ({} {})'.format(newRemain,
*sizeNotator(newSize))
s.parent.update()
def upProBar(s, steps):
s.dynProBar.step(steps)
s.parent.update()
def setProBarMax(s, newMax):
s.dynProBar['maximum'] = newMax
s.parent.update()
def lieToMe(s):
s.setProBarMax(100)
for i in range(100):
s.upFileName('Test_File_Name{}.file'.format(i))
s.upFoldFrom(r'C:\Old{}'.format(r'\Old'*int(i/10)))
s.upFoldTo(r'C:\New{}'.format(r'\New'*int(i/10)))
s.upItemRemainig(100-i, 1152921504606846976-(i*1024))
s.upProBar(1)
time.sleep(.05)
def Cancel(s):
s.parent.destroy()
raise CopyCanceled('Copy job has been canceled by user.')
class CopyCanceled(Exception):
pass
def sizeNotator(sizeInBytes):
notation = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')
for i in range(8):
if len(str(int(sizeInBytes/(1024**i)))) < 4:
return round(sizeInBytes/(1024**i),1), notation[i]
def prepareFiles(srcDir, dstDir):
fileTotal = 0
sizeTotal = 0
newDirs = []
readyFiles = [[], [], []]
for path, dirs, files in os.walk(srcDir):
srcFiles = [os.path.join(path, file) for file in files]
dstFiles = [file.replace(srcDir, dstDir) for file in srcFiles]
fileSizes = [os.path.getsize(file) for file in srcFiles]
dirs = [os.path.join(path, dirdir) for dirdir in dirs]
newDirs.extend([dirdir.replace(srcDir, dstDir) for dirdir in dirs])
fileTotal += len(files)
sizeTotal += sum(fileSizes)
readyFiles[0].extend(srcFiles)
readyFiles[1].extend(dstFiles)
readyFiles[2].extend(fileSizes)
readyFiles = tuple(zip(*readyFiles))
newDirs = uniqueDirs(newDirs)
return fileTotal, sizeTotal, tuple(newDirs), readyFiles
def uniqueDirs(dirs):
remove = []
for dirA in list(dirs):
for dirB in list(dirs):
if dirB in dirA and dirB != dirA:
dirs.pop(dirs.index(dirB))
return uniqueDirs(dirs)
elif dirA in dirB and dirB != dirA:
dirs.pop(dirs.index(dirA))
return uniqueDirs(dirs)
return dirs
def copyTreeGUI(srcDir, dstDir):
structureFiles = prepareFiles(srcDir, dstDir)
queue = Queue.Queue()
root = tk.Tk()
GUI = CopyGui(root, queue, structureFiles[0], structureFiles[1])
copyTreeTHREAD(queue, structureFiles)
root.mainloop()
def copyTreeTHREAD(queue, structureFiles):
fileCount, totalSize, newDirs, files = structureFiles
for newDir in newDirs:
os.makedirs(newDir, exist_ok=True)
for file in files:
fileCount -= 1
totalSize -= file[2]
forQueue = (os.path.basename(file[0]), os.path.dirname(file[0]),
os.path.dirname(file[1]), fileCount, totalSize, 1)
queue.put(forQueue)
shutil.copy(file[0], file[1])
queue.put((0, 0, 0, 0, 0, 0))
def folderShortener(folder, length):
if len(folder) <= length:
return folder
else:
splitFolder = folder.split('\\')
if folder.startswith('\\\\'):
pass
else:
if len(splitFolder[1]+splitFolder[-1])+9 <= length:
return '\\'.join((splitFolder[0], splitFolder[1], '...',
splitFolder[-1]))
else:
if len(splitFolder[1]) > len(splitFolder[-1]):
diff = (len(splitFolder[1]+splitFolder[-1])+8) - length
splitFolder[1] = splitFolder[1][:-(abs(length)+3)] + '...'
return '\\'.join((splitFolder[0], splitFolder[1],
splitFolder[-1]))
else:
diff = (len(splitFolder[1]+splitFolder[-1])+8) - length
splitFolder[-1] = '...' + splitFolder[-1][(abs(length)+3):]
return '\\'.join((splitFolder[0], splitFolder[1],
splitFolder[-1]))
def testRun():
copyTreeGUI(r'C:\Users\Jacob\OneDrive\Documents', r'C:\TEST')
input('Press [Enter] to clean up')
shutil.rmtree()