grr-cli 0.4.0

Google tools from the terminal, at maximum performance: zero-config Gmail, Calendar, Drive, Contacts, Chat and Forms over HTTP/3
use std::path::Path;

use bytes::Bytes;

use crate::core::error::Result;

#[cfg(target_os = "linux")]
use std::path::PathBuf;

#[cfg(target_os = "linux")]
use tokio_uring::buf::BoundedBuf;

#[cfg(target_os = "linux")]
use crate::core::error::GrrError;

pub async fn write_file(path: &Path, data: Bytes) -> Result<()> {
    #[cfg(target_os = "linux")]
    if crate::core::runtime::has_io_uring().await {
        return ring_write(path.to_path_buf(), data).await;
    }
    if let Some(parent) = path.parent() {
        tokio::fs::create_dir_all(parent).await?;
    }
    tokio::fs::write(path, &data[..]).await?;
    Ok(())
}

pub async fn read_file(path: &Path) -> Result<Bytes> {
    #[cfg(target_os = "linux")]
    if crate::core::runtime::has_io_uring().await {
        return ring_read(path.to_path_buf()).await;
    }
    let vec = tokio::fs::read(path).await?;
    Ok(Bytes::from(vec))
}

#[cfg(target_os = "linux")]
async fn ring_write(path: PathBuf, data: Bytes) -> Result<()> {
    tokio::task::spawn_blocking(move || {
        tokio_uring::start(async move {
            if let Some(parent) = path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            let file = tokio_uring::fs::File::create(&path).await?;
            let mut buffer = data.to_vec();
            let mut offset = 0_u64;
            while offset < buffer.len() as u64 {
                let (result, returned) = file
                    .write_at(buffer.slice(offset as usize..), offset)
                    .submit()
                    .await;
                buffer = returned.into_inner();
                let written = result?;
                if written == 0 {
                    return Err(GrrError::Internal(format!(
                        "io_uring write made no progress at offset {offset}"
                    )));
                }
                offset += written as u64;
            }
            file.sync_all().await?;
            file.close().await?;
            Ok(())
        })
    })
    .await
    .map_err(|error| GrrError::Internal(error.to_string()))?
}

#[cfg(target_os = "linux")]
async fn ring_read(path: PathBuf) -> Result<Bytes> {
    tokio::task::spawn_blocking(move || {
        tokio_uring::start(async move {
            let file = tokio_uring::fs::File::open(&path).await?;
            let len = usize::try_from(std::fs::metadata(&path)?.len()).map_err(|error| {
                GrrError::Internal(format!("file is too large to read: {error}"))
            })?;
            let buffer = vec![0_u8; len];
            let (result, mut buffer) = file.read_at(buffer, 0).await;
            let read = result?;
            if read != len {
                return Err(GrrError::Internal(format!(
                    "io_uring short read on {path:?}: got {read} of {len} bytes"
                )));
            }
            buffer.truncate(read);
            file.close().await?;
            Ok::<_, GrrError>(Bytes::from(buffer))
        })
    })
    .await
    .map_err(|error| GrrError::Internal(error.to_string()))?
}