use std::path::Path;
use link_cli::storage::LinksStorage;
use link_cli::transactions::{
CommitMode, FileTransitionLog, GenericTransactionsDecorator, LogRetentionPolicy,
};
use link_cli::{DoubletsStorage, LinkError, StorageRevision};
type Store = DoubletsStorage<usize, link_cli::FileMappedUnitStore<usize>>;
type Transactions = GenericTransactionsDecorator<usize, Store, FileTransitionLog>;
fn open(database: &Path, log: &Path) -> Result<Transactions, LinkError> {
let store = Store::open_exclusive(database)?;
let log = FileTransitionLog::open(log)?;
GenericTransactionsDecorator::new(
store,
log,
LogRetentionPolicy::default(),
CommitMode::default(),
false,
)
}
#[cfg(unix)]
fn file_identity(path: &Path) -> std::io::Result<Option<u64>> {
use std::os::unix::fs::MetadataExt;
Ok(Some(std::fs::metadata(path)?.ino()))
}
#[cfg(not(unix))]
fn file_identity(_path: &Path) -> std::io::Result<Option<u64>> {
Ok(None)
}
fn main() -> Result<(), LinkError> {
let directory = std::env::args()
.nth(1)
.unwrap_or_else(|| "/tmp/clink-embedded-store".to_string());
let directory = Path::new(&directory);
let _ = std::fs::remove_dir_all(directory);
std::fs::create_dir_all(directory)?;
let database = directory.join("db.links");
let log = directory.join("db.transitions.log");
let committed;
let abandoned;
let first_identity;
{
let mut transactions = open(&database, &log)?;
transactions.begin_transaction()?;
committed = transactions.create(0, 0)?;
transactions.commit()?;
println!("committed link {committed}");
first_identity = file_identity(&database)?;
transactions.begin_transaction()?;
abandoned = transactions.create(0, 0)?;
println!("wrote link {abandoned} inside a transaction that never commits");
assert!(Store::try_open_exclusive(&database)?.is_none());
println!("a second writer is locked out while the first one is open");
}
let revision;
{
let transactions = open(&database, &log)?;
assert!(
transactions.exists(committed),
"committed write must survive"
);
assert!(
!transactions.exists(abandoned),
"uncommitted write must be rolled back"
);
println!("after recovery: {committed} is present, {abandoned} is gone");
assert_eq!(
file_identity(&database)?,
first_identity,
"the database must be mutated in place, never replaced"
);
match first_identity {
Some(inode) => {
println!("inode unchanged ({inode}): another process's mapping stays valid")
}
None => println!("file identity unchanged: another process's mapping stays valid"),
}
revision = StorageRevision::of(&database)?;
}
{
let mut transactions = open(&database, &log)?;
transactions.begin_transaction()?;
let extra = transactions.create(committed, committed)?;
transactions.commit()?;
transactions.flush()?;
println!("another holder committed link {extra}");
}
let after = StorageRevision::of(&database)?;
println!("revision changed: {}", after != revision);
let store = Store::open_shared(&database)?;
println!("links in the store: {}", store.links_count());
Ok(())
}