From 6d94d79b7ca197288dd4f7d62afa09d5d6686264 Mon Sep 17 00:00:00 2001 From: szbk Date: Sat, 13 Dec 2025 13:53:35 +0300 Subject: [PATCH] =?UTF-8?q?feat(tv):=20S=C3=BCr=C3=BCm=20tabanl=C4=B1=20ye?= =?UTF-8?q?nileme=20ile=20ayr=C4=B1nt=C4=B1l=C4=B1=20TV=20=C5=9Fov=20yenid?= =?UTF-8?q?en=20tarama=20deste=C4=9Fi=20eklendi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Daha güvenilir güncellemeler için sayım tabanlı depolama yerine sürüm tabanlı yenileme mekanizması kullanıldı. Belirli TV kök dizinlerini hedeflemeyi ve seçici önbellek temizlemeyi desteklemek için medya yeniden tarama sistemi geliştirildi. Yalnızca etkilenen dizinler için yeniden taramaları tetiklemek üzere çöp kutusu işlemleri iyileştirildi, böylece gereksiz işleme azaltıldı. --- client/src/routes/TvShows.svelte | 15 ++---- client/src/stores/tvStore.js | 3 ++ server/server.js | 87 ++++++++++++++++++++++++++------ 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/client/src/routes/TvShows.svelte b/client/src/routes/TvShows.svelte index aaeca69..dd60488 100644 --- a/client/src/routes/TvShows.svelte +++ b/client/src/routes/TvShows.svelte @@ -2,7 +2,7 @@ import { onMount, tick } from "svelte"; import { API, apiFetch } from "../utils/api.js"; import { cleanFileName } from "../utils/filename.js"; - import { tvShowCount } from "../stores/tvStore.js"; + import { tvShowRefreshVersion } from "../stores/tvStore.js"; import { activeSearchTerm, setSearchScope @@ -14,8 +14,7 @@ let rescanning = false; let error = null; let mounted = false; - let lastLoadedCount = null; - let unsubscribeCount = null; + let unsubscribeVersion = null; let selectedShow = null; let selectedSeason = null; @@ -191,13 +190,9 @@ let filteredShows = []; if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const list = await resp.json(); shows = Array.isArray(list) ? list.map(normalizeShow) : []; - tvShowCount.set(shows.length); - lastLoadedCount = shows.length; } catch (err) { error = err?.message || "TV dizileri alınamadı."; shows = []; - tvShowCount.set(0); - lastLoadedCount = 0; } finally { loading = false; } @@ -259,15 +254,15 @@ let filteredShows = []; onMount(() => { mounted = true; loadShows(); - unsubscribeCount = tvShowCount.subscribe((val) => { + unsubscribeVersion = tvShowRefreshVersion.subscribe((ver) => { if (!mounted) return; if (loading || refreshing || rescanning) return; - if (val === lastLoadedCount) return; + if (ver === null) return; loadShows(); }); return () => { mounted = false; - unsubscribeCount && unsubscribeCount(); + unsubscribeVersion && unsubscribeVersion(); }; }); diff --git a/client/src/stores/tvStore.js b/client/src/stores/tvStore.js index 4b3fbf9..3580ebd 100644 --- a/client/src/stores/tvStore.js +++ b/client/src/stores/tvStore.js @@ -2,6 +2,8 @@ import { writable } from "svelte/store"; import { apiFetch } from "../utils/api.js"; export const tvShowCount = writable(0); +export const tvShowRefreshVersion = writable(0); + let requestSeq = 0; let lastValue = 0; let zeroTimer = null; @@ -35,6 +37,7 @@ export async function refreshTvShowCount() { lastValue = 0; tvShowCount.set(0); } + tvShowRefreshVersion.update((v) => v + 1); } catch (err) { console.warn("⚠️ TV show count güncellenemedi:", err?.message || err); // Hata durumunda mevcut değeri koru, titreşimi önle diff --git a/server/server.js b/server/server.js index 99adbe2..927b13f 100644 --- a/server/server.js +++ b/server/server.js @@ -4128,14 +4128,37 @@ function broadcastSnapshot() { } let mediaRescanTask = null; -let pendingMediaRescan = { movies: false, tv: false }; -let lastMediaRescanReason = "manual"; +let pendingMediaRescan = { + movies: false, + tv: false, + tvRoots: new Set(), + clearCacheMovies: false, + clearCacheTv: false, + reason: "manual" +}; -function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {}) { +function queueMediaRescan({ + movies = false, + tv = false, + reason = "manual", + roots = [], + clearCacheMovies = false, + clearCacheTv = false +} = {}) { if (!movies && !tv) return; pendingMediaRescan.movies = pendingMediaRescan.movies || movies; pendingMediaRescan.tv = pendingMediaRescan.tv || tv; - lastMediaRescanReason = reason; + pendingMediaRescan.clearCacheMovies = + pendingMediaRescan.clearCacheMovies || clearCacheMovies; + pendingMediaRescan.clearCacheTv = + pendingMediaRescan.clearCacheTv || clearCacheTv; + pendingMediaRescan.reason = reason; + if (tv && Array.isArray(roots)) { + roots.forEach((r) => { + const safe = sanitizeRelative(r); + if (safe) pendingMediaRescan.tvRoots.add(safe); + }); + } if (!mediaRescanTask) { mediaRescanTask = runQueuedMediaRescan().finally(() => { mediaRescanTask = null; @@ -4145,16 +4168,29 @@ function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {} async function runQueuedMediaRescan() { while (pendingMediaRescan.movies || pendingMediaRescan.tv) { - const targets = { ...pendingMediaRescan }; - pendingMediaRescan = { movies: false, tv: false }; - const reason = lastMediaRescanReason; + const targets = { + movies: pendingMediaRescan.movies, + tv: pendingMediaRescan.tv, + tvRoots: new Set(pendingMediaRescan.tvRoots), + clearCacheMovies: pendingMediaRescan.clearCacheMovies, + clearCacheTv: pendingMediaRescan.clearCacheTv, + reason: pendingMediaRescan.reason + }; + pendingMediaRescan = { + movies: false, + tv: false, + tvRoots: new Set(), + clearCacheMovies: false, + clearCacheTv: false, + reason: "manual" + }; console.log( - `🔁 Medya taraması tetiklendi (${reason}) -> movies:${targets.movies} tv:${targets.tv}` + `🔁 Medya taraması tetiklendi (${targets.reason}) -> movies:${targets.movies} tv:${targets.tv} roots:${Array.from(targets.tvRoots).join(",") || "-"}` ); try { if (targets.movies) { if (TMDB_API_KEY) { - await rebuildMovieMetadata({ clearCache: true }); + await rebuildMovieMetadata({ clearCache: targets.clearCacheMovies }); } else { console.warn("⚠️ TMDB anahtarı tanımsız olduğu için film taraması atlandı."); } @@ -4162,7 +4198,10 @@ async function runQueuedMediaRescan() { if (targets.tv) { if (TVDB_API_KEY) { - await rebuildTvMetadata({ clearCache: true }); + await rebuildTvMetadata({ + clearCache: targets.clearCacheTv, + roots: Array.from(targets.tvRoots) + }); } else { console.warn("⚠️ TVDB anahtarı tanımsız olduğu için dizi taraması atlandı."); } @@ -5125,7 +5164,10 @@ app.delete("/api/file", requireAuth, (req, res) => { queueMediaRescan({ movies: mediaFlags.movies, tv: mediaFlags.tv, - reason: "trash-add" + reason: "trash-add", + roots: [folderId], + clearCacheMovies: false, + clearCacheTv: false }); } @@ -5598,7 +5640,10 @@ app.post("/api/trash/restore", requireAuth, (req, res) => { queueMediaRescan({ movies: mediaFlags.movies, tv: mediaFlags.tv, - reason: "trash-restore" + reason: "trash-restore", + roots: [rootFolder], + clearCacheMovies: false, + clearCacheTv: false }); } @@ -6487,12 +6532,19 @@ app.get("/api/music", requireAuth, (req, res) => { } }); -async function rebuildTvMetadata({ clearCache = false } = {}) { +async function rebuildTvMetadata({ clearCache = false, roots = null } = {}) { if (!TVDB_API_KEY) { throw new Error("TVDB API erişimi için gerekli anahtar tanımlı değil."); } - if (clearCache && fs.existsSync(TV_DATA_ROOT)) { + const limitedRoots = + Array.isArray(roots) && roots.length + ? roots + .map((r) => sanitizeRelative(r)) + .filter(Boolean) + : null; + + if (clearCache && !limitedRoots && fs.existsSync(TV_DATA_ROOT)) { try { fs.rmSync(TV_DATA_ROOT, { recursive: true, force: true }); console.log("🧹 TV cache temizlendi."); @@ -6511,10 +6563,15 @@ async function rebuildTvMetadata({ clearCache = false } = {}) { tvdbEpisodeDetailCache.clear(); } - const dirEntries = fs + let dirEntries = fs .readdirSync(DOWNLOAD_DIR, { withFileTypes: true }) .filter((d) => d.isDirectory()); + if (limitedRoots) { + const allowed = new Set(limitedRoots); + dirEntries = dirEntries.filter((d) => allowed.has(d.name)); + } + const processed = []; for (const dirent of dirEntries) {