refactor(v2): inline lyrics delegates and delete legacy lyrics commands

Inlines the bodies of `lyrics_get` and `lyrics_get_cache_stats` from
crate::lyrics::commands::* directly into commands_v2/legacy_compat.rs,
mapping the legacy String error type to RuntimeError::Internal at the
return boundary for v2_lyrics_get. Renames LyricsCacheStats to
V2LyricsCacheStats, hosted next to the inlined v2_lyrics_get_cache_stats.

Deletes lyrics/commands.rs entirely (also drops the dead
lyrics_clear_cache helper which had no callers; v2_lyrics_clear_cache in
commands_v2/library.rs already inlines its own logic).

The v2 functions retain their existing names, parameter shapes, and
Tauri::State signatures; only the body changes. No frontend invoke
changes required.

Step 6 of the legacy cleanup plan, group A (lyrics).

cc
This commit is contained in:
vicrodh committed 2026-04-26 18:09:59 -06:00
1 parent 42f872fd86
commit ba878b8419
3 files changed
+138 -166

No files matched your search

+138 -5
View File
@@ -324,9 +324,123 @@ pub async fn v2_lyrics_get(
durationSecs: Option<u64>, durationSecs: Option<u64>,
state: State<'_, LyricsState>, state: State<'_, LyricsState>,
) -> Result<Option<crate::lyrics::LyricsPayload>, RuntimeError> { ) -> Result<Option<crate::lyrics::LyricsPayload>, RuntimeError> {
crate::lyrics::commands::lyrics_get(trackId, title, artist, album, durationSecs, state) use crate::lyrics::providers::{fetch_lrclib, fetch_lyrics_ovh};
.await use crate::lyrics::{build_cache_key, LyricsPayload};
.map_err(RuntimeError::Internal)
let track_id = trackId;
let duration_secs = durationSecs;
let title_trimmed = title.trim();
let artist_trimmed = artist.trim();
if title_trimmed.is_empty() || artist_trimmed.is_empty() {
return Err(RuntimeError::Internal(
"Lyrics lookup requires title and artist".to_string(),
));
}
let cache_key = build_cache_key(title_trimmed, artist_trimmed, duration_secs);
// Try cache by track_id first, then by key.
// If cached entry has plain but no synced lyrics, treat as miss and re-fetch
// (search-first strategy is likely to find synced now).
{
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or_else(|| RuntimeError::Internal("No active session - please log in".to_string()))?;
let cached = if let Some(id) = track_id {
db.get_by_track_id(id).ok().flatten()
} else {
None
}
.or_else(|| db.get_by_cache_key(&cache_key).ok().flatten());
if let Some(payload) = cached {
let has_synced = payload
.synced_lrc
.as_ref()
.map(|s| !s.trim().is_empty())
.unwrap_or(false);
if has_synced {
return Ok(Some(payload));
}
// plain-only cache: fall through to re-fetch for synced
}
}
// Provider chain: LRCLIB (with 1 retry on network error) -> lyrics.ovh
let lrclib_data = match fetch_lrclib(title_trimmed, artist_trimmed, duration_secs).await {
Ok(data) => data,
Err(e) => {
// Network error — retry once
eprintln!("[Lyrics] LRCLIB attempt 1 failed: {}, retrying…", e);
match fetch_lrclib(title_trimmed, artist_trimmed, duration_secs).await {
Ok(data) => data,
Err(e2) => {
eprintln!(
"[Lyrics] LRCLIB attempt 2 failed: {}, falling back to lyrics.ovh",
e2
);
None
}
}
}
};
if let Some(data) = lrclib_data {
let payload = LyricsPayload {
track_id,
title: title_trimmed.to_string(),
artist: artist_trimmed.to_string(),
album: album.clone(),
duration_secs,
plain: data.plain,
synced_lrc: data.synced_lrc,
provider: data.provider,
cached: false,
};
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or_else(|| RuntimeError::Internal("No active session - please log in".to_string()))?;
db.upsert(&cache_key, &payload)
.map_err(RuntimeError::Internal)?;
return Ok(Some(payload));
}
if let Some(data) = fetch_lyrics_ovh(title_trimmed, artist_trimmed).await {
let payload = LyricsPayload {
track_id,
title: title_trimmed.to_string(),
artist: artist_trimmed.to_string(),
album,
duration_secs,
plain: data.plain,
synced_lrc: data.synced_lrc,
provider: data.provider,
cached: false,
};
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or_else(|| RuntimeError::Internal("No active session - please log in".to_string()))?;
db.upsert(&cache_key, &payload)
.map_err(RuntimeError::Internal)?;
return Ok(Some(payload));
}
Ok(None)
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct V2LyricsCacheStats {
pub entries: u64,
pub size_bytes: u64,
} }
#[tauri::command] #[tauri::command]
@@ -1424,8 +1538,27 @@ pub fn v2_get_download_settings(
#[tauri::command] #[tauri::command]
pub async fn v2_lyrics_get_cache_stats( pub async fn v2_lyrics_get_cache_stats(
state: State<'_, crate::lyrics::LyricsState>, state: State<'_, crate::lyrics::LyricsState>,
) -> Result<crate::lyrics::commands::LyricsCacheStats, String> { ) -> Result<V2LyricsCacheStats, String> {
crate::lyrics::commands::lyrics_get_cache_stats(state).await let entries = {
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
db.count_entries()?
};
// Approximate on-disk usage as the SQLite DB file size.
let db_path = dirs::cache_dir()
.ok_or("Could not determine cache directory")?
.join("qbz")
.join("lyrics")
.join("lyrics.db");
let size_bytes = std::fs::metadata(&db_path).map(|m| m.len()).unwrap_or(0);
Ok(V2LyricsCacheStats {
entries,
size_bytes,
})
} }
#[tauri::command] #[tauri::command]
-160
View File
@@ -1,160 +0,0 @@
//! Tauri commands for lyrics
use serde::Serialize;
use tauri::State;
use super::providers::{fetch_lrclib, fetch_lyrics_ovh};
use super::{build_cache_key, LyricsPayload, LyricsState};
#[tauri::command]
pub async fn lyrics_get(
track_id: Option<u64>,
title: String,
artist: String,
album: Option<String>,
duration_secs: Option<u64>,
state: State<'_, LyricsState>,
) -> Result<Option<LyricsPayload>, String> {
let title_trimmed = title.trim();
let artist_trimmed = artist.trim();
if title_trimmed.is_empty() || artist_trimmed.is_empty() {
return Err("Lyrics lookup requires title and artist".to_string());
}
let cache_key = build_cache_key(title_trimmed, artist_trimmed, duration_secs);
// Try cache by track_id first, then by key.
// If cached entry has plain but no synced lyrics, treat as miss and re-fetch
// (search-first strategy is likely to find synced now).
{
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
let cached = if let Some(id) = track_id {
db.get_by_track_id(id).ok().flatten()
} else {
None
}
.or_else(|| db.get_by_cache_key(&cache_key).ok().flatten());
if let Some(payload) = cached {
let has_synced = payload
.synced_lrc
.as_ref()
.map(|s| !s.trim().is_empty())
.unwrap_or(false);
if has_synced {
return Ok(Some(payload));
}
// plain-only cache: fall through to re-fetch for synced
}
}
// Provider chain: LRCLIB (with 1 retry on network error) -> lyrics.ovh
let lrclib_data = match fetch_lrclib(title_trimmed, artist_trimmed, duration_secs).await {
Ok(data) => data,
Err(e) => {
// Network error — retry once
eprintln!("[Lyrics] LRCLIB attempt 1 failed: {}, retrying…", e);
match fetch_lrclib(title_trimmed, artist_trimmed, duration_secs).await {
Ok(data) => data,
Err(e2) => {
eprintln!(
"[Lyrics] LRCLIB attempt 2 failed: {}, falling back to lyrics.ovh",
e2
);
None
}
}
}
};
if let Some(data) = lrclib_data {
let payload = LyricsPayload {
track_id,
title: title_trimmed.to_string(),
artist: artist_trimmed.to_string(),
album: album.clone(),
duration_secs,
plain: data.plain,
synced_lrc: data.synced_lrc,
provider: data.provider,
cached: false,
};
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
db.upsert(&cache_key, &payload)?;
return Ok(Some(payload));
}
if let Some(data) = fetch_lyrics_ovh(title_trimmed, artist_trimmed).await {
let payload = LyricsPayload {
track_id,
title: title_trimmed.to_string(),
artist: artist_trimmed.to_string(),
album,
duration_secs,
plain: data.plain,
synced_lrc: data.synced_lrc,
provider: data.provider,
cached: false,
};
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
db.upsert(&cache_key, &payload)?;
return Ok(Some(payload));
}
Ok(None)
}
#[tauri::command]
pub async fn lyrics_clear_cache(state: State<'_, LyricsState>) -> Result<(), String> {
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
db.clear()
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LyricsCacheStats {
pub entries: u64,
pub size_bytes: u64,
}
#[tauri::command]
pub async fn lyrics_get_cache_stats(
state: State<'_, LyricsState>,
) -> Result<LyricsCacheStats, String> {
let entries = {
let db_opt__ = state.db.lock().await;
let db = db_opt__
.as_ref()
.ok_or("No active session - please log in")?;
db.count_entries()?
};
// Approximate on-disk usage as the SQLite DB file size.
let db_path = dirs::cache_dir()
.ok_or("Could not determine cache directory")?
.join("qbz")
.join("lyrics")
.join("lyrics.db");
let size_bytes = std::fs::metadata(&db_path).map(|m| m.len()).unwrap_or(0);
Ok(LyricsCacheStats {
entries,
size_bytes,
})
}
-1
View File
@@ -3,7 +3,6 @@
//! Fetches and caches lyrics from public providers. //! Fetches and caches lyrics from public providers.
pub mod cache; pub mod cache;
pub mod commands;
pub mod providers; pub mod providers;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};