use std::path::{Path, PathBuf};
use crate::config::Config;
use crate::db::connection::Database;
use crate::db::queries;
use crate::player::state::{ItemState, PlaylistItem, QueueItemId};
use super::*;
fn resolve_item_path(
found: &mut Vec<(i64, PathBuf)>,
cfg: &Config,
track: &queries::TrackRow,
remote_url: Option<&str>,
album_date: Option<&str>,
) -> (PathBuf, ItemState) {
match queries::choose_playback_source(
track.path.as_deref(),
track.cached_path.as_deref(),
remote_url,
) {
Some(queries::PlaybackSource::Local(p)) => (p, ItemState::Ready),
Some(queries::PlaybackSource::Cached(p)) => {
let state = if is_cached_audio(&p) {
ItemState::Ready
} else {
ItemState::Pending
};
(p, state)
}
Some(queries::PlaybackSource::Remote(_)) => {
let dest = cache_path_for_track(&cfg.cache_dir(), track, album_date);
if dest.exists() && is_cached_audio(&dest) {
if track.cached_path.as_deref() != Some(&*dest.to_string_lossy()) {
found.push((track.id, dest.clone()));
}
(dest, ItemState::Ready)
} else {
(dest, ItemState::Pending)
}
}
_ => {
let dest = cache_path_for_track(&cfg.cache_dir(), track, album_date);
(dest, ItemState::Pending)
}
}
}
pub fn playlist_item_from_track(
track: &queries::TrackRow,
album_date: Option<&str>,
dest: PathBuf,
state: ItemState,
) -> PlaylistItem {
let year = album_date.and_then(year_of).map(str::to_string);
PlaylistItem {
playlist_entry_id: None,
id: QueueItemId::new(),
db_id: Some(track.id),
path: dest,
title: track.title.clone(),
artist: track.artist_name.clone(),
album_artist: track.album_artist_name.clone(),
album: track.album_title.clone(),
year,
codec: track.codec.clone(),
track_number: track.track_number.map(|n| n as i64),
disc: track.disc.map(|n| n as i64),
duration_ms: track.duration_ms.map(|d| d as u64),
state,
played: false,
}
}
pub fn playlist_items_for_tracks(db: &Database, tracks: &[queries::TrackRow]) -> Vec<PlaylistItem> {
playlist_items_with(db, &Config::load().unwrap_or_default(), tracks)
}
pub(super) fn playlist_items_with(
db: &Database,
cfg: &Config,
tracks: &[queries::TrackRow],
) -> Vec<PlaylistItem> {
let ids: Vec<i64> = tracks.iter().map(|t| t.id).collect();
let extras = queries::queue_item_extras(&db.conn, &ids).unwrap_or_default();
let mut found = Vec::new();
let items = tracks
.iter()
.map(|track| {
let extra = extras.get(&track.id);
let remote_url = extra.and_then(|e| e.remote_url.as_deref());
let album_date = extra.and_then(|e| e.album_date.as_deref());
let (path, state) = resolve_item_path(&mut found, cfg, track, remote_url, album_date);
playlist_item_from_track(track, album_date, path, state)
})
.collect();
if !found.is_empty() {
let recorded = crate::db::connection::without_waiting(&db.conn, |conn| {
queries::atomically(conn, || {
found.iter().try_for_each(|(id, path)| {
queries::set_cached_path(conn, *id, &path.to_string_lossy())
})
})
});
if let Err(e) = recorded {
log::debug!(
"left {} cached file(s) unrecorded for now: {e}",
found.len()
);
}
}
items
}
pub(super) fn adopt_cached(conn: &rusqlite::Connection, track: &queries::TrackRow, dest: &Path) {
let dest = dest.to_string_lossy();
if track.cached_path.as_deref() == Some(&*dest) {
return;
}
if let Err(e) = queries::set_cached_path(conn, track.id, &dest) {
log::warn!("found {dest} in the cache but failed to record it ({e})");
}
}