Skip to main content

null_e/cleaners/
gamedev.rs

1//! Game Development cleanup module
2//!
3//! Handles cleanup of game development artifacts:
4//! - Unity (Library, Temp, Builds, Logs)
5//! - Unreal Engine (Intermediate, Saved, DerivedDataCache)
6//! - Godot (cache, .import)
7
8use super::{calculate_dir_size, CleanableItem, SafetyLevel};
9use crate::error::Result;
10use std::path::PathBuf;
11
12/// Game Development cleaner
13pub struct GameDevCleaner {
14    home: PathBuf,
15}
16
17impl GameDevCleaner {
18    /// Create a new game dev cleaner
19    pub fn new() -> Option<Self> {
20        let home = dirs::home_dir()?;
21        Some(Self { home })
22    }
23
24    /// Detect all game development cleanable items
25    pub fn detect(&self) -> Result<Vec<CleanableItem>> {
26        let mut items = Vec::new();
27
28        // Unity Hub and Editor caches
29        items.extend(self.detect_unity_global()?);
30
31        // Unreal Engine global caches
32        items.extend(self.detect_unreal_global()?);
33
34        // Godot global caches
35        items.extend(self.detect_godot_global()?);
36
37        Ok(items)
38    }
39
40    /// Detect Unity global caches (Unity Hub, Editor)
41    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        // Unity Hub
100        #[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    /// Detect Unreal Engine global caches
127    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        // DerivedDataCache (can be huge)
176        #[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    /// Detect Godot global caches
203    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    /// Scan for Unity project folders and their cleanable directories
255    pub fn scan_unity_projects(&self, search_path: &PathBuf) -> Result<Vec<CleanableItem>> {
256        let mut items = Vec::new();
257
258        // This would be called during a project scan
259        // Unity projects have: Library/, Temp/, Logs/, Builds/
260        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    /// Scan for Unreal project folders and their cleanable directories
305    pub fn scan_unreal_projects(&self, search_path: &PathBuf) -> Result<Vec<CleanableItem>> {
306        let mut items = Vec::new();
307
308        // Unreal projects have: Intermediate/, Saved/, DerivedDataCache/, Binaries/
309        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, // May contain saves
325                "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}