Files
readest/apps/readest-app/src/utils/cache.ts
T
Huang Xin bed31e8181 feat(library): add Manage Cache to advanced settings (#4359)
Add a "Manage Cache" item to the library Advanced Settings menu (native
mobile apps only) that opens a modern dialog showing the combined size and
file count of the app's reclaimable storage, with a confirm-gated clear that
reports per-file progress.

- iOS clears Cache + Temp + Documents/Inbox; Android clears Cache + Temp.
- Multi-source helper (getCacheEntries/getCacheStats/clearCacheEntries) with
  unit tests; per-file failures are counted, never abort the run.
- Dialog uses the centered-hero + btn-contrast design language, theme-neutral
  progress, and is e-ink correct.
- i18n: new strings translated across all locales (+ en plural forms).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 19:57:07 +02:00

85 lines
2.3 KiB
TypeScript

import { AppService, BaseDir } from '@/types/system';
export interface CacheClearProgress {
current: number;
total: number;
currentFile?: string;
}
export interface CacheClearResult {
deleted: number;
failed: number;
}
/** A cache location to scan and clear. */
export interface CacheSource {
base: BaseDir;
/** Directory within `base` to scan; '' for the base root. */
dir: string;
}
/** A single deletable file, with a path usable directly by deleteFile(path, base). */
export interface CacheEntry {
base: BaseDir;
path: string;
size: number;
}
/**
* List every file under the given cache sources as deletable entries. A source
* that can't be read (e.g. an Inbox that doesn't exist yet) simply contributes
* nothing instead of failing the whole scan.
*/
export const getCacheEntries = async (
appService: AppService,
sources: CacheSource[],
): Promise<CacheEntry[]> => {
const entries: CacheEntry[] = [];
for (const source of sources) {
try {
const files = await appService.readDirectory(source.dir, source.base);
for (const file of files) {
entries.push({
base: source.base,
path: source.dir ? `${source.dir}/${file.path}` : file.path,
size: file.size || 0,
});
}
} catch {
// Missing or unreadable source — skip it.
}
}
return entries;
};
/** Total file count and byte size for a set of cache entries. */
export const getCacheStats = (entries: CacheEntry[]): { count: number; size: number } => ({
count: entries.length,
size: entries.reduce((acc, entry) => acc + entry.size, 0),
});
/**
* Delete the given cache entries one at a time, reporting progress before each
* deletion. Individual failures are counted but never abort the loop, so a
* single locked file can't leave the cache half-cleared without feedback.
*/
export const clearCacheEntries = async (
appService: AppService,
entries: CacheEntry[],
onProgress?: (progress: CacheClearProgress) => void,
): Promise<CacheClearResult> => {
let deleted = 0;
let failed = 0;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i]!;
onProgress?.({ current: i + 1, total: entries.length, currentFile: entry.path });
try {
await appService.deleteFile(entry.path, entry.base);
deleted++;
} catch {
failed++;
}
}
return { deleted, failed };
};