Skip to content

Instantly share code, notes, and snippets.

@iancoleman
Created August 26, 2016 04:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iancoleman/d840e345b9a76a12103e5339b9564188 to your computer and use it in GitHub Desktop.
Save iancoleman/d840e345b9a76a12103e5339b9564188 to your computer and use it in GitHub Desktop.
SAFE network vault chunk_store using RAM instead of disk IO
// Copyright 2016 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under (1) the MaidSafe.net Commercial License,
// version 1.0 or later, or (2) The General Public License (GPL), version 3, depending on which
// licence you accepted on initial access to the Software (the "Licences").
//
// By contributing code to the SAFE Network Software, or to this project generally, you agree to be
// bound by the terms of the MaidSafe Contributor Agreement, version 1.0. This, along with the
// Licenses can be found in the root directory of this project at LICENSE, COPYING and CONTRIBUTOR.
//
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.
//
// Please review the Licences for the specific language governing permissions and limitations
// relating to use of the SAFE Network Software.
use std::hash::Hash;
use std::fs::{self, File};
use std::clone::Clone;
use std::cmp::Eq;
use std::io;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::collections::HashMap;
use fs2::FileExt;
use maidsafe_utilities::serialisation::{self, SerialisationError};
use rustc_serialize::{Decodable, Encodable};
/// The name of the lock file for the chunk directory.
const LOCK_FILE_NAME: &'static str = "lock";
quick_error! {
/// `ChunkStore` error.
#[derive(Debug)]
pub enum Error {
/// Error during filesystem IO operations.
Io(error: io::Error) {
description("IO error")
display("IO error: {}", error)
cause(error)
from()
}
/// Error during serialisation or deserialisation of keys or values.
Serialisation(error: SerialisationError) {
description("Serialisation error")
display("Serialisation error: {}", error)
cause(error)
from()
}
/// Not enough space in `ChunkStore` to perform `put`.
NotEnoughSpace {
description("Not enough space")
display("Not enough space")
}
/// Key, Value pair not found in `ChunkStore`.
NotFound {
description("Key, Value not found")
display("Key, Value not found")
}
}
}
/// `ChunkStore` is a store of data held as serialised files on disk, implementing a maximum disk
/// usage to restrict storage.
///
/// The data chunks are deleted when the `ChunkStore` goes out of scope.
pub struct ChunkStore<Key, Value> {
rootdir: PathBuf,
lock_file: Option<File>,
max_space: u64,
used_space: u64,
phantom: PhantomData<(Key, Value)>,
files: HashMap<Key, Vec<u8>>,
}
impl<Key, Value> ChunkStore<Key, Value>
where Key: Decodable + Encodable + Eq + Hash + Clone,
Value: Decodable + Encodable
{
/// Creates a new `ChunkStore` with `max_space` allowed storage space.
///
/// The data is stored in a root directory. If `root` doesn't exist, it will be created.
pub fn new(root: PathBuf, max_space: u64) -> Result<ChunkStore<Key, Value>, Error> {
let lock_file = try!(Self::lock_and_clear_dir(&root));
Ok(ChunkStore {
rootdir: root,
lock_file: Some(lock_file),
max_space: max_space,
used_space: 0,
phantom: PhantomData,
files: HashMap::new(),
})
}
/// Stores a new data chunk under `key`.
///
/// If there is not enough storage space available, returns `Error::NotEnoughSpace`. In case of
/// an IO error, it returns `Error::Io`.
///
/// If the key already exists, it will be overwritten.
pub fn put(&mut self, key: Key, value: &Value) -> Result<(), Error> {
let serialised_value = try!(serialisation::serialise(value));
if self.used_space + serialised_value.len() as u64 > self.max_space {
return Err(Error::NotEnoughSpace);
}
let _ = self.files.insert(key, serialised_value);
// TODO self.used_space should be updated here
Ok(())
}
/// Deletes the data chunk stored under `key`.
///
/// If the data doesn't exist, it does nothing and returns `Ok`. In the case of an IO error, it
/// returns `Error::Io`.
pub fn delete(&mut self, key: &Key) -> Result<(), Error> {
let _ = self.files.remove(key);
Ok(())
}
/// Returns a data chunk previously stored under `key`.
///
/// If the data file can't be accessed, it returns `Error::ChunkNotFound`.
pub fn get(&self, key: &Key) -> Result<Value, Error> {
let contents = self.files.get(key).unwrap();
Ok(try!(serialisation::deserialise::<Value>(contents)))
}
/// Tests if a data chunk has been previously stored under `key`.
pub fn has(&self, key: &Key) -> bool {
self.files.contains_key(key)
}
/// Lists all keys of currently-data stored.
pub fn keys(&self) -> Vec<Key> {
let keys: Vec<Key> = self.files.keys().cloned().collect();
keys
}
/// Returns the maximum amount of storage space available for this ChunkStore.
pub fn max_space(&self) -> u64 {
self.max_space
}
/// Returns the amount of storage space already used by this ChunkStore.
pub fn used_space(&self) -> u64 {
self.used_space
}
/// Creates and clears the given root directory and returns a locked file inside it.
fn lock_and_clear_dir(root: &PathBuf) -> Result<File, Error> {
// Create the chunk directory and a lock file.
try!(fs::create_dir_all(&root));
let lock_file_path = root.join(LOCK_FILE_NAME);
let lock_file = try!(File::create(&lock_file_path));
try!(lock_file.try_lock_exclusive());
Ok(lock_file)
}
}
impl<Key, Value> Drop for ChunkStore<Key, Value> {
fn drop(&mut self) {
let _ = self.lock_file.take().iter().map(File::unlock);
let _ = fs::remove_dir_all(&self.rootdir);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment