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:
144
server/server.js
144
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 {
|
||||
|
||||
Reference in New Issue
Block a user