From 8a8ea5fb94de53491117fe4315da53cf34d1ad08 Mon Sep 17 00:00:00 2001 From: Reinout Meliesie Date: Thu, 22 Jan 2026 12:02:51 +0100 Subject: [PATCH] Attach shared and local DBs to temporary main DB We now have one connection to the temporary main DB instead of one for each. The two actual databases are attached to it during initialization. --- src/persist/common.rs | 20 +++ src/persist/data_manager.rs | 6 +- src/persist/sqlite_manager.rs | 117 ++++++++++-------- .../components/media_details/film_details.rs | 11 +- 4 files changed, 92 insertions(+), 62 deletions(-) diff --git a/src/persist/common.rs b/src/persist/common.rs index 1ea4719..5a26a55 100644 --- a/src/persist/common.rs +++ b/src/persist/common.rs @@ -10,4 +10,24 @@ macro_rules! concat_os_str { +pub trait ResultExt { + async fn and_then_async(self, op: F) -> Result + where + F: AsyncFnOnce(T) -> Result; +} + +impl ResultExt for Result { + async fn and_then_async(self, op: F) -> Result + where + F: AsyncFnOnce(T) -> Result, + { + match self { + Ok(value) => op(value).await, + Err(error) => Err(error), + } + } +} + + + pub(crate) use concat_os_str; diff --git a/src/persist/data_manager.rs b/src/persist/data_manager.rs index 2e265a5..d4a6278 100644 --- a/src/persist/data_manager.rs +++ b/src/persist/data_manager.rs @@ -74,9 +74,7 @@ impl DataManager { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DataManagerError { NoHomeDir, - CannotOpenSharedDB, - UnknownSharedDBError, - CannotOpenLocalDB, - UnknownLocalDBError, + CannotOpenDB, + UnknownDBError, UnknownTextureError, } diff --git a/src/persist/sqlite_manager.rs b/src/persist/sqlite_manager.rs index 2599f44..f3ace8a 100644 --- a/src/persist/sqlite_manager.rs +++ b/src/persist/sqlite_manager.rs @@ -5,38 +5,29 @@ 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::common::{ResultExt, concat_os_str}; use crate::persist::data_manager::DataManagerError; use crate::views::overview::{FilmOverview, SeriesOverview}; pub struct SqliteManager { - client_shared: Client, - client_local: Client, + client: 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, - }) + let client = create_client(data_dir).await?; + Ok(SqliteManager { client }) } // The order of the items is undefined. pub async fn films_overview(&self) -> Result, DataManagerError> { let overview = self - .client_shared + .client .conn(|connection| { - let overview = connection + connection .prepare( " select uuid, name, original_name, release_date, runtime_minutes @@ -47,15 +38,14 @@ impl SqliteManager { .query(()) .expect("parameters in films overview query should match those in its statement") .map(row_to_film_overview) - .collect()?; - Ok(overview) + .collect() }) .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::Closed => { + panic!("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") @@ -66,18 +56,18 @@ impl SqliteManager { rusqlite::Error::InvalidColumnType(..) => panic!( "values obtained from films overview query should have a type matching their column" ), - _ => DataManagerError::UnknownSharedDBError, + _ => DataManagerError::UnknownDBError, }, - _ => DataManagerError::UnknownSharedDBError, + _ => DataManagerError::UnknownDBError, }) } // The order of the items is undefined. pub async fn series_overview(&self) -> Result, DataManagerError> { let overview = self - .client_shared + .client .conn(|connection| { - let overview = connection + connection .prepare( " select series.uuid, series.name, series.original_name, @@ -91,15 +81,14 @@ impl SqliteManager { .query(()) .expect("parameters in series overview query should match those in its statement") .map(row_to_series_overview) - .collect()?; - Ok(overview) + .collect() }) .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::Closed => { + panic!("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") @@ -110,9 +99,9 @@ impl SqliteManager { rusqlite::Error::InvalidColumnType(..) => panic!( "values obtained from series overview query should have a type matching their column" ), - _ => DataManagerError::UnknownSharedDBError, + _ => DataManagerError::UnknownDBError, }, - _ => DataManagerError::UnknownSharedDBError, + _ => DataManagerError::UnknownDBError, }) } } @@ -123,39 +112,57 @@ impl Debug for SqliteManager { } } -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, - }; - +async fn create_client(data_dir: &OsStr) -> Result { let client = ClientBuilder::new() - .path(concat_os_str!(data_dir, filename)) - .flags(open_mode | OpenFlags::SQLITE_OPEN_NO_MUTEX) + .path("") + .flags(OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX) .open() .await; + let data_dir = data_dir.to_os_string(); + let client = client + .and_then_async(async move |client| { + client + .conn(move |connection| { + let shared_path_os_str = concat_os_str!(&data_dir, "/shared.sqlite"); + let shared_path = shared_path_os_str + .to_str() + .expect("shared database path should be valid Unicode"); + connection + .execute("attach database :path as shared", &[(":path", shared_path)]) + .expect( + "shared database attaching statement should be valid SQL with matching parameters", + ); + + let local_path_os_str = concat_os_str!(&data_dir, "/local.sqlite"); + let local_path = local_path_os_str + .to_str() + .expect("local database path should be valid Unicode"); + connection + .execute("attach database :path as local", &[(":path", local_path)]) + .expect( + "local database attaching statement should be valid SQL with matching parameters", + ); + + Ok(()) + }) + .await + .map(|_| client) + }) + .await; + client.map_err(|async_sqlite_error| match async_sqlite_error { + async_sqlite::Error::Closed => { + panic!("database connection should remain open as long as the application is running") + } 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, + rusqlite::ffi::ErrorCode::CannotOpen => DataManagerError::CannotOpenDB, + _ => DataManagerError::UnknownDBError, }, - _ => unknown_err, + _ => DataManagerError::UnknownDBError, }, - _ => unknown_err, + _ => DataManagerError::UnknownDBError, }) } diff --git a/src/ui/components/media_details/film_details.rs b/src/ui/components/media_details/film_details.rs index a1e0e87..1638587 100644 --- a/src/ui/components/media_details/film_details.rs +++ b/src/ui/components/media_details/film_details.rs @@ -1,5 +1,5 @@ -use gtk4::prelude::{BoxExt, OrientableExt, WidgetExt}; -use gtk4::{Label, Orientation}; +use gtk4::prelude::{BoxExt, ButtonExt, OrientableExt, WidgetExt}; +use gtk4::{Button, Label, Orientation}; use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent, component}; use crate::views::overview::FilmOverview; @@ -25,7 +25,12 @@ impl SimpleComponent for FilmDetails { Label { set_css_classes: &["title-1"], set_label: model.film_overview.name.as_str(), - } + }, + + Button { + set_css_classes: &["suggested-action", "circular"], + set_icon_name: "media-playback-start-symbolic", + }, } }