mirror of
https://github.com/tw93/Mole.git
synced 2026-02-05 15:33:51 +00:00
Greatly improve scanning speed
This commit is contained in:
@@ -19,7 +19,7 @@ source "$LIB_DIR/common.sh"
|
||||
# Constants
|
||||
readonly CACHE_DIR="${HOME}/.config/mole/cache"
|
||||
readonly TEMP_PREFIX="/tmp/mole_analyze_$$"
|
||||
readonly MIN_LARGE_FILE_SIZE="1000000000" # 1GB
|
||||
readonly MIN_LARGE_FILE_SIZE="500000000" # 500MB (more sensitive)
|
||||
readonly MIN_MEDIUM_FILE_SIZE="100000000" # 100MB
|
||||
|
||||
# Emoji badges for list displays only
|
||||
@@ -105,9 +105,9 @@ scan_directories() {
|
||||
|
||||
# Check if we can use parallel processing
|
||||
if command -v xargs &> /dev/null && [[ $depth -eq 1 ]]; then
|
||||
# Fast parallel scan for depth 1
|
||||
# Fast parallel scan for depth 1 (increased parallelism)
|
||||
find "$target_path" -mindepth 1 -maxdepth 1 -type d -print0 2> /dev/null |
|
||||
xargs -0 -P 4 -I {} du -sk {} 2> /dev/null |
|
||||
xargs -0 -P 8 -I {} du -sk {} 2> /dev/null |
|
||||
sort -rn |
|
||||
while IFS=$'\t' read -r size path; do
|
||||
echo "$((size * 1024))|$path"
|
||||
@@ -1375,9 +1375,12 @@ scan_directory_contents_fast() {
|
||||
# Auto-detect optimal parallel jobs using common function
|
||||
local num_jobs
|
||||
num_jobs=$(get_optimal_parallel_jobs "io")
|
||||
# Cap at reasonable limits for I/O operations
|
||||
[[ $num_jobs -gt 24 ]] && num_jobs=24
|
||||
[[ $num_jobs -lt 12 ]] && num_jobs=12
|
||||
# Keep within practical limits to avoid IO thrashing on small machines
|
||||
if [[ $num_jobs -gt 32 ]]; then
|
||||
num_jobs=32
|
||||
elif [[ $num_jobs -lt 4 ]]; then
|
||||
num_jobs=4
|
||||
fi
|
||||
|
||||
local temp_dirs="$output_file.dirs"
|
||||
local temp_files="$output_file.files"
|
||||
@@ -1448,7 +1451,7 @@ scan_directory_contents_fast() {
|
||||
fi
|
||||
[[ ${#spinner[@]} -eq 0 ]] && spinner=('|' '/' '-' '\\')
|
||||
local i=0
|
||||
local max_wait=30 # Reduced to 30 seconds (fast fail)
|
||||
local max_wait=45 # Balanced timeout (fast but not too aggressive)
|
||||
local elapsed=0
|
||||
local tick=0
|
||||
local spin_len=${#spinner[@]}
|
||||
@@ -1563,12 +1566,19 @@ show_volumes_overview() {
|
||||
[[ -d "$HOME/Library" ]] && echo "700|$HOME/Library|User Library"
|
||||
[[ -d "/Library" ]] && echo "600|/Library|System Library"
|
||||
|
||||
# External volumes (if any)
|
||||
# External volumes (filter obvious system mounts)
|
||||
if [[ -d "/Volumes" ]]; then
|
||||
local vol_priority=500
|
||||
find /Volumes -mindepth 1 -maxdepth 1 -type d 2> /dev/null | while IFS= read -r vol; do
|
||||
local vol_name
|
||||
vol_name=$(basename "$vol")
|
||||
|
||||
# Skip internal/system volumes and dmg helper mounts, but keep user disks
|
||||
case "$vol_name" in
|
||||
"MacintoshHD" | "Macintosh HD" | "Macintosh HD - Data") continue ;;
|
||||
dmg.* | *.dmg) continue ;;
|
||||
esac
|
||||
|
||||
echo "$((vol_priority))|$vol|Volume: $vol_name"
|
||||
((vol_priority--))
|
||||
done
|
||||
|
||||
@@ -60,6 +60,44 @@ get_app_last_used() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Compact the "last used" descriptor for aligned summaries
|
||||
format_last_used_summary() {
|
||||
local value="$1"
|
||||
|
||||
case "$value" in
|
||||
"" | "Unknown")
|
||||
echo "Unknown"
|
||||
return 0
|
||||
;;
|
||||
"Never" | "Recent" | "Today" | "Yesterday" | "This year" | "Old")
|
||||
echo "$value"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $value =~ ^([0-9]+)[[:space:]]+days?\ ago$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}d ago"
|
||||
return 0
|
||||
fi
|
||||
if [[ $value =~ ^([0-9]+)[[:space:]]+weeks?\ ago$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}w ago"
|
||||
return 0
|
||||
fi
|
||||
if [[ $value =~ ^([0-9]+)[[:space:]]+months?\ ago$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}m ago"
|
||||
return 0
|
||||
fi
|
||||
if [[ $value =~ ^([0-9]+)[[:space:]]+month\(s\)\ ago$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}m ago"
|
||||
return 0
|
||||
fi
|
||||
if [[ $value =~ ^([0-9]+)[[:space:]]+years?\ ago$ ]]; then
|
||||
echo "${BASH_REMATCH[1]}y ago"
|
||||
return 0
|
||||
fi
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
# Scan applications and collect information
|
||||
scan_applications() {
|
||||
# Cache configuration
|
||||
@@ -195,7 +233,14 @@ scan_applications() {
|
||||
# Second pass: process each app with parallel size calculation
|
||||
local app_count=0
|
||||
local total_apps=${#app_data_tuples[@]}
|
||||
local max_parallel=10 # Process 10 apps in parallel
|
||||
# Bound parallelism so small machines stay responsive
|
||||
local max_parallel
|
||||
max_parallel=$(get_optimal_parallel_jobs "io")
|
||||
if [[ $max_parallel -lt 4 ]]; then
|
||||
max_parallel=4
|
||||
elif [[ $max_parallel -gt 16 ]]; then
|
||||
max_parallel=16
|
||||
fi
|
||||
local pids=()
|
||||
local inline_loading=false
|
||||
if [[ "${MOLE_INLINE_LOADING:-}" == "1" || "${MOLE_INLINE_LOADING:-}" == "true" ]]; then
|
||||
@@ -215,9 +260,9 @@ scan_applications() {
|
||||
local app_size="N/A"
|
||||
local app_size_kb="0"
|
||||
if [[ -d "$app_path" ]]; then
|
||||
# numeric size (KB) for sorting + human-readable for display
|
||||
# Get size in KB, then format for display (single du call)
|
||||
app_size_kb=$(du -sk "$app_path" 2> /dev/null | awk '{print $1}' || echo "0")
|
||||
app_size=$(du -sh "$app_path" 2> /dev/null | cut -f1 || echo "N/A")
|
||||
app_size=$(bytes_to_human "$((app_size_kb * 1024))")
|
||||
fi
|
||||
|
||||
# Get real last used date from macOS metadata
|
||||
@@ -633,26 +678,43 @@ main() {
|
||||
rm -f "$apps_file"
|
||||
return 0
|
||||
fi
|
||||
# Show selected apps, max 3 per line
|
||||
# Show selected apps with clean alignment
|
||||
echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} app(s):"
|
||||
local idx=0
|
||||
local line=""
|
||||
local -a summary_rows=()
|
||||
local max_name_width=0
|
||||
local max_size_width=0
|
||||
local name_trunc_limit=30
|
||||
|
||||
for selected_app in "${selected_apps[@]}"; do
|
||||
IFS='|' read -r epoch app_path app_name bundle_id size last_used <<< "$selected_app"
|
||||
local display_item="${app_name}(${size})"
|
||||
|
||||
if ((idx % 3 == 0)); then
|
||||
# Start new line
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
line="$display_item"
|
||||
else
|
||||
# Add to current line
|
||||
line="$line, $display_item"
|
||||
local display_name="$app_name"
|
||||
if [[ ${#display_name} -gt $name_trunc_limit ]]; then
|
||||
display_name="${display_name:0:$((name_trunc_limit - 3))}..."
|
||||
fi
|
||||
((idx++))
|
||||
[[ ${#display_name} -gt $max_name_width ]] && max_name_width=${#display_name}
|
||||
|
||||
local size_display="$size"
|
||||
if [[ -z "$size_display" || "$size_display" == "0" || "$size_display" == "N/A" ]]; then
|
||||
size_display="Unknown"
|
||||
fi
|
||||
[[ ${#size_display} -gt $max_size_width ]] && max_size_width=${#size_display}
|
||||
|
||||
local last_display
|
||||
last_display=$(format_last_used_summary "$last_used")
|
||||
|
||||
summary_rows+=("$display_name|$size_display|$last_display")
|
||||
done
|
||||
|
||||
((max_name_width < 16)) && max_name_width=16
|
||||
((max_size_width < 5)) && max_size_width=5
|
||||
|
||||
local index=1
|
||||
for row in "${summary_rows[@]}"; do
|
||||
IFS='|' read -r name_cell size_cell last_cell <<< "$row"
|
||||
printf " %2d. %-*s %*s | Last: %s\n" "$index" "$max_name_width" "$name_cell" "$max_size_width" "$size_cell" "$last_cell"
|
||||
((index++))
|
||||
done
|
||||
# Print the last line
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
echo ""
|
||||
|
||||
# Execute batch uninstallation (handles confirmation)
|
||||
|
||||
@@ -217,7 +217,7 @@ read_key() {
|
||||
case "$key" in
|
||||
$'\n' | $'\r') echo "ENTER" ;;
|
||||
' ') echo "SPACE" ;;
|
||||
'Q') echo "QUIT" ;;
|
||||
'q' | 'Q') echo "QUIT" ;;
|
||||
'R') echo "RETRY" ;;
|
||||
'o' | 'O') echo "OPEN" ;;
|
||||
'/') echo "FILTER" ;; # Trigger filter mode
|
||||
@@ -1425,13 +1425,27 @@ readonly DATA_PROTECTED_BUNDLES=(
|
||||
# Legacy function - preserved for backward compatibility
|
||||
# Use should_protect_from_uninstall() or should_protect_data() instead
|
||||
readonly PRESERVED_BUNDLE_PATTERNS=("${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}")
|
||||
|
||||
# Check whether a bundle ID matches a pattern (supports globs)
|
||||
bundle_matches_pattern() {
|
||||
local bundle_id="$1"
|
||||
local pattern="$2"
|
||||
|
||||
[[ -z "$pattern" ]] && return 1
|
||||
|
||||
# shellcheck disable=SC2254 # allow glob pattern matching for bundle rules
|
||||
case "$bundle_id" in
|
||||
$pattern) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
should_preserve_bundle() {
|
||||
local bundle_id="$1"
|
||||
for pattern in "${PRESERVED_BUNDLE_PATTERNS[@]}"; do
|
||||
# Use case for safer glob matching
|
||||
case "$bundle_id" in
|
||||
"$pattern") return 0 ;;
|
||||
esac
|
||||
if bundle_matches_pattern "$bundle_id" "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
@@ -1440,10 +1454,9 @@ should_preserve_bundle() {
|
||||
should_protect_from_uninstall() {
|
||||
local bundle_id="$1"
|
||||
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
|
||||
# Use case for safer glob matching
|
||||
case "$bundle_id" in
|
||||
"$pattern") return 0 ;;
|
||||
esac
|
||||
if bundle_matches_pattern "$bundle_id" "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
@@ -1453,10 +1466,9 @@ should_protect_data() {
|
||||
local bundle_id="$1"
|
||||
# Protect both system critical and data protected bundles during cleanup
|
||||
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do
|
||||
# Use case for safer glob matching
|
||||
case "$bundle_id" in
|
||||
"$pattern") return 0 ;;
|
||||
esac
|
||||
if bundle_matches_pattern "$bundle_id" "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
@@ -1598,6 +1610,36 @@ find_app_files() {
|
||||
files_to_clean+=("$framework")
|
||||
done < <(find ~/Library/PrivateFrameworks \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Audio Plug-Ins
|
||||
while IFS= read -r -d '' plugin; do
|
||||
files_to_clean+=("$plugin")
|
||||
done < <(find ~/Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Components
|
||||
while IFS= read -r -d '' component; do
|
||||
files_to_clean+=("$component")
|
||||
done < <(find ~/Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Metadata
|
||||
while IFS= read -r -d '' metadata; do
|
||||
files_to_clean+=("$metadata")
|
||||
done < <(find ~/Library/Metadata \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Workflows
|
||||
[[ -d ~/Library/Workflows/"$app_name".workflow ]] && files_to_clean+=("$HOME/Library/Workflows/$app_name.workflow")
|
||||
while IFS= read -r -d '' workflow; do
|
||||
files_to_clean+=("$workflow")
|
||||
done < <(find ~/Library/Workflows \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Favorites (excluding Safari)
|
||||
while IFS= read -r -d '' favorite; do
|
||||
# Skip Safari favorites
|
||||
case "$favorite" in
|
||||
*Safari*) continue ;;
|
||||
esac
|
||||
files_to_clean+=("$favorite")
|
||||
done < <(find ~/Library/Favorites \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Only print if array has elements to avoid unbound variable error
|
||||
if [[ ${#files_to_clean[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${files_to_clean[@]}"
|
||||
@@ -1704,6 +1746,21 @@ find_app_system_files() {
|
||||
[[ -d /Library/Caches/"$bundle_id" ]] && system_files+=("/Library/Caches/$bundle_id")
|
||||
[[ -d /Library/Caches/"$app_name" ]] && system_files+=("/Library/Caches/$app_name")
|
||||
|
||||
# System Audio Plug-Ins
|
||||
while IFS= read -r -d '' plugin; do
|
||||
system_files+=("$plugin")
|
||||
done < <(find /Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# System Components
|
||||
while IFS= read -r -d '' component; do
|
||||
system_files+=("$component")
|
||||
done < <(find /Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# System Extensions
|
||||
while IFS= read -r -d '' extension; do
|
||||
system_files+=("$extension")
|
||||
done < <(find /Library/Extensions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
# Only print if array has elements
|
||||
if [[ ${#system_files[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${system_files[@]}"
|
||||
|
||||
@@ -7,17 +7,41 @@ set -euo pipefail
|
||||
format_app_display() {
|
||||
local display_name="$1" size="$2" last_used="$3"
|
||||
|
||||
# Truncate long names
|
||||
# Compact last-used wording to keep column width tidy
|
||||
local compact_last_used
|
||||
case "$last_used" in
|
||||
"" | "Unknown") compact_last_used="Unknown" ;;
|
||||
"Never" | "Recent" | "Today" | "Yesterday" | "This year" | "Old") compact_last_used="$last_used" ;;
|
||||
*)
|
||||
if [[ $last_used =~ ^([0-9]+)[[:space:]]+days?\ ago$ ]]; then
|
||||
compact_last_used="${BASH_REMATCH[1]}d ago"
|
||||
elif [[ $last_used =~ ^([0-9]+)[[:space:]]+weeks?\ ago$ ]]; then
|
||||
compact_last_used="${BASH_REMATCH[1]}w ago"
|
||||
elif [[ $last_used =~ ^([0-9]+)[[:space:]]+months?\ ago$ ]]; then
|
||||
compact_last_used="${BASH_REMATCH[1]}m ago"
|
||||
elif [[ $last_used =~ ^([0-9]+)[[:space:]]+month\(s\)\ ago$ ]]; then
|
||||
compact_last_used="${BASH_REMATCH[1]}m ago"
|
||||
elif [[ $last_used =~ ^([0-9]+)[[:space:]]+years?\ ago$ ]]; then
|
||||
compact_last_used="${BASH_REMATCH[1]}y ago"
|
||||
else
|
||||
compact_last_used="$last_used"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Truncate long names with consistent width
|
||||
local truncated_name="$display_name"
|
||||
if [[ ${#display_name} -gt 24 ]]; then
|
||||
truncated_name="${display_name:0:21}..."
|
||||
if [[ ${#display_name} -gt 22 ]]; then
|
||||
truncated_name="${display_name:0:19}..."
|
||||
fi
|
||||
|
||||
# Format size
|
||||
local size_str="Unknown"
|
||||
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size"
|
||||
|
||||
printf "%-24s (%s) | %s" "$truncated_name" "$size_str" "$last_used"
|
||||
# Use consistent column widths for perfect alignment:
|
||||
# name column (22), right-aligned size column (9), then compact last-used value.
|
||||
printf "%-22s %9s | %s" "$truncated_name" "$size_str" "$compact_last_used"
|
||||
}
|
||||
|
||||
# Global variable to store selection result (bash 3.2 compatible)
|
||||
|
||||
Reference in New Issue
Block a user