- Create playlist_track_custom_order table for storing user-defined track positions - Add database functions: get, init, set, move track, has, clear custom order - Add Tauri commands for frontend access - Migration-safe for existing databases cc
831 lines
36 KiB
Rust
831 lines
36 KiB
Rust
//! QBZ-NIX: Native high-fidelity streaming client for Linux
|
|
//!
|
|
//! This application uses the Qobuz API but is not certified by Qobuz.
|
|
|
|
pub mod api;
|
|
pub mod api_cache;
|
|
pub mod audio;
|
|
pub mod cache;
|
|
pub mod cast;
|
|
pub mod commands;
|
|
pub mod config;
|
|
pub mod credentials;
|
|
pub mod discogs;
|
|
pub mod offline_cache;
|
|
pub mod flatpak;
|
|
pub mod lastfm;
|
|
pub mod library;
|
|
pub mod listenbrainz;
|
|
pub mod lyrics;
|
|
pub mod media_controls;
|
|
pub mod musicbrainz;
|
|
pub mod network;
|
|
pub mod offline;
|
|
pub mod playback_context;
|
|
pub mod player;
|
|
pub mod playlist_import;
|
|
pub mod queue;
|
|
pub mod reco_store;
|
|
pub mod radio_engine;
|
|
pub mod session_store;
|
|
pub mod share;
|
|
pub mod tray;
|
|
|
|
use std::sync::Arc;
|
|
use tauri::{Emitter, Manager};
|
|
use tokio::sync::Mutex;
|
|
|
|
use api::QobuzClient;
|
|
use cache::{AudioCache, PlaybackCache};
|
|
use lastfm::LastFmClient;
|
|
use media_controls::{MediaControlsManager, TrackInfo};
|
|
use playback_context::ContextManager;
|
|
use player::Player;
|
|
use queue::QueueManager;
|
|
use share::SongLinkClient;
|
|
|
|
/// Application state shared across commands
|
|
pub struct AppState {
|
|
pub client: Arc<Mutex<QobuzClient>>,
|
|
pub player: Player,
|
|
pub queue: QueueManager,
|
|
pub context: ContextManager,
|
|
pub media_controls: MediaControlsManager,
|
|
pub audio_cache: Arc<AudioCache>,
|
|
pub lastfm: Arc<Mutex<LastFmClient>>,
|
|
pub songlink: SongLinkClient,
|
|
}
|
|
|
|
impl AppState {
|
|
pub fn new() -> Self {
|
|
Self::with_device_and_settings(None, config::audio_settings::AudioSettings::default())
|
|
}
|
|
|
|
pub fn with_device(device_name: Option<String>) -> Self {
|
|
Self::with_device_and_settings(device_name, config::audio_settings::AudioSettings::default())
|
|
}
|
|
|
|
pub fn with_device_and_settings(
|
|
device_name: Option<String>,
|
|
audio_settings: config::audio_settings::AudioSettings,
|
|
) -> Self {
|
|
// Create playback cache (L2 - disk, 500MB)
|
|
let playback_cache = match PlaybackCache::new(500 * 1024 * 1024) {
|
|
Ok(cache) => Some(Arc::new(cache)),
|
|
Err(e) => {
|
|
log::warn!("Failed to create playback cache: {}. Disk spillover disabled.", e);
|
|
None
|
|
}
|
|
};
|
|
|
|
// Create audio cache (L1 - memory, 300MB) with optional disk spillover
|
|
let audio_cache = if let Some(pc) = playback_cache {
|
|
Arc::new(AudioCache::with_playback_cache(300 * 1024 * 1024, pc))
|
|
} else {
|
|
Arc::new(AudioCache::default())
|
|
};
|
|
|
|
Self {
|
|
client: Arc::new(Mutex::new(QobuzClient::default())),
|
|
player: Player::new(device_name, audio_settings),
|
|
queue: QueueManager::new(),
|
|
context: ContextManager::new(),
|
|
media_controls: MediaControlsManager::new(),
|
|
audio_cache,
|
|
lastfm: Arc::new(Mutex::new(LastFmClient::default())),
|
|
songlink: SongLinkClient::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for AppState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Update MPRIS metadata when track changes
|
|
pub fn update_media_controls_metadata(
|
|
media_controls: &MediaControlsManager,
|
|
title: &str,
|
|
artist: &str,
|
|
album: &str,
|
|
duration_secs: Option<u64>,
|
|
cover_url: Option<String>,
|
|
) {
|
|
let track_info = TrackInfo {
|
|
title: title.to_string(),
|
|
artist: artist.to_string(),
|
|
album: album.to_string(),
|
|
duration_secs,
|
|
cover_url,
|
|
};
|
|
media_controls.set_metadata(&track_info);
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
// Load .env file if present (for development)
|
|
// Silently ignore if not found (production builds use compile-time env vars)
|
|
dotenvy::dotenv().ok();
|
|
|
|
// Initialize logging
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
|
|
.format_timestamp_millis()
|
|
.init();
|
|
|
|
log::info!("QBZ starting...");
|
|
|
|
// Migrate data from old App ID if needed
|
|
match flatpak::migrate_app_id_data() {
|
|
Ok(true) => log::info!("App ID migration completed successfully"),
|
|
Ok(false) => log::debug!("No App ID migration needed"),
|
|
Err(e) => log::error!("App ID migration failed: {}", e),
|
|
}
|
|
|
|
// Initialize library state
|
|
let library_state = library::init_library_state()
|
|
.expect("Failed to initialize library database");
|
|
|
|
// Initialize casting state (Chromecast, DLNA, AirPlay)
|
|
// CastState creates the media server, DLNA shares it
|
|
let cast_state = cast::CastState::new()
|
|
.expect("Failed to initialize Chromecast state");
|
|
let dlna_state = cast::dlna::commands::DlnaState::new(cast_state.media_server.clone())
|
|
.expect("Failed to initialize DLNA state");
|
|
// AirPlay state - DISABLED until RAOP implementation is complete
|
|
// See qbz-nix-docs/AIRPLAY_IMPLEMENTATION_STATUS.md for details
|
|
// let airplay_state = cast::airplay::commands::AirPlayState::new()
|
|
// .expect("Failed to initialize AirPlay state");
|
|
|
|
// Initialize offline cache state
|
|
let offline_cache_state = offline_cache::OfflineCacheState::new()
|
|
.expect("Failed to initialize offline cache");
|
|
// Initialize lyrics cache state
|
|
let lyrics_state = lyrics::LyricsState::new()
|
|
.expect("Failed to initialize lyrics cache");
|
|
// Initialize recommendation store state
|
|
let reco_state = reco_store::RecoState::new()
|
|
.expect("Failed to initialize recommendation store");
|
|
// Initialize API cache state
|
|
let api_cache_state = api_cache::ApiCacheState::new()
|
|
.expect("Failed to initialize API cache");
|
|
// Initialize session store state
|
|
let session_store_state = session_store::SessionStoreState::new()
|
|
.expect("Failed to initialize session store");
|
|
// Initialize audio settings state
|
|
let audio_settings_state = config::audio_settings::AudioSettingsState::new()
|
|
.expect("Failed to initialize audio settings");
|
|
// Initialize download settings state
|
|
let download_settings_state = config::download_settings::create_download_settings_state()
|
|
.expect("Failed to initialize download settings");
|
|
// Initialize offline mode state
|
|
let offline_state = offline::OfflineState::new()
|
|
.expect("Failed to initialize offline state");
|
|
// Initialize playback preferences state
|
|
let playback_prefs_state = config::playback_preferences::PlaybackPreferencesState::new()
|
|
.expect("Failed to initialize playback preferences");
|
|
// Initialize favorites preferences state
|
|
let favorites_prefs_state = config::favorites_preferences::FavoritesPreferencesState::new()
|
|
.expect("Failed to initialize favorites preferences");
|
|
// Initialize tray settings state
|
|
let tray_settings_state = config::tray_settings::TraySettingsState::new()
|
|
.expect("Failed to initialize tray settings");
|
|
// Initialize subscription validity tracking (for offline download compliance)
|
|
let subscription_state = config::create_subscription_state()
|
|
.expect("Failed to initialize subscription state");
|
|
// Initialize MusicBrainz integration state
|
|
let musicbrainz_state = musicbrainz::MusicBrainzSharedState::new()
|
|
.expect("Failed to initialize MusicBrainz state");
|
|
// Initialize ListenBrainz integration state
|
|
let listenbrainz_state = listenbrainz::ListenBrainzSharedState::new()
|
|
.expect("Failed to initialize ListenBrainz state");
|
|
// Initialize remote metadata state (for Tag Editor service integration)
|
|
let remote_metadata_state = commands::RemoteMetadataSharedState {
|
|
inner: Arc::new(Mutex::new(library::remote_metadata::RemoteMetadataState::new(
|
|
Some(Arc::new(musicbrainz::MusicBrainzSharedState::new()
|
|
.expect("Failed to initialize MusicBrainz for remote metadata")))
|
|
))),
|
|
};
|
|
|
|
// Read saved audio device and settings for player initialization
|
|
let (saved_device, audio_settings) = audio_settings_state
|
|
.store
|
|
.lock()
|
|
.ok()
|
|
.and_then(|store| {
|
|
store.get_settings().ok().map(|settings| {
|
|
(settings.output_device.clone(), settings)
|
|
})
|
|
})
|
|
.unwrap_or_else(|| {
|
|
log::info!("No saved audio settings found, using defaults");
|
|
(None, config::audio_settings::AudioSettings::default())
|
|
});
|
|
|
|
if let Some(ref device) = saved_device {
|
|
log::info!("Initializing player with saved device: {}", device);
|
|
}
|
|
log::info!(
|
|
"Audio settings: exclusive_mode={}, dac_passthrough={}, sample_rate={:?}",
|
|
audio_settings.exclusive_mode,
|
|
audio_settings.dac_passthrough,
|
|
audio_settings.preferred_sample_rate
|
|
);
|
|
|
|
// Read tray settings for initialization
|
|
let tray_settings = tray_settings_state.get_settings().unwrap_or_default();
|
|
log::info!(
|
|
"Tray settings: enable={}, minimize_to_tray={}, close_to_tray={}",
|
|
tray_settings.enable_tray,
|
|
tray_settings.minimize_to_tray,
|
|
tray_settings.close_to_tray
|
|
);
|
|
|
|
// Clone settings for use in closures
|
|
let enable_tray = tray_settings.enable_tray;
|
|
let close_to_tray = tray_settings.close_to_tray;
|
|
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_opener::init())
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.plugin(tauri_plugin_clipboard_manager::init())
|
|
.manage(AppState::with_device_and_settings(saved_device, audio_settings))
|
|
.setup(move |app| {
|
|
// Initialize system tray icon (only if enabled)
|
|
if enable_tray {
|
|
if let Err(e) = tray::init_tray(app.handle()) {
|
|
log::error!("Failed to initialize tray icon: {}", e);
|
|
}
|
|
} else {
|
|
log::info!("System tray icon disabled by user setting");
|
|
}
|
|
|
|
// Initialize media controls (MPRIS) now that we have an AppHandle
|
|
app.state::<AppState>()
|
|
.media_controls
|
|
.init(app.handle().clone());
|
|
|
|
// Purge offline downloads if the subscription has been invalid for > 3 days
|
|
// (compliance requirement for cached content).
|
|
let app_handle = app.handle().clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
let now = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs() as i64;
|
|
|
|
let subscription_state = app_handle
|
|
.state::<config::SubscriptionStateState>()
|
|
.inner()
|
|
.clone();
|
|
let should_purge = {
|
|
let store = match subscription_state.lock() {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
log::warn!("Subscription state lock error: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
match store.should_purge_offline_cache(now) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
log::warn!("Failed to evaluate purge condition: {}", e);
|
|
false
|
|
}
|
|
}
|
|
};
|
|
|
|
if !should_purge {
|
|
return;
|
|
}
|
|
|
|
log::warn!("Subscription invalid for >3 days. Purging offline cache.");
|
|
let cache_state = app_handle.state::<offline_cache::OfflineCacheState>();
|
|
let library_state = app_handle.state::<crate::library::commands::LibraryState>();
|
|
if let Err(e) = offline_cache::commands::purge_all_cached_files(cache_state.inner(), library_state.inner()).await {
|
|
log::error!("Failed to purge offline cache: {}", e);
|
|
return;
|
|
}
|
|
|
|
let store = match subscription_state.lock() {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
log::warn!("Subscription state lock error: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
if let Err(e) = store.mark_offline_cache_purged(now) {
|
|
log::warn!("Failed to persist purge timestamp: {}", e);
|
|
}
|
|
});
|
|
|
|
// Start background task to emit playback events
|
|
let app_handle = app.handle().clone();
|
|
let player_state = app.state::<AppState>().player.state.clone();
|
|
|
|
std::thread::spawn(move || {
|
|
let mut last_position: u64 = 0;
|
|
let mut last_is_playing: bool = false;
|
|
let mut last_track_id: u64 = 0;
|
|
|
|
loop {
|
|
// Check playing/track state first to determine sleep duration
|
|
let is_playing = player_state.is_playing();
|
|
let track_id = player_state.current_track_id();
|
|
|
|
// Adaptive polling:
|
|
// - fast (250ms) when playing
|
|
// - slow (1000ms) when paused/stopped with a track loaded
|
|
// - very slow (5000ms) when no track is loaded (idle)
|
|
let sleep_duration = if is_playing {
|
|
std::time::Duration::from_millis(250)
|
|
} else if track_id == 0 {
|
|
std::time::Duration::from_millis(5000)
|
|
} else {
|
|
std::time::Duration::from_millis(1000)
|
|
};
|
|
std::thread::sleep(sleep_duration);
|
|
|
|
// Re-check after sleep (state might have changed)
|
|
let is_playing = player_state.is_playing();
|
|
let position = player_state.current_position();
|
|
let duration = player_state.duration();
|
|
let track_id = player_state.current_track_id();
|
|
let volume = player_state.volume();
|
|
|
|
// Only emit if state changed or position advanced
|
|
let should_emit = track_id != 0 && (
|
|
is_playing != last_is_playing
|
|
|| track_id != last_track_id
|
|
|| (is_playing && position != last_position)
|
|
);
|
|
|
|
let should_update_mpris = should_emit || (track_id == 0 && last_track_id != 0);
|
|
|
|
if should_emit {
|
|
let event = player::PlaybackEvent {
|
|
is_playing,
|
|
position,
|
|
duration,
|
|
track_id,
|
|
volume,
|
|
};
|
|
let _ = app_handle.emit("playback:state", &event);
|
|
last_position = position;
|
|
last_is_playing = is_playing;
|
|
last_track_id = track_id;
|
|
}
|
|
|
|
if should_update_mpris {
|
|
let media_controls = &app_handle.state::<AppState>().media_controls;
|
|
if track_id == 0 {
|
|
media_controls.set_stopped();
|
|
} else {
|
|
media_controls.set_playback_with_progress(is_playing, position);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
})
|
|
.on_window_event(move |window, event| {
|
|
// Handle close to tray
|
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
|
if close_to_tray {
|
|
log::info!("Close to tray: hiding window instead of closing");
|
|
let _ = window.hide();
|
|
api.prevent_close();
|
|
} else {
|
|
// Cleanup cast devices on actual close
|
|
log::info!("App closing: cleaning up cast devices");
|
|
|
|
// Disconnect Chromecast if connected (sends message through channel)
|
|
if let Some(cast_state) = window.app_handle().try_state::<cast::CastState>() {
|
|
log::info!("Disconnecting Chromecast on app exit");
|
|
cast_state.chromecast.disconnect();
|
|
}
|
|
|
|
// Note: DLNA connection will be dropped when the app exits,
|
|
// which will naturally close the connection. The tokio Mutex
|
|
// prevents us from synchronously stopping playback here.
|
|
log::info!("DLNA connection will be cleaned up on drop");
|
|
}
|
|
}
|
|
})
|
|
.manage(library_state)
|
|
.manage(cast_state)
|
|
.manage(dlna_state)
|
|
// .manage(airplay_state) // AirPlay DISABLED
|
|
.manage(offline_cache_state)
|
|
.manage(lyrics_state)
|
|
.manage(reco_state)
|
|
.manage(api_cache_state)
|
|
.manage(session_store_state)
|
|
.manage(audio_settings_state)
|
|
.manage(download_settings_state)
|
|
.manage(subscription_state)
|
|
.manage(offline_state)
|
|
.manage(playback_prefs_state)
|
|
.manage(favorites_prefs_state)
|
|
.manage(tray_settings_state)
|
|
.manage(musicbrainz_state)
|
|
.manage(listenbrainz_state)
|
|
.manage(remote_metadata_state)
|
|
.invoke_handler(tauri::generate_handler![
|
|
// Auth commands
|
|
commands::init_client,
|
|
commands::login,
|
|
commands::logout,
|
|
commands::is_logged_in,
|
|
commands::get_user_info,
|
|
commands::set_api_locale,
|
|
// Credential persistence commands
|
|
commands::has_saved_credentials,
|
|
commands::save_credentials,
|
|
commands::clear_saved_credentials,
|
|
commands::auto_login,
|
|
// Search commands
|
|
commands::search_albums,
|
|
commands::search_tracks,
|
|
commands::search_artists,
|
|
commands::search_all,
|
|
commands::get_album,
|
|
commands::get_featured_albums,
|
|
commands::get_track,
|
|
commands::get_artist,
|
|
commands::get_artist_detail,
|
|
commands::get_artist_albums,
|
|
commands::get_similar_artists,
|
|
commands::get_label,
|
|
// Playback commands
|
|
commands::play_track,
|
|
commands::prefetch_track,
|
|
commands::pause_playback,
|
|
commands::resume_playback,
|
|
commands::stop_playback,
|
|
commands::set_volume,
|
|
commands::seek,
|
|
commands::get_playback_state,
|
|
commands::set_media_metadata,
|
|
commands::get_audio_devices,
|
|
commands::get_audio_output_status,
|
|
commands::get_pipewire_sinks,
|
|
commands::debug_get_cpal_devices,
|
|
commands::set_pipewire_default_sink,
|
|
commands::reinit_audio_device,
|
|
commands::get_hardware_audio_status,
|
|
// Queue commands
|
|
commands::add_to_queue,
|
|
commands::add_to_queue_next,
|
|
commands::add_tracks_to_queue,
|
|
commands::set_queue,
|
|
commands::clear_queue,
|
|
commands::remove_from_queue,
|
|
commands::move_queue_track,
|
|
commands::get_current_queue_track,
|
|
commands::peek_next_track,
|
|
commands::next_track,
|
|
commands::previous_track,
|
|
commands::play_queue_index,
|
|
commands::set_shuffle,
|
|
commands::get_shuffle,
|
|
commands::set_repeat,
|
|
commands::get_repeat,
|
|
commands::get_queue_state,
|
|
// Radio engine commands
|
|
commands::create_artist_radio,
|
|
commands::create_track_radio,
|
|
commands::refill_radio_queue,
|
|
commands::get_queue_remaining,
|
|
commands::create_infinite_radio,
|
|
// Playback context commands
|
|
commands::get_playback_context,
|
|
commands::set_playback_context,
|
|
commands::clear_playback_context,
|
|
commands::has_playback_context,
|
|
// Playlist commands
|
|
commands::get_user_playlists,
|
|
commands::get_playlist,
|
|
commands::search_playlists,
|
|
commands::create_playlist,
|
|
commands::delete_playlist,
|
|
commands::add_tracks_to_playlist,
|
|
commands::remove_tracks_from_playlist,
|
|
commands::update_playlist,
|
|
commands::get_tracks_by_ids,
|
|
commands::get_current_user_id,
|
|
commands::subscribe_playlist,
|
|
commands::get_track_info,
|
|
commands::get_album_credits,
|
|
// Playlist import commands
|
|
commands::playlist_import_preview,
|
|
commands::playlist_import_execute,
|
|
// Favorites commands
|
|
commands::get_favorites,
|
|
commands::add_favorite,
|
|
commands::remove_favorite,
|
|
// Notification commands
|
|
commands::show_track_notification,
|
|
commands::show_notification,
|
|
// Cache commands
|
|
commands::get_cache_stats,
|
|
commands::clear_cache,
|
|
commands::clear_artist_cache,
|
|
// Last.fm commands
|
|
commands::lastfm_has_embedded_credentials,
|
|
commands::lastfm_has_credentials,
|
|
commands::lastfm_set_credentials,
|
|
commands::lastfm_is_authenticated,
|
|
commands::lastfm_get_auth_url,
|
|
commands::lastfm_open_auth_url,
|
|
commands::lastfm_authenticate,
|
|
commands::lastfm_set_session,
|
|
commands::lastfm_disconnect,
|
|
commands::lastfm_scrobble,
|
|
commands::lastfm_now_playing,
|
|
// Share commands
|
|
commands::share_track_songlink,
|
|
commands::share_album_songlink,
|
|
commands::get_qobuz_track_url,
|
|
commands::get_qobuz_album_url,
|
|
commands::get_qobuz_artist_url,
|
|
// Local library commands
|
|
library::commands::library_add_folder,
|
|
library::commands::library_remove_folder,
|
|
library::commands::library_get_folders,
|
|
library::commands::library_get_folders_with_metadata,
|
|
library::commands::library_get_folder,
|
|
library::commands::library_update_folder_settings,
|
|
library::commands::library_set_folder_enabled,
|
|
library::commands::library_update_folder_path,
|
|
library::commands::library_check_folder_accessible,
|
|
library::commands::library_scan,
|
|
library::commands::library_scan_folder,
|
|
library::commands::library_get_scan_progress,
|
|
library::commands::library_stop_scan,
|
|
library::commands::library_get_albums,
|
|
library::commands::library_get_album_tracks,
|
|
library::commands::library_get_artists,
|
|
library::commands::library_search,
|
|
library::commands::library_get_stats,
|
|
library::commands::library_clear,
|
|
library::commands::library_get_track,
|
|
library::commands::get_track_by_path,
|
|
library::commands::library_play_track,
|
|
library::commands::library_get_tracks_by_ids,
|
|
// Playlist local settings commands
|
|
library::commands::playlist_get_settings,
|
|
library::commands::playlist_save_settings,
|
|
library::commands::playlist_set_sort,
|
|
library::commands::playlist_set_artwork,
|
|
library::commands::playlist_add_local_track,
|
|
library::commands::playlist_remove_local_track,
|
|
library::commands::playlist_get_local_tracks,
|
|
library::commands::playlist_get_local_tracks_with_position,
|
|
library::commands::playlist_get_all_local_track_counts,
|
|
library::commands::playlist_clear_local_tracks,
|
|
// Playlist management commands
|
|
library::commands::playlist_get_all_settings,
|
|
library::commands::playlist_set_hidden,
|
|
library::commands::playlist_set_favorite,
|
|
library::commands::playlist_get_favorites,
|
|
library::commands::playlist_set_position,
|
|
library::commands::playlist_reorder,
|
|
library::commands::playlist_get_stats,
|
|
library::commands::playlist_get_all_stats,
|
|
library::commands::playlist_increment_play_count,
|
|
// Playlist custom order commands
|
|
library::commands::playlist_get_custom_order,
|
|
library::commands::playlist_init_custom_order,
|
|
library::commands::playlist_set_custom_order,
|
|
library::commands::playlist_move_track,
|
|
library::commands::playlist_has_custom_order,
|
|
library::commands::playlist_clear_custom_order,
|
|
// Playlist folders commands
|
|
library::commands::create_playlist_folder,
|
|
library::commands::get_playlist_folders,
|
|
library::commands::update_playlist_folder,
|
|
library::commands::delete_playlist_folder,
|
|
library::commands::reorder_playlist_folders,
|
|
library::commands::move_playlist_to_folder,
|
|
// Discogs artwork commands
|
|
library::commands::discogs_has_credentials,
|
|
library::commands::discogs_search_artist,
|
|
library::commands::discogs_search_artwork,
|
|
library::commands::discogs_download_artwork,
|
|
// Thumbnail commands
|
|
library::commands::library_get_thumbnail,
|
|
library::commands::library_clear_thumbnails,
|
|
library::commands::library_get_thumbnails_cache_size,
|
|
// Artwork commands
|
|
library::commands::library_fetch_missing_artwork,
|
|
library::commands::library_fetch_album_artwork,
|
|
library::commands::library_set_album_artwork,
|
|
// Album settings commands
|
|
library::commands::library_get_album_settings,
|
|
library::commands::library_set_album_hidden,
|
|
library::commands::library_update_album_metadata,
|
|
library::commands::library_write_album_metadata_to_files,
|
|
library::commands::library_refresh_album_metadata_from_files,
|
|
library::commands::library_get_hidden_albums,
|
|
library::commands::library_backfill_downloads,
|
|
// Artist images commands
|
|
library::commands::library_get_artist_image,
|
|
library::commands::library_get_artist_images,
|
|
library::commands::library_cache_artist_image,
|
|
library::commands::library_set_custom_artist_image,
|
|
library::commands::library_get_canonical_names,
|
|
// Playlist local content analysis commands (offline mode)
|
|
library::commands::playlist_analyze_local_content,
|
|
library::commands::playlist_get_local_content_status,
|
|
library::commands::playlist_track_is_local,
|
|
library::commands::playlist_get_local_track_id,
|
|
library::commands::playlist_get_tracks_with_local_copies,
|
|
library::commands::playlist_get_offline_available,
|
|
// Chromecast casting commands
|
|
cast::commands::cast_start_discovery,
|
|
cast::commands::cast_stop_discovery,
|
|
cast::commands::cast_get_devices,
|
|
cast::commands::cast_connect,
|
|
cast::commands::cast_disconnect,
|
|
cast::commands::cast_get_status,
|
|
cast::commands::cast_get_position,
|
|
cast::commands::cast_play_track,
|
|
cast::commands::cast_play_local_track,
|
|
cast::commands::cast_play,
|
|
cast::commands::cast_pause,
|
|
cast::commands::cast_stop,
|
|
cast::commands::cast_seek,
|
|
cast::commands::cast_set_volume,
|
|
// DLNA casting commands
|
|
cast::dlna::commands::dlna_start_discovery,
|
|
cast::dlna::commands::dlna_stop_discovery,
|
|
cast::dlna::commands::dlna_get_devices,
|
|
cast::dlna::commands::dlna_connect,
|
|
cast::dlna::commands::dlna_disconnect,
|
|
cast::dlna::commands::dlna_get_status,
|
|
cast::dlna::commands::dlna_get_position,
|
|
cast::dlna::commands::dlna_play_track,
|
|
cast::dlna::commands::dlna_load_media,
|
|
cast::dlna::commands::dlna_play,
|
|
cast::dlna::commands::dlna_pause,
|
|
cast::dlna::commands::dlna_stop,
|
|
cast::dlna::commands::dlna_seek,
|
|
cast::dlna::commands::dlna_set_volume,
|
|
// AirPlay casting commands - DISABLED until RAOP implementation is complete
|
|
// See docs/AIRPLAY_IMPLEMENTATION_STATUS.md for details
|
|
// cast::airplay::commands::airplay_start_discovery,
|
|
// cast::airplay::commands::airplay_stop_discovery,
|
|
// cast::airplay::commands::airplay_get_devices,
|
|
// cast::airplay::commands::airplay_connect,
|
|
// cast::airplay::commands::airplay_disconnect,
|
|
// cast::airplay::commands::airplay_get_status,
|
|
// cast::airplay::commands::airplay_load_media,
|
|
// cast::airplay::commands::airplay_play,
|
|
// cast::airplay::commands::airplay_pause,
|
|
// cast::airplay::commands::airplay_stop,
|
|
// cast::airplay::commands::airplay_set_volume,
|
|
// Offline cache commands
|
|
offline_cache::commands::cache_track_for_offline,
|
|
offline_cache::commands::is_track_cached,
|
|
offline_cache::commands::get_cached_track_path,
|
|
offline_cache::commands::get_cached_track,
|
|
offline_cache::commands::get_cached_tracks,
|
|
offline_cache::commands::get_offline_cache_stats,
|
|
offline_cache::commands::remove_cached_track,
|
|
offline_cache::commands::clear_offline_cache,
|
|
offline_cache::commands::set_offline_cache_limit,
|
|
offline_cache::commands::open_offline_cache_folder,
|
|
offline_cache::commands::open_album_folder,
|
|
offline_cache::commands::open_track_folder,
|
|
offline_cache::commands::check_album_fully_cached,
|
|
offline_cache::commands::check_offline_root_mounted,
|
|
offline_cache::commands::validate_offline_path,
|
|
offline_cache::commands::move_offline_cache_to_path,
|
|
offline_cache::commands::detect_legacy_cached_files,
|
|
offline_cache::commands::start_legacy_migration,
|
|
offline_cache::commands::sync_offline_cache_to_library,
|
|
// Lyrics commands
|
|
lyrics::commands::lyrics_get,
|
|
lyrics::commands::lyrics_get_cache_stats,
|
|
lyrics::commands::lyrics_clear_cache,
|
|
// Recommendation store commands
|
|
reco_store::commands::reco_log_event,
|
|
reco_store::commands::reco_get_home,
|
|
reco_store::commands::reco_train_scores,
|
|
reco_store::commands::reco_get_home_ml,
|
|
reco_store::commands::get_playlist_suggestions,
|
|
// Session persistence commands
|
|
session_store::save_session_state,
|
|
session_store::load_session_state,
|
|
session_store::save_session_volume,
|
|
session_store::save_session_position,
|
|
session_store::save_session_playback_mode,
|
|
session_store::clear_session,
|
|
// Audio settings commands
|
|
config::audio_settings::get_audio_settings,
|
|
config::audio_settings::set_audio_output_device,
|
|
config::audio_settings::set_audio_exclusive_mode,
|
|
config::audio_settings::set_audio_dac_passthrough,
|
|
config::audio_settings::set_audio_sample_rate,
|
|
config::audio_settings::set_audio_backend_type,
|
|
config::audio_settings::set_audio_alsa_plugin,
|
|
config::audio_settings::set_audio_alsa_hardware_volume,
|
|
config::audio_settings::set_audio_stream_first_track,
|
|
config::audio_settings::set_audio_stream_buffer_seconds,
|
|
// Audio backend commands
|
|
commands::get_available_backends,
|
|
commands::get_devices_for_backend,
|
|
commands::get_alsa_plugins,
|
|
// Download settings commands
|
|
config::download_settings::get_download_settings,
|
|
config::download_settings::set_download_root,
|
|
config::download_settings::set_show_downloads_in_library,
|
|
config::download_settings::validate_download_root,
|
|
// Offline mode commands
|
|
offline::commands::get_offline_status,
|
|
offline::commands::get_offline_settings,
|
|
offline::commands::set_manual_offline,
|
|
offline::commands::set_show_partial_playlists,
|
|
offline::commands::set_allow_cast_while_offline,
|
|
// Playback preferences commands
|
|
config::playback_preferences::get_playback_preferences,
|
|
config::playback_preferences::set_autoplay_mode,
|
|
config::playback_preferences::set_show_context_icon,
|
|
config::favorites_preferences::get_favorites_preferences,
|
|
config::favorites_preferences::save_favorites_preferences,
|
|
offline::commands::set_allow_immediate_scrobbling,
|
|
offline::commands::set_allow_accumulated_scrobbling,
|
|
offline::commands::set_show_network_folders_in_manual_offline,
|
|
offline::commands::check_network,
|
|
// Offline playlist sync queue commands
|
|
offline::commands::create_pending_playlist,
|
|
offline::commands::get_pending_playlists,
|
|
offline::commands::add_tracks_to_pending_playlist,
|
|
offline::commands::get_pending_playlist_count,
|
|
offline::commands::update_pending_playlist_qobuz_id,
|
|
offline::commands::mark_pending_playlist_synced,
|
|
offline::commands::delete_pending_playlist,
|
|
// Scrobble queue commands (for offline Last.fm scrobbling)
|
|
offline::commands::queue_scrobble,
|
|
offline::commands::get_queued_scrobbles,
|
|
offline::commands::mark_scrobbles_sent,
|
|
offline::commands::get_queued_scrobble_count,
|
|
offline::commands::cleanup_sent_scrobbles,
|
|
// Network folder detection commands
|
|
network::commands::check_network_path,
|
|
network::commands::get_network_mounts_cmd,
|
|
network::commands::check_mount_accessible,
|
|
network::commands::check_network_paths_batch,
|
|
// Flatpak detection commands
|
|
flatpak::is_running_in_flatpak,
|
|
flatpak::get_flatpak_help_text,
|
|
// Tray settings commands
|
|
config::tray_settings::get_tray_settings,
|
|
config::tray_settings::set_enable_tray,
|
|
config::tray_settings::set_minimize_to_tray,
|
|
config::tray_settings::set_close_to_tray,
|
|
// MusicBrainz integration commands
|
|
commands::musicbrainz_resolve_track,
|
|
commands::musicbrainz_resolve_artist,
|
|
commands::musicbrainz_resolve_release,
|
|
commands::musicbrainz_get_artist_relationships,
|
|
commands::musicbrainz_is_enabled,
|
|
commands::musicbrainz_set_enabled,
|
|
commands::musicbrainz_get_cache_stats,
|
|
commands::musicbrainz_clear_cache,
|
|
commands::musicbrainz_cleanup_cache,
|
|
// Musician resolution commands
|
|
commands::resolve_musician,
|
|
commands::get_musician_appearances,
|
|
// ListenBrainz integration commands
|
|
commands::listenbrainz_get_status,
|
|
commands::listenbrainz_is_enabled,
|
|
commands::listenbrainz_set_enabled,
|
|
commands::listenbrainz_connect,
|
|
commands::listenbrainz_disconnect,
|
|
commands::listenbrainz_now_playing,
|
|
commands::listenbrainz_scrobble,
|
|
commands::listenbrainz_queue_listen,
|
|
commands::listenbrainz_get_queue,
|
|
commands::listenbrainz_get_queue_count,
|
|
commands::listenbrainz_mark_sent,
|
|
commands::listenbrainz_flush_queue,
|
|
commands::listenbrainz_clear_queue,
|
|
commands::listenbrainz_cleanup_queue,
|
|
// Smart playlist generation commands
|
|
commands::smart_playlist_preview,
|
|
commands::smart_playlist_resolve_artist,
|
|
commands::smart_playlist_get_available_types,
|
|
// Remote metadata commands (Tag Editor service integration)
|
|
commands::remote_metadata_search,
|
|
commands::remote_metadata_get_album,
|
|
commands::remote_metadata_cache_stats,
|
|
commands::remote_metadata_clear_cache,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running tauri application");
|
|
}
|