syncless 0.2.0

ordered, atomic storage without durability guarantees
Documentation
//! syncless: ordered, atomic storage without durability guarantees.
//!
//! Many times you don't want to pay the cost of continuous fsyncs, and are ok
//! with losing the latest updates if an OS crash/power outage were to happen
//! and the user is unlucky - but it's **not acceptable** to corrupt older
//! data.  Think of cases like "browser bookmarks" or "history": synchronous
//! requirements are overkill for these (and stressful on the entire system).
//!
//! Once `syncless` has opened a file (except for hardware errors or bugs)
//! writes are **atomic** and **consistently ordered**: if you see a write you
//! will see all of it, and all prior writes.
//!
//! Writes are **not isolated** (a single reader/writer is assumed) and **not
//! durable**: data may remain buffered in memory and be lost on crash or
//! power failure.  However, crash recovery will never expose torn writes or
//! corrupt earlier data.
//!
//! ## Guarantees
//!
//! - Atomic visibility of individual writes
//! - Ordered visibility of writes
//! - Crash-safe recovery of previously visible data
//!
//! ## Non-guarantees
//!
//! - Durability (recent writes may be lost)
//! - Isolation (single writer assumed)
//! - Multi-process coordination
//!
//! ## Example: atomically storing a JSON file
//!
//! This saves a JSON blob (perhaps your program's config?) using syncless
//! so it either gets the old or new one, never a corrupted version.
//! In practice you would probably keep the Store<Writable> object around,
//! as reloading it can be expensive if it has many changes.
//!
//! ```no_run
//! use syncless::{open, Store, Writable, Error};
//!
//! pub fn save_config(
//!     store: &mut Store<syncless::Writable>,
//!     json: &str,
//! ) -> Result<(), Error> {
//!     store.write(0, json.as_bytes())
//! }
//!
//! pub fn load_config(
//!     store: &mut Store<syncless::ReadOnly>
//! ) -> Result<String, Error> {
//!     let mut buf = vec![0u8; store.size() as usize];
//!
//!     store.read(0, &mut buf)?;
//!     let s = std::str::from_utf8(&buf)
//!         .map_err(|e| Error::CorruptRecord)?;
//!     Ok(s.to_owned())
//! }
//! ```
#![deny(warnings)]
#![deny(missing_docs)]
#![forbid(unsafe_op_in_unsafe_fn)]
mod header;
mod record;
mod store;

/// Errors from our functions.
#[derive(Debug)]
pub enum Error {
    /// Underlying filesystem issue (ENOENT, ENOSPC, etc).
    Io(std::io::Error),
    /// Open: not a file created by Syncless.
    NotSyncless,
    /// Open: a future version of Syncless, which says we're not compatible.
    UnsupportedVersion,
    /// Read: we just wrote a record, and it wasn't valid when we read it back.
    /// This should not happen.
    CorruptRecord,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Error::Io(err)
    }
}

/// Store comes in two flavors: ReadOnly and Writable.
pub struct Store<M> {
    base: StoreBase,
    writable: bool,
    _mode: std::marker::PhantomData<M>,
}

/// Phantom data to make a Readonly store
pub struct ReadOnly;
/// Phantom data to make a Writable store
pub struct Writable;

/// How to open the Syncless store file:
pub enum WriteOpenMode {
    /// Must exist, must be a Syncless store file.
    MustExist,
    /// Must not exist.
    MustNotExist,
    /// Must not exist or be a Syncless store file.
    MayExist,
}

pub use store::open_readonly;
pub use store::open;
use store::StoreBase;