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 }>
|
Array<{ id: string; name: string; channelCount: number }>
|
||||||
>([]);
|
>([]);
|
||||||
const [tvBusy, setTvBusy] = useState(false);
|
const [tvBusy, setTvBusy] = useState(false);
|
||||||
|
const [tvRefreshBusy, setTvRefreshBusy] = useState(false);
|
||||||
const [tvMsg, setTvMsg] = useState<string | null>(null);
|
const [tvMsg, setTvMsg] = useState<string | null>(null);
|
||||||
const [tvErr, setTvErr] = 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|
@ -876,15 +904,27 @@ export default function SettingsPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-form" style={{ marginTop: "1rem" }}>
|
<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"}
|
{tvBusy ? "Bezig…" : "Opslaan"}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={tvBusy} onClick={() => loadTvSettings()}>
|
<button
|
||||||
Vernieuwen
|
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>
|
</button>
|
||||||
{tvMsg && <span className="ok-text">{tvMsg}</span>}
|
{tvMsg && <span className="ok-text">{tvMsg}</span>}
|
||||||
{tvErr && <span className="danger-text">{tvErr}</span>}
|
{tvErr && <span className="danger-text">{tvErr}</span>}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,28 @@ type GlobalListEntry = { at: number; data: unknown };
|
||||||
const globalLiveListCache = new Map<string, GlobalListEntry>();
|
const globalLiveListCache = new Map<string, GlobalListEntry>();
|
||||||
const globalLiveListRefreshing = new Set<string>();
|
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. */
|
/** Optionele externe rijke feeds (icons/desc), gezet vanuit admin/config. */
|
||||||
let externalEpgXmlUrls: string[] = [];
|
let externalEpgXmlUrls: string[] = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
upsertGooglePlaySettings,
|
upsertGooglePlaySettings,
|
||||||
} from "./google-play";
|
} from "./google-play";
|
||||||
import { getIptvSettings, upsertIptvSettings } from "./iptv";
|
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";
|
import type { GooglePlayAccessService } from "../google-play/service";
|
||||||
|
|
||||||
export function registerSettingsRoutes(
|
export function registerSettingsRoutes(
|
||||||
|
|
@ -86,4 +86,10 @@ export function registerSettingsRoutes(
|
||||||
const categories = await listIptvCategoriesForAdmin(config);
|
const categories = await listIptvCategoriesForAdmin(config);
|
||||||
return { settings, categories };
|
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 {
|
import {
|
||||||
decodeEpgText,
|
decodeEpgText,
|
||||||
epgToIso,
|
epgToIso,
|
||||||
|
invalidateAllXtreamGlobalCaches,
|
||||||
recomputeGuideNowNext,
|
recomputeGuideNowNext,
|
||||||
type XtreamClient,
|
type XtreamClient,
|
||||||
type XtreamEpgListing,
|
type XtreamEpgListing,
|
||||||
|
|
@ -387,6 +388,14 @@ type StreamEpgCacheEntry = {
|
||||||
};
|
};
|
||||||
const epgStreamCache = new Map<string, 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 {
|
function epgCacheKey(tokenId: string, categoryId?: string): string {
|
||||||
return `${tokenId}:${categoryId?.trim() || "__all__"}`;
|
return `${tokenId}:${categoryId?.trim() || "__all__"}`;
|
||||||
}
|
}
|
||||||
|
|
@ -819,3 +828,53 @@ export function startIptvEpgWarmup(config: Config) {
|
||||||
setTimeout(() => void tick(), 5_000);
|
setTimeout(() => void tick(), 5_000);
|
||||||
setInterval(() => void tick(), INTERVAL_MS);
|
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