Added logging, cleaned up some functions

This commit is contained in:
Thomas Wilczynski 2025-09-03 13:33:47 -07:00
commit 5a04dd37f1
5 changed files with 85 additions and 43 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
.vscode/ .vscode/
__pycache__/ __pycache__/
venv/ venv/
venv_linux/
plan.svg plan.svg
data/file_rules_custom.json data/file_rules_custom.json
app.log

44
app.py
View file

@ -5,6 +5,7 @@ __author__ = "Gull"
import tkinter as tk import tkinter as tk
import file_manager as fm import file_manager as fm
import logging as log
import utils as ut import utils as ut
from tkinter import ttk from tkinter import ttk
from tkinter.messagebox import showinfo from tkinter.messagebox import showinfo
@ -18,10 +19,22 @@ def set_debug_mode(value):
else: else:
debug_mode = value debug_mode = value
def setup_log():
import sys
log.basicConfig(
filename="app.log",
filemode="w",
level=log.DEBUG,
format="{asctime}: [{levelname}] {message}",
style="{")
log.getLogger().addHandler(log.StreamHandler(sys.stdout)) # Console
log.info("App started")
class App(tk.Tk): class App(tk.Tk):
def __init__(self, width=640, height=480): def __init__(self, width=640, height=480):
super().__init__() super().__init__()
self.title("Download Utils") self.title("Download Sorter")
self.current_mode = "all" self.current_mode = "all"
self.option_add('*tearOff', tk.FALSE) self.option_add('*tearOff', tk.FALSE)
self.fm = fm.Manager(True) self.fm = fm.Manager(True)
@ -37,9 +50,10 @@ 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, tree=True):
self.fm.set_directory(self.fm.get_directory()) self.fm.set_directory(self.fm.get_directory())
self.fm.update_file_data() self.fm.update_file_data()
log.info(f"Processed file view with {len(self.fm.filedata)} items")
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])
@ -49,17 +63,25 @@ class App(tk.Tk):
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 load_config(self, filepath="data/file_rules_custom.json"): def load_config(self, filepath="%/Temp/file_rules_custom.json"):
data = ut.load_json_file(filepath) data = ut.load_json_file(ut.parse_dir(filepath))
self.fm.setup_file_rules(data, True) self.fm.setup_file_rules(data, True)
log.info(f"Loaded custom file rules")
def save_config(self, filepath="data/file_rules_custom.json"): def save_config(self, filepath="%/Temp/file_rules_custom.json"):
ut.save_json_file(filepath, self.fm.filemodes) ut.save_json_file(ut.parse_dir(filepath), self.fm.filemodes)
log.info(f"Saved custom file rules")
def reset_config(self):
data = ut.load_json_file(self.fm.config_path)
self.fm.setup_file_rules(data, True)
self.prep()
log.info(f"Reloaded default file rules")
def update_mode_data(self, mode, key, value): def update_mode_data(self, mode, key, value):
print(f"{key} : {value}")
if not mode in self.fm.filemodes: return if not mode in self.fm.filemodes: return
self.fm.filemodes[mode].update({key: value}) self.fm.filemodes[mode].update({key: value})
log.info(f"Set properties for file mode: {key} = {value}")
def set_rules_variables(self, key): def set_rules_variables(self, key):
if not key in self.fm.filemodes: return if not key in self.fm.filemodes: return
@ -82,6 +104,7 @@ class App(tk.Tk):
if len(key) > 0: if len(key) > 0:
self.current_mode = key self.current_mode = key
self.set_rules_variables(key) self.set_rules_variables(key)
log.debug(f"Selected file mode: {key}")
def prep(self): def prep(self):
self.update_fileview() self.update_fileview()
@ -89,11 +112,11 @@ class App(tk.Tk):
self.set_rules_variables(self.current_mode) self.set_rules_variables(self.current_mode)
def run_task(self): def run_task(self):
print("Task started") log.info("File organization started")
self.fm.run_task() self.fm.run_task()
def run_backup(self): def run_backup(self):
print("Backup started") log.info("File backup started")
def gui(self): def gui(self):
self.gui_menu() self.gui_menu()
@ -159,7 +182,7 @@ class App(tk.Tk):
button = ttk.Button(box, text="Save Config", command=self.save_config) button = ttk.Button(box, text="Save Config", command=self.save_config)
button.grid(column=0, row=0, pady=5) button.grid(column=0, row=0, pady=5)
button = ttk.Button(box, text="Reset") button = ttk.Button(box, text="Reset", command=self.reset_config)
button.grid(column=1, row=0, pady=5) button.grid(column=1, row=0, pady=5)
box.pack(side=tk.TOP, fill=tk.X) box.pack(side=tk.TOP, fill=tk.X)
@ -225,5 +248,6 @@ class App(tk.Tk):
return frame return frame
if __name__ == "__main__": if __name__ == "__main__":
setup_log()
app = App() app = App()
app.mainloop() app.mainloop()

View file

@ -11,28 +11,28 @@
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Pictures/", "destination": "~/Pictures/",
"extensions": ["jpg", "jpeg", "png", "gif", "webp"], "extensions": ["jpg", "jpeg", "png", "gif", "bmp", "psd", "raw", "webp"],
"name": "Images" "name": "Images"
}, },
"audio": { "audio": {
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Music/", "destination": "~/Music/",
"extensions": ["wav", "mp3", "ogg"], "extensions": ["wav", "mp3", "ogg", "flac", "wma", "aiff", "aac"],
"name": "Audio" "name": "Audio"
}, },
"video": { "video": {
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Videos/", "destination": "~/Videos/",
"extensions": ["avi", "mpeg", "mp4", "webm"], "extensions": ["avi", "mpeg", "mp4", "mov", "mkv", "ogv", "webm"],
"name": "Video" "name": "Video"
}, },
"document": { "document": {
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Documents/", "destination": "~/Documents/",
"extensions": ["txt", "doc", "docx", "pdf"], "extensions": ["txt", "doc", "docx", "pdf", "rtf"],
"name": "Documents" "name": "Documents"
}, },
"data": { "data": {
@ -46,14 +46,14 @@
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Downloads/", "destination": "~/Downloads/",
"extensions": ["exe", "msi"], "extensions": ["exe", "msi", "elf"],
"name": "Programs" "name": "Programs"
}, },
"archive": { "archive": {
"action": "ignore", "action": "ignore",
"active": true, "active": true,
"destination": "~/Downloads/", "destination": "~/Downloads/",
"extensions": ["zip", "rar", "gz"], "extensions": ["zip", "rar", "tar", "iso", "gz", "lz", "rz", "7z", "dmg"],
"name": "Archives" "name": "Archives"
}, },
"other": { "other": {
@ -63,6 +63,5 @@
"extensions": [], "extensions": [],
"name": "Other" "name": "Other"
} }
}, }
"version": 1
} }

View file

@ -4,7 +4,9 @@ __author__ = "Gull"
import os import os
import shutil import shutil
import logging as log
import utils as ut import utils as ut
import send2trash as s2t
debug_mode = True debug_mode = True
@ -61,6 +63,9 @@ class Manager:
def run_task(self): def run_task(self):
self.match_file_types() # Updates file types dict self.match_file_types() # Updates file types dict
log.info(f"Now processing {len(self.filedata)} files")
if debug_mode:
log.warning("Debug mode is enabled; file actions will be ignored")
for k, v in self.filedata.items(): for k, v in self.filedata.items():
fullpath = os.path.join(self.dir, k) fullpath = os.path.join(self.dir, k)
if os.path.exists(fullpath): if os.path.exists(fullpath):
@ -79,30 +84,23 @@ class Manager:
elif rule["action"] == "delete": elif rule["action"] == "delete":
delete(v["fullpath"]) delete(v["fullpath"])
def parse_dir(dir):
"""Checks if a directory exists, and if it is for the user."""
if dir[0] == "~":
dir = dir.lstrip("~/")
dir = os.path.join(os.path.expanduser("~"), dir)
return dir
def move(src, dst): def move(src, dst):
src, dst = parse_dir(src), parse_dir(dst) src, dst = ut.parse_dir(src), ut.parse_dir(dst)
if debug_mode: log.info(f"Moved file: {src} > {dst}")
print(f"Move: {src} > {dst}") if not debug_mode:
else:
shutil.move(src, dst) shutil.move(src, dst)
def copy(src, dst): def copy(src, dst):
src, dst = parse_dir(src), parse_dir(dst) src, dst = ut.parse_dir(src), ut.parse_dir(dst)
if debug_mode: log.info(f"Copied file: {src} > {dst}")
print(f"Copy: {src} > {dst}") if not debug_mode:
else:
shutil.copy2(src, dst) shutil.copy2(src, dst)
def delete(src): def delete(src):
src = parse_dir(src) src = ut.parse_dir(src)
if debug_mode: log.info(f"Deleted file: {src}")
print(f"Delete: {src}") if not debug_mode:
else: try:
os.remove(src) s2t.send2trash(src)
except s2t.TrashPermissionError:
pass

View file

@ -2,8 +2,10 @@
__author__ = "Gull" __author__ = "Gull"
import os
import time import time
import json import json
import appdirs as ad
KILOBYTES = 1024 KILOBYTES = 1024
@ -20,13 +22,13 @@ def format_bytes(bytes):
if bytes < units["KB"]: if bytes < units["KB"]:
return f"{bytes} B" return f"{bytes} B"
elif units["KB"] <= bytes < units["MB"]: elif units["KB"] <= bytes < units["MB"]:
return f"{(bytes / units["KB"]):.2f} KB" return f"{(bytes / units['KB']):.2f} KB"
elif units["MB"] <= bytes < units["GB"]: elif units["MB"] <= bytes < units["GB"]:
return f"{(bytes / units["MB"]):.2f} MB" return f"{(bytes / units['MB']):.2f} MB"
elif units["GB"] <= bytes < units["TB"]: elif units["GB"] <= bytes < units["TB"]:
return f"{(bytes / units["GB"]):.2f} GB" return f"{(bytes / units['GB']):.2f} GB"
elif units["TB"] <= bytes: elif units["TB"] <= 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.""" """Returns a time string (ISO 8601) from the given integer timestamp."""
@ -42,3 +44,20 @@ def save_json_file(filepath, data, raw=False):
"""Saves a JSON file, given a dict.""" """Saves a JSON file, given a dict."""
with open(filepath, "w", encoding="utf-8") as f: with open(filepath, "w", encoding="utf-8") as f:
json.dump(data, f, sort_keys=True, indent=None if raw else 2) json.dump(data, f, sort_keys=True, indent=None if raw else 2)
def get_user_dir(dir=""):
"""Returns the joined user directory."""
return os.path.join(os.path.expanduser("~"), dir)
def get_appdata_dir(dir=""):
"""Returns the joined appdata directory."""
return os.path.join(ad.user_data_dir(None, False), dir)
def parse_dir(dir=""):
"""Checks if a directory exists, and if it is special."""
if dir[0] == "~": # Denotes the current user directory
dir = get_user_dir(dir.lstrip("~/"))
elif dir[0] == "%": # Denotes the local appdata directory
dir = get_appdata_dir(dir.lstrip("%/"))
return dir