1use crate::client::DatalabClient;
2use crate::error::{DatalabError, Result};
3use crate::output::Progress;
4use clap::{Args, Subcommand};
5use serde_json::json;
6use std::fs;
7use std::path::PathBuf;
8
9#[derive(Subcommand, Debug)]
10pub enum FilesCommand {
11 Upload(UploadArgs),
13 List(ListArgs),
15 Get(GetArgs),
17 Download(DownloadArgs),
19 Delete(DeleteArgs),
21}
22
23#[derive(Args, Debug)]
24pub struct UploadArgs {
25 #[arg(value_name = "FILE")]
27 pub file: PathBuf,
28
29 #[arg(long, default_value = "300", value_name = "SECS")]
31 pub timeout: u64,
32}
33
34#[derive(Args, Debug)]
35pub struct ListArgs {
36 #[arg(long, default_value = "50", value_name = "N")]
38 pub limit: u32,
39
40 #[arg(long, default_value = "0", value_name = "N")]
42 pub offset: u32,
43
44 #[arg(long, default_value = "60", value_name = "SECS")]
46 pub timeout: u64,
47}
48
49#[derive(Args, Debug)]
50pub struct GetArgs {
51 #[arg(value_name = "FILE_ID")]
53 pub file_id: String,
54
55 #[arg(long, default_value = "60", value_name = "SECS")]
57 pub timeout: u64,
58}
59
60#[derive(Args, Debug)]
61pub struct DownloadArgs {
62 #[arg(value_name = "FILE_ID")]
64 pub file_id: String,
65
66 #[arg(long, short, value_name = "FILE")]
68 pub output: Option<PathBuf>,
69
70 #[arg(long, default_value = "3600", value_name = "SECS")]
72 pub expires_in: u32,
73
74 #[arg(long, default_value = "300", value_name = "SECS")]
76 pub timeout: u64,
77}
78
79#[derive(Args, Debug)]
80pub struct DeleteArgs {
81 #[arg(value_name = "FILE_ID")]
83 pub file_id: String,
84
85 #[arg(long, default_value = "60", value_name = "SECS")]
87 pub timeout: u64,
88}
89
90pub async fn execute(cmd: FilesCommand, progress: &Progress) -> Result<()> {
91 match cmd {
92 FilesCommand::Upload(args) => upload(args, progress).await,
93 FilesCommand::List(args) => list(args, progress).await,
94 FilesCommand::Get(args) => get(args, progress).await,
95 FilesCommand::Download(args) => download(args, progress).await,
96 FilesCommand::Delete(args) => delete(args, progress).await,
97 }
98}
99
100async fn upload(args: UploadArgs, progress: &Progress) -> Result<()> {
101 let client = DatalabClient::new(Some(args.timeout))?;
102
103 if !args.file.exists() {
104 return Err(DatalabError::FileNotFound(args.file.clone()));
105 }
106
107 progress.start("upload", Some(&args.file.to_string_lossy()));
108
109 let filename = args
110 .file
111 .file_name()
112 .and_then(|n| n.to_str())
113 .unwrap_or("file")
114 .to_string();
115
116 let content_type = mime_guess::from_path(&args.file)
117 .first_or_octet_stream()
118 .to_string();
119
120 let request_body = json!({
121 "filename": filename,
122 "content_type": content_type,
123 });
124
125 let response = client.post_json("files/upload", &request_body).await?;
126
127 let upload_url = response
128 .get("upload_url")
129 .and_then(|v| v.as_str())
130 .ok_or_else(|| DatalabError::ProcessingFailed("No upload URL returned".to_string()))?;
131
132 let file_id = response
133 .get("file_id")
134 .and_then(|v| v.as_str())
135 .ok_or_else(|| DatalabError::ProcessingFailed("No file ID returned".to_string()))?;
136
137 client
138 .upload_file_to_presigned_url(upload_url, &args.file, &content_type, progress)
139 .await?;
140
141 let confirm_response = client.get(&format!("files/{}/confirm", file_id)).await?;
142
143 println!(
144 "{}",
145 serde_json::to_string_pretty(&json!({
146 "file_id": file_id,
147 "reference": response.get("reference"),
148 "upload_status": confirm_response.get("upload_status"),
149 "file_size": confirm_response.get("file_size"),
150 }))?
151 );
152
153 Ok(())
154}
155
156async fn list(args: ListArgs, progress: &Progress) -> Result<()> {
157 let client = DatalabClient::new(Some(args.timeout))?;
158
159 progress.start("list-files", None);
160
161 let path = format!("files?limit={}&offset={}", args.limit, args.offset);
162 let response = client.get(&path).await?;
163
164 println!("{}", serde_json::to_string_pretty(&response)?);
165
166 Ok(())
167}
168
169async fn get(args: GetArgs, progress: &Progress) -> Result<()> {
170 let client = DatalabClient::new(Some(args.timeout))?;
171
172 progress.start("get-file", Some(&args.file_id));
173
174 let path = format!("files/{}", args.file_id);
175 let response = client.get(&path).await?;
176
177 println!("{}", serde_json::to_string_pretty(&response)?);
178
179 Ok(())
180}
181
182async fn download(args: DownloadArgs, progress: &Progress) -> Result<()> {
183 let client = DatalabClient::new(Some(args.timeout))?;
184
185 progress.start("download", Some(&args.file_id));
186
187 let path = format!(
188 "files/{}/download?expires_in={}",
189 args.file_id, args.expires_in
190 );
191 let response = client.get(&path).await?;
192
193 if let Some(output_path) = args.output {
194 let download_url = response
195 .get("download_url")
196 .and_then(|v| v.as_str())
197 .ok_or_else(|| {
198 DatalabError::ProcessingFailed("No download URL returned".to_string())
199 })?;
200
201 let http_client = reqwest::Client::new();
202 let file_response = http_client.get(download_url).send().await?;
203
204 if !file_response.status().is_success() {
205 return Err(DatalabError::ApiError {
206 status: file_response.status().as_u16(),
207 message: "Failed to download file".to_string(),
208 });
209 }
210
211 let bytes = file_response.bytes().await?;
212 fs::write(&output_path, &bytes)?;
213
214 println!(
215 "{}",
216 serde_json::to_string_pretty(&json!({
217 "downloaded_to": output_path.to_string_lossy(),
218 "size": bytes.len(),
219 }))?
220 );
221 } else {
222 println!("{}", serde_json::to_string_pretty(&response)?);
223 }
224
225 Ok(())
226}
227
228async fn delete(args: DeleteArgs, progress: &Progress) -> Result<()> {
229 let client = DatalabClient::new(Some(args.timeout))?;
230
231 progress.start("delete", Some(&args.file_id));
232
233 let path = format!("files/{}", args.file_id);
234 client.delete(&path).await?;
235
236 println!(
237 "{}",
238 serde_json::to_string_pretty(&json!({
239 "deleted": true,
240 "file_id": args.file_id,
241 }))?
242 );
243
244 Ok(())
245}