From 238670189778c4bfcc51912f8198a723df7b16ed Mon Sep 17 00:00:00 2001 From: Tw93 Date: Mon, 8 Dec 2025 17:40:54 +0800 Subject: [PATCH] Fix search problems and best practices --- bin/clean.sh | 5 ++- bin/uninstall.sh | 11 +++-- lib/core/common.sh | 90 ++++++++++++++++++++++++++++++++++++++++ lib/core/ui.sh | 3 ++ lib/manage/whitelist.sh | 30 ++++++++++++-- lib/ui/menu_paginated.sh | 16 ++----- lib/uninstall/batch.sh | 20 +++++++-- 7 files changed, 151 insertions(+), 24 deletions(-) diff --git a/bin/clean.sh b/bin/clean.sh index 6d3c51e..b8605a2 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -456,7 +456,10 @@ safe_clean() { } start_cleanup() { - clear + if [[ -t 1 ]]; then + # Avoid relying on TERM since CI often runs without it + printf '\033[2J\033[H' + fi printf '\n' echo -e "${PURPLE_BOLD}Clean Your Mac${NC}" echo "" diff --git a/bin/uninstall.sh b/bin/uninstall.sh index ef2398b..b30f136 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -73,11 +73,12 @@ scan_applications() { local cache_dir="$HOME/.cache/mole" local cache_file="$cache_dir/app_scan_cache" local cache_ttl=86400 # 24 hours + local force_rescan="${1:-false}" mkdir -p "$cache_dir" 2> /dev/null # Check if cache exists and is fresh - if [[ -f "$cache_file" ]]; then + if [[ $force_rescan == false && -f "$cache_file" ]]; then local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file if [[ $cache_age -lt $cache_ttl ]]; then @@ -405,11 +406,15 @@ trap cleanup EXIT INT TERM # Main function main() { # Parse args + local force_rescan=false for arg in "$@"; do case "$arg" in "--debug") export MO_DEBUG=1 ;; + "--force-rescan") + force_rescan=true + ;; esac done @@ -425,7 +430,7 @@ main() { # (scan_applications handles cache internally) local needs_scanning=true local cache_file="$HOME/.cache/mole/app_scan_cache" - if [[ -f "$cache_file" ]]; then + if [[ $force_rescan == false && -f "$cache_file" ]]; then local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file [[ $cache_age -lt 86400 ]] && needs_scanning=false @@ -444,7 +449,7 @@ main() { # Scan applications local apps_file="" - if ! apps_file=$(scan_applications); then + if ! apps_file=$(scan_applications "$force_rescan"); then if [[ "${MOLE_ALT_SCREEN_ACTIVE:-}" == "1" ]]; then printf "\033[2J\033[H" >&2 leave_alt_screen diff --git a/lib/core/common.sh b/lib/core/common.sh index 9faf180..7c05663 100755 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -73,3 +73,93 @@ update_via_homebrew() { # Clear update cache 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_apps_from_dock() { + if [[ $# -eq 0 ]]; then + return 0 + fi + + local plist="$HOME/Library/Preferences/com.apple.dock.plist" + [[ -f "$plist" ]] || return 0 + + if ! command -v python3 > /dev/null 2>&1; then + return 0 + fi + + # Execute Python helper to prune dock entries for the given app paths + python3 - "$@" << 'PY' 2>/dev/null || return 0 +import os +import plistlib +import subprocess +import sys +import urllib.parse + +plist_path = os.path.expanduser('~/Library/Preferences/com.apple.dock.plist') +if not os.path.exists(plist_path): + sys.exit(0) + +def normalise(path): + if not path: + return '' + return os.path.normpath(os.path.realpath(path.rstrip('/'))) + +targets = {normalise(arg) for arg in sys.argv[1:] if arg} +targets = {t for t in targets if t} +if not targets: + sys.exit(0) + +with open(plist_path, 'rb') as fh: + try: + data = plistlib.load(fh) + except Exception: + sys.exit(0) + +apps = data.get('persistent-apps') +if not isinstance(apps, list): + sys.exit(0) + +changed = False +filtered = [] +for item in apps: + try: + url = item['tile-data']['file-data']['_CFURLString'] + except (KeyError, TypeError): + filtered.append(item) + continue + + if not isinstance(url, str): + filtered.append(item) + continue + + parsed = urllib.parse.urlparse(url) + path = urllib.parse.unquote(parsed.path or '') + if not path: + filtered.append(item) + continue + + candidate = normalise(path) + if any(candidate == t or candidate.startswith(t + os.sep) for t in targets): + changed = True + continue + + filtered.append(item) + +if not changed: + sys.exit(0) + +data['persistent-apps'] = filtered +with open(plist_path, 'wb') as fh: + try: + plistlib.dump(data, fh, fmt=plistlib.FMT_BINARY) + except Exception: + plistlib.dump(data, fh) + +# Restart Dock to apply changes +try: + subprocess.run(['killall', 'Dock'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) +except Exception: + pass +PY +} diff --git a/lib/core/ui.sh b/lib/core/ui.sh index fb4589e..4532854 100755 --- a/lib/core/ui.sh +++ b/lib/core/ui.sh @@ -49,6 +49,9 @@ read_key() { case "$key" in $'\n' | $'\r') echo "ENTER" ;; ' ') echo "SPACE" ;; + '/') echo "FILTER" ;; + 'q' | 'Q') echo "QUIT" ;; + 'R') echo "RETRY" ;; $'\x03') echo "QUIT" ;; $'\x7f' | $'\x08') echo "DELETE" ;; $'\x1b') diff --git a/lib/manage/whitelist.sh b/lib/manage/whitelist.sh index 1d49d59..c99f68a 100755 --- a/lib/manage/whitelist.sh +++ b/lib/manage/whitelist.sh @@ -11,16 +11,25 @@ source "$_MOLE_MANAGE_DIR/../ui/menu_simple.sh" # Config file paths readonly WHITELIST_CONFIG_CLEAN="$HOME/.config/mole/whitelist" -readonly WHITELIST_CONFIG_OPTIMIZE="$HOME/.config/mole/whitelist_checks" +readonly WHITELIST_CONFIG_OPTIMIZE="$HOME/.config/mole/whitelist_optimize" +readonly WHITELIST_CONFIG_OPTIMIZE_LEGACY="$HOME/.config/mole/whitelist_checks" # Default whitelist patterns defined in lib/core/common.sh: # - DEFAULT_WHITELIST_PATTERNS # - FINDER_METADATA_SENTINEL -# Save whitelist patterns to config +# Save whitelist patterns to config (defaults to "clean" for legacy callers) save_whitelist_patterns() { - local mode="$1" - shift + local mode="clean" + if [[ $# -gt 0 ]]; then + case "$1" in + clean | optimize) + mode="$1" + shift + ;; + esac + fi + local -a patterns patterns=("$@") @@ -170,13 +179,21 @@ load_whitelist() { local mode="${1:-clean}" local -a patterns=() local config_file + local legacy_file="" if [[ "$mode" == "optimize" ]]; then config_file="$WHITELIST_CONFIG_OPTIMIZE" + legacy_file="$WHITELIST_CONFIG_OPTIMIZE_LEGACY" else config_file="$WHITELIST_CONFIG_CLEAN" fi + local using_legacy="false" + if [[ ! -f "$config_file" && -n "$legacy_file" && -f "$legacy_file" ]]; then + config_file="$legacy_file" + using_legacy="true" + fi + if [[ -f "$config_file" ]]; then while IFS= read -r line; do # shellcheck disable=SC2295 @@ -210,6 +227,11 @@ load_whitelist() { unique_patterns+=("$pattern") done CURRENT_WHITELIST_PATTERNS=("${unique_patterns[@]}") + + # Migrate legacy optimize config to the new path automatically + if [[ "$mode" == "optimize" && "$using_legacy" == "true" && "$config_file" != "$WHITELIST_CONFIG_OPTIMIZE" ]]; then + save_whitelist_patterns "$mode" "${CURRENT_WHITELIST_PATTERNS[@]}" + fi else CURRENT_WHITELIST_PATTERNS=() fi diff --git a/lib/ui/menu_paginated.sh b/lib/ui/menu_paginated.sh index 01459e0..40c5cab 100755 --- a/lib/ui/menu_paginated.sh +++ b/lib/ui/menu_paginated.sh @@ -399,7 +399,7 @@ paginated_multi_select() { for ((i = 0; i < items_per_page; i++)); do printf "${clear_line}\n" >&2 done - printf "${clear_line}${GRAY}Type to filter | Delete | Enter | / Exit | ESC${NC}\n" >&2 + printf "${clear_line}${GRAY}Type to filter | Delete | Enter Confirm | ESC Cancel${NC}\n" >&2 printf "${clear_line}" >&2 return else @@ -480,9 +480,8 @@ paginated_multi_select() { local -a _segs_filter=( "${GRAY}Filter: ${filter_status}${NC}" "${GRAY}Delete${NC}" - "${GRAY}Enter${NC}" - "${GRAY}/ Exit${NC}" - "${GRAY}ESC${NC}" + "${GRAY}Enter Confirm${NC}" + "${GRAY}ESC Cancel${NC}" ) _print_wrapped_controls "$sep" "${_segs_filter[@]}" else @@ -668,15 +667,8 @@ paginated_multi_select() { CHAR:*) if [[ "$filter_mode" == "true" ]]; then local ch="${key#CHAR:}" - # Special handling for /: exit filter mode - if [[ "$ch" == "/" ]]; then - filter_mode="false" - unset MOLE_READ_KEY_FORCE_CHAR - filter_query="" - applied_query="" - rebuild_view # avoid accidental leading spaces - elif [[ -n "$filter_query" || "$ch" != " " ]]; then + if [[ -n "$filter_query" || "$ch" != " " ]]; then filter_query+="$ch" fi fi diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index 368d530..6bc3ff2 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -113,7 +113,6 @@ batch_uninstall_applications() { local -a sudo_apps=() local total_estimated_size=0 local -a app_details=() - local -a dock_cleanup_paths=() # Analyze selected apps with progress indicator if [[ -t 1 ]]; then start_inline_spinner "Scanning files..."; fi @@ -315,7 +314,6 @@ batch_uninstall_applications() { ((files_cleaned++)) ((total_items++)) success_items+=("$app_name") - dock_cleanup_paths+=("$app_path") else ((failed_count++)) failed_items+=("$app_name:$reason") @@ -406,8 +404,22 @@ batch_uninstall_applications() { print_summary_block "$summary_status" "Uninstall complete" "${summary_details[@]}" printf '\n' - if [[ ${#dock_cleanup_paths[@]} -gt 0 ]]; then - remove_apps_from_dock "${dock_cleanup_paths[@]}" + # Clean up Dock entries for uninstalled apps + if [[ $success_count -gt 0 ]]; then + local -a removed_paths=() + for detail in "${app_details[@]}"; do + IFS='|' read -r app_name app_path _ _ _ _ <<< "$detail" + # Check if this app was successfully removed + for success_name in "${success_items[@]}"; do + if [[ "$success_name" == "$app_name" ]]; then + removed_paths+=("$app_path") + break + fi + done + done + if [[ ${#removed_paths[@]} -gt 0 ]]; then + remove_apps_from_dock "${removed_paths[@]}" 2>/dev/null || true + fi fi # Clean up sudo keepalive if it was started