anki/rslib/src/collection.rs

142 lines
4.0 KiB
Rust
Raw Normal View History

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::err::Result;
use crate::i18n::I18n;
use crate::log::Logger;
use crate::types::Usn;
use crate::{
decks::{Deck, DeckID},
2020-04-18 02:23:14 +02:00
notetype::{NoteType, NoteTypeID},
storage::SqliteStorage,
undo::UndoManager,
};
2020-04-18 02:23:14 +02:00
use std::{collections::HashMap, path::PathBuf, sync::Arc};
pub fn open_collection<P: Into<PathBuf>>(
path: P,
media_folder: P,
media_db: P,
server: bool,
i18n: I18n,
log: Logger,
) -> Result<Collection> {
let col_path = path.into();
let storage = SqliteStorage::open_or_create(&col_path, &i18n, server)?;
let col = Collection {
storage,
col_path,
media_folder: media_folder.into(),
media_db: media_db.into(),
i18n,
log,
server,
state: CollectionState::default(),
};
Ok(col)
}
2021-01-09 13:56:30 +01:00
// We need to make a Builder for Collection in the future.
2020-03-29 09:52:16 +02:00
#[cfg(test)]
pub fn open_test_collection() -> Collection {
2021-02-23 08:14:55 +01:00
use crate::config::SchedulerVersion;
let col = open_test_collection_with_server(false);
// our unit tests assume v2 is the default, but at the time of writing v1
// is still the default
col.set_scheduler_version_config_key(SchedulerVersion::V2)
.unwrap();
col
2021-01-09 13:56:30 +01:00
}
#[cfg(test)]
pub fn open_test_collection_with_server(server: bool) -> Collection {
2020-03-29 09:52:16 +02:00
use crate::log;
let i18n = I18n::new(&[""], "", log::terminal());
2021-01-09 13:56:30 +01:00
open_collection(":memory:", "", "", server, i18n, log::terminal()).unwrap()
2020-03-29 09:52:16 +02:00
}
#[derive(Debug, Default)]
pub struct CollectionState {
2020-03-29 09:52:16 +02:00
pub(crate) undo: UndoManager,
2020-04-18 02:23:14 +02:00
pub(crate) notetype_cache: HashMap<NoteTypeID, Arc<NoteType>>,
pub(crate) deck_cache: HashMap<DeckID, Arc<Deck>>,
}
pub struct Collection {
pub(crate) storage: SqliteStorage,
#[allow(dead_code)]
pub(crate) col_path: PathBuf,
pub(crate) media_folder: PathBuf,
pub(crate) media_db: PathBuf,
pub(crate) i18n: I18n,
pub(crate) log: Logger,
pub(crate) server: bool,
2020-03-29 09:52:16 +02:00
pub(crate) state: CollectionState,
}
2020-03-29 09:52:16 +02:00
#[derive(Debug, Clone, PartialEq)]
pub enum CollectionOp {
UpdateCard,
}
impl Collection {
/// Execute the provided closure in a transaction, rolling back if
/// an error is returned.
pub(crate) fn transact<F, R>(&mut self, op: Option<CollectionOp>, func: F) -> Result<R>
where
F: FnOnce(&mut Collection) -> Result<R>,
{
self.storage.begin_rust_trx()?;
2020-03-29 09:52:16 +02:00
self.state.undo.begin_step(op);
let mut res = func(self);
if res.is_ok() {
if let Err(e) = self.storage.mark_modified() {
res = Err(e);
2020-03-29 09:52:16 +02:00
} else if let Err(e) = self.storage.commit_rust_trx() {
res = Err(e);
}
}
if res.is_err() {
2020-03-29 09:52:16 +02:00
self.state.undo.discard_step();
self.storage.rollback_rust_trx()?;
2020-03-29 09:52:16 +02:00
} else {
self.state.undo.end_step();
}
res
}
pub(crate) fn close(self, downgrade: bool) -> Result<()> {
self.storage.close(downgrade)
}
pub(crate) fn usn(&self) -> Result<Usn> {
// if we cache this in the future, must make sure to invalidate cache when usn bumped in sync.finish()
self.storage.usn(self.server)
}
2020-03-30 06:39:46 +02:00
2020-05-17 06:13:21 +02:00
/// Prepare for upload. Caller should not create transaction.
pub(crate) fn before_upload(&mut self) -> Result<()> {
self.transact(None, |col| {
col.storage.clear_all_graves()?;
col.storage.clear_pending_note_usns()?;
col.storage.clear_pending_card_usns()?;
col.storage.clear_pending_revlog_usns()?;
col.storage.clear_tag_usns()?;
col.storage.clear_deck_conf_usns()?;
col.storage.clear_deck_usns()?;
col.storage.clear_notetype_usns()?;
col.storage.increment_usn()?;
col.storage.set_schema_modified()?;
col.storage.set_last_sync(col.storage.get_schema_mtime()?)
})?;
self.storage.optimize()
}
}