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

Поток данных

На этой странице прослеживается движение данных от пользовательского ввода до аудиовыхода с указанием слоёв, преобразующих данные, и мест хранения состояния.


Поиск → entities

Пользователь вводит запрос
  ▼ stores/search.ts: runSearch(query)
  ▼ search/engine.ts: engine.query(query, enabledProviders)
  │   ├── resolver.ts: invoke("resolve_query") → ResolveResult
  │   │     (Brave Search → парсинг заголовков Genius → canonical (artist, title))
  │   └── new SearchSession(providerQuery, providers)
  ▼ SearchSession запускает provider-ы параллельно:
  ├── RutrackerProvider.search(query)
  │     ├── invoke("rutracker_search") → строки topic-ов
  │     ├── для каждого topic: invoke("rutracker_get_torrent_details") → файлы
  │     ├── detectAlbums(files) → группы альбомов
  │     └── отправка snapshot-ов PipelineEntity[]
  └── SoulseekProvider.search(query)
        ├── invoke("soulseek_search") → запускает поиск на бэкенде
        ├── listen("soulseek-search-batch") → batch-ы от peer-ов
        └── groupSlskRowsToEntities(raw) → отправка snapshot-ов PipelineEntity[]
  ▼ SearchSession._flushImmediate() (на каждом rAF)
  │   объединение snapshot-ов → runPipeline([normalize, dedup, score, filter])
  │   → session.results.value обновляется
  ▼ stores/search.ts: watch(session.results) → _applyFromSession()
  │   registerEntities(entities) → реестр stores/entities.ts
  │   searchEntities.value = зарегистрированные entities
  ▼ Results.vue: читает searchEntities → рендерит карточки альбомов/треков

Клик для воспроизведения

Пользователь кликает трек
  ▼ stores/queue.ts: enqueue / set position
  ▼ Player.vue: watch(nowPlaying)
  ▼ torrent/api.ts: streamUrl(magnet, fileIdx, opts)
  ├── [путь RuTracker]
  │   _cachedTorrentFileB64(topicId) → base64 .torrent (из cache или dl.php)
  │   invoke("torrent_prepare_stream", { magnet, fileIdx, torrentFileB64 })
  │   → Rust: VozduxanStreamState.prepare()
  │       проверка версии → отмена старого token → вызов vozduxan C++ → предбуферизация
  │   → "http://127.0.0.1:<port>/stream/<token>"
  └── [путь SoulSeek]
      soulseekPrepareStream(username, filepath, filesize)
      → invoke("soulseek_prepare_stream")
      → Rust: download_and_stream()
          P connection → handshake → F connection → download_loop
          http_server_loop на 127.0.0.1:0
      → "http://127.0.0.1:<port>/slsk/<token>"
  ▼ <audio src={url} />
  ├── timeupdate → invoke("vozduxan_notify_position", { token, byteOffset })
  │               → C++: сдвигает окно приоритетов кусков
  ├── прогресс ~22% / 30с → prefetchNextInQueue(current, next)
  │   → invoke("torrent_prefetch_next_track", { ..., warmOnly: false })
  │     сохраняется в bucket prefetch_token
  └── трек закончился / пропуск
      invoke("torrent_release_stream", { token })
      → C++: ожидает завершения потока приоритетов (~100мс), помечает stream как idle

Сохранение лайков

Пользователь нажимает кнопку лайка
  ▼ stores/library.ts: likes.toggleTrack(trackId)
  │   likes.trackIds.value.add/delete(trackId)
  │   likes.likedAt.value.set(trackId, Date.now())
  │   saveLikesSnapshot() → localStorage "neegde.likes.v2"
  ▼ Перезапуск приложения
      loadLikesSnapshot() → likes.trackIds восстановлены
      hydrateTrack(id) для каждого понравившегося id ← trackCache (localStorage)
      или появляется снова при следующем поиске → registerEntity

Сохранение очереди

Любое изменение очереди (добавление/удаление/переупорядочение)
  saveQueueSnapshot() → localStorage "neegde.queue.v2": { ids, pos }
  Перезапуск приложения
  ▼ stores/queue.ts: seedFromSnapshot()
      queue.ids = snapshot.ids
      queue.pos = snapshot.pos
      suppressAutoplay = true  ← без HTML-автовоспроизведения при восстановлении
      hydrateTrack(id) для каждого id ← trackCache

Поток данных реестра entities

Provider отправляет PipelineEntity[]
  registerEntities(entities) в stores/entities.ts
  для каждого entity:
    withMergedPersisted(entity)    ← объединение coverUrl/albumTitle из trackCache
    normalize(entity)              ← buildTrack() или buildAlbum()
    _byId.value.set(id, instance)
    если track: putTrack() → trackCache (localStorage)
    если track: recordGeneralList() → general-list.json (debug-реестр)
  bump(): triggerRef(_byId) + entitiesVersion.value++
  Computed-сигналы инвалидируются:
    - queue.nowPlaying, .next, .secondNext
    - queue.tracks
    - library.likedTracks, .likedAlbums
    - любой компонент, читающий entitiesVersion.value

Сводка по владению состоянием

Данные Местонахождение Сохранение
Реестр entities stores/entities._byId trackCache / albumCache в localStorage
Очередь stores/queue.playbackQueue neegde.queue.v2 в localStorage
Понравившиеся треки/альбомы stores/library.likes neegde.likes.v2 в localStorage
Плейлисты stores/library.library neegde.playlists.v2 в localStorage
Результаты поиска stores/search.searchEntities Не сохраняются (только в сессии)
Активные stream token-ы vozduxan_stream.rs VozduxanSessionInner Не сохраняются (в памяти процесса)
SoulSeek stream-ы soulseek/mod.rs SoulSeekState.streams Не сохраняются (временные файлы в OS tmp)
Сессия RuTracker rutracker/mod.rs + session.json + meta.json На диске (относительно исполняемого файла)
Сессия SoulSeek soulseek/mod.rs Session creds.json (только учётные данные, без сессии)
Cache файлов .torrent torrent/api.ts _torrentFileCache localStorage torrent_file_b64_v1_*
Лог отладки torrent_stream/debug_log.rs AppDebugLog app_debug.json (только флаг включения)