cool_plugin/upload/
local.rs1use crate::plugin::PluginInfo;
6use crate::upload::{
7 Mode, ModeType, PathValidator, UploadContext, UploadError, UploadHook, UploadResult,
8};
9use async_trait::async_trait;
10use chrono::Local;
11use std::fs;
12use std::path::{Path, PathBuf};
13use uuid::Uuid;
14
15pub struct LocalUploadHook {
19 plugin_info: PluginInfo,
21 base_path: PathBuf,
23 domain: String,
25}
26
27impl LocalUploadHook {
28 pub fn new(plugin_info: PluginInfo, base_path: impl Into<PathBuf>, domain: String) -> Self {
30 Self {
31 plugin_info,
32 base_path: base_path.into(),
33 domain,
34 }
35 }
36
37 fn get_date_dir(&self) -> String {
39 Local::now().format("%Y%m%d").to_string()
40 }
41
42 fn ensure_dir(&self, dir_path: &Path) -> UploadResult<()> {
44 if !dir_path.exists() {
45 fs::create_dir_all(dir_path)?;
46 }
47 Ok(())
48 }
49}
50
51#[async_trait]
52impl UploadHook for LocalUploadHook {
53 fn plugin_info(&self) -> &PluginInfo {
54 &self.plugin_info
55 }
56
57 async fn get_mode(&self) -> UploadResult<Mode> {
58 Ok(Mode {
59 mode: ModeType::Local,
60 r#type: "local".to_string(),
61 })
62 }
63
64 async fn down_and_upload(&self, url: &str, file_name: Option<&str>) -> UploadResult<String> {
65 let file_data = if url.starts_with("http://") || url.starts_with("https://") {
67 return Err(UploadError::Http(
71 "HTTP 下载功能需要 reqwest 依赖".to_string(),
72 ));
73 } else {
74 fs::read(url)?
76 };
77
78 let date_dir = self.get_date_dir();
79 let base_path = &self.base_path;
80
81 let extension = if let Some(name) = file_name {
83 Path::new(name)
84 .extension()
85 .and_then(|ext| ext.to_str())
86 .unwrap_or("")
87 } else {
88 Path::new(url)
89 .extension()
90 .and_then(|ext| ext.to_str())
91 .unwrap_or("")
92 };
93
94 let safe_file_name = if let Some(name) = file_name {
96 let sanitized = PathValidator::sanitize_path(name)?;
97 Path::new(&sanitized)
99 .file_name()
100 .and_then(|n| n.to_str())
101 .unwrap_or(&sanitized)
102 .to_string()
103 } else {
104 format!("{}.{}", Uuid::new_v4(), extension)
105 };
106
107 let dir_path = base_path.join(&date_dir);
109 self.ensure_dir(&dir_path)?;
110
111 let target_path = dir_path.join(&safe_file_name);
113 PathValidator::validate_target_path(&target_path, base_path)?;
114 fs::write(&target_path, file_data)?;
115
116 Ok(format!(
117 "{}/upload/{}/{}",
118 self.domain, date_dir, safe_file_name
119 ))
120 }
121
122 async fn upload_with_key(&self, file_path: &Path, key: &str) -> UploadResult<String> {
123 let date_dir = self.get_date_dir();
124 let base_path = &self.base_path;
125
126 let safe_key = PathValidator::sanitize_path(key)?;
128
129 let data = fs::read(file_path)?;
131
132 let target_path = base_path.join(&date_dir).join(&safe_key);
134 let dir_path = target_path.parent().unwrap_or(base_path);
135
136 PathValidator::validate_target_path(&target_path, base_path)?;
138
139 self.ensure_dir(dir_path)?;
141
142 fs::write(&target_path, data)?;
144
145 Ok(format!("{}/upload/{}/{}", self.domain, date_dir, safe_key))
146 }
147
148 async fn upload(&self, ctx: &UploadContext) -> UploadResult<String> {
149 if ctx.file_data.is_empty() {
150 return Err(UploadError::EmptyFile);
151 }
152
153 let date_dir = self.get_date_dir();
154 let base_path = &self.base_path;
155
156 let safe_key = if let Some(key) = &ctx.key {
158 Some(PathValidator::sanitize_path(key)?)
159 } else {
160 None
161 };
162
163 let extension = Path::new(&ctx.filename)
165 .extension()
166 .and_then(|ext| ext.to_str())
167 .unwrap_or("");
168
169 let final_name = if let Some(key) = safe_key {
171 key
172 } else {
173 format!("{}.{}", Uuid::new_v4(), extension)
174 };
175
176 let name = format!("{}/{}", date_dir, final_name);
177 let target = base_path.join(&name);
178
179 PathValidator::validate_target_path(&target, base_path)?;
181
182 let dir_path = base_path.join(&date_dir);
184 self.ensure_dir(&dir_path)?;
185
186 fs::write(&target, &ctx.file_data)?;
188
189 Ok(format!("{}/upload/{}", self.domain, name))
190 }
191}