diff options
Diffstat (limited to 'lib/entry/libimagentryref/src/refstore.rs')
-rw-r--r-- | lib/entry/libimagentryref/src/refstore.rs | 268 |
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) + } + +} + |