diff --git a/.gitignore b/.gitignore index e6bb150..c0ab7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ venv_linux/ build/ dist/ Finch Filer.spec +test.bat diff --git a/README.md b/README.md index 95d9698..72cea46 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Finch Filer V1.0.0 beta +# Finch Filer V1.0.1 --- ## 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: @@ -14,7 +14,7 @@ Other use cases: - 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 @@ -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 +- Advanced: Create an automated task for scheduled file sorting + ## Usage 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. 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. -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 -thomas.j.wilc@gmail.com +Email: thomas.j.wilc@gmail.com ## Changelog +1.0.1 + +- Improved directory structure + +- Changed scripts available at project root + 1.0.0 - Initial public release of Finch Filer diff --git a/finch_filer_cli.py b/finch_filer_cli.py new file mode 100644 index 0000000..ebf4493 --- /dev/null +++ b/finch_filer_cli.py @@ -0,0 +1,2 @@ +from src.finch_filer import script +script.run() \ No newline at end of file diff --git a/finch_filer_gui.py b/finch_filer_gui.py new file mode 100644 index 0000000..a5c1ba1 --- /dev/null +++ b/finch_filer_gui.py @@ -0,0 +1,2 @@ +from src.finch_filer import app +app.run() \ No newline at end of file diff --git a/requirements.txt b/require_dev.txt similarity index 56% rename from requirements.txt rename to require_dev.txt index c17ecd9..b6ea63f 100644 Binary files a/requirements.txt and b/require_dev.txt differ diff --git a/require_user.txt b/require_user.txt new file mode 100644 index 0000000..55aa8f0 Binary files /dev/null and b/require_user.txt differ diff --git a/src/finch_filer/__init__.py b/src/finch_filer/__init__.py new file mode 100644 index 0000000..06f3aaa --- /dev/null +++ b/src/finch_filer/__init__.py @@ -0,0 +1,2 @@ +__version__ = "1.0.1" +__author__ = "Gull" \ No newline at end of file diff --git a/app.py b/src/finch_filer/app.py similarity index 98% rename from app.py rename to src/finch_filer/app.py index bfe26eb..93910af 100644 --- a/app.py +++ b/src/finch_filer/app.py @@ -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. """ -__name__ = "__main__" -__version__ = "1.0.0" __author__ = "Gull" +import add_root_to_sys_path +add_root_to_sys_path.add_to_sys_path(__file__, "src") + import tkinter as tk -import file_manager as fm 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 messagebox as mb from tkinter import filedialog as fd @@ -41,6 +42,12 @@ def setup(): except ImportError: pass +def run(): + """Sets up and runs the app.""" + setup() + app = App() + app.mainloop() + class App(tk.Tk): """TKinter GUI and related methods.""" def __init__(self, width=640, height=480): @@ -454,6 +461,4 @@ class App(tk.Tk): return frame if __name__ == "__main__": - setup() - app = App() - app.mainloop() \ No newline at end of file + run() \ No newline at end of file diff --git a/file_manager.py b/src/finch_filer/file_manager.py similarity index 90% rename from file_manager.py rename to src/finch_filer/file_manager.py index 0541220..e229497 100644 --- a/file_manager.py +++ b/src/finch_filer/file_manager.py @@ -5,8 +5,8 @@ __author__ = "Gull" import os import shutil import logging as log -import utils as ut import send2trash as s2t +from finch_filer import utils as ut debug_mode = False @@ -156,36 +156,60 @@ def set_debug_mode(value): def move(src, dst): """Moves a file from a source to a destination.""" - src, dst = ut.parse_dir(src), ut.parse_dir(dst) - log.info(f"Moved file: {src} > {dst}") + try: + 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: try: shutil.move(src, dst) except Exception as error: log.error(f"Failed to move file: {src} -> {dst}") print(error) + return src, dst + + log.info(f"Moved file: {src} > {dst}") return src, dst def copy(src, dst): """Copies a file from a source to a destination.""" - src, dst = ut.parse_dir(src), ut.parse_dir(dst) - log.info(f"Copied file: {src} > {dst}") + try: + 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: try: shutil.copy2(src, dst) except Exception as error: log.error(f"Failed to copy file: {src} -> {dst}") print(error) + return src, dst + + log.info(f"Copied file: {src} > {dst}") return src, dst def delete(src): """Moves a file to the OS trash equivalent.""" - src = ut.parse_dir(src) - log.info(f"Deleted file: {src}") + try: + src = ut.parse_dir(src) + except Exception as error: + log.error(f"Invalid source: {src}") + print(error) + return src + if not debug_mode: try: s2t.send2trash(src) except s2t.TrashPermissionError as error: log.error(f"Failed to delete file: {src}") print(error) + return src + + log.info(f"Deleted file: {src}") return src \ No newline at end of file diff --git a/script.py b/src/finch_filer/script.py similarity index 87% rename from script.py rename to src/finch_filer/script.py index c99bfe9..eba0410 100644 --- a/script.py +++ b/src/finch_filer/script.py @@ -2,11 +2,14 @@ __author__ = "Gull" +import add_root_to_sys_path +add_root_to_sys_path.add_to_sys_path(__file__, "src") + import sys import logging as log -import utils as ut -import file_manager as fm 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" SOURCE_PATH = "~/Downloads" @@ -18,7 +21,7 @@ def setup_log(): log.getLogger().addHandler(log.StreamHandler(sys.stdout)) # Console log.info("Script started") -def run(): +def get_parser(): parser = ap(prog="Finch Filer", description="Moves, copies, or deletes files from a source " "directory.", @@ -28,12 +31,14 @@ def run(): parser.add_argument("-r", "--rules", default=RULES_PATH, help="config file (default: Same as app setting)") parser.add_argument("-m", "--mode", default="default", - help="action for all files (move, copy, delete, " - "ignore)") + help="action for all files (move, copy, delete)") parser.add_argument("-d", "--debug", action="store_true", 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) rules = ut.parse_dir(args.rules, True) mode = args.mode @@ -65,4 +70,5 @@ def run(): mgr.run_task() log.info("Script finished") -run() \ No newline at end of file +if __name__ == "__main__": + run() \ No newline at end of file diff --git a/utils.py b/src/finch_filer/utils.py similarity index 93% rename from utils.py rename to src/finch_filer/utils.py index 2a4fbaa..e69a393 100644 --- a/utils.py +++ b/src/finch_filer/utils.py @@ -59,13 +59,13 @@ def parse_dir(path="", ignore_mkdir=False): path = pathlib.Path(path).expanduser() elif path[0] == "%": # Denotes the local appdata directory 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: base_path = sys._MEIPASS # For PyInstaller 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: path = pathlib.Path(path)