use crate::{ media::{TrackId, TrackInfo}, FatalError, }; use flow::{error, ok, Flow}; use rusqlite::Connection; use serde::Serialize; use std::collections::HashMap; use std::{ path::PathBuf, sync::{Arc, Mutex, RwLock}, }; use thiserror::Error; #[derive(Debug, Error, PartialEq, Serialize)] pub enum DatabaseError { #[error("database is unreadable")] DatabaseUnreadable, #[error("unhandled database problem: {0}")] UnhandledError(String), } pub trait MusicIndex: Sync + Send { fn add_track(&self, track: TrackInfo) -> Flow<(), FatalError, DatabaseError>; fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError>; fn get_track_info(&self, id: &TrackId) -> Flow, FatalError, DatabaseError>; fn list_tracks<'a>(&'a self) -> Flow, FatalError, DatabaseError>; } pub struct MemoryIndex { tracks: RwLock>, } impl MemoryIndex { pub fn new() -> MemoryIndex { MemoryIndex { tracks: RwLock::new(HashMap::default()), } } } impl MusicIndex for MemoryIndex { fn add_track(&self, info: TrackInfo) -> Flow<(), FatalError, DatabaseError> { let mut tracks = self.tracks.write().unwrap(); tracks.insert(info.id.clone(), info); ok(()) } fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError> { let mut tracks = self.tracks.write().unwrap(); tracks.remove(&id); ok(()) } fn get_track_info<'a>( &'a self, id: &TrackId, ) -> Flow, FatalError, DatabaseError> { let track = { let tracks = self.tracks.read().unwrap(); tracks.get(&id).cloned() }; ok(track) } fn list_tracks<'a>(&'a self) -> Flow, FatalError, DatabaseError> { ok(self .tracks .read() .unwrap() .values() .cloned() .collect::>()) } } pub struct ManagedConnection<'a> { pool: &'a Database, conn: Option, } impl<'a> Drop for ManagedConnection<'a> { fn drop(&mut self) { self.pool.r(self.conn.take().unwrap()); } } #[derive(Clone)] pub struct Database { path: PathBuf, pool: Arc>>, } impl Database { pub fn new(path: PathBuf) -> Flow { let connection = match Connection::open(path.clone()) { Ok(connection) => connection, Err(err) => return error(DatabaseError::UnhandledError(err.to_string())), }; ok(Database { path, pool: Arc::new(Mutex::new(vec![connection])), }) } pub fn r(&self, conn: Connection) { let mut pool = self.pool.lock().unwrap(); pool.push(conn); } } #[cfg(test)] mod test { use super::*; fn with_memory_index(f: F) where F: Fn(&dyn MusicIndex), { let index = MemoryIndex::new(); f(&index) } #[test] fn it_saves_and_loads_data() { with_memory_index(|index| { let info = TrackInfo { id: TrackId::from("track_1".to_owned()), track_number: None, name: None, album: None, artist: None, duration: None, filetype: "text/plain".parse::().unwrap(), }; index.add_track(info.clone()); assert_eq!( Flow::Ok(Some(info)), index.get_track_info(&TrackId::from("track_1".to_owned())) ); }); } }