Перейти к содержанию

SearchEngine

SearchEngine — это синглтон, который владеет реестром provider'ов, кэшем session'ов и логикой диспетчеризации запрос → session. В пределах одной app session существует ровно один экземпляр — он создаётся в App.vue и передаётся дочерним компонентам через provide/inject.

Источник: src/search/engine.ts


Создание

export function createSearchEngine(opts: SearchEngineOptions = {}): SearchEngine {
  const cache = new SessionCache<SearchSession>(opts.cacheMax ?? 30);
  const registry: Record<string, SearchProvider> = {
    rutracker: rutrackerProvider,
    soulseek: soulseekProvider,
  };
  // ...
}

SessionCache — LRU-кэш с ключом "${normalizedQuery}|${enabledProviders}". Это означает, что повторный поиск по тому же запросу с тем же набором включённых provider'ов вернёт существующую живую session, а не запустит новую — полезно при навигации назад.


query()

async function query(
  rawQuery: string,
  enabled: Record<string, boolean>,  // { rutracker: true, soulseek: true }
  queryOpts: { skipResolver?: boolean } = {},
): Promise<SearchSession>

Полный поток выполнения:

rawQuery (например, "нон стоп молли")
normalizeQuery → lowercase trim → cacheKey
  ↓ промах кэша
resolveQuery(rawQuery)  [Tauri IPC → Rust resolver]
  ↓ ResolveResult | null
формирование providerQ:
  canonical == null            → providerQ = rawQuery
  canonical.title == ""        → providerQ = stripBrackets(artist)
  иначе                        → providerQ = "Artist Title" (скобки удалены)
new SearchSession(providerQ, { providers, pipeline, resolved, rawQuery })
cache.set(cacheKey, session)
return session

Удаление скобок

Перед формированием providerQ из канонического результата удаляются скобки:

const stripBrackets = (s: unknown): string =>
  String(s ?? "")
    .replace(/\([^()]*\)/g, "")
    .replace(/\[[^\]]*\]/g, "")
    .replace(/\s{2,}/g, " ")
    .trim();

Это предотвращает отправку запроса "1.Kla$ (Первый класс)" provider'ам — вставка в скобках является аннотацией Genius, а не частью реальной строки поиска.

skipResolver

При skipResolver: true поиск в кэше также пропускается, а необработанный запрос используется напрямую как providerQ. Используется для ручного режима «буквального поиска» в интерфейсе.


Реестр provider'ов

При запуске регистрируются два provider'а:

const registry: Record<string, SearchProvider> = {
  rutracker: rutrackerProvider,
  soulseek:  soulseekProvider,
};

Параметр enabled (из search store, управляемого переключателями в интерфейсе) определяет, какие provider'ы реально выполняются для данного запроса:

const enabledKinds = Object.keys(registry)
  .filter((k) => enabled?.[k])
  .sort();

Сортировка обеспечивает детерминированность ключей кэша независимо от порядка свойств объекта.


Cache

Session'ы кэшируются по ключу "${normalizedQuery}|${sortedEnabledProviders}". Попадание в кэш возвращает существующую session — её генераторы могут ещё выполняться, а вызывающий код получает живое реактивное представление.

Размер кэша по умолчанию — 30. LRU-вытеснение удаляет наименее недавно используемую session при заполнении кэша.

clearCache() вызывается, когда пользователь явно очищает результаты поиска или когда изменяются настройки.