From d84c76d0d35e78253b692dfabcf9bf2697db91ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Wiesner?= Date: Thu, 5 Mar 2026 11:01:42 +0100 Subject: [PATCH] - ignoring files implementation basics --- sample_settings.json | 18 +++++++ sync_client.py | 111 +++++++++++++++++++++++++++++++++---------- 2 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 sample_settings.json diff --git a/sample_settings.json b/sample_settings.json new file mode 100644 index 0000000..615dd59 --- /dev/null +++ b/sample_settings.json @@ -0,0 +1,18 @@ +{ + "copy-policy": { + "general": "prohibited", + "per-file": { + "allowed": [ + "main.c", + "main.h", + "test1.md" + ], + "prohibited": [ + "echo_path.sh" + ] + } + }, + "ignored-paths": [ + "build/sub/*" + ] +} diff --git a/sync_client.py b/sync_client.py index 80a3736..7a3f408 100644 --- a/sync_client.py +++ b/sync_client.py @@ -1,3 +1,4 @@ +import fnmatch import platform import tkinter as tk from tkinter import ttk @@ -18,12 +19,17 @@ class SyncClient: else: return path.removeprefix("/") + SETTINGS_DIR: str = ".codecast" + MAIN_SETTINGS_FILE: str = SETTINGS_DIR + os.sep + "settings.json" + def start(self) -> None: _sself = self + self.load_settings() + self._event_queue = queue.Queue() - def comm_thread_routine(queue: queue.Queue): + def fs_watch_thread_routine(queue: queue.Queue): run = True while run: event: FileSystemEvent = queue.get() @@ -31,8 +37,14 @@ class SyncClient: if event.src_path == "**CLOSE**" and event.is_synthetic == True: run = False continue + elif event.src_path == SyncClient.MAIN_SETTINGS_FILE or event.dest_path == SyncClient.MAIN_SETTINGS_FILE: + _sself.load_settings() rel_path = str(event.src_path).removeprefix(_sself.dir()).removeprefix(os.sep) + for pattern in self.get_ignored_paths(): + if fnmatch.fnmatch(rel_path, pattern): + continue + if event.event_type == "created" or event.event_type == "modified": _sself.upload(rel_path) elif event.event_type == "moved": @@ -53,8 +65,8 @@ class SyncClient: if event.src_path != _sself.dir(): _sself._event_queue.put(event) - self.__comm_thread = threading.Thread(target=comm_thread_routine, args=(self._event_queue,)) - self.__comm_thread.start() + self.__watch_thread = threading.Thread(target=fs_watch_thread_routine, args=(self._event_queue,)) + self.__watch_thread.start() observer = Observer() event_handler = FileChangeHandler() @@ -69,8 +81,9 @@ class SyncClient: self.__url = url self.__id = id self.__key = key + self.__ignored_paths = set() - self.__comm_thread: threading.Thread + self.__watch_thread: threading.Thread self._event_queue: queue.Queue @@ -90,6 +103,32 @@ class SyncClient: return "file" + def _set_ignored_paths(self, ignored_paths: list | set) -> None: + old_ignored_paths = set(self.__ignored_paths) + new_ignored_paths = set(ignored_paths) + + included = old_ignored_paths - new_ignored_paths + excluded = new_ignored_paths - old_ignored_paths + + self.upload_all(included) + self.delete_all(excluded) + + self.__ignored_paths = new_ignored_paths + + + def get_ignored_paths(self) -> set: + return self.__ignored_paths + + + def load_settings(self) -> None: + try: + with open(self.__dir + os.sep + self.MAIN_SETTINGS_FILE, "r") as sf: + settings = json.loads(sf.read()) + self._set_ignored_paths(settings.get("ignored-paths", [])) + except: + self._set_ignored_paths(set()) + + def upload(self, filename: str) -> bool: print("[UPLOAD]", filename) @@ -164,41 +203,62 @@ class SyncClient: def close(self) -> None: self.__observer.stop() self._event_queue.put(FileSystemEvent("**CLOSE**", is_synthetic=True)) - self.__comm_thread.join() + self.__watch_thread.join() return - def upload_all(self) -> None: + def __list_files(self, types = [ "directory", "file" ], path_match: set | None = None) -> list: + def diriter(path, rel_path = "", types = [ "directory", "file" ], path_match: set | None = None): + listing = [] + for entry in os.scandir(path): + path = (rel_path + os.sep + entry.name).removeprefix(os.sep) + + if path_match is not None: + skip_entry = True + for pattern in path_match: + if fnmatch.fnmatch(path, pattern): + skip_entry = False + break + else: + skip_entry = False + + if not skip_entry: + name = entry.name.removeprefix(os.sep) + if entry.is_dir(): + if "directory" in types: + listing.append({"type": "directory", "path": path, "name": name}) + dir_listing = diriter(entry.path, rel_path=path, types=types, path_match=path_match) + listing.extend(dir_listing) + else: + if "file" in types: + listing.append({"type": "file", + "path": path, + "name": name}) + return listing + + return diriter(self.dir(), types=types, path_match=path_match) + + + def upload_all(self, path_match: set | None = None) -> None: # create directories first - def diriter(path, rel_path = "", types = [ "directory", "file" ]): - listing = [] - for entry in os.scandir(path): - path = (rel_path + os.sep + entry.name).removeprefix(os.sep) - name = entry.name.removeprefix(os.sep) - if entry.is_dir(): - if "directory" in types: - listing.append({"type": "directory", "path": path, "name": name}) - dir_listing = diriter(entry.path, rel_path=path, types=types) - listing.extend(dir_listing) - else: - if "file" in types: - listing.append({"type": "file", - "path": path, - "name": name}) - return listing - - dirs = diriter(self.dir(), types=["directory"]) + dirs = self.__list_files(types=["directory"], path_match=path_match) for dir in dirs: self.upload(dir["path"]) # then upload the files - files = diriter(self.dir(), types=["file"]) + files = self.__list_files(types=["file"], path_match=path_match) for file in files: self.upload(file["path"]) pass + + def delete_all(self, path_match: set | None = None) -> None: + files_and_dirs = self.__list_files(path_match=path_match) + + for entry in files_and_dirs: + self.delete(entry["path"]) # ------------------ @@ -346,7 +406,6 @@ class SyncClientGui: self.__sc = SyncClient(self.__dir, self.__url, self.__id, self.__key) self.__sc.wipe() - self.__sc.upload_all() self.__sc.start()