Added settings; implemented reading of file modes

This commit is contained in:
Thomas Wilczynski 2025-08-19 13:42:16 -07:00
commit 3d97c3b698
5 changed files with 178 additions and 38 deletions

View file

@ -1,12 +1,16 @@
A simple app that manages your downloads folder. A simple app that manages your downloads folder.
Features: Features:
- Menu
- Exit
- File list view, separated by type - File list view, separated by type
- File name, date modified, size - File name, date modified, size
- Buttons - Buttons: Start Task, Refresh, Backup
- Start Task, Refresh, Backup
- Status bar
- Rules manager - Rules manager
- Per file type: Move to dir, copy to dir, delete, or ignore - Per file type: Move to dir, copy to dir, delete, or ignore
- Custom define file types - Custom define file types (text box)
- Save and load rules config - Buttons: Save Config, Reset Config
- Status bar
Thoughts:
- How should the file manager initiate, save, and reset the rules data?

99
app.py
View file

@ -23,7 +23,7 @@ class App(tk.Tk):
super().__init__() super().__init__()
self.title("Download Utils") self.title("Download Utils")
self.option_add('*tearOff', tk.FALSE) self.option_add('*tearOff', tk.FALSE)
self.fm = fm.Manager() self.fm = fm.Manager(True)
self.gui() self.gui()
screen_width = self.winfo_screenwidth() screen_width = self.winfo_screenwidth()
@ -35,18 +35,33 @@ class App(tk.Tk):
self.geometry(f"{width}x{height}+{center_x}+{center_y}") self.geometry(f"{width}x{height}+{center_x}+{center_y}")
def update_fileview(self): def update_fileview(self):
self.fmgr = fm.Manager() self.fm.set_directory(self.fm.get_directory())
self.fmgr.set_directory(self.fmgr.get_directory()) self.fm.update_file_data()
self.fmgr.update_file_data()
while len(self.fileview.get_children()) > 0: while len(self.fileview.get_children()) > 0:
self.fileview.delete(self.fileview.get_children()[-1]) 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"])), values = (k, str(ut.format_date(v["time"])),
str(ut.format_bytes(v["size"]))) str(ut.format_bytes(v["size"])))
self.fileview.insert("", tk.END, k, values=values) 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): def gui(self):
self.gui_menu() self.gui_menu()
@ -70,15 +85,6 @@ class App(tk.Tk):
underline=0 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): def gui_files(self):
frame = ttk.Frame(self.book) frame = ttk.Frame(self.book)
@ -117,11 +123,68 @@ class App(tk.Tk):
def gui_rules(self): def gui_rules(self):
frame = ttk.Frame(self.book) frame = ttk.Frame(self.book)
# Display a simple panel on the left having file meta types box = ttk.Frame(frame)
# Mode: button = ttk.Button(box, text="Save Config")
# Target dir: button.grid(column=0, row=0, pady=5)
# File extensions:
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('<<ListboxSelect>>', 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 return frame

View file

@ -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
}

View file

@ -6,23 +6,18 @@ import os
import json import json
class Manager: class Manager:
def __init__(self): def __init__(self, auto_config=False):
self.dir = os.path.expanduser("~") # Default directory self.dir = os.path.expanduser("~") # Default directory
self.filedata = dict() self.filedata = dict()
self.filetypes = { self.filemodes = dict() # Contains file mode data
"images": ["jpg", "jpeg", "png", "gif", "webp"], self.filetypes = dict() # Maps file extensions to modes
"audio": ["wav", "mp3", "ogg"],
"video": ["wmv", "mpg", "mpeg", "avi"], if auto_config:
"text": ["txt", "doc", "docx"], self.set_directory(self.get_directory())
"data": ["csv", "json"], self.setup_file_rules()
"programs": ["exe", "msi"],
"archives": ["zip", "gz", "rar"],
"other": []
}
def get_directory(self, target="Downloads"): def get_directory(self, target="Downloads"):
dir = os.path.expanduser("~") dir = os.path.join(os.path.expanduser("~"), target)
dir = os.path.join(dir, target)
if os.path.exists(dir): # Needs error handling if os.path.exists(dir): # Needs error handling
return dir return dir
@ -30,6 +25,15 @@ class Manager:
if os.path.exists(dir): if os.path.exists(dir):
self.dir = 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): def modify_type_list(self, key, values):
if key in self.filetypes: if key in self.filetypes:
self.filetypes[key] = list(values) self.filetypes[key] = list(values)
@ -41,4 +45,4 @@ class Manager:
if entry.is_file(): if entry.is_file():
st = entry.stat() st = entry.stat()
data = {"time": st.st_mtime, "size": st.st_size} data = {"time": st.st_mtime, "size": st.st_size}
self.filedata.update({entry.name: data}) self.filedata[entry.name] = data

View file

@ -7,7 +7,7 @@ import time
KILOBYTES = 1024 KILOBYTES = 1024
def format_bytes(bytes): 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 = { units = {
"B": float(1), "B": float(1),
"KB": float(KILOBYTES), "KB": float(KILOBYTES),
@ -28,4 +28,5 @@ def format_bytes(bytes):
return f"{(bytes / units["TB"]):.2f} TB" return f"{(bytes / units["TB"]):.2f} TB"
def format_date(timestamp): 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)) return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))