use std::{collections::HashMap, path::{Path, PathBuf}};
use bincode_next::config;
use serde::{Deserialize, Serialize};
use tokio::{fs::{self, OpenOptions}, io::{self, AsyncReadExt, AsyncWriteExt}};
use crate::{info_log, lyrics::Lyrics, song::SongData};
#[derive(Clone, Debug)]
pub struct Cache {
map: HashMap<String, Vec<CacheEntry>>,
location: PathBuf,
}
impl Cache {
fn new(location: PathBuf) -> Cache {
Cache {
map: HashMap::new(),
location,
}
}
pub async fn read_from_file(path: &Path) -> io::Result<Self> {
fs::create_dir_all(path.parent().expect("No parent directories?")).await?;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.await?;
let mut cache = Cache::new(path.into());
let mut buf = vec![];
file.read_to_end(&mut buf).await?;
let entries: Vec<CacheEntry> = match bincode_next::serde::decode_from_slice(&buf, config::standard()) {
Ok((entries, _)) => entries,
Err(e) => {
info_log(format!("Error when parsing: {}", e));
return Ok(cache)
},
};
for entry in entries {
cache.save_lyrics(&entry.data, &entry.lyrics, entry.occurences);
}
Ok(cache)
}
pub async fn save_to_file(&self) -> io::Result<()> {
let entries = Into::<Vec<CacheEntry>>::into(self.clone());
let mut file = OpenOptions::new()
.write(true)
.open(&self.location)
.await?;
let serialized = bincode_next::serde::encode_to_vec(entries, config::standard())
.expect("Serialization failure");
file.write_all(&serialized).await?;
Ok(())
}
pub fn get_lyrics(&mut self, data: &SongData) -> Option<Lyrics> {
let entries = self.map.get_mut(&data.title)?;
let found = entries.iter_mut().fold(None::<&mut CacheEntry>, |closest_match, entry| {
let closesness = match &closest_match {
Some(closest) => get_closeness(&closest.data, &data),
None => 0,
};
let entry_closeness = get_closeness(&entry.data, &data);
if closesness > entry_closeness {
closest_match
} else if entry_closeness > 0 {
Some(entry)
} else {
closest_match
}
})?;
found.occurences += 1;
found.lyrics.clone()
}
pub fn save_lyrics(&mut self, data: &SongData, lyrics: &Option<Lyrics>, occurences: usize) {
let entries = match self.map.get_mut(&data.title) {
Some(entries) => entries,
None => {
self.map.insert(data.title.clone(), vec![]);
self.map.get_mut(&data.title).expect("Should be inserted now. (unreachable?)")
},
};
entries.push(CacheEntry::new(data.clone(), lyrics.clone(), occurences));
}
}
fn get_closeness(a: &SongData, b: &SongData) -> u8 {
let mut closeness = 0;
if attribute_close(&a.artist, &b.artist) { closeness += 1 };
if attribute_close(&a.album, &b.album) { closeness += 1 };
if attribute_close(&a.duration, &b.duration) { closeness += 2 };
closeness
}
fn attribute_close<T>(attribute: &Option<T>, target: &Option<T>) -> bool
where
T: PartialEq,
{
match (attribute, target) {
(Some(a), Some(b)) => *a == *b,
(Some(_), None) => true,
(None, Some(_)) => false,
(None, None) => true,
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct CacheEntry {
occurences: usize,
data: SongData,
lyrics: Option<Lyrics>,
}
impl CacheEntry {
fn new(data: SongData, lyrics: Option<Lyrics>, occurences: usize) -> CacheEntry {
CacheEntry {
occurences,
data,
lyrics,
}
}
}
impl From<Cache> for Vec<CacheEntry> {
fn from(value: Cache) -> Self {
value.map.into_iter()
.map(|(_, b)| b)
.flatten()
.collect()
}
}