Added settings; implemented reading of file modes
This commit is contained in:
parent
f62008028a
commit
3d97c3b698
5 changed files with 178 additions and 38 deletions
14
README.txt
14
README.txt
|
|
@ -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
99
app.py
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
68
data/settings_default.json
Normal file
68
data/settings_default.json
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
3
utils.py
3
utils.py
|
|
@ -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))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue