legado书源缓存
This commit is contained in:
@@ -32,6 +32,8 @@ interface StoredManifest {
|
|||||||
const CHUNK_SIZE = Number(process.env.LEGADO_SUBSCRIPTION_CHUNK_SIZE || 100);
|
const CHUNK_SIZE = Number(process.env.LEGADO_SUBSCRIPTION_CHUNK_SIZE || 100);
|
||||||
const TIMEOUT_MS = Number(process.env.LEGADO_SUBSCRIPTION_TIMEOUT_MS || process.env.LEGADO_TIMEOUT_MS || 30000);
|
const TIMEOUT_MS = Number(process.env.LEGADO_SUBSCRIPTION_TIMEOUT_MS || process.env.LEGADO_TIMEOUT_MS || 30000);
|
||||||
const MAX_BYTES = Number(process.env.LEGADO_SUBSCRIPTION_MAX_BYTES || 20 * 1024 * 1024);
|
const MAX_BYTES = Number(process.env.LEGADO_SUBSCRIPTION_MAX_BYTES || 20 * 1024 * 1024);
|
||||||
|
const sourcesCache = new Map<string, BookSource[]>();
|
||||||
|
const sourcesLoadPromises = new Map<string, Promise<BookSource[]>>();
|
||||||
|
|
||||||
function stableId(input: string) {
|
function stableId(input: string) {
|
||||||
return crypto.createHash('sha1').update(input).digest('hex').slice(0, 16);
|
return crypto.createHash('sha1').update(input).digest('hex').slice(0, 16);
|
||||||
@@ -131,6 +133,24 @@ async function readManifest(id: string): Promise<StoredManifest | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function readSourcesFromDb(id: string): Promise<BookSource[]> {
|
||||||
|
const manifest = await readManifest(id);
|
||||||
|
if (!manifest) return [];
|
||||||
|
const chunks = await Promise.all(
|
||||||
|
Array.from({ length: manifest.chunkCount }, async (_, index) => {
|
||||||
|
const raw = await db.getGlobalValue(chunkKey(id, index));
|
||||||
|
if (!raw) return [] as BookSource[];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed) ? (parsed as BookSource[]) : [];
|
||||||
|
} catch {
|
||||||
|
return [] as BookSource[];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return chunks.flat();
|
||||||
|
}
|
||||||
|
|
||||||
export const legadoSubscriptionStore = {
|
export const legadoSubscriptionStore = {
|
||||||
makeId: subscriptionId,
|
makeId: subscriptionId,
|
||||||
|
|
||||||
@@ -163,25 +183,30 @@ export const legadoSubscriptionStore = {
|
|||||||
}
|
}
|
||||||
const manifest: StoredManifest = { id, name, url, hash, sourceCount: sources.length, chunkCount, updatedAt: Date.now(), etag, lastModified };
|
const manifest: StoredManifest = { id, name, url, hash, sourceCount: sources.length, chunkCount, updatedAt: Date.now(), etag, lastModified };
|
||||||
await db.setGlobalValue(manifestKey(id), JSON.stringify(manifest));
|
await db.setGlobalValue(manifestKey(id), JSON.stringify(manifest));
|
||||||
|
sourcesLoadPromises.delete(id);
|
||||||
|
sourcesCache.set(id, sources);
|
||||||
return { id, name, url, enabled: true, sourceCount: sources.length, lastSyncAt: manifest.updatedAt, lastSuccessAt: manifest.updatedAt, lastError: '' };
|
return { id, name, url, enabled: true, sourceCount: sources.length, lastSyncAt: manifest.updatedAt, lastSuccessAt: manifest.updatedAt, lastError: '' };
|
||||||
},
|
},
|
||||||
|
|
||||||
async getSources(id: string): Promise<BookSource[]> {
|
async getSources(id: string): Promise<BookSource[]> {
|
||||||
const manifest = await readManifest(id);
|
const cached = sourcesCache.get(id);
|
||||||
if (!manifest) return [];
|
if (cached) return cached;
|
||||||
const chunks = await Promise.all(
|
|
||||||
Array.from({ length: manifest.chunkCount }, async (_, index) => {
|
const pending = sourcesLoadPromises.get(id);
|
||||||
const raw = await db.getGlobalValue(chunkKey(id, index));
|
if (pending) return pending;
|
||||||
if (!raw) return [] as BookSource[];
|
|
||||||
try {
|
const promise = readSourcesFromDb(id)
|
||||||
const parsed = JSON.parse(raw);
|
.then((sources) => {
|
||||||
return Array.isArray(parsed) ? (parsed as BookSource[]) : [];
|
sourcesCache.set(id, sources);
|
||||||
} catch {
|
sourcesLoadPromises.delete(id);
|
||||||
return [] as BookSource[];
|
return sources;
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
.catch((error) => {
|
||||||
return chunks.flat();
|
sourcesLoadPromises.delete(id);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
sourcesLoadPromises.set(id, promise);
|
||||||
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getSourcesForSubscriptions(subscriptions: LegadoSubscriptionMeta[] = []): Promise<BookSource[]> {
|
async getSourcesForSubscriptions(subscriptions: LegadoSubscriptionMeta[] = []): Promise<BookSource[]> {
|
||||||
@@ -191,6 +216,8 @@ export const legadoSubscriptionStore = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
|
sourcesLoadPromises.delete(id);
|
||||||
|
sourcesCache.delete(id);
|
||||||
const manifest = await readManifest(id);
|
const manifest = await readManifest(id);
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
for (let index = 0; index < manifest.chunkCount; index += 1) {
|
for (let index = 0; index < manifest.chunkCount; index += 1) {
|
||||||
@@ -200,6 +227,16 @@ export const legadoSubscriptionStore = {
|
|||||||
await db.deleteGlobalValue(manifestKey(id));
|
await db.deleteGlobalValue(manifestKey(id));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearCache(id?: string): void {
|
||||||
|
if (id) {
|
||||||
|
sourcesLoadPromises.delete(id);
|
||||||
|
sourcesCache.delete(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sourcesLoadPromises.clear();
|
||||||
|
sourcesCache.clear();
|
||||||
|
},
|
||||||
|
|
||||||
mergeMeta(config: AdminConfig, meta: LegadoSubscriptionMeta): AdminConfig {
|
mergeMeta(config: AdminConfig, meta: LegadoSubscriptionMeta): AdminConfig {
|
||||||
const opds = config.OPDSConfig || { Enabled: false, Sources: [], CacheTTL: 10 * 60 * 1000 };
|
const opds = config.OPDSConfig || { Enabled: false, Sources: [], CacheTTL: 10 * 60 * 1000 };
|
||||||
const list = opds.LegadoSubscriptions || [];
|
const list = opds.LegadoSubscriptions || [];
|
||||||
|
|||||||
Reference in New Issue
Block a user