From 3d97c3b69828270c66f385056e1abcf9893506e7 Mon Sep 17 00:00:00 2001 From: Thomas Wilczynski <47839545+Gamergull@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:42:16 -0700 Subject: [PATCH] Added settings; implemented reading of file modes --- README.txt | 14 ++++-- app.py | 99 +++++++++++++++++++++++++++++++------- data/settings_default.json | 68 ++++++++++++++++++++++++++ file_manager.py | 32 ++++++------ utils.py | 3 +- 5 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 data/settings_default.json diff --git a/README.txt b/README.txt index 14ada7f..3a91004 100644 --- a/README.txt +++ b/README.txt @@ -1,12 +1,16 @@ A simple app that manages your downloads folder. Features: + - Menu + - Exit - File list view, separated by type - File name, date modified, size - - Buttons - - Start Task, Refresh, Backup - - Status bar + - Buttons: Start Task, Refresh, Backup - Rules manager - Per file type: Move to dir, copy to dir, delete, or ignore - - Custom define file types - - Save and load rules config \ No newline at end of file + - Custom define file types (text box) + - Buttons: Save Config, Reset Config + - Status bar + +Thoughts: + - How should the file manager initiate, save, and reset the rules data? \ No newline at end of file diff --git a/app.py b/app.py index d663149..d1b7864 100644 --- a/app.py +++ b/app.py @@ -23,7 +23,7 @@ class App(tk.Tk): super().__init__() self.title("Download Utils") self.option_add('*tearOff', tk.FALSE) - self.fm = fm.Manager() + self.fm = fm.Manager(True) self.gui() screen_width = self.winfo_screenwidth() @@ -35,18 +35,33 @@ class App(tk.Tk): self.geometry(f"{width}x{height}+{center_x}+{center_y}") def update_fileview(self): - self.fmgr = fm.Manager() - self.fmgr.set_directory(self.fmgr.get_directory()) - self.fmgr.update_file_data() + self.fm.set_directory(self.fm.get_directory()) + self.fm.update_file_data() while len(self.fileview.get_children()) > 0: self.fileview.delete(self.fileview.get_children()[-1]) - for k, v in self.fmgr.filedata.items(): + for k, v in self.fm.filedata.items(): values = (k, str(ut.format_date(v["time"])), str(ut.format_bytes(v["size"]))) self.fileview.insert("", tk.END, k, values=values) + def select_mode_from_list(self, event): + key = "" + value = self.w_list.get(self.w_list.curselection()) + for k, v in self.fm.filemodes.items(): + if v["name"] == value: + key = k + break + + if len(key) == 0: return # Do error + + data = self.fm.filemodes[key] + print(data) + self.v_radio.set(data["action"]) + self.v_targetdir.set(data["destination"]) + self.v_extstr.set(", ".join(data["extensions"])) + def gui(self): self.gui_menu() @@ -69,15 +84,6 @@ class App(tk.Tk): menu=a_menu_file, underline=0 ) - - a_menu_settings = tk.Menu(a_menu) - a_menu_settings.add_command(label="Save Config") - - a_menu.add_cascade( - label="Settings", - menu=a_menu_settings, - underline=0 - ) def gui_files(self): frame = ttk.Frame(self.book) @@ -117,11 +123,68 @@ class App(tk.Tk): def gui_rules(self): frame = ttk.Frame(self.book) - # Display a simple panel on the left having file meta types + box = ttk.Frame(frame) - # Mode: - # Target dir: - # File extensions: + button = ttk.Button(box, text="Save Config") + button.grid(column=0, row=0, pady=5) + + button = ttk.Button(box, text="Reset") + button.grid(column=1, row=0, pady=5) + + box.pack(side=tk.TOP, fill=tk.X) + + main = ttk.Frame(frame) + main.columnconfigure(0, weight=1, uniform="column") + main.columnconfigure(1, weight=3, uniform="column") + + modelist = [v["name"] for v in self.fm.filemodes.values()] + self.v_modelist = tk.Variable(value=modelist) + self.w_list = tk.Listbox(main, listvariable=self.v_modelist, + selectmode=tk.SINGLE) + self.w_list.bind('<>', self.select_mode_from_list) + self.w_list.grid(row=0, column=0, sticky=tk.NS) + + details = ttk.Frame(main) + + i_frame = ttk.Labelframe(details, text="Action") + + self.v_radio = tk.StringVar() + self.v_actions = ("move", "copy", "delete", "ignore") + for s in self.v_actions: + str = f"{s.capitalize()} " + radio = ttk.Radiobutton(i_frame, text=str, value=s, + variable=self.v_radio) + radio.pack(side=tk.LEFT) + i_frame.pack(side=tk.TOP, anchor=tk.W) + + i_frame = ttk.Labelframe(details, text="Target Directory") + + label = ttk.Label(i_frame, + text="Start with \"~/\" " + "for your home (user) directory.") + label.pack(side=tk.TOP, anchor=tk.W) + + self.v_targetdir = tk.StringVar() + entry = ttk.Entry(i_frame, textvariable=self.v_targetdir) + entry.pack(side=tk.TOP, fill=tk.X) + + i_frame.pack(side=tk.TOP, fill=tk.X) + + i_frame = ttk.Labelframe(details, text="File Extensions") + + label = ttk.Label(i_frame, + text="Separate extensions with a comma and space.") + label.pack(side=tk.TOP, anchor=tk.W) + + self.v_extstr = tk.StringVar() + entry = ttk.Entry(i_frame, textvariable=self.v_extstr) + entry.pack(side=tk.TOP, fill=tk.X) + + i_frame.pack(side=tk.TOP, fill=tk.X) + + details.grid(row=0, column=1, sticky=tk.NSEW) + + main.pack(side=tk.TOP, fill=tk.X) return frame diff --git a/data/settings_default.json b/data/settings_default.json new file mode 100644 index 0000000..8c6ce88 --- /dev/null +++ b/data/settings_default.json @@ -0,0 +1,68 @@ +{ + "filemodes": { + "all": { + "action": "delete", + "active": true, + "destination": "~/Downloads/", + "extensions": ["*"], + "name": "All Files" + }, + "image": { + "action": "ignore", + "active": true, + "destination": "~/Pictures/", + "extensions": ["jpg", "jpeg", "png", "gif", "webp"], + "name": "Images" + }, + "audio": { + "action": "ignore", + "active": true, + "destination": "~/Music/", + "extensions": ["wav", "mp3", "ogg"], + "name": "Audio" + }, + "video": { + "action": "ignore", + "active": true, + "destination": "~/Videos/", + "extensions": ["avi", "mpeg", "mp4", "webm"], + "name": "Video" + }, + "document": { + "action": "ignore", + "active": true, + "destination": "~/Documents/", + "extensions": ["txt", "doc", "docx", "pdf"], + "name": "Documents" + }, + "data": { + "action": "ignore", + "active": true, + "destination": "~/Documents/data/", + "extensions": ["json", "csv", "db"], + "name": "Data" + }, + "program": { + "action": "ignore", + "active": true, + "destination": "~/Downloads/", + "extensions": ["exe", "msi"], + "name": "Programs" + }, + "archive": { + "action": "ignore", + "active": true, + "destination": "~/Downloads/", + "extensions": ["zip", "rar", "gz"], + "name": "Archives" + }, + "other": { + "action": "ignore", + "active": true, + "destination": "~/Downloads/", + "extensions": [], + "name": "Other" + } + }, + "version": 1 +} \ No newline at end of file diff --git a/file_manager.py b/file_manager.py index a556243..e93f490 100644 --- a/file_manager.py +++ b/file_manager.py @@ -6,23 +6,18 @@ import os import json class Manager: - def __init__(self): + def __init__(self, auto_config=False): self.dir = os.path.expanduser("~") # Default directory self.filedata = dict() - self.filetypes = { - "images": ["jpg", "jpeg", "png", "gif", "webp"], - "audio": ["wav", "mp3", "ogg"], - "video": ["wmv", "mpg", "mpeg", "avi"], - "text": ["txt", "doc", "docx"], - "data": ["csv", "json"], - "programs": ["exe", "msi"], - "archives": ["zip", "gz", "rar"], - "other": [] - } + self.filemodes = dict() # Contains file mode data + self.filetypes = dict() # Maps file extensions to modes + + if auto_config: + self.set_directory(self.get_directory()) + self.setup_file_rules() def get_directory(self, target="Downloads"): - dir = os.path.expanduser("~") - dir = os.path.join(dir, target) + dir = os.path.join(os.path.expanduser("~"), target) if os.path.exists(dir): # Needs error handling return dir @@ -30,6 +25,15 @@ class Manager: if os.path.exists(dir): self.dir = dir + def setup_file_rules(self, file="data/settings_default.json", data=None): + with open(file) as f: + data = json.load(f) + self.filemodes = data["filemodes"] + self.filetypes.clear() + for k, v in self.filemodes.items(): + for ext in v["extensions"]: + self.filetypes[ext] = k + def modify_type_list(self, key, values): if key in self.filetypes: self.filetypes[key] = list(values) @@ -41,4 +45,4 @@ class Manager: if entry.is_file(): st = entry.stat() data = {"time": st.st_mtime, "size": st.st_size} - self.filedata.update({entry.name: data}) \ No newline at end of file + self.filedata[entry.name] = data diff --git a/utils.py b/utils.py index cada610..195eb7b 100644 --- a/utils.py +++ b/utils.py @@ -7,7 +7,7 @@ import time KILOBYTES = 1024 def format_bytes(bytes): - """Return the given bytes as a human friendly KB, MB, GB, or TB string.""" + """Returns a B, KB, MB, GB, or TB string from the given bytes.""" units = { "B": float(1), "KB": float(KILOBYTES), @@ -28,4 +28,5 @@ def format_bytes(bytes): return f"{(bytes / units["TB"]):.2f} TB" def format_date(timestamp): + """Returns a time string (ISO 8601) from the given integer timestamp.""" return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) \ No newline at end of file