Skip to main content

cloudreve_api/api/v4/
file.rs

1//! File-related API endpoints for Cloudreve v4 API
2
3use crate::Error;
4use crate::api::v4::ApiV4Client;
5use crate::api::v4::models::*;
6use crate::api::v4::uri::*;
7
8/// File management methods
9impl ApiV4Client {
10    pub async fn upload_file(&self, request: &UploadRequest<'_>) -> Result<File, Error> {
11        let response: ApiResponse<File> = self.post("/file", request).await?;
12        match response.data {
13            Some(data) => Ok(data),
14            None => Err(Error::InvalidResponse(format!(
15                "API returned no data for upload_file request: {:?}",
16                response
17            ))),
18        }
19    }
20
21    /// Lists files in a directory with full response including metadata
22    ///
23    /// Returns a complete `ListResponse` containing:
24    /// - `files`: Vector of files/folders in the directory
25    /// - `parent`: Information about the parent directory
26    /// - `pagination`: Pagination information (page, total_items, etc.)
27    /// - `storage_policy`: Preferred storage policy for uploads to this directory
28    /// - `props`: Navigator capabilities and settings
29    ///
30    /// # Arguments
31    /// * `request` - ListFilesRequest with path and optional pagination params
32    pub async fn list_files(&self, request: &ListFilesRequest<'_>) -> Result<ListResponse, Error> {
33        let mut url = "/file".to_string();
34        if let Some(path) = request.path.strip_prefix('/') {
35            url.push_str(&format!("?uri={}", path_to_uri(path)));
36        } else {
37            url.push_str(&format!("?uri={}", path_to_uri(request.path)));
38        }
39        if let Some(page) = request.page {
40            url.push_str(&format!("&page={}", page));
41        }
42        if let Some(page_size) = request.page_size {
43            url.push_str(&format!("&page_size={}", page_size));
44        }
45        if let Some(order_by) = request.order_by {
46            url.push_str(&format!("&order_by={}", order_by));
47        }
48        if let Some(order_direction) = request.order_direction {
49            url.push_str(&format!("&order_direction={}", order_direction));
50        }
51        if let Some(next_page_token) = request.next_page_token {
52            url.push_str(&format!("&next_page_token={}", next_page_token));
53        }
54
55        let response: ApiResponse<ListResponse> = self.get(&url).await?;
56        match response.data {
57            Some(data) => Ok(data),
58            None => Err(Error::InvalidResponse(format!(
59                "API returned no data for list_files request: {:?}",
60                response
61            ))),
62        }
63    }
64
65    pub async fn get_file_info(&self, file_path: &str) -> Result<File, Error> {
66        // URI encode the path for V4 API, use /file/info endpoint
67        let uri = path_to_uri(file_path);
68        let response: ApiResponse<File> = self.get(&format!("/file/info?uri={}", uri)).await?;
69        match response.data {
70            Some(data) => Ok(data),
71            None => Err(Error::InvalidResponse(format!(
72                "API returned no data for get_file_info request: {:?}",
73                response
74            ))),
75        }
76    }
77
78    pub async fn get_file_stat(&self, file_path: &str) -> Result<FileStat, Error> {
79        let response: ApiResponse<FileStat> =
80            self.get(&format!("/file/stat/{}", file_path)).await?;
81        match response.data {
82            Some(data) => Ok(data),
83            None => Err(Error::InvalidResponse(format!(
84                "API returned no data for get_file_stat request: {:?}",
85                response
86            ))),
87        }
88    }
89
90    pub async fn move_file(&self, request: &MoveFileRequest<'_>) -> Result<(), Error> {
91        let response: ApiResponse<()> = self.post("/file/move", request).await?;
92        match response.code {
93            0 => Ok(()),
94            code => Err(Error::Api {
95                code,
96                message: response.msg,
97            }),
98        }
99    }
100
101    pub async fn copy_file(&self, request: &CopyFileRequest<'_>) -> Result<(), Error> {
102        // V4 API uses /file/move endpoint with copy=true for copy operations
103        let move_request = MoveFileRequest {
104            uris: request.uris.clone(),
105            dst: request.dst,
106            copy: Some(true),
107        };
108        let response: ApiResponse<()> = self.post("/file/move", &move_request).await?;
109        match response.code {
110            0 => Ok(()),
111            code => Err(Error::Api {
112                code,
113                message: response.msg,
114            }),
115        }
116    }
117
118    pub async fn rename_file(
119        &self,
120        request: &RenameFileRequest<'_>,
121    ) -> Result<crate::api::v4::models::File, Error> {
122        let response: ApiResponse<crate::api::v4::models::File> =
123            self.post("/file/rename", request).await?;
124        match response.data {
125            Some(data) => Ok(data),
126            None => Err(Error::InvalidResponse(format!(
127                "API returned no data for rename_file request: {:?}",
128                response
129            ))),
130        }
131    }
132
133    pub async fn delete_file(&self, file_path: &str) -> Result<(), Error> {
134        // Convert path to URI for V4 API
135        let uri = path_to_uri(file_path);
136        let request = DeleteFileRequest {
137            uris: vec![uri.as_str()],
138            unlink: None,
139            skip_soft_delete: None,
140        };
141        let response: ApiResponse<()> = self.delete_with_body("/file", &request).await?;
142        match response.code {
143            0 => Ok(()),
144            code => Err(Error::Api {
145                code,
146                message: response.msg,
147            }),
148        }
149    }
150
151    pub async fn create_directory(&self, path: &str) -> Result<(), Error> {
152        // Convert path to URI format
153        let uri = path_to_uri(path);
154
155        // Use the correct /file/create endpoint
156        let request = serde_json::json!({
157            "uri": uri,
158            "type": "folder"
159        });
160
161        let response: ApiResponse<serde_json::Value> = self.post("/file/create", &request).await?;
162        match response.code {
163            0 => Ok(()),
164            code => Err(Error::Api {
165                code,
166                message: response.msg,
167            }),
168        }
169    }
170
171    pub async fn set_file_permission(
172        &self,
173        request: &SetFilePermissionRequest<'_>,
174    ) -> Result<(), Error> {
175        let response: ApiResponse<()> = self.post("/file/permission", request).await?;
176        match response.code {
177            0 => Ok(()),
178            code => Err(Error::Api {
179                code,
180                message: response.msg,
181            }),
182        }
183    }
184
185    pub async fn delete_file_permission(&self, path: &str) -> Result<(), Error> {
186        let uri = path_to_uri(path);
187        let response: ApiResponse<()> = self
188            .delete(&format!("/file/permission?uri={}", uri))
189            .await?;
190        match response.code {
191            0 => Ok(()),
192            code => Err(Error::Api {
193                code,
194                message: response.msg,
195            }),
196        }
197    }
198
199    pub async fn create_upload_session(
200        &self,
201        request: &CreateUploadSessionRequest<'_>,
202    ) -> Result<UploadSessionResponse, Error> {
203        let response: ApiResponse<UploadSessionResponse> =
204            self.put("/file/upload", request).await?;
205        match response.data {
206            Some(data) => Ok(data),
207            None => Err(Error::InvalidResponse(format!(
208                "API returned no data for create_upload_session request: {:?}",
209                response
210            ))),
211        }
212    }
213
214    pub async fn upload_file_chunk(
215        &self,
216        session_id: &str,
217        index: u32,
218        chunk_data: &[u8],
219    ) -> Result<(), Error> {
220        let url = format!("/file/upload/{}/{}", session_id, index);
221        let full_url = self.get_url(&url);
222
223        let mut request = self.http_client.post(&full_url).body(chunk_data.to_vec());
224
225        if let Some(token) = &self.token {
226            request = request.bearer_auth(token);
227        }
228
229        let response = request.send().await?;
230        let status = response.status();
231
232        if !status.is_success() {
233            let error_text = response
234                .text()
235                .await
236                .unwrap_or_else(|_| "Unknown error".to_string());
237            return Err(Error::Api {
238                code: status.as_u16() as i32,
239                message: error_text,
240            });
241        }
242
243        Ok(())
244    }
245
246    pub async fn delete_upload_session(&self, path: &str, session_id: &str) -> Result<(), Error> {
247        let uri = path_to_uri(path);
248        let request = DeleteUploadSessionRequest {
249            id: session_id,
250            uri: &uri,
251        };
252        let url = self.get_url("/file/upload");
253
254        let body = serde_json::to_string(&request)?;
255
256        let mut http_req = self.http_client.delete(&url);
257        if let Some(token) = &self.token {
258            http_req = http_req.bearer_auth(token);
259        }
260
261        let response = http_req
262            .header("Content-Type", "application/json")
263            .body(body)
264            .send()
265            .await?;
266
267        let status = response.status();
268        if !status.is_success() {
269            let error_text = response
270                .text()
271                .await
272                .unwrap_or_else(|_| "Unknown error".to_string());
273            return Err(Error::Api {
274                code: status.as_u16() as i32,
275                message: error_text,
276            });
277        }
278
279        Ok(())
280    }
281
282    pub async fn get_thumbnail_url(
283        &self,
284        path: &str,
285        width: Option<u32>,
286        height: Option<u32>,
287    ) -> Result<String, Error> {
288        let uri = path_to_uri(path);
289        let mut url = format!("/file/thumb?uri={}", uri);
290        if let Some(w) = width {
291            url.push_str(&format!("&width={}", w));
292        }
293        if let Some(h) = height {
294            url.push_str(&format!("&height={}", h));
295        }
296
297        let response: ApiResponse<String> = self.get(&url).await?;
298        match response.data {
299            Some(data) => Ok(data),
300            None => Err(Error::InvalidResponse(format!(
301                "API returned no data for get_thumbnail_url request: {:?}",
302                response
303            ))),
304        }
305    }
306
307    pub async fn get_file_content(&self, path: &str) -> Result<String, Error> {
308        let uri = path_to_uri(path);
309        let response: ApiResponse<String> = self.get(&format!("/file/content?uri={}", uri)).await?;
310        match response.data {
311            Some(data) => Ok(data),
312            None => Err(Error::InvalidResponse(format!(
313                "API returned no data for get_file_content request: {:?}",
314                response
315            ))),
316        }
317    }
318
319    pub async fn update_file_content(
320        &self,
321        request: &UpdateFileContentRequest<'_>,
322    ) -> Result<(), Error> {
323        let response: ApiResponse<()> = self.put("/file/content", request).await?;
324        match response.code {
325            0 => Ok(()),
326            code => Err(Error::Api {
327                code,
328                message: response.msg,
329            }),
330        }
331    }
332
333    pub async fn create_viewer_session(
334        &self,
335        request: &CreateViewerSessionRequest<'_>,
336    ) -> Result<ViewerSessionResponse, Error> {
337        let response: ApiResponse<ViewerSessionResponse> =
338            self.put("/file/viewerSession", request).await?;
339        match response.data {
340            Some(data) => Ok(data),
341            None => Err(Error::InvalidResponse(format!(
342                "API returned no data for create_viewer_session request: {:?}",
343                response
344            ))),
345        }
346    }
347
348    pub async fn create_file(&self, request: &CreateFileRequest<'_>) -> Result<File, Error> {
349        let response: ApiResponse<File> = self.post("/file/create", request).await?;
350        match response.data {
351            Some(data) => Ok(data),
352            None => Err(Error::InvalidResponse(format!(
353                "API returned no data for create_file request: {:?}",
354                response
355            ))),
356        }
357    }
358
359    pub async fn rename_multiple(&self, request: &RenameMultipleRequest<'_>) -> Result<(), Error> {
360        let response: ApiResponse<()> = self.post("/file/rename", request).await?;
361        match response.code {
362            0 => Ok(()),
363            code => Err(Error::Api {
364                code,
365                message: response.msg,
366            }),
367        }
368    }
369
370    pub async fn move_copy_files(&self, request: &MoveCopyFileRequest<'_>) -> Result<(), Error> {
371        let response: ApiResponse<()> = self.post("/file/move", request).await?;
372        match response.code {
373            0 => Ok(()),
374            code => Err(Error::Api {
375                code,
376                message: response.msg,
377            }),
378        }
379    }
380
381    pub async fn create_download_url(
382        &self,
383        request: &CreateDownloadUrlRequest<'_>,
384    ) -> Result<DownloadUrlResponse, Error> {
385        let uris = paths_to_uris(&request.uris);
386        let uris_refs: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
387
388        let converted_request = CreateDownloadUrlRequest {
389            uris: uris_refs,
390            download: request.download,
391            redirect: request.redirect,
392            entity: request.entity,
393            use_primary_site_url: request.use_primary_site_url,
394            skip_error: request.skip_error,
395            archive: request.archive,
396            no_cache: request.no_cache,
397        };
398
399        let response: ApiResponse<DownloadUrlResponse> =
400            self.post("/file/url", &converted_request).await?;
401        match response.data {
402            Some(data) => Ok(data),
403            None => Err(Error::InvalidResponse(format!(
404                "API returned no data for create_download_url request: {:?}",
405                response
406            ))),
407        }
408    }
409
410    pub async fn restore_from_trash(&self, request: &RestoreFileRequest<'_>) -> Result<(), Error> {
411        let uris = paths_to_uris(&request.uris);
412        let uris_refs: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
413
414        let converted_request = RestoreFileRequest { uris: uris_refs };
415
416        let response: ApiResponse<()> = self.post("/file/restore", &converted_request).await?;
417        match response.code {
418            0 => Ok(()),
419            code => Err(Error::Api {
420                code,
421                message: response.msg,
422            }),
423        }
424    }
425
426    pub async fn force_unlock(&self, path: &str) -> Result<(), Error> {
427        let uri = path_to_uri(path);
428        let response: ApiResponse<()> = self.delete(&format!("/file/lock?uri={}", uri)).await?;
429        match response.code {
430            0 => Ok(()),
431            code => Err(Error::Api {
432                code,
433                message: response.msg,
434            }),
435        }
436    }
437
438    pub async fn patch_metadata(
439        &self,
440        path: &str,
441        request: &UpdateMetadataRequest,
442    ) -> Result<(), Error> {
443        let uri = path_to_uri(path);
444        let full_url = format!("/file/metadata?uri={}", uri);
445        let response: ApiResponse<()> = self.patch(&full_url, request).await?;
446        match response.code {
447            0 => Ok(()),
448            code => Err(Error::Api {
449                code,
450                message: response.msg,
451            }),
452        }
453    }
454
455    pub async fn mount_storage_policy(
456        &self,
457        path: &str,
458        request: &MountStoragePolicyRequest,
459    ) -> Result<(), Error> {
460        let uri = path_to_uri(path);
461        let full_url = format!("/file/policy?uri={}", uri);
462        let response: ApiResponse<()> = self.patch(&full_url, request).await?;
463        match response.code {
464            0 => Ok(()),
465            code => Err(Error::Api {
466                code,
467                message: response.msg,
468            }),
469        }
470    }
471
472    pub async fn update_view_settings(
473        &self,
474        path: &str,
475        request: &UpdateViewRequest,
476    ) -> Result<(), Error> {
477        let uri = path_to_uri(path);
478        let full_url = format!("/file/view?uri={}", uri);
479        let response: ApiResponse<()> = self.patch(&full_url, request).await?;
480        match response.code {
481            0 => Ok(()),
482            code => Err(Error::Api {
483                code,
484                message: response.msg,
485            }),
486        }
487    }
488
489    pub async fn get_file_activities(
490        &self,
491        path: &str,
492        page: Option<u32>,
493        page_size: Option<u32>,
494    ) -> Result<FileActivitiesResponse, Error> {
495        let uri = path_to_uri(path);
496        let mut url = format!("/file/activities?uri={}", uri);
497        if let Some(p) = page {
498            url.push_str(&format!("&page={}", p));
499        }
500        if let Some(ps) = page_size {
501            url.push_str(&format!("&page_size={}", ps));
502        }
503
504        let response: ApiResponse<FileActivitiesResponse> = self.get(&url).await?;
505        match response.data {
506            Some(data) => Ok(data),
507            None => Err(Error::InvalidResponse(format!(
508                "API returned no data for get_file_activities request: {:?}",
509                response
510            ))),
511        }
512    }
513
514    pub async fn get_file_info_extended(
515        &self,
516        request: &GetFileInfoRequest<'_>,
517    ) -> Result<File, Error> {
518        let uri = path_to_uri(request.uri);
519        let mut url = format!("/file/info?uri={}", uri);
520        if let Some(include_extended) = request.include_extended_info {
521            url.push_str(&format!("&extended={}", include_extended));
522        }
523
524        let response: ApiResponse<File> = self.get(&url).await?;
525        match response.data {
526            Some(data) => Ok(data),
527            None => Err(Error::InvalidResponse(format!(
528                "API returned no data for get_file_info_extended request: {:?}",
529                response
530            ))),
531        }
532    }
533
534    pub async fn get_archive_list(
535        &self,
536        request: &GetArchiveListRequest<'_>,
537    ) -> Result<ArchiveListResponse, Error> {
538        let uri = path_to_uri(request.uri);
539        let url = format!("/file/archive?uri={}", uri);
540
541        let response: ApiResponse<ArchiveListResponse> = self.get(&url).await?;
542        match response.data {
543            Some(data) => Ok(data),
544            None => Err(Error::InvalidResponse(format!(
545                "API returned no data for get_archive_list request: {:?}",
546                response
547            ))),
548        }
549    }
550}