From 8bd9605b252bdc728659f79bbcc779deef0e4f3e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 31 Jan 2022 00:04:58 +0000 Subject: [PATCH] support starting and stopping tasks --- integration-tests/src/bindings_tests/task.c | 55 ++++++++++++++ lib/src/replica.rs | 4 +- lib/src/task.rs | 83 ++++++++++++++------- lib/taskchampion.h | 27 ++++++- 4 files changed, 136 insertions(+), 33 deletions(-) diff --git a/integration-tests/src/bindings_tests/task.c b/integration-tests/src/bindings_tests/task.c index b4173a1eb..b75320df3 100644 --- a/integration-tests/src/bindings_tests/task.c +++ b/integration-tests/src/bindings_tests/task.c @@ -26,6 +26,34 @@ static void test_task_creation(void) { tc_replica_free(rep); } +// freeing a mutable task works, marking it immutable +static void test_task_free_mutable_task(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); + TCUuid uuid = tc_task_get_uuid(task); + + tc_task_to_mut(task, rep); + TEST_ASSERT_TRUE(tc_task_set_status(task, TC_STATUS_DELETED)); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); + + tc_task_free(task); // implicitly converts to immut + + task = tc_replica_get_task(rep, uuid); + TEST_ASSERT_NOT_NULL(task); + TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); + tc_task_free(task); + + tc_replica_free(rep); +} + // updating status on a task works static void test_task_get_set_status(void) { TCReplica *rep = tc_replica_new_in_memory(); @@ -83,11 +111,38 @@ static void test_task_get_set_description(void) { tc_replica_free(rep); } +// starting and stopping a task works, as seen by tc_task_is_active +static void test_task_start_stop_is_active(void) { + TCReplica *rep = tc_replica_new_in_memory(); + TEST_ASSERT_NULL(tc_replica_error(rep)); + + TCTask *task = tc_replica_new_task( + rep, + TC_STATUS_PENDING, + tc_string_borrow("my task")); + TEST_ASSERT_NOT_NULL(task); + + TEST_ASSERT_FALSE(tc_task_is_active(task)); + + tc_task_to_mut(task, rep); + + TEST_ASSERT_FALSE(tc_task_is_active(task)); + tc_task_start(task); + TEST_ASSERT_TRUE(tc_task_is_active(task)); + tc_task_stop(task); + TEST_ASSERT_FALSE(tc_task_is_active(task)); + + tc_task_free(task); + tc_replica_free(rep); +} + int task_tests(void) { UNITY_BEGIN(); // each test case above should be named here, in order. RUN_TEST(test_task_creation); + RUN_TEST(test_task_free_mutable_task); RUN_TEST(test_task_get_set_status); RUN_TEST(test_task_get_set_description); + RUN_TEST(test_task_start_stop_is_active); return UNITY_END(); } diff --git a/lib/src/replica.rs b/lib/src/replica.rs index a5643f045..3dd1f0df9 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -251,8 +251,8 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st #[no_mangle] pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: - // - rep is not NULL - // - caller will not use the TCReplica after this + // - rep is not NULL (promised by caller) + // - caller will not use the TCReplica after this (promised by caller) let replica = unsafe { TCReplica::from_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); diff --git a/lib/src/task.rs b/lib/src/task.rs index 558843b17..14aa82746 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -10,6 +10,8 @@ use taskchampion::{Task, TaskMut}; /// /// A task carries no reference to the replica that created it, and can /// be used until it is freed or converted to a TaskMut. +/// +/// All `tc_task_..` functions taking a task as an argument require that it not be NULL. pub enum TCTask { /// A regular, immutable task Immutable(Task), @@ -202,7 +204,7 @@ pub extern "C" fn tc_task_get_status<'a>(task: *const TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } -// TODO: get_taskmap +// TODO: tc_task_get_taskmap (?? then we have to wrap a map..) /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). @@ -214,19 +216,25 @@ pub extern "C" fn tc_task_get_description<'a>(task: *const TCTask) -> *mut TCStr }) } -// TODO: :get_entry -// TODO: :get_wait -// TODO: :get_modified -// TODO: :is_waiting -// TODO: :is_active -// TODO: :has_tag -// TODO: :get_tags -// TODO: :get_annotations -// TODO: :get_uda -// TODO: :get_udas -// TODO: :get_legacy_uda -// TODO: :get_legacy_udas -// TODO: :get_modified +// TODO: tc_task_get_entry +// TODO: tc_task_get_wait +// TODO: tc_task_get_modified +// TODO: tc_task_is_waiting + +/// Check if a task is active (started and not stopped). +#[no_mangle] +pub extern "C" fn tc_task_is_active<'a>(task: *const TCTask) -> bool { + wrap(task, |task| task.is_active()) +} + +// TODO: tc_task_has_tag +// TODO: tc_task_get_tags +// TODO: tc_task_get_annotations +// TODO: tc_task_get_uda +// TODO: tc_task_get_udas +// TODO: tc_task_get_legacy_uda +// TODO: tc_task_get_legacy_udas +// TODO: tc_task_get_modified /// Set a mutable task's status. /// @@ -261,8 +269,29 @@ pub extern "C" fn tc_task_set_description<'a>( // TODO: tc_task_set_entry // TODO: tc_task_set_wait // TODO: tc_task_set_modified -// TODO: tc_task_start -// TODO: tc_task_stop + +/// Start a task. +/// +/// TODO: error +#[no_mangle] +pub extern "C" fn tc_task_start<'a>(task: *mut TCTask) { + wrap_mut(task, |task| { + task.start()?; + Ok(()) + }) +} + +/// Stop a task. +/// +/// TODO: error +#[no_mangle] +pub extern "C" fn tc_task_stop<'a>(task: *mut TCTask) { + wrap_mut(task, |task| { + task.stop()?; + Ok(()) + }) +} + // TODO: tc_task_done // TODO: tc_task_delete // TODO: tc_task_add_tag @@ -274,21 +303,19 @@ pub extern "C" fn tc_task_set_description<'a>( // TODO: tc_task_set_legacy_uda // TODO: tc_task_remove_legacy_uda -/// Free a task. The given task must not be NULL and must be immutable. The task must not be used -/// after this function returns, and must not be freed more than once. +/// Free a task. The given task must not be NULL. The task must not be used after this function +/// returns, and must not be freed more than once. /// -/// The restriction that the task must be immutable may be lifted (TODO) +/// If the task is currently mutable, it will first be made immutable. #[no_mangle] pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: - // - rep is not NULL - // - caller will not use the TCTask after this - let tctask = unsafe { TCTask::from_arg(task) }; - if !matches!(tctask, TCTask::Immutable(_)) { - // this limit is in place because we require the caller to supply a pointer - // to the replica to make a task immutable, and no such pointer is available - // here. - panic!("Task must be immutable when freed"); - } + // - rep is not NULL (promised by caller) + // - caller will not use the TCTask after this (promised by caller) + let mut tctask = unsafe { TCTask::from_arg(task) }; + + // convert to immut if it was mutable + tctask.to_immut(); + drop(tctask); } diff --git a/lib/taskchampion.h b/lib/taskchampion.h index cda313171..65de06667 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -67,6 +67,8 @@ typedef struct TCString TCString; * * A task carries no reference to the replica that created it, and can * be used until it is freed or converted to a TaskMut. + * + * All `tc_task_..` functions taking a task as an argument require that it not be NULL. */ typedef struct TCTask TCTask; @@ -248,6 +250,11 @@ enum TCStatus tc_task_get_status(const struct TCTask *task); */ struct TCString *tc_task_get_description(const struct TCTask *task); +/** + * Check if a task is active (started and not stopped). + */ +bool tc_task_is_active(const struct TCTask *task); + /** * Set a mutable task's status. * @@ -263,10 +270,24 @@ bool tc_task_set_status(struct TCTask *task, enum TCStatus status); bool tc_task_set_description(struct TCTask *task, struct TCString *description); /** - * Free a task. The given task must not be NULL and must be immutable. The task must not be used - * after this function returns, and must not be freed more than once. + * Start a task. * - * The restriction that the task must be immutable may be lifted (TODO) + * TODO: error + */ +void tc_task_start(struct TCTask *task); + +/** + * Stop a task. + * + * TODO: error + */ +void tc_task_stop(struct TCTask *task); + +/** + * Free a task. The given task must not be NULL. The task must not be used after this function + * returns, and must not be freed more than once. + * + * If the task is currently mutable, it will first be made immutable. */ void tc_task_free(struct TCTask *task);