2020-03-05 07:29:04 +01:00
|
|
|
// Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2020-03-14 02:52:39 +01:00
|
|
|
use crate::err::{AnkiError, Result};
|
2020-03-05 05:35:48 +01:00
|
|
|
use crate::i18n::I18n;
|
|
|
|
use crate::log::Logger;
|
|
|
|
use crate::storage::{SqliteStorage, StorageContext};
|
2020-03-13 23:26:42 +01:00
|
|
|
use std::path::PathBuf;
|
2020-03-05 05:35:48 +01:00
|
|
|
|
2020-03-13 23:26:42 +01:00
|
|
|
pub fn open_collection<P: Into<PathBuf>>(
|
2020-03-05 05:35:48 +01:00
|
|
|
path: P,
|
2020-03-13 23:26:42 +01:00
|
|
|
media_folder: P,
|
|
|
|
media_db: P,
|
2020-03-05 05:35:48 +01:00
|
|
|
server: bool,
|
|
|
|
i18n: I18n,
|
|
|
|
log: Logger,
|
|
|
|
) -> Result<Collection> {
|
2020-03-13 23:26:42 +01:00
|
|
|
let col_path = path.into();
|
|
|
|
let storage = SqliteStorage::open_or_create(&col_path)?;
|
2020-03-05 05:35:48 +01:00
|
|
|
|
|
|
|
let col = Collection {
|
|
|
|
storage,
|
2020-03-13 23:26:42 +01:00
|
|
|
col_path,
|
|
|
|
media_folder: media_folder.into(),
|
|
|
|
media_db: media_db.into(),
|
2020-03-05 05:35:48 +01:00
|
|
|
server,
|
|
|
|
i18n,
|
|
|
|
log,
|
2020-03-14 02:52:39 +01:00
|
|
|
state: CollectionState::Normal,
|
2020-03-05 05:35:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(col)
|
|
|
|
}
|
|
|
|
|
2020-03-14 02:52:39 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum CollectionState {
|
|
|
|
Normal,
|
|
|
|
// in this state, the DB must not be closed
|
|
|
|
MediaSyncRunning,
|
|
|
|
}
|
|
|
|
|
2020-03-05 05:35:48 +01:00
|
|
|
pub struct Collection {
|
|
|
|
pub(crate) storage: SqliteStorage,
|
2020-03-13 23:26:42 +01:00
|
|
|
#[allow(dead_code)]
|
|
|
|
pub(crate) col_path: PathBuf,
|
|
|
|
pub(crate) media_folder: PathBuf,
|
|
|
|
pub(crate) media_db: PathBuf,
|
2020-03-05 05:35:48 +01:00
|
|
|
pub(crate) server: bool,
|
|
|
|
pub(crate) i18n: I18n,
|
|
|
|
pub(crate) log: Logger,
|
2020-03-14 02:52:39 +01:00
|
|
|
state: CollectionState,
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|
|
|
|
|
2020-03-27 06:11:07 +01:00
|
|
|
pub(crate) enum CollectionOp {}
|
2020-03-05 05:35:48 +01:00
|
|
|
|
|
|
|
pub(crate) struct RequestContext<'a> {
|
|
|
|
pub storage: StorageContext<'a>,
|
|
|
|
pub i18n: &'a I18n,
|
|
|
|
pub log: &'a Logger,
|
2020-03-05 07:29:04 +01:00
|
|
|
pub should_commit: bool,
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Collection {
|
|
|
|
/// Call the provided closure with a RequestContext that exists for
|
|
|
|
/// the duration of the call. The request will cache prepared sql
|
|
|
|
/// statements, so should be passed down the call tree.
|
|
|
|
///
|
|
|
|
/// This function should be used for read-only requests. To mutate
|
|
|
|
/// the database, use transact() instead.
|
|
|
|
pub(crate) fn with_ctx<F, R>(&self, func: F) -> Result<R>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut RequestContext) -> Result<R>,
|
|
|
|
{
|
|
|
|
let mut ctx = RequestContext {
|
|
|
|
storage: self.storage.context(self.server),
|
|
|
|
i18n: &self.i18n,
|
|
|
|
log: &self.log,
|
2020-03-05 07:29:04 +01:00
|
|
|
should_commit: true,
|
2020-03-05 05:35:48 +01:00
|
|
|
};
|
|
|
|
func(&mut ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Execute the provided closure in a transaction, rolling back if
|
|
|
|
/// an error is returned.
|
|
|
|
pub(crate) fn transact<F, R>(&self, op: Option<CollectionOp>, func: F) -> Result<R>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut RequestContext) -> Result<R>,
|
|
|
|
{
|
|
|
|
self.with_ctx(|ctx| {
|
|
|
|
ctx.storage.begin_rust_trx()?;
|
|
|
|
|
|
|
|
let mut res = func(ctx);
|
|
|
|
|
2020-03-05 07:29:04 +01:00
|
|
|
if res.is_ok() && ctx.should_commit {
|
|
|
|
if let Err(e) = ctx.storage.mark_modified() {
|
|
|
|
res = Err(e);
|
|
|
|
} else if let Err(e) = ctx.storage.commit_rust_op(op) {
|
2020-03-05 05:35:48 +01:00
|
|
|
res = Err(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-05 07:29:04 +01:00
|
|
|
if res.is_err() || !ctx.should_commit {
|
2020-03-05 05:35:48 +01:00
|
|
|
ctx.storage.rollback_rust_trx()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
res
|
|
|
|
})
|
|
|
|
}
|
2020-03-14 02:52:39 +01:00
|
|
|
|
|
|
|
pub(crate) fn set_media_sync_running(&mut self) -> Result<()> {
|
|
|
|
if self.state == CollectionState::Normal {
|
|
|
|
self.state = CollectionState::MediaSyncRunning;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(AnkiError::invalid_input("media sync already running"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn set_media_sync_finished(&mut self) -> Result<()> {
|
|
|
|
if self.state == CollectionState::MediaSyncRunning {
|
|
|
|
self.state = CollectionState::Normal;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(AnkiError::invalid_input("media sync not running"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn can_close(&self) -> bool {
|
|
|
|
self.state == CollectionState::Normal
|
|
|
|
}
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|