mirror of
https://github.com/tw93/Mole.git
synced 2026-02-16 02:36:12 +00:00
Implement spinner stack management and ANSI terminal capability checks
This commit is contained in:
274
lib/core/base.sh
274
lib/core/base.sh
@@ -30,16 +30,15 @@ readonly NC="${ESC}[0m"
|
|||||||
readonly ICON_CONFIRM="◎"
|
readonly ICON_CONFIRM="◎"
|
||||||
readonly ICON_ADMIN="⚙"
|
readonly ICON_ADMIN="⚙"
|
||||||
readonly ICON_SUCCESS="✓"
|
readonly ICON_SUCCESS="✓"
|
||||||
readonly ICON_ERROR="☻"
|
readonly ICON_ERROR="☹︎"
|
||||||
|
readonly ICON_WARNING="☺︎"
|
||||||
readonly ICON_EMPTY="○"
|
readonly ICON_EMPTY="○"
|
||||||
readonly ICON_SOLID="●"
|
readonly ICON_SOLID="●"
|
||||||
readonly ICON_LIST="•"
|
readonly ICON_LIST="•"
|
||||||
readonly ICON_ARROW="➤"
|
readonly ICON_ARROW="➤"
|
||||||
readonly ICON_WARNING="☻"
|
readonly ICON_DRY_RUN="→"
|
||||||
readonly ICON_NAV_UP="↑"
|
readonly ICON_NAV_UP="↑"
|
||||||
readonly ICON_NAV_DOWN="↓"
|
readonly ICON_NAV_DOWN="↓"
|
||||||
readonly ICON_NAV_LEFT="←"
|
|
||||||
readonly ICON_NAV_RIGHT="→"
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Global Configuration Constants
|
# Global Configuration Constants
|
||||||
@@ -53,6 +52,8 @@ readonly MOLE_LOG_AGE_DAYS=7 # Log retention (days)
|
|||||||
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
||||||
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # Saved state retention (days)
|
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # Saved state retention (days)
|
||||||
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
||||||
|
readonly MOLE_MAX_DS_STORE_FILES=500 # Max .DS_Store files to clean per scan
|
||||||
|
readonly MOLE_MAX_ORPHAN_ITERATIONS=100 # Max iterations for orphaned app data scan
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Seasonal Functions
|
# Seasonal Functions
|
||||||
@@ -566,3 +567,268 @@ note_activity() {
|
|||||||
SECTION_ACTIVITY=1
|
SECTION_ACTIVITY=1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Start a section spinner with optional message
|
||||||
|
# Usage: start_section_spinner "message"
|
||||||
|
start_section_spinner() {
|
||||||
|
local message="${1:-Scanning...}"
|
||||||
|
stop_inline_spinner 2> /dev/null || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "$message"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop spinner and clear the line
|
||||||
|
# Usage: stop_section_spinner
|
||||||
|
stop_section_spinner() {
|
||||||
|
stop_inline_spinner 2> /dev/null || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
echo -ne "\r\033[K" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safe terminal line clearing with terminal type detection
|
||||||
|
# Usage: safe_clear_lines <num_lines> [tty_device]
|
||||||
|
# Returns: 0 on success, 1 if terminal doesn't support ANSI
|
||||||
|
safe_clear_lines() {
|
||||||
|
local lines="${1:-1}"
|
||||||
|
local tty_device="${2:-/dev/tty}"
|
||||||
|
|
||||||
|
# Use centralized ANSI support check (defined below)
|
||||||
|
# Note: This forward reference works because functions are parsed before execution
|
||||||
|
is_ansi_supported 2>/dev/null || return 1
|
||||||
|
|
||||||
|
# Clear lines one by one (more reliable than multi-line sequences)
|
||||||
|
local i
|
||||||
|
for ((i=0; i<lines; i++)); do
|
||||||
|
printf "\033[1A\r\033[K" > "$tty_device" 2>/dev/null || return 1
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safe single line clear with fallback
|
||||||
|
# Usage: safe_clear_line [tty_device]
|
||||||
|
safe_clear_line() {
|
||||||
|
local tty_device="${1:-/dev/tty}"
|
||||||
|
|
||||||
|
# Use centralized ANSI support check
|
||||||
|
is_ansi_supported 2>/dev/null || return 1
|
||||||
|
|
||||||
|
printf "\r\033[K" > "$tty_device" 2>/dev/null || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update progress spinner if enough time has elapsed
|
||||||
|
# Usage: update_progress_if_needed <completed> <total> <last_update_time_var> [interval]
|
||||||
|
# Example: update_progress_if_needed "$completed" "$total" last_progress_update 2
|
||||||
|
# Returns: 0 if updated, 1 if skipped
|
||||||
|
update_progress_if_needed() {
|
||||||
|
local completed="$1"
|
||||||
|
local total="$2"
|
||||||
|
local last_update_var="$3" # Name of variable holding last update time
|
||||||
|
local interval="${4:-2}" # Default: update every 2 seconds
|
||||||
|
|
||||||
|
# Get current time
|
||||||
|
local current_time=$(date +%s)
|
||||||
|
|
||||||
|
# Get last update time from variable
|
||||||
|
local last_time
|
||||||
|
eval "last_time=\${$last_update_var:-0}"
|
||||||
|
|
||||||
|
# Check if enough time has elapsed
|
||||||
|
if [[ $((current_time - last_time)) -ge $interval ]]; then
|
||||||
|
# Update the spinner with progress
|
||||||
|
stop_section_spinner
|
||||||
|
start_section_spinner "Scanning items... ($completed/$total)"
|
||||||
|
|
||||||
|
# Update the last_update_time variable
|
||||||
|
eval "$last_update_var=$current_time"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Spinner Stack Management (prevents nesting issues)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Global spinner stack
|
||||||
|
declare -a MOLE_SPINNER_STACK=()
|
||||||
|
|
||||||
|
# Push current spinner state onto stack
|
||||||
|
# Usage: push_spinner_state
|
||||||
|
push_spinner_state() {
|
||||||
|
local current_state=""
|
||||||
|
|
||||||
|
# Save current spinner PID if running
|
||||||
|
if [[ -n "${MOLE_SPINNER_PID:-}" ]] && kill -0 "$MOLE_SPINNER_PID" 2>/dev/null; then
|
||||||
|
current_state="running:$MOLE_SPINNER_PID"
|
||||||
|
else
|
||||||
|
current_state="stopped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MOLE_SPINNER_STACK+=("$current_state")
|
||||||
|
debug_log "Pushed spinner state: $current_state (stack depth: ${#MOLE_SPINNER_STACK[@]})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pop and restore spinner state from stack
|
||||||
|
# Usage: pop_spinner_state
|
||||||
|
pop_spinner_state() {
|
||||||
|
if [[ ${#MOLE_SPINNER_STACK[@]} -eq 0 ]]; then
|
||||||
|
debug_log "Warning: Attempted to pop from empty spinner stack"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stack depth safety check
|
||||||
|
if [[ ${#MOLE_SPINNER_STACK[@]} -gt 10 ]]; then
|
||||||
|
debug_log "Warning: Spinner stack depth excessive (${#MOLE_SPINNER_STACK[@]}), possible leak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local last_idx=$((${#MOLE_SPINNER_STACK[@]} - 1))
|
||||||
|
local state="${MOLE_SPINNER_STACK[$last_idx]}"
|
||||||
|
|
||||||
|
# Remove from stack (Bash 3.2 compatible way)
|
||||||
|
# Instead of unset, rebuild array without last element
|
||||||
|
local -a new_stack=()
|
||||||
|
local i
|
||||||
|
for ((i=0; i<last_idx; i++)); do
|
||||||
|
new_stack+=("${MOLE_SPINNER_STACK[$i]}")
|
||||||
|
done
|
||||||
|
MOLE_SPINNER_STACK=("${new_stack[@]}")
|
||||||
|
|
||||||
|
debug_log "Popped spinner state: $state (remaining depth: ${#MOLE_SPINNER_STACK[@]})"
|
||||||
|
|
||||||
|
# Restore state if needed
|
||||||
|
if [[ "$state" == running:* ]]; then
|
||||||
|
# Previous spinner was running - we don't restart it automatically
|
||||||
|
# This is intentional to avoid UI conflicts
|
||||||
|
:
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safe spinner start with stack management
|
||||||
|
# Usage: safe_start_spinner <message>
|
||||||
|
safe_start_spinner() {
|
||||||
|
local message="${1:-Working...}"
|
||||||
|
|
||||||
|
# Push current state
|
||||||
|
push_spinner_state
|
||||||
|
|
||||||
|
# Stop any existing spinner
|
||||||
|
stop_section_spinner 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start new spinner
|
||||||
|
start_section_spinner "$message"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safe spinner stop with stack management
|
||||||
|
# Usage: safe_stop_spinner
|
||||||
|
safe_stop_spinner() {
|
||||||
|
# Stop current spinner
|
||||||
|
stop_section_spinner 2>/dev/null || true
|
||||||
|
|
||||||
|
# Pop previous state
|
||||||
|
pop_spinner_state || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Terminal Compatibility Checks
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Check if terminal supports ANSI escape codes
|
||||||
|
# Usage: is_ansi_supported
|
||||||
|
# Returns: 0 if supported, 1 if not
|
||||||
|
is_ansi_supported() {
|
||||||
|
# Check if running in interactive terminal
|
||||||
|
[[ -t 1 ]] || return 1
|
||||||
|
|
||||||
|
# Check TERM variable
|
||||||
|
[[ -n "${TERM:-}" ]] || return 1
|
||||||
|
|
||||||
|
# Check for known ANSI-compatible terminals
|
||||||
|
case "$TERM" in
|
||||||
|
xterm*|vt100|vt220|screen*|tmux*|ansi|linux|rxvt*|konsole*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
dumb|unknown)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Check terminfo database if available
|
||||||
|
if command -v tput >/dev/null 2>&1; then
|
||||||
|
# Test if terminal supports colors (good proxy for ANSI support)
|
||||||
|
local colors=$(tput colors 2>/dev/null || echo "0")
|
||||||
|
[[ "$colors" -ge 8 ]] && return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get terminal capability info
|
||||||
|
# Usage: get_terminal_info
|
||||||
|
get_terminal_info() {
|
||||||
|
local info="Terminal: ${TERM:-unknown}"
|
||||||
|
|
||||||
|
if is_ansi_supported; then
|
||||||
|
info+=" (ANSI supported)"
|
||||||
|
|
||||||
|
if command -v tput >/dev/null 2>&1; then
|
||||||
|
local cols=$(tput cols 2>/dev/null || echo "?")
|
||||||
|
local lines=$(tput lines 2>/dev/null || echo "?")
|
||||||
|
local colors=$(tput colors 2>/dev/null || echo "?")
|
||||||
|
info+=" ${cols}x${lines}, ${colors} colors"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info+=" (ANSI not supported)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$info"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate terminal environment before running
|
||||||
|
# Usage: validate_terminal_environment
|
||||||
|
# Returns: 0 if OK, 1 with warning if issues detected
|
||||||
|
validate_terminal_environment() {
|
||||||
|
local warnings=0
|
||||||
|
|
||||||
|
# Check if TERM is set
|
||||||
|
if [[ -z "${TERM:-}" ]]; then
|
||||||
|
log_warning "TERM environment variable not set"
|
||||||
|
((warnings++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if running in a known problematic terminal
|
||||||
|
case "${TERM:-}" in
|
||||||
|
dumb)
|
||||||
|
log_warning "Running in 'dumb' terminal - limited functionality"
|
||||||
|
((warnings++))
|
||||||
|
;;
|
||||||
|
unknown)
|
||||||
|
log_warning "Terminal type unknown - may have display issues"
|
||||||
|
((warnings++))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check terminal size if available
|
||||||
|
if command -v tput >/dev/null 2>&1; then
|
||||||
|
local cols=$(tput cols 2>/dev/null || echo "80")
|
||||||
|
if [[ "$cols" -lt 60 ]]; then
|
||||||
|
log_warning "Terminal width ($cols cols) is narrow - output may wrap"
|
||||||
|
((warnings++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report compatibility
|
||||||
|
if [[ $warnings -eq 0 ]]; then
|
||||||
|
debug_log "Terminal environment validated: $(get_terminal_info)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
debug_log "Terminal compatibility warnings: $warnings"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user