1use std::future::Future;
2
3use log::{debug, info};
7use serde::{Deserialize, Serialize};
8
9use super::FileClient;
10use crate::errors::{NetDiskError, NetDiskResult};
11
12pub(crate) trait FileManagementExt {
14 fn create_folder(&self, path: &str) -> impl Future<Output = NetDiskResult<FolderInfo>> + Send;
15
16 fn create_folder_with_options(
17 &self,
18 path: &str,
19 options: FolderCreateOptions,
20 ) -> impl Future<Output = NetDiskResult<FolderInfo>> + Send;
21
22 fn rename(&self, path: &str, new_name: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
23
24 fn delete(&self, path: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
25
26 fn move_file(&self, path: &str, dest: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
27
28 fn copy_file(&self, path: &str, dest: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
29}
30
31impl FileManagementExt for FileClient {
32 async fn create_folder(&self, path: &str) -> NetDiskResult<FolderInfo> {
33 self.create_folder_with_options(path, FolderCreateOptions::default())
34 .await
35 }
36
37 async fn create_folder_with_options(
38 &self,
39 path: &str,
40 options: FolderCreateOptions,
41 ) -> NetDiskResult<FolderInfo> {
42 let token = self.token_getter.get_token().await?;
43 let params = [("method", "create"), ("access_token", &token.access_token)];
44
45 let form = [
46 ("path", path),
47 ("isdir", "1"),
48 ("rtype", &options.rtype.to_string()),
49 ("mode", &options.mode.to_string()),
50 ];
51
52 debug!(
53 "Creating folder at path: {} with options: {:?}",
54 path, options
55 );
56
57 let response: FolderResponse = self
58 .http_client()
59 .post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
60 .await?;
61
62 if response.errno != 0 {
63 let errmsg = response.errmsg.as_deref().unwrap_or("Unknown error");
64 return Err(NetDiskError::api_error(response.errno, errmsg));
65 }
66
67 info!("Folder created successfully at: {}", path);
68
69 Ok(FolderInfo {
70 fs_id: response.fs_id,
71 path: response.path.unwrap_or_else(|| path.to_string()),
72 ctime: response.ctime,
73 mtime: response.mtime,
74 isdir: response.isdir,
75 category: response.category,
76 })
77 }
78
79 async fn rename(&self, path: &str, new_name: &str) -> NetDiskResult<()> {
80 let token = self.token_getter.get_token().await?;
81 let filelist = serde_json::json!([
82 {
83 "path": path,
84 "newname": new_name
85 }
86 ]);
87
88 let params = [
89 ("method", "filemanager"),
90 ("access_token", &token.access_token),
91 ("opera", "rename"),
92 ];
93
94 let form = [
95 ("async", "0"),
96 ("filelist", &filelist.to_string()),
97 ("ondup", "overwrite"),
98 ];
99
100 debug!("Renaming path: {} to: {}", path, new_name);
101
102 let response: FileOperationResponse = self
103 .http_client()
104 .post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
105 .await?;
106
107 if response.errno != 0 {
108 return Err(NetDiskError::api_error(response.errno, "Rename failed"));
109 }
110
111 info!("Renamed successfully: {} -> {}", path, new_name);
112 Ok(())
113 }
114
115 async fn delete(&self, path: &str) -> NetDiskResult<()> {
116 let token = self.token_getter.get_token().await?;
117 let filelist = serde_json::json!([
118 {
119 "path": path
120 }
121 ]);
122
123 let params = [
124 ("method", "filemanager"),
125 ("access_token", &token.access_token),
126 ("opera", "delete"),
127 ];
128
129 let form = [
130 ("async", "0"),
131 ("filelist", &filelist.to_string()),
132 ("ondup", "overwrite"),
133 ];
134
135 debug!("Deleting path: {}", path);
136
137 let response: FileOperationResponse = self
138 .http_client()
139 .post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
140 .await?;
141
142 if response.errno != 0 {
143 return Err(NetDiskError::api_error(response.errno, "Delete failed"));
144 }
145
146 info!("Deleted successfully: {}", path);
147 Ok(())
148 }
149
150 async fn move_file(&self, path: &str, dest: &str) -> NetDiskResult<()> {
151 let token = self.token_getter.get_token().await?;
152 let filename = path.rsplit('/').next().unwrap_or(path);
153
154 let filelist = serde_json::json!([
155 {
156 "path": path,
157 "dest": dest,
158 "newname": filename,
159 "ondup": "fail"
160 }
161 ]);
162
163 let params = [
164 ("method", "filemanager"),
165 ("access_token", &token.access_token),
166 ("opera", "move"),
167 ];
168
169 let form = [
170 ("async", "0"),
171 ("filelist", &filelist.to_string()),
172 ("ondup", "fail"),
173 ];
174
175 debug!("Moving path: {} to: {}", path, dest);
176
177 let response: FileOperationResponse = self
178 .http_client()
179 .post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
180 .await?;
181
182 if response.errno != 0 {
183 return Err(NetDiskError::api_error(response.errno, "Move failed"));
184 }
185
186 info!("Moved successfully: {} -> {}", path, dest);
187 Ok(())
188 }
189
190 async fn copy_file(&self, path: &str, dest: &str) -> NetDiskResult<()> {
191 let token = self.token_getter.get_token().await?;
192 let filename = path.rsplit('/').next().unwrap_or(path);
193
194 let filelist = serde_json::json!([
195 {
196 "path": path,
197 "dest": dest,
198 "newname": filename,
199 "ondup": "fail"
200 }
201 ]);
202
203 let params = [
204 ("method", "filemanager"),
205 ("access_token", &token.access_token),
206 ("opera", "copy"),
207 ];
208
209 let form = [
210 ("async", "0"),
211 ("filelist", &filelist.to_string()),
212 ("ondup", "fail"),
213 ];
214
215 debug!("Copying path: {} to: {}", path, dest);
216
217 let response: FileOperationResponse = self
218 .http_client()
219 .post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
220 .await?;
221
222 if response.errno != 0 {
223 return Err(NetDiskError::api_error(response.errno, "Copy failed"));
224 }
225
226 info!("Copied successfully: {} -> {}", path, dest);
227 Ok(())
228 }
229}
230
231#[derive(Debug, Deserialize, Serialize, Default)]
232pub struct FolderCreateOptions {
233 pub rtype: i32,
234 pub mode: i32,
235}
236
237impl FolderCreateOptions {
238 pub fn new() -> Self {
239 Self::default()
240 }
241
242 pub fn rtype(mut self, rtype: i32) -> Self {
243 self.rtype = rtype;
244 self
245 }
246
247 pub fn mode(mut self, mode: i32) -> Self {
248 self.mode = mode;
249 self
250 }
251}
252
253#[derive(Debug, Deserialize, Serialize, Clone)]
254pub struct FolderInfo {
255 pub fs_id: Option<u64>,
256 pub path: String,
257 pub ctime: Option<u64>,
258 pub mtime: Option<u64>,
259 pub isdir: Option<i32>,
260 pub category: Option<i32>,
261}
262
263#[derive(Debug, Deserialize)]
264struct FolderResponse {
265 errno: i32,
266 errmsg: Option<String>,
267 fs_id: Option<u64>,
268 path: Option<String>,
269 ctime: Option<u64>,
270 mtime: Option<u64>,
271 isdir: Option<i32>,
272 category: Option<i32>,
273}
274
275#[derive(Debug, Deserialize)]
276#[allow(dead_code)]
277struct FileOperationResponse {
278 errno: i32,
279 info: Option<Vec<FileProcessStatus>>,
280 taskid: Option<u64>,
281}
282
283#[derive(Debug, Deserialize)]
284#[allow(dead_code)]
285struct FileProcessStatus {
286 errno: i32,
287 path: String,
288}