General List¶
General List — отладочный реестр, фиксирующий каждый TrackData, который когда-либо видело приложение, вместе с полным снимком состояния всех слоёв кэша на момент регистрации. Это основной инструмент для диагностики промахов кэша, сбоев загрузки обложек и проблем с обогащением метаданных.
Исходники: src/persistence/generalList.ts, src/persistence/generalListCacheProbe.ts, src-tauri/src/general_list.rs
Форма записи¶
interface GeneralListEntry {
id: string;
track: TrackData; // полный TrackData на момент последнего обнаружения
meta: GeneralListMeta;
cacheKeys: GeneralListCacheKeys;
cache: GeneralListCache; // живой снимок кэша, сделанный при регистрации
}
interface GeneralListMeta {
firstSeenAt: number; // epoch ms
lastSeenAt: number;
seenCount: number;
contexts: string[]; // например ["search_results", "player"]
lastContext: string;
lastSearchQuery?: string;
}
seenCount увеличивается при каждом вызове registerEntity для одного и того же track ID. Это позволяет легко обнаружить треки, которые постоянно появляются в результатах поиска при разных запросах.
Двойная запись¶
Каждое сохранение пишет в два места:
- localStorage ключ
neegde.generalList.v1— переживает перезагрузку страницы, запись мгновенная. - Диск через Tauri-команду
general_list_write→{app_data_dir}/general-list.json— переживает перезапуск приложения, доступно из файловой системы.
Обе записи дебоунсируются на 500 мс. Пачка вызовов recordGeneralList (например, поиск вернул 100 строк) инициирует одну запись, а не 100.
flushGeneralList() обходит дебоунс — используется перед закрытием приложения или перед показом файла в Finder/Explorer.
Путь записи: recordGeneralList¶
Вызывается из registerEntity в stores/entities.ts при каждой регистрации трека:
При первом обнаружении — создаёт новую запись с seenCount=1.
При повторном обнаружении — обновляет track (новые данные могут содержать свежеполученный coverUrl), увеличивает seenCount, добавляет context если он новый, обновляет снимок cache.
После записи _scheduleSave() взводит таймер дебоунса.
Обновление кэша: refreshGeneralListCache¶
Вызывается после завершения загрузки обложки или обогащения через Deezer (например, из RutrackerTrack.startCoverFetch, SoulseekTrack.startCoverFetch):
Повторно запускает probeCache для существующей записи, не затрагивая meta. Это обновляет снимок cache для отражения нового состояния обложки — полезно для проверки того, приземлился ли вызов rutracker_get_cover в LRU в памяти.
Ключи кэша: buildCacheKeys¶
buildCacheKeys(data) выводит логический ключ кэша для каждого слоя, который потенциально может хранить данные для этого трека:
interface GeneralListCacheKeys {
trackCacheId: string; // = track.id
rutrackerTopicCover?: string; // "${mirror}\n${topicId}"
deezerCanonical?: string; // нормализованный "artist|title"
deezerAlbumArt?: string; // нормализованный "deezer-album-art|artist|album"
torrentFileB64?: string; // topicId (суффикс ключа localStorage)
streamIdentity?: { magnet?, fileIdx?, slskUsername?, slskFilepath? };
soulseekCover?: string; // "${username}\n${filepath}"
}
Ключи — логические идентификаторы, а не хэшированные идентификаторы слотов. probeCache использует их для поиска в реальном хранилище.
Зондирование кэша: probeCache¶
probeCache(data, keys) делает снимок каждого задействованного кэша в момент вызова:
interface GeneralListCache {
capturedAt: number;
trackCache: CacheEntry; // localStorage "neegde.trackCache.v1"
covers?: {
rutrackerTopic?: CacheEntry; // localStorage LRU "neegde.rtCover.v1.*"
soulseekFile?: CacheEntry; // дисковый кэш Rust "soulseek/covers/"
inlinedOnTrack?: { present, kind }; // data: URL / https URL в 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 — "hit", "miss" или "unknown". Поле payloadHint содержит первые 80 символов payload при попадании (удобно для проверки наличия data: URL вместо пустой строки).
Зондирование SoulSeek cover вызывает peekSlskCover — синхронный peek в память без запуска сетевого запроса. Если обложка ни разу не загружалась или кэш в памяти был сброшен, состояние будет "miss", даже если файл существует на диске.
Tauri-команды¶
| Команда | Назначение |
|---|---|
general_list_write(json) |
Записывает JSON-строку в {app_data_dir}/general-list.json |
general_list_path |
Возвращает абсолютный путь к файлу |
factory_reset |
Удаляет всё содержимое app_data_dir, затем возвращает управление |
factory_reset вызывается через Настройки → Сбросить данные. Удаляет general-list.json вместе со всеми остальными файлами данных (session cookies, обложки, BT-состояние и т.д.).
Открытие файла¶
export async function revealGeneralListFile(): Promise<void> {
const filePath = await invoke<string>("general_list_path");
await openPath(filePath);
}
Вызывает tauri-plugin-opener для открытия родительской папки в Finder/Explorer. Путь к файлу разрешается через Rust, поэтому фронтенд никогда не хардкодит его.