use crate::plugin::PluginInfo;
use crate::upload::{
Mode, ModeType, PathValidator, UploadContext, UploadError, UploadHook, UploadResult,
};
use async_trait::async_trait;
use chrono::Local;
use std::fs;
use std::path::{Path, PathBuf};
use uuid::Uuid;
pub struct LocalUploadHook {
plugin_info: PluginInfo,
base_path: PathBuf,
domain: String,
}
impl LocalUploadHook {
pub fn new(plugin_info: PluginInfo, base_path: impl Into<PathBuf>, domain: String) -> Self {
Self {
plugin_info,
base_path: base_path.into(),
domain,
}
}
fn get_date_dir(&self) -> String {
Local::now().format("%Y%m%d").to_string()
}
fn ensure_dir(&self, dir_path: &Path) -> UploadResult<()> {
if !dir_path.exists() {
fs::create_dir_all(dir_path)?;
}
Ok(())
}
}
#[async_trait]
impl UploadHook for LocalUploadHook {
fn plugin_info(&self) -> &PluginInfo {
&self.plugin_info
}
async fn get_mode(&self) -> UploadResult<Mode> {
Ok(Mode {
mode: ModeType::Local,
r#type: "local".to_string(),
})
}
async fn down_and_upload(&self, url: &str, file_name: Option<&str>) -> UploadResult<String> {
let file_data = if url.starts_with("http://") || url.starts_with("https://") {
return Err(UploadError::Http(
"HTTP 下载功能需要 reqwest 依赖".to_string(),
));
} else {
fs::read(url)?
};
let date_dir = self.get_date_dir();
let base_path = &self.base_path;
let extension = if let Some(name) = file_name {
Path::new(name)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
} else {
Path::new(url)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
};
let safe_file_name = if let Some(name) = file_name {
let sanitized = PathValidator::sanitize_path(name)?;
Path::new(&sanitized)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&sanitized)
.to_string()
} else {
format!("{}.{}", Uuid::new_v4(), extension)
};
let dir_path = base_path.join(&date_dir);
self.ensure_dir(&dir_path)?;
let target_path = dir_path.join(&safe_file_name);
PathValidator::validate_target_path(&target_path, base_path)?;
fs::write(&target_path, file_data)?;
Ok(format!(
"{}/upload/{}/{}",
self.domain, date_dir, safe_file_name
))
}
async fn upload_with_key(&self, file_path: &Path, key: &str) -> UploadResult<String> {
let date_dir = self.get_date_dir();
let base_path = &self.base_path;
let safe_key = PathValidator::sanitize_path(key)?;
let data = fs::read(file_path)?;
let target_path = base_path.join(&date_dir).join(&safe_key);
let dir_path = target_path.parent().unwrap_or(base_path);
PathValidator::validate_target_path(&target_path, base_path)?;
self.ensure_dir(dir_path)?;
fs::write(&target_path, data)?;
Ok(format!("{}/upload/{}/{}", self.domain, date_dir, safe_key))
}
async fn upload(&self, ctx: &UploadContext) -> UploadResult<String> {
if ctx.file_data.is_empty() {
return Err(UploadError::EmptyFile);
}
let date_dir = self.get_date_dir();
let base_path = &self.base_path;
let safe_key = if let Some(key) = &ctx.key {
Some(PathValidator::sanitize_path(key)?)
} else {
None
};
let extension = Path::new(&ctx.filename)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
let final_name = if let Some(key) = safe_key {
key
} else {
format!("{}.{}", Uuid::new_v4(), extension)
};
let name = format!("{}/{}", date_dir, final_name);
let target = base_path.join(&name);
PathValidator::validate_target_path(&target, base_path)?;
let dir_path = base_path.join(&date_dir);
self.ensure_dir(&dir_path)?;
fs::write(&target, &ctx.file_data)?;
Ok(format!("{}/upload/{}", self.domain, name))
}
}