Files
SoulStream/src-tauri/Cargo.toml
KeelrLeek 515b3da71f android auto: the tree under the root — four tabs read cold out of library.db
Round forty-nine put SoulStream in the car with an empty root. This fills it.
`onGetChildren` answers the four tabs Kyle picked — Recent · Albums · Artists ·
Saved — and everything under them: albums with their artist and track count,
artists grouped the way the app groups them, an album's tracks in disc and
track order, and every local file under Saved. `src-tauri/src/auto_browse.rs`
is one JNI export over `library.db`, and `AutoNative` in the plugin is the
Kotlin half.

**It reads read-only and it never calls `Library::open`.** That constructor
runs `PRAGMA journal_mode = WAL` and a `CREATE TABLE IF NOT EXISTS` batch —
writes, both of them — and a service that takes a write lock on the library to
draw a list is a bug waiting for a long drive. The path is derived the way
`config::download_dir` derives it, but without it: that function takes a
`tauri::AppHandle` and there is no Tauri in this process, so `config.json` is
parsed read-only for `download_dir` and the default is `<dataDir>/Library`,
with the database under `.soulstream/` as the portability rule requires.

**`AutoNative` calls `System.loadLibrary("app_lib")` itself, and that is the
round's other half.** When Auto binds a cold app there is no Activity, so
nothing has loaded the Rust side and none of it exists. Loading it here also
runs `JNI_OnLoad`, which publishes the JavaVM and the app Context to
`ndk-context` — the same hand-off the audio backend needs, arriving before
there is any audio, which is what round fifty-one stands on. `loadLibrary` is
idempotent, so it costs nothing when the app is already up.

Two things a head unit taught that no unit test would have. **The root must not
depend on the database.** The four tabs are a constant, and gating the whole
dispatch on a connection meant one unreadable library cost the car its entire
structure — it read as "Media isn't available for this list", which names
neither the tabs nor the reason. A library that cannot be read should cost the
ROWS, never the shape. And **`log::warn!` goes nowhere in a cold service
process**: nothing has run Tauri, so no `android_logger` is installed, and the
first failure here was completely silent. It writes to the platform log
directly now, which is the only reason the second failure had a cause attached.

`open_read_only` falls back to a read-write handle it immediately sets
`query_only` on. A read-only connection to a WAL-mode database needs the `-shm`
beside it and cannot create one, and the mode lives in the header, so it does
not go away when the sidecars do. This has NOT been observed to bite — the one
failure that looked like it was SELinux, below — but the fallback is cheap and
the connection still cannot change a row. `CREATE` is in neither flag set, so a
missing database stays an error rather than becoming an empty one.

Verified twice. The prober walks it cold on the phone AVD: 12 nodes, 139
leaves, 2.26 s, with the album tracklists in the right order and both artists
carrying the right albums. And it is on an Android Automotive head unit —
`ss_car`, API 34 — showing the four tabs, eleven Recent rows and five albums
with their counts, which is the first time any of this work has been seen
rendered by a car rather than described.

**Not done, and deliberately not guessed at.** There is no artwork: the plan
called for cover art through the existing FileProvider via a `files-path`
entry, but the library lives under the app's DATA dir and `files-path` maps to
`getFilesDir()`, which does not reach it — so the mechanism needs deciding, not
patching. And the rows are all lists; the grid Kyle chose for albums is a
content-style hint that is not set yet. Both are presentation over a tree that
is now correct, and both want the car in front of them.
2026-09-15 10:05:39 -05:00

115 lines
4.4 KiB
TOML

[package]
name = "app"
version.workspace = true
description = "SoulStream — music player, downloader and streamer for the Soulseek network"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dev-dependencies]
# Import-apply tests build a whole source/pen/library bed on disk; already in
# the lock file via the dependency tree, so this adds no download.
tempfile = "3"
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-log = "2"
tauri-plugin-dialog = "2"
serde.workspace = true
serde_json.workspace = true
log.workspace = true
image.workspace = true
tokio.workspace = true
base64 = "0.22"
bincode = "1"
bs58 = "0.5"
rand = "0.8"
# Shazam signature packets (shazam.rs) carry a CRC-32 of their body.
crc32fast = "1"
# Inflate for the deno release zip (ytdlp.rs) — already in the lock via the
# image stack, so this adds no download.
flate2 = "1"
# STFT/iSTFT for the vocal separator (vocal_sep.rs) — pure Rust, so the
# no-child-processes charter holds; wraps rustfft with real transforms.
realfft = "3"
# XSPF playlist import (playlist_formats.rs). quick-xml never resolves DTDs
# or external entities, which is exactly the safety posture the importer wants.
quick-xml = "0.37"
keyring = { version = "3", features = ["windows-native"] }
sysinfo = "0.33"
soulseek-proto = { path = "../crates/soulseek-proto" }
soulseek-client = { path = "../crates/soulseek-client" }
freenet-adapter = { path = "../crates/freenet-adapter" }
persona-state = { path = "../crates/persona-state" }
player = { path = "../crates/player" }
tagger = { path = "../crates/tagger" }
metadata = { path = "../crates/metadata" }
library = { path = "../crates/library" }
audio-formats = { path = "../crates/audio-formats" }
# The phone's lock-screen card and background playback (a Kotlin foreground
# service). Registered everywhere so the permission set resolves on every
# platform; on the desktop its commands answer "not here".
tauri-plugin-media-session = { path = "../crates/tauri-plugin-media-session" }
tauri-plugin-storage = { path = "../crates/tauri-plugin-storage" }
tauri-plugin-share = { path = "../crates/tauri-plugin-share" }
# The phone's downloads notice (a second Kotlin foreground service, dataSync).
# Registered everywhere so the permission set resolves on every platform; on
# the desktop its commands answer "not here".
tauri-plugin-transfer-notice = { path = "../crates/tauri-plugin-transfer-notice" }
semver = "1"
tauri-plugin-opener = "2.5.5"
# LRCLIB lyrics client (lyrics.rs). Already in the tree via the metadata crate.
# Desktop keeps the platform TLS (schannel on Windows); Android has no OpenSSL
# to build against, so it takes rustls with the bundled roots instead.
[target.'cfg(not(target_os = "android"))'.dependencies]
reqwest = { version = "0.12", features = ["json"] }
# Release checks and the silent install are a desktop story; the phone gets
# its builds from the store or a sideload, and the plugin is desktop-only.
tauri-plugin-updater = "2"
[target.'cfg(target_os = "android")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
# JNI_OnLoad (android_glue.rs): hands the JavaVM and the app Context to
# ndk-context, which cpal's AAudio backend reads and tao 0.35 never fills.
jni = "0.21"
ndk-context = "0.1"
# auto_browse.rs opens library.db READ-ONLY from the media service's process,
# where there is no AppHandle and so no reaching the `library` crate's own
# handle. Same version and features as crates/library so cargo builds sqlite
# once for both.
rusqlite = { version = "0.32", features = ["bundled"] }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.3", features = [
"AI_MachineLearning",
"Foundation_Collections",
"Globalization",
"Graphics_Imaging",
# SystemMediaTransportControls — the OS media card (see src/smtc.rs).
"Media",
"Media_FaceAnalysis",
"Media_Ocr",
"Storage_Streams",
"Win32_Foundation",
# Taskbar thumbnail toolbar — ITaskbarList3 + the window subclass that
# hears its clicks, and the GDI/registry bits that draw its icons.
"Win32_Graphics_Gdi",
"Win32_System_Com",
"Win32_System_Registry",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Performance",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
"Win32_System_WinRT",
] }