Reorganize handling of task data

This abandons field-by-field compatibility with the TaskWarrior TDB2
format, which wasn't a sustainable strategy anyway.

Instead, tasks are represented as a TaskMap with custom key formats.  In
this commit, there are only a few allowed keys, with room to grow.

Replica returns convenience wrappers Task (read-only) and TaskMut
(read-write) with getters and setters to make modifying tasks easier.
This commit is contained in:
Dustin J. Mitchell 2020-11-23 12:38:32 -05:00
parent c2c2a00ed5
commit 634aaadb73
10 changed files with 276 additions and 490 deletions

View file

@ -6,5 +6,6 @@
- [Data Model](./data-model.md)
- [Replica Storage](./storage.md)
- [Task Database](./taskdb.md)
- [Tasks](./tasks.md)
- [Synchronization](./sync.md)
- [Planned Functionality](./plans.md)

View file

@ -7,47 +7,15 @@ The storage is transaction-protected, with the expectation of a serializable iso
The storage contains the following information:
- `tasks`: a set of tasks, indexed by UUID
- `base_version`: the number of the last version sync'd from the server
- `base_version`: the number of the last version sync'd from the server (a single integer)
- `operations`: all operations performed since base_version
- `working_set`: a mapping from integer -> UUID, used to keep stable small-integer indexes into the tasks for users' convenience. This data is not synchronized with the server and does not affect any consistency guarantees.
## Tasks
The tasks are stored as an un-ordered collection, keyed by task UUID.
Each task in the database has an arbitrary-sized set of key/value properties, with string values.
Tasks are only created and modified; "deleted" tasks continue to stick around and can be modified and even un-deleted.
Tasks have an expiration time, after which they may be purged from the database.
### Task Fields
Each task can have any of the following fields.
Timestamps are stored as UNIX epoch timestamps, in the form of an integer expressed in decimal notation.
Note that it is possible, in task storage, for any field to be omitted.
NOTE: This structure is based on https://taskwarrior.org/docs/design/task.html, but will diverge from that
model over time.
* `status` - one of `Pending`, `Completed`, `Deleted`, `Recurring`, or `Waiting`
* `entry` (timestamp) - time that the task was created
* `description` - the one-line summary of the task
* `start` (timestamp) - if set, the task is active and this field gives the time the task was started
* `end` (timestamp) - the time at which the task was deleted or completed
* `due` (timestamp) - the time at which the task is due
* `until` (timestamp) - the time after which recurrent child tasks should not be created
* `wait` (timestamp) - the time before which this task is considered waiting and should not be shown
* `modified` (timestamp) - time that the task was last modified
* `scheduled` (timestamp) - time that the task is available to start
* `recur` - recurrence frequency
* `mask` - recurrence history
* `imask` - for children of recurring tasks, the index into the `mask` property on the parent
* `parent` - for children of recurring tasks, the uuid of the parent task
* `project` - the task's project (usually a short identifier)
* `priority` - the task's priority, one of `L`, `M`, or `H`.
* `depends` - a comma (`,`) separated list of uuids of tasks on which this task depends
* `tags` - a comma (`,`) separated list of tags for this task
* `annotation_<timestamp>` - an annotation for this task, with the timestamp as part of the key
* `udas` - user-defined attributes
Each task in the database has represented by a key-value map.
See [Tasks](./tasks.md) for details on the content of that map.
## Operations

38
docs/src/tasks.md Normal file
View file

@ -0,0 +1,38 @@
# Tasks
Tasks are stored internally as a key/value map with string keys and values.
All fields are optional: the `Create` operation creates an empty task.
Display layers should apply appropriate defaults where necessary.
## Atomicity
The synchronization process does not support read-modify-write operations.
For example, suppose tags are updated by reading a list of tags, adding a tag, and writing the result back.
This would be captured as an `Update` operation containing the amended list of tags.
Suppose two such `Update` operations are made in different replicas and must be reconciled:
* `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag1", "2020-11-23T14:21:22Z")`
* `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag2", "2020-11-23T15:08:57Z")`
The result of this reconciliation will be `oldtag,newtag2`, while the user almost certainly intended `oldtag,newtag1,newtag2`.
The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation.
## Representations
Integers are stored in decimal notation.
Timestamps are stored as UNIX epoch timestamps, in the form of an integer.
## Keys
The following keys, and key formats, are defined:
* `status` - one of `P` for a pending task (the default), `C` for completed or `D` for deleted
* `description` - the one-line summary of the task
* `modified` - the time of the last modification of this task
The following are not yet implemented:
* `dep.<uuid>` - indicates this task depends on `<uuid>` (value is an empty string)
* `tag.<tag>` - indicates this task has tag `<tag>` (value is an empty string)
* `annotation.<timestamp>` - value is an annotation created at the given time