Rust · 8895 bytes Raw Blame History
1 //! wanda doctor - Diagnose issues
2
3 use clap::Args;
4 use console::style;
5 use std::path::PathBuf;
6 use wanda_core::{
7 config::WandaConfig,
8 prefix::{PrefixHealth, PrefixManager},
9 steam::{ProtonCompatibility, ProtonManager, SteamInstallation},
10 Result,
11 };
12
13 #[derive(Args)]
14 pub struct DoctorArgs {
15 /// Generate full diagnostic report
16 #[arg(long)]
17 full: bool,
18
19 /// Export report to file
20 #[arg(long)]
21 export: Option<PathBuf>,
22
23 /// Attempt to fix detected issues
24 #[arg(long)]
25 fix: bool,
26 }
27
28 pub async fn run(args: DoctorArgs, config_path: Option<PathBuf>) -> Result<()> {
29 let config = match &config_path {
30 Some(path) => WandaConfig::load_from(path)?,
31 None => WandaConfig::load()?,
32 };
33
34 println!("WANDA Diagnostic Report\n");
35 println!("{}\n", "=".repeat(50));
36
37 let mut issues = Vec::new();
38
39 // Check Steam
40 print_section("Steam Installation");
41 match SteamInstallation::discover(&config) {
42 Ok(steam) => {
43 println!(
44 " {} Found at {}{}",
45 style("OK").green(),
46 steam.root_path.display(),
47 if steam.is_flatpak { " (Flatpak)" } else { "" }
48 );
49 println!(" {} {} libraries", style("OK").green(), steam.libraries.len());
50
51 let total_games: usize = steam.libraries.iter().map(|l| l.apps.len()).sum();
52 let proton_games = steam
53 .get_all_games()
54 .iter()
55 .filter(|g| g.uses_proton())
56 .count();
57 println!(" {} {} games ({} using Proton)", style("OK").green(), total_games, proton_games);
58
59 // Check Proton
60 print_section("Proton");
61 match ProtonManager::discover(&steam, &config) {
62 Ok(proton_mgr) => {
63 if proton_mgr.versions.is_empty() {
64 println!(" {} No Proton versions found", style("ERROR").red());
65 issues.push("No Proton versions installed".to_string());
66 } else {
67 println!(
68 " {} {} versions found",
69 style("OK").green(),
70 proton_mgr.versions.len()
71 );
72
73 // List versions in full mode
74 if args.full {
75 for v in &proton_mgr.versions {
76 let compat = match v.compatibility {
77 ProtonCompatibility::Recommended => {
78 style("RECOMMENDED").green()
79 }
80 ProtonCompatibility::Supported => style("SUPPORTED").blue(),
81 ProtonCompatibility::Experimental => {
82 style("EXPERIMENTAL").yellow()
83 }
84 ProtonCompatibility::Unsupported => style("UNSUPPORTED").red(),
85 };
86 println!(" {} {}", v.name, compat);
87 }
88 }
89
90 if let Some(recommended) = proton_mgr.get_recommended() {
91 println!(
92 " {} Recommended: {}",
93 style("OK").green(),
94 recommended.name
95 );
96 } else {
97 println!(
98 " {} No recommended Proton version",
99 style("WARN").yellow()
100 );
101 issues.push("No recommended Proton version found".to_string());
102 }
103 }
104 }
105 Err(e) => {
106 println!(" {} Error: {}", style("ERROR").red(), e);
107 issues.push(format!("Proton detection failed: {}", e));
108 }
109 }
110 }
111 Err(e) => {
112 println!(" {} Not found: {}", style("ERROR").red(), e);
113 issues.push("Steam installation not found".to_string());
114 }
115 }
116
117 // Check WANDA prefix
118 print_section("WANDA Prefix");
119 let mut prefix_manager = PrefixManager::new(&config);
120 prefix_manager.load()?;
121
122 if let Some(prefix) = prefix_manager.get("default") {
123 println!(" {} Found at {}", style("OK").green(), prefix.path.display());
124
125 let health = prefix_manager.validate("default")?;
126 match health {
127 PrefixHealth::Healthy => {
128 println!(" {} Prefix is healthy", style("OK").green());
129 }
130 PrefixHealth::NeedsRepair(ref issue_list) => {
131 println!(" {} Prefix needs repair:", style("WARN").yellow());
132 for issue in issue_list {
133 println!(" - {}", issue);
134 issues.push(format!("Prefix issue: {}", issue));
135 }
136 }
137 PrefixHealth::Corrupted(reason) => {
138 println!(" {} Prefix is corrupted: {}", style("ERROR").red(), reason);
139 issues.push(format!("Prefix corrupted: {}", reason));
140 }
141 PrefixHealth::NotCreated => {
142 println!(" {} Prefix not initialized", style("WARN").yellow());
143 issues.push("WANDA prefix not created".to_string());
144 }
145 }
146
147 // Check WeMod
148 print_section("WeMod");
149 if prefix.wemod_installed {
150 println!(
151 " {} Installed (version {})",
152 style("OK").green(),
153 prefix.wemod_version.as_deref().unwrap_or("unknown")
154 );
155
156 if prefix.wemod_exe().exists() {
157 println!(" {} Executable found", style("OK").green());
158 } else {
159 println!(" {} Executable missing", style("ERROR").red());
160 issues.push("WeMod executable not found".to_string());
161 }
162 } else {
163 println!(" {} Not installed", style("WARN").yellow());
164 issues.push("WeMod not installed".to_string());
165 }
166 } else {
167 println!(" {} Not initialized", style("WARN").yellow());
168 issues.push("WANDA not initialized".to_string());
169 }
170
171 // Check system dependencies
172 print_section("System Dependencies");
173 check_command("wine", "Wine", &mut issues).await;
174 check_command("winetricks", "Winetricks", &mut issues).await;
175 check_command("steam", "Steam CLI", &mut issues).await;
176 check_command("xdg-open", "XDG utilities", &mut issues).await;
177
178 // Summary
179 println!("\n{}", "=".repeat(50));
180 if issues.is_empty() {
181 println!(
182 "\n{} All checks passed! WANDA is ready to use.",
183 style("SUCCESS").green().bold()
184 );
185 } else {
186 println!(
187 "\n{} Found {} issue(s):\n",
188 style("ISSUES").yellow().bold(),
189 issues.len()
190 );
191 for (i, issue) in issues.iter().enumerate() {
192 println!(" {}. {}", i + 1, issue);
193 }
194
195 println!("\nSuggestions:");
196 if issues.iter().any(|i| i.contains("not initialized")) {
197 println!(" - Run 'wanda init' to initialize WANDA");
198 }
199 if issues.iter().any(|i| i.contains("WeMod")) {
200 println!(" - Run 'wanda wemod install' to install WeMod");
201 }
202 if issues.iter().any(|i| i.contains("Prefix")) {
203 println!(" - Run 'wanda prefix repair' to fix prefix issues");
204 }
205 if issues.iter().any(|i| i.contains("Proton")) {
206 println!(" - Install GE-Proton via ProtonUp-Qt or Steam");
207 }
208 }
209
210 // Export if requested
211 if let Some(export_path) = args.export {
212 let report = format!(
213 "WANDA Diagnostic Report\n\nIssues:\n{}\n",
214 issues.join("\n")
215 );
216 std::fs::write(&export_path, report)?;
217 println!("\nReport exported to: {}", export_path.display());
218 }
219
220 Ok(())
221 }
222
223 fn print_section(name: &str) {
224 println!("\n{}", style(format!("[{}]", name)).bold());
225 }
226
227 async fn check_command(cmd: &str, name: &str, issues: &mut Vec<String>) {
228 let result = tokio::process::Command::new("which")
229 .arg(cmd)
230 .output()
231 .await;
232
233 match result {
234 Ok(output) if output.status.success() => {
235 let path = String::from_utf8_lossy(&output.stdout);
236 println!(" {} {} ({})", style("OK").green(), name, path.trim());
237 }
238 _ => {
239 println!(" {} {} not found", style("WARN").yellow(), name);
240 issues.push(format!("{} not found in PATH", name));
241 }
242 }
243 }
244