summaryrefslogtreecommitdiffstats
path: root/lib/entry/libimagentryref/src/refstore.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/entry/libimagentryref/src/refstore.rs')
-rw-r--r--lib/entry/libimagentryref/src/refstore.rs268
1 files changed, 268 insertions, 0 deletions
diff --git a/lib/entry/libimagentryref/src/refstore.rs b/lib/entry/libimagentryref/src/refstore.rs
new file mode 100644
index 00000000..b005a665
--- /dev/null
+++ b/lib/entry/libimagentryref/src/refstore.rs
@@ -0,0 +1,268 @@
+//
+// 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 toml::Value;
+
+use error::RefErrorKind as REK;
+use error::RefError as RE;
+use error::ResultExt;
+use error::Result;
+use flags::RefFlags;
+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()
+ .chain_err(|| REK::PathCanonicalizationError)
+ .and_then(|c| hash_path(&c))
+ .chain_err(|| REK::PathHashingError)
+ .and_then(|hash| {
+ self.retrieve_for_module("ref")
+ .map(|iter| (hash, iter))
+ .chain_err(|| 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()
+ .chain_err(|| 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(RE::from_kind(REK::StoreReadError)),
+ Err(e) => return Err(e).chain_err(|| 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).chain_err(|| REK::StoreReadError),
+ Ok(None) => return Err(RE::from_kind(REK::RefNotInStore)),
+ 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))
+ .chain_err(|| 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))
+ .chain_err(|| 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(RE::from_kind(REK::RefTargetDoesNotExist));
+ }
+ if flags.get_content_hashing() && pb.is_dir() {
+ return Err(RE::from_kind(REK::RefTargetCannotBeHashed));
+ }
+
+ let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
+ try!(File::open(pb.clone())
+ .chain_err(|| 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())
+ .chain_err(|| 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
+ .chain_err(|| 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).chain_err(|| 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(RE::from_kind(REK::PathUTF8Error)),
+ 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))
+ .chain_err(|| 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 = RE::from_kind(REK::HeaderFieldAlreadyExistsError);
+ return Err(e).chain_err(|| REK::HeaderFieldWriteError);
+ },
+ Ok(None) => {
+ // Okay, we just inserted a new header value...
+ },
+ Err(e) => return Err(e).chain_err(|| REK::HeaderFieldWriteError),
+ }
+ }
+ &None => {
+ debug!("Not going to insert.");
+ }
+ }
+ }
+
+ Ok(fle)
+ }
+
+}
+