mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 16:49:41 +00:00
Neat and uniform output
This commit is contained in:
@@ -57,23 +57,32 @@ batch_uninstall_applications() {
|
||||
# Format size display (convert KB to bytes for bytes_to_human())
|
||||
local size_display=$(bytes_to_human "$((total_estimated_size * 1024))")
|
||||
|
||||
# Request sudo access if needed (do this before confirmation)
|
||||
# Show summary and get batch confirmation first (before asking for password)
|
||||
local app_total=${#selected_apps[@]}
|
||||
local app_text="app"
|
||||
[[ $app_total -gt 1 ]] && app_text="apps"
|
||||
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
||||
echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Force quit: ${running_apps[*]} | Enter=go / ESC=q: "
|
||||
else
|
||||
echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Enter=go / ESC=q: "
|
||||
fi
|
||||
IFS= read -r -s -n1 key || key=""
|
||||
case "$key" in
|
||||
$'\e'|q|Q) echo ""; return 0 ;;
|
||||
""|$'\n'|$'\r'|y|Y) echo "" ;;
|
||||
*) echo ""; return 0 ;;
|
||||
esac
|
||||
|
||||
# User confirmed, now request sudo access if needed
|
||||
if [[ ${#sudo_apps[@]} -gt 0 ]]; then
|
||||
# Check if sudo is already cached
|
||||
if sudo -n true 2>/dev/null; then
|
||||
echo "◎ Admin access confirmed for: ${sudo_apps[*]}"
|
||||
else
|
||||
echo "◎ Admin required for: ${sudo_apps[*]}"
|
||||
echo ""
|
||||
if ! request_sudo_access "Uninstalling system apps requires admin access"; then
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
if ! request_sudo_access "Admin required for system apps: ${sudo_apps[*]}"; then
|
||||
echo ""
|
||||
log_error "Admin access denied"
|
||||
return 1
|
||||
fi
|
||||
echo ""
|
||||
echo "✓ Admin access granted"
|
||||
fi
|
||||
echo "◎ Gathering targets..."
|
||||
(while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null) &
|
||||
local sudo_keepalive_pid=$!
|
||||
local _trap_cleanup_cmd="kill $sudo_keepalive_pid 2>/dev/null || true; wait $sudo_keepalive_pid 2>/dev/null || true"
|
||||
@@ -87,22 +96,7 @@ batch_uninstall_applications() {
|
||||
done
|
||||
fi
|
||||
|
||||
# Show summary and get batch confirmation
|
||||
local app_total=${#selected_apps[@]}
|
||||
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
||||
echo -n "${BLUE}◎ Remove ${app_total} app(s) (${size_display}) | Quit: ${running_apps[*]} | Enter=go / ESC=q:${NC} "
|
||||
else
|
||||
echo -n "${BLUE}◎ Remove ${app_total} app(s) (${size_display}) | Enter=go / ESC=q:${NC} "
|
||||
fi
|
||||
IFS= read -r -s -n1 key || key=""
|
||||
case "$key" in
|
||||
$'\e'|q|Q) echo ""; return 0 ;;
|
||||
""|$'\n'|$'\r'|y|Y) echo "" ;;
|
||||
*) echo ""; return 0 ;;
|
||||
esac
|
||||
|
||||
echo -n "◎ Starting in 3s... 3"; sleep 1; echo -ne "\r◎ Starting in 3s... 2"; sleep 1; echo -ne "\r◎ Starting in 3s... 1"; sleep 1
|
||||
echo -ne "\r\033[K"
|
||||
echo ""
|
||||
if [[ -t 1 ]]; then start_inline_spinner "Uninstalling apps..."; fi
|
||||
|
||||
# Force quit running apps first (batch)
|
||||
@@ -113,11 +107,11 @@ batch_uninstall_applications() {
|
||||
if pgrep -f "${running_apps[0]}" >/dev/null 2>&1; then sleep 1; fi
|
||||
fi
|
||||
|
||||
# Perform uninstallations (compact output)
|
||||
# Perform uninstallations (silent mode, show results at end)
|
||||
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||
echo ""
|
||||
local success_count=0 failed_count=0
|
||||
local -a failed_items=()
|
||||
local -a success_items=()
|
||||
for detail in "${app_details[@]}"; do
|
||||
IFS='|' read -r app_name app_path bundle_id total_kb encoded_files <<< "$detail"
|
||||
local related_files=$(echo "$encoded_files" | base64 -d)
|
||||
@@ -144,7 +138,7 @@ batch_uninstall_applications() {
|
||||
((success_count++))
|
||||
((files_cleaned++))
|
||||
((total_items++))
|
||||
printf " ${GREEN}OK${NC} %-20s%s\n" "$app_name" $([[ $files_removed -gt 0 ]] && echo "+$files_removed" )
|
||||
success_items+=("$app_name")
|
||||
else
|
||||
((failed_count++))
|
||||
failed_items+=("$app_name:$reason")
|
||||
@@ -152,32 +146,36 @@ batch_uninstall_applications() {
|
||||
done
|
||||
|
||||
# Summary
|
||||
local freed_display="0B"
|
||||
if [[ $total_size_freed -gt 0 ]]; then
|
||||
local freed_kb=$total_size_freed
|
||||
if [[ $freed_kb -ge 1048576 ]]; then
|
||||
freed_display=$(echo "$freed_kb" | awk '{printf "%.2fGB", $1/1024/1024}')
|
||||
elif [[ $freed_kb -ge 1024 ]]; then
|
||||
freed_display=$(echo "$freed_kb" | awk '{printf "%.1fMB", $1/1024}')
|
||||
else
|
||||
freed_display="${freed_kb}KB"
|
||||
fi
|
||||
fi
|
||||
local freed_display=$(bytes_to_human "$((total_size_freed * 1024))")
|
||||
local bar="================================================================================"
|
||||
echo ""
|
||||
echo "$bar"
|
||||
if [[ $success_count -gt 0 ]]; then
|
||||
local success_list="${success_items[*]}"
|
||||
echo -e "Removed: ${GREEN}${success_list}${NC} | Freed: ${GREEN}${freed_display}${NC}"
|
||||
fi
|
||||
if [[ $failed_count -gt 0 ]]; then
|
||||
echo -e "Removed: ${GREEN}$success_count${NC} | Failed: ${RED}$failed_count${NC} | Freed: ${GREEN}$freed_display${NC}"
|
||||
local failed_names=()
|
||||
local reason_summary=""
|
||||
for item in "${failed_items[@]}"; do
|
||||
local name=${item%%:*}
|
||||
failed_names+=("$name")
|
||||
done
|
||||
local failed_list="${failed_names[*]}"
|
||||
|
||||
# Determine primary reason
|
||||
if [[ $failed_count -eq 1 ]]; then
|
||||
local first="${failed_items[0]}"
|
||||
local name=${first%%:*}
|
||||
local reason=${first#*:}
|
||||
echo "${name} $(map_uninstall_reason "$reason")"
|
||||
local first_reason=${failed_items[0]#*:}
|
||||
case "$first_reason" in
|
||||
still*running*) reason_summary="still running" ;;
|
||||
remove*failed*) reason_summary="could not be removed" ;;
|
||||
permission*) reason_summary="permission denied" ;;
|
||||
*) reason_summary="$first_reason" ;;
|
||||
esac
|
||||
echo -e "Failed: ${RED}${failed_list}${NC} ${reason_summary}"
|
||||
else
|
||||
local joined="${failed_items[*]}"; echo "Failures: $joined"
|
||||
echo -e "Failed: ${RED}${failed_list}${NC} could not be removed"
|
||||
fi
|
||||
else
|
||||
echo -e "Removed: ${GREEN}$success_count${NC} | Failed: ${RED}$failed_count${NC} | Freed: ${GREEN}$freed_display${NC}"
|
||||
fi
|
||||
echo "$bar"
|
||||
|
||||
|
||||
129
lib/common.sh
129
lib/common.sh
@@ -20,6 +20,15 @@ readonly RED="${ESC}[0;31m"
|
||||
readonly GRAY="${ESC}[0;90m"
|
||||
readonly NC="${ESC}[0m"
|
||||
|
||||
# Icon definitions
|
||||
readonly ICON_CONFIRM="◎" # Confirm operation
|
||||
readonly ICON_ADMIN="●" # Admin permission
|
||||
readonly ICON_SUCCESS="✓" # Success
|
||||
readonly ICON_ERROR="✗" # Error
|
||||
readonly ICON_EMPTY="○" # Empty state
|
||||
readonly ICON_LIST="-" # List item
|
||||
readonly ICON_MENU="▸" # Menu item
|
||||
|
||||
# Spinner character helpers (ASCII by default, overridable via env)
|
||||
mo_spinner_chars() {
|
||||
local chars="${MO_SPINNER_CHARS:-|/-\\}"
|
||||
@@ -52,7 +61,7 @@ log_info() {
|
||||
|
||||
log_success() {
|
||||
rotate_log
|
||||
echo -e " ${GREEN}✓${NC} $1"
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
@@ -64,16 +73,47 @@ log_warning() {
|
||||
|
||||
log_error() {
|
||||
rotate_log
|
||||
echo -e "${RED}$1${NC}" >&2
|
||||
echo -e "${RED}${ICON_ERROR}${NC} $1" >&2
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
log_header() {
|
||||
rotate_log
|
||||
echo -e "\n${PURPLE}▶ $1${NC}"
|
||||
echo -e "\n${PURPLE}${ICON_MENU} $1${NC}"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECTION: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Icon output helpers
|
||||
icon_confirm() {
|
||||
echo -e "${BLUE}${ICON_CONFIRM}${NC} $1"
|
||||
}
|
||||
|
||||
icon_admin() {
|
||||
echo -e "${BLUE}${ICON_ADMIN}${NC} $1"
|
||||
}
|
||||
|
||||
icon_success() {
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||
}
|
||||
|
||||
icon_error() {
|
||||
echo -e " ${RED}${ICON_ERROR}${NC} $1"
|
||||
}
|
||||
|
||||
icon_empty() {
|
||||
echo -e " ${BLUE}${ICON_EMPTY}${NC} $1"
|
||||
}
|
||||
|
||||
icon_list() {
|
||||
echo -e " ${ICON_LIST} $1"
|
||||
}
|
||||
|
||||
icon_menu() {
|
||||
local num="$1"
|
||||
local text="$2"
|
||||
echo -e "${BLUE}${ICON_MENU} ${num}. ${text}${NC}"
|
||||
}
|
||||
|
||||
# System detection
|
||||
detect_architecture() {
|
||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||
@@ -276,7 +316,7 @@ request_sudo_access() {
|
||||
|
||||
# If Touch ID is supported and not forced to use password
|
||||
if [[ "$force_password" != "true" ]] && check_touchid_support; then
|
||||
echo -e "${BLUE}${prompt_msg}${NC} ${GRAY}(Touch ID or password)${NC}"
|
||||
echo -e "${BLUE}${ICON_ADMIN}${NC} ${prompt_msg} ${GRAY}(Touch ID or password)${NC}"
|
||||
if sudo -v 2>/dev/null; then
|
||||
return 0
|
||||
else
|
||||
@@ -284,8 +324,8 @@ request_sudo_access() {
|
||||
fi
|
||||
else
|
||||
# Traditional password method
|
||||
echo -e "${BLUE}${prompt_msg}${NC}"
|
||||
echo -ne "${BLUE} Password> ${NC}"
|
||||
echo -e "${BLUE}${ICON_ADMIN}${NC} ${prompt_msg}"
|
||||
echo -ne "${BLUE}${ICON_MENU}${NC} Password: "
|
||||
read -s password
|
||||
echo ""
|
||||
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
||||
@@ -313,13 +353,27 @@ request_sudo() {
|
||||
update_via_homebrew() {
|
||||
local version="${1:-unknown}"
|
||||
|
||||
echo -e "${BLUE}|${NC} Updating Homebrew..."
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Updating Homebrew..."
|
||||
else
|
||||
echo "Updating Homebrew..."
|
||||
fi
|
||||
# Filter out common noise but show important info
|
||||
brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}|${NC} Upgrading Mole..."
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Upgrading Mole..."
|
||||
else
|
||||
echo "Upgrading Mole..."
|
||||
fi
|
||||
local upgrade_output
|
||||
upgrade_output=$(brew upgrade mole 2>&1) || true
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if echo "$upgrade_output" | grep -q "already installed"; then
|
||||
# Get current version
|
||||
@@ -397,17 +451,21 @@ start_inline_spinner() {
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
(
|
||||
trap 'exit 0' TERM INT EXIT
|
||||
local chars
|
||||
chars="$(mo_spinner_chars)"
|
||||
[[ -z "$chars" ]] && chars='|/-\'
|
||||
local i=0
|
||||
while true; do
|
||||
local c="${chars:$((i % ${#chars})):1}"
|
||||
printf "\r${MOLE_SPINNER_PREFIX:-}${BLUE}%s${NC} %s" "$c" "$message"
|
||||
printf "\r${MOLE_SPINNER_PREFIX:-}${BLUE}%s${NC} %s" "$c" "$message" 2>/dev/null || exit 0
|
||||
((i++))
|
||||
sleep 0.12
|
||||
# macOS supports decimal sleep, this is the primary target
|
||||
sleep 0.1 2>/dev/null || sleep 1 2>/dev/null || exit 0
|
||||
done
|
||||
) &
|
||||
INLINE_SPINNER_PID=$!
|
||||
disown 2>/dev/null || true
|
||||
else
|
||||
echo -n " ${BLUE}|${NC} $message"
|
||||
fi
|
||||
@@ -419,7 +477,7 @@ stop_inline_spinner() {
|
||||
kill "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||
wait "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||
INLINE_SPINNER_PID=""
|
||||
[[ -t 1 ]] && printf "\r"
|
||||
[[ -t 1 ]] && printf "\r\033[K"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -556,10 +614,45 @@ parallel_execute() {
|
||||
# Set MOLE_SPINNER_PREFIX=" " for indented spinner (e.g., in clean context)
|
||||
with_spinner() {
|
||||
local msg="$1"; shift || true
|
||||
local timeout="${MOLE_CMD_TIMEOUT:-180}" # Default 3min timeout
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "$msg"
|
||||
fi
|
||||
"$@" >/dev/null 2>&1 || return $?
|
||||
|
||||
# Run command with timeout protection
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
# GNU timeout available
|
||||
timeout "$timeout" "$@" >/dev/null 2>&1 || {
|
||||
local exit_code=$?
|
||||
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||
# Exit code 124 means timeout
|
||||
[[ $exit_code -eq 124 ]] && echo -e " ${YELLOW}⚠${NC} $msg timed out (skipped)" >&2
|
||||
return $exit_code
|
||||
}
|
||||
else
|
||||
# Fallback: run in background with manual timeout
|
||||
"$@" >/dev/null 2>&1 &
|
||||
local cmd_pid=$!
|
||||
local elapsed=0
|
||||
while kill -0 $cmd_pid 2>/dev/null; do
|
||||
if [[ $elapsed -ge $timeout ]]; then
|
||||
kill -TERM $cmd_pid 2>/dev/null || true
|
||||
wait $cmd_pid 2>/dev/null || true
|
||||
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||
echo -e " ${YELLOW}⚠${NC} $msg timed out (skipped)" >&2
|
||||
return 124
|
||||
fi
|
||||
sleep 1
|
||||
((elapsed++))
|
||||
done
|
||||
wait $cmd_pid 2>/dev/null || {
|
||||
local exit_code=$?
|
||||
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||
return $exit_code
|
||||
}
|
||||
fi
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
@@ -575,8 +668,16 @@ clean_tool_cache() {
|
||||
echo -e " ${YELLOW}→${NC} $label (would clean)"
|
||||
return 0
|
||||
fi
|
||||
MOLE_SPINNER_PREFIX=" " with_spinner "$label" "$@"
|
||||
echo -e " ${GREEN}✓${NC} $label"
|
||||
if MOLE_SPINNER_PREFIX=" " with_spinner "$label" "$@"; then
|
||||
echo -e " ${GREEN}✓${NC} $label"
|
||||
else
|
||||
local exit_code=$?
|
||||
# Timeout returns 124, don't show error message (already shown by with_spinner)
|
||||
if [[ $exit_code -ne 124 ]]; then
|
||||
echo -e " ${YELLOW}⚠${NC} $label failed (skipped)" >&2
|
||||
fi
|
||||
fi
|
||||
return 0 # Always return success to continue cleanup
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -42,13 +42,21 @@ collect_files_to_be_cleaned() {
|
||||
local clean_sh="$SCRIPT_DIR/../bin/clean.sh"
|
||||
local -a items=()
|
||||
|
||||
echo -e "${BLUE}|${NC} Scanning cache files..."
|
||||
echo ""
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Scanning cache files..."
|
||||
else
|
||||
echo "Scanning cache files..."
|
||||
fi
|
||||
|
||||
# Run clean.sh in dry-run mode
|
||||
local temp_output=$(create_temp_file)
|
||||
echo "" | bash "$clean_sh" --dry-run 2>&1 > "$temp_output" || true
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Strip ANSI color codes for parsing
|
||||
local temp_plain=$(create_temp_file)
|
||||
sed $'s/\033\[[0-9;]*m//g' "$temp_output" > "$temp_plain"
|
||||
|
||||
Reference in New Issue
Block a user