diff --git a/.env b/.env index 0e59fb9..18731d7 100644 --- a/.env +++ b/.env @@ -2,8 +2,7 @@ APP_HOST=0.0.0.0 APP_PORT=8080 APP_DATA_DIR=/app/data -#MEDIA_ROOT=/data/media -ALLOWED_MEDIA_ROOTS=/Volumes/8TB/Shared_Folders/Downloads, /Volumes/8TB/Shared_Folders/TV_Shows, /Volumes/8TB/Shared_Folders/Library/TV_Shows +ALLOWED_MEDIA_ROOTS=/Volumes/8TB/Shared_Folders/Downloads, /Volumes/8TB/Shared_Folders/TV_Shows, /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows, /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows/#Documentaires TVDB_API_KEY=2c951d0c-0b7e-405b-bdb2-e250491dc69d TVDB_PIN= TVDB_BASE_URL=https://api4.thetvdb.com/v4 diff --git a/app/services/file_discovery_service.py b/app/services/file_discovery_service.py index 7c7f43b..a0ed68e 100644 --- a/app/services/file_discovery_service.py +++ b/app/services/file_discovery_service.py @@ -1,4 +1,5 @@ import os +import re from pathlib import Path @@ -49,8 +50,6 @@ class FileDiscoveryService: iterator = target.iterdir() for entry in iterator: - if len(files) >= limit: - break if not entry.is_file(): continue ext = entry.suffix.lower() @@ -74,6 +73,10 @@ class FileDiscoveryService: } ) + files.sort(key=lambda item: self._natural_sort_key(item.get("relative_path", ""))) + if len(files) > limit: + files = files[:limit] + return { "root_id": root["id"], "root_path": str(root["path"]), @@ -83,6 +86,10 @@ class FileDiscoveryService: "items": files, } + def _natural_sort_key(self, value: str): + text = str(value or "") + return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", text)] + def list_folders( self, root_id: str, diff --git a/app/services/session_service.py b/app/services/session_service.py index 733eb23..92ee067 100644 --- a/app/services/session_service.py +++ b/app/services/session_service.py @@ -736,6 +736,8 @@ class SessionService: proposed_filename=proposed_filename, allowed_roots=allowed_roots, ) + if source_path_str and source_path == destination_path: + errors = [err for err in errors if err != "source and destination paths are equal"] status = "ready" if errors: @@ -777,17 +779,25 @@ class SessionService: for item in preflight_items: source_path = Path(item["source_path"]) destination_path = Path(item["destination_path"]) - os.replace(str(source_path), str(destination_path)) + rename_needed = source_path != destination_path + + if rename_needed: + os.replace(str(source_path), str(destination_path)) + target_path = destination_path + item_status = "renamed" + else: + target_path = source_path + item_status = "unchanged" file_date_status, file_date_detail = self._apply_file_date_after_rename( enabled=set_file_date_to_first_aired, aired_value=aired_by_index.get(int(item["index"])), - destination_path=destination_path, + destination_path=target_path, ) results.append( { **item, - "status": "renamed", + "status": item_status, "errors": [], "file_date_status": file_date_status, "file_date_detail": file_date_detail, diff --git a/app/static/app.js b/app/static/app.js index 1e73875..d09173b 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -213,6 +213,17 @@ return idx >= 0 ? normalized.slice(idx + 1) : normalized; } + function compareModalFilesByName(a, b) { + const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); + const aName = (a && (a.name || basename(a.relative_path || a.path || ""))) || ""; + const bName = (b && (b.name || basename(b.relative_path || b.path || ""))) || ""; + const byName = collator.compare(aName, bName); + if (byName !== 0) return byName; + const aPath = (a && (a.relative_path || a.path || "")) || ""; + const bPath = (b && (b.relative_path || b.path || "")) || ""; + return collator.compare(aPath, bPath); + } + function fallbackText(value) { const text = (value || "").toString().trim(); return text || "-"; @@ -890,7 +901,7 @@ const visible = (state.modalFiles || []).filter((file) => { const text = `${file.relative_path || ""} ${file.name || ""}`.toLowerCase(); return !filter || text.includes(filter); - }); + }).sort(compareModalFilesByName); state.modalVisibleFiles = visible; el.modalFilesList.innerHTML = ""; @@ -966,7 +977,7 @@ const recursive = el.modalRecursiveInput.checked ? "true" : "false"; const data = await api( - `/api/files/discover?root_id=${encodeURIComponent(rootId)}&subpath=${encodeURIComponent(chosenSubpath)}&recursive=${recursive}&limit=200` + `/api/files/discover?root_id=${encodeURIComponent(rootId)}&subpath=${encodeURIComponent(chosenSubpath)}&recursive=${recursive}&limit=1000` ); state.modalSelectionAnchorPath = null; state.modalFiles = data.items || []; diff --git a/data/session_state.sqlite3 b/data/session_state.sqlite3 index f083fe3..fb70ba0 100644 Binary files a/data/session_state.sqlite3 and b/data/session_state.sqlite3 differ