Improved directory structure; updated readme

This commit is contained in:
Thomas Wilczynski 2025-11-17 17:58:08 -08:00
commit d99c5d2f23
11 changed files with 111 additions and 31 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ venv_linux/
build/ build/
dist/ dist/
Finch Filer.spec Finch Filer.spec
test.bat

View file

@ -1,10 +1,10 @@
# Finch Filer V1.0.0 beta # Finch Filer V1.0.1
--- ---
## Description ## Description
A simple and humble file sorter app, built with the Python TKinter library. It can automatically move, copy, or delete files from a given directory, based on file types. For example, maybe your Downloads folder is cluttered with a bunch of useless files, but you want to keep the image files by moving them into your Pictures folder. This app can help you move the images and delete the junk, all in one click (after a bit of setup). A simple and humble file sorter app, built with the Python TKinter library. It can automatically move, copy, or delete files from a given directory, based on file types. For example, maybe your Downloads folder is cluttered with a bunch of useless files, but you want to keep all image files by moving them into your Pictures folder. This app can help you move the images and delete the junk, all in one click (after a bit of setup).
Other use cases: Other use cases:
@ -14,7 +14,7 @@ Other use cases:
- Moving only files that contain the name "invoice" - Moving only files that contain the name "invoice"
This is my first real Python project, after many unimportant scripts and experiments. I really needed a project like this in my portfolio, even if it's not truly amazing. This is my first real Python project, after many unimportant scripts and experiments. I really needed a project like this in my portfolio, even if it's not truly amazing. Expect some roughness here and there!
## Features ## Features
@ -24,10 +24,24 @@ This is my first real Python project, after many unimportant scripts and experim
- Save your file rules configuration for future use - Save your file rules configuration for future use
- Advanced: Create an automated task for scheduled file sorting
## Usage ## Usage
You can easily launch the app on Windows by entering into the "Finch Filer.exe" executable. You can easily launch the app on Windows by entering into the "Finch Filer.exe" executable.
Alternatively, open your terminal at the main directory (ensure that Python is installed).
If this is your first time, you may need to install a few dependencies:
`pip install -r require_user.txt`
Now you can launch the app:
`python finch_filer_gui.py`
By default, the first thing you'll see is a list of all files in your Downloads directory. You can switch to a directory of your choice by going to File -> Open Directory. Currently, this app displays only items in the chosen directory and not nested ones. You can inspect this list, as well as sort by date and size by clicking on the column headers. By default, the first thing you'll see is a list of all files in your Downloads directory. You can switch to a directory of your choice by going to File -> Open Directory. Currently, this app displays only items in the chosen directory and not nested ones. You can inspect this list, as well as sort by date and size by clicking on the column headers.
Importantly, you'll want to set up file rules if you want things to happen. Select the Rules tab. You'll see a list of selectable generic file types, and options for each one. This is how you can filter out which file types to keep and which ones to delete, for example. Note that the type All Files overrides all other types, unless its action is set to "Ignore". Importantly, you'll want to set up file rules if you want things to happen. Select the Rules tab. You'll see a list of selectable generic file types, and options for each one. This is how you can filter out which file types to keep and which ones to delete, for example. Note that the type All Files overrides all other types, unless its action is set to "Ignore".
@ -36,18 +50,42 @@ For moving and copying all files of a given type, you can set the action as desi
When you are ready, go back to the Files tab and click on the Start Task button. The sorter will do its job, and a summary will be shown at the end. When you are ready, go back to the Files tab and click on the Start Task button. The sorter will do its job, and a summary will be shown at the end.
For advanced usage, you can use the command line mode by opening your terminal at this directory and entering:
`python script.py`
This guide is incomplete. More info will be added later. For advanced usage, you can use the command line mode. Open your terminal at the main directory, and enter:
`python finch_filer_cli.py`
By default, it will use the saved settings from the app to determine how files should be sorted. There are a few flags if you want other options:
```
-h, --help show this help message and exit
-s, --source SOURCE source directory (default: User Downloads)
-r, --rules RULES config file (default: Same as app setting)
-m, --mode MODE action for all files (move, copy, delete)
-d, --debug enables debug mode and ignores file actions
```
Example:
`python finch_filer_cli.py -s ~/Documents/Temp -m delete`
Hint: You can use `~/` as an alias for your user directory.
You can automate this task by having this command in a batch, shell, or bash script. This is useful if you want to periodically clean up any given directory.
## About ## About
thomas.j.wilc@gmail.com Email: thomas.j.wilc@gmail.com
## Changelog ## Changelog
1.0.1
- Improved directory structure
- Changed scripts available at project root
1.0.0 1.0.0
- Initial public release of Finch Filer - Initial public release of Finch Filer

2
finch_filer_cli.py Normal file
View file

@ -0,0 +1,2 @@
from src.finch_filer import script
script.run()

2
finch_filer_gui.py Normal file
View file

@ -0,0 +1,2 @@
from src.finch_filer import app
app.run()

Binary file not shown.

BIN
require_user.txt Normal file

Binary file not shown.

View file

@ -0,0 +1,2 @@
__version__ = "1.0.1"
__author__ = "Gull"

View file

@ -4,14 +4,15 @@ Frontend GUI that displays a list of files from a folder.
File actions (move, copy, or delete) can be set for generic file types. File actions (move, copy, or delete) can be set for generic file types.
""" """
__name__ = "__main__"
__version__ = "1.0.0"
__author__ = "Gull" __author__ = "Gull"
import add_root_to_sys_path
add_root_to_sys_path.add_to_sys_path(__file__, "src")
import tkinter as tk import tkinter as tk
import file_manager as fm
import logging as log import logging as log
import utils as ut from finch_filer import file_manager as fm
from finch_filer import utils as ut
from tkinter import ttk from tkinter import ttk
from tkinter import messagebox as mb from tkinter import messagebox as mb
from tkinter import filedialog as fd from tkinter import filedialog as fd
@ -41,6 +42,12 @@ def setup():
except ImportError: except ImportError:
pass pass
def run():
"""Sets up and runs the app."""
setup()
app = App()
app.mainloop()
class App(tk.Tk): class App(tk.Tk):
"""TKinter GUI and related methods.""" """TKinter GUI and related methods."""
def __init__(self, width=640, height=480): def __init__(self, width=640, height=480):
@ -454,6 +461,4 @@ class App(tk.Tk):
return frame return frame
if __name__ == "__main__": if __name__ == "__main__":
setup() run()
app = App()
app.mainloop()

View file

@ -5,8 +5,8 @@ __author__ = "Gull"
import os import os
import shutil import shutil
import logging as log import logging as log
import utils as ut
import send2trash as s2t import send2trash as s2t
from finch_filer import utils as ut
debug_mode = False debug_mode = False
@ -156,36 +156,60 @@ def set_debug_mode(value):
def move(src, dst): def move(src, dst):
"""Moves a file from a source to a destination.""" """Moves a file from a source to a destination."""
src, dst = ut.parse_dir(src), ut.parse_dir(dst) try:
log.info(f"Moved file: {src} > {dst}") src, dst = ut.parse_dir(src), ut.parse_dir(dst)
except Exception as error:
log.error(f"Invalid source or destination: {src} -> {dst}")
print(error)
return src, dst
if not debug_mode: if not debug_mode:
try: try:
shutil.move(src, dst) shutil.move(src, dst)
except Exception as error: except Exception as error:
log.error(f"Failed to move file: {src} -> {dst}") log.error(f"Failed to move file: {src} -> {dst}")
print(error) print(error)
return src, dst
log.info(f"Moved file: {src} > {dst}")
return src, dst return src, dst
def copy(src, dst): def copy(src, dst):
"""Copies a file from a source to a destination.""" """Copies a file from a source to a destination."""
src, dst = ut.parse_dir(src), ut.parse_dir(dst) try:
log.info(f"Copied file: {src} > {dst}") src, dst = ut.parse_dir(src), ut.parse_dir(dst)
except Exception as error:
log.error(f"Invalid source or destination: {src} -> {dst}")
print(error)
return src, dst
if not debug_mode: if not debug_mode:
try: try:
shutil.copy2(src, dst) shutil.copy2(src, dst)
except Exception as error: except Exception as error:
log.error(f"Failed to copy file: {src} -> {dst}") log.error(f"Failed to copy file: {src} -> {dst}")
print(error) print(error)
return src, dst
log.info(f"Copied file: {src} > {dst}")
return src, dst return src, dst
def delete(src): def delete(src):
"""Moves a file to the OS trash equivalent.""" """Moves a file to the OS trash equivalent."""
src = ut.parse_dir(src) try:
log.info(f"Deleted file: {src}") src = ut.parse_dir(src)
except Exception as error:
log.error(f"Invalid source: {src}")
print(error)
return src
if not debug_mode: if not debug_mode:
try: try:
s2t.send2trash(src) s2t.send2trash(src)
except s2t.TrashPermissionError as error: except s2t.TrashPermissionError as error:
log.error(f"Failed to delete file: {src}") log.error(f"Failed to delete file: {src}")
print(error) print(error)
return src
log.info(f"Deleted file: {src}")
return src return src

View file

@ -2,11 +2,14 @@
__author__ = "Gull" __author__ = "Gull"
import add_root_to_sys_path
add_root_to_sys_path.add_to_sys_path(__file__, "src")
import sys import sys
import logging as log import logging as log
import utils as ut
import file_manager as fm
from argparse import ArgumentParser as ap from argparse import ArgumentParser as ap
from finch_filer import file_manager as fm
from finch_filer import utils as ut
LOG_PATH = "%/Gullbase/FinchFiler/script.log" LOG_PATH = "%/Gullbase/FinchFiler/script.log"
SOURCE_PATH = "~/Downloads" SOURCE_PATH = "~/Downloads"
@ -18,7 +21,7 @@ def setup_log():
log.getLogger().addHandler(log.StreamHandler(sys.stdout)) # Console log.getLogger().addHandler(log.StreamHandler(sys.stdout)) # Console
log.info("Script started") log.info("Script started")
def run(): def get_parser():
parser = ap(prog="Finch Filer", parser = ap(prog="Finch Filer",
description="Moves, copies, or deletes files from a source " description="Moves, copies, or deletes files from a source "
"directory.", "directory.",
@ -28,12 +31,14 @@ def run():
parser.add_argument("-r", "--rules", default=RULES_PATH, parser.add_argument("-r", "--rules", default=RULES_PATH,
help="config file (default: Same as app setting)") help="config file (default: Same as app setting)")
parser.add_argument("-m", "--mode", default="default", parser.add_argument("-m", "--mode", default="default",
help="action for all files (move, copy, delete, " help="action for all files (move, copy, delete)")
"ignore)")
parser.add_argument("-d", "--debug", action="store_true", parser.add_argument("-d", "--debug", action="store_true",
help="enables debug mode and ignores file actions") help="enables debug mode and ignores file actions")
args = parser.parse_args() return parser
def run():
args = get_parser().parse_args()
source = ut.parse_dir(args.source, True) source = ut.parse_dir(args.source, True)
rules = ut.parse_dir(args.rules, True) rules = ut.parse_dir(args.rules, True)
mode = args.mode mode = args.mode
@ -65,4 +70,5 @@ def run():
mgr.run_task() mgr.run_task()
log.info("Script finished") log.info("Script finished")
run() if __name__ == "__main__":
run()

View file

@ -59,13 +59,13 @@ def parse_dir(path="", ignore_mkdir=False):
path = pathlib.Path(path).expanduser() path = pathlib.Path(path).expanduser()
elif path[0] == "%": # Denotes the local appdata directory elif path[0] == "%": # Denotes the local appdata directory
path = pathlib.Path(ad.user_data_dir(None, False)) / path.lstrip("%/") path = pathlib.Path(ad.user_data_dir(None, False)) / path.lstrip("%/")
elif path[0] == "#": # Denotes the project dev directory elif path[0] == "#": # Denotes the project root directory
try: try:
base_path = sys._MEIPASS # For PyInstaller base_path = sys._MEIPASS # For PyInstaller
except Exception: except Exception:
base_path = os.path.dirname(__file__) base_path = os.path.dirname(os.path.abspath(__file__))
path = pathlib.Path(base_path) / path.lstrip("#/") path = pathlib.Path(base_path).parent.parent / path.lstrip("#/")
else: else:
path = pathlib.Path(path) path = pathlib.Path(path)