zoodex/src/data_manager.rs

271 lines
7.8 KiB
Rust
Raw Normal View History

2025-02-05 14:57:07 +01:00
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 :: * ;
2025-02-05 14:57:07 +01:00
use std :: path :: * ;
2024-11-20 16:32:37 +01:00
2025-02-05 14:57:07 +01:00
use crate :: error :: * ;
use crate :: error :: ZoodexError :: * ;
use crate :: utility :: * ;
2024-11-20 16:32:37 +01:00
pub struct DataManager {
sqlite_client_local : Client ,
sqlite_client_shared : Client ,
}
2024-11-20 16:32:37 +01:00
2025-02-05 00:03:26 +01:00
impl DataManager {
pub async fn new () -> Result <Self> {
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 ,
} )
}
2025-02-12 11:34:38 +01:00
pub async fn get_collection_overview ( & self ) -> Result <CollectionOverview> {
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 } )
2025-02-12 11:34:38 +01:00
} ) . await ? ;
Ok (collection_overview)
}
pub async fn get_film_details ( & self , uuid : String ) -> Result <FilmDetails> {
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 <FilmOverview> ,
pub series : Vec <SeriesOverview> ,
2024-11-20 16:32:37 +01:00
}
2024-11-29 21:06:14 +01:00
pub trait MediaOverview : Clone {
fn get_uuid ( & self ) -> String ;
fn get_name ( & self ) -> String ;
fn get_original_name ( & self ) -> Option <String> ;
fn get_release_date ( & self ) -> String ;
fn get_runtime_minutes ( & self ) -> Option <u32> ;
fn get_poster_file_path ( & self ) -> Option <PathBuf> ;
}
# [ derive (Clone) ] pub struct FilmOverview {
pub uuid : String ,
pub name : String ,
pub original_name : Option <String> ,
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 <PathBuf> ,
}
# [ derive (Clone) ] pub struct SeriesOverview {
pub uuid : String ,
pub name : String ,
pub original_name : Option <String> ,
pub first_release_date : String , // TODO: Switch to chrono types, I think rusqlite has crate option for it
pub poster_file_path : Option <PathBuf> ,
}
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 <String> { self . original_name . clone () }
fn get_release_date ( & self ) -> String { self . release_date . clone () }
fn get_runtime_minutes ( & self ) -> Option <u32> { Some ( self . runtime_minutes ) }
fn get_poster_file_path ( & self ) -> Option <PathBuf> { 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 <String> { self . original_name . clone () }
fn get_release_date ( & self ) -> String { self . first_release_date . clone () }
fn get_runtime_minutes ( & self ) -> Option <u32> { None }
fn get_poster_file_path ( & self ) -> Option <PathBuf> { self . poster_file_path . clone () }
}
fn row_to_film_overview ( row : & Row ) -> rusqlite :: Result <FilmOverview> {
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 <String> > (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 <SeriesOverview> {
let uuid = row . get (0) ? ;
let name = row . get (1) ? ;
let original_name = row . get (2) ? ;
let poster_file_path = row. get :: < _ , Option <String> > (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 <String> ,
pub release_date : String ,
pub runtime_minutes : u32 ,
pub poster_file_path : Option <PathBuf> ,
pub source : Option <SourceDetails> ,
}
# [ derive (Clone) ] pub struct SourceDetails {
pub bittorrent_hash : String ,
pub file_path : PathBuf ,
pub audio_track : Option <u32> ,
pub subtitle_track : Option <u32> ,
}
fn row_to_film_details ( row : & Row ) -> rusqlite :: Result <FilmDetails> {
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 <String> > (5) ? . map ( PathBuf :: from ) ;
let source_media_uuid = row . get :: < _ , Option <String> > (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 <Error> 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 {
2024-11-29 21:06:14 +01:00
match error {
SqliteFailure ( error , _ ) => {
match error . code {
CannotOpen => CollectionFileReadError ,
_ => panic ! ( "{}" , error ) ,
}
} ,
_ => panic ! ( "{}" , error ) ,
2024-11-29 21:06:14 +01:00
}
}
}