Skip to content

Commit 39b9252

Browse files
committed
Performance improvements
1 parent b6df3a0 commit 39b9252

1 file changed

Lines changed: 30 additions & 81 deletions

File tree

backdrop.py

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
import subprocess
1313
import clipboard
1414
import time
15-
from sys import argv
1615

1716
# Set meta info
18-
appVersion = '1.0.0'
17+
appVersion = '1.0.1'
1918

2019
# TODO: When loading a config, warn if drive in config isn't connected
2120
# If replacement drive is selected that gives sufficient size, prompt for replace confirmation
@@ -26,12 +25,7 @@
2625
# This would prevent counting for existing data, though it's probably safe to wipe the drive of things that aren't getting copied anyway
2726
# When we copy, check directory size of source and dest, and if the dest is larger than source, copy those first to free up space for ones that increased
2827
# TODO: Add a button for deleting the config from selected drives
29-
30-
# Disallow CLI options
3128
# TODO: Add interactive CLI option if correct parameters are passed in
32-
if len(argv) > 1:
33-
print('CLI options are not currently supported. Please use the GUI.')
34-
exit()
3529

3630
# Centers a tkinter window
3731
def center(win):
@@ -80,7 +74,7 @@ class color:
8074
GREEN = '#6db500'
8175
GOLD = '#ebb300'
8276
RED = '#c00'
83-
GRAY = '#aaa'
77+
GRAY = '#999'
8478

8579
FINISHED = GREEN
8680
RUNNING = BLUE
@@ -91,8 +85,8 @@ class color:
9185
sourceDrive = None
9286
backupConfigFile = 'backup.config'
9387
appConfigFile = 'defaults.config'
94-
appDataFolder = os.getenv('LocalAppData') + '\\TechGeek01UnraidBackup'
95-
halfPadding = 8
88+
appDataFolder = os.getenv('LocalAppData') + '\\BackDrop'
89+
elemPadding = 16
9690

9791
config = {
9892
'shares': [],
@@ -270,10 +264,7 @@ def analyzeBackup(shares, drives):
270264

271265
for i, drive in enumerate(driveInfo):
272266
# Get list of shares small enough to fit on drive
273-
smallShares = {}
274-
for share, size in shareInfo.items():
275-
if size <= drive['size']:
276-
smallShares[share] = size
267+
smallShares = {share: size for share, size in shareInfo.items() if size <= drive['size']}
277268

278269
# Try every combination of shares that fit to find result that uses most of that drive
279270
largestSum = 0
@@ -317,8 +308,7 @@ def analyzeBackup(shares, drives):
317308
driveShareList[drive['name']].extend(sharesThatFit) # Shares that fit on current drive
318309

319310
# Put remaining small shares back into pool to work with for next drive
320-
for (share, size) in remainingSmallShares.items():
321-
shareInfo[share] = size
311+
shareInfo.update({share: size for share, size in remainingSmallShares.items()})
322312
else:
323313
# Fit all small shares onto drive
324314
driveShareList[drive['name']].extend(sharesThatFit)
@@ -348,10 +338,7 @@ def splitShare(share):
348338

349339
for i, drive in enumerate(driveInfo):
350340
# Get list of files small enough to fit on drive
351-
totalSmallFiles = {}
352-
for file, size in fileInfo.items():
353-
if size <= drive['free']:
354-
totalSmallFiles[file] = size
341+
totalSmallFiles = {file: size for file, size in fileInfo.items() if size <= drive['free']}
355342

356343
# Since the list of files is truncated to prevent an unreasonably large
357344
# number of combinations to check, we need to keep processing the file list
@@ -452,9 +439,7 @@ def splitShare(share):
452439
rawExclusions = allFiles.copy()
453440
rawExclusions.pop(drive, None)
454441

455-
masterExclusions = []
456-
for files in rawExclusions.values():
457-
masterExclusions.extend(files)
442+
masterExclusions = [files for files in rawExclusions.values()]
458443

459444
fileExclusions = [sourcePathStub + file for file in masterExclusions if os.path.isfile(sourcePathStub + file)]
460445
dirExclusions = [sourcePathStub + file for file in masterExclusions if os.path.isdir(sourcePathStub + file)]
@@ -521,7 +506,7 @@ def startBackupAnalysis():
521506
root.attributes('-alpha', 1.0)
522507

523508
mainFrame = tk.Frame(root)
524-
mainFrame.pack(fill = 'both', expand = 1, padx = halfPadding, pady = halfPadding)
509+
mainFrame.pack(fill = 'both', expand = 1, padx = elemPadding, pady = (elemPadding / 2, elemPadding))
525510

526511
# Set some default styling
527512
buttonWinStyle = ttk.Style()
@@ -576,13 +561,13 @@ def readSettingFromFile(file, default, verifyData = None):
576561

577562
# Tree frames for tree and scrollbar
578563
sourceTreeFrame = tk.Frame(mainFrame)
579-
sourceTreeFrame.grid(row = 1, column = 0, sticky = 'ns', padx = halfPadding, pady = (halfPadding, 0))
564+
sourceTreeFrame.grid(row = 1, column = 0, sticky = 'ns')
580565
destTreeFrame = tk.Frame(mainFrame)
581-
destTreeFrame.grid(row = 1, column = 1, sticky = 'ns', padx = halfPadding, pady = (halfPadding, 0))
566+
destTreeFrame.grid(row = 1, column = 1, sticky = 'ns', padx = (elemPadding, 0))
582567

583568
# Progress/status values
584569
progressBar = ttk.Progressbar(mainFrame, maximum = 100)
585-
progressBar.grid(row = 10, column = 0, columnspan = 3, sticky = 'ew', padx = halfPadding, pady = halfPadding)
570+
progressBar.grid(row = 10, column = 0, columnspan = 3, sticky = 'ew', pady = (elemPadding, 0))
586571

587572
sourceTree = ttk.Treeview(sourceTreeFrame, columns = ('size', 'rawsize'))
588573
sourceTree.heading('#0', text = 'Share')
@@ -599,7 +584,7 @@ def readSettingFromFile(file, default, verifyData = None):
599584
# There's an invisible 1px background on buttons. When changing this in icon buttons, it becomes
600585
# visible, so 1px needs to be added back
601586
sourceMetaFrame = tk.Frame(mainFrame)
602-
sourceMetaFrame.grid(row = 2, column = 0, sticky = 'nsew', padx = halfPadding, pady = (1, halfPadding))
587+
sourceMetaFrame.grid(row = 2, column = 0, sticky = 'nsew', pady = (1, elemPadding))
603588
tk.Grid.columnconfigure(sourceMetaFrame, 0, weight = 1)
604589

605590
shareSpaceFrame = tk.Frame(sourceMetaFrame)
@@ -647,7 +632,7 @@ def changeSourceDrive(selection):
647632
writeSettingToFile(sourceDrive, appDataFolder + '\\sourceDrive.default')
648633

649634
sourceSelectFrame = tk.Frame(mainFrame)
650-
sourceSelectFrame.grid(row = 0, column = 0)
635+
sourceSelectFrame.grid(row = 0, column = 0, pady = (0, elemPadding / 2))
651636
tk.Label(sourceSelectFrame, text = 'Source:').pack(side = 'left')
652637
sourceSelectMenu = ttk.OptionMenu(sourceSelectFrame, sourceDriveDefault, sourceDrive, *tuple(remoteDrives), command = changeSourceDrive)
653638
sourceSelectMenu.pack(side = 'left', padx = (12, 0))
@@ -767,8 +752,7 @@ def loadDest():
767752
try:
768753
for physicalDisk in wmi.WMI().Win32_DiskDrive():
769754
for partition in physicalDisk.associators("Win32_DiskDriveToDiskPartition"):
770-
for logicalDisk in partition.associators("Win32_LogicalDiskToPartition"):
771-
logicalPhysicalMap[logicalDisk.DeviceID[0]] = physicalDisk.SerialNumber.strip()
755+
logicalPhysicalMap.update({logicalDisk.DeviceID[0]: physicalDisk.SerialNumber.strip() for logicalDisk in partition.associators("Win32_LogicalDiskToPartition")})
772756
finally:
773757
pythoncom.CoUninitialize()
774758

@@ -817,7 +801,7 @@ def startRefreshDest():
817801
# There's an invisible 1px background on buttons. When changing this in icon buttons, it becomes
818802
# visible, so 1px needs to be added back
819803
destMetaFrame = tk.Frame(mainFrame)
820-
destMetaFrame.grid(row = 2, column = 1, sticky = 'nsew', padx = halfPadding, pady = (1, halfPadding))
804+
destMetaFrame.grid(row = 2, column = 1, sticky = 'nsew', pady = (1, elemPadding))
821805
tk.Grid.columnconfigure(destMetaFrame, 0, weight = 1)
822806

823807
driveSpaceFrame = tk.Frame(destMetaFrame)
@@ -835,21 +819,15 @@ def startRefreshDest():
835819
# Using the current config, make selections in the GUI to match
836820
def selectFromConfig():
837821
global driveSelectBind
838-
sourceShareList = sourceTree.get_children()
839-
sourceTreeIdList = []
840-
for item in sourceShareList:
841-
if sourceTree.item(item, 'text') in config['shares']:
842-
sourceTreeIdList.append(item)
822+
823+
# Get list of shares in config
824+
sourceTreeIdList = [item for item in sourceTree.get_children() if sourceTree.item(item, 'text') in config['shares']]
843825

844826
sourceTree.focus(sourceTreeIdList[-1])
845827
sourceTree.selection_set(tuple(sourceTreeIdList))
846828

847-
driveDestList = destTree.get_children()
848-
driveTreeIdList = []
849-
for item in driveDestList:
850-
driveVid = destTree.item(item, 'values')[3]
851-
if driveVid in config['vidList']:
852-
driveTreeIdList.append(item)
829+
# Get list of drives where volume ID is in config
830+
driveTreeIdList = [item for item in destTree.get_children() if destTree.item(item, 'values')[3] in config['vidList']]
853831

854832
# Only redo the selection if the config data is different from the current
855833
# selection (that is, the drive we selected to load a config is not the only
@@ -962,19 +940,16 @@ def selectDriveInBackground(event):
962940
# TODO: Make changes to existing config check the existing for missing drives, and delete the config file from drives we unselected if there's multiple drives in a config
963941
def writeConfigFile():
964942
if len(config['shares']) > 0 and len(config['drives']) > 0:
965-
driveLetters = []
966-
for drive in config['drives']:
967-
driveLetters.append(destDriveMap[drive['vid']])
943+
driveConfigList = ''.join(['\n%s,%s' % (drive['vid'], drive['serial']) for drive in config['drives']])
968944

969945
# For each drive letter, get drive info, and write file
970-
for drive in driveLetters:
971-
f = open('%s:/%s' % (drive, backupConfigFile), 'w')
946+
for drive in config['drives']:
947+
f = open('%s:/%s' % (destDriveMap[drive['vid']], backupConfigFile), 'w')
972948
# f.write('[id]\n%s,%s\n\n' % (driveInfo['vid'], driveInfo['serial']))
973949
f.write('[shares]\n%s\n\n' % (','.join(config['shares'])))
974-
f.write('[drives]')
975950

976-
for confDrive in config['drives']:
977-
f.write('\n%s,%s' % (confDrive['vid'], confDrive['serial']))
951+
f.write('[drives]')
952+
f.write(driveConfigList)
978953

979954
f.close()
980955
else:
@@ -984,7 +959,7 @@ def writeConfigFile():
984959
# Add activity frame for backup status output
985960
tk.Grid.rowconfigure(mainFrame, 5, weight = 1)
986961
backupActivityFrame = tk.Frame(mainFrame)
987-
backupActivityFrame.grid(row = 5, column = 0, columnspan = 2, sticky = 'nsew', padx = halfPadding, pady = halfPadding)
962+
backupActivityFrame.grid(row = 5, column = 0, columnspan = 2, sticky = 'nsew')
988963

989964
backupActivityInfoCanvas = tk.Canvas(backupActivityFrame)
990965
backupActivityInfoCanvas.pack(side = 'left', fill = 'both', expand = 1)
@@ -1007,10 +982,10 @@ def writeConfigFile():
1007982
tk.Grid.columnconfigure(mainFrame, 2, weight = 1)
1008983

1009984
rightSideFrame = tk.Frame(mainFrame)
1010-
rightSideFrame.grid(row = 0, column = 2, rowspan = 6, sticky = 'nsew', padx = halfPadding, pady = halfPadding)
985+
rightSideFrame.grid(row = 0, column = 2, rowspan = 6, sticky = 'nsew', pady = (elemPadding / 2, 0))
1011986

1012987
backupSummaryFrame = tk.Frame(rightSideFrame)
1013-
backupSummaryFrame.pack(fill = 'both', expand = 1, padx = halfPadding, pady = halfPadding)
988+
backupSummaryFrame.pack(fill = 'both', expand = 1, padx = (elemPadding, 0))
1014989
backupSummaryFrame.update()
1015990

1016991
backupTitle = tk.Label(backupSummaryFrame, text = 'Analysis Summary', font = (None, 20))
@@ -1051,39 +1026,13 @@ def runBackup():
10511026
process = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stdin = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
10521027
# process = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
10531028

1054-
# isDone = False
1055-
# finalLine = 0
1056-
10571029
while not backupHalted and process.poll() is None:
10581030
try:
10591031
out = process.stdout.readline().decode().strip()
10601032
cmdInfoBlocks[i]['state'].configure(text = 'Running', fg = color.RUNNING)
1061-
# print(out, end = '')
1062-
# out = re.sub('\s+', ' ', out)
1063-
# outParts = out.split()
1064-
10651033
cmdInfoBlocks[i]['lastOutResult'].configure(text = out.strip(), fg = color.NORMAL)
1066-
1067-
# if not isDone and outParts[0] == 'Total':
1068-
# isDone = True
1069-
1070-
# for headIndex, heading in enumerate(outParts):
1071-
# tk.Label(cmdInfoBlocks[i]['statusStatsFrame'], text = heading, font = (None, 10, 'bold')).grid(row = 0, column = headIndex + 1)
1072-
# elif isDone and out != '':
1073-
# outParts = re.sub(' : ', ' ', out).split()
1074-
# print(outParts[0])
1075-
# if outParts[0] in ['Dirs', 'Files']:
1076-
# finalLine += 1
1077-
# for statIndex, stat in enumerate(outParts):
1078-
# stat = (stat + ':') if statIndex == 0 else stat
1079-
# font = (None, 10)
1080-
1081-
# tk.Label(cmdInfoBlocks[i]['statusStatsFrame'], text = stat, font = font).grid(row = finalLine, column = statIndex)
1082-
# else:
1083-
# print(out)
10841034
except Exception as e:
10851035
pass
1086-
# print("\n ______ _ _ _____ _ __\n | ____| | | | | / ____| | |/ /\n | |__ | | | | | | | ' / \n | __| | | | | | | | < \n | | | |__| | | |____ | . \\ \n |_| \\____/ \\_____| |_|\\_\\n \n")
10871036
process.terminate()
10881037

10891038
if not backupHalted:
@@ -1123,7 +1072,7 @@ def killBackup():
11231072
tk.Label(backupSummaryTextFrame, text = 'Please start a backup analysis to generate a summary.',
11241073
wraplength = backupSummaryFrame.winfo_width() - 2, justify = 'left').pack(anchor = 'w')
11251074
startBackupBtn = ttk.Button(backupSummaryFrame, text = 'Run Backup', command = startBackup, state = 'disable', style = 'win.TButton')
1126-
startBackupBtn.pack(padx = halfPadding, pady = halfPadding)
1075+
startBackupBtn.pack(pady = elemPadding / 2)
11271076

11281077
loadThread = threading.Thread(target = loadDest, name = 'Init', daemon = True)
11291078
loadThread.start()

0 commit comments

Comments
 (0)