From 45a1f0ae9314f21abad384c7496dd11dff2551ba Mon Sep 17 00:00:00 2001 From: wander Date: Sun, 8 Mar 2026 05:25:11 -0400 Subject: [PATCH] feat: enhance permission logging and display detailed errors in UI --- ta_symlink.py | 69 +++++++++++++++++++++++--- ui/src/components/RecoveryModal.svelte | 5 +- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/ta_symlink.py b/ta_symlink.py index 633d70e..24abd4d 100644 --- a/ta_symlink.py +++ b/ta_symlink.py @@ -23,6 +23,7 @@ SOURCE_DIR = Path("/app/source") TARGET_DIR = Path("/app/target") HIDDEN_DIR = Path("/app/hidden") IMPORT_DIR = Path("/app/import") +DATA_DIR = Path("/app/data") HEADERS = {"Authorization": f"Token {API_TOKEN}"} # Serve static files from ui/dist @@ -1147,6 +1148,8 @@ def api_recovery_delete_batch(): fail_count = 0 errors = [] + log(f"๐Ÿ”ฅ Batch Delete started. Items: {len(paths)}, Destruct: {destruct}") + # Refresh metadata for destruct mode video_map = fetch_all_metadata() if destruct else {} @@ -1159,10 +1162,15 @@ def api_recovery_delete_batch(): if meta.get('path') == path or meta.get('filesystem_path') == path: source_path = meta.get('filesystem_path') if source_path and os.path.exists(source_path): - os.remove(source_path) - log(f"โ˜ข๏ธ [DESTRUCT] Deleted source: {source_path}") - source_deleted = True - break + try: + os.remove(source_path) + log(f"โ˜ข๏ธ [DESTRUCT] Deleted source: {source_path}") + source_deleted = True + break + except Exception as se: + log(f"โŒ [DESTRUCT] Failed to delete source {source_path}: {se}") + raise Exception(f"Source deletion failed: {se}") + if not source_deleted: log(f"โš ๏ธ [DESTRUCT] Source not found for: {path}") @@ -1173,13 +1181,16 @@ def api_recovery_delete_batch(): shutil.rmtree(p) else: p.unlink() + log(f"๐Ÿ—‘๏ธ Deleted target: {path}") # 3. Cleanup empty parent parent = p.parent if parent != Path(TARGET_DIR) and parent != Path(HIDDEN_DIR): if parent.exists() and not any(parent.iterdir()): - parent.rmdir() - log(f"๐Ÿงน [CLEANUP] Removed empty folder: {parent}") + try: + parent.rmdir() + log(f"๐Ÿงน [CLEANUP] Removed empty folder: {parent}") + except: pass success_count += 1 except Exception as e: @@ -1195,6 +1206,52 @@ def api_recovery_delete_batch(): "errors": errors[:5] }) +@app.route("/api/system/check-permissions", methods=["GET"]) +@requires_auth +def api_check_permissions(): + results = {} + test_dirs = [ + ("source", SOURCE_DIR), + ("target", TARGET_DIR), + ("hidden", HIDDEN_DIR), + ("data", DATA_DIR) + ] + + log("๐Ÿ” Running System Permission Check...") + + for name, path in test_dirs: + if not path: + results[name] = {"status": "unset", "writeable": False} + continue + + p = Path(path) + if not p.exists(): + results[name] = {"status": "missing", "writeable": False, "message": "Directory does not exist"} + log(f" โŒ {name} ({path}): MISSING") + continue + + test_file = p / f".write_test_{os.getpid()}" + try: + # Try to write + log(f" ๐Ÿงช Testing write on {name}...") + if test_file.exists(): test_file.unlink() # Cleanup old failure + with open(test_file, "w") as f: + f.write("test") + # Try to delete + test_file.unlink() + + results[name] = {"status": "ok", "writeable": True} + log(f" โœ… {name} ({path}): WRITEABLE") + except Exception as e: + msg = str(e) + results[name] = {"status": "error", "writeable": False, "message": msg} + log(f" โŒ {name} ({path}): READ-ONLY or PERMISSION DENIED - {msg}") + # Identify if it is literally "Read-only file system" + if "Read-only file system" in msg: + log(f" ๐Ÿšจ POSITIVE R/O MOUNT DETECTED for {name}") + + return jsonify(results) + @app.route("/api/recovery/delete", methods=["POST"]) @requires_auth def api_recovery_delete(): diff --git a/ui/src/components/RecoveryModal.svelte b/ui/src/components/RecoveryModal.svelte index 63ad641..891224b 100644 --- a/ui/src/components/RecoveryModal.svelte +++ b/ui/src/components/RecoveryModal.svelte @@ -248,11 +248,14 @@
Filesystem Alert: {#if sourceRO && targetRO} - Both Source AND Target directories are currently READ-ONLY. Deletion and reorganization will fail. + Both Source AND Target directories are currently READ-ONLY. +
Errors: {permissions?.source?.message} | {permissions?.target?.message}
{:else if sourceRO} Source archive is READ-ONLY. Destruct Mode will fail. +
Error: {permissions?.source?.message}
{:else} Target library is READ-ONLY. Symlink cleanup will fail. +
Error: {permissions?.target?.message}
{/if}