Files
SpotiFLAC-Mobile/rust_backend/crates/extensions/src/provider.rs
T
zarzet aa99726439 feat(backend): migrate production backend to Rust
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.
2026-09-14 22:58:27 +07:00

80 lines
2.7 KiB
Rust

//! Native numeric and album-quality rules used while reading provider objects.
pub(crate) fn integer(value: f64, wide: bool) -> String {
let integer = value as i64;
if wide {
integer.to_string()
} else {
(integer as isize).to_string()
}
}
pub(crate) fn audio_traits(qualities: Vec<String>, modes: Vec<String>) -> Vec<String> {
let mut atmos = false;
let mut hires = false;
let mut lossless = false;
for (quality, mode) in qualities.iter().zip(&modes) {
// Go uses one-rune uppercase mappings, so expanding ß to SS must not
// accidentally make an unrecognized label match LOSSLESS.
let upper = |value: &str| {
value
.chars()
.map(|character| {
let mut mapped = character.to_uppercase();
let first = mapped.next().unwrap();
if mapped.next().is_none() {
first
} else {
character
}
})
.collect::<String>()
};
let quality = upper(quality);
atmos |= upper(mode).contains("ATMOS") || quality.contains("ATMOS");
hires |= ["HI_RES", "HIRES", "MASTER", "MQA"]
.iter()
.any(|part| quality.contains(part));
lossless |= quality.contains("LOSSLESS") || quality.contains("FLAC");
let lower = spotiflac_core::matching::lowercase(&quality);
let numeric_prefix = |suffix: &str, decimal: bool| {
lower
.find(suffix)
.map(|end| {
let mut start = end;
while start > 0
&& (lower.as_bytes()[start - 1].is_ascii_digit()
|| (decimal && lower.as_bytes()[start - 1] == b'.'))
{
start -= 1;
}
&lower[start..end]
})
.unwrap_or("")
};
let depth = numeric_prefix("bit", false).parse::<isize>().unwrap_or(0);
let rate = numeric_prefix("khz", true)
.parse::<f64>()
.ok()
.filter(|value| value.is_finite())
.unwrap_or(0.0);
if depth > 0 {
if depth > 16 || rate > 48.0 {
hires = true;
} else {
lossless = true;
}
}
}
let mut traits = Vec::new();
if atmos {
traits.push("dolby_atmos".into());
}
if hires {
traits.push("hi_res_lossless".into());
} else if lossless {
traits.push("lossless".into());
}
traits
}