From 9e74032649bcaa2fe0de4f489e0242ad5822b1d0 Mon Sep 17 00:00:00 2001 From: CosmicKillsSouls Date: Fri, 21 Aug 2026 07:07:21 -0500 Subject: [PATCH] Initial Commit --- Restore.py | 307 +++++++++++++++++++++++++++++++++++++++++++ Search.py | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++++ config.ini | 13 ++ 3 files changed, 697 insertions(+) create mode 100644 Restore.py create mode 100644 Search.py create mode 100644 config.ini diff --git a/Restore.py b/Restore.py new file mode 100644 index 0000000..13bfe6e --- /dev/null +++ b/Restore.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +import os +import tkinter as tk +from tkinter import scrolledtext +import tkinter.font as tkfont +import shutil +import configparser + + +# ============================================================ +# CONFIG: +config = configparser.ConfigParser() +config.read(r"C:\ProgramData\Program-Search\config.ini") +SEARCH_ROOT = config["Paths"]["SEARCH_ROOT"] +TRASH_DIR = config["Paths"]["TRASH_DIR"] +EDITOR_ENABLED = config.getboolean("Features", "EDITOR_ENABLED") +AUTO_CLOSE_SECONDS = config.getint("Features", "AUTO_CLOSE_SECONDS") +CLOSE_ALL_ENABLED = config.getboolean("Features", "CLOSE_ALL_ENABLED") +# ============================================================ + +os.makedirs(TRASH_DIR, exist_ok=True) + + +def custom_messagebox(title, message, kind="info"): + win = tk.Toplevel() + win.title(title) + win.geometry("600x300") + win.grab_set() + win.focus_force() + center_window(win) + + frame = tk.Frame(win, padx=20, pady=20) + frame.pack(expand=True, fill="both") + + tk.Label(frame, text=message, font=("Consolas", 24), wraplength=550).pack(pady=20) + + if kind == "yesno": + result = {"value": False} + + def yes(): + result["value"] = True + win.destroy() + + def no(): + result["value"] = False + win.destroy() + + tk.Button(frame, text="Yes", font=("Consolas", 24), padx=10, pady=10, command=yes).pack(side="left", padx=16) + tk.Button(frame, text="No", font=("Consolas", 24), padx=10, pady=10, command=no).pack(side="right", padx=16) + + win.wait_window() + return result["value"] + + else: + def close(): + win.destroy() + + tk.Button(frame, text="OK", font=("Consolas", 24), padx=10, pady=10, command=close).pack(pady=20) + win.bind("", lambda event: close()) + win.wait_window() + + +def center_window(win): + win.update_idletasks() + w = win.winfo_width() + h = win.winfo_height() + sw = win.winfo_screenwidth() + sh = win.winfo_screenheight() + x = (sw // 2) - (w // 2) + y = (sh // 2) - (h // 2) + win.geometry(f"{w}x{h}+{x}+{y}") + + +def popup_search_input(): + win = tk.Toplevel() + win.title("Search Trash") + win.geometry("600x300") + center_window(win) + + tk.Label(win, text="Enter part number or name:", font=("Consolas", 18)).pack(pady=20) + + entry = tk.Entry(win, font=("Consolas", 18)) + entry.pack(pady=10) + entry.focus_set() + + result = {"value": None} + + def submit(): + result["value"] = entry.get().strip().lower() + win.destroy() + + entry.bind("", lambda event: submit()) + win.bind("", lambda event: submit()) + + tk.Button(win, text="Search", font=("Consolas", 18), padx=20, pady=10, command=submit).pack(pady=40) + + win.wait_window() + return result["value"] + + +def search_files(term): + matches = [] + + for root, dirs, files in os.walk(TRASH_DIR): + for fname in files: + path = os.path.join(root, fname) + fname_lower = fname.lower() + + filename_match = term in fname_lower + contents_match = False + + try: + with open(path, "r", errors="ignore") as f: + if term in f.read().lower(): + contents_match = True + except: + pass + + if filename_match or contents_match: + matches.append(path) + + return matches + + +def popup_results_list(matches): + win = tk.Toplevel() + win.title("Matches Found") + win.geometry("1200x1000") + center_window(win) + + tk.Label(win, text="Select files to preview:", font=("Consolas", 18)).pack(pady=20) + + lb = tk.Listbox(win, selectmode=tk.MULTIPLE, font=("Consolas", 18)) + lb.pack(expand=True, fill="both") + + for m in matches: + lb.insert(tk.END, m) + + result = {"selected": []} + + def select_all(): + lb.select_set(0, tk.END) + + def submit(): + sel = lb.curselection() + result["selected"] = [matches[i] for i in sel] + win.destroy() + + lb.bind("", lambda event: submit()) + win.bind("", lambda event: submit()) + + tk.Button(win, text="Open Selected", font=("Consolas", 18), padx=20, pady=10, command=submit).pack(side="right", padx=20, pady=20) + tk.Button(win, text="Select All", font=("Consolas", 18), padx=20, pady=10, command=select_all).pack(side="left", padx=20, pady=20) + + win.wait_window() + return result["selected"] + + +def popup_file_preview(path, on_close_callback): + win = tk.Toplevel() + win.title(path) + win.geometry("900x1000") + center_window(win) + + box = scrolledtext.ScrolledText(win, wrap=tk.WORD, font=("Consolas", 18)) + box.pack(expand=True, fill="both") + + try: + with open(path, "r", errors="ignore") as f: + original_text = f.read() + box.insert(tk.END, original_text) + box.edit_modified(False) + except Exception as e: + box.insert(tk.END, f"Error reading file:\n{e}") + + modified = {"changed": False} + + def mark_modified(event=None): + if box.edit_modified(): + modified["changed"] = True + box.edit_modified(False) + + if EDITOR_ENABLED: + box.bind("<>", mark_modified) + else: + box.configure(state="disabled") + + frame = tk.Frame(win) + frame.pack(fill="x", pady=10) + + def save_file(): + try: + with open(path, "w", errors="ignore") as f: + f.write(box.get("1.0", tk.END)) + modified["changed"] = False + custom_messagebox("Saved", f"File saved:\n{path}") + except Exception as e: + custom_messagebox("Save Error", str(e)) + + if EDITOR_ENABLED: + tk.Button(frame, text="Save", fg="green", font=("Consolas", 18), padx=15, pady=8, command=save_file).pack(side="left", padx=10, pady=10) + + def restore_file(): + if EDITOR_ENABLED and modified["changed"]: + ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before restoring?", kind="yesno") + if ask: + save_file() + + try: + rel_path = os.path.relpath(path, TRASH_DIR) + dest_path = os.path.join(SEARCH_ROOT, rel_path) + + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + shutil.move(path, dest_path) + + custom_messagebox("Restored", f"File restored to:\n{dest_path}") + win.destroy() + on_close_callback() + + except Exception as e: + custom_messagebox("Restore Error", str(e)) + + tk.Button(frame, text="Restore File", fg="blue", font=("Consolas", 18), padx=15, pady=8, command=restore_file).pack(side="left", padx=10, pady=10) + + def close(): + if EDITOR_ENABLED and modified["changed"]: + ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before closing?", kind="yesno") + if ask: + save_file() + + win.destroy() + on_close_callback() + + tk.Button(frame, text="Close", font=("Consolas", 18), padx=15, pady=8, command=close).pack(side="right", padx=10, pady=10) + + def close_all_button(): + ask = custom_messagebox("WARNING!", "Are you sure you want to close all windows?", kind="yesno") + if ask: + close_all(win.master) + + if CLOSE_ALL_ENABLED: + tk.Button(frame, text="Close All", font=("Consolas", 18), padx=15, pady=8, command=close_all_button).pack(side="right", padx=10, pady=10) + + def auto_close(): + if win.winfo_exists(): + win.destroy() + on_close_callback() + + win.protocol("WM_DELETE_WINDOW", close) + win.after(AUTO_CLOSE_SECONDS * 1000, auto_close) + + +def close_all(root): + for w in root.winfo_children(): + try: + w.destroy() + except: + pass + root.destroy() + + +def main(): + root = tk.Tk() + root.withdraw() + + default_font = tkfont.nametofont("TkDefaultFont") + default_font.configure(family="Consolas", size=18) + + text_font = tkfont.nametofont("TkTextFont") + text_font.configure(family="Consolas", size=18) + + fixed_font = tkfont.nametofont("TkFixedFont") + fixed_font.configure(family="Consolas", size=18) + + while True: + search_term = popup_search_input() + if not search_term: + root.destroy() + return + + matches = search_files(search_term) + + if matches: + break + + custom_messagebox("No Results", "No matching files found.") + + selected = popup_results_list(matches) + if not selected: + root.destroy() + return + + open_windows = {"count": len(selected)} + + def window_closed(): + open_windows["count"] -= 1 + if open_windows["count"] == 0: + root.destroy() + + for path in selected: + popup_file_preview(path, window_closed) + + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/Search.py b/Search.py new file mode 100644 index 0000000..7c326f9 --- /dev/null +++ b/Search.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +import os +import tkinter as tk +from tkinter import filedialog, messagebox, scrolledtext +import tkinter.font as tkfont +import shutil +import ctypes +import string +import configparser + + +# ============================================================ +# CONFIG: +config = configparser.ConfigParser() +config.read(r"C:\ProgramData\Program-Search\config.ini") +SEARCH_ROOT = config["Paths"]["SEARCH_ROOT"] +TRASH_DIR = config["Paths"]["TRASH_DIR"] +TRASH_ENABLED = config.getboolean("Features", "TRASH_ENABLED") +EDITOR_ENABLED = config.getboolean("Features", "EDITOR_ENABLED") +MANUEAL_REPLACE_ENABLED = config.getboolean("Features", "MANUEAL_REPLACE_ENABLED") +CLOSE_ALL_ENABLED = config.getboolean("Features", "CLOSE_ALL_ENABLED") +AUTO_CLOSE_SECONDS = config.getint("Features", "AUTO_CLOSE_SECONDS") +Extension = config["Copy"]["Extension"] +# ============================================================ + + +os.makedirs(TRASH_DIR, exist_ok=True) + + +def custom_messagebox(title, message, kind="info"): + win = tk.Toplevel() + win.title(title) + win.geometry("600x300") + win.grab_set() + win.focus_force() + frame = tk.Frame(win, padx=20, pady=20) + frame.pack(expand=True, fill="both") + center_window(win) + + tk.Label(frame, text=message, font=("Consolas", 24), wraplength=550).pack(pady=20) + def close(): + win.destroy() + + if kind == "yesno": + result = {"value": False} + IsInfo = False + def yes(): + result["value"] = True + win.destroy() + + def no(): + result["value"] = False + win.destroy() + + tk.Button(frame, text="Yes", font=("Consolas", 24), padx=10, pady=10, command=yes).pack(side="left", padx=16) + tk.Button(frame, text="No", font=("Consolas", 24), padx=10, pady=10, command=no).pack(side="right", padx=16) + + win.wait_window() + return result["value"] + + else: + tk.Button(frame, text="OK", font=("Consolas", 24), padx=10, pady=10, command=close).pack(pady=20) + win.bind("", lambda event: close()) + win.wait_window() + + +def center_window(win): + win.update_idletasks() + w = win.winfo_width() + h = win.winfo_height() + sw = win.winfo_screenwidth() + sh = win.winfo_screenheight() + x = (sw // 2) - (w // 2) + y = (sh // 2) - (h // 2) + win.geometry(f"{w}x{h}+{x}+{y}") + + +def find_external_drives(): + drives = [] + DRIVE_REMOVABLE = 2 + + for letter in string.ascii_uppercase: + drive = f"{letter}:\\" + if os.path.exists(drive): + dtype = ctypes.windll.kernel32.GetDriveTypeW(drive) + if dtype == DRIVE_REMOVABLE: + drives.append(drive) + + return drives + + +def close_all(root): + for w in root.winfo_children(): + try: + w.destroy() + except: + pass + root.destroy() + + +def popup_search_input(): + win = tk.Toplevel() + win.title("Search") + win.geometry("600x300") + center_window(win) + + tk.Label(win, text="Enter program number, part number, or name:").pack(pady=10) + entry = tk.Entry(win) + entry.pack(pady=5) + entry.focus_set() + entry.bind("", lambda event: submit()) + win.bind("", lambda event: submit()) + + result = {"value": None} + + def submit(): + result["value"] = entry.get().strip().lower() + win.destroy() + + tk.Button(win, text="Search", command=submit, padx=20, pady=20).pack(pady=60) + + win.wait_window(); return result["value"] + + +def search_files(term): + matches = [] + + for root, dirs, files in os.walk(SEARCH_ROOT): + for fname in files: + path = os.path.join(root, fname) + fname_lower = fname.lower() + + filename_match = term in fname_lower + contents_match = False + + try: + with open(path, "r", errors="ignore") as f: + if term in f.read().lower(): + contents_match = True + except: + pass + + if filename_match or contents_match: + matches.append(path) + + return matches + + +def popup_results_list(matches): + win = tk.Toplevel() + win.title("Matches Found") + win.geometry("1200x1000") + center_window(win) + + tk.Label(win, text="Select files to preview:").pack(pady=10) + + lb = tk.Listbox(win, selectmode=tk.MULTIPLE) + lb.pack(expand=True, fill="both") + + for m in matches: + lb.insert(tk.END, m) + + result = {"selected": []} + + def select_all(): + lb.select_set(0, tk.END) + def submit(): + sel = lb.curselection() + result["selected"] = [matches[i] for i in sel] + win.destroy() + + tk.Button(win, text="Open Selected", command=submit, padx=20, pady=10).pack(side ="right", padx=20, pady=10) + tk.Button(win, text="Select All", command=select_all, padx=20, pady=10).pack(side ="left", padx=20, pady=10) + + lb.bind("", lambda event: submit()) + win.bind("", lambda event: submit()) + + win.wait_window(); return result["selected"] + + +def popup_file_preview(path, on_close_callback): + win = tk.Toplevel() + win.title(path) + win.geometry("900x1000") + + box = scrolledtext.ScrolledText(win, wrap=tk.WORD) + box.pack(expand=True, fill="both") + + try: + with open(path, "r", errors="ignore") as f: + original_text = f.read() + box.insert(tk.END, original_text) + box.edit_modified(False) + except Exception as e: + original_text = "" + box.insert(tk.END, f"Error reading file:\n{e}") + + modified = {"changed": False} + + def mark_modified(event=None): + if box.edit_modified(): + modified["changed"] = True + box.edit_modified(False) + + if EDITOR_ENABLED: + box.bind("<>", mark_modified) + else: + box.configure(state="disabled") + + frame = tk.Frame(win) + frame.pack(fill="x", pady=10) + + + def save_file(): + try: + with open(path, "w", errors="ignore") as f: + f.write(box.get("1.0", tk.END)) + modified["changed"] = False + custom_messagebox("Saved", f"File saved:\n{path}") + except Exception as e: + custom_messagebox("Save Error", str(e)) + + def copy_to_external(): + if EDITOR_ENABLED and modified["changed"]: + ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before copying?", kind = "yesno") + if ask: + save_file() + + drives = find_external_drives() + if not drives: + custom_messagebox("No External Drive Found", "No external drive detected."); return + + base_name = os.path.splitext(os.path.basename(path))[0] + new_name = base_name + Extension + + copied = [] + for d in drives: + try: + dest = os.path.join(d, new_name) + shutil.copy2(path, dest) + copied.append(dest) + except Exception as e: + custom_messagebox("Copy Error", f"Could not copy to {d}:\n{e}") + + if copied: + custom_messagebox("Copied Successfully", "Copied to:\n\n" + "\n".join(copied)) + win.destroy() + on_close_callback() + close_all(win.master) + + def move_to_trash(): + confirm = custom_messagebox("Move to Trash",f"Are you sure you want to move this file to trash?\n\n{path}", kind = "yesno") + if confirm: + try: + rel_path = os.path.relpath(path, SEARCH_ROOT) + dest_path = os.path.join(TRASH_DIR, rel_path) + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + shutil.move(path, dest_path) + custom_messagebox("Trashed", f"Moved to trash:\n{dest_path}") + win.destroy() + on_close_callback() + + except Exception as e: + custom_messagebox("Trash Error", str(e)) + + def manual_replace(): + drives = find_external_drives() + if not drives: custom_messagebox("No External Drive Found","No external drive detected."); return + base_name = os.path.splitext(os.path.basename(path))[0] + new_name = base_name + Extension + replacement_path = None + for d in drives: + candidate = os.path.join(d, new_name) + if os.path.exists(candidate): replacement_path = candidate; + break + if not replacement_path: custom_messagebox("File Not Found",f"No replacement file named:\n\n{new_name}\n\nwas found on any external drive."); return + try: + with open(replacement_path,"r",errors="ignore") as f: new_contents = f.read() + except Exception as e: custom_messagebox("Read Error",f"Could not read replacement file:\n{e}"); return + try: + with open(path,"w",errors="ignore") as f: f.write(new_contents) + except Exception as e: custom_messagebox("Write Error",f"Could not overwrite original file:\n{e}"); return + box.delete("1.0",tk.END); box.insert(tk.END,new_contents); box.edit_modified(False); modified["changed"] = False + custom_messagebox("Replaced Successfully",f"File replaced using:\n{replacement_path}") + + def close(): + if EDITOR_ENABLED and modified["changed"]: + ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before closing?", kind = "yesno") + if ask: + save_file() + + win.destroy() + on_close_callback() + + def auto_close(): + if win.winfo_exists(): + win.destroy() + on_close_callback() + + def close_all_button(): + ask = custom_messagebox("WARNING!", "Are you sure you want to cloose all windows?", kind = "yesno") + if ask: + close_all(win.master) + + win.protocol("WM_DELETE_WINDOW", close) + win.after(AUTO_CLOSE_SECONDS * 1000, auto_close) + + if EDITOR_ENABLED: + tk.Button(win, text="Save", fg="green", command=save_file, padx=15, pady=8).pack(side ="left",padx=10, pady=10) + + if TRASH_ENABLED == True: + tk.Button(win, text="Move to Trash", fg="red", command=move_to_trash, padx=15, pady=8).pack(side ="left",padx=10, pady=10) + + tk.Button(win, text="Copy to External Drive", command=copy_to_external, padx=15, pady=8).pack(side ="left",padx=10, pady=10) + + tk.Button(win, text="Close", command=close, padx=15, pady=8).pack(side ="right",padx=10, pady=10) + + if CLOSE_ALL_ENABLED: + tk.Button(win, text="Close All", command=lambda: close_all_button(), padx=15, pady=8).pack(side ="right",padx=10, pady=10) + + if MANUEAL_REPLACE_ENABLED == True: + tk.Button(win, text="Replace from External Drive", command=manual_replace, padx=15, pady=8).pack(side ="right",padx=10, pady=10) + + + +def main(): + root = tk.Tk() + root.withdraw() + default_font = tkfont.nametofont("TkDefaultFont") + default_font.configure(family="Consolas", size=18) + + text_font = tkfont.nametofont("TkTextFont") + text_font.configure(family="Consolas", size=18) + + fixed_font = tkfont.nametofont("TkFixedFont") + fixed_font.configure(family="Consolas", size=18) + + root.option_add("*Dialog.msg.font", "Consolas 18") + root.option_add("*Dialog.msg.width", 60) + root.option_add("*Dialog.msg.height", 10) + root.option_add("*Dialog.msg.wrapLength", 600) + + while True: + search_term = popup_search_input() + if not search_term: + root.destroy() + return + + matches = search_files(search_term) + + if matches: + break + + custom_messagebox("No Results", "No matching files found.") + + selected = popup_results_list(matches) + if not selected: + root.destroy() + return + + + open_windows = {"count": len(selected)} + + def window_closed(): + open_windows["count"] -= 1 + if open_windows["count"] == 0: + root.destroy() + + + for path in selected: + popup_file_preview(path, window_closed) + + root.mainloop() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..d436f2f --- /dev/null +++ b/config.ini @@ -0,0 +1,13 @@ +[Paths] +SEARCH_ROOT = C:\CNCPrograms\Programs +TRASH_DIR = C:\CNCPrograms\Trash + +[Features] +TRASH_ENABLED = False +EDITOR_ENABLED = False +MANUEAL_REPLACE_ENABLED = False +CLOSE_ALL_ENABLED = True +AUTO_CLOSE_SECONDS = 300 + +[Copy] +Extension = \ No newline at end of file