diff --git a/server/server.js b/server/server.js index 667c786..85836ab 100644 --- a/server/server.js +++ b/server/server.js @@ -5052,6 +5052,196 @@ function moveInfoDataBetweenRoots(oldRoot, newRoot, oldRel, newRel, isDirectory) return true; } +function collectSeriesIdsForPath(info, oldRel, isDirectory) { + const ids = new Set(); + if (!info || typeof info !== "object") return ids; + 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}/`) + ); + }; + + const episodes = info.seriesEpisodes || {}; + for (const [key, value] of Object.entries(episodes)) { + if (!shouldMatch(key)) continue; + const id = value?.showId ?? value?.id ?? null; + if (id) ids.add(id); + } + + const files = info.files || {}; + for (const [key, value] of Object.entries(files)) { + if (!shouldMatch(key)) continue; + const id = value?.seriesMatch?.id ?? null; + if (id) ids.add(id); + } + + return ids; +} + +function updateSeriesJsonAfterRootMove(seriesData, oldRoot, newRoot, oldRel, newRel) { + if (!seriesData || typeof seriesData !== "object") return false; + let changed = false; + const oldKey = seriesData?._dupe?.key || null; + if (seriesData._dupe) { + seriesData._dupe.folder = newRoot; + seriesData._dupe.key = tvSeriesKey(newRoot, seriesData._dupe.seriesId); + changed = true; + } + + const encodeKey = (key) => + String(key || "") + .split(path.sep) + .map(encodeURIComponent) + .join("/"); + const oldKeyEncoded = oldKey ? encodeKey(oldKey) : null; + const newKeyEncoded = seriesData?._dupe?.key + ? encodeKey(seriesData._dupe.key) + : null; + const oldPrefix = oldKeyEncoded ? `/tv-data/${oldKeyEncoded}/` : null; + const newPrefix = newKeyEncoded ? `/tv-data/${newKeyEncoded}/` : null; + const replaceTvDataPath = (value) => { + if (!value || !oldPrefix || !newPrefix || typeof value !== "string") { + return value; + } + if (value.includes(oldPrefix)) { + changed = true; + return value.replace(oldPrefix, newPrefix); + } + return value; + }; + + if (seriesData.poster) seriesData.poster = replaceTvDataPath(seriesData.poster); + if (seriesData.backdrop) + seriesData.backdrop = replaceTvDataPath(seriesData.backdrop); + + const oldRelNorm = normalizeTrashPath(oldRel); + const newRelNorm = normalizeTrashPath(newRel); + const shouldTransform = (value) => { + const normalized = normalizeTrashPath(value); + if (!oldRelNorm) return true; + if (normalized === oldRelNorm) return true; + return ( + oldRelNorm && normalized.startsWith(`${oldRelNorm}/`) + ); + }; + const transformRel = (value) => { + const normalized = normalizeTrashPath(value); + if (!shouldTransform(normalized)) return value; + const suffix = oldRelNorm + ? normalized.slice(oldRelNorm.length).replace(/^\/+/, "") + : normalized; + const next = newRelNorm ? `${newRelNorm}${suffix ? `/${suffix}` : ""}` : suffix; + if (next !== value) changed = true; + return next; + }; + + const seasons = seriesData?.seasons || {}; + for (const season of Object.values(seasons)) { + if (!season) continue; + if (season.poster) season.poster = replaceTvDataPath(season.poster); + if (!season.episodes) continue; + for (const episode of Object.values(season.episodes)) { + if (!episode) continue; + if (episode.still) episode.still = replaceTvDataPath(episode.still); + if (episode.file) { + const nextFile = transformRel(episode.file); + if (nextFile !== episode.file) { + episode.file = nextFile; + changed = true; + } + } + if (episode.videoPath) { + const video = String(episode.videoPath).replace(/\\/g, "/"); + if (video.startsWith(`${oldRoot}/`)) { + episode.videoPath = `${newRoot}/${video.slice(oldRoot.length + 1)}`; + changed = true; + } else { + const nextVideo = transformRel(video); + if (nextVideo !== video) { + episode.videoPath = nextVideo; + changed = true; + } + } + } + } + } + + return changed; +} + +function moveSeriesDataBetweenRoots(oldRoot, newRoot, oldRel, newRel, seriesIds) { + if (!oldRoot || !newRoot) return false; + if (!seriesIds || !seriesIds.size) return false; + + let movedAny = false; + for (const seriesId of seriesIds) { + if (!seriesId) continue; + const oldPaths = tvSeriesPaths(oldRoot, seriesId); + if (!oldPaths || !fs.existsSync(oldPaths.dir)) continue; + const newKey = tvSeriesKey(newRoot, seriesId); + if (!newKey) continue; + const newPaths = tvSeriesPathsByKey(newKey); + if (!newPaths) continue; + + if (fs.existsSync(newPaths.dir)) { + try { + fs.rmSync(newPaths.dir, { recursive: true, force: true }); + } catch (err) { + console.warn(`⚠️ TV metadata hedefi temizlenemedi (${newPaths.dir}): ${err.message}`); + } + } + + try { + fs.renameSync(oldPaths.dir, newPaths.dir); + } catch (err) { + try { + fs.cpSync(oldPaths.dir, newPaths.dir, { recursive: true }); + fs.rmSync(oldPaths.dir, { recursive: true, force: true }); + } catch (copyErr) { + console.warn(`⚠️ TV metadata taşınamadı (${oldPaths.dir}): ${copyErr.message}`); + continue; + } + } + + const metadataPath = path.join(newPaths.dir, "series.json"); + if (fs.existsSync(metadataPath)) { + try { + const seriesData = JSON.parse(fs.readFileSync(metadataPath, "utf-8")); + const changed = updateSeriesJsonAfterRootMove( + seriesData, + oldRoot, + newRoot, + oldRel, + newRel + ); + if (changed) { + fs.writeFileSync( + metadataPath, + JSON.stringify(seriesData, null, 2), + "utf-8" + ); + } + } catch (err) { + console.warn(`⚠️ series.json güncellenemedi (${metadataPath}): ${err.message}`); + } + } + + movedAny = true; + } + + if (movedAny) { + renameSeriesDataPaths(newRoot, oldRel, newRel); + } + + return movedAny; +} + function renameInfoPaths(rootFolder, oldRel, newRel) { if (!rootFolder) return; const info = readInfoForRoot(rootFolder); @@ -6601,6 +6791,13 @@ app.post("/api/file/move", requireAuth, (req, res) => { .json({ error: "Kök klasör bu yöntemle taşınamaz" }); } + const preMoveInfo = sourceRoot ? readInfoForRoot(sourceRoot) : null; + const affectedSeriesIds = collectSeriesIdsForPath( + preMoveInfo, + sourceRelWithinRoot, + isDirectory + ); + fs.renameSync(sourceFullPath, newFullPath); const sameRoot = @@ -6627,6 +6824,15 @@ app.post("/api/file/move", requireAuth, (req, res) => { destRelWithinRoot, isDirectory ); + if (affectedSeriesIds.size) { + moveSeriesDataBetweenRoots( + sourceRoot, + destRoot, + sourceRelWithinRoot, + destRelWithinRoot, + affectedSeriesIds + ); + } if (isDirectory) { removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot); } else {