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

View file

@ -19,16 +19,18 @@
use std::process::exit;
use clap::ArgMatches;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagdiary::error::DiaryErrorKind as DEK;
use libimagdiary::error::MapErrInto;
use libimagentryedit::edit::Edit;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagdiary::entry::Entry;
use libimagdiary::result::Result;
use libimagerror::trace::trace_error_exit;
use libimagutil::warn_exit::warn_exit;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use util::get_diary_name;
@ -36,20 +38,49 @@ pub fn create(rt: &Runtime) {
let diaryname = get_diary_name(rt)
.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") {
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 {
trace_error_exit(&e, 1);
} else {
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 create = rt.cli().subcommand_matches("create").unwrap();
if !create.is_present("timed") {
debug!("Creating non-timed entry");
diary.new_entry_today()
} 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 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 })
@ -60,22 +91,17 @@ pub fn create(rt: &Runtime) {
})
.unwrap_or(time.hour());
time.with_hour(hr).with_minute(0)
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") => {
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 time = get_hourly_id(create);
let min = create
.value_of("minute")
.map(|m| { debug!("minute = {:?}", m); m })
@ -86,7 +112,7 @@ pub fn create(rt: &Runtime) {
})
.unwrap_or(time.minute());
time.with_hour(hr).with_minute(min)
time.with_minute(min)
},
Some(_) => {
@ -96,28 +122,6 @@ pub fn create(rt: &Runtime) {
},
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 {
trace_error(&e);
} else {
info!("Ok!");
}
}

View file

@ -17,15 +17,17 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::process::exit;
use chrono::naive::NaiveDateTime;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error_exit;
use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse;
use libimagutil::warn_exit::warn_exit;
use libimagstore::storeid::IntoStoreId;
use util::get_diary_name;
@ -35,36 +37,34 @@ pub fn delete(rt: &Runtime) {
let diaryname = get_diary_name(rt)
.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[..]);
debug!("Diary opened: {:?}", diary);
let datetime : Option<NaiveDateTime> = rt
let to_del_location = rt
.cli()
.subcommand_matches("delete")
.unwrap()
.value_of("datetime")
.map(|dt| { debug!("DateTime = {:?}", dt); dt })
.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 {
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)) {
if !ask_bool(&format!("Deleting {:?}", to_del_location), Some(true)) {
info!("Aborting delete action");
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)
}

View file

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

View file

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

View file

@ -27,10 +27,9 @@ use util::get_diary_name;
pub fn view(rt: &Runtime) {
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");
diary.entries()
Diary::entries(rt.store(), &diaryname)
.and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
.map_err_trace()
.ok();

View file

@ -17,5 +17,4 @@ version = "2.0.1"
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
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" }

View file

@ -25,11 +25,9 @@ extern crate libimagrt;
extern crate libimagmail;
extern crate libimagerror;
extern crate libimagutil;
extern crate libimagentryref;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
use libimagmail::mail::Mail;
use libimagentryref::reference::Ref;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagutil::info_result::*;
@ -74,11 +72,11 @@ fn list(rt: &Runtime) {
let iter = match store.retrieve_for_module("ref") {
Ok(iter) => iter.filter_map(|id| {
Ref::get(store, id)
.map_err_into(MEK::RefHandlingError)
.and_then(|rf| Mail::from_ref(rf))
.map_err_trace()
.ok()
match store.get(id).map_err_into(MEK::RefHandlingError).map_err_trace() {
Ok(Some(fle)) => Mail::from_fle(fle).map_err_trace().ok(),
Ok(None) => None,
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::setup::generate_runtime_setup;
use libimagtodo::task::Task;
use libimagtodo::taskstore::TaskStore;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
mod ui;
@ -61,9 +61,11 @@ fn tw_hook(rt: &Runtime) {
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
if subcmd.is_present("add") {
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),
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
// per usage und wants one (the second one) back.
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 {
// Should not be possible, as one argument is required via
// ArgGroup
@ -92,18 +94,21 @@ fn list(rt: &Runtime) {
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
// filter out the ones were we can read the uuid
let uuids : Vec<_> = iter.filter_map(|t| match t {
Ok(v) => match v.get_header().read(&String::from("todo.uuid")) {
let uuids : Vec<_> = iter.filter_map(|storeid| {
match rt.store().retrieve(storeid) {
Ok(fle) => {
match fle.get_header().read(&String::from("todo.uuid")) {
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
Ok(Some(_)) => {
warn!("Header type error");
error!("Header type error, expected String at 'todo.uuid' in {}",
fle.get_location());
None
},
Ok(None) => {
warn!("Header missing field");
error!("Header missing field in {}", fle.get_location());
None
},
Err(e) => {
@ -112,10 +117,12 @@ fn list(rt: &Runtime) {
}
None
}
}
},
Err(e) => {
trace_error(&e);
None
},
}
})
.collect();

View file

@ -19,85 +19,85 @@
use std::cmp::Ordering;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use libimagstore::storeid::IntoStoreId;
use libimagerror::trace::trace_error;
use chrono::offset::Local;
use chrono::Datelike;
use itertools::Itertools;
use chrono::naive::NaiveDateTime;
use chrono::Timelike;
use entry::Entry;
use entry::DiaryEntry;
use diaryid::DiaryId;
use error::DiaryError as DE;
use error::DiaryErrorKind as DEK;
use error::MapErrInto;
use result::Result;
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> {
Diary {
store: store,
name: name,
}
// create or get a new entry for today
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry> {
let dt = Local::now();
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
pub fn new_entry_today(&self) -> Result<Entry> {
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry> {
let dt = Local::now();
let ndt = dt.naive_local();
let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
self.new_entry_by_id(id)
}
let id = DiaryId::new(String::from(diary_name),
ndt.year(),
ndt.month(),
ndt.day(),
ndt.minute(),
ndt.second());
pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> {
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))))
self.retrieve(id).map_err_into(DEK::StoreReadError)
}
// Get an iterator for iterating over all entries
pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> {
self.store
.retrieve_for_module("diary")
.map(|iter| DiaryEntryIterator::new(self.name, self.store, iter))
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator> {
self.retrieve_for_module("diary")
.map(|iter| DiaryEntryIterator::new(self, String::from(diary_name), iter))
.map_err_into(DEK::StoreReadError)
}
pub fn delete_entry(&self, entry: Entry) -> Result<()> {
if !entry.is_in_diary(self.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() {
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>> {
match Diary::entries(self, diary_name) {
Err(e) => Some(Err(e)),
Ok(entries) => {
entries.sorted_by(|a, b| {
entries
.map(|e| e.and_then(|e| e.diary_id()))
.sorted_by(|a, b| {
match (a, b) {
(&Ok(ref a), &Ok(ref b)) => {
let a : NaiveDateTime = a.diary_id().into();
let b : NaiveDateTime = b.diary_id().into();
let a : NaiveDateTime = a.clone().into();
let b : NaiveDateTime = b.clone().into();
a.cmp(&b)
},
@ -116,13 +116,20 @@ impl<'a> Diary<'a> {
Ordering::Equal
},
}
}).into_iter().next()
})
.into_iter()
//.map(|sidres| sidres.map(|sid| DiaryId::from_storeid(&sid)))
.next()
}
}
}
pub fn name(&self) -> &'a str {
&self.name
/// Get all diary names
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
//
use std::ops::Deref;
use std::ops::DerefMut;
use libimagstore::store::FileLockEntry;
use libimagentryedit::edit::Edit;
use libimagentryedit::result::Result as EditResult;
use libimagrt::runtime::Runtime;
use libimagstore::store::Entry;
use diaryid::DiaryId;
use diaryid::FromStoreId;
use result::Result;
#[derive(Debug)]
pub struct Entry<'a>(FileLockEntry<'a>);
impl<'a> Deref for Entry<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
pub trait DiaryEntry {
fn diary_id(&self) -> Result<DiaryId>;
}
impl<'a> DerefMut for Entry<'a> {
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)
}
impl DiaryEntry for 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
pub fn diary_id(&self) -> DiaryId {
DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
fn diary_id(&self) -> Result<DiaryId> {
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",
IOError => "IO Error",
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_a_diary_entry(&self) -> bool;
}
impl IsInDiary for Entry {
@ -32,6 +34,10 @@ impl IsInDiary for Entry {
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 {
@ -40,5 +46,9 @@ impl IsInDiary for StoreId {
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 libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreIdIterator;
use libimagerror::trace::trace_error;
use libimagerror::into::IntoError;
use diaryid::DiaryId;
use diaryid::FromStoreId;
use is_in_diary::IsInDiary;
use entry::Entry as DiaryEntry;
use error::DiaryError as DE;
use error::DiaryErrorKind as DEK;
use error::MapErrInto;
use result::Result;
use libimagerror::trace::trace_error;
use is_in_diary::IsInDiary;
/// A iterator for iterating over diary entries
pub struct DiaryEntryIterator<'a> {
store: &'a Store,
name: &'a str,
name: String,
iter: StoreIdIterator,
year: Option<i32>,
@ -54,7 +56,7 @@ impl<'a> Debug for 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 {
store: store,
name: diaryname,
@ -87,9 +89,9 @@ impl<'a> 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 {
let next = match self.iter.next() {
Some(s) => s,
@ -97,7 +99,7 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
};
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);
let id = match DiaryId::from_storeid(&next) {
Ok(i) => i,
@ -118,7 +120,6 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
return Some(self
.store
.retrieve(next)
.map(|fle| DiaryEntry::new(fle))
.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.
use entry::Entry;
use entry::DiaryEntry;
use error::DiaryErrorKind as DEK;
use error::MapErrInto;
use result::Result;
use libimagstore::store::FileLockEntry;
use libimagentryview::viewer::Viewer;
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
/// 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
/// 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 {
let id = entry.diary_id();
println!("{} :\n", id);
match entry.diary_id() {
Ok(id) => println!("{} :\n", id),
Err(e) => trace_error(&e),
}
let _ = try!(self.0
.view_entry(&entry)
.map_err_into(DEK::ViewError)

View file

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

View file

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

View file

@ -23,7 +23,10 @@ generate_error_module!(
StoreError => "Store Error",
StoreIdError => "Store Id handling error",
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 result;
pub mod task;
pub mod taskstore;

View file

@ -17,271 +17,31 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
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 error::TodoErrorKind as TEK;
use error::MapErrInto;
use result::Result;
/// Task struct containing a `FileLockEntry`
#[derive(Debug)]
pub struct Task<'a>(FileLockEntry<'a>);
use libimagstore::store::Entry;
use libimagerror::into::IntoError;
impl<'a> Task<'a> {
use uuid::Uuid;
use toml::Value;
use toml_query::read::TomlValueReadExt;
/// Concstructs a new `Task` with a `FileLockEntry`
pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
Task(fle)
}
pub trait Task {
fn get_uuid(&self) -> Result<Uuid>;
}
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
impl Task for Entry {
fn get_uuid(&self) -> Result<Uuid> {
match self.get_header().read("todo.uuid") {
Ok(Some(&Value::String(ref uuid))) => {
Uuid::parse_str(uuid).map_err_into(TEK::UuidParserError)
},
Err(e) => return Err(e).map_err_into(TEK::ImportError),
Ok(Some(_)) => Err(TEK::HeaderTypeError.into_error()),
Ok(None) => Err(TEK::HeaderFieldMissing.into_error()),
Err(e) => Err(e).map_err_into(TEK::StoreError),
}
}
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))
}
}
impl<'a> Deref for Task<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
}
impl<'a> DerefMut for Task<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
/// 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 libimagstore::store::Entry;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagutil::debug_result::*;
use libimagerror::into::IntoError;
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
@ -58,37 +58,32 @@ use url::Url;
use crypto::sha1::Sha1;
use crypto::digest::Digest;
/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
pub struct Link<'a> {
link: FileLockEntry<'a>
pub trait Link {
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> {
Link { link: fle }
}
/// Get a link Url object from a `FileLockEntry`, ignore errors.
fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
file.get_header()
fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>> {
self.get_header()
.read("imag.content.url")
.ok()
.map_err_into(LEK::EntryHeaderReadError)
.and_then(|opt| match opt {
Some(&Value::String(ref 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>> {
let opt = self.link
.get_header()
.read("imag.content.url");
match opt {
fn get_url(&self) -> Result<Option<Url>> {
match self.get_header().read("imag.content.url") {
Ok(Some(&Value::String(ref s))) => {
Url::parse(&s[..])
.map(Some)
@ -261,9 +256,10 @@ pub mod iter {
type Item = Result<Url>;
fn next(&mut self) -> Option<Self::Item> {
use super::get_external_link_from_file;
use external::Link;
self.0
loop {
let next = self.0
.next()
.map(|id| {
debug!("Retrieving entry for id: '{:?}'", id);
@ -274,10 +270,18 @@ pub mod iter {
.and_then(|f| {
debug!("Store::retrieve({:?}) succeeded", id);
debug!("getting external link from file now");
get_external_link_from_file(&f)
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")
}
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
/// 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.

View file

@ -18,8 +18,8 @@ itertools = "0.5"
log = "0.3"
rust-crypto = "0.2"
toml = "^0.4"
walkdir = "1.0.*"
toml-query = "0.3.0"
walkdir = "1.0.*"
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
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::digest::Digest;
use libimagerror::into::IntoError;
use hasher::Hasher;
use result::Result;
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> {
let s = contents
let s = try!(contents
.bytes()
.take(self.n)
.collect::<RResult<Vec<u8>, _>>()
.map_err_into(REK::IOError)
.and_then(|v| String::from_utf8(v).map_err_into(REK::IOError))
.map_err(Box::new)
.map_err(|e| REK::UTF8Error.into_error_with_cause(e))
.map_err_into(REK::IOError);
self.hasher.input_str(&try!(s)[..]);
.and_then(|v| String::from_utf8(v).map_err_into(REK::UTF8Error)));
self.hasher.input_str(&s[..]);
Ok(self.hasher.result_str())
}

View file

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

View file

@ -20,13 +20,14 @@
use std::default::Default;
use std::io::stdout;
use std::io::Write;
use std::ops::Deref;
use libimagentrylist::lister::Lister;
use libimagentrylist::result::Result;
use libimagerror::trace::trace_error;
use libimagstore::store::FileLockEntry;
use libimagerror::into::IntoError;
use libimagentrylist::error::ListErrorKind as LEK;
use libimagentrylist::error as lerror;
use reference::Ref;
use error::MapErrInto;
@ -88,15 +89,45 @@ impl Lister for RefLister {
debug!("fold({:?}, {:?})", accu, entry);
let r = accu.and_then(|_| {
debug!("Listing Entry: {:?}", entry);
lister_fn(entry,
self.check_dead,
self.check_changed,
self.check_changed_content,
self.check_changed_permiss)
{
let is_dead = if self.check_dead {
if try!(entry.fs_link_exists()) {
"dead"
} 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| {
write!(stdout(), "{}\n", s)
.map_err(Box::new)
.map_err(|e| LEK::FormatError.into_error_with_cause(e))
lerror::MapErrInto::map_err_into(write!(stdout(), "{}\n", s), LEK::FormatError)
})
.map_err_into(REK::RefToDisplayError)
})
@ -104,73 +135,16 @@ impl Lister for RefLister {
(r, i + 1)
});
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,
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 {
fn check_changed<R: Ref>(r: &R) -> bool {
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()
.and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored)))
.map(|(hash, stored)| hash == stored);
@ -178,7 +152,7 @@ fn check_changed_content(r: &Ref) -> bool {
match eq {
Ok(eq) => eq,
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);
// 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.");
false
}

View file

@ -21,238 +21,104 @@
//! files outside of the imag store.
use std::path::PathBuf;
use std::ops::Deref;
use std::ops::DerefMut;
use std::collections::BTreeMap;
use std::fs::File;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::fs::Permissions;
use std::result::Result as RResult;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagstore::store::Entry;
use libimagerror::into::IntoError;
use toml::Value;
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
use toml_query::insert::TomlValueInsertExt;
use error::RefErrorKind as REK;
use error::MapErrInto;
use flags::RefFlags;
use result::Result;
use hasher::*;
use module_path::ModuleEntryPath;
#[derive(Debug)]
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()),
}
}
pub trait Ref {
/// Get the hash from the path of the ref
pub fn get_path_hash(&self) -> Result<String> {
self.0
.get_location()
fn get_path_hash(&self) -> Result<String>;
/// 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()
.into_pathbuf()
.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
pub fn get_stored_hash(&self) -> Result<String> {
fn get_stored_hash(&self) -> Result<String> {
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
/// custom Hasher instance.
pub 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())[..]) {
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
// content hash stored...
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
pub fn get_current_hash(&self) -> Result<String> {
fn get_current_hash(&self) -> Result<String> {
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
/// 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()
.and_then(|pb| {
File::open(pb.clone())
.map(|f| (pb, f))
.map_err(Box::new)
.map_err(|e| REK::IOError.into_error_with_cause(e))
})
.and_then(|pb| File::open(pb.clone()).map(|f| (pb, f)).map_err_into(REK::IOError))
.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
pub fn fs_link_exists(&self) -> Result<bool> {
fn fs_link_exists(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.exists())
}
/// 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())
}
/// 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())
}
/// 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)
}
@ -347,7 +191,7 @@ impl<'a> Ref<'a> {
/// This includes:
/// - Hashsum of the file is still the same as stored in the Ref
/// - 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()) {
(Ok(true) , Ok(true)) => Ok(true),
(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
/// permissions
pub fn fs_link_valid_permissions(&self) -> Result<bool> {
self.0
fn fs_link_valid_permissions(&self) -> Result<bool> {
self
.get_header()
.read("ref.permissions.ro")
.map_err(Box::new)
.map_err(|e| REK::HeaderFieldReadError.into_error_with_cause(e))
.map_err_into(REK::HeaderFieldReadError)
.and_then(|ro| {
match ro {
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()))
.map_err(Box::new)
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
.map_err_into(REK::RefTargetCannotReadPermissions)
}
/// 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 current_hash = try!(self.get_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
/// 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())
}
/// 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()
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_perm = try!(self.get_current_permissions());
try!(self.0
try!(self
.get_header_mut()
.set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
.map_err(Box::new)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
.map_err_into(REK::StoreWriteError)
);
try!(self.0
try!(self
.get_header_mut()
.set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
.map_err(Box::new)
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
.map_err_into(REK::StoreWriteError)
);
Ok(())
}
/// Get the path of the file which is reffered to by this Ref
pub fn fs_file(&self) -> Result<PathBuf> {
match self.0.get_header().read("ref.path") {
fn fs_file(&self) -> Result<PathBuf> {
match self.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()),
@ -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
///
/// 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
///
/// 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())
}
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>
{
use itertools::Itertools;
@ -505,13 +296,11 @@ impl<'a> Ref<'a> {
.into_iter()
.map(|entry| {
entry
.map_err(Box::new)
.map_err(|e| REK::IOError.into_error_with_cause(e))
.map_err_into(REK::IOError)
.and_then(|entry| {
let pb = PathBuf::from(entry.path());
File::open(entry.path())
.map_err(Box::new)
.map_err(|e| REK::IOError.into_error_with_cause(e))
.map_err_into(REK::IOError)
.map(|f| (pb, f))
})
.and_then(|(p, mut f)| h.create_hash(&p, &mut f).map(|h| (p, h)))
@ -522,8 +311,7 @@ impl<'a> Ref<'a> {
None
}
})
.map_err(Box::new)
.map_err(|e| REK::IOError.into_error_with_cause(e))
.map_err_into(REK::IOError)
})
.filter_map(|e| e.ok())
.filter_map(|e| e)
@ -535,43 +323,16 @@ impl<'a> Ref<'a> {
})
}
}
impl<'a> Deref for Ref<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
}
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
/// 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_into(REK::HeaderFieldReadError))
.and_then(|file| {
file
.metadata()
.map(|md| md.permissions())
.map_err_into(REK::RefTargetCannotReadPermissions)
})
}
}

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))),
}
}