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;
|
2020-03-29 04:26:24 +02:00
|
|
|
use crate::timestamp::TimestampSecs;
|
2020-03-29 04:50:30 +02:00
|
|
|
use crate::types::Usn;
|
2020-04-05 13:38:58 +02:00
|
|
|
use crate::{
|
2020-04-18 02:23:14 +02:00
|
|
|
notetype::{NoteType, NoteTypeID},
|
2020-04-05 13:38:58 +02:00
|
|
|
sched::cutoff::{sched_timing_today, SchedTimingToday},
|
|
|
|
storage::SqliteStorage,
|
|
|
|
undo::UndoManager,
|
|
|
|
};
|
2020-04-18 02:23:14 +02:00
|
|
|
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
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();
|
2020-04-01 09:49:25 +02:00
|
|
|
let storage = SqliteStorage::open_or_create(&col_path, &i18n)?;
|
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
|
|
|
i18n,
|
|
|
|
log,
|
2020-03-29 04:50:30 +02:00
|
|
|
server,
|
2020-03-29 04:26:24 +02:00
|
|
|
state: CollectionState::default(),
|
2020-03-05 05:35:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(col)
|
|
|
|
}
|
|
|
|
|
2020-03-29 09:52:16 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
pub fn open_test_collection() -> Collection {
|
|
|
|
use crate::log;
|
|
|
|
let i18n = I18n::new(&[""], "", log::terminal());
|
|
|
|
open_collection(":memory:", "", "", false, i18n, log::terminal()).unwrap()
|
|
|
|
}
|
|
|
|
|
2020-03-29 04:26:24 +02:00
|
|
|
#[derive(Debug, Default)]
|
2020-03-29 00:58:33 +01:00
|
|
|
pub struct CollectionState {
|
|
|
|
task_state: CollectionTaskState,
|
2020-03-29 09:52:16 +02:00
|
|
|
pub(crate) undo: UndoManager,
|
2020-03-29 04:26:24 +02:00
|
|
|
timing_today: Option<SchedTimingToday>,
|
2020-04-18 02:23:14 +02:00
|
|
|
pub(crate) notetype_cache: HashMap<NoteTypeID, Arc<NoteType>>,
|
2020-03-29 00:58:33 +01:00
|
|
|
}
|
|
|
|
|
2020-03-14 02:52:39 +01:00
|
|
|
#[derive(Debug, PartialEq)]
|
2020-03-29 00:58:33 +01:00
|
|
|
pub enum CollectionTaskState {
|
2020-03-14 02:52:39 +01:00
|
|
|
Normal,
|
|
|
|
// in this state, the DB must not be closed
|
|
|
|
MediaSyncRunning,
|
|
|
|
}
|
|
|
|
|
2020-03-29 04:26:24 +02:00
|
|
|
impl Default for CollectionTaskState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Normal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) i18n: I18n,
|
|
|
|
pub(crate) log: Logger,
|
2020-03-29 04:50:30 +02:00
|
|
|
pub(crate) server: bool,
|
2020-03-29 09:52:16 +02:00
|
|
|
pub(crate) state: CollectionState,
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|
|
|
|
|
2020-03-29 09:52:16 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub enum CollectionOp {
|
|
|
|
UpdateCard,
|
|
|
|
}
|
2020-03-05 05:35:48 +01:00
|
|
|
|
|
|
|
impl Collection {
|
|
|
|
/// Execute the provided closure in a transaction, rolling back if
|
|
|
|
/// an error is returned.
|
2020-03-29 00:58:33 +01:00
|
|
|
pub(crate) fn transact<F, R>(&mut self, op: Option<CollectionOp>, func: F) -> Result<R>
|
2020-03-05 05:35:48 +01:00
|
|
|
where
|
2020-03-29 00:58:33 +01:00
|
|
|
F: FnOnce(&mut Collection) -> Result<R>,
|
2020-03-05 05:35:48 +01:00
|
|
|
{
|
2020-03-29 00:58:33 +01:00
|
|
|
self.storage.begin_rust_trx()?;
|
2020-03-29 09:52:16 +02:00
|
|
|
self.state.undo.begin_step(op);
|
2020-03-05 05:35:48 +01:00
|
|
|
|
2020-03-29 00:58:33 +01:00
|
|
|
let mut res = func(self);
|
2020-03-05 05:35:48 +01:00
|
|
|
|
2020-03-29 00:58:33 +01:00
|
|
|
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() {
|
2020-03-29 00:58:33 +01:00
|
|
|
res = Err(e);
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|
2020-03-29 00:58:33 +01:00
|
|
|
}
|
2020-03-05 05:35:48 +01:00
|
|
|
|
2020-03-29 00:58:33 +01:00
|
|
|
if res.is_err() {
|
2020-03-29 09:52:16 +02:00
|
|
|
self.state.undo.discard_step();
|
2020-03-29 00:58:33 +01:00
|
|
|
self.storage.rollback_rust_trx()?;
|
2020-03-29 09:52:16 +02:00
|
|
|
} else {
|
|
|
|
self.state.undo.end_step();
|
2020-03-29 00:58:33 +01:00
|
|
|
}
|
2020-03-05 05:35:48 +01:00
|
|
|
|
2020-03-29 00:58:33 +01:00
|
|
|
res
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|
2020-03-14 02:52:39 +01:00
|
|
|
|
|
|
|
pub(crate) fn set_media_sync_running(&mut self) -> Result<()> {
|
2020-03-29 00:58:33 +01:00
|
|
|
if self.state.task_state == CollectionTaskState::Normal {
|
|
|
|
self.state.task_state = CollectionTaskState::MediaSyncRunning;
|
2020-03-14 02:52:39 +01:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(AnkiError::invalid_input("media sync already running"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn set_media_sync_finished(&mut self) -> Result<()> {
|
2020-03-29 00:58:33 +01:00
|
|
|
if self.state.task_state == CollectionTaskState::MediaSyncRunning {
|
|
|
|
self.state.task_state = CollectionTaskState::Normal;
|
2020-03-14 02:52:39 +01:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(AnkiError::invalid_input("media sync not running"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn can_close(&self) -> bool {
|
2020-03-29 00:58:33 +01:00
|
|
|
self.state.task_state == CollectionTaskState::Normal
|
2020-03-14 02:52:39 +01:00
|
|
|
}
|
2020-03-29 04:26:24 +02:00
|
|
|
|
2020-04-04 09:21:45 +02:00
|
|
|
pub(crate) fn close(self, downgrade: bool) -> Result<()> {
|
|
|
|
self.storage.close(downgrade)
|
2020-03-30 12:01:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-01 09:36:33 +02:00
|
|
|
// fixme: invalidate when config changes
|
2020-03-29 04:26:24 +02:00
|
|
|
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
|
|
|
|
if let Some(timing) = &self.state.timing_today {
|
|
|
|
if timing.next_day_at > TimestampSecs::now().0 {
|
2020-03-29 04:50:30 +02:00
|
|
|
return Ok(*timing);
|
2020-03-29 04:26:24 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-05 13:38:58 +02:00
|
|
|
|
|
|
|
let local_offset = if self.server {
|
|
|
|
self.get_local_mins_west()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let timing = sched_timing_today(
|
|
|
|
self.storage.creation_stamp()?,
|
|
|
|
TimestampSecs::now(),
|
|
|
|
self.get_creation_mins_west(),
|
|
|
|
local_offset,
|
|
|
|
self.get_rollover(),
|
|
|
|
);
|
|
|
|
|
|
|
|
self.state.timing_today = Some(timing);
|
|
|
|
|
|
|
|
Ok(timing)
|
2020-03-29 04:26:24 +02:00
|
|
|
}
|
2020-03-29 04:50:30 +02:00
|
|
|
|
|
|
|
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-04-03 11:30:42 +02:00
|
|
|
pub(crate) fn before_upload(&self) -> Result<()> {
|
|
|
|
self.storage.clear_tag_usns()?;
|
|
|
|
self.storage.clear_deck_conf_usns()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-03-05 05:35:48 +01:00
|
|
|
}
|