General List¶
The General List is a debug registry that records every TrackData the app has ever seen, together with a full snapshot of every cache layer state at the time of registration. It is the primary tool for diagnosing cache misses, cover loading failures, and enrichment gaps.
Sources: src/persistence/generalList.ts, src/persistence/generalListCacheProbe.ts, src-tauri/src/general_list.rs
Entry shape¶
interface GeneralListEntry {
id: string;
track: TrackData; // full TrackData as of last seen
meta: GeneralListMeta;
cacheKeys: GeneralListCacheKeys;
cache: GeneralListCache; // live cache probe captured at registration
}
interface GeneralListMeta {
firstSeenAt: number; // epoch ms
lastSeenAt: number;
seenCount: number;
contexts: string[]; // e.g. ["search_results", "player"]
lastContext: string;
lastSearchQuery?: string;
}
seenCount increments on every registerEntity call for the same track ID. This makes it easy to spot tracks that keep appearing in search results across multiple queries.
Dual-write storage¶
Every save writes to two locations:
- localStorage key
neegde.generalList.v1— survives page reloads, immediate. - Disk via
general_list_writeTauri command →{app_data_dir}/general-list.json— survives app restarts, searchable from the filesystem.
Both writes are debounced at 500 ms. A burst of recordGeneralList calls (e.g. a search returning 100 rows) triggers one write, not 100.
flushGeneralList() bypasses the debounce — used before the app closes or before revealing the file in Finder/Explorer.
Write path: recordGeneralList¶
Called from registerEntity in stores/entities.ts for every track registration:
On first sight — creates a new entry with seenCount=1.
On re-sight — updates track (new data may carry freshly enriched coverUrl), increments seenCount, appends context if new, refreshes cache snapshot.
After the entry is written, _scheduleSave() arms the debounce timer.
Cache refresh: refreshGeneralListCache¶
Called after a cover fetch or Deezer enrichment completes (e.g. from RutrackerTrack.startCoverFetch, SoulseekTrack.startCoverFetch):
Re-runs probeCache on the existing entry without touching meta. This updates the cache snapshot to reflect the new cover state — useful for seeing whether a rutracker_get_cover call landed in the in-memory LRU.
Cache keys: buildCacheKeys¶
buildCacheKeys(data) derives the logical cache key for every cache layer that could hold data for this track:
interface GeneralListCacheKeys {
trackCacheId: string; // = track.id
rutrackerTopicCover?: string; // "${mirror}\n${topicId}"
deezerCanonical?: string; // normalized "artist|title"
deezerAlbumArt?: string; // "deezer-album-art|artist|album" normalized
torrentFileB64?: string; // topicId (suffix for localStorage key)
streamIdentity?: { magnet?, fileIdx?, slskUsername?, slskFilepath? };
soulseekCover?: string; // "${username}\n${filepath}"
}
Keys are logical identifiers — they are not hashed slot IDs. probeCache uses these to look up the actual storage.
Cache probe: probeCache¶
probeCache(data, keys) snapshots every relevant cache at call time:
interface GeneralListCache {
capturedAt: number;
trackCache: CacheEntry; // localStorage "neegde.trackCache.v1"
covers?: {
rutrackerTopic?: CacheEntry; // localStorage LRU "neegde.rtCover.v1.*"
soulseekFile?: CacheEntry; // Rust disk "soulseek/covers/"
inlinedOnTrack?: { present, kind }; // data: URL / https URL on TrackData.coverUrl
};
enrichment?: {
deezerCanonical?: CacheEntry; // localStorage LRU "neegde.dzTrackCanon.v1.*"
deezerAlbumArt?: CacheEntry; // localStorage LRU "neegde.dzAlbumArt.v1.*"
};
torrent?: {
fileListB64?: CacheEntry; // localStorage "torrent_file_b64_v1_<topicId>"
};
streaming?: {
backendDiskCache: "hit"|"partial"|"unknown"|"na";
note?: string;
};
}
CacheEntry.state is "hit", "miss", or "unknown". The payloadHint field carries the first 80 characters of a hit's payload (useful for confirming a data: URL is present vs. an empty string).
The SoulSeek cover probe calls peekSlskCover — a synchronous in-memory peek that does not trigger a network fetch. If the cover was never fetched or the memory cache was cleared, the state is "miss" even if it exists on disk.
Tauri commands¶
| Command | Purpose |
|---|---|
general_list_write(json) |
Writes the JSON string to {app_data_dir}/general-list.json |
general_list_path |
Returns the absolute path of the file |
factory_reset |
Deletes everything in app_data_dir, then returns |
factory_reset is called by Settings → Reset app data. It deletes general-list.json along with every other data file (session cookies, covers, BT state, etc.).
Revealing the file¶
export async function revealGeneralListFile(): Promise<void> {
const filePath = await invoke<string>("general_list_path");
await openPath(filePath);
}
Calls tauri-plugin-opener to open the parent directory in Finder/Explorer. The file path is resolved from Rust so the frontend never hardcodes it.