Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/null-e-core", "crates/null-e-cli", "tauri"]
resolver = "2"

[workspace.package]
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = ["us"]
license = "WTFPL"
Expand Down
1 change: 1 addition & 0 deletions tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ serde_json = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }
dirs = { workspace = true }

[build-dependencies]
tauri-build = { version = "2", features = [] }
Expand Down
6 changes: 3 additions & 3 deletions tauri/src/commands/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ pub async fn detect_caches(
}

#[tauri::command]
pub async fn clean_cache(_state: tauri::State<'_, AppState>, id: String) -> Result<(), String> {
pub async fn clean_cache(_state: tauri::State<'_, AppState>, id: String) -> Result<u64, String> {
tokio::task::spawn_blocking(move || {
let caches = null_e_core::caches::detect_caches().map_err(|e| e.to_string())?;
let cache = caches
.iter()
.find(|c| c.id == id)
.ok_or_else(|| format!("Cache '{}' not found", id))?;
null_e_core::caches::clean_cache(cache, true).map_err(|e| e.to_string())?;
Ok::<(), String>(())
let result = null_e_core::caches::clean_cache(cache, true).map_err(|e| e.to_string())?;
Ok::<u64, String>(result.bytes_freed)
})
.await
.map_err(|e| e.to_string())?
Expand Down
37 changes: 35 additions & 2 deletions tauri/src/commands/clean.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::dto::{CleanConfigDto, CleanProgressDto, CleanSummaryDto};
use crate::dto::{CleanConfigDto, CleanFailureDto, CleanProgressDto, CleanSummaryDto};
use crate::state::AppState;
use null_e_core::error::NullEError;
use null_e_core::trash::{delete_path, DeleteMethod};
use std::path::PathBuf;
use tauri::{AppHandle, Emitter};
Expand Down Expand Up @@ -29,6 +30,7 @@ pub async fn start_clean(
let mut succeeded = 0usize;
let mut failed = 0usize;
let mut bytes_freed = 0u64;
let mut failures = Vec::new();

for (i, target) in targets.iter().enumerate() {
let path = PathBuf::from(target);
Expand All @@ -46,8 +48,14 @@ pub async fn start_clean(
succeeded += 1;
bytes_freed += freed;
}
Err(_) => {
Err(err) => {
failed += 1;
let (reason, is_tcc) = classify_delete_error(&err);
failures.push(CleanFailureDto {
path: target.clone(),
reason,
is_tcc,
});
}
}
}
Expand All @@ -58,6 +66,11 @@ pub async fn start_clean(
failed,
bytes_freed,
used_trash: method == DeleteMethod::Trash,
method_label: match method {
DeleteMethod::Trash => "Trash".to_string(),
DeleteMethod::Permanent | DeleteMethod::DryRun => "Deleted".to_string(),
},
failures,
};
let _ = app_handle.emit("clean:complete", &summary);
});
Expand All @@ -72,3 +85,23 @@ pub async fn cancel_clean(state: tauri::State<'_, AppState>) -> Result<(), Strin
}
Ok(())
}

fn classify_delete_error(err: &NullEError) -> (String, bool) {
match err {
NullEError::Io(io_err) => {
let is_tcc = io_err.raw_os_error() == Some(1);
(io_err.to_string(), is_tcc)
}
NullEError::PermissionDenied(path) => {
(format!("Permission denied: {}", path.display()), false)
}
NullEError::Trash(message) => {
let lower = message.to_lowercase();
let is_tcc = lower.contains("operation not permitted")
|| lower.contains("eperm")
|| lower.contains("os error 1");
(message.clone(), is_tcc)
}
_ => (err.to_string(), false),
}
}
42 changes: 41 additions & 1 deletion tauri/src/commands/system.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::dto::DiskInfoDto;
use crate::dto::{DiskInfoDto, FdaStatusDto};
use crate::state::AppState;
use std::fs;
use std::io::ErrorKind;
use std::process::Command;

#[tauri::command]
Expand Down Expand Up @@ -39,3 +41,41 @@ pub async fn get_disk_info(_state: tauri::State<'_, AppState>) -> Result<DiskInf
pub async fn get_app_version(_state: tauri::State<'_, AppState>) -> Result<String, String> {
Ok(null_e_core::VERSION.to_string())
}

#[tauri::command]
pub async fn check_fda_status(_state: tauri::State<'_, AppState>) -> Result<FdaStatusDto, String> {
Ok(check_fda_status_inner())
}

#[cfg(target_os = "macos")]
fn check_fda_status_inner() -> FdaStatusDto {
let platform = std::env::consts::OS.to_string();
let Some(home) = dirs::home_dir() else {
return FdaStatusDto {
status: "unknown".to_string(),
platform,
};
};

let tcc_dir = home.join("Library/Application Support/com.apple.TCC");
let status = match fs::read_dir(tcc_dir) {
Ok(_) => "granted",
Err(err) if err.kind() == ErrorKind::PermissionDenied || err.raw_os_error() == Some(1) => {
"not_granted"
}
Err(_) => "unknown",
};

FdaStatusDto {
status: status.to_string(),
platform,
}
}

#[cfg(not(target_os = "macos"))]
fn check_fda_status_inner() -> FdaStatusDto {
FdaStatusDto {
status: "granted".to_string(),
platform: std::env::consts::OS.to_string(),
}
}
15 changes: 15 additions & 0 deletions tauri/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ pub struct CleanSummaryDto {
pub failed: usize,
pub bytes_freed: u64,
pub used_trash: bool,
pub method_label: String,
pub failures: Vec<CleanFailureDto>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CleanFailureDto {
pub path: String,
pub reason: String,
pub is_tcc: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -196,3 +205,9 @@ pub struct DiskInfoDto {
pub available: u64,
pub mount_point: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FdaStatusDto {
pub status: String,
pub platform: String,
}
1 change: 1 addition & 0 deletions tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn run() {
commands::config::save_config,
commands::system::get_disk_info,
commands::system::get_app_version,
commands::system::check_fda_status,
])
.setup(|app| {
// System tray menu
Expand Down
4 changes: 2 additions & 2 deletions tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
"productName": "null-e",
"version": "0.4.0",
"version": "0.4.1",
"identifier": "com.null-e.disk-cleaner",
"build": {
"frontendDist": "../ui/dist",
Expand Down Expand Up @@ -63,7 +63,7 @@
},
"plugins": {
"shell": {
"open": true
"open": "^(https?://.+|mailto:.+|tel:.+|x-apple\\.systempreferences:.+)$"
},
"updater": {
"endpoints": [
Expand Down
18 changes: 13 additions & 5 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ import { useUiStore } from '@/stores/ui-store';
import { useScanStore } from '@/stores/scan-store';
import { useTheme } from '@/hooks/useTheme';
import { useScanProgress } from '@/hooks/useScanProgress';
import { useFdaCheck } from '@/hooks/useFdaCheck';

export function App() {
useTheme();
useScanProgress();

const disclaimerAccepted = useUiStore((s) => s.disclaimerAccepted);

if (!disclaimerAccepted) {
return <DisclaimerModal />;
}

return <AppMain />;
}

function AppMain() {
useScanProgress();
useFdaCheck();

const appState = useUiStore((s) => s.appState);
const result = useScanStore((s) => s.result);
const scanError = useScanStore((s) => s.error);
Expand All @@ -35,10 +47,6 @@ export function App() {
}
}, [scanError, appState]);

if (!disclaimerAccepted) {
return <DisclaimerModal />;
}

return (
<div className="flex flex-col h-full">
<AppBar />
Expand Down
Loading
Loading