mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
add working-set support
This commit is contained in:
parent
ad560fdb79
commit
1488355b89
6 changed files with 233 additions and 4 deletions
|
@ -40,12 +40,74 @@ static void test_replica_add_undo_point(void) {
|
||||||
tc_replica_free(rep);
|
tc_replica_free(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebuilding working set succeeds
|
// working set operations succeed
|
||||||
static void test_replica_rebuild_working_set(void) {
|
static void test_replica_working_set(void) {
|
||||||
|
TCWorkingSet *ws;
|
||||||
|
TCTask *task1, *task2, *task3;
|
||||||
|
TCUuid uuid, uuid1, uuid2, uuid3;
|
||||||
|
|
||||||
TCReplica *rep = tc_replica_new_in_memory();
|
TCReplica *rep = tc_replica_new_in_memory();
|
||||||
TEST_ASSERT_NULL(tc_replica_error(rep));
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true));
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true));
|
||||||
TEST_ASSERT_NULL(tc_replica_error(rep));
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
ws = tc_replica_working_set(rep);
|
||||||
|
TEST_ASSERT_EQUAL(0, tc_working_set_len(ws));
|
||||||
|
tc_working_set_free(ws);
|
||||||
|
|
||||||
|
task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task1"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task1);
|
||||||
|
uuid1 = tc_task_get_uuid(task1);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true));
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task2"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task2);
|
||||||
|
uuid2 = tc_task_get_uuid(task2);
|
||||||
|
|
||||||
|
task3 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task3"));
|
||||||
|
TEST_ASSERT_NOT_NULL(task3);
|
||||||
|
uuid3 = tc_task_get_uuid(task3);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false));
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
// finish task2 to leave a "hole"
|
||||||
|
tc_task_to_mut(task2, rep);
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task2));
|
||||||
|
tc_task_to_immut(task2);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false));
|
||||||
|
TEST_ASSERT_NULL(tc_replica_error(rep));
|
||||||
|
|
||||||
|
tc_task_free(task1);
|
||||||
|
tc_task_free(task2);
|
||||||
|
tc_task_free(task3);
|
||||||
|
|
||||||
|
// working set should now be
|
||||||
|
// 0 -> None
|
||||||
|
// 1 -> uuid1
|
||||||
|
// 2 -> None
|
||||||
|
// 3 -> uuid3
|
||||||
|
ws = tc_replica_working_set(rep);
|
||||||
|
TEST_ASSERT_EQUAL(2, tc_working_set_len(ws));
|
||||||
|
TEST_ASSERT_EQUAL(3, tc_working_set_largest_index(ws));
|
||||||
|
|
||||||
|
TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 0, &uuid));
|
||||||
|
TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 1, &uuid));
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(uuid1.bytes, uuid.bytes, sizeof(uuid));
|
||||||
|
TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 2, &uuid));
|
||||||
|
TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 3, &uuid));
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(uuid3.bytes, uuid.bytes, sizeof(uuid));
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(1, tc_working_set_by_uuid(ws, uuid1));
|
||||||
|
TEST_ASSERT_EQUAL(0, tc_working_set_by_uuid(ws, uuid2));
|
||||||
|
TEST_ASSERT_EQUAL(3, tc_working_set_by_uuid(ws, uuid3));
|
||||||
|
|
||||||
|
tc_working_set_free(ws);
|
||||||
|
|
||||||
tc_replica_free(rep);
|
tc_replica_free(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +271,7 @@ int replica_tests(void) {
|
||||||
RUN_TEST(test_replica_creation_disk);
|
RUN_TEST(test_replica_creation_disk);
|
||||||
RUN_TEST(test_replica_undo_empty);
|
RUN_TEST(test_replica_undo_empty);
|
||||||
RUN_TEST(test_replica_add_undo_point);
|
RUN_TEST(test_replica_add_undo_point);
|
||||||
RUN_TEST(test_replica_rebuild_working_set);
|
RUN_TEST(test_replica_working_set);
|
||||||
RUN_TEST(test_replica_undo_empty_null_undone_out);
|
RUN_TEST(test_replica_undo_empty_null_undone_out);
|
||||||
RUN_TEST(test_replica_task_creation);
|
RUN_TEST(test_replica_task_creation);
|
||||||
RUN_TEST(test_replica_all_tasks);
|
RUN_TEST(test_replica_all_tasks);
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod string;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
pub mod uda;
|
pub mod uda;
|
||||||
pub mod uuid;
|
pub mod uuid;
|
||||||
|
pub mod workingset;
|
||||||
|
|
||||||
pub(crate) mod types {
|
pub(crate) mod types {
|
||||||
pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList};
|
pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList};
|
||||||
|
@ -24,4 +25,5 @@ pub(crate) mod types {
|
||||||
pub(crate) use crate::task::{TCTask, TCTaskList};
|
pub(crate) use crate::task::{TCTask, TCTaskList};
|
||||||
pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA};
|
pub(crate) use crate::uda::{TCUDAList, TCUDA, UDA};
|
||||||
pub(crate) use crate::uuid::{TCUuid, TCUuidList};
|
pub(crate) use crate::uuid::{TCUuid, TCUuidList};
|
||||||
|
pub(crate) use crate::workingset::TCWorkingSet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,21 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tc_replica_working_set
|
/// Get the current working set for this replica.
|
||||||
|
///
|
||||||
|
/// Returns NULL on error.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet {
|
||||||
|
wrap(
|
||||||
|
rep,
|
||||||
|
|rep| {
|
||||||
|
let ws = rep.working_set()?;
|
||||||
|
// SAFETY: caller promises to free this task
|
||||||
|
Ok(unsafe { TCWorkingSet::return_val(ws.into()) })
|
||||||
|
},
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get an existing task by its UUID.
|
/// Get an existing task by its UUID.
|
||||||
///
|
///
|
||||||
|
|
87
lib/src/workingset.rs
Normal file
87
lib/src/workingset.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use crate::traits::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use taskchampion::{Uuid, WorkingSet};
|
||||||
|
|
||||||
|
/// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically
|
||||||
|
/// updated based on changes in the replica. Its lifetime is independent of the replica and it can
|
||||||
|
/// be freed at any time.
|
||||||
|
///
|
||||||
|
/// To iterate over a working set, search indexes 1 through largest_index.
|
||||||
|
pub struct TCWorkingSet(WorkingSet);
|
||||||
|
|
||||||
|
impl PassByPointer for TCWorkingSet {}
|
||||||
|
|
||||||
|
impl From<WorkingSet> for TCWorkingSet {
|
||||||
|
fn from(ws: WorkingSet) -> TCWorkingSet {
|
||||||
|
TCWorkingSet(ws)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function to get a shared reference to the underlying WorkingSet.
|
||||||
|
fn wrap<'a, T, F>(ws: *mut TCWorkingSet, f: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(&WorkingSet) -> T,
|
||||||
|
{
|
||||||
|
// SAFETY:
|
||||||
|
// - ws is not null (promised by caller)
|
||||||
|
// - ws outlives 'a (promised by caller)
|
||||||
|
let tcws: &'a TCWorkingSet = unsafe { TCWorkingSet::from_arg_ref(ws) };
|
||||||
|
f(&tcws.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the working set's length, or the number of UUIDs it contains.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize {
|
||||||
|
wrap(ws, |ws| ws.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the working set's largest index.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize {
|
||||||
|
wrap(ws, |ws| ws.largest_index())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the UUID for the task at the given index. Returns true if the UUID exists in the working
|
||||||
|
/// set. If not, returns false and does not change uuid_out.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_working_set_by_index(
|
||||||
|
ws: *mut TCWorkingSet,
|
||||||
|
index: usize,
|
||||||
|
uuid_out: *mut TCUuid,
|
||||||
|
) -> bool {
|
||||||
|
debug_assert!(!uuid_out.is_null());
|
||||||
|
wrap(ws, |ws| {
|
||||||
|
if let Some(uuid) = ws.by_index(index) {
|
||||||
|
// SAFETY:
|
||||||
|
// - uuid_out is not NULL (promised by caller)
|
||||||
|
// - alignment is not required
|
||||||
|
unsafe { TCUuid::to_arg_out(uuid, uuid_out) };
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the working set index for the task with the given UUID. Returns 0 if the task is not in
|
||||||
|
/// the working set.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize {
|
||||||
|
wrap(ws, |ws| {
|
||||||
|
// SAFETY:
|
||||||
|
// - tcuuid is a valid TCUuid (all byte patterns are valid)
|
||||||
|
let uuid: Uuid = unsafe { TCUuid::from_arg(uuid) };
|
||||||
|
ws.by_uuid(uuid).unwrap_or(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this
|
||||||
|
/// function returns, and must not be freed more than once.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) {
|
||||||
|
// SAFETY:
|
||||||
|
// - rep is not NULL (promised by caller)
|
||||||
|
// - caller will not use the TCWorkingSet after this (promised by caller)
|
||||||
|
let ws = unsafe { TCWorkingSet::take_from_arg(ws) };
|
||||||
|
drop(ws);
|
||||||
|
}
|
|
@ -117,6 +117,15 @@ typedef struct TCString TCString;
|
||||||
*/
|
*/
|
||||||
typedef struct TCTask TCTask;
|
typedef struct TCTask TCTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically
|
||||||
|
* updated based on changes in the replica. Its lifetime is independent of the replica and it can
|
||||||
|
* be freed at any time.
|
||||||
|
*
|
||||||
|
* To iterate over a working set, search indexes 1 through largest_index.
|
||||||
|
*/
|
||||||
|
typedef struct TCWorkingSet TCWorkingSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCAnnotation contains the details of an annotation.
|
* TCAnnotation contains the details of an annotation.
|
||||||
*/
|
*/
|
||||||
|
@ -309,6 +318,13 @@ struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep);
|
||||||
*/
|
*/
|
||||||
struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep);
|
struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current working set for this replica.
|
||||||
|
*
|
||||||
|
* Returns NULL on error.
|
||||||
|
*/
|
||||||
|
struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an existing task by its UUID.
|
* Get an existing task by its UUID.
|
||||||
*
|
*
|
||||||
|
@ -721,6 +737,34 @@ TCResult tc_uuid_from_str(struct TCString *s, struct TCUuid *uuid_out);
|
||||||
*/
|
*/
|
||||||
void tc_uuid_list_free(struct TCUuidList *tcuuids);
|
void tc_uuid_list_free(struct TCUuidList *tcuuids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the working set's length, or the number of UUIDs it contains.
|
||||||
|
*/
|
||||||
|
size_t tc_working_set_len(struct TCWorkingSet *ws);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the working set's largest index.
|
||||||
|
*/
|
||||||
|
size_t tc_working_set_largest_index(struct TCWorkingSet *ws);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the UUID for the task at the given index. Returns true if the UUID exists in the working
|
||||||
|
* set. If not, returns false and does not change uuid_out.
|
||||||
|
*/
|
||||||
|
bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the working set index for the task with the given UUID. Returns 0 if the task is not in
|
||||||
|
* the working set.
|
||||||
|
*/
|
||||||
|
size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this
|
||||||
|
* function returns, and must not be freed more than once.
|
||||||
|
*/
|
||||||
|
void tc_working_set_free(struct TCWorkingSet *ws);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
|
@ -38,6 +38,11 @@ impl WorkingSet {
|
||||||
self.by_index.iter().filter(|e| e.is_some()).count()
|
self.by_index.iter().filter(|e| e.is_some()).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the largest index in the working set, or zero if the set is empty.
|
||||||
|
pub fn largest_index(&self) -> usize {
|
||||||
|
self.by_index.len().saturating_sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
/// True if the length is zero
|
/// True if the length is zero
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.by_index.iter().all(|e| e.is_none())
|
self.by_index.iter().all(|e| e.is_none())
|
||||||
|
@ -103,6 +108,21 @@ mod test {
|
||||||
assert_eq!(ws.is_empty(), true);
|
assert_eq!(ws.is_empty(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_largest_index() {
|
||||||
|
let (uuid1, uuid2, ws) = make();
|
||||||
|
assert_eq!(ws.largest_index(), 0);
|
||||||
|
|
||||||
|
let ws = WorkingSet::new(vec![None, Some(uuid1)]);
|
||||||
|
assert_eq!(ws.largest_index(), 1);
|
||||||
|
|
||||||
|
let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2)]);
|
||||||
|
assert_eq!(ws.largest_index(), 3);
|
||||||
|
|
||||||
|
let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]);
|
||||||
|
assert_eq!(ws.largest_index(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_by_index() {
|
fn test_by_index() {
|
||||||
let (uuid1, uuid2, ws) = make();
|
let (uuid1, uuid2, ws) = make();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue