Merge pull request #1029 from matthiasbeyer/all-extensions-as-traits
All extensions as traits
This commit is contained in:
commit
6d1dab3117
29 changed files with 1101 additions and 1056 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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,88 +38,90 @@ 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>> {
|
||||
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 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)
|
||||
}
|
||||
});
|
||||
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(&e);
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
None => Err(DEK::EntryNotInDiary.into_error()),
|
||||
}
|
||||
.map_err_trace().ok();
|
||||
.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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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,30 +94,35 @@ 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")) {
|
||||
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
|
||||
Ok(Some(_)) => {
|
||||
warn!("Header type error");
|
||||
None
|
||||
},
|
||||
Ok(None) => {
|
||||
warn!("Header missing field");
|
||||
None
|
||||
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(_)) => {
|
||||
error!("Header type error, expected String at 'todo.uuid' in {}",
|
||||
fle.get_location());
|
||||
None
|
||||
},
|
||||
Ok(None) => {
|
||||
error!("Header missing field in {}", fle.get_location());
|
||||
None
|
||||
},
|
||||
Err(e) => {
|
||||
if !no_identifier(&e) {
|
||||
trace_error(&e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if !no_identifier(&e) {
|
||||
trace_error(&e);
|
||||
}
|
||||
trace_error(&e);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
trace_error(&e);
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -19,110 +19,117 @@
|
|||
|
||||
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| {
|
||||
match (a, b) {
|
||||
(&Ok(ref a), &Ok(ref b)) => {
|
||||
let a : NaiveDateTime = a.diary_id().into();
|
||||
let b : NaiveDateTime = b.diary_id().into();
|
||||
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.clone().into();
|
||||
let b : NaiveDateTime = b.clone().into();
|
||||
|
||||
a.cmp(&b)
|
||||
},
|
||||
a.cmp(&b)
|
||||
},
|
||||
|
||||
(&Ok(_), &Err(ref e)) => {
|
||||
trace_error(e);
|
||||
Ordering::Less
|
||||
},
|
||||
(&Err(ref e), &Ok(_)) => {
|
||||
trace_error(e);
|
||||
Ordering::Greater
|
||||
},
|
||||
(&Err(ref e1), &Err(ref e2)) => {
|
||||
trace_error(e1);
|
||||
trace_error(e2);
|
||||
Ordering::Equal
|
||||
},
|
||||
}
|
||||
}).into_iter().next()
|
||||
(&Ok(_), &Err(ref e)) => {
|
||||
trace_error(e);
|
||||
Ordering::Less
|
||||
},
|
||||
(&Err(ref e), &Ok(_)) => {
|
||||
trace_error(e);
|
||||
Ordering::Greater
|
||||
},
|
||||
(&Err(ref e1), &Err(ref e2)) => {
|
||||
trace_error(e1);
|
||||
trace_error(e2);
|
||||
Ordering::Equal
|
||||
},
|
||||
}
|
||||
})
|
||||
.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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
);
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
);
|
||||
|
||||
|
|
|
@ -48,4 +48,5 @@ module_entry_path_mod!("todo");
|
|||
pub mod error;
|
||||
pub mod result;
|
||||
pub mod task;
|
||||
pub mod taskstore;
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
||||
/// Concstructs a new `Task` with a `FileLockEntry`
|
||||
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))
|
||||
}
|
||||
use uuid::Uuid;
|
||||
use toml::Value;
|
||||
use toml_query::read::TomlValueReadExt;
|
||||
|
||||
pub trait Task {
|
||||
fn get_uuid(&self) -> Result<Uuid>;
|
||||
}
|
||||
|
||||
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 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)
|
||||
},
|
||||
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> 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
205
lib/domain/libimagtodo/src/taskstore.rs
Normal file
205
lib/domain/libimagtodo/src/taskstore.rs
Normal 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)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,23 +256,32 @@ 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
|
||||
.next()
|
||||
.map(|id| {
|
||||
debug!("Retrieving entry for id: '{:?}'", id);
|
||||
self.1
|
||||
.retrieve(id.clone())
|
||||
.map_err_into(LEK::StoreReadError)
|
||||
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
|
||||
.and_then(|f| {
|
||||
debug!("Store::retrieve({:?}) succeeded", id);
|
||||
debug!("getting external link from file now");
|
||||
get_external_link_from_file(&f)
|
||||
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
|
||||
})
|
||||
})
|
||||
loop {
|
||||
let next = self.0
|
||||
.next()
|
||||
.map(|id| {
|
||||
debug!("Retrieving entry for id: '{:?}'", id);
|
||||
self.1
|
||||
.retrieve(id.clone())
|
||||
.map_err_into(LEK::StoreReadError)
|
||||
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
|
||||
.and_then(|f| {
|
||||
debug!("Store::retrieve({:?}) succeeded", id);
|
||||
debug!("getting external link from file now");
|
||||
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.
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -52,4 +52,6 @@ pub mod hasher;
|
|||
pub mod hashers;
|
||||
pub mod lister;
|
||||
pub mod reference;
|
||||
pub mod refstore;
|
||||
pub mod result;
|
||||
mod util;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
269
lib/entry/libimagentryref/src/refstore.rs
Normal file
269
lib/entry/libimagentryref/src/refstore.rs
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
56
lib/entry/libimagentryref/src/util.rs
Normal file
56
lib/entry/libimagentryref/src/util.rs
Normal 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))),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue