diff --git a/Cargo.lock b/Cargo.lock index cc65b59..62a40b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "async-sqlite" @@ -76,9 +76,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fallible-iterator" @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6efc7705f7863d37b12ad6974cbb310d35d054f5108cdc1e69037742f573c4c" +checksum = "7563afd6ff0a221edfbb70a78add5075b8d9cb48e637a40a24c3ece3fea414d0" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -224,9 +224,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0196720118f880f71fe7da971eff58cc43a89c9cf73f46076b7cb1e60889b15" +checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b0e1340bd15e7a78810cf39fed9e5d85f0a8f80b1d999d384ca17dcc452b60" +checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a517657589a174be9f60c667f1fec8b7ac82ed5db4ebf56cf073a3b5955d8e2e" +checksum = "a4f00c70f8029d84ea7572dd0e1aaa79e5329667b4c17f329d79ffb1e6277487" dependencies = [ "futures-channel", "futures-core", @@ -273,9 +273,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8446d9b475730ebef81802c1738d972db42fde1c5a36a627ebc4d665fc87db04" +checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" dependencies = [ "glib-sys", "gobject-sys", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f969edf089188d821a30cde713b6f9eb08b20c63fc2e584aba2892a7984a8cc0" +checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" dependencies = [ "bitflags", "futures-channel", @@ -320,9 +320,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b360ff0f90d71de99095f79c526a5888c9c92fc9ee1b19da06c6f5e75f0c2a53" +checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" dependencies = [ "libc", "system-deps", @@ -330,9 +330,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a56235e971a63bfd75abb13ef70064e1346388723422a68580d8a6fbac6423" +checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" dependencies = [ "glib-sys", "libc", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39d3bcd2e24fd9c2874a56f277b72c03e728de9bdc95a8d4ef4c962f10ced98" +checksum = "3cbc5911bfb32d68dcfa92c9510c462696c2f715548fcd7f3f1be424c739de19" dependencies = [ "glib", "graphene-sys", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b9188db0a6219e708b6b6e7225718e459def664023dbddb8395ca1486d8102" +checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" dependencies = [ "cairo-rs", "gdk4", @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca10fc65d68528a548efa3d8747934adcbe7058b73695c9a7f43a25352fce14" +checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697ff938136625f6acf75f01951220f47a45adcf0060ee55b4671cf734dac44" +checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" dependencies = [ "cairo-rs", "field-offset", @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af4b680cee5d2f786a2f91f1c77e95ecf2254522f0ca4edf3a2dce6cb35cecf" +checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -512,9 +512,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libsqlite3-sys" @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e89bd74250a03a05cec047b43465469102af803be2bf5e5a1088f8b8455e087" +checksum = "6b1f5dc1b8cf9bc08bfc0843a04ee0fa2e78f1e1fa4b126844a383af4f25f0ec" dependencies = [ "gio", "glib", @@ -555,9 +555,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.20.7" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71787e0019b499a5eda889279e4adb455a4f3fdd6870cd5ab7f4a5aa25df6699" +checksum = "0dbb9b751673bd8fe49eb78620547973a1e719ed431372122b20abd12445bab5" dependencies = [ "glib-sys", "gobject-sys", @@ -641,18 +641,18 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "syn" @@ -749,9 +749,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "vcpkg" @@ -840,9 +840,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 10e75ec..b9c468e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ name = "zoodex" version = "1.0.0" authors = [ "Reinout Meliesie " ] -edition = "2021" -rust-version = "1.81.0" +edition = "2024" +rust-version = "1.85.0" license = "GPL-3.0-or-later" [ profile . release ] @@ -13,5 +13,5 @@ lto = true async-sqlite = { version = "0.5.0" , default-features = false } fallible-iterator = "0.3.0" # Must match version used by async-sqlite futures = "0.3.31" -gtk4 = { version = "0.9.5" , features = [ "v4_16" ] } +gtk4 = { version = "0.9.6" , features = [ "v4_16" ] } libadwaita = { version = "0.7.1" , features = [ "v1_6" ] } diff --git a/src/application.css b/src/application.css index fd5507b..a0ae4fa 100644 --- a/src/application.css +++ b/src/application.css @@ -14,6 +14,3 @@ .media-modal { padding : 100px ; } -.media-modal .top-padding { - padding-top : 40px ; -} diff --git a/src/data_manager.rs b/src/data_manager.rs index a7fc953..c2973d1 100644 --- a/src/data_manager.rs +++ b/src/data_manager.rs @@ -51,8 +51,40 @@ impl DataManager { } ) . await ? ; Ok (collection_overview) } + + pub async fn get_film_details ( & self , uuid : String ) -> Result { + let film_details = self . sqlite_client . 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 , + films . source , + sources . file_path , + sources . subtitle_file_path , + sources . video_track , + sources . audio_track , + sources . subtitle_track + from films left join sources + on films . source = sources . 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 , @@ -135,11 +167,71 @@ fn row_to_series_overview ( row : & Row ) -> rusqlite :: Result } ) } + + +# [ 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 file_path : PathBuf , + pub subtitle_file_path : Option , + pub video_track : Option , + 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_uuid = row . get :: < _ , Option > (6) ? ; + let source = match source_uuid { + Some (_) => { + let file_path = PathBuf :: from ( row . get :: < _ , String > (7) ? ) ; + let subtitle_file_path = row . get :: < _ , Option > (8) ? . map ( PathBuf :: from ) ; + let video_track = row . get (9) ? ; + let audio_track = row . get (10) ? ; + let subtitle_track = row . get (11) ? ; + + Some ( SourceDetails { + file_path , + subtitle_file_path , + video_track , + 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 ! () , + _ => panic ! ( "{}" , error ) , } } } @@ -150,10 +242,10 @@ impl From < rusqlite :: Error > for ZoodexError { SqliteFailure ( error , _ ) => { match error . code { CannotOpen => CollectionFileReadError , - _ => panic ! () , + _ => panic ! ( "{}" , error ) , } } , - _ => panic ! () , + _ => panic ! ( "{}" , error ) , } } } diff --git a/src/main.rs b/src/main.rs index e8ec642..417e095 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,14 @@ use gtk4 :: style_context_add_provider_for_display ; use gtk4 :: STYLE_PROVIDER_PRIORITY_APPLICATION ; use gtk4 :: gdk :: * ; use gtk4 :: glib :: * ; -use libadwaita :: * ; +use libadwaita :: Application ; use libadwaita :: prelude :: * ; use crate :: data_manager :: * ; use crate :: error :: * ; use crate :: error :: ZoodexError :: * ; use crate :: ui :: * ; +use crate :: utility :: * ; @@ -38,13 +39,22 @@ fn add_style_provider ( _ : & Application ) { } fn show_window ( application : & Application ) { - let ui = UI :: new (application) ; - ui . show_window () ; + let window = leak ( Window :: new (application) ) ; spawn_future_local ( async move { async_result_context ! ( async { - let data_manager = DataManager :: new () . await ? ; + let data_manager = leak ( DataManager :: new () . await ? ) ; + + let ui = UI :: new ( + window , + async |film_uuid| { + data_manager . get_film_details (film_uuid) . await + . expect ("A film with the given UUID should exist") + } , + ) ; + window . show () ; + let collection = data_manager . get_collection_overview () . await ? ; ui . render_collection_overview (collection) . await ; Ok (()) @@ -53,7 +63,7 @@ fn show_window ( application : & Application ) { match error { CollectionFileReadError => eprintln ! ("Could not read collection file") , } ; - ui . close_window () ; + window . close () ; } , ) ; } ) ; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 4d93e8f..c4fac8b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -8,6 +8,7 @@ use gtk4 :: prelude :: * ; use libadwaita :: * ; use libadwaita :: prelude :: * ; use libadwaita :: ViewSwitcherPolicy :: * ; +use std :: process :: * ; use crate :: data_manager :: * ; use crate :: ui :: collatable_container :: * ; @@ -18,43 +19,76 @@ use crate :: utility :: * ; pub struct UI { - window : & 'static ApplicationWindow , films_component : CollatableMediaContainer , series_component : CollatableMediaContainer , } impl UI { - pub fn new ( application : & Application ) -> UI { - let window = leak ( application_window ! ( - @ application : application ; - @ title : "Zoödex" ; - ) ) ; + pub fn new ( + window : & 'static Window , + get_film_details : impl AsyncFn (String) -> FilmDetails + 'static , + ) -> UI { + let get_film_details = leak (get_film_details) ; - let films_component = CollatableMediaContainer :: :: new ( - |film| dialog ! ( - & g_box ! ( - @ option_children ; - @ orientation : Vertical ; - @ css_classes : & [ "media-modal" ] ; + let films_component = CollatableMediaContainer :: :: new ( |film| { + glib :: spawn_future_local ( async { + let film_details = get_film_details ( film . uuid ) . await ; - Some ( label ! ( - @ css_classes : & [ "title-1" ] ; - film . name . as_str () , - ) ) . as_ref () , + dialog ! ( + & g_box ! ( + @ option_children ; + @ orientation : Vertical ; + @ spacing : 40 ; + @ css_classes : & [ "media-modal" ] ; - film . original_name . map ( - |original_name| label ! ( original_name . as_str () ) , - ) . as_ref () , + Some ( label ! ( + @ css_classes : & [ "title-1" ] ; + film_details . name . as_str () , + ) ) . as_ref () , - Some ( label ! ( - @ css_classes : & [ "top-padding" ] ; - & format ! ( "Release date: {}" , film . release_date ) , - ) ) . as_ref () , - ) , - ) . present ( Some (window) ) , - ) ; + film_details . original_name . map ( + |original_name| label ! ( original_name . as_str () ) , + ) . as_ref () , + + Some ( label ! ( + & format ! ( "Release date: {}" , film_details . release_date ) , + ) ) . as_ref () , + + film_details . source . map ( + |source| button ! ( + @ css_classes : & [ "suggested-action" , "circular" ] ; + @ connect_clicked : move |_| { + let source = source . clone () ; + + let arguments = [ + Some ( source . file_path . as_os_str () . to_owned () ) , + source . subtitle_file_path . map ( + |subtitle_file_path| concat_os_str ! ( "--mpv-sub-file=" , subtitle_file_path ) , + ) , + source . video_track . map ( + |video_track| concat_os_str ! ( "--mpv-vid=" , to_os_string (video_track) ) , + ) , + source . audio_track . map ( + |audio_track| concat_os_str ! ( "--mpv-aid=" , to_os_string (audio_track) ) , + ) , + source . subtitle_track . map ( + |subtitle_track| concat_os_str ! ( "--mpv-sid=" , to_os_string (subtitle_track) ) , + ) , + ] . iter () . filter_map ( Option :: clone ) . collect :: < Vec <_> > () ; + + Command :: new ("/usr/bin/celluloid") . args (arguments) . spawn () + . unwrap () ; // TODO: Better error handling for UI callbacks in general + } ; + & image ! ( @ icon_name : "media-playback-start-symbolic" ; ) , + ) , + ) . as_ref () , + ) , + ) . present ( Some ( & window . libadwaita_window ) ) + + } ) ; + } ) ; let series_component = CollatableMediaContainer :: :: new ( - |series| dialog ! () . present ( Some (window) ) , + |series| dialog ! () . present ( Some ( & window . libadwaita_window ) ) , ) ; let switched = view_stack ! ( ( "Films" , "camera-video-symbolic" , films_component . get_widget () ) , @@ -64,17 +98,13 @@ impl UI { & view_switcher ! ( @ policy : Wide ; & switched ) , ) ; - window . set_content ( Some ( + window . libadwaita_window . set_content ( Some ( & toolbar_view ! ( @ top_bar : & header_bar ; & switched ) , ) ) ; - UI { window , films_component , series_component } + UI { films_component , series_component } } - pub fn show_window ( & self ) { self . window . set_visible (true) } - - pub fn close_window ( & self ) { self . window . close () } - pub async fn render_collection_overview ( & self , collection : CollectionOverview ) { join ! ( self . films_component . set_media ( collection . films ) , @@ -82,3 +112,23 @@ impl UI { ) ; } } + + + +pub struct Window { + libadwaita_window : ApplicationWindow , +} + +impl Window { + pub fn new (application : & Application ) -> Self { + let libadwaita_window = ApplicationWindow :: builder () + . application (application) + . title ("Zoödex") + . build () ; + Self { libadwaita_window } + } + + pub fn show ( & self ) { self . libadwaita_window . set_visible (true) } + + pub fn close ( & self ) { self . libadwaita_window . close () } +} diff --git a/src/ui/utility.rs b/src/ui/utility.rs index 53178b6..4f5e210 100644 --- a/src/ui/utility.rs +++ b/src/ui/utility.rs @@ -253,7 +253,7 @@ macro_rules ! pango_attributes { ( -pub (crate) use { +# [ allow (unused_imports) ] pub (crate) use { application_window , bin , button , diff --git a/src/utility.rs b/src/utility.rs index e6ad3d5..a5d7323 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -1,3 +1,29 @@ +use std :: ffi :: * ; +use std :: fmt :: * ; + + + +macro_rules ! concat_os_str { ( + $ base : expr , + $ ( $ suffix : expr ) , + +) => { { + let mut base = std :: ffi :: OsString :: from ( $ base ) ; + $ ( base . push ( $ suffix ) ; ) + + base +} } } + pub fn leak < 'l , Type > ( inner : Type ) -> & 'l Type { Box :: leak ( Box :: new (inner) ) } + +pub fn leak_mut < 'l , Type > ( inner : Type ) -> & 'l mut Type { + Box :: leak ( Box :: new (inner) ) +} + +pub fn to_os_string ( value : impl Display + Sized ) -> OsString { + OsString :: from ( ToString :: to_string ( & value ) ) +} + + + +# [ allow (unused_imports) ] pub (crate) use concat_os_str ;