mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 09:46:44 +00:00
feat: add spinner feedback to optimization tasks, enhance app discovery for uninstall, and improve UI robustness and signal handling
This commit is contained in:
@@ -16,13 +16,20 @@ source "$SCRIPT_DIR/lib/manage/autofix.sh"
|
||||
source "$SCRIPT_DIR/lib/check/all.sh"
|
||||
|
||||
cleanup_all() {
|
||||
stop_inline_spinner 2> /dev/null || true
|
||||
stop_sudo_session
|
||||
cleanup_temp_files
|
||||
}
|
||||
|
||||
handle_interrupt() {
|
||||
cleanup_all
|
||||
exit 130
|
||||
}
|
||||
|
||||
main() {
|
||||
# Register unified cleanup handler
|
||||
trap cleanup_all EXIT INT TERM
|
||||
trap cleanup_all EXIT
|
||||
trap handle_interrupt INT TERM
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
clear
|
||||
|
||||
@@ -319,10 +319,16 @@ perform_security_fixes() {
|
||||
}
|
||||
|
||||
cleanup_all() {
|
||||
stop_inline_spinner 2> /dev/null || true
|
||||
stop_sudo_session
|
||||
cleanup_temp_files
|
||||
}
|
||||
|
||||
handle_interrupt() {
|
||||
cleanup_all
|
||||
exit 130
|
||||
}
|
||||
|
||||
main() {
|
||||
local health_json
|
||||
for arg in "$@"; do
|
||||
@@ -340,7 +346,8 @@ main() {
|
||||
esac
|
||||
done
|
||||
|
||||
trap cleanup_all EXIT INT TERM
|
||||
trap cleanup_all EXIT
|
||||
trap handle_interrupt INT TERM
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
clear
|
||||
|
||||
@@ -77,6 +77,23 @@ scan_applications() {
|
||||
"/Applications"
|
||||
"$HOME/Applications"
|
||||
)
|
||||
local vol_app_dir
|
||||
local nullglob_was_set=0
|
||||
shopt -q nullglob && nullglob_was_set=1
|
||||
shopt -s nullglob
|
||||
for vol_app_dir in /Volumes/*/Applications; do
|
||||
[[ -d "$vol_app_dir" && -r "$vol_app_dir" ]] || continue
|
||||
if [[ -d "/Applications" && "$vol_app_dir" -ef "/Applications" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -d "$HOME/Applications" && "$vol_app_dir" -ef "$HOME/Applications" ]]; then
|
||||
continue
|
||||
fi
|
||||
app_dirs+=("$vol_app_dir")
|
||||
done
|
||||
if [[ $nullglob_was_set -eq 0 ]]; then
|
||||
shopt -u nullglob
|
||||
fi
|
||||
|
||||
for app_dir in "${app_dirs[@]}"; do
|
||||
if [[ ! -d "$app_dir" ]]; then continue; fi
|
||||
|
||||
@@ -111,40 +111,61 @@ scan_applications() {
|
||||
|
||||
# First pass: quickly collect all valid app paths and bundle IDs (NO mdls calls)
|
||||
local -a app_data_tuples=()
|
||||
while IFS= read -r -d '' app_path; do
|
||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||
|
||||
local app_name
|
||||
app_name=$(basename "$app_path" .app)
|
||||
|
||||
# Skip nested apps (e.g. inside Wrapper/ or Frameworks/ of another app)
|
||||
# Check if parent path component ends in .app (e.g. /Foo.app/Bar.app or /Foo.app/Contents/Bar.app)
|
||||
# This prevents false positives like /Old.apps/Target.app
|
||||
local parent_dir
|
||||
parent_dir=$(dirname "$app_path")
|
||||
if [[ "$parent_dir" == *".app" || "$parent_dir" == *".app/"* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get bundle ID only (fast, no mdls calls in first pass)
|
||||
local bundle_id="unknown"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "unknown")
|
||||
fi
|
||||
|
||||
# Skip system critical apps (input methods, system components)
|
||||
if should_protect_from_uninstall "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Store tuple: app_path|app_name|bundle_id (display_name will be resolved in parallel later)
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}")
|
||||
done < <(
|
||||
# Scan both system and user application directories
|
||||
# Using maxdepth 3 to find apps in subdirectories (e.g., Adobe apps in /Applications/Adobe X/)
|
||||
command find /Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
|
||||
command find ~/Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
|
||||
local -a app_dirs=(
|
||||
"/Applications"
|
||||
"$HOME/Applications"
|
||||
)
|
||||
local vol_app_dir
|
||||
local nullglob_was_set=0
|
||||
shopt -q nullglob && nullglob_was_set=1
|
||||
shopt -s nullglob
|
||||
for vol_app_dir in /Volumes/*/Applications; do
|
||||
[[ -d "$vol_app_dir" && -r "$vol_app_dir" ]] || continue
|
||||
if [[ -d "/Applications" && "$vol_app_dir" -ef "/Applications" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -d "$HOME/Applications" && "$vol_app_dir" -ef "$HOME/Applications" ]]; then
|
||||
continue
|
||||
fi
|
||||
app_dirs+=("$vol_app_dir")
|
||||
done
|
||||
if [[ $nullglob_was_set -eq 0 ]]; then
|
||||
shopt -u nullglob
|
||||
fi
|
||||
|
||||
for app_dir in "${app_dirs[@]}"; do
|
||||
if [[ ! -d "$app_dir" ]]; then continue; fi
|
||||
|
||||
while IFS= read -r -d '' app_path; do
|
||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||
|
||||
local app_name
|
||||
app_name=$(basename "$app_path" .app)
|
||||
|
||||
# Skip nested apps (e.g. inside Wrapper/ or Frameworks/ of another app)
|
||||
# Check if parent path component ends in .app (e.g. /Foo.app/Bar.app or /Foo.app/Contents/Bar.app)
|
||||
# This prevents false positives like /Old.apps/Target.app
|
||||
local parent_dir
|
||||
parent_dir=$(dirname "$app_path")
|
||||
if [[ "$parent_dir" == *".app" || "$parent_dir" == *".app/"* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get bundle ID only (fast, no mdls calls in first pass)
|
||||
local bundle_id="unknown"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "unknown")
|
||||
fi
|
||||
|
||||
# Skip system critical apps (input methods, system components)
|
||||
if should_protect_from_uninstall "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Store tuple: app_path|app_name|bundle_id (display_name will be resolved in parallel later)
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}")
|
||||
done < <(command find "$app_dir" -name "*.app" -maxdepth 3 -print0 2> /dev/null)
|
||||
done
|
||||
|
||||
# Second pass: process each app with parallel size calculation
|
||||
local app_count=0
|
||||
|
||||
@@ -174,8 +174,18 @@ opt_saved_state_cleanup() {
|
||||
# Removed: opt_local_snapshots - Deletes user Time Machine recovery points, breaks backup continuity
|
||||
|
||||
opt_fix_broken_configs() {
|
||||
local spinner_started="false"
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking preferences..."
|
||||
spinner_started="true"
|
||||
fi
|
||||
|
||||
local broken_prefs=$(fix_broken_preferences)
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if [[ $broken_prefs -gt 0 ]]; then
|
||||
opt_msg "Repaired $broken_prefs corrupted preference files"
|
||||
else
|
||||
@@ -324,7 +334,7 @@ opt_sqlite_vacuum() {
|
||||
fi
|
||||
|
||||
if [[ $skipped -gt 0 ]]; then
|
||||
echo -e " ${GRAY}Already optimal for $skipped databases (size or integrity limits)${NC}"
|
||||
opt_msg "Already optimal for $skipped databases"
|
||||
fi
|
||||
|
||||
if [[ $timed_out -gt 0 ]]; then
|
||||
@@ -520,8 +530,17 @@ opt_disk_permissions_repair() {
|
||||
# 1. Checks if default audio output is Bluetooth (precise)
|
||||
# 2. Falls back to Bluetooth + media app detection (compatibility)
|
||||
opt_bluetooth_reset() {
|
||||
local spinner_started="false"
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking Bluetooth..."
|
||||
spinner_started="true"
|
||||
fi
|
||||
|
||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||
if has_bluetooth_hid_connected; then
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
opt_msg "Bluetooth already optimal"
|
||||
return 0
|
||||
fi
|
||||
@@ -557,6 +576,9 @@ opt_bluetooth_reset() {
|
||||
fi
|
||||
|
||||
if [[ "$bt_audio_active" == "true" ]]; then
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
opt_msg "Bluetooth already optimal"
|
||||
return 0
|
||||
fi
|
||||
@@ -567,12 +589,21 @@ opt_bluetooth_reset() {
|
||||
if pgrep -x bluetoothd > /dev/null 2>&1; then
|
||||
sudo pkill -KILL bluetoothd > /dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
opt_msg "Bluetooth module restarted"
|
||||
opt_msg "Connectivity issues resolved"
|
||||
else
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
opt_msg "Bluetooth already optimal"
|
||||
fi
|
||||
else
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
opt_msg "Bluetooth module restarted"
|
||||
opt_msg "Connectivity issues resolved"
|
||||
fi
|
||||
|
||||
@@ -235,11 +235,13 @@ paginated_multi_select() {
|
||||
|
||||
local cols="${COLUMNS:-}"
|
||||
[[ -z "$cols" ]] && cols=$(tput cols 2> /dev/null || echo 80)
|
||||
[[ "$cols" =~ ^[0-9]+$ ]] || cols=80
|
||||
|
||||
_strip_ansi_len() {
|
||||
local text="$1"
|
||||
local stripped
|
||||
stripped=$(printf "%s" "$text" | LC_ALL=C awk '{gsub(/\033\[[0-9;]*[A-Za-z]/,""); print}')
|
||||
stripped=$(printf "%s" "$text" | LC_ALL=C awk '{gsub(/\033\[[0-9;]*[A-Za-z]/,""); print}' || true)
|
||||
[[ -z "$stripped" ]] && stripped="$text"
|
||||
printf "%d" "${#stripped}"
|
||||
}
|
||||
|
||||
@@ -251,7 +253,10 @@ paginated_multi_select() {
|
||||
else
|
||||
candidate="$line${sep}${s}"
|
||||
fi
|
||||
if (($(_strip_ansi_len "$candidate") > cols)); then
|
||||
local candidate_len
|
||||
candidate_len=$(_strip_ansi_len "$candidate")
|
||||
[[ -z "$candidate_len" ]] && candidate_len=0
|
||||
if ((candidate_len > cols)); then
|
||||
printf "%s%s\n" "$clear_line" "$line" >&2
|
||||
line="$s"
|
||||
else
|
||||
@@ -526,6 +531,7 @@ paginated_multi_select() {
|
||||
# Normal: show full controls with dynamic reduction
|
||||
local term_width="${COLUMNS:-}"
|
||||
[[ -z "$term_width" ]] && term_width=$(tput cols 2> /dev/null || echo 80)
|
||||
[[ "$term_width" =~ ^[0-9]+$ ]] || term_width=80
|
||||
|
||||
# Level 0: Full controls
|
||||
local -a _segs=("$nav" "$space_select" "$enter" "$refresh" "$search" "$sort_ctrl" "$order_ctrl" "$exit")
|
||||
|
||||
Reference in New Issue
Block a user