diff --git a/rslib/src/backend/dbproxy.rs b/rslib/src/backend/dbproxy.rs index df5b88197..fb85d2071 100644 --- a/rslib/src/backend/dbproxy.rs +++ b/rslib/src/backend/dbproxy.rs @@ -1,10 +1,11 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::storage::SqliteStorage; -use crate::{collection::Collection, error::Result}; -use rusqlite::types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef}; -use rusqlite::OptionalExtension; +use crate::{prelude::*, storage::SqliteStorage}; +use rusqlite::{ + types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef}, + OptionalExtension, +}; use serde_derive::{Deserialize, Serialize}; #[derive(Deserialize)] @@ -88,7 +89,7 @@ pub(super) fn db_command_bytes(col: &mut Collection, input: &[u8]) -> Result { if col.state.modified_by_dbproxy { - col.storage.set_modified()?; + col.storage.set_modified_time(TimestampMillis::now())?; col.state.modified_by_dbproxy = false; } col.storage.commit_trx()?; diff --git a/rslib/src/collection.rs b/rslib/src/collection/mod.rs similarity index 60% rename from rslib/src/collection.rs rename to rslib/src/collection/mod.rs index a2bc09ce7..b6e8986fc 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection/mod.rs @@ -1,17 +1,20 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +pub(crate) mod timestamps; +mod transact; +pub(crate) mod undo; + +use crate::i18n::I18n; use crate::types::Usn; use crate::{ browser_table, decks::{Deck, DeckId}, notetype::{Notetype, NotetypeId}, - prelude::*, storage::SqliteStorage, undo::UndoManager, }; use crate::{error::Result, scheduler::queue::CardQueues}; -use crate::{i18n::I18n, ops::StateChanges}; use crate::{log::Logger, scheduler::SchedulerInfo}; use std::{collections::HashMap, path::PathBuf, sync::Arc}; @@ -86,68 +89,6 @@ pub struct Collection { } impl Collection { - fn transact_inner(&mut self, op: Option, func: F) -> Result> - where - F: FnOnce(&mut Collection) -> Result, - { - self.storage.begin_rust_trx()?; - self.begin_undoable_operation(op); - - let mut res = func(self); - - if res.is_ok() { - if let Err(e) = self.storage.set_modified() { - res = Err(e); - } else if let Err(e) = self.storage.commit_rust_trx() { - res = Err(e); - } - } - - match res { - Ok(output) => { - let changes = if op.is_some() { - let changes = self.op_changes(); - self.maybe_clear_study_queues_after_op(changes); - self.maybe_coalesce_note_undo_entry(changes); - changes - } else { - self.clear_study_queues(); - // dummy value, not used by transact_no_undo(). only required - // until we can migrate all the code to undoable ops - OpChanges { - op: Op::SetFlag, - changes: StateChanges::default(), - } - }; - self.end_undoable_operation(); - Ok(OpOutput { output, changes }) - } - Err(err) => { - self.discard_undo_and_study_queues(); - self.storage.rollback_rust_trx()?; - Err(err) - } - } - } - - /// Execute the provided closure in a transaction, rolling back if - /// an error is returned. Records undo state, and returns changes. - pub(crate) fn transact(&mut self, op: Op, func: F) -> Result> - where - F: FnOnce(&mut Collection) -> Result, - { - self.transact_inner(Some(op), func) - } - - /// Execute the provided closure in a transaction, rolling back if - /// an error is returned. - pub(crate) fn transact_no_undo(&mut self, func: F) -> Result - where - F: FnOnce(&mut Collection) -> Result, - { - self.transact_inner(None, func).map(|out| out.output) - } - pub(crate) fn close(self, downgrade: bool) -> Result<()> { self.storage.close(downgrade) } @@ -169,8 +110,9 @@ impl Collection { 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()?) + col.set_schema_modified()?; + col.storage + .set_last_sync(col.storage.get_collection_timestamps()?.schema_change) })?; self.storage.optimize() } diff --git a/rslib/src/collection/timestamps.rs b/rslib/src/collection/timestamps.rs new file mode 100644 index 000000000..a813e4436 --- /dev/null +++ b/rslib/src/collection/timestamps.rs @@ -0,0 +1,32 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::prelude::*; + +pub(crate) struct CollectionTimestamps { + pub collection_change: TimestampMillis, + pub schema_change: TimestampMillis, + pub last_sync: TimestampMillis, +} + +impl CollectionTimestamps { + pub fn collection_changed_since_sync(&self) -> bool { + self.collection_change > self.last_sync + } + + pub fn schema_changed_since_sync(&self) -> bool { + self.schema_change > self.last_sync + } +} + +impl Collection { + pub(crate) fn set_modified(&mut self) -> Result<()> { + let stamps = self.storage.get_collection_timestamps()?; + self.set_modified_time_undoable(TimestampMillis::now(), stamps.collection_change) + } + + pub(crate) fn set_schema_modified(&mut self) -> Result<()> { + let stamps = self.storage.get_collection_timestamps()?; + self.set_schema_modified_time_undoable(TimestampMillis::now(), stamps.schema_change) + } +} diff --git a/rslib/src/collection/transact.rs b/rslib/src/collection/transact.rs new file mode 100644 index 000000000..8704e08a2 --- /dev/null +++ b/rslib/src/collection/transact.rs @@ -0,0 +1,68 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::{ops::StateChanges, prelude::*}; + +impl Collection { + fn transact_inner(&mut self, op: Option, func: F) -> Result> + where + F: FnOnce(&mut Collection) -> Result, + { + self.storage.begin_rust_trx()?; + self.begin_undoable_operation(op); + + let mut res = func(self); + + if res.is_ok() { + if let Err(e) = self.set_modified() { + res = Err(e); + } else if let Err(e) = self.storage.commit_rust_trx() { + res = Err(e); + } + } + + match res { + Ok(output) => { + let changes = if op.is_some() { + let changes = self.op_changes(); + self.maybe_clear_study_queues_after_op(changes); + self.maybe_coalesce_note_undo_entry(changes); + changes + } else { + self.clear_study_queues(); + // dummy value, not used by transact_no_undo(). only required + // until we can migrate all the code to undoable ops + OpChanges { + op: Op::SetFlag, + changes: StateChanges::default(), + } + }; + self.end_undoable_operation(); + Ok(OpOutput { output, changes }) + } + Err(err) => { + self.discard_undo_and_study_queues(); + self.storage.rollback_rust_trx()?; + Err(err) + } + } + } + + /// Execute the provided closure in a transaction, rolling back if + /// an error is returned. Records undo state, and returns changes. + pub(crate) fn transact(&mut self, op: Op, func: F) -> Result> + where + F: FnOnce(&mut Collection) -> Result, + { + self.transact_inner(Some(op), func) + } + + /// Execute the provided closure in a transaction, rolling back if + /// an error is returned. + pub(crate) fn transact_no_undo(&mut self, func: F) -> Result + where + F: FnOnce(&mut Collection) -> Result, + { + self.transact_inner(None, func).map(|out| out.output) + } +} diff --git a/rslib/src/collection/undo.rs b/rslib/src/collection/undo.rs new file mode 100644 index 000000000..6ec005b2f --- /dev/null +++ b/rslib/src/collection/undo.rs @@ -0,0 +1,46 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::prelude::*; + +#[derive(Debug)] +pub(crate) enum UndoableCollectionChange { + Schema(TimestampMillis), + Modified(TimestampMillis), +} + +impl Collection { + pub(crate) fn undo_collection_change( + &mut self, + change: UndoableCollectionChange, + ) -> Result<()> { + match change { + UndoableCollectionChange::Schema(schema) => { + let current = self.storage.get_collection_timestamps()?.schema_change; + self.set_schema_modified_time_undoable(schema, current) + } + UndoableCollectionChange::Modified(modified) => { + let current = self.storage.get_collection_timestamps()?.collection_change; + self.set_schema_modified_time_undoable(modified, current) + } + } + } + + pub(super) fn set_modified_time_undoable( + &mut self, + modified: TimestampMillis, + original: TimestampMillis, + ) -> Result<()> { + self.save_undo(UndoableCollectionChange::Modified(original)); + self.storage.set_modified_time(modified) + } + + pub(super) fn set_schema_modified_time_undoable( + &mut self, + schema: TimestampMillis, + original: TimestampMillis, + ) -> Result<()> { + self.save_undo(UndoableCollectionChange::Schema(original)); + self.storage.set_schema_modified_time(schema) + } +} diff --git a/rslib/src/dbcheck.rs b/rslib/src/dbcheck.rs index 39be2868b..733661883 100644 --- a/rslib/src/dbcheck.rs +++ b/rslib/src/dbcheck.rs @@ -153,7 +153,7 @@ impl Collection { fn check_orphaned_cards(&mut self, out: &mut CheckDatabaseOutput) -> Result<()> { let cnt = self.storage.delete_orphaned_cards()?; if cnt > 0 { - self.storage.set_schema_modified()?; + self.set_schema_modified()?; out.cards_missing_note = cnt; } Ok(()) @@ -186,7 +186,7 @@ impl Collection { } if wrong > 0 { - self.storage.set_schema_modified()?; + self.set_schema_modified()?; out.card_properties_invalid += wrong; } @@ -275,7 +275,7 @@ impl Collection { || out.templates_missing > 0 || out.notetypes_recovered > 0 { - self.storage.set_schema_modified()?; + self.set_schema_modified()?; } Ok(()) @@ -365,10 +365,10 @@ impl Collection { Ok(Arc::new(basic)) } - fn check_revlog(&self, out: &mut CheckDatabaseOutput) -> Result<()> { + fn check_revlog(&mut self, out: &mut CheckDatabaseOutput) -> Result<()> { let cnt = self.storage.fix_revlog_properties()?; if cnt > 0 { - self.storage.set_schema_modified()?; + self.set_schema_modified()?; out.revlog_properties_invalid = cnt; } diff --git a/rslib/src/deckconf/mod.rs b/rslib/src/deckconf/mod.rs index c28878526..8927eb4f8 100644 --- a/rslib/src/deckconf/mod.rs +++ b/rslib/src/deckconf/mod.rs @@ -2,6 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html mod schema11; +pub(crate) mod undo; mod update; pub use { @@ -114,11 +115,15 @@ impl Collection { } /// Remove a deck configuration. This will force a full sync. - pub(crate) fn remove_deck_config(&self, dcid: DeckConfId) -> Result<()> { + pub(crate) fn remove_deck_config(&mut self, dcid: DeckConfId) -> Result<()> { if dcid.0 == 1 { return Err(AnkiError::invalid_input("can't delete default conf")); } - self.storage.set_schema_modified()?; - self.storage.remove_deck_conf(dcid) + let conf = self + .storage + .get_deck_config(dcid)? + .ok_or(AnkiError::NotFound)?; + self.set_schema_modified()?; + self.remove_deck_config_undoable(conf) } } diff --git a/rslib/src/deckconf/undo.rs b/rslib/src/deckconf/undo.rs new file mode 100644 index 000000000..02a368129 --- /dev/null +++ b/rslib/src/deckconf/undo.rs @@ -0,0 +1,76 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::prelude::*; + +#[derive(Debug)] + +pub(crate) enum UndoableDeckConfigChange { + Added(Box), + Updated(Box), + Removed(Box), +} + +// fixme: schema mod + +impl Collection { + pub(crate) fn undo_deck_config_change( + &mut self, + change: UndoableDeckConfigChange, + ) -> Result<()> { + match change { + UndoableDeckConfigChange::Added(config) => self.remove_deck_config_undoable(*config), + UndoableDeckConfigChange::Updated(mut config) => { + let current = self + .storage + .get_deck_config(config.id)? + .ok_or_else(|| AnkiError::invalid_input("deck config disappeared"))?; + self.update_single_deck_config_undoable(&mut *config, current) + } + UndoableDeckConfigChange::Removed(config) => self.restore_deleted_deck_config(*config), + } + } + + pub(crate) fn remove_deck_config_undoable(&mut self, config: DeckConf) -> Result<()> { + self.storage.remove_deck_conf(config.id)?; + self.save_undo(UndoableDeckConfigChange::Removed(Box::new(config))); + Ok(()) + } + + #[allow(dead_code)] + pub(super) fn add_deck_config_undoable( + &mut self, + config: &mut DeckConf, + ) -> Result<(), AnkiError> { + self.storage.add_deck_conf(config)?; + self.save_undo(UndoableDeckConfigChange::Added(Box::new(config.clone()))); + Ok(()) + } + + pub(super) fn update_single_deck_config_undoable( + &mut self, + config: &mut DeckConf, + original: DeckConf, + ) -> Result<()> { + self.save_undo(UndoableDeckConfigChange::Updated(Box::new(original))); + self.storage.update_deck_conf(config) + } + + #[allow(dead_code)] + fn add_or_update_deck_config_with_existing_id_undoable( + &mut self, + config: &mut DeckConf, + ) -> Result<(), AnkiError> { + self.storage + .add_or_update_deck_config_with_existing_id(config)?; + self.save_undo(UndoableDeckConfigChange::Added(Box::new(config.clone()))); + Ok(()) + } + + fn restore_deleted_deck_config(&mut self, config: DeckConf) -> Result<()> { + self.storage + .add_or_update_deck_config_with_existing_id(&config)?; + self.save_undo(UndoableDeckConfigChange::Added(Box::new(config))); + Ok(()) + } +} diff --git a/rslib/src/deckconf/update.rs b/rslib/src/deckconf/update.rs index 82959fc6f..ae2fb1008 100644 --- a/rslib/src/deckconf/update.rs +++ b/rslib/src/deckconf/update.rs @@ -11,7 +11,7 @@ pub struct UpdateDeckConfigsIn { pub target_deck_id: DeckId, /// Deck will be set to last provided deck config. pub configs: Vec, - pub removed_config_ids: Vec, + pub removed_config_ids: Vec, pub apply_to_children: bool, } @@ -25,7 +25,10 @@ impl Collection { all_config: self.get_deck_config_with_extra_for_update()?, current_deck: Some(self.get_current_deck_for_update(deck)?), defaults: Some(DeckConf::default().into()), - schema_modified: self.storage.schema_modified()?, + schema_modified: self + .storage + .get_collection_timestamps()? + .schema_changed_since_sync(), }) } @@ -98,6 +101,10 @@ impl Collection { return Err(AnkiError::invalid_input("config not provided")); } + for dcid in &input.removed_config_ids { + self.remove_deck_config(*dcid)?; + } + todo!(); } } diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index c5cfc53e2..6bfdc7045 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -494,7 +494,7 @@ impl Collection { // fixme: currently the storage layer is taking care of removing the notes and cards, // but we need to do it in this layer in the future for undo handling self.transact_no_undo(|col| { - col.storage.set_schema_modified()?; + col.set_schema_modified()?; col.state.notetype_cache.remove(&ntid); col.clear_aux_config_for_notetype(ntid)?; col.storage.remove_notetype(ntid)?; diff --git a/rslib/src/notetype/schemachange.rs b/rslib/src/notetype/schemachange.rs index 455823e08..296af2a92 100644 --- a/rslib/src/notetype/schemachange.rs +++ b/rslib/src/notetype/schemachange.rs @@ -74,7 +74,7 @@ impl Collection { } } - self.storage.set_schema_modified()?; + self.set_schema_modified()?; let nids = self.search_notes(&format!("mid:{}", nt.id))?; let usn = self.usn()?; @@ -115,7 +115,7 @@ impl Collection { return Ok(()); } - self.storage.set_schema_modified()?; + self.set_schema_modified()?; let changes = TemplateOrdChanges::new(ords, previous_template_count as u32); if !changes.removed.is_empty() { diff --git a/rslib/src/ops.rs b/rslib/src/ops.rs index aecd68507..e0e3c2b67 100644 --- a/rslib/src/ops.rs +++ b/rslib/src/ops.rs @@ -85,6 +85,9 @@ pub struct StateChanges { pub tag: bool, pub notetype: bool, pub config: bool, + pub deck_config: bool, + /// covers schema change (and last_sync in the future), but not mtime + pub collection: bool, } #[derive(Debug, Clone, Copy)] diff --git a/rslib/src/scheduler/upgrade.rs b/rslib/src/scheduler/upgrade.rs index e657cd639..b9ae06de6 100644 --- a/rslib/src/scheduler/upgrade.rs +++ b/rslib/src/scheduler/upgrade.rs @@ -127,7 +127,7 @@ impl Collection { } // force full sync - self.storage.set_schema_modified() + self.set_schema_modified() } fn upgrade_cards_to_v2(&mut self) -> Result<()> { diff --git a/rslib/src/storage/collection_timestamps.rs b/rslib/src/storage/collection_timestamps.rs new file mode 100644 index 000000000..5949bcb8c --- /dev/null +++ b/rslib/src/storage/collection_timestamps.rs @@ -0,0 +1,58 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use super::SqliteStorage; +use crate::{collection::timestamps::CollectionTimestamps, prelude::*}; +use rusqlite::{params, NO_PARAMS}; + +impl SqliteStorage { + pub(crate) fn get_collection_timestamps(&self) -> Result { + self.db + .prepare_cached("select mod, scm, ls from col")? + .query_row(NO_PARAMS, |row| { + Ok(CollectionTimestamps { + collection_change: row.get(0)?, + schema_change: row.get(1)?, + last_sync: row.get(2)?, + }) + }) + .map_err(Into::into) + } + + pub(crate) fn set_schema_modified_time(&self, stamp: TimestampMillis) -> Result<()> { + self.db + .prepare_cached("update col set scm = ?")? + .execute(&[stamp])?; + Ok(()) + } + + pub(crate) fn set_last_sync(&self, stamp: TimestampMillis) -> Result<()> { + self.db + .prepare("update col set ls = ?")? + .execute(&[stamp])?; + Ok(()) + } + + pub(crate) fn set_modified_time(&self, stamp: TimestampMillis) -> Result<()> { + self.db + .prepare_cached("update col set mod=?")? + .execute(params![stamp])?; + Ok(()) + } + + // Creation timestamp is used less frequently, and has separate accessor + + pub(crate) fn creation_stamp(&self) -> Result { + self.db + .prepare_cached("select crt from col")? + .query_row(NO_PARAMS, |row| row.get(0)) + .map_err(Into::into) + } + + pub(crate) fn set_creation_stamp(&self, stamp: TimestampSecs) -> Result<()> { + self.db + .prepare("update col set crt = ?")? + .execute(&[stamp])?; + Ok(()) + } +} diff --git a/rslib/src/storage/deckconf/mod.rs b/rslib/src/storage/deckconf/mod.rs index c3310e82e..19326a5a5 100644 --- a/rslib/src/storage/deckconf/mod.rs +++ b/rslib/src/storage/deckconf/mod.rs @@ -4,8 +4,7 @@ use super::SqliteStorage; use crate::{ deckconf::{DeckConf, DeckConfId, DeckConfSchema11, DeckConfigInner}, - error::Result, - i18n::I18n, + prelude::*, }; use prost::Message; use rusqlite::{params, Row, NO_PARAMS}; @@ -81,8 +80,12 @@ impl SqliteStorage { Ok(()) } - /// Used for syncing. - pub(crate) fn add_or_update_deck_config(&self, conf: &DeckConf) -> Result<()> { + /// Used for syncing&undo; will keep provided ID. Shouldn't be used to add + /// new config normally, since it does not allocate an id. + pub(crate) fn add_or_update_deck_config_with_existing_id(&self, conf: &DeckConf) -> Result<()> { + if conf.id.0 == 0 { + return Err(AnkiError::invalid_input("deck with id 0")); + } let mut conf_bytes = vec![]; conf.inner.encode(&mut conf_bytes)?; self.db diff --git a/rslib/src/storage/mod.rs b/rslib/src/storage/mod.rs index a1ca6baa6..80c902dfb 100644 --- a/rslib/src/storage/mod.rs +++ b/rslib/src/storage/mod.rs @@ -2,6 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html pub(crate) mod card; +mod collection_timestamps; mod config; mod deck; mod deckconf; diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index 967d5c974..909b016a7 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -4,7 +4,7 @@ use crate::config::schema11::schema11_config_as_string; use crate::error::Result; use crate::error::{AnkiError, DbErrorKind}; -use crate::timestamp::{TimestampMillis, TimestampSecs}; +use crate::timestamp::TimestampMillis; use crate::{i18n::I18n, scheduler::timing::v1_creation_date, text::without_combining}; use regex::Regex; use rusqlite::{functions::FunctionFlags, params, Connection, NO_PARAMS}; @@ -256,81 +256,6 @@ impl SqliteStorage { ////////////////////////////////////////// - pub(crate) fn set_modified(&self) -> Result<()> { - self.set_modified_time(TimestampMillis::now()) - } - - pub(crate) fn set_modified_time(&self, stamp: TimestampMillis) -> Result<()> { - self.db - .prepare_cached("update col set mod=?")? - .execute(params![stamp])?; - Ok(()) - } - - pub(crate) fn get_modified_time(&self) -> Result { - self.db - .prepare_cached("select mod from col")? - .query_and_then(NO_PARAMS, |r| r.get(0))? - .next() - .ok_or_else(|| AnkiError::invalid_input("missing col"))? - .map_err(Into::into) - } - - pub(crate) fn creation_stamp(&self) -> Result { - self.db - .prepare_cached("select crt from col")? - .query_row(NO_PARAMS, |row| row.get(0)) - .map_err(Into::into) - } - - pub(crate) fn set_creation_stamp(&self, stamp: TimestampSecs) -> Result<()> { - self.db - .prepare("update col set crt = ?")? - .execute(&[stamp])?; - Ok(()) - } - - pub(crate) fn set_schema_modified(&self) -> Result<()> { - self.db - .prepare_cached("update col set scm = ?")? - .execute(&[TimestampMillis::now()])?; - Ok(()) - } - - pub(crate) fn schema_modified(&self) -> Result { - self.db - .prepare_cached("select scm > ls from col")? - .query_row(NO_PARAMS, |r| r.get(0)) - .map_err(Into::into) - } - - pub(crate) fn get_schema_mtime(&self) -> Result { - self.db - .prepare_cached("select scm from col")? - .query_and_then(NO_PARAMS, |r| r.get(0))? - .next() - .ok_or_else(|| AnkiError::invalid_input("missing col"))? - .map_err(Into::into) - } - - pub(crate) fn get_last_sync(&self) -> Result { - self.db - .prepare_cached("select ls from col")? - .query_and_then(NO_PARAMS, |r| r.get(0))? - .next() - .ok_or_else(|| AnkiError::invalid_input("missing col"))? - .map_err(Into::into) - } - - pub(crate) fn set_last_sync(&self, stamp: TimestampMillis) -> Result<()> { - self.db - .prepare("update col set ls = ?")? - .execute(&[stamp])?; - Ok(()) - } - - ////////////////////////////////////////// - /// true if corrupt/can't access pub(crate) fn quick_check_corrupt(&self) -> bool { match self.db.pragma_query_value(None, "quick_check", |row| { diff --git a/rslib/src/sync/mod.rs b/rslib/src/sync/mod.rs index cb145ecf7..b7d405fba 100644 --- a/rslib/src/sync/mod.rs +++ b/rslib/src/sync/mod.rs @@ -610,12 +610,10 @@ pub(crate) async fn get_remote_sync_meta(auth: SyncAuth) -> Result { impl Collection { pub fn get_local_sync_status(&mut self) -> Result { - let last_sync = self.storage.get_last_sync()?; - let schema_mod = self.storage.get_schema_mtime()?; - let normal_mod = self.storage.get_modified_time()?; - let required = if schema_mod > last_sync { + let stamps = self.storage.get_collection_timestamps()?; + let required = if stamps.schema_changed_since_sync() { sync_status_out::Required::FullSync - } else if normal_mod > last_sync { + } else if stamps.collection_changed_since_sync() { sync_status_out::Required::NormalSync } else { sync_status_out::Required::NoChanges @@ -687,9 +685,10 @@ impl Collection { } fn sync_meta(&self) -> Result { + let stamps = self.storage.get_collection_timestamps()?; Ok(SyncMeta { - modified: self.storage.get_modified_time()?, - schema: self.storage.get_schema_mtime()?, + modified: stamps.collection_change, + schema: stamps.schema_change, usn: self.storage.usn(true)?, current_time: TimestampSecs::now(), server_message: "".into(), @@ -894,7 +893,8 @@ impl Collection { }; if proceed { let conf = conf.into(); - self.storage.add_or_update_deck_config(&conf)?; + self.storage + .add_or_update_deck_config_with_existing_id(&conf)?; } } Ok(()) diff --git a/rslib/src/sync/server.rs b/rslib/src/sync/server.rs index 79d6adba7..c794a31b4 100644 --- a/rslib/src/sync/server.rs +++ b/rslib/src/sync/server.rs @@ -80,9 +80,10 @@ impl LocalServer { #[async_trait(?Send)] impl SyncServer for LocalServer { async fn meta(&self) -> Result { + let stamps = self.col.storage.get_collection_timestamps()?; Ok(SyncMeta { - modified: self.col.storage.get_modified_time()?, - schema: self.col.storage.get_schema_mtime()?, + modified: stamps.collection_change, + schema: stamps.schema_change, usn: self.col.storage.usn(true)?, current_time: TimestampSecs::now(), server_message: String::new(), diff --git a/rslib/src/undo/changes.rs b/rslib/src/undo/changes.rs index f50eba795..2cd651d7a 100644 --- a/rslib/src/undo/changes.rs +++ b/rslib/src/undo/changes.rs @@ -2,7 +2,8 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::{ - card::undo::UndoableCardChange, config::undo::UndoableConfigChange, + card::undo::UndoableCardChange, collection::undo::UndoableCollectionChange, + config::undo::UndoableConfigChange, deckconf::undo::UndoableDeckConfigChange, decks::undo::UndoableDeckChange, notes::undo::UndoableNoteChange, prelude::*, revlog::undo::UndoableRevlogChange, scheduler::queue::undo::UndoableQueueChange, tags::undo::UndoableTagChange, @@ -13,10 +14,12 @@ pub(crate) enum UndoableChange { Card(UndoableCardChange), Note(UndoableNoteChange), Deck(UndoableDeckChange), + DeckConfig(UndoableDeckConfigChange), Tag(UndoableTagChange), Revlog(UndoableRevlogChange), Queue(UndoableQueueChange), Config(UndoableConfigChange), + Collection(UndoableCollectionChange), } impl UndoableChange { @@ -29,6 +32,8 @@ impl UndoableChange { UndoableChange::Revlog(c) => col.undo_revlog_change(c), UndoableChange::Queue(c) => col.undo_queue_change(c), UndoableChange::Config(c) => col.undo_config_change(c), + UndoableChange::DeckConfig(c) => col.undo_deck_config_change(c), + UndoableChange::Collection(c) => col.undo_collection_change(c), } } } @@ -51,6 +56,12 @@ impl From for UndoableChange { } } +impl From for UndoableChange { + fn from(c: UndoableDeckConfigChange) -> Self { + UndoableChange::DeckConfig(c) + } +} + impl From for UndoableChange { fn from(c: UndoableTagChange) -> Self { UndoableChange::Tag(c) @@ -74,3 +85,9 @@ impl From for UndoableChange { UndoableChange::Config(c) } } + +impl From for UndoableChange { + fn from(c: UndoableCollectionChange) -> Self { + UndoableChange::Collection(c) + } +} diff --git a/rslib/src/undo/mod.rs b/rslib/src/undo/mod.rs index 88c5e1897..2f6751e84 100644 --- a/rslib/src/undo/mod.rs +++ b/rslib/src/undo/mod.rs @@ -127,6 +127,8 @@ impl UndoManager { UndoableChange::Revlog(_) => {} UndoableChange::Queue(_) => {} UndoableChange::Config(_) => changes.config = true, + UndoableChange::DeckConfig(_) => changes.deck_config = true, + UndoableChange::Collection(_) => changes.collection = true, } }