feat(tv): Sürüm tabanlı yenileme ile ayrıntılı TV şov yeniden tarama desteği eklendi
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ı.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { API, apiFetch } from "../utils/api.js";
|
import { API, apiFetch } from "../utils/api.js";
|
||||||
import { cleanFileName } from "../utils/filename.js";
|
import { cleanFileName } from "../utils/filename.js";
|
||||||
import { tvShowCount } from "../stores/tvStore.js";
|
import { tvShowRefreshVersion } from "../stores/tvStore.js";
|
||||||
import {
|
import {
|
||||||
activeSearchTerm,
|
activeSearchTerm,
|
||||||
setSearchScope
|
setSearchScope
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
let rescanning = false;
|
let rescanning = false;
|
||||||
let error = null;
|
let error = null;
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
let lastLoadedCount = null;
|
let unsubscribeVersion = null;
|
||||||
let unsubscribeCount = null;
|
|
||||||
|
|
||||||
let selectedShow = null;
|
let selectedShow = null;
|
||||||
let selectedSeason = null;
|
let selectedSeason = null;
|
||||||
@@ -191,13 +190,9 @@ let filteredShows = [];
|
|||||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
const list = await resp.json();
|
const list = await resp.json();
|
||||||
shows = Array.isArray(list) ? list.map(normalizeShow) : [];
|
shows = Array.isArray(list) ? list.map(normalizeShow) : [];
|
||||||
tvShowCount.set(shows.length);
|
|
||||||
lastLoadedCount = shows.length;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err?.message || "TV dizileri alınamadı.";
|
error = err?.message || "TV dizileri alınamadı.";
|
||||||
shows = [];
|
shows = [];
|
||||||
tvShowCount.set(0);
|
|
||||||
lastLoadedCount = 0;
|
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -259,15 +254,15 @@ let filteredShows = [];
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true;
|
mounted = true;
|
||||||
loadShows();
|
loadShows();
|
||||||
unsubscribeCount = tvShowCount.subscribe((val) => {
|
unsubscribeVersion = tvShowRefreshVersion.subscribe((ver) => {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (loading || refreshing || rescanning) return;
|
if (loading || refreshing || rescanning) return;
|
||||||
if (val === lastLoadedCount) return;
|
if (ver === null) return;
|
||||||
loadShows();
|
loadShows();
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
unsubscribeCount && unsubscribeCount();
|
unsubscribeVersion && unsubscribeVersion();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { writable } from "svelte/store";
|
|||||||
import { apiFetch } from "../utils/api.js";
|
import { apiFetch } from "../utils/api.js";
|
||||||
|
|
||||||
export const tvShowCount = writable(0);
|
export const tvShowCount = writable(0);
|
||||||
|
export const tvShowRefreshVersion = writable(0);
|
||||||
|
|
||||||
let requestSeq = 0;
|
let requestSeq = 0;
|
||||||
let lastValue = 0;
|
let lastValue = 0;
|
||||||
let zeroTimer = null;
|
let zeroTimer = null;
|
||||||
@@ -35,6 +37,7 @@ export async function refreshTvShowCount() {
|
|||||||
lastValue = 0;
|
lastValue = 0;
|
||||||
tvShowCount.set(0);
|
tvShowCount.set(0);
|
||||||
}
|
}
|
||||||
|
tvShowRefreshVersion.update((v) => v + 1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("⚠️ TV show count güncellenemedi:", err?.message || err);
|
console.warn("⚠️ TV show count güncellenemedi:", err?.message || err);
|
||||||
// Hata durumunda mevcut değeri koru, titreşimi önle
|
// Hata durumunda mevcut değeri koru, titreşimi önle
|
||||||
|
|||||||
@@ -4128,14 +4128,37 @@ function broadcastSnapshot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mediaRescanTask = null;
|
let mediaRescanTask = null;
|
||||||
let pendingMediaRescan = { movies: false, tv: false };
|
let pendingMediaRescan = {
|
||||||
let lastMediaRescanReason = "manual";
|
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;
|
if (!movies && !tv) return;
|
||||||
pendingMediaRescan.movies = pendingMediaRescan.movies || movies;
|
pendingMediaRescan.movies = pendingMediaRescan.movies || movies;
|
||||||
pendingMediaRescan.tv = pendingMediaRescan.tv || tv;
|
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) {
|
if (!mediaRescanTask) {
|
||||||
mediaRescanTask = runQueuedMediaRescan().finally(() => {
|
mediaRescanTask = runQueuedMediaRescan().finally(() => {
|
||||||
mediaRescanTask = null;
|
mediaRescanTask = null;
|
||||||
@@ -4145,16 +4168,29 @@ function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {}
|
|||||||
|
|
||||||
async function runQueuedMediaRescan() {
|
async function runQueuedMediaRescan() {
|
||||||
while (pendingMediaRescan.movies || pendingMediaRescan.tv) {
|
while (pendingMediaRescan.movies || pendingMediaRescan.tv) {
|
||||||
const targets = { ...pendingMediaRescan };
|
const targets = {
|
||||||
pendingMediaRescan = { movies: false, tv: false };
|
movies: pendingMediaRescan.movies,
|
||||||
const reason = lastMediaRescanReason;
|
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(
|
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 {
|
try {
|
||||||
if (targets.movies) {
|
if (targets.movies) {
|
||||||
if (TMDB_API_KEY) {
|
if (TMDB_API_KEY) {
|
||||||
await rebuildMovieMetadata({ clearCache: true });
|
await rebuildMovieMetadata({ clearCache: targets.clearCacheMovies });
|
||||||
} else {
|
} else {
|
||||||
console.warn("⚠️ TMDB anahtarı tanımsız olduğu için film taraması atlandı.");
|
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 (targets.tv) {
|
||||||
if (TVDB_API_KEY) {
|
if (TVDB_API_KEY) {
|
||||||
await rebuildTvMetadata({ clearCache: true });
|
await rebuildTvMetadata({
|
||||||
|
clearCache: targets.clearCacheTv,
|
||||||
|
roots: Array.from(targets.tvRoots)
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.warn("⚠️ TVDB anahtarı tanımsız olduğu için dizi taraması atlandı.");
|
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({
|
queueMediaRescan({
|
||||||
movies: mediaFlags.movies,
|
movies: mediaFlags.movies,
|
||||||
tv: mediaFlags.tv,
|
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({
|
queueMediaRescan({
|
||||||
movies: mediaFlags.movies,
|
movies: mediaFlags.movies,
|
||||||
tv: mediaFlags.tv,
|
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) {
|
if (!TVDB_API_KEY) {
|
||||||
throw new Error("TVDB API erişimi için gerekli anahtar tanımlı değil.");
|
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 {
|
try {
|
||||||
fs.rmSync(TV_DATA_ROOT, { recursive: true, force: true });
|
fs.rmSync(TV_DATA_ROOT, { recursive: true, force: true });
|
||||||
console.log("🧹 TV cache temizlendi.");
|
console.log("🧹 TV cache temizlendi.");
|
||||||
@@ -6511,10 +6563,15 @@ async function rebuildTvMetadata({ clearCache = false } = {}) {
|
|||||||
tvdbEpisodeDetailCache.clear();
|
tvdbEpisodeDetailCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirEntries = fs
|
let dirEntries = fs
|
||||||
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
||||||
.filter((d) => d.isDirectory());
|
.filter((d) => d.isDirectory());
|
||||||
|
|
||||||
|
if (limitedRoots) {
|
||||||
|
const allowed = new Set(limitedRoots);
|
||||||
|
dirEntries = dirEntries.filter((d) => allowed.has(d.name));
|
||||||
|
}
|
||||||
|
|
||||||
const processed = [];
|
const processed = [];
|
||||||
|
|
||||||
for (const dirent of dirEntries) {
|
for (const dirent of dirEntries) {
|
||||||
|
|||||||
Reference in New Issue
Block a user