More than half of the crate files had no module doc, so a contributor had to
read a large file top to bottom to learn what it owns. Every file over 300
lines now states its responsibility in one or two lines.
Keep one copy of helpers that were implemented several times, each proven
equivalent to the copies it replaces by a test that runs the removed
implementation (kept inline in the test module) against the survivor:
- Go-style lexical path clean: new `core::fs::clean`, used by
`isrc::native_files` and re-exported as `extensions::files::clean`.
- Dart `String.trim` whitespace: `path_keys::dart_whitespace` and
`dart_trim` now back the lyrics parser's explicit character list and the
playlist importer's private `trim`; the test compares every Unicode
scalar value.
- `normalized()` (Dart trim then simple lowercase): the native backup
importer uses the identical `native_library::normalized`.
- Lowercase hex encoding: `spotiflac_core::lower_hex` replaces the per-byte
`format!` collectors in data_jobs and network_tags, the signed-session
`hex` and the `hex` arm of `binary::encode`; checked for all 256 bytes.
- ID3 syncsafe decode: the tag writer's `size` reuses the reader's
`tags::id3::syncsafe`; checked across the 0x7F/0x80 boundaries per byte.
- Bounded body reads: `HttpStream::read_limited` replaces the read-to-limit
loops of the lyrics HTTP client, the resolver HTTP client and the
extension registry reader. Callers keep their buffer sizes (16 KiB,
16 KiB, 64 KiB), their own error mapping and their messages; a loopback
test compares it with the previous loop at and around the limit.
The mobile ID3 `synchsafe` is not merged: it validates the length and the
high bits and returns `None`, unlike the core decoder.
client.rs held the connection pool, the streaming response type, header
helpers, tests and a 274-line execute_stream in one 1220-line file (audit
RS-15). Move the self-contained parts into client/ and break
execute_stream into phase helpers so the redirect loop reads top-down.
Pure moves (verbatim apart from pub(super) on items the parent uses):
- client/pool.rs: Connector and Pool.
- client/stream.rs: ResponseBody and HttpStream (re-exported from client,
so crate::HttpStream is unchanged).
- client/headers.rs: update_explicit_cookies, forward_sensitive_headers
and canonical_header. error_chain stays in client.rs; it is not a
header helper.
- client/tests.rs: the existing unit tests.
execute_stream helpers (base line ranges refer to main:client.rs). Each
body is the statements it replaces; the loop, await points, error
mapping, redirect limit and header mutations stay in the same order:
- prepare_request: 720-769, request validation, method, body, headers,
user agent, default JSON content type and explicit referer, returned
as PreparedRequest. Only the mut markers moved to the call site.
- outgoing_headers: 773-802, per-attempt cookie header and gzip
negotiation, returning (outgoing, gzip). header(&url) became
header(url) because url is now a reference.
- send: 803-845, the request, its 45 s header timeout and the native
HTTPS-to-HTTP fallback retry. The permit is passed by value and
returned, so drop(permit) still runs before the retry acquires a new
one. matches!(method, ..) became matches!(*method, ..).
- redirect_target: 862-869, Location resolution, credential rejection
and redirect policy validation, returning the target.
- release_redirect_body: 906-928, the 20 ms drain of a small HTTP/1.1
redirect body.
- response_reader: 932-959, discard eligibility and gzip decoder setup,
returning (headers, small_http1, reader).
- canonical_headers: 960-970, canonical response header map.
The redirect request rewrite (871-905) stays inline: it updates the
loop-carried method, body and headers and reads six other values, so a
helper would need nine parameters or a state struct that rewrites every
statement in the loop.
hyper is not a direct dependency, so the response type is named through
the pooled client's tower Service implementation (HyperResponse).
No public path or signature changes.
NetworkService::set_network_compatibility_options ignored the
insecure_tls flag by design because TLS verification is never disabled.
Remove the parameter from the network service and the extension
environment so the dead flag no longer travels through internal layers.
The UniFFI method keeps its signature: Kotlin and Swift still pass the
flag, and the mobile binding accepts and ignores it until the binding
and channel cleanup lands separately.
Rust is the only backend and routes the app on Android and iOS. Rewrite
the migration-era crate, script and comment wording that implied Go still
runs, and note in each crate doc that Go mentions describe the retired
backend whose observable behavior remains a compatibility contract.
Remove tokio from spotiflac-extensions and thiserror and the direct hyper
entry from spotiflac-network (hyper stays resolved through hyper-util).
Move roxmltree, rusqlite and libc into workspace.dependencies. libc already
resolves to 0.2.190, so the workspace pin uses that exact version. No
resolved version or enabled feature changes.
Replace the Go backend with the Rust workspace and route Android/iOS through
UniFFI bindings. Include the migrated extension runtime, network, providers,
media metadata, downloads and library operations, with version 5.0.0+147.
Remove Go sources, adapters, native build selection and CI dependencies.
Build Rust unconditionally and lock the iOS Rust pod using a relative path.
Retain legacy source and migration evidence in a local ignored archive.
Validation: Android Kotlin compile and 53 native tests; Swift Rust-branch
equivalence and syntax; CocoaPods install; workflow, shell, Ruby, plist and
diff checks. Reuse the preceding 100 Rust tests, fmt/Clippy and five-ABI
build checkpoint; no new APK or iOS application build for this cleanup.
Known follow-up: URL/URLSearchParams globals are missing from the Rust JS
runtime. A controlled extension replay confirms a URL-resolution regression;
this commit does not fix that runtime gap.