1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-10 05:34:17 +00:00

Merge branch 'main' into dev

This commit is contained in:
tw93
2026-01-31 17:37:46 +08:00
22 changed files with 1642 additions and 414 deletions

View File

@@ -422,8 +422,11 @@ get_macos_update_labels() {
# ============================================================================
check_disk_space() {
local free_gb=$(command df -H / | awk 'NR==2 {print $4}' | sed 's/G//')
local free_num=$(echo "$free_gb" | tr -d 'G' | cut -d'.' -f1)
# Use df -k to get KB values (always numeric), then calculate GB via math
# This avoids unit suffix parsing issues (df -H can return MB or GB)
local free_kb=$(command df -k / | awk 'NR==2 {print $4}')
local free_gb=$(awk "BEGIN {printf \"%.1f\", $free_kb / 1048576}")
local free_num=$(awk "BEGIN {printf \"%d\", $free_kb / 1048576}")
export DISK_FREE_GB=$free_num

View File

@@ -114,6 +114,11 @@ clean_media_players() {
fi
safe_clean ~/Library/Caches/com.apple.Music "Apple Music cache"
safe_clean ~/Library/Caches/com.apple.podcasts "Apple Podcasts cache"
# Apple Podcasts sandbox container: zombie sparse files and stale artwork cache (#387)
safe_clean ~/Library/Containers/com.apple.podcasts/Data/tmp/StreamedMedia "Podcasts streamed media"
safe_clean ~/Library/Containers/com.apple.podcasts/Data/tmp/*.heic "Podcasts artwork cache"
safe_clean ~/Library/Containers/com.apple.podcasts/Data/tmp/*.img "Podcasts image cache"
safe_clean ~/Library/Containers/com.apple.podcasts/Data/tmp/*CFNetworkDownload*.tmp "Podcasts download temp"
safe_clean ~/Library/Caches/com.apple.TV/* "Apple TV cache"
safe_clean ~/Library/Caches/tv.plex.player.desktop "Plex cache"
safe_clean ~/Library/Caches/com.netease.163music "NetEase Music cache"

View File

@@ -413,7 +413,7 @@ clean_orphaned_system_services() {
fi
orphaned_files+=("$plist")
local size_kb
size_kb=$(sudo du -sk "$plist" 2> /dev/null | awk '{print $1}' || echo "0")
size_kb=$(sudo du -skP "$plist" 2> /dev/null | awk '{print $1}' || echo "0")
((total_orphaned_kb += size_kb))
((orphaned_count++))
break
@@ -444,7 +444,7 @@ clean_orphaned_system_services() {
fi
orphaned_files+=("$plist")
local size_kb
size_kb=$(sudo du -sk "$plist" 2> /dev/null | awk '{print $1}' || echo "0")
size_kb=$(sudo du -skP "$plist" 2> /dev/null | awk '{print $1}' || echo "0")
((total_orphaned_kb += size_kb))
((orphaned_count++))
break
@@ -474,7 +474,7 @@ clean_orphaned_system_services() {
fi
orphaned_files+=("$helper")
local size_kb
size_kb=$(sudo du -sk "$helper" 2> /dev/null | awk '{print $1}' || echo "0")
size_kb=$(sudo du -skP "$helper" 2> /dev/null | awk '{print $1}' || echo "0")
((total_orphaned_kb += size_kb))
((orphaned_count++))
break

View File

@@ -29,7 +29,7 @@ clean_homebrew() {
local skip_cleanup=false
local brew_cache_size=0
if [[ -d ~/Library/Caches/Homebrew ]]; then
brew_cache_size=$(run_with_timeout 3 du -sk ~/Library/Caches/Homebrew 2> /dev/null | awk '{print $1}')
brew_cache_size=$(run_with_timeout 3 du -skP ~/Library/Caches/Homebrew 2> /dev/null | awk '{print $1}')
local du_exit=$?
if [[ $du_exit -eq 0 && -n "$brew_cache_size" && "$brew_cache_size" -lt 51200 ]]; then
skip_cleanup=true

View File

@@ -97,7 +97,7 @@ check_multiple_versions() {
if [[ -n "$list_cmd" ]]; then
hint=" · ${GRAY}${list_cmd}${NC}"
fi
echo -e " ${GRAY}${ICON_WARNING}${NC} ${tool_name}: ${count} found${hint}"
echo -e " ${GREEN}${ICON_SUCCESS}${NC} ${tool_name}: ${count} found${hint}"
fi
}

View File

@@ -489,7 +489,7 @@ is_recently_modified() {
get_dir_size_kb() {
local path="$1"
if [[ -d "$path" ]]; then
du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0"
du -skP "$path" 2> /dev/null | awk '{print $1}' || echo "0"
else
echo "0"
fi

View File

@@ -14,7 +14,7 @@ clean_user_essentials() {
[[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0"
if [[ "$DRY_RUN" == "true" ]]; then
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GRAY}${ICON_EMPTY}${NC} Trash · already empty"
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · already empty"
elif [[ $trash_count -gt 0 ]]; then
if osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied, $trash_count items"
@@ -25,7 +25,7 @@ clean_user_essentials() {
done < <(command find "$HOME/.Trash" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
fi
else
echo -e " ${GRAY}${ICON_EMPTY}${NC} Trash · already empty"
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · already empty"
fi
fi
}
@@ -628,7 +628,7 @@ check_ios_device_backups() {
if [[ -d "$backup_dir" ]]; then
local backup_kb=$(get_path_size_kb "$backup_dir")
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then
local backup_human=$(command du -sh "$backup_dir" 2> /dev/null | awk '{print $1}')
local backup_human=$(command du -shP "$backup_dir" 2> /dev/null | awk '{print $1}')
if [[ -n "$backup_human" ]]; then
note_activity
echo -e " ${YELLOW}${ICON_WARNING}${NC} iOS backups: ${GREEN}${backup_human}${NC}${GRAY}, Path: $backup_dir${NC}"

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ readonly MOLE_BASE_LOADED=1
# ============================================================================
readonly ESC=$'\033'
readonly GREEN="${ESC}[0;32m"
readonly BLUE="${ESC}[0;34m"
readonly BLUE="${ESC}[1;34m"
readonly CYAN="${ESC}[0;36m"
readonly YELLOW="${ESC}[0;33m"
readonly PURPLE="${ESC}[0;35m"
@@ -626,9 +626,12 @@ start_section_spinner() {
# 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 || true
# Only clear line if spinner was actually running
if [[ -n "${INLINE_SPINNER_PID:-}" ]]; then
stop_inline_spinner 2> /dev/null || true
if [[ -t 1 ]]; then
echo -ne "\r\033[2K" >&2 || true
fi
fi
}
@@ -646,7 +649,7 @@ safe_clear_lines() {
# 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
printf "\033[1A\r\033[2K" > "$tty_device" 2> /dev/null || return 1
done
return 0
@@ -660,7 +663,7 @@ safe_clear_line() {
# Use centralized ANSI support check
is_ansi_supported 2> /dev/null || return 1
printf "\r\033[K" > "$tty_device" 2> /dev/null || return 1
printf "\r\033[2K" > "$tty_device" 2> /dev/null || return 1
return 0
}

View File

@@ -267,7 +267,7 @@ safe_sudo_remove() {
if sudo test -e "$path" 2> /dev/null; then
local size_kb
size_kb=$(sudo du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0")
size_kb=$(sudo du -skP "$path" 2> /dev/null | awk '{print $1}' || echo "0")
if [[ "$size_kb" -gt 0 ]]; then
file_size=$(bytes_to_human "$((size_kb * 1024))")
fi
@@ -297,7 +297,7 @@ safe_sudo_remove() {
local size_human=""
if oplog_enabled; then
if sudo test -e "$path" 2> /dev/null; then
size_kb=$(sudo du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0")
size_kb=$(sudo du -skP "$path" 2> /dev/null | awk '{print $1}' || echo "0")
if [[ "$size_kb" =~ ^[0-9]+$ ]] && [[ "$size_kb" -gt 0 ]]; then
size_human=$(bytes_to_human "$((size_kb * 1024))" 2> /dev/null || echo "${size_kb}KB")
fi
@@ -418,7 +418,7 @@ get_path_size_kb() {
# Use || echo 0 to ensure failure in du (e.g. permission error) doesn't exit script under set -e
# Pipefail would normally cause the pipeline to fail if du fails, but || handle catches it.
local size
size=$(command du -sk "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
size=$(command du -skP "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
# Ensure size is a valid number (fix for non-numeric du output)
if [[ "$size" =~ ^[0-9]+$ ]]; then

View File

@@ -44,17 +44,25 @@ rotate_log_once() {
export MOLE_LOG_ROTATED=1
local max_size="$LOG_MAX_SIZE_DEFAULT"
if [[ -f "$LOG_FILE" ]] && [[ $(get_file_size "$LOG_FILE") -gt "$max_size" ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old" 2> /dev/null || true
ensure_user_file "$LOG_FILE"
if [[ -f "$LOG_FILE" ]]; then
local size
size=$(get_file_size "$LOG_FILE")
if [[ "$size" -gt "$max_size" ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old" 2> /dev/null || true
ensure_user_file "$LOG_FILE"
fi
fi
# Rotate operations log (5MB limit)
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
local oplog_max_size="$OPLOG_MAX_SIZE_DEFAULT"
if [[ -f "$OPERATIONS_LOG_FILE" ]] && [[ $(get_file_size "$OPERATIONS_LOG_FILE") -gt "$oplog_max_size" ]]; then
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2> /dev/null || true
ensure_user_file "$OPERATIONS_LOG_FILE"
if [[ -f "$OPERATIONS_LOG_FILE" ]]; then
local size
size=$(get_file_size "$OPERATIONS_LOG_FILE")
if [[ "$size" -gt "$oplog_max_size" ]]; then
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2> /dev/null || true
ensure_user_file "$OPERATIONS_LOG_FILE"
fi
fi
fi
}
@@ -63,10 +71,16 @@ rotate_log_once() {
# Logging Functions
# ============================================================================
# Get current timestamp (centralized for consistency)
get_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
# Log informational message
log_info() {
echo -e "${BLUE}$1${NC}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local timestamp
timestamp=$(get_timestamp)
echo "[$timestamp] INFO: $1" >> "$LOG_FILE" 2> /dev/null || true
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo "[$timestamp] INFO: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
@@ -76,38 +90,43 @@ log_info() {
# Log success message
log_success() {
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local timestamp
timestamp=$(get_timestamp)
echo "[$timestamp] SUCCESS: $1" >> "$LOG_FILE" 2> /dev/null || true
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo "[$timestamp] SUCCESS: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
fi
}
# Log warning message
# shellcheck disable=SC2329
log_warning() {
echo -e "${YELLOW}$1${NC}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local timestamp
timestamp=$(get_timestamp)
echo "[$timestamp] WARNING: $1" >> "$LOG_FILE" 2> /dev/null || true
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo "[$timestamp] WARNING: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
fi
}
# Log error message
# shellcheck disable=SC2329
log_error() {
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local timestamp
timestamp=$(get_timestamp)
echo "[$timestamp] ERROR: $1" >> "$LOG_FILE" 2> /dev/null || true
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo "[$timestamp] ERROR: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
fi
}
# Debug logging (active when MO_DEBUG=1)
# shellcheck disable=SC2329
debug_log() {
if [[ "${MO_DEBUG:-}" == "1" ]]; then
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
local timestamp
timestamp=$(get_timestamp)
echo "[$timestamp] DEBUG: $*" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
fi
}
@@ -139,7 +158,7 @@ log_operation() {
[[ -z "$path" ]] && return 0
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
timestamp=$(get_timestamp)
local log_line="[$timestamp] [$command] $action $path"
[[ -n "$detail" ]] && log_line+=" ($detail)"
@@ -154,7 +173,7 @@ log_operation_session_start() {
local command="${1:-mole}"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
timestamp=$(get_timestamp)
{
echo ""
@@ -162,8 +181,7 @@ log_operation_session_start() {
} >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
}
# Log session end with summary
# Usage: log_operation_session_end <command> <items_count> <total_size>
# shellcheck disable=SC2329
log_operation_session_end() {
oplog_enabled || return 0
@@ -171,7 +189,7 @@ log_operation_session_end() {
local items="${2:-0}"
local size="${3:-0}"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
timestamp=$(get_timestamp)
local size_human=""
if [[ "$size" =~ ^[0-9]+$ ]] && [[ "$size" -gt 0 ]]; then

View File

@@ -301,6 +301,9 @@ start_inline_spinner() {
[[ -z "$chars" ]] && chars="|/-\\"
local i=0
# Clear line on first output to prevent text remnants from previous messages
printf "\r\033[2K" >&2 || true
# Cooperative exit: check for stop file instead of relying on signals
while [[ ! -f "$stop_file" ]]; do
local c="${chars:$((i % ${#chars})):1}"