monorepo/music-player/server/src/database.rs

145 lines
3.6 KiB
Rust

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<Option<TrackInfo>, FatalError, DatabaseError>;
fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError>;
}
pub struct MemoryIndex {
tracks: RwLock<HashMap<TrackId, TrackInfo>>,
}
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<Option<TrackInfo>, FatalError, DatabaseError> {
let track = {
let tracks = self.tracks.read().unwrap();
tracks.get(&id).cloned()
};
ok(track)
}
fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError> {
ok(self
.tracks
.read()
.unwrap()
.values()
.cloned()
.collect::<Vec<TrackInfo>>())
}
}
pub struct ManagedConnection<'a> {
pool: &'a Database,
conn: Option<Connection>,
}
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<Mutex<Vec<Connection>>>,
}
impl Database {
pub fn new(path: PathBuf) -> Flow<Database, FatalError, DatabaseError> {
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: 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::<mime::Mime>().unwrap(),
};
index.add_track(info.clone());
assert_eq!(
Flow::Ok(Some(info)),
index.get_track_info(&TrackId::from("track_1".to_owned()))
);
});
}
}