romm_cli/commands/
roms.rs1use std::path::PathBuf;
2use std::time::Duration;
3
4use anyhow::Result;
5use clap::{Args, Subcommand};
6use indicatif::{ProgressBar, ProgressStyle};
7
8use crate::client::RommClient;
9use crate::commands::library_scan::{
10 run_scan_library_flow, ScanCacheInvalidate, ScanLibraryOptions,
11};
12use crate::commands::print::print_roms_table;
13use crate::commands::OutputFormat;
14use crate::endpoints::roms::GetRoms;
15use crate::services::RomService;
16
17#[derive(Args, Debug)]
19pub struct RomsCommand {
20 #[command(subcommand)]
21 pub action: Option<RomsAction>,
22
23 #[arg(long, global = true, visible_aliases = ["query", "q"])]
25 pub search_term: Option<String>,
26
27 #[arg(long, global = true, visible_alias = "id")]
29 pub platform_id: Option<u64>,
30
31 #[arg(long, global = true)]
33 pub limit: Option<u32>,
34
35 #[arg(long, global = true)]
37 pub offset: Option<u32>,
38
39 #[arg(long, global = true)]
41 pub json: bool,
42}
43
44#[derive(Subcommand, Debug)]
45pub enum RomsAction {
46 #[command(visible_alias = "ls")]
48 List,
49 #[command(visible_alias = "info")]
51 Get {
52 id: u64,
54 },
55 #[command(visible_alias = "up")]
57 Upload {
58 platform_id: u64,
60 file: PathBuf,
62 #[arg(short, long)]
64 scan: bool,
65 #[arg(long, requires = "scan")]
67 wait: bool,
68 #[arg(long, requires = "wait")]
70 wait_timeout_secs: Option<u64>,
71 },
72}
73
74fn make_progress_style() -> ProgressStyle {
75 ProgressStyle::with_template(
76 "[{elapsed_precise}] {bar:40.cyan/blue} {bytes}/{total_bytes} ({eta}) {msg}",
77 )
78 .unwrap()
79 .progress_chars("#>-")
80}
81
82async fn upload_one(
83 client: &RommClient,
84 platform_id: u64,
85 file_path: std::path::PathBuf,
86 pb: ProgressBar,
87) -> Result<()> {
88 let filename = file_path
89 .file_name()
90 .and_then(|n| n.to_str())
91 .unwrap_or("file")
92 .to_string();
93
94 pb.set_message(format!("Uploading {}", filename));
95
96 client
97 .upload_rom(platform_id, &file_path, {
98 let pb = pb.clone();
99 move |uploaded, total| {
100 if pb.length() != Some(total) {
101 pb.set_length(total);
102 }
103 pb.set_position(uploaded);
104 }
105 })
106 .await?;
107
108 pb.finish_with_message(format!("✓ Upload complete: {}", filename));
109 Ok(())
110}
111
112pub async fn handle(cmd: RomsCommand, client: &RommClient, format: OutputFormat) -> Result<()> {
113 let action = cmd.action.unwrap_or(RomsAction::List);
114
115 match action {
116 RomsAction::List => {
117 let ep = GetRoms {
118 search_term: cmd.search_term.clone(),
119 platform_id: cmd.platform_id,
120 collection_id: None,
121 smart_collection_id: None,
122 virtual_collection_id: None,
123 limit: cmd.limit,
124 offset: cmd.offset,
125 };
126
127 let service = RomService::new(client);
128 let results = service.search_roms(&ep).await?;
129
130 match format {
131 OutputFormat::Json => {
132 println!("{}", serde_json::to_string_pretty(&results)?);
133 }
134 OutputFormat::Text => {
135 print_roms_table(&results);
136 }
137 }
138 }
139 RomsAction::Get { id } => {
140 let service = RomService::new(client);
141 let rom = service.get_rom(id).await?;
142
143 match format {
144 OutputFormat::Json => {
145 println!("{}", serde_json::to_string_pretty(&rom)?);
146 }
147 OutputFormat::Text => {
148 println!("{}", serde_json::to_string_pretty(&rom)?);
151 }
152 }
153 }
154 RomsAction::Upload {
155 file,
156 platform_id,
157 scan,
158 wait,
159 wait_timeout_secs,
160 } => {
161 if !file.exists() {
162 anyhow::bail!("File or directory does not exist: {:?}", file);
163 }
164
165 let mut files = Vec::new();
166 if file.is_dir() {
167 let mut entries = tokio::fs::read_dir(&file).await?;
168 while let Some(entry) = entries.next_entry().await? {
169 let path = entry.path();
170 if path.is_file() {
171 files.push(path);
172 }
173 }
174 files.sort(); } else {
176 files.push(file);
177 }
178
179 if files.is_empty() {
180 println!("No files found to upload.");
181 return Ok(());
182 }
183
184 if files.len() > 1 {
185 println!("Found {} files to upload.", files.len());
186 }
187
188 let mp = indicatif::MultiProgress::new();
189 let mut successes = 0u32;
190 for path in files {
191 let pb = mp.add(ProgressBar::new(0));
192 pb.set_style(make_progress_style());
193 match upload_one(client, platform_id, path.clone(), pb).await {
194 Ok(()) => successes += 1,
195 Err(e) => eprintln!("Error uploading {:?}: {}", path, e),
196 }
197 }
198
199 if scan {
200 if successes == 0 {
201 eprintln!("Skipping library scan: no uploads completed successfully.");
202 } else {
203 let options = ScanLibraryOptions {
204 wait,
205 wait_timeout: Duration::from_secs(wait_timeout_secs.unwrap_or(3600)),
206 cache_invalidate: if wait {
207 ScanCacheInvalidate::Platform(platform_id)
208 } else {
209 ScanCacheInvalidate::None
210 },
211 };
212 run_scan_library_flow(client, options, format).await?;
213 }
214 }
215 }
216 }
217
218 Ok(())
219}