persy 1.5.0

Transactional Persistence Engine
Documentation
use crate::{
    config::Config,
    error::{OpenError, OpenMemoryError, PE},
    persy::PersyImpl,
    Persy, Recover,
};
use std::{fs, fs::File, path::Path, sync::Arc};

/// Options, flags, configs which can be used
/// to configure how a persy database is opened.
///
/// ```
/// use persy::{OpenOptions, Persy, PersyId, ValueMode};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// // This function will only be called on database creation
/// fn init(persy: &Persy) -> Result<(), Box<dyn std::error::Error>> {
///     let mut tx = persy.begin()?;
///
///     tx.create_segment("data")?;
///     tx.create_index::<u64, PersyId>("index", ValueMode::Replace)?;
///
///     let prepared = tx.prepare()?;
///     prepared.commit()?;
///
///     println!("Segment and Index successfully created");
///     Ok(())
/// }
///
/// let persy = OpenOptions::new().create(true).prepare_with(init).open("target/persy.db")?;
/// # std::fs::remove_file("target/persy.db")?;
/// # Ok(())
/// # }
/// ```
pub struct OpenOptions {
    truncate: bool,
    create: bool,
    create_new: bool,
    config: Config,
    prepare: Option<Box<dyn Fn(&Persy) -> Result<(), Box<dyn std::error::Error>>>>,
    recover: Option<Box<dyn Fn(&Vec<u8>) -> bool>>,
}

impl OpenOptions {
    pub fn new() -> OpenOptions {
        OpenOptions {
            truncate: false,
            create: false,
            create_new: false,
            config: Config::new(),
            prepare: None,
            recover: None,
        }
    }
    /// Truncate the file on open removing all the persistent data
    pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
        self.truncate = truncate;
        self
    }

    /// Create a new file if not exists
    pub fn create(&mut self, create: bool) -> &mut OpenOptions {
        self.create = create;
        self
    }

    /// Create a new file if exists fail
    pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
        self.create_new = create_new;
        self
    }

    /// Provide a function for initialize the storage in case did not existed
    ///
    pub fn prepare_with<F>(&mut self, prepare: F) -> &mut OpenOptions
    where
        F: Fn(&Persy) -> Result<(), Box<dyn std::error::Error>> + 'static,
    {
        self.prepare = Some(Box::new(prepare));
        self
    }

    /// Provide a function for check if a transaction must be committed or rollback in case of
    /// recover from crash
    pub fn recover_with<F>(&mut self, recover: F) -> &mut OpenOptions
    where
        F: Fn(&Vec<u8>) -> bool + 'static,
    {
        self.recover = Some(Box::new(recover));
        self
    }

    /// Provide general storage configurations
    pub fn config(&mut self, config: Config) -> &mut OpenOptions {
        self.config = config;
        self
    }

    /// Open a file to a recover structure to list pending transactions and select witch commit and
    /// rollback
    pub fn recover<P>(&mut self, path: P) -> Result<Recover, PE<OpenError>>
    where
        P: AsRef<Path>,
    {
        let path = path.as_ref();
        let exists = path.exists();

        let file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .create(self.create)
            .create_new(self.create_new)
            .open(path)?;
        self.int_recover_file(file, exists)
    }

    /// Open a file to a recover structure to list pending transactions and select witch commit and
    /// rollback
    pub fn recover_file(&mut self, file: File) -> Result<Recover, PE<OpenError>> {
        self.int_recover_file(file, true)
    }
    /// Open a file to a recover structure to list pending transactions and select witch commit and
    /// rollback
    fn int_recover_file(&mut self, file: File, exists: bool) -> Result<Recover, PE<OpenError>> {
        let must_prepare = !exists || self.truncate;

        let config = self.config.clone();

        if must_prepare {
            let (persy_impl, recov) = if self.truncate {
                PersyImpl::truncate_and_open(file, config)?
            } else {
                PersyImpl::create_and_open(file, config)?
            };
            let p = Arc::new(persy_impl);
            if let Some(prepare) = &mut self.prepare {
                let persy = Persy { persy_impl: p.clone() };
                (prepare)(&persy).map_err(|e| OpenError::InitError(format!("{}", e)))?;
            }
            Ok(Recover::new(recov, p))
        } else if let Some(recover) = &self.recover {
            let (persy_impl, mut recov) = PersyImpl::open_recover(file, config)?;
            recov.apply(recover)?;
            Ok(Recover::new(recov, Arc::new(persy_impl)))
        } else {
            let (persy_impl, recov) = PersyImpl::open_recover(file, config)?;
            Ok(Recover::new(recov, Arc::new(persy_impl)))
        }
    }

    /// Open a file from the given path with the current options
    pub fn open<P>(&mut self, path: P) -> Result<Persy, PE<OpenError>>
    where
        P: AsRef<Path>,
    {
        let recover = self.recover(path)?;
        recover.finalize().map_err(|e| PE::PE(OpenError::from(e.error())))
    }

    /// Create a persy instance backed by a `Vec<u8>` with no persistence
    ///
    ///
    /// # Example
    /// ```
    /// use persy::{OpenOptions, Persy, PersyId, ValueMode};
    ///
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///
    /// let persy = OpenOptions::new().memory()?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn memory(&mut self) -> Result<Persy, PE<OpenMemoryError>> {
        let config = self.config.clone();
        let persy_impl = PersyImpl::memory(config)?;
        let persy = Persy {
            persy_impl: Arc::new(persy_impl),
        };
        if let Some(prepare) = &mut self.prepare {
            (prepare)(&persy).map_err(|e| OpenMemoryError::InitError(format!("{}", e)))?;
        }
        Ok(persy)
    }
}

impl Default for OpenOptions {
    fn default() -> OpenOptions {
        OpenOptions::new()
    }
}