small-bin 1.0.4

Sync'eM ALL - File/ Screenshot sync application with SFTP support, and web dashboard.
Documentation
use crate::*;
use anyhow::Result;
use std::{
    fmt::Debug,
    fs,
    future::Future,
    io::{self, Read, Write},
    path::{Path, PathBuf},
    pin::Pin,
    time::SystemTime,
};
use tracing_subscriber::{EnvFilter, fmt};


/// Initialize the instruments-subscriber
pub fn initialize() {
    let env_log = match EnvFilter::try_from_env("LOG") {
        Ok(env_value_from_env) => env_value_from_env,
        Err(_) => EnvFilter::from("info"),
    };
    fmt()
        .compact()
        .with_thread_names(false)
        .with_thread_ids(false)
        .with_ansi(true)
        .with_env_filter(env_log)
        .with_filter_reloading()
        .init();
}


pub fn put_to_clipboard(text: &str) -> Result<()> {
    let mut clipboard = clippers::Clipboard::get();
    clipboard.write_text(text)?;
    Ok(())
}


pub fn local_file_size<P: AsRef<Path>>(file_path: P) -> Result<u64> {
    let metadata = fs::metadata(file_path)?;
    Ok(metadata.len())
}


pub async fn wait_for_file<P>(path: P) -> Result<()>
where
    P: AsRef<Path> + Debug,
{
    fn check(
        path: PathBuf,
        last_size: u64,
        last_mtime: Option<SystemTime>,
        stable_intervals: u32,
        attempts: u32,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
        Box::pin(async move {
            if attempts >= 20 {
                warn!("File {path:?} did not stabilize after 5 seconds");
                Ok(())
            } else {
                let (new_size, new_mtime) = fs::metadata(&path)
                    .map(|m| (m.len(), m.modified().ok()))
                    .unwrap_or((0, None));

                let current_stable_intervals =
                    if new_size > 0 && new_size == last_size && new_mtime == last_mtime {
                        stable_intervals + 1
                    } else {
                        0
                    };

                if current_stable_intervals >= 2 {
                    debug!("File {:?} is stable after {}ms", path, attempts * 250);
                    Ok(())
                } else {
                    tokio::time::sleep(std::time::Duration::from_millis(250)).await;
                    check(
                        path,
                        new_size,
                        new_mtime,
                        current_stable_intervals,
                        attempts + 1,
                    )
                    .await
                }
            }
        })
    }

    check(path.as_ref().to_path_buf(), 0, None, 0, 0).await
}


pub fn size_kib(size_in_bytes: u64) -> f64 {
    (size_in_bytes as f64) / 1024.0
}


pub fn file_extension<P: AsRef<Path>>(path: P) -> String {
    path.as_ref()
        .extension()
        .and_then(|e| e.to_str())
        .map(|e| format!(".{e}"))
        .unwrap_or_default()
}


pub fn stream_file_to_remote<R, W>(
    reader: &mut R,
    writer: &mut W,
    buffer_size: usize,
    total_size: u64,
) -> Result<()>
where
    R: Read,
    W: Write,
{
    let mut buffer = vec![0u8; buffer_size];
    // let mut bytes_written = 0u64;
    let chunks = if total_size > 0 {
        (total_size / buffer_size as u64) + 1
    } else {
        1
    };

    info!(
        "Streaming file of size: {:.2}KiB to remote server..",
        size_kib(total_size)
    );

    let mut chunk_index = 0u64;
    loop {
        let bytes_read = reader.read(&mut buffer)?;
        if bytes_read == 0 {
            break;
        }

        writer.write_all(&buffer[..bytes_read])?;
        chunk_index += 1;

        let percent = if chunks > 0 {
            (chunk_index as f64 * 100.0) / chunks as f64
        } else {
            100.0
        };

        eprint!("\rProgress: {percent:.2}% ");
        io::stderr().flush()?;
    }

    eprintln!(); // New line after progress
    info!("Upload complete!");
    Ok(())
}