gobby-wiki 0.7.0

Gobby wiki CLI shell
#[cfg(unix)]
use std::fs::File;
use std::fs::{self, OpenOptions};
use std::io::{ErrorKind, Write};
use std::path::{Path, PathBuf};

use crate::WikiError;

pub(super) fn write_video_markdown_atomic(path: &Path, bytes: &[u8]) -> Result<(), WikiError> {
    let temp_path = temp_sibling_path(path);
    let mut file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(&temp_path)
        .map_err(|error| WikiError::Io {
            action: "create video derived markdown temp file",
            path: Some(temp_path.clone()),
            source: error,
        })?;
    if let Err(error) = file.write_all(bytes) {
        let _ = fs::remove_file(&temp_path);
        return Err(WikiError::Io {
            action: "write video derived markdown temp file",
            path: Some(temp_path),
            source: error,
        });
    }
    if let Err(error) = file.sync_all() {
        let _ = fs::remove_file(&temp_path);
        return Err(WikiError::Io {
            action: "sync video derived markdown temp file",
            path: Some(temp_path),
            source: error,
        });
    }
    drop(file);
    if let Err(error) = fs::rename(&temp_path, path) {
        if error.kind() == ErrorKind::AlreadyExists {
            if let Err(remove_error) = fs::remove_file(path) {
                let _ = fs::remove_file(&temp_path);
                return Err(WikiError::Io {
                    action: "replace existing video derived markdown",
                    path: Some(path.to_path_buf()),
                    source: remove_error,
                });
            }
            if let Err(rename_error) = fs::rename(&temp_path, path) {
                let _ = fs::remove_file(&temp_path);
                return Err(WikiError::Io {
                    action: "write video derived markdown",
                    path: Some(path.to_path_buf()),
                    source: rename_error,
                });
            }
            return sync_parent_dir(path);
        }
        let _ = fs::remove_file(&temp_path);
        return Err(WikiError::Io {
            action: "write video derived markdown",
            path: Some(path.to_path_buf()),
            source: error,
        });
    }
    sync_parent_dir(path)
}

fn temp_sibling_path(path: &Path) -> PathBuf {
    let file_name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("video.md");
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|duration| duration.as_nanos())
        .unwrap_or_default();
    path.with_file_name(format!(".{file_name}.{}.{nanos}.tmp", std::process::id()))
}

fn sync_parent_dir(path: &Path) -> Result<(), WikiError> {
    #[cfg(not(unix))]
    {
        let _ = path;
        Ok(())
    }
    #[cfg(unix)]
    {
        let Some(parent) = path.parent() else {
            return Ok(());
        };
        File::open(parent)
            .and_then(|dir| dir.sync_all())
            .map_err(|error| WikiError::Io {
                action: "sync video derived markdown directory",
                path: Some(parent.to_path_buf()),
                source: error,
            })
    }
}