Files
SpotiFLAC-Mobile/rust_backend/crates/mobile/src/repository.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

106 lines
3.0 KiB
Rust

use crate::manager::ExtensionManager;
use spotiflac_extensions::repository::{ExtensionRepository as CoreRepository, RepositoryError};
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum ExtensionRepositoryError {
#[error("{0}")]
Operation(String),
}
impl From<RepositoryError> for ExtensionRepositoryError {
fn from(error: RepositoryError) -> Self {
Self::Operation(error.to_string())
}
}
/// Retains the manager for current installed versions and shares its network
/// policy. Registry and package IO must run on a native background thread.
#[derive(uniffi::Object)]
pub struct ExtensionRepository {
inner: CoreRepository,
manager: Arc<ExtensionManager>,
}
#[uniffi::export]
impl ExtensionRepository {
#[uniffi::constructor]
pub fn new(
manager: Arc<ExtensionManager>,
cache_directory: String,
) -> Result<Self, ExtensionRepositoryError> {
let inner = manager
.inner
.environment()
.repository(Path::new(&cache_directory))?;
Ok(Self { inner, manager })
}
pub fn registry_url(&self) -> Result<String, ExtensionRepositoryError> {
self.inner.registry_url().map_err(Into::into)
}
pub fn set_registry_url(&self, url: String) -> Result<(), ExtensionRepositoryError> {
self.inner.set_registry_url(&url).map_err(Into::into)
}
pub fn clear_registry_url(&self) -> Result<(), ExtensionRepositoryError> {
self.inner.clear_registry_url().map_err(Into::into)
}
pub fn extensions(&self, force_refresh: bool) -> Result<String, ExtensionRepositoryError> {
self.search_inner(force_refresh, "", "")
}
pub fn search(
&self,
query: String,
category: String,
) -> Result<String, ExtensionRepositoryError> {
self.search_inner(false, &query, &category)
}
pub fn categories(&self) -> Result<Vec<String>, ExtensionRepositoryError> {
self.inner.categories().map_err(Into::into)
}
pub fn clear_cache(&self) -> Result<(), ExtensionRepositoryError> {
self.inner.clear_cache().map_err(Into::into)
}
pub fn download(
&self,
extension_id: String,
destination_directory: String,
) -> Result<String, ExtensionRepositoryError> {
self.inner
.download(&extension_id, Path::new(&destination_directory))
.map(|path| path.to_string_lossy().into_owned())
.map_err(Into::into)
}
pub fn shutdown(&self) {
self.inner.shutdown();
}
}
impl ExtensionRepository {
fn search_inner(
&self,
force: bool,
query: &str,
category: &str,
) -> Result<String, ExtensionRepositoryError> {
let installed = self
.manager
.inner
.installed_versions()
.map_err(|e| ExtensionRepositoryError::Operation(e.to_string()))?;
self.inner
.extensions(force, &installed, query, category)
.map_err(Into::into)
}
}