Add admin button to force-refresh IPTV EPG caches.
Clears global XMLTV and derived caches, re-fetches XUI plus configured feeds, so XUI mapping changes show up without waiting for SWR TTL.
This commit is contained in:
parent
a7038f04fa
commit
940470a97d
4 changed files with 131 additions and 4 deletions
|
|
@ -149,6 +149,7 @@ export default function SettingsPage() {
|
|||
Array<{ id: string; name: string; channelCount: number }>
|
||||
>([]);
|
||||
const [tvBusy, setTvBusy] = useState(false);
|
||||
const [tvRefreshBusy, setTvRefreshBusy] = useState(false);
|
||||
const [tvMsg, setTvMsg] = useState<string | null>(null);
|
||||
const [tvErr, setTvErr] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -518,6 +519,33 @@ export default function SettingsPage() {
|
|||
}
|
||||
}
|
||||
|
||||
async function refreshTvEpg() {
|
||||
setTvRefreshBusy(true);
|
||||
setTvMsg(null);
|
||||
setTvErr(null);
|
||||
try {
|
||||
const res = await fetch("/api/v1/admin/settings/iptv/refresh-epg", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
setTvErr(data.error?.message ?? "EPG vernieuwen mislukt");
|
||||
return;
|
||||
}
|
||||
const ok = data.xmltvOk ?? 0;
|
||||
const fail = data.xmltvFailed ?? 0;
|
||||
setTvMsg(
|
||||
fail > 0
|
||||
? `EPG vernieuwd (${ok} ok, ${fail} mislukt) — open TV in de app opnieuw`
|
||||
: `EPG vernieuwd (${ok} lijn${ok === 1 ? "" : "en"}) — open TV in de app opnieuw`
|
||||
);
|
||||
if (Array.isArray(data.categories)) setTvCategories(data.categories);
|
||||
} finally {
|
||||
setTvRefreshBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
|
|
@ -876,15 +904,27 @@ export default function SettingsPage() {
|
|||
))}
|
||||
</div>
|
||||
<div className="inline-form" style={{ marginTop: "1rem" }}>
|
||||
<button type="button" disabled={tvBusy} onClick={() => void saveTvSettings()}>
|
||||
<button type="button" disabled={tvBusy || tvRefreshBusy} onClick={() => void saveTvSettings()}>
|
||||
{tvBusy ? "Bezig…" : "Opslaan"}
|
||||
</button>
|
||||
<button type="button" disabled={tvBusy} onClick={() => loadTvSettings()}>
|
||||
Vernieuwen
|
||||
<button
|
||||
type="button"
|
||||
disabled={tvBusy || tvRefreshBusy}
|
||||
onClick={() => void refreshTvEpg()}
|
||||
title="Leegt EPG-cache en haalt XUI + Admin-URL's opnieuw op"
|
||||
>
|
||||
{tvRefreshBusy ? "EPG laden…" : "EPG vernieuwen"}
|
||||
</button>
|
||||
<button type="button" disabled={tvBusy || tvRefreshBusy} onClick={() => loadTvSettings()}>
|
||||
Categorieën herladen
|
||||
</button>
|
||||
{tvMsg && <span className="ok-text">{tvMsg}</span>}
|
||||
{tvErr && <span className="danger-text">{tvErr}</span>}
|
||||
</div>
|
||||
<p className="muted" style={{ marginTop: "0.75rem", fontSize: "0.85rem" }}>
|
||||
Na een wijziging in XUI: klik <strong>EPG vernieuwen</strong>, daarna in de app de TV-tab
|
||||
opnieuw openen (of app herstarten) zodat de lijst niet uit lokaal geheugen komt.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,28 @@ type GlobalListEntry = { at: number; data: unknown };
|
|||
const globalLiveListCache = new Map<string, GlobalListEntry>();
|
||||
const globalLiveListRefreshing = new Set<string>();
|
||||
|
||||
/** Leeg alle process-globale XMLTV-caches (na XUI/EPG-wijziging). */
|
||||
export function invalidateGlobalXmltvCaches() {
|
||||
globalXmltvGuides.clear();
|
||||
globalXmltvRefreshing.clear();
|
||||
}
|
||||
|
||||
/** Leeg gecachte live categories/streams. */
|
||||
export function invalidateGlobalLiveListCaches() {
|
||||
globalLiveListCache.clear();
|
||||
globalLiveListRefreshing.clear();
|
||||
}
|
||||
|
||||
export function invalidateAllXtreamGlobalCaches() {
|
||||
invalidateGlobalXmltvCaches();
|
||||
invalidateGlobalLiveListCaches();
|
||||
}
|
||||
|
||||
/** Alias voor Admin “EPG vernieuwen”. */
|
||||
export function invalidateGlobalIptvCaches() {
|
||||
invalidateAllXtreamGlobalCaches();
|
||||
}
|
||||
|
||||
/** Optionele externe rijke feeds (icons/desc), gezet vanuit admin/config. */
|
||||
let externalEpgXmlUrls: string[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
upsertGooglePlaySettings,
|
||||
} from "./google-play";
|
||||
import { getIptvSettings, upsertIptvSettings } from "./iptv";
|
||||
import { listIptvCategoriesForAdmin } from "../viewer/iptv-client";
|
||||
import { listIptvCategoriesForAdmin, refreshIptvEpgNow } from "../viewer/iptv-client";
|
||||
import type { GooglePlayAccessService } from "../google-play/service";
|
||||
|
||||
export function registerSettingsRoutes(
|
||||
|
|
@ -86,4 +86,10 @@ export function registerSettingsRoutes(
|
|||
const categories = await listIptvCategoriesForAdmin(config);
|
||||
return { settings, categories };
|
||||
});
|
||||
|
||||
app.post("/api/v1/admin/settings/iptv/refresh-epg", { preHandler: requireAdmin }, async () => {
|
||||
const result = await refreshIptvEpgNow(config);
|
||||
const categories = await listIptvCategoriesForAdmin(config);
|
||||
return { ok: true, ...result, categories };
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { loadXtreamByLineId, loadXtreamForViewer } from "../iptv/service";
|
|||
import {
|
||||
decodeEpgText,
|
||||
epgToIso,
|
||||
invalidateAllXtreamGlobalCaches,
|
||||
recomputeGuideNowNext,
|
||||
type XtreamClient,
|
||||
type XtreamEpgListing,
|
||||
|
|
@ -387,6 +388,14 @@ type StreamEpgCacheEntry = {
|
|||
};
|
||||
const epgStreamCache = new Map<string, StreamEpgCacheEntry>();
|
||||
|
||||
/** Wis Nu/Straks + detail-EPG caches (na XUI-wijziging). */
|
||||
export function invalidateIptvEpgDerivedCaches() {
|
||||
epgCategoryCache.clear();
|
||||
epgCategoryRefreshing.clear();
|
||||
epgStreamCache.clear();
|
||||
xmltvLineRefreshing.clear();
|
||||
}
|
||||
|
||||
function epgCacheKey(tokenId: string, categoryId?: string): string {
|
||||
return `${tokenId}:${categoryId?.trim() || "__all__"}`;
|
||||
}
|
||||
|
|
@ -819,3 +828,53 @@ export function startIptvEpgWarmup(config: Config) {
|
|||
setTimeout(() => void tick(), 5_000);
|
||||
setInterval(() => void tick(), INTERVAL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: caches legen + XMLTV/lijsten opnieuw ophalen zodat XUI-wijzigingen meteen binnenkomen.
|
||||
*/
|
||||
export async function refreshIptvEpgNow(config: Config): Promise<{
|
||||
lines: number;
|
||||
xmltvOk: number;
|
||||
xmltvFailed: number;
|
||||
}> {
|
||||
invalidateAllXtreamGlobalCaches();
|
||||
invalidateIptvEpgDerivedCaches();
|
||||
await applyIptvEpgUrlsToRuntime(config);
|
||||
|
||||
const lines = await prisma.iptvLine.findMany({
|
||||
where: {
|
||||
enableLive: true,
|
||||
OR: [
|
||||
{ viewerUserId: { not: null } },
|
||||
{ addonToken: { is: { revoked: false } } },
|
||||
],
|
||||
},
|
||||
select: { id: true },
|
||||
take: 12,
|
||||
orderBy: { updatedAt: "desc" },
|
||||
});
|
||||
|
||||
let xmltvOk = 0;
|
||||
let xmltvFailed = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const xt = await loadXtreamByLineId(line.id, config);
|
||||
if (!xt?.line.enableLive) continue;
|
||||
await xt.client.getLiveCategories().catch(() => []);
|
||||
const all = sortChannels(await xt.client.getLiveStreams().catch(() => []));
|
||||
await xt.client.getXmltvGuidesByChannelId(0);
|
||||
if (all.length > 0) {
|
||||
await resolveCategoryEpgTitles(xt.cacheKey, "__all__", all, xt.client, {
|
||||
waitForXmltv: true,
|
||||
});
|
||||
}
|
||||
xmltvOk++;
|
||||
} catch (err) {
|
||||
xmltvFailed++;
|
||||
console.warn("[iptv-epg-refresh]", line.id, err);
|
||||
}
|
||||
}
|
||||
|
||||
return { lines: lines.length, xmltvOk, xmltvFailed };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue