3
\$\begingroup\$

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()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.