Film/Dizi çöpe gönderildiğinde Movies ve Tv Shows ekranlarında görünmesi engellendi.
This commit is contained in:
176
server/server.js
176
server/server.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user