zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
pub use crate::core::error2::Error;
pub use crate::core::error2::Result;
use actix_multipart::Field;
use futures_util::TryStreamExt as _;
use md5::{Digest, Md5};
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;

use futures_util::stream::StreamExt;
use tokio_util::io::StreamReader;

pub fn file_stem(file_name: &str) -> String {
    std::path::Path::new(&file_name)
        .file_stem()
        .unwrap()
        .to_string_lossy()
        .into_owned()
}

pub fn file_name(file_name: &str) -> String {
    std::path::Path::new(&file_name)
        .file_name()
        .unwrap()
        .to_string_lossy()
        .into_owned()
}

pub fn file_ext(file_name: &str) -> String {
    std::path::Path::new(&file_name)
        .extension()
        .unwrap()
        .to_string_lossy()
        .into_owned()
}

pub async fn save_upload(
    mut upload_file: actix_multipart::Field,
) -> Result<(String, std::path::PathBuf, String)> {
    let curr_file_name: String;

    if let Some(name) = upload_file.content_disposition().unwrap().get_filename() {
        curr_file_name = name.to_string();
    } else {
        // anyhow::bail!("Cannot get the upload filename");
        return Err(Error::run_time("Cannot get the upload filename"));
    }

    let file_name = format!("{}-{}", uuid::Uuid::now_v7(), curr_file_name);
    let destination = std::path::PathBuf::from(super::upload_folder()).join(file_name.clone());

    let mut saved_file = std::fs::File::create(&destination).map_err(|e| anyhow::anyhow!(e))?;

    while let Ok(Some(chunk)) = upload_file.try_next().await {
        saved_file
            .write_all(&chunk)
            .map_err(|e| anyhow::anyhow!(e))?;
    }

    let file_md5 = super::file_md5(&destination);

    Ok((curr_file_name, destination, file_md5))
}

pub fn field_to_futures_reader(field: Field) -> impl tokio::io::AsyncRead {
    let stream = field.map(|res| res.map_err(|e| std::io::Error::other(format!("{}", e))));

    StreamReader::new(stream)
}

pub fn extract_filename(field: &Field) -> Result<String> {
    // content_disposition() 可能为 None
    let cd = field
        .content_disposition()
        .ok_or_else(|| anyhow::anyhow!("Missing content disposition"))?;

    // get_filename() 也可能为 None
    let filename = cd
        .get_filename()
        .ok_or_else(|| anyhow::anyhow!("Missing filename in content disposition"))?;

    // 将 Cow<'_, str> 转成 String
    Ok(filename.to_string())
}

pub fn path_name(path: &std::path::Path) -> String {
    path.file_name().unwrap().to_string_lossy().into_owned()
}

pub fn file_md5(file_path: &std::path::Path) -> String {
    // Open the file
    let mut file = File::open(file_path).unwrap();

    // Create an MD5 hasher
    let mut context = Md5::default();

    // Read the file in chunks and update the hasher
    let mut buffer = [0; 1024];

    loop {
        let n = file.read(&mut buffer).unwrap();

        if n == 0 {
            break;
        }

        // context.consume(&buffer[..n]);
        context.update(&buffer[..n]);
    }

    // Get the MD5 hash as a hexadecimal string
    let result = format!("{:x}", context.finalize());

    result
}

pub fn list_files(dir: std::path::PathBuf, _ext: &str) -> Result<Vec<std::path::PathBuf>> {
    let paths = std::fs::read_dir(dir)?
        // Filter out all those directory entries which couldn't be read
        .filter_map(|res| res.ok())
        // Map the directory entries to paths
        .map(|dir_entry| dir_entry.path())
        // Filter out all paths with extensions other than `csv`
        .filter_map(|path| {
            if path.extension().is_some_and(|ext| ext == _ext) {
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    Ok(paths)
}

pub fn list_all_files(dir: &str, _ext: &str) -> anyhow::Result<Vec<PathBuf>> {
    let mut files = Vec::new();

    for entry in glob::glob(dir)? {
        let path = entry?;

        if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some(_ext) {
            files.push(path);
        }
    }

    Ok(files)
}