Film/Dizi çöpe gönderildiğinde Movies ve Tv Shows ekranlarında görünmesi engellendi.

This commit is contained in:
2025-11-30 14:46:59 +03:00
parent 6fba9cb288
commit cc5265761a

View File

@@ -119,11 +119,13 @@ function enumerateVideoFiles(rootFolder) {
const absPath = path.join(currentDir, name);
if (entry.isDirectory()) {
if (isPathTrashed(safe, relPath, true)) continue;
stack.push(relPath);
continue;
}
if (!entry.isFile()) continue;
if (isPathTrashed(safe, relPath, false)) continue;
const ext = path.extname(name).toLowerCase();
if (!VIDEO_EXTS.includes(ext)) continue;
@@ -3261,6 +3263,135 @@ function broadcastSnapshot() {
wss.clients.forEach((c) => c.readyState === 1 && c.send(data));
}
let mediaRescanTask = null;
let pendingMediaRescan = { movies: false, tv: false };
let lastMediaRescanReason = "manual";
function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {}) {
if (!movies && !tv) return;
pendingMediaRescan.movies = pendingMediaRescan.movies || movies;
pendingMediaRescan.tv = pendingMediaRescan.tv || tv;
lastMediaRescanReason = reason;
if (!mediaRescanTask) {
mediaRescanTask = runQueuedMediaRescan().finally(() => {
mediaRescanTask = null;
});
}
}
async function runQueuedMediaRescan() {
while (pendingMediaRescan.movies || pendingMediaRescan.tv) {
const targets = { ...pendingMediaRescan };
pendingMediaRescan = { movies: false, tv: false };
const reason = lastMediaRescanReason;
console.log(
`🔁 Medya taraması tetiklendi (${reason}) -> movies:${targets.movies} tv:${targets.tv}`
);
try {
if (targets.movies) {
if (TMDB_API_KEY) {
await rebuildMovieMetadata({ clearCache: true });
} else {
console.warn(" TMDB anahtarı tanımsız olduğu için film taraması atlandı.");
}
}
if (targets.tv) {
if (TVDB_API_KEY) {
await rebuildTvMetadata({ clearCache: true });
} else {
console.warn(" TVDB anahtarı tanımsız olduğu için dizi taraması atlandı.");
}
}
if (targets.movies || targets.tv) {
broadcastFileUpdate("media-library");
}
} catch (err) {
console.error(" Medya kütüphanesi taraması başarısız:", err?.message || err);
}
}
}
function detectMediaFlagsForPath(info, relWithinRoot, isDirectory) {
const flags = { movies: false, tv: false };
if (!info || typeof info !== "object") return flags;
const normalized = normalizeTrashPath(relWithinRoot);
const matchesPath = (candidate) => {
const normalizedCandidate = normalizeTrashPath(candidate);
if (!normalized) return true;
if (isDirectory) {
return (
normalizedCandidate === normalized ||
normalizedCandidate.startsWith(`${normalized}/`)
);
}
return normalizedCandidate === normalized;
};
const files = info.files || {};
for (const [key, meta] of Object.entries(files)) {
if (!meta) continue;
if (!matchesPath(key)) continue;
if (meta.movieMatch) flags.movies = true;
if (meta.seriesMatch) flags.tv = true;
}
const episodes = info.seriesEpisodes || {};
for (const [key] of Object.entries(episodes)) {
if (!matchesPath(key)) continue;
flags.tv = true;
break;
}
if (!normalized || isDirectory) {
if (info.movieMatch || info.primaryVideoPath) {
flags.movies = true;
}
if (
info.seriesEpisodes &&
Object.keys(info.seriesEpisodes).length &&
!flags.tv
) {
flags.tv = true;
}
}
return flags;
}
function inferMediaFlagsFromTrashEntry(entry) {
if (!entry) return { movies: false, tv: false };
if (
entry.mediaFlags &&
typeof entry.mediaFlags === "object" &&
("movies" in entry.mediaFlags || "tv" in entry.mediaFlags)
) {
return {
movies: Boolean(entry.mediaFlags.movies),
tv: Boolean(entry.mediaFlags.tv)
};
}
const normalized = normalizeTrashPath(entry.path || entry.originalPath || "");
if (!normalized || entry.isDirectory) {
return { movies: true, tv: true };
}
const ext = path.extname(normalized).toLowerCase();
if (!VIDEO_EXTS.includes(ext)) {
return { movies: false, tv: false };
}
const base = path.basename(normalized);
const seriesCandidate = parseSeriesInfo(base);
if (seriesCandidate) {
return { movies: false, tv: true };
}
return { movies: true, tv: false };
}
// --- Snapshot (thumbnail dahil, tracker + tarih eklendi) ---
function snapshot() {
return Array.from(torrents.values()).map(
@@ -3988,6 +4119,7 @@ app.delete("/api/file", requireAuth, (req, res) => {
const fullPath = path.join(DOWNLOAD_DIR, safePath);
const folderId = (safePath.split(/[\/]/)[0] || "").trim();
const rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
let mediaFlags = { movies: false, tv: false };
let stats = null;
try {
@@ -4010,6 +4142,16 @@ app.delete("/api/file", requireAuth, (req, res) => {
const isDirectory = stats.isDirectory();
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
let trashEntry = null;
if (folderId && rootDir) {
const infoBeforeDelete = readInfoForRoot(folderId);
mediaFlags = detectMediaFlagsForPath(
infoBeforeDelete,
relWithinRoot,
isDirectory
);
} else {
mediaFlags = { movies: false, tv: false };
}
if (folderId && rootDir) {
trashEntry = addTrashEntry(folderId, {
@@ -4019,7 +4161,8 @@ app.delete("/api/file", requireAuth, (req, res) => {
deletedAt: Date.now(),
type: isDirectory
? "inode/directory"
: mime.lookup(fullPath) || "application/octet-stream"
: mime.lookup(fullPath) || "application/octet-stream",
mediaFlags: { ...mediaFlags }
});
if (isDirectory) {
@@ -4077,6 +4220,17 @@ app.delete("/api/file", requireAuth, (req, res) => {
broadcastSnapshot();
}
if (
folderId &&
(mediaFlags.movies || mediaFlags.tv)
) {
queueMediaRescan({
movies: mediaFlags.movies,
tv: mediaFlags.tv,
reason: "trash-add"
});
}
res.json({ ok: true, filesRemoved: true });
} catch (err) {
console.error(" Dosya silinemedi:", err.message);
@@ -4519,10 +4673,18 @@ app.post("/api/trash/restore", requireAuth, (req, res) => {
if (!removed) {
return res.status(404).json({ error: "Çöp öğesi bulunamadı" });
}
const mediaFlags = inferMediaFlagsFromTrashEntry(removed);
console.log(`♻️ Öğe geri yüklendi: ${safeName}`);
broadcastFileUpdate(rootFolder);
if (mediaFlags.movies || mediaFlags.tv) {
queueMediaRescan({
movies: mediaFlags.movies,
tv: mediaFlags.tv,
reason: "trash-restore"
});
}
res.json({
success: true,
@@ -4692,7 +4854,7 @@ app.get("/api/movies", requireAuth, (req, res) => {
}
});
async function rebuildMovieMetadata({ clearCache = false } = {}) {
async function rebuildMovieMetadata({ clearCache = false, resetSeriesData = false } = {}) {
if (!TMDB_API_KEY) {
throw new Error("TMDB API key tanımlı değil.");
}
@@ -4732,7 +4894,9 @@ async function rebuildMovieMetadata({ clearCache = false } = {}) {
if (!videoEntries.length) {
removeMovieData(folder);
removeSeriesData(folder);
if (resetSeriesData) {
removeSeriesData(folder);
}
const update = {
primaryVideoPath: null,
primaryMediaInfo: null,
@@ -4757,7 +4921,9 @@ async function rebuildMovieMetadata({ clearCache = false } = {}) {
}
removeMovieData(folder);
removeSeriesData(folder);
if (resetSeriesData) {
removeSeriesData(folder);
}
const matches = [];
@@ -5346,11 +5512,13 @@ async function rebuildTvMetadata({ clearCache = false } = {}) {
const absPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
if (isPathTrashed(folder, relPath, true)) continue;
await walkDir(absPath, relPath);
continue;
}
if (!entry.isFile()) continue;
if (isPathTrashed(folder, relPath, false)) continue;
if (entry.name.toLowerCase() === INFO_FILENAME) continue;
const ext = path.extname(entry.name).toLowerCase();
if (!VIDEO_EXTS.includes(ext)) continue;