sqlite3: add optional directory field in DatabaseDescription

Databases described by `DatabaseDescription` are created in XDG Data
directories by default. Add an optional explicit directory field so that
tests and other API consumers can override that location.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/473/head
Manos Pitsidianakis 2 months ago
parent 26d33ce523
commit 32e3be8b10
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -43,6 +43,7 @@ const DB: DatabaseDescription = DatabaseDescription {
name: "index.db",
identifier: None,
application_prefix: "meli",
directory: None,
init_script: Some(
"CREATE TABLE IF NOT EXISTS envelopes (
id INTEGER PRIMARY KEY,

@ -214,9 +214,10 @@ impl UIDStore {
#[cfg(not(feature = "sqlite3"))]
return Ok(None);
#[cfg(feature = "sqlite3")]
return Ok(Some(sync::sqlite3_cache::Sqlite3Cache::get(Arc::clone(
self,
))?));
return Ok(Some(sync::sqlite3_cache::Sqlite3Cache::get(
Arc::clone(self),
None,
)?));
}
pub fn reset_db(self: &Arc<Self>) -> Result<()> {
@ -228,7 +229,7 @@ impl UIDStore {
#[cfg(feature = "sqlite3")]
use crate::imap::sync::cache::ImapCacheReset;
#[cfg(feature = "sqlite3")]
return sync::sqlite3_cache::Sqlite3Cache::reset_db(self);
return sync::sqlite3_cache::Sqlite3Cache::reset_db(self, None);
}
}

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::convert::TryFrom;
use std::{convert::TryFrom, path::Path};
use super::*;
use crate::{
@ -110,7 +110,7 @@ pub trait ImapCache: Send + std::fmt::Debug {
}
pub trait ImapCacheReset: Send + std::fmt::Debug {
fn reset_db(uid_store: &UIDStore) -> Result<()>
fn reset_db(uid_store: &UIDStore, data_dir: Option<&Path>) -> Result<()>
where
Self: Sized;
}

@ -20,7 +20,11 @@
//
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
use std::{collections::BTreeSet, sync::Arc};
use std::{
collections::BTreeSet,
path::{Path, PathBuf},
sync::Arc,
};
use smallvec::SmallVec;
@ -46,12 +50,14 @@ pub struct Sqlite3Cache {
connection: Connection,
loaded_mailboxes: BTreeSet<MailboxHash>,
uid_store: Arc<UIDStore>,
data_dir: Option<PathBuf>,
}
const DB_DESCRIPTION: DatabaseDescription = DatabaseDescription {
name: "header_cache.db",
identifier: None,
application_prefix: "meli",
directory: None,
init_script: Some(
"PRAGMA foreign_keys = true;
PRAGMA encoding = 'UTF-8';
@ -103,9 +109,11 @@ impl FromSql for ModSequence {
}
impl Sqlite3Cache {
pub fn get(uid_store: Arc<UIDStore>) -> Result<Box<dyn ImapCache>> {
pub fn get(uid_store: Arc<UIDStore>, data_dir: Option<&Path>) -> Result<Box<dyn ImapCache>> {
let data_dir = data_dir.map(|p| p.to_path_buf());
let db_desc = DatabaseDescription {
identifier: Some(uid_store.account_name.to_string().into()),
directory: data_dir.clone().map(|p| p.into()),
..DB_DESCRIPTION.clone()
};
let connection = match db_desc.open_or_create_db() {
@ -124,6 +132,7 @@ impl Sqlite3Cache {
connection,
loaded_mailboxes: BTreeSet::default(),
uid_store,
data_dir,
}))
}
@ -141,9 +150,10 @@ impl Sqlite3Cache {
}
impl ImapCacheReset for Sqlite3Cache {
fn reset_db(uid_store: &UIDStore) -> Result<()> {
fn reset_db(uid_store: &UIDStore, data_dir: Option<&Path>) -> Result<()> {
let db_desc = DatabaseDescription {
identifier: Some(uid_store.account_name.to_string().into()),
directory: data_dir.map(|p| p.to_path_buf().into()),
..DB_DESCRIPTION.clone()
};
db_desc.reset_db()
@ -152,7 +162,7 @@ impl ImapCacheReset for Sqlite3Cache {
impl ImapCache for Sqlite3Cache {
fn reset(&mut self) -> Result<()> {
Self::reset_db(&self.uid_store)
Self::reset_db(&self.uid_store, self.data_dir.as_deref())
}
fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>> {
@ -427,6 +437,7 @@ impl ImapCache for Sqlite3Cache {
ref mut connection,
ref uid_store,
loaded_mailboxes: _,
data_dir: _,
} = self;
let tx = connection.transaction()?;
for item in fetches {
@ -485,6 +496,7 @@ impl ImapCache for Sqlite3Cache {
ref mut connection,
ref uid_store,
loaded_mailboxes: _,
data_dir: _,
} = self;
let tx = connection.transaction()?;
let values = std::rc::Rc::new(env_hashes.iter().map(Value::from).collect::<Vec<Value>>());
@ -544,6 +556,7 @@ impl ImapCache for Sqlite3Cache {
ref mut connection,
ref uid_store,
loaded_mailboxes: _,
data_dir: _,
} = self;
let tx = connection.transaction()?;
let mut hash_index_lck = uid_store.hash_index.lock().unwrap();

@ -37,6 +37,7 @@ mod inner {
name: "nntp_store.db",
application_prefix: "meli",
identifier: None,
directory: None,
init_script: Some(
"PRAGMA foreign_keys = true;
PRAGMA encoding = 'UTF-8';

@ -19,7 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{borrow::Cow, os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc};
use std::{
borrow::Cow,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
sync::Arc,
};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput};
pub use rusqlite::{self, config::DbConfig, params, Connection};
@ -40,6 +45,9 @@ pub struct DatabaseDescription {
/// directories, used for when the consumer application is not `meli`
/// itself.
pub application_prefix: &'static str,
/// Optionally override file system location instead of saving at `XDG` data
/// directory.
pub directory: Option<Cow<'static, Path>>,
/// A script that initializes the schema of the database.
pub init_script: Option<&'static str>,
/// The current value of the `user_version` `PRAGMA` of the `sqlite3`
@ -60,16 +68,65 @@ impl DatabaseDescription {
|| self.name.into(),
|id| format!("{}_{}", id, self.name).into(),
);
for (field_name, field_value) in [
("name", self.name),
("identifier", self.identifier.as_deref().unwrap_or_default()),
("application_prefix", self.application_prefix),
] {
if field_value.contains(std::path::MAIN_SEPARATOR_STR) {
return Err(Error::new(format!(
"Database description for `{}{}{}` field {} cannot contain current platform's \
path separator {}. Got: {}.",
self.identifier.as_deref().unwrap_or_default(),
if self.identifier.is_none() { "" } else { ":" },
self.name,
field_name,
std::path::MAIN_SEPARATOR_STR,
field_value,
))
.set_kind(ErrorKind::ValueError));
}
}
if let Some(directory) = self.directory.as_deref() {
if !directory.is_dir() {
return Err(Error::new(format!(
"Database description for `{}{}{}` expects a valid directory path value. Got: \
{}.",
self.identifier.as_deref().unwrap_or_default(),
if self.identifier.is_none() { "" } else { ":" },
self.name,
directory.display()
))
.set_kind(ErrorKind::ValueError));
}
return Ok(directory.join(name.as_ref()));
}
let data_dir =
xdg::BaseDirectories::with_prefix(self.application_prefix).map_err(|err| {
Error::new(format!(
"Could not create sqlite3 database file for `{}{}{}` in XDG data directory.",
self.identifier.as_deref().unwrap_or_default(),
if self.identifier.is_none() { "" } else { ":" },
self.name,
))
.set_details(format!(
"Could not open XDG data directory with prefix {}",
self.application_prefix
))
.set_kind(ErrorKind::Platform)
.set_source(Some(Arc::new(err)))
})?;
data_dir.place_data_file(name.as_ref()).map_err(|err| {
Error::new(format!("Could not create `{}`", name)).set_source(Some(Arc::new(err)))
Error::new(format!(
"Could not create sqlite3 database file for `{}{}{}` in XDG data directory.",
self.identifier.as_deref().unwrap_or_default(),
if self.identifier.is_none() { "" } else { ":" },
self.name,
))
.set_kind(ErrorKind::Platform)
.set_source(Some(Arc::new(err)))
})
}

Loading…
Cancel
Save