use std::ffi::OsStr; use std::fmt; use std::fmt::{Debug, Formatter}; use async_sqlite::rusqlite::fallible_iterator::FallibleIterator; use async_sqlite::rusqlite::{OpenFlags, Row}; use async_sqlite::{Client, ClientBuilder, rusqlite}; use tokio::try_join; use crate::persist::common::concat_os_str; use crate::persist::data_manager::DataManagerError; use crate::views::overview::{FilmOverview, SeriesOverview}; pub struct SqliteManager { client_shared: Client, client_local: Client, } impl SqliteManager { pub async fn new(data_dir: &OsStr) -> Result { let (client_shared, client_local) = try_join!( create_client(data_dir, DBType::Shared), create_client(data_dir, DBType::Local), )?; Ok(SqliteManager { client_shared, client_local, }) } // The order of the items is undefined. pub async fn films_overview(&self) -> Result, DataManagerError> { let overview = self .client_shared .conn(|connection| { let overview = connection .prepare( " select uuid, name, original_name, release_date, runtime_minutes from films ", ) .expect("films overview statement should be valid SQL") .query(()) .expect("parameters in films overview query should match those in its statement") .map(row_to_film_overview) .collect()?; Ok(overview) }) .await; overview.map_err(|async_sqlite_error| match async_sqlite_error { async_sqlite::Error::Closed => panic!( "shared database connection should remain open as long as the application is running" ), async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error { rusqlite::Error::InvalidColumnIndex(_) => { panic!("column indices obtained from films overview query should exist") } rusqlite::Error::InvalidColumnName(_) => { panic!("column names obtained from films overview query should exist") } rusqlite::Error::InvalidColumnType(..) => panic!( "values obtained from films overview query should have a type matching their column" ), _ => DataManagerError::UnknownSharedDBError, }, _ => DataManagerError::UnknownSharedDBError, }) } // The order of the items is undefined. pub async fn series_overview(&self) -> Result, DataManagerError> { let overview = self .client_shared .conn(|connection| { let overview = connection .prepare( " select series.uuid, series.name, series.original_name, min(episodes.release_date) as first_release_date from series, seasons, episodes where series.uuid = seasons.series and seasons.uuid = episodes.season group by series.uuid ", ) .expect("series overview statement should be valid SQL") .query(()) .expect("parameters in series overview query should match those in its statement") .map(row_to_series_overview) .collect()?; Ok(overview) }) .await; overview.map_err(|async_sqlite_error| match async_sqlite_error { async_sqlite::Error::Closed => panic!( "shared database connection should remain open as long as the application is running" ), async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error { rusqlite::Error::InvalidColumnIndex(_) => { panic!("column indices obtained from series overview query should exist") } rusqlite::Error::InvalidColumnName(_) => { panic!("column names obtained from series overview query should exist") } rusqlite::Error::InvalidColumnType(..) => panic!( "values obtained from series overview query should have a type matching their column" ), _ => DataManagerError::UnknownSharedDBError, }, _ => DataManagerError::UnknownSharedDBError, }) } } impl Debug for SqliteManager { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter.write_str("SqliteManager { Client, Client }") } } async fn create_client(data_dir: &OsStr, db_type: DBType) -> Result { let open_mode = match db_type { DBType::Shared => OpenFlags::SQLITE_OPEN_READ_ONLY, DBType::Local => OpenFlags::SQLITE_OPEN_READ_WRITE, }; let filename = match db_type { DBType::Shared => "/shared.sqlite", DBType::Local => "/local.sqlite", }; let cannot_open_err = match db_type { DBType::Shared => DataManagerError::CannotOpenSharedDB, DBType::Local => DataManagerError::CannotOpenLocalDB, }; let unknown_err = match db_type { DBType::Shared => DataManagerError::UnknownSharedDBError, DBType::Local => DataManagerError::UnknownLocalDBError, }; let client = ClientBuilder::new() .path(concat_os_str!(data_dir, filename)) .flags(open_mode | OpenFlags::SQLITE_OPEN_NO_MUTEX) .open() .await; client.map_err(|async_sqlite_error| match async_sqlite_error { async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error { rusqlite::Error::SqliteFailure(sqlite_error, _) => match sqlite_error.code { rusqlite::ffi::ErrorCode::CannotOpen => cannot_open_err, _ => unknown_err, }, _ => unknown_err, }, _ => unknown_err, }) } fn row_to_film_overview(row: &Row) -> rusqlite::Result { let uuid = row.get("uuid")?; let name = row.get("name")?; let original_name = row.get("original_name")?; let release_date = row.get("release_date")?; let runtime = row.get("runtime_minutes")?; Ok(FilmOverview { uuid, name, original_name, release_date, runtime, }) } fn row_to_series_overview(row: &Row) -> rusqlite::Result { let uuid = row.get("uuid")?; let name = row.get("name")?; let original_name = row.get("original_name")?; let first_release_date = row.get("first_release_date")?; Ok(SeriesOverview { uuid, name, original_name, first_release_date, }) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum DBType { Shared, Local, }