mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 09:46:44 +00:00
Neat and uniform output
This commit is contained in:
@@ -1220,17 +1220,38 @@ display_file_types() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Analyze common file types
|
# Analyze common file types (bash 3.2 compatible - no associative arrays)
|
||||||
local -A type_map=(
|
local -a type_names=("Videos" "Images" "Archives" "Documents" "Audio")
|
||||||
["Videos"]="kMDItemContentType == 'public.movie' || kMDItemContentType == 'public.video'"
|
|
||||||
["Images"]="kMDItemContentType == 'public.image'"
|
local type_name
|
||||||
["Archives"]="kMDItemContentType == 'public.archive' || kMDItemContentType == 'public.zip-archive'"
|
for type_name in "${type_names[@]}"; do
|
||||||
["Documents"]="kMDItemContentType == 'com.adobe.pdf' || kMDItemContentType == 'public.text'"
|
local query=""
|
||||||
["Audio"]="kMDItemContentType == 'public.audio'"
|
local badge="$BADGE_FILE"
|
||||||
)
|
|
||||||
|
# Map type name to query and badge
|
||||||
for type_name in "${!type_map[@]}"; do
|
case "$type_name" in
|
||||||
local query="${type_map[$type_name]}"
|
"Videos")
|
||||||
|
query="kMDItemContentType == 'public.movie' || kMDItemContentType == 'public.video'"
|
||||||
|
badge="$BADGE_MEDIA"
|
||||||
|
;;
|
||||||
|
"Images")
|
||||||
|
query="kMDItemContentType == 'public.image'"
|
||||||
|
badge="$BADGE_MEDIA"
|
||||||
|
;;
|
||||||
|
"Archives")
|
||||||
|
query="kMDItemContentType == 'public.archive' || kMDItemContentType == 'public.zip-archive'"
|
||||||
|
badge="$BADGE_BUNDLE"
|
||||||
|
;;
|
||||||
|
"Documents")
|
||||||
|
query="kMDItemContentType == 'com.adobe.pdf' || kMDItemContentType == 'public.text'"
|
||||||
|
badge="$BADGE_FILE"
|
||||||
|
;;
|
||||||
|
"Audio")
|
||||||
|
query="kMDItemContentType == 'public.audio'"
|
||||||
|
badge="🎵"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
local files=$(mdfind -onlyin "$CURRENT_PATH" "$query" 2>/dev/null)
|
local files=$(mdfind -onlyin "$CURRENT_PATH" "$query" 2>/dev/null)
|
||||||
local count=$(echo "$files" | grep -c . || echo "0")
|
local count=$(echo "$files" | grep -c . || echo "0")
|
||||||
local total_size=0
|
local total_size=0
|
||||||
@@ -1245,13 +1266,6 @@ display_file_types() {
|
|||||||
|
|
||||||
if [[ $total_size -gt 0 ]]; then
|
if [[ $total_size -gt 0 ]]; then
|
||||||
local human_size=$(bytes_to_human "$total_size")
|
local human_size=$(bytes_to_human "$total_size")
|
||||||
local badge="$BADGE_FILE"
|
|
||||||
case "$type_name" in
|
|
||||||
"Videos"|"Images") badge="$BADGE_MEDIA" ;;
|
|
||||||
"Archives") badge="$BADGE_BUNDLE" ;;
|
|
||||||
"Documents") badge="$BADGE_FILE" ;;
|
|
||||||
"Audio") badge="🎵" ;;
|
|
||||||
esac
|
|
||||||
printf " %s %-12s %8s (%d files)\n" "$badge" "$type_name:" "$human_size" "$count"
|
printf " %s %-12s %8s (%d files)\n" "$badge" "$type_name:" "$human_size" "$count"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
31
bin/clean.sh
31
bin/clean.sh
@@ -1325,11 +1325,30 @@ perform_cleanup() {
|
|||||||
local freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
|
local freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
echo "Potential reclaimable space: ${GREEN}${freed_gb}GB${NC} (no changes made) | Free space now: $(get_free_space)"
|
echo "Potential reclaimable space: ${GREEN}${freed_gb}GB${NC} (no changes made) | Free space now: $(get_free_space)"
|
||||||
|
|
||||||
|
# Show file/category stats for dry run
|
||||||
|
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
||||||
|
printf "Files to clean: %s | Categories: %s\n" "$files_cleaned" "$total_items"
|
||||||
|
elif [[ $files_cleaned -gt 0 ]]; then
|
||||||
|
printf "Files to clean: %s\n" "$files_cleaned"
|
||||||
|
elif [[ $total_items -gt 0 ]]; then
|
||||||
|
printf "Categories: %s\n" "$total_items"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "To protect specific cache files from deletion, run: mole clean --whitelist"
|
||||||
else
|
else
|
||||||
echo "Space freed: ${GREEN}${freed_gb}GB${NC} | Free space now: $(get_free_space)"
|
echo "Space freed: ${GREEN}${freed_gb}GB${NC} | Free space now: $(get_free_space)"
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$DRY_RUN" != "true" ]]; then
|
# Show file/category stats for actual cleanup
|
||||||
|
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
||||||
|
printf "Files cleaned: %s | Categories: %s\n" "$files_cleaned" "$total_items"
|
||||||
|
elif [[ $files_cleaned -gt 0 ]]; then
|
||||||
|
printf "Files cleaned: %s\n" "$files_cleaned"
|
||||||
|
elif [[ $total_items -gt 0 ]]; then
|
||||||
|
printf "Categories: %s\n" "$total_items"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; then
|
if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; then
|
||||||
local movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}')
|
local movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}')
|
||||||
if [[ $movies -gt 0 ]]; then
|
if [[ $movies -gt 0 ]]; then
|
||||||
@@ -1344,14 +1363,6 @@ perform_cleanup() {
|
|||||||
echo "No significant space was freed (system was already clean) | Free space: $(get_free_space)"
|
echo "No significant space was freed (system was already clean) | Free space: $(get_free_space)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
|
||||||
printf "Files cleaned: %s | Categories processed: %s\n" "$files_cleaned" "$total_items"
|
|
||||||
elif [[ $files_cleaned -gt 0 ]]; then
|
|
||||||
printf "Files cleaned: %s\n" "$files_cleaned"
|
|
||||||
elif [[ $total_items -gt 0 ]]; then
|
|
||||||
printf "Categories processed: %s\n" "$total_items"
|
|
||||||
fi
|
|
||||||
printf "====================================================================\n"
|
printf "====================================================================\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ scan_applications() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local temp_file=$(mktemp_file)
|
local temp_file=$(create_temp_file)
|
||||||
|
|
||||||
echo "" >&2 # Add space before scanning output without breaking stdout return
|
echo "" >&2 # Add space before scanning output without breaking stdout return
|
||||||
# Pre-cache current epoch to avoid repeated calls
|
# Pre-cache current epoch to avoid repeated calls
|
||||||
@@ -375,7 +375,8 @@ load_applications() {
|
|||||||
uninstall_applications() {
|
uninstall_applications() {
|
||||||
local total_size_freed=0
|
local total_size_freed=0
|
||||||
|
|
||||||
log_header "Uninstalling selected applications"
|
echo ""
|
||||||
|
echo -e "${PURPLE}▶ Uninstalling selected applications${NC}"
|
||||||
|
|
||||||
if [[ ${#selected_apps[@]} -eq 0 ]]; then
|
if [[ ${#selected_apps[@]} -eq 0 ]]; then
|
||||||
log_warning "No applications selected for uninstallation"
|
log_warning "No applications selected for uninstallation"
|
||||||
@@ -389,14 +390,14 @@ uninstall_applications() {
|
|||||||
|
|
||||||
# Check if app is running
|
# Check if app is running
|
||||||
if pgrep -f "$app_name" >/dev/null 2>&1; then
|
if pgrep -f "$app_name" >/dev/null 2>&1; then
|
||||||
log_warning "$app_name is currently running"
|
echo -e "${YELLOW}⚠ $app_name is currently running${NC}"
|
||||||
read -p " Force quit $app_name? (y/N): " -n 1 -r
|
read -p " Force quit $app_name? (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
pkill -f "$app_name" 2>/dev/null || true
|
pkill -f "$app_name" 2>/dev/null || true
|
||||||
sleep 2
|
sleep 2
|
||||||
else
|
else
|
||||||
log_warning "Skipping $app_name (still running)"
|
echo -e " ${BLUE}○${NC} Skipped $app_name"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -414,7 +415,7 @@ uninstall_applications() {
|
|||||||
local total_kb=$((app_size_kb + related_size_kb + system_size_kb))
|
local total_kb=$((app_size_kb + related_size_kb + system_size_kb))
|
||||||
|
|
||||||
# Show what will be removed
|
# Show what will be removed
|
||||||
echo -e " ${YELLOW}Files to be removed:${NC}"
|
echo -e "${BLUE}◎${NC} $app_name - Files to be removed:"
|
||||||
echo -e " ${GREEN}✓${NC} Application: $(echo "$app_path" | sed "s|$HOME|~|")"
|
echo -e " ${GREEN}✓${NC} Application: $(echo "$app_path" | sed "s|$HOME|~|")"
|
||||||
|
|
||||||
# Show user-level files
|
# Show user-level files
|
||||||
@@ -425,7 +426,7 @@ uninstall_applications() {
|
|||||||
# Show system-level files
|
# Show system-level files
|
||||||
if [[ -n "$system_files" ]]; then
|
if [[ -n "$system_files" ]]; then
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
[[ -n "$file" && -e "$file" ]] && echo -e " ${YELLOW}✓${NC} [System] $file"
|
[[ -n "$file" && -e "$file" ]] && echo -e " ${BLUE}●${NC} System: $file"
|
||||||
done <<< "$system_files"
|
done <<< "$system_files"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -448,7 +449,7 @@ uninstall_applications() {
|
|||||||
if rm -rf "$app_path" 2>/dev/null; then
|
if rm -rf "$app_path" 2>/dev/null; then
|
||||||
echo -e " ${GREEN}✓${NC} Removed application"
|
echo -e " ${GREEN}✓${NC} Removed application"
|
||||||
else
|
else
|
||||||
log_error "Failed to remove $app_path"
|
echo -e " ${RED}✗${NC} Failed to remove $app_path"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -463,13 +464,13 @@ uninstall_applications() {
|
|||||||
|
|
||||||
# Remove system-level files (requires sudo)
|
# Remove system-level files (requires sudo)
|
||||||
if [[ -n "$system_files" ]]; then
|
if [[ -n "$system_files" ]]; then
|
||||||
echo -e " ${YELLOW}System-level files require administrator privileges${NC}"
|
echo -e " ${BLUE}●${NC} Admin access required for system files"
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
if [[ -n "$file" && -e "$file" ]]; then
|
if [[ -n "$file" && -e "$file" ]]; then
|
||||||
if sudo rm -rf "$file" 2>/dev/null; then
|
if sudo rm -rf "$file" 2>/dev/null; then
|
||||||
echo -e " ${GREEN}✓${NC} Removed [System] $(basename "$file")"
|
echo -e " ${GREEN}✓${NC} Removed $(basename "$file")"
|
||||||
else
|
else
|
||||||
log_warning "Failed to remove system file: $file"
|
echo -e " ${YELLOW}⚠${NC} Failed to remove: $file"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done <<< "$system_files"
|
done <<< "$system_files"
|
||||||
@@ -479,15 +480,15 @@ uninstall_applications() {
|
|||||||
((files_cleaned++))
|
((files_cleaned++))
|
||||||
((total_items++))
|
((total_items++))
|
||||||
|
|
||||||
log_success "$app_name uninstalled successfully"
|
echo -e " ${GREEN}✓${NC} $app_name uninstalled successfully"
|
||||||
else
|
else
|
||||||
echo -e " ${BLUE}❂${NC} Skipped $app_name"
|
echo -e " ${BLUE}○${NC} Skipped $app_name"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Show final summary
|
# Show final summary
|
||||||
echo ""
|
echo ""
|
||||||
log_header "Uninstallation Summary"
|
echo -e "${PURPLE}▶ Uninstallation Summary${NC}"
|
||||||
|
|
||||||
if [[ $total_size_freed -gt 0 ]]; then
|
if [[ $total_size_freed -gt 0 ]]; then
|
||||||
if [[ $total_size_freed -gt 1048576 ]]; then # > 1GB
|
if [[ $total_size_freed -gt 1048576 ]]; then # > 1GB
|
||||||
@@ -498,10 +499,10 @@ uninstall_applications() {
|
|||||||
local freed_display="${total_size_freed}KB"
|
local freed_display="${total_size_freed}KB"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_success "Freed $freed_display of disk space"
|
echo -e " ${GREEN}✓${NC} Freed $freed_display of disk space"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Applications uninstalled: $files_cleaned"
|
echo -e " ${GREEN}✓${NC} Applications uninstalled: $files_cleaned"
|
||||||
((total_size_cleaned += total_size_freed))
|
((total_size_cleaned += total_size_freed))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +562,7 @@ main() {
|
|||||||
local extra=$((selection_count-3))
|
local extra=$((selection_count-3))
|
||||||
local list="${names[*]}"
|
local list="${names[*]}"
|
||||||
[[ $extra -gt 0 ]] && list+=" +${extra}"
|
[[ $extra -gt 0 ]] && list+=" +${extra}"
|
||||||
echo "◎ ${selection_count} apps: ${list}"
|
echo -e "${BLUE}◎${NC} ${selection_count} apps: ${list}"
|
||||||
|
|
||||||
# Execute batch uninstallation (handles confirmation)
|
# Execute batch uninstallation (handles confirmation)
|
||||||
batch_uninstall_applications
|
batch_uninstall_applications
|
||||||
|
|||||||
62
install.sh
62
install.sh
@@ -19,17 +19,25 @@ start_line_spinner() {
|
|||||||
( while true; do c="${chars:$((i % ${#chars})):1}"; printf "\r ${BLUE}%s${NC} %s" "$c" "$msg"; ((i++)); sleep 0.12; done ) &
|
( while true; do c="${chars:$((i % ${#chars})):1}"; printf "\r ${BLUE}%s${NC} %s" "$c" "$msg"; ((i++)); sleep 0.12; done ) &
|
||||||
_SPINNER_PID=$!
|
_SPINNER_PID=$!
|
||||||
}
|
}
|
||||||
stop_line_spinner() { if [[ -n "$_SPINNER_PID" ]]; then kill "$_SPINNER_PID" 2>/dev/null || true; wait "$_SPINNER_PID" 2>/dev/null || true; _SPINNER_PID=""; printf "\r"; fi; }
|
stop_line_spinner() { if [[ -n "$_SPINNER_PID" ]]; then kill "$_SPINNER_PID" 2>/dev/null || true; wait "$_SPINNER_PID" 2>/dev/null || true; _SPINNER_PID=""; printf "\r\033[K"; fi; }
|
||||||
|
|
||||||
|
|
||||||
# Verbosity (0 = quiet, 1 = verbose)
|
# Verbosity (0 = quiet, 1 = verbose)
|
||||||
VERBOSE=1
|
VERBOSE=1
|
||||||
|
|
||||||
|
# Icons (duplicated from lib/common.sh - necessary as install.sh runs standalone)
|
||||||
|
readonly ICON_SUCCESS="✓"
|
||||||
|
readonly ICON_ADMIN="●"
|
||||||
|
readonly ICON_CONFIRM="◎"
|
||||||
|
readonly ICON_ERROR="✗"
|
||||||
|
|
||||||
# Logging functions
|
# Logging functions
|
||||||
log_info() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}$1${NC}"; }
|
log_info() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}$1${NC}"; }
|
||||||
log_success() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${GREEN}$1${NC}"; }
|
log_success() { [[ ${VERBOSE} -eq 1 ]] && echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"; }
|
||||||
log_warning() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${YELLOW}$1${NC}"; }
|
log_warning() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${YELLOW}$1${NC}"; }
|
||||||
log_error() { echo -e "${RED}$1${NC}"; }
|
log_error() { echo -e "${RED}${ICON_ERROR}${NC} $1"; }
|
||||||
|
log_admin() { [[ ${VERBOSE} -eq 1 ]] && echo -e " ${BLUE}${ICON_ADMIN}${NC} $1"; }
|
||||||
|
log_confirm() { [[ ${VERBOSE} -eq 1 ]] && echo -e "${BLUE}${ICON_CONFIRM}${NC} $1"; }
|
||||||
|
|
||||||
# Default installation directory
|
# Default installation directory
|
||||||
INSTALL_DIR="/usr/local/bin"
|
INSTALL_DIR="/usr/local/bin"
|
||||||
@@ -239,12 +247,14 @@ install_files() {
|
|||||||
if [[ -f "$SOURCE_DIR/mole" ]]; then
|
if [[ -f "$SOURCE_DIR/mole" ]]; then
|
||||||
if [[ "$source_dir_abs" != "$install_dir_abs" ]]; then
|
if [[ "$source_dir_abs" != "$install_dir_abs" ]]; then
|
||||||
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
||||||
|
log_admin "Admin access required for /usr/local/bin"
|
||||||
sudo cp "$SOURCE_DIR/mole" "$INSTALL_DIR/mole"
|
sudo cp "$SOURCE_DIR/mole" "$INSTALL_DIR/mole"
|
||||||
sudo chmod +x "$INSTALL_DIR/mole"
|
sudo chmod +x "$INSTALL_DIR/mole"
|
||||||
else
|
else
|
||||||
cp "$SOURCE_DIR/mole" "$INSTALL_DIR/mole"
|
cp "$SOURCE_DIR/mole" "$INSTALL_DIR/mole"
|
||||||
chmod +x "$INSTALL_DIR/mole"
|
chmod +x "$INSTALL_DIR/mole"
|
||||||
fi
|
fi
|
||||||
|
log_success "Installed mole to $INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "mole executable not found in ${SOURCE_DIR:-unknown}"
|
log_error "mole executable not found in ${SOURCE_DIR:-unknown}"
|
||||||
@@ -254,7 +264,7 @@ install_files() {
|
|||||||
# Install mo alias for Mole if available
|
# Install mo alias for Mole if available
|
||||||
if [[ -f "$SOURCE_DIR/mo" ]]; then
|
if [[ -f "$SOURCE_DIR/mo" ]]; then
|
||||||
if [[ "$source_dir_abs" == "$install_dir_abs" ]]; then
|
if [[ "$source_dir_abs" == "$install_dir_abs" ]]; then
|
||||||
log_info "mo alias already present in $INSTALL_DIR"
|
log_success "mo alias already present"
|
||||||
else
|
else
|
||||||
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
||||||
sudo cp "$SOURCE_DIR/mo" "$INSTALL_DIR/mo"
|
sudo cp "$SOURCE_DIR/mo" "$INSTALL_DIR/mo"
|
||||||
@@ -263,6 +273,7 @@ install_files() {
|
|||||||
cp "$SOURCE_DIR/mo" "$INSTALL_DIR/mo"
|
cp "$SOURCE_DIR/mo" "$INSTALL_DIR/mo"
|
||||||
chmod +x "$INSTALL_DIR/mo"
|
chmod +x "$INSTALL_DIR/mo"
|
||||||
fi
|
fi
|
||||||
|
log_success "Installed mo alias"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -271,10 +282,11 @@ install_files() {
|
|||||||
local source_bin_abs="$(cd "$SOURCE_DIR/bin" && pwd)"
|
local source_bin_abs="$(cd "$SOURCE_DIR/bin" && pwd)"
|
||||||
local config_bin_abs="$(cd "$CONFIG_DIR/bin" && pwd)"
|
local config_bin_abs="$(cd "$CONFIG_DIR/bin" && pwd)"
|
||||||
if [[ "$source_bin_abs" == "$config_bin_abs" ]]; then
|
if [[ "$source_bin_abs" == "$config_bin_abs" ]]; then
|
||||||
log_info "Configuration bin directory already synced"
|
log_success "Modules already synced"
|
||||||
else
|
else
|
||||||
cp -r "$SOURCE_DIR/bin"/* "$CONFIG_DIR/bin/"
|
cp -r "$SOURCE_DIR/bin"/* "$CONFIG_DIR/bin/"
|
||||||
chmod +x "$CONFIG_DIR/bin"/*
|
chmod +x "$CONFIG_DIR/bin"/*
|
||||||
|
log_success "Installed modules"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -282,9 +294,10 @@ install_files() {
|
|||||||
local source_lib_abs="$(cd "$SOURCE_DIR/lib" && pwd)"
|
local source_lib_abs="$(cd "$SOURCE_DIR/lib" && pwd)"
|
||||||
local config_lib_abs="$(cd "$CONFIG_DIR/lib" && pwd)"
|
local config_lib_abs="$(cd "$CONFIG_DIR/lib" && pwd)"
|
||||||
if [[ "$source_lib_abs" == "$config_lib_abs" ]]; then
|
if [[ "$source_lib_abs" == "$config_lib_abs" ]]; then
|
||||||
log_info "Configuration lib directory already synced"
|
log_success "Libraries already synced"
|
||||||
else
|
else
|
||||||
cp -r "$SOURCE_DIR/lib"/* "$CONFIG_DIR/lib/"
|
cp -r "$SOURCE_DIR/lib"/* "$CONFIG_DIR/lib/"
|
||||||
|
log_success "Installed libraries"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -355,6 +368,8 @@ print_usage_summary() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
local message="Mole ${action} successfully"
|
local message="Mole ${action} successfully"
|
||||||
|
|
||||||
if [[ "$action" == "updated" && -n "$previous_version" && -n "$new_version" && "$previous_version" != "$new_version" ]]; then
|
if [[ "$action" == "updated" && -n "$previous_version" && -n "$new_version" && "$previous_version" != "$new_version" ]]; then
|
||||||
@@ -363,7 +378,7 @@ print_usage_summary() {
|
|||||||
message+=" (version ${new_version})"
|
message+=" (version ${new_version})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_success "$message!"
|
log_confirm "$message"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
@@ -389,16 +404,18 @@ print_usage_summary() {
|
|||||||
|
|
||||||
# Uninstall function
|
# Uninstall function
|
||||||
uninstall_mole() {
|
uninstall_mole() {
|
||||||
log_info "Uninstalling mole..."
|
log_confirm "Uninstalling Mole"
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Remove executable
|
# Remove executable
|
||||||
if [[ -f "$INSTALL_DIR/mole" ]]; then
|
if [[ -f "$INSTALL_DIR/mole" ]]; then
|
||||||
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]] && [[ ! -w "$INSTALL_DIR" ]]; then
|
||||||
|
log_admin "Admin access required"
|
||||||
sudo rm -f "$INSTALL_DIR/mole"
|
sudo rm -f "$INSTALL_DIR/mole"
|
||||||
else
|
else
|
||||||
rm -f "$INSTALL_DIR/mole"
|
rm -f "$INSTALL_DIR/mole"
|
||||||
fi
|
fi
|
||||||
log_success "Removed executable from $INSTALL_DIR"
|
log_success "Removed mole executable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$INSTALL_DIR/mo" ]]; then
|
if [[ -f "$INSTALL_DIR/mo" ]]; then
|
||||||
@@ -407,7 +424,7 @@ uninstall_mole() {
|
|||||||
else
|
else
|
||||||
rm -f "$INSTALL_DIR/mo"
|
rm -f "$INSTALL_DIR/mo"
|
||||||
fi
|
fi
|
||||||
log_success "Removed mo alias from $INSTALL_DIR"
|
log_success "Removed mo alias"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# SAFETY CHECK: Verify config directory is safe to remove
|
# SAFETY CHECK: Verify config directory is safe to remove
|
||||||
@@ -442,14 +459,15 @@ uninstall_mole() {
|
|||||||
echo ""
|
echo ""
|
||||||
read -p "Remove configuration directory $CONFIG_DIR? (y/N): " -n 1 -r; echo ""; if [[ $REPLY =~ ^[Yy]$ ]]; then
|
read -p "Remove configuration directory $CONFIG_DIR? (y/N): " -n 1 -r; echo ""; if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
rm -rf "$CONFIG_DIR"
|
rm -rf "$CONFIG_DIR"
|
||||||
log_success "Removed configuration directory"
|
log_success "Removed configuration"
|
||||||
else
|
else
|
||||||
log_info "Configuration directory preserved"
|
log_success "Configuration preserved"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_success "Mole uninstalled successfully"
|
echo ""
|
||||||
|
log_confirm "Mole uninstalled successfully"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main installation function
|
# Main installation function
|
||||||
@@ -486,12 +504,26 @@ perform_update() {
|
|||||||
update_via_homebrew "$VERSION"
|
update_via_homebrew "$VERSION"
|
||||||
else
|
else
|
||||||
# Fallback: inline implementation
|
# Fallback: inline implementation
|
||||||
echo -e "${BLUE}|${NC} Updating Homebrew..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_line_spinner "Updating Homebrew..."
|
||||||
|
else
|
||||||
|
echo "Updating Homebrew..."
|
||||||
|
fi
|
||||||
brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true
|
brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_line_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}|${NC} Upgrading Mole..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_line_spinner "Upgrading Mole..."
|
||||||
|
else
|
||||||
|
echo "Upgrading Mole..."
|
||||||
|
fi
|
||||||
local upgrade_output
|
local upgrade_output
|
||||||
upgrade_output=$(brew upgrade mole 2>&1) || true
|
upgrade_output=$(brew upgrade mole 2>&1) || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_line_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
if echo "$upgrade_output" | grep -q "already installed"; then
|
if echo "$upgrade_output" | grep -q "already installed"; then
|
||||||
local current_version
|
local current_version
|
||||||
|
|||||||
@@ -57,23 +57,32 @@ batch_uninstall_applications() {
|
|||||||
# Format size display (convert KB to bytes for bytes_to_human())
|
# Format size display (convert KB to bytes for bytes_to_human())
|
||||||
local size_display=$(bytes_to_human "$((total_estimated_size * 1024))")
|
local size_display=$(bytes_to_human "$((total_estimated_size * 1024))")
|
||||||
|
|
||||||
# Request sudo access if needed (do this before confirmation)
|
# Show summary and get batch confirmation first (before asking for password)
|
||||||
|
local app_total=${#selected_apps[@]}
|
||||||
|
local app_text="app"
|
||||||
|
[[ $app_total -gt 1 ]] && app_text="apps"
|
||||||
|
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
||||||
|
echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Force quit: ${running_apps[*]} | Enter=go / ESC=q: "
|
||||||
|
else
|
||||||
|
echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Enter=go / ESC=q: "
|
||||||
|
fi
|
||||||
|
IFS= read -r -s -n1 key || key=""
|
||||||
|
case "$key" in
|
||||||
|
$'\e'|q|Q) echo ""; return 0 ;;
|
||||||
|
""|$'\n'|$'\r'|y|Y) echo "" ;;
|
||||||
|
*) echo ""; return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# User confirmed, now request sudo access if needed
|
||||||
if [[ ${#sudo_apps[@]} -gt 0 ]]; then
|
if [[ ${#sudo_apps[@]} -gt 0 ]]; then
|
||||||
# Check if sudo is already cached
|
# Check if sudo is already cached
|
||||||
if sudo -n true 2>/dev/null; then
|
if ! sudo -n true 2>/dev/null; then
|
||||||
echo "◎ Admin access confirmed for: ${sudo_apps[*]}"
|
if ! request_sudo_access "Admin required for system apps: ${sudo_apps[*]}"; then
|
||||||
else
|
|
||||||
echo "◎ Admin required for: ${sudo_apps[*]}"
|
|
||||||
echo ""
|
|
||||||
if ! request_sudo_access "Uninstalling system apps requires admin access"; then
|
|
||||||
echo ""
|
echo ""
|
||||||
log_error "Admin access denied"
|
log_error "Admin access denied"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
echo ""
|
|
||||||
echo "✓ Admin access granted"
|
|
||||||
fi
|
fi
|
||||||
echo "◎ Gathering targets..."
|
|
||||||
(while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null) &
|
(while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null) &
|
||||||
local sudo_keepalive_pid=$!
|
local sudo_keepalive_pid=$!
|
||||||
local _trap_cleanup_cmd="kill $sudo_keepalive_pid 2>/dev/null || true; wait $sudo_keepalive_pid 2>/dev/null || true"
|
local _trap_cleanup_cmd="kill $sudo_keepalive_pid 2>/dev/null || true; wait $sudo_keepalive_pid 2>/dev/null || true"
|
||||||
@@ -87,22 +96,7 @@ batch_uninstall_applications() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show summary and get batch confirmation
|
echo ""
|
||||||
local app_total=${#selected_apps[@]}
|
|
||||||
if [[ ${#running_apps[@]} -gt 0 ]]; then
|
|
||||||
echo -n "${BLUE}◎ Remove ${app_total} app(s) (${size_display}) | Quit: ${running_apps[*]} | Enter=go / ESC=q:${NC} "
|
|
||||||
else
|
|
||||||
echo -n "${BLUE}◎ Remove ${app_total} app(s) (${size_display}) | Enter=go / ESC=q:${NC} "
|
|
||||||
fi
|
|
||||||
IFS= read -r -s -n1 key || key=""
|
|
||||||
case "$key" in
|
|
||||||
$'\e'|q|Q) echo ""; return 0 ;;
|
|
||||||
""|$'\n'|$'\r'|y|Y) echo "" ;;
|
|
||||||
*) echo ""; return 0 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo -n "◎ Starting in 3s... 3"; sleep 1; echo -ne "\r◎ Starting in 3s... 2"; sleep 1; echo -ne "\r◎ Starting in 3s... 1"; sleep 1
|
|
||||||
echo -ne "\r\033[K"
|
|
||||||
if [[ -t 1 ]]; then start_inline_spinner "Uninstalling apps..."; fi
|
if [[ -t 1 ]]; then start_inline_spinner "Uninstalling apps..."; fi
|
||||||
|
|
||||||
# Force quit running apps first (batch)
|
# Force quit running apps first (batch)
|
||||||
@@ -113,11 +107,11 @@ batch_uninstall_applications() {
|
|||||||
if pgrep -f "${running_apps[0]}" >/dev/null 2>&1; then sleep 1; fi
|
if pgrep -f "${running_apps[0]}" >/dev/null 2>&1; then sleep 1; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Perform uninstallations (compact output)
|
# Perform uninstallations (silent mode, show results at end)
|
||||||
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
echo ""
|
|
||||||
local success_count=0 failed_count=0
|
local success_count=0 failed_count=0
|
||||||
local -a failed_items=()
|
local -a failed_items=()
|
||||||
|
local -a success_items=()
|
||||||
for detail in "${app_details[@]}"; do
|
for detail in "${app_details[@]}"; do
|
||||||
IFS='|' read -r app_name app_path bundle_id total_kb encoded_files <<< "$detail"
|
IFS='|' read -r app_name app_path bundle_id total_kb encoded_files <<< "$detail"
|
||||||
local related_files=$(echo "$encoded_files" | base64 -d)
|
local related_files=$(echo "$encoded_files" | base64 -d)
|
||||||
@@ -144,7 +138,7 @@ batch_uninstall_applications() {
|
|||||||
((success_count++))
|
((success_count++))
|
||||||
((files_cleaned++))
|
((files_cleaned++))
|
||||||
((total_items++))
|
((total_items++))
|
||||||
printf " ${GREEN}OK${NC} %-20s%s\n" "$app_name" $([[ $files_removed -gt 0 ]] && echo "+$files_removed" )
|
success_items+=("$app_name")
|
||||||
else
|
else
|
||||||
((failed_count++))
|
((failed_count++))
|
||||||
failed_items+=("$app_name:$reason")
|
failed_items+=("$app_name:$reason")
|
||||||
@@ -152,32 +146,36 @@ batch_uninstall_applications() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
local freed_display="0B"
|
local freed_display=$(bytes_to_human "$((total_size_freed * 1024))")
|
||||||
if [[ $total_size_freed -gt 0 ]]; then
|
|
||||||
local freed_kb=$total_size_freed
|
|
||||||
if [[ $freed_kb -ge 1048576 ]]; then
|
|
||||||
freed_display=$(echo "$freed_kb" | awk '{printf "%.2fGB", $1/1024/1024}')
|
|
||||||
elif [[ $freed_kb -ge 1024 ]]; then
|
|
||||||
freed_display=$(echo "$freed_kb" | awk '{printf "%.1fMB", $1/1024}')
|
|
||||||
else
|
|
||||||
freed_display="${freed_kb}KB"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
local bar="================================================================================"
|
local bar="================================================================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "$bar"
|
echo "$bar"
|
||||||
|
if [[ $success_count -gt 0 ]]; then
|
||||||
|
local success_list="${success_items[*]}"
|
||||||
|
echo -e "Removed: ${GREEN}${success_list}${NC} | Freed: ${GREEN}${freed_display}${NC}"
|
||||||
|
fi
|
||||||
if [[ $failed_count -gt 0 ]]; then
|
if [[ $failed_count -gt 0 ]]; then
|
||||||
echo -e "Removed: ${GREEN}$success_count${NC} | Failed: ${RED}$failed_count${NC} | Freed: ${GREEN}$freed_display${NC}"
|
local failed_names=()
|
||||||
|
local reason_summary=""
|
||||||
|
for item in "${failed_items[@]}"; do
|
||||||
|
local name=${item%%:*}
|
||||||
|
failed_names+=("$name")
|
||||||
|
done
|
||||||
|
local failed_list="${failed_names[*]}"
|
||||||
|
|
||||||
|
# Determine primary reason
|
||||||
if [[ $failed_count -eq 1 ]]; then
|
if [[ $failed_count -eq 1 ]]; then
|
||||||
local first="${failed_items[0]}"
|
local first_reason=${failed_items[0]#*:}
|
||||||
local name=${first%%:*}
|
case "$first_reason" in
|
||||||
local reason=${first#*:}
|
still*running*) reason_summary="still running" ;;
|
||||||
echo "${name} $(map_uninstall_reason "$reason")"
|
remove*failed*) reason_summary="could not be removed" ;;
|
||||||
|
permission*) reason_summary="permission denied" ;;
|
||||||
|
*) reason_summary="$first_reason" ;;
|
||||||
|
esac
|
||||||
|
echo -e "Failed: ${RED}${failed_list}${NC} ${reason_summary}"
|
||||||
else
|
else
|
||||||
local joined="${failed_items[*]}"; echo "Failures: $joined"
|
echo -e "Failed: ${RED}${failed_list}${NC} could not be removed"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
echo -e "Removed: ${GREEN}$success_count${NC} | Failed: ${RED}$failed_count${NC} | Freed: ${GREEN}$freed_display${NC}"
|
|
||||||
fi
|
fi
|
||||||
echo "$bar"
|
echo "$bar"
|
||||||
|
|
||||||
|
|||||||
129
lib/common.sh
129
lib/common.sh
@@ -20,6 +20,15 @@ readonly RED="${ESC}[0;31m"
|
|||||||
readonly GRAY="${ESC}[0;90m"
|
readonly GRAY="${ESC}[0;90m"
|
||||||
readonly NC="${ESC}[0m"
|
readonly NC="${ESC}[0m"
|
||||||
|
|
||||||
|
# Icon definitions
|
||||||
|
readonly ICON_CONFIRM="◎" # Confirm operation
|
||||||
|
readonly ICON_ADMIN="●" # Admin permission
|
||||||
|
readonly ICON_SUCCESS="✓" # Success
|
||||||
|
readonly ICON_ERROR="✗" # Error
|
||||||
|
readonly ICON_EMPTY="○" # Empty state
|
||||||
|
readonly ICON_LIST="-" # List item
|
||||||
|
readonly ICON_MENU="▸" # Menu item
|
||||||
|
|
||||||
# Spinner character helpers (ASCII by default, overridable via env)
|
# Spinner character helpers (ASCII by default, overridable via env)
|
||||||
mo_spinner_chars() {
|
mo_spinner_chars() {
|
||||||
local chars="${MO_SPINNER_CHARS:-|/-\\}"
|
local chars="${MO_SPINNER_CHARS:-|/-\\}"
|
||||||
@@ -52,7 +61,7 @@ log_info() {
|
|||||||
|
|
||||||
log_success() {
|
log_success() {
|
||||||
rotate_log
|
rotate_log
|
||||||
echo -e " ${GREEN}✓${NC} $1"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE" 2>/dev/null || true
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,16 +73,47 @@ log_warning() {
|
|||||||
|
|
||||||
log_error() {
|
log_error() {
|
||||||
rotate_log
|
rotate_log
|
||||||
echo -e "${RED}$1${NC}" >&2
|
echo -e "${RED}${ICON_ERROR}${NC} $1" >&2
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE" 2>/dev/null || true
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
log_header() {
|
log_header() {
|
||||||
rotate_log
|
rotate_log
|
||||||
echo -e "\n${PURPLE}▶ $1${NC}"
|
echo -e "\n${PURPLE}${ICON_MENU} $1${NC}"
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECTION: $1" >> "$LOG_FILE" 2>/dev/null || true
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECTION: $1" >> "$LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Icon output helpers
|
||||||
|
icon_confirm() {
|
||||||
|
echo -e "${BLUE}${ICON_CONFIRM}${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_admin() {
|
||||||
|
echo -e "${BLUE}${ICON_ADMIN}${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_success() {
|
||||||
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_error() {
|
||||||
|
echo -e " ${RED}${ICON_ERROR}${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_empty() {
|
||||||
|
echo -e " ${BLUE}${ICON_EMPTY}${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_list() {
|
||||||
|
echo -e " ${ICON_LIST} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_menu() {
|
||||||
|
local num="$1"
|
||||||
|
local text="$2"
|
||||||
|
echo -e "${BLUE}${ICON_MENU} ${num}. ${text}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
# System detection
|
# System detection
|
||||||
detect_architecture() {
|
detect_architecture() {
|
||||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||||
@@ -276,7 +316,7 @@ request_sudo_access() {
|
|||||||
|
|
||||||
# If Touch ID is supported and not forced to use password
|
# If Touch ID is supported and not forced to use password
|
||||||
if [[ "$force_password" != "true" ]] && check_touchid_support; then
|
if [[ "$force_password" != "true" ]] && check_touchid_support; then
|
||||||
echo -e "${BLUE}${prompt_msg}${NC} ${GRAY}(Touch ID or password)${NC}"
|
echo -e "${BLUE}${ICON_ADMIN}${NC} ${prompt_msg} ${GRAY}(Touch ID or password)${NC}"
|
||||||
if sudo -v 2>/dev/null; then
|
if sudo -v 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -284,8 +324,8 @@ request_sudo_access() {
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Traditional password method
|
# Traditional password method
|
||||||
echo -e "${BLUE}${prompt_msg}${NC}"
|
echo -e "${BLUE}${ICON_ADMIN}${NC} ${prompt_msg}"
|
||||||
echo -ne "${BLUE} Password> ${NC}"
|
echo -ne "${BLUE}${ICON_MENU}${NC} Password: "
|
||||||
read -s password
|
read -s password
|
||||||
echo ""
|
echo ""
|
||||||
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
||||||
@@ -313,13 +353,27 @@ request_sudo() {
|
|||||||
update_via_homebrew() {
|
update_via_homebrew() {
|
||||||
local version="${1:-unknown}"
|
local version="${1:-unknown}"
|
||||||
|
|
||||||
echo -e "${BLUE}|${NC} Updating Homebrew..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Updating Homebrew..."
|
||||||
|
else
|
||||||
|
echo "Updating Homebrew..."
|
||||||
|
fi
|
||||||
# Filter out common noise but show important info
|
# Filter out common noise but show important info
|
||||||
brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true
|
brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}|${NC} Upgrading Mole..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Upgrading Mole..."
|
||||||
|
else
|
||||||
|
echo "Upgrading Mole..."
|
||||||
|
fi
|
||||||
local upgrade_output
|
local upgrade_output
|
||||||
upgrade_output=$(brew upgrade mole 2>&1) || true
|
upgrade_output=$(brew upgrade mole 2>&1) || true
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
if echo "$upgrade_output" | grep -q "already installed"; then
|
if echo "$upgrade_output" | grep -q "already installed"; then
|
||||||
# Get current version
|
# Get current version
|
||||||
@@ -397,17 +451,21 @@ start_inline_spinner() {
|
|||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
(
|
(
|
||||||
|
trap 'exit 0' TERM INT EXIT
|
||||||
local chars
|
local chars
|
||||||
chars="$(mo_spinner_chars)"
|
chars="$(mo_spinner_chars)"
|
||||||
|
[[ -z "$chars" ]] && chars='|/-\'
|
||||||
local i=0
|
local i=0
|
||||||
while true; do
|
while true; do
|
||||||
local c="${chars:$((i % ${#chars})):1}"
|
local c="${chars:$((i % ${#chars})):1}"
|
||||||
printf "\r${MOLE_SPINNER_PREFIX:-}${BLUE}%s${NC} %s" "$c" "$message"
|
printf "\r${MOLE_SPINNER_PREFIX:-}${BLUE}%s${NC} %s" "$c" "$message" 2>/dev/null || exit 0
|
||||||
((i++))
|
((i++))
|
||||||
sleep 0.12
|
# macOS supports decimal sleep, this is the primary target
|
||||||
|
sleep 0.1 2>/dev/null || sleep 1 2>/dev/null || exit 0
|
||||||
done
|
done
|
||||||
) &
|
) &
|
||||||
INLINE_SPINNER_PID=$!
|
INLINE_SPINNER_PID=$!
|
||||||
|
disown 2>/dev/null || true
|
||||||
else
|
else
|
||||||
echo -n " ${BLUE}|${NC} $message"
|
echo -n " ${BLUE}|${NC} $message"
|
||||||
fi
|
fi
|
||||||
@@ -419,7 +477,7 @@ stop_inline_spinner() {
|
|||||||
kill "$INLINE_SPINNER_PID" 2>/dev/null || true
|
kill "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||||
wait "$INLINE_SPINNER_PID" 2>/dev/null || true
|
wait "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||||
INLINE_SPINNER_PID=""
|
INLINE_SPINNER_PID=""
|
||||||
[[ -t 1 ]] && printf "\r"
|
[[ -t 1 ]] && printf "\r\033[K"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,10 +614,45 @@ parallel_execute() {
|
|||||||
# Set MOLE_SPINNER_PREFIX=" " for indented spinner (e.g., in clean context)
|
# Set MOLE_SPINNER_PREFIX=" " for indented spinner (e.g., in clean context)
|
||||||
with_spinner() {
|
with_spinner() {
|
||||||
local msg="$1"; shift || true
|
local msg="$1"; shift || true
|
||||||
|
local timeout="${MOLE_CMD_TIMEOUT:-180}" # Default 3min timeout
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
start_inline_spinner "$msg"
|
start_inline_spinner "$msg"
|
||||||
fi
|
fi
|
||||||
"$@" >/dev/null 2>&1 || return $?
|
|
||||||
|
# Run command with timeout protection
|
||||||
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
# GNU timeout available
|
||||||
|
timeout "$timeout" "$@" >/dev/null 2>&1 || {
|
||||||
|
local exit_code=$?
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
|
# Exit code 124 means timeout
|
||||||
|
[[ $exit_code -eq 124 ]] && echo -e " ${YELLOW}⚠${NC} $msg timed out (skipped)" >&2
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
else
|
||||||
|
# Fallback: run in background with manual timeout
|
||||||
|
"$@" >/dev/null 2>&1 &
|
||||||
|
local cmd_pid=$!
|
||||||
|
local elapsed=0
|
||||||
|
while kill -0 $cmd_pid 2>/dev/null; do
|
||||||
|
if [[ $elapsed -ge $timeout ]]; then
|
||||||
|
kill -TERM $cmd_pid 2>/dev/null || true
|
||||||
|
wait $cmd_pid 2>/dev/null || true
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
|
echo -e " ${YELLOW}⚠${NC} $msg timed out (skipped)" >&2
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed++))
|
||||||
|
done
|
||||||
|
wait $cmd_pid 2>/dev/null || {
|
||||||
|
local exit_code=$?
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
stop_inline_spinner
|
stop_inline_spinner
|
||||||
fi
|
fi
|
||||||
@@ -575,8 +668,16 @@ clean_tool_cache() {
|
|||||||
echo -e " ${YELLOW}→${NC} $label (would clean)"
|
echo -e " ${YELLOW}→${NC} $label (would clean)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
MOLE_SPINNER_PREFIX=" " with_spinner "$label" "$@"
|
if MOLE_SPINNER_PREFIX=" " with_spinner "$label" "$@"; then
|
||||||
echo -e " ${GREEN}✓${NC} $label"
|
echo -e " ${GREEN}✓${NC} $label"
|
||||||
|
else
|
||||||
|
local exit_code=$?
|
||||||
|
# Timeout returns 124, don't show error message (already shown by with_spinner)
|
||||||
|
if [[ $exit_code -ne 124 ]]; then
|
||||||
|
echo -e " ${YELLOW}⚠${NC} $label failed (skipped)" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0 # Always return success to continue cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -42,13 +42,21 @@ collect_files_to_be_cleaned() {
|
|||||||
local clean_sh="$SCRIPT_DIR/../bin/clean.sh"
|
local clean_sh="$SCRIPT_DIR/../bin/clean.sh"
|
||||||
local -a items=()
|
local -a items=()
|
||||||
|
|
||||||
echo -e "${BLUE}|${NC} Scanning cache files..."
|
if [[ -t 1 ]]; then
|
||||||
echo ""
|
start_inline_spinner "Scanning cache files..."
|
||||||
|
else
|
||||||
|
echo "Scanning cache files..."
|
||||||
|
fi
|
||||||
|
|
||||||
# Run clean.sh in dry-run mode
|
# Run clean.sh in dry-run mode
|
||||||
local temp_output=$(create_temp_file)
|
local temp_output=$(create_temp_file)
|
||||||
echo "" | bash "$clean_sh" --dry-run 2>&1 > "$temp_output" || true
|
echo "" | bash "$clean_sh" --dry-run 2>&1 > "$temp_output" || true
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Strip ANSI color codes for parsing
|
# Strip ANSI color codes for parsing
|
||||||
local temp_plain=$(create_temp_file)
|
local temp_plain=$(create_temp_file)
|
||||||
sed $'s/\033\[[0-9;]*m//g' "$temp_output" > "$temp_plain"
|
sed $'s/\033\[[0-9;]*m//g' "$temp_output" > "$temp_plain"
|
||||||
|
|||||||
2
mo
2
mo
@@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Lightweight alias to run Mole via `mo`
|
# Lightweight alias to run Mole via `mo`
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
exec "$SCRIPT_DIR/mole" "$@"
|
exec "$SCRIPT_DIR/mole" "$@"
|
||||||
|
|||||||
107
mole
107
mole
@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
source "$SCRIPT_DIR/lib/common.sh"
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
|
|
||||||
# Version info
|
# Version info
|
||||||
VERSION="1.7.0"
|
VERSION="1.7.1"
|
||||||
MOLE_TAGLINE="can dig deep to clean your Mac."
|
MOLE_TAGLINE="can dig deep to clean your Mac."
|
||||||
|
|
||||||
# Check for updates (non-blocking, cached)
|
# Check for updates (non-blocking, cached)
|
||||||
@@ -148,7 +148,11 @@ update_mole() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Download and run installer with progress
|
# Download and run installer with progress
|
||||||
echo -e "${BLUE}|${NC} Downloading latest version..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Downloading latest version..."
|
||||||
|
else
|
||||||
|
echo "Downloading latest version..."
|
||||||
|
fi
|
||||||
|
|
||||||
local installer_url="https://raw.githubusercontent.com/tw93/mole/main/install.sh"
|
local installer_url="https://raw.githubusercontent.com/tw93/mole/main/install.sh"
|
||||||
local tmp_installer
|
local tmp_installer
|
||||||
@@ -157,22 +161,26 @@ update_mole() {
|
|||||||
# Download installer with progress
|
# Download installer with progress
|
||||||
if command -v curl >/dev/null 2>&1; then
|
if command -v curl >/dev/null 2>&1; then
|
||||||
if ! curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1; then
|
if ! curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1; then
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
rm -f "$tmp_installer"
|
rm -f "$tmp_installer"
|
||||||
log_error "Update failed. Check network connection."
|
log_error "Update failed. Check network connection."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
if ! wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1; then
|
if ! wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1; then
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
rm -f "$tmp_installer"
|
rm -f "$tmp_installer"
|
||||||
log_error "Update failed. Check network connection."
|
log_error "Update failed. Check network connection."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
rm -f "$tmp_installer"
|
rm -f "$tmp_installer"
|
||||||
log_error "curl or wget required"
|
log_error "curl or wget required"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
chmod +x "$tmp_installer"
|
chmod +x "$tmp_installer"
|
||||||
|
|
||||||
# Determine install directory
|
# Determine install directory
|
||||||
@@ -181,23 +189,35 @@ update_mole() {
|
|||||||
local install_dir
|
local install_dir
|
||||||
install_dir="$(cd "$(dirname "$mole_path")" && pwd)"
|
install_dir="$(cd "$(dirname "$mole_path")" && pwd)"
|
||||||
|
|
||||||
echo -e "${BLUE}|${NC} Installing update..."
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Installing update..."
|
||||||
|
else
|
||||||
|
echo "Installing update..."
|
||||||
|
fi
|
||||||
|
|
||||||
# Run installer with visible output (but capture for error handling)
|
# Run installer with visible output (but capture for error handling)
|
||||||
local install_output
|
local install_output
|
||||||
if install_output=$("$tmp_installer" --prefix "$install_dir" --config "$HOME/.config/mole" --update 2>&1); then
|
if install_output=$("$tmp_installer" --prefix "$install_dir" --config "$HOME/.config/mole" --update 2>&1); then
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
echo "$install_output" | grep -Ev "^$" || true
|
echo "$install_output" | grep -Ev "^$" || true
|
||||||
local new_version
|
# Only show success message if not already shown by installer
|
||||||
new_version=$("$mole_path" --version 2>/dev/null | awk 'NF {print $NF}' || echo "")
|
if ! echo "$install_output" | grep -q "Already on latest version"; then
|
||||||
echo -e "${GREEN}✓${NC} Updated to latest version (${new_version:-unknown})"
|
|
||||||
else
|
|
||||||
# Retry without --update flag
|
|
||||||
if install_output=$("$tmp_installer" --prefix "$install_dir" --config "$HOME/.config/mole" 2>&1); then
|
|
||||||
echo "$install_output" | grep -Ev "^$" || true
|
|
||||||
local new_version
|
local new_version
|
||||||
new_version=$("$mole_path" --version 2>/dev/null | awk 'NF {print $NF}' || echo "")
|
new_version=$("$mole_path" --version 2>/dev/null | awk 'NF {print $NF}' || echo "")
|
||||||
echo -e "${GREEN}✓${NC} Updated to latest version (${new_version:-unknown})"
|
echo -e "${GREEN}✓${NC} Updated to latest version (${new_version:-unknown})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Retry without --update flag
|
||||||
|
if install_output=$("$tmp_installer" --prefix "$install_dir" --config "$HOME/.config/mole" 2>&1); then
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
|
echo "$install_output" | grep -Ev "^$" || true
|
||||||
|
if ! echo "$install_output" | grep -q "Already on latest version"; then
|
||||||
|
local new_version
|
||||||
|
new_version=$("$mole_path" --version 2>/dev/null | awk 'NF {print $NF}' || echo "")
|
||||||
|
echo -e "${GREEN}✓${NC} Updated to latest version (${new_version:-unknown})"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
||||||
rm -f "$tmp_installer"
|
rm -f "$tmp_installer"
|
||||||
log_error "Update failed"
|
log_error "Update failed"
|
||||||
echo "$install_output" | tail -10 >&2 # Show last 10 lines of error
|
echo "$install_output" | tail -10 >&2 # Show last 10 lines of error
|
||||||
@@ -216,7 +236,13 @@ remove_mole() {
|
|||||||
echo -e "${YELLOW}Remove Mole${NC}"
|
echo -e "${YELLOW}Remove Mole${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Detect all installations
|
# Detect all installations with loading
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Detecting Mole installations..."
|
||||||
|
else
|
||||||
|
echo "Detecting installations..."
|
||||||
|
fi
|
||||||
|
|
||||||
local is_homebrew=false
|
local is_homebrew=false
|
||||||
local -a manual_installs=()
|
local -a manual_installs=()
|
||||||
local -a alias_installs=()
|
local -a alias_installs=()
|
||||||
@@ -254,43 +280,52 @@ remove_mole() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Show what will be removed
|
if [[ -t 1 ]]; then
|
||||||
echo "This will remove:"
|
stop_inline_spinner
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [[ "$is_homebrew" == "true" ]]; then
|
|
||||||
echo -e " ${GREEN}✓${NC} Mole (via Homebrew)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#manual_installs[@]} -gt 0 ]]; then
|
# Check if anything to remove
|
||||||
for install in "${manual_installs[@]}"; do
|
|
||||||
echo -e " ${GREEN}✓${NC} $install"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#alias_installs[@]} -gt 0 ]]; then
|
|
||||||
for alias in "${alias_installs[@]}"; do
|
|
||||||
echo -e " ${GREEN}✓${NC} $alias"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e " ${GREEN}✓${NC} ~/.config/mole/ (configuration)"
|
|
||||||
echo -e " ${GREEN}✓${NC} ~/.cache/mole/ (cache)"
|
|
||||||
|
|
||||||
if [[ "$is_homebrew" == "false" && ${#manual_installs[@]} -eq 0 && ${#alias_installs[@]} -eq 0 ]]; then
|
if [[ "$is_homebrew" == "false" && ${#manual_installs[@]} -eq 0 && ${#alias_installs[@]} -eq 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${YELLOW}No Mole installation detected${NC}"
|
echo -e "${YELLOW}No Mole installation detected${NC}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Show what will be removed
|
||||||
|
echo "Will remove:"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Confirm removal
|
if [[ "$is_homebrew" == "true" ]]; then
|
||||||
read -p "Are you sure you want to remove Mole? (y/N): " -n 1 -r; echo ""; if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
echo " - Mole via Homebrew"
|
||||||
echo "Cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for install in "${manual_installs[@]}" "${alias_installs[@]}"; do
|
||||||
|
echo " - $install"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo " - ~/.config/mole"
|
||||||
|
echo " - ~/.cache/mole"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -n "Press Enter to confirm, ESC or q to cancel: "
|
||||||
|
|
||||||
|
# Read single key
|
||||||
|
IFS= read -r -s -n1 key || key=""
|
||||||
|
echo ""
|
||||||
|
case "$key" in
|
||||||
|
$'\e'|q|Q)
|
||||||
|
echo "Cancelled"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
""|$'\n'|$'\r')
|
||||||
|
# Continue with removal
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Cancelled"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Remove Homebrew installation
|
# Remove Homebrew installation
|
||||||
|
|||||||
Reference in New Issue
Block a user