feat(webdav): film verilerini taşıma desteği ekle

Dosya taşıma işlemi sırasında etkilenen film verilerini ve metadatasını
korumak için yeni mantık eklendi. `collectMovieRelPathsForMove` ile
etkilenen yollar tespit edilirken, `moveMovieDataDir` ile fiziksel veri
klasörleri ve metadata.json dosyaları taşınarak `_dupe` referansları
güncelleniyor. Aynı kök dizin içinde veya farklı kök dizinler arasında
taşıma işlemleri destekleniyor.
This commit is contained in:
2026-02-01 17:47:52 +03:00
parent e20b3ad591
commit e7925aa39f

View File

@@ -5084,6 +5084,126 @@ function collectSeriesIdsForPath(info, oldRel, isDirectory) {
return ids; 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) { function updateSeriesJsonAfterRootMove(seriesData, oldRoot, newRoot, oldRel, newRel) {
if (!seriesData || typeof seriesData !== "object") return false; if (!seriesData || typeof seriesData !== "object") return false;
let changed = false; let changed = false;
@@ -6797,6 +6917,11 @@ app.post("/api/file/move", requireAuth, (req, res) => {
sourceRelWithinRoot, sourceRelWithinRoot,
isDirectory isDirectory
); );
const affectedMovieRelPaths = collectMovieRelPathsForMove(
preMoveInfo,
sourceRelWithinRoot,
isDirectory
);
fs.renameSync(sourceFullPath, newFullPath); fs.renameSync(sourceFullPath, newFullPath);
@@ -6809,6 +6934,15 @@ app.post("/api/file/move", requireAuth, (req, res) => {
renameInfoPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); renameInfoPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
renameSeriesDataPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); renameSeriesDataPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
renameTrashEntries(sourceRoot, sourceRelWithinRoot, destRelWithinRoot); renameTrashEntries(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
if (affectedMovieRelPaths.size) {
moveMovieDataWithinRoot(
sourceRoot,
sourceRelWithinRoot,
destRelWithinRoot,
affectedMovieRelPaths,
isDirectory
);
}
if (isDirectory) { if (isDirectory) {
removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot); removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot);
} else { } else {
@@ -6833,6 +6967,16 @@ app.post("/api/file/move", requireAuth, (req, res) => {
affectedSeriesIds affectedSeriesIds
); );
} }
if (affectedMovieRelPaths.size) {
moveMovieDataBetweenRoots(
sourceRoot,
destRoot,
sourceRelWithinRoot,
destRelWithinRoot,
affectedMovieRelPaths,
isDirectory
);
}
if (isDirectory) { if (isDirectory) {
removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot); removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot);
} else { } else {