mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 19:44:44 +00:00
- Add Homebrew cask detection and use 'brew uninstall --cask' for proper cleanup - Add real-time progress feedback during uninstallation - Optimize scroll performance by only redrawing visible items - Replace Python-based Dock removal with PlistBuddy for better compatibility - Add comprehensive tests for Homebrew functionality Fixes #306
218 lines
7.1 KiB
Bash
Executable File
218 lines
7.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Mole - Common Functions Library
|
|
# Main entry point that loads all core modules
|
|
|
|
set -euo pipefail
|
|
|
|
# Prevent multiple sourcing
|
|
if [[ -n "${MOLE_COMMON_LOADED:-}" ]]; then
|
|
return 0
|
|
fi
|
|
readonly MOLE_COMMON_LOADED=1
|
|
|
|
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Load core modules
|
|
source "$_MOLE_CORE_DIR/base.sh"
|
|
source "$_MOLE_CORE_DIR/log.sh"
|
|
|
|
source "$_MOLE_CORE_DIR/timeout.sh"
|
|
source "$_MOLE_CORE_DIR/file_ops.sh"
|
|
source "$_MOLE_CORE_DIR/ui.sh"
|
|
source "$_MOLE_CORE_DIR/app_protection.sh"
|
|
|
|
# Load sudo management if available
|
|
if [[ -f "$_MOLE_CORE_DIR/sudo.sh" ]]; then
|
|
source "$_MOLE_CORE_DIR/sudo.sh"
|
|
fi
|
|
|
|
# Update via Homebrew
|
|
update_via_homebrew() {
|
|
local current_version="$1"
|
|
local temp_update temp_upgrade
|
|
temp_update=$(mktemp_file "brew_update")
|
|
temp_upgrade=$(mktemp_file "brew_upgrade")
|
|
|
|
# Set up trap for interruption (Ctrl+C) with inline cleanup
|
|
trap 'stop_inline_spinner 2>/dev/null; safe_remove "$temp_update" true; safe_remove "$temp_upgrade" true; echo ""; exit 130' INT TERM
|
|
|
|
# Update Homebrew
|
|
if [[ -t 1 ]]; then
|
|
start_inline_spinner "Updating Homebrew..."
|
|
else
|
|
echo "Updating Homebrew..."
|
|
fi
|
|
|
|
brew update > "$temp_update" 2>&1 &
|
|
local update_pid=$!
|
|
wait $update_pid 2> /dev/null || true # Continue even if brew update fails
|
|
|
|
if [[ -t 1 ]]; then
|
|
stop_inline_spinner
|
|
fi
|
|
|
|
# Upgrade Mole
|
|
if [[ -t 1 ]]; then
|
|
start_inline_spinner "Upgrading Mole..."
|
|
else
|
|
echo "Upgrading Mole..."
|
|
fi
|
|
|
|
brew upgrade mole > "$temp_upgrade" 2>&1 &
|
|
local upgrade_pid=$!
|
|
wait $upgrade_pid 2> /dev/null || true # Continue even if brew upgrade fails
|
|
|
|
local upgrade_output
|
|
upgrade_output=$(cat "$temp_upgrade")
|
|
|
|
if [[ -t 1 ]]; then
|
|
stop_inline_spinner
|
|
fi
|
|
|
|
# Clear trap
|
|
trap - INT TERM
|
|
|
|
# Cleanup temp files
|
|
safe_remove "$temp_update" true
|
|
safe_remove "$temp_upgrade" true
|
|
|
|
if echo "$upgrade_output" | grep -q "already installed"; then
|
|
local installed_version
|
|
installed_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
|
|
echo ""
|
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${installed_version:-$current_version})"
|
|
echo ""
|
|
elif echo "$upgrade_output" | grep -q "Error:"; then
|
|
log_error "Homebrew upgrade failed"
|
|
echo "$upgrade_output" | grep "Error:" >&2
|
|
return 1
|
|
else
|
|
echo "$upgrade_output" | grep -Ev "^(==>|Updating Homebrew|Warning:)" || true
|
|
local new_version
|
|
new_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
|
|
echo ""
|
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-$current_version})"
|
|
echo ""
|
|
fi
|
|
|
|
# Clear update cache (suppress errors if cache doesn't exist or is locked)
|
|
rm -f "$HOME/.cache/mole/version_check" "$HOME/.cache/mole/update_message" 2> /dev/null || true
|
|
}
|
|
|
|
# Get Homebrew cask name for an application bundle
|
|
get_brew_cask_name() {
|
|
local app_path="$1"
|
|
[[ -z "$app_path" || ! -d "$app_path" ]] && return 1
|
|
|
|
# Check if brew command exists
|
|
command -v brew > /dev/null 2>&1 || return 1
|
|
|
|
local app_bundle_name
|
|
app_bundle_name=$(basename "$app_path")
|
|
|
|
# 1. Search in Homebrew Caskroom for the app bundle (most reliable for name mismatches)
|
|
# Checks /opt/homebrew (Apple Silicon) and /usr/local (Intel)
|
|
# Note: Modern Homebrew uses symlinks in Caskroom, not directories
|
|
local cask_match
|
|
for room in "/opt/homebrew/Caskroom" "/usr/local/Caskroom"; do
|
|
[[ -d "$room" ]] || continue
|
|
# Path is room/token/version/App.app (can be directory or symlink)
|
|
cask_match=$(find "$room" -maxdepth 3 -name "$app_bundle_name" 2> /dev/null | head -1 || echo "")
|
|
if [[ -n "$cask_match" ]]; then
|
|
local relative="${cask_match#$room/}"
|
|
echo "${relative%%/*}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
# 2. Check for symlink from Caskroom
|
|
if [[ -L "$app_path" ]]; then
|
|
local target
|
|
target=$(readlink "$app_path")
|
|
for room in "/opt/homebrew/Caskroom" "/usr/local/Caskroom"; do
|
|
if [[ "$target" == "$room/"* ]]; then
|
|
local relative="${target#$room/}"
|
|
echo "${relative%%/*}"
|
|
return 0
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# 3. Fallback: Direct list check (handles some cases where app is moved)
|
|
local app_name_only="${app_bundle_name%.app}"
|
|
local cask_name
|
|
cask_name=$(brew list --cask 2> /dev/null | grep -Fx "$(echo "$app_name_only" | LC_ALL=C tr '[:upper:]' '[:lower:]')" || echo "")
|
|
if [[ -n "$cask_name" ]]; then
|
|
if brew info --cask "$cask_name" 2> /dev/null | grep -q "$app_path"; then
|
|
echo "$cask_name"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Remove applications from Dock
|
|
remove_apps_from_dock() {
|
|
if [[ $# -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local -a targets=()
|
|
for arg in "$@"; do
|
|
[[ -n "$arg" ]] && targets+=("$arg")
|
|
done
|
|
|
|
if [[ ${#targets[@]} -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Use pure shell (PlistBuddy) to remove items from Dock
|
|
# This avoids dependencies on Python 3 or osascript (AppleScript)
|
|
local plist="$HOME/Library/Preferences/com.apple.dock.plist"
|
|
[[ -f "$plist" ]] || return 0
|
|
|
|
command -v PlistBuddy > /dev/null 2>&1 || return 0
|
|
|
|
local changed=false
|
|
for target in "${targets[@]}"; do
|
|
local app_path="$target"
|
|
local app_name
|
|
app_name=$(basename "$app_path" .app)
|
|
|
|
# Normalize path for comparison - realpath might fail if app is already deleted
|
|
local full_path
|
|
full_path=$(cd "$(dirname "$app_path")" 2> /dev/null && pwd || echo "")
|
|
[[ -n "$full_path" ]] && full_path="$full_path/$(basename "$app_path")"
|
|
|
|
# Find the index of the app in persistent-apps
|
|
local i=0
|
|
while true; do
|
|
local label
|
|
label=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-label" "$plist" 2> /dev/null || echo "")
|
|
[[ -z "$label" ]] && break
|
|
|
|
local url
|
|
url=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-data:_CFURLString" "$plist" 2> /dev/null || echo "")
|
|
|
|
# Match by label or by path (parsing the CFURLString which is usually a file:// URL)
|
|
if [[ "$label" == "$app_name" ]] || [[ "$url" == *"$app_name.app"* ]]; then
|
|
# Double check path if possible to avoid false positives for similarly named apps
|
|
if [[ -n "$full_path" && "$url" == *"$full_path"* ]] || [[ "$label" == "$app_name" ]]; then
|
|
if /usr/libexec/PlistBuddy -c "Delete :persistent-apps:$i" "$plist" 2> /dev/null; then
|
|
changed=true
|
|
# After deletion, current index i now points to the next item
|
|
continue
|
|
fi
|
|
fi
|
|
fi
|
|
((i++))
|
|
done
|
|
done
|
|
|
|
if [[ "$changed" == "true" ]]; then
|
|
# Restart Dock to apply changes from the plist
|
|
killall Dock 2> /dev/null || true
|
|
fi
|
|
}
|