ghee 0.6.1

That thin layer of data change management over the filesystem
Documentation
use std::{
    fs::{create_dir, rename},
    path::PathBuf,
};

use anyhow::Result;
use btrfsutil::subvolume::{SnapshotFlags, Subvolume};
use file_owner::{owner_group, set_owner_group};
use sudo::escalate_if_needed;
use thiserror::Error;
use uuid::Uuid;

use crate::{
    paths::{table_snapshot_path, table_snapshots_path},
    XATTR_COMMIT_MESSAGE, XATTR_HEAD,
};

#[derive(Error, Debug)]
pub enum CommitErr {
    #[error("Filesystem is not BTRFS, or some other error occurred")]
    FilesystemIsNotBtrfs,

    #[error("Could not escalate privileges")]
    CouldNotEscalatePrivileges,
}

/**
 * Commit a table, storing its contents as a BTRFS snapshot
 *
 * Returns the commit UUID
 */
pub fn commit(dir: &PathBuf, message: &Option<String>, verbose: bool) -> Result<Uuid> {
    escalate_if_needed().map_err(|_e| CommitErr::CouldNotEscalatePrivileges)?;

    let subvol = Subvolume::get(dir.as_path()).map_err(|e| {
        eprintln!("LibError: {}", e);
        CommitErr::FilesystemIsNotBtrfs
    })?;

    let snapshots_dir = table_snapshots_path(dir);

    // Only create the snapshot dir itself, because its parent is `dir`
    if !snapshots_dir.exists() {
        create_dir(&snapshots_dir)?;

        let (owner, group) = owner_group(dir)?;

        set_owner_group(&snapshots_dir, owner, group)?;
    }

    let snapshot_initial_path = {
        let mut p = snapshots_dir.clone();

        p.push(subvol.id().to_string());

        p
    };

    let flags = {
        let mut f = SnapshotFlags::empty();
        f.set(SnapshotFlags::READ_ONLY, true);
        // f.set(SnapshotFlags::RECURSIVE, true);
        f
    };

    // Set the snapshot message now so it's captured in the snapshot
    if let Some(message) = message.as_ref() {
        xattr::set(dir, XATTR_COMMIT_MESSAGE.to_osstring(), message.as_bytes())?;
    }

    let snap = subvol.snapshot(snapshot_initial_path.as_path(), Some(flags), None)?;

    let uuid = snap.info().unwrap().uuid;

    let snapshot_path = table_snapshot_path(dir, uuid);

    // Relocate to snapshot uuid based path
    rename(snapshot_initial_path, snapshot_path)?;

    // Set XATTR_MOST_RECENT_SNAPSHOT to the snapshot uuid
    xattr::set(dir, XATTR_HEAD.to_osstring(), uuid.to_string().as_bytes())?;

    // Unset XATTR_SNAPSHOT_MESSAGE
    xattr::remove(dir, XATTR_COMMIT_MESSAGE.to_osstring())?;

    if verbose {
        println!("Committed snapshot {}", uuid);
    }

    Ok(uuid)
}

// #[cfg(test)]
// mod test {
//     use std::{
//         fs::{copy, create_dir, remove_file},
//         path::Path,
//     };

//     use is_superuser::is_superuser;
//     use sys_mount::{Mount, UnmountFlags};
//     use tempdir::TempDir;

//     use crate::{
//         cmd::{init, touch},
//         parser::key::Key,
//     };

//     use super::commit;

//     #[test]
//     fn test_commit_btrfs() {
//         if is_superuser() {
//             let btrfs_img = Path::new("./btrfs.img");
//             let dir = TempDir::new("ghee-test-commit").unwrap().into_path();

//             let btrfs_rw_img = {
//                 let mut p = dir.clone();
//                 p.push("btrfs_rw.img");
//                 p
//             };

//             copy(btrfs_img, &btrfs_rw_img).unwrap();

//             let mount_target = {
//                 let mut p = dir.clone();
//                 p.push("mount-target");
//                 p
//             };

//             create_dir(&mount_target).unwrap();

//             println!("btrfs_rw_img: {}", btrfs_rw_img.display());
//             {
//                 let _mount_result = Mount::builder()
//                     .fstype("btrfs")
//                     // .data("subvol=@home")
//                     .mount_autodrop(&btrfs_rw_img, &mount_target, UnmountFlags::empty())
//                     .unwrap();

//                 let key = Key::from_string("purpleness");

//                 // Make a database where things are indexed by their degree of purpleness (1 to 10)
//                 init(&mount_target, &key, false).unwrap();

//                 let file = {
//                     let mut p = mount_target.clone();
//                     p.push("7");
//                     p
//                 };

//                 // Create the record: { "purpleness": 7 }
//                 touch(&file, false).unwrap();

//                 commit(
//                     &mount_target,
//                     &Some(String::from("Add purpleness of 7")),
//                     false,
//                 )
//                 .unwrap();
//             }

//             remove_file(btrfs_rw_img).unwrap();
//         } else {
//             eprintln!("Test of `commit` requires superuser privileges");
//         }
//     }
// }