pointbreak 0.10.0

Durable terminal code review for changes humans and coding agents collaborate on together
Documentation
#![cfg_attr(not(test), allow(dead_code))]

use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};

use crate::session::derived_access::layout::DerivedStorageLayout;

#[derive(Debug, thiserror::Error)]
pub(crate) enum WriterLockError {
    #[error("derived-access writer is busy")]
    Busy,
    #[error("writer-lock I/O failed at {path}: {message}")]
    Io { path: PathBuf, message: String },
    #[error("writer-lock layout resolution failed: {0}")]
    Layout(String),
}

#[derive(Debug)]
pub(crate) struct StoreWriterLock {
    file: File,
}

impl StoreWriterLock {
    pub(crate) fn acquire(store_root: &Path) -> Result<Self, WriterLockError> {
        let (file, path) = open_lock_file(store_root)?;
        file.lock().map_err(|error| io_error(&path, error))?;
        Ok(Self { file })
    }

    pub(crate) fn try_acquire(store_root: &Path) -> Result<Self, WriterLockError> {
        let (file, path) = open_lock_file(store_root)?;
        match file.try_lock() {
            Ok(()) => Ok(Self { file }),
            Err(std::fs::TryLockError::WouldBlock) => Err(WriterLockError::Busy),
            Err(std::fs::TryLockError::Error(error)) => Err(io_error(&path, error)),
        }
    }
}

impl Drop for StoreWriterLock {
    fn drop(&mut self) {
        let _ = self.file.unlock();
    }
}

fn open_lock_file(store_root: &Path) -> Result<(File, PathBuf), WriterLockError> {
    std::fs::create_dir_all(store_root).map_err(|error| io_error(store_root, error))?;
    let canonical_root = store_root
        .canonicalize()
        .map_err(|error| io_error(store_root, error))?;
    let path = DerivedStorageLayout::resolve(&canonical_root)
        .map_err(|error| WriterLockError::Layout(error.to_string()))?
        .writer_lock();
    let file = OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .truncate(false)
        .open(&path)
        .map_err(|error| io_error(&path, error))?;
    Ok((file, path))
}

fn io_error(path: &Path, error: std::io::Error) -> WriterLockError {
    WriterLockError::Io {
        path: path.to_path_buf(),
        message: error.to_string(),
    }
}