1use super::{calculate_dir_size, CleanableItem, SafetyLevel};
9use crate::error::Result;
10use std::path::PathBuf;
11
12pub struct GameDevCleaner {
14 home: PathBuf,
15}
16
17impl GameDevCleaner {
18 pub fn new() -> Option<Self> {
20 let home = dirs::home_dir()?;
21 Some(Self { home })
22 }
23
24 pub fn detect(&self) -> Result<Vec<CleanableItem>> {
26 let mut items = Vec::new();
27
28 items.extend(self.detect_unity_global()?);
30
31 items.extend(self.detect_unreal_global()?);
33
34 items.extend(self.detect_godot_global()?);
36
37 Ok(items)
38 }
39
40 fn detect_unity_global(&self) -> Result<Vec<CleanableItem>> {
42 let mut items = Vec::new();
43
44 #[cfg(target_os = "macos")]
45 let unity_paths = [
46 ("Library/Application Support/Unity", "Unity Settings", "🎮"),
47 ("Library/Caches/com.unity3d.UnityEditor", "Unity Editor Cache", "🎮"),
48 ("Library/Unity/Asset Store-5.x", "Unity Asset Store Cache", "🛒"),
49 ("Library/Unity/cache", "Unity Global Cache", "🎮"),
50 ("Library/Logs/Unity", "Unity Logs", "📝"),
51 ];
52
53 #[cfg(target_os = "linux")]
54 let unity_paths = [
55 (".config/unity3d", "Unity Settings", "🎮"),
56 (".cache/unity3d", "Unity Cache", "🎮"),
57 (".local/share/unity3d/Asset Store-5.x", "Unity Asset Store Cache", "🛒"),
58 ];
59
60 #[cfg(target_os = "windows")]
61 let unity_paths = [
62 ("AppData/Roaming/Unity", "Unity Settings", "🎮"),
63 ("AppData/Local/Unity/cache", "Unity Cache", "🎮"),
64 ("AppData/Roaming/Unity/Asset Store-5.x", "Unity Asset Store Cache", "🛒"),
65 ];
66
67 for (rel_path, name, icon) in unity_paths {
68 let path = self.home.join(rel_path);
69 if !path.exists() {
70 continue;
71 }
72
73 let (size, file_count) = calculate_dir_size(&path)?;
74 if size < 100_000_000 {
75 continue;
76 }
77
78 let is_asset_store = rel_path.contains("Asset Store");
79
80 items.push(CleanableItem {
81 name: name.to_string(),
82 category: "Game Development".to_string(),
83 subcategory: "Unity".to_string(),
84 icon,
85 path,
86 size,
87 file_count: Some(file_count),
88 last_modified: None,
89 description: if is_asset_store {
90 "Downloaded Asset Store packages. Can be re-downloaded."
91 } else {
92 "Unity Editor cache and data. Will be rebuilt."
93 },
94 safe_to_delete: SafetyLevel::SafeWithCost,
95 clean_command: None,
96 });
97 }
98
99 #[cfg(target_os = "macos")]
101 {
102 let hub_path = self.home.join("Library/Application Support/UnityHub");
103 if hub_path.exists() {
104 let (size, file_count) = calculate_dir_size(&hub_path)?;
105 if size > 100_000_000 {
106 items.push(CleanableItem {
107 name: "Unity Hub Cache".to_string(),
108 category: "Game Development".to_string(),
109 subcategory: "Unity".to_string(),
110 icon: "🎮",
111 path: hub_path,
112 size,
113 file_count: Some(file_count),
114 last_modified: None,
115 description: "Unity Hub installer cache.",
116 safe_to_delete: SafetyLevel::Safe,
117 clean_command: None,
118 });
119 }
120 }
121 }
122
123 Ok(items)
124 }
125
126 fn detect_unreal_global(&self) -> Result<Vec<CleanableItem>> {
128 let mut items = Vec::new();
129
130 #[cfg(target_os = "macos")]
131 let unreal_paths = [
132 ("Library/Application Support/Epic", "Epic Games Cache", "🎯"),
133 ("Library/Caches/com.epicgames.UnrealEngine", "Unreal Engine Cache", "🎯"),
134 ("Library/Application Support/Unreal Engine", "Unreal Engine Data", "🎯"),
135 ];
136
137 #[cfg(target_os = "linux")]
138 let unreal_paths = [
139 (".config/Epic", "Epic Games Config", "🎯"),
140 (".cache/UnrealEngine", "Unreal Engine Cache", "🎯"),
141 ];
142
143 #[cfg(target_os = "windows")]
144 let unreal_paths = [
145 ("AppData/Local/EpicGamesLauncher", "Epic Games Launcher", "🎯"),
146 ("AppData/Local/UnrealEngine", "Unreal Engine Cache", "🎯"),
147 ];
148
149 for (rel_path, name, icon) in unreal_paths {
150 let path = self.home.join(rel_path);
151 if !path.exists() {
152 continue;
153 }
154
155 let (size, file_count) = calculate_dir_size(&path)?;
156 if size < 100_000_000 {
157 continue;
158 }
159
160 items.push(CleanableItem {
161 name: name.to_string(),
162 category: "Game Development".to_string(),
163 subcategory: "Unreal Engine".to_string(),
164 icon,
165 path,
166 size,
167 file_count: Some(file_count),
168 last_modified: None,
169 description: "Unreal Engine cache and shader data.",
170 safe_to_delete: SafetyLevel::SafeWithCost,
171 clean_command: None,
172 });
173 }
174
175 #[cfg(target_os = "macos")]
177 {
178 let ddc_path = self.home.join("Library/Application Support/Unreal Engine/Common/DerivedDataCache");
179 if ddc_path.exists() {
180 let (size, file_count) = calculate_dir_size(&ddc_path)?;
181 if size > 500_000_000 {
182 items.push(CleanableItem {
183 name: "Unreal Derived Data Cache".to_string(),
184 category: "Game Development".to_string(),
185 subcategory: "Unreal Engine".to_string(),
186 icon: "🎯",
187 path: ddc_path,
188 size,
189 file_count: Some(file_count),
190 last_modified: None,
191 description: "Shared Derived Data Cache. Can be very large. Will be rebuilt.",
192 safe_to_delete: SafetyLevel::SafeWithCost,
193 clean_command: None,
194 });
195 }
196 }
197 }
198
199 Ok(items)
200 }
201
202 fn detect_godot_global(&self) -> Result<Vec<CleanableItem>> {
204 let mut items = Vec::new();
205
206 #[cfg(target_os = "macos")]
207 let godot_paths = [
208 ("Library/Application Support/Godot", "Godot Data", "🤖"),
209 ("Library/Caches/Godot", "Godot Cache", "🤖"),
210 ];
211
212 #[cfg(target_os = "linux")]
213 let godot_paths = [
214 (".config/godot", "Godot Config", "🤖"),
215 (".cache/godot", "Godot Cache", "🤖"),
216 (".local/share/godot", "Godot Data", "🤖"),
217 ];
218
219 #[cfg(target_os = "windows")]
220 let godot_paths = [
221 ("AppData/Roaming/Godot", "Godot Data", "🤖"),
222 ("AppData/Local/Godot", "Godot Cache", "🤖"),
223 ];
224
225 for (rel_path, name, icon) in godot_paths {
226 let path = self.home.join(rel_path);
227 if !path.exists() {
228 continue;
229 }
230
231 let (size, file_count) = calculate_dir_size(&path)?;
232 if size < 50_000_000 {
233 continue;
234 }
235
236 items.push(CleanableItem {
237 name: name.to_string(),
238 category: "Game Development".to_string(),
239 subcategory: "Godot".to_string(),
240 icon,
241 path,
242 size,
243 file_count: Some(file_count),
244 last_modified: None,
245 description: "Godot engine cache and editor data.",
246 safe_to_delete: SafetyLevel::SafeWithCost,
247 clean_command: None,
248 });
249 }
250
251 Ok(items)
252 }
253
254 pub fn scan_unity_projects(&self, search_path: &PathBuf) -> Result<Vec<CleanableItem>> {
256 let mut items = Vec::new();
257
258 let unity_cleanable = ["Library", "Temp", "Logs", "Builds", "obj"];
261
262 for dir_name in unity_cleanable {
263 let path = search_path.join(dir_name);
264 if !path.exists() {
265 continue;
266 }
267
268 let (size, file_count) = calculate_dir_size(&path)?;
269 if size < 50_000_000 {
270 continue;
271 }
272
273 let safety = match dir_name {
274 "Temp" | "Logs" => SafetyLevel::Safe,
275 "Library" => SafetyLevel::SafeWithCost,
276 "Builds" => SafetyLevel::Caution,
277 _ => SafetyLevel::Safe,
278 };
279
280 items.push(CleanableItem {
281 name: format!("Unity {}", dir_name),
282 category: "Game Development".to_string(),
283 subcategory: "Unity Project".to_string(),
284 icon: "🎮",
285 path,
286 size,
287 file_count: Some(file_count),
288 last_modified: None,
289 description: match dir_name {
290 "Library" => "Unity Library cache. Will be rebuilt on project open.",
291 "Temp" => "Temporary build files. Safe to delete.",
292 "Logs" => "Unity log files. Safe to delete.",
293 "Builds" => "Build output. Check if needed before deleting.",
294 _ => "Unity project files.",
295 },
296 safe_to_delete: safety,
297 clean_command: None,
298 });
299 }
300
301 Ok(items)
302 }
303
304 pub fn scan_unreal_projects(&self, search_path: &PathBuf) -> Result<Vec<CleanableItem>> {
306 let mut items = Vec::new();
307
308 let unreal_cleanable = ["Intermediate", "Saved", "DerivedDataCache", "Binaries"];
310
311 for dir_name in unreal_cleanable {
312 let path = search_path.join(dir_name);
313 if !path.exists() {
314 continue;
315 }
316
317 let (size, file_count) = calculate_dir_size(&path)?;
318 if size < 100_000_000 {
319 continue;
320 }
321
322 let safety = match dir_name {
323 "Intermediate" | "DerivedDataCache" => SafetyLevel::SafeWithCost,
324 "Saved" => SafetyLevel::Caution, "Binaries" => SafetyLevel::SafeWithCost,
326 _ => SafetyLevel::Safe,
327 };
328
329 items.push(CleanableItem {
330 name: format!("Unreal {}", dir_name),
331 category: "Game Development".to_string(),
332 subcategory: "Unreal Project".to_string(),
333 icon: "🎯",
334 path,
335 size,
336 file_count: Some(file_count),
337 last_modified: None,
338 description: match dir_name {
339 "Intermediate" => "Build intermediate files. Will be rebuilt.",
340 "DerivedDataCache" => "Shader and asset cache. Will be rebuilt.",
341 "Saved" => "Saved data including autosaves. Check before deleting.",
342 "Binaries" => "Compiled binaries. Will be rebuilt.",
343 _ => "Unreal project files.",
344 },
345 safe_to_delete: safety,
346 clean_command: None,
347 });
348 }
349
350 Ok(items)
351 }
352}
353
354impl Default for GameDevCleaner {
355 fn default() -> Self {
356 Self::new().expect("GameDevCleaner requires home directory")
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_gamedev_cleaner_creation() {
366 let cleaner = GameDevCleaner::new();
367 assert!(cleaner.is_some());
368 }
369
370 #[test]
371 fn test_gamedev_detection() {
372 if let Some(cleaner) = GameDevCleaner::new() {
373 let items = cleaner.detect().unwrap();
374 println!("Found {} game dev items", items.len());
375 for item in &items {
376 println!(" {} {} ({} bytes)", item.icon, item.name, item.size);
377 }
378 }
379 }
380}