mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +00:00
refactor: rename installers command to installer across commands, script, and documentation.
This commit is contained in:
319
bin/installer.sh
Executable file
319
bin/installer.sh
Executable file
@@ -0,0 +1,319 @@
|
||||
#!/bin/bash
|
||||
# Mole - Installer command
|
||||
# Find and remove installer files (.dmg, .pkg, .mpkg, .iso, .xip, .zip)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
# External variables set by menu_paginated.sh and environment
|
||||
declare MOLE_SELECTION_RESULT
|
||||
declare MOLE_INSTALLER_SCAN_MAX_DEPTH
|
||||
|
||||
export LC_ALL=C
|
||||
export LANG=C
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/ui/menu_paginated.sh"
|
||||
|
||||
|
||||
cleanup() {
|
||||
show_cursor
|
||||
cleanup_temp_files
|
||||
}
|
||||
trap cleanup EXIT
|
||||
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" # Search one level deeper
|
||||
)
|
||||
readonly MAX_ZIP_ENTRIES=5
|
||||
|
||||
# Check for installer payloads inside ZIP (single pass, fused size and pattern check)
|
||||
is_installer_zip() {
|
||||
local zip="$1"
|
||||
local cap="$MAX_ZIP_ENTRIES"
|
||||
|
||||
zipinfo -1 "$zip" >/dev/null 2>&1 || return 1
|
||||
|
||||
zipinfo -1 "$zip" 2>/dev/null \
|
||||
| head -n $((cap + 1)) \
|
||||
| awk -v cap="$cap" '
|
||||
/\.(app|pkg|dmg|xip)(\/|$)/ { found=1 }
|
||||
END {
|
||||
if (NR > cap) exit 1
|
||||
exit found ? 0 : 1
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
scan_installers_in_path() {
|
||||
local path="$1"
|
||||
local max_depth="${MOLE_INSTALLER_SCAN_MAX_DEPTH:-$INSTALLER_SCAN_MAX_DEPTH_DEFAULT}"
|
||||
|
||||
[[ -d "$path" ]] || return 0
|
||||
|
||||
local file
|
||||
|
||||
if command -v fd > /dev/null 2>&1; then
|
||||
while IFS= read -r file; do
|
||||
[[ -L "$file" ]] && continue # Skip symlinks explicitly
|
||||
case "$file" in
|
||||
*.dmg|*.pkg|*.mpkg|*.iso|*.xip)
|
||||
echo "$file"
|
||||
;;
|
||||
*.zip)
|
||||
[[ -r "$file" ]] || continue
|
||||
if is_installer_zip "$file" 2>/dev/null; then
|
||||
echo "$file"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
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
|
||||
)
|
||||
else
|
||||
while IFS= read -r file; do
|
||||
[[ -L "$file" ]] && continue # Skip symlinks explicitly
|
||||
case "$file" in
|
||||
*.dmg|*.pkg|*.mpkg|*.iso|*.xip)
|
||||
echo "$file"
|
||||
;;
|
||||
*.zip)
|
||||
[[ -r "$file" ]] || continue
|
||||
if is_installer_zip "$file" 2>/dev/null; then
|
||||
echo "$file"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
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
|
||||
)
|
||||
fi
|
||||
}
|
||||
|
||||
scan_all_installers() {
|
||||
for path in "${INSTALLER_SCAN_PATHS[@]}"; do
|
||||
scan_installers_in_path "$path"
|
||||
done
|
||||
}
|
||||
|
||||
# Initialize stats
|
||||
declare -i total_deleted=0
|
||||
declare -i total_size_freed_kb=0
|
||||
|
||||
# Global arrays for installer data
|
||||
declare -a INSTALLER_PATHS=()
|
||||
declare -a INSTALLER_SIZES=()
|
||||
declare -a DISPLAY_NAMES=()
|
||||
|
||||
# Collect all installers with their metadata
|
||||
collect_installers() {
|
||||
printf '\n'
|
||||
echo -e "${BLUE}━━━ Scanning for installers ━━━${NC}"
|
||||
|
||||
# Clear previous results
|
||||
INSTALLER_PATHS=()
|
||||
INSTALLER_SIZES=()
|
||||
DISPLAY_NAMES=()
|
||||
|
||||
# Scan all paths, deduplicate, and sort results
|
||||
local -a all_files=()
|
||||
local sorted_paths
|
||||
sorted_paths=$(scan_all_installers | sort -u)
|
||||
|
||||
if [[ -z "$sorted_paths" ]]; then
|
||||
echo -e " ${YELLOW}No installer files found${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Read sorted results into array
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
all_files+=("$file")
|
||||
done <<< "$sorted_paths"
|
||||
|
||||
# Process each installer
|
||||
for file in "${all_files[@]}"; do
|
||||
# Calculate file size
|
||||
local file_size=0
|
||||
if [[ -f "$file" ]]; then
|
||||
file_size=$(get_file_size "$file")
|
||||
fi
|
||||
|
||||
# Store installer path and size in parallel arrays
|
||||
INSTALLER_PATHS+=("$file")
|
||||
INSTALLER_SIZES+=("$file_size")
|
||||
DISPLAY_NAMES+=("$(basename "$file")")
|
||||
done
|
||||
|
||||
echo -e " ${GREEN}Found ${#INSTALLER_PATHS[@]} installer(s)${NC}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Show menu for user selection
|
||||
show_installer_menu() {
|
||||
if [[ ${#DISPLAY_NAMES[@]} -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
local title="Select installers to remove"
|
||||
MOLE_SELECTION_RESULT=""
|
||||
paginated_multi_select "$title" "${DISPLAY_NAMES[@]}"
|
||||
local selection_exit=$?
|
||||
|
||||
if [[ $selection_exit -ne 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Cancelled${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Delete selected installers
|
||||
delete_selected_installers() {
|
||||
# Parse selection indices
|
||||
local -a selected_indices=()
|
||||
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<<"$MOLE_SELECTION_RESULT"
|
||||
|
||||
if [[ ${#selected_indices[@]} -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
echo -e "${BLUE}━━━ Removing installers ━━━${NC}"
|
||||
|
||||
# Delete each selected installer
|
||||
total_deleted=0
|
||||
total_size_freed_kb=0
|
||||
for idx in "${selected_indices[@]}"; do
|
||||
if [[ ! "$idx" =~ ^[0-9]+$ ]] || [[ $idx -ge ${#INSTALLER_PATHS[@]} ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local file_path="${INSTALLER_PATHS[$idx]}"
|
||||
local file_size="${INSTALLER_SIZES[$idx]}"
|
||||
|
||||
# Validate path before deletion
|
||||
if ! validate_path_for_deletion "$file_path"; then
|
||||
echo -e " ${RED}${ICON_FAILED}${NC} Cannot delete (invalid path): $(basename "$file_path")"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Delete the file
|
||||
if safe_remove "$file_path" true; then
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$file_size")
|
||||
total_size_freed_kb=$((total_size_freed_kb + ((file_size + 1023) / 1024)))
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Deleted: $(basename "$file_path") ${GRAY}($human_size)${NC}"
|
||||
total_deleted=$((total_deleted + 1))
|
||||
else
|
||||
echo -e " ${RED}${ICON_FAILED}${NC} Failed to delete: $(basename "$file_path")"
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Perform the installers cleanup
|
||||
perform_installers() {
|
||||
# Collect installers
|
||||
if ! collect_installers; then
|
||||
return 2 # Nothing to clean
|
||||
fi
|
||||
|
||||
# Show menu
|
||||
if ! show_installer_menu; then
|
||||
return 1 # User cancelled
|
||||
fi
|
||||
|
||||
# Delete selected
|
||||
delete_selected_installers
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
show_summary() {
|
||||
echo ""
|
||||
local summary_heading="Cleanup complete"
|
||||
local -a summary_details=()
|
||||
|
||||
if [[ $total_deleted -gt 0 ]]; then
|
||||
local freed_mb
|
||||
freed_mb=$(echo "$total_size_freed_kb" | awk '{printf "%.2f", $1/1024}')
|
||||
|
||||
summary_details+=("Installers removed: $total_deleted")
|
||||
summary_details+=("Space freed: ${GREEN}${freed_mb}MB${NC}")
|
||||
summary_details+=("Free space now: $(get_free_space)")
|
||||
else
|
||||
summary_details+=("No installers were removed")
|
||||
summary_details+=("Free space now: $(get_free_space)")
|
||||
fi
|
||||
|
||||
print_summary_block "$summary_heading" "${summary_details[@]}"
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
"--debug")
|
||||
export MO_DEBUG=1
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $arg"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Clear screen for better UX
|
||||
if [[ -t 1 ]]; then
|
||||
printf '\033[2J\033[H'
|
||||
fi
|
||||
printf '\n'
|
||||
echo -e "${PURPLE_BOLD}Clean Installer Files${NC}"
|
||||
|
||||
hide_cursor
|
||||
perform_installers
|
||||
local exit_code=$?
|
||||
show_cursor
|
||||
|
||||
case $exit_code in
|
||||
0)
|
||||
show_summary
|
||||
;;
|
||||
1)
|
||||
printf '\n'
|
||||
;;
|
||||
2)
|
||||
printf '\n'
|
||||
echo -e "${YELLOW}No installer files found in default locations${NC}"
|
||||
printf '\n'
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Only run main if not in test mode
|
||||
if [[ "${MOLE_TEST_MODE:-0}" != "1" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user