Merge pull request #1029 from matthiasbeyer/all-extensions-as-traits

All extensions as traits
This commit is contained in:
Matthias Beyer 2017-09-04 21:59:54 +02:00 committed by GitHub
commit 6d1dab3117
29 changed files with 1101 additions and 1056 deletions

View file

@ -48,7 +48,7 @@ use ui::build_ui;
use std::path::PathBuf; use std::path::PathBuf;
use libimagentryref::reference::Ref; use libimagentryref::refstore::RefStore;
use libimagentryref::flags::RefFlags; use libimagentryref::flags::RefFlags;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error;
use libimagrt::setup::generate_runtime_setup; use libimagrt::setup::generate_runtime_setup;
@ -82,7 +82,7 @@ fn add(rt: &Runtime) {
.with_content_hashing(cmd.is_present("track-content")) .with_content_hashing(cmd.is_present("track-content"))
.with_permission_tracking(cmd.is_present("track-permissions")); .with_permission_tracking(cmd.is_present("track-permissions"));
match Ref::create(rt.store(), path, flags) { match RefStore::create(rt.store(), path, flags) {
Ok(r) => { Ok(r) => {
debug!("Reference created: {:?}", r); debug!("Reference created: {:?}", r);
info!("Ok"); info!("Ok");
@ -102,7 +102,7 @@ fn remove(rt: &Runtime) {
let yes = cmd.is_present("yes"); let yes = cmd.is_present("yes");
if yes || ask_bool(&format!("Delete Ref with hash '{}'", hash)[..], None) { if yes || ask_bool(&format!("Delete Ref with hash '{}'", hash)[..], None) {
match Ref::delete_by_hash(rt.store(), hash) { match rt.store().delete_by_hash(hash) {
Err(e) => trace_error(&e), Err(e) => trace_error(&e),
Ok(_) => info!("Ok"), Ok(_) => info!("Ok"),
} }
@ -126,7 +126,7 @@ fn list(rt: &Runtime) {
let iter = match rt.store().retrieve_for_module("ref") { let iter = match rt.store().retrieve_for_module("ref") {
Ok(iter) => iter.filter_map(|id| { Ok(iter) => iter.filter_map(|id| {
match Ref::get(rt.store(), id) { match rt.store().get(id) {
Ok(r) => Some(r), Ok(r) => Some(r),
Err(e) => { Err(e) => {
trace_error(&e); trace_error(&e);
@ -145,7 +145,7 @@ fn list(rt: &Runtime) {
.check_changed(do_check_changed) .check_changed(do_check_changed)
.check_changed_content(do_check_changed_content) .check_changed_content(do_check_changed_content)
.check_changed_permiss(do_check_changed_permiss) .check_changed_permiss(do_check_changed_permiss)
.list(iter.map(|e| e.into())) .list(iter.filter_map(Into::into))
.ok(); .ok();
} }

View file

@ -19,16 +19,18 @@
use std::process::exit; use std::process::exit;
use clap::ArgMatches;
use libimagdiary::diary::Diary; use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId; use libimagdiary::diaryid::DiaryId;
use libimagdiary::error::DiaryErrorKind as DEK; use libimagdiary::error::DiaryErrorKind as DEK;
use libimagdiary::error::MapErrInto; use libimagdiary::error::MapErrInto;
use libimagentryedit::edit::Edit; use libimagentryedit::edit::Edit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error_exit;
use libimagdiary::entry::Entry;
use libimagdiary::result::Result;
use libimagutil::warn_exit::warn_exit; use libimagutil::warn_exit::warn_exit;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use util::get_diary_name; use util::get_diary_name;
@ -36,88 +38,90 @@ pub fn create(rt: &Runtime) {
let diaryname = get_diary_name(rt) let diaryname = get_diary_name(rt)
.unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1)); .unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
let prevent_edit = rt.cli().subcommand_matches("create").unwrap().is_present("no-edit"); let mut entry = create_entry(rt.store(), &diaryname, rt);
fn create_entry<'a>(diary: &'a Diary, rt: &Runtime) -> Result<Entry<'a>> { let res = if rt.cli().subcommand_matches("create").unwrap().is_present("no-edit") {
use std::str::FromStr; debug!("Not editing new diary entry");
Ok(())
let create = rt.cli().subcommand_matches("create").unwrap(); } else {
if !create.is_present("timed") { debug!("Editing new diary entry");
debug!("Creating non-timed entry"); entry.edit_content(rt)
diary.new_entry_today() .map_err_into(DEK::DiaryEditError)
} else { };
let id = match create.value_of("timed") {
Some("h") | Some("hourly") => {
debug!("Creating hourly-timed entry");
let time = DiaryId::now(String::from(diary.name()));
let hr = create
.value_of("hour")
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
time.with_hour(hr).with_minute(0)
},
Some("m") | Some("minutely") => {
debug!("Creating minutely-timed entry");
let time = DiaryId::now(String::from(diary.name()));
let hr = create
.value_of("hour")
.map(|h| { debug!("hour = {:?}", h); h })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
let min = create
.value_of("minute")
.map(|m| { debug!("minute = {:?}", m); m })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse minute: '{}'", s))
.ok()
})
.unwrap_or(time.minute());
time.with_hour(hr).with_minute(min)
},
Some(_) => {
warn!("Timed creation failed: Unknown spec '{}'",
create.value_of("timed").unwrap());
exit(1);
},
None => warn_exit("Unexpected error, cannot continue", 1)
};
diary.new_entry_by_id(id)
}
}
let diary = Diary::open(rt.store(), &diaryname[..]);
let res = create_entry(&diary, rt)
.and_then(|mut entry| {
if prevent_edit {
debug!("Not editing new diary entry");
Ok(())
} else {
debug!("Editing new diary entry");
entry.edit_content(rt).map_err_into(DEK::DiaryEditError)
}
});
if let Err(e) = res { if let Err(e) = res {
trace_error(&e); trace_error_exit(&e, 1);
} else { } else {
info!("Ok!"); info!("Ok!");
} }
} }
fn create_entry<'a>(diary: &'a Store, diaryname: &str, rt: &Runtime) -> FileLockEntry<'a> {
let create = rt.cli().subcommand_matches("create").unwrap();
let entry = if !create.is_present("timed") {
debug!("Creating non-timed entry");
diary.new_entry_today(diaryname)
} else {
let id = create_id_from_clispec(&create, &diaryname);
diary.retrieve(id).map_err_into(DEK::StoreReadError)
};
match entry {
Err(e) => trace_error_exit(&e, 1),
Ok(e) => {
debug!("Created: {}", e.get_location());
e
}
}
}
fn create_id_from_clispec(create: &ArgMatches, diaryname: &str) -> DiaryId {
use std::str::FromStr;
let get_hourly_id = |create: &ArgMatches| -> DiaryId {
let time = DiaryId::now(String::from(diaryname));
let hr = create
.value_of("hour")
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
time.with_hour(hr)
};
match create.value_of("timed") {
Some("h") | Some("hourly") => {
debug!("Creating hourly-timed entry");
get_hourly_id(create)
},
Some("m") | Some("minutely") => {
let time = get_hourly_id(create);
let min = create
.value_of("minute")
.map(|m| { debug!("minute = {:?}", m); m })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse minute: '{}'", s))
.ok()
})
.unwrap_or(time.minute());
time.with_minute(min)
},
Some(_) => {
warn!("Timed creation failed: Unknown spec '{}'",
create.value_of("timed").unwrap());
exit(1);
},
None => warn_exit("Unexpected error, cannot continue", 1)
}
}

View file

@ -17,15 +17,17 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use std::process::exit;
use chrono::naive::NaiveDateTime; use chrono::naive::NaiveDateTime;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId; use libimagdiary::diaryid::DiaryId;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error_exit; use libimagerror::trace::trace_error_exit;
use libimagtimeui::datetime::DateTime; use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse; use libimagtimeui::parse::Parse;
use libimagutil::warn_exit::warn_exit; use libimagutil::warn_exit::warn_exit;
use libimagstore::storeid::IntoStoreId;
use util::get_diary_name; use util::get_diary_name;
@ -35,36 +37,34 @@ pub fn delete(rt: &Runtime) {
let diaryname = get_diary_name(rt) let diaryname = get_diary_name(rt)
.unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1)); .unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
let diary = Diary::open(rt.store(), &diaryname[..]); let to_del_location = rt
debug!("Diary opened: {:?}", diary);
let datetime : Option<NaiveDateTime> = rt
.cli() .cli()
.subcommand_matches("delete") .subcommand_matches("delete")
.unwrap() .unwrap()
.value_of("datetime") .value_of("datetime")
.map(|dt| { debug!("DateTime = {:?}", dt); dt }) .map(|dt| { debug!("DateTime = {:?}", dt); dt })
.and_then(DateTime::parse) .and_then(DateTime::parse)
.map(|dt| dt.into()); .map(|dt| dt.into())
.ok_or_else(|| {
warn!("Not deleting entries, because missing date/time specification");
exit(1);
})
.and_then(|dt: NaiveDateTime| {
DiaryId::from_datetime(diaryname.clone(), dt)
.into_storeid()
.map(|id| rt.store().retrieve(id))
.unwrap_or_else(|e| trace_error_exit(&e, 1))
})
.unwrap_or_else(|e| trace_error_exit(&e, 1))
.get_location()
.clone();
let to_del = match datetime { if !ask_bool(&format!("Deleting {:?}", to_del_location), Some(true)) {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
None => diary.get_youngest_entry(),
};
let to_del = match to_del {
Some(Ok(e)) => e,
Some(Err(e)) => trace_error_exit(&e, 1),
None => warn_exit("No entry", 1)
};
if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) {
info!("Aborting delete action"); info!("Aborting delete action");
return; return;
} }
if let Err(e) = diary.delete_entry(to_del) { if let Err(e) = rt.store().delete(to_del_location) {
trace_error_exit(&e, 1) trace_error_exit(&e, 1)
} }

View file

@ -17,6 +17,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use std::process::exit;
use chrono::naive::NaiveDateTime; use chrono::naive::NaiveDateTime;
use libimagdiary::diary::Diary; use libimagdiary::diary::Diary;
@ -30,33 +32,39 @@ use libimagerror::into::IntoError;
use libimagtimeui::datetime::DateTime; use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse; use libimagtimeui::parse::Parse;
use libimagutil::warn_exit::warn_exit; use libimagutil::warn_exit::warn_exit;
use libimagerror::trace::trace_error_exit;
use util::get_diary_name; use util::get_diary_name;
pub fn edit(rt: &Runtime) { pub fn edit(rt: &Runtime) {
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1)); let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
let diary = Diary::open(rt.store(), &diaryname[..]);
let datetime : Option<NaiveDateTime> = rt rt.cli()
.cli()
.subcommand_matches("edit") .subcommand_matches("edit")
.unwrap() .unwrap()
.value_of("datetime") .value_of("datetime")
.and_then(DateTime::parse) .and_then(DateTime::parse)
.map(|dt| dt.into()); .map(|dt| dt.into())
.map(|dt: NaiveDateTime| DiaryId::from_datetime(diaryname.clone(), dt))
let to_edit = match datetime { .or_else(|| {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))), rt.store()
None => diary.get_youngest_entry(), .get_youngest_entry_id(&diaryname)
}; .map(|optid| match optid {
Ok(id) => id,
match to_edit { Err(e) => trace_error_exit(&e, 1),
Some(Ok(mut e)) => e.edit_content(rt).map_err_into(DEK::IOError), })
})
Some(Err(e)) => Err(e), .ok_or_else(|| {
None => Err(DEK::EntryNotInDiary.into_error()), error!("No entries in diary. Aborting");
} exit(1)
.map_err_trace().ok(); })
.and_then(|id| rt.store().get(id))
.map(|opte| match opte {
Some(mut e) => e.edit_content(rt).map_err_into(DEK::IOError),
None => Err(DEK::EntryNotInDiary.into_error()),
})
.map_err_trace()
.ok();
} }

View file

@ -42,9 +42,7 @@ pub fn list(rt: &Runtime) {
.unwrap_or(String::from("<<Path Parsing Error>>")) .unwrap_or(String::from("<<Path Parsing Error>>"))
} }
let diary = Diary::open(rt.store(), &diaryname[..]); Diary::entries(rt.store(), &diaryname)
debug!("Diary opened: {:?}", diary);
diary.entries()
.and_then(|es| { .and_then(|es| {
debug!("Iterator for listing: {:?}", es); debug!("Iterator for listing: {:?}", es);

View file

@ -27,10 +27,9 @@ use util::get_diary_name;
pub fn view(rt: &Runtime) { pub fn view(rt: &Runtime) {
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1)); let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
let diary = Diary::open(rt.store(), &diaryname[..]);
let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header"); let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
diary.entries() Diary::entries(rt.store(), &diaryname)
.and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok))) .and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
.map_err_trace() .map_err_trace()
.ok(); .ok();

View file

@ -17,5 +17,4 @@ version = "2.0.1"
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" } libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" } libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" } libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" }
libimagentryref = { version = "0.4.0", path = "../../../lib/entry/libimagentryref" }
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" } libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }

View file

@ -25,11 +25,9 @@ extern crate libimagrt;
extern crate libimagmail; extern crate libimagmail;
extern crate libimagerror; extern crate libimagerror;
extern crate libimagutil; extern crate libimagutil;
extern crate libimagentryref;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit}; use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
use libimagmail::mail::Mail; use libimagmail::mail::Mail;
use libimagentryref::reference::Ref;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup; use libimagrt::setup::generate_runtime_setup;
use libimagutil::info_result::*; use libimagutil::info_result::*;
@ -74,11 +72,11 @@ fn list(rt: &Runtime) {
let iter = match store.retrieve_for_module("ref") { let iter = match store.retrieve_for_module("ref") {
Ok(iter) => iter.filter_map(|id| { Ok(iter) => iter.filter_map(|id| {
Ref::get(store, id) match store.get(id).map_err_into(MEK::RefHandlingError).map_err_trace() {
.map_err_into(MEK::RefHandlingError) Ok(Some(fle)) => Mail::from_fle(fle).map_err_trace().ok(),
.and_then(|rf| Mail::from_ref(rf)) Ok(None) => None,
.map_err_trace() Err(e) => trace_error_exit(&e, 1),
.ok() }
}), }),
Err(e) => trace_error_exit(&e, 1), Err(e) => trace_error_exit(&e, 1),
}; };

View file

@ -35,7 +35,7 @@ use toml::Value;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup; use libimagrt::setup::generate_runtime_setup;
use libimagtodo::task::Task; use libimagtodo::taskstore::TaskStore;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit}; use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
mod ui; mod ui;
@ -61,9 +61,11 @@ fn tw_hook(rt: &Runtime) {
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap(); let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
if subcmd.is_present("add") { if subcmd.is_present("add") {
let stdin = stdin(); let stdin = stdin();
let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`
match Task::import(rt.store(), stdin) { // implements BufRead which is required for `Store::import_task_from_reader()`
let stdin = stdin.lock();
match rt.store().import_task_from_reader(stdin) {
Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid), Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
Err(e) => trace_error_exit(&e, 1), Err(e) => trace_error_exit(&e, 1),
} }
@ -71,7 +73,7 @@ fn tw_hook(rt: &Runtime) {
// The used hook is "on-modify". This hook gives two json-objects // The used hook is "on-modify". This hook gives two json-objects
// per usage und wants one (the second one) back. // per usage und wants one (the second one) back.
let stdin = stdin(); let stdin = stdin();
Task::delete_by_imports(rt.store(), stdin.lock()).map_err_trace().ok(); rt.store().delete_tasks_by_imports(stdin.lock()).map_err_trace().ok();
} else { } else {
// Should not be possible, as one argument is required via // Should not be possible, as one argument is required via
// ArgGroup // ArgGroup
@ -92,30 +94,35 @@ fn list(rt: &Runtime) {
is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_)) is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
}; };
let res = Task::all(rt.store()) // get all tasks let res = rt.store().all_tasks() // get all tasks
.map(|iter| { // and if this succeeded .map(|iter| { // and if this succeeded
// filter out the ones were we can read the uuid // filter out the ones were we can read the uuid
let uuids : Vec<_> = iter.filter_map(|t| match t { let uuids : Vec<_> = iter.filter_map(|storeid| {
Ok(v) => match v.get_header().read(&String::from("todo.uuid")) { match rt.store().retrieve(storeid) {
Ok(Some(&Value::String(ref u))) => Some(u.clone()), Ok(fle) => {
Ok(Some(_)) => { match fle.get_header().read(&String::from("todo.uuid")) {
warn!("Header type error"); Ok(Some(&Value::String(ref u))) => Some(u.clone()),
None Ok(Some(_)) => {
}, error!("Header type error, expected String at 'todo.uuid' in {}",
Ok(None) => { fle.get_location());
warn!("Header missing field"); None
None },
Ok(None) => {
error!("Header missing field in {}", fle.get_location());
None
},
Err(e) => {
if !no_identifier(&e) {
trace_error(&e);
}
None
}
}
}, },
Err(e) => { Err(e) => {
if !no_identifier(&e) { trace_error(&e);
trace_error(&e);
}
None None
} },
},
Err(e) => {
trace_error(&e);
None
} }
}) })
.collect(); .collect();

View file

@ -19,110 +19,117 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store; use libimagstore::store::Store;
use libimagstore::storeid::IntoStoreId;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error;
use chrono::offset::Local; use chrono::offset::Local;
use chrono::Datelike; use chrono::Datelike;
use itertools::Itertools; use itertools::Itertools;
use chrono::naive::NaiveDateTime; use chrono::naive::NaiveDateTime;
use chrono::Timelike;
use entry::Entry; use entry::DiaryEntry;
use diaryid::DiaryId; use diaryid::DiaryId;
use error::DiaryError as DE;
use error::DiaryErrorKind as DEK; use error::DiaryErrorKind as DEK;
use error::MapErrInto;
use result::Result; use result::Result;
use iter::DiaryEntryIterator; use iter::DiaryEntryIterator;
use is_in_diary::IsInDiary; use iter::DiaryNameIterator;
pub trait Diary {
// create or get a new entry for today
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry>;
// create or get a new entry for now
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry>;
// Get an iterator for iterating over all entries of a Diary
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator>;
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>>;
/// Get all diary names
fn diary_names(&self) -> Result<DiaryNameIterator>;
#[derive(Debug)]
pub struct Diary<'a> {
store: &'a Store,
name: &'a str,
} }
impl<'a> Diary<'a> { impl Diary for Store {
pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> { // create or get a new entry for today
Diary { fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry> {
store: store, let dt = Local::now();
name: name, let ndt = dt.naive_local();
} let id = DiaryId::new(String::from(diary_name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
self.retrieve(id).map_err_into(DEK::StoreReadError)
} }
// create or get a new entry for today // create or get a new entry for today
pub fn new_entry_today(&self) -> Result<Entry> { fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry> {
let dt = Local::now(); let dt = Local::now();
let ndt = dt.naive_local(); let ndt = dt.naive_local();
let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0); let id = DiaryId::new(String::from(diary_name),
self.new_entry_by_id(id) ndt.year(),
} ndt.month(),
ndt.day(),
ndt.minute(),
ndt.second());
pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> { self.retrieve(id).map_err_into(DEK::StoreReadError)
self.retrieve(id.with_diary_name(String::from(self.name)))
}
pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
id.into_storeid()
.and_then(|id| self.store.retrieve(id))
.map(|fle| Entry::new(fle))
.map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
} }
// Get an iterator for iterating over all entries // Get an iterator for iterating over all entries
pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> { fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator> {
self.store self.retrieve_for_module("diary")
.retrieve_for_module("diary") .map(|iter| DiaryEntryIterator::new(self, String::from(diary_name), iter))
.map(|iter| DiaryEntryIterator::new(self.name, self.store, iter)) .map_err_into(DEK::StoreReadError)
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
} }
pub fn delete_entry(&self, entry: Entry) -> Result<()> { fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>> {
if !entry.is_in_diary(self.name) { match Diary::entries(self, diary_name) {
return Err(DE::new(DEK::EntryNotInDiary, None));
}
let id = entry.get_location().clone();
drop(entry);
self.store.delete(id)
.map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
}
pub fn get_youngest_entry(&self) -> Option<Result<Entry>> {
match self.entries() {
Err(e) => Some(Err(e)), Err(e) => Some(Err(e)),
Ok(entries) => { Ok(entries) => {
entries.sorted_by(|a, b| { entries
match (a, b) { .map(|e| e.and_then(|e| e.diary_id()))
(&Ok(ref a), &Ok(ref b)) => { .sorted_by(|a, b| {
let a : NaiveDateTime = a.diary_id().into(); match (a, b) {
let b : NaiveDateTime = b.diary_id().into(); (&Ok(ref a), &Ok(ref b)) => {
let a : NaiveDateTime = a.clone().into();
let b : NaiveDateTime = b.clone().into();
a.cmp(&b) a.cmp(&b)
}, },
(&Ok(_), &Err(ref e)) => { (&Ok(_), &Err(ref e)) => {
trace_error(e); trace_error(e);
Ordering::Less Ordering::Less
}, },
(&Err(ref e), &Ok(_)) => { (&Err(ref e), &Ok(_)) => {
trace_error(e); trace_error(e);
Ordering::Greater Ordering::Greater
}, },
(&Err(ref e1), &Err(ref e2)) => { (&Err(ref e1), &Err(ref e2)) => {
trace_error(e1); trace_error(e1);
trace_error(e2); trace_error(e2);
Ordering::Equal Ordering::Equal
}, },
} }
}).into_iter().next() })
.into_iter()
//.map(|sidres| sidres.map(|sid| DiaryId::from_storeid(&sid)))
.next()
} }
} }
} }
pub fn name(&self) -> &'a str { /// Get all diary names
&self.name fn diary_names(&self) -> Result<DiaryNameIterator> {
self.retrieve_for_module("diary")
.map_err_into(DEK::StoreReadError)
.map(DiaryNameIterator::new)
} }
} }

View file

@ -17,74 +17,24 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use std::ops::Deref; use libimagstore::store::Entry;
use std::ops::DerefMut;
use libimagstore::store::FileLockEntry;
use libimagentryedit::edit::Edit;
use libimagentryedit::result::Result as EditResult;
use libimagrt::runtime::Runtime;
use diaryid::DiaryId; use diaryid::DiaryId;
use diaryid::FromStoreId; use diaryid::FromStoreId;
use result::Result;
#[derive(Debug)] pub trait DiaryEntry {
pub struct Entry<'a>(FileLockEntry<'a>); fn diary_id(&self) -> Result<DiaryId>;
impl<'a> Deref for Entry<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
} }
impl<'a> DerefMut for Entry<'a> { impl DiaryEntry for Entry {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
impl<'a> Entry<'a> {
pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry(fle)
}
/// Get the diary id for this entry. /// Get the diary id for this entry.
/// ///
/// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable /// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
pub fn diary_id(&self) -> DiaryId { fn diary_id(&self) -> Result<DiaryId> {
DiaryId::from_storeid(&self.0.get_location().clone()).unwrap() DiaryId::from_storeid(&self.get_location().clone())
} }
} }
impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
}
}
impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry::new(fle)
}
}
impl<'a> Edit for Entry<'a> {
fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
self.0.edit_content(rt)
}
}

View file

@ -28,7 +28,8 @@ generate_error_module!(
EntryNotInDiary => "Entry not in Diary", EntryNotInDiary => "Entry not in Diary",
IOError => "IO Error", IOError => "IO Error",
ViewError => "Error viewing diary entry", ViewError => "Error viewing diary entry",
IdParseError => "Error while parsing ID" IdParseError => "Error while parsing ID",
DiaryNameFindingError => "Error while finding a diary name"
); );
); );

View file

@ -24,6 +24,8 @@ pub trait IsInDiary {
fn is_in_diary(&self, name: &str) -> bool; fn is_in_diary(&self, name: &str) -> bool;
fn is_a_diary_entry(&self) -> bool;
} }
impl IsInDiary for Entry { impl IsInDiary for Entry {
@ -32,6 +34,10 @@ impl IsInDiary for Entry {
self.get_location().clone().is_in_diary(name) self.get_location().clone().is_in_diary(name)
} }
fn is_a_diary_entry(&self) -> bool {
self.get_location().clone().is_a_diary_entry()
}
} }
impl IsInDiary for StoreId { impl IsInDiary for StoreId {
@ -40,5 +46,9 @@ impl IsInDiary for StoreId {
self.local().starts_with(format!("diary/{}", name)) self.local().starts_with(format!("diary/{}", name))
} }
fn is_a_diary_entry(&self) -> bool {
self.local().starts_with("diary")
}
} }

View file

@ -21,21 +21,23 @@ use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult; use std::result::Result as RResult;
use libimagstore::store::Store; use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreIdIterator; use libimagstore::storeid::StoreIdIterator;
use libimagerror::trace::trace_error;
use libimagerror::into::IntoError;
use diaryid::DiaryId; use diaryid::DiaryId;
use diaryid::FromStoreId; use diaryid::FromStoreId;
use is_in_diary::IsInDiary;
use entry::Entry as DiaryEntry;
use error::DiaryError as DE; use error::DiaryError as DE;
use error::DiaryErrorKind as DEK; use error::DiaryErrorKind as DEK;
use error::MapErrInto;
use result::Result; use result::Result;
use libimagerror::trace::trace_error; use is_in_diary::IsInDiary;
/// A iterator for iterating over diary entries /// A iterator for iterating over diary entries
pub struct DiaryEntryIterator<'a> { pub struct DiaryEntryIterator<'a> {
store: &'a Store, store: &'a Store,
name: &'a str, name: String,
iter: StoreIdIterator, iter: StoreIdIterator,
year: Option<i32>, year: Option<i32>,
@ -54,7 +56,7 @@ impl<'a> Debug for DiaryEntryIterator<'a> {
impl<'a> DiaryEntryIterator<'a> { impl<'a> DiaryEntryIterator<'a> {
pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> { pub fn new(store: &'a Store, diaryname: String, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
DiaryEntryIterator { DiaryEntryIterator {
store: store, store: store,
name: diaryname, name: diaryname,
@ -87,9 +89,9 @@ impl<'a> DiaryEntryIterator<'a> {
} }
impl<'a> Iterator for DiaryEntryIterator<'a> { impl<'a> Iterator for DiaryEntryIterator<'a> {
type Item = Result<DiaryEntry<'a>>; type Item = Result<FileLockEntry<'a>>;
fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
let next = match self.iter.next() { let next = match self.iter.next() {
Some(s) => s, Some(s) => s,
@ -97,7 +99,7 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
}; };
debug!("Next element: {:?}", next); debug!("Next element: {:?}", next);
if next.is_in_diary(self.name) { if next.is_in_diary(&self.name) {
debug!("Seems to be in diary: {:?}", next); debug!("Seems to be in diary: {:?}", next);
let id = match DiaryId::from_storeid(&next) { let id = match DiaryId::from_storeid(&next) {
Ok(i) => i, Ok(i) => i,
@ -118,7 +120,6 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
return Some(self return Some(self
.store .store
.retrieve(next) .retrieve(next)
.map(|fle| DiaryEntry::new(fle))
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e)))) .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
); );
} }
@ -130,3 +131,37 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
} }
/// Get diary names.
///
/// # Warning
///
/// Does _not_ run a `unique` on the iterator!
pub struct DiaryNameIterator(StoreIdIterator);
impl DiaryNameIterator {
pub fn new(s: StoreIdIterator) -> DiaryNameIterator {
DiaryNameIterator(s)
}
}
impl Iterator for DiaryNameIterator {
type Item = Result<String>;
fn next(&mut self) -> Option<Self::Item> {
self.0
.next()
.map(|s| {
s.to_str()
.map_err_into(DEK::DiaryNameFindingError)
.and_then(|s| {
s.split("diary/")
.nth(1)
.and_then(|n| n.split("/").nth(0).map(String::from))
.ok_or(DEK::DiaryNameFindingError.into_error())
})
})
}
}

View file

@ -19,13 +19,15 @@
//! A diary viewer built on libimagentryview. //! A diary viewer built on libimagentryview.
use entry::Entry; use entry::DiaryEntry;
use error::DiaryErrorKind as DEK; use error::DiaryErrorKind as DEK;
use error::MapErrInto; use error::MapErrInto;
use result::Result; use result::Result;
use libimagstore::store::FileLockEntry;
use libimagentryview::viewer::Viewer; use libimagentryview::viewer::Viewer;
use libimagentryview::builtin::plain::PlainViewer; use libimagentryview::builtin::plain::PlainViewer;
use libimagerror::trace::trace_error;
/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to /// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
/// call some diary-type specific functions on the entries passed to this. /// call some diary-type specific functions on the entries passed to this.
@ -46,10 +48,12 @@ impl DiaryViewer {
/// View all entries from the iterator, or stop immediately if an error occurs, returning that /// View all entries from the iterator, or stop immediately if an error occurs, returning that
/// error. /// error.
pub fn view_entries<'a, I: Iterator<Item = Entry<'a>>>(&self, entries: I) -> Result<()> { pub fn view_entries<'a, I: Iterator<Item = FileLockEntry<'a>>>(&self, entries: I) -> Result<()> {
for entry in entries { for entry in entries {
let id = entry.diary_id(); match entry.diary_id() {
println!("{} :\n", id); Ok(id) => println!("{} :\n", id),
Err(e) => trace_error(&e),
}
let _ = try!(self.0 let _ = try!(self.0
.view_entry(&entry) .view_entry(&entry)
.map_err_into(DEK::ViewError) .map_err_into(DEK::ViewError)

View file

@ -27,16 +27,16 @@
use mail::Mail; use mail::Mail;
use result::Result; use result::Result;
use libimagentryref::reference::Ref; use libimagstore::store::FileLockEntry;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct MailIter<'a, I: 'a + Iterator<Item = Ref<'a>>> { pub struct MailIter<'a, I: Iterator<Item = FileLockEntry<'a>>> {
_marker: PhantomData<&'a I>, _marker: PhantomData<I>,
i: I, i: I,
} }
impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> { impl<'a, I: Iterator<Item = FileLockEntry<'a>>> MailIter<'a, I> {
pub fn new(i: I) -> MailIter<'a, I> { pub fn new(i: I) -> MailIter<'a, I> {
MailIter { _marker: PhantomData, i: i } MailIter { _marker: PhantomData, i: i }
@ -44,12 +44,11 @@ impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
} }
impl<'a, I: Iterator<Item = Ref<'a>>> Iterator for MailIter<'a, I> { impl<'a, I: Iterator<Item = FileLockEntry<'a>>> Iterator for MailIter<'a, I> {
type Item = Result<Mail<'a>>; type Item = Result<Mail<'a>>;
fn next(&mut self) -> Option<Result<Mail<'a>>> { fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(Mail::from_ref) self.i.next().map(Mail::from_fle)
} }
} }

View file

@ -23,8 +23,10 @@ use std::fs::File;
use std::io::Read; use std::io::Read;
use libimagstore::store::Store; use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagentryref::reference::Ref; use libimagentryref::reference::Ref;
use libimagentryref::flags::RefFlags; use libimagentryref::flags::RefFlags;
use libimagentryref::refstore::RefStore;
use email::MimeMessage; use email::MimeMessage;
use email::results::ParsingResult as EmailParsingResult; use email::results::ParsingResult as EmailParsingResult;
@ -47,7 +49,7 @@ impl From<String> for Buffer {
} }
} }
pub struct Mail<'a>(Ref<'a>, Buffer); pub struct Mail<'a>(FileLockEntry<'a>, Buffer);
impl<'a> Mail<'a> { impl<'a> Mail<'a> {
@ -58,7 +60,7 @@ impl<'a> Mail<'a> {
let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false); let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
let p = PathBuf::from(p.as_ref()); let p = PathBuf::from(p.as_ref());
Ref::create_with_hasher(store, p, f, h) store.create_with_hasher(p, f, h)
.map_err_into(MEK::RefCreationError) .map_err_into(MEK::RefCreationError)
.and_then(|reference| { .and_then(|reference| {
debug!("Build reference file: {:?}", reference); debug!("Build reference file: {:?}", reference);
@ -79,20 +81,19 @@ impl<'a> Mail<'a> {
/// Opens a mail by the passed hash /// Opens a mail by the passed hash
pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> { pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
debug!("Opening Mail by Hash"); debug!("Opening Mail by Hash");
Ref::get_by_hash(store, String::from(hash.as_ref())) store.get_by_hash(String::from(hash.as_ref()))
.map_err_into(MEK::FetchByHashError) .map_err_into(MEK::FetchByHashError)
.map_err_into(MEK::FetchError) .map_err_into(MEK::FetchError)
.and_then(|o| match o { .and_then(|o| match o {
Some(r) => Mail::from_ref(r).map(Some), Some(r) => Mail::from_fle(r).map(Some),
None => Ok(None), None => Ok(None),
}) })
} }
/// Implement me as TryFrom as soon as it is stable /// Implement me as TryFrom as soon as it is stable
pub fn from_ref(r: Ref<'a>) -> Result<Mail> { pub fn from_fle(fle: FileLockEntry<'a>) -> Result<Mail<'a>> {
debug!("Building Mail object from Ref: {:?}", r); fle.fs_file()
r.fs_file()
.map_err_into(MEK::RefHandlingError) .map_err_into(MEK::RefHandlingError)
.and_then(|path| File::open(path).map_err_into(MEK::IOError)) .and_then(|path| File::open(path).map_err_into(MEK::IOError))
.and_then(|mut file| { .and_then(|mut file| {
@ -102,7 +103,7 @@ impl<'a> Mail<'a> {
.map_err_into(MEK::IOError) .map_err_into(MEK::IOError)
}) })
.map(Buffer::from) .map(Buffer::from)
.map(|buffer| Mail(r, buffer)) .map(|buffer| Mail(fle, buffer))
} }
pub fn get_field(&self, field: &str) -> Result<Option<String>> { pub fn get_field(&self, field: &str) -> Result<Option<String>> {

View file

@ -23,7 +23,10 @@ generate_error_module!(
StoreError => "Store Error", StoreError => "Store Error",
StoreIdError => "Store Id handling error", StoreIdError => "Store Id handling error",
ImportError => "Error importing", ImportError => "Error importing",
UTF8Error => "Encountered non-UTF8 characters while reading input" UTF8Error => "Encountered non-UTF8 characters while reading input",
HeaderFieldMissing => "Header field missing",
HeaderTypeError => "Header field type error",
UuidParserError => "Uuid parser error"
); );
); );

View file

@ -48,4 +48,5 @@ module_entry_path_mod!("todo");
pub mod error; pub mod error;
pub mod result; pub mod result;
pub mod task; pub mod task;
pub mod taskstore;

View file

@ -17,271 +17,31 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use std::collections::BTreeMap; use error::TodoErrorKind as TEK;
use std::ops::{Deref, DerefMut}; use error::MapErrInto;
use std::io::BufRead;
use std::result::Result as RResult;
use toml::Value;
use uuid::Uuid;
use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};
use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
use module_path::ModuleEntryPath;
use error::{TodoErrorKind as TEK, MapErrInto};
use result::Result; use result::Result;
/// Task struct containing a `FileLockEntry` use libimagstore::store::Entry;
#[derive(Debug)] use libimagerror::into::IntoError;
pub struct Task<'a>(FileLockEntry<'a>);
impl<'a> Task<'a> { use uuid::Uuid;
use toml::Value;
/// Concstructs a new `Task` with a `FileLockEntry` use toml_query::read::TomlValueReadExt;
pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
Task(fle)
}
pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
import_task(&line.as_str())
.map_err_into(TEK::ImportError)
.and_then(|t| {
let uuid = t.uuid().clone();
t.into_task(store).map(|t| (t, line, uuid))
})
}
/// 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>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
where R: BufRead
{
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
Task::get_from_string(store, line)
}
/// 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)
///
/// 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>> {
import_task(s.as_str())
.map_err_into(TEK::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)),
})
}
/// Get a task from an UUID.
///
/// 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>>> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|store_id| store.get(store_id))
.map(|o| o.map(Task::new))
.map_err_into(TEK::StoreError)
}
/// 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>> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
Task::retrieve_from_string(store, line)
}
/// 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>> {
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(TEK::ImportError)
.and_then(|t| t.into_task(store)),
})
}
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(TEK::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(TEK::ImportError),
}
}
Ok(())
}
pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|id| store.delete(id))
.map_err_into(TEK::StoreError)
}
pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
store.retrieve_for_module("todo/taskwarrior")
.map_err_into(TEK::StoreError)
}
pub fn all(store: &Store) -> Result<TaskIterator> {
Task::all_as_ids(store)
.map(|iter| TaskIterator::new(store, iter))
}
pub trait Task {
fn get_uuid(&self) -> Result<Uuid>;
} }
impl<'a> Deref for Task<'a> { impl Task for Entry {
type Target = FileLockEntry<'a>; fn get_uuid(&self) -> Result<Uuid> {
match self.get_header().read("todo.uuid") {
fn deref(&self) -> &FileLockEntry<'a> { Ok(Some(&Value::String(ref uuid))) => {
&self.0 Uuid::parse_str(uuid).map_err_into(TEK::UuidParserError)
} },
Ok(Some(_)) => Err(TEK::HeaderTypeError.into_error()),
} Ok(None) => Err(TEK::HeaderFieldMissing.into_error()),
Err(e) => Err(e).map_err_into(TEK::StoreError),
impl<'a> DerefMut for Task<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
pub trait IntoTask<'a> {
/// # 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"));
/// }
/// ```
fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
}
impl<'a> IntoTask<'a> for TTask {
fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
let uuid = self.uuid();
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.map_err_into(TEK::StoreIdError)
.and_then(|id| {
store.retrieve(id)
.map_err_into(TEK::StoreError)
.and_then(|mut fle| {
{
let hdr = fle.get_header_mut();
if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
try!(hdr
.set("todo", Value::Table(BTreeMap::new()))
.map_err_into(TEK::StoreError));
}
try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
.map_err_into(TEK::StoreError));
}
// If none of the errors above have returned the function, everything is fine
Ok(Task::new(fle))
})
})
}
}
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>> {
store.retrieve(id)
.map_err_into(TEK::StoreError)
.map(Task::new)
}
}
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))
}
} }

View file

@ -0,0 +1,205 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::collections::BTreeMap;
use std::io::BufRead;
use std::result::Result as RResult;
use toml::Value;
use uuid::Uuid;
use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};
use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator};
use module_path::ModuleEntryPath;
use error::{TodoErrorKind as TEK, MapErrInto};
use result::Result;
/// Task struct containing a `FileLockEntry`
pub trait TaskStore<'a> {
fn import_task_from_reader<R: BufRead>(&'a self, r: R) -> Result<(FileLockEntry<'a>, String, Uuid)>;
fn get_task_from_import<R: BufRead>(&'a self, r: R) -> Result<RResult<FileLockEntry<'a>, String>>;
fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>>;
fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>>;
fn retrieve_task_from_import<R: BufRead>(&'a self, r: R) -> Result<FileLockEntry<'a>>;
fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>>;
fn delete_tasks_by_imports<R: BufRead>(&self, r: R) -> Result<()>;
fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()>;
fn all_tasks(&self) -> Result<StoreIdIterator>;
fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>>;
}
impl<'a> TaskStore<'a> for Store {
fn import_task_from_reader<R: BufRead>(&'a self, mut r: R) -> Result<(FileLockEntry<'a>, String, Uuid)> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
import_task(&line.as_str())
.map_err_into(TEK::ImportError)
.and_then(|t| {
let uuid = t.uuid().clone();
self.new_from_twtask(t).map(|t| (t, line, uuid))
})
}
/// 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
///
fn get_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<RResult<FileLockEntry<'a>, String>> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
self.get_task_from_string(line)
}
/// 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)
///
/// For an explanation on the return values see `Task::get_from_import()`.
fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>> {
import_task(s.as_str())
.map_err_into(TEK::ImportError)
.map(|t| t.uuid().clone())
.and_then(|uuid| self.get_task_from_uuid(uuid))
.and_then(|o| match o {
None => Ok(Err(s)),
Some(t) => Ok(Ok(t)),
})
}
/// Get a task from an UUID.
///
/// If there is no task with this UUID, this returns `Ok(None)`.
fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|store_id| self.get(store_id))
.map_err_into(TEK::StoreError)
}
/// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
/// implicitely create the task if it does not exist.
fn retrieve_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<FileLockEntry<'a>> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
self.retrieve_task_from_string(line)
}
/// 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)
fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>> {
self.get_task_from_string(s)
.and_then(|opt| match opt {
Ok(task) => Ok(task),
Err(string) => import_task(string.as_str())
.map_err_into(TEK::ImportError)
.and_then(|t| self.new_from_twtask(t)),
})
}
fn delete_tasks_by_imports<R: BufRead>(&self, 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(TEK::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 self.delete_task_by_uuid(*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(TEK::ImportError),
}
}
Ok(())
}
fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|id| self.delete(id))
.map_err_into(TEK::StoreError)
}
fn all_tasks(&self) -> Result<StoreIdIterator> {
self.retrieve_for_module("todo/taskwarrior")
.map_err_into(TEK::StoreError)
}
fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>> {
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
let uuid = task.uuid();
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.map_err_into(TEK::StoreIdError)
.and_then(|id| {
self.retrieve(id)
.map_err_into(TEK::StoreError)
.and_then(|mut fle| {
{
let hdr = fle.get_header_mut();
if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
try!(hdr
.set("todo", Value::Table(BTreeMap::new()))
.map_err_into(TEK::StoreError));
}
try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
.map_err_into(TEK::StoreError));
}
// If none of the errors above have returned the function, everything is fine
Ok(fle)
})
})
}
}

View file

@ -35,11 +35,11 @@ use std::collections::BTreeMap;
use std::fmt::Debug; use std::fmt::Debug;
use libimagstore::store::Entry; use libimagstore::store::Entry;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store; use libimagstore::store::Store;
use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId; use libimagstore::storeid::IntoStoreId;
use libimagutil::debug_result::*; use libimagutil::debug_result::*;
use libimagerror::into::IntoError;
use toml_query::read::TomlValueReadExt; use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt; use toml_query::set::TomlValueSetExt;
@ -58,37 +58,32 @@ use url::Url;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use crypto::digest::Digest; use crypto::digest::Digest;
/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally. pub trait Link {
pub struct Link<'a> {
link: FileLockEntry<'a> fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>>;
fn get_url(&self) -> Result<Option<Url>>;
} }
impl<'a> Link<'a> { impl Link for Entry {
pub fn new(fle: FileLockEntry<'a>) -> Link<'a> { fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>> {
Link { link: fle } self.get_header()
}
/// Get a link Url object from a `FileLockEntry`, ignore errors.
fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
file.get_header()
.read("imag.content.url") .read("imag.content.url")
.ok() .map_err_into(LEK::EntryHeaderReadError)
.and_then(|opt| match opt { .and_then(|opt| match opt {
Some(&Value::String(ref s)) => { Some(&Value::String(ref s)) => {
debug!("Found url, parsing: {:?}", s); debug!("Found url, parsing: {:?}", s);
Url::parse(&s[..]).ok() Url::parse(&s[..]).map_err_into(LEK::InvalidUri).map(Some)
}, },
_ => None Some(_) => Err(LEK::LinkParserFieldTypeError.into_error()),
None => Ok(None),
}) })
} }
pub fn get_url(&self) -> Result<Option<Url>> { fn get_url(&self) -> Result<Option<Url>> {
let opt = self.link match self.get_header().read("imag.content.url") {
.get_header()
.read("imag.content.url");
match opt {
Ok(Some(&Value::String(ref s))) => { Ok(Some(&Value::String(ref s))) => {
Url::parse(&s[..]) Url::parse(&s[..])
.map(Some) .map(Some)
@ -261,23 +256,32 @@ pub mod iter {
type Item = Result<Url>; type Item = Result<Url>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
use super::get_external_link_from_file; use external::Link;
self.0 loop {
.next() let next = self.0
.map(|id| { .next()
debug!("Retrieving entry for id: '{:?}'", id); .map(|id| {
self.1 debug!("Retrieving entry for id: '{:?}'", id);
.retrieve(id.clone()) self.1
.map_err_into(LEK::StoreReadError) .retrieve(id.clone())
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id)) .map_err_into(LEK::StoreReadError)
.and_then(|f| { .map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
debug!("Store::retrieve({:?}) succeeded", id); .and_then(|f| {
debug!("getting external link from file now"); debug!("Store::retrieve({:?}) succeeded", id);
get_external_link_from_file(&f) debug!("getting external link from file now");
.map_dbg_err(|e| format!("URL -> Err = {:?}", e)) f.get_link_uri_from_filelockentry()
}) .map_dbg_err(|e| format!("URL -> Err = {:?}", e))
}) })
});
match next {
Some(Ok(Some(link))) => return Some(Ok(link)),
Some(Ok(None)) => continue,
Some(Err(e)) => return Some(Err(e)),
None => return None
}
}
} }
} }
@ -291,11 +295,6 @@ pub fn is_external_link_storeid<A: AsRef<StoreId> + Debug>(id: A) -> bool {
id.as_ref().local().starts_with("links/external") id.as_ref().local().starts_with("links/external")
} }
fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
Link::get_link_uri_from_filelockentry(entry) // TODO: Do not hide error by using this function
.ok_or(LE::new(LEK::StoreReadError, None))
}
/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external /// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
/// link in an entry, but internal links to other entries which serve as external links, as one /// link in an entry, but internal links to other entries which serve as external links, as one
/// entry in the store can only have one external link. /// entry in the store can only have one external link.

View file

@ -18,8 +18,8 @@ itertools = "0.5"
log = "0.3" log = "0.3"
rust-crypto = "0.2" rust-crypto = "0.2"
toml = "^0.4" toml = "^0.4"
walkdir = "1.0.*"
toml-query = "0.3.0" toml-query = "0.3.0"
walkdir = "1.0.*"
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" } libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" } libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }

View file

@ -24,8 +24,6 @@ use std::result::Result as RResult;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use crypto::digest::Digest; use crypto::digest::Digest;
use libimagerror::into::IntoError;
use hasher::Hasher; use hasher::Hasher;
use result::Result; use result::Result;
use error::RefErrorKind as REK; use error::RefErrorKind as REK;
@ -54,16 +52,13 @@ impl Hasher for NBytesHasher {
} }
fn create_hash<R: Read>(&mut self, _: &PathBuf, contents: &mut R) -> Result<String> { fn create_hash<R: Read>(&mut self, _: &PathBuf, contents: &mut R) -> Result<String> {
let s = contents let s = try!(contents
.bytes() .bytes()
.take(self.n) .take(self.n)
.collect::<RResult<Vec<u8>, _>>() .collect::<RResult<Vec<u8>, _>>()
.map_err_into(REK::IOError) .map_err_into(REK::IOError)
.and_then(|v| String::from_utf8(v).map_err_into(REK::IOError)) .and_then(|v| String::from_utf8(v).map_err_into(REK::UTF8Error)));
.map_err(Box::new) self.hasher.input_str(&s[..]);
.map_err(|e| REK::UTF8Error.into_error_with_cause(e))
.map_err_into(REK::IOError);
self.hasher.input_str(&try!(s)[..]);
Ok(self.hasher.result_str()) Ok(self.hasher.result_str())
} }

View file

@ -52,4 +52,6 @@ pub mod hasher;
pub mod hashers; pub mod hashers;
pub mod lister; pub mod lister;
pub mod reference; pub mod reference;
pub mod refstore;
pub mod result; pub mod result;
mod util;

View file

@ -20,13 +20,14 @@
use std::default::Default; use std::default::Default;
use std::io::stdout; use std::io::stdout;
use std::io::Write; use std::io::Write;
use std::ops::Deref;
use libimagentrylist::lister::Lister; use libimagentrylist::lister::Lister;
use libimagentrylist::result::Result; use libimagentrylist::result::Result;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagerror::into::IntoError;
use libimagentrylist::error::ListErrorKind as LEK; use libimagentrylist::error::ListErrorKind as LEK;
use libimagentrylist::error as lerror;
use reference::Ref; use reference::Ref;
use error::MapErrInto; use error::MapErrInto;
@ -88,15 +89,45 @@ impl Lister for RefLister {
debug!("fold({:?}, {:?})", accu, entry); debug!("fold({:?}, {:?})", accu, entry);
let r = accu.and_then(|_| { let r = accu.and_then(|_| {
debug!("Listing Entry: {:?}", entry); debug!("Listing Entry: {:?}", entry);
lister_fn(entry, {
self.check_dead, let is_dead = if self.check_dead {
self.check_changed, if try!(entry.fs_link_exists()) {
self.check_changed_content, "dead"
self.check_changed_permiss) } else {
"alive"
}
} else {
"not checked"
};
let is_changed = if self.check_changed {
if check_changed(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_content = if self.check_changed_content {
if check_changed_content(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_permiss = if self.check_changed_permiss {
if check_changed_permiss(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
Ok(format!("{} | {} | {} | {} | {} | {}",
is_dead,
is_changed,
is_changed_content,
is_changed_permiss,
entry.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
entry.get_location()))
}
.and_then(|s| { .and_then(|s| {
write!(stdout(), "{}\n", s) lerror::MapErrInto::map_err_into(write!(stdout(), "{}\n", s), LEK::FormatError)
.map_err(Box::new)
.map_err(|e| LEK::FormatError.into_error_with_cause(e))
}) })
.map_err_into(REK::RefToDisplayError) .map_err_into(REK::RefToDisplayError)
}) })
@ -104,73 +135,16 @@ impl Lister for RefLister {
(r, i + 1) (r, i + 1)
}); });
debug!("Iterated over {} entries", n); debug!("Iterated over {} entries", n);
r.map_err(|e| LEK::FormatError.into_error_with_cause(Box::new(e))) lerror::MapErrInto::map_err_into(r, LEK::FormatError)
} }
} }
fn lister_fn(fle: FileLockEntry, fn check_changed<R: Ref>(r: &R) -> bool {
do_check_dead: bool,
do_check_changed: bool,
do_check_changed_content: bool,
do_check_changed_permiss: bool) -> Result<String>
{
Ref::from_filelockentry(fle)
.map(|r| {
let is_dead = if do_check_dead {
if check_dead(&r) { "dead" } else { "alive" }
} else {
"not checked"
};
let is_changed = if do_check_changed {
if check_changed(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_content = if do_check_changed_content {
if check_changed_content(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_permiss = if do_check_changed_permiss {
if check_changed_permiss(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
format!("{} | {} | {} | {} | {} | {}",
is_dead,
is_changed,
is_changed_content,
is_changed_permiss,
r.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
r.get_location())
})
.map_err(|e| LEK::FormatError.into_error_with_cause(Box::new(e)))
}
fn check_dead(r: &Ref) -> bool {
match r.fs_link_exists() {
Ok(b) => b,
Err(e) => {
warn!("Could not check whether the ref {} exists on the FS:", r);
trace_error(&e);
// We continue here and tell the callee that this reference is dead, what is kind of
// true actually, as we might not have access to it right now
true
},
}
}
fn check_changed(r: &Ref) -> bool {
check_changed_content(r) && check_changed_permiss(r) check_changed_content(r) && check_changed_permiss(r)
} }
fn check_changed_content(r: &Ref) -> bool { fn check_changed_content<R: Ref>(r: &R) -> bool {
let eq = r.get_current_hash() let eq = r.get_current_hash()
.and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored))) .and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored)))
.map(|(hash, stored)| hash == stored); .map(|(hash, stored)| hash == stored);
@ -178,7 +152,7 @@ fn check_changed_content(r: &Ref) -> bool {
match eq { match eq {
Ok(eq) => eq, Ok(eq) => eq,
Err(e) => { Err(e) => {
warn!("Could not check whether the ref {} changed on the FS:", r); warn!("Could not check whether the ref changed on the FS");
trace_error(&e); trace_error(&e);
// We continue here and tell the callee that this reference is unchanged // We continue here and tell the callee that this reference is unchanged
@ -187,7 +161,7 @@ fn check_changed_content(r: &Ref) -> bool {
} }
} }
fn check_changed_permiss(_: &Ref) -> bool { fn check_changed_permiss<R: Ref>(_: &R) -> bool {
warn!("Permission changes tracking not supported yet."); warn!("Permission changes tracking not supported yet.");
false false
} }

View file

@ -21,238 +21,104 @@
//! files outside of the imag store. //! files outside of the imag store.
use std::path::PathBuf; use std::path::PathBuf;
use std::ops::Deref;
use std::ops::DerefMut;
use std::collections::BTreeMap;
use std::fs::File; use std::fs::File;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::fs::Permissions; use std::fs::Permissions;
use std::result::Result as RResult;
use libimagstore::store::FileLockEntry; use libimagstore::store::Entry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagerror::into::IntoError; use libimagerror::into::IntoError;
use toml::Value; use toml::Value;
use toml_query::read::TomlValueReadExt; use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt; use toml_query::set::TomlValueSetExt;
use toml_query::insert::TomlValueInsertExt;
use error::RefErrorKind as REK; use error::RefErrorKind as REK;
use error::MapErrInto; use error::MapErrInto;
use flags::RefFlags;
use result::Result; use result::Result;
use hasher::*; use hasher::*;
use module_path::ModuleEntryPath;
#[derive(Debug)] pub trait Ref {
pub struct Ref<'a>(FileLockEntry<'a>);
impl<'a> Ref<'a> {
/// Try to build a Ref object based on an existing FileLockEntry object
pub fn from_filelockentry(fle: FileLockEntry<'a>) -> Result<Ref<'a>> {
Ref::read_reference(&fle).map(|_| Ref(fle))
}
/// Try to get `si` as Ref object from the store
pub fn get(store: &'a Store, si: StoreId) -> Result<Ref<'a>> {
match store.get(si) {
Err(e) => return Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
Ok(None) => return Err(REK::RefNotInStore.into_error()),
Ok(Some(fle)) => Ref::from_filelockentry(fle),
}
}
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
pub fn get_by_hash(store: &'a Store, hash: String) -> Result<Option<Ref<'a>>> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| store.get(id))
.map(|opt_fle| opt_fle.map(|fle| Ref(fle)))
.map_err(Box::new)
.map_err(|e| REK::StoreReadError.into_error_with_cause(e))
}
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
pub fn delete_by_hash(store: &'a Store, hash: String) -> Result<()> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| store.delete(id))
.map_err(Box::new)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
}
fn read_reference(fle: &FileLockEntry<'a>) -> Result<PathBuf> {
match fle.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
Err(e) => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
}
}
pub fn create_with_hasher<H: Hasher>(store: &'a Store, pb: PathBuf, flags: RefFlags, mut h: H)
-> Result<Ref<'a>>
{
if !pb.exists() {
return Err(REK::RefTargetDoesNotExist.into_error());
}
if flags.get_content_hashing() && pb.is_dir() {
return Err(REK::RefTargetCannotBeHashed.into_error());
}
let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
try!(File::open(pb.clone())
.map_err(Box::new)
.map_err(|e| REK::RefTargetFileCannotBeOpened.into_error_with_cause(e))
// If we were able to open this file,
// we hash the contents of the file and return (file, hash)
.and_then(|mut file| {
let opt_contenthash = if flags.get_content_hashing() {
Some(try!(h.create_hash(&pb, &mut file)))
} else {
None
};
Ok((file, opt_contenthash))
})
// and then we get the permissions if we have to
// and return (file, content hash, permissions)
.and_then(|(file, opt_contenthash)| {
let opt_permissions = if flags.get_permission_tracking() {
Some(try!(file
.metadata()
.map(|md| md.permissions())
.map_err(Box::new)
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
))
} else {
None
};
Ok((opt_contenthash, opt_permissions))
})
// and then we try to canonicalize the PathBuf, because we want to store a
// canonicalized path
// and return (file, content hash, permissions, canonicalized path)
.and_then(|(opt_contenthash, opt_permissions)| {
pb.canonicalize()
.map(|can| (opt_contenthash, opt_permissions, can))
// if PathBuf::canonicalize() failed, build an error from the return value
.map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(Box::new(e)))
})
// and then we hash the canonicalized path
// and return (file, content hash, permissions, canonicalized path, path hash)
.and_then(|(opt_contenthash, opt_permissions, can)| {
let path_hash = try!(Ref::hash_path(&can)
.map_err(Box::new)
.map_err(|e| REK::PathHashingError.into_error_with_cause(e))
);
Ok((opt_contenthash, opt_permissions, can, path_hash))
})
// and then we convert the PathBuf of the canonicalized path to a String to be able
// to save it in the Ref FileLockEntry obj
// and return
// (file, content hash, permissions, canonicalized path as String, path hash)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
match can.to_str().map(String::from) {
// UTF convert error in PathBuf::to_str(),
None => Err(REK::PathUTF8Error.into_error()),
Some(can) => Ok((opt_conhash, opt_perm, can, path_hash))
}
})
// and then we create the FileLockEntry in the Store
// and return (filelockentry, content hash, permissions, canonicalized path)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
let fle = try!(store
.create(ModuleEntryPath::new(path_hash))
.map_err(Box::new)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
);
Ok((fle, opt_conhash, opt_perm, can))
})
)
};
for tpl in [
Some((String::from("ref"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.permissions"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.path"), Value::String(canonical_path))),
Some((String::from("ref.content_hash"), Value::Table(BTreeMap::new()))),
content_hash.map(|hash| {
(format!("ref.content_hash.{}", h.hash_name()), Value::String(hash))
}),
permissions.map(|p| {
(String::from("ref.permissions.ro"), Value::Boolean(p.readonly()))
}),
].into_iter()
{
match tpl {
&Some((ref s, ref v)) => {
match fle.get_header_mut().insert(s, v.clone()) {
Ok(None) => {
debug!("Header insert worked");
}
Ok(Some(val)) => {
debug!("Overwrote: {}, which was: {:?}", s, val);
},
Err(e) => {
let e = Box::new(e);
let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
return Err(e);
},
}
}
&None => {
debug!("Not going to insert.");
}
}
}
Ok(Ref(fle))
}
/// Create a Ref object which refers to `pb`
pub fn create(store: &'a Store, pb: PathBuf, flags: RefFlags) -> Result<Ref<'a>> {
Ref::create_with_hasher(store, pb, flags, DefaultHasher::new())
}
/// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
/// algorithm on it
fn hash_path(pb: &PathBuf) -> Result<String> {
use crypto::sha1::Sha1;
use crypto::digest::Digest;
match pb.to_str() {
Some(s) => {
let mut hasher = Sha1::new();
hasher.input_str(s);
Ok(hasher.result_str())
},
None => return Err(REK::PathUTF8Error.into_error()),
}
}
/// Get the hash from the path of the ref /// Get the hash from the path of the ref
pub fn get_path_hash(&self) -> Result<String> { fn get_path_hash(&self) -> Result<String>;
self.0
.get_location() /// Get the hash of the link target which is stored in the ref object
fn get_stored_hash(&self) -> Result<String>;
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
/// custom Hasher instance.
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String>;
/// Get the hash of the link target by reading the link target and hashing the contents
fn get_current_hash(&self) -> Result<String>;
/// Get the hash of the link target by reading the link target and hashing the contents with the
/// custom hasher
fn get_current_hash_with_hasher<H: Hasher>(&self, h: H) -> Result<String>;
/// check whether the pointer the Ref represents still points to a file which exists
fn fs_link_exists(&self) -> Result<bool>;
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
fn is_ref_to_file(&self) -> Result<bool>;
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
fn is_ref_to_dir(&self) -> Result<bool>;
/// Alias for `!Ref::fs_link_exists()`
fn is_dangling(&self) -> Result<bool>;
/// check whether the pointer the Ref represents is valid
/// This includes:
/// - Hashsum of the file is still the same as stored in the Ref
/// - file permissions are still valid
fn fs_link_valid(&self) -> Result<bool>;
/// Check whether the file permissions of the referenced file are equal to the stored
/// permissions
fn fs_link_valid_permissions(&self) -> Result<bool>;
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
fn fs_link_valid_hash(&self) -> Result<bool>;
/// Update the Ref by re-checking the file from FS
/// This errors if the file is not present or cannot be read()
fn update_ref(&mut self) -> Result<()>;
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
/// This errors if the file is not present or cannot be read()
fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()>;
/// Get the path of the file which is reffered to by this Ref
fn fs_file(&self) -> Result<PathBuf>;
/// Re-find a referenced file
///
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
/// for a file which matches the hash of the Ref.
///
/// If `search_roots` is `None`, it starts at the filesystem root `/`.
///
/// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
///
/// # Warning
///
/// This option causes heavy I/O as it recursively searches the Filesystem.
fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf>;
/// See documentation of `Ref::refind()`
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, h: H)
-> Result<PathBuf>;
/// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions>;
}
impl Ref for Entry {
/// Get the hash from the path of the ref
fn get_path_hash(&self) -> Result<String> {
self.get_location()
.clone() .clone()
.into_pathbuf() .into_pathbuf()
.map_err_into(REK::StoreIdError) .map_err_into(REK::StoreIdError)
@ -266,14 +132,14 @@ impl<'a> Ref<'a> {
} }
/// Get the hash of the link target which is stored in the ref object /// Get the hash of the link target which is stored in the ref object
pub fn get_stored_hash(&self) -> Result<String> { fn get_stored_hash(&self) -> Result<String> {
self.get_stored_hash_with_hasher(&DefaultHasher::new()) self.get_stored_hash_with_hasher(&DefaultHasher::new())
} }
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a /// Get the hahs of the link target which is stored in the ref object, which is hashed with a
/// custom Hasher instance. /// custom Hasher instance.
pub fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> { fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
match self.0.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) { match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
// content hash stored... // content hash stored...
Ok(Some(&Value::String(ref s))) => Ok(s.clone()), Ok(Some(&Value::String(ref s))) => Ok(s.clone()),
@ -289,57 +155,35 @@ impl<'a> Ref<'a> {
} }
/// Get the hash of the link target by reading the link target and hashing the contents /// Get the hash of the link target by reading the link target and hashing the contents
pub fn get_current_hash(&self) -> Result<String> { fn get_current_hash(&self) -> Result<String> {
self.get_current_hash_with_hasher(DefaultHasher::new()) self.get_current_hash_with_hasher(DefaultHasher::new())
} }
/// Get the hash of the link target by reading the link target and hashing the contents with the /// Get the hash of the link target by reading the link target and hashing the contents with the
/// custom hasher /// custom hasher
pub fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> { fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
self.fs_file() self.fs_file()
.and_then(|pb| { .and_then(|pb| File::open(pb.clone()).map(|f| (pb, f)).map_err_into(REK::IOError))
File::open(pb.clone())
.map(|f| (pb, f))
.map_err(Box::new)
.map_err(|e| REK::IOError.into_error_with_cause(e))
})
.and_then(|(path, mut file)| h.create_hash(&path, &mut file)) .and_then(|(path, mut file)| h.create_hash(&path, &mut file))
} }
/// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions> {
self.fs_file()
.and_then(|pb| {
File::open(pb)
.map_err(Box::new)
.map_err(|e| REK::HeaderFieldReadError.into_error_with_cause(e))
})
.and_then(|file| {
file
.metadata()
.map(|md| md.permissions())
.map_err(Box::new)
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
})
}
/// check whether the pointer the Ref represents still points to a file which exists /// check whether the pointer the Ref represents still points to a file which exists
pub fn fs_link_exists(&self) -> Result<bool> { fn fs_link_exists(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.exists()) self.fs_file().map(|pathbuf| pathbuf.exists())
} }
/// Alias for `r.fs_link_exists() && r.deref().is_file()` /// Alias for `r.fs_link_exists() && r.deref().is_file()`
pub fn is_ref_to_file(&self) -> Result<bool> { fn is_ref_to_file(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.is_file()) self.fs_file().map(|pathbuf| pathbuf.is_file())
} }
/// Alias for `r.fs_link_exists() && r.deref().is_dir()` /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
pub fn is_ref_to_dir(&self) -> Result<bool> { fn is_ref_to_dir(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.is_dir()) self.fs_file().map(|pathbuf| pathbuf.is_dir())
} }
/// Alias for `!Ref::fs_link_exists()` /// Alias for `!Ref::fs_link_exists()`
pub fn is_dangling(&self) -> Result<bool> { fn is_dangling(&self) -> Result<bool> {
self.fs_link_exists().map(|b| !b) self.fs_link_exists().map(|b| !b)
} }
@ -347,7 +191,7 @@ impl<'a> Ref<'a> {
/// This includes: /// This includes:
/// - Hashsum of the file is still the same as stored in the Ref /// - Hashsum of the file is still the same as stored in the Ref
/// - file permissions are still valid /// - file permissions are still valid
pub fn fs_link_valid(&self) -> Result<bool> { fn fs_link_valid(&self) -> Result<bool> {
match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) { match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
(Ok(true) , Ok(true)) => Ok(true), (Ok(true) , Ok(true)) => Ok(true),
(Ok(_) , Ok(_)) => Ok(false), (Ok(_) , Ok(_)) => Ok(false),
@ -358,12 +202,11 @@ impl<'a> Ref<'a> {
/// Check whether the file permissions of the referenced file are equal to the stored /// Check whether the file permissions of the referenced file are equal to the stored
/// permissions /// permissions
pub fn fs_link_valid_permissions(&self) -> Result<bool> { fn fs_link_valid_permissions(&self) -> Result<bool> {
self.0 self
.get_header() .get_header()
.read("ref.permissions.ro") .read("ref.permissions.ro")
.map_err(Box::new) .map_err_into(REK::HeaderFieldReadError)
.map_err(|e| REK::HeaderFieldReadError.into_error_with_cause(e))
.and_then(|ro| { .and_then(|ro| {
match ro { match ro {
Some(&Value::Boolean(b)) => Ok(b), Some(&Value::Boolean(b)) => Ok(b),
@ -372,12 +215,11 @@ impl<'a> Ref<'a> {
} }
}) })
.and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly())) .and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly()))
.map_err(Box::new) .map_err_into(REK::RefTargetCannotReadPermissions)
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
} }
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
pub fn fs_link_valid_hash(&self) -> Result<bool> { fn fs_link_valid_hash(&self) -> Result<bool> {
let stored_hash = try!(self.get_stored_hash()); let stored_hash = try!(self.get_stored_hash());
let current_hash = try!(self.get_current_hash()); let current_hash = try!(self.get_current_hash());
Ok(stored_hash == current_hash) Ok(stored_hash == current_hash)
@ -385,36 +227,34 @@ impl<'a> Ref<'a> {
/// Update the Ref by re-checking the file from FS /// Update the Ref by re-checking the file from FS
/// This errors if the file is not present or cannot be read() /// This errors if the file is not present or cannot be read()
pub fn update_ref(&mut self) -> Result<()> { fn update_ref(&mut self) -> Result<()> {
self.update_ref_with_hasher(&DefaultHasher::new()) self.update_ref_with_hasher(&DefaultHasher::new())
} }
/// Update the Ref by re-checking the file from FS using the passed Hasher instance /// Update the Ref by re-checking the file from FS using the passed Hasher instance
/// This errors if the file is not present or cannot be read() /// This errors if the file is not present or cannot be read()
pub fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> { fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
let current_hash = try!(self.get_current_hash()); // uses the default hasher let current_hash = try!(self.get_current_hash()); // uses the default hasher
let current_perm = try!(self.get_current_permissions()); let current_perm = try!(self.get_current_permissions());
try!(self.0 try!(self
.get_header_mut() .get_header_mut()
.set("ref.permissions.ro", Value::Boolean(current_perm.readonly())) .set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
.map_err(Box::new) .map_err_into(REK::StoreWriteError)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
); );
try!(self.0 try!(self
.get_header_mut() .get_header_mut()
.set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash)) .set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
.map_err(Box::new) .map_err_into(REK::StoreWriteError)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
); );
Ok(()) Ok(())
} }
/// Get the path of the file which is reffered to by this Ref /// Get the path of the file which is reffered to by this Ref
pub fn fs_file(&self) -> Result<PathBuf> { fn fs_file(&self) -> Result<PathBuf> {
match self.0.get_header().read("ref.path") { match self.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)), Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()), Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()), Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
@ -422,56 +262,6 @@ impl<'a> Ref<'a> {
} }
} }
/// Check whether there is a reference to the file at `pb`
pub fn exists(store: &Store, pb: PathBuf) -> Result<bool> {
pb.canonicalize()
.map_err(Box::new)
.map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(e))
.and_then(|can| {
Ref::hash_path(&can)
.map_err(Box::new)
.map_err(|e| REK::PathHashingError.into_error_with_cause(e))
})
.and_then(|hash| {
store.retrieve_for_module("ref").map(|iter| (hash, iter))
.map_err(Box::new)
.map_err(|e| REK::StoreReadError.into_error_with_cause(e))
})
.and_then(|(hash, possible_refs)| {
// This is kind of a manual Iterator::filter() call what we do here, but with the
// actual ::filter method we cannot return the error in a nice way, so we do it
// manually here. If you can come up with a better version of this, feel free to
// take this note as a todo.
for r in possible_refs {
let contains_hash = try!(r.to_str()
.map_err_into(REK::TypeConversionError)
.map(|s| s.contains(&hash[..])));
if !contains_hash {
continue;
}
match store.get(r) {
Ok(Some(fle)) => {
if Ref::read_reference(&fle).map(|path| path == pb).unwrap_or(false) {
return Ok(true)
}
},
Ok(None) => { // Something weird just happened
return Err(REK::StoreReadError.into_error());
},
Err(e) => {
return Err(REK::StoreReadError.into_error_with_cause(Box::new(e)));
},
}
}
Ok(false)
})
}
/// Re-find a referenced file /// Re-find a referenced file
/// ///
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
@ -484,11 +274,12 @@ impl<'a> Ref<'a> {
/// # Warning /// # Warning
/// ///
/// This option causes heavy I/O as it recursively searches the Filesystem. /// This option causes heavy I/O as it recursively searches the Filesystem.
pub fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> { fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
self.refind_with_hasher(search_roots, DefaultHasher::new()) self.refind_with_hasher(search_roots, DefaultHasher::new())
} }
pub fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H) /// See documentation of `Ref::refind()`
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
-> Result<PathBuf> -> Result<PathBuf>
{ {
use itertools::Itertools; use itertools::Itertools;
@ -505,13 +296,11 @@ impl<'a> Ref<'a> {
.into_iter() .into_iter()
.map(|entry| { .map(|entry| {
entry entry
.map_err(Box::new) .map_err_into(REK::IOError)
.map_err(|e| REK::IOError.into_error_with_cause(e))
.and_then(|entry| { .and_then(|entry| {
let pb = PathBuf::from(entry.path()); let pb = PathBuf::from(entry.path());
File::open(entry.path()) File::open(entry.path())
.map_err(Box::new) .map_err_into(REK::IOError)
.map_err(|e| REK::IOError.into_error_with_cause(e))
.map(|f| (pb, f)) .map(|f| (pb, f))
}) })
.and_then(|(p, mut f)| h.create_hash(&p, &mut f).map(|h| (p, h))) .and_then(|(p, mut f)| h.create_hash(&p, &mut f).map(|h| (p, h)))
@ -522,8 +311,7 @@ impl<'a> Ref<'a> {
None None
} }
}) })
.map_err(Box::new) .map_err_into(REK::IOError)
.map_err(|e| REK::IOError.into_error_with_cause(e))
}) })
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.filter_map(|e| e) .filter_map(|e| e)
@ -535,43 +323,16 @@ impl<'a> Ref<'a> {
}) })
} }
} /// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions> {
impl<'a> Deref for Ref<'a> { self.fs_file()
type Target = FileLockEntry<'a>; .and_then(|pb| File::open(pb).map_err_into(REK::HeaderFieldReadError))
.and_then(|file| {
fn deref(&self) -> &FileLockEntry<'a> { file
&self.0 .metadata()
} .map(|md| md.permissions())
.map_err_into(REK::RefTargetCannotReadPermissions)
} })
impl<'a> DerefMut for Ref<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
impl<'a> Display for Ref<'a> {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
let path = self.fs_file()
.map(|pb| String::from(pb.to_str().unwrap_or("<UTF8-Error>")))
.unwrap_or(String::from("Could not read Path from reference object"));
let hash = self.get_stored_hash().unwrap_or(String::from("<could not read hash>"));
write!(fmt, "Ref({} -> {})", hash, path)
}
}
impl<'a> Into<FileLockEntry<'a>> for Ref<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
} }
} }

View file

@ -0,0 +1,269 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::path::PathBuf;
use std::collections::BTreeMap;
use std::fs::File;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagerror::into::IntoError;
use toml::Value;
use error::RefErrorKind as REK;
use error::MapErrInto;
use flags::RefFlags;
use result::Result;
use hasher::*;
use module_path::ModuleEntryPath;
use util::*;
pub trait RefStore {
/// Check whether there is a reference to the file at `pb`
fn exists(&self, pb: PathBuf) -> Result<bool>;
/// Try to get `si` as Ref object from the store
fn get<'a>(&'a self, si: StoreId) -> Result<FileLockEntry<'a>>;
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
fn get_by_hash<'a>(&'a self, hash: String) -> Result<Option<FileLockEntry<'a>>>;
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
fn delete_by_hash(&self, hash: String) -> Result<()>;
/// Create a Ref object which refers to `pb`
fn create<'a>(&'a self, pb: PathBuf, flags: RefFlags) -> Result<FileLockEntry<'a>>;
fn create_with_hasher<'a, H: Hasher>(&'a self, pb: PathBuf, flags: RefFlags, h: H)
-> Result<FileLockEntry<'a>>;
}
impl RefStore for Store {
/// Check whether there is a reference to the file at `pb`
fn exists(&self, pb: PathBuf) -> Result<bool> {
pb.canonicalize()
.map_err_into(REK::PathCanonicalizationError)
.and_then(|c| hash_path(&c))
.map_err_into(REK::PathHashingError)
.and_then(|hash| {
self.retrieve_for_module("ref")
.map(|iter| (hash, iter))
.map_err_into(REK::StoreReadError)
})
.and_then(|(hash, possible_refs)| {
// This is kind of a manual Iterator::filter() call what we do here, but with the
// actual ::filter method we cannot return the error in a nice way, so we do it
// manually here. If you can come up with a better version of this, feel free to
// take this note as a todo.
for r in possible_refs {
let contains_hash = try!(r.to_str()
.map_err_into(REK::TypeConversionError)
.map(|s| s.contains(&hash[..]))
);
if !contains_hash {
continue;
}
match self.get(r) {
Ok(Some(fle)) => {
if read_reference(&fle).map(|path| path == pb).unwrap_or(false) {
return Ok(true)
}
},
Ok(None) => return Err(REK::StoreReadError.into_error()),
Err(e) => return Err(e).map_err_into(REK::StoreReadError)
}
}
Ok(false)
})
}
/// Try to get `si` as Ref object from the store
fn get<'a>(&'a self, si: StoreId) -> Result<FileLockEntry<'a>> {
match self.get(si) {
Err(e) => return Err(e).map_err_into(REK::StoreReadError),
Ok(None) => return Err(REK::RefNotInStore.into_error()),
Ok(Some(fle)) => Ok(fle),
}
}
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
fn get_by_hash<'a>(&'a self, hash: String) -> Result<Option<FileLockEntry<'a>>> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| self.get(id))
.map_err_into(REK::StoreReadError)
}
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
fn delete_by_hash(&self, hash: String) -> Result<()> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| self.delete(id))
.map_err_into(REK::StoreWriteError)
}
/// Create a Ref object which refers to `pb`
fn create<'a>(&'a self, pb: PathBuf, flags: RefFlags) -> Result<FileLockEntry<'a>> {
self.create_with_hasher(pb, flags, DefaultHasher::new())
}
fn create_with_hasher<'a, H: Hasher>(&'a self, pb: PathBuf, flags: RefFlags, mut h: H)
-> Result<FileLockEntry<'a>>
{
use toml_query::insert::TomlValueInsertExt;
if !pb.exists() {
return Err(REK::RefTargetDoesNotExist.into_error());
}
if flags.get_content_hashing() && pb.is_dir() {
return Err(REK::RefTargetCannotBeHashed.into_error());
}
let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
try!(File::open(pb.clone())
.map_err_into(REK::RefTargetFileCannotBeOpened)
// If we were able to open this file,
// we hash the contents of the file and return (file, hash)
.and_then(|mut file| {
let opt_contenthash = if flags.get_content_hashing() {
Some(try!(h.create_hash(&pb, &mut file)))
} else {
None
};
Ok((file, opt_contenthash))
})
// and then we get the permissions if we have to
// and return (file, content hash, permissions)
.and_then(|(file, opt_contenthash)| {
let opt_permissions = if flags.get_permission_tracking() {
Some(try!(file
.metadata()
.map(|md| md.permissions())
.map_err_into(REK::RefTargetCannotReadPermissions)
))
} else {
None
};
Ok((opt_contenthash, opt_permissions))
})
// and then we try to canonicalize the PathBuf, because we want to store a
// canonicalized path
// and return (file, content hash, permissions, canonicalized path)
.and_then(|(opt_contenthash, opt_permissions)| {
pb.canonicalize()
.map(|can| (opt_contenthash, opt_permissions, can))
// if PathBuf::canonicalize() failed, build an error from the return value
.map_err_into(REK::PathCanonicalizationError)
})
// and then we hash the canonicalized path
// and return (file, content hash, permissions, canonicalized path, path hash)
.and_then(|(opt_contenthash, opt_permissions, can)| {
let path_hash = try!(hash_path(&can).map_err_into(REK::PathHashingError));
Ok((opt_contenthash, opt_permissions, can, path_hash))
})
// and then we convert the PathBuf of the canonicalized path to a String to be able
// to save it in the Ref FileLockEntry obj
// and return
// (file, content hash, permissions, canonicalized path as String, path hash)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
match can.to_str().map(String::from) {
// UTF convert error in PathBuf::to_str(),
None => Err(REK::PathUTF8Error.into_error()),
Some(can) => Ok((opt_conhash, opt_perm, can, path_hash))
}
})
// and then we create the FileLockEntry in the Store
// and return (filelockentry, content hash, permissions, canonicalized path)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
let fle = try!(self
.create(ModuleEntryPath::new(path_hash))
.map_err_into(REK::StoreWriteError)
);
Ok((fle, opt_conhash, opt_perm, can))
})
)
};
for tpl in [
Some((String::from("ref"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.permissions"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.path"), Value::String(canonical_path))),
Some((String::from("ref.content_hash"), Value::Table(BTreeMap::new()))),
content_hash.map(|hash| {
(format!("ref.content_hash.{}", h.hash_name()), Value::String(hash))
}),
permissions.map(|p| {
(String::from("ref.permissions.ro"), Value::Boolean(p.readonly()))
}),
].into_iter()
{
match tpl {
&Some((ref s, ref v)) => {
match fle.get_header_mut().insert(s, v.clone()) {
Ok(Some(_)) => {
let e = REK::HeaderFieldAlreadyExistsError.into_error();
return Err(e).map_err_into(REK::HeaderFieldWriteError);
},
Ok(None) => {
// Okay, we just inserted a new header value...
},
Err(e) => return Err(e).map_err_into(REK::HeaderFieldWriteError),
_ => (),
}
}
&None => {
debug!("Not going to insert.");
}
}
}
Ok(fle)
}
}

View file

@ -0,0 +1,56 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::path::PathBuf;
use error::RefErrorKind as REK;
use result::Result;
use libimagstore::store::Entry;
use libimagerror::into::IntoError;
use toml::Value;
use toml_query::read::TomlValueReadExt;
/// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
/// algorithm on it
pub fn hash_path(pb: &PathBuf) -> Result<String> {
use crypto::sha1::Sha1;
use crypto::digest::Digest;
match pb.to_str() {
Some(s) => {
let mut hasher = Sha1::new();
hasher.input_str(s);
Ok(hasher.result_str())
},
None => return Err(REK::PathUTF8Error.into_error()),
}
}
/// Read the reference from a file
pub fn read_reference(refentry: &Entry) -> Result<PathBuf> {
match refentry.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
Err(e) => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
}
}