From 1378ddc5801523753dffe01779e3f2f6562e579a Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 19:13:39 +1000 Subject: [PATCH 01/28] feat: added symlinking --- .env.example | 3 ++- functions/appFunctions.py | 24 +++++++++++++++++++++++- functions/fuseFilesystemFunctions.py | 13 ++++++++++++- library/filesystem.py | 5 ++++- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 2286029..e92c381 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ TORBOX_API_KEY= MOUNT_METHOD=strm -MOUNT_PATH=/torbox \ No newline at end of file +MOUNT_PATH=/torbox +SYMLINK_PATH=/symlinks diff --git a/functions/appFunctions.py b/functions/appFunctions.py index f2d0d2b..8f9dabe 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -1,5 +1,5 @@ from functions.torboxFunctions import getUserDownloads, DownloadType -from library.filesystem import MOUNT_METHOD, MOUNT_PATH +from library.filesystem import MOUNT_METHOD, MOUNT_PATH, SYMLINK_PATH from library.app import MOUNT_REFRESH_TIME from library.torbox import TORBOX_API_KEY from functions.databaseFunctions import getAllData, clearDatabase @@ -16,6 +16,11 @@ def initializeFolders(): os.path.join(MOUNT_PATH, "movies"), os.path.join(MOUNT_PATH, "series"), ] + sym_folders = [ + SYMLINK_PATH, + os.path.join(SYMLINK_PATH, "movies"), + os.path.join(SYMLINK_PATH, "series"), + ] for folder in folders: if os.path.exists(folder): @@ -30,6 +35,19 @@ def initializeFolders(): logging.debug(f"Creating folder {folder}...") os.makedirs(folder, exist_ok=True) + for folder in sym_folders: + if os.path.exists(folder): + logging.debug(f"Folder {folder} already exists. Deleting...") + for item in os.listdir(folder): + item_path = os.path.join(folder, item) + if os.path.isdir(item_path): + shutil.rmtree(item_path) + else: + os.remove(item_path) + else: + logging.debug(f"Creating folder {folder}...") + os.makedirs(folder, exist_ok=True) + def getAllUserDownloadsFresh(): all_downloads = [] @@ -68,6 +86,7 @@ def bootUp(): logging.debug("Booting up...") logging.info("Mount method: %s", MOUNT_METHOD) logging.info("Mount path: %s", MOUNT_PATH) + logging.info("Symlink Path: %s", SYMLINK_PATH) logging.info("TorBox API Key: %s", TORBOX_API_KEY) logging.info("Mount refresh time: %s %s", MOUNT_REFRESH_TIME, "hours") initializeFolders() @@ -80,5 +99,8 @@ def getMountMethod(): def getMountPath(): return MOUNT_PATH +def getSymPath(): + return SYMLINK_PATH + def getMountRefreshTime(): return MOUNT_REFRESH_TIME \ No newline at end of file diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 6a64d9b..afa421e 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -1,5 +1,5 @@ import os -from library.filesystem import MOUNT_PATH +from library.filesystem import MOUNT_PATH, SYMLINK_PATH import stat import errno from functions.torboxFunctions import getDownloadLink, downloadFile @@ -74,9 +74,11 @@ def _build_file_map(self): if f.get('metadata_mediatype') == 'movie': path = f'/movies/{f.get("metadata_rootfoldername")}/{f.get("metadata_filename")}' file_map[path] = f + self.create_symlink_in_symlink_path(path,SYMLINK_PATH) else: # series path = f'/series/{f.get("metadata_rootfoldername")}/{f.get("metadata_foldername")}/{f.get("metadata_filename")}' file_map[path] = f + self.create_symlink_in_symlink_path(path,SYMLINK_PATH) return file_map @@ -92,6 +94,15 @@ def get_file(self, path): def list_dir(self, path): return self.structure.get(path, []) + + def create_symlink_in_symlink_path(self, vfs_path, symlink_path): + # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) + # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) + if os.path.exists(symlink_path) or os.path.islink(symlink_path): + os.remove(symlink_path) + os.symlink(vfs_path, symlink_path) + + class FuseStat(fuse.Stat): def __init__(self): self.st_mode = 0 diff --git a/library/filesystem.py b/library/filesystem.py index cfb8f6d..cd368e1 100644 --- a/library/filesystem.py +++ b/library/filesystem.py @@ -12,4 +12,7 @@ class MountMethods(Enum): assert MOUNT_METHOD in [method.value for method in MountMethods], "MOUNT_METHOD is not set correctly in .env file" MOUNT_PATH = os.getenv("MOUNT_PATH", "./torbox") -assert MOUNT_PATH, "MOUNT_PATH is not set in .env file" \ No newline at end of file +assert MOUNT_PATH, "MOUNT_PATH is not set in .env file" + +SYMLINK_PATH = os.getenv("SYMLINK_PATH", "./symlinks") +assert SYMLINK_PATH, "SYMLINK_PATH is not set in .env file" \ No newline at end of file From 0291ac4fe716dff239facf40c94d3c497e825c12 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 19:18:15 +1000 Subject: [PATCH 02/28] fix: folder creation --- functions/appFunctions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index 8f9dabe..c793af5 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -15,8 +15,6 @@ def initializeFolders(): MOUNT_PATH, os.path.join(MOUNT_PATH, "movies"), os.path.join(MOUNT_PATH, "series"), - ] - sym_folders = [ SYMLINK_PATH, os.path.join(SYMLINK_PATH, "movies"), os.path.join(SYMLINK_PATH, "series"), From bdbe6efc06a7dabceb180e6c341adef04319eae4 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 14 Jul 2025 20:14:30 +1000 Subject: [PATCH 03/28] fix: symlink --- functions/appFunctions.py | 13 ------------- functions/fuseFilesystemFunctions.py | 10 ++-------- functions/torboxFunctions.py | 23 ++++++++++++++++++++++- main.py | 2 +- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index c793af5..f2dcb40 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -33,19 +33,6 @@ def initializeFolders(): logging.debug(f"Creating folder {folder}...") os.makedirs(folder, exist_ok=True) - for folder in sym_folders: - if os.path.exists(folder): - logging.debug(f"Folder {folder} already exists. Deleting...") - for item in os.listdir(folder): - item_path = os.path.join(folder, item) - if os.path.isdir(item_path): - shutil.rmtree(item_path) - else: - os.remove(item_path) - else: - logging.debug(f"Creating folder {folder}...") - os.makedirs(folder, exist_ok=True) - def getAllUserDownloadsFresh(): all_downloads = [] diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index afa421e..a59dd63 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -74,11 +74,11 @@ def _build_file_map(self): if f.get('metadata_mediatype') == 'movie': path = f'/movies/{f.get("metadata_rootfoldername")}/{f.get("metadata_filename")}' file_map[path] = f - self.create_symlink_in_symlink_path(path,SYMLINK_PATH) + # self.create_symlink_in_symlink_path(path,SYMLINK_PATH) else: # series path = f'/series/{f.get("metadata_rootfoldername")}/{f.get("metadata_foldername")}/{f.get("metadata_filename")}' file_map[path] = f - self.create_symlink_in_symlink_path(path,SYMLINK_PATH) + # self.create_symlink_in_symlink_path(path,SYMLINK_PATH) return file_map @@ -95,12 +95,6 @@ def list_dir(self, path): return self.structure.get(path, []) - def create_symlink_in_symlink_path(self, vfs_path, symlink_path): - # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) - # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) - if os.path.exists(symlink_path) or os.path.islink(symlink_path): - os.remove(symlink_path) - os.symlink(vfs_path, symlink_path) class FuseStat(fuse.Stat): diff --git a/functions/torboxFunctions.py b/functions/torboxFunctions.py index bb5bcd8..7995a31 100644 --- a/functions/torboxFunctions.py +++ b/functions/torboxFunctions.py @@ -3,6 +3,7 @@ from enum import Enum import PTN from library.torbox import TORBOX_API_KEY +from library.filesystem import MOUNT_PATH, SYMLINK_PATH from functions.mediaFunctions import constructSeriesTitle, cleanTitle, cleanYear from functions.databaseFunctions import insertData import os @@ -52,7 +53,7 @@ def process_file(item, file, type): metadata, _, _ = searchMetadata(title_data.get("title", file.get("short_name")), title_data, file.get("short_name"), f"{item.get('name')} {file.get('short_name')}") data.update(metadata) - logging.debug(data) + logging.debug(f"Processing data {data}") insertData(data, type.value) return data @@ -114,8 +115,17 @@ def getUserDownloads(type: DownloadType): for future in as_completed(future_to_file): try: data = future.result() + logging.debug(f"Future result data: {data}") if data: files.append(data) + if data.get('metadata_mediatype') == 'movie': + path_tail = f"movie/{data.get('metadata_rootfoldername')}/{data.get('metadata_filename')}" + else: + path_tail = f"series/{data.get('metadata_rootfoldername')}/{data.get('metadata_foldername')}/{data.get('metadata_filename')}" + + v_path = f"{MOUNT_PATH}/{path_tail}" + s_path = f"{SYMLINK_PATH}/{path_tail}" + create_symlink_in_symlink_path(v_path, s_path) except Exception as e: item, file = future_to_file[future] logging.error(f"Error processing file {file.get('short_name', 'unknown')}: {e}") @@ -200,3 +210,14 @@ def downloadFile(url: str, size: int, offset: int = 0): logging.error(f"Error downloading file: {response.status_code}") raise Exception(f"Error downloading file: {response.status_code}") + + +def create_symlink_in_symlink_path(vfs_path, symlink_path): + # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) + # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) + try: + if os.path.exists(symlink_path) or os.path.islink(symlink_path): + os.remove(symlink_path) + os.symlink(vfs_path, symlink_path) + except Exception as e: + logging.error(f"Error creating symlink: {e}") diff --git a/main.py b/main.py index 6555858..eb7202d 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from sys import platform logging.basicConfig( - level=logging.INFO, + level=logging.DEBUG, format='%(asctime)s,%(msecs)03d %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) From 2df4ef30d41e5e44485df0e562209bd940ea875e Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:25:36 +1000 Subject: [PATCH 04/28] fix: retry --- functions/fuseFilesystemFunctions.py | 2 +- functions/torboxFunctions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index a59dd63..9f19cdd 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -132,7 +132,7 @@ def getFiles(self): if files: self.files = files self.vfs = VirtualFileSystem(self.files) - logging.debug(f"Updated {len(self.files)} files in VFS") + logging.debug(f"Updated {len(self.files)} files in VFS\n{self.files}") time.sleep(300) def getattr(self, path): diff --git a/functions/torboxFunctions.py b/functions/torboxFunctions.py index 7995a31..46986bc 100644 --- a/functions/torboxFunctions.py +++ b/functions/torboxFunctions.py @@ -119,7 +119,7 @@ def getUserDownloads(type: DownloadType): if data: files.append(data) if data.get('metadata_mediatype') == 'movie': - path_tail = f"movie/{data.get('metadata_rootfoldername')}/{data.get('metadata_filename')}" + path_tail = f"movies/{data.get('metadata_rootfoldername')}/{data.get('metadata_filename')}" else: path_tail = f"series/{data.get('metadata_rootfoldername')}/{data.get('metadata_foldername')}/{data.get('metadata_filename')}" From 06dd307a1bbc89f802137479d73fafc690335a57 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:30:12 +1000 Subject: [PATCH 05/28] fix: relocate symlink method --- functions/fuseFilesystemFunctions.py | 25 +++++++++++++++++++++++-- functions/torboxFunctions.py | 8 -------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 9f19cdd..58ed606 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -132,7 +132,17 @@ def getFiles(self): if files: self.files = files self.vfs = VirtualFileSystem(self.files) - logging.debug(f"Updated {len(self.files)} files in VFS\n{self.files}") + logging.debug(f"Updated {len(self.files)} files in VFS") + for file_item in files: + if file_item.get('metadata_mediatype') == 'movie': + path_tail = f"movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" + else: + path_tail = f"series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" + v_path = f"{MOUNT_PATH}/{path_tail}" + s_path = f"{SYMLINK_PATH}/{path_tail}" + logging.debug(f"Attempting to symlink {v_path} to {s_path}") + create_symlink_in_symlink_path(v_path, s_path) + time.sleep(300) def getattr(self, path): @@ -260,4 +270,15 @@ def unmountFuse(): except OSError as e: logging.error(f"Error unmounting: {e}") sys.exit(1) - logging.info("Unmounted successfully.") \ No newline at end of file + logging.info("Unmounted successfully.") + + +def create_symlink_in_symlink_path(vfs_path, symlink_path): + # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) + # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) + try: + if os.path.exists(symlink_path) or os.path.islink(symlink_path): + os.remove(symlink_path) + os.symlink(vfs_path, symlink_path) + except Exception as e: + logging.error(f"Error creating symlink: {e}") diff --git a/functions/torboxFunctions.py b/functions/torboxFunctions.py index 46986bc..45ec799 100644 --- a/functions/torboxFunctions.py +++ b/functions/torboxFunctions.py @@ -118,14 +118,6 @@ def getUserDownloads(type: DownloadType): logging.debug(f"Future result data: {data}") if data: files.append(data) - if data.get('metadata_mediatype') == 'movie': - path_tail = f"movies/{data.get('metadata_rootfoldername')}/{data.get('metadata_filename')}" - else: - path_tail = f"series/{data.get('metadata_rootfoldername')}/{data.get('metadata_foldername')}/{data.get('metadata_filename')}" - - v_path = f"{MOUNT_PATH}/{path_tail}" - s_path = f"{SYMLINK_PATH}/{path_tail}" - create_symlink_in_symlink_path(v_path, s_path) except Exception as e: item, file = future_to_file[future] logging.error(f"Error processing file {file.get('short_name', 'unknown')}: {e}") From b77f4d9b3d2a154c1569db976f98b7ec0ca6a61e Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:41:00 +1000 Subject: [PATCH 06/28] fix: symlink paths missing --- functions/fuseFilesystemFunctions.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 58ed606..16a5bfc 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -277,8 +277,20 @@ def create_symlink_in_symlink_path(vfs_path, symlink_path): # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) try: + path_split = str(symlink_path).split('/') + path_split = path_split[:-1] + path_split = [p for p in path_split if p] + path_joined = '' + for folder in path_split: + path_joined = f'{path_joined}/{folder}' + if os.path.exists(path_joined) == False: + logging.debug(f"Creating folder {path_joined}...") + os.makedirs(path_joined, exist_ok=True) + if os.path.exists(symlink_path) or os.path.islink(symlink_path): + logging.debug(f"Removing existing symlink {symlink_path}") os.remove(symlink_path) os.symlink(vfs_path, symlink_path) + logging.debug(f"Symlinked {vfs_path} -> {symlink_path}") except Exception as e: logging.error(f"Error creating symlink: {e}") From cd47f7a64d28e9854b6295a9b663ccb6992d5788 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:47:26 +1000 Subject: [PATCH 07/28] fix: allow no symlink in env --- functions/fuseFilesystemFunctions.py | 22 ++++++++++------------ library/filesystem.py | 3 +-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 16a5bfc..c18cc84 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -74,11 +74,9 @@ def _build_file_map(self): if f.get('metadata_mediatype') == 'movie': path = f'/movies/{f.get("metadata_rootfoldername")}/{f.get("metadata_filename")}' file_map[path] = f - # self.create_symlink_in_symlink_path(path,SYMLINK_PATH) else: # series path = f'/series/{f.get("metadata_rootfoldername")}/{f.get("metadata_foldername")}/{f.get("metadata_filename")}' file_map[path] = f - # self.create_symlink_in_symlink_path(path,SYMLINK_PATH) return file_map @@ -133,16 +131,16 @@ def getFiles(self): self.files = files self.vfs = VirtualFileSystem(self.files) logging.debug(f"Updated {len(self.files)} files in VFS") - for file_item in files: - if file_item.get('metadata_mediatype') == 'movie': - path_tail = f"movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" - else: - path_tail = f"series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" - v_path = f"{MOUNT_PATH}/{path_tail}" - s_path = f"{SYMLINK_PATH}/{path_tail}" - logging.debug(f"Attempting to symlink {v_path} to {s_path}") - create_symlink_in_symlink_path(v_path, s_path) - + if SYMLINK_PATH: + for file_item in files: + if file_item.get('metadata_mediatype') == 'movie': + path_tail = f"movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" + else: + path_tail = f"series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" + v_path = f"{MOUNT_PATH}/{path_tail}" + s_path = f"{SYMLINK_PATH}/{path_tail}" + logging.debug(f"Attempting to symlink {v_path} to {s_path}") + create_symlink_in_symlink_path(v_path, s_path) time.sleep(300) def getattr(self, path): diff --git a/library/filesystem.py b/library/filesystem.py index cd368e1..7de6bd7 100644 --- a/library/filesystem.py +++ b/library/filesystem.py @@ -14,5 +14,4 @@ class MountMethods(Enum): MOUNT_PATH = os.getenv("MOUNT_PATH", "./torbox") assert MOUNT_PATH, "MOUNT_PATH is not set in .env file" -SYMLINK_PATH = os.getenv("SYMLINK_PATH", "./symlinks") -assert SYMLINK_PATH, "SYMLINK_PATH is not set in .env file" \ No newline at end of file +SYMLINK_PATH = os.getenv("SYMLINK_PATH", None) From 4771471ed68d63d8ecf0ea040e542769f2ce8e3c Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:52:38 +1000 Subject: [PATCH 08/28] docs: info logging --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index eb7202d..6555858 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ from sys import platform logging.basicConfig( - level=logging.DEBUG, + level=logging.INFO, format='%(asctime)s,%(msecs)03d %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) From f8441716ed9be0c0aeb58a1804bf01832b16e543 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 20:55:27 +1000 Subject: [PATCH 09/28] fix: remove redundant symlink method --- functions/torboxFunctions.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/functions/torboxFunctions.py b/functions/torboxFunctions.py index 45ec799..8318ce1 100644 --- a/functions/torboxFunctions.py +++ b/functions/torboxFunctions.py @@ -202,14 +202,3 @@ def downloadFile(url: str, size: int, offset: int = 0): logging.error(f"Error downloading file: {response.status_code}") raise Exception(f"Error downloading file: {response.status_code}") - - -def create_symlink_in_symlink_path(vfs_path, symlink_path): - # vfs_path: the path inside the FUSE mount (e.g., /mnt/torbox_media/movies/Foo (2024)/Foo (2024).mkv) - # symlink_path: the desired symlink location (e.g., /home/youruser/symlinks/Foo (2024).mkv) - try: - if os.path.exists(symlink_path) or os.path.islink(symlink_path): - os.remove(symlink_path) - os.symlink(vfs_path, symlink_path) - except Exception as e: - logging.error(f"Error creating symlink: {e}") From 2db6351757a5652d1fa12abd8c792e423abe24ae Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 21:12:26 +1000 Subject: [PATCH 10/28] fix: symlinking folders --- functions/appFunctions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index f2dcb40..d1daee0 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -15,10 +15,13 @@ def initializeFolders(): MOUNT_PATH, os.path.join(MOUNT_PATH, "movies"), os.path.join(MOUNT_PATH, "series"), - SYMLINK_PATH, - os.path.join(SYMLINK_PATH, "movies"), - os.path.join(SYMLINK_PATH, "series"), ] + if SYMLINK_PATH: + folders.extend = [ + SYMLINK_PATH, + os.path.join(SYMLINK_PATH, "movies"), + os.path.join(SYMLINK_PATH, "series"), + ] for folder in folders: if os.path.exists(folder): From 0d7a583f61cf00142bfe1614b06a6df8f75a6272 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 14 Jul 2025 21:18:59 +1000 Subject: [PATCH 11/28] fix: folders extend code error --- functions/appFunctions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index d1daee0..7a60fc1 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -17,11 +17,11 @@ def initializeFolders(): os.path.join(MOUNT_PATH, "series"), ] if SYMLINK_PATH: - folders.extend = [ + folders.extend([ SYMLINK_PATH, os.path.join(SYMLINK_PATH, "movies"), os.path.join(SYMLINK_PATH, "series"), - ] + ]) for folder in folders: if os.path.exists(folder): From 002961ae536678d7bd8ac414a9755f197c1e2588 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 15 Jul 2025 21:21:56 +1000 Subject: [PATCH 12/28] fix: separate symlink folder creation --- functions/appFunctions.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index 7a60fc1..3e69b1c 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -17,11 +17,11 @@ def initializeFolders(): os.path.join(MOUNT_PATH, "series"), ] if SYMLINK_PATH: - folders.extend([ + symfolders = [ SYMLINK_PATH, os.path.join(SYMLINK_PATH, "movies"), os.path.join(SYMLINK_PATH, "series"), - ]) + ] for folder in folders: if os.path.exists(folder): @@ -36,6 +36,13 @@ def initializeFolders(): logging.debug(f"Creating folder {folder}...") os.makedirs(folder, exist_ok=True) + for folder in symfolders: + if os.path.exists(folder): + logging.debug(f"Folder {folder} already exists...") + else: + logging.debug(f"Creating folder {folder}...") + os.makedirs(folder, exist_ok=True) + def getAllUserDownloadsFresh(): all_downloads = [] From d34f3570029ec0cbd3912027f8a9ae4a809a52dc Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 09:25:28 +1000 Subject: [PATCH 13/28] feat: symlink creation --- .env.example | 1 + README.md | 5 +++++ functions/databaseFunctions.py | 2 +- functions/fuseFilesystemFunctions.py | 20 +++++++++++++++++--- library/filesystem.py | 7 +++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index e92c381..d0da4f6 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,4 @@ TORBOX_API_KEY= MOUNT_METHOD=strm MOUNT_PATH=/torbox SYMLINK_PATH=/symlinks +SYMLINK_CREATION=always diff --git a/README.md b/README.md index bd1bbea..d7ea91f 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,11 @@ To run this project you will need to add the following environment variables to `MOUNT_REFRESH_TIME` How fast you would like your mount to look for new files. Must be either `slow` for every 3 hours, `normal` for every 2 hours, `fast` for every 1 hour, or `instant` for every 6 minutes. The default is `fast` and is optional. +`SYMLINK_PATH` The path where symlinks to your files should be created if using `MOUNT_METHOD` of `fuse`. If inside of Docker, this path needs to be accessible to other applications. If running locally without Docker, this path must be owned. Setting is optional, omit to skip symlink creation. + +`SYMLINK_CREATION` When the symlinks should be created. Must be either `once` or `always`. `always` will create them each time the mount is refreshed, `once` will create them only the first time the file is created in the mount path after app startup. The default is `always` and is optional. + + ## 🐳 Running on Docker with one command (recommended) We provide bash scripts for running the TorBox Media Center easily by simply copying the script to your server or computer, and running it, following the prompts. This can be helpful if you aren't familiar with Docker, permissions or servers in general. Simply choose one in [this folder](https://github.com/TorBox-App/torbox-media-center/blob/main/scripts) that pertains to your system and run it in the terminal. diff --git a/functions/databaseFunctions.py b/functions/databaseFunctions.py index 186777e..c084506 100644 --- a/functions/databaseFunctions.py +++ b/functions/databaseFunctions.py @@ -1,4 +1,4 @@ -from tinydb import TinyDB +from tinydb import TinyDB, Query import threading import logging diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index c18cc84..5aad70b 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -1,5 +1,5 @@ import os -from library.filesystem import MOUNT_PATH, SYMLINK_PATH +from library.filesystem import MOUNT_PATH, SYMLINK_PATH, SYMLINK_CREATION import stat import errno from functions.torboxFunctions import getDownloadLink, downloadFile @@ -7,6 +7,7 @@ import sys import logging from functions.appFunctions import getAllUserDownloads +from functions.databaseFunctions import insertData, getAllData import threading from sys import platform @@ -132,15 +133,28 @@ def getFiles(self): self.vfs = VirtualFileSystem(self.files) logging.debug(f"Updated {len(self.files)} files in VFS") if SYMLINK_PATH: + try: + get_symlink_data = getAllData('symlinks') + except: + get_symlink_data = [] + for file_item in files: + symlink_record = file_item if file_item.get('metadata_mediatype') == 'movie': path_tail = f"movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" else: path_tail = f"series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" v_path = f"{MOUNT_PATH}/{path_tail}" s_path = f"{SYMLINK_PATH}/{path_tail}" - logging.debug(f"Attempting to symlink {v_path} to {s_path}") - create_symlink_in_symlink_path(v_path, s_path) + symlink_record['real_path'] = v_path + symlink_record['symlink_path'] = s_path + if symlink_record not in get_symlink_data or SYMLINK_CREATION == 'always': + logging.debug(f"Attempting to symlink {v_path} to {s_path}") + create_symlink_in_symlink_path(v_path, s_path) + insertData(symlink_record,'symlinks') + else: + logging.debug(f"Symlink created previously and creation set to 'once'. Skipping") + time.sleep(300) def getattr(self, path): diff --git a/library/filesystem.py b/library/filesystem.py index 7de6bd7..6219079 100644 --- a/library/filesystem.py +++ b/library/filesystem.py @@ -8,6 +8,10 @@ class MountMethods(Enum): strm = "strm" fuse = "fuse" +class SymlinkCreation(Enum): + once = "once" + always = "always" + MOUNT_METHOD = os.getenv("MOUNT_METHOD", MountMethods.strm.value) assert MOUNT_METHOD in [method.value for method in MountMethods], "MOUNT_METHOD is not set correctly in .env file" @@ -15,3 +19,6 @@ class MountMethods(Enum): assert MOUNT_PATH, "MOUNT_PATH is not set in .env file" SYMLINK_PATH = os.getenv("SYMLINK_PATH", None) + +SYMLINK_CREATION = os.getenv("SYMLINK_CREATION", "always") +assert SYMLINK_CREATION in [symlink.value for symlink in SymlinkCreation], "SYMLINK_CREATION is not set correctly in .env file" From e89cbadb97475d9dc6c4294dc182591b929250de Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 09:29:01 +1000 Subject: [PATCH 14/28] logging --- functions/appFunctions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index 3e69b1c..db42c24 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -1,5 +1,5 @@ from functions.torboxFunctions import getUserDownloads, DownloadType -from library.filesystem import MOUNT_METHOD, MOUNT_PATH, SYMLINK_PATH +from library.filesystem import MOUNT_METHOD, MOUNT_PATH, SYMLINK_PATH, SYMLINK_CREATION from library.app import MOUNT_REFRESH_TIME from library.torbox import TORBOX_API_KEY from functions.databaseFunctions import getAllData, clearDatabase @@ -82,6 +82,7 @@ def bootUp(): logging.info("Mount method: %s", MOUNT_METHOD) logging.info("Mount path: %s", MOUNT_PATH) logging.info("Symlink Path: %s", SYMLINK_PATH) + logging.info("Symlink Method: %s", SYMLINK_CREATION) logging.info("TorBox API Key: %s", TORBOX_API_KEY) logging.info("Mount refresh time: %s %s", MOUNT_REFRESH_TIME, "hours") initializeFolders() @@ -97,5 +98,8 @@ def getMountPath(): def getSymPath(): return SYMLINK_PATH +def getSymCreation(): + return SYMLINK_CREATION + def getMountRefreshTime(): return MOUNT_REFRESH_TIME \ No newline at end of file From adeb12f27be1feabbd7e7b38b959a28c910866a4 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 09:44:54 +1000 Subject: [PATCH 15/28] fix: list generator --- functions/fuseFilesystemFunctions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 5aad70b..9540fd5 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -148,7 +148,8 @@ def getFiles(self): s_path = f"{SYMLINK_PATH}/{path_tail}" symlink_record['real_path'] = v_path symlink_record['symlink_path'] = s_path - if symlink_record not in get_symlink_data or SYMLINK_CREATION == 'always': + exists = any(d.get("symlink_path") == s_path for d in get_symlink_data) + if exists or SYMLINK_CREATION == 'always': logging.debug(f"Attempting to symlink {v_path} to {s_path}") create_symlink_in_symlink_path(v_path, s_path) insertData(symlink_record,'symlinks') From ee0e110be504fe7d67952133dc183d51d44378b4 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 09:57:30 +1000 Subject: [PATCH 16/28] fix: symlink spawn --- README.md | 2 +- functions/appFunctions.py | 7 ++++++- functions/fuseFilesystemFunctions.py | 2 +- library/filesystem.py | 1 + main.py | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d7ea91f..976be79 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ To run this project you will need to add the following environment variables to `SYMLINK_PATH` The path where symlinks to your files should be created if using `MOUNT_METHOD` of `fuse`. If inside of Docker, this path needs to be accessible to other applications. If running locally without Docker, this path must be owned. Setting is optional, omit to skip symlink creation. -`SYMLINK_CREATION` When the symlinks should be created. Must be either `once` or `always`. `always` will create them each time the mount is refreshed, `once` will create them only the first time the file is created in the mount path after app startup. The default is `always` and is optional. +`SYMLINK_CREATION` When the symlinks should be created. Must be either `once`, `spawn` or `always`. `always` will create them each time the mount is refreshed, `spawn` will create them once per session or the first time the file is created in the mount path after the app starts, `once` will create them one-time only. The default is `always` and is optional. ## 🐳 Running on Docker with one command (recommended) diff --git a/functions/appFunctions.py b/functions/appFunctions.py index db42c24..a49fda3 100644 --- a/functions/appFunctions.py +++ b/functions/appFunctions.py @@ -82,9 +82,14 @@ def bootUp(): logging.info("Mount method: %s", MOUNT_METHOD) logging.info("Mount path: %s", MOUNT_PATH) logging.info("Symlink Path: %s", SYMLINK_PATH) - logging.info("Symlink Method: %s", SYMLINK_CREATION) + logging.info("Symlink Creation Method: %s", SYMLINK_CREATION) logging.info("TorBox API Key: %s", TORBOX_API_KEY) logging.info("Mount refresh time: %s %s", MOUNT_REFRESH_TIME, "hours") + if SYMLINK_CREATION != 'once': + try: + os.remove(f"{os.curdir}/symlinks.json") + except Exception as e: + logging.debug("symlinks database not yet created") initializeFolders() return True diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 9540fd5..c5af6e2 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -149,7 +149,7 @@ def getFiles(self): symlink_record['real_path'] = v_path symlink_record['symlink_path'] = s_path exists = any(d.get("symlink_path") == s_path for d in get_symlink_data) - if exists or SYMLINK_CREATION == 'always': + if not exists or SYMLINK_CREATION == 'always': logging.debug(f"Attempting to symlink {v_path} to {s_path}") create_symlink_in_symlink_path(v_path, s_path) insertData(symlink_record,'symlinks') diff --git a/library/filesystem.py b/library/filesystem.py index 6219079..ca5ee06 100644 --- a/library/filesystem.py +++ b/library/filesystem.py @@ -10,6 +10,7 @@ class MountMethods(Enum): class SymlinkCreation(Enum): once = "once" + spawn = "spawn" always = "always" MOUNT_METHOD = os.getenv("MOUNT_METHOD", MountMethods.strm.value) diff --git a/main.py b/main.py index 6555858..c6fa88c 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ from functions.databaseFunctions import closeAllDatabases import logging from sys import platform +import os logging.basicConfig( level=logging.INFO, From 4d2e14a07b82bbe955bf419bdd8cf10fc8676892 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:00:36 +1000 Subject: [PATCH 17/28] fix: symlink db --- functions/fuseFilesystemFunctions.py | 2 +- main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index c5af6e2..628c6c6 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -148,7 +148,7 @@ def getFiles(self): s_path = f"{SYMLINK_PATH}/{path_tail}" symlink_record['real_path'] = v_path symlink_record['symlink_path'] = s_path - exists = any(d.get("symlink_path") == s_path for d in get_symlink_data) + exists = any(d.get("symlink_path",None) == s_path for d in get_symlink_data) if not exists or SYMLINK_CREATION == 'always': logging.debug(f"Attempting to symlink {v_path} to {s_path}") create_symlink_in_symlink_path(v_path, s_path) diff --git a/main.py b/main.py index c6fa88c..88c9608 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ import os logging.basicConfig( - level=logging.INFO, + level=logging.DEBUG, format='%(asctime)s,%(msecs)03d %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) From 4e0f528de6e35bbb30b425c6fca9a974571554c3 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:04:23 +1000 Subject: [PATCH 18/28] fix: exists --- functions/fuseFilesystemFunctions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 628c6c6..98eaa3c 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -148,7 +148,10 @@ def getFiles(self): s_path = f"{SYMLINK_PATH}/{path_tail}" symlink_record['real_path'] = v_path symlink_record['symlink_path'] = s_path - exists = any(d.get("symlink_path",None) == s_path for d in get_symlink_data) + if isinstance(get_symlink_data,list) and len(get_symlink_data) > 0: + exists = any(d.get("symlink_path",None) == s_path for d in get_symlink_data) + else: + exists = False if not exists or SYMLINK_CREATION == 'always': logging.debug(f"Attempting to symlink {v_path} to {s_path}") create_symlink_in_symlink_path(v_path, s_path) From 7ee16f3c1331fafc53f289702c2e4d270f6e7012 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:05:19 +1000 Subject: [PATCH 19/28] fix: exists == False --- functions/fuseFilesystemFunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 98eaa3c..150537e 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -152,7 +152,7 @@ def getFiles(self): exists = any(d.get("symlink_path",None) == s_path for d in get_symlink_data) else: exists = False - if not exists or SYMLINK_CREATION == 'always': + if exists == False or SYMLINK_CREATION == 'always': logging.debug(f"Attempting to symlink {v_path} to {s_path}") create_symlink_in_symlink_path(v_path, s_path) insertData(symlink_record,'symlinks') From 4fbceb161294105a62d26e4904a4a7eca0ba9fe4 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:07:16 +1000 Subject: [PATCH 20/28] fix: debugs --- functions/fuseFilesystemFunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 150537e..5ea1d88 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -137,7 +137,7 @@ def getFiles(self): get_symlink_data = getAllData('symlinks') except: get_symlink_data = [] - + logging.debug(f"Symlink db:\n{get_symlink_data}") for file_item in files: symlink_record = file_item if file_item.get('metadata_mediatype') == 'movie': From ea257092cda52f0312d25cc3e09b1d5f2fe904e7 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:09:18 +1000 Subject: [PATCH 21/28] fix: symlink db --- functions/fuseFilesystemFunctions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 5ea1d88..6619b15 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -134,10 +134,10 @@ def getFiles(self): logging.debug(f"Updated {len(self.files)} files in VFS") if SYMLINK_PATH: try: - get_symlink_data = getAllData('symlinks') + get_symlink_data = getAllData('symlinks')[0] except: get_symlink_data = [] - logging.debug(f"Symlink db:\n{get_symlink_data}") + # logging.debug(f"Symlink db:\n{get_symlink_data}") for file_item in files: symlink_record = file_item if file_item.get('metadata_mediatype') == 'movie': From 3c99103c084709d73ba84cb8c62861d6102f6e97 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:16:13 +1000 Subject: [PATCH 22/28] fix: s_path --- functions/fuseFilesystemFunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 6619b15..d1e8f28 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -157,7 +157,7 @@ def getFiles(self): create_symlink_in_symlink_path(v_path, s_path) insertData(symlink_record,'symlinks') else: - logging.debug(f"Symlink created previously and creation set to 'once'. Skipping") + logging.debug(f"Symlink {s_path} created previously and creation set to 'once'. Skipping") time.sleep(300) From b581148491fbd5faaff9e7ae9be873f3d3fbdb86 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 10:23:01 +1000 Subject: [PATCH 23/28] fix: debug mode --- .env.example | 1 + functions/fuseFilesystemFunctions.py | 2 +- library/app.py | 4 +++- main.py | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index d0da4f6..18bad5b 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,4 @@ MOUNT_METHOD=strm MOUNT_PATH=/torbox SYMLINK_PATH=/symlinks SYMLINK_CREATION=always +DEBUG_MODE=false diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index d1e8f28..5ed4fe9 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -157,7 +157,7 @@ def getFiles(self): create_symlink_in_symlink_path(v_path, s_path) insertData(symlink_record,'symlinks') else: - logging.debug(f"Symlink {s_path} created previously and creation set to 'once'. Skipping") + logging.debug(f"Symlink {s_path} created previously and creation set to '{SYMLINK_CREATION}'. Skipping") time.sleep(300) diff --git a/library/app.py b/library/app.py index e36416c..6c29c5c 100644 --- a/library/app.py +++ b/library/app.py @@ -15,4 +15,6 @@ class MountRefreshTimes(Enum): MOUNT_REFRESH_TIME = MOUNT_REFRESH_TIME.lower() assert MOUNT_REFRESH_TIME in [e.name for e in MountRefreshTimes], f"Invalid mount refresh time: {MOUNT_REFRESH_TIME}. Valid options are: {[e.name for e in MountRefreshTimes]}" -MOUNT_REFRESH_TIME = MountRefreshTimes[MOUNT_REFRESH_TIME].value \ No newline at end of file +MOUNT_REFRESH_TIME = MountRefreshTimes[MOUNT_REFRESH_TIME].value + +DEBUG_MODE = os.getenv("DEBUG_MODE", False) in [True,'true'] diff --git a/main.py b/main.py index 88c9608..27bb3a3 100644 --- a/main.py +++ b/main.py @@ -2,12 +2,13 @@ from apscheduler.schedulers.background import BackgroundScheduler from functions.appFunctions import bootUp, getMountMethod, getAllUserDownloadsFresh, getMountRefreshTime from functions.databaseFunctions import closeAllDatabases +from library.app import DEBUG_MODE import logging from sys import platform import os logging.basicConfig( - level=logging.DEBUG, + level=logging.DEBUG if DEBUG_MODE == True else logging.INFO, format='%(asctime)s,%(msecs)03d %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) From 8d30c2858afb9cd146491f8d3e02382ed3c4dfca Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 11:49:26 +1000 Subject: [PATCH 24/28] fix: remove dead symlinks --- functions/databaseFunctions.py | 19 +++++++++++++++++++ functions/fuseFilesystemFunctions.py | 24 +++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/functions/databaseFunctions.py b/functions/databaseFunctions.py index c084506..4107be2 100644 --- a/functions/databaseFunctions.py +++ b/functions/databaseFunctions.py @@ -67,6 +67,25 @@ def insertData(data: dict, type: str): except Exception as e: return False, f"Error inserting data. {e}" + +def deleteData(data: dict, type: str): + """ + Deletes data from the database with thread safety. + """ + db = getDatabase(type) + db_lock = getDatabaseLock(type) + + if db is None or db_lock is None: + return False, "Database connection failed." + + with db_lock: + rem_query = Query() + try: + db.remove(rem_query.item_id == data.get('item_id',None)) + return True, "Data removed successfully." + except Exception as e: + return False, f"Error removing data. {e}" + def getAllData(type: str): """ Retrieves all data from the database with thread safety. diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 5ed4fe9..e7b2326 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -7,7 +7,7 @@ import sys import logging from functions.appFunctions import getAllUserDownloads -from functions.databaseFunctions import insertData, getAllData +from functions.databaseFunctions import insertData, getAllData, deleteData import threading from sys import platform @@ -126,6 +126,7 @@ def __init__(self, *args, **kwargs): self.max_blocks = 16 def getFiles(self): + prev_files = [] while True: files = getAllUserDownloads() if files: @@ -159,6 +160,27 @@ def getFiles(self): else: logging.debug(f"Symlink {s_path} created previously and creation set to '{SYMLINK_CREATION}'. Skipping") + deleted_files = list(set(prev_files) - set(files)) + if deleted_files and SYMLINK_PATH: + for file_item in deleted_files: + symlink_record = file_item + if file_item.get('metadata_mediatype') == 'movie': + s_path = f"{SYMLINK_PATH}/movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" + else: + s_path = f"{SYMLINK_PATH}/series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" + symlink_record['symlink_path'] = s_path + deleteData(symlink_record,'symlinks') + if os.path.islink(s_path): + try: + os.unlink(s_path) + logging.debug(f"Removed symlink {s_path}") + except Exception as e: + logging.error(f"Cannot remove symlink {s_path}: {e}") + pass + else: + logging.debug(f"Symlink {s_path} does not exist") + + prev_files = files time.sleep(300) def getattr(self, path): From a9512578e5a2a178dc9ca13bf30b09d80b062e41 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 11:51:26 +1000 Subject: [PATCH 25/28] fix: unhashable document --- functions/fuseFilesystemFunctions.py | 38 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index e7b2326..e5ae4a5 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -160,25 +160,25 @@ def getFiles(self): else: logging.debug(f"Symlink {s_path} created previously and creation set to '{SYMLINK_CREATION}'. Skipping") - deleted_files = list(set(prev_files) - set(files)) - if deleted_files and SYMLINK_PATH: - for file_item in deleted_files: - symlink_record = file_item - if file_item.get('metadata_mediatype') == 'movie': - s_path = f"{SYMLINK_PATH}/movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" - else: - s_path = f"{SYMLINK_PATH}/series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" - symlink_record['symlink_path'] = s_path - deleteData(symlink_record,'symlinks') - if os.path.islink(s_path): - try: - os.unlink(s_path) - logging.debug(f"Removed symlink {s_path}") - except Exception as e: - logging.error(f"Cannot remove symlink {s_path}: {e}") - pass - else: - logging.debug(f"Symlink {s_path} does not exist") + deleted_files = list(set(prev_files) - set(files)) + if deleted_files and SYMLINK_PATH: + for file_item in deleted_files: + symlink_record = file_item + if file_item.get('metadata_mediatype') == 'movie': + s_path = f"{SYMLINK_PATH}/movies/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_filename')}" + else: + s_path = f"{SYMLINK_PATH}/series/{file_item.get('metadata_rootfoldername')}/{file_item.get('metadata_foldername')}/{file_item.get('metadata_filename')}" + symlink_record['symlink_path'] = s_path + deleteData(symlink_record,'symlinks') + if os.path.islink(s_path): + try: + os.unlink(s_path) + logging.debug(f"Removed symlink {s_path}") + except Exception as e: + logging.error(f"Cannot remove symlink {s_path}: {e}") + pass + else: + logging.debug(f"Symlink {s_path} does not exist") prev_files = files time.sleep(300) From 161ba93a9f6484d37336f8db65f77146f31701cb Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 11:57:06 +1000 Subject: [PATCH 26/28] fix: Document del files --- functions/fuseFilesystemFunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index e5ae4a5..ca8c3ec 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -160,7 +160,7 @@ def getFiles(self): else: logging.debug(f"Symlink {s_path} created previously and creation set to '{SYMLINK_CREATION}'. Skipping") - deleted_files = list(set(prev_files) - set(files)) + deleted_files = list({doc.get('item_id') for doc in prev_files} - {doc.get('item_id') for doc in files}) if deleted_files and SYMLINK_PATH: for file_item in deleted_files: symlink_record = file_item From 082306290207e21458022bc3814b5bc64dc026dd Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 12:07:47 +1000 Subject: [PATCH 27/28] fix: better logs --- functions/fuseFilesystemFunctions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index ca8c3ec..33991f9 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -181,6 +181,7 @@ def getFiles(self): logging.debug(f"Symlink {s_path} does not exist") prev_files = files + logging.debug(f"Waiting 5mins before querying Torbox again for changes") time.sleep(300) def getattr(self, path): From 34619b173e5adf220a87fffc79c443d1e122a3c8 Mon Sep 17 00:00:00 2001 From: Lexi Date: Wed, 16 Jul 2025 12:10:37 +1000 Subject: [PATCH 28/28] fix: improve summary logging --- functions/fuseFilesystemFunctions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/functions/fuseFilesystemFunctions.py b/functions/fuseFilesystemFunctions.py index 33991f9..d505821 100644 --- a/functions/fuseFilesystemFunctions.py +++ b/functions/fuseFilesystemFunctions.py @@ -132,7 +132,7 @@ def getFiles(self): if files: self.files = files self.vfs = VirtualFileSystem(self.files) - logging.debug(f"Updated {len(self.files)} files in VFS") + logging.info(f"Updated {len(self.files)} files in VFS") if SYMLINK_PATH: try: get_symlink_data = getAllData('symlinks')[0] @@ -160,6 +160,7 @@ def getFiles(self): else: logging.debug(f"Symlink {s_path} created previously and creation set to '{SYMLINK_CREATION}'. Skipping") + logging.info(f"Updated {len(files)} symlinks") deleted_files = list({doc.get('item_id') for doc in prev_files} - {doc.get('item_id') for doc in files}) if deleted_files and SYMLINK_PATH: for file_item in deleted_files: @@ -179,6 +180,7 @@ def getFiles(self): pass else: logging.debug(f"Symlink {s_path} does not exist") + logging.info(f"Removed {len(deleted_files)} broken or dead symlinks") prev_files = files logging.debug(f"Waiting 5mins before querying Torbox again for changes")