mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 19:05:07 +00:00
- Capture terminal width in parent before forking; avoids unreliable tput calls inside the background subshell - Write spinner output directly to /dev/tty to prevent stdout state contention between parent and background processes - Use \033[2K (erase entire line) instead of \033[K (erase to EOL) - Add handle_interrupt() so Ctrl-C cleanly stops spinner, restores cursor, and exits 130 - cleanup_monitor now writes \r\033[2K\n so subsequent output starts on a clean line rather than on the cleared spinner line
280 lines
8.5 KiB
Bash
Executable File
280 lines
8.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# Mole - Purge command.
|
|
# Cleans heavy project build artifacts.
|
|
# Interactive selection by project.
|
|
|
|
set -euo pipefail
|
|
|
|
# Fix locale issues (avoid Perl warnings on non-English systems)
|
|
export LC_ALL=C
|
|
export LANG=C
|
|
|
|
# Get script directory and source common functions
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/../lib/core/common.sh"
|
|
|
|
# Set up cleanup trap for temporary files
|
|
trap cleanup_temp_files EXIT INT TERM
|
|
source "$SCRIPT_DIR/../lib/core/log.sh"
|
|
source "$SCRIPT_DIR/../lib/clean/project.sh"
|
|
|
|
# Configuration
|
|
CURRENT_SECTION=""
|
|
|
|
# Section management
|
|
start_section() {
|
|
local section_name="$1"
|
|
CURRENT_SECTION="$section_name"
|
|
printf '\n'
|
|
echo -e "${BLUE}━━━ ${section_name} ━━━${NC}"
|
|
}
|
|
|
|
end_section() {
|
|
CURRENT_SECTION=""
|
|
}
|
|
|
|
# Note activity for export list
|
|
note_activity() {
|
|
if [[ -n "$CURRENT_SECTION" ]]; then
|
|
printf '%s\n' "$CURRENT_SECTION" >> "$EXPORT_LIST_FILE"
|
|
fi
|
|
}
|
|
|
|
# Main purge function
|
|
start_purge() {
|
|
# Set current command for operation logging
|
|
export MOLE_CURRENT_COMMAND="purge"
|
|
log_operation_session_start "purge"
|
|
|
|
# Clear screen for better UX
|
|
if [[ -t 1 ]]; then
|
|
printf '\033[2J\033[H'
|
|
fi
|
|
|
|
# Initialize stats file in user cache directory
|
|
local stats_dir="${XDG_CACHE_HOME:-$HOME/.cache}/mole"
|
|
ensure_user_dir "$stats_dir"
|
|
ensure_user_file "$stats_dir/purge_stats"
|
|
ensure_user_file "$stats_dir/purge_count"
|
|
ensure_user_file "$stats_dir/purge_scanning"
|
|
echo "0" > "$stats_dir/purge_stats"
|
|
echo "0" > "$stats_dir/purge_count"
|
|
echo "" > "$stats_dir/purge_scanning"
|
|
}
|
|
|
|
# Perform the purge
|
|
perform_purge() {
|
|
local stats_dir="${XDG_CACHE_HOME:-$HOME/.cache}/mole"
|
|
local monitor_pid=""
|
|
|
|
# Cleanup function - use flag to prevent duplicate execution
|
|
_cleanup_done=false
|
|
cleanup_monitor() {
|
|
# Prevent multiple cleanup executions from trap conflicts
|
|
[[ "$_cleanup_done" == "true" ]] && return
|
|
_cleanup_done=true
|
|
|
|
# Remove scanning file to stop monitor
|
|
rm -f "$stats_dir/purge_scanning" 2> /dev/null || true
|
|
|
|
if [[ -n "$monitor_pid" ]]; then
|
|
kill "$monitor_pid" 2> /dev/null || true
|
|
wait "$monitor_pid" 2> /dev/null || true
|
|
fi
|
|
if [[ -t 1 ]]; then
|
|
printf '\r\033[2K\n' > /dev/tty 2> /dev/null || true
|
|
fi
|
|
}
|
|
|
|
# Ensure Ctrl-C/TERM always stops spinner(s) and exits immediately.
|
|
handle_interrupt() {
|
|
cleanup_monitor
|
|
stop_inline_spinner 2> /dev/null || true
|
|
show_cursor 2> /dev/null || true
|
|
printf '\n' >&2
|
|
exit 130
|
|
}
|
|
|
|
# Set up trap for cleanup + abort
|
|
trap handle_interrupt INT TERM
|
|
|
|
# Show scanning with spinner below the title line
|
|
if [[ -t 1 ]]; then
|
|
# Print title ONCE with newline; spinner occupies the line below
|
|
printf '%s\n' "${PURPLE_BOLD}Purge Project Artifacts${NC}"
|
|
|
|
# Capture terminal width in parent (most reliable before forking)
|
|
local _parent_cols=80
|
|
local _stty_out
|
|
if _stty_out=$(stty size < /dev/tty 2> /dev/null); then
|
|
_parent_cols="${_stty_out##* }" # "rows cols" -> take cols
|
|
else
|
|
_parent_cols=$(tput cols 2> /dev/null || echo 80)
|
|
fi
|
|
[[ "$_parent_cols" =~ ^[0-9]+$ && $_parent_cols -gt 0 ]] || _parent_cols=80
|
|
|
|
# Start background monitor: writes directly to /dev/tty to avoid stdout state issues
|
|
(
|
|
local spinner_chars="|/-\\"
|
|
local spinner_idx=0
|
|
local last_path=""
|
|
# Use parent-captured width; never refresh inside the loop (avoids unreliable tput in bg)
|
|
local term_cols="$_parent_cols"
|
|
# Visible prefix "| Scanning " = 11 chars; reserve 25 total for safety margin
|
|
local max_path_len=$((term_cols - 25))
|
|
((max_path_len < 5)) && max_path_len=5
|
|
|
|
# Set up trap to exit cleanly (erase the spinner line via /dev/tty)
|
|
trap 'printf "\r\033[2K" >/dev/tty 2>/dev/null; exit 0' INT TERM
|
|
|
|
# Truncate path to guaranteed fit
|
|
truncate_path() {
|
|
local path="$1"
|
|
if [[ ${#path} -le $max_path_len ]]; then
|
|
echo "$path"
|
|
return
|
|
fi
|
|
local side_len=$(((max_path_len - 3) / 2))
|
|
echo "${path:0:$side_len}...${path: -$side_len}"
|
|
}
|
|
|
|
while [[ -f "$stats_dir/purge_scanning" ]]; do
|
|
local current_path
|
|
current_path=$(cat "$stats_dir/purge_scanning" 2> /dev/null || echo "")
|
|
|
|
if [[ -n "$current_path" ]]; then
|
|
local display_path="${current_path/#$HOME/~}"
|
|
display_path=$(truncate_path "$display_path")
|
|
last_path="$display_path"
|
|
fi
|
|
|
|
local spin_char="${spinner_chars:$spinner_idx:1}"
|
|
spinner_idx=$(((spinner_idx + 1) % ${#spinner_chars}))
|
|
|
|
# Write directly to /dev/tty: \033[2K clears entire current line, \r goes to start
|
|
if [[ -n "$last_path" ]]; then
|
|
printf '\r\033[2K%s %sScanning %s%s' \
|
|
"${BLUE}${spin_char}${NC}" \
|
|
"${GRAY}" "$last_path" "${NC}" > /dev/tty 2> /dev/null
|
|
else
|
|
printf '\r\033[2K%s %sScanning...%s' \
|
|
"${BLUE}${spin_char}${NC}" \
|
|
"${GRAY}" "${NC}" > /dev/tty 2> /dev/null
|
|
fi
|
|
|
|
sleep 0.05
|
|
done
|
|
printf '\r\033[2K' > /dev/tty 2> /dev/null
|
|
exit 0
|
|
) &
|
|
monitor_pid=$!
|
|
else
|
|
echo -e "${PURPLE_BOLD}Purge Project Artifacts${NC}"
|
|
fi
|
|
|
|
clean_project_artifacts
|
|
local exit_code=$?
|
|
|
|
# Clean up
|
|
trap - INT TERM
|
|
cleanup_monitor
|
|
|
|
# Exit codes:
|
|
# 0 = success, show summary
|
|
# 1 = user cancelled
|
|
# 2 = nothing to clean
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Final summary (matching clean.sh format)
|
|
echo ""
|
|
|
|
local summary_heading="Purge complete"
|
|
local -a summary_details=()
|
|
local total_size_cleaned=0
|
|
local total_items_cleaned=0
|
|
|
|
if [[ -f "$stats_dir/purge_stats" ]]; then
|
|
total_size_cleaned=$(cat "$stats_dir/purge_stats" 2> /dev/null || echo "0")
|
|
rm -f "$stats_dir/purge_stats"
|
|
fi
|
|
|
|
if [[ -f "$stats_dir/purge_count" ]]; then
|
|
total_items_cleaned=$(cat "$stats_dir/purge_count" 2> /dev/null || echo "0")
|
|
rm -f "$stats_dir/purge_count"
|
|
fi
|
|
|
|
if [[ $total_size_cleaned -gt 0 ]]; then
|
|
local freed_gb
|
|
freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
|
|
|
|
local summary_line="Space freed: ${GREEN}${freed_gb}GB${NC}"
|
|
[[ $total_items_cleaned -gt 0 ]] && summary_line+=" | Items: $total_items_cleaned"
|
|
summary_line+=" | Free: $(get_free_space)"
|
|
summary_details+=("$summary_line")
|
|
else
|
|
summary_details+=("No old project artifacts to clean.")
|
|
summary_details+=("Free space: $(get_free_space)")
|
|
fi
|
|
|
|
# Log session end
|
|
log_operation_session_end "purge" "${total_items_cleaned:-0}" "${total_size_cleaned:-0}"
|
|
|
|
print_summary_block "$summary_heading" "${summary_details[@]}"
|
|
printf '\n'
|
|
}
|
|
|
|
# Show help message
|
|
show_help() {
|
|
echo -e "${PURPLE_BOLD}Mole Purge${NC}, Clean old project build artifacts"
|
|
echo ""
|
|
echo -e "${YELLOW}Usage:${NC} mo purge [options]"
|
|
echo ""
|
|
echo -e "${YELLOW}Options:${NC}"
|
|
echo " --paths Edit custom scan directories"
|
|
echo " --debug Enable debug logging"
|
|
echo " --help Show this help message"
|
|
echo ""
|
|
echo -e "${YELLOW}Default Paths:${NC}"
|
|
for path in "${DEFAULT_PURGE_SEARCH_PATHS[@]}"; do
|
|
echo " * $path"
|
|
done
|
|
}
|
|
|
|
# Main entry point
|
|
main() {
|
|
# Set up signal handling
|
|
trap 'show_cursor; exit 130' INT TERM
|
|
|
|
# Parse arguments
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
"--paths")
|
|
source "$SCRIPT_DIR/../lib/manage/purge_paths.sh"
|
|
manage_purge_paths
|
|
exit 0
|
|
;;
|
|
"--help")
|
|
show_help
|
|
exit 0
|
|
;;
|
|
"--debug")
|
|
export MO_DEBUG=1
|
|
;;
|
|
*)
|
|
echo "Unknown option: $arg"
|
|
echo "Use 'mo purge --help' for usage information"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
start_purge
|
|
hide_cursor
|
|
perform_purge
|
|
show_cursor
|
|
}
|
|
|
|
main "$@"
|