-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add Symlink support for fuse #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1378ddc
0291ac4
bdbe6ef
2df4ef3
06dd307
b77f4d9
cd47f7a
4771471
f844171
2db6351
0d7a583
002961a
d34f357
e89cbad
adeb12f
ee0e110
4d2e14a
4e0f528
7ee16f3
4fbceb1
ea25709
3c99103
b581148
8d30c28
a951257
161ba93
0823062
34619b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| TORBOX_API_KEY= | ||
| MOUNT_METHOD=strm | ||
| MOUNT_PATH=/torbox | ||
| MOUNT_PATH=/torbox | ||
| SYMLINK_PATH=/symlinks | ||
| SYMLINK_CREATION=always | ||
| DEBUG_MODE=false |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you write about the benefits of using symlinking with FUSE? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,13 @@ | ||
| import os | ||
| from library.filesystem import MOUNT_PATH | ||
| from library.filesystem import MOUNT_PATH, SYMLINK_PATH, SYMLINK_CREATION | ||
| import stat | ||
| import errno | ||
| from functions.torboxFunctions import getDownloadLink, downloadFile | ||
| import time | ||
| import sys | ||
| import logging | ||
| from functions.appFunctions import getAllUserDownloads | ||
| from functions.databaseFunctions import insertData, getAllData, deleteData | ||
| import threading | ||
| from sys import platform | ||
|
|
||
|
|
@@ -92,6 +93,9 @@ def get_file(self, path): | |
| def list_dir(self, path): | ||
| return self.structure.get(path, []) | ||
|
|
||
|
|
||
|
|
||
|
|
||
| class FuseStat(fuse.Stat): | ||
| def __init__(self): | ||
| self.st_mode = 0 | ||
|
|
@@ -122,12 +126,64 @@ def __init__(self, *args, **kwargs): | |
| self.max_blocks = 16 | ||
|
|
||
| def getFiles(self): | ||
| prev_files = [] | ||
| while True: | ||
| files = getAllUserDownloads() | ||
| 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] | ||
| except: | ||
| get_symlink_data = [] | ||
| # logging.debug(f"Symlink db:\n{get_symlink_data}") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keeping the debug would be helpful |
||
| 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}" | ||
| symlink_record['real_path'] = v_path | ||
| symlink_record['symlink_path'] = s_path | ||
| 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 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') | ||
| 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: | ||
| 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") | ||
| 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") | ||
| time.sleep(300) | ||
|
|
||
| def getattr(self, path): | ||
|
|
@@ -255,4 +311,27 @@ def unmountFuse(): | |
| except OSError as e: | ||
| logging.error(f"Error unmounting: {e}") | ||
| sys.exit(1) | ||
| logging.info("Unmounted successfully.") | ||
| logging.info("Unmounted successfully.") | ||
|
|
||
|
|
||
| def create_symlink_in_symlink_path(vfs_path, symlink_path): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use camelcase |
||
| # 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}") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| MOUNT_REFRESH_TIME = MountRefreshTimes[MOUNT_REFRESH_TIME].value | ||
|
|
||
| DEBUG_MODE = os.getenv("DEBUG_MODE", False) in [True,'true'] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should just wrap in a bool to convert it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty sure it works like this, but needs testing |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the default be off?