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);
|
const absPath = path.join(currentDir, name);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
|
if (isPathTrashed(safe, relPath, true)) continue;
|
||||||
stack.push(relPath);
|
stack.push(relPath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.isFile()) continue;
|
if (!entry.isFile()) continue;
|
||||||
|
if (isPathTrashed(safe, relPath, false)) continue;
|
||||||
|
|
||||||
const ext = path.extname(name).toLowerCase();
|
const ext = path.extname(name).toLowerCase();
|
||||||
if (!VIDEO_EXTS.includes(ext)) continue;
|
if (!VIDEO_EXTS.includes(ext)) continue;
|
||||||
@@ -3261,6 +3263,135 @@ function broadcastSnapshot() {
|
|||||||
wss.clients.forEach((c) => c.readyState === 1 && c.send(data));
|
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) ---
|
// --- Snapshot (thumbnail dahil, tracker + tarih eklendi) ---
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
return Array.from(torrents.values()).map(
|
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 fullPath = path.join(DOWNLOAD_DIR, safePath);
|
||||||
const folderId = (safePath.split(/[\/]/)[0] || "").trim();
|
const folderId = (safePath.split(/[\/]/)[0] || "").trim();
|
||||||
const rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
|
const rootDir = folderId ? path.join(DOWNLOAD_DIR, folderId) : null;
|
||||||
|
let mediaFlags = { movies: false, tv: false };
|
||||||
|
|
||||||
let stats = null;
|
let stats = null;
|
||||||
try {
|
try {
|
||||||
@@ -4010,6 +4142,16 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
const isDirectory = stats.isDirectory();
|
const isDirectory = stats.isDirectory();
|
||||||
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
||||||
let trashEntry = null;
|
let trashEntry = null;
|
||||||
|
if (folderId && rootDir) {
|
||||||
|
const infoBeforeDelete = readInfoForRoot(folderId);
|
||||||
|
mediaFlags = detectMediaFlagsForPath(
|
||||||
|
infoBeforeDelete,
|
||||||
|
relWithinRoot,
|
||||||
|
isDirectory
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mediaFlags = { movies: false, tv: false };
|
||||||
|
}
|
||||||
|
|
||||||
if (folderId && rootDir) {
|
if (folderId && rootDir) {
|
||||||
trashEntry = addTrashEntry(folderId, {
|
trashEntry = addTrashEntry(folderId, {
|
||||||
@@ -4019,7 +4161,8 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
deletedAt: Date.now(),
|
deletedAt: Date.now(),
|
||||||
type: isDirectory
|
type: isDirectory
|
||||||
? "inode/directory"
|
? "inode/directory"
|
||||||
: mime.lookup(fullPath) || "application/octet-stream"
|
: mime.lookup(fullPath) || "application/octet-stream",
|
||||||
|
mediaFlags: { ...mediaFlags }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
@@ -4077,6 +4220,17 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
broadcastSnapshot();
|
broadcastSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
folderId &&
|
||||||
|
(mediaFlags.movies || mediaFlags.tv)
|
||||||
|
) {
|
||||||
|
queueMediaRescan({
|
||||||
|
movies: mediaFlags.movies,
|
||||||
|
tv: mediaFlags.tv,
|
||||||
|
reason: "trash-add"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ ok: true, filesRemoved: true });
|
res.json({ ok: true, filesRemoved: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Dosya silinemedi:", err.message);
|
console.error("❌ Dosya silinemedi:", err.message);
|
||||||
@@ -4519,10 +4673,18 @@ app.post("/api/trash/restore", requireAuth, (req, res) => {
|
|||||||
if (!removed) {
|
if (!removed) {
|
||||||
return res.status(404).json({ error: "Çöp öğesi bulunamadı" });
|
return res.status(404).json({ error: "Çöp öğesi bulunamadı" });
|
||||||
}
|
}
|
||||||
|
const mediaFlags = inferMediaFlagsFromTrashEntry(removed);
|
||||||
|
|
||||||
console.log(`♻️ Öğe geri yüklendi: ${safeName}`);
|
console.log(`♻️ Öğe geri yüklendi: ${safeName}`);
|
||||||
|
|
||||||
broadcastFileUpdate(rootFolder);
|
broadcastFileUpdate(rootFolder);
|
||||||
|
if (mediaFlags.movies || mediaFlags.tv) {
|
||||||
|
queueMediaRescan({
|
||||||
|
movies: mediaFlags.movies,
|
||||||
|
tv: mediaFlags.tv,
|
||||||
|
reason: "trash-restore"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
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) {
|
if (!TMDB_API_KEY) {
|
||||||
throw new Error("TMDB API key tanımlı değil.");
|
throw new Error("TMDB API key tanımlı değil.");
|
||||||
}
|
}
|
||||||
@@ -4732,7 +4894,9 @@ async function rebuildMovieMetadata({ clearCache = false } = {}) {
|
|||||||
|
|
||||||
if (!videoEntries.length) {
|
if (!videoEntries.length) {
|
||||||
removeMovieData(folder);
|
removeMovieData(folder);
|
||||||
removeSeriesData(folder);
|
if (resetSeriesData) {
|
||||||
|
removeSeriesData(folder);
|
||||||
|
}
|
||||||
const update = {
|
const update = {
|
||||||
primaryVideoPath: null,
|
primaryVideoPath: null,
|
||||||
primaryMediaInfo: null,
|
primaryMediaInfo: null,
|
||||||
@@ -4757,7 +4921,9 @@ async function rebuildMovieMetadata({ clearCache = false } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeMovieData(folder);
|
removeMovieData(folder);
|
||||||
removeSeriesData(folder);
|
if (resetSeriesData) {
|
||||||
|
removeSeriesData(folder);
|
||||||
|
}
|
||||||
|
|
||||||
const matches = [];
|
const matches = [];
|
||||||
|
|
||||||
@@ -5346,11 +5512,13 @@ async function rebuildTvMetadata({ clearCache = false } = {}) {
|
|||||||
const absPath = path.join(currentDir, entry.name);
|
const absPath = path.join(currentDir, entry.name);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
|
if (isPathTrashed(folder, relPath, true)) continue;
|
||||||
await walkDir(absPath, relPath);
|
await walkDir(absPath, relPath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.isFile()) continue;
|
if (!entry.isFile()) continue;
|
||||||
|
if (isPathTrashed(folder, relPath, false)) continue;
|
||||||
if (entry.name.toLowerCase() === INFO_FILENAME) continue;
|
if (entry.name.toLowerCase() === INFO_FILENAME) continue;
|
||||||
const ext = path.extname(entry.name).toLowerCase();
|
const ext = path.extname(entry.name).toLowerCase();
|
||||||
if (!VIDEO_EXTS.includes(ext)) continue;
|
if (!VIDEO_EXTS.includes(ext)) continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user