use std::collections::HashMap;
use std::path::PathBuf;
use reader::PlaylistStore;
use reader::models::{ArtistImageRef, Playlist, PlaylistFolder};
use sqlx::SqlitePool;
use crate::{ArtistImages, DbError, QueueSnapshot, Source};
pub async fn artist_images(pool: &SqlitePool) -> Result<ArtistImages, DbError> {
let rows = sqlx::query!("SELECT artist_norm, kind, image_ref FROM artist_images")
.fetch_all(pool)
.await?;
let mut overrides = HashMap::new();
let mut photos: HashMap<String, ArtistImageRef> = HashMap::new();
for r in rows {
match r.kind.as_str() {
"custom" => {
overrides.insert(r.artist_norm, PathBuf::from(r.image_ref));
}
"server" => {
photos.insert(r.artist_norm, ArtistImageRef::Remote(r.image_ref));
}
_ => {
photos
.entry(r.artist_norm)
.or_insert_with(|| ArtistImageRef::Local(PathBuf::from(r.image_ref)));
}
}
}
Ok((overrides, photos))
}
pub async fn load_playlists(pool: &SqlitePool, source: &Source) -> Result<PlaylistStore, DbError> {
let src = source.as_str();
let rows = sqlx::query!(
"SELECT rowid_pk as \"rowid_pk!\", source_pl_id, name, cover_path, image_tag \
FROM playlists WHERE source = ?1 AND source_pl_id != 'LM' ORDER BY position",
src
)
.fetch_all(pool)
.await?;
let track_rows = sqlx::query!(
"SELECT pt.playlist_pk, pt.track_ref \
FROM playlist_tracks pt \
JOIN playlists p ON p.rowid_pk = pt.playlist_pk \
WHERE p.source = ?1 \
ORDER BY pt.playlist_pk, pt.position",
src
)
.fetch_all(pool)
.await?;
let mut tracks_by_pk: HashMap<i64, Vec<String>> = HashMap::new();
for t in track_rows {
tracks_by_pk
.entry(t.playlist_pk)
.or_default()
.push(t.track_ref);
}
let playlists = rows
.into_iter()
.map(|r| Playlist {
id: r.source_pl_id,
name: r.name,
tracks: tracks_by_pk.remove(&r.rowid_pk).unwrap_or_default(),
image_tag: r.image_tag,
cover_path: r.cover_path.map(PathBuf::from),
})
.collect();
let folder_rows = sqlx::query!("SELECT id, name FROM folders")
.fetch_all(pool)
.await?;
let member_rows = sqlx::query!(
"SELECT folder_id, playlist_ref FROM folder_playlists ORDER BY folder_id, position"
)
.fetch_all(pool)
.await?;
let mut members_by_folder: HashMap<String, Vec<String>> = HashMap::new();
for m in member_rows {
members_by_folder
.entry(m.folder_id)
.or_default()
.push(m.playlist_ref);
}
let folders = folder_rows
.into_iter()
.map(|f| PlaylistFolder {
playlist_ids: members_by_folder.remove(&f.id).unwrap_or_default(),
id: f.id,
name: f.name,
})
.collect();
Ok(PlaylistStore { playlists, folders })
}
pub async fn load_queue(pool: &SqlitePool) -> Result<QueueSnapshot, DbError> {
let row = sqlx::query!(
"SELECT version, queue_json, current_queue_index, progress_secs, \
shuffle_order_json, shuffle_enabled \
FROM queue_state WHERE id = 1"
)
.fetch_optional(pool)
.await?;
let Some(row) = row else {
return Ok(QueueSnapshot::default());
};
Ok(QueueSnapshot {
version: row.version.clamp(0, u8::MAX as i64) as u8,
queue: serde_json::from_str(&row.queue_json).unwrap_or_default(),
current_queue_index: row.current_queue_index.max(0) as usize,
progress_secs: row.progress_secs.max(0) as u64,
shuffle_order: serde_json::from_str(&row.shuffle_order_json).unwrap_or_default(),
shuffle_enabled: row.shuffle_enabled != 0,
})
}