From 5761fd87c8e386404e0af317a1dc79d2cf5086dc Mon Sep 17 00:00:00 2001 From: Tw93 Date: Fri, 5 Dec 2025 14:21:18 +0800 Subject: [PATCH] Simpler and faster --- bin/clean.sh | 14 ++--- lib/clean/apps.sh | 11 ++-- lib/clean/brew.sh | 4 +- lib/clean/caches.sh | 20 ++++++- lib/clean/maintenance.sh | 35 ++++++------- lib/clean/system.sh | 109 ++++++++++++++++++++------------------- lib/clean/user.sh | 78 ++++++++++++++-------------- lib/core/common.sh | 44 ++++++---------- lib/uninstall/batch.sh | 8 +-- mole | 2 +- 10 files changed, 166 insertions(+), 159 deletions(-) diff --git a/bin/clean.sh b/bin/clean.sh index bd50259..a7ee74c 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -271,7 +271,7 @@ safe_clean() { for path in "${existing_paths[@]}"; do ( local size - # Timeout protection: prevent du from hanging on problematic paths + # Get size quickly with depth limit size=$(get_path_size_kb "$path") [[ -z "$size" || ! "$size" =~ ^[0-9]+$ ]] && size=0 local count @@ -338,7 +338,7 @@ safe_clean() { for path in "${existing_paths[@]}"; do local size_bytes count - # Get size quickly - du is fast + # Get size quickly with depth limit size_bytes=$(get_path_size_kb "$path") [[ -z "$size_bytes" || ! "$size_bytes" =~ ^[0-9]+$ ]] && size_bytes=0 # Quick file count for display - limit for performance @@ -695,11 +695,11 @@ perform_cleanup() { grep -v "^$" >> "$installed_bundles" || true done - # Get running applications - no timeout needed for fast osascript - osascript -e 'tell application "System Events" to get bundle identifier of every application process' 2> /dev/null | + # Get running applications - timeout protection for osascript + run_with_timeout 5 osascript -e 'tell application "System Events" to get bundle identifier of every application process' 2> /dev/null | tr ',' '\n' | sed -e 's/^ *//;s/ *$//' -e '/^$/d' >> "$installed_bundles" || true - # Get LaunchAgents - fast operation, no timeout needed + # Get LaunchAgents find ~/Library/LaunchAgents /Library/LaunchAgents -name "*.plist" -type f 2> /dev/null | xargs -I {} basename {} .plist >> "$installed_bundles" 2> /dev/null || true @@ -817,9 +817,9 @@ perform_cleanup() { bundle_id="${bundle_id%.binarycookies}" if is_orphaned "$bundle_id" "$match"; then - # Use timeout to prevent du from hanging on large/problematic directories + # Use timeout to prevent du from hanging on network mounts or problematic paths local size_kb - size_kb=$(run_with_timeout 2 get_path_size_kb "$match") + size_kb=$(run_with_timeout 5 get_path_size_kb "$match") if [[ -z "$size_kb" || "$size_kb" == "0" ]]; then continue fi diff --git a/lib/clean/apps.sh b/lib/clean/apps.sh index d4cfb33..2b7ba56 100644 --- a/lib/clean/apps.sh +++ b/lib/clean/apps.sh @@ -100,7 +100,7 @@ clean_orphaned_app_data() { for app_dir in "${app_dirs[@]}"; do [[ -d "$app_dir" ]] || continue - command find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do + find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do local bundle_id bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "") [[ -n "$bundle_id" ]] && echo "$bundle_id" @@ -112,9 +112,8 @@ clean_orphaned_app_data() { echo "$running_apps" | tr ',' '\n' | sed -e 's/^ *//;s/ *$//' -e '/^$/d' >> "$installed_bundles" run_with_timeout 5 find ~/Library/LaunchAgents /Library/LaunchAgents \ - -name "*.plist" -type f 2> /dev/null | while IFS= read -r plist; do - basename "$plist" .plist - done >> "$installed_bundles" 2> /dev/null || true + -name "*.plist" -type f 2> /dev/null | + xargs -I {} basename {} .plist >> "$installed_bundles" 2> /dev/null || true # Deduplicate sort -u "$installed_bundles" -o "$installed_bundles" @@ -230,9 +229,9 @@ clean_orphaned_app_data() { bundle_id="${bundle_id%.binarycookies}" if is_orphaned "$bundle_id" "$match"; then - # Use timeout to prevent du from hanging on large/problematic directories + # Use timeout to prevent du from hanging on network mounts or problematic paths local size_kb - size_kb=$(run_with_timeout 2 get_path_size_kb "$match") + size_kb=$(run_with_timeout 5 get_path_size_kb "$match") if [[ -z "$size_kb" || "$size_kb" == "0" ]]; then continue fi diff --git a/lib/clean/brew.sh b/lib/clean/brew.sh index 40f9a06..f8843d0 100644 --- a/lib/clean/brew.sh +++ b/lib/clean/brew.sh @@ -39,9 +39,9 @@ clean_orphaned_casks() { true > "$cask_cache" while IFS= read -r cask; do - # Get app path from cask info (expensive call, hence caching) + # Get app path from cask info with timeout protection (expensive call, hence caching) local cask_info - cask_info=$(brew info --cask "$cask" 2> /dev/null || true) + cask_info=$(run_with_timeout 10 brew info --cask "$cask" 2> /dev/null || true) # Extract app name from "AppName.app (App)" format in cask info output local app_name diff --git a/lib/clean/caches.sh b/lib/clean/caches.sh index ccb1390..a00e058 100644 --- a/lib/clean/caches.sh +++ b/lib/clean/caches.sh @@ -97,13 +97,29 @@ clean_service_worker_cache() { done < <(command find "$cache_path" -type d -depth 2 2> /dev/null) if [[ $cleaned_size -gt 0 ]]; then + # Temporarily stop spinner for clean output + local spinner_was_running=false + if [[ -t 1 && -n "${INLINE_SPINNER_PID:-}" ]]; then + stop_inline_spinner + spinner_was_running=true + fi + local cleaned_mb=$((cleaned_size / 1024)) if [[ "$DRY_RUN" != "true" ]]; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker cache (${cleaned_mb}MB cleaned, $protected_count protected)" + if [[ $protected_count -gt 0 ]]; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker (${cleaned_mb}MB, ${protected_count} protected)" + else + echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker (${cleaned_mb}MB)" + fi else - echo -e " ${YELLOW}→${NC} $browser_name Service Worker cache (would clean ${cleaned_mb}MB, $protected_count protected)" + echo -e " ${YELLOW}→${NC} $browser_name Service Worker (would clean ${cleaned_mb}MB, ${protected_count} protected)" fi note_activity + + # Restart spinner if it was running + if [[ "$spinner_was_running" == "true" ]]; then + MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning browser Service Worker caches..." + fi fi } diff --git a/lib/clean/maintenance.sh b/lib/clean/maintenance.sh index 794295d..24310bf 100644 --- a/lib/clean/maintenance.sh +++ b/lib/clean/maintenance.sh @@ -38,20 +38,18 @@ clean_broken_preferences() { esac # Validate plist using plutil - if ! plutil -lint "$plist_file" > /dev/null 2>&1; then - local size_kb - size_kb=$(get_path_size_kb "$plist_file") + plutil -lint "$plist_file" > /dev/null 2>&1 && continue - if [[ "$DRY_RUN" != "true" ]]; then - rm -f "$plist_file" 2> /dev/null || true - fi + local size_kb + size_kb=$(get_path_size_kb "$plist_file") - ((broken_count++)) - ((total_size_kb += size_kb)) - fi + [[ "$DRY_RUN" != "true" ]] && rm -f "$plist_file" 2> /dev/null || true + + ((broken_count++)) + ((total_size_kb += size_kb)) done < <(command find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true) - # Check ByHost preferences + # Check ByHost preferences with timeout protection local byhost_dir="$prefs_dir/ByHost" if [[ -d "$byhost_dir" ]]; then while IFS= read -r plist_file; do @@ -65,17 +63,15 @@ clean_broken_preferences() { ;; esac - if ! plutil -lint "$plist_file" > /dev/null 2>&1; then - local size_kb - size_kb=$(get_path_size_kb "$plist_file") + plutil -lint "$plist_file" > /dev/null 2>&1 && continue - if [[ "$DRY_RUN" != "true" ]]; then - rm -f "$plist_file" 2> /dev/null || true - fi + local size_kb + size_kb=$(get_path_size_kb "$plist_file") - ((broken_count++)) - ((total_size_kb += size_kb)) - fi + [[ "$DRY_RUN" != "true" ]] && rm -f "$plist_file" 2> /dev/null || true + + ((broken_count++)) + ((total_size_kb += size_kb)) done < <(command find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true) fi @@ -146,7 +142,6 @@ clean_broken_login_items() { size_kb=$(get_path_size_kb "$plist_file") if [[ "$DRY_RUN" != "true" ]]; then - # Unload first if loaded launchctl unload "$plist_file" 2> /dev/null || true rm -f "$plist_file" 2> /dev/null || true fi diff --git a/lib/clean/system.sh b/lib/clean/system.sh index b60a3ba..a38cd60 100644 --- a/lib/clean/system.sh +++ b/lib/clean/system.sh @@ -13,12 +13,12 @@ clean_deep_system() { # Clean old temp files local tmp_cleaned=0 - local tmp_count=$(sudo command find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') + local tmp_count=$(sudo find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') if [[ "$tmp_count" -gt 0 ]]; then safe_sudo_find_delete "/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true tmp_cleaned=1 fi - local var_tmp_count=$(sudo command find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') + local var_tmp_count=$(sudo find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') if [[ "$var_tmp_count" -gt 0 ]]; then safe_sudo_find_delete "/var/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true tmp_cleaned=1 @@ -42,7 +42,7 @@ clean_deep_system() { # These files are system-protected and cannot be removed : # No-op, silently skip else - # SIP is disabled, attempt cleanup with restricted flag check + # SIP is disabled, attempt cleanup with restricted flag check and timeout protection local updates_cleaned=0 while IFS= read -r -d '' item; do # Skip system-protected files (restricted flag) @@ -55,7 +55,7 @@ clean_deep_system() { if safe_sudo_remove "$item"; then ((updates_cleaned++)) fi - done < <(command find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null) + done < <(command find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true) [[ $updates_cleaned -gt 0 ]] && log_success "System library updates" fi fi @@ -124,33 +124,35 @@ clean_time_machine_failed_backups() { fi local size_kb=$(get_path_size_kb "$inprogress_file") - if [[ "$size_kb" -gt 0 ]]; then - local backup_name=$(basename "$inprogress_file") + [[ "$size_kb" -le 0 ]] && continue - if [[ "$DRY_RUN" != "true" ]]; then - if command -v tmutil > /dev/null 2>&1; then - if tmutil delete "$inprogress_file" 2> /dev/null; then - local size_human=$(bytes_to_human "$((size_kb * 1024))") - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Failed backup: $backup_name ${GREEN}($size_human)${NC}" - ((tm_cleaned++)) - ((files_cleaned++)) - ((total_size_cleaned += size_kb)) - ((total_items++)) - note_activity - else - echo -e " ${YELLOW}!${NC} Could not delete: $backup_name (try manually with sudo)" - fi - else - echo -e " ${YELLOW}!${NC} tmutil not available, skipping: $backup_name" - fi - else - local size_human=$(bytes_to_human "$((size_kb * 1024))") - echo -e " ${YELLOW}→${NC} Failed backup: $backup_name ${YELLOW}($size_human dry)${NC}" - ((tm_cleaned++)) - note_activity - fi + local backup_name=$(basename "$inprogress_file") + local size_human=$(bytes_to_human "$((size_kb * 1024))") + + if [[ "$DRY_RUN" == "true" ]]; then + echo -e " ${YELLOW}→${NC} Failed backup: $backup_name ${YELLOW}($size_human dry)${NC}" + ((tm_cleaned++)) + note_activity + continue fi - done < <(command find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) + + # Real deletion + if ! command -v tmutil > /dev/null 2>&1; then + echo -e " ${YELLOW}!${NC} tmutil not available, skipping: $backup_name" + continue + fi + + if tmutil delete "$inprogress_file" 2> /dev/null; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Failed backup: $backup_name ${GREEN}($size_human)${NC}" + ((tm_cleaned++)) + ((files_cleaned++)) + ((total_size_cleaned += size_kb)) + ((total_items++)) + note_activity + else + echo -e " ${YELLOW}!${NC} Could not delete: $backup_name (try manually with sudo)" + fi + done < <(run_with_timeout 15 find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) fi # APFS style backups (.backupbundle or .sparsebundle) @@ -176,31 +178,34 @@ clean_time_machine_failed_backups() { fi local size_kb=$(get_path_size_kb "$inprogress_file") - if [[ "$size_kb" -gt 0 ]]; then - local backup_name=$(basename "$inprogress_file") + [[ "$size_kb" -le 0 ]] && continue - if [[ "$DRY_RUN" != "true" ]]; then - if command -v tmutil > /dev/null 2>&1; then - if tmutil delete "$inprogress_file" 2> /dev/null; then - local size_human=$(bytes_to_human "$((size_kb * 1024))") - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Failed APFS backup in $bundle_name: $backup_name ${GREEN}($size_human)${NC}" - ((tm_cleaned++)) - ((files_cleaned++)) - ((total_size_cleaned += size_kb)) - ((total_items++)) - note_activity - else - echo -e " ${YELLOW}!${NC} Could not delete from bundle: $backup_name" - fi - fi - else - local size_human=$(bytes_to_human "$((size_kb * 1024))") - echo -e " ${YELLOW}→${NC} Failed APFS backup in $bundle_name: $backup_name ${YELLOW}($size_human dry)${NC}" - ((tm_cleaned++)) - note_activity - fi + local backup_name=$(basename "$inprogress_file") + local size_human=$(bytes_to_human "$((size_kb * 1024))") + + if [[ "$DRY_RUN" == "true" ]]; then + echo -e " ${YELLOW}→${NC} Failed APFS backup in $bundle_name: $backup_name ${YELLOW}($size_human dry)${NC}" + ((tm_cleaned++)) + note_activity + continue fi - done < <(command find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) + + # Real deletion + if ! command -v tmutil > /dev/null 2>&1; then + continue + fi + + if tmutil delete "$inprogress_file" 2> /dev/null; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Failed APFS backup in $bundle_name: $backup_name ${GREEN}($size_human)${NC}" + ((tm_cleaned++)) + ((files_cleaned++)) + ((total_size_cleaned += size_kb)) + ((total_items++)) + note_activity + else + echo -e " ${YELLOW}!${NC} Could not delete from bundle: $backup_name" + fi + done < <(run_with_timeout 15 find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) fi done done diff --git a/lib/clean/user.sh b/lib/clean/user.sh index 09e8a54..7f979db 100644 --- a/lib/clean/user.sh +++ b/lib/clean/user.sh @@ -21,14 +21,14 @@ clean_user_essentials() { esac # Verify volume is mounted and not a symlink - if mount | grep -q "on $volume " && [[ ! -L "$volume/.Trashes" ]]; then - if [[ "$DRY_RUN" != "true" ]]; then - # Safely iterate and remove each item - while IFS= read -r -d '' item; do - safe_remove "$item" true || true - done < <(command find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null) - fi - fi + mount | grep -q "on $volume " || continue + [[ -L "$volume/.Trashes" ]] && continue + [[ "$DRY_RUN" == "true" ]] && continue + + # Safely iterate and remove each item + while IFS= read -r -d '' item; do + safe_remove "$item" true || true + done < <(command find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null) done fi @@ -55,8 +55,7 @@ clean_user_essentials() { clean_finder_metadata() { if [[ "$PROTECT_FINDER_METADATA" == "true" ]]; then note_activity - echo -e " ${YELLOW}☻${NC} Finder metadata protected by whitelist" - echo -e " ${YELLOW}☻${NC} Run ${GRAY}mo clean --whitelist${NC} to allow cleaning .DS_Store files" + echo -e " ${GRAY}${ICON_SUCCESS}${NC} Finder metadata (whitelisted)" else clean_ds_store_tree "$HOME" "Home directory (.DS_Store)" @@ -99,26 +98,7 @@ clean_sandboxed_app_caches() { } # Clean browser caches (Safari, Chrome, Edge, Firefox, etc.) -# Warns if browsers are running (some cache files may be locked) clean_browsers() { - # Check for running browsers and warn user - local running_browsers="" - - # Check each browser with case-insensitive process name matching - pgrep -i "safari" > /dev/null 2>&1 && running_browsers="Safari" - pgrep -i "chrome" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Chrome" - pgrep -i "firefox" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Firefox" - pgrep -i "edge" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Edge" - pgrep -i "brave" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Brave" - pgrep -i "arc" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Arc" - pgrep -i "opera" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Opera" - pgrep -i "vivaldi" > /dev/null 2>&1 && running_browsers="${running_browsers:+$running_browsers, }Vivaldi" - - if [[ -n "$running_browsers" ]]; then - echo -e " ${YELLOW}${ICON_WARNING}${NC} Running: $running_browsers (some files may be locked)" - note_activity - fi - safe_clean ~/Library/Caches/com.apple.Safari/* "Safari cache" # Chrome/Chromium @@ -140,7 +120,12 @@ clean_browsers() { safe_clean ~/Library/Application\ Support/Firefox/Profiles/*/cache2/* "Firefox profile cache" # Service Worker CacheStorage (all profiles) - # Limit search depth to prevent hanging on large profile directories + # Show loading indicator for potentially slow scan + if [[ -t 1 ]]; then + MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning browser Service Worker caches..." + fi + + # Scan for Service Worker caches with timeout protection while IFS= read -r sw_path; do [[ -z "$sw_path" ]] && continue local profile_name=$(basename "$(dirname "$(dirname "$sw_path")")") @@ -150,11 +135,16 @@ clean_browsers() { [[ "$sw_path" == *"Arc"* ]] && browser_name="Arc" [[ "$profile_name" != "Default" ]] && browser_name="$browser_name ($profile_name)" clean_service_worker_cache "$browser_name" "$sw_path" - done < <(command find "$HOME/Library/Application Support/Google/Chrome" \ + done < <(run_with_timeout 10 find "$HOME/Library/Application Support/Google/Chrome" \ "$HOME/Library/Application Support/Microsoft Edge" \ "$HOME/Library/Application Support/BraveSoftware/Brave-Browser" \ "$HOME/Library/Application Support/Arc/User Data" \ -maxdepth 6 -type d -name "CacheStorage" -path "*/Service Worker/*" 2> /dev/null || true) + + # Stop spinner after scan completes + if [[ -t 1 ]]; then + stop_inline_spinner + fi } # Clean cloud storage app caches @@ -197,14 +187,21 @@ clean_application_support_logs() { return 0 fi + # Show loading indicator for this potentially slow operation + if [[ -t 1 ]]; then + MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning Application Support directories..." + fi + # Clean log directories and cache patterns with iteration limit + # Limit iterations to balance thoroughness and performance local iteration_count=0 - local max_iterations=200 + local max_iterations=100 + local cleaned_any=false for app_dir in ~/Library/Application\ Support/*; do [[ -d "$app_dir" ]] || continue - # Safety: limit iterations + # Safety: limit iterations to avoid excessive scanning ((iteration_count++)) if [[ $iteration_count -gt $max_iterations ]]; then break @@ -244,15 +241,15 @@ clean_application_support_logs() { safe_clean "$app_dir/Crashpad/completed"/* "Crash reports: $app_name" fi - # Clean Service Worker caches (CacheStorage and ScriptCache) + # Clean Service Worker caches (CacheStorage and ScriptCache) with timeout protection while IFS= read -r -d '' sw_cache; do local profile_path=$(dirname "$(dirname "$sw_cache")") local profile_name=$(basename "$profile_path") [[ "$profile_name" == "User Data" ]] && profile_name=$(basename "$(dirname "$profile_path")") clean_service_worker_cache "$app_name ($profile_name)" "$sw_cache" - done < <(command find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" 2> /dev/null || true) + done < <(find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" -print0 2> /dev/null || true) - # Clean stale update downloads (older than 7 days) + # Clean stale update downloads (older than 7 days) with timeout protection if [[ -d "$app_dir/update" ]] && ls "$app_dir/update" > /dev/null 2>&1; then while IFS= read -r update_dir; do local dir_age_days=$((($(date +%s) - $(get_file_mtime "$update_dir")) / 86400)) @@ -263,19 +260,24 @@ clean_application_support_logs() { fi done - # Clean Group Containers logs + # Clean Group Containers logs with timeout protection if [[ -d "$HOME/Library/Group Containers" ]]; then while IFS= read -r logs_dir; do local container_name=$(basename "$(dirname "$logs_dir")") safe_clean "$logs_dir"/* "Group container logs: $container_name" done < <(command find "$HOME/Library/Group Containers" -maxdepth 2 -type d -name "Logs" 2> /dev/null || true) fi + + # Stop loading indicator + if [[ -t 1 ]]; then + stop_inline_spinner + fi } # Check and show iOS device backup info check_ios_device_backups() { local backup_dir="$HOME/Library/Application Support/MobileSync/Backup" - if [[ -d "$backup_dir" ]] && command find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then + if [[ -d "$backup_dir" ]] && command find "$backup_dir" -mindepth 1 -maxdepth 1 2> /dev/null | read -r _; then local backup_kb=$(get_path_size_kb "$backup_dir") if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then local backup_human=$(command du -sh "$backup_dir" 2> /dev/null | awk '{print $1}') diff --git a/lib/core/common.sh b/lib/core/common.sh index 927f1c7..359e522 100755 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -1201,48 +1201,36 @@ force_kill_app() { # Use executable name for precise matching, fallback to app name local match_pattern="${exec_name:-$app_name}" - # Check if main process is running using exact match - local has_main_process=false - if pgrep -x "$match_pattern" > /dev/null 2>&1; then - has_main_process=true - fi - - # Also check for related processes using fuzzy match - local has_related_processes=false - if pgrep -i "$match_pattern" > /dev/null 2>&1; then - has_related_processes=true - fi - - # If nothing is running, return success - if [[ "$has_main_process" == false && "$has_related_processes" == false ]]; then + # Check if process is running using exact match only + if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi - # Try graceful termination first for exact match - if [[ "$has_main_process" == true ]]; then - pkill -x "$match_pattern" 2> /dev/null || true - fi - - # Also try graceful termination for related processes - if [[ "$has_related_processes" == true ]]; then - pkill -i "$match_pattern" 2> /dev/null || true - fi - + # Try graceful termination first + pkill -x "$match_pattern" 2> /dev/null || true sleep 2 # Check again after graceful kill - if ! pgrep -i "$match_pattern" > /dev/null 2>&1; then + if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi # Force kill if still running - pkill -9 -i "$match_pattern" 2> /dev/null || true + pkill -9 -x "$match_pattern" 2> /dev/null || true sleep 2 + # If still running and sudo is available, try with sudo + if pgrep -x "$match_pattern" > /dev/null 2>&1; then + if sudo -n true 2> /dev/null; then + sudo pkill -9 -x "$match_pattern" 2> /dev/null || true + sleep 2 + fi + fi + # Final check with longer timeout for stubborn processes local retries=3 while [[ $retries -gt 0 ]]; do - if ! pgrep -i "$match_pattern" > /dev/null 2>&1; then + if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi sleep 1 @@ -1250,7 +1238,7 @@ force_kill_app() { done # Still running after all attempts - pgrep -i "$match_pattern" > /dev/null 2>&1 && return 1 || return 0 + pgrep -x "$match_pattern" > /dev/null 2>&1 && return 1 || return 0 } # Remove application icons from the Dock (best effort) diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index 5c93328..368d530 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -115,7 +115,8 @@ batch_uninstall_applications() { local -a app_details=() local -a dock_cleanup_paths=() - # Silent analysis without spinner output (avoid visual flicker) + # Analyze selected apps with progress indicator + if [[ -t 1 ]]; then start_inline_spinner "Scanning files..."; fi for selected_app in "${selected_apps[@]}"; do [[ -z "$selected_app" ]] && continue IFS='|' read -r _ app_path app_name bundle_id _ _ <<< "$selected_app" @@ -161,6 +162,7 @@ batch_uninstall_applications() { encoded_system_files=$(printf '%s' "$system_files" | base64 | tr -d '\n') app_details+=("$app_name|$app_path|$bundle_id|$total_kb|$encoded_files|$encoded_system_files") done + if [[ -t 1 ]]; then stop_inline_spinner; fi # Format size display (convert KB to bytes for bytes_to_human()) local size_display=$(bytes_to_human "$((total_estimated_size * 1024))") @@ -292,7 +294,7 @@ batch_uninstall_applications() { reason="still running" fi - # Remove the application + # Remove the application only if not running if [[ -z "$reason" ]]; then if [[ "$needs_sudo" == true ]]; then safe_sudo_remove "$app_path" || reason="remove failed" @@ -301,7 +303,7 @@ batch_uninstall_applications() { fi fi - # Remove user-level and system-level files + # Remove related files if app removal succeeded if [[ -z "$reason" ]]; then # Remove user-level files remove_file_list "$related_files" "false" > /dev/null diff --git a/mole b/mole index 0277fa8..ba9ebd5 100755 --- a/mole +++ b/mole @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/core/common.sh" # Version info -VERSION="1.11.23" +VERSION="1.11.24" MOLE_TAGLINE="can dig deep to clean your Mac." # Check if Touch ID is already configured