diff --git a/taskchampion/integration-tests/src/bindings_tests/replica.c b/taskchampion/integration-tests/src/bindings_tests/replica.c index 639cf5f48..b45b810f2 100644 --- a/taskchampion/integration-tests/src/bindings_tests/replica.c +++ b/taskchampion/integration-tests/src/bindings_tests/replica.c @@ -109,7 +109,7 @@ static void test_replica_working_set(void) { tc_working_set_free(ws); - TEST_ASSERT_EQUAL(19, tc_replica_num_local_operations(rep)); + TEST_ASSERT_EQUAL(18, tc_replica_num_local_operations(rep)); tc_replica_free(rep); } diff --git a/taskchampion/lib/src/replica.rs b/taskchampion/lib/src/replica.rs index 9ad5f29db..53b3beb74 100644 --- a/taskchampion/lib/src/replica.rs +++ b/taskchampion/lib/src/replica.rs @@ -360,7 +360,8 @@ pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i ) } -/// Get the number of local, un-synchronized operations, or -1 on error +/// Get the number of local, un-synchronized operations (not including undo points), or -1 on +/// error. #[no_mangle] pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 { wrap( @@ -373,6 +374,19 @@ pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> ) } +/// Get the number of undo points (number of undo calls possible), or -1 on error. +#[no_mangle] +pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 { + wrap( + rep, + |rep| { + let count = rep.num_undo_points()? as i64; + Ok(count) + }, + -1, + ) +} + /// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically /// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already /// been created by this Replica, and may be useful when a Replica instance is held for a long time diff --git a/taskchampion/lib/taskchampion.h b/taskchampion/lib/taskchampion.h index 4286e705f..a40d1ad41 100644 --- a/taskchampion/lib/taskchampion.h +++ b/taskchampion/lib/taskchampion.h @@ -558,10 +558,16 @@ TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool av TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); /** - * Get the number of local, un-synchronized operations, or -1 on error + * Get the number of local, un-synchronized operations (not including undo points), or -1 on + * error. */ int64_t tc_replica_num_local_operations(struct TCReplica *rep); +/** + * Get the number of undo points (number of undo calls possible), or -1 on error. + */ +int64_t tc_replica_num_undo_points(struct TCReplica *rep); + /** * Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically * when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already diff --git a/taskchampion/taskchampion/src/replica.rs b/taskchampion/taskchampion/src/replica.rs index 4bbbc9c32..1c0f171be 100644 --- a/taskchampion/taskchampion/src/replica.rs +++ b/taskchampion/taskchampion/src/replica.rs @@ -259,6 +259,11 @@ impl Replica { pub fn num_local_operations(&mut self) -> anyhow::Result { self.taskdb.num_operations() } + + /// Get the number of undo points available (number of times `undo` will succeed). + pub fn num_undo_points(&mut self) -> anyhow::Result { + self.taskdb.num_undo_points() + } } #[cfg(test)] @@ -405,7 +410,11 @@ mod tests { ] ); - assert_eq!(rep.num_local_operations().unwrap(), 10); + // num_local_operations includes all but the undo point + assert_eq!(rep.num_local_operations().unwrap(), 9); + + // num_undo_points includes only the undo point + assert_eq!(rep.num_undo_points().unwrap(), 1); } #[test] diff --git a/taskchampion/taskchampion/src/storage/op.rs b/taskchampion/taskchampion/src/storage/op.rs index bc20d99e9..a742238bc 100644 --- a/taskchampion/taskchampion/src/storage/op.rs +++ b/taskchampion/taskchampion/src/storage/op.rs @@ -59,6 +59,11 @@ impl ReplicaOp { } } + /// Determine whether this is an undo point. + pub fn is_undo_point(&self) -> bool { + self == &Self::UndoPoint + } + /// Generate a sequence of SyncOp's to reverse the effects of this ReplicaOp. pub fn reverse_ops(self) -> Vec { match self { diff --git a/taskchampion/taskchampion/src/taskdb/mod.rs b/taskchampion/taskchampion/src/taskdb/mod.rs index 71404d968..697827795 100644 --- a/taskchampion/taskchampion/src/taskdb/mod.rs +++ b/taskchampion/taskchampion/src/taskdb/mod.rs @@ -128,10 +128,25 @@ impl TaskDb { undo::undo(txn.as_mut()) } - /// Get the number of un-synchronized operations in storage. + /// Get the number of un-synchronized operations in storage, excluding undo + /// operations. pub fn num_operations(&mut self) -> anyhow::Result { let mut txn = self.storage.txn().unwrap(); - txn.num_operations() + Ok(txn + .operations()? + .iter() + .filter(|o| !o.is_undo_point()) + .count()) + } + + /// Get the number of (un-synchronized) undo points in storage. + pub fn num_undo_points(&mut self) -> anyhow::Result { + let mut txn = self.storage.txn().unwrap(); + Ok(txn + .operations()? + .iter() + .filter(|o| o.is_undo_point()) + .count()) } // functions for supporting tests @@ -196,6 +211,30 @@ mod tests { assert_eq!(db.operations(), vec![ReplicaOp::UndoPoint]); } + #[test] + fn test_num_operations() { + let mut db = TaskDb::new_inmemory(); + db.apply(SyncOp::Create { + uuid: Uuid::new_v4(), + }) + .unwrap(); + db.add_undo_point().unwrap(); + db.apply(SyncOp::Create { + uuid: Uuid::new_v4(), + }) + .unwrap(); + assert_eq!(db.num_operations().unwrap(), 2); + } + + #[test] + fn test_num_undo_points() { + let mut db = TaskDb::new_inmemory(); + db.add_undo_point().unwrap(); + assert_eq!(db.num_undo_points().unwrap(), 1); + db.add_undo_point().unwrap(); + assert_eq!(db.num_undo_points().unwrap(), 2); + } + fn newdb() -> TaskDb { TaskDb::new(Box::new(InMemoryStorage::new())) }