mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 14:26:46 +00:00
Merge branch 'main' of github.com:tw93/Mole
This commit is contained in:
@@ -16,7 +16,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/ui/menu_paginated.sh"
|
||||
|
||||
|
||||
cleanup() {
|
||||
if [[ "${IN_ALT_SCREEN:-0}" == "1" ]]; then
|
||||
leave_alt_screen
|
||||
@@ -31,18 +30,18 @@ trap 'trap - EXIT; cleanup; exit 130' INT TERM
|
||||
# Scan configuration
|
||||
readonly INSTALLER_SCAN_MAX_DEPTH_DEFAULT=2
|
||||
readonly INSTALLER_SCAN_PATHS=(
|
||||
"$HOME/Downloads"
|
||||
"$HOME/Desktop"
|
||||
"$HOME/Documents"
|
||||
"$HOME/Public"
|
||||
"$HOME/Library/Downloads"
|
||||
"/Users/Shared"
|
||||
"/Users/Shared/Downloads"
|
||||
"$HOME/Library/Caches/Homebrew"
|
||||
"$HOME/Library/Mobile Documents/com~apple~CloudDocs/Downloads"
|
||||
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads"
|
||||
"$HOME/Library/Application Support/Telegram Desktop"
|
||||
"$HOME/Downloads/Telegram Desktop"
|
||||
"$HOME/Downloads"
|
||||
"$HOME/Desktop"
|
||||
"$HOME/Documents"
|
||||
"$HOME/Public"
|
||||
"$HOME/Library/Downloads"
|
||||
"/Users/Shared"
|
||||
"/Users/Shared/Downloads"
|
||||
"$HOME/Library/Caches/Homebrew"
|
||||
"$HOME/Library/Mobile Documents/com~apple~CloudDocs/Downloads"
|
||||
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads"
|
||||
"$HOME/Library/Application Support/Telegram Desktop"
|
||||
"$HOME/Downloads/Telegram Desktop"
|
||||
)
|
||||
readonly MAX_ZIP_ENTRIES=5
|
||||
ZIP_LIST_CMD=()
|
||||
@@ -63,9 +62,9 @@ is_installer_zip() {
|
||||
|
||||
[[ ${#ZIP_LIST_CMD[@]} -gt 0 ]] || return 1
|
||||
|
||||
if ! "${ZIP_LIST_CMD[@]}" "$zip" 2>/dev/null \
|
||||
| head -n $((cap + 1)) \
|
||||
| awk -v cap="$cap" '
|
||||
if ! "${ZIP_LIST_CMD[@]}" "$zip" 2> /dev/null |
|
||||
head -n $((cap + 1)) |
|
||||
awk -v cap="$cap" '
|
||||
/\.(app|pkg|dmg|xip)(\/|$)/ { found=1 }
|
||||
END {
|
||||
if (NR > cap) exit 1
|
||||
@@ -81,14 +80,14 @@ is_installer_zip() {
|
||||
handle_candidate_file() {
|
||||
local file="$1"
|
||||
|
||||
[[ -L "$file" ]] && return 0 # Skip symlinks explicitly
|
||||
[[ -L "$file" ]] && return 0 # Skip symlinks explicitly
|
||||
case "$file" in
|
||||
*.dmg|*.pkg|*.mpkg|*.iso|*.xip)
|
||||
*.dmg | *.pkg | *.mpkg | *.iso | *.xip)
|
||||
echo "$file"
|
||||
;;
|
||||
*.zip)
|
||||
[[ -r "$file" ]] || return 0
|
||||
if is_installer_zip "$file" 2>/dev/null; then
|
||||
if is_installer_zip "$file" 2> /dev/null; then
|
||||
echo "$file"
|
||||
fi
|
||||
;;
|
||||
@@ -109,7 +108,7 @@ scan_installers_in_path() {
|
||||
done < <(
|
||||
fd --no-ignore --hidden --type f --max-depth "$max_depth" \
|
||||
-e dmg -e pkg -e mpkg -e iso -e xip -e zip \
|
||||
. "$path" 2>/dev/null || true
|
||||
. "$path" 2> /dev/null || true
|
||||
)
|
||||
else
|
||||
while IFS= read -r file; do
|
||||
@@ -117,8 +116,8 @@ scan_installers_in_path() {
|
||||
done < <(
|
||||
find "$path" -maxdepth "$max_depth" -type f \
|
||||
\( -name '*.dmg' -o -name '*.pkg' -o -name '*.mpkg' \
|
||||
-o -name '*.iso' -o -name '*.xip' -o -name '*.zip' \) \
|
||||
2>/dev/null || true
|
||||
-o -name '*.iso' -o -name '*.xip' -o -name '*.zip' \) \
|
||||
2> /dev/null || true
|
||||
)
|
||||
fi
|
||||
}
|
||||
@@ -162,7 +161,7 @@ get_source_display() {
|
||||
|
||||
get_terminal_width() {
|
||||
if [[ $TERMINAL_WIDTH -le 0 ]]; then
|
||||
TERMINAL_WIDTH=$(tput cols 2>/dev/null || echo 80)
|
||||
TERMINAL_WIDTH=$(tput cols 2> /dev/null || echo 80)
|
||||
fi
|
||||
echo "$TERMINAL_WIDTH"
|
||||
}
|
||||
@@ -176,7 +175,7 @@ format_installer_display() {
|
||||
# Terminal width for alignment
|
||||
local terminal_width
|
||||
terminal_width=$(get_terminal_width)
|
||||
local fixed_width=24 # Reserve for size and source
|
||||
local fixed_width=24 # Reserve for size and source
|
||||
local available_width=$((terminal_width - fixed_width))
|
||||
|
||||
# Bounds check: 20-40 chars for filename
|
||||
@@ -280,11 +279,11 @@ select_installers() {
|
||||
_get_items_per_page() {
|
||||
local term_height=24
|
||||
if [[ -t 0 ]] || [[ -t 2 ]]; then
|
||||
term_height=$(stty size </dev/tty 2>/dev/null | awk '{print $1}')
|
||||
term_height=$(stty size < /dev/tty 2> /dev/null | awk '{print $1}')
|
||||
fi
|
||||
if [[ -z "$term_height" || $term_height -le 0 ]]; then
|
||||
if command -v tput > /dev/null 2>&1; then
|
||||
term_height=$(tput lines 2>/dev/null || echo "24")
|
||||
term_height=$(tput lines 2> /dev/null || echo "24")
|
||||
else
|
||||
term_height=24
|
||||
fi
|
||||
@@ -312,7 +311,7 @@ select_installers() {
|
||||
|
||||
local original_stty=""
|
||||
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
|
||||
original_stty=$(stty -g 2>/dev/null || echo "")
|
||||
original_stty=$(stty -g 2> /dev/null || echo "")
|
||||
fi
|
||||
|
||||
restore_terminal() {
|
||||
@@ -323,11 +322,10 @@ select_installers() {
|
||||
fi
|
||||
show_cursor
|
||||
if [[ -n "${original_stty:-}" ]]; then
|
||||
stty "${original_stty}" 2>/dev/null || stty sane 2>/dev/null || true
|
||||
stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
handle_interrupt() {
|
||||
restore_terminal
|
||||
exit 130
|
||||
@@ -407,7 +405,7 @@ select_installers() {
|
||||
|
||||
trap restore_terminal EXIT
|
||||
trap handle_interrupt INT TERM
|
||||
stty -echo -icanon intr ^C 2>/dev/null || true
|
||||
stty -echo -icanon intr ^C 2> /dev/null || true
|
||||
hide_cursor
|
||||
if [[ -t 1 ]]; then
|
||||
printf "\033[2J\033[H" >&2
|
||||
@@ -459,12 +457,12 @@ select_installers() {
|
||||
selected[idx]=true
|
||||
fi
|
||||
;;
|
||||
"a"|"A") # Select all
|
||||
"a" | "A") # Select all
|
||||
for ((i = 0; i < total_items; i++)); do
|
||||
selected[i]=true
|
||||
done
|
||||
;;
|
||||
"i"|"I") # Invert selection
|
||||
"i" | "I") # Invert selection
|
||||
for ((i = 0; i < total_items; i++)); do
|
||||
if [[ ${selected[i]} == true ]]; then
|
||||
selected[i]=false
|
||||
@@ -473,11 +471,11 @@ select_installers() {
|
||||
fi
|
||||
done
|
||||
;;
|
||||
"q"|"Q"|$'\x03') # Quit or Ctrl-C
|
||||
"q" | "Q" | $'\x03') # Quit or Ctrl-C
|
||||
restore_terminal
|
||||
return 1
|
||||
;;
|
||||
""|$'\n'|$'\r') # Enter - confirm
|
||||
"" | $'\n' | $'\r') # Enter - confirm
|
||||
MOLE_SELECTION_RESULT=""
|
||||
for ((i = 0; i < total_items; i++)); do
|
||||
if [[ ${selected[i]} == true ]]; then
|
||||
@@ -512,7 +510,7 @@ show_installer_menu() {
|
||||
delete_selected_installers() {
|
||||
# Parse selection indices
|
||||
local -a selected_indices=()
|
||||
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<<"$MOLE_SELECTION_RESULT"
|
||||
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<< "$MOLE_SELECTION_RESULT"
|
||||
|
||||
if [[ ${#selected_indices[@]} -eq 0 ]]; then
|
||||
return 1
|
||||
@@ -546,12 +544,12 @@ delete_selected_installers() {
|
||||
|
||||
IFS= read -r -s -n1 confirm || confirm=""
|
||||
case "$confirm" in
|
||||
$'\e'|q|Q)
|
||||
$'\e' | q | Q)
|
||||
return 1
|
||||
;;
|
||||
""|$'\n'|$'\r')
|
||||
printf "\r\033[K" # Clear prompt line
|
||||
echo "" # Single line break
|
||||
"" | $'\n' | $'\r')
|
||||
printf "\r\033[K" # Clear prompt line
|
||||
echo "" # Single line break
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
@@ -611,7 +609,7 @@ perform_installers() {
|
||||
printf '\n'
|
||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Great! No installer files to clean"
|
||||
printf '\n'
|
||||
return 2 # Nothing to clean
|
||||
return 2 # Nothing to clean
|
||||
fi
|
||||
|
||||
# Show menu
|
||||
@@ -620,7 +618,7 @@ perform_installers() {
|
||||
leave_alt_screen
|
||||
IN_ALT_SCREEN=0
|
||||
fi
|
||||
return 1 # User cancelled
|
||||
return 1 # User cancelled
|
||||
fi
|
||||
|
||||
# Leave alt screen before deletion (so confirmation and results are on main screen)
|
||||
@@ -655,7 +653,6 @@ show_summary() {
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
|
||||
@@ -601,7 +601,7 @@ is_path_whitelisted() {
|
||||
local check_pattern="${pattern%/}"
|
||||
local has_glob="false"
|
||||
case "$check_pattern" in
|
||||
*\**|*\?*|*\[*)
|
||||
*\** | *\?* | *\[*)
|
||||
has_glob="true"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -106,18 +106,18 @@ safe_remove() {
|
||||
|
||||
if [[ -e "$path" ]]; then
|
||||
local size_kb
|
||||
size_kb=$(get_path_size_kb "$path" 2>/dev/null || echo "0")
|
||||
size_kb=$(get_path_size_kb "$path" 2> /dev/null || echo "0")
|
||||
if [[ "$size_kb" -gt 0 ]]; then
|
||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||
fi
|
||||
|
||||
if [[ -f "$path" || -d "$path" ]] && ! [[ -L "$path" ]]; then
|
||||
local mod_time
|
||||
mod_time=$(stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
mod_time=$(stat -f%m "$path" 2> /dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s 2>/dev/null || echo "0")
|
||||
now=$(date +%s 2> /dev/null || echo "0")
|
||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||
file_age=$(( (now - mod_time) / 86400 ))
|
||||
file_age=$(((now - mod_time) / 86400))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -183,20 +183,20 @@ safe_sudo_remove() {
|
||||
local file_size=""
|
||||
local file_age=""
|
||||
|
||||
if sudo test -e "$path" 2>/dev/null; then
|
||||
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 -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0")
|
||||
if [[ "$size_kb" -gt 0 ]]; then
|
||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||
fi
|
||||
|
||||
if sudo test -f "$path" 2>/dev/null || sudo test -d "$path" 2>/dev/null; then
|
||||
if sudo test -f "$path" 2> /dev/null || sudo test -d "$path" 2> /dev/null; then
|
||||
local mod_time
|
||||
mod_time=$(sudo stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
mod_time=$(sudo stat -f%m "$path" 2> /dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s 2>/dev/null || echo "0")
|
||||
now=$(date +%s 2> /dev/null || echo "0")
|
||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||
file_age=$(( (now - mod_time) / 86400 ))
|
||||
file_age=$(((now - mod_time) / 86400))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -118,7 +118,7 @@ debug_operation_start() {
|
||||
|
||||
# Log detailed operation information
|
||||
debug_operation_detail() {
|
||||
local detail_type="$1" # e.g., "Method", "Target", "Expected Outcome"
|
||||
local detail_type="$1" # e.g., "Method", "Target", "Expected Outcome"
|
||||
local detail_value="$2"
|
||||
|
||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||
@@ -132,7 +132,7 @@ debug_operation_detail() {
|
||||
|
||||
# Log individual file action with metadata
|
||||
debug_file_action() {
|
||||
local action="$1" # e.g., "Would remove", "Removing"
|
||||
local action="$1" # e.g., "Would remove", "Removing"
|
||||
local file_path="$2"
|
||||
local file_size="${3:-}"
|
||||
local file_age="${4:-}"
|
||||
@@ -153,7 +153,7 @@ debug_file_action() {
|
||||
|
||||
# Log risk level for operations
|
||||
debug_risk_level() {
|
||||
local risk_level="$1" # LOW, MEDIUM, HIGH
|
||||
local risk_level="$1" # LOW, MEDIUM, HIGH
|
||||
local reason="$2"
|
||||
|
||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||
|
||||
@@ -133,7 +133,7 @@ opt_cache_refresh() {
|
||||
for target_path in "${cache_targets[@]}"; do
|
||||
if [[ -e "$target_path" ]]; then
|
||||
local size_kb
|
||||
size_kb=$(get_path_size_kb "$target_path" 2>/dev/null || echo "0")
|
||||
size_kb=$(get_path_size_kb "$target_path" 2> /dev/null || echo "0")
|
||||
local size_human="unknown"
|
||||
if [[ "$size_kb" -gt 0 ]]; then
|
||||
size_human=$(bytes_to_human "$((size_kb * 1024))")
|
||||
|
||||
Reference in New Issue
Block a user