use async_sqlite :: * ; use async_sqlite :: Error :: * ; use async_sqlite :: rusqlite :: OpenFlags ; use async_sqlite :: rusqlite :: Row ; use async_sqlite :: rusqlite :: Error :: * ; use async_sqlite :: rusqlite :: ffi :: ErrorCode :: * ; use fallible_iterator :: * ; use std :: env :: * ; use std :: path :: * ; use crate :: error :: * ; use crate :: error :: ZoodexError :: * ; use crate :: utility :: * ; pub struct DataManager { sqlite_client_local : Client , sqlite_client_shared : Client , } impl DataManager { pub async fn new () -> Result { let home_dir = var_os ("HOME") . unwrap () ; let xdg_data_home = var_os ("XDG_DATA_HOME") ; let data_dir = match xdg_data_home { Some (xdg_data_home) => concat_os_str ! ( xdg_data_home , "/zoodex" ) , None => concat_os_str ! ( home_dir , "/.local/share/zoodex" ) , } ; let sqlite_client_shared = ClientBuilder :: new () . path ( concat_os_str ! ( & data_dir , "/shared.sqlite" ) ) . flags ( OpenFlags :: SQLITE_OPEN_READ_ONLY | OpenFlags :: SQLITE_OPEN_NO_MUTEX ) . open () . await ? ; let sqlite_client_local = ClientBuilder :: new () . path ( concat_os_str ! ( & data_dir , "/shared.sqlite" ) ) . flags ( OpenFlags :: SQLITE_OPEN_READ_WRITE | OpenFlags :: SQLITE_OPEN_NO_MUTEX ) . open () . await ? ; Ok ( Self { sqlite_client_local , sqlite_client_shared , } ) } pub async fn get_collection_overview ( & self ) -> Result { let collection_overview = self . sqlite_client_shared . conn ( |sqlite_connection| { let films = sqlite_connection . prepare ( " select uuid , name , original_name , release_date , runtime_minutes , poster_file_path from films " ) ? . query (()) ? . map (row_to_film_overview) . collect () ? ; let series = sqlite_connection . prepare ( " select series . uuid , series . name , series . original_name , series . poster_file_path , min ( episodes . release_date ) from series , seasons , episodes where series . uuid = seasons . series and seasons . uuid = episodes . season group by series . uuid " ) ? . query (()) ? . map (row_to_series_overview) . collect () ? ; Ok ( CollectionOverview { films , series } ) } ) . await ? ; Ok (collection_overview) } pub async fn get_film_details ( & self , uuid : String ) -> Result { let film_details = self . sqlite_client_shared . conn ( |sqlite_connection| { let film_details = sqlite_connection . prepare ( " select films . uuid , films . name , films . original_name , films . release_date , films . runtime_minutes , films . poster_file_path , sources . media_uuid , sources . bittorrent_hash , sources . file_path , sources . audio_track , sources . subtitle_track from films left join sources on films . uuid = sources . media_uuid where films . uuid = (?1) " ) ? . query ( [ uuid ] ) ? . map (row_to_film_details) . next () ? . unwrap () ; Ok (film_details) } ) . await ? ; Ok (film_details) } } pub struct CollectionOverview { pub films : Vec , pub series : Vec , } pub trait MediaOverview : Clone { fn get_uuid ( & self ) -> String ; fn get_name ( & self ) -> String ; fn get_original_name ( & self ) -> Option ; fn get_release_date ( & self ) -> String ; fn get_runtime_minutes ( & self ) -> Option ; fn get_poster_file_path ( & self ) -> Option ; } # [ derive (Clone) ] pub struct FilmOverview { pub uuid : String , pub name : String , pub original_name : Option , pub release_date : String , // TODO: Switch to chrono types, I think rusqlite has crate option for it pub runtime_minutes : u32 , pub poster_file_path : Option , } # [ derive (Clone) ] pub struct SeriesOverview { pub uuid : String , pub name : String , pub original_name : Option , pub first_release_date : String , // TODO: Switch to chrono types, I think rusqlite has crate option for it pub poster_file_path : Option , } impl MediaOverview for FilmOverview { fn get_uuid ( & self ) -> String { self . uuid . clone () } fn get_name ( & self ) -> String { self . name . clone () } fn get_original_name ( & self ) -> Option { self . original_name . clone () } fn get_release_date ( & self ) -> String { self . release_date . clone () } fn get_runtime_minutes ( & self ) -> Option { Some ( self . runtime_minutes ) } fn get_poster_file_path ( & self ) -> Option { self . poster_file_path . clone () } } impl MediaOverview for SeriesOverview { fn get_uuid ( & self ) -> String { self . uuid . clone () } fn get_name ( & self ) -> String { self . name . clone () } fn get_original_name ( & self ) -> Option { self . original_name . clone () } fn get_release_date ( & self ) -> String { self . first_release_date . clone () } fn get_runtime_minutes ( & self ) -> Option { None } fn get_poster_file_path ( & self ) -> Option { self . poster_file_path . clone () } } fn row_to_film_overview ( row : & Row ) -> rusqlite :: Result { let uuid = row . get (0) ? ; let name = row . get (1) ? ; let original_name = row . get (2) ? ; let release_date = row . get (3) ? ; let runtime_minutes = row . get (4) ? ; let poster_file_path = row . get :: < _ , Option > (5) ? . map ( PathBuf :: from ) ; Ok ( FilmOverview { uuid , name , original_name , release_date , runtime_minutes , poster_file_path , } ) } fn row_to_series_overview ( row : & Row ) -> rusqlite :: Result { let uuid = row . get (0) ? ; let name = row . get (1) ? ; let original_name = row . get (2) ? ; let poster_file_path = row. get :: < _ , Option > (3) ? . map ( PathBuf :: from ) ; let first_release_date = row . get (4) ? ; Ok ( SeriesOverview { uuid , name , original_name , first_release_date , poster_file_path , } ) } # [ derive (Clone) ] pub struct FilmDetails { pub uuid : String , pub name : String , pub original_name : Option , pub release_date : String , pub runtime_minutes : u32 , pub poster_file_path : Option , pub source : Option , } # [ derive (Clone) ] pub struct SourceDetails { pub bittorrent_hash : String , pub file_path : PathBuf , pub audio_track : Option , pub subtitle_track : Option , } fn row_to_film_details ( row : & Row ) -> rusqlite :: Result { let uuid = row . get (0) ? ; let name = row . get (1) ? ; let original_name = row . get (2) ? ; let release_date = row . get (3) ? ; let runtime_minutes = row . get (4) ? ; let poster_file_path = row . get :: < _ , Option > (5) ? . map ( PathBuf :: from ) ; let source_media_uuid = row . get :: < _ , Option > (6) ? ; let source = match source_media_uuid { Some (_) => { let bittorrent_hash = row . get (7) ? ; let file_path = PathBuf :: from ( row . get :: < _ , String > (8) ? ) ; let audio_track = row . get (9) ? ; let subtitle_track = row . get (10) ? ; Some ( SourceDetails { bittorrent_hash , file_path , audio_track , subtitle_track , } ) } , None => None , } ; Ok ( FilmDetails { uuid , name , original_name , release_date , runtime_minutes , poster_file_path , source , } ) } impl From for ZoodexError { fn from ( error : Error ) -> Self { match error { Rusqlite (error) => ZoodexError :: from (error) , _ => panic ! ( "{}" , error ) , } } } impl From < rusqlite :: Error > for ZoodexError { fn from ( error : rusqlite :: Error ) -> Self { match error { SqliteFailure ( error , _ ) => { match error . code { CannotOpen => CollectionFileReadError , _ => panic ! ( "{}" , error ) , } } , _ => panic ! ( "{}" , error ) , } } }