files_sdk/files/
file_actions.rs

1//! File action operations
2//!
3//! This module provides specialized file operations including:
4//! - Begin upload (first stage of file upload process)
5//! - Copy files
6//! - Move files
7//! - Get metadata
8//!
9//! The most important operation here is `begin_upload`, which must be called
10//! before uploading any file to Files.com.
11
12use crate::utils::encode_path;
13use crate::{FileUploadPartEntity, FilesClient, Result};
14use serde_json::json;
15
16/// Handler for file action operations
17///
18/// Provides methods for specialized file operations that are separate
19/// from basic file CRUD operations.
20#[derive(Debug, Clone)]
21pub struct FileActionHandler {
22    client: FilesClient,
23}
24
25impl FileActionHandler {
26    /// Creates a new FileActionHandler
27    ///
28    /// # Arguments
29    ///
30    /// * `client` - FilesClient instance
31    pub fn new(client: FilesClient) -> Self {
32        Self { client }
33    }
34
35    /// Begin file upload (Stage 1 of upload process)
36    ///
37    /// This must be called before uploading any file. It returns upload URLs
38    /// and parameters needed to perform the actual upload.
39    ///
40    /// # Arguments
41    ///
42    /// * `path` - Destination path for the file
43    /// * `size` - Total size of file in bytes (optional)
44    /// * `mkdir_parents` - Create parent directories if they don't exist
45    ///
46    /// # Returns
47    ///
48    /// Returns a vector of `FileUploadPartEntity` containing upload URLs and parameters.
49    /// For small files, this will typically be a single element. For large files,
50    /// it may contain multiple parts for parallel upload.
51    ///
52    /// # Examples
53    ///
54    /// ```rust,no_run
55    /// use files_sdk::{FilesClient, FileActionHandler};
56    ///
57    /// # #[tokio::main]
58    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
59    /// let client = FilesClient::builder()
60    ///     .api_key("your-api-key")
61    ///     .build()?;
62    ///
63    /// let handler = FileActionHandler::new(client);
64    ///
65    /// // Begin upload for a 1KB file
66    /// let upload_info = handler
67    ///     .begin_upload("/uploads/test.txt", Some(1024), true)
68    ///     .await?;
69    ///
70    /// println!("Upload URL: {:?}", upload_info[0].upload_uri);
71    /// # Ok(())
72    /// # }
73    /// ```
74    pub async fn begin_upload(
75        &self,
76        path: &str,
77        size: Option<i64>,
78        mkdir_parents: bool,
79    ) -> Result<Vec<FileUploadPartEntity>> {
80        let mut body = json!({
81            "mkdir_parents": mkdir_parents,
82        });
83
84        if let Some(size) = size {
85            body["size"] = json!(size);
86        }
87
88        let encoded_path = encode_path(path);
89        let endpoint = format!("/file_actions/begin_upload{}", encoded_path);
90        let response = self.client.post_raw(&endpoint, body).await?;
91
92        // Response can be a single object or an array
93        if response.is_array() {
94            Ok(serde_json::from_value(response)?)
95        } else {
96            // Single object - wrap in array
97            Ok(vec![serde_json::from_value(response)?])
98        }
99    }
100
101    /// Begin multi-part upload for large files
102    ///
103    /// For files larger than the default part size, this allows requesting
104    /// multiple upload parts for parallel uploading.
105    ///
106    /// # Arguments
107    ///
108    /// * `path` - Destination path for the file
109    /// * `size` - Total size of file in bytes
110    /// * `parts` - Number of parts to split the upload into
111    /// * `mkdir_parents` - Create parent directories if they don't exist
112    ///
113    /// # Returns
114    ///
115    /// Returns a vector of `FileUploadPartEntity`, one for each part
116    pub async fn begin_multipart_upload(
117        &self,
118        path: &str,
119        size: i64,
120        parts: i32,
121        mkdir_parents: bool,
122    ) -> Result<Vec<FileUploadPartEntity>> {
123        let body = json!({
124            "size": size,
125            "parts": parts,
126            "mkdir_parents": mkdir_parents,
127        });
128
129        let encoded_path = encode_path(path);
130        let endpoint = format!("/file_actions/begin_upload{}", encoded_path);
131        let response = self.client.post_raw(&endpoint, body).await?;
132
133        // Response should be an array for multipart
134        if response.is_array() {
135            Ok(serde_json::from_value(response)?)
136        } else {
137            Ok(vec![serde_json::from_value(response)?])
138        }
139    }
140
141    /// Copy a file to a new location
142    ///
143    /// # Arguments
144    ///
145    /// * `path` - Source file path
146    /// * `destination` - Destination path
147    ///
148    /// # Examples
149    ///
150    /// ```rust,no_run
151    /// # use files_sdk::{FilesClient, FileActionHandler};
152    /// # #[tokio::main]
153    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
154    /// # let client = FilesClient::builder().api_key("key").build()?;
155    /// let handler = FileActionHandler::new(client);
156    /// handler.copy_file("/source/file.txt", "/dest/file.txt").await?;
157    /// # Ok(())
158    /// # }
159    /// ```
160    pub async fn copy_file(&self, path: &str, destination: &str) -> Result<()> {
161        let body = json!({
162            "destination": destination,
163        });
164
165        let encoded_path = encode_path(path);
166        let endpoint = format!("/file_actions/copy{}", encoded_path);
167        self.client.post_raw(&endpoint, body).await?;
168        Ok(())
169    }
170
171    /// Move a file to a new location
172    ///
173    /// # Arguments
174    ///
175    /// * `path` - Source file path
176    /// * `destination` - Destination path
177    ///
178    /// # Examples
179    ///
180    /// ```rust,no_run
181    /// # use files_sdk::{FilesClient, FileActionHandler};
182    /// # #[tokio::main]
183    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
184    /// # let client = FilesClient::builder().api_key("key").build()?;
185    /// let handler = FileActionHandler::new(client);
186    /// handler.move_file("/source/file.txt", "/dest/file.txt").await?;
187    /// # Ok(())
188    /// # }
189    /// ```
190    pub async fn move_file(&self, path: &str, destination: &str) -> Result<()> {
191        let body = json!({
192            "destination": destination,
193        });
194
195        let encoded_path = encode_path(path);
196        let endpoint = format!("/file_actions/move{}", encoded_path);
197        self.client.post_raw(&endpoint, body).await?;
198        Ok(())
199    }
200
201    /// Get file metadata without downloading
202    ///
203    /// This is useful when you want file information without generating
204    /// download URLs or logging download activity.
205    ///
206    /// # Arguments
207    ///
208    /// * `path` - File path
209    ///
210    /// # Examples
211    ///
212    /// ```rust,no_run
213    /// # use files_sdk::{FilesClient, FileActionHandler};
214    /// # #[tokio::main]
215    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
216    /// # let client = FilesClient::builder().api_key("key").build()?;
217    /// let handler = FileActionHandler::new(client);
218    /// let metadata = handler.get_metadata("/path/to/file.txt").await?;
219    /// println!("Size: {:?}", metadata.size);
220    /// # Ok(())
221    /// # }
222    /// ```
223    pub async fn get_metadata(&self, path: &str) -> Result<crate::FileEntity> {
224        let encoded_path = encode_path(path);
225        let endpoint = format!("/file_actions/metadata{}", encoded_path);
226        let response = self.client.post_raw(&endpoint, json!({})).await?;
227        Ok(serde_json::from_value(response)?)
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_handler_creation() {
237        let client = FilesClient::builder().api_key("test-key").build().unwrap();
238        let _handler = FileActionHandler::new(client);
239    }
240}