mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
feat: persist webtorrent probes and tracker health checks to localstorage
- Add storage helpers for torrent probes and tracker health in `js/utils/storage.js` - Update `js/gridHealth.js` to read/write torrent probes from localStorage - Update `js/healthService.js` to read/write tracker health from localStorage - Ensure TTLs (5m for probes, 2m for trackers) are respected across sessions - Add verification test `tests/verify-storage-persistence.mjs` (removed before completion)
This commit is contained in:
1 parent
3de038dc08
commit
f06b2c7b1d
3 files changed
+158
-4
No files matched your search
+24
-2
@@ -3,6 +3,11 @@ import { infoHashFromMagnet } from "./magnets.js";
|
||||
import { updateVideoCardSourceVisibility } from "./utils/cardSourceVisibility.js";
|
||||
import { TorrentClient, torrentClient } from "./webtorrent.js";
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
readTorrentProbeFromStorage,
|
||||
removeTorrentProbeFromStorage,
|
||||
writeTorrentProbeToStorage,
|
||||
} from "./utils/storage.js";
|
||||
|
||||
const badgeUpdateListeners = new Set();
|
||||
|
||||
@@ -33,14 +38,29 @@ function getCacheEntry(key) {
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
const entry = probeCache.get(key);
|
||||
let entry = probeCache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
// Try storage if memory miss
|
||||
entry = readTorrentProbeFromStorage(key);
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify freshness
|
||||
if (now() - entry.ts > PROBE_CACHE_TTL_MS) {
|
||||
probeCache.delete(key);
|
||||
removeTorrentProbeFromStorage(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Populate memory if it came from storage
|
||||
if (!probeCache.has(key)) {
|
||||
probeCache.set(key, entry);
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
@@ -48,7 +68,9 @@ function setCacheEntry(key, value) {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
probeCache.set(key, { ts: now(), value });
|
||||
const entry = { ts: now(), value };
|
||||
probeCache.set(key, entry);
|
||||
writeTorrentProbeToStorage(key, entry);
|
||||
}
|
||||
|
||||
function formatTime(ts) {
|
||||
|
||||
+20
-2
@@ -3,6 +3,11 @@ import { trackerPing } from "./trackerPing.js";
|
||||
import { infoHashFromMagnet } from "./magnets.js";
|
||||
import { HEALTH_TTL_MS, CONCURRENCY } from "./trackerConfig.js";
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
readTrackerHealthFromStorage,
|
||||
removeTrackerHealthFromStorage,
|
||||
writeTrackerHealthToStorage,
|
||||
} from "./utils/storage.js";
|
||||
|
||||
const queue = new PQueue({ concurrency: CONCURRENCY });
|
||||
const cache = new Map();
|
||||
@@ -22,14 +27,25 @@ export function getHealthCached(infoHash) {
|
||||
if (!infoHash) {
|
||||
return null;
|
||||
}
|
||||
const entry = cache.get(infoHash);
|
||||
let entry = cache.get(infoHash);
|
||||
|
||||
if (!entry) {
|
||||
entry = readTrackerHealthFromStorage(infoHash);
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() - entry.ts > HEALTH_TTL_MS) {
|
||||
cache.delete(infoHash);
|
||||
removeTrackerHealthFromStorage(infoHash);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cache.has(infoHash)) {
|
||||
cache.set(infoHash, entry);
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
@@ -37,7 +53,9 @@ export function setHealthCache(infoHash, value) {
|
||||
if (!infoHash) {
|
||||
return;
|
||||
}
|
||||
cache.set(infoHash, { ts: Date.now(), value });
|
||||
const entry = { ts: Date.now(), value };
|
||||
cache.set(infoHash, entry);
|
||||
writeTrackerHealthToStorage(infoHash, entry);
|
||||
}
|
||||
|
||||
export function queueHealthCheck(magnet, onResult) {
|
||||
|
||||
@@ -2,11 +2,21 @@ import { userLogger } from "./logger.js";
|
||||
// js/utils/storage.js
|
||||
|
||||
const URL_HEALTH_STORAGE_PREFIX = "bitvid:urlHealth:";
|
||||
const TORRENT_PROBE_STORAGE_PREFIX = "bitvid:torrentProbe:";
|
||||
const TRACKER_HEALTH_STORAGE_PREFIX = "bitvid:trackerHealth:";
|
||||
|
||||
export function getUrlHealthStorageKey(eventId) {
|
||||
return `${URL_HEALTH_STORAGE_PREFIX}${eventId}`;
|
||||
}
|
||||
|
||||
export function getTorrentProbeStorageKey(infoHash) {
|
||||
return `${TORRENT_PROBE_STORAGE_PREFIX}${infoHash}`;
|
||||
}
|
||||
|
||||
export function getTrackerHealthStorageKey(infoHash) {
|
||||
return `${TRACKER_HEALTH_STORAGE_PREFIX}${infoHash}`;
|
||||
}
|
||||
|
||||
export function readUrlHealthFromStorage(eventId) {
|
||||
if (!eventId || typeof localStorage === "undefined") {
|
||||
return null;
|
||||
@@ -55,3 +65,107 @@ export function removeUrlHealthFromStorage(eventId) {
|
||||
userLogger.warn(`Failed to remove URL health for ${eventId}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export function readTorrentProbeFromStorage(infoHash) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(getTorrentProbeStorageKey(infoHash));
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === "object") {
|
||||
return parsed;
|
||||
}
|
||||
} catch (err) {
|
||||
userLogger.warn(
|
||||
`Failed to parse stored torrent probe for ${infoHash}:`,
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
removeTorrentProbeFromStorage(infoHash);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function writeTorrentProbeToStorage(infoHash, entry) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
getTorrentProbeStorageKey(infoHash),
|
||||
JSON.stringify(entry)
|
||||
);
|
||||
} catch (err) {
|
||||
userLogger.warn(`Failed to persist torrent probe for ${infoHash}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTorrentProbeFromStorage(infoHash) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.removeItem(getTorrentProbeStorageKey(infoHash));
|
||||
} catch (err) {
|
||||
userLogger.warn(`Failed to remove torrent probe for ${infoHash}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export function readTrackerHealthFromStorage(infoHash) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(getTrackerHealthStorageKey(infoHash));
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === "object") {
|
||||
return parsed;
|
||||
}
|
||||
} catch (err) {
|
||||
userLogger.warn(
|
||||
`Failed to parse stored tracker health for ${infoHash}:`,
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
removeTrackerHealthFromStorage(infoHash);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function writeTrackerHealthToStorage(infoHash, entry) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
getTrackerHealthStorageKey(infoHash),
|
||||
JSON.stringify(entry)
|
||||
);
|
||||
} catch (err) {
|
||||
userLogger.warn(`Failed to persist tracker health for ${infoHash}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTrackerHealthFromStorage(infoHash) {
|
||||
if (!infoHash || typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.removeItem(getTrackerHealthStorageKey(infoHash));
|
||||
} catch (err) {
|
||||
userLogger.warn(`Failed to remove tracker health for ${infoHash}:`, err);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user