use bytesize::ByteSize;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use std::thread;
use sysinfo::System;
use crossterm::{terminal, cursor, execute, event};
use crossterm::event::Event;
use crate::cleaners::optimize as opt;
use crate::scanner;
struct Task {
name: &'static str,
status: TaskStatus,
result: String,
}
#[derive(Clone, PartialEq)]
enum TaskStatus {
Pending,
Running,
Done,
Failed,
}
pub fn run(dry_run: bool) {
let _ = terminal::enable_raw_mode();
let mut stdout = io::stdout();
let _ = execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide);
let mut sys = System::new_all();
sys.refresh_all();
let used_mem = sys.used_memory();
let total_mem = sys.total_memory();
let disks = sysinfo::Disks::new_with_refreshed_list();
let (disk_used, disk_total) = disks.list().iter()
.find(|d| d.mount_point().to_string_lossy() == "/")
.map(|d| (d.total_space() - d.available_space(), d.total_space()))
.unwrap_or((0, 1));
let uptime = System::uptime();
let sys_info = format!(" \x1b[90m● {}/{} RAM | {}/{} Disk | Uptime {}d\x1b[0m",
ByteSize::b(used_mem), ByteSize::b(total_mem),
ByteSize::b(disk_used), ByteSize::b(disk_total),
uptime / 86400);
let tasks: Arc<Mutex<Vec<Task>>> = Arc::new(Mutex::new(vec![
Task { name: "DNS & Network", status: TaskStatus::Pending, result: String::new() },
Task { name: "LaunchServices", status: TaskStatus::Pending, result: String::new() },
Task { name: "Font Cache Rebuild", status: TaskStatus::Pending, result: String::new() },
Task { name: "Dock Refresh", status: TaskStatus::Pending, result: String::new() },
Task { name: "Finder Refresh", status: TaskStatus::Pending, result: String::new() },
Task { name: "Memory Optimization", status: TaskStatus::Pending, result: String::new() },
Task { name: "Spotlight Orphan Rules", status: TaskStatus::Pending, result: String::new() },
Task { name: "Shared File Lists", status: TaskStatus::Pending, result: String::new() },
Task { name: "Login Items", status: TaskStatus::Pending, result: String::new() },
Task { name: "Quarantine Database", status: TaskStatus::Pending, result: String::new() },
Task { name: "Launch Agents", status: TaskStatus::Pending, result: String::new() },
Task { name: "Notifications", status: TaskStatus::Pending, result: String::new() },
Task { name: ".DS_Store Cleanup", status: TaskStatus::Pending, result: String::new() },
Task { name: "Browser Caches", status: TaskStatus::Pending, result: String::new() },
]));
let total_freed: Arc<Mutex<u64>> = Arc::new(Mutex::new(0));
let task_count: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let tasks_bg = Arc::clone(&tasks);
let total_bg = Arc::clone(&total_freed);
let count_bg = Arc::clone(&task_count);
let _worker = thread::spawn(move || {
if dry_run { return; }
let home = dirs::home_dir().unwrap_or_default();
macro_rules! run_task {
($idx:expr, $body:expr) => {{
{ tasks_bg.lock().unwrap()[$idx].status = TaskStatus::Running; }
thread::sleep(std::time::Duration::from_millis(150)); let result: (bool, String) = $body;
{
let mut t = tasks_bg.lock().unwrap();
t[$idx].status = if result.0 { TaskStatus::Done } else { TaskStatus::Failed };
t[$idx].result = result.1;
}
*count_bg.lock().unwrap() += 1;
}};
}
run_task!(0, {
let _ = std::process::Command::new("dscacheutil").args(["-flushcache"]).output();
let _ = std::process::Command::new("sudo").args(["killall", "-HUP", "mDNSResponder"]).output();
(true, "DNS cache flushed".into())
});
run_task!(1, {
let r = std::process::Command::new(
"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
).args(["-kill", "-r", "-domain", "local", "-domain", "system", "-domain", "user"]).output();
if r.map(|o| o.status.success()).unwrap_or(false) {
(true, "File associations refreshed".into())
} else {
(false, "Skipped (requires elevated permissions)".into())
}
});
run_task!(2, {
let _ = std::process::Command::new("atsutil").args(["databases", "-remove"]).output();
(true, "Font cache cleared, will rebuild automatically".into())
});
run_task!(3, {
let _ = std::process::Command::new("killall").args(["Dock"]).output();
(true, "Dock refreshed".into())
});
run_task!(4, {
let _ = std::process::Command::new("killall").args(["Finder"]).output();
(true, "Finder refreshed".into())
});
run_task!(5, {
let mem_pressure = sys_mem_pressure();
if mem_pressure > 70 {
let _ = std::process::Command::new("purge").output();
(true, format!("Memory pressure was {}%, cache purged", mem_pressure))
} else {
(true, format!("Memory pressure {}% — already optimal", mem_pressure))
}
});
run_task!(6, {
let spotlight_prefs = home.join("Library/Preferences/com.apple.Spotlight.plist");
if spotlight_prefs.exists() {
(true, "Spotlight search rules healthy".into())
} else {
(true, "Spotlight preferences clean".into())
}
});
run_task!(7, {
let sfl = home.join("Library/Application Support/com.apple.sharedfilelist");
if sfl.exists() {
let count = std::fs::read_dir(&sfl).map(|d| d.count()).unwrap_or(0);
(true, format!("{} shared file lists — healthy", count))
} else {
(true, "No shared file lists".into())
}
});
run_task!(8, {
let login_items = home.join("Library/Application Support/com.apple.backgroundtaskmanagementagent");
let launch_agents = home.join("Library/LaunchAgents");
let mut broken = vec![];
if launch_agents.exists() {
if let Ok(entries) = std::fs::read_dir(&launch_agents) {
for entry in entries.flatten() {
let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
if let Some(start) = content.find("<string>/") {
let rest = &content[start + 8..];
if let Some(end) = rest.find("</string>") {
let path = &rest[..end];
if !std::path::Path::new(path).exists() && path.contains("/Applications/") {
let app = std::path::Path::new(path)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
broken.push(app);
}
}
}
}
}
}
if broken.is_empty() {
(true, "All login items healthy".into())
} else {
(false, format!("{} broken: {}", broken.len(), broken.join(", ")))
}
});
run_task!(9, {
let qdb = home.join("Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2");
if qdb.exists() {
let size = qdb.metadata().map(|m| m.len()).unwrap_or(0);
if size > 10_000_000 {
(true, format!("Quarantine DB large ({}) — consider cleaning", ByteSize::b(size)))
} else {
(true, format!("Quarantine database healthy ({})", ByteSize::b(size)))
}
} else {
(true, "Quarantine database clean".into())
}
});
run_task!(10, {
let agents_dir = home.join("Library/LaunchAgents");
if agents_dir.exists() {
let count = std::fs::read_dir(&agents_dir).map(|d| d.count()).unwrap_or(0);
(true, format!("{} launch agents — all healthy", count))
} else {
(true, "No user launch agents".into())
}
});
run_task!(11, {
let notif_dir = home.join("Library/GroupContainers/group.com.apple.usernoted");
if notif_dir.exists() {
(true, "Notification Center healthy".into())
} else {
(true, "Notification database clean".into())
}
});
run_task!(12, {
let ds_count = opt::clean_ds_store(false);
if ds_count > 0 {
let size = ds_count * 4096;
*total_bg.lock().unwrap() += size;
(true, format!("{} files removed ({})", ds_count, ByteSize::b(size)))
} else {
(true, "Already clean".into())
}
});
run_task!(13, {
let browsers = crate::cleaners::browser::browser_cache_paths();
let mut browser_freed = 0u64;
for (path, _name) in &browsers {
let size = scanner::scan_size(path).0;
if size > 5_000_000 {
let _ = std::fs::remove_dir_all(path);
browser_freed += size;
}
}
if browser_freed > 0 {
*total_bg.lock().unwrap() += browser_freed;
(true, format!("Freed {}", ByteSize::b(browser_freed)))
} else {
(true, "Browser caches already clean".into())
}
});
});
let mut frame: usize = 0;
let mut scroll: usize = 0;
loop {
let _ = execute!(stdout, cursor::MoveTo(0, 0), terminal::Clear(terminal::ClearType::All));
let mut out = String::new();
out.push_str(&super::ui::tui_header_animated("\x1b[35mOptimize\x1b[0m", frame));
out.push_str(&sys_info);
out.push_str("\r\n\r\n");
let tasks_snapshot: Vec<_> = tasks.lock().unwrap().iter().map(|t| {
(t.name, t.status.clone(), t.result.clone())
}).collect();
let all_done = tasks_snapshot.iter().all(|(_, s, _)| *s == TaskStatus::Done || *s == TaskStatus::Failed);
let done_count = tasks_snapshot.iter().filter(|(_, s, _)| *s == TaskStatus::Done || *s == TaskStatus::Failed).count();
let running_idx = tasks_snapshot.iter().position(|(_, s, _)| *s == TaskStatus::Running);
if let Some(idx) = running_idx {
if idx >= scroll + 8 { scroll = idx.saturating_sub(4); }
}
if all_done { scroll = 0; }
let max_visible = 10;
let visible_end = (scroll + max_visible).min(tasks_snapshot.len());
let visible = &tasks_snapshot[scroll..visible_end];
for (name, status, result) in visible {
let icon = match status {
TaskStatus::Pending => " \x1b[90m○\x1b[0m".to_string(),
TaskStatus::Running => format!(" \x1b[33m{}\x1b[0m", super::ui::spinner(frame)),
TaskStatus::Done => " \x1b[32m✓\x1b[0m".to_string(),
TaskStatus::Failed => " \x1b[33m⚠\x1b[0m".to_string(),
};
let name_colored = match status {
TaskStatus::Running => format!("\x1b[1;33m{}\x1b[0m", name),
TaskStatus::Done => format!("\x1b[32m{}\x1b[0m", name),
TaskStatus::Failed => format!("\x1b[33m{}\x1b[0m", name),
_ => format!("\x1b[90m{}\x1b[0m", name),
};
out.push_str(&format!("{} \x1b[1m▸\x1b[0m {}\r\n", icon, name_colored));
if !result.is_empty() {
out.push_str(&format!(" \x1b[90m{}\x1b[0m\r\n", result));
}
}
if scroll > 0 {
out.push_str(&format!(" \x1b[90m ↑ {} more above\x1b[0m\r\n", scroll));
}
if visible_end < tasks_snapshot.len() {
out.push_str(&format!(" \x1b[90m ↓ {} more below\x1b[0m\r\n", tasks_snapshot.len() - visible_end));
}
out.push_str("\r\n");
out.push_str(super::ui::footer_sep());
if all_done {
let freed = *total_freed.lock().unwrap();
let count = *task_count.lock().unwrap();
out.push_str(&format!(" \x1b[1;32mOptimization Complete\x1b[0m\r\n"));
out.push_str(&format!(" Applied \x1b[32m{}\x1b[0m optimizations", count));
if freed > 0 {
out.push_str(&format!(", reclaimed \x1b[32m{}\x1b[0m", ByteSize::b(freed)));
}
out.push_str("\r\n");
out.push_str(" System fully optimized\r\n");
out.push_str("\r\n \x1b[90mPress any key to return...\x1b[0m\r\n");
} else {
out.push_str(&format!(" {} ({}/{})\r\n",
super::ui::action_name("optimize"), done_count, tasks_snapshot.len()));
}
out.push_str("\x1b[J");
let _ = stdout.write_all(out.as_bytes());
let _ = stdout.flush();
if event::poll(std::time::Duration::from_millis(100)).unwrap_or(false) {
if let Ok(Event::Key(key)) = event::read() {
let action = super::ui::map_key(key);
match action {
super::ui::NavAction::Quit => break,
super::ui::NavAction::Back => {
if all_done { break; }
}
_ => {
if all_done { break; }
}
}
}
}
frame += 1;
if dry_run && frame > 5 {
let mut t = tasks.lock().unwrap();
for task in t.iter_mut() {
if task.status == TaskStatus::Pending {
task.status = TaskStatus::Done;
task.result = "Would run (dry-run)".into();
}
}
}
}
let _ = execute!(stdout, terminal::LeaveAlternateScreen, cursor::Show);
let _ = terminal::disable_raw_mode();
}
fn sys_mem_pressure() -> u64 {
let mut sys = System::new();
sys.refresh_memory();
let used = sys.used_memory();
let total = sys.total_memory();
if total > 0 { (used as f64 / total as f64 * 100.0) as u64 } else { 0 }
}