mirror of
https://github.com/tw93/Mole.git
synced 2026-02-14 15:52:29 +00:00
Complete automated testing
This commit is contained in:
330
bin/analyze.sh
330
bin/analyze.sh
@@ -74,7 +74,8 @@ scan_large_files() {
|
||||
local file=""
|
||||
while IFS= read -r file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
local size
|
||||
size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
echo "$size|$file"
|
||||
fi
|
||||
done < <(mdfind -onlyin "$target_path" "kMDItemFSSize > $MIN_LARGE_FILE_SIZE" 2>/dev/null) | \
|
||||
@@ -93,7 +94,8 @@ scan_medium_files() {
|
||||
local file=""
|
||||
while IFS= read -r file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
local size
|
||||
size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
echo "$size|$file"
|
||||
fi
|
||||
done < <(mdfind -onlyin "$target_path" \
|
||||
@@ -158,7 +160,8 @@ aggregate_by_directory() {
|
||||
# Get cache file path for a directory
|
||||
get_cache_file() {
|
||||
local target_path="$1"
|
||||
local path_hash=$(echo "$target_path" | md5 2>/dev/null || echo "$target_path" | shasum | cut -d' ' -f1)
|
||||
local path_hash
|
||||
path_hash=$(echo "$target_path" | md5 2>/dev/null || echo "$target_path" | shasum | cut -d' ' -f1)
|
||||
echo "$CACHE_DIR/scan_${path_hash}.cache"
|
||||
}
|
||||
|
||||
@@ -171,7 +174,8 @@ is_cache_valid() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || echo 0)))
|
||||
local cache_age
|
||||
cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || echo 0)))
|
||||
if [[ $cache_age -lt $max_age ]]; then
|
||||
return 0
|
||||
fi
|
||||
@@ -236,7 +240,8 @@ perform_scan() {
|
||||
local force_rescan="${2:-false}"
|
||||
|
||||
# Check cache first
|
||||
local cache_file=$(get_cache_file "$target_path")
|
||||
local cache_file
|
||||
cache_file=$(get_cache_file "$target_path")
|
||||
if [[ "$force_rescan" != "true" ]] && is_cache_valid "$cache_file" 3600; then
|
||||
log_info "Loading cached results for $target_path..."
|
||||
load_from_cache "$cache_file"
|
||||
@@ -328,8 +333,10 @@ generate_bar() {
|
||||
return
|
||||
fi
|
||||
|
||||
local filled=$((current * width / max))
|
||||
local empty=$((width - filled))
|
||||
local filled
|
||||
filled=$((current * width / max))
|
||||
local empty
|
||||
empty=$((width - filled))
|
||||
|
||||
# Ensure non-negative
|
||||
[[ $filled -lt 0 ]] && filled=0
|
||||
@@ -372,7 +379,8 @@ display_large_files_compact() {
|
||||
|
||||
local count=0
|
||||
local total_size=0
|
||||
local total_count=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
local total_count
|
||||
total_count=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
|
||||
# Calculate total size
|
||||
while IFS='|' read -r size path; do
|
||||
@@ -385,11 +393,15 @@ display_large_files_compact() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local filename=$(basename "$path")
|
||||
local dirname=$(basename "$(dirname "$path")")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local filename
|
||||
filename=$(basename "$path")
|
||||
local dirname
|
||||
dirname=$(basename "$(dirname "$path")")
|
||||
|
||||
local info=$(get_file_info "$path")
|
||||
local info
|
||||
info=$(get_file_info "$path")
|
||||
local badge="${info%|*}"
|
||||
printf " ${GREEN}%-8s${NC} %s %-40s ${GRAY}%s${NC}\n" \
|
||||
"$human_size" "$badge" "${filename:0:40}" "$dirname"
|
||||
@@ -398,7 +410,8 @@ display_large_files_compact() {
|
||||
done < "$temp_large"
|
||||
|
||||
echo ""
|
||||
local total_human=$(bytes_to_human "$total_size")
|
||||
local total_human
|
||||
total_human=$(bytes_to_human "$total_size")
|
||||
echo " ${GRAY}Found $total_count large files (>1GB), totaling $total_human${NC}"
|
||||
echo ""
|
||||
}
|
||||
@@ -430,13 +443,19 @@ display_large_files() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local percentage=$(calc_percentage "$size" "$max_size")
|
||||
local bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local filename=$(basename "$path")
|
||||
local dirname=$(dirname "$path" | sed "s|^$HOME|~|")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local percentage
|
||||
percentage=$(calc_percentage "$size" "$max_size")
|
||||
local bar
|
||||
bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local filename
|
||||
filename=$(basename "$path")
|
||||
local dirname
|
||||
dirname=$(dirname "$path" | sed "s|^$HOME|~|")
|
||||
|
||||
local info=$(get_file_info "$path")
|
||||
local info
|
||||
info=$(get_file_info "$path")
|
||||
local badge="${info%|*}"
|
||||
printf " %s [${GREEN}%s${NC}] %7s\n" "$bar" "$human_size" ""
|
||||
printf " %s %s\n" "$badge" "$filename"
|
||||
@@ -446,7 +465,8 @@ display_large_files() {
|
||||
done < "$temp_large"
|
||||
|
||||
# Show total count
|
||||
local total_count=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
local total_count
|
||||
total_count=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
if [[ $total_count -gt 10 ]]; then
|
||||
echo " ${GRAY}... and $((total_count - 10)) more files${NC}"
|
||||
echo ""
|
||||
@@ -479,17 +499,22 @@ display_directories_compact() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local percentage=$(calc_percentage "$size" "$total_size")
|
||||
local dirname=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local percentage
|
||||
percentage=$(calc_percentage "$size" "$total_size")
|
||||
local dirname
|
||||
dirname=$(basename "$path")
|
||||
|
||||
# Simple bar (10 chars)
|
||||
local bar_width=10
|
||||
local percentage_int=${percentage%.*} # Remove decimal part
|
||||
local filled=$((percentage_int * bar_width / 100))
|
||||
local filled
|
||||
filled=$((percentage_int * bar_width / 100))
|
||||
[[ $filled -gt $bar_width ]] && filled=$bar_width
|
||||
[[ $filled -lt 0 ]] && filled=0
|
||||
local empty=$((bar_width - filled))
|
||||
local empty
|
||||
empty=$((bar_width - filled))
|
||||
[[ $empty -lt 0 ]] && empty=0
|
||||
local bar=""
|
||||
if [[ $filled -gt 0 ]]; then
|
||||
@@ -538,11 +563,16 @@ display_directories() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local percentage=$(calc_percentage "$size" "$total_size")
|
||||
local bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
local dirname=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local percentage
|
||||
percentage=$(calc_percentage "$size" "$total_size")
|
||||
local bar
|
||||
bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local display_path
|
||||
display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
local dirname
|
||||
dirname=$(basename "$path")
|
||||
|
||||
printf " %s [${BLUE}%s${NC}] %5s%%\n" "$bar" "$human_size" "$percentage"
|
||||
printf " %s %s\n\n" "$BADGE_DIR" "$display_path"
|
||||
@@ -568,8 +598,10 @@ display_hotspots() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local display_path
|
||||
display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
|
||||
printf " %s\n" "$display_path"
|
||||
printf " ${GREEN}%s${NC} in ${YELLOW}%d${NC} large files\n\n" \
|
||||
@@ -589,9 +621,11 @@ display_cleanup_suggestions_compact() {
|
||||
# Check common cache locations (only if analyzing Library/Caches or system paths)
|
||||
if [[ "$CURRENT_PATH" == "$HOME/Library/Caches"* ]] || [[ "$CURRENT_PATH" == "$HOME/Library"* ]]; then
|
||||
if [[ -d "$HOME/Library/Caches" ]]; then
|
||||
local cache_size=$(du -sk "$HOME/Library/Caches" 2>/dev/null | cut -f1)
|
||||
local cache_size
|
||||
cache_size=$(du -sk "$HOME/Library/Caches" 2>/dev/null | cut -f1)
|
||||
if [[ $cache_size -gt 1048576 ]]; then # > 1GB
|
||||
local human=$(bytes_to_human $((cache_size * 1024)))
|
||||
local human
|
||||
human=$(bytes_to_human $((cache_size * 1024)))
|
||||
top_suggestion="Clear app caches ($human)"
|
||||
action_command="mole clean"
|
||||
((potential_space += cache_size * 1024))
|
||||
@@ -602,7 +636,8 @@ display_cleanup_suggestions_compact() {
|
||||
|
||||
# Check Downloads folder (only if analyzing Downloads)
|
||||
if [[ "$CURRENT_PATH" == "$HOME/Downloads"* ]]; then
|
||||
local old_files=$(find "$CURRENT_PATH" -type f -mtime +90 2>/dev/null | wc -l | tr -d ' ')
|
||||
local old_files
|
||||
old_files=$(find "$CURRENT_PATH" -type f -mtime +90 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ $old_files -gt 0 ]]; then
|
||||
[[ -z "$top_suggestion" ]] && top_suggestion="$old_files files older than 90 days found"
|
||||
[[ -z "$action_command" ]] && action_command="manually review old files"
|
||||
@@ -618,7 +653,8 @@ display_cleanup_suggestions_compact() {
|
||||
local dmg_size=$(mdfind -onlyin "$CURRENT_PATH" \
|
||||
"kMDItemFSSize > 500000000 && kMDItemDisplayName == '*.dmg'" 2>/dev/null | \
|
||||
xargs stat -f%z 2>/dev/null | awk '{sum+=$1} END {print sum}')
|
||||
local dmg_human=$(bytes_to_human "$dmg_size")
|
||||
local dmg_human
|
||||
dmg_human=$(bytes_to_human "$dmg_size")
|
||||
[[ -z "$top_suggestion" ]] && top_suggestion="$dmg_count DMG files ($dmg_human) can be removed"
|
||||
[[ -z "$action_command" ]] && action_command="manually delete DMG files"
|
||||
((potential_space += dmg_size))
|
||||
@@ -628,9 +664,11 @@ display_cleanup_suggestions_compact() {
|
||||
|
||||
# Check Xcode (only if in developer paths)
|
||||
if [[ "$CURRENT_PATH" == "$HOME/Library/Developer"* ]] && [[ -d "$HOME/Library/Developer/Xcode/DerivedData" ]]; then
|
||||
local xcode_size=$(du -sk "$HOME/Library/Developer/Xcode/DerivedData" 2>/dev/null | cut -f1)
|
||||
local xcode_size
|
||||
xcode_size=$(du -sk "$HOME/Library/Developer/Xcode/DerivedData" 2>/dev/null | cut -f1)
|
||||
if [[ $xcode_size -gt 10485760 ]]; then
|
||||
local xcode_human=$(bytes_to_human $((xcode_size * 1024)))
|
||||
local xcode_human
|
||||
xcode_human=$(bytes_to_human $((xcode_size * 1024)))
|
||||
[[ -z "$top_suggestion" ]] && top_suggestion="Xcode cache ($xcode_human) can be cleared"
|
||||
[[ -z "$action_command" ]] && action_command="mole clean"
|
||||
((potential_space += xcode_size * 1024))
|
||||
@@ -656,7 +694,8 @@ display_cleanup_suggestions_compact() {
|
||||
echo " ${GRAY}... and $((suggestions_count - 1)) more insights${NC}"
|
||||
fi
|
||||
if [[ $potential_space -gt 0 ]]; then
|
||||
local space_human=$(bytes_to_human "$potential_space")
|
||||
local space_human
|
||||
space_human=$(bytes_to_human "$potential_space")
|
||||
echo " ${GREEN}Potential recovery: ~$space_human${NC}"
|
||||
fi
|
||||
echo ""
|
||||
@@ -680,16 +719,19 @@ display_cleanup_suggestions() {
|
||||
|
||||
# Check common cache locations
|
||||
if [[ -d "$HOME/Library/Caches" ]]; then
|
||||
local cache_size=$(du -sk "$HOME/Library/Caches" 2>/dev/null | cut -f1)
|
||||
local cache_size
|
||||
cache_size=$(du -sk "$HOME/Library/Caches" 2>/dev/null | cut -f1)
|
||||
if [[ $cache_size -gt 1048576 ]]; then # > 1GB
|
||||
local human=$(bytes_to_human $((cache_size * 1024)))
|
||||
local human
|
||||
human=$(bytes_to_human $((cache_size * 1024)))
|
||||
suggestions+=(" Clear application caches: $human")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check Downloads folder
|
||||
if [[ -d "$HOME/Downloads" ]]; then
|
||||
local old_files=$(find "$HOME/Downloads" -type f -mtime +90 2>/dev/null | wc -l | tr -d ' ')
|
||||
local old_files
|
||||
old_files=$(find "$HOME/Downloads" -type f -mtime +90 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ $old_files -gt 0 ]]; then
|
||||
suggestions+=(" Clean old downloads: $old_files files older than 90 days")
|
||||
fi
|
||||
@@ -706,18 +748,22 @@ display_cleanup_suggestions() {
|
||||
|
||||
# Check Xcode derived data
|
||||
if [[ -d "$HOME/Library/Developer/Xcode/DerivedData" ]]; then
|
||||
local xcode_size=$(du -sk "$HOME/Library/Developer/Xcode/DerivedData" 2>/dev/null | cut -f1)
|
||||
local xcode_size
|
||||
xcode_size=$(du -sk "$HOME/Library/Developer/Xcode/DerivedData" 2>/dev/null | cut -f1)
|
||||
if [[ $xcode_size -gt 10485760 ]]; then # > 10GB
|
||||
local human=$(bytes_to_human $((xcode_size * 1024)))
|
||||
local human
|
||||
human=$(bytes_to_human $((xcode_size * 1024)))
|
||||
suggestions+=(" Clear Xcode cache: $human")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check iOS device backups
|
||||
if [[ -d "$HOME/Library/Application Support/MobileSync/Backup" ]]; then
|
||||
local backup_size=$(du -sk "$HOME/Library/Application Support/MobileSync/Backup" 2>/dev/null | cut -f1)
|
||||
local backup_size
|
||||
backup_size=$(du -sk "$HOME/Library/Application Support/MobileSync/Backup" 2>/dev/null | cut -f1)
|
||||
if [[ $backup_size -gt 5242880 ]]; then # > 5GB
|
||||
local human=$(bytes_to_human $((backup_size * 1024)))
|
||||
local human
|
||||
human=$(bytes_to_human $((backup_size * 1024)))
|
||||
suggestions+=(" 📱 Review iOS backups: $human")
|
||||
fi
|
||||
fi
|
||||
@@ -728,7 +774,8 @@ display_cleanup_suggestions() {
|
||||
mdfind -onlyin "$CURRENT_PATH" "kMDItemFSSize > 10000000" 2>/dev/null | \
|
||||
xargs -I {} stat -f "%z" {} 2>/dev/null | \
|
||||
sort | uniq -d | wc -l | tr -d ' ' > "$temp_dup" 2>/dev/null || echo "0" > "$temp_dup"
|
||||
local dup_count=$(cat "$temp_dup" 2>/dev/null || echo "0")
|
||||
local dup_count
|
||||
dup_count=$(cat "$temp_dup" 2>/dev/null || echo "0")
|
||||
if [[ $dup_count -gt 5 ]]; then
|
||||
suggestions+=(" ♻️ Possible duplicates: $dup_count size matches in large files (>10MB)")
|
||||
fi
|
||||
@@ -772,11 +819,13 @@ display_disk_summary() {
|
||||
|
||||
log_header "Disk Situation"
|
||||
|
||||
local target_display=$(echo "$CURRENT_PATH" | sed "s|^$HOME|~|")
|
||||
local target_display
|
||||
target_display=$(echo "$CURRENT_PATH" | sed "s|^$HOME|~|")
|
||||
echo " ${BLUE}Scanning:${NC} $target_display | ${BLUE}Free:${NC} $(get_free_space)"
|
||||
|
||||
if [[ $total_large_count -gt 0 ]]; then
|
||||
local large_human=$(bytes_to_human "$total_large_size")
|
||||
local large_human
|
||||
large_human=$(bytes_to_human "$total_large_size")
|
||||
echo " ${BLUE}Large Files:${NC} $total_large_count files ($large_human) | ${BLUE}Total:${NC} $(bytes_to_human "$total_dirs_size") in $total_dirs_count dirs"
|
||||
elif [[ $total_dirs_size -gt 0 ]]; then
|
||||
echo " ${BLUE}Total Scanned:${NC} $(bytes_to_human "$total_dirs_size") across $total_dirs_count directories"
|
||||
@@ -820,10 +869,14 @@ get_file_age() {
|
||||
return
|
||||
fi
|
||||
|
||||
local mtime=$(stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
local diff=$((now - mtime))
|
||||
local days=$((diff / 86400))
|
||||
local mtime
|
||||
mtime=$(stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s)
|
||||
local diff
|
||||
diff=$((now - mtime))
|
||||
local days
|
||||
days=$((diff / 86400))
|
||||
|
||||
if [[ $days -lt 1 ]]; then
|
||||
echo "Today"
|
||||
@@ -832,10 +885,12 @@ get_file_age() {
|
||||
elif [[ $days -lt 30 ]]; then
|
||||
echo "${days}d"
|
||||
elif [[ $days -lt 365 ]]; then
|
||||
local months=$((days / 30))
|
||||
local months
|
||||
months=$((days / 30))
|
||||
echo "${months}mo"
|
||||
else
|
||||
local years=$((days / 365))
|
||||
local years
|
||||
years=$((days / 365))
|
||||
echo "${years}yr"
|
||||
fi
|
||||
}
|
||||
@@ -860,13 +915,17 @@ display_large_files_table() {
|
||||
break
|
||||
fi
|
||||
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local filename=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local filename
|
||||
filename=$(basename "$path")
|
||||
local ext="${filename##*.}"
|
||||
local age=$(get_file_age "$path")
|
||||
local age
|
||||
age=$(get_file_age "$path")
|
||||
|
||||
# Get file info and badge
|
||||
local info=$(get_file_info "$path")
|
||||
local info
|
||||
info=$(get_file_info "$path")
|
||||
local badge="${info%|*}"
|
||||
|
||||
# Truncate filename if too long
|
||||
@@ -889,7 +948,8 @@ display_large_files_table() {
|
||||
((count++))
|
||||
done < "$temp_large"
|
||||
|
||||
local total=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
local total
|
||||
total=$(wc -l < "$temp_large" | tr -d ' ')
|
||||
if [[ $total -gt 20 ]]; then
|
||||
echo " ${GRAY}... $((total - 20)) more files${NC}"
|
||||
fi
|
||||
@@ -925,19 +985,24 @@ display_unified_directories() {
|
||||
break
|
||||
fi
|
||||
|
||||
local percentage=$((size * 100 / total_size))
|
||||
local bar_width=$((percentage * chart_width / 100))
|
||||
local percentage
|
||||
percentage=$((size * 100 / total_size))
|
||||
local bar_width
|
||||
bar_width=$((percentage * chart_width / 100))
|
||||
[[ $bar_width -lt 1 ]] && bar_width=1
|
||||
|
||||
local dirname=$(basename "$path")
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local dirname
|
||||
dirname=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
|
||||
# Build compact bar
|
||||
local bar=""
|
||||
if [[ $bar_width -gt 0 ]]; then
|
||||
bar=$(printf "%${bar_width}s" "" | tr ' ' '▓')
|
||||
fi
|
||||
local empty=$((chart_width - bar_width))
|
||||
local empty
|
||||
empty=$((chart_width - bar_width))
|
||||
if [[ $empty -gt 0 ]]; then
|
||||
bar="${bar}$(printf "%${empty}s" "" | tr ' ' '░')"
|
||||
fi
|
||||
@@ -1009,12 +1074,16 @@ display_space_chart() {
|
||||
break
|
||||
fi
|
||||
|
||||
local percentage=$((size * 100 / total_size))
|
||||
local bar_width=$((percentage * chart_width / 100))
|
||||
local percentage
|
||||
percentage=$((size * 100 / total_size))
|
||||
local bar_width
|
||||
bar_width=$((percentage * chart_width / 100))
|
||||
[[ $bar_width -lt 1 ]] && bar_width=1
|
||||
|
||||
local dirname=$(basename "$path")
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local dirname
|
||||
dirname=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
|
||||
# Build visual bar
|
||||
local bar=""
|
||||
@@ -1048,8 +1117,10 @@ display_recent_large_files() {
|
||||
"kMDItemFSSize > 100000000 && kMDItemContentCreationDate >= \$time.today(-30)" 2>/dev/null | \
|
||||
while IFS= read -r file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
local mtime=$(stat -f%m "$file" 2>/dev/null || echo "0")
|
||||
local size
|
||||
size=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
local mtime
|
||||
mtime=$(stat -f%m "$file" 2>/dev/null || echo "0")
|
||||
echo "$size|$mtime|$file"
|
||||
fi
|
||||
done | sort -t'|' -k1 -rn | head -10 > "$temp_recent"
|
||||
@@ -1062,12 +1133,17 @@ display_recent_large_files() {
|
||||
|
||||
local count=0
|
||||
while IFS='|' read -r size mtime path; do
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local filename=$(basename "$path")
|
||||
local dirname=$(dirname "$path" | sed "s|^$HOME|~|")
|
||||
local days_ago=$(( ($(date +%s) - mtime) / 86400 ))
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local filename
|
||||
filename=$(basename "$path")
|
||||
local dirname
|
||||
dirname=$(dirname "$path" | sed "s|^$HOME|~|")
|
||||
local days_ago
|
||||
days_ago=$(( ($(date +%s) - mtime) / 86400 ))
|
||||
|
||||
local info=$(get_file_info "$path")
|
||||
local info
|
||||
info=$(get_file_info "$path")
|
||||
local badge="${info%|*}"
|
||||
|
||||
printf " %s %s ${GRAY}(%s)${NC}\n" "$badge" "$filename" "$human_size"
|
||||
@@ -1088,7 +1164,8 @@ get_subdirectories() {
|
||||
|
||||
find "$target" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | \
|
||||
while IFS= read -r dir; do
|
||||
local size=$(du -sk "$dir" 2>/dev/null | cut -f1)
|
||||
local size
|
||||
size=$(du -sk "$dir" 2>/dev/null | cut -f1)
|
||||
echo "$((size * 1024))|$dir"
|
||||
done | sort -t'|' -k1 -rn > "$temp_file"
|
||||
}
|
||||
@@ -1116,11 +1193,16 @@ display_directory_list() {
|
||||
|
||||
# Display with cursor
|
||||
while IFS='|' read -r size path; do
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local percentage=$(calc_percentage "$size" "$total_size")
|
||||
local bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
local dirname=$(basename "$path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
local percentage
|
||||
percentage=$(calc_percentage "$size" "$total_size")
|
||||
local bar
|
||||
bar=$(generate_bar "$size" "$max_size" 20)
|
||||
local display_path
|
||||
display_path=$(echo "$path" | sed "s|^$HOME|~|")
|
||||
local dirname
|
||||
dirname=$(basename "$path")
|
||||
|
||||
# Highlight selected line
|
||||
if [[ $idx -eq $cursor_pos ]]; then
|
||||
@@ -1168,7 +1250,8 @@ count_directories() {
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
local count=$(wc -l < "$temp_dirs" | tr -d ' ')
|
||||
local count
|
||||
count=$(wc -l < "$temp_dirs" | tr -d ' ')
|
||||
[[ $count -gt 15 ]] && count=15
|
||||
echo "$count"
|
||||
}
|
||||
@@ -1252,20 +1335,24 @@ display_file_types() {
|
||||
;;
|
||||
esac
|
||||
|
||||
local files=$(mdfind -onlyin "$CURRENT_PATH" "$query" 2>/dev/null)
|
||||
local count=$(echo "$files" | grep -c . || echo "0")
|
||||
local files
|
||||
files=$(mdfind -onlyin "$CURRENT_PATH" "$query" 2>/dev/null)
|
||||
local count
|
||||
count=$(echo "$files" | grep -c . || echo "0")
|
||||
local total_size=0
|
||||
|
||||
if [[ $count -gt 0 ]]; then
|
||||
while IFS= read -r file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local fsize=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
local fsize
|
||||
fsize=$(stat -f%z "$file" 2>/dev/null || echo "0")
|
||||
((total_size += fsize))
|
||||
fi
|
||||
done <<< "$files"
|
||||
|
||||
if [[ $total_size -gt 0 ]]; then
|
||||
local human_size=$(bytes_to_human "$total_size")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$total_size")
|
||||
printf " %s %-12s %8s (%d files)\n" "$badge" "$type_name:" "$human_size" "$count"
|
||||
fi
|
||||
fi
|
||||
@@ -1292,7 +1379,8 @@ scan_directory_contents_fast() {
|
||||
local show_progress="${4:-true}"
|
||||
|
||||
# Auto-detect optimal parallel jobs using common function
|
||||
local num_jobs=$(get_optimal_parallel_jobs "io")
|
||||
local num_jobs
|
||||
num_jobs=$(get_optimal_parallel_jobs "io")
|
||||
# Cap at reasonable limits for I/O operations
|
||||
[[ $num_jobs -gt 24 ]] && num_jobs=24
|
||||
[[ $num_jobs -lt 12 ]] && num_jobs=12
|
||||
@@ -1456,7 +1544,8 @@ combine_initial_scan_results() {
|
||||
if [[ -f "$temp_large" ]]; then
|
||||
while IFS='|' read -r size path; do
|
||||
# Only include if parent directory is the current scan path
|
||||
local parent=$(dirname "$path")
|
||||
local parent
|
||||
parent=$(dirname "$path")
|
||||
if [[ "$parent" == "$CURRENT_PATH" ]]; then
|
||||
echo "$size|file|$path"
|
||||
fi
|
||||
@@ -1484,7 +1573,8 @@ show_volumes_overview() {
|
||||
if [[ -d "/Volumes" ]]; then
|
||||
local vol_priority=500
|
||||
find /Volumes -mindepth 1 -maxdepth 1 -type d 2>/dev/null | while IFS= read -r vol; do
|
||||
local vol_name=$(basename "$vol")
|
||||
local vol_name
|
||||
vol_name=$(basename "$vol")
|
||||
echo "$((vol_priority))|$vol|Volume: $vol_name"
|
||||
((vol_priority--))
|
||||
done
|
||||
@@ -1505,7 +1595,8 @@ show_volumes_overview() {
|
||||
stty -echo 2>/dev/null || true
|
||||
|
||||
local cursor=0
|
||||
local total_items=$(wc -l < "$temp_volumes" | tr -d ' ')
|
||||
local total_items
|
||||
total_items=$(wc -l < "$temp_volumes" | tr -d ' ')
|
||||
|
||||
while true; do
|
||||
# Ensure cursor is always hidden
|
||||
@@ -1655,7 +1746,8 @@ interactive_drill_down() {
|
||||
# Only scan if needed (directory changed or refresh requested)
|
||||
if [[ "$need_scan" == "true" ]]; then
|
||||
# Generate cache key (use md5 hash of path)
|
||||
local cache_key=$(echo "$current_path" | md5 2>/dev/null || echo "$current_path" | shasum | cut -d' ' -f1)
|
||||
local cache_key
|
||||
cache_key=$(echo "$current_path" | md5 2>/dev/null || echo "$current_path" | shasum | cut -d' ' -f1)
|
||||
local cache_file="$cache_dir/$cache_key"
|
||||
|
||||
# Check if we have cached results for this directory
|
||||
@@ -1732,7 +1824,8 @@ interactive_drill_down() {
|
||||
if [[ ${#path_stack[@]} -gt 0 ]]; then
|
||||
# Use bash 3.2 compatible way to get last element
|
||||
local stack_size=${#path_stack[@]}
|
||||
local last_index=$((stack_size - 1))
|
||||
local last_index
|
||||
last_index=$((stack_size - 1))
|
||||
current_path="${path_stack[$last_index]}"
|
||||
unset "path_stack[$last_index]"
|
||||
cursor=0
|
||||
@@ -1757,7 +1850,8 @@ interactive_drill_down() {
|
||||
|
||||
local max_show=15 # Show 15 items per page
|
||||
local page_start=$scroll_offset
|
||||
local page_end=$((scroll_offset + max_show))
|
||||
local page_end
|
||||
page_end=$((scroll_offset + max_show))
|
||||
[[ $page_end -gt $total_items ]] && page_end=$total_items
|
||||
|
||||
local display_idx=0
|
||||
@@ -1778,7 +1872,8 @@ interactive_drill_down() {
|
||||
local rest="${item_info#*|}"
|
||||
local type="${rest%%|*}"
|
||||
local path="${rest#*|}"
|
||||
local name=$(basename "$path")
|
||||
local name
|
||||
name=$(basename "$path")
|
||||
|
||||
local human_size
|
||||
if [[ "$size" -eq 0 ]]; then
|
||||
@@ -1796,7 +1891,8 @@ interactive_drill_down() {
|
||||
fi
|
||||
else
|
||||
local ext="${name##*.}"
|
||||
local info=$(get_file_info "$path")
|
||||
local info
|
||||
info=$(get_file_info "$path")
|
||||
badge="${info%|*}"
|
||||
case "$ext" in
|
||||
dmg|iso|pkg|zip|tar|gz|rar|7z)
|
||||
@@ -1871,7 +1967,8 @@ interactive_drill_down() {
|
||||
if [[ $cursor -lt $((total_items - 1)) ]]; then
|
||||
((cursor++))
|
||||
# Scroll down if cursor goes below visible area
|
||||
local page_end=$((scroll_offset + max_show))
|
||||
local page_end
|
||||
page_end=$((scroll_offset + max_show))
|
||||
if [[ $cursor -ge $page_end ]]; then
|
||||
scroll_offset=$((cursor - max_show + 1))
|
||||
fi
|
||||
@@ -1895,7 +1992,8 @@ interactive_drill_down() {
|
||||
else
|
||||
# It's a file - open it for viewing
|
||||
local file_ext="${selected_path##*.}"
|
||||
local filename=$(basename "$selected_path")
|
||||
local filename
|
||||
filename=$(basename "$selected_path")
|
||||
local open_success=false
|
||||
|
||||
# For text-like files, use less or fallback to open
|
||||
@@ -1972,7 +2070,8 @@ interactive_drill_down() {
|
||||
# Pop from stack and go back
|
||||
# Use bash 3.2 compatible way to get last element
|
||||
local stack_size=${#path_stack[@]}
|
||||
local last_index=$((stack_size - 1))
|
||||
local last_index
|
||||
last_index=$((stack_size - 1))
|
||||
current_path="${path_stack[$last_index]}"
|
||||
unset "path_stack[$last_index]"
|
||||
cursor=0
|
||||
@@ -2008,8 +2107,10 @@ interactive_drill_down() {
|
||||
local rest="${selected#*|}"
|
||||
local type="${rest%%|*}"
|
||||
local selected_path="${rest#*|}"
|
||||
local selected_name=$(basename "$selected_path")
|
||||
local human_size=$(bytes_to_human "$size")
|
||||
local selected_name
|
||||
selected_name=$(basename "$selected_path")
|
||||
local human_size
|
||||
human_size=$(bytes_to_human "$size")
|
||||
|
||||
# Check if sudo is needed
|
||||
local needs_sudo=false
|
||||
@@ -2034,7 +2135,8 @@ interactive_drill_down() {
|
||||
if [[ "$type" == "dir" ]]; then
|
||||
echo " ${BADGE_DIR} ${YELLOW}$selected_name${NC}"
|
||||
else
|
||||
local info=$(get_file_info "$selected_path")
|
||||
local info
|
||||
info=$(get_file_info "$selected_path")
|
||||
local badge="${info%|*}"
|
||||
echo " $badge ${YELLOW}$selected_name${NC}"
|
||||
fi
|
||||
@@ -2092,7 +2194,8 @@ interactive_drill_down() {
|
||||
sleep 0.8
|
||||
|
||||
# Clear cache to force rescan
|
||||
local cache_key=$(echo "$current_path" | md5 2>/dev/null || echo "$current_path" | shasum | cut -d' ' -f1)
|
||||
local cache_key
|
||||
cache_key=$(echo "$current_path" | md5 2>/dev/null || echo "$current_path" | shasum | cut -d' ' -f1)
|
||||
local cache_file="$cache_dir/$cache_key"
|
||||
rm -f "$cache_file" 2>/dev/null || true
|
||||
|
||||
@@ -2142,7 +2245,8 @@ interactive_mode() {
|
||||
type drain_pending_input >/dev/null 2>&1 && drain_pending_input
|
||||
display_interactive_menu
|
||||
|
||||
local key=$(read_key)
|
||||
local key
|
||||
key=$(read_key)
|
||||
case "$key" in
|
||||
"QUIT")
|
||||
break
|
||||
@@ -2154,14 +2258,16 @@ interactive_mode() {
|
||||
;;
|
||||
"DOWN")
|
||||
if [[ "$VIEW_MODE" == "navigate" ]]; then
|
||||
local max_count=$(count_directories)
|
||||
local max_count
|
||||
max_count=$(count_directories)
|
||||
((CURSOR_POS < max_count - 1)) && ((CURSOR_POS++))
|
||||
fi
|
||||
;;
|
||||
"RIGHT")
|
||||
if [[ "$VIEW_MODE" == "navigate" ]]; then
|
||||
# Enter selected directory
|
||||
local selected_path=$(get_path_at_cursor "$CURSOR_POS")
|
||||
local selected_path
|
||||
selected_path=$(get_path_at_cursor "$CURSOR_POS")
|
||||
if [[ -n "$selected_path" ]] && [[ -d "$selected_path" ]]; then
|
||||
CURRENT_PATH="$selected_path"
|
||||
CURSOR_POS=0
|
||||
@@ -2194,7 +2300,8 @@ interactive_mode() {
|
||||
"ENTER")
|
||||
if [[ "$VIEW_MODE" == "navigate" ]]; then
|
||||
# Same as RIGHT
|
||||
local selected_path=$(get_path_at_cursor "$CURSOR_POS")
|
||||
local selected_path
|
||||
selected_path=$(get_path_at_cursor "$CURSOR_POS")
|
||||
if [[ -n "$selected_path" ]] && [[ -d "$selected_path" ]]; then
|
||||
CURRENT_PATH="$selected_path"
|
||||
CURSOR_POS=0
|
||||
@@ -2231,7 +2338,8 @@ export_to_csv() {
|
||||
{
|
||||
echo "Size (Bytes),Size (Human),Path"
|
||||
while IFS='|' read -r size path; do
|
||||
local human=$(bytes_to_human "$size")
|
||||
local human
|
||||
human=$(bytes_to_human "$size")
|
||||
echo "$size,\"$human\",\"$path\""
|
||||
done < "$temp_dirs"
|
||||
} > "$output_file"
|
||||
@@ -2260,7 +2368,8 @@ export_to_json() {
|
||||
while IFS='|' read -r size path; do
|
||||
[[ "$first" == "false" ]] && echo ","
|
||||
first=false
|
||||
local human=$(bytes_to_human "$size")
|
||||
local human
|
||||
human=$(bytes_to_human "$size")
|
||||
printf ' {"size": %d, "size_human": "%s", "path": "%s"}' "$size" "$human" "$path"
|
||||
done < "$temp_dirs"
|
||||
|
||||
@@ -2273,7 +2382,8 @@ export_to_json() {
|
||||
while IFS='|' read -r size path; do
|
||||
[[ "$first" == "false" ]] && echo ","
|
||||
first=false
|
||||
local human=$(bytes_to_human "$size")
|
||||
local human
|
||||
human=$(bytes_to_human "$size")
|
||||
printf ' {"size": %d, "size_human": "%s", "path": "%s"}' "$size" "$human" "$path"
|
||||
done < "$temp_large"
|
||||
echo ""
|
||||
|
||||
@@ -211,7 +211,9 @@ safe_clean() {
|
||||
description="$1"
|
||||
targets=("$1")
|
||||
else
|
||||
description="${@: -1}"
|
||||
# Get last argument as description
|
||||
description="${*: -1}"
|
||||
# Get all arguments except last as targets array
|
||||
targets=("${@:1:$#-1}")
|
||||
fi
|
||||
|
||||
|
||||
@@ -67,13 +67,16 @@ total_size_cleaned=0
|
||||
# Get app last used date in human readable format
|
||||
get_app_last_used() {
|
||||
local app_path="$1"
|
||||
local last_used=$(mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null)
|
||||
local last_used
|
||||
last_used=$(mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null)
|
||||
|
||||
if [[ "$last_used" == "(null)" || -z "$last_used" ]]; then
|
||||
echo "Never"
|
||||
else
|
||||
local last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$last_used" "+%s" 2>/dev/null)
|
||||
local current_epoch=$(date "+%s")
|
||||
local last_used_epoch
|
||||
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$last_used" "+%s" 2>/dev/null)
|
||||
local current_epoch
|
||||
current_epoch=$(date "+%s")
|
||||
local days_ago=$(( (current_epoch - last_used_epoch) / 86400 ))
|
||||
|
||||
if [[ $days_ago -eq 0 ]]; then
|
||||
@@ -103,7 +106,8 @@ scan_applications() {
|
||||
mkdir -p "$cache_dir" 2>/dev/null
|
||||
|
||||
# Quick count of current apps (system + user directories)
|
||||
local current_app_count=$(
|
||||
local current_app_count
|
||||
current_app_count=$(
|
||||
(find /Applications -name "*.app" -maxdepth 1 2>/dev/null;
|
||||
find ~/Applications -name "*.app" -maxdepth 1 2>/dev/null) | wc -l | tr -d ' '
|
||||
)
|
||||
@@ -111,7 +115,8 @@ scan_applications() {
|
||||
# Check if cache is valid unless explicitly disabled
|
||||
if [[ -f "$cache_file" && -f "$cache_meta" ]]; then
|
||||
local cache_age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || echo 0)))
|
||||
local cached_app_count=$(cat "$cache_meta" 2>/dev/null || echo "0")
|
||||
local cached_app_count
|
||||
cached_app_count=$(cat "$cache_meta" 2>/dev/null || echo "0")
|
||||
|
||||
# Cache is valid if: age < TTL AND app count matches
|
||||
if [[ $cache_age -lt $cache_ttl && "$cached_app_count" == "$current_app_count" ]]; then
|
||||
@@ -121,10 +126,12 @@ scan_applications() {
|
||||
fi
|
||||
fi
|
||||
|
||||
local temp_file=$(create_temp_file)
|
||||
local temp_file
|
||||
temp_file=$(create_temp_file)
|
||||
|
||||
# Pre-cache current epoch to avoid repeated calls
|
||||
local current_epoch=$(date "+%s")
|
||||
local current_epoch
|
||||
current_epoch=$(date "+%s")
|
||||
|
||||
# Spinner for scanning feedback (simple ASCII for compatibility)
|
||||
local spinner_chars="|/-\\"
|
||||
@@ -135,7 +142,8 @@ scan_applications() {
|
||||
while IFS= read -r -d '' app_path; do
|
||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||
|
||||
local app_name=$(basename "$app_path" .app)
|
||||
local app_name
|
||||
app_name=$(basename "$app_path" .app)
|
||||
|
||||
# Try to get English name from bundle info, fallback to folder name
|
||||
local bundle_id="unknown"
|
||||
@@ -144,14 +152,17 @@ scan_applications() {
|
||||
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
|
||||
|
||||
# Try to get English name from bundle info
|
||||
local bundle_executable=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2>/dev/null)
|
||||
local bundle_executable
|
||||
bundle_executable=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2>/dev/null)
|
||||
|
||||
# Smart display name selection - prefer descriptive names over generic ones
|
||||
local candidates=()
|
||||
|
||||
# Get all potential names
|
||||
local bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2>/dev/null)
|
||||
local bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2>/dev/null)
|
||||
local bundle_display_name
|
||||
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2>/dev/null)
|
||||
local bundle_name
|
||||
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2>/dev/null)
|
||||
|
||||
# Check if executable name is generic/technical (should be avoided)
|
||||
local is_generic_executable=false
|
||||
@@ -242,7 +253,8 @@ scan_applications() {
|
||||
local last_used_epoch=0
|
||||
|
||||
if [[ -d "$app_path" ]]; then
|
||||
local metadata_date=$(mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null)
|
||||
local metadata_date
|
||||
metadata_date=$(mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null)
|
||||
|
||||
if [[ "$metadata_date" != "(null)" && -n "$metadata_date" ]]; then
|
||||
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2>/dev/null || echo "0")
|
||||
@@ -417,15 +429,20 @@ uninstall_applications() {
|
||||
fi
|
||||
|
||||
# Find related files (user-level)
|
||||
local related_files=$(find_app_files "$bundle_id" "$app_name")
|
||||
local related_files
|
||||
related_files=$(find_app_files "$bundle_id" "$app_name")
|
||||
|
||||
# Find system-level files (requires sudo)
|
||||
local system_files=$(find_app_system_files "$bundle_id" "$app_name")
|
||||
local system_files
|
||||
system_files=$(find_app_system_files "$bundle_id" "$app_name")
|
||||
|
||||
# Calculate total size
|
||||
local app_size_kb=$(du -sk "$app_path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
local related_size_kb=$(calculate_total_size "$related_files")
|
||||
local system_size_kb=$(calculate_total_size "$system_files")
|
||||
local app_size_kb
|
||||
app_size_kb=$(du -sk "$app_path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
local related_size_kb
|
||||
related_size_kb=$(calculate_total_size "$related_files")
|
||||
local system_size_kb
|
||||
system_size_kb=$(calculate_total_size "$system_files")
|
||||
local total_kb=$((app_size_kb + related_size_kb + system_size_kb))
|
||||
|
||||
# Show what will be removed
|
||||
@@ -619,20 +636,27 @@ main() {
|
||||
if [[ $selection_count -eq 0 ]]; then
|
||||
echo "No apps selected"; rm -f "$apps_file"; return 0
|
||||
fi
|
||||
# Compact one-line summary (list up to 3 names, aggregate rest)
|
||||
local names=()
|
||||
# Show selected apps, max 3 per line
|
||||
echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} app(s):"
|
||||
local idx=0
|
||||
local line=""
|
||||
for selected_app in "${selected_apps[@]}"; do
|
||||
IFS='|' read -r epoch app_path app_name bundle_id size last_used <<< "$selected_app"
|
||||
if (( idx < 3 )); then
|
||||
names+=("${app_name}(${size})")
|
||||
local display_item="${app_name}(${size})"
|
||||
|
||||
if (( idx % 3 == 0 )); then
|
||||
# Start new line
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
line="$display_item"
|
||||
else
|
||||
# Add to current line
|
||||
line="$line, $display_item"
|
||||
fi
|
||||
((idx++))
|
||||
done
|
||||
local extra=$((selection_count-3))
|
||||
local list="${names[*]}"
|
||||
[[ $extra -gt 0 ]] && list+=" +${extra}"
|
||||
echo -e "${BLUE}${ICON_CONFIRM}${NC} ${selection_count} apps: ${list}"
|
||||
# Print the last line
|
||||
[[ -n "$line" ]] && echo " $line"
|
||||
echo ""
|
||||
|
||||
# Execute batch uninstallation (handles confirmation)
|
||||
batch_uninstall_applications
|
||||
|
||||
Reference in New Issue
Block a user