Skip to content

Commit 446f660

Browse files
committed
feat: Implement Smart Cleanup Optimizer (#125)
Added CleanupOptimizer, LogManager, TempCleaner logic. Integrated with CLI via 'cleanup' command group. Added unit tests.
1 parent e010296 commit 446f660

File tree

7 files changed

+427
-411
lines changed

7 files changed

+427
-411
lines changed

cortex/cli.py

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
)
4141
# Import Notification Manager
4242
from cortex.notification_manager import NotificationManager
43+
from cortex.optimizer import CleanupOptimizer
4344

4445

4546
class CortexCLI:
@@ -221,79 +222,90 @@ def health(self, args):
221222

222223
return 0
223224

224-
225-
# --- Cleanup Command ---
226225
def cleanup(self, args):
227-
"""Run disk space optimizer"""
228-
from cortex.optimizer import DiskOptimizer
229-
230-
optimizer = DiskOptimizer()
226+
"""Run system cleanup optimization"""
227+
optimizer = CleanupOptimizer()
231228

232229
if args.cleanup_action == 'scan':
233230
self._print_status("🔍", "Scanning for cleanup opportunities...")
234-
results = optimizer.scan()
235-
236-
console.print()
237-
cx_header("Cleanup Opportunities")
231+
opportunities = optimizer.scan()
238232

239-
# Package Cache
240-
cache_size = optimizer._format_size(results["package_cache"])
241-
console.print(f"📦 [bold]Package Cache:[/bold] {cache_size}")
233+
if not opportunities:
234+
self._print_success("No cleanup opportunities found! system is clean.")
235+
return 0
236+
237+
total_bytes = sum(o.size_bytes for o in opportunities)
238+
total_mb = total_bytes / (1024 * 1024)
242239

243-
# Orphaned Packages
244-
orphans_count = len(results["orphaned_packages"])
245-
orphans_size = optimizer._format_size(results["orphaned_size_est"])
246-
console.print(f"🗑️ [bold]Orphaned Packages:[/bold] {orphans_count} packages (~{orphans_size})")
247-
if orphans_count > 0 and self.verbose:
248-
for p in results["orphaned_packages"]:
249-
console.print(f" - {p}", style="dim")
240+
console.print()
241+
cx_header(f"Cleanup Scan Results ({total_mb:.1f} MB Reclaimable)")
250242

251-
# Logs
252-
logs_count = len(results["logs"])
253-
logs_size = optimizer._format_size(results["logs_size"])
254-
console.print(f"📝 [bold]Old Logs:[/bold] {logs_count} files ({logs_size})")
243+
from rich.table import Table
244+
table = Table(box=None)
245+
table.add_column("Type", style="cyan")
246+
table.add_column("Description")
247+
table.add_column("Size", justify="right", style="green")
255248

256-
# Temp Files
257-
temp_count = len(results["temp_files"])
258-
temp_size = optimizer._format_size(results["temp_size"])
259-
console.print(f"🧹 [bold]Temp Files:[/bold] {temp_count} files ({temp_size})")
249+
for opp in opportunities:
250+
size_mb = opp.size_bytes / (1024 * 1024)
251+
table.add_row(
252+
opp.type.replace('_', ' ').title(),
253+
opp.description,
254+
f"{size_mb:.1f} MB"
255+
)
260256

257+
console.print(table)
261258
console.print()
262-
total_size = optimizer._format_size(results["total_reclaimable"])
263-
console.print(f"✨ [bold green]Total Reclaimable:[/bold green] {total_size}")
264-
console.print()
265-
console.print("[dim]Run 'cortex cleanup run --safe' to perform cleanup[/dim]")
259+
console.print("[dim]Run 'cortex cleanup run' to clean these items.[/dim]")
266260
return 0
267261

268262
elif args.cleanup_action == 'run':
269-
if not args.safe:
270-
# Require confirmation if not explicitly safe (though implementation implies safe only for now)
271-
# But specification says --safe mode. We'll default to requiring --safe for actual run
272-
# or prompt user. Let's implementing prompting or requiring --safe.
273-
# Given the 'run --safe' spec, 'run' without safe might imply aggressive or just need confirmation.
274-
# For safety let's require --safe or confirmation.
275-
confirm = input("⚠️ Run cleanup? This will remove files. (y/n): ")
263+
safe_mode = not args.force
264+
265+
self._print_status("🔍", "Preparing cleanup plan...")
266+
commands = optimizer.get_cleanup_plan(safe_mode=safe_mode)
267+
268+
if not commands:
269+
self._print_success("Nothing to clean!")
270+
return 0
271+
272+
console.print("[bold]Proposed Cleanup Operations:[/bold]")
273+
for i, cmd in enumerate(commands, 1):
274+
console.print(f" {i}. {cmd}")
275+
276+
if getattr(args, 'dry_run', False):
277+
console.print("\n[dim](Dry run mode - no changes made)[/dim]")
278+
return 0
279+
280+
if not args.yes:
281+
if not safe_mode:
282+
console.print("\n[bold red]WARNING: Running in FORCE mode (no backups)[/bold red]")
283+
284+
confirm = input("\nProceed with cleanup? (y/n): ")
276285
if confirm.lower() != 'y':
277286
print("Operation cancelled.")
278287
return 0
279288

280-
self._print_status("🧹", "Cleaning up...")
281-
stats = optimizer.clean(safe_mode=True)
282-
283-
console.print()
284-
for action in stats["actions"]:
285-
if "Failed" in action:
286-
console.print(f"❌ {action}", style="red")
287-
else:
288-
console.print(f"✓ {action}", style="green")
289+
# Use InstallationCoordinator for execution
290+
def progress_callback(current, total, step):
291+
print(f"[{current}/{total}] {step.description}")
289292

290-
console.print()
291-
freed = optimizer._format_size(stats["freed_bytes"])
292-
self._print_success(f"Cleanup complete! Freed {freed}")
293-
return 0
293+
coordinator = InstallationCoordinator(
294+
commands=commands,
295+
descriptions=[f"Cleanup Step {i+1}" for i in range(len(commands))],
296+
progress_callback=progress_callback
297+
)
294298

299+
result = coordinator.execute()
300+
if result.success:
301+
self._print_success("Cleanup completed successfully!")
302+
return 0
303+
else:
304+
self._print_error("Cleanup encountered errors.")
305+
return 1
306+
295307
else:
296-
self._print_error("Please specify a subcommand (scan/run)")
308+
self._print_error("Unknown cleanup action")
297309
return 1
298310

299311
def install(self, software: str, execute: bool = False, dry_run: bool = False):
@@ -663,7 +675,6 @@ def show_rich_help():
663675
table.add_row("install <pkg>", "Install software")
664676
table.add_row("history", "View history")
665677
table.add_row("rollback <id>", "Undo installation")
666-
table.add_row("cleanup", "Optimize disk space")
667678
table.add_row("notify", "Manage desktop notifications")
668679
table.add_row("health", "Check system health score") # Added this line
669680

@@ -738,18 +749,20 @@ def main():
738749
send_parser.add_argument('--level', choices=['low', 'normal', 'critical'], default='normal')
739750
send_parser.add_argument('--actions', nargs='*', help='Action buttons')
740751

741-
752+
# --- New Health Command ---
753+
health_parser = subparsers.add_parser('health', help='Check system health score')
754+
742755
# --- Cleanup Command ---
743756
cleanup_parser = subparsers.add_parser('cleanup', help='Optimize disk space')
744757
cleanup_subs = cleanup_parser.add_subparsers(dest='cleanup_action', help='Cleanup actions')
745758

746-
cleanup_subs.add_parser('scan', help='Scan for cleanable items')
759+
scan_parser = cleanup_subs.add_parser('scan', help='Scan for cleanable items')
747760

748761
run_parser = cleanup_subs.add_parser('run', help='Execute cleanup')
749-
run_parser.add_argument('--safe', action='store_true', help='Safe cleanup mode')
750-
751-
# --- New Health Command ---
752-
health_parser = subparsers.add_parser('health', help='Check system health score')
762+
run_parser.add_argument('--safe', action='store_true', default=True, help='Run safely (with backups)')
763+
run_parser.add_argument('--force', action='store_true', help='Force cleanup (no backups)')
764+
run_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation')
765+
run_parser.add_argument('--dry-run', action='store_true', help='Show proposed changes without executing')
753766
# --------------------------
754767

755768
args = parser.parse_args()
@@ -779,11 +792,13 @@ def main():
779792
return cli.edit_pref(action=args.action, key=args.key, value=args.value)
780793
elif args.command == 'notify':
781794
return cli.notify(args)
782-
elif args.command == 'cleanup':
783-
return cli.cleanup(args)
784795
# Handle new command
796+
elif args.command == 'notify':
797+
return cli.notify(args)
785798
elif args.command == 'health':
786799
return cli.health(args)
800+
elif args.command == 'cleanup':
801+
return cli.cleanup(args)
787802
else:
788803
parser.print_help()
789804
return 1

0 commit comments

Comments
 (0)