display files with cache image
This commit is contained in:
@@ -3,58 +3,113 @@ import { LocalFileEntry, SyncStatus } from '../types';
|
||||
|
||||
const storage = createMMKV({ id: 'vaultdrop-local-files' });
|
||||
|
||||
const INDEX_KEY = 'local_files_index';
|
||||
const BLOB_KEY = 'local_files_v2';
|
||||
const LEGACY_INDEX_KEY = 'local_files_index';
|
||||
|
||||
function getAllIds(): string[] {
|
||||
const raw = storage.getString(INDEX_KEY);
|
||||
if (!raw) return [];
|
||||
return JSON.parse(raw) as string[];
|
||||
interface RegistryBlob {
|
||||
entries: Record<string, LocalFileEntry>;
|
||||
backendIndex: Record<string, string>;
|
||||
}
|
||||
|
||||
function setAllIds(ids: string[]) {
|
||||
storage.set(INDEX_KEY, JSON.stringify(ids));
|
||||
let memoryCache: RegistryBlob | null = null;
|
||||
|
||||
function loadBlob(): RegistryBlob {
|
||||
if (memoryCache) return memoryCache;
|
||||
|
||||
const raw = storage.getString(BLOB_KEY);
|
||||
if (raw) {
|
||||
memoryCache = JSON.parse(raw) as RegistryBlob;
|
||||
return memoryCache;
|
||||
}
|
||||
|
||||
memoryCache = migrateFromLegacy();
|
||||
saveBlob(memoryCache);
|
||||
return memoryCache;
|
||||
}
|
||||
|
||||
function entryKey(id: string): string {
|
||||
return `local_file_${id}`;
|
||||
function saveBlob(blob: RegistryBlob) {
|
||||
memoryCache = blob;
|
||||
storage.set(BLOB_KEY, JSON.stringify(blob));
|
||||
}
|
||||
|
||||
function migrateFromLegacy(): RegistryBlob {
|
||||
const blob: RegistryBlob = { entries: {}, backendIndex: {} };
|
||||
|
||||
const rawIndex = storage.getString(LEGACY_INDEX_KEY);
|
||||
if (!rawIndex) return blob;
|
||||
|
||||
const ids: string[] = JSON.parse(rawIndex);
|
||||
for (const id of ids) {
|
||||
const raw = storage.getString(`local_file_${id}`);
|
||||
if (!raw) continue;
|
||||
const entry: LocalFileEntry = JSON.parse(raw);
|
||||
blob.entries[entry.id] = entry;
|
||||
if (entry.backendFileId) {
|
||||
blob.backendIndex[entry.backendFileId] = entry.id;
|
||||
}
|
||||
}
|
||||
|
||||
storage.remove(LEGACY_INDEX_KEY);
|
||||
for (const id of ids) {
|
||||
storage.remove(`local_file_${id}`);
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
export const localFileRegistry = {
|
||||
register(entry: LocalFileEntry) {
|
||||
storage.set(entryKey(entry.id), JSON.stringify(entry));
|
||||
const ids = getAllIds();
|
||||
if (!ids.includes(entry.id)) {
|
||||
setAllIds([entry.id, ...ids]);
|
||||
const blob = loadBlob();
|
||||
blob.entries[entry.id] = entry;
|
||||
if (entry.backendFileId) {
|
||||
blob.backendIndex[entry.backendFileId] = entry.id;
|
||||
}
|
||||
saveBlob(blob);
|
||||
},
|
||||
|
||||
registerBatch(entries: LocalFileEntry[]) {
|
||||
if (entries.length === 0) return;
|
||||
const blob = loadBlob();
|
||||
for (const entry of entries) {
|
||||
blob.entries[entry.id] = entry;
|
||||
if (entry.backendFileId) {
|
||||
blob.backendIndex[entry.backendFileId] = entry.id;
|
||||
}
|
||||
}
|
||||
saveBlob(blob);
|
||||
},
|
||||
|
||||
get(id: string): LocalFileEntry | undefined {
|
||||
const raw = storage.getString(entryKey(id));
|
||||
if (!raw) return undefined;
|
||||
return JSON.parse(raw) as LocalFileEntry;
|
||||
return loadBlob().entries[id];
|
||||
},
|
||||
|
||||
getByBackendId(backendId: string): LocalFileEntry | undefined {
|
||||
const ids = getAllIds();
|
||||
for (const id of ids) {
|
||||
const entry = this.get(id);
|
||||
if (entry?.backendFileId === backendId) return entry;
|
||||
}
|
||||
return undefined;
|
||||
const blob = loadBlob();
|
||||
const entryId = blob.backendIndex[backendId];
|
||||
if (!entryId) return undefined;
|
||||
return blob.entries[entryId];
|
||||
},
|
||||
|
||||
getAll(): LocalFileEntry[] {
|
||||
const ids = getAllIds();
|
||||
return ids
|
||||
.map((id) => this.get(id))
|
||||
.filter((e): e is LocalFileEntry => e !== undefined);
|
||||
const blob = loadBlob();
|
||||
return Object.values(blob.entries);
|
||||
},
|
||||
|
||||
update(id: string, updates: Partial<LocalFileEntry>) {
|
||||
const existing = this.get(id);
|
||||
const blob = loadBlob();
|
||||
const existing = blob.entries[id];
|
||||
if (!existing) return;
|
||||
|
||||
if (existing.backendFileId && updates.backendFileId === undefined && updates.syncStatus === 'cloud') {
|
||||
delete blob.backendIndex[existing.backendFileId];
|
||||
}
|
||||
|
||||
const updated = { ...existing, ...updates };
|
||||
storage.set(entryKey(id), JSON.stringify(updated));
|
||||
blob.entries[id] = updated;
|
||||
if (updated.backendFileId) {
|
||||
blob.backendIndex[updated.backendFileId] = id;
|
||||
}
|
||||
saveBlob(blob);
|
||||
},
|
||||
|
||||
updateSyncStatus(id: string, syncStatus: SyncStatus) {
|
||||
@@ -66,17 +121,26 @@ export const localFileRegistry = {
|
||||
},
|
||||
|
||||
remove(id: string) {
|
||||
storage.remove(entryKey(id));
|
||||
const ids = getAllIds().filter((i) => i !== id);
|
||||
setAllIds(ids);
|
||||
const blob = loadBlob();
|
||||
const entry = blob.entries[id];
|
||||
if (entry?.backendFileId) {
|
||||
delete blob.backendIndex[entry.backendFileId];
|
||||
}
|
||||
delete blob.entries[id];
|
||||
saveBlob(blob);
|
||||
},
|
||||
|
||||
removeByBackendId(backendId: string) {
|
||||
const entry = this.getByBackendId(backendId);
|
||||
if (entry) this.remove(entry.id);
|
||||
const blob = loadBlob();
|
||||
const entryId = blob.backendIndex[backendId];
|
||||
if (entryId) {
|
||||
delete blob.entries[entryId];
|
||||
delete blob.backendIndex[backendId];
|
||||
saveBlob(blob);
|
||||
}
|
||||
},
|
||||
|
||||
count(): number {
|
||||
return getAllIds().length;
|
||||
return Object.keys(loadBlob().entries).length;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { createMMKV } from 'react-native-mmkv';
|
||||
import { FileItem } from '../types';
|
||||
|
||||
const storage = createMMKV({ id: 'vaultdrop-metadata' });
|
||||
|
||||
const FILES_KEY = 'backend_files_cache';
|
||||
const UPDATED_AT_KEY = 'cache_updated_at';
|
||||
const STALE_MS = 5 * 60 * 1000;
|
||||
|
||||
interface CachedFiles {
|
||||
files: FileItem[];
|
||||
page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const metadataCache = {
|
||||
getFiles(): CachedFiles | null {
|
||||
const raw = storage.getString(FILES_KEY);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return JSON.parse(raw) as CachedFiles;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
setFiles(files: FileItem[], page: number, total: number) {
|
||||
const data: CachedFiles = { files, page, total };
|
||||
storage.set(FILES_KEY, JSON.stringify(data));
|
||||
storage.set(UPDATED_AT_KEY, Date.now());
|
||||
},
|
||||
|
||||
isStale(): boolean {
|
||||
const raw = storage.getString(UPDATED_AT_KEY);
|
||||
if (!raw) return true;
|
||||
const updatedAt = Number(raw);
|
||||
return Date.now() - updatedAt > STALE_MS;
|
||||
},
|
||||
|
||||
clear() {
|
||||
storage.remove(FILES_KEY);
|
||||
storage.remove(UPDATED_AT_KEY);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { createMMKV } from 'react-native-mmkv';
|
||||
|
||||
const storage = createMMKV({ id: 'vaultdrop-thumbnails' });
|
||||
|
||||
const BLOB_KEY = 'thumbnail_urls';
|
||||
const STALE_MS = 50 * 60 * 1000;
|
||||
|
||||
interface ThumbnailCacheEntry {
|
||||
url: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
let memoryCache: Record<string, ThumbnailCacheEntry> | null = null;
|
||||
|
||||
function loadCache(): Record<string, ThumbnailCacheEntry> {
|
||||
if (memoryCache) return memoryCache;
|
||||
const raw = storage.getString(BLOB_KEY);
|
||||
memoryCache = raw ? JSON.parse(raw) : {};
|
||||
return memoryCache!;
|
||||
}
|
||||
|
||||
function saveCache(cache: Record<string, ThumbnailCacheEntry>) {
|
||||
memoryCache = cache;
|
||||
storage.set(BLOB_KEY, JSON.stringify(cache));
|
||||
}
|
||||
|
||||
export const thumbnailCache = {
|
||||
get(fileId: string): string | null {
|
||||
const cache = loadCache();
|
||||
const entry = cache[fileId];
|
||||
if (!entry) return null;
|
||||
if (Date.now() > entry.expiresAt) {
|
||||
delete cache[fileId];
|
||||
saveCache(cache);
|
||||
return null;
|
||||
}
|
||||
return entry.url;
|
||||
},
|
||||
|
||||
set(fileId: string, url: string, expiresAt: number) {
|
||||
const cache = loadCache();
|
||||
cache[fileId] = { url, expiresAt };
|
||||
saveCache(cache);
|
||||
},
|
||||
|
||||
setBatch(entries: Array<{ fileId: string; url: string; expiresAt: number }>) {
|
||||
if (entries.length === 0) return;
|
||||
const cache = loadCache();
|
||||
for (const e of entries) {
|
||||
cache[e.fileId] = { url: e.url, expiresAt: e.expiresAt };
|
||||
}
|
||||
saveCache(cache);
|
||||
},
|
||||
|
||||
remove(fileId: string) {
|
||||
const cache = loadCache();
|
||||
delete cache[fileId];
|
||||
saveCache(cache);
|
||||
},
|
||||
|
||||
clear() {
|
||||
memoryCache = {};
|
||||
storage.remove(BLOB_KEY);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user