use std::collections::HashMap; use std::fs::{File, remove_file}; use std::ops::Drop; use std::path::PathBuf; use std::result::Result as RResult; use std::sync::Arc; use std::sync::RwLock; use fs2::FileExt; use entry::Entry; use error::{StoreError, StoreErrorKind}; /// The Result Type returned by any interaction with the store that could fail pub type Result = RResult; /// The Index into the Store pub type StoreId = PathBuf; /// This Trait allows you to convert various representations to a single one /// suitable for usage in the Store trait IntoStoreId { fn into_storeid(self) -> StoreId; } impl<'a> IntoStoreId for &'a str { fn into_storeid(self) -> StoreId { PathBuf::from(self) } } impl<'a> IntoStoreId for &'a String{ fn into_storeid(self) -> StoreId { PathBuf::from(self) } } impl IntoStoreId for String{ fn into_storeid(self) -> StoreId { PathBuf::from(self) } } impl IntoStoreId for PathBuf { fn into_storeid(self) -> StoreId { self } } impl<'a> IntoStoreId for &'a PathBuf { fn into_storeid(self) -> StoreId { self.clone() } } impl IntoStoreId for (ISI, ISI) { fn into_storeid(self) -> StoreId { let (first, second) = self; let mut res : StoreId = first.into_storeid(); res.push(second.into_storeid()); res } } #[derive(PartialEq)] enum StoreEntryPresence { Present, Borrowed } /// A store entry, depending on the option type it is either borrowed currently /// or not. struct StoreEntry { file: File, entry: StoreEntryPresence } impl StoreEntry { /// The entry is currently borrowed, meaning that some thread is currently /// mutating it fn is_borrowed(&self) -> bool { self.entry == StoreEntryPresence::Borrowed } /// Flush the entry to disk fn set_entry(&mut self, entry: Entry) -> Result<()> { unimplemented!() } /// We borrow the entry fn get_entry(&mut self) -> Result { unimplemented!() } /// We copy the entry fn copy_entry(&mut self) -> Result { unimplemented!() } } /// The Store itself, through this object one can interact with IMAG's entries pub struct Store { location: PathBuf, /** * Internal Path->File cache map * * Caches the files, so they remain flock()ed * * Could be optimized for a threadsafe HashMap */ entries: Arc>>, } impl Store { /// Create a new Store object pub fn new(location: PathBuf) -> Store { Store { location: location, entries: Arc::new(RwLock::new(HashMap::new())), } } /// Creates the Entry at the given location (inside the entry) pub fn create(&self, entry: Entry) -> Result<()> { unimplemented!(); } /// Borrow a given Entry. When the `FileLockEntry` is either `update`d or /// dropped, the new Entry is written to disk pub fn retrieve<'a>(&'a self, id: StoreId) -> Result> { unimplemented!(); } /// Return the `FileLockEntry` and write to disk pub fn update<'a>(&'a self, entry: FileLockEntry<'a>) -> Result<()> { self._update(&entry) } /// Internal method to write to the filesystem store. /// /// # Assumptions /// This method assumes that entry is dropped _right after_ the call, hence /// it is not public. fn _update<'a>(&'a self, entry: &FileLockEntry<'a>) -> Result<()> { unimplemented!(); } /// Retrieve a copy of a given entry, this cannot be used to mutate /// the one on disk pub fn retrieve_copy(&self, id: StoreId) -> Result { unimplemented!(); } /// Delete an entry pub fn delete(&self, id: StoreId) -> Result<()> { let mut entries_lock = self.entries.write(); let mut entries = entries_lock.unwrap(); // if the entry is currently modified by the user, we cannot drop it if entries.get(&id).map(|e| e.is_borrowed()).unwrap_or(false) { return Err(StoreError::new(StoreErrorKind::IdLocked, None)); } // remove the entry first, then the file entries.remove(&id); remove_file(&id).map_err(|e| StoreError::new(StoreErrorKind::FileError, Some(Box::new(e)))) } } impl Drop for Store { /** * Unlock all files on drop * * TODO: Error message when file cannot be unlocked? */ fn drop(&mut self) { self.entries.write().unwrap() .iter().map(|f| f.1.file.unlock()); } } /// A struct that allows you to borrow an Entry pub struct FileLockEntry<'a> { store: &'a Store, entry: Entry, key: StoreId, } impl<'a> FileLockEntry<'a, > { fn new(store: &'a Store, entry: Entry, key: StoreId) -> FileLockEntry<'a> { FileLockEntry { store: store, entry: entry, key: key, } } } impl<'a> ::std::ops::Deref for FileLockEntry<'a> { type Target = Entry; fn deref(&self) -> &Self::Target { &self.entry } } impl<'a> ::std::ops::DerefMut for FileLockEntry<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.entry } } impl<'a> Drop for FileLockEntry<'a> { fn drop(&mut self) { self.store._update(self).unwrap() } } #[cfg(test)] mod test { use std::path::PathBuf; use store::{StoreId, IntoStoreId}; #[test] fn into_storeid_trait() { let buf = PathBuf::from("abc/def"); let test = ("abc", "def"); assert_eq!(buf, test.into_storeid()); let test = "abc/def"; assert_eq!(buf, test.into_storeid()); let test = String::from("abc/def"); assert_eq!(buf, test.into_storeid()); let test = PathBuf::from("abc/def"); assert_eq!(buf, test.into_storeid()); } }