1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 20:19:45 +00:00

docs: Improve clarity and conciseness of comments and documentation

This commit is contained in:
Tw93
2025-12-18 17:35:54 +08:00
parent 8eeed7d079
commit 79e40b9c91
14 changed files with 101 additions and 185 deletions

View File

@@ -13,10 +13,10 @@ _MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[[ -z "${MOLE_BASE_LOADED:-}" ]] && source "$_MOLE_CORE_DIR/base.sh"
# ============================================================================
# App Management Functions
# Application Management
# ============================================================================
# System critical components that should NEVER be uninstalled
# Critical system components protected from uninstallation
readonly SYSTEM_CRITICAL_BUNDLES=(
"com.apple.*" # System essentials
"loginwindow"
@@ -68,7 +68,7 @@ readonly SYSTEM_CRITICAL_BUNDLES=(
"com.apple.TextInputSwitcher"
)
# Apps with important data/licenses - protect during cleanup but allow uninstall
# Applications with sensitive data; protected during cleanup but removable
readonly DATA_PROTECTED_BUNDLES=(
# ============================================================================
# System Utilities & Cleanup Tools
@@ -165,7 +165,7 @@ readonly DATA_PROTECTED_BUNDLES=(
"com.usebruno.app" # Bruno (API client)
# ============================================================================
# Network Proxy & VPN Tools (Broad Glob Protection)
# Network Proxy & VPN Tools (pattern-based protection)
# ============================================================================
# Clash variants
"*clash*" # All Clash variants (ClashX, ClashX Pro, Clash Verge, etc)
@@ -439,7 +439,7 @@ readonly DATA_PROTECTED_BUNDLES=(
# 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)
# Check if bundle ID matches pattern (glob support)
bundle_matches_pattern() {
local bundle_id="$1"
local pattern="$2"
@@ -454,7 +454,7 @@ bundle_matches_pattern() {
return 1
}
# Check if app is a system component that should never be uninstalled
# Check if application is a protected system component
should_protect_from_uninstall() {
local bundle_id="$1"
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
@@ -465,7 +465,7 @@ should_protect_from_uninstall() {
return 1
}
# Check if app data should be protected during cleanup (but app can be uninstalled)
# Check if application data should be protected during cleanup
should_protect_data() {
local bundle_id="$1"
# Protect both system critical and data protected bundles during cleanup
@@ -477,7 +477,7 @@ should_protect_data() {
return 1
}
# Check if a specific path should be protected from deletion
# Check if a path is protected from deletion
# Centralized logic to protect system settings, control center, and critical apps
#
# Args: $1 - path to check
@@ -489,7 +489,7 @@ should_protect_path() {
local path_lower
path_lower=$(echo "$path" | tr '[:upper:]' '[:lower:]')
# 1. Check for explicit critical system keywords in path (case-insensitive)
# 1. Keyword-based matching for system components
# Protect System Settings, Preferences, Control Center, and related XPC services
# Also protect "Settings" (used in macOS Sequoia) and savedState files
if [[ "$path_lower" =~ systemsettings || "$path_lower" =~ systempreferences || "$path_lower" =~ controlcenter ]]; then
@@ -501,7 +501,7 @@ should_protect_path() {
return 0
fi
# 2. Protect system-critical cache directories that cause UI corruption
# 2. Protect caches critical for system UI rendering
# These caches are essential for modern macOS (Sonoma/Sequoia) system UI rendering
case "$path" in
# System Settings and Control Center caches (CRITICAL - prevents blank panel bug)
@@ -525,7 +525,7 @@ should_protect_path() {
;;
esac
# 3. Extract bundle ID from app container/group container paths
# 3. Extract bundle ID from sandbox paths
# Matches: .../Library/Containers/bundle.id/...
# Matches: .../Library/Group Containers/group.id/...
if [[ "$path" =~ /Library/Containers/([^/]+) ]] || [[ "$path" =~ /Library/Group\ Containers/([^/]+) ]]; then
@@ -553,7 +553,7 @@ should_protect_path() {
;;
esac
# 6. Check the full path against protected patterns (Broad Glob Match)
# 6. Match full path against protected patterns
# This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude*
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do
if bundle_matches_pattern "$path" "$pattern"; then
@@ -571,13 +571,13 @@ should_protect_path() {
return 1
}
# Find and list app-related files (consolidated from duplicates)
# Locate files associated with an application
find_app_files() {
local bundle_id="$1"
local app_name="$2"
local -a files_to_clean=()
# Sanitized App Name (remove spaces)
# Normalize app name for matching
local nospace_name="${app_name// /}"
local underscore_name="${app_name// /_}"
@@ -635,7 +635,7 @@ find_app_files() {
[[ -e "$expanded_path" ]] && files_to_clean+=("$expanded_path")
done
# Preferences and ByHost (special handling)
# Handle Preferences and ByHost variants
[[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist")
[[ -d ~/Library/Preferences/ByHost ]] && while IFS= read -r -d '' pref; do
files_to_clean+=("$pref")
@@ -655,7 +655,7 @@ find_app_files() {
done < <(command find ~/Library/LaunchAgents -maxdepth 1 \( -name "*$app_name*.plist" \) -print0 2> /dev/null)
fi
# Specialized toolchain cleanup (non-loopable or highly specific)
# Handle specialized toolchains and development environments
# 1. DevEco-Studio (Huawei)
if [[ "$app_name" =~ DevEco|deveco ]] || [[ "$bundle_id" =~ huawei.*deveco ]]; then
for d in ~/DevEcoStudioProjects ~/DevEco-Studio ~/Library/Application\ Support/Huawei ~/Library/Caches/Huawei ~/Library/Logs/Huawei ~/Library/Huawei ~/Huawei ~/HarmonyOS ~/.huawei ~/.ohos; do
@@ -697,7 +697,7 @@ find_app_files() {
[[ ${#files_to_clean[@]} -gt 0 ]] && printf '%s\n' "${files_to_clean[@]}"
}
# Find system-level app files (requires sudo)
# Locate system-level application files
find_app_system_files() {
local bundle_id="$1"
local app_name="$2"
@@ -765,7 +765,7 @@ find_app_system_files() {
find_app_receipt_files "$bundle_id"
}
# Find files from installation receipts (Bom files)
# Locate files using installation receipts (BOM)
find_app_receipt_files() {
local bundle_id="$1"
@@ -798,13 +798,13 @@ find_app_receipt_files() {
# Standardize path (remove leading dot)
local clean_path="${file_path#.}"
# Ensure it starts with /
# Ensure absolute path
if [[ "$clean_path" != /* ]]; then
clean_path="/$clean_path"
fi
# ------------------------------------------------------------------------
# SAFETY FILTER: Only allow specific removal paths
# Safety check: restrict removal to trusted paths
# ------------------------------------------------------------------------
local is_safe=false
@@ -839,15 +839,7 @@ find_app_receipt_files() {
esac
if [[ "$is_safe" == "true" && -e "$clean_path" ]]; then
# Only valid files
# Don't delete directories if they are non-empty parents?
# lsbom lists directories too.
# If we return a directory, `safe_remove` logic handles it.
# `uninstall.sh` uses `remove_file_list`.
# If `lsbom` lists `/Applications` (it shouldn't, only contents), we must be careful.
# `lsbom` usually lists `./Applications/MyApp.app`.
# If it lists `./Applications`, we must skip it.
# If lsbom lists /Applications, skip to avoid system damage.
# Extra check: path must be deep enough?
# If path is just "/Applications", skip.
if [[ "$clean_path" == "/Applications" || "$clean_path" == "/Library" || "$clean_path" == "/usr/local" ]]; then
@@ -865,9 +857,9 @@ find_app_receipt_files() {
fi
}
# Force quit an application
# Terminate a running application
force_kill_app() {
# Args: app_name [app_path]; tries graceful then force kill; returns 0 if stopped, 1 otherwise
# Gracefully terminates or force-kills an application
local app_name="$1"
local app_path="${2:-""}"

View File

@@ -44,15 +44,15 @@ readonly ICON_NAV_RIGHT="→"
# ============================================================================
# Global Configuration Constants
# ============================================================================
readonly MOLE_TEMP_FILE_AGE_DAYS=7 # Temp file cleanup threshold
readonly MOLE_ORPHAN_AGE_DAYS=60 # Orphaned data threshold
readonly MOLE_TEMP_FILE_AGE_DAYS=7 # Temp file retention (days)
readonly MOLE_ORPHAN_AGE_DAYS=60 # Orphaned data retention (days)
readonly MOLE_MAX_PARALLEL_JOBS=15 # Parallel job limit
readonly MOLE_MAIL_DOWNLOADS_MIN_KB=5120 # Mail attachments size threshold
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment cleanup threshold (30+ days)
readonly MOLE_LOG_AGE_DAYS=7 # System log retention
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # App saved state retention
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # Time Machine failed backup safety window
readonly MOLE_MAIL_DOWNLOADS_MIN_KB=5120 # Mail attachment size threshold
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment retention (days)
readonly MOLE_LOG_AGE_DAYS=7 # Log 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_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
# ============================================================================
# Seasonal Functions
@@ -99,7 +99,7 @@ declare -a DEFAULT_OPTIMIZE_WHITELIST_PATTERNS=(
# ============================================================================
readonly STAT_BSD="/usr/bin/stat"
# Get file size in bytes using BSD stat
# Get file size in bytes
get_file_size() {
local file="$1"
local result
@@ -107,8 +107,7 @@ get_file_size() {
echo "${result:-0}"
}
# Get file modification time using BSD stat
# Returns: epoch seconds
# Get file modification time in epoch seconds
get_file_mtime() {
local file="$1"
[[ -z "$file" ]] && {
@@ -120,7 +119,7 @@ get_file_mtime() {
echo "${result:-0}"
}
# Get file owner username using BSD stat
# Get file owner username
get_file_owner() {
local file="$1"
$STAT_BSD -f%Su "$file" 2> /dev/null || echo ""
@@ -147,8 +146,7 @@ is_sip_enabled() {
fi
}
# Check if running in interactive terminal
# Returns: 0 if interactive, 1 otherwise
# Check if running in an interactive terminal
is_interactive() {
[[ -t 1 ]]
}
@@ -169,9 +167,7 @@ get_free_space() {
command df -h / | awk 'NR==2 {print $4}'
}
# Get optimal number of parallel jobs for a given operation type
# Args: $1 - operation type (scan|io|compute|default)
# Returns: number of jobs
# Get optimal parallel jobs for operation type (scan|io|compute|default)
get_optimal_parallel_jobs() {
local operation_type="${1:-default}"
local cpu_cores
@@ -193,9 +189,7 @@ get_optimal_parallel_jobs() {
# Formatting Utilities
# ============================================================================
# Convert bytes to human-readable format
# Args: $1 - size in bytes
# Returns: formatted string (e.g., "1.50GB", "256MB", "4KB")
# Convert bytes to human-readable format (e.g., 1.5GB)
bytes_to_human() {
local bytes="$1"
if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then
@@ -245,9 +239,7 @@ bytes_to_human_kb() {
bytes_to_human "$((${1:-0} * 1024))"
}
# Get brand-friendly name for an application
# Args: $1 - application name
# Returns: localized name based on system language preference
# Get brand-friendly localized name for an application
get_brand_name() {
local name="$1"
@@ -305,7 +297,6 @@ declare -a MOLE_TEMP_FILES=()
declare -a MOLE_TEMP_DIRS=()
# Create tracked temporary file
# Returns: temp file path
create_temp_file() {
local temp
temp=$(mktemp) || return 1
@@ -314,7 +305,6 @@ create_temp_file() {
}
# Create tracked temporary directory
# Returns: temp directory path
create_temp_dir() {
local temp
temp=$(mktemp -d) || return 1

View File

@@ -12,7 +12,7 @@ readonly MOLE_COMMON_LOADED=1
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Load core modules in dependency order
# Load core modules
source "$_MOLE_CORE_DIR/base.sh"
source "$_MOLE_CORE_DIR/log.sh"
@@ -26,8 +26,7 @@ if [[ -f "$_MOLE_CORE_DIR/sudo.sh" ]]; then
source "$_MOLE_CORE_DIR/sudo.sh"
fi
# Update Mole via Homebrew
# Args: $1 = current version
# Update via Homebrew
update_via_homebrew() {
local current_version="$1"
@@ -75,8 +74,7 @@ update_via_homebrew() {
rm -f "$HOME/.cache/mole/version_check" "$HOME/.cache/mole/update_message" 2> /dev/null || true
}
# Remove apps from Dock
# Args: app paths to remove
# Remove applications from Dock
remove_apps_from_dock() {
if [[ $# -eq 0 ]]; then
return 0
@@ -89,7 +87,7 @@ remove_apps_from_dock() {
return 0
fi
# Execute Python helper to prune dock entries for the given app paths
# Prune dock entries using Python helper
python3 - "$@" << 'PY' 2> /dev/null || return 0
import os
import plistlib

View File

@@ -29,11 +29,7 @@ fi
# Path Validation
# ============================================================================
# Validate path for deletion operations
# Checks: non-empty, absolute, no traversal, no control chars, not system dir
#
# Args: $1 - path to validate
# Returns: 0 if safe, 1 if unsafe
# Validate path for deletion (absolute, no traversal, not system dir)
validate_path_for_deletion() {
local path="$1"
@@ -76,13 +72,7 @@ validate_path_for_deletion() {
# Safe Removal Operations
# ============================================================================
# Safe wrapper around rm -rf with path validation
#
# Args:
# $1 - path to remove
# $2 - silent mode (optional, default: false)
#
# Returns: 0 on success, 1 on failure
# Safe wrapper around rm -rf with validation
safe_remove() {
local path="$1"
local silent="${2:-false}"
@@ -108,10 +98,7 @@ safe_remove() {
fi
}
# Safe sudo remove with additional symlink protection
#
# Args: $1 - path to remove
# Returns: 0 on success, 1 on failure
# Safe sudo removal with symlink protection
safe_sudo_remove() {
local path="$1"
@@ -147,15 +134,7 @@ safe_sudo_remove() {
# Safe Find and Delete Operations
# ============================================================================
# Safe find delete with depth limit and validation
#
# Args:
# $1 - base directory
# $2 - file pattern (e.g., "*.log")
# $3 - age in days (0 = all files, default: 7)
# $4 - type filter ("f" or "d", default: "f")
#
# Returns: 0 on success, 1 on failure
# Safe file discovery and deletion with depth and age limits
safe_find_delete() {
local base_dir="$1"
local pattern="$2"
@@ -202,10 +181,7 @@ safe_find_delete() {
return 0
}
# Safe sudo find delete (same as safe_find_delete but with sudo)
#
# Args: same as safe_find_delete
# Returns: 0 on success, 1 on failure
# Safe sudo discovery and deletion
safe_sudo_find_delete() {
local base_dir="$1"
local pattern="$2"
@@ -254,11 +230,7 @@ safe_sudo_find_delete() {
# Size Calculation
# ============================================================================
# Get path size in kilobytes
# Uses timeout protection to prevent du from hanging on large directories
#
# Args: $1 - path
# Returns: size in KB (0 if path doesn't exist)
# Get path size in KB (returns 0 if not found)
get_path_size_kb() {
local path="$1"
[[ -z "$path" || ! -e "$path" ]] && {
@@ -271,10 +243,7 @@ get_path_size_kb() {
echo "${size:-0}"
}
# Calculate total size of multiple paths
#
# Args: $1 - newline-separated list of paths
# Returns: total size in KB
# Calculate total size for multiple paths
calculate_total_size() {
local files="$1"
local total_kb=0

View File

@@ -32,8 +32,7 @@ mkdir -p "$(dirname "$LOG_FILE")" 2> /dev/null || true
# Log Rotation
# ============================================================================
# Rotate log file if it exceeds max size
# Called once at module load, not per log entry
# Rotate log file if it exceeds maximum size
rotate_log_once() {
# Skip if already checked this session
[[ -n "${MOLE_LOG_ROTATED:-}" ]] && return 0
@@ -51,7 +50,6 @@ rotate_log_once() {
# ============================================================================
# Log informational message
# Args: $1 - message
log_info() {
echo -e "${BLUE}$1${NC}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
@@ -62,7 +60,6 @@ log_info() {
}
# Log success message
# Args: $1 - message
log_success() {
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
@@ -73,7 +70,6 @@ log_success() {
}
# Log warning message
# Args: $1 - message
log_warning() {
echo -e "${YELLOW}$1${NC}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
@@ -84,7 +80,6 @@ log_warning() {
}
# Log error message
# Args: $1 - message
log_error() {
echo -e "${RED}${ICON_ERROR}${NC} $1" >&2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
@@ -94,8 +89,7 @@ log_error() {
fi
}
# Debug logging - only shown when MO_DEBUG=1
# Args: $@ - debug message components
# Debug logging (active when MO_DEBUG=1)
debug_log() {
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
@@ -143,15 +137,12 @@ log_system_info() {
# Command Execution Wrappers
# ============================================================================
# Run command silently, ignore errors
# Args: $@ - command and arguments
# Run command silently (ignore errors)
run_silent() {
"$@" > /dev/null 2>&1 || true
}
# Run command with error logging
# Args: $@ - command and arguments
# Returns: command exit code
run_logged() {
local cmd="$1"
# Log to main file, and also to debug file if enabled
@@ -173,8 +164,7 @@ run_logged() {
# Formatted Output
# ============================================================================
# Print formatted summary block with heading and details
# Args: $1=status (ignored), $2=heading, $@=details
# Print formatted summary block
print_summary_block() {
local heading=""
local -a details=()

View File

@@ -16,6 +16,7 @@ check_touchid_support() {
return 1
}
# Detect clamshell mode (lid closed)
is_clamshell_mode() {
# ioreg is missing (not macOS) -> treat as lid open
if ! command -v ioreg > /dev/null 2>&1; then
@@ -182,8 +183,7 @@ request_sudo_access() {
MOLE_SUDO_KEEPALIVE_PID=""
MOLE_SUDO_ESTABLISHED="false"
# Start sudo keepalive background process
# Returns: PID of keepalive process
# Start sudo keepalive
_start_sudo_keepalive() {
# Start background keepalive process with all outputs redirected
# This is critical: command substitution waits for all file descriptors to close
@@ -212,8 +212,7 @@ _start_sudo_keepalive() {
echo $pid
}
# Stop sudo keepalive process
# Args: $1 - PID of keepalive process
# Stop sudo keepalive
_stop_sudo_keepalive() {
local pid="${1:-}"
if [[ -n "$pid" ]]; then
@@ -227,8 +226,7 @@ has_sudo_session() {
sudo -n true 2> /dev/null
}
# Request sudo access (wrapper for common.sh function)
# Args: $1 - prompt message
# Request administrative access
request_sudo() {
local prompt_msg="${1:-Admin access required}"
@@ -244,8 +242,7 @@ request_sudo() {
fi
}
# Ensure sudo session is established with keepalive
# Args: $1 - prompt message
# Maintain active sudo session with keepalive
ensure_sudo_session() {
local prompt="${1:-Admin access required}"
@@ -287,8 +284,7 @@ register_sudo_cleanup() {
trap stop_sudo_session EXIT INT TERM
}
# Check if sudo is likely needed for given operations
# Args: $@ - list of operations to check
# Predict if operation requires administrative access
will_need_sudo() {
local -a operations=("$@")
for op in "${operations[@]}"; do

View File

@@ -17,10 +17,7 @@ clear_screen() { printf '\033[2J\033[H'; }
hide_cursor() { [[ -t 1 ]] && printf '\033[?25l' >&2 || true; }
show_cursor() { [[ -t 1 ]] && printf '\033[?25h' >&2 || true; }
# Calculate display width of a string (CJK characters count as 2)
# Args: $1 - string to measure
# Returns: display width
# Note: Works correctly even when LC_ALL=C is set
# Calculate display width (CJK characters count as 2)
get_display_width() {
local str="$1"
@@ -66,8 +63,7 @@ get_display_width() {
echo "$width"
}
# Truncate string by display width (handles CJK correctly)
# Args: $1 - string, $2 - max display width
# Truncate string by display width (handles CJK)
truncate_by_display_width() {
local str="$1"
local max_width="$2"
@@ -140,7 +136,7 @@ truncate_by_display_width() {
echo "${truncated}..."
}
# Keyboard input - read single keypress
# Read single keyboard input
read_key() {
local key rest read_status
IFS= read -r -s -n 1 key
@@ -222,7 +218,7 @@ drain_pending_input() {
done
}
# Menu display
# Format menu option display
show_menu_option() {
local number="$1"
local text="$2"
@@ -235,7 +231,7 @@ show_menu_option() {
fi
}
# Inline spinner
# Background spinner implementation
INLINE_SPINNER_PID=""
start_inline_spinner() {
stop_inline_spinner 2> /dev/null || true
@@ -281,7 +277,7 @@ stop_inline_spinner() {
fi
}
# Wrapper for running commands with spinner
# Run command with a terminal spinner
with_spinner() {
local msg="$1"
shift || true
@@ -302,9 +298,7 @@ mo_spinner_chars() {
printf "%s" "$chars"
}
# Format last used time for display
# Args: $1 = last used string (e.g., "3 days ago", "Today", "Never")
# Returns: Compact version (e.g., "3d ago", "Today", "Never")
# Format relative time for compact display (e.g., 3d ago)
format_last_used_summary() {
local value="$1"