2023-02-11 17:59:15 +00:00
|
|
|
use crate::{
|
2023-03-02 04:54:23 +00:00
|
|
|
media::{TrackId, TrackInfo},
|
2023-02-11 17:59:15 +00:00
|
|
|
FatalError,
|
|
|
|
};
|
|
|
|
use flow::{error, ok, Flow};
|
|
|
|
use rusqlite::Connection;
|
2023-03-11 19:46:51 +00:00
|
|
|
use serde::Serialize;
|
2023-02-11 17:59:15 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::{
|
|
|
|
path::PathBuf,
|
|
|
|
sync::{Arc, Mutex, RwLock},
|
|
|
|
};
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2023-03-11 19:46:51 +00:00
|
|
|
#[derive(Debug, Error, PartialEq, Serialize)]
|
2023-02-11 17:59:15 +00:00
|
|
|
pub enum DatabaseError {
|
|
|
|
#[error("database is unreadable")]
|
|
|
|
DatabaseUnreadable,
|
|
|
|
#[error("unhandled database problem: {0}")]
|
2023-03-11 19:46:51 +00:00
|
|
|
UnhandledError(String),
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait MusicIndex: Sync + Send {
|
2023-02-26 03:17:00 +00:00
|
|
|
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>;
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MemoryIndex {
|
2023-02-26 03:17:00 +00:00
|
|
|
tracks: RwLock<HashMap<TrackId, TrackInfo>>,
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MemoryIndex {
|
|
|
|
pub fn new() -> MemoryIndex {
|
|
|
|
MemoryIndex {
|
|
|
|
tracks: RwLock::new(HashMap::default()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MusicIndex for MemoryIndex {
|
2023-02-26 03:17:00 +00:00
|
|
|
fn add_track(&self, info: TrackInfo) -> Flow<(), FatalError, DatabaseError> {
|
2023-02-11 17:59:15 +00:00
|
|
|
let mut tracks = self.tracks.write().unwrap();
|
2023-02-26 03:17:00 +00:00
|
|
|
tracks.insert(info.id.clone(), info);
|
|
|
|
ok(())
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
2023-02-26 03:17:00 +00:00
|
|
|
fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError> {
|
2023-02-11 17:59:15 +00:00
|
|
|
let mut tracks = self.tracks.write().unwrap();
|
|
|
|
tracks.remove(&id);
|
|
|
|
ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_track_info<'a>(
|
|
|
|
&'a self,
|
|
|
|
id: &TrackId,
|
2023-02-26 03:17:00 +00:00
|
|
|
) -> Flow<Option<TrackInfo>, FatalError, DatabaseError> {
|
2023-02-11 17:59:15 +00:00
|
|
|
let track = {
|
|
|
|
let tracks = self.tracks.read().unwrap();
|
|
|
|
tracks.get(&id).cloned()
|
|
|
|
};
|
|
|
|
ok(track)
|
|
|
|
}
|
2023-02-26 03:17:00 +00:00
|
|
|
|
|
|
|
fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError> {
|
|
|
|
ok(self
|
|
|
|
.tracks
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.values()
|
|
|
|
.cloned()
|
|
|
|
.collect::<Vec<TrackInfo>>())
|
|
|
|
}
|
2023-02-11 17:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2023-03-11 19:46:51 +00:00
|
|
|
Err(err) => return error(DatabaseError::UnhandledError(err.to_string())),
|
2023-02-11 17:59:15 +00:00
|
|
|
};
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2023-02-26 03:17:00 +00:00
|
|
|
|
|
|
|
#[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,
|
2023-03-02 04:54:23 +00:00
|
|
|
duration: None,
|
|
|
|
filetype: "text/plain".parse::<mime::Mime>().unwrap(),
|
2023-02-26 03:17:00 +00:00
|
|
|
};
|
|
|
|
index.add_track(info.clone());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Flow::Ok(Some(info)),
|
|
|
|
index.get_track_info(&TrackId::from("track_1".to_owned()))
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|