Rust · 8750 bytes Raw Blame History
1 //! wanda prefix - Manage Wine/Proton prefixes
2
3 use clap::{Args, Subcommand};
4 use console::style;
5 use std::path::PathBuf;
6 use wanda_core::{
7 config::WandaConfig,
8 prefix::{PrefixHealth, PrefixManager},
9 steam::{ProtonManager, SteamInstallation},
10 Result, WandaError,
11 };
12
13 #[derive(Subcommand)]
14 pub enum PrefixCommands {
15 /// List all WANDA prefixes
16 List,
17
18 /// Create a new prefix
19 Create(CreateArgs),
20
21 /// Delete a prefix
22 Delete(DeleteArgs),
23
24 /// Repair a prefix
25 Repair(RepairArgs),
26
27 /// Validate prefix health
28 Validate(ValidateArgs),
29
30 /// Show prefix details
31 Info(InfoArgs),
32 }
33
34 #[derive(Args)]
35 pub struct CreateArgs {
36 /// Name for the new prefix
37 name: String,
38 }
39
40 #[derive(Args)]
41 pub struct DeleteArgs {
42 /// Name of the prefix to delete
43 name: String,
44
45 /// Skip confirmation
46 #[arg(long, short)]
47 force: bool,
48 }
49
50 #[derive(Args)]
51 pub struct RepairArgs {
52 /// Name of the prefix to repair (default: "default")
53 #[arg(default_value = "default")]
54 name: String,
55 }
56
57 #[derive(Args)]
58 pub struct ValidateArgs {
59 /// Name of the prefix to validate (default: "default")
60 #[arg(default_value = "default")]
61 name: String,
62 }
63
64 #[derive(Args)]
65 pub struct InfoArgs {
66 /// Name of the prefix (default: "default")
67 #[arg(default_value = "default")]
68 name: String,
69 }
70
71 pub async fn run(cmd: PrefixCommands, config_path: Option<PathBuf>) -> Result<()> {
72 let config = match &config_path {
73 Some(path) => WandaConfig::load_from(path)?,
74 None => WandaConfig::load()?,
75 };
76
77 let mut prefix_manager = PrefixManager::new(&config);
78 prefix_manager.load()?;
79
80 match cmd {
81 PrefixCommands::List => list(&prefix_manager),
82 PrefixCommands::Create(args) => create(&mut prefix_manager, &config, args).await,
83 PrefixCommands::Delete(args) => delete(&mut prefix_manager, args),
84 PrefixCommands::Repair(args) => repair(&mut prefix_manager, &config, args).await,
85 PrefixCommands::Validate(args) => validate(&prefix_manager, args),
86 PrefixCommands::Info(args) => info(&prefix_manager, args),
87 }
88 }
89
90 fn list(manager: &PrefixManager) -> Result<()> {
91 let prefixes = manager.list();
92
93 if prefixes.is_empty() {
94 println!("No prefixes found.");
95 println!("Run 'wanda init' to create the default prefix.");
96 return Ok(());
97 }
98
99 println!("WANDA Prefixes:\n");
100
101 for prefix in prefixes {
102 let health = manager.validate(&prefix.name)?;
103 let health_indicator = match health {
104 PrefixHealth::Healthy => style("HEALTHY").green(),
105 PrefixHealth::NeedsRepair(_) => style("NEEDS REPAIR").yellow(),
106 PrefixHealth::Corrupted(_) => style("CORRUPTED").red(),
107 PrefixHealth::NotCreated => style("NOT CREATED").dim(),
108 };
109
110 let wemod_status = if prefix.wemod_installed {
111 format!(
112 "WeMod {}",
113 prefix.wemod_version.as_deref().unwrap_or("(unknown version)")
114 )
115 } else {
116 "WeMod not installed".to_string()
117 };
118
119 println!(
120 " {} {}",
121 style(&prefix.name).bold(),
122 health_indicator
123 );
124 println!(" Path: {}", prefix.path.display());
125 println!(" {}", wemod_status);
126 if let Some(ref proton) = prefix.proton_version {
127 println!(" Proton: {}", proton);
128 }
129 println!();
130 }
131
132 Ok(())
133 }
134
135 async fn create(
136 manager: &mut PrefixManager,
137 config: &WandaConfig,
138 args: CreateArgs,
139 ) -> Result<()> {
140 if manager.get(&args.name).is_some() {
141 return Err(WandaError::PrefixCreationFailed {
142 path: manager.base_path.join(&args.name),
143 reason: "Prefix already exists".to_string(),
144 });
145 }
146
147 println!("Creating prefix '{}'...", args.name);
148
149 // Get Proton version
150 let steam = SteamInstallation::discover(config)?;
151 let proton_manager = ProtonManager::discover(&steam, config)?;
152 let proton = proton_manager.get_preferred(config)?;
153
154 println!(" Using Proton: {}", proton.name);
155 println!(" This may take several minutes...");
156
157 manager.create(&args.name, proton).await?;
158
159 println!("\n{} Prefix '{}' created successfully!",
160 style("SUCCESS").green().bold(),
161 args.name
162 );
163
164 Ok(())
165 }
166
167 fn delete(manager: &mut PrefixManager, args: DeleteArgs) -> Result<()> {
168 let prefix = manager.get(&args.name).ok_or_else(|| WandaError::PrefixNotFound {
169 path: manager.base_path.join(&args.name),
170 })?;
171
172 if !args.force {
173 println!(
174 "Are you sure you want to delete prefix '{}'?",
175 args.name
176 );
177 println!(" Path: {}", prefix.path.display());
178 println!("\nThis action cannot be undone.");
179 println!("Use --force to skip this confirmation.");
180 return Ok(());
181 }
182
183 manager.delete(&args.name)?;
184 println!("Prefix '{}' deleted.", args.name);
185
186 Ok(())
187 }
188
189 async fn repair(
190 manager: &mut PrefixManager,
191 config: &WandaConfig,
192 args: RepairArgs,
193 ) -> Result<()> {
194 let health = manager.validate(&args.name)?;
195
196 match health {
197 PrefixHealth::Healthy => {
198 println!("Prefix '{}' is healthy, no repair needed.", args.name);
199 return Ok(());
200 }
201 PrefixHealth::NotCreated => {
202 println!("Prefix '{}' doesn't exist.", args.name);
203 return Ok(());
204 }
205 PrefixHealth::Corrupted(reason) => {
206 println!(
207 "Prefix '{}' is corrupted: {}",
208 args.name, reason
209 );
210 println!("Consider deleting and recreating the prefix.");
211 return Err(WandaError::PrefixCorrupted {
212 path: manager.base_path.join(&args.name),
213 reason,
214 });
215 }
216 PrefixHealth::NeedsRepair(issues) => {
217 println!("Repairing prefix '{}'...", args.name);
218 for issue in &issues {
219 println!(" - {}", issue);
220 }
221 }
222 }
223
224 let steam = SteamInstallation::discover(config)?;
225 let proton_manager = ProtonManager::discover(&steam, config)?;
226 let proton = proton_manager.get_preferred(config)?;
227
228 manager.repair(&args.name, proton).await?;
229
230 println!("\n{} Prefix repaired!", style("SUCCESS").green().bold());
231
232 Ok(())
233 }
234
235 fn validate(manager: &PrefixManager, args: ValidateArgs) -> Result<()> {
236 let health = manager.validate(&args.name)?;
237
238 println!("Prefix '{}' health check:\n", args.name);
239
240 match health {
241 PrefixHealth::Healthy => {
242 println!(" {} All checks passed!", style("HEALTHY").green().bold());
243 }
244 PrefixHealth::NeedsRepair(issues) => {
245 println!(" {} Issues found:", style("NEEDS REPAIR").yellow().bold());
246 for issue in issues {
247 println!(" - {}", issue);
248 }
249 println!("\nRun 'wanda prefix repair {}' to fix.", args.name);
250 }
251 PrefixHealth::Corrupted(reason) => {
252 println!(" {} {}", style("CORRUPTED").red().bold(), reason);
253 println!("\nConsider deleting and recreating the prefix.");
254 }
255 PrefixHealth::NotCreated => {
256 println!(" {} Prefix has not been created yet.", style("NOT CREATED").dim());
257 println!("\nRun 'wanda init' to create the default prefix.");
258 }
259 }
260
261 Ok(())
262 }
263
264 fn info(manager: &PrefixManager, args: InfoArgs) -> Result<()> {
265 let prefix = manager.get(&args.name).ok_or_else(|| WandaError::PrefixNotFound {
266 path: manager.base_path.join(&args.name),
267 })?;
268
269 let health = manager.validate(&args.name)?;
270
271 println!("Prefix: {}\n", style(&args.name).bold());
272 println!(" Path: {}", prefix.path.display());
273 println!(
274 " Health: {:?}",
275 match health {
276 PrefixHealth::Healthy => style("Healthy").green(),
277 PrefixHealth::NeedsRepair(_) => style("Needs Repair").yellow(),
278 PrefixHealth::Corrupted(_) => style("Corrupted").red(),
279 PrefixHealth::NotCreated => style("Not Created").dim(),
280 }
281 );
282 println!(
283 " WeMod: {}",
284 if prefix.wemod_installed {
285 format!(
286 "Installed ({})",
287 prefix.wemod_version.as_deref().unwrap_or("unknown")
288 )
289 } else {
290 "Not installed".to_string()
291 }
292 );
293 if let Some(ref proton) = prefix.proton_version {
294 println!(" Proton: {}", proton);
295 }
296 if let Some(ref created) = prefix.created_at {
297 println!(" Created: {}", created);
298 }
299
300 Ok(())
301 }
302