|
|
@ -1,41 +1,113 @@ |
|
|
|
!/bin/python3 |
|
|
|
#!/bin/python3 |
|
|
|
|
|
|
|
import os |
|
|
|
import Settings |
|
|
|
|
|
|
|
photosPath = "/home/macoy/Camera S9 Test" |
|
|
|
archivePhotosPath = "/home/macoy/Archive-Photos-Sync" |
|
|
|
|
|
|
|
# One gibibyte max size |
|
|
|
maxPhotosSize = 1024 * 1024 * 1024 * 1 |
|
|
|
# Don't actually do anything |
|
|
|
softRun = True |
|
|
|
|
|
|
|
def getAllFilesInPath(path): |
|
|
|
fileList = [] |
|
|
|
for root, dirs, files in os.walk(path): |
|
|
|
# Ignore hidden folders (lazy programming detected!) |
|
|
|
# Note that this can mean thumbnails build up forever if they're still running google photos |
|
|
|
if "/." in root: |
|
|
|
continue |
|
|
|
for file in files: |
|
|
|
# if file.endswith(renderedContentExtensions): |
|
|
|
filePath = os.path.join(root, file) |
|
|
|
fileList.append(filePath) |
|
|
|
return fileList |
|
|
|
|
|
|
|
# From https://stackoverflow.com/questions/1392413/calculating-a-directorys-size-using-python |
|
|
|
def getFolderSize(path='.'): |
|
|
|
total = 0 |
|
|
|
for entry in os.scandir(path): |
|
|
|
if entry.is_file(): |
|
|
|
total += entry.stat().st_size |
|
|
|
elif entry.is_dir(): |
|
|
|
total += getFolderSize(entry.path) |
|
|
|
return total |
|
|
|
|
|
|
|
def makeRelativePath(fullPath, sourceDirectory): |
|
|
|
if sourceDirectory not in fullPath: |
|
|
|
print("ERROR: Path '{}' cannot be made relative.\n" |
|
|
|
"Make sure '{}' is an absolute path" |
|
|
|
.format(fullPath, sourceDirectory)) |
|
|
|
exit(1) |
|
|
|
return fullPath[len(sourceDirectory):] |
|
|
|
|
|
|
|
def bytesToKiBString(byteCount): |
|
|
|
return "{:,}".format(int(byteCount / 1024)) |
|
|
|
|
|
|
|
def main(): |
|
|
|
print("Checking Photos for cleanup...") |
|
|
|
def bytesToMiBString(byteCount): |
|
|
|
return "{:,}".format(int(byteCount / 1024 / 1024)) |
|
|
|
|
|
|
|
def CleanUpFolder(photosPath, archivePhotosPath, maxPhotosSize): |
|
|
|
print("Checking folder {} against {} for cleanup..." |
|
|
|
.format(photosPath, archivePhotosPath)) |
|
|
|
|
|
|
|
# Is the folder too big? |
|
|
|
photosSize = os.path.getsize(photosPath) |
|
|
|
print("Photos folder size = {} GiB".format(photosSize / (1024 * 1024))) |
|
|
|
photosSize = getFolderSize(photosPath) |
|
|
|
print("Folder size = {} MiB".format(bytesToMiBString(photosSize))) |
|
|
|
if photosSize < maxPhotosSize: |
|
|
|
print("Photos folder within size. Nothing to do") |
|
|
|
print("Folder within size. Nothing to do") |
|
|
|
return |
|
|
|
|
|
|
|
print("Going to try to free up {} MiB" |
|
|
|
.format(bytesToMiBString(photosSize - maxPhotosSize))) |
|
|
|
|
|
|
|
# Build file lists |
|
|
|
photosFiles = getAllFilesInPath(photosPath) |
|
|
|
archivePhotosFiles = getAllFilesInPath(archivePhotosPath) |
|
|
|
archivePhotosFilesList = getAllFilesInPath(archivePhotosPath) |
|
|
|
archivePhotosFilesDict = {} |
|
|
|
# For faster lookups |
|
|
|
for archivePhoto in archivePhotosFilesList: |
|
|
|
archivePhotosFilesDict[makeRelativePath(archivePhoto, archivePhotosPath)] = None |
|
|
|
|
|
|
|
print("Found {} photos and {} archive photos" |
|
|
|
.format(len(photosFiles), len(archivePhotosPath)) |
|
|
|
# Sort photos by age |
|
|
|
.format(len(photosFiles), len(archivePhotosFilesList))) |
|
|
|
|
|
|
|
# Sort photos by age (oldest = first) |
|
|
|
photosFiles.sort(key=os.path.getmtime) |
|
|
|
|
|
|
|
sizeLeftToFree = photosSize - maxPhotosSize |
|
|
|
totalFreed = 0 |
|
|
|
totalDeletedFiles = 0 |
|
|
|
# Are there any files we can delete? |
|
|
|
# Delete files |
|
|
|
for photoFile in photosFiles: |
|
|
|
if makeRelativePath(photoFile, photosPath) in archivePhotosFilesDict: |
|
|
|
# It's already archived. Delete it! |
|
|
|
photoSize = os.stat(photoFile).st_size |
|
|
|
sizeLeftToFree -= photoSize |
|
|
|
totalFreed += photoSize |
|
|
|
totalDeletedFiles += 1 |
|
|
|
|
|
|
|
print("Deleting {}".format(photoFile)) |
|
|
|
if not softRun: |
|
|
|
os.remove(photoFile) |
|
|
|
|
|
|
|
if sizeLeftToFree > 0: |
|
|
|
print("Removed {} KiB, {} MiB to go..." |
|
|
|
.format(bytesToKiBString(photoSize), bytesToMiBString(sizeLeftToFree))) |
|
|
|
# Finished deleting! |
|
|
|
else: |
|
|
|
print("Removed {} KiB" |
|
|
|
.format(bytesToKiBString(photoSize))) |
|
|
|
break |
|
|
|
else: |
|
|
|
# While I could do the copying here, I wanted this script to be for cleanup only |
|
|
|
print("WARNING: {} was not found in {}. It will not eligible for deletion." |
|
|
|
.format(photoFile, archivePhotosPath)) |
|
|
|
|
|
|
|
print("Deleted {} files, reclaiming {} MiB" |
|
|
|
.format(totalDeletedFiles, bytesToMiBString(totalFreed))) |
|
|
|
|
|
|
|
if sizeLeftToFree > 0: |
|
|
|
print("FAILED to free {} MiB".format(bytesToMiBString(sizeLeftToFree))) |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
main() |
|
|
|
if __name__ == '__main__': |
|
|
|
for cleanSetting in Settings.settings: |
|
|
|
CleanUpFolder(cleanSetting["folder"], |
|
|
|
cleanSetting["archive"], |
|
|
|
cleanSetting["maxSizeGiB"] * 1024 * 1024 * 1024) |