diff --git a/server/server.js b/server/server.js index 85836ab..a0c51c2 100644 --- a/server/server.js +++ b/server/server.js @@ -5084,6 +5084,126 @@ function collectSeriesIdsForPath(info, oldRel, isDirectory) { return ids; } +function collectMovieRelPathsForMove(info, oldRel, isDirectory) { + const relPaths = new Set(); + if (!info || typeof info !== "object") return relPaths; + const normalizedOldRel = normalizeTrashPath(oldRel); + const shouldMatch = (key) => { + if (!normalizedOldRel) return true; + const normalizedKey = normalizeTrashPath(key); + if (normalizedKey === normalizedOldRel) return true; + return ( + isDirectory && + normalizedOldRel && + normalizedKey.startsWith(`${normalizedOldRel}/`) + ); + }; + + if (info.primaryVideoPath && shouldMatch(info.primaryVideoPath)) { + relPaths.add(normalizeTrashPath(info.primaryVideoPath)); + } + + const files = info.files || {}; + for (const [key, value] of Object.entries(files)) { + if (!value?.movieMatch) continue; + if (!shouldMatch(key)) continue; + relPaths.add(normalizeTrashPath(key)); + } + + return relPaths; +} + +function mapRelPathForMove(oldRel, newRel, relPath, isDirectory) { + const normalizedOldRel = normalizeTrashPath(oldRel); + const normalizedNewRel = normalizeTrashPath(newRel); + const normalizedRel = normalizeTrashPath(relPath); + if (!normalizedOldRel) return normalizedRel; + if (normalizedRel === normalizedOldRel) return normalizedNewRel; + if ( + isDirectory && + normalizedRel.startsWith(`${normalizedOldRel}/`) + ) { + const suffix = normalizedRel.slice(normalizedOldRel.length).replace(/^\/+/, ""); + return normalizedNewRel + ? `${normalizedNewRel}${suffix ? `/${suffix}` : ""}` + : suffix; + } + return normalizedRel; +} + +function moveMovieDataDir(oldKey, newKey, oldRoot, newRoot, newRelPath) { + if (!oldKey || !newKey) return false; + if (oldKey === newKey) return false; + const oldDir = movieDataPathsByKey(oldKey).dir; + const newDir = movieDataPathsByKey(newKey).dir; + if (!fs.existsSync(oldDir)) return false; + if (fs.existsSync(newDir)) { + try { + fs.rmSync(newDir, { recursive: true, force: true }); + } catch (err) { + console.warn(`⚠️ Movie metadata hedefi temizlenemedi (${newDir}): ${err.message}`); + } + } + try { + fs.renameSync(oldDir, newDir); + } catch (err) { + try { + fs.cpSync(oldDir, newDir, { recursive: true }); + fs.rmSync(oldDir, { recursive: true, force: true }); + } catch (copyErr) { + console.warn(`⚠️ Movie metadata taşınamadı (${oldDir}): ${copyErr.message}`); + return false; + } + } + + const metadataPath = path.join(newDir, "metadata.json"); + if (fs.existsSync(metadataPath)) { + try { + const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8")); + if (metadata?._dupe) { + metadata._dupe.folder = newRoot; + metadata._dupe.videoPath = newRelPath; + metadata._dupe.cacheKey = newKey; + } + fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"); + } catch (err) { + console.warn(`⚠️ movie metadata güncellenemedi (${metadataPath}): ${err.message}`); + } + } + + return true; +} + +function moveMovieDataBetweenRoots(oldRoot, newRoot, oldRel, newRel, relPaths, isDirectory) { + if (!oldRoot || !newRoot) return false; + if (!relPaths || relPaths.size === 0) return false; + let movedAny = false; + for (const relPath of relPaths) { + const mappedRel = mapRelPathForMove(oldRel, newRel, relPath, isDirectory); + const oldKey = movieDataKey(oldRoot, relPath); + const newKey = movieDataKey(newRoot, mappedRel); + if (moveMovieDataDir(oldKey, newKey, oldRoot, newRoot, mappedRel)) { + movedAny = true; + } + } + return movedAny; +} + +function moveMovieDataWithinRoot(rootFolder, oldRel, newRel, relPaths, isDirectory) { + if (!rootFolder) return false; + if (!relPaths || relPaths.size === 0) return false; + let movedAny = false; + for (const relPath of relPaths) { + const mappedRel = mapRelPathForMove(oldRel, newRel, relPath, isDirectory); + const oldKey = movieDataKey(rootFolder, relPath); + const newKey = movieDataKey(rootFolder, mappedRel); + if (moveMovieDataDir(oldKey, newKey, rootFolder, rootFolder, mappedRel)) { + movedAny = true; + } + } + return movedAny; +} + function updateSeriesJsonAfterRootMove(seriesData, oldRoot, newRoot, oldRel, newRel) { if (!seriesData || typeof seriesData !== "object") return false; let changed = false; @@ -6797,6 +6917,11 @@ app.post("/api/file/move", requireAuth, (req, res) => { sourceRelWithinRoot, isDirectory ); + const affectedMovieRelPaths = collectMovieRelPathsForMove( + preMoveInfo, + sourceRelWithinRoot, + isDirectory + ); fs.renameSync(sourceFullPath, newFullPath); @@ -6809,6 +6934,15 @@ app.post("/api/file/move", requireAuth, (req, res) => { renameInfoPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); renameSeriesDataPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); renameTrashEntries(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); + if (affectedMovieRelPaths.size) { + moveMovieDataWithinRoot( + sourceRoot, + sourceRelWithinRoot, + destRelWithinRoot, + affectedMovieRelPaths, + isDirectory + ); + } if (isDirectory) { removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot); } else { @@ -6833,6 +6967,16 @@ app.post("/api/file/move", requireAuth, (req, res) => { affectedSeriesIds ); } + if (affectedMovieRelPaths.size) { + moveMovieDataBetweenRoots( + sourceRoot, + destRoot, + sourceRelWithinRoot, + destRelWithinRoot, + affectedMovieRelPaths, + isDirectory + ); + } if (isDirectory) { removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot); } else {