2016-06-28 18:13:11 +00:00
|
|
|
use std::collections::BTreeMap;
|
2016-07-06 18:05:37 +00:00
|
|
|
use std::ops::{Deref, DerefMut};
|
2016-07-10 14:33:49 +00:00
|
|
|
use std::io::BufRead;
|
2016-07-15 13:34:10 +00:00
|
|
|
use std::result::Result as RResult;
|
2016-07-06 18:05:37 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
use toml::Value;
|
2016-07-06 18:08:50 +00:00
|
|
|
use uuid::Uuid;
|
2016-05-04 11:47:39 +00:00
|
|
|
|
2016-05-04 12:37:25 +00:00
|
|
|
use task_hookrs::task::Task as TTask;
|
2016-07-10 14:33:49 +00:00
|
|
|
use task_hookrs::import::{import_task, import_tasks};
|
2016-05-04 12:37:25 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
use libimagstore::store::{FileLockEntry, Store};
|
2016-07-06 18:11:31 +00:00
|
|
|
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
|
2016-05-09 15:22:40 +00:00
|
|
|
use module_path::ModuleEntryPath;
|
2016-05-04 11:47:39 +00:00
|
|
|
|
2016-07-10 14:33:49 +00:00
|
|
|
use error::{TodoError, TodoErrorKind, MapErrInto};
|
2016-06-28 18:30:54 +00:00
|
|
|
use result::Result;
|
2016-05-09 10:11:13 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
/// Task struct containing a `FileLockEntry`
|
2016-05-04 13:00:48 +00:00
|
|
|
#[derive(Debug)]
|
2016-07-06 18:04:22 +00:00
|
|
|
pub struct Task<'a>(FileLockEntry<'a>);
|
2016-05-09 15:22:40 +00:00
|
|
|
|
|
|
|
impl<'a> Task<'a> {
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
/// Concstructs a new `Task` with a `FileLockEntry`
|
2016-07-06 18:04:22 +00:00
|
|
|
pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
|
|
|
|
Task(fle)
|
2016-05-04 13:00:48 +00:00
|
|
|
}
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-08-06 08:11:22 +00:00
|
|
|
pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
|
2016-07-10 14:33:49 +00:00
|
|
|
let mut line = String::new();
|
|
|
|
r.read_line(&mut line);
|
|
|
|
import_task(&line.as_str())
|
|
|
|
.map_err_into(TodoErrorKind::ImportError)
|
|
|
|
.and_then(|t| {
|
|
|
|
let uuid = t.uuid().clone();
|
2016-08-06 08:11:22 +00:00
|
|
|
t.into_task(store).map(|t| (t, line, uuid))
|
2016-07-10 14:33:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-07-10 14:50:18 +00:00
|
|
|
/// Get a task from an import string. That is: read the imported string, get the UUID from it
|
|
|
|
/// and try to load this UUID from store.
|
|
|
|
///
|
|
|
|
/// Possible return values are:
|
|
|
|
///
|
|
|
|
/// * Ok(Ok(Task))
|
|
|
|
/// * Ok(Err(String)) - where the String is the String read from the `r` parameter
|
|
|
|
/// * Err(_) - where the error is an error that happened during evaluation
|
|
|
|
///
|
|
|
|
pub fn get_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
|
|
|
|
{
|
2016-07-15 13:34:21 +00:00
|
|
|
let mut line = String::new();
|
|
|
|
r.read_line(&mut line);
|
|
|
|
Task::get_from_string(store, line)
|
2016-07-10 14:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a task from a String. The String is expected to contain the JSON-representation of the
|
|
|
|
/// Task to get from the store (only the UUID really matters in this case)
|
2016-07-15 13:34:10 +00:00
|
|
|
///
|
|
|
|
/// For an explanation on the return values see `Task::get_from_import()`.
|
|
|
|
pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
|
2016-07-15 13:34:31 +00:00
|
|
|
import_task(s.as_str())
|
|
|
|
.map_err_into(TodoErrorKind::ImportError)
|
|
|
|
.map(|t| t.uuid().clone())
|
|
|
|
.and_then(|uuid| Task::get_from_uuid(store, uuid))
|
|
|
|
.and_then(|o| match o {
|
|
|
|
None => Ok(Err(s)),
|
|
|
|
Some(t) => Ok(Ok(t)),
|
|
|
|
})
|
2016-07-10 14:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a task from an UUID.
|
2016-07-15 13:34:10 +00:00
|
|
|
///
|
|
|
|
/// If there is no task with this UUID, this returns `Ok(None)`.
|
|
|
|
pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
|
2016-07-15 13:34:41 +00:00
|
|
|
let store_id = ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid();
|
|
|
|
|
|
|
|
store.get(store_id)
|
|
|
|
.map(|o| o.map(Task::new))
|
|
|
|
.map_err_into(TodoErrorKind::StoreError)
|
2016-07-10 14:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
|
|
|
|
/// implicitely create the task if it does not exist.
|
|
|
|
pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
|
2016-07-15 13:42:02 +00:00
|
|
|
let mut line = String::new();
|
|
|
|
r.read_line(&mut line);
|
|
|
|
Task::retrieve_from_string(store, line)
|
2016-07-10 14:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve a task from a String. The String is expected to contain the JSON-representation of
|
|
|
|
/// the Task to retrieve from the store (only the UUID really matters in this case)
|
|
|
|
pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
|
2016-07-15 13:41:51 +00:00
|
|
|
Task::get_from_string(store, s)
|
|
|
|
.and_then(|opt| match opt {
|
|
|
|
Ok(task) => Ok(task),
|
|
|
|
Err(string) => import_task(string.as_str())
|
|
|
|
.map_err_into(TodoErrorKind::ImportError)
|
|
|
|
.and_then(|t| t.into_task(store)),
|
|
|
|
})
|
2016-07-10 14:50:18 +00:00
|
|
|
}
|
|
|
|
|
2016-08-06 09:39:31 +00:00
|
|
|
pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
|
|
|
|
use serde_json::ser::to_string as serde_to_string;
|
|
|
|
use task_hookrs::status::TaskStatus;
|
|
|
|
|
|
|
|
for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
|
|
|
|
match res_ttask {
|
|
|
|
Ok(ttask) => {
|
|
|
|
if counter % 2 == 1 {
|
|
|
|
// Only every second task is needed, the first one is the
|
|
|
|
// task before the change, and the second one after
|
|
|
|
// the change. The (maybe modified) second one is
|
|
|
|
// expected by taskwarrior.
|
|
|
|
match serde_to_string(&ttask).map_err_into(TodoErrorKind::ImportError) {
|
|
|
|
// use println!() here, as we talk with TW
|
|
|
|
Ok(val) => println!("{}", val),
|
|
|
|
Err(e) => return Err(e),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Taskwarrior does not have the concept of deleted tasks, but only modified
|
|
|
|
// ones.
|
|
|
|
//
|
|
|
|
// Here we check if the status of a task is deleted and if yes, we delete it
|
|
|
|
// from the store.
|
|
|
|
if *ttask.status() == TaskStatus::Deleted {
|
|
|
|
match Task::delete_by_uuid(store, *ttask.uuid()) {
|
|
|
|
Ok(_) => info!("Deleted task {}", *ttask.uuid()),
|
|
|
|
Err(e) => return Err(e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end if c % 2
|
|
|
|
},
|
|
|
|
Err(e) => return Err(e).map_err_into(TodoErrorKind::ImportError),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-07-06 18:08:50 +00:00
|
|
|
pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
|
|
|
|
store.delete(ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid())
|
|
|
|
.map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
|
|
|
|
}
|
|
|
|
|
2016-07-06 18:13:20 +00:00
|
|
|
pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
|
2016-07-06 18:11:31 +00:00
|
|
|
store.retrieve_for_module("todo/taskwarrior")
|
|
|
|
.map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
|
|
|
|
}
|
|
|
|
|
2016-07-06 18:13:20 +00:00
|
|
|
pub fn all(store: &Store) -> Result<TaskIterator> {
|
|
|
|
Task::all_as_ids(store)
|
|
|
|
.map(|iter| TaskIterator::new(store, iter))
|
|
|
|
}
|
|
|
|
|
2016-05-04 12:37:25 +00:00
|
|
|
}
|
2016-05-09 15:22:40 +00:00
|
|
|
|
2016-07-06 18:05:37 +00:00
|
|
|
impl<'a> Deref for Task<'a> {
|
|
|
|
type Target = FileLockEntry<'a>;
|
|
|
|
|
|
|
|
fn deref(&self) -> &FileLockEntry<'a> {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> DerefMut for Task<'a> {
|
|
|
|
|
|
|
|
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
|
|
|
|
&mut self.0
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
|
|
|
|
pub trait IntoTask<'a> {
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
/// # Usage
|
|
|
|
/// ```ignore
|
|
|
|
/// use std::io::stdin;
|
|
|
|
///
|
|
|
|
/// use task_hookrs::task::Task;
|
|
|
|
/// use task_hookrs::import::import;
|
|
|
|
/// use libimagstore::store::{Store, FileLockEntry};
|
|
|
|
///
|
|
|
|
/// if let Ok(task_hookrs_task) = import(stdin()) {
|
|
|
|
/// // Store is given at runtime
|
|
|
|
/// let task = task_hookrs_task.into_filelockentry(store);
|
|
|
|
/// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
|
|
|
|
/// }
|
|
|
|
/// ```
|
2016-07-06 18:14:26 +00:00
|
|
|
fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
}
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 10:11:13 +00:00
|
|
|
impl<'a> IntoTask<'a> for TTask {
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-07-06 18:14:26 +00:00
|
|
|
fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
|
2016-07-06 18:03:20 +00:00
|
|
|
let uuid = self.uuid();
|
2016-05-09 15:22:40 +00:00
|
|
|
let store_id = ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid();
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 15:22:40 +00:00
|
|
|
match store.retrieve(store_id) {
|
2016-07-06 18:03:20 +00:00
|
|
|
Err(e) => return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))),
|
2016-06-28 18:13:11 +00:00
|
|
|
Ok(mut fle) => {
|
|
|
|
{
|
|
|
|
let mut header = fle.get_header_mut();
|
|
|
|
match header.read("todo") {
|
|
|
|
Ok(None) => {
|
2016-07-06 18:03:20 +00:00
|
|
|
if let Err(e) = header.set("todo", Value::Table(BTreeMap::new())) {
|
|
|
|
return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
|
2016-06-28 18:13:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(Some(_)) => { }
|
|
|
|
Err(e) => {
|
2016-06-30 17:06:26 +00:00
|
|
|
return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
|
2016-06-28 18:13:11 +00:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 18:03:20 +00:00
|
|
|
|
|
|
|
if let Err(e) = header.set("todo.uuid", Value::String(format!("{}",uuid))) {
|
|
|
|
return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
|
2016-06-28 18:13:11 +00:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-06-28 18:13:11 +00:00
|
|
|
// If none of the errors above have returned the function, everything is fine
|
2016-07-06 18:04:22 +00:00
|
|
|
Ok(Task::new(fle))
|
2016-06-28 18:13:11 +00:00
|
|
|
}
|
2016-05-09 15:22:40 +00:00
|
|
|
}
|
2016-05-09 10:11:13 +00:00
|
|
|
}
|
2016-07-06 18:03:20 +00:00
|
|
|
|
2016-05-09 10:11:13 +00:00
|
|
|
}
|
2016-07-06 18:11:31 +00:00
|
|
|
|
|
|
|
trait FromStoreId {
|
|
|
|
fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> FromStoreId for Task<'a> {
|
|
|
|
|
|
|
|
fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
|
|
|
|
match store.retrieve(id) {
|
|
|
|
Err(e) => Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))),
|
|
|
|
Ok(c) => Ok(Task::new( c )),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct TaskIterator<'a> {
|
|
|
|
store: &'a Store,
|
|
|
|
iditer: StoreIdIterator,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> TaskIterator<'a> {
|
|
|
|
|
|
|
|
pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
|
|
|
|
TaskIterator {
|
|
|
|
store: store,
|
|
|
|
iditer: iditer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for TaskIterator<'a> {
|
|
|
|
type Item = Result<Task<'a>>;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Result<Task<'a>>> {
|
|
|
|
self.iditer.next().map(|id| Task::from_storeid(self.store, id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|