cloudreve_api/api/v4/
file.rs

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