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}