2024-06-23 02:51:50 +02:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
2024-07-12 17:18:31 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-06-22 23:07:11 +02:00
|
|
|
use sqlx::{prelude::FromRow, SqlitePool};
|
2024-06-22 16:40:56 +02:00
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
use crate::context::Error;
|
2024-06-22 16:40:56 +02:00
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
// {{{ Difficuly
|
2024-07-12 17:18:31 +02:00
|
|
|
#[derive(
|
|
|
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type, Serialize, Deserialize,
|
|
|
|
)]
|
2024-06-22 16:40:56 +02:00
|
|
|
pub enum Difficulty {
|
|
|
|
PST,
|
|
|
|
PRS,
|
|
|
|
FTR,
|
|
|
|
ETR,
|
|
|
|
BYD,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Difficulty {
|
2024-06-22 23:07:11 +02:00
|
|
|
pub const DIFFICULTIES: [Difficulty; 5] =
|
|
|
|
[Self::PST, Self::PRS, Self::FTR, Self::ETR, Self::BYD];
|
|
|
|
|
2024-07-01 18:00:03 +02:00
|
|
|
pub const DIFFICULTY_SHORTHANDS: [&'static str; 5] = ["PST", "PRS", "FTR", "ETR", "BYD"];
|
|
|
|
pub const DIFFICULTY_STRINGS: [&'static str; 5] =
|
|
|
|
["past", "present", "future", "eternal", "beyond"];
|
2024-06-22 23:07:11 +02:00
|
|
|
|
2024-06-22 16:40:56 +02:00
|
|
|
#[inline]
|
|
|
|
pub fn to_index(self) -> usize {
|
|
|
|
self as usize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
impl TryFrom<String> for Difficulty {
|
|
|
|
type Error = String;
|
|
|
|
|
|
|
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
2024-07-01 18:00:03 +02:00
|
|
|
for (i, s) in Self::DIFFICULTY_SHORTHANDS.iter().enumerate() {
|
2024-06-22 23:07:11 +02:00
|
|
|
if value == **s {
|
|
|
|
return Ok(Self::DIFFICULTIES[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(format!("Cannot convert {} to difficulty", value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
2024-07-12 17:18:31 +02:00
|
|
|
// {{{ Side
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum Side {
|
|
|
|
Light,
|
|
|
|
Conflict,
|
|
|
|
Silent,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Side {
|
|
|
|
pub const SIDES: [Self; 3] = [Self::Light, Self::Conflict, Self::Silent];
|
|
|
|
pub const SIDE_STRINGS: [&'static str; 3] = ["light", "conflict", "silent"];
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn to_index(self) -> usize {
|
|
|
|
self as usize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<String> for Side {
|
|
|
|
type Error = String;
|
|
|
|
|
|
|
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
|
|
for (i, s) in Self::SIDE_STRINGS.iter().enumerate() {
|
|
|
|
if value == **s {
|
|
|
|
return Ok(Self::SIDES[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(format!("Cannot convert {} to difficulty", value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }}}
|
2024-06-22 23:07:11 +02:00
|
|
|
// {{{ Song
|
2024-06-22 16:40:56 +02:00
|
|
|
#[derive(Debug, Clone, FromRow)]
|
|
|
|
pub struct Song {
|
|
|
|
pub id: u32,
|
|
|
|
pub title: String,
|
2024-07-12 17:18:31 +02:00
|
|
|
pub lowercase_title: String,
|
2024-07-01 18:00:03 +02:00
|
|
|
pub artist: String,
|
2024-07-12 17:18:31 +02:00
|
|
|
|
|
|
|
pub bpm: String,
|
|
|
|
pub pack: Option<String>,
|
|
|
|
pub side: Side,
|
2024-06-23 02:51:50 +02:00
|
|
|
}
|
2024-06-22 23:07:11 +02:00
|
|
|
// }}}
|
|
|
|
// {{{ Chart
|
2024-07-12 17:18:31 +02:00
|
|
|
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
2024-06-22 16:40:56 +02:00
|
|
|
pub struct Chart {
|
|
|
|
pub id: u32,
|
|
|
|
pub song_id: u32,
|
2024-07-01 18:00:03 +02:00
|
|
|
pub shorthand: Option<String>,
|
2024-07-12 17:18:31 +02:00
|
|
|
pub note_design: Option<String>,
|
2024-06-22 16:40:56 +02:00
|
|
|
|
|
|
|
pub difficulty: Difficulty,
|
2024-06-22 23:07:11 +02:00
|
|
|
pub level: String, // TODO: this could become an enum
|
2024-06-22 16:40:56 +02:00
|
|
|
|
|
|
|
pub note_count: u32,
|
|
|
|
pub chart_constant: u32,
|
2024-06-23 02:51:50 +02:00
|
|
|
|
2024-07-01 18:00:03 +02:00
|
|
|
pub cached_jacket: Option<&'static [u8]>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Chart {
|
|
|
|
#[inline]
|
|
|
|
pub fn jacket_path(&self, data_dir: &PathBuf) -> PathBuf {
|
|
|
|
data_dir
|
|
|
|
.join("jackets")
|
|
|
|
.join(format!("{}-{}.jpg", self.song_id, self.id))
|
|
|
|
}
|
2024-06-22 16:40:56 +02:00
|
|
|
}
|
2024-06-22 23:07:11 +02:00
|
|
|
// }}}
|
2024-06-23 02:51:50 +02:00
|
|
|
// {{{ Cached song
|
2024-06-22 16:40:56 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct CachedSong {
|
2024-06-22 23:07:11 +02:00
|
|
|
pub song: Song,
|
2024-06-22 16:40:56 +02:00
|
|
|
charts: [Option<Chart>; 5],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CachedSong {
|
2024-06-22 23:07:11 +02:00
|
|
|
#[inline]
|
2024-06-22 16:40:56 +02:00
|
|
|
pub fn new(song: Song, charts: [Option<Chart>; 5]) -> Self {
|
|
|
|
Self { song, charts }
|
|
|
|
}
|
2024-06-22 23:07:11 +02:00
|
|
|
|
|
|
|
#[inline]
|
2024-06-27 21:22:44 +02:00
|
|
|
pub fn lookup(&self, difficulty: Difficulty) -> Result<&Chart, Error> {
|
2024-06-22 23:07:11 +02:00
|
|
|
self.charts
|
|
|
|
.get(difficulty.to_index())
|
|
|
|
.and_then(|c| c.as_ref())
|
2024-06-27 21:22:44 +02:00
|
|
|
.ok_or_else(|| {
|
|
|
|
format!(
|
|
|
|
"Could not find difficulty {:?} for song {}",
|
|
|
|
difficulty, self.song.title
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
})
|
2024-06-22 23:07:11 +02:00
|
|
|
}
|
2024-06-22 16:40:56 +02:00
|
|
|
|
2024-06-23 02:51:50 +02:00
|
|
|
#[inline]
|
2024-06-27 21:22:44 +02:00
|
|
|
pub fn lookup_mut(&mut self, difficulty: Difficulty) -> Result<&mut Chart, Error> {
|
2024-06-23 02:51:50 +02:00
|
|
|
self.charts
|
|
|
|
.get_mut(difficulty.to_index())
|
|
|
|
.and_then(|c| c.as_mut())
|
2024-06-27 21:22:44 +02:00
|
|
|
.ok_or_else(|| {
|
|
|
|
format!(
|
|
|
|
"Could not find difficulty {:?} for song {}",
|
|
|
|
difficulty, self.song.title
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
})
|
2024-06-23 02:51:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn charts(&self) -> impl Iterator<Item = &Chart> {
|
|
|
|
self.charts.iter().filter_map(|i| i.as_ref())
|
|
|
|
}
|
2024-07-01 18:00:03 +02:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn charts_mut(&mut self) -> impl Iterator<Item = &mut Chart> {
|
|
|
|
self.charts.iter_mut().filter_map(|i| i.as_mut())
|
|
|
|
}
|
2024-06-23 02:51:50 +02:00
|
|
|
}
|
|
|
|
// }}}
|
|
|
|
// {{{ Song cache
|
2024-06-22 16:40:56 +02:00
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
pub struct SongCache {
|
|
|
|
songs: Vec<Option<CachedSong>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SongCache {
|
2024-06-22 23:07:11 +02:00
|
|
|
#[inline]
|
2024-07-01 18:00:03 +02:00
|
|
|
pub fn lookup(&self, id: u32) -> Result<&CachedSong, Error> {
|
|
|
|
self.songs
|
|
|
|
.get(id as usize)
|
|
|
|
.and_then(|i| i.as_ref())
|
|
|
|
.ok_or_else(|| format!("Could not find song with id {}", id).into())
|
2024-06-22 23:07:11 +02:00
|
|
|
}
|
|
|
|
|
2024-06-23 02:51:50 +02:00
|
|
|
#[inline]
|
2024-06-27 21:22:44 +02:00
|
|
|
pub fn lookup_chart(&self, chart_id: u32) -> Result<(&Song, &Chart), Error> {
|
|
|
|
self.songs()
|
|
|
|
.find_map(|item| {
|
|
|
|
item.charts().find_map(|chart| {
|
|
|
|
if chart.id == chart_id {
|
|
|
|
Some((&item.song, chart))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.ok_or_else(|| format!("Could not find chart with id {}", chart_id).into())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn lookup_mut(&mut self, id: u32) -> Result<&mut CachedSong, Error> {
|
|
|
|
self.songs
|
|
|
|
.get_mut(id as usize)
|
|
|
|
.and_then(|i| i.as_mut())
|
|
|
|
.ok_or_else(|| format!("Could not find song with id {}", id).into())
|
2024-06-23 02:51:50 +02:00
|
|
|
}
|
|
|
|
|
2024-07-01 18:00:03 +02:00
|
|
|
#[inline]
|
|
|
|
pub fn lookup_chart_mut(&mut self, chart_id: u32) -> Result<&mut Chart, Error> {
|
|
|
|
self.songs_mut()
|
|
|
|
.find_map(|item| {
|
|
|
|
item.charts_mut().find_map(|chart| {
|
|
|
|
if chart.id == chart_id {
|
|
|
|
Some(chart)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.ok_or_else(|| format!("Could not find chart with id {}", chart_id).into())
|
|
|
|
}
|
|
|
|
|
2024-06-23 02:51:50 +02:00
|
|
|
#[inline]
|
|
|
|
pub fn songs(&self) -> impl Iterator<Item = &CachedSong> {
|
|
|
|
self.songs.iter().filter_map(|i| i.as_ref())
|
|
|
|
}
|
|
|
|
|
2024-07-01 18:00:03 +02:00
|
|
|
#[inline]
|
|
|
|
pub fn songs_mut(&mut self) -> impl Iterator<Item = &mut CachedSong> {
|
|
|
|
self.songs.iter_mut().filter_map(|i| i.as_mut())
|
|
|
|
}
|
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
// {{{ Populate cache
|
2024-07-01 18:00:03 +02:00
|
|
|
pub async fn new(pool: &SqlitePool) -> Result<Self, Error> {
|
2024-06-22 16:40:56 +02:00
|
|
|
let mut result = Self::default();
|
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
let songs = sqlx::query!("SELECT * FROM songs").fetch_all(pool).await?;
|
2024-06-22 16:40:56 +02:00
|
|
|
|
|
|
|
for song in songs {
|
2024-06-22 23:07:11 +02:00
|
|
|
let song = Song {
|
|
|
|
id: song.id as u32,
|
2024-07-12 17:18:31 +02:00
|
|
|
lowercase_title: song.title.to_lowercase(),
|
2024-06-22 23:07:11 +02:00
|
|
|
title: song.title,
|
|
|
|
artist: song.artist,
|
2024-07-12 17:18:31 +02:00
|
|
|
pack: song.pack,
|
|
|
|
bpm: song.bpm,
|
|
|
|
side: Side::try_from(song.side)?,
|
2024-06-22 23:07:11 +02:00
|
|
|
};
|
|
|
|
|
2024-06-22 16:40:56 +02:00
|
|
|
let song_id = song.id as usize;
|
|
|
|
|
|
|
|
if song_id >= result.songs.len() {
|
2024-06-22 23:07:11 +02:00
|
|
|
result.songs.resize(song_id + 1, None);
|
2024-06-22 16:40:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
let charts = sqlx::query!("SELECT * FROM charts WHERE song_id=?", song.id)
|
|
|
|
.fetch_all(pool)
|
2024-06-22 16:40:56 +02:00
|
|
|
.await?;
|
|
|
|
|
2024-06-22 23:07:11 +02:00
|
|
|
let mut chart_cache: [Option<_>; 5] = Default::default();
|
2024-06-22 16:40:56 +02:00
|
|
|
for chart in charts {
|
2024-06-22 23:07:11 +02:00
|
|
|
let chart = Chart {
|
|
|
|
id: chart.id as u32,
|
|
|
|
song_id: chart.song_id as u32,
|
2024-07-01 18:00:03 +02:00
|
|
|
shorthand: chart.shorthand,
|
2024-06-22 23:07:11 +02:00
|
|
|
difficulty: Difficulty::try_from(chart.difficulty)?,
|
|
|
|
level: chart.level,
|
|
|
|
chart_constant: chart.chart_constant as u32,
|
|
|
|
note_count: chart.note_count as u32,
|
2024-07-01 18:00:03 +02:00
|
|
|
cached_jacket: None,
|
2024-07-12 17:18:31 +02:00
|
|
|
note_design: chart.note_design,
|
2024-06-22 23:07:11 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let index = chart.difficulty.to_index();
|
|
|
|
chart_cache[index] = Some(chart);
|
2024-06-22 16:40:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result.songs[song_id] = Some(CachedSong::new(song, chart_cache));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
}
|
2024-06-22 23:07:11 +02:00
|
|
|
// }}}
|
2024-06-22 16:40:56 +02:00
|
|
|
}
|
2024-06-22 23:07:11 +02:00
|
|
|
// }}}
|