mirror of
https://github.com/tw93/Mole.git
synced 2026-02-09 01:29:19 +00:00
🎨 Updates for better use and support
This commit is contained in:
110
bin/clean.sh
110
bin/clean.sh
@@ -130,26 +130,74 @@ safe_clean() {
|
||||
local total_size_bytes=0
|
||||
local total_count=0
|
||||
|
||||
# Optimized: skip size calculation for empty checks, just try to delete
|
||||
# Size calculation is the slowest part - do it in parallel
|
||||
local -a existing_paths=()
|
||||
for path in "${targets[@]}"; do
|
||||
local size_bytes=0
|
||||
local count=0
|
||||
|
||||
if [[ -e "$path" ]]; then
|
||||
size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [[ "$count" -eq 0 || "$size_bytes" -eq 0 ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
rm -rf "$path" 2>/dev/null || true
|
||||
|
||||
((total_size_bytes += size_bytes))
|
||||
((total_count += count))
|
||||
removed_any=1
|
||||
fi
|
||||
[[ -e "$path" ]] && existing_paths+=("$path")
|
||||
done
|
||||
|
||||
if [[ ${#existing_paths[@]} -eq 0 ]]; then
|
||||
LAST_CLEAN_RESULT=0
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fast parallel processing for multiple targets
|
||||
if [[ ${#existing_paths[@]} -gt 3 ]]; then
|
||||
local temp_dir=$(mktemp -d)
|
||||
|
||||
# Launch parallel du jobs (bash 3.2 compatible)
|
||||
local -a pids=()
|
||||
for path in "${existing_paths[@]}"; do
|
||||
(
|
||||
local size=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo "$size $count" > "$temp_dir/$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)"
|
||||
) &
|
||||
pids+=($!)
|
||||
|
||||
# Limit to 15 parallel jobs (bash 3.2 compatible)
|
||||
if (( ${#pids[@]} >= 15 )); then
|
||||
wait "${pids[0]}" 2>/dev/null || true
|
||||
pids=("${pids[@]:1}")
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait for remaining jobs
|
||||
for pid in "${pids[@]}"; do
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Collect results and delete
|
||||
for path in "${existing_paths[@]}"; do
|
||||
local hash=$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)
|
||||
if [[ -f "$temp_dir/$hash" ]]; then
|
||||
read -r size count < "$temp_dir/$hash"
|
||||
if [[ "$count" -gt 0 && "$size" -gt 0 ]]; then
|
||||
rm -rf "$path" 2>/dev/null || true
|
||||
((total_size_bytes += size))
|
||||
((total_count += count))
|
||||
removed_any=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
else
|
||||
# Serial processing for few targets (faster than parallel overhead)
|
||||
for path in "${existing_paths[@]}"; do
|
||||
local size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [[ "$count" -gt 0 && "$size_bytes" -gt 0 ]]; then
|
||||
rm -rf "$path" 2>/dev/null || true
|
||||
((total_size_bytes += size_bytes))
|
||||
((total_count += count))
|
||||
removed_any=1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Only show output if something was actually cleaned
|
||||
if [[ $removed_any -eq 1 ]]; then
|
||||
local size_human
|
||||
@@ -178,7 +226,7 @@ safe_clean() {
|
||||
}
|
||||
|
||||
start_cleanup() {
|
||||
echo "Removing app caches, browser data, developer tools, and temporary files..."
|
||||
echo "Mole will remove app caches, browser data, developer tools, and temporary files."
|
||||
echo ""
|
||||
|
||||
# Check if we're in an interactive terminal
|
||||
@@ -191,13 +239,33 @@ start_cleanup() {
|
||||
else
|
||||
# Non-interactive mode - skip password prompt
|
||||
password=""
|
||||
log_info "Running in non-interactive mode, skipping system-level cleanup."
|
||||
echo ""
|
||||
echo -e "${BLUE}ℹ${NC} Running in non-interactive mode"
|
||||
echo " • System-level cleanup will be skipped (requires password)"
|
||||
echo " • User-level cleanup will proceed automatically"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
||||
SYSTEM_CLEAN=true
|
||||
# Start sudo keepalive with shorter intervals for reliability
|
||||
while true; do sudo -n true; sleep 30; kill -0 "$$" 2>/dev/null || exit; done 2>/dev/null &
|
||||
# Start sudo keepalive with error handling and shorter intervals
|
||||
(
|
||||
local retry_count=0
|
||||
while true; do
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
((retry_count++))
|
||||
if [[ $retry_count -ge 3 ]]; then
|
||||
log_warning "Sudo keepalive failed, system-level cleanup may be interrupted" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
retry_count=0
|
||||
sleep 30
|
||||
kill -0 "$$" 2>/dev/null || exit
|
||||
done
|
||||
) 2>/dev/null &
|
||||
SUDO_KEEPALIVE_PID=$!
|
||||
log_info "Starting comprehensive cleanup with admin privileges..."
|
||||
else
|
||||
|
||||
122
bin/uninstall.sh
122
bin/uninstall.sh
@@ -90,6 +90,30 @@ get_app_last_used() {
|
||||
|
||||
# Scan applications and collect information
|
||||
scan_applications() {
|
||||
# Cache configuration
|
||||
local cache_dir="$HOME/.cache/mole"
|
||||
local cache_file="$cache_dir/app_scan_cache"
|
||||
local cache_meta="$cache_dir/app_scan_meta"
|
||||
local cache_ttl=3600 # 1 hour cache validity
|
||||
|
||||
mkdir -p "$cache_dir" 2>/dev/null
|
||||
|
||||
# Quick count of current apps
|
||||
local current_app_count=$(find /Applications -name "*.app" -maxdepth 1 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
# Check if cache is valid
|
||||
if [[ -f "$cache_file" && -f "$cache_meta" ]]; then
|
||||
local cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || echo 0)))
|
||||
local cached_app_count=$(cat "$cache_meta" 2>/dev/null || echo "0")
|
||||
|
||||
# Cache is valid if: age < TTL AND app count matches
|
||||
if [[ $cache_age -lt $cache_ttl && "$cached_app_count" == "$current_app_count" ]]; then
|
||||
echo "Using cached app list (${cache_age}s old, $current_app_count apps) ✓" >&2
|
||||
echo "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
local temp_file=$(mktemp)
|
||||
|
||||
echo -n "Scanning... " >&2
|
||||
@@ -160,24 +184,9 @@ scan_applications() {
|
||||
|
||||
# Select the first (best) candidate
|
||||
display_name="${candidates[0]:-$app_name}"
|
||||
|
||||
# Brand name mapping for better user recognition (post-process)
|
||||
case "$display_name" in
|
||||
"qiyimac"|"爱奇艺") display_name="iQiyi" ;;
|
||||
"wechat"|"微信") display_name="WeChat" ;;
|
||||
"QQ"|"QQ") display_name="QQ" ;;
|
||||
"VooV Meeting"|"腾讯会议") display_name="VooV Meeting" ;;
|
||||
"dingtalk"|"钉钉") display_name="DingTalk" ;;
|
||||
"NeteaseMusic"|"网易云音乐") display_name="NetEase Music" ;;
|
||||
"BaiduNetdisk"|"百度网盘") display_name="Baidu NetDisk" ;;
|
||||
"alipay"|"支付宝") display_name="Alipay" ;;
|
||||
"taobao"|"淘宝") display_name="Taobao" ;;
|
||||
"futunn"|"富途牛牛") display_name="Futu NiuNiu" ;;
|
||||
"tencent lemon"|"Tencent Lemon Cleaner") display_name="Tencent Lemon" ;;
|
||||
"keynote"|"Keynote") display_name="Keynote" ;;
|
||||
"pages"|"Pages") display_name="Pages" ;;
|
||||
"numbers"|"Numbers") display_name="Numbers" ;;
|
||||
esac
|
||||
|
||||
# Apply brand name mapping from common.sh
|
||||
display_name="$(get_brand_name "$display_name")"
|
||||
fi
|
||||
|
||||
# Skip protected system apps early
|
||||
@@ -189,20 +198,21 @@ scan_applications() {
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}|${display_name}")
|
||||
done < <(find /Applications -name "*.app" -maxdepth 1 -print0 2>/dev/null)
|
||||
|
||||
# Second pass: process each app with accurate size calculation
|
||||
# Second pass: process each app with parallel size calculation
|
||||
local app_count=0
|
||||
local total_apps=${#app_data_tuples[@]}
|
||||
local max_parallel=10 # Process 10 apps in parallel
|
||||
local pids=()
|
||||
|
||||
# Process app metadata extraction function
|
||||
process_app_metadata() {
|
||||
local app_data_tuple="$1"
|
||||
local output_file="$2"
|
||||
local current_epoch="$3"
|
||||
|
||||
for app_data_tuple in "${app_data_tuples[@]}"; do
|
||||
IFS='|' read -r app_path app_name bundle_id display_name <<< "$app_data_tuple"
|
||||
|
||||
# Show progress every few items
|
||||
((app_count++))
|
||||
if (( app_count % 5 == 0 )) || [[ $app_count -eq $total_apps ]]; then
|
||||
echo -ne "\rScanning... $app_count/$total_apps" >&2
|
||||
fi
|
||||
|
||||
# Accurate size calculation - this is what takes time but user wants it
|
||||
# Parallel size calculation
|
||||
local app_size="N/A"
|
||||
if [[ -d "$app_path" ]]; then
|
||||
app_size=$(du -sh "$app_path" 2>/dev/null | cut -f1 || echo "N/A")
|
||||
@@ -216,7 +226,6 @@ scan_applications() {
|
||||
local metadata_date=$(mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null)
|
||||
|
||||
if [[ "$metadata_date" != "(null)" && -n "$metadata_date" ]]; then
|
||||
# Convert macOS date format to epoch
|
||||
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2>/dev/null || echo "0")
|
||||
|
||||
if [[ $last_used_epoch -gt 0 ]]; then
|
||||
@@ -230,29 +239,17 @@ scan_applications() {
|
||||
last_used="${days_ago} days ago"
|
||||
elif [[ $days_ago -lt 30 ]]; then
|
||||
local weeks_ago=$(( days_ago / 7 ))
|
||||
if [[ $weeks_ago -eq 1 ]]; then
|
||||
last_used="1 week ago"
|
||||
else
|
||||
last_used="${weeks_ago} weeks ago"
|
||||
fi
|
||||
[[ $weeks_ago -eq 1 ]] && last_used="1 week ago" || last_used="${weeks_ago} weeks ago"
|
||||
elif [[ $days_ago -lt 365 ]]; then
|
||||
local months_ago=$(( days_ago / 30 ))
|
||||
if [[ $months_ago -eq 1 ]]; then
|
||||
last_used="1 month ago"
|
||||
else
|
||||
last_used="${months_ago} months ago"
|
||||
fi
|
||||
[[ $months_ago -eq 1 ]] && last_used="1 month ago" || last_used="${months_ago} months ago"
|
||||
else
|
||||
local years_ago=$(( days_ago / 365 ))
|
||||
if [[ $years_ago -eq 1 ]]; then
|
||||
last_used="1 year ago"
|
||||
else
|
||||
last_used="${years_ago} years ago"
|
||||
fi
|
||||
[[ $years_ago -eq 1 ]] && last_used="1 year ago" || last_used="${years_ago} years ago"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Fallback to file modification time if no usage metadata
|
||||
# Fallback to file modification time
|
||||
last_used_epoch=$(stat -f%m "$app_path" 2>/dev/null || echo "0")
|
||||
if [[ $last_used_epoch -gt 0 ]]; then
|
||||
local days_ago=$(( (current_epoch - last_used_epoch) / 86400 ))
|
||||
@@ -267,8 +264,33 @@ scan_applications() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Format: epoch|app_path|display_name|bundle_id|size|last_used_display
|
||||
echo "${last_used_epoch}|${app_path}|${display_name}|${bundle_id}|${app_size}|${last_used}" >> "$temp_file"
|
||||
# Write to output file atomically
|
||||
echo "${last_used_epoch}|${app_path}|${display_name}|${bundle_id}|${app_size}|${last_used}" >> "$output_file"
|
||||
}
|
||||
|
||||
export -f process_app_metadata
|
||||
|
||||
# Process apps in parallel batches
|
||||
for app_data_tuple in "${app_data_tuples[@]}"; do
|
||||
((app_count++))
|
||||
|
||||
# Launch background process
|
||||
process_app_metadata "$app_data_tuple" "$temp_file" "$current_epoch" &
|
||||
pids+=($!)
|
||||
|
||||
# Update progress
|
||||
echo -ne "\rScanning... $app_count/$total_apps" >&2
|
||||
|
||||
# Wait if we've hit max parallel limit
|
||||
if (( ${#pids[@]} >= max_parallel )); then
|
||||
wait "${pids[0]}" 2>/dev/null
|
||||
pids=("${pids[@]:1}") # Remove first pid
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait for remaining background processes
|
||||
for pid in "${pids[@]}"; do
|
||||
wait "$pid" 2>/dev/null
|
||||
done
|
||||
|
||||
echo -e "\rFound $app_count applications ✓" >&2
|
||||
@@ -279,9 +301,13 @@ scan_applications() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sort by last used (oldest first) and return the temp file path
|
||||
# Sort by last used (oldest first) and cache the result
|
||||
sort -t'|' -k1,1n "$temp_file" > "${temp_file}.sorted"
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Update cache with app count metadata
|
||||
cp "${temp_file}.sorted" "$cache_file" 2>/dev/null || true
|
||||
echo "$current_app_count" > "$cache_meta" 2>/dev/null || true
|
||||
echo "${temp_file}.sorted"
|
||||
}
|
||||
|
||||
@@ -479,7 +505,7 @@ main() {
|
||||
fi
|
||||
|
||||
echo ""
|
||||
# 直接执行批量卸载,确认已在批量卸载函数中处理
|
||||
# Execute batch uninstallation, confirmation handled in batch_uninstall_applications
|
||||
batch_uninstall_applications
|
||||
|
||||
# Cleanup
|
||||
|
||||
@@ -40,6 +40,8 @@ select_apps_for_uninstall() {
|
||||
echo ""
|
||||
echo "🗑️ App Uninstaller"
|
||||
echo ""
|
||||
echo "Mole will uninstall selected apps and clean all their related files."
|
||||
echo ""
|
||||
echo "Found ${#apps_data[@]} apps. Select apps to remove:"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ batch_uninstall_applications() {
|
||||
local total_estimated_size=0
|
||||
local -a app_details=()
|
||||
|
||||
echo "📋 Analyzing selected applications..."
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 Analyzing selected applications...${NC}"
|
||||
for selected_app in "${selected_apps[@]}"; do
|
||||
IFS='|' read -r epoch app_path app_name bundle_id size last_used <<< "$selected_app"
|
||||
|
||||
@@ -51,22 +52,24 @@ batch_uninstall_applications() {
|
||||
|
||||
# Show summary and get batch confirmation
|
||||
echo ""
|
||||
echo "Will remove ${#selected_apps[@]} applications, free $size_display"
|
||||
echo -e "${YELLOW}📦 Will remove ${BLUE}${#selected_apps[@]}${YELLOW} applications, free ${GREEN}$size_display${NC}"
|
||||
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
||||
echo "Running apps will be force-quit: ${running_apps[*]}"
|
||||
echo -e "${YELLOW}⚠️ Running apps will be force-quit: ${RED}${running_apps[*]}${NC}"
|
||||
fi
|
||||
echo ""
|
||||
read -p "Press ENTER to confirm, or any other key to cancel: " -r
|
||||
echo -e -n "${BLUE}Press ENTER to confirm, or any other key to cancel:${NC} "
|
||||
read -r
|
||||
|
||||
if [[ -n "$REPLY" ]]; then
|
||||
log_info "Uninstallation cancelled by user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "⚡ Starting uninstallation in 3 seconds... (Press Ctrl+C to abort)"
|
||||
sleep 1 && echo "⚡ 2..."
|
||||
sleep 1 && echo "⚡ 1..."
|
||||
echo -e "${PURPLE}⚡ Starting uninstallation in 3 seconds...${NC} ${YELLOW}(Press Ctrl+C to abort)${NC}"
|
||||
sleep 1 && echo -e "${PURPLE}⚡ ${BLUE}2${PURPLE}...${NC}"
|
||||
sleep 1 && echo -e "${PURPLE}⚡ ${BLUE}1${PURPLE}...${NC}"
|
||||
sleep 1
|
||||
echo -e "${GREEN}✨ Let's go!${NC}"
|
||||
|
||||
# Force quit running apps first (batch)
|
||||
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
||||
@@ -92,7 +95,7 @@ batch_uninstall_applications() {
|
||||
# Decode the related files list
|
||||
local related_files=$(echo "$encoded_files" | base64 -d)
|
||||
|
||||
echo "🗑️ Uninstalling: $app_name"
|
||||
echo -e "${YELLOW}🗑️ Uninstalling: ${BLUE}$app_name${NC}"
|
||||
|
||||
# Remove the application
|
||||
if rm -rf "$app_path" 2>/dev/null; then
|
||||
@@ -127,24 +130,24 @@ batch_uninstall_applications() {
|
||||
echo ""
|
||||
echo "===================================================================="
|
||||
echo "🎉 UNINSTALLATION COMPLETE!"
|
||||
|
||||
|
||||
if [[ $success_count -gt 0 ]]; then
|
||||
if [[ $total_size_freed -gt 1048576 ]]; then
|
||||
local freed_display=$(echo "$total_size_freed" | awk '{printf "%.1fGB", $1/1024/1024}')
|
||||
local freed_display=$(echo "$total_size_freed" | awk '{printf "%.2fGB", $1/1024/1024}')
|
||||
elif [[ $total_size_freed -gt 1024 ]]; then
|
||||
local freed_display=$(echo "$total_size_freed" | awk '{printf "%.1fMB", $1/1024}')
|
||||
else
|
||||
local freed_display="${total_size_freed}KB"
|
||||
fi
|
||||
echo "🗑️ Apps uninstalled: $success_count | Space freed: $freed_display"
|
||||
echo "🗑️ Apps uninstalled: $success_count | Space freed: ${GREEN}${freed_display}${NC}"
|
||||
else
|
||||
echo "🗑️ No applications were uninstalled"
|
||||
fi
|
||||
|
||||
|
||||
if [[ $failed_count -gt 0 ]]; then
|
||||
echo "⚠️ Failed to uninstall: $failed_count"
|
||||
echo -e "${RED}⚠️ Failed to uninstall: $failed_count${NC}"
|
||||
fi
|
||||
|
||||
|
||||
echo "===================================================================="
|
||||
if [[ $failed_count -gt 0 ]]; then
|
||||
log_warning "$failed_count applications failed to uninstall"
|
||||
|
||||
@@ -29,7 +29,7 @@ log_info() {
|
||||
|
||||
log_success() {
|
||||
rotate_log
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
echo -e " ${GREEN}✓${NC} $1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
@@ -116,7 +116,8 @@ read_key() {
|
||||
*) echo "OTHER" ;;
|
||||
esac
|
||||
else
|
||||
echo "OTHER"
|
||||
# ESC pressed alone - treat as quit
|
||||
echo "QUIT"
|
||||
fi
|
||||
;;
|
||||
*) echo "OTHER" ;;
|
||||
@@ -253,15 +254,22 @@ readonly PRESERVED_BUNDLE_PATTERNS=(
|
||||
".GlobalPreferences"
|
||||
|
||||
# Input methods (critical for international users)
|
||||
"com.tencent.inputmethod.*"
|
||||
"com.sogou.*"
|
||||
"com.baidu.*"
|
||||
"*.inputmethod.*"
|
||||
"*input*"
|
||||
"*inputmethod*"
|
||||
"*InputMethod*"
|
||||
"*ime*"
|
||||
"*IME*"
|
||||
# Specific input method bundles
|
||||
"com.tencent.inputmethod.QQInput"
|
||||
"com.sogou.inputmethod.*"
|
||||
"com.baidu.inputmethod.*"
|
||||
"com.apple.inputmethod.*"
|
||||
"com.googlecode.rimeime.*"
|
||||
"im.rime.*"
|
||||
"org.pqrs.Karabiner*"
|
||||
# Generic patterns (more conservative)
|
||||
"*.inputmethod"
|
||||
"*.InputMethod"
|
||||
"*IME"
|
||||
# Keep system input services safe
|
||||
"com.apple.inputsource*"
|
||||
"com.apple.TextInputMenuAgent"
|
||||
"com.apple.TextInputSwitcher"
|
||||
|
||||
# Cleanup and system tools (avoid infinite loops and preserve licenses)
|
||||
"com.nektony.*" # App Cleaner & Uninstaller
|
||||
@@ -382,3 +390,27 @@ calculate_total_size() {
|
||||
|
||||
echo "$total_kb"
|
||||
}
|
||||
|
||||
# Get normalized brand name (bash 3.2 compatible using case statement)
|
||||
get_brand_name() {
|
||||
local name="$1"
|
||||
|
||||
# Brand name mapping for better user recognition
|
||||
case "$name" in
|
||||
"qiyimac"|"爱奇艺") echo "iQiyi" ;;
|
||||
"wechat"|"微信") echo "WeChat" ;;
|
||||
"QQ") echo "QQ" ;;
|
||||
"VooV Meeting"|"腾讯会议") echo "VooV Meeting" ;;
|
||||
"dingtalk"|"钉钉") echo "DingTalk" ;;
|
||||
"NeteaseMusic"|"网易云音乐") echo "NetEase Music" ;;
|
||||
"BaiduNetdisk"|"百度网盘") echo "Baidu NetDisk" ;;
|
||||
"alipay"|"支付宝") echo "Alipay" ;;
|
||||
"taobao"|"淘宝") echo "Taobao" ;;
|
||||
"futunn"|"富途牛牛") echo "Futu NiuNiu" ;;
|
||||
"tencent lemon"|"Tencent Lemon Cleaner") echo "Tencent Lemon" ;;
|
||||
"keynote"|"Keynote") echo "Keynote" ;;
|
||||
"pages"|"Pages") echo "Pages" ;;
|
||||
"numbers"|"Numbers") echo "Numbers" ;;
|
||||
*) echo "$name" ;; # Return original if no mapping found
|
||||
esac
|
||||
}
|
||||
|
||||
80
mole
80
mole
@@ -21,11 +21,72 @@ source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Version info
|
||||
VERSION="1.1.0"
|
||||
MOLE_TAGLINE="Dig deep like a mole to clean your Mac."
|
||||
MOLE_TAGLINE="can dig deep to clean your Mac."
|
||||
|
||||
# Check for updates (non-blocking, cached)
|
||||
check_for_updates() {
|
||||
local cache_dir="$HOME/.cache/mole"
|
||||
local version_cache="$cache_dir/version_check"
|
||||
local check_interval=86400 # Check once per day (24 hours)
|
||||
|
||||
mkdir -p "$cache_dir" 2>/dev/null
|
||||
|
||||
# Check if we should run version check (based on cache age)
|
||||
if [[ -f "$version_cache" ]]; then
|
||||
local cache_age=$(($(date +%s) - $(stat -f%m "$version_cache" 2>/dev/null || echo 0)))
|
||||
if [[ $cache_age -lt $check_interval ]]; then
|
||||
# Cache is still fresh, show cached message if exists
|
||||
if [[ -s "$version_cache" ]]; then
|
||||
cat "$version_cache"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run version check in background (non-blocking)
|
||||
(
|
||||
local latest_version=""
|
||||
local timeout=3 # 3 second timeout for version check
|
||||
|
||||
# Try to fetch latest version from GitHub with timeout
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
latest_version=$(curl -fsSL --connect-timeout 2 --max-time $timeout \
|
||||
"https://api.github.com/repos/tw93/mole/releases/latest" 2>/dev/null | \
|
||||
grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/^[Vv]//')
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
latest_version=$(wget -qO- --timeout=$timeout --tries=1 \
|
||||
"https://api.github.com/repos/tw93/mole/releases/latest" 2>/dev/null | \
|
||||
grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/^[Vv]//')
|
||||
fi
|
||||
|
||||
# Compare versions if fetch succeeded
|
||||
if [[ -n "$latest_version" && "$latest_version" != "$VERSION" ]]; then
|
||||
# Version mismatch - cache the update message
|
||||
local msg="${YELLOW}📢 New version available: ${GREEN}${latest_version}${YELLOW} (current: ${VERSION})${NC}\n Run ${GREEN}mole update${YELLOW} to upgrade${NC}"
|
||||
echo -e "$msg" > "$version_cache"
|
||||
echo -e "$msg"
|
||||
else
|
||||
# Up to date or check failed - clear cache
|
||||
echo "" > "$version_cache"
|
||||
fi
|
||||
|
||||
# Touch cache file to update timestamp
|
||||
touch "$version_cache" 2>/dev/null
|
||||
) &
|
||||
|
||||
# Don't wait for background check
|
||||
disown 2>/dev/null || true
|
||||
}
|
||||
|
||||
show_brand_banner() {
|
||||
printf '%b🦡 Mole — %s%b\n' \
|
||||
"$GREEN" "$MOLE_TAGLINE" "$NC"
|
||||
cat << EOF
|
||||
${GREEN} __ __ _ ${NC}
|
||||
${GREEN}| \/ | ___ | | ___ ${NC}
|
||||
${GREEN}| |\/| |/ _ \| |/ _ \\${NC}
|
||||
${GREEN}| | | | (_) | | __/${NC} ${BLUE}https://github.com/tw93/mole${NC}
|
||||
${GREEN}|_| |_|\___/|_|\___|${NC} ${GREEN}${MOLE_TAGLINE}${NC}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
show_version() {
|
||||
@@ -58,17 +119,17 @@ update_mole() {
|
||||
local tmp_installer
|
||||
tmp_installer="$(mktemp)" || { log_error "Failed to create temp file"; exit 1; }
|
||||
|
||||
# Download installer
|
||||
# Download installer with timeout
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if ! curl -fsSL "$installer_url" -o "$tmp_installer"; then
|
||||
if ! curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer"; then
|
||||
rm -f "$tmp_installer"
|
||||
log_error "Failed to download installer"
|
||||
log_error "Failed to download installer (network timeout or error)"
|
||||
exit 1
|
||||
fi
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
if ! wget -qO "$tmp_installer" "$installer_url"; then
|
||||
if ! wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url"; then
|
||||
rm -f "$tmp_installer"
|
||||
log_error "Failed to download installer"
|
||||
log_error "Failed to download installer (network timeout or error)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
@@ -183,6 +244,9 @@ interactive_main_menu() {
|
||||
}
|
||||
|
||||
main() {
|
||||
# Check for updates (non-blocking, won't delay startup)
|
||||
check_for_updates
|
||||
|
||||
case "${1:-""}" in
|
||||
"clean")
|
||||
exec "$SCRIPT_DIR/bin/clean.sh"
|
||||
|
||||
Reference in New Issue
Block a user