use std::path::PathBuf; use sqlx::{prelude::FromRow, SqlitePool}; use crate::context::Error; // {{{ Difficuly #[derive(Debug, Clone, Copy, sqlx::Type)] pub enum Difficulty { PST, PRS, FTR, ETR, BYD, } impl Difficulty { pub const DIFFICULTIES: [Difficulty; 5] = [Self::PST, Self::PRS, Self::FTR, Self::ETR, Self::BYD]; pub const DIFFICULTY_STRINGS: [&'static str; 5] = ["PST", "PRS", "FTR", "ETR", "BYD"]; #[inline] pub fn to_index(self) -> usize { self as usize } } impl TryFrom for Difficulty { type Error = String; fn try_from(value: String) -> Result { for (i, s) in Self::DIFFICULTY_STRINGS.iter().enumerate() { if value == **s { return Ok(Self::DIFFICULTIES[i]); } } Err(format!("Cannot convert {} to difficulty", value)) } } // }}} // {{{ Song #[derive(Debug, Clone, FromRow)] pub struct Song { pub id: u32, pub title: String, pub ocr_alias: Option, pub artist: Option, } impl Song { #[inline] pub fn ocr_string(&self) -> &str { (&self.ocr_alias).as_ref().unwrap_or(&self.title) } } // }}} // {{{ Chart #[derive(Debug, Clone, FromRow)] pub struct Chart { pub id: u32, pub song_id: u32, pub difficulty: Difficulty, pub level: String, // TODO: this could become an enum pub note_count: u32, pub chart_constant: u32, pub jacket: Option, } // }}} // {{{ Cached song #[derive(Debug, Clone)] pub struct CachedSong { pub song: Song, charts: [Option; 5], } impl CachedSong { #[inline] pub fn new(song: Song, charts: [Option; 5]) -> Self { Self { song, charts } } #[inline] pub fn lookup(&self, difficulty: Difficulty) -> Option<&Chart> { self.charts .get(difficulty.to_index()) .and_then(|c| c.as_ref()) } #[inline] pub fn lookup_mut(&mut self, difficulty: Difficulty) -> Option<&mut Chart> { self.charts .get_mut(difficulty.to_index()) .and_then(|c| c.as_mut()) } #[inline] pub fn charts(&self) -> impl Iterator { self.charts.iter().filter_map(|i| i.as_ref()) } } // }}} // {{{ Song cache #[derive(Debug, Clone, Default)] pub struct SongCache { songs: Vec>, } impl SongCache { #[inline] pub fn lookup(&self, id: u32) -> Option<&CachedSong> { self.songs.get(id as usize).and_then(|i| i.as_ref()) } #[inline] pub fn lookup_mut(&mut self, id: u32) -> Option<&mut CachedSong> { self.songs.get_mut(id as usize).and_then(|i| i.as_mut()) } #[inline] pub fn songs(&self) -> impl Iterator { self.songs.iter().filter_map(|i| i.as_ref()) } // {{{ Populate cache pub async fn new(data_dir: &PathBuf, pool: &SqlitePool) -> Result { let mut result = Self::default(); let songs = sqlx::query!("SELECT * FROM songs").fetch_all(pool).await?; for song in songs { let song = Song { id: song.id as u32, title: song.title, ocr_alias: song.ocr_alias, artist: song.artist, }; let song_id = song.id as usize; if song_id >= result.songs.len() { result.songs.resize(song_id + 1, None); } let charts = sqlx::query!("SELECT * FROM charts WHERE song_id=?", song.id) .fetch_all(pool) .await?; let mut chart_cache: [Option<_>; 5] = Default::default(); for chart in charts { let chart = Chart { id: chart.id as u32, song_id: chart.song_id as u32, difficulty: Difficulty::try_from(chart.difficulty)?, level: chart.level, chart_constant: chart.chart_constant as u32, note_count: chart.note_count as u32, jacket: chart .jacket .map(|jacket| data_dir.join("jackets").join(format!("{}.png", jacket))), }; let index = chart.difficulty.to_index(); chart_cache[index] = Some(chart); } result.songs[song_id] = Some(CachedSong::new(song, chart_cache)); } Ok(result) } // }}} } // }}}