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 :: * ;
|
2025-03-17 13:16:03 +01:00
|
|
|
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 :: * ;
|
2025-03-17 13:16:03 +01:00
|
|
|
use crate :: utility :: * ;
|
2024-11-20 16:32:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-17 13:16:03 +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> {
|
2025-03-17 13:16:03 +01:00
|
|
|
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" ) )
|
2025-02-04 23:46:51 +01:00
|
|
|
. flags ( OpenFlags :: SQLITE_OPEN_READ_WRITE | OpenFlags :: SQLITE_OPEN_NO_MUTEX )
|
|
|
|
. open ()
|
|
|
|
. await ? ;
|
2025-03-17 13:16:03 +01:00
|
|
|
|
|
|
|
Ok ( Self {
|
|
|
|
sqlite_client_local ,
|
|
|
|
sqlite_client_shared ,
|
|
|
|
} )
|
2025-02-04 23:46:51 +01:00
|
|
|
}
|
|
|
|
|
2025-02-12 11:34:38 +01:00
|
|
|
pub async fn get_collection_overview ( & self ) -> Result <CollectionOverview> {
|
2025-03-17 13:16:03 +01:00
|
|
|
let collection_overview = self . sqlite_client_shared . conn ( |sqlite_connection| {
|
2025-02-04 23:46:51 +01:00
|
|
|
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)
|
2025-02-04 23:46:51 +01:00
|
|
|
}
|
2025-02-18 15:09:05 +01:00
|
|
|
|
|
|
|
pub async fn get_film_details ( & self , uuid : String ) -> Result <FilmDetails> {
|
2025-03-17 13:16:03 +01:00
|
|
|
let film_details = self . sqlite_client_shared . conn ( |sqlite_connection| {
|
2025-02-18 15:09:05 +01:00
|
|
|
let film_details = sqlite_connection
|
|
|
|
. prepare ( "
|
|
|
|
select
|
|
|
|
films . uuid ,
|
|
|
|
films . name ,
|
|
|
|
films . original_name ,
|
|
|
|
films . release_date ,
|
|
|
|
films . runtime_minutes ,
|
|
|
|
films . poster_file_path ,
|
2025-03-17 13:16:03 +01:00
|
|
|
sources . media_uuid ,
|
|
|
|
sources . bittorrent_hash ,
|
2025-02-21 16:07:35 +01:00
|
|
|
sources . file_path ,
|
2025-02-18 15:09:05 +01:00
|
|
|
sources . audio_track ,
|
|
|
|
sources . subtitle_track
|
|
|
|
from films left join sources
|
2025-03-17 13:16:03 +01:00
|
|
|
on films . uuid = sources . media_uuid
|
2025-02-18 15:09:05 +01:00
|
|
|
where films . uuid = (?1)
|
|
|
|
" ) ?
|
|
|
|
. query ( [ uuid ] ) ?
|
|
|
|
. map (row_to_film_details)
|
|
|
|
. next () ?
|
|
|
|
. unwrap () ;
|
|
|
|
Ok (film_details)
|
|
|
|
} ) . await ? ;
|
|
|
|
Ok (film_details)
|
|
|
|
}
|
2025-02-04 23:46:51 +01:00
|
|
|
}
|
2024-12-23 23:20:08 +01:00
|
|
|
|
2025-02-18 15:09:05 +01:00
|
|
|
|
|
|
|
|
2025-02-04 23:46:51 +01:00
|
|
|
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
|
|
|
|
2025-02-10 23:56:48 +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> ;
|
|
|
|
}
|
|
|
|
|
2025-02-04 23:46:51 +01:00
|
|
|
# [ 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> ,
|
|
|
|
}
|
|
|
|
|
2025-02-10 23:56:48 +01:00
|
|
|
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 () }
|
|
|
|
}
|
|
|
|
|
2025-02-04 23:46:51 +01:00
|
|
|
fn row_to_film_overview ( row : & Row ) -> rusqlite :: Result <FilmOverview> {
|
2024-12-23 23:25:25 +01:00
|
|
|
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 ) ;
|
|
|
|
|
2025-02-04 23:46:51 +01:00
|
|
|
Ok ( FilmOverview {
|
2024-12-23 23:25:25 +01:00
|
|
|
uuid ,
|
|
|
|
name ,
|
|
|
|
original_name ,
|
|
|
|
release_date ,
|
|
|
|
runtime_minutes ,
|
|
|
|
poster_file_path ,
|
|
|
|
} )
|
|
|
|
}
|
2025-02-04 23:46:51 +01:00
|
|
|
fn row_to_series_overview ( row : & Row ) -> rusqlite :: Result <SeriesOverview> {
|
2024-12-23 23:25:25 +01:00
|
|
|
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 ) ;
|
2025-02-04 23:46:51 +01:00
|
|
|
let first_release_date = row . get (4) ? ;
|
2024-12-23 23:25:25 +01:00
|
|
|
|
2025-02-11 00:20:29 +01:00
|
|
|
Ok ( SeriesOverview {
|
|
|
|
uuid ,
|
|
|
|
name ,
|
|
|
|
original_name ,
|
|
|
|
first_release_date ,
|
|
|
|
poster_file_path ,
|
|
|
|
} )
|
2024-12-23 23:25:25 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 15:09:05 +01:00
|
|
|
|
|
|
|
|
2025-02-21 16:07:35 +01:00
|
|
|
# [ derive (Clone) ] pub struct FilmDetails {
|
2025-02-18 15:09:05 +01:00
|
|
|
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> ,
|
2025-02-18 17:50:32 +01:00
|
|
|
pub source : Option <SourceDetails> ,
|
|
|
|
}
|
2025-02-21 16:07:35 +01:00
|
|
|
# [ derive (Clone) ] pub struct SourceDetails {
|
2025-03-17 13:16:03 +01:00
|
|
|
pub bittorrent_hash : String ,
|
2025-02-21 16:07:35 +01:00
|
|
|
pub file_path : PathBuf ,
|
2025-02-18 15:09:05 +01:00
|
|
|
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 ) ;
|
2025-02-18 17:50:32 +01:00
|
|
|
|
2025-03-17 13:16:03 +01:00
|
|
|
let source_media_uuid = row . get :: < _ , Option <String> > (6) ? ;
|
|
|
|
let source = match source_media_uuid {
|
2025-02-18 17:50:32 +01:00
|
|
|
Some (_) => {
|
2025-03-17 13:16:03 +01:00
|
|
|
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) ? ;
|
2025-02-18 17:50:32 +01:00
|
|
|
|
|
|
|
Some ( SourceDetails {
|
2025-03-17 13:16:03 +01:00
|
|
|
bittorrent_hash ,
|
2025-02-21 16:07:35 +01:00
|
|
|
file_path ,
|
2025-02-18 17:50:32 +01:00
|
|
|
audio_track ,
|
|
|
|
subtitle_track ,
|
|
|
|
} )
|
|
|
|
} ,
|
|
|
|
None => None ,
|
|
|
|
} ;
|
2025-02-18 15:09:05 +01:00
|
|
|
|
|
|
|
Ok ( FilmDetails {
|
|
|
|
uuid ,
|
|
|
|
name ,
|
|
|
|
original_name ,
|
|
|
|
release_date ,
|
|
|
|
runtime_minutes ,
|
|
|
|
poster_file_path ,
|
2025-02-18 17:50:32 +01:00
|
|
|
source ,
|
2025-02-18 15:09:05 +01:00
|
|
|
} )
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-11-30 14:38:08 +01:00
|
|
|
impl From <Error> for ZoodexError {
|
|
|
|
fn from ( error : Error ) -> Self {
|
2024-12-23 23:20:08 +01:00
|
|
|
match error {
|
|
|
|
Rusqlite (error) => ZoodexError :: from (error) ,
|
2025-02-18 15:09:05 +01:00
|
|
|
_ => panic ! ( "{}" , error ) ,
|
2024-12-23 23:20:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From < rusqlite :: Error > for ZoodexError {
|
|
|
|
fn from ( error : rusqlite :: Error ) -> Self {
|
2024-11-29 21:06:14 +01:00
|
|
|
match error {
|
2024-11-30 14:38:08 +01:00
|
|
|
SqliteFailure ( error , _ ) => {
|
|
|
|
match error . code {
|
2024-12-23 23:20:08 +01:00
|
|
|
CannotOpen => CollectionFileReadError ,
|
2025-02-18 15:09:05 +01:00
|
|
|
_ => panic ! ( "{}" , error ) ,
|
2024-11-30 14:38:08 +01:00
|
|
|
}
|
|
|
|
} ,
|
2025-02-18 15:09:05 +01:00
|
|
|
_ => panic ! ( "{}" , error ) ,
|
2024-11-29 21:06:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|