-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcleaner.py
More file actions
133 lines (110 loc) · 5.03 KB
/
Copy pathcleaner.py
File metadata and controls
133 lines (110 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import os
import shutil
import hashlib
from pathlib import Path
from datetime import datetime
from plyer import notification
# Set to True to preview changes without moving files, False to actually move them
DRY_RUN = True
# Automatically detects your user profile's Downloads folder
DOWNLOADS_DIR = Path.home() / "Downloads"
# Define mappings of subfolders to their respective file extensions
FILE_CATEGORIES = {
"Images": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".heic"],
"PDFs": [".pdf"],
"InstallationFiles": [".exe", ".msi", ".dmg", ".pkg", ".deb", ".rpm"],
"Documents": [".docx", ".doc", ".txt", ".xlsx", ".xls", ".pptx", ".csv", ".md"],
"Archives": [".zip", ".tar", ".gz", ".rar", ".7z"],
"Audio_Video": [".mp3", ".wav", ".mp4", ".mov", ".mkv", ".avi", ".flac"]
}
def file_hash(file_path):
hasher = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hasher.update(chunk)
return hasher.hexdigest()
def send_notification(title, message):
"""Send a native OS desktop notification."""
try:
notification.notify(
title=title,
message=message,
timeout=5
)
except Exception:
pass # Silently skip if notifications are unavailable
def clean_downloads():
if not DOWNLOADS_DIR.exists():
print(f"❌ Error: Could not find the folder at {DOWNLOADS_DIR}")
return
mode = "DRY RUN" if DRY_RUN else "LIVE"
print(f"🧹 [{mode}] Scanning and organizing: {DOWNLOADS_DIR}\n")
files_moved = 0
dupes_removed = 0
# Iterate through every item in the Downloads folder
for item in DOWNLOADS_DIR.iterdir():
# Skip directories (we only want to sort loose files)
if item.is_dir():
continue
# Get the file extension in lowercase
file_ext = item.suffix.lower()
# Track if the file found a home
moved = False
for category, extensions in FILE_CATEGORIES.items():
if file_ext in extensions:
# Sort into date-based subfolders: /Category/2026/June
timestamp = item.stat().st_mtime
file_date = datetime.fromtimestamp(timestamp)
year_month = file_date.strftime("%Y/%B") # e.g., "2026/June"
target_dir = DOWNLOADS_DIR / category / year_month
target_dir.mkdir(parents=True, exist_ok=True)
destination = target_dir / item.name
# Check for exact duplicate by MD5 hash
item_hash = file_hash(item)
is_duplicate = False
for existing in target_dir.iterdir():
if existing.is_file() and file_hash(existing) == item_hash:
is_duplicate = True
if DRY_RUN:
print(f"[DRY RUN] Would delete duplicate: {item.name} (matches {existing.name})")
else:
item.unlink()
print(f"🗑️ Deleted duplicate: {item.name} (matches {existing.name})")
dupes_removed += 1
break
if is_duplicate:
moved = True
break
# Prevent overwriting if a file with the same name already exists
if destination.exists():
destination = target_dir / f"{item.stem}_copy{file_ext}"
if DRY_RUN:
print(f"[DRY RUN] Would move: {item.name} ➡️ /{category}/{year_month}")
else:
shutil.move(str(item), str(destination))
print(f"📁 Moved: {item.name} ➡️ /{category}/{year_month}")
files_moved += 1
moved = True
break
# Optional: Catch-all for miscellaneous files not defined in FILE_CATEGORIES
if not moved and file_ext != "":
# Sort "Others" into date-based subfolders too
timestamp = item.stat().st_mtime
file_date = datetime.fromtimestamp(timestamp)
year_month = file_date.strftime("%Y/%B")
others_dir = DOWNLOADS_DIR / "Others" / year_month
others_dir.mkdir(parents=True, exist_ok=True)
destination = others_dir / item.name
if not destination.exists():
if DRY_RUN:
print(f"[DRY RUN] Would move: {item.name} ➡️ /Others")
else:
shutil.move(str(item), str(destination))
print(f"📁 Moved unknown file: {item.name} ➡️ /Others")
files_moved += 1
summary = f"Organized {files_moved} files, removed {dupes_removed} duplicates."
print(f"\n✨ Clean-up complete! {summary}")
if not DRY_RUN and (files_moved > 0 or dupes_removed > 0):
send_notification("Downloads Cleaned! 🧹", summary)
if __name__ == "__main__":
clean_downloads()