From a41479c0ec0dbd530aecb94e7631b16b3e354270 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 23 Oct 2018 15:26:57 +0200 Subject: doc: Rewrite README for libimagentryref --- doc/src/05100-lib-entryref.md | 92 +++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/doc/src/05100-lib-entryref.md b/doc/src/05100-lib-entryref.md index b54ba102..156af102 100644 --- a/doc/src/05100-lib-entryref.md +++ b/doc/src/05100-lib-entryref.md @@ -3,52 +3,74 @@ This library crate contains functionality to generate _references_ within the imag store. -A reference is a "pointer" to a file or directory on the filesystem and outside -the store. -It differs from `libimagentrylink`/external linking as -it is designed exclusively for filesystem references, not for URLs. +### Problem -A reference is created with a unique identifier, like a hash. The implementation -how this hash is calculated can be defined by the user of `libimagentryref`. +The problem this library solves is the following: A user wants to refer to a +file which exists on her filesystem from within imag. +But unfortunately, the user has several devices and the filesystem layout (the +way the $HOME is organized) is not the same on every device. +With this library, the user is able to refer to a file, but without specifying +the whole path. -So this library helps to resemble something like a _symlink_. +Each device can have a different "base path", files are re-found via their +hashes and file names, assuming that the files are equal on different devices or +have at least the same name. -### Usage -Users have to implement the `UniqueRefPathGenerator` trait which should -implement a hashing functionality for pathes. +### User Story / Usecase + +Alice has a music library on her workstation and on her notebook. On her +workstation, the music collection is at `home/alice/music`, on the notebook, it +exists in `/home/al/media/music`. + +From within imag, alice wants to create a link to a file +`$music_store/Psy_trance_2018_yearmix.mp3`. + +`libimagentryref` helps her, because she can provide a "base path" in the +imag configuration file of each device and then link the file. imag only stores +data about the file and its relative path, but not its abolute path. + +When moving the imag store from the workstation to the notebook, the base path +for the music collection is not `/home/alice/music` anymore, but +`/home/al/media/music` and imag can find the file automatically. -### Limits -This is _not_ intended to be a version control system or something like that. -We also can not use _real symlinks_ as we need imag-store-objects to be able to -link stuff. +### Solution, Details -### Usecase +libimagentryref does store the following data: -This library offers functionality to refer to content outside of the store. -It can be used to refer to _nearly static stuff_ pretty easily - think of a -Maildir - you add new mails by fetching them, but you mostly do not remove -mails. -If mails get moved, they can be re-found via their hash, because Maildir objects -hardly change. Or because the hash implementation which is used to refer to them -hashes only the `Message-Id` and that does not change. +```toml +[ref] +filehash.sha1 = "" +relpath = "/Psy_trance_2018_yearmix.mp3" +collection = "music" +``` -### Long-term TODO +The filehash is stored so that libimagentryref can re-find the file whenever it +was moved. The `sha1` key is added to be able to upgrade hashes later to other +hashing algorithms. +`relpath` is the part of the path that when joined with the "base" path from +the configuration results in the full path of the file for the current machine. +The "collection" key hints to the configuration key in the imag config file. -Not implemented yet: +The configuration section for the collections looks like this: -- [ ] Re-finding of files via their hash. - This must be implemented with several things in mind - * The user of the library should be able to provide a way how the - filesystem is searched. Basically a Functor which yields pathes to - check based on the original path of the missing file. - This enables implementations which do only search a certain subset - of pathes, or does depth-first-search rather than - breadth-first-search. +```toml +[ref.basepathes] +music = "/home/alice/music" +documents = "/home/alice/doc" +``` + +libimagentryref provides functionality to get the file. +libimagentryref also offers functionality to find files _only_ using their +filename (x)or filehash and correct the filehash or filename respectively +(automatically or explicitely). + + +### Limits -### Known problems +As soon as the file is renamed _and_ modified, this fails. +This does also not cover the use case where the same file has different names on +different machines. -The functionality this library provides fails to work when syncing the imag -store between two devices where the data layout is different on each device. -- cgit v1.2.3 From db7121ccba11b956c53bd28b246e830446e2c13f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 23 Oct 2018 15:39:23 +0200 Subject: Rewrite library * Remove old code * Rewrite with tests This implements a fassade pattern for ref library With the fassade, we can specify the hasher in a rather easy way, which is not possible with default generics for traits. The "default" part in "default generic type" is not properly implemented yet (as visible in the tests), as I don't know how to realize this. For simplicity, the `hasher` module exports a `default` module with a `DefaultHasher` type, which resolves to the `Sha1Hasher`. Signed-off-by: Matthias Beyer --- doc/src/05100-lib-entryref.md | 2 +- lib/entry/libimagentryref/Cargo.toml | 28 +- lib/entry/libimagentryref/src/generators/base.rs | 47 --- lib/entry/libimagentryref/src/generators/mod.rs | 276 ------------- lib/entry/libimagentryref/src/hasher.rs | 55 +++ lib/entry/libimagentryref/src/lib.rs | 38 +- lib/entry/libimagentryref/src/reference.rs | 488 ++++++++++++++++++++--- lib/entry/libimagentryref/src/refstore.rs | 128 ------ 8 files changed, 499 insertions(+), 563 deletions(-) delete mode 100644 lib/entry/libimagentryref/src/generators/base.rs delete mode 100644 lib/entry/libimagentryref/src/generators/mod.rs create mode 100644 lib/entry/libimagentryref/src/hasher.rs delete mode 100644 lib/entry/libimagentryref/src/refstore.rs diff --git a/doc/src/05100-lib-entryref.md b/doc/src/05100-lib-entryref.md index 156af102..46409205 100644 --- a/doc/src/05100-lib-entryref.md +++ b/doc/src/05100-lib-entryref.md @@ -42,7 +42,7 @@ libimagentryref does store the following data: ```toml [ref] filehash.sha1 = "" -relpath = "/Psy_trance_2018_yearmix.mp3" +relpath = "Psy_trance_2018_yearmix.mp3" collection = "music" ``` diff --git a/lib/entry/libimagentryref/Cargo.toml b/lib/entry/libimagentryref/Cargo.toml index e82249b9..60692acd 100644 --- a/lib/entry/libimagentryref/Cargo.toml +++ b/lib/entry/libimagentryref/Cargo.toml @@ -20,27 +20,19 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" } maintenance = { status = "actively-developed" } [dependencies] -itertools = "0.7" -log = "0.4.0" -toml = "0.4" -toml-query = "0.8" -failure = "0.1" -sha-1 = { version = "0.7", optional = true } -sha2 = { version = "0.7", optional = true } -sha3 = { version = "0.7", optional = true } -hex = { version = "0.3", optional = true } +itertools = "0.7" +log = "0.4.0" +failure = "0.1" +sha-1 = "0.8" +toml = "0.4" +toml-query = "0.8" +serde = "1" +serde_derive = "1" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil" } -[features] -default = [] -generators = [] -generators-sha1 = ["sha-1", "hex"] -generators-sha224 = ["sha2", "hex"] -generators-sha256 = ["sha2", "hex"] -generators-sha384 = ["sha2", "hex"] -generators-sha512 = ["sha2", "hex"] -generators-sha3 = ["sha3", "hex"] +[dev-dependencies] +env_logger = "0.5" diff --git a/lib/entry/libimagentryref/src/generators/base.rs b/lib/entry/libimagentryref/src/generators/base.rs deleted file mode 100644 index 1ccbb760..00000000 --- a/lib/entry/libimagentryref/src/generators/base.rs +++ /dev/null @@ -1,47 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2019 Matthias Beyer 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::Path; - -use libimagstore::storeid::StoreId; - -use refstore::UniqueRefPathGenerator; - -use failure::Fallible as Result; - -/// A base UniqueRefPathGenerator which must be overridden by the actual UniqueRefPathGenerator -/// which is provided by this crate -#[allow(dead_code)] -pub struct Base; - -impl UniqueRefPathGenerator for Base { - fn collection() -> &'static str { - "ref" - } - - fn unique_hash>(_path: A) -> Result { - // This has to be overridden - panic!("Not overridden base functionality. This is a BUG!") - } - - fn postprocess_storeid(sid: StoreId) -> Result { - Ok(sid) // default implementation - } -} - diff --git a/lib/entry/libimagentryref/src/generators/mod.rs b/lib/entry/libimagentryref/src/generators/mod.rs deleted file mode 100644 index 27d0fa9b..00000000 --- a/lib/entry/libimagentryref/src/generators/mod.rs +++ /dev/null @@ -1,276 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2019 Matthias Beyer 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 -// - -//! Default generators -//! -//! This module provides a number of default `UniqueRefPathGenerator`s -//! which can be used for generating references. -//! -//! These generators are _NOT_ domain specific. So there won't be a "UniqueMailRefPathGenerator" in -//! here, for example. -//! -//! All these generators use "ref" as collection name. -//! They can be overridden using the `make_unique_ref_path_generator!()` convenience macro. -//! -//! # Note -//! -//! You must enable the appropriate crate feature to use any of the provided generators. With the -//! `generators` feature, you only get the convenience macro `make_unique_ref_path_generator!()`. -//! - -/// A convenience macro for wrapping a generator in a new one, reusing the functionality from the -/// underlying generator -/// -/// The UniqueRefPathGenerator must be in scope. -/// -/// The macro creates a new struct `$name` over `$underlying` and changes the collection name to -/// `$collectionname`. -/// If passed, the new implementation is used (defaults to the implementation from the underlying -/// generator). -/// If passed, the new postprocessing is used (defaults to not changing the StoreId) -/// -#[macro_export] -macro_rules! make_unique_ref_path_generator { - ( - $name:ident - over $underlying:ty - => with collection name $collectionname:expr - ) => { - struct $name; - - impl $crate::refstore::UniqueRefPathGenerator for $name { - - fn collection() -> &'static str { - $collectionname - } - - fn unique_hash>(path: A) -> Result { - $underlying::unique_hash(path) - } - - fn postprocess_storeid(sid: ::libimagstore::storeid::StoreId) - -> Result<::libimagstore::storeid::StoreId> - { - Ok(sid) - } - } - }; - - ( - $name:ident - over $underlying:ty - => with collection name $collectionname:expr - => $impl:expr - ) => { - struct $name; - - impl $crate::refstore::UniqueRefPathGenerator for $name { - type Error = $errtype; - - fn collection() -> &'static str { - $collectionname - } - - fn unique_hash>(path: A) -> Result { - debug!("Making unique hash for path: {:?}", path.as_ref()); - $impl(path) - } - - fn postprocess_storeid(sid: ::libimagstore::storeid::StoreId) - -> Result<::libimagstore::storeid::StoreId> - { - Ok(sid) - } - } - }; - - ( - pub $name:ident - over $underlying:ty - => with collection name $collectionname:expr - => $impl:expr - ) => { - make_unique_ref_path_generator!( - pub $name - over $underlying - => with collection name $collectionname - => $impl => |sid| { Ok(sid) } - ); - }; - - ( - pub $name:ident - over $underlying:ty - => with collection name $collectionname:expr - => $impl:expr - => $postproc:expr - ) => { - pub struct $name; - - impl $crate::refstore::UniqueRefPathGenerator for $name { - fn collection() -> &'static str { - $collectionname - } - - fn unique_hash>(path: A) -> ::failure::Fallible { - debug!("Making unique hash for path: {:?}", path.as_ref()); - $impl(path) - } - - fn postprocess_storeid(sid: ::libimagstore::storeid::StoreId) - -> ::failure::Fallible<::libimagstore::storeid::StoreId> - { - $postproc(sid) - } - } - }; -} - - -#[cfg(any( - feature = "generators-sha1", - feature = "generators-sha224", - feature = "generators-sha256", - feature = "generators-sha384", - feature = "generators-sha512", - feature = "generators-sha3", - ))] -mod base; - -/// Helper macro for generating implementations for the various Sha algorithms -macro_rules! make_sha_mod { - { - $modname:ident, - $hashname:ident, - $hashingimpl:expr - } => { - pub mod $modname { - use std::path::Path; - use std::fs::OpenOptions; - use std::io::Read; - - use hex; - make_unique_ref_path_generator! ( - pub $hashname - over generators::base::Base - => with collection name "ref" - => |path| { - OpenOptions::new() - .read(true) - .write(false) - .create(false) - .open(path) - .map_err(::failure::Error::from) - .and_then(|mut file| { - let mut buffer = String::new(); - let _ = file.read_to_string(&mut buffer)?; - $hashingimpl(buffer) - }) - } - ); - - impl $hashname { - - /// Function which can be used by a wrapping UniqueRefPathGenerator to hash only N bytes. - pub fn hash_n_bytes>(path: A, n: usize) -> ::failure::Fallible { - debug!("Opening '{}' for hashing", path.as_ref().display()); - OpenOptions::new() - .read(true) - .write(false) - .create(false) - .open(path) - .map_err(::failure::Error::from) - .and_then(|mut file| { - let mut buffer = vec![0; n]; - debug!("Allocated {} bytes", buffer.capacity()); - - match file.read_exact(&mut buffer) { - Ok(_) => { /* yay */ Ok(()) }, - Err(e) => if e.kind() == ::std::io::ErrorKind::UnexpectedEof { - debug!("Ignoring unexpected EOF before {} bytes were read", n); - Ok(()) - } else { - Err(e) - } - }?; - - let buffer = String::from_utf8(buffer)?; - $hashingimpl(buffer) - }) - } - - } - - } - } -} - -#[cfg(feature = "generators-sha1")] -make_sha_mod! { - sha1, Sha1, |buffer: String| { - use sha1::{Sha1, Digest}; - - trace!("Hashing: '{:?}'", buffer); - let res = hex::encode(Sha1::digest(buffer.as_bytes())); - trace!("Hash => '{:?}'", res); - - Ok(res) - } -} - -#[cfg(feature = "generators-sha224")] -make_sha_mod! { - sha224, Sha224, |buffer: String| { - use sha2::{Sha224, Digest}; - Ok(hex::encode(Sha224::digest(buffer.as_bytes()))) - } -} - -#[cfg(feature = "generators-sha256")] -make_sha_mod! { - sha256, Sha256, |buffer: String| { - use sha2::{Sha256, Digest}; - Ok(hex::encode(Sha256::digest(buffer.as_bytes()))) - } -} - -#[cfg(feature = "generators-sha384")] -make_sha_mod! { - sha384, Sha384, |buffer: String| { - use sha2::{Sha384, Digest}; - Ok(hex::encode(Sha384::digest(buffer.as_bytes()))) - } -} - -#[cfg(feature = "generators-sha512")] -make_sha_mod! { - sha512, Sha512, |buffer: String| { - use sha2::{Sha512, Digest}; - Ok(hex::encode(Sha512::digest(buffer.as_bytes()))) - } -} - -#[cfg(feature = "generators-sha3")] -make_sha_mod! { - sha3, Sha3, |buffer: String| { - use sha3::{Sha3_256, Digest}; - Ok(hex::encode(Sha3_256::digest(buffer.as_bytes()))) - } -} - diff --git a/lib/entry/libimagentryref/src/hasher.rs b/lib/entry/libimagentryref/src/hasher.rs new file mode 100644 index 00000000..aec7eebc --- /dev/null +++ b/lib/entry/libimagentryref/src/hasher.rs @@ -0,0 +1,55 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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::Path; + +use failure::Fallible as Result; + +pub trait Hasher { + const NAME: &'static str; + + /// hash the file at path `path` + fn hash>(path: P) -> Result; +} + +pub mod default { + pub use super::sha1::Sha1Hasher as DefaultHasher; +} + +pub mod sha1 { + use std::path::Path; + + use failure::Fallible as Result; + use sha1::{Sha1, Digest}; + + use hasher::Hasher; + + pub struct Sha1Hasher; + + impl Hasher for Sha1Hasher { + const NAME : &'static str = "sha1"; + + fn hash>(path: P) -> Result { + let digest = Sha1::digest(::std::fs::read_to_string(path)?.as_bytes()); + Ok(format!("{:x}", digest)) // TODO: Ugh... + } + } + +} + diff --git a/lib/entry/libimagentryref/src/lib.rs b/lib/entry/libimagentryref/src/lib.rs index da9bda3d..4d8e02e8 100644 --- a/lib/entry/libimagentryref/src/lib.rs +++ b/lib/entry/libimagentryref/src/lib.rs @@ -41,41 +41,17 @@ extern crate itertools; extern crate toml; extern crate toml_query; +#[macro_use] extern crate serde_derive; +extern crate sha1; -#[macro_use] extern crate libimagstore; +extern crate libimagstore; extern crate libimagerror; #[macro_use] extern crate libimagentryutil; -extern crate failure; +#[macro_use] extern crate failure; -module_entry_path_mod!("ref"); +#[cfg(test)] +extern crate env_logger; +pub mod hasher; pub mod reference; -pub mod refstore; - -#[cfg(feature = "generators-sha1")] -extern crate sha1; - -#[cfg(any( - feature = "generators-sha224", - feature = "generators-sha256", - feature = "generators-sha384", - feature = "generators-sha512", -))] -extern crate sha2; - -#[cfg(feature = "generators-sha3")] -extern crate sha3; - -#[cfg(any( - feature = "generators-sha1", - feature = "generators-sha224", - feature = "generators-sha256", - feature = "generators-sha384", - feature = "generators-sha512", - feature = "generators-sha3", -))] -extern crate hex; - -#[cfg(feature = "generators")] -pub mod generators; diff --git a/lib/entry/libimagentryref/src/reference.rs b/lib/entry/libimagentryref/src/reference.rs index d09464c5..96a13530 100644 --- a/lib/entry/libimagentryref/src/reference.rs +++ b/lib/entry/libimagentryref/src/reference.rs @@ -17,15 +17,13 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -//! The Ref object is a helper over the link functionality, so one is able to create references to -//! files outside of the imag store. - use std::path::Path; use std::path::PathBuf; +use std::collections::BTreeMap; +use std::ops::Deref; use libimagentryutil::isa::Is; use libimagentryutil::isa::IsKindHeaderPathProvider; -use libimagstore::store::Entry; use libimagerror::errors::ErrorMsg as EM; use toml::Value; @@ -34,85 +32,148 @@ use toml_query::delete::TomlValueDeleteExt; use toml_query::insert::TomlValueInsertExt; use failure::Fallible as Result; use failure::Error; +use failure::err_msg; +use failure::ResultExt; -use refstore::UniqueRefPathGenerator; +use hasher::Hasher; -pub trait Ref { +/// A configuration of "collection name" -> "collection path" mappings +/// +/// Should be deserializeable from the configuration file right away, because we expect a +/// configuration like this in the config file: +/// +/// ```toml +/// [ref.collections] +/// music = "/home/alice/music" +/// documents = "/home/alice/doc" +/// ``` +/// +/// for example. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Config(BTreeMap); - /// Check whether the underlying object is actually a ref - fn is_ref(&self) -> Result; +impl Config { + pub fn new(map: BTreeMap) -> Self { + Config(map) + } +} - /// Get the stored hash. - /// - /// Does not need a `UniqueRefPathGenerator` as it reads the hash stored in the header - fn get_hash(&self) -> Result<&str>; +impl Deref for Config { + type Target = BTreeMap; - /// Make this object a ref - fn make_ref>(&mut self, hash: String, path: P) -> Result<()>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} - /// Get the referenced path. - /// - /// Does not need a `UniqueRefPathGenerator` as it reads the path stored in the header. - fn get_path(&self) -> Result; +provide_kindflag_path!(pub IsRef, "ref.is_ref"); - /// Check whether the referenced file still matches its hash - fn hash_valid(&self) -> Result; +/// Fassade module +/// +/// This module is necessary to build a generic fassade around the "entry with a (default) hasher to +/// represent the entry as a ref". +/// +/// The module is for code-structuring only, all types in the module are exported publicly in the +/// supermodule. +pub mod fassade { + use std::marker::PhantomData; - fn remove_ref(&mut self) -> Result<()>; + use libimagstore::store::Entry; + use libimagentryutil::isa::Is; + + use failure::Fallible as Result; + use failure::Error; - /// Alias for `r.fs_link_exists() && r.deref().is_file()` - fn is_ref_to_file(&self) -> Result { - self.get_path().map(|p| p.is_file()) + use hasher::sha1::Sha1Hasher; + use hasher::Hasher; + use super::IsRef; + + pub trait RefFassade { + fn is_ref(&self) -> Result; + fn as_ref_with_hasher(&self) -> RefWithHasher; + fn as_ref_with_hasher_mut(&mut self) -> MutRefWithHasher; } - /// Alias for `r.fs_link_exists() && r.deref().is_dir()` - fn is_ref_to_dir(&self) -> Result { - self.get_path().map(|p| p.is_dir()) + impl RefFassade for Entry { + /// Check whether the underlying object is actually a ref + fn is_ref(&self) -> Result { + self.is::().map_err(Error::from) + } + + fn as_ref_with_hasher(&self) -> RefWithHasher { + RefWithHasher::new(self) + } + + fn as_ref_with_hasher_mut(&mut self) -> MutRefWithHasher { + MutRefWithHasher::new(self) + } + } - /// Alias for `!Ref::fs_link_exists()` - fn is_dangling(&self) -> Result { - self.get_path().map(|p| !p.exists()) + pub struct RefWithHasher<'a, H: Hasher = Sha1Hasher>(pub(crate) &'a Entry, PhantomData); + + impl<'a, H> RefWithHasher<'a, H> + where H: Hasher + { + fn new(entry: &'a Entry) -> Self { + RefWithHasher(entry, PhantomData) + } } + pub struct MutRefWithHasher<'a, H: Hasher = Sha1Hasher>(pub(crate) &'a mut Entry, PhantomData); + + impl<'a, H> MutRefWithHasher<'a, H> + where H: Hasher + { + fn new(entry: &'a mut Entry) -> Self { + MutRefWithHasher(entry, PhantomData) + } + } } +pub use self::fassade::*; -provide_kindflag_path!(pub IsRef, "ref.is_ref"); -impl Ref for Entry { +pub trait Ref { + + /// Check whether the underlying object is actually a ref + fn is_ref(&self) -> Result; + + /// Get the stored hash. + fn get_path(&self) -> Result; + + /// Get the stored hash. + fn get_hash(&self) -> Result<&str>; + + /// Check whether the referenced file still matches its hash + fn hash_valid(&self, config: &Config) -> Result; +} + +impl<'a, H: Hasher> Ref for RefWithHasher<'a, H> { /// Check whether the underlying object is actually a ref fn is_ref(&self) -> Result { - self.is::().map_err(Error::from) + self.0.is::().map_err(Error::from) } fn get_hash(&self) -> Result<&str> { - self.get_header() - .read("ref.hash") + let header_path = format!("ref.hash.{}", H::NAME); + self.0 + .get_header() + .read(&header_path) .map_err(Error::from)? - .ok_or_else(|| Error::from(EM::EntryHeaderFieldMissing("ref.hash"))) + .ok_or_else(|| { + Error::from(EM::EntryHeaderFieldMissing("ref.hash.")) + }) .and_then(|v| { - v.as_str().ok_or_else(|| Error::from(EM::EntryHeaderTypeError2("ref.hash", "string"))) + v.as_str().ok_or_else(|| { + Error::from(EM::EntryHeaderTypeError2("ref.hash.", "string")) + }) }) } - fn make_ref>(&mut self, hash: String, path: P) -> Result<()> { - let path_str : String = path - .as_ref() - .to_str() - .map(String::from) - .ok_or_else(|| EM::UTF8Error)?; - - let _ = self.set_isflag::()?; - let hdr = self.get_header_mut(); - hdr.insert("ref.path", Value::String(String::from(path_str)))?; - hdr.insert("ref.hash", Value::String(hash))?; - - Ok(()) - } - fn get_path(&self) -> Result { - self.get_header() + self.0 + .get_header() .read("ref.path") .map_err(Error::from)? .ok_or_else(|| Error::from(EM::EntryHeaderFieldMissing("ref.path"))) @@ -124,21 +185,324 @@ impl Ref for Entry { .map(PathBuf::from) } - fn hash_valid(&self) -> Result { - self.get_path() + fn hash_valid(&self, config: &Config) -> Result { + let ref_header = self.0 + .get_header() + .read("ref")? + .ok_or_else(|| err_msg("Header missing at 'ref'"))?; + + let collection_name = ref_header + .read("collection") + .map_err(Error::from)? + .ok_or_else(|| err_msg("Header missing at 'ref.collection'"))? + .as_str() + .ok_or_else(|| Error::from(EM::EntryHeaderTypeError2("ref.hash.", "string")))?; + + let path = ref_header + .read("path") + .map_err(Error::from)? + .ok_or_else(|| err_msg("Header missing at 'ref.path'"))? + .as_str() .map(PathBuf::from) - .map_err(Error::from) - .and_then(|pb| RPG::unique_hash(pb)) - .and_then(|h| Ok(h == self.get_hash()?)) + .ok_or_else(|| Error::from(EM::EntryHeaderTypeError2("ref.hash.", "string")))?; + + + let file_path = get_file_path(config, collection_name.as_ref(), &path)?; + + ref_header + .read(H::NAME) + .map_err(Error::from)? + .ok_or_else(|| format_err!("Header missing at 'ref.{}'", H::NAME)) + .and_then(|v| { + v.as_str().ok_or_else(|| { + Error::from(EM::EntryHeaderTypeError2("ref.hash.", "string")) + }) + }) + .and_then(|hash| H::hash(file_path).map(|h| h == hash)) } +} + +pub trait MutRef { + fn remove_ref(&mut self) -> Result<()>; + + /// Make a ref out of a normal (non-ref) entry. + /// + /// If the entry is already a ref, this fails if `force` is false + fn make_ref(&mut self, path: P, collection_name: Coll, config: &Config, force: bool) + -> Result<()> + where P: AsRef, + Coll: AsRef; +} + + +impl<'a, H> MutRef for MutRefWithHasher<'a, H> + where H: Hasher +{ + fn remove_ref(&mut self) -> Result<()> { - let hdr = self.get_header_mut(); - let _ = hdr.delete("ref.hash")?; - let _ = hdr.delete("ref.path")?; - let _ = hdr.delete("ref")?; + debug!("Removing 'ref' header section"); + { + let header = self.0.get_header_mut(); + trace!("header = {:?}", header); + + let _ = header.delete("ref.relpath").context("Removing ref.relpath")?; + + if let Some(hash_tbl) = header.read_mut("ref.hash")? { + match hash_tbl { + Value::Table(ref mut tbl) => *tbl = BTreeMap::new(), + _ => { + // should not happen + } + } + } + + let _ = header.delete("ref.hash").context("Removing ref.hash")?; + let _ = header.delete("ref.collection").context("Removing ref.collection")?; + } + + debug!("Removing 'ref' header marker"); + self.0.remove_isflag::().context("Removing ref")?; + + let _ = self.0 + .get_header_mut() + .delete("ref") + .context("Removing ref")?; + + trace!("header = {:?}", self.0.get_header()); Ok(()) } + /// Make a ref out of a normal (non-ref) entry. + /// + /// `path` is the path to refer to, + /// + /// # Warning + /// + /// If the entry is already a ref, this fails if `force` is false + /// + fn make_ref(&mut self, path: P, collection_name: Coll, config: &Config, force: bool) + -> Result<()> + where P: AsRef, + Coll: AsRef + { + trace!("Making ref out of {:?}", self.0); + trace!("Making ref with collection name {:?}", collection_name.as_ref()); + trace!("Making ref with config {:?}", config); + trace!("Making ref forced = {}", force); + + if self.0.get_header().read("ref.is_ref")?.is_some() && !force { + debug!("Entry is already a Ref!"); + let _ = Err(err_msg("Entry is already a reference")).context("Making ref out of entry")?; + } + + let file_path = get_file_path(config, collection_name.as_ref(), &path)?; + + if !file_path.exists() { + let msg = format_err!("File '{:?}' does not exist", file_path); + let _ = Err(msg).context("Making ref out of entry")?; + } + + debug!("Entry hashing..."); + let _ = H::hash(&file_path) + .and_then(|hash| make_header_section(hash, H::NAME, path, collection_name)) + .and_then(|h| self.0.get_header_mut().insert("ref", h).map_err(Error::from)) + .and_then(|_| self.0.set_isflag::()) + .context("Making ref out of entry")?; + + debug!("Setting is-ref flag"); + self.0 + .set_isflag::() + .context("Setting ref-flag") + .map_err(Error::from) + .map(|_| ()) + } + +} + +/// Create a new header section for a "ref". +/// +/// # Warning +/// +/// The `relpath` _must_ be relative to the configured path for that collection. +pub(crate) fn make_header_section(hash: String, hashname: H, relpath: P, collection: C) + -> Result + where P: AsRef, + C: AsRef, + H: AsRef, +{ + let mut header_section = Value::Table(BTreeMap::new()); + { + let relpath = relpath + .as_ref() + .to_str() + .map(String::from) + .ok_or_else(|| { + let msg = format_err!("UTF Error in '{:?}'", relpath.as_ref()); + Error::from(msg) + })?; + + let _ = header_section.insert("relpath", Value::String(relpath))?; + } + + { + let mut hash_table = Value::Table(BTreeMap::new()); + let _ = hash_table.insert(hashname.as_ref(), Value::String(hash))?; + let _ = header_section.insert("hash", hash_table)?; + } + + let _ = header_section.insert("collection", Value::String(String::from(collection.as_ref()))); + + Ok(header_section) +} + +fn get_file_path

(config: &Config, collection_name: &str, path: P) -> Result + where P: AsRef +{ + config + .get(collection_name) + .map(PathBuf::clone) + .ok_or_else(|| { + format_err!("Configuration missing for collection: '{}'", collection_name) + }) + .context("Making ref out of entry") + .map_err(Error::from) + .map(|p| { + let filepath = p.join(&path); + trace!("Found filepath: {:?}", filepath.display()); + filepath + }) +} + + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use libimagstore::store::Store; + use libimagstore::store::Entry; + + use super::*; + use hasher::Hasher; + + fn setup_logging() { + let _ = ::env_logger::try_init(); + } + + pub fn get_store() -> Store { + Store::new_inmemory(PathBuf::from("/"), &None).unwrap() + } + + struct TestHasher; + impl Hasher for TestHasher { + const NAME: &'static str = "Testhasher"; + + fn hash>(path: P) -> Result { + path.as_ref() + .to_str() + .map(String::from) + .ok_or_else(|| Error::from(err_msg("Failed to create test hash"))) + } + } + + + #[test] + fn test_isref() { + setup_logging(); + let store = get_store(); + let entry = store.retrieve(PathBuf::from("test_isref")).unwrap(); + + assert!(!entry.is_ref().unwrap()); + } + + #[test] + fn test_makeref() { + setup_logging(); + let store = get_store(); + let mut entry = store.retrieve(PathBuf::from("test_makeref")).unwrap(); + let file = PathBuf::from("/"); // has to exist + let collection_name = "some_collection"; + let config = Config({ + let mut c = BTreeMap::new(); + c.insert(String::from("some_collection"), PathBuf::from("/tmp")); + c + }); + + let r = entry.as_ref_with_hasher_mut::().make_ref(file, collection_name, &config, false); + assert!(r.is_ok()); + } + + #[test] + fn test_makeref_isref() { + setup_logging(); + let store = get_store(); + let mut entry = store.retrieve(PathBuf::from("test_makeref_isref")).unwrap(); + let file = PathBuf::from("/"); // has to exists + let collection_name = "some_collection"; + let config = Config({ + let mut c = BTreeMap::new(); + c.insert(String::from("some_collection"), PathBuf::from("/tmp")); + c + }); + + let res = entry.as_ref_with_hasher_mut::().make_ref(file, collection_name, &config, false); + assert!(res.is_ok(), "Expected to be ok: {:?}", res); + + assert!(entry.as_ref_with_hasher::().is_ref().unwrap()); + } + + #[test] + fn test_makeref_is_ref_with_testhash() { + setup_logging(); + let store = get_store(); + let mut entry = store.retrieve(PathBuf::from("test_makeref_is_ref_with_testhash")).unwrap(); + let file = PathBuf::from("/"); // has to exist + let collection_name = "some_collection"; + let config = Config({ + let mut c = BTreeMap::new(); + c.insert(String::from("some_collection"), PathBuf::from("/")); + c + }); + + assert!(entry.as_ref_with_hasher_mut::().make_ref(file, collection_name, &config, false).is_ok()); + + let check_isstr = |entry: &Entry, location, shouldbe| { + let var = entry.get_header().read(location); + + assert!(var.is_ok(), "{} is not Ok(_): {:?}", location, var); + let var = var.unwrap(); + + assert!(var.is_some(), "{} is not Some(_): {:?}", location, var); + let var = var.unwrap().as_str(); + + assert!(var.is_some(), "{} is not String: {:?}", location, var); + assert_eq!(var.unwrap(), shouldbe, "{} is not == {}", location, shouldbe); + }; + + check_isstr(&entry, "ref.relpath", "/"); + check_isstr(&entry, "ref.hash.Testhasher", "/"); // TestHasher hashes by returning the path itself + check_isstr(&entry, "ref.collection", "some_collection"); + } + + #[test] + fn test_makeref_remref() { + setup_logging(); + let store = get_store(); + let mut entry = store.retrieve(PathBuf::from("test_makeref_remref")).unwrap(); + let file = PathBuf::from("/"); // has to exist + let collection_name = "some_collection"; + let config = Config({ + let mut c = BTreeMap::new(); + c.insert(String::from("some_collection"), PathBuf::from("/")); + c + }); + + assert!(entry.as_ref_with_hasher_mut::().make_ref(file, collection_name, &config, false).is_ok()); + assert!(entry.as_ref_with_hasher::().is_ref().unwrap()); + let res = entry.as_ref_with_hasher_mut::().remove_ref(); + assert!(res.is_ok(), "Expected to be ok: {:?}", res); + assert!(!entry.as_ref_with_hasher::().is_ref().unwrap()); + } + } diff --git a/lib/entry/libimagentryref/src/refstore.rs b/lib/entry/libimagentryref/src/refstore.rs deleted file mode 100644 index c378d6fd..00000000 --- a/lib/entry/libimagentryref/src/refstore.rs +++ /dev/null @@ -1,128 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2019 Matthias Beyer 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::Path; -use std::path::PathBuf; - -use libimagstore::store::FileLockEntry; -use libimagstore::store::Store; -use libimagstore::storeid::StoreId; - -use reference::Ref; - -use failure::Fallible as Result; -use failure::Error; - -/// A UniqueRefPathGenerator generates unique Pathes -/// -/// It is basically a functor which generates a StoreId from a &Path. -/// For more information have a look at the documentation of RefStore. -pub trait UniqueRefPathGenerator { - /// The collection the `StoreId` should be created for - fn collection() -> &'static str { - "ref" - } - - /// A function which should generate a unique string for a Path - fn unique_hash>(path: A) -> Result; - - /// Postprocess the generated `StoreId` object - fn postprocess_storeid(sid: StoreId) -> Result { - Ok(sid) - } -} - -/// A extensions for the `Store` to handle `Ref` objects -/// -/// The RefStore handles refs using a `UniqueRefPathGenerator`. The `UniqueRefPathGenerator`, as it -/// name suggests, generates unique `StoreId`s for a `&Path`. It is a functor `&Path -> StoreId`. -/// -/// It provides three functions which are called in the following sequence: -/// -/// * The `UniqueRefPathGenerator::collection()` function is used for get the collection a `StoreId` -/// should be in (The first element of the `StoreId` path) -/// * The `UniqueRefPathGenerator::unique_hash()` gets the `&Path` which it then should generate a -/// unique String for. How this is done does not matter. It can hash the Path itself, read the -/// file and hash that or something else. It should be reproduceable, though. -/// * These two parts are joined and put into a `StoreId` which the -/// `UniqueRefPathGenerator::postprocess_storeid()` function is then allowed to postprocess (for -/// example add more parts to the StoreId). The default implementation does nothing. -/// -/// The StoreId which is generated is then used to carry out the actual action (reading, creating -/// ...). -/// If a entry is created, header information is set (that it is a ref, the hash which was just -/// generated and the path of the referenced file) -/// -/// # Details -/// -/// The `UniqueRefPathGenerator` is passed as type parameter to enforce some semantics: -/// -/// * The used `UniqueRefPathGenerator` is defined by the implementation rather than by the runtime -/// of the program or some environment. Of course this is only a small hurdle to enforce this, but -/// a hint. -/// * The `UniqueRefPathGenerator` is a functor which does not carry state. -/// -pub trait RefStore<'a> { - - fn get_ref>(&'a self, hash: H) -> Result>>; - fn create_ref>(&'a self, path: A) -> Result>; - fn retrieve_ref>(&'a self, path: A) -> Result>; - -} - -impl<'a> RefStore<'a> for Store { - - fn get_ref>(&'a self, hash: H) - -> Result>> - { - let sid = StoreId::new(PathBuf::from(format!("{}/{}", RPG::collection(), hash.as_ref()))) - .map_err(Error::from)?; - - debug!("Getting: {:?}", sid); - self.get(sid) - .map_err(Error::from) - } - - fn create_ref>(&'a self, path: A) - -> Result> - { - let hash = RPG::unique_hash(&path)?; - let pathbuf = PathBuf::from(format!("{}/{}", RPG::collection(), hash)); - let sid = StoreId::new(pathbuf.clone())?; - - debug!("Creating: {:?}", sid); - self.create(sid) - .map_err(Error::from) - .and_then(|mut fle| { - fle.make_ref(hash, path)?; - Ok(fle) - }) - } - - fn retrieve_ref>(&'a self, path: A) - -> Result> - { - match self.get_ref::(RPG::unique_hash(path.as_ref())?)? { - Some(r) => Ok(r), - None => self.create_ref::(path), - } - } - -} - -- cgit v1.2.3 From d8ad741fc22a93bc70dd720720bc1198d8d33af7 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 15 Nov 2018 19:19:06 +0100 Subject: Rewrite libimagentrymarkdown imports --- lib/entry/libimagentrymarkdown/Cargo.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/entry/libimagentrymarkdown/Cargo.toml b/lib/entry/libimagentrymarkdown/Cargo.toml index 0226634c..8248412a 100644 --- a/lib/entry/libimagentrymarkdown/Cargo.toml +++ b/lib/entry/libimagentrymarkdown/Cargo.toml @@ -29,11 +29,6 @@ failure = "0.1" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagentrylink = { version = "0.10.0", path = "../../../lib/entry/libimagentrylink/" } +libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref/" } libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil/" } -[dependencies.libimagentryref] -version = "0.10.0" -path = "../../../lib/entry/libimagentryref/" -default-features = false -features = [ "generators", "generators-sha512" ] - -- cgit v1.2.3 From ac5a3c02987d318a126b4f0823ea42fc27a9c875 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 3 Dec 2018 00:23:17 +0100 Subject: Reimplement LinkProcessor::process() for new libimagentryref API The documentation of the function kind of explains why this change is necessary. It might get changed in the future so that the user has more flexibility. Signed-off-by: Matthias Beyer --- lib/entry/libimagentrymarkdown/Cargo.toml | 1 + lib/entry/libimagentrymarkdown/src/lib.rs | 1 + lib/entry/libimagentrymarkdown/src/processor.rs | 105 ++++++++++++++++++------ 3 files changed, 81 insertions(+), 26 deletions(-) diff --git a/lib/entry/libimagentrymarkdown/Cargo.toml b/lib/entry/libimagentrymarkdown/Cargo.toml index 8248412a..cf082a03 100644 --- a/lib/entry/libimagentrymarkdown/Cargo.toml +++ b/lib/entry/libimagentrymarkdown/Cargo.toml @@ -25,6 +25,7 @@ hoedown = "6.0.0" url = "1.5" env_logger = "0.5" failure = "0.1" +sha-1 = "0.8" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } diff --git a/lib/entry/libimagentrymarkdown/src/lib.rs b/lib/entry/libimagentrymarkdown/src/lib.rs index a108fdfd..8eedff8d 100644 --- a/lib/entry/libimagentrymarkdown/src/lib.rs +++ b/lib/entry/libimagentrymarkdown/src/lib.rs @@ -46,6 +46,7 @@ extern crate libimagentryref; extern crate libimagutil; #[macro_use] extern crate failure; #[macro_use] extern crate log; +extern crate sha1; #[cfg(test)] extern crate env_logger; diff --git a/lib/entry/libimagentrymarkdown/src/processor.rs b/lib/entry/libimagentrymarkdown/src/processor.rs index 6be62a91..6476040b 100644 --- a/lib/entry/libimagentrymarkdown/src/processor.rs +++ b/lib/entry/libimagentrymarkdown/src/processor.rs @@ -17,7 +17,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use std::path::Path; +use std::collections::BTreeMap; use failure::Fallible as Result; use failure::ResultExt; @@ -26,34 +26,18 @@ use link::extract_links; use libimagentrylink::external::ExternalLinker; use libimagentrylink::internal::InternalLinker; -use libimagentryref::refstore::RefStore; -use libimagentryref::refstore::UniqueRefPathGenerator; -use libimagentryref::generators::sha512::Sha512; +use libimagentryref::reference::MutRef; +use libimagentryref::reference::RefFassade; +use libimagentryref::hasher::sha1::Sha1Hasher; use libimagstore::store::Entry; use libimagstore::store::Store; use libimagstore::storeid::StoreId; +use libimagerror::errors::ErrorMsg; use std::path::PathBuf; use url::Url; - -pub struct UniqueMarkdownRefGenerator; - -impl UniqueRefPathGenerator for UniqueMarkdownRefGenerator { - fn collection() -> &'static str { - "ref" // we can only use this collection, as we don't know about context - } - - fn unique_hash>(path: A) -> Result { - Sha512::unique_hash(path).map_err(Error::from) - } - - fn postprocess_storeid(sid: StoreId) -> Result { - Ok(sid) // don't do anything - } -} - /// A link Processor which collects the links from a Markdown and passes them on to /// `libimagentrylink` functionality /// @@ -117,11 +101,37 @@ impl LinkProcessor { /// Process an Entry for its links /// + /// + /// # Notice + /// + /// Whenever a "ref" is created, that means when a URL points to a filesystem path (normally + /// when using `file:///home/user/foobar.file` for example), the _current_ implementation uses + /// libimagentryref to create make the entry into a ref. + /// + /// The configuration of the `libimagentryref::reference::Reference::make_ref()` call is as + /// follows: + /// + /// * Name of the collection: "root" + /// * Configuration: `{"root": "/"}` + /// + /// This implementation might change in the future, so that the configuration and the name of + /// the collection can be passed to the function, or in a way that the user is asked what to do + /// during the runtime of this function. + /// + /// /// # Warning /// /// When `LinkProcessor::create_internal_targets()` was called to set the setting to true, this /// function returns all errors returned by the Store. /// + /// That means: + /// + /// * For an internal link, the linked target is created if create_internal_targets() is true, + /// else error + /// * For an external link, if create_internal_targets() is true, libimagentrylink creates the + /// external link entry, else the link is ignored + /// * all other cases do not create elements in the store + /// pub fn process<'a>(&self, entry: &mut Entry, store: &'a Store) -> Result<()> { let text = entry.to_str()?; trace!("Processing: {:?}", entry.get_location()); @@ -151,18 +161,58 @@ impl LinkProcessor { entry.add_external_link(store, url)?; }, LinkQualification::RefLink(url) => { + use sha1::{Sha1, Digest}; + if !self.process_refs { + trace!("Not processing refs... continue..."); continue } + // because we can make one entry only into _one_ ref, but a markdown document + // might contain several "ref" links, we create a new entry for the ref we're + // about to create + // + // We generate the StoreId with the SHA1 hash of the path, which is the best + // option we have + // right now + // + // TODO: Does this make sense? Can we improve this? + let path = url.host_str().unwrap_or_else(|| url.path()); + let path = PathBuf::from(path); + let ref_entry_id = { + let digest = Sha1::digest(path.to_str().ok_or(ErrorMsg::UTF8Error)?.as_bytes()); + StoreId::new(PathBuf::from(format!("ref/{:x}", digest)))? // TODO: Ugh... + }; + let mut ref_entry = store.retrieve(ref_entry_id)?; + + let ref_collection_name = "root"; + + // TODO: Maybe this can be a const? + // TODO: Maybe we need this ot be overrideable? Not sure. + let ref_collection_config = { + let mut map = BTreeMap::new(); + map.insert(String::from("root"), PathBuf::from("/")); + ::libimagentryref::reference::Config::new(map) + }; + trace!("URL = {:?}", url); trace!("URL.path() = {:?}", url.path()); trace!("URL.host_str() = {:?}", url.host_str()); - let path = url.host_str().unwrap_or_else(|| url.path()); - let path = PathBuf::from(path); - let mut target = store.create_ref::(path)?; - entry.add_internal_link(&mut target)?; + trace!("Processing ref: {:?} -> {path}, collection: {ref_collection_name}, cfg: {cfg:?}", + path = path.display(), + ref_collection_name = ref_collection_name, + cfg = ref_collection_config); + + ref_entry.as_ref_with_hasher_mut::() + .make_ref(path, + ref_collection_name, + &ref_collection_config, + false)?; + + trace!("Ready processing, linking new ref entry..."); + + let _ = entry.add_internal_link(&mut ref_entry)?; }, LinkQualification::Undecidable(e) => { // error @@ -188,9 +238,11 @@ enum LinkQualification { impl LinkQualification { fn qualify(text: &str) -> LinkQualification { + trace!("Qualifying: {}", text); match Url::parse(text) { Ok(url) => { if url.scheme() == "file" { + trace!("Qualifying = RefLink"); return LinkQualification::RefLink(url) } @@ -205,6 +257,7 @@ impl LinkQualification { Err(e) => { match e { ::url::ParseError::RelativeUrlWithoutBase => { + trace!("Qualifying = InternalLink"); LinkQualification::InternalLink }, @@ -438,7 +491,7 @@ mod tests { assert!(entries.is_ok()); let entries : Vec<_> = entries.unwrap().into_storeid_iter().collect(); - assert_eq!(2, entries.len(), "Expected 2 links, got: {:?}", entries); + assert_eq!(2, entries.len(), "Expected 1 entries, got: {:?}", entries); debug!("{:?}", entries); } -- cgit v1.2.3 From 49df7f54a940d40aa000243f8da710412cff27e2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 23 Oct 2018 15:52:13 +0200 Subject: Rewrite imag-ref --- bin/core/imag-ref/src/main.rs | 112 ++++++++++++++++++++++-------------------- bin/core/imag-ref/src/ui.rs | 108 ++++++++++++++++++++++++++++++++++++---- imagrc.toml | 7 +++ 3 files changed, 166 insertions(+), 61 deletions(-) diff --git a/bin/core/imag-ref/src/main.rs b/bin/core/imag-ref/src/main.rs index 9d8e131a..332e9477 100644 --- a/bin/core/imag-ref/src/main.rs +++ b/bin/core/imag-ref/src/main.rs @@ -47,15 +47,17 @@ extern crate libimagutil; mod ui; use ui::build_ui; -use std::path::PathBuf; use std::process::exit; +use std::io::Write; use libimagerror::trace::MapErrTrace; use libimagerror::exit::ExitUnwrap; use libimagrt::setup::generate_runtime_setup; use libimagrt::runtime::Runtime; -use libimagstore::storeid::IntoStoreId; use libimagentryref::reference::Ref; +use libimagentryref::reference::MutRef; +use libimagentryref::reference::RefFassade; +use libimagentryref::hasher::default::DefaultHasher; fn main() { let version = make_imag_version!(); @@ -69,6 +71,7 @@ fn main() { debug!("Call: {}", name); match name { "deref" => deref(&rt), + "create" => create(&rt), "remove" => remove(&rt), other => { debug!("Unknown command"); @@ -82,47 +85,43 @@ fn main() { } fn deref(rt: &Runtime) { - let cmd = rt.cli().subcommand_matches("deref").unwrap(); - let id = cmd.value_of("ID") - .map(String::from) - .map(PathBuf::from) - .unwrap() // saved by clap - .into_storeid() - .map_err_trace_exit_unwrap(); - - match rt.store().get(id.clone()).map_err_trace_exit_unwrap() { - Some(entry) => { - entry - .get_path() - .map_err_trace_exit_unwrap() - .to_str() - .ok_or_else(|| { - error!("Could not transform path into string!"); + let cmd = rt.cli().subcommand_matches("deref").unwrap(); + let ids = rt.ids::<::ui::PathProvider>().map_err_trace_exit_unwrap(); + let out = rt.stdout(); + let mut outlock = out.lock(); + + ids.into_iter() + .for_each(|id| { + match rt.store().get(id.clone()).map_err_trace_exit_unwrap() { + Some(entry) => { + entry + .as_ref_with_hasher::() + .get_path() + .map_err_trace_exit_unwrap() + .to_str() + .ok_or_else(|| { + error!("Could not transform path into string!"); + exit(1) + }) + .map(|s| writeln!(outlock, "{}", s)) + .ok(); // safe here because we exited already in the error case + + let _ = rt.report_touched(&id).unwrap_or_exit(); + }, + None => { + error!("No entry for id '{}' found", id); exit(1) - }) - .map(|s| info!("{}", s)) - .ok(); // safe here because we exited already in the error case - - let _ = rt.report_touched(&id).unwrap_or_exit(); - }, - None => { - error!("No entry for id '{}' found", id); - exit(1) - }, - }; + }, + } + }); } fn remove(rt: &Runtime) { use libimaginteraction::ask::ask_bool; - let cmd = rt.cli().subcommand_matches("remove").unwrap(); - let yes = cmd.is_present("yes"); - let id = cmd.value_of("ID") - .map(String::from) - .map(PathBuf::from) - .unwrap() // saved by clap - .into_storeid() - .map_err_trace_exit_unwrap(); + let cmd = rt.cli().subcommand_matches("remove").unwrap(); + let yes = cmd.is_present("yes"); + let ids = rt.ids::<::ui::PathProvider>().map_err_trace_exit_unwrap(); let mut input = rt.stdin().unwrap_or_else(|| { error!("No input stream. Cannot ask for permission"); @@ -131,21 +130,30 @@ fn remove(rt: &Runtime) { let mut output = rt.stdout(); - match rt.store().get(id.clone()).map_err_trace_exit_unwrap() { - Some(mut entry) => { - if yes || - ask_bool(&format!("Delete ref from entry '{}'", id), None, &mut input, &mut output) - .map_err_trace_exit_unwrap() - { - let _ = entry.remove_ref().map_err_trace_exit_unwrap(); - } else { - info!("Aborted"); + ids.into_iter() + .for_each(|id| { + match rt.store().get(id.clone()).map_err_trace_exit_unwrap() { + Some(mut entry) => { + if yes || + ask_bool(&format!("Delete ref from entry '{}'", id), None, &mut input, &mut output) + .map_err_trace_exit_unwrap() + { + let _ = entry.as_ref_with_hasher_mut::() + .remove_ref() + .map_err_trace_exit_unwrap(); + } else { + info!("Aborted"); + } + }, + None => { + error!("No entry for id '{}' found", id); + exit(1) + }, } - }, - None => { - error!("No entry for id '{}' found", id); - exit(1) - }, - }; + }); +} + +fn create(rt: &Runtime) { + unimplemented!() } diff --git a/bin/core/imag-ref/src/ui.rs b/bin/core/imag-ref/src/ui.rs index e7d714ee..c14eafe2 100644 --- a/bin/core/imag-ref/src/ui.rs +++ b/bin/core/imag-ref/src/ui.rs @@ -17,19 +17,34 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use clap::{Arg, App, SubCommand}; +use std::path::PathBuf; + +use clap::{Arg, App, ArgMatches, SubCommand}; + +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; +use libimagrt::runtime::IdPathProvider; +use libimagerror::trace::MapErrTrace; pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { app .subcommand(SubCommand::with_name("deref") - .about("'Dereference' a ref. This prints the Path of the referenced file") + .about("'Dereference a ref. This prints the Path(es) of the referenced file(s)") .version("0.1") .arg(Arg::with_name("ID") .index(1) .takes_value(true) - .required(true) - .help("The id of the store entry to dereference") + .required(false) + .multiple(true) + .help("The id of the store entry to dereference.") .value_name("ID")) + + .arg(Arg::with_name("ignore-noref") + .long("ignore-noref") + .takes_value(false) + .required(false) + .multiple(false) + .help("Ignore store entries which are not refs and do not print error message")) ) .subcommand(SubCommand::with_name("remove") @@ -38,14 +53,89 @@ pub fn build_ui<'a>(app: App<