use crate::app_system_error;
use crate::tina::data::binary::StoredFileData;
use crate::tina::data::number::RoundNumber;
use crate::tina::data::AppResult;
use crate::tina::file::{FileContent, FileData};
use crate::tina::server::application::Application;
use crate::tina::util::not_empty::INotEmpty;
use crate::tina::util::string::{AsStr, IntoStr};
use bytes::Bytes;
use chrono::{Datelike, Local};
use image::EncodableLayout;
use once_cell::sync::Lazy;
use regex::{Regex, RegexBuilder};
use std::fs::File;
use std::io::Write;
use std::path::Path;
use tracing::error;
use uuid::Uuid;
static FILENAME_PATTERN: Lazy<Regex> =
Lazy::new(|| RegexBuilder::new("[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+").build().expect("build FILENAME_PATTERN failed"));
pub struct FileUtil;
impl FileUtil {
pub const KB: u64 = 1024;
pub const MB: u64 = Self::KB * 1024;
pub const GB: u64 = Self::MB * 1024;
pub const TB: u64 = Self::GB * 1024;
const DEFAULT_ALLOWED_EXTENSION: [&str; 22] = [
"bmp", "gif", "jpg", "jpeg", "png", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", "rar", "zip", "gz", "bz2", "mp4", "avi", "rmvb", "pdf", ];
pub async fn write_data_to_local(application: &Application, prefix: impl AsRef<str>, data: &mut FileData) -> AppResult<(String, u64)> {
let prefix = prefix.as_ref();
let extension = Self::get_file_ext_name(data);
let time = Local::now();
let file_path = format!("{}/{}/{}/{}/{}.{}", prefix, time.year(), time.month(), time.day(), Uuid::new_v4(), extension);
let path = format!("{}/{}", application.get_server_config()?.file_base_path, file_path);
let path = Path::new(path.as_str());
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|v| app_system_error!("创建目录失败: {}, reason: {:?}", parent.to_str().as_str(), v))?;
}
let mut file_size = 0;
let mut file = File::create(path).map_err(|v| app_system_error!("创建文件失败: {}, reason: {:?}", file_path, v))?;
while let Some(r) = futures::StreamExt::next(data).await {
let buf: Bytes = r?.into();
let buf_bytes = buf.as_bytes();
file.write(buf_bytes).map_err(|v| app_system_error!("写入文件内容失败: {}, reason: {:?}", file_path.as_str(), v))?;
file_size += buf_bytes.len() as u64;
}
Ok((file_path, file_size))
}
pub async fn read_local_data(application: &Application, related_path: impl AsRef<str>) -> AppResult<Option<FileContent>> {
let file_path = related_path.as_ref();
let path = format!("{}/{}", application.get_server_config()?.file_base_path, file_path);
let path = Path::new(path.as_str());
match path.exists() {
true => {
let file_data = StoredFileData::new(path, false);
Ok(Some(FileContent::from_stored_file_data(file_data)))
}
false => Ok(None),
}
}
pub fn get_file_name(file: &FileData) -> &str {
match file.get_original_filename() {
None => file.get_name(),
Some(v) => v,
}
}
pub fn get_file_ext_name(file: &FileData) -> String {
let content_type = file.get_content_type();
let mut ext = content_type.split_once('/').map(|v| v.1).into_str();
if ext.is_empty() {
let name = file.get_original_filename().into_str();
if name.not_empty() && name.contains('.') {
ext = name.rsplit_once('.').map(|v| v.1).into_str();
}
}
if ext.is_empty() {
let name = file.get_name();
if name.not_empty() && name.contains('.') {
ext = name.rsplit_once('.').map(|v| v.1).into_str();
}
}
while ext.starts_with('.') {
ext = ext.split_once('.').map(|v| v.1).into_str();
}
ext.to_lowercase()
}
pub fn delete_file(application: &Application, relative_path: impl AsRef<str>) -> AppResult<bool> {
let path = format!("{}/{}", application.get_server_config()?.file_base_path, relative_path.as_ref());
let path = Path::new(path.as_str());
match std::fs::remove_file(path) {
Ok(_) => Ok(true),
Err(err) => {
error!("删除文件失败: {:?}, reason: {:?}", path, err);
Ok(false)
}
}
}
pub fn get_file_type(filename: &str) -> &str {
filename.rsplit_once('.').map(|v| v.1).unwrap_or_default()
}
pub fn is_valid_filename(filename: impl AsRef<str>) -> bool {
let filename = filename.as_ref();
FILENAME_PATTERN.is_match(filename)
}
pub fn check_allow_download(resource: impl AsRef<str>) -> bool {
let resource = resource.as_ref();
if resource.contains("..") {
return false;
}
let file_type = Self::get_file_type(resource);
if Self::DEFAULT_ALLOWED_EXTENSION.contains(&file_type) {
return true;
}
false
}
pub fn format_file_size(size: u64) -> String {
if size >= Self::TB {
return format!("{} TB", ((size as f64) / (Self::TB as f64)).round_num(2, true));
}
if size >= Self::GB {
return format!("{} GB", ((size as f64) / (Self::GB as f64)).round_num(2, true));
}
if size >= Self::MB {
return format!("{} MB", ((size as f64) / (Self::MB as f64)).round_num(2, true));
}
if size >= Self::KB {
return format!("{} KB", ((size as f64) / (Self::KB as f64)).round_num(2, true));
}
format!("{} B", size)
}
}