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 _: ApiResponse<()> = self.post("/file/move", request).await?;
92        Ok(())
93    }
94
95    pub async fn copy_file(&self, request: &CopyFileRequest<'_>) -> Result<(), Error> {
96        let _: ApiResponse<()> = self.post("/file/copy", request).await?;
97        Ok(())
98    }
99
100    pub async fn rename_file(
101        &self,
102        file_path: &str,
103        request: &RenameFileRequest<'_>,
104    ) -> Result<(), Error> {
105        // V4 API may not have /file/rename endpoint, use /file/move instead
106        // Extract parent directory and construct new path
107        let uri = path_to_uri(file_path);
108        let new_uri = if let Some(parent) = file_path.rsplit('/').nth(1) {
109            if parent.is_empty() {
110                // Root directory
111                path_to_uri(&format!("/{}", request.name))
112            } else {
113                path_to_uri(&format!("{}/{}", parent, request.name))
114            }
115        } else {
116            path_to_uri(&format!("/{}", request.name))
117        };
118
119        let move_req = MoveFileRequest {
120            from: &uri,
121            to: &new_uri,
122        };
123        self.move_file(&move_req).await
124    }
125
126    pub async fn delete_file(&self, file_path: &str) -> Result<(), Error> {
127        // URI encode the path for V4 API
128        let uri = path_to_uri(file_path);
129        let url = format!("/file?uri={}", uri);
130        let _: ApiResponse<()> = self.delete(&url).await?;
131        Ok(())
132    }
133
134    pub async fn create_directory(&self, path: &str) -> Result<(), Error> {
135        // Convert path to URI format
136        let uri = path_to_uri(path);
137
138        // Use the correct /file/create endpoint
139        let request = serde_json::json!({
140            "uri": uri,
141            "type": "folder"
142        });
143
144        let response: ApiResponse<serde_json::Value> = self.post("/file/create", &request).await?;
145        match response.code {
146            0 => Ok(()),
147            code => Err(Error::Api {
148                code,
149                message: response.msg,
150            }),
151        }
152    }
153
154    pub async fn set_file_permission(
155        &self,
156        request: &SetFilePermissionRequest<'_>,
157    ) -> Result<(), Error> {
158        let _: ApiResponse<()> = self.post("/file/permission", request).await?;
159        Ok(())
160    }
161
162    pub async fn delete_file_permission(&self, path: &str) -> Result<(), Error> {
163        let uri = path_to_uri(path);
164        let _: ApiResponse<()> = self
165            .delete(&format!("/file/permission?uri={}", uri))
166            .await?;
167        Ok(())
168    }
169
170    pub async fn create_upload_session(
171        &self,
172        request: &CreateUploadSessionRequest<'_>,
173    ) -> Result<UploadSessionResponse, Error> {
174        let response: ApiResponse<UploadSessionResponse> =
175            self.put("/file/upload", request).await?;
176        match response.data {
177            Some(data) => Ok(data),
178            None => Err(Error::InvalidResponse(format!(
179                "API returned no data for create_upload_session request: {:?}",
180                response
181            ))),
182        }
183    }
184
185    pub async fn upload_file_chunk(
186        &self,
187        session_id: &str,
188        index: u32,
189        chunk_data: &[u8],
190    ) -> Result<(), Error> {
191        let url = format!("/file/upload/{}/{}", session_id, index);
192        let full_url = self.get_url(&url);
193
194        let mut request = self.http_client.post(&full_url).body(chunk_data.to_vec());
195
196        if let Some(token) = &self.token {
197            request = request.bearer_auth(token);
198        }
199
200        let response = request.send().await?;
201        let status = response.status();
202
203        if !status.is_success() {
204            let error_text = response
205                .text()
206                .await
207                .unwrap_or_else(|_| "Unknown error".to_string());
208            return Err(Error::Api {
209                code: status.as_u16() as i32,
210                message: error_text,
211            });
212        }
213
214        Ok(())
215    }
216
217    pub async fn delete_upload_session(&self, path: &str, session_id: &str) -> Result<(), Error> {
218        let uri = path_to_uri(path);
219        let request = DeleteUploadSessionRequest {
220            id: session_id,
221            uri: &uri,
222        };
223        let url = self.get_url("/file/upload");
224
225        let body = serde_json::to_string(&request)?;
226
227        let mut http_req = self.http_client.delete(&url);
228        if let Some(token) = &self.token {
229            http_req = http_req.bearer_auth(token);
230        }
231
232        let response = http_req
233            .header("Content-Type", "application/json")
234            .body(body)
235            .send()
236            .await?;
237
238        let status = response.status();
239        if !status.is_success() {
240            let error_text = response
241                .text()
242                .await
243                .unwrap_or_else(|_| "Unknown error".to_string());
244            return Err(Error::Api {
245                code: status.as_u16() as i32,
246                message: error_text,
247            });
248        }
249
250        Ok(())
251    }
252
253    pub async fn get_thumbnail_url(
254        &self,
255        path: &str,
256        width: Option<u32>,
257        height: Option<u32>,
258    ) -> Result<String, Error> {
259        let uri = path_to_uri(path);
260        let mut url = format!("/file/thumb?uri={}", uri);
261        if let Some(w) = width {
262            url.push_str(&format!("&width={}", w));
263        }
264        if let Some(h) = height {
265            url.push_str(&format!("&height={}", h));
266        }
267
268        let response: ApiResponse<String> = self.get(&url).await?;
269        match response.data {
270            Some(data) => Ok(data),
271            None => Err(Error::InvalidResponse(format!(
272                "API returned no data for get_thumbnail_url request: {:?}",
273                response
274            ))),
275        }
276    }
277
278    pub async fn get_file_content(&self, path: &str) -> Result<String, Error> {
279        let uri = path_to_uri(path);
280        let response: ApiResponse<String> = self.get(&format!("/file/content?uri={}", uri)).await?;
281        match response.data {
282            Some(data) => Ok(data),
283            None => Err(Error::InvalidResponse(format!(
284                "API returned no data for get_file_content request: {:?}",
285                response
286            ))),
287        }
288    }
289
290    pub async fn update_file_content(
291        &self,
292        request: &UpdateFileContentRequest<'_>,
293    ) -> Result<(), Error> {
294        let _: ApiResponse<()> = self.put("/file/content", request).await?;
295        Ok(())
296    }
297
298    pub async fn create_viewer_session(
299        &self,
300        request: &CreateViewerSessionRequest<'_>,
301    ) -> Result<ViewerSessionResponse, Error> {
302        let response: ApiResponse<ViewerSessionResponse> =
303            self.put("/file/viewerSession", request).await?;
304        match response.data {
305            Some(data) => Ok(data),
306            None => Err(Error::InvalidResponse(format!(
307                "API returned no data for create_viewer_session request: {:?}",
308                response
309            ))),
310        }
311    }
312
313    pub async fn create_file(&self, request: &CreateFileRequest<'_>) -> Result<File, Error> {
314        let response: ApiResponse<File> = self.post("/file/create", request).await?;
315        match response.data {
316            Some(data) => Ok(data),
317            None => Err(Error::InvalidResponse(format!(
318                "API returned no data for create_file request: {:?}",
319                response
320            ))),
321        }
322    }
323
324    pub async fn rename_multiple(&self, request: &RenameMultipleRequest<'_>) -> Result<(), Error> {
325        let _: ApiResponse<()> = self.post("/file/rename", request).await?;
326        Ok(())
327    }
328
329    pub async fn move_copy_files(&self, request: &MoveCopyFileRequest<'_>) -> Result<(), Error> {
330        let _: ApiResponse<()> = self.post("/file/move", request).await?;
331        Ok(())
332    }
333
334    pub async fn create_download_url(
335        &self,
336        request: &CreateDownloadUrlRequest<'_>,
337    ) -> Result<DownloadUrlResponse, Error> {
338        let uris = paths_to_uris(&request.uris);
339        let uris_refs: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
340
341        let converted_request = CreateDownloadUrlRequest {
342            uris: uris_refs,
343            download: request.download,
344            redirect: request.redirect,
345            entity: request.entity,
346            use_primary_site_url: request.use_primary_site_url,
347            skip_error: request.skip_error,
348            archive: request.archive,
349            no_cache: request.no_cache,
350        };
351
352        let response: ApiResponse<DownloadUrlResponse> =
353            self.post("/file/url", &converted_request).await?;
354        match response.data {
355            Some(data) => Ok(data),
356            None => Err(Error::InvalidResponse(format!(
357                "API returned no data for create_download_url request: {:?}",
358                response
359            ))),
360        }
361    }
362
363    pub async fn restore_from_trash(&self, request: &RestoreFileRequest<'_>) -> Result<(), Error> {
364        let uris = paths_to_uris(&request.uris);
365        let uris_refs: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
366
367        let converted_request = RestoreFileRequest { uris: uris_refs };
368
369        let _: ApiResponse<()> = self.post("/file/restore", &converted_request).await?;
370        Ok(())
371    }
372
373    pub async fn force_unlock(&self, path: &str) -> Result<(), Error> {
374        let uri = path_to_uri(path);
375        let _: ApiResponse<()> = self.delete(&format!("/file/lock?uri={}", uri)).await?;
376        Ok(())
377    }
378
379    pub async fn patch_metadata(
380        &self,
381        path: &str,
382        request: &UpdateMetadataRequest,
383    ) -> Result<(), Error> {
384        let uri = path_to_uri(path);
385        let full_url = format!("/file/metadata?uri={}", uri);
386        let _: ApiResponse<()> = self.patch(&full_url, request).await?;
387        Ok(())
388    }
389
390    pub async fn mount_storage_policy(
391        &self,
392        path: &str,
393        request: &MountStoragePolicyRequest,
394    ) -> Result<(), Error> {
395        let uri = path_to_uri(path);
396        let full_url = format!("/file/policy?uri={}", uri);
397        let _: ApiResponse<()> = self.patch(&full_url, request).await?;
398        Ok(())
399    }
400
401    pub async fn update_view_settings(
402        &self,
403        path: &str,
404        request: &UpdateViewRequest,
405    ) -> Result<(), Error> {
406        let uri = path_to_uri(path);
407        let full_url = format!("/file/view?uri={}", uri);
408        let _: ApiResponse<()> = self.patch(&full_url, request).await?;
409        Ok(())
410    }
411
412    pub async fn get_file_activities(
413        &self,
414        path: &str,
415        page: Option<u32>,
416        page_size: Option<u32>,
417    ) -> Result<FileActivitiesResponse, Error> {
418        let uri = path_to_uri(path);
419        let mut url = format!("/file/activities?uri={}", uri);
420        if let Some(p) = page {
421            url.push_str(&format!("&page={}", p));
422        }
423        if let Some(ps) = page_size {
424            url.push_str(&format!("&page_size={}", ps));
425        }
426
427        let response: ApiResponse<FileActivitiesResponse> = self.get(&url).await?;
428        match response.data {
429            Some(data) => Ok(data),
430            None => Err(Error::InvalidResponse(format!(
431                "API returned no data for get_file_activities request: {:?}",
432                response
433            ))),
434        }
435    }
436
437    pub async fn get_file_info_extended(
438        &self,
439        request: &GetFileInfoRequest<'_>,
440    ) -> Result<File, Error> {
441        let uri = path_to_uri(request.uri);
442        let mut url = format!("/file/info?uri={}", uri);
443        if let Some(include_extended) = request.include_extended_info {
444            url.push_str(&format!("&extended={}", include_extended));
445        }
446
447        let response: ApiResponse<File> = self.get(&url).await?;
448        match response.data {
449            Some(data) => Ok(data),
450            None => Err(Error::InvalidResponse(format!(
451                "API returned no data for get_file_info_extended request: {:?}",
452                response
453            ))),
454        }
455    }
456
457    pub async fn get_archive_list(
458        &self,
459        request: &GetArchiveListRequest<'_>,
460    ) -> Result<ArchiveListResponse, Error> {
461        let uri = path_to_uri(request.uri);
462        let url = format!("/file/archive?uri={}", uri);
463
464        let response: ApiResponse<ArchiveListResponse> = self.get(&url).await?;
465        match response.data {
466            Some(data) => Ok(data),
467            None => Err(Error::InvalidResponse(format!(
468                "API returned no data for get_archive_list request: {:?}",
469                response
470            ))),
471        }
472    }
473}