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 std::path::PathBuf;
|
||||||
|
|
||||||
use libimagentryref::reference::Ref;
|
use libimagentryref::refstore::RefStore;
|
||||||
use libimagentryref::flags::RefFlags;
|
use libimagentryref::flags::RefFlags;
|
||||||
use libimagerror::trace::trace_error;
|
use libimagerror::trace::trace_error;
|
||||||
use libimagrt::setup::generate_runtime_setup;
|
use libimagrt::setup::generate_runtime_setup;
|
||||||
|
@ -82,7 +82,7 @@ fn add(rt: &Runtime) {
|
||||||
.with_content_hashing(cmd.is_present("track-content"))
|
.with_content_hashing(cmd.is_present("track-content"))
|
||||||
.with_permission_tracking(cmd.is_present("track-permissions"));
|
.with_permission_tracking(cmd.is_present("track-permissions"));
|
||||||
|
|
||||||
match Ref::create(rt.store(), path, flags) {
|
match RefStore::create(rt.store(), path, flags) {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
debug!("Reference created: {:?}", r);
|
debug!("Reference created: {:?}", r);
|
||||||
info!("Ok");
|
info!("Ok");
|
||||||
|
@ -102,7 +102,7 @@ fn remove(rt: &Runtime) {
|
||||||
let yes = cmd.is_present("yes");
|
let yes = cmd.is_present("yes");
|
||||||
|
|
||||||
if yes || ask_bool(&format!("Delete Ref with hash '{}'", hash)[..], None) {
|
if yes || ask_bool(&format!("Delete Ref with hash '{}'", hash)[..], None) {
|
||||||
match Ref::delete_by_hash(rt.store(), hash) {
|
match rt.store().delete_by_hash(hash) {
|
||||||
Err(e) => trace_error(&e),
|
Err(e) => trace_error(&e),
|
||||||
Ok(_) => info!("Ok"),
|
Ok(_) => info!("Ok"),
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ fn list(rt: &Runtime) {
|
||||||
|
|
||||||
let iter = match rt.store().retrieve_for_module("ref") {
|
let iter = match rt.store().retrieve_for_module("ref") {
|
||||||
Ok(iter) => iter.filter_map(|id| {
|
Ok(iter) => iter.filter_map(|id| {
|
||||||
match Ref::get(rt.store(), id) {
|
match rt.store().get(id) {
|
||||||
Ok(r) => Some(r),
|
Ok(r) => Some(r),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
trace_error(&e);
|
trace_error(&e);
|
||||||
|
@ -145,7 +145,7 @@ fn list(rt: &Runtime) {
|
||||||
.check_changed(do_check_changed)
|
.check_changed(do_check_changed)
|
||||||
.check_changed_content(do_check_changed_content)
|
.check_changed_content(do_check_changed_content)
|
||||||
.check_changed_permiss(do_check_changed_permiss)
|
.check_changed_permiss(do_check_changed_permiss)
|
||||||
.list(iter.map(|e| e.into()))
|
.list(iter.filter_map(Into::into))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,18 @@
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use libimagdiary::diary::Diary;
|
use libimagdiary::diary::Diary;
|
||||||
use libimagdiary::diaryid::DiaryId;
|
use libimagdiary::diaryid::DiaryId;
|
||||||
use libimagdiary::error::DiaryErrorKind as DEK;
|
use libimagdiary::error::DiaryErrorKind as DEK;
|
||||||
use libimagdiary::error::MapErrInto;
|
use libimagdiary::error::MapErrInto;
|
||||||
use libimagentryedit::edit::Edit;
|
use libimagentryedit::edit::Edit;
|
||||||
use libimagrt::runtime::Runtime;
|
use libimagrt::runtime::Runtime;
|
||||||
use libimagerror::trace::trace_error;
|
use libimagerror::trace::trace_error_exit;
|
||||||
use libimagdiary::entry::Entry;
|
|
||||||
use libimagdiary::result::Result;
|
|
||||||
use libimagutil::warn_exit::warn_exit;
|
use libimagutil::warn_exit::warn_exit;
|
||||||
|
use libimagstore::store::FileLockEntry;
|
||||||
|
use libimagstore::store::Store;
|
||||||
|
|
||||||
use util::get_diary_name;
|
use util::get_diary_name;
|
||||||
|
|
||||||
|
@ -36,88 +38,90 @@ pub fn create(rt: &Runtime) {
|
||||||
let diaryname = get_diary_name(rt)
|
let diaryname = get_diary_name(rt)
|
||||||
.unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
|
.unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
|
||||||
|
|
||||||
let prevent_edit = rt.cli().subcommand_matches("create").unwrap().is_present("no-edit");
|
let mut entry = create_entry(rt.store(), &diaryname, rt);
|
||||||
|
|
||||||
fn create_entry<'a>(diary: &'a Diary, rt: &Runtime) -> Result<Entry<'a>> {
|
let res = if rt.cli().subcommand_matches("create").unwrap().is_present("no-edit") {
|
||||||
use std::str::FromStr;
|
debug!("Not editing new diary entry");
|
||||||
|
Ok(())
|
||||||
let create = rt.cli().subcommand_matches("create").unwrap();
|
} else {
|
||||||
if !create.is_present("timed") {
|
debug!("Editing new diary entry");
|
||||||
debug!("Creating non-timed entry");
|
entry.edit_content(rt)
|
||||||
diary.new_entry_today()
|
.map_err_into(DEK::DiaryEditError)
|
||||||
} else {
|
};
|
||||||
let id = match create.value_of("timed") {
|
|
||||||
Some("h") | Some("hourly") => {
|
|
||||||
debug!("Creating hourly-timed entry");
|
|
||||||
let time = DiaryId::now(String::from(diary.name()));
|
|
||||||
let hr = create
|
|
||||||
.value_of("hour")
|
|
||||||
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
|
|
||||||
.and_then(|s| {
|
|
||||||
FromStr::from_str(s)
|
|
||||||
.map_err(|_| warn!("Could not parse hour: '{}'", s))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or(time.hour());
|
|
||||||
|
|
||||||
time.with_hour(hr).with_minute(0)
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("m") | Some("minutely") => {
|
|
||||||
debug!("Creating minutely-timed entry");
|
|
||||||
let time = DiaryId::now(String::from(diary.name()));
|
|
||||||
let hr = create
|
|
||||||
.value_of("hour")
|
|
||||||
.map(|h| { debug!("hour = {:?}", h); h })
|
|
||||||
.and_then(|s| {
|
|
||||||
FromStr::from_str(s)
|
|
||||||
.map_err(|_| warn!("Could not parse hour: '{}'", s))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or(time.hour());
|
|
||||||
|
|
||||||
let min = create
|
|
||||||
.value_of("minute")
|
|
||||||
.map(|m| { debug!("minute = {:?}", m); m })
|
|
||||||
.and_then(|s| {
|
|
||||||
FromStr::from_str(s)
|
|
||||||
.map_err(|_| warn!("Could not parse minute: '{}'", s))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or(time.minute());
|
|
||||||
|
|
||||||
time.with_hour(hr).with_minute(min)
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(_) => {
|
|
||||||
warn!("Timed creation failed: Unknown spec '{}'",
|
|
||||||
create.value_of("timed").unwrap());
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
None => warn_exit("Unexpected error, cannot continue", 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
diary.new_entry_by_id(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let diary = Diary::open(rt.store(), &diaryname[..]);
|
|
||||||
let res = create_entry(&diary, rt)
|
|
||||||
.and_then(|mut entry| {
|
|
||||||
if prevent_edit {
|
|
||||||
debug!("Not editing new diary entry");
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
debug!("Editing new diary entry");
|
|
||||||
entry.edit_content(rt).map_err_into(DEK::DiaryEditError)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
trace_error(&e);
|
trace_error_exit(&e, 1);
|
||||||
} else {
|
} else {
|
||||||
info!("Ok!");
|
info!("Ok!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_entry<'a>(diary: &'a Store, diaryname: &str, rt: &Runtime) -> FileLockEntry<'a> {
|
||||||
|
let create = rt.cli().subcommand_matches("create").unwrap();
|
||||||
|
let entry = if !create.is_present("timed") {
|
||||||
|
debug!("Creating non-timed entry");
|
||||||
|
diary.new_entry_today(diaryname)
|
||||||
|
} else {
|
||||||
|
let id = create_id_from_clispec(&create, &diaryname);
|
||||||
|
diary.retrieve(id).map_err_into(DEK::StoreReadError)
|
||||||
|
};
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
Err(e) => trace_error_exit(&e, 1),
|
||||||
|
Ok(e) => {
|
||||||
|
debug!("Created: {}", e.get_location());
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn create_id_from_clispec(create: &ArgMatches, diaryname: &str) -> DiaryId {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
let get_hourly_id = |create: &ArgMatches| -> DiaryId {
|
||||||
|
let time = DiaryId::now(String::from(diaryname));
|
||||||
|
let hr = create
|
||||||
|
.value_of("hour")
|
||||||
|
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
|
||||||
|
.and_then(|s| {
|
||||||
|
FromStr::from_str(s)
|
||||||
|
.map_err(|_| warn!("Could not parse hour: '{}'", s))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.unwrap_or(time.hour());
|
||||||
|
|
||||||
|
time.with_hour(hr)
|
||||||
|
};
|
||||||
|
|
||||||
|
match create.value_of("timed") {
|
||||||
|
Some("h") | Some("hourly") => {
|
||||||
|
debug!("Creating hourly-timed entry");
|
||||||
|
get_hourly_id(create)
|
||||||
|
},
|
||||||
|
|
||||||
|
Some("m") | Some("minutely") => {
|
||||||
|
let time = get_hourly_id(create);
|
||||||
|
let min = create
|
||||||
|
.value_of("minute")
|
||||||
|
.map(|m| { debug!("minute = {:?}", m); m })
|
||||||
|
.and_then(|s| {
|
||||||
|
FromStr::from_str(s)
|
||||||
|
.map_err(|_| warn!("Could not parse minute: '{}'", s))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.unwrap_or(time.minute());
|
||||||
|
|
||||||
|
time.with_minute(min)
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(_) => {
|
||||||
|
warn!("Timed creation failed: Unknown spec '{}'",
|
||||||
|
create.value_of("timed").unwrap());
|
||||||
|
exit(1);
|
||||||
|
},
|
||||||
|
|
||||||
|
None => warn_exit("Unexpected error, cannot continue", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,17 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use chrono::naive::NaiveDateTime;
|
use chrono::naive::NaiveDateTime;
|
||||||
|
|
||||||
use libimagdiary::diary::Diary;
|
|
||||||
use libimagdiary::diaryid::DiaryId;
|
use libimagdiary::diaryid::DiaryId;
|
||||||
use libimagrt::runtime::Runtime;
|
use libimagrt::runtime::Runtime;
|
||||||
use libimagerror::trace::trace_error_exit;
|
use libimagerror::trace::trace_error_exit;
|
||||||
use libimagtimeui::datetime::DateTime;
|
use libimagtimeui::datetime::DateTime;
|
||||||
use libimagtimeui::parse::Parse;
|
use libimagtimeui::parse::Parse;
|
||||||
use libimagutil::warn_exit::warn_exit;
|
use libimagutil::warn_exit::warn_exit;
|
||||||
|
use libimagstore::storeid::IntoStoreId;
|
||||||
|
|
||||||
use util::get_diary_name;
|
use util::get_diary_name;
|
||||||
|
|
||||||
|
@ -35,36 +37,34 @@ pub fn delete(rt: &Runtime) {
|
||||||
let diaryname = get_diary_name(rt)
|
let diaryname = get_diary_name(rt)
|
||||||
.unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
|
.unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
|
||||||
|
|
||||||
let diary = Diary::open(rt.store(), &diaryname[..]);
|
let to_del_location = rt
|
||||||
debug!("Diary opened: {:?}", diary);
|
|
||||||
|
|
||||||
let datetime : Option<NaiveDateTime> = rt
|
|
||||||
.cli()
|
.cli()
|
||||||
.subcommand_matches("delete")
|
.subcommand_matches("delete")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.value_of("datetime")
|
.value_of("datetime")
|
||||||
.map(|dt| { debug!("DateTime = {:?}", dt); dt })
|
.map(|dt| { debug!("DateTime = {:?}", dt); dt })
|
||||||
.and_then(DateTime::parse)
|
.and_then(DateTime::parse)
|
||||||
.map(|dt| dt.into());
|
.map(|dt| dt.into())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
warn!("Not deleting entries, because missing date/time specification");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
.and_then(|dt: NaiveDateTime| {
|
||||||
|
DiaryId::from_datetime(diaryname.clone(), dt)
|
||||||
|
.into_storeid()
|
||||||
|
.map(|id| rt.store().retrieve(id))
|
||||||
|
.unwrap_or_else(|e| trace_error_exit(&e, 1))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| trace_error_exit(&e, 1))
|
||||||
|
.get_location()
|
||||||
|
.clone();
|
||||||
|
|
||||||
let to_del = match datetime {
|
if !ask_bool(&format!("Deleting {:?}", to_del_location), Some(true)) {
|
||||||
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
|
|
||||||
None => diary.get_youngest_entry(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let to_del = match to_del {
|
|
||||||
Some(Ok(e)) => e,
|
|
||||||
|
|
||||||
Some(Err(e)) => trace_error_exit(&e, 1),
|
|
||||||
None => warn_exit("No entry", 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) {
|
|
||||||
info!("Aborting delete action");
|
info!("Aborting delete action");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = diary.delete_entry(to_del) {
|
if let Err(e) = rt.store().delete(to_del_location) {
|
||||||
trace_error_exit(&e, 1)
|
trace_error_exit(&e, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use chrono::naive::NaiveDateTime;
|
use chrono::naive::NaiveDateTime;
|
||||||
|
|
||||||
use libimagdiary::diary::Diary;
|
use libimagdiary::diary::Diary;
|
||||||
|
@ -30,33 +32,39 @@ use libimagerror::into::IntoError;
|
||||||
use libimagtimeui::datetime::DateTime;
|
use libimagtimeui::datetime::DateTime;
|
||||||
use libimagtimeui::parse::Parse;
|
use libimagtimeui::parse::Parse;
|
||||||
use libimagutil::warn_exit::warn_exit;
|
use libimagutil::warn_exit::warn_exit;
|
||||||
|
use libimagerror::trace::trace_error_exit;
|
||||||
|
|
||||||
use util::get_diary_name;
|
use util::get_diary_name;
|
||||||
|
|
||||||
pub fn edit(rt: &Runtime) {
|
pub fn edit(rt: &Runtime) {
|
||||||
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
|
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
|
||||||
let diary = Diary::open(rt.store(), &diaryname[..]);
|
|
||||||
|
|
||||||
let datetime : Option<NaiveDateTime> = rt
|
rt.cli()
|
||||||
.cli()
|
|
||||||
.subcommand_matches("edit")
|
.subcommand_matches("edit")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.value_of("datetime")
|
.value_of("datetime")
|
||||||
.and_then(DateTime::parse)
|
.and_then(DateTime::parse)
|
||||||
.map(|dt| dt.into());
|
.map(|dt| dt.into())
|
||||||
|
.map(|dt: NaiveDateTime| DiaryId::from_datetime(diaryname.clone(), dt))
|
||||||
let to_edit = match datetime {
|
.or_else(|| {
|
||||||
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
|
rt.store()
|
||||||
None => diary.get_youngest_entry(),
|
.get_youngest_entry_id(&diaryname)
|
||||||
};
|
.map(|optid| match optid {
|
||||||
|
Ok(id) => id,
|
||||||
match to_edit {
|
Err(e) => trace_error_exit(&e, 1),
|
||||||
Some(Ok(mut e)) => e.edit_content(rt).map_err_into(DEK::IOError),
|
})
|
||||||
|
})
|
||||||
Some(Err(e)) => Err(e),
|
.ok_or_else(|| {
|
||||||
None => Err(DEK::EntryNotInDiary.into_error()),
|
error!("No entries in diary. Aborting");
|
||||||
}
|
exit(1)
|
||||||
.map_err_trace().ok();
|
})
|
||||||
|
.and_then(|id| rt.store().get(id))
|
||||||
|
.map(|opte| match opte {
|
||||||
|
Some(mut e) => e.edit_content(rt).map_err_into(DEK::IOError),
|
||||||
|
None => Err(DEK::EntryNotInDiary.into_error()),
|
||||||
|
})
|
||||||
|
.map_err_trace()
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,7 @@ pub fn list(rt: &Runtime) {
|
||||||
.unwrap_or(String::from("<<Path Parsing Error>>"))
|
.unwrap_or(String::from("<<Path Parsing Error>>"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let diary = Diary::open(rt.store(), &diaryname[..]);
|
Diary::entries(rt.store(), &diaryname)
|
||||||
debug!("Diary opened: {:?}", diary);
|
|
||||||
diary.entries()
|
|
||||||
.and_then(|es| {
|
.and_then(|es| {
|
||||||
debug!("Iterator for listing: {:?}", es);
|
debug!("Iterator for listing: {:?}", es);
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,9 @@ use util::get_diary_name;
|
||||||
|
|
||||||
pub fn view(rt: &Runtime) {
|
pub fn view(rt: &Runtime) {
|
||||||
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
|
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
|
||||||
let diary = Diary::open(rt.store(), &diaryname[..]);
|
|
||||||
let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
|
let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
|
||||||
|
|
||||||
diary.entries()
|
Diary::entries(rt.store(), &diaryname)
|
||||||
.and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
|
.and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
|
||||||
.map_err_trace()
|
.map_err_trace()
|
||||||
.ok();
|
.ok();
|
||||||
|
|
|
@ -17,5 +17,4 @@ version = "2.0.1"
|
||||||
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
|
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
|
||||||
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
|
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
|
||||||
libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" }
|
libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" }
|
||||||
libimagentryref = { version = "0.4.0", path = "../../../lib/entry/libimagentryref" }
|
|
||||||
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
|
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
|
||||||
|
|
|
@ -25,11 +25,9 @@ extern crate libimagrt;
|
||||||
extern crate libimagmail;
|
extern crate libimagmail;
|
||||||
extern crate libimagerror;
|
extern crate libimagerror;
|
||||||
extern crate libimagutil;
|
extern crate libimagutil;
|
||||||
extern crate libimagentryref;
|
|
||||||
|
|
||||||
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
|
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
|
||||||
use libimagmail::mail::Mail;
|
use libimagmail::mail::Mail;
|
||||||
use libimagentryref::reference::Ref;
|
|
||||||
use libimagrt::runtime::Runtime;
|
use libimagrt::runtime::Runtime;
|
||||||
use libimagrt::setup::generate_runtime_setup;
|
use libimagrt::setup::generate_runtime_setup;
|
||||||
use libimagutil::info_result::*;
|
use libimagutil::info_result::*;
|
||||||
|
@ -74,11 +72,11 @@ fn list(rt: &Runtime) {
|
||||||
|
|
||||||
let iter = match store.retrieve_for_module("ref") {
|
let iter = match store.retrieve_for_module("ref") {
|
||||||
Ok(iter) => iter.filter_map(|id| {
|
Ok(iter) => iter.filter_map(|id| {
|
||||||
Ref::get(store, id)
|
match store.get(id).map_err_into(MEK::RefHandlingError).map_err_trace() {
|
||||||
.map_err_into(MEK::RefHandlingError)
|
Ok(Some(fle)) => Mail::from_fle(fle).map_err_trace().ok(),
|
||||||
.and_then(|rf| Mail::from_ref(rf))
|
Ok(None) => None,
|
||||||
.map_err_trace()
|
Err(e) => trace_error_exit(&e, 1),
|
||||||
.ok()
|
}
|
||||||
}),
|
}),
|
||||||
Err(e) => trace_error_exit(&e, 1),
|
Err(e) => trace_error_exit(&e, 1),
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@ use toml::Value;
|
||||||
|
|
||||||
use libimagrt::runtime::Runtime;
|
use libimagrt::runtime::Runtime;
|
||||||
use libimagrt::setup::generate_runtime_setup;
|
use libimagrt::setup::generate_runtime_setup;
|
||||||
use libimagtodo::task::Task;
|
use libimagtodo::taskstore::TaskStore;
|
||||||
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
|
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
@ -61,9 +61,11 @@ fn tw_hook(rt: &Runtime) {
|
||||||
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
|
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
|
||||||
if subcmd.is_present("add") {
|
if subcmd.is_present("add") {
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`
|
|
||||||
|
|
||||||
match Task::import(rt.store(), stdin) {
|
// implements BufRead which is required for `Store::import_task_from_reader()`
|
||||||
|
let stdin = stdin.lock();
|
||||||
|
|
||||||
|
match rt.store().import_task_from_reader(stdin) {
|
||||||
Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
|
Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
|
||||||
Err(e) => trace_error_exit(&e, 1),
|
Err(e) => trace_error_exit(&e, 1),
|
||||||
}
|
}
|
||||||
|
@ -71,7 +73,7 @@ fn tw_hook(rt: &Runtime) {
|
||||||
// The used hook is "on-modify". This hook gives two json-objects
|
// The used hook is "on-modify". This hook gives two json-objects
|
||||||
// per usage und wants one (the second one) back.
|
// per usage und wants one (the second one) back.
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
Task::delete_by_imports(rt.store(), stdin.lock()).map_err_trace().ok();
|
rt.store().delete_tasks_by_imports(stdin.lock()).map_err_trace().ok();
|
||||||
} else {
|
} else {
|
||||||
// Should not be possible, as one argument is required via
|
// Should not be possible, as one argument is required via
|
||||||
// ArgGroup
|
// ArgGroup
|
||||||
|
@ -92,30 +94,35 @@ fn list(rt: &Runtime) {
|
||||||
is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
|
is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = Task::all(rt.store()) // get all tasks
|
let res = rt.store().all_tasks() // get all tasks
|
||||||
.map(|iter| { // and if this succeeded
|
.map(|iter| { // and if this succeeded
|
||||||
// filter out the ones were we can read the uuid
|
// filter out the ones were we can read the uuid
|
||||||
let uuids : Vec<_> = iter.filter_map(|t| match t {
|
let uuids : Vec<_> = iter.filter_map(|storeid| {
|
||||||
Ok(v) => match v.get_header().read(&String::from("todo.uuid")) {
|
match rt.store().retrieve(storeid) {
|
||||||
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
|
Ok(fle) => {
|
||||||
Ok(Some(_)) => {
|
match fle.get_header().read(&String::from("todo.uuid")) {
|
||||||
warn!("Header type error");
|
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
|
||||||
None
|
Ok(Some(_)) => {
|
||||||
},
|
error!("Header type error, expected String at 'todo.uuid' in {}",
|
||||||
Ok(None) => {
|
fle.get_location());
|
||||||
warn!("Header missing field");
|
None
|
||||||
None
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
error!("Header missing field in {}", fle.get_location());
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
if !no_identifier(&e) {
|
||||||
|
trace_error(&e);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !no_identifier(&e) {
|
trace_error(&e);
|
||||||
trace_error(&e);
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
},
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
trace_error(&e);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -19,110 +19,117 @@
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use libimagstore::store::FileLockEntry;
|
||||||
use libimagstore::store::Store;
|
use libimagstore::store::Store;
|
||||||
use libimagstore::storeid::IntoStoreId;
|
|
||||||
use libimagerror::trace::trace_error;
|
use libimagerror::trace::trace_error;
|
||||||
|
|
||||||
use chrono::offset::Local;
|
use chrono::offset::Local;
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use chrono::naive::NaiveDateTime;
|
use chrono::naive::NaiveDateTime;
|
||||||
|
use chrono::Timelike;
|
||||||
|
|
||||||
use entry::Entry;
|
use entry::DiaryEntry;
|
||||||
use diaryid::DiaryId;
|
use diaryid::DiaryId;
|
||||||
use error::DiaryError as DE;
|
|
||||||
use error::DiaryErrorKind as DEK;
|
use error::DiaryErrorKind as DEK;
|
||||||
|
use error::MapErrInto;
|
||||||
use result::Result;
|
use result::Result;
|
||||||
use iter::DiaryEntryIterator;
|
use iter::DiaryEntryIterator;
|
||||||
use is_in_diary::IsInDiary;
|
use iter::DiaryNameIterator;
|
||||||
|
|
||||||
|
pub trait Diary {
|
||||||
|
|
||||||
|
// create or get a new entry for today
|
||||||
|
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry>;
|
||||||
|
|
||||||
|
// create or get a new entry for now
|
||||||
|
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry>;
|
||||||
|
|
||||||
|
// Get an iterator for iterating over all entries of a Diary
|
||||||
|
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator>;
|
||||||
|
|
||||||
|
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>>;
|
||||||
|
|
||||||
|
/// Get all diary names
|
||||||
|
fn diary_names(&self) -> Result<DiaryNameIterator>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Diary<'a> {
|
|
||||||
store: &'a Store,
|
|
||||||
name: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Diary<'a> {
|
impl Diary for Store {
|
||||||
|
|
||||||
pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> {
|
// create or get a new entry for today
|
||||||
Diary {
|
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry> {
|
||||||
store: store,
|
let dt = Local::now();
|
||||||
name: name,
|
let ndt = dt.naive_local();
|
||||||
}
|
let id = DiaryId::new(String::from(diary_name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
|
||||||
|
|
||||||
|
self.retrieve(id).map_err_into(DEK::StoreReadError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create or get a new entry for today
|
// create or get a new entry for today
|
||||||
pub fn new_entry_today(&self) -> Result<Entry> {
|
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry> {
|
||||||
let dt = Local::now();
|
let dt = Local::now();
|
||||||
let ndt = dt.naive_local();
|
let ndt = dt.naive_local();
|
||||||
let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
|
let id = DiaryId::new(String::from(diary_name),
|
||||||
self.new_entry_by_id(id)
|
ndt.year(),
|
||||||
}
|
ndt.month(),
|
||||||
|
ndt.day(),
|
||||||
|
ndt.minute(),
|
||||||
|
ndt.second());
|
||||||
|
|
||||||
pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> {
|
self.retrieve(id).map_err_into(DEK::StoreReadError)
|
||||||
self.retrieve(id.with_diary_name(String::from(self.name)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
|
|
||||||
id.into_storeid()
|
|
||||||
.and_then(|id| self.store.retrieve(id))
|
|
||||||
.map(|fle| Entry::new(fle))
|
|
||||||
.map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an iterator for iterating over all entries
|
// Get an iterator for iterating over all entries
|
||||||
pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> {
|
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator> {
|
||||||
self.store
|
self.retrieve_for_module("diary")
|
||||||
.retrieve_for_module("diary")
|
.map(|iter| DiaryEntryIterator::new(self, String::from(diary_name), iter))
|
||||||
.map(|iter| DiaryEntryIterator::new(self.name, self.store, iter))
|
.map_err_into(DEK::StoreReadError)
|
||||||
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_entry(&self, entry: Entry) -> Result<()> {
|
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>> {
|
||||||
if !entry.is_in_diary(self.name) {
|
match Diary::entries(self, diary_name) {
|
||||||
return Err(DE::new(DEK::EntryNotInDiary, None));
|
|
||||||
}
|
|
||||||
let id = entry.get_location().clone();
|
|
||||||
drop(entry);
|
|
||||||
|
|
||||||
self.store.delete(id)
|
|
||||||
.map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_youngest_entry(&self) -> Option<Result<Entry>> {
|
|
||||||
match self.entries() {
|
|
||||||
Err(e) => Some(Err(e)),
|
Err(e) => Some(Err(e)),
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
entries.sorted_by(|a, b| {
|
entries
|
||||||
match (a, b) {
|
.map(|e| e.and_then(|e| e.diary_id()))
|
||||||
(&Ok(ref a), &Ok(ref b)) => {
|
.sorted_by(|a, b| {
|
||||||
let a : NaiveDateTime = a.diary_id().into();
|
match (a, b) {
|
||||||
let b : NaiveDateTime = b.diary_id().into();
|
(&Ok(ref a), &Ok(ref b)) => {
|
||||||
|
let a : NaiveDateTime = a.clone().into();
|
||||||
|
let b : NaiveDateTime = b.clone().into();
|
||||||
|
|
||||||
a.cmp(&b)
|
a.cmp(&b)
|
||||||
},
|
},
|
||||||
|
|
||||||
(&Ok(_), &Err(ref e)) => {
|
(&Ok(_), &Err(ref e)) => {
|
||||||
trace_error(e);
|
trace_error(e);
|
||||||
Ordering::Less
|
Ordering::Less
|
||||||
},
|
},
|
||||||
(&Err(ref e), &Ok(_)) => {
|
(&Err(ref e), &Ok(_)) => {
|
||||||
trace_error(e);
|
trace_error(e);
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
},
|
},
|
||||||
(&Err(ref e1), &Err(ref e2)) => {
|
(&Err(ref e1), &Err(ref e2)) => {
|
||||||
trace_error(e1);
|
trace_error(e1);
|
||||||
trace_error(e2);
|
trace_error(e2);
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}).into_iter().next()
|
})
|
||||||
|
.into_iter()
|
||||||
|
//.map(|sidres| sidres.map(|sid| DiaryId::from_storeid(&sid)))
|
||||||
|
.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &'a str {
|
/// Get all diary names
|
||||||
&self.name
|
fn diary_names(&self) -> Result<DiaryNameIterator> {
|
||||||
|
self.retrieve_for_module("diary")
|
||||||
|
.map_err_into(DEK::StoreReadError)
|
||||||
|
.map(DiaryNameIterator::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,74 +17,24 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::ops::Deref;
|
use libimagstore::store::Entry;
|
||||||
use std::ops::DerefMut;
|
|
||||||
|
|
||||||
use libimagstore::store::FileLockEntry;
|
|
||||||
use libimagentryedit::edit::Edit;
|
|
||||||
use libimagentryedit::result::Result as EditResult;
|
|
||||||
use libimagrt::runtime::Runtime;
|
|
||||||
|
|
||||||
use diaryid::DiaryId;
|
use diaryid::DiaryId;
|
||||||
use diaryid::FromStoreId;
|
use diaryid::FromStoreId;
|
||||||
|
use result::Result;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait DiaryEntry {
|
||||||
pub struct Entry<'a>(FileLockEntry<'a>);
|
fn diary_id(&self) -> Result<DiaryId>;
|
||||||
|
|
||||||
impl<'a> Deref for Entry<'a> {
|
|
||||||
type Target = FileLockEntry<'a>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &FileLockEntry<'a> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DerefMut for Entry<'a> {
|
impl DiaryEntry for Entry {
|
||||||
|
|
||||||
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Entry<'a> {
|
|
||||||
|
|
||||||
pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
|
|
||||||
Entry(fle)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the diary id for this entry.
|
/// Get the diary id for this entry.
|
||||||
///
|
///
|
||||||
/// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
|
/// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
|
||||||
pub fn diary_id(&self) -> DiaryId {
|
fn diary_id(&self) -> Result<DiaryId> {
|
||||||
DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
|
DiaryId::from_storeid(&self.get_location().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
|
|
||||||
|
|
||||||
fn into(self) -> FileLockEntry<'a> {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
|
|
||||||
|
|
||||||
fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
|
|
||||||
Entry::new(fle)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Edit for Entry<'a> {
|
|
||||||
|
|
||||||
fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
|
|
||||||
self.0.edit_content(rt)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ generate_error_module!(
|
||||||
EntryNotInDiary => "Entry not in Diary",
|
EntryNotInDiary => "Entry not in Diary",
|
||||||
IOError => "IO Error",
|
IOError => "IO Error",
|
||||||
ViewError => "Error viewing diary entry",
|
ViewError => "Error viewing diary entry",
|
||||||
IdParseError => "Error while parsing ID"
|
IdParseError => "Error while parsing ID",
|
||||||
|
DiaryNameFindingError => "Error while finding a diary name"
|
||||||
);
|
);
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ pub trait IsInDiary {
|
||||||
|
|
||||||
fn is_in_diary(&self, name: &str) -> bool;
|
fn is_in_diary(&self, name: &str) -> bool;
|
||||||
|
|
||||||
|
fn is_a_diary_entry(&self) -> bool;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IsInDiary for Entry {
|
impl IsInDiary for Entry {
|
||||||
|
@ -32,6 +34,10 @@ impl IsInDiary for Entry {
|
||||||
self.get_location().clone().is_in_diary(name)
|
self.get_location().clone().is_in_diary(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_a_diary_entry(&self) -> bool {
|
||||||
|
self.get_location().clone().is_a_diary_entry()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IsInDiary for StoreId {
|
impl IsInDiary for StoreId {
|
||||||
|
@ -40,5 +46,9 @@ impl IsInDiary for StoreId {
|
||||||
self.local().starts_with(format!("diary/{}", name))
|
self.local().starts_with(format!("diary/{}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_a_diary_entry(&self) -> bool {
|
||||||
|
self.local().starts_with("diary")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,21 +21,23 @@ use std::fmt::{Debug, Formatter, Error as FmtError};
|
||||||
use std::result::Result as RResult;
|
use std::result::Result as RResult;
|
||||||
|
|
||||||
use libimagstore::store::Store;
|
use libimagstore::store::Store;
|
||||||
|
use libimagstore::store::FileLockEntry;
|
||||||
use libimagstore::storeid::StoreIdIterator;
|
use libimagstore::storeid::StoreIdIterator;
|
||||||
|
use libimagerror::trace::trace_error;
|
||||||
|
use libimagerror::into::IntoError;
|
||||||
|
|
||||||
use diaryid::DiaryId;
|
use diaryid::DiaryId;
|
||||||
use diaryid::FromStoreId;
|
use diaryid::FromStoreId;
|
||||||
use is_in_diary::IsInDiary;
|
|
||||||
use entry::Entry as DiaryEntry;
|
|
||||||
use error::DiaryError as DE;
|
use error::DiaryError as DE;
|
||||||
use error::DiaryErrorKind as DEK;
|
use error::DiaryErrorKind as DEK;
|
||||||
|
use error::MapErrInto;
|
||||||
use result::Result;
|
use result::Result;
|
||||||
use libimagerror::trace::trace_error;
|
use is_in_diary::IsInDiary;
|
||||||
|
|
||||||
/// A iterator for iterating over diary entries
|
/// A iterator for iterating over diary entries
|
||||||
pub struct DiaryEntryIterator<'a> {
|
pub struct DiaryEntryIterator<'a> {
|
||||||
store: &'a Store,
|
store: &'a Store,
|
||||||
name: &'a str,
|
name: String,
|
||||||
iter: StoreIdIterator,
|
iter: StoreIdIterator,
|
||||||
|
|
||||||
year: Option<i32>,
|
year: Option<i32>,
|
||||||
|
@ -54,7 +56,7 @@ impl<'a> Debug for DiaryEntryIterator<'a> {
|
||||||
|
|
||||||
impl<'a> DiaryEntryIterator<'a> {
|
impl<'a> DiaryEntryIterator<'a> {
|
||||||
|
|
||||||
pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
|
pub fn new(store: &'a Store, diaryname: String, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
|
||||||
DiaryEntryIterator {
|
DiaryEntryIterator {
|
||||||
store: store,
|
store: store,
|
||||||
name: diaryname,
|
name: diaryname,
|
||||||
|
@ -87,9 +89,9 @@ impl<'a> DiaryEntryIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for DiaryEntryIterator<'a> {
|
impl<'a> Iterator for DiaryEntryIterator<'a> {
|
||||||
type Item = Result<DiaryEntry<'a>>;
|
type Item = Result<FileLockEntry<'a>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
let next = match self.iter.next() {
|
let next = match self.iter.next() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
|
@ -97,7 +99,7 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
|
||||||
};
|
};
|
||||||
debug!("Next element: {:?}", next);
|
debug!("Next element: {:?}", next);
|
||||||
|
|
||||||
if next.is_in_diary(self.name) {
|
if next.is_in_diary(&self.name) {
|
||||||
debug!("Seems to be in diary: {:?}", next);
|
debug!("Seems to be in diary: {:?}", next);
|
||||||
let id = match DiaryId::from_storeid(&next) {
|
let id = match DiaryId::from_storeid(&next) {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
|
@ -118,7 +120,6 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
|
||||||
return Some(self
|
return Some(self
|
||||||
.store
|
.store
|
||||||
.retrieve(next)
|
.retrieve(next)
|
||||||
.map(|fle| DiaryEntry::new(fle))
|
|
||||||
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
|
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -130,3 +131,37 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Get diary names.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
///
|
||||||
|
/// Does _not_ run a `unique` on the iterator!
|
||||||
|
pub struct DiaryNameIterator(StoreIdIterator);
|
||||||
|
|
||||||
|
impl DiaryNameIterator {
|
||||||
|
pub fn new(s: StoreIdIterator) -> DiaryNameIterator {
|
||||||
|
DiaryNameIterator(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for DiaryNameIterator {
|
||||||
|
type Item = Result<String>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0
|
||||||
|
.next()
|
||||||
|
.map(|s| {
|
||||||
|
s.to_str()
|
||||||
|
.map_err_into(DEK::DiaryNameFindingError)
|
||||||
|
.and_then(|s| {
|
||||||
|
s.split("diary/")
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|n| n.split("/").nth(0).map(String::from))
|
||||||
|
.ok_or(DEK::DiaryNameFindingError.into_error())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,15 @@
|
||||||
|
|
||||||
//! A diary viewer built on libimagentryview.
|
//! A diary viewer built on libimagentryview.
|
||||||
|
|
||||||
use entry::Entry;
|
use entry::DiaryEntry;
|
||||||
use error::DiaryErrorKind as DEK;
|
use error::DiaryErrorKind as DEK;
|
||||||
use error::MapErrInto;
|
use error::MapErrInto;
|
||||||
use result::Result;
|
use result::Result;
|
||||||
|
|
||||||
|
use libimagstore::store::FileLockEntry;
|
||||||
use libimagentryview::viewer::Viewer;
|
use libimagentryview::viewer::Viewer;
|
||||||
use libimagentryview::builtin::plain::PlainViewer;
|
use libimagentryview::builtin::plain::PlainViewer;
|
||||||
|
use libimagerror::trace::trace_error;
|
||||||
|
|
||||||
/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
|
/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
|
||||||
/// call some diary-type specific functions on the entries passed to this.
|
/// call some diary-type specific functions on the entries passed to this.
|
||||||
|
@ -46,10 +48,12 @@ impl DiaryViewer {
|
||||||
|
|
||||||
/// View all entries from the iterator, or stop immediately if an error occurs, returning that
|
/// View all entries from the iterator, or stop immediately if an error occurs, returning that
|
||||||
/// error.
|
/// error.
|
||||||
pub fn view_entries<'a, I: Iterator<Item = Entry<'a>>>(&self, entries: I) -> Result<()> {
|
pub fn view_entries<'a, I: Iterator<Item = FileLockEntry<'a>>>(&self, entries: I) -> Result<()> {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let id = entry.diary_id();
|
match entry.diary_id() {
|
||||||
println!("{} :\n", id);
|
Ok(id) => println!("{} :\n", id),
|
||||||
|
Err(e) => trace_error(&e),
|
||||||
|
}
|
||||||
let _ = try!(self.0
|
let _ = try!(self.0
|
||||||
.view_entry(&entry)
|
.view_entry(&entry)
|
||||||
.map_err_into(DEK::ViewError)
|
.map_err_into(DEK::ViewError)
|
||||||
|
|
|
@ -27,16 +27,16 @@
|
||||||
use mail::Mail;
|
use mail::Mail;
|
||||||
use result::Result;
|
use result::Result;
|
||||||
|
|
||||||
use libimagentryref::reference::Ref;
|
use libimagstore::store::FileLockEntry;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct MailIter<'a, I: 'a + Iterator<Item = Ref<'a>>> {
|
pub struct MailIter<'a, I: Iterator<Item = FileLockEntry<'a>>> {
|
||||||
_marker: PhantomData<&'a I>,
|
_marker: PhantomData<I>,
|
||||||
i: I,
|
i: I,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
|
impl<'a, I: Iterator<Item = FileLockEntry<'a>>> MailIter<'a, I> {
|
||||||
|
|
||||||
pub fn new(i: I) -> MailIter<'a, I> {
|
pub fn new(i: I) -> MailIter<'a, I> {
|
||||||
MailIter { _marker: PhantomData, i: i }
|
MailIter { _marker: PhantomData, i: i }
|
||||||
|
@ -44,12 +44,11 @@ impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, I: Iterator<Item = Ref<'a>>> Iterator for MailIter<'a, I> {
|
impl<'a, I: Iterator<Item = FileLockEntry<'a>>> Iterator for MailIter<'a, I> {
|
||||||
|
|
||||||
type Item = Result<Mail<'a>>;
|
type Item = Result<Mail<'a>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<Mail<'a>>> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.i.next().map(Mail::from_ref)
|
self.i.next().map(Mail::from_fle)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,10 @@ use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use libimagstore::store::Store;
|
use libimagstore::store::Store;
|
||||||
|
use libimagstore::store::FileLockEntry;
|
||||||
use libimagentryref::reference::Ref;
|
use libimagentryref::reference::Ref;
|
||||||
use libimagentryref::flags::RefFlags;
|
use libimagentryref::flags::RefFlags;
|
||||||
|
use libimagentryref::refstore::RefStore;
|
||||||
|
|
||||||
use email::MimeMessage;
|
use email::MimeMessage;
|
||||||
use email::results::ParsingResult as EmailParsingResult;
|
use email::results::ParsingResult as EmailParsingResult;
|
||||||
|
@ -47,7 +49,7 @@ impl From<String> for Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mail<'a>(Ref<'a>, Buffer);
|
pub struct Mail<'a>(FileLockEntry<'a>, Buffer);
|
||||||
|
|
||||||
impl<'a> Mail<'a> {
|
impl<'a> Mail<'a> {
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ impl<'a> Mail<'a> {
|
||||||
let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
|
let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
|
||||||
let p = PathBuf::from(p.as_ref());
|
let p = PathBuf::from(p.as_ref());
|
||||||
|
|
||||||
Ref::create_with_hasher(store, p, f, h)
|
store.create_with_hasher(p, f, h)
|
||||||
.map_err_into(MEK::RefCreationError)
|
.map_err_into(MEK::RefCreationError)
|
||||||
.and_then(|reference| {
|
.and_then(|reference| {
|
||||||
debug!("Build reference file: {:?}", reference);
|
debug!("Build reference file: {:?}", reference);
|
||||||
|
@ -79,20 +81,19 @@ impl<'a> Mail<'a> {
|
||||||
/// Opens a mail by the passed hash
|
/// Opens a mail by the passed hash
|
||||||
pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
|
pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
|
||||||
debug!("Opening Mail by Hash");
|
debug!("Opening Mail by Hash");
|
||||||
Ref::get_by_hash(store, String::from(hash.as_ref()))
|
store.get_by_hash(String::from(hash.as_ref()))
|
||||||
.map_err_into(MEK::FetchByHashError)
|
.map_err_into(MEK::FetchByHashError)
|
||||||
.map_err_into(MEK::FetchError)
|
.map_err_into(MEK::FetchError)
|
||||||
.and_then(|o| match o {
|
.and_then(|o| match o {
|
||||||
Some(r) => Mail::from_ref(r).map(Some),
|
Some(r) => Mail::from_fle(r).map(Some),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement me as TryFrom as soon as it is stable
|
/// Implement me as TryFrom as soon as it is stable
|
||||||
pub fn from_ref(r: Ref<'a>) -> Result<Mail> {
|
pub fn from_fle(fle: FileLockEntry<'a>) -> Result<Mail<'a>> {
|
||||||
debug!("Building Mail object from Ref: {:?}", r);
|
fle.fs_file()
|
||||||
r.fs_file()
|
|
||||||
.map_err_into(MEK::RefHandlingError)
|
.map_err_into(MEK::RefHandlingError)
|
||||||
.and_then(|path| File::open(path).map_err_into(MEK::IOError))
|
.and_then(|path| File::open(path).map_err_into(MEK::IOError))
|
||||||
.and_then(|mut file| {
|
.and_then(|mut file| {
|
||||||
|
@ -102,7 +103,7 @@ impl<'a> Mail<'a> {
|
||||||
.map_err_into(MEK::IOError)
|
.map_err_into(MEK::IOError)
|
||||||
})
|
})
|
||||||
.map(Buffer::from)
|
.map(Buffer::from)
|
||||||
.map(|buffer| Mail(r, buffer))
|
.map(|buffer| Mail(fle, buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_field(&self, field: &str) -> Result<Option<String>> {
|
pub fn get_field(&self, field: &str) -> Result<Option<String>> {
|
||||||
|
|
|
@ -23,7 +23,10 @@ generate_error_module!(
|
||||||
StoreError => "Store Error",
|
StoreError => "Store Error",
|
||||||
StoreIdError => "Store Id handling error",
|
StoreIdError => "Store Id handling error",
|
||||||
ImportError => "Error importing",
|
ImportError => "Error importing",
|
||||||
UTF8Error => "Encountered non-UTF8 characters while reading input"
|
UTF8Error => "Encountered non-UTF8 characters while reading input",
|
||||||
|
HeaderFieldMissing => "Header field missing",
|
||||||
|
HeaderTypeError => "Header field type error",
|
||||||
|
UuidParserError => "Uuid parser error"
|
||||||
);
|
);
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -48,4 +48,5 @@ module_entry_path_mod!("todo");
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
pub mod taskstore;
|
||||||
|
|
||||||
|
|
|
@ -17,271 +17,31 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use error::TodoErrorKind as TEK;
|
||||||
use std::ops::{Deref, DerefMut};
|
use error::MapErrInto;
|
||||||
use std::io::BufRead;
|
|
||||||
use std::result::Result as RResult;
|
|
||||||
|
|
||||||
use toml::Value;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use task_hookrs::task::Task as TTask;
|
|
||||||
use task_hookrs::import::{import_task, import_tasks};
|
|
||||||
|
|
||||||
use libimagstore::store::{FileLockEntry, Store};
|
|
||||||
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
|
|
||||||
use module_path::ModuleEntryPath;
|
|
||||||
|
|
||||||
use error::{TodoErrorKind as TEK, MapErrInto};
|
|
||||||
use result::Result;
|
use result::Result;
|
||||||
|
|
||||||
/// Task struct containing a `FileLockEntry`
|
use libimagstore::store::Entry;
|
||||||
#[derive(Debug)]
|
use libimagerror::into::IntoError;
|
||||||
pub struct Task<'a>(FileLockEntry<'a>);
|
|
||||||
|
|
||||||
impl<'a> Task<'a> {
|
use uuid::Uuid;
|
||||||
|
use toml::Value;
|
||||||
/// Concstructs a new `Task` with a `FileLockEntry`
|
use toml_query::read::TomlValueReadExt;
|
||||||
pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
|
|
||||||
Task(fle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
|
|
||||||
let mut line = String::new();
|
|
||||||
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
|
|
||||||
import_task(&line.as_str())
|
|
||||||
.map_err_into(TEK::ImportError)
|
|
||||||
.and_then(|t| {
|
|
||||||
let uuid = t.uuid().clone();
|
|
||||||
t.into_task(store).map(|t| (t, line, uuid))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a task from an import string. That is: read the imported string, get the UUID from it
|
|
||||||
/// and try to load this UUID from store.
|
|
||||||
///
|
|
||||||
/// Possible return values are:
|
|
||||||
///
|
|
||||||
/// * Ok(Ok(Task))
|
|
||||||
/// * Ok(Err(String)) - where the String is the String read from the `r` parameter
|
|
||||||
/// * Err(_) - where the error is an error that happened during evaluation
|
|
||||||
///
|
|
||||||
pub fn get_from_import<R>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
|
|
||||||
where R: BufRead
|
|
||||||
{
|
|
||||||
let mut line = String::new();
|
|
||||||
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
|
|
||||||
Task::get_from_string(store, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a task from a String. The String is expected to contain the JSON-representation of the
|
|
||||||
/// Task to get from the store (only the UUID really matters in this case)
|
|
||||||
///
|
|
||||||
/// For an explanation on the return values see `Task::get_from_import()`.
|
|
||||||
pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
|
|
||||||
import_task(s.as_str())
|
|
||||||
.map_err_into(TEK::ImportError)
|
|
||||||
.map(|t| t.uuid().clone())
|
|
||||||
.and_then(|uuid| Task::get_from_uuid(store, uuid))
|
|
||||||
.and_then(|o| match o {
|
|
||||||
None => Ok(Err(s)),
|
|
||||||
Some(t) => Ok(Ok(t)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a task from an UUID.
|
|
||||||
///
|
|
||||||
/// If there is no task with this UUID, this returns `Ok(None)`.
|
|
||||||
pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
|
|
||||||
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
|
|
||||||
.into_storeid()
|
|
||||||
.and_then(|store_id| store.get(store_id))
|
|
||||||
.map(|o| o.map(Task::new))
|
|
||||||
.map_err_into(TEK::StoreError)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
|
|
||||||
/// implicitely create the task if it does not exist.
|
|
||||||
pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
|
|
||||||
let mut line = String::new();
|
|
||||||
try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
|
|
||||||
Task::retrieve_from_string(store, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a task from a String. The String is expected to contain the JSON-representation of
|
|
||||||
/// the Task to retrieve from the store (only the UUID really matters in this case)
|
|
||||||
pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
|
|
||||||
Task::get_from_string(store, s)
|
|
||||||
.and_then(|opt| match opt {
|
|
||||||
Ok(task) => Ok(task),
|
|
||||||
Err(string) => import_task(string.as_str())
|
|
||||||
.map_err_into(TEK::ImportError)
|
|
||||||
.and_then(|t| t.into_task(store)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
|
|
||||||
use serde_json::ser::to_string as serde_to_string;
|
|
||||||
use task_hookrs::status::TaskStatus;
|
|
||||||
|
|
||||||
for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
|
|
||||||
match res_ttask {
|
|
||||||
Ok(ttask) => {
|
|
||||||
if counter % 2 == 1 {
|
|
||||||
// Only every second task is needed, the first one is the
|
|
||||||
// task before the change, and the second one after
|
|
||||||
// the change. The (maybe modified) second one is
|
|
||||||
// expected by taskwarrior.
|
|
||||||
match serde_to_string(&ttask).map_err_into(TEK::ImportError) {
|
|
||||||
// use println!() here, as we talk with TW
|
|
||||||
Ok(val) => println!("{}", val),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taskwarrior does not have the concept of deleted tasks, but only modified
|
|
||||||
// ones.
|
|
||||||
//
|
|
||||||
// Here we check if the status of a task is deleted and if yes, we delete it
|
|
||||||
// from the store.
|
|
||||||
if *ttask.status() == TaskStatus::Deleted {
|
|
||||||
match Task::delete_by_uuid(store, *ttask.uuid()) {
|
|
||||||
Ok(_) => info!("Deleted task {}", *ttask.uuid()),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // end if c % 2
|
|
||||||
},
|
|
||||||
Err(e) => return Err(e).map_err_into(TEK::ImportError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
|
|
||||||
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
|
|
||||||
.into_storeid()
|
|
||||||
.and_then(|id| store.delete(id))
|
|
||||||
.map_err_into(TEK::StoreError)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
|
|
||||||
store.retrieve_for_module("todo/taskwarrior")
|
|
||||||
.map_err_into(TEK::StoreError)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all(store: &Store) -> Result<TaskIterator> {
|
|
||||||
Task::all_as_ids(store)
|
|
||||||
.map(|iter| TaskIterator::new(store, iter))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub trait Task {
|
||||||
|
fn get_uuid(&self) -> Result<Uuid>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for Task<'a> {
|
impl Task for Entry {
|
||||||
type Target = FileLockEntry<'a>;
|
fn get_uuid(&self) -> Result<Uuid> {
|
||||||
|
match self.get_header().read("todo.uuid") {
|
||||||
fn deref(&self) -> &FileLockEntry<'a> {
|
Ok(Some(&Value::String(ref uuid))) => {
|
||||||
&self.0
|
Uuid::parse_str(uuid).map_err_into(TEK::UuidParserError)
|
||||||
}
|
},
|
||||||
|
Ok(Some(_)) => Err(TEK::HeaderTypeError.into_error()),
|
||||||
}
|
Ok(None) => Err(TEK::HeaderFieldMissing.into_error()),
|
||||||
|
Err(e) => Err(e).map_err_into(TEK::StoreError),
|
||||||
impl<'a> DerefMut for Task<'a> {
|
|
||||||
|
|
||||||
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
|
|
||||||
pub trait IntoTask<'a> {
|
|
||||||
|
|
||||||
/// # Usage
|
|
||||||
/// ```ignore
|
|
||||||
/// use std::io::stdin;
|
|
||||||
///
|
|
||||||
/// use task_hookrs::task::Task;
|
|
||||||
/// use task_hookrs::import::import;
|
|
||||||
/// use libimagstore::store::{Store, FileLockEntry};
|
|
||||||
///
|
|
||||||
/// if let Ok(task_hookrs_task) = import(stdin()) {
|
|
||||||
/// // Store is given at runtime
|
|
||||||
/// let task = task_hookrs_task.into_filelockentry(store);
|
|
||||||
/// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoTask<'a> for TTask {
|
|
||||||
|
|
||||||
fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
|
|
||||||
use toml_query::read::TomlValueReadExt;
|
|
||||||
use toml_query::set::TomlValueSetExt;
|
|
||||||
|
|
||||||
let uuid = self.uuid();
|
|
||||||
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
|
|
||||||
.into_storeid()
|
|
||||||
.map_err_into(TEK::StoreIdError)
|
|
||||||
.and_then(|id| {
|
|
||||||
store.retrieve(id)
|
|
||||||
.map_err_into(TEK::StoreError)
|
|
||||||
.and_then(|mut fle| {
|
|
||||||
{
|
|
||||||
let hdr = fle.get_header_mut();
|
|
||||||
if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
|
|
||||||
try!(hdr
|
|
||||||
.set("todo", Value::Table(BTreeMap::new()))
|
|
||||||
.map_err_into(TEK::StoreError));
|
|
||||||
}
|
|
||||||
|
|
||||||
try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
|
|
||||||
.map_err_into(TEK::StoreError));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the errors above have returned the function, everything is fine
|
|
||||||
Ok(Task::new(fle))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
trait FromStoreId {
|
|
||||||
fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FromStoreId for Task<'a> {
|
|
||||||
|
|
||||||
fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
|
|
||||||
store.retrieve(id)
|
|
||||||
.map_err_into(TEK::StoreError)
|
|
||||||
.map(Task::new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TaskIterator<'a> {
|
|
||||||
store: &'a Store,
|
|
||||||
iditer: StoreIdIterator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TaskIterator<'a> {
|
|
||||||
|
|
||||||
pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
|
|
||||||
TaskIterator {
|
|
||||||
store: store,
|
|
||||||
iditer: iditer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for TaskIterator<'a> {
|
|
||||||
type Item = Result<Task<'a>>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<Task<'a>>> {
|
|
||||||
self.iditer.next().map(|id| Task::from_storeid(self.store, id))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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 std::fmt::Debug;
|
||||||
|
|
||||||
use libimagstore::store::Entry;
|
use libimagstore::store::Entry;
|
||||||
use libimagstore::store::FileLockEntry;
|
|
||||||
use libimagstore::store::Store;
|
use libimagstore::store::Store;
|
||||||
use libimagstore::storeid::StoreId;
|
use libimagstore::storeid::StoreId;
|
||||||
use libimagstore::storeid::IntoStoreId;
|
use libimagstore::storeid::IntoStoreId;
|
||||||
use libimagutil::debug_result::*;
|
use libimagutil::debug_result::*;
|
||||||
|
use libimagerror::into::IntoError;
|
||||||
|
|
||||||
use toml_query::read::TomlValueReadExt;
|
use toml_query::read::TomlValueReadExt;
|
||||||
use toml_query::set::TomlValueSetExt;
|
use toml_query::set::TomlValueSetExt;
|
||||||
|
@ -58,37 +58,32 @@ use url::Url;
|
||||||
use crypto::sha1::Sha1;
|
use crypto::sha1::Sha1;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
|
|
||||||
/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
|
pub trait Link {
|
||||||
pub struct Link<'a> {
|
|
||||||
link: FileLockEntry<'a>
|
fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>>;
|
||||||
|
|
||||||
|
fn get_url(&self) -> Result<Option<Url>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Link<'a> {
|
impl Link for Entry {
|
||||||
|
|
||||||
pub fn new(fle: FileLockEntry<'a>) -> Link<'a> {
|
fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>> {
|
||||||
Link { link: fle }
|
self.get_header()
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a link Url object from a `FileLockEntry`, ignore errors.
|
|
||||||
fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
|
|
||||||
file.get_header()
|
|
||||||
.read("imag.content.url")
|
.read("imag.content.url")
|
||||||
.ok()
|
.map_err_into(LEK::EntryHeaderReadError)
|
||||||
.and_then(|opt| match opt {
|
.and_then(|opt| match opt {
|
||||||
Some(&Value::String(ref s)) => {
|
Some(&Value::String(ref s)) => {
|
||||||
debug!("Found url, parsing: {:?}", s);
|
debug!("Found url, parsing: {:?}", s);
|
||||||
Url::parse(&s[..]).ok()
|
Url::parse(&s[..]).map_err_into(LEK::InvalidUri).map(Some)
|
||||||
},
|
},
|
||||||
_ => None
|
Some(_) => Err(LEK::LinkParserFieldTypeError.into_error()),
|
||||||
|
None => Ok(None),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_url(&self) -> Result<Option<Url>> {
|
fn get_url(&self) -> Result<Option<Url>> {
|
||||||
let opt = self.link
|
match self.get_header().read("imag.content.url") {
|
||||||
.get_header()
|
|
||||||
.read("imag.content.url");
|
|
||||||
|
|
||||||
match opt {
|
|
||||||
Ok(Some(&Value::String(ref s))) => {
|
Ok(Some(&Value::String(ref s))) => {
|
||||||
Url::parse(&s[..])
|
Url::parse(&s[..])
|
||||||
.map(Some)
|
.map(Some)
|
||||||
|
@ -261,23 +256,32 @@ pub mod iter {
|
||||||
type Item = Result<Url>;
|
type Item = Result<Url>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
use super::get_external_link_from_file;
|
use external::Link;
|
||||||
|
|
||||||
self.0
|
loop {
|
||||||
.next()
|
let next = self.0
|
||||||
.map(|id| {
|
.next()
|
||||||
debug!("Retrieving entry for id: '{:?}'", id);
|
.map(|id| {
|
||||||
self.1
|
debug!("Retrieving entry for id: '{:?}'", id);
|
||||||
.retrieve(id.clone())
|
self.1
|
||||||
.map_err_into(LEK::StoreReadError)
|
.retrieve(id.clone())
|
||||||
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
|
.map_err_into(LEK::StoreReadError)
|
||||||
.and_then(|f| {
|
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
|
||||||
debug!("Store::retrieve({:?}) succeeded", id);
|
.and_then(|f| {
|
||||||
debug!("getting external link from file now");
|
debug!("Store::retrieve({:?}) succeeded", id);
|
||||||
get_external_link_from_file(&f)
|
debug!("getting external link from file now");
|
||||||
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
|
f.get_link_uri_from_filelockentry()
|
||||||
})
|
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
|
||||||
})
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Some(Ok(Some(link))) => return Some(Ok(link)),
|
||||||
|
Some(Ok(None)) => continue,
|
||||||
|
Some(Err(e)) => return Some(Err(e)),
|
||||||
|
None => return None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -291,11 +295,6 @@ pub fn is_external_link_storeid<A: AsRef<StoreId> + Debug>(id: A) -> bool {
|
||||||
id.as_ref().local().starts_with("links/external")
|
id.as_ref().local().starts_with("links/external")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
|
|
||||||
Link::get_link_uri_from_filelockentry(entry) // TODO: Do not hide error by using this function
|
|
||||||
.ok_or(LE::new(LEK::StoreReadError, None))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
|
/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
|
||||||
/// link in an entry, but internal links to other entries which serve as external links, as one
|
/// link in an entry, but internal links to other entries which serve as external links, as one
|
||||||
/// entry in the store can only have one external link.
|
/// entry in the store can only have one external link.
|
||||||
|
|
|
@ -18,8 +18,8 @@ itertools = "0.5"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
rust-crypto = "0.2"
|
rust-crypto = "0.2"
|
||||||
toml = "^0.4"
|
toml = "^0.4"
|
||||||
walkdir = "1.0.*"
|
|
||||||
toml-query = "0.3.0"
|
toml-query = "0.3.0"
|
||||||
|
walkdir = "1.0.*"
|
||||||
|
|
||||||
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
|
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
|
||||||
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
|
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
|
||||||
|
|
|
@ -24,8 +24,6 @@ use std::result::Result as RResult;
|
||||||
use crypto::sha1::Sha1;
|
use crypto::sha1::Sha1;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
|
|
||||||
use libimagerror::into::IntoError;
|
|
||||||
|
|
||||||
use hasher::Hasher;
|
use hasher::Hasher;
|
||||||
use result::Result;
|
use result::Result;
|
||||||
use error::RefErrorKind as REK;
|
use error::RefErrorKind as REK;
|
||||||
|
@ -54,16 +52,13 @@ impl Hasher for NBytesHasher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_hash<R: Read>(&mut self, _: &PathBuf, contents: &mut R) -> Result<String> {
|
fn create_hash<R: Read>(&mut self, _: &PathBuf, contents: &mut R) -> Result<String> {
|
||||||
let s = contents
|
let s = try!(contents
|
||||||
.bytes()
|
.bytes()
|
||||||
.take(self.n)
|
.take(self.n)
|
||||||
.collect::<RResult<Vec<u8>, _>>()
|
.collect::<RResult<Vec<u8>, _>>()
|
||||||
.map_err_into(REK::IOError)
|
.map_err_into(REK::IOError)
|
||||||
.and_then(|v| String::from_utf8(v).map_err_into(REK::IOError))
|
.and_then(|v| String::from_utf8(v).map_err_into(REK::UTF8Error)));
|
||||||
.map_err(Box::new)
|
self.hasher.input_str(&s[..]);
|
||||||
.map_err(|e| REK::UTF8Error.into_error_with_cause(e))
|
|
||||||
.map_err_into(REK::IOError);
|
|
||||||
self.hasher.input_str(&try!(s)[..]);
|
|
||||||
Ok(self.hasher.result_str())
|
Ok(self.hasher.result_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,4 +52,6 @@ pub mod hasher;
|
||||||
pub mod hashers;
|
pub mod hashers;
|
||||||
pub mod lister;
|
pub mod lister;
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
|
pub mod refstore;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
|
mod util;
|
||||||
|
|
|
@ -20,13 +20,14 @@
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use libimagentrylist::lister::Lister;
|
use libimagentrylist::lister::Lister;
|
||||||
use libimagentrylist::result::Result;
|
use libimagentrylist::result::Result;
|
||||||
use libimagerror::trace::trace_error;
|
use libimagerror::trace::trace_error;
|
||||||
use libimagstore::store::FileLockEntry;
|
use libimagstore::store::FileLockEntry;
|
||||||
use libimagerror::into::IntoError;
|
|
||||||
use libimagentrylist::error::ListErrorKind as LEK;
|
use libimagentrylist::error::ListErrorKind as LEK;
|
||||||
|
use libimagentrylist::error as lerror;
|
||||||
|
|
||||||
use reference::Ref;
|
use reference::Ref;
|
||||||
use error::MapErrInto;
|
use error::MapErrInto;
|
||||||
|
@ -88,15 +89,45 @@ impl Lister for RefLister {
|
||||||
debug!("fold({:?}, {:?})", accu, entry);
|
debug!("fold({:?}, {:?})", accu, entry);
|
||||||
let r = accu.and_then(|_| {
|
let r = accu.and_then(|_| {
|
||||||
debug!("Listing Entry: {:?}", entry);
|
debug!("Listing Entry: {:?}", entry);
|
||||||
lister_fn(entry,
|
{
|
||||||
self.check_dead,
|
let is_dead = if self.check_dead {
|
||||||
self.check_changed,
|
if try!(entry.fs_link_exists()) {
|
||||||
self.check_changed_content,
|
"dead"
|
||||||
self.check_changed_permiss)
|
} else {
|
||||||
|
"alive"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"not checked"
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_changed = if self.check_changed {
|
||||||
|
if check_changed(entry.deref()) { "changed" } else { "unchanged" }
|
||||||
|
} else {
|
||||||
|
"not checked"
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_changed_content = if self.check_changed_content {
|
||||||
|
if check_changed_content(entry.deref()) { "changed" } else { "unchanged" }
|
||||||
|
} else {
|
||||||
|
"not checked"
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_changed_permiss = if self.check_changed_permiss {
|
||||||
|
if check_changed_permiss(entry.deref()) { "changed" } else { "unchanged" }
|
||||||
|
} else {
|
||||||
|
"not checked"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!("{} | {} | {} | {} | {} | {}",
|
||||||
|
is_dead,
|
||||||
|
is_changed,
|
||||||
|
is_changed_content,
|
||||||
|
is_changed_permiss,
|
||||||
|
entry.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
|
||||||
|
entry.get_location()))
|
||||||
|
}
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
write!(stdout(), "{}\n", s)
|
lerror::MapErrInto::map_err_into(write!(stdout(), "{}\n", s), LEK::FormatError)
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| LEK::FormatError.into_error_with_cause(e))
|
|
||||||
})
|
})
|
||||||
.map_err_into(REK::RefToDisplayError)
|
.map_err_into(REK::RefToDisplayError)
|
||||||
})
|
})
|
||||||
|
@ -104,73 +135,16 @@ impl Lister for RefLister {
|
||||||
(r, i + 1)
|
(r, i + 1)
|
||||||
});
|
});
|
||||||
debug!("Iterated over {} entries", n);
|
debug!("Iterated over {} entries", n);
|
||||||
r.map_err(|e| LEK::FormatError.into_error_with_cause(Box::new(e)))
|
lerror::MapErrInto::map_err_into(r, LEK::FormatError)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lister_fn(fle: FileLockEntry,
|
fn check_changed<R: Ref>(r: &R) -> bool {
|
||||||
do_check_dead: bool,
|
|
||||||
do_check_changed: bool,
|
|
||||||
do_check_changed_content: bool,
|
|
||||||
do_check_changed_permiss: bool) -> Result<String>
|
|
||||||
{
|
|
||||||
Ref::from_filelockentry(fle)
|
|
||||||
.map(|r| {
|
|
||||||
let is_dead = if do_check_dead {
|
|
||||||
if check_dead(&r) { "dead" } else { "alive" }
|
|
||||||
} else {
|
|
||||||
"not checked"
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_changed = if do_check_changed {
|
|
||||||
if check_changed(&r) { "changed" } else { "unchanged" }
|
|
||||||
} else {
|
|
||||||
"not checked"
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_changed_content = if do_check_changed_content {
|
|
||||||
if check_changed_content(&r) { "changed" } else { "unchanged" }
|
|
||||||
} else {
|
|
||||||
"not checked"
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_changed_permiss = if do_check_changed_permiss {
|
|
||||||
if check_changed_permiss(&r) { "changed" } else { "unchanged" }
|
|
||||||
} else {
|
|
||||||
"not checked"
|
|
||||||
};
|
|
||||||
|
|
||||||
format!("{} | {} | {} | {} | {} | {}",
|
|
||||||
is_dead,
|
|
||||||
is_changed,
|
|
||||||
is_changed_content,
|
|
||||||
is_changed_permiss,
|
|
||||||
r.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
|
|
||||||
r.get_location())
|
|
||||||
})
|
|
||||||
.map_err(|e| LEK::FormatError.into_error_with_cause(Box::new(e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_dead(r: &Ref) -> bool {
|
|
||||||
match r.fs_link_exists() {
|
|
||||||
Ok(b) => b,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Could not check whether the ref {} exists on the FS:", r);
|
|
||||||
trace_error(&e);
|
|
||||||
|
|
||||||
// We continue here and tell the callee that this reference is dead, what is kind of
|
|
||||||
// true actually, as we might not have access to it right now
|
|
||||||
true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_changed(r: &Ref) -> bool {
|
|
||||||
check_changed_content(r) && check_changed_permiss(r)
|
check_changed_content(r) && check_changed_permiss(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_changed_content(r: &Ref) -> bool {
|
fn check_changed_content<R: Ref>(r: &R) -> bool {
|
||||||
let eq = r.get_current_hash()
|
let eq = r.get_current_hash()
|
||||||
.and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored)))
|
.and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored)))
|
||||||
.map(|(hash, stored)| hash == stored);
|
.map(|(hash, stored)| hash == stored);
|
||||||
|
@ -178,7 +152,7 @@ fn check_changed_content(r: &Ref) -> bool {
|
||||||
match eq {
|
match eq {
|
||||||
Ok(eq) => eq,
|
Ok(eq) => eq,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Could not check whether the ref {} changed on the FS:", r);
|
warn!("Could not check whether the ref changed on the FS");
|
||||||
trace_error(&e);
|
trace_error(&e);
|
||||||
|
|
||||||
// We continue here and tell the callee that this reference is unchanged
|
// We continue here and tell the callee that this reference is unchanged
|
||||||
|
@ -187,7 +161,7 @@ fn check_changed_content(r: &Ref) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_changed_permiss(_: &Ref) -> bool {
|
fn check_changed_permiss<R: Ref>(_: &R) -> bool {
|
||||||
warn!("Permission changes tracking not supported yet.");
|
warn!("Permission changes tracking not supported yet.");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,238 +21,104 @@
|
||||||
//! files outside of the imag store.
|
//! files outside of the imag store.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fmt::{Display, Error as FmtError, Formatter};
|
|
||||||
use std::fs::Permissions;
|
use std::fs::Permissions;
|
||||||
use std::result::Result as RResult;
|
|
||||||
|
|
||||||
use libimagstore::store::FileLockEntry;
|
use libimagstore::store::Entry;
|
||||||
use libimagstore::storeid::StoreId;
|
|
||||||
use libimagstore::storeid::IntoStoreId;
|
|
||||||
use libimagstore::store::Store;
|
|
||||||
use libimagerror::into::IntoError;
|
use libimagerror::into::IntoError;
|
||||||
|
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
use toml_query::read::TomlValueReadExt;
|
use toml_query::read::TomlValueReadExt;
|
||||||
use toml_query::set::TomlValueSetExt;
|
use toml_query::set::TomlValueSetExt;
|
||||||
use toml_query::insert::TomlValueInsertExt;
|
|
||||||
|
|
||||||
use error::RefErrorKind as REK;
|
use error::RefErrorKind as REK;
|
||||||
use error::MapErrInto;
|
use error::MapErrInto;
|
||||||
use flags::RefFlags;
|
|
||||||
use result::Result;
|
use result::Result;
|
||||||
use hasher::*;
|
use hasher::*;
|
||||||
use module_path::ModuleEntryPath;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait Ref {
|
||||||
pub struct Ref<'a>(FileLockEntry<'a>);
|
|
||||||
|
|
||||||
impl<'a> Ref<'a> {
|
|
||||||
|
|
||||||
/// Try to build a Ref object based on an existing FileLockEntry object
|
|
||||||
pub fn from_filelockentry(fle: FileLockEntry<'a>) -> Result<Ref<'a>> {
|
|
||||||
Ref::read_reference(&fle).map(|_| Ref(fle))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to get `si` as Ref object from the store
|
|
||||||
pub fn get(store: &'a Store, si: StoreId) -> Result<Ref<'a>> {
|
|
||||||
match store.get(si) {
|
|
||||||
Err(e) => return Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
|
|
||||||
Ok(None) => return Err(REK::RefNotInStore.into_error()),
|
|
||||||
Ok(Some(fle)) => Ref::from_filelockentry(fle),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a Ref object from the store by hash.
|
|
||||||
///
|
|
||||||
/// Returns None if the hash cannot be found.
|
|
||||||
pub fn get_by_hash(store: &'a Store, hash: String) -> Result<Option<Ref<'a>>> {
|
|
||||||
ModuleEntryPath::new(hash)
|
|
||||||
.into_storeid()
|
|
||||||
.and_then(|id| store.get(id))
|
|
||||||
.map(|opt_fle| opt_fle.map(|fle| Ref(fle)))
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::StoreReadError.into_error_with_cause(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a ref by hash
|
|
||||||
///
|
|
||||||
/// If the returned Result contains an error, the ref might not be deleted.
|
|
||||||
pub fn delete_by_hash(store: &'a Store, hash: String) -> Result<()> {
|
|
||||||
ModuleEntryPath::new(hash)
|
|
||||||
.into_storeid()
|
|
||||||
.and_then(|id| store.delete(id))
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_reference(fle: &FileLockEntry<'a>) -> Result<PathBuf> {
|
|
||||||
match fle.get_header().read("ref.path") {
|
|
||||||
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
|
|
||||||
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
|
|
||||||
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
|
|
||||||
Err(e) => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_with_hasher<H: Hasher>(store: &'a Store, pb: PathBuf, flags: RefFlags, mut h: H)
|
|
||||||
-> Result<Ref<'a>>
|
|
||||||
{
|
|
||||||
if !pb.exists() {
|
|
||||||
return Err(REK::RefTargetDoesNotExist.into_error());
|
|
||||||
}
|
|
||||||
if flags.get_content_hashing() && pb.is_dir() {
|
|
||||||
return Err(REK::RefTargetCannotBeHashed.into_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
|
|
||||||
try!(File::open(pb.clone())
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::RefTargetFileCannotBeOpened.into_error_with_cause(e))
|
|
||||||
|
|
||||||
// If we were able to open this file,
|
|
||||||
// we hash the contents of the file and return (file, hash)
|
|
||||||
.and_then(|mut file| {
|
|
||||||
let opt_contenthash = if flags.get_content_hashing() {
|
|
||||||
Some(try!(h.create_hash(&pb, &mut file)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((file, opt_contenthash))
|
|
||||||
})
|
|
||||||
|
|
||||||
// and then we get the permissions if we have to
|
|
||||||
// and return (file, content hash, permissions)
|
|
||||||
.and_then(|(file, opt_contenthash)| {
|
|
||||||
let opt_permissions = if flags.get_permission_tracking() {
|
|
||||||
Some(try!(file
|
|
||||||
.metadata()
|
|
||||||
.map(|md| md.permissions())
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((opt_contenthash, opt_permissions))
|
|
||||||
})
|
|
||||||
|
|
||||||
// and then we try to canonicalize the PathBuf, because we want to store a
|
|
||||||
// canonicalized path
|
|
||||||
// and return (file, content hash, permissions, canonicalized path)
|
|
||||||
.and_then(|(opt_contenthash, opt_permissions)| {
|
|
||||||
pb.canonicalize()
|
|
||||||
.map(|can| (opt_contenthash, opt_permissions, can))
|
|
||||||
// if PathBuf::canonicalize() failed, build an error from the return value
|
|
||||||
.map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(Box::new(e)))
|
|
||||||
})
|
|
||||||
|
|
||||||
// and then we hash the canonicalized path
|
|
||||||
// and return (file, content hash, permissions, canonicalized path, path hash)
|
|
||||||
.and_then(|(opt_contenthash, opt_permissions, can)| {
|
|
||||||
let path_hash = try!(Ref::hash_path(&can)
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::PathHashingError.into_error_with_cause(e))
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok((opt_contenthash, opt_permissions, can, path_hash))
|
|
||||||
})
|
|
||||||
|
|
||||||
// and then we convert the PathBuf of the canonicalized path to a String to be able
|
|
||||||
// to save it in the Ref FileLockEntry obj
|
|
||||||
// and return
|
|
||||||
// (file, content hash, permissions, canonicalized path as String, path hash)
|
|
||||||
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
|
|
||||||
match can.to_str().map(String::from) {
|
|
||||||
// UTF convert error in PathBuf::to_str(),
|
|
||||||
None => Err(REK::PathUTF8Error.into_error()),
|
|
||||||
Some(can) => Ok((opt_conhash, opt_perm, can, path_hash))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// and then we create the FileLockEntry in the Store
|
|
||||||
// and return (filelockentry, content hash, permissions, canonicalized path)
|
|
||||||
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
|
|
||||||
let fle = try!(store
|
|
||||||
.create(ModuleEntryPath::new(path_hash))
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok((fle, opt_conhash, opt_perm, can))
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
for tpl in [
|
|
||||||
Some((String::from("ref"), Value::Table(BTreeMap::new()))),
|
|
||||||
Some((String::from("ref.permissions"), Value::Table(BTreeMap::new()))),
|
|
||||||
Some((String::from("ref.path"), Value::String(canonical_path))),
|
|
||||||
Some((String::from("ref.content_hash"), Value::Table(BTreeMap::new()))),
|
|
||||||
|
|
||||||
content_hash.map(|hash| {
|
|
||||||
(format!("ref.content_hash.{}", h.hash_name()), Value::String(hash))
|
|
||||||
}),
|
|
||||||
permissions.map(|p| {
|
|
||||||
(String::from("ref.permissions.ro"), Value::Boolean(p.readonly()))
|
|
||||||
}),
|
|
||||||
].into_iter()
|
|
||||||
{
|
|
||||||
match tpl {
|
|
||||||
&Some((ref s, ref v)) => {
|
|
||||||
match fle.get_header_mut().insert(s, v.clone()) {
|
|
||||||
Ok(None) => {
|
|
||||||
debug!("Header insert worked");
|
|
||||||
}
|
|
||||||
Ok(Some(val)) => {
|
|
||||||
debug!("Overwrote: {}, which was: {:?}", s, val);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
let e = Box::new(e);
|
|
||||||
let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
|
|
||||||
return Err(e);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&None => {
|
|
||||||
debug!("Not going to insert.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ref(fle))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a Ref object which refers to `pb`
|
|
||||||
pub fn create(store: &'a Store, pb: PathBuf, flags: RefFlags) -> Result<Ref<'a>> {
|
|
||||||
Ref::create_with_hasher(store, pb, flags, DefaultHasher::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
|
|
||||||
/// algorithm on it
|
|
||||||
fn hash_path(pb: &PathBuf) -> Result<String> {
|
|
||||||
use crypto::sha1::Sha1;
|
|
||||||
use crypto::digest::Digest;
|
|
||||||
|
|
||||||
match pb.to_str() {
|
|
||||||
Some(s) => {
|
|
||||||
let mut hasher = Sha1::new();
|
|
||||||
hasher.input_str(s);
|
|
||||||
Ok(hasher.result_str())
|
|
||||||
},
|
|
||||||
None => return Err(REK::PathUTF8Error.into_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the hash from the path of the ref
|
/// Get the hash from the path of the ref
|
||||||
pub fn get_path_hash(&self) -> Result<String> {
|
fn get_path_hash(&self) -> Result<String>;
|
||||||
self.0
|
|
||||||
.get_location()
|
/// Get the hash of the link target which is stored in the ref object
|
||||||
|
fn get_stored_hash(&self) -> Result<String>;
|
||||||
|
|
||||||
|
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
|
||||||
|
/// custom Hasher instance.
|
||||||
|
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String>;
|
||||||
|
|
||||||
|
/// Get the hash of the link target by reading the link target and hashing the contents
|
||||||
|
fn get_current_hash(&self) -> Result<String>;
|
||||||
|
|
||||||
|
/// Get the hash of the link target by reading the link target and hashing the contents with the
|
||||||
|
/// custom hasher
|
||||||
|
fn get_current_hash_with_hasher<H: Hasher>(&self, h: H) -> Result<String>;
|
||||||
|
|
||||||
|
/// check whether the pointer the Ref represents still points to a file which exists
|
||||||
|
fn fs_link_exists(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
|
||||||
|
fn is_ref_to_file(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
|
||||||
|
fn is_ref_to_dir(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Alias for `!Ref::fs_link_exists()`
|
||||||
|
fn is_dangling(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// check whether the pointer the Ref represents is valid
|
||||||
|
/// This includes:
|
||||||
|
/// - Hashsum of the file is still the same as stored in the Ref
|
||||||
|
/// - file permissions are still valid
|
||||||
|
fn fs_link_valid(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Check whether the file permissions of the referenced file are equal to the stored
|
||||||
|
/// permissions
|
||||||
|
fn fs_link_valid_permissions(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
|
||||||
|
fn fs_link_valid_hash(&self) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Update the Ref by re-checking the file from FS
|
||||||
|
/// This errors if the file is not present or cannot be read()
|
||||||
|
fn update_ref(&mut self) -> Result<()>;
|
||||||
|
|
||||||
|
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
|
||||||
|
/// This errors if the file is not present or cannot be read()
|
||||||
|
fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()>;
|
||||||
|
|
||||||
|
/// Get the path of the file which is reffered to by this Ref
|
||||||
|
fn fs_file(&self) -> Result<PathBuf>;
|
||||||
|
|
||||||
|
/// Re-find a referenced file
|
||||||
|
///
|
||||||
|
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
|
||||||
|
/// for a file which matches the hash of the Ref.
|
||||||
|
///
|
||||||
|
/// If `search_roots` is `None`, it starts at the filesystem root `/`.
|
||||||
|
///
|
||||||
|
/// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
///
|
||||||
|
/// This option causes heavy I/O as it recursively searches the Filesystem.
|
||||||
|
fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf>;
|
||||||
|
|
||||||
|
/// See documentation of `Ref::refind()`
|
||||||
|
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, h: H)
|
||||||
|
-> Result<PathBuf>;
|
||||||
|
|
||||||
|
/// Get the permissions of the file which are present
|
||||||
|
fn get_current_permissions(&self) -> Result<Permissions>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Ref for Entry {
|
||||||
|
|
||||||
|
/// Get the hash from the path of the ref
|
||||||
|
fn get_path_hash(&self) -> Result<String> {
|
||||||
|
self.get_location()
|
||||||
.clone()
|
.clone()
|
||||||
.into_pathbuf()
|
.into_pathbuf()
|
||||||
.map_err_into(REK::StoreIdError)
|
.map_err_into(REK::StoreIdError)
|
||||||
|
@ -266,14 +132,14 @@ impl<'a> Ref<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the hash of the link target which is stored in the ref object
|
/// Get the hash of the link target which is stored in the ref object
|
||||||
pub fn get_stored_hash(&self) -> Result<String> {
|
fn get_stored_hash(&self) -> Result<String> {
|
||||||
self.get_stored_hash_with_hasher(&DefaultHasher::new())
|
self.get_stored_hash_with_hasher(&DefaultHasher::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
|
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
|
||||||
/// custom Hasher instance.
|
/// custom Hasher instance.
|
||||||
pub fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
|
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
|
||||||
match self.0.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
|
match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
|
||||||
// content hash stored...
|
// content hash stored...
|
||||||
Ok(Some(&Value::String(ref s))) => Ok(s.clone()),
|
Ok(Some(&Value::String(ref s))) => Ok(s.clone()),
|
||||||
|
|
||||||
|
@ -289,57 +155,35 @@ impl<'a> Ref<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the hash of the link target by reading the link target and hashing the contents
|
/// Get the hash of the link target by reading the link target and hashing the contents
|
||||||
pub fn get_current_hash(&self) -> Result<String> {
|
fn get_current_hash(&self) -> Result<String> {
|
||||||
self.get_current_hash_with_hasher(DefaultHasher::new())
|
self.get_current_hash_with_hasher(DefaultHasher::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the hash of the link target by reading the link target and hashing the contents with the
|
/// Get the hash of the link target by reading the link target and hashing the contents with the
|
||||||
/// custom hasher
|
/// custom hasher
|
||||||
pub fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
|
fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
|
||||||
self.fs_file()
|
self.fs_file()
|
||||||
.and_then(|pb| {
|
.and_then(|pb| File::open(pb.clone()).map(|f| (pb, f)).map_err_into(REK::IOError))
|
||||||
File::open(pb.clone())
|
|
||||||
.map(|f| (pb, f))
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::IOError.into_error_with_cause(e))
|
|
||||||
})
|
|
||||||
.and_then(|(path, mut file)| h.create_hash(&path, &mut file))
|
.and_then(|(path, mut file)| h.create_hash(&path, &mut file))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the permissions of the file which are present
|
|
||||||
fn get_current_permissions(&self) -> Result<Permissions> {
|
|
||||||
self.fs_file()
|
|
||||||
.and_then(|pb| {
|
|
||||||
File::open(pb)
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::HeaderFieldReadError.into_error_with_cause(e))
|
|
||||||
})
|
|
||||||
.and_then(|file| {
|
|
||||||
file
|
|
||||||
.metadata()
|
|
||||||
.map(|md| md.permissions())
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// check whether the pointer the Ref represents still points to a file which exists
|
/// check whether the pointer the Ref represents still points to a file which exists
|
||||||
pub fn fs_link_exists(&self) -> Result<bool> {
|
fn fs_link_exists(&self) -> Result<bool> {
|
||||||
self.fs_file().map(|pathbuf| pathbuf.exists())
|
self.fs_file().map(|pathbuf| pathbuf.exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
|
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
|
||||||
pub fn is_ref_to_file(&self) -> Result<bool> {
|
fn is_ref_to_file(&self) -> Result<bool> {
|
||||||
self.fs_file().map(|pathbuf| pathbuf.is_file())
|
self.fs_file().map(|pathbuf| pathbuf.is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
|
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
|
||||||
pub fn is_ref_to_dir(&self) -> Result<bool> {
|
fn is_ref_to_dir(&self) -> Result<bool> {
|
||||||
self.fs_file().map(|pathbuf| pathbuf.is_dir())
|
self.fs_file().map(|pathbuf| pathbuf.is_dir())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for `!Ref::fs_link_exists()`
|
/// Alias for `!Ref::fs_link_exists()`
|
||||||
pub fn is_dangling(&self) -> Result<bool> {
|
fn is_dangling(&self) -> Result<bool> {
|
||||||
self.fs_link_exists().map(|b| !b)
|
self.fs_link_exists().map(|b| !b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +191,7 @@ impl<'a> Ref<'a> {
|
||||||
/// This includes:
|
/// This includes:
|
||||||
/// - Hashsum of the file is still the same as stored in the Ref
|
/// - Hashsum of the file is still the same as stored in the Ref
|
||||||
/// - file permissions are still valid
|
/// - file permissions are still valid
|
||||||
pub fn fs_link_valid(&self) -> Result<bool> {
|
fn fs_link_valid(&self) -> Result<bool> {
|
||||||
match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
|
match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
|
||||||
(Ok(true) , Ok(true)) => Ok(true),
|
(Ok(true) , Ok(true)) => Ok(true),
|
||||||
(Ok(_) , Ok(_)) => Ok(false),
|
(Ok(_) , Ok(_)) => Ok(false),
|
||||||
|
@ -358,12 +202,11 @@ impl<'a> Ref<'a> {
|
||||||
|
|
||||||
/// Check whether the file permissions of the referenced file are equal to the stored
|
/// Check whether the file permissions of the referenced file are equal to the stored
|
||||||
/// permissions
|
/// permissions
|
||||||
pub fn fs_link_valid_permissions(&self) -> Result<bool> {
|
fn fs_link_valid_permissions(&self) -> Result<bool> {
|
||||||
self.0
|
self
|
||||||
.get_header()
|
.get_header()
|
||||||
.read("ref.permissions.ro")
|
.read("ref.permissions.ro")
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::HeaderFieldReadError)
|
||||||
.map_err(|e| REK::HeaderFieldReadError.into_error_with_cause(e))
|
|
||||||
.and_then(|ro| {
|
.and_then(|ro| {
|
||||||
match ro {
|
match ro {
|
||||||
Some(&Value::Boolean(b)) => Ok(b),
|
Some(&Value::Boolean(b)) => Ok(b),
|
||||||
|
@ -372,12 +215,11 @@ impl<'a> Ref<'a> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly()))
|
.and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly()))
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::RefTargetCannotReadPermissions)
|
||||||
.map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
|
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
|
||||||
pub fn fs_link_valid_hash(&self) -> Result<bool> {
|
fn fs_link_valid_hash(&self) -> Result<bool> {
|
||||||
let stored_hash = try!(self.get_stored_hash());
|
let stored_hash = try!(self.get_stored_hash());
|
||||||
let current_hash = try!(self.get_current_hash());
|
let current_hash = try!(self.get_current_hash());
|
||||||
Ok(stored_hash == current_hash)
|
Ok(stored_hash == current_hash)
|
||||||
|
@ -385,36 +227,34 @@ impl<'a> Ref<'a> {
|
||||||
|
|
||||||
/// Update the Ref by re-checking the file from FS
|
/// Update the Ref by re-checking the file from FS
|
||||||
/// This errors if the file is not present or cannot be read()
|
/// This errors if the file is not present or cannot be read()
|
||||||
pub fn update_ref(&mut self) -> Result<()> {
|
fn update_ref(&mut self) -> Result<()> {
|
||||||
self.update_ref_with_hasher(&DefaultHasher::new())
|
self.update_ref_with_hasher(&DefaultHasher::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
|
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
|
||||||
/// This errors if the file is not present or cannot be read()
|
/// This errors if the file is not present or cannot be read()
|
||||||
pub fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
|
fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
|
||||||
let current_hash = try!(self.get_current_hash()); // uses the default hasher
|
let current_hash = try!(self.get_current_hash()); // uses the default hasher
|
||||||
let current_perm = try!(self.get_current_permissions());
|
let current_perm = try!(self.get_current_permissions());
|
||||||
|
|
||||||
try!(self.0
|
try!(self
|
||||||
.get_header_mut()
|
.get_header_mut()
|
||||||
.set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
|
.set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::StoreWriteError)
|
||||||
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try!(self.0
|
try!(self
|
||||||
.get_header_mut()
|
.get_header_mut()
|
||||||
.set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
|
.set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::StoreWriteError)
|
||||||
.map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path of the file which is reffered to by this Ref
|
/// Get the path of the file which is reffered to by this Ref
|
||||||
pub fn fs_file(&self) -> Result<PathBuf> {
|
fn fs_file(&self) -> Result<PathBuf> {
|
||||||
match self.0.get_header().read("ref.path") {
|
match self.get_header().read("ref.path") {
|
||||||
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
|
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
|
||||||
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
|
Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
|
||||||
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
|
Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),
|
||||||
|
@ -422,56 +262,6 @@ impl<'a> Ref<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether there is a reference to the file at `pb`
|
|
||||||
pub fn exists(store: &Store, pb: PathBuf) -> Result<bool> {
|
|
||||||
pb.canonicalize()
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(e))
|
|
||||||
.and_then(|can| {
|
|
||||||
Ref::hash_path(&can)
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::PathHashingError.into_error_with_cause(e))
|
|
||||||
})
|
|
||||||
.and_then(|hash| {
|
|
||||||
store.retrieve_for_module("ref").map(|iter| (hash, iter))
|
|
||||||
.map_err(Box::new)
|
|
||||||
.map_err(|e| REK::StoreReadError.into_error_with_cause(e))
|
|
||||||
})
|
|
||||||
.and_then(|(hash, possible_refs)| {
|
|
||||||
// This is kind of a manual Iterator::filter() call what we do here, but with the
|
|
||||||
// actual ::filter method we cannot return the error in a nice way, so we do it
|
|
||||||
// manually here. If you can come up with a better version of this, feel free to
|
|
||||||
// take this note as a todo.
|
|
||||||
for r in possible_refs {
|
|
||||||
let contains_hash = try!(r.to_str()
|
|
||||||
.map_err_into(REK::TypeConversionError)
|
|
||||||
.map(|s| s.contains(&hash[..])));
|
|
||||||
|
|
||||||
if !contains_hash {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match store.get(r) {
|
|
||||||
Ok(Some(fle)) => {
|
|
||||||
if Ref::read_reference(&fle).map(|path| path == pb).unwrap_or(false) {
|
|
||||||
return Ok(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Ok(None) => { // Something weird just happened
|
|
||||||
return Err(REK::StoreReadError.into_error());
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
return Err(REK::StoreReadError.into_error_with_cause(Box::new(e)));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Re-find a referenced file
|
/// Re-find a referenced file
|
||||||
///
|
///
|
||||||
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
|
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
|
||||||
|
@ -484,11 +274,12 @@ impl<'a> Ref<'a> {
|
||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// This option causes heavy I/O as it recursively searches the Filesystem.
|
/// This option causes heavy I/O as it recursively searches the Filesystem.
|
||||||
pub fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
|
fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
|
||||||
self.refind_with_hasher(search_roots, DefaultHasher::new())
|
self.refind_with_hasher(search_roots, DefaultHasher::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
|
/// See documentation of `Ref::refind()`
|
||||||
|
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
|
||||||
-> Result<PathBuf>
|
-> Result<PathBuf>
|
||||||
{
|
{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -505,13 +296,11 @@ impl<'a> Ref<'a> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
entry
|
entry
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::IOError)
|
||||||
.map_err(|e| REK::IOError.into_error_with_cause(e))
|
|
||||||
.and_then(|entry| {
|
.and_then(|entry| {
|
||||||
let pb = PathBuf::from(entry.path());
|
let pb = PathBuf::from(entry.path());
|
||||||
File::open(entry.path())
|
File::open(entry.path())
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::IOError)
|
||||||
.map_err(|e| REK::IOError.into_error_with_cause(e))
|
|
||||||
.map(|f| (pb, f))
|
.map(|f| (pb, f))
|
||||||
})
|
})
|
||||||
.and_then(|(p, mut f)| h.create_hash(&p, &mut f).map(|h| (p, h)))
|
.and_then(|(p, mut f)| h.create_hash(&p, &mut f).map(|h| (p, h)))
|
||||||
|
@ -522,8 +311,7 @@ impl<'a> Ref<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map_err(Box::new)
|
.map_err_into(REK::IOError)
|
||||||
.map_err(|e| REK::IOError.into_error_with_cause(e))
|
|
||||||
})
|
})
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter_map(|e| e)
|
.filter_map(|e| e)
|
||||||
|
@ -535,43 +323,16 @@ impl<'a> Ref<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/// Get the permissions of the file which are present
|
||||||
|
fn get_current_permissions(&self) -> Result<Permissions> {
|
||||||
impl<'a> Deref for Ref<'a> {
|
self.fs_file()
|
||||||
type Target = FileLockEntry<'a>;
|
.and_then(|pb| File::open(pb).map_err_into(REK::HeaderFieldReadError))
|
||||||
|
.and_then(|file| {
|
||||||
fn deref(&self) -> &FileLockEntry<'a> {
|
file
|
||||||
&self.0
|
.metadata()
|
||||||
}
|
.map(|md| md.permissions())
|
||||||
|
.map_err_into(REK::RefTargetCannotReadPermissions)
|
||||||
}
|
})
|
||||||
|
|
||||||
impl<'a> DerefMut for Ref<'a> {
|
|
||||||
|
|
||||||
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Ref<'a> {
|
|
||||||
|
|
||||||
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
|
|
||||||
let path = self.fs_file()
|
|
||||||
.map(|pb| String::from(pb.to_str().unwrap_or("<UTF8-Error>")))
|
|
||||||
.unwrap_or(String::from("Could not read Path from reference object"));
|
|
||||||
|
|
||||||
let hash = self.get_stored_hash().unwrap_or(String::from("<could not read hash>"));
|
|
||||||
|
|
||||||
write!(fmt, "Ref({} -> {})", hash, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Into<FileLockEntry<'a>> for Ref<'a> {
|
|
||||||
|
|
||||||
fn into(self) -> FileLockEntry<'a> {
|
|
||||||
self.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
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