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.
This commit is contained in:
parent
ec0b5ce757
commit
8a8ea5fb94
4 changed files with 92 additions and 62 deletions
|
|
@ -10,4 +10,24 @@ macro_rules! concat_os_str {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub trait ResultExt<T, E> {
|
||||||
|
async fn and_then_async<U, F>(self, op: F) -> Result<U, E>
|
||||||
|
where
|
||||||
|
F: AsyncFnOnce(T) -> Result<U, E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> ResultExt<T, E> for Result<T, E> {
|
||||||
|
async fn and_then_async<U, F>(self, op: F) -> Result<U, E>
|
||||||
|
where
|
||||||
|
F: AsyncFnOnce(T) -> Result<U, E>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Ok(value) => op(value).await,
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub(crate) use concat_os_str;
|
pub(crate) use concat_os_str;
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,7 @@ impl DataManager {
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum DataManagerError {
|
pub enum DataManagerError {
|
||||||
NoHomeDir,
|
NoHomeDir,
|
||||||
CannotOpenSharedDB,
|
CannotOpenDB,
|
||||||
UnknownSharedDBError,
|
UnknownDBError,
|
||||||
CannotOpenLocalDB,
|
|
||||||
UnknownLocalDBError,
|
|
||||||
UnknownTextureError,
|
UnknownTextureError,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,38 +5,29 @@ use std::fmt::{Debug, Formatter};
|
||||||
use async_sqlite::rusqlite::fallible_iterator::FallibleIterator;
|
use async_sqlite::rusqlite::fallible_iterator::FallibleIterator;
|
||||||
use async_sqlite::rusqlite::{OpenFlags, Row};
|
use async_sqlite::rusqlite::{OpenFlags, Row};
|
||||||
use async_sqlite::{Client, ClientBuilder, rusqlite};
|
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::persist::data_manager::DataManagerError;
|
||||||
use crate::views::overview::{FilmOverview, SeriesOverview};
|
use crate::views::overview::{FilmOverview, SeriesOverview};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct SqliteManager {
|
pub struct SqliteManager {
|
||||||
client_shared: Client,
|
client: Client,
|
||||||
client_local: Client,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SqliteManager {
|
impl SqliteManager {
|
||||||
pub async fn new(data_dir: &OsStr) -> Result<SqliteManager, DataManagerError> {
|
pub async fn new(data_dir: &OsStr) -> Result<SqliteManager, DataManagerError> {
|
||||||
let (client_shared, client_local) = try_join!(
|
let client = create_client(data_dir).await?;
|
||||||
create_client(data_dir, DBType::Shared),
|
Ok(SqliteManager { client })
|
||||||
create_client(data_dir, DBType::Local),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(SqliteManager {
|
|
||||||
client_shared,
|
|
||||||
client_local,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The order of the items is undefined.
|
// The order of the items is undefined.
|
||||||
pub async fn films_overview(&self) -> Result<Vec<FilmOverview>, DataManagerError> {
|
pub async fn films_overview(&self) -> Result<Vec<FilmOverview>, DataManagerError> {
|
||||||
let overview = self
|
let overview = self
|
||||||
.client_shared
|
.client
|
||||||
.conn(|connection| {
|
.conn(|connection| {
|
||||||
let overview = connection
|
connection
|
||||||
.prepare(
|
.prepare(
|
||||||
"
|
"
|
||||||
select uuid, name, original_name, release_date, runtime_minutes
|
select uuid, name, original_name, release_date, runtime_minutes
|
||||||
|
|
@ -47,15 +38,14 @@ impl SqliteManager {
|
||||||
.query(())
|
.query(())
|
||||||
.expect("parameters in films overview query should match those in its statement")
|
.expect("parameters in films overview query should match those in its statement")
|
||||||
.map(row_to_film_overview)
|
.map(row_to_film_overview)
|
||||||
.collect()?;
|
.collect()
|
||||||
Ok(overview)
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
overview.map_err(|async_sqlite_error| match async_sqlite_error {
|
overview.map_err(|async_sqlite_error| match async_sqlite_error {
|
||||||
async_sqlite::Error::Closed => panic!(
|
async_sqlite::Error::Closed => {
|
||||||
"shared database connection should remain open as long as the application is running"
|
panic!("database connection should remain open as long as the application is running")
|
||||||
),
|
}
|
||||||
async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error {
|
async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error {
|
||||||
rusqlite::Error::InvalidColumnIndex(_) => {
|
rusqlite::Error::InvalidColumnIndex(_) => {
|
||||||
panic!("column indices obtained from films overview query should exist")
|
panic!("column indices obtained from films overview query should exist")
|
||||||
|
|
@ -66,18 +56,18 @@ impl SqliteManager {
|
||||||
rusqlite::Error::InvalidColumnType(..) => panic!(
|
rusqlite::Error::InvalidColumnType(..) => panic!(
|
||||||
"values obtained from films overview query should have a type matching their column"
|
"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.
|
// The order of the items is undefined.
|
||||||
pub async fn series_overview(&self) -> Result<Vec<SeriesOverview>, DataManagerError> {
|
pub async fn series_overview(&self) -> Result<Vec<SeriesOverview>, DataManagerError> {
|
||||||
let overview = self
|
let overview = self
|
||||||
.client_shared
|
.client
|
||||||
.conn(|connection| {
|
.conn(|connection| {
|
||||||
let overview = connection
|
connection
|
||||||
.prepare(
|
.prepare(
|
||||||
"
|
"
|
||||||
select series.uuid, series.name, series.original_name,
|
select series.uuid, series.name, series.original_name,
|
||||||
|
|
@ -91,15 +81,14 @@ impl SqliteManager {
|
||||||
.query(())
|
.query(())
|
||||||
.expect("parameters in series overview query should match those in its statement")
|
.expect("parameters in series overview query should match those in its statement")
|
||||||
.map(row_to_series_overview)
|
.map(row_to_series_overview)
|
||||||
.collect()?;
|
.collect()
|
||||||
Ok(overview)
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
overview.map_err(|async_sqlite_error| match async_sqlite_error {
|
overview.map_err(|async_sqlite_error| match async_sqlite_error {
|
||||||
async_sqlite::Error::Closed => panic!(
|
async_sqlite::Error::Closed => {
|
||||||
"shared database connection should remain open as long as the application is running"
|
panic!("database connection should remain open as long as the application is running")
|
||||||
),
|
}
|
||||||
async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error {
|
async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error {
|
||||||
rusqlite::Error::InvalidColumnIndex(_) => {
|
rusqlite::Error::InvalidColumnIndex(_) => {
|
||||||
panic!("column indices obtained from series overview query should exist")
|
panic!("column indices obtained from series overview query should exist")
|
||||||
|
|
@ -110,9 +99,9 @@ impl SqliteManager {
|
||||||
rusqlite::Error::InvalidColumnType(..) => panic!(
|
rusqlite::Error::InvalidColumnType(..) => panic!(
|
||||||
"values obtained from series overview query should have a type matching their column"
|
"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<Client, DataManagerError> {
|
async fn create_client(data_dir: &OsStr) -> Result<Client, DataManagerError> {
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.path(concat_os_str!(data_dir, filename))
|
.path("")
|
||||||
.flags(open_mode | OpenFlags::SQLITE_OPEN_NO_MUTEX)
|
.flags(OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX)
|
||||||
.open()
|
.open()
|
||||||
.await;
|
.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 {
|
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 {
|
async_sqlite::Error::Rusqlite(rusqlite_error) => match rusqlite_error {
|
||||||
rusqlite::Error::SqliteFailure(sqlite_error, _) => match sqlite_error.code {
|
rusqlite::Error::SqliteFailure(sqlite_error, _) => match sqlite_error.code {
|
||||||
rusqlite::ffi::ErrorCode::CannotOpen => cannot_open_err,
|
rusqlite::ffi::ErrorCode::CannotOpen => DataManagerError::CannotOpenDB,
|
||||||
_ => unknown_err,
|
_ => DataManagerError::UnknownDBError,
|
||||||
},
|
},
|
||||||
_ => unknown_err,
|
_ => DataManagerError::UnknownDBError,
|
||||||
},
|
},
|
||||||
_ => unknown_err,
|
_ => DataManagerError::UnknownDBError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use gtk4::prelude::{BoxExt, OrientableExt, WidgetExt};
|
use gtk4::prelude::{BoxExt, ButtonExt, OrientableExt, WidgetExt};
|
||||||
use gtk4::{Label, Orientation};
|
use gtk4::{Button, Label, Orientation};
|
||||||
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent, component};
|
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent, component};
|
||||||
|
|
||||||
use crate::views::overview::FilmOverview;
|
use crate::views::overview::FilmOverview;
|
||||||
|
|
@ -25,7 +25,12 @@ impl SimpleComponent for FilmDetails {
|
||||||
Label {
|
Label {
|
||||||
set_css_classes: &["title-1"],
|
set_css_classes: &["title-1"],
|
||||||
set_label: model.film_overview.name.as_str(),
|
set_label: model.film_overview.name.as_str(),
|
||||||
}
|
},
|
||||||
|
|
||||||
|
Button {
|
||||||
|
set_css_classes: &["suggested-action", "circular"],
|
||||||
|
set_icon_name: "media-playback-start-symbolic",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue