Skip to content

Commit 2f8e20d

Browse files
authored
Update PhotoCopy.py
1 parent b1c4e6f commit 2f8e20d

File tree

1 file changed

+102
-87
lines changed

1 file changed

+102
-87
lines changed

PhotoCopy.py

Lines changed: 102 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,132 +6,149 @@
66
from pathlib import Path
77
from tkinter.ttk import Progressbar
88

9-
PHOTO_EXTENSIONS = {
10-
".jpeg", ".jpg", ".gif", ".pdf", ".png",
11-
".heic", ".tiff", ".tif", ".bmp", ".webp", ".raw"
9+
EXTENSIONS = {
10+
"Photos": {".jpeg", ".jpg", ".gif", ".pdf", ".png", ".heic", ".tiff", ".tif", ".bmp", ".webp", ".raw"},
11+
"Documents": {".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf", ".odt"},
12+
"Music": {".mp3", ".wav", ".flac", ".aac", ".wma", ".ogg", ".m4a"},
13+
"Videos": {".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".mpeg"},
14+
"QuickBooks": {".qbw", ".qbb", ".qbm", ".qbo", ".des", ".qbr", ".qwc", ".qbstbl2.usa", ".qbx",
15+
".qba", ".qby", ".qbj", ".iif", ".nd", ".tlg", ".ecml", ".qbp", ".qsm", ".qss",
16+
".qst", ".qb2016", ".qb2019", ".mac.qbb"}
1217
}
1318

14-
class FileScannerApp:
19+
class FileCopyTool:
1520
def __init__(self, root):
1621
self.root = root
17-
self.root.title("Drive Scanner and Photo Copier")
18-
self.root.geometry("620x570")
22+
self.root.title("Data Recovery Tool")
23+
self.root.geometry("660x700")
1924
self.root.resizable(False, False)
2025

21-
self.source_drive = tk.StringVar()
22-
self.destination_folder = tk.StringVar()
26+
self.source = tk.StringVar()
27+
self.destination = tk.StringVar()
2328
self.min_file_size_kb = tk.IntVar(value=50)
2429
self.cancel_requested = False
30+
self.file_type_mode = None
2531

26-
self.create_widgets()
32+
self.build_menu()
2733

28-
def create_widgets(self):
29-
# Source drive dropdown
30-
tk.Label(self.root, text="Select Drive to Scan:").pack(pady=(10, 0))
31-
drives = self.get_available_drives()
32-
self.source_drive.set(drives[0] if drives else "")
33-
tk.OptionMenu(self.root, self.source_drive, *drives).pack()
34+
def build_menu(self):
35+
tk.Label(self.root, text="Select Operation", font=("Arial", 14)).pack(pady=10)
3436

35-
# Destination folder picker
36-
tk.Label(self.root, text="Select Destination Folder:").pack(pady=(10, 0))
37-
entry_frame = tk.Frame(self.root)
38-
entry_frame.pack()
39-
tk.Entry(entry_frame, textvariable=self.destination_folder, width=50).pack(side=tk.LEFT, padx=(5, 5))
40-
tk.Button(entry_frame, text="Browse", command=self.browse_folder).pack(side=tk.LEFT)
37+
btns = [
38+
("Copy Photos", lambda: self.select_and_copy("Photos")),
39+
("Copy Documents", lambda: self.select_and_copy("Documents")),
40+
("Copy Music", lambda: self.select_and_copy("Music")),
41+
("Copy Videos", lambda: self.select_and_copy("Videos")),
42+
("Copy QB Files", lambda: self.select_and_copy("QuickBooks")),
43+
("Copy User Data", self.copy_user_data)
44+
]
45+
46+
for label, cmd in btns:
47+
tk.Button(self.root, text=label, width=30, command=cmd).pack(pady=5)
4148

42-
# Minimum file size filter
4349
tk.Label(self.root, text="Minimum File Size to Copy (KB):").pack(pady=(10, 0))
4450
tk.Entry(self.root, textvariable=self.min_file_size_kb, width=10).pack()
4551

46-
# Progress bar
4752
tk.Label(self.root, text="Progress:").pack(pady=(10, 0))
4853
self.progress = Progressbar(self.root, length=580, mode='determinate')
49-
self.progress.pack(pady=(0, 10))
54+
self.progress.pack(pady=(0, 5))
55+
56+
self.status_label = tk.Label(self.root, text="", font=("Arial", 10), fg="blue")
57+
self.status_label.pack(pady=(0, 5))
5058

51-
# Start and Cancel buttons
5259
button_frame = tk.Frame(self.root)
53-
button_frame.pack(pady=5)
54-
tk.Button(button_frame, text="Start Scan", command=self.start_scan_thread).pack(side=tk.LEFT, padx=10)
55-
self.cancel_button = tk.Button(button_frame, text="Cancel Scan", command=self.cancel_scan)
56-
self.cancel_button.pack(side=tk.LEFT)
60+
button_frame.pack(pady=(0, 10))
61+
self.cancel_button = tk.Button(button_frame, text="Cancel Scan", command=self.cancel_scan_confirm)
62+
self.cancel_button.pack()
5763
self.cancel_button.config(state=tk.DISABLED)
5864

59-
# Log output
60-
self.log_output = scrolledtext.ScrolledText(self.root, height=15, width=72, state='disabled')
61-
self.log_output.pack(padx=10, pady=10)
62-
63-
# Footer
64-
tk.Label(self.root, text="Designed by Neverklear Technologies 2025", font=("Arial", 9), fg="gray").pack(pady=(5, 5))
65-
66-
def get_available_drives(self):
67-
drives = []
68-
for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
69-
if os.path.exists(f"{letter}:\\"):
70-
drives.append(f"{letter}:\\")
71-
return drives
72-
73-
def browse_folder(self):
74-
folder = filedialog.askdirectory()
75-
if folder:
76-
self.destination_folder.set(folder)
77-
78-
def start_scan_thread(self):
79-
self.cancel_requested = False
80-
self.cancel_button.config(state=tk.NORMAL)
81-
thread = Thread(target=self.scan_and_copy_files)
82-
thread.start()
65+
self.log_output = scrolledtext.ScrolledText(self.root, height=15, width=75, state='disabled')
66+
self.log_output.pack(padx=10, pady=5)
8367

84-
def cancel_scan(self):
85-
self.cancel_requested = True
86-
self.cancel_button.config(state=tk.DISABLED)
68+
tk.Label(self.root, text="Designed by Neverklear Technologies 2025", font=("Arial", 9), fg="gray").pack(pady=5)
8769

8870
def log(self, message):
8971
self.log_output.configure(state='normal')
9072
self.log_output.insert(tk.END, message + "\n")
9173
self.log_output.see(tk.END)
9274
self.log_output.configure(state='disabled')
75+
with open("logfile.txt", "a", encoding="utf-8") as logf:
76+
logf.write(message + "\n")
9377

94-
def scan_and_copy_files(self):
95-
source = self.source_drive.get()
96-
dest = self.destination_folder.get()
97-
min_size_bytes = self.min_file_size_kb.get() * 1024
78+
def cancel_scan_confirm(self):
79+
if messagebox.askyesno("Confirm Cancel", "Are you sure you want to cancel the scan?"):
80+
self.cancel_requested = True
81+
self.status_label.config(text="Scan cancelled by user.")
82+
self.cancel_button.config(state=tk.DISABLED)
9883

99-
if not source or not os.path.exists(source):
100-
messagebox.showerror("Error", "Please select a valid source drive.")
84+
def select_and_copy(self, file_type):
85+
self.cancel_requested = False
86+
self.file_type_mode = file_type
87+
source = filedialog.askdirectory(title="Select Drive to Scan")
88+
dest = filedialog.askdirectory(title="Select Destination Folder")
89+
if source and dest:
90+
self.source.set(source)
91+
self.destination.set(dest)
92+
self.start_thread()
93+
94+
def copy_user_data(self):
95+
self.cancel_requested = False
96+
drive = filedialog.askdirectory(title="Select Windows Installation Drive (e.g., C:\\)")
97+
dest = filedialog.askdirectory(title="Select Destination Folder")
98+
if not drive or not dest:
10199
return
102-
103-
if not dest:
104-
messagebox.showerror("Error", "Please select a destination folder.")
100+
user_root = os.path.join(drive, "Users")
101+
if not os.path.exists(user_root):
102+
messagebox.showerror("Error", "The selected drive does not appear to contain a Users folder.")
105103
return
106104

107-
self.log(f"Scanning {source} for image/PDF files > {self.min_file_size_kb.get()} KB...\n")
105+
self.source.set(user_root)
106+
self.destination.set(dest)
107+
self.file_type_mode = "UserData"
108+
self.start_thread()
109+
110+
def start_thread(self):
111+
self.progress["value"] = 0
112+
self.progress.update()
113+
self.status_label.config(text="Scanning and copying files...")
114+
self.cancel_button.config(state=tk.NORMAL)
115+
thread = Thread(target=self.perform_copy)
116+
thread.start()
117+
118+
def perform_copy(self):
119+
source = self.source.get()
120+
dest = self.destination.get()
121+
min_size_bytes = self.min_file_size_kb.get() * 1024
108122

109-
# Step 1: Find all files to copy
110123
files_to_copy = []
111-
for root_dir, _, files in os.walk(source):
112-
for file in files:
113-
if self.cancel_requested:
114-
self.log("\n🚫 Scan canceled by user.")
115-
return
116-
if any(file.lower().endswith(ext) for ext in PHOTO_EXTENSIONS):
117-
full_path = os.path.join(root_dir, file)
118-
try:
119-
if os.path.getsize(full_path) >= min_size_bytes:
120-
files_to_copy.append(full_path)
121-
except Exception:
122-
continue
124+
125+
if self.file_type_mode == "UserData":
126+
for user in os.listdir(source):
127+
user_path = os.path.join(source, user)
128+
if os.path.isdir(user_path):
129+
files_to_copy.extend([os.path.join(dp, f) for dp, dn, filenames in os.walk(user_path) for f in filenames])
130+
else:
131+
extensions = EXTENSIONS.get(self.file_type_mode, set())
132+
for root_dir, _, files in os.walk(source):
133+
for file in files:
134+
if self.cancel_requested:
135+
return
136+
if any(file.lower().endswith(ext) for ext in extensions):
137+
full_path = os.path.join(root_dir, file)
138+
try:
139+
if os.path.getsize(full_path) >= min_size_bytes:
140+
files_to_copy.append(full_path)
141+
except Exception as e:
142+
self.log(f"✘ Error reading size of {full_path}: {e}")
123143

124144
total_files = len(files_to_copy)
125145
self.progress["maximum"] = total_files
126-
self.progress["value"] = 0
127-
128-
# Step 2: Copy files
129146
copied = 0
130147
failed = 0
131148

132149
for full_path in files_to_copy:
133150
if self.cancel_requested:
134-
self.log("\n🚫 Scan canceled by user.")
151+
self.log("🚫 Scan cancelled by user.")
135152
break
136153
try:
137154
relative_path = os.path.relpath(full_path, source)
@@ -146,12 +163,10 @@ def scan_and_copy_files(self):
146163
self.progress["value"] += 1
147164
self.progress.update()
148165

149-
if not self.cancel_requested:
150-
self.log(f"\n✅ Scan Complete. {copied} copied, {failed} failed.\n")
166+
self.status_label.config(text=f"✅ Done: {copied} copied, {failed} failed, {total_files} total.")
151167
self.cancel_button.config(state=tk.DISABLED)
152168

153-
# Launch the app
154169
if __name__ == "__main__":
155170
root = tk.Tk()
156-
app = FileScannerApp(root)
171+
app = FileCopyTool(root)
157172
root.mainloop()

0 commit comments

Comments
 (0)