1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 15:04:42 +00:00

Merge branch 'main' of github.com:tw93/Mole

This commit is contained in:
Tw93
2026-01-17 10:14:43 +08:00
4 changed files with 193 additions and 193 deletions

View File

@@ -69,10 +69,10 @@ if [[ -f "$HOME/.config/mole/whitelist" ]]; then
fi fi
case "$line" in case "$line" in
/ | /System | /System/* | /bin | /bin/* | /sbin | /sbin/* | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /etc | /etc/* | /var/db | /var/db/*) / | /System | /System/* | /bin | /bin/* | /sbin | /sbin/* | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /etc | /etc/* | /var/db | /var/db/*)
WHITELIST_WARNINGS+=("Protected system path: $line") WHITELIST_WARNINGS+=("Protected system path: $line")
continue continue
;; ;;
esac esac
duplicate="false" duplicate="false"
@@ -86,7 +86,7 @@ if [[ -f "$HOME/.config/mole/whitelist" ]]; then
fi fi
[[ "$duplicate" == "true" ]] && continue [[ "$duplicate" == "true" ]] && continue
WHITELIST_PATTERNS+=("$line") WHITELIST_PATTERNS+=("$line")
done <"$HOME/.config/mole/whitelist" done < "$HOME/.config/mole/whitelist"
else else
WHITELIST_PATTERNS=("${DEFAULT_WHITELIST_PATTERNS[@]}") WHITELIST_PATTERNS=("${DEFAULT_WHITELIST_PATTERNS[@]}")
fi fi
@@ -140,7 +140,7 @@ cleanup() {
fi fi
CLEANUP_DONE=true CLEANUP_DONE=true
stop_inline_spinner 2>/dev/null || true stop_inline_spinner 2> /dev/null || true
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
printf "\r\033[K" >&2 || true printf "\r\033[K" >&2 || true
@@ -166,8 +166,8 @@ start_section() {
if [[ "$DRY_RUN" == "true" ]]; then if [[ "$DRY_RUN" == "true" ]]; then
ensure_user_file "$EXPORT_LIST_FILE" ensure_user_file "$EXPORT_LIST_FILE"
echo "" >>"$EXPORT_LIST_FILE" echo "" >> "$EXPORT_LIST_FILE"
echo "=== $1 ===" >>"$EXPORT_LIST_FILE" echo "=== $1 ===" >> "$EXPORT_LIST_FILE"
fi fi
} }
@@ -220,7 +220,7 @@ normalize_paths_for_cleanup() {
done done
fi fi
[[ "$is_child" == "true" ]] || result_paths+=("$path") [[ "$is_child" == "true" ]] || result_paths+=("$path")
done <<<"$sorted_paths" done <<< "$sorted_paths"
if [[ ${#result_paths[@]} -gt 0 ]]; then if [[ ${#result_paths[@]} -gt 0 ]]; then
printf '%s\n' "${result_paths[@]}" printf '%s\n' "${result_paths[@]}"
@@ -232,9 +232,9 @@ get_cleanup_path_size_kb() {
local path="$1" local path="$1"
if [[ -f "$path" && ! -L "$path" ]]; then if [[ -f "$path" && ! -L "$path" ]]; then
if command -v stat >/dev/null 2>&1; then if command -v stat > /dev/null 2>&1; then
local bytes local bytes
bytes=$(stat -f%z "$path" 2>/dev/null || echo "0") bytes=$(stat -f%z "$path" 2> /dev/null || echo "0")
if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then
echo $(((bytes + 1023) / 1024)) echo $(((bytes + 1023) / 1024))
return 0 return 0
@@ -243,9 +243,9 @@ get_cleanup_path_size_kb() {
fi fi
if [[ -L "$path" ]]; then if [[ -L "$path" ]]; then
if command -v stat >/dev/null 2>&1; then if command -v stat > /dev/null 2>&1; then
local bytes local bytes
bytes=$(stat -f%z "$path" 2>/dev/null || echo "0") bytes=$(stat -f%z "$path" 2> /dev/null || echo "0")
if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then
echo $(((bytes + 1023) / 1024)) echo $(((bytes + 1023) / 1024))
else else
@@ -461,9 +461,9 @@ safe_clean() {
[[ ! "$size" =~ ^[0-9]+$ ]] && size=0 [[ ! "$size" =~ ^[0-9]+$ ]] && size=0
if [[ "$size" -gt 0 ]]; then if [[ "$size" -gt 0 ]]; then
echo "$size 1" >"$temp_dir/result_${idx}" echo "$size 1" > "$temp_dir/result_${idx}"
else else
echo "0 0" >"$temp_dir/result_${idx}" echo "0 0" > "$temp_dir/result_${idx}"
fi fi
((idx++)) ((idx++))
@@ -488,17 +488,17 @@ safe_clean() {
[[ ! "$size" =~ ^[0-9]+$ ]] && size=0 [[ ! "$size" =~ ^[0-9]+$ ]] && size=0
local tmp_file="$temp_dir/result_${idx}.$$" local tmp_file="$temp_dir/result_${idx}.$$"
if [[ "$size" -gt 0 ]]; then if [[ "$size" -gt 0 ]]; then
echo "$size 1" >"$tmp_file" echo "$size 1" > "$tmp_file"
else else
echo "0 0" >"$tmp_file" echo "0 0" > "$tmp_file"
fi fi
mv "$tmp_file" "$temp_dir/result_${idx}" 2>/dev/null || true mv "$tmp_file" "$temp_dir/result_${idx}" 2> /dev/null || true
) & ) &
pids+=($!) pids+=($!)
((idx++)) ((idx++))
if ((${#pids[@]} >= MOLE_MAX_PARALLEL_JOBS)); then if ((${#pids[@]} >= MOLE_MAX_PARALLEL_JOBS)); then
wait "${pids[0]}" 2>/dev/null || true wait "${pids[0]}" 2> /dev/null || true
pids=("${pids[@]:1}") pids=("${pids[@]:1}")
((completed++)) ((completed++))
@@ -511,7 +511,7 @@ safe_clean() {
if [[ ${#pids[@]} -gt 0 ]]; then if [[ ${#pids[@]} -gt 0 ]]; then
for pid in "${pids[@]}"; do for pid in "${pids[@]}"; do
wait "$pid" 2>/dev/null || true wait "$pid" 2> /dev/null || true
((completed++)) ((completed++))
if [[ "$show_spinner" == "true" && -t 1 ]]; then if [[ "$show_spinner" == "true" && -t 1 ]]; then
@@ -527,11 +527,11 @@ safe_clean() {
for path in "${existing_paths[@]}"; do for path in "${existing_paths[@]}"; do
local result_file="$temp_dir/result_${idx}" local result_file="$temp_dir/result_${idx}"
if [[ -f "$result_file" ]]; then if [[ -f "$result_file" ]]; then
read -r size count <"$result_file" 2>/dev/null || true read -r size count < "$result_file" 2> /dev/null || true
local removed=0 local removed=0
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
if [[ -L "$path" ]]; then if [[ -L "$path" ]]; then
rm "$path" 2>/dev/null && removed=1 rm "$path" 2> /dev/null && removed=1
else else
if safe_remove "$path" true; then if safe_remove "$path" true; then
removed=1 removed=1
@@ -568,7 +568,7 @@ safe_clean() {
local removed=0 local removed=0
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
if [[ -L "$path" ]]; then if [[ -L "$path" ]]; then
rm "$path" 2>/dev/null && removed=1 rm "$path" 2> /dev/null && removed=1
else else
if safe_remove "$path" true; then if safe_remove "$path" true; then
removed=1 removed=1
@@ -629,9 +629,9 @@ safe_clean() {
local size=0 local size=0
if [[ -n "${temp_dir:-}" && -f "$temp_dir/result_${idx}" ]]; then if [[ -n "${temp_dir:-}" && -f "$temp_dir/result_${idx}" ]]; then
read -r size count <"$temp_dir/result_${idx}" 2>/dev/null || true read -r size count < "$temp_dir/result_${idx}" 2> /dev/null || true
else else
size=$(get_cleanup_path_size_kb "$path" 2>/dev/null || echo "0") size=$(get_cleanup_path_size_kb "$path" 2> /dev/null || echo "0")
fi fi
[[ "$size" == "0" || -z "$size" ]] && { [[ "$size" == "0" || -z "$size" ]] && {
@@ -639,7 +639,7 @@ safe_clean() {
continue continue
} }
echo "$(dirname "$path")|$size|$path" >>"$paths_temp" echo "$(dirname "$path")|$size|$path" >> "$paths_temp"
((idx++)) ((idx++))
done done
fi fi
@@ -670,9 +670,9 @@ safe_clean() {
' | while IFS='|' read -r display_path total_size child_count; do ' | while IFS='|' read -r display_path total_size child_count; do
local size_human=$(bytes_to_human "$((total_size * 1024))") local size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ $child_count -gt 1 ]]; then if [[ $child_count -gt 1 ]]; then
echo "$display_path # $size_human ($child_count items)" >>"$EXPORT_LIST_FILE" echo "$display_path # $size_human ($child_count items)" >> "$EXPORT_LIST_FILE"
else else
echo "$display_path # $size_human" >>"$EXPORT_LIST_FILE" echo "$display_path # $size_human" >> "$EXPORT_LIST_FILE"
fi fi
done done
@@ -708,7 +708,7 @@ start_cleanup() {
SYSTEM_CLEAN=false SYSTEM_CLEAN=false
ensure_user_file "$EXPORT_LIST_FILE" ensure_user_file "$EXPORT_LIST_FILE"
cat >"$EXPORT_LIST_FILE" <<EOF cat > "$EXPORT_LIST_FILE" << EOF
# Mole Cleanup Preview - $(date '+%Y-%m-%d %H:%M:%S') # Mole Cleanup Preview - $(date '+%Y-%m-%d %H:%M:%S')
# #
# How to protect files: # How to protect files:
@@ -998,7 +998,7 @@ perform_cleanup() {
echo "# Potential cleanup: ${freed_gb}GB" echo "# Potential cleanup: ${freed_gb}GB"
echo "# Items: $files_cleaned" echo "# Items: $files_cleaned"
echo "# Categories: $total_items" echo "# Categories: $total_items"
} >>"$EXPORT_LIST_FILE" } >> "$EXPORT_LIST_FILE"
summary_details+=("Detailed file list: ${GRAY}$EXPORT_LIST_FILE${NC}") summary_details+=("Detailed file list: ${GRAY}$EXPORT_LIST_FILE${NC}")
summary_details+=("Use ${GRAY}mo clean --whitelist${NC} to add protection rules") summary_details+=("Use ${GRAY}mo clean --whitelist${NC} to add protection rules")
@@ -1047,17 +1047,17 @@ perform_cleanup() {
main() { main() {
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
"--debug") "--debug")
export MO_DEBUG=1 export MO_DEBUG=1
;; ;;
"--dry-run" | "-n") "--dry-run" | "-n")
DRY_RUN=true DRY_RUN=true
;; ;;
"--whitelist") "--whitelist")
source "$SCRIPT_DIR/../lib/manage/whitelist.sh" source "$SCRIPT_DIR/../lib/manage/whitelist.sh"
manage_whitelist "clean" manage_whitelist "clean"
exit 0 exit 0
;; ;;
esac esac
done done

View File

@@ -27,14 +27,14 @@ clean_deep_system() {
continue continue
fi fi
local item_flags local item_flags
item_flags=$($STAT_BSD -f%Sf "$item" 2>/dev/null || echo "") item_flags=$($STAT_BSD -f%Sf "$item" 2> /dev/null || echo "")
if [[ "$item_flags" == *"restricted"* ]]; then if [[ "$item_flags" == *"restricted"* ]]; then
continue continue
fi fi
if safe_sudo_remove "$item"; then if safe_sudo_remove "$item"; then
((updates_cleaned++)) ((updates_cleaned++))
fi fi
done < <(find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2>/dev/null || true) done < <(find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
[[ $updates_cleaned -gt 0 ]] && log_success "System library updates" [[ $updates_cleaned -gt 0 ]] && log_success "System library updates"
fi fi
fi fi
@@ -76,7 +76,7 @@ clean_deep_system() {
last_update_time=$current_time last_update_time=$current_time
fi fi
fi fi
done < <(run_with_timeout 5 command find /private/var/folders -type d -name "*.code_sign_clone" -path "*/X/*" -print0 2>/dev/null || true) done < <(run_with_timeout 5 command find /private/var/folders -type d -name "*.code_sign_clone" -path "*/X/*" -print0 2> /dev/null || true)
stop_section_spinner stop_section_spinner
[[ $code_sign_cleaned -gt 0 ]] && log_success "Browser code signature caches ($code_sign_cleaned items)" [[ $code_sign_cleaned -gt 0 ]] && log_success "Browser code signature caches ($code_sign_cleaned items)"
@@ -101,7 +101,7 @@ clean_deep_system() {
# Incomplete Time Machine backups. # Incomplete Time Machine backups.
clean_time_machine_failed_backups() { clean_time_machine_failed_backups() {
local tm_cleaned=0 local tm_cleaned=0
if ! command -v tmutil >/dev/null 2>&1; then if ! command -v tmutil > /dev/null 2>&1; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} No incomplete backups found" echo -e " ${GREEN}${ICON_SUCCESS}${NC} No incomplete backups found"
return 0 return 0
fi fi
@@ -155,9 +155,9 @@ clean_time_machine_failed_backups() {
fi fi
for volume in "${backup_volumes[@]}"; do for volume in "${backup_volumes[@]}"; do
local fs_type local fs_type
fs_type=$(run_with_timeout 1 command df -T "$volume" 2>/dev/null | tail -1 | awk '{print $2}' || echo "unknown") fs_type=$(run_with_timeout 1 command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}' || echo "unknown")
case "$fs_type" in case "$fs_type" in
nfs | smbfs | afpfs | cifs | webdav | unknown) continue ;; nfs | smbfs | afpfs | cifs | webdav | unknown) continue ;;
esac esac
local backupdb_dir="$volume/Backups.backupdb" local backupdb_dir="$volume/Backups.backupdb"
if [[ -d "$backupdb_dir" ]]; then if [[ -d "$backupdb_dir" ]]; then
@@ -185,11 +185,11 @@ clean_time_machine_failed_backups() {
note_activity note_activity
continue continue
fi fi
if ! command -v tmutil >/dev/null 2>&1; then if ! command -v tmutil > /dev/null 2>&1; then
echo -e " ${YELLOW}!${NC} tmutil not available, skipping: $backup_name" echo -e " ${YELLOW}!${NC} tmutil not available, skipping: $backup_name"
continue continue
fi fi
if tmutil delete "$inprogress_file" 2>/dev/null; then if tmutil delete "$inprogress_file" 2> /dev/null; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name ${GREEN}($size_human)${NC}"
((tm_cleaned++)) ((tm_cleaned++))
((files_cleaned++)) ((files_cleaned++))
@@ -199,14 +199,14 @@ clean_time_machine_failed_backups() {
else else
echo -e " ${YELLOW}!${NC} Could not delete: $backup_name · try manually with sudo" echo -e " ${YELLOW}!${NC} Could not delete: $backup_name · try manually with sudo"
fi fi
done < <(run_with_timeout 15 find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2>/dev/null || true) done < <(run_with_timeout 15 find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
fi fi
# APFS bundles. # APFS bundles.
for bundle in "$volume"/*.backupbundle "$volume"/*.sparsebundle; do for bundle in "$volume"/*.backupbundle "$volume"/*.sparsebundle; do
[[ -e "$bundle" ]] || continue [[ -e "$bundle" ]] || continue
[[ -d "$bundle" ]] || continue [[ -d "$bundle" ]] || continue
local bundle_name=$(basename "$bundle") local bundle_name=$(basename "$bundle")
local mounted_path=$(hdiutil info 2>/dev/null | grep -A 5 "image-path.*$bundle_name" | grep "/Volumes/" | awk '{print $1}' | head -1 || echo "") local mounted_path=$(hdiutil info 2> /dev/null | grep -A 5 "image-path.*$bundle_name" | grep "/Volumes/" | awk '{print $1}' | head -1 || echo "")
if [[ -n "$mounted_path" && -d "$mounted_path" ]]; then if [[ -n "$mounted_path" && -d "$mounted_path" ]]; then
while IFS= read -r inprogress_file; do while IFS= read -r inprogress_file; do
[[ -d "$inprogress_file" ]] || continue [[ -d "$inprogress_file" ]] || continue
@@ -231,10 +231,10 @@ clean_time_machine_failed_backups() {
note_activity note_activity
continue continue
fi fi
if ! command -v tmutil >/dev/null 2>&1; then if ! command -v tmutil > /dev/null 2>&1; then
continue continue
fi fi
if tmutil delete "$inprogress_file" 2>/dev/null; then if tmutil delete "$inprogress_file" 2> /dev/null; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name ${GREEN}($size_human)${NC}" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name ${GREEN}($size_human)${NC}"
((tm_cleaned++)) ((tm_cleaned++))
((files_cleaned++)) ((files_cleaned++))
@@ -244,7 +244,7 @@ clean_time_machine_failed_backups() {
else else
echo -e " ${YELLOW}!${NC} Could not delete from bundle: $backup_name" echo -e " ${YELLOW}!${NC} Could not delete from bundle: $backup_name"
fi fi
done < <(run_with_timeout 15 find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2>/dev/null || true) done < <(run_with_timeout 15 find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
fi fi
done done
done done
@@ -260,20 +260,20 @@ clean_time_machine_failed_backups() {
# Returns 2 if status cannot be determined # Returns 2 if status cannot be determined
tm_is_running() { tm_is_running() {
local st local st
st="$(tmutil status 2>/dev/null)" || return 2 st="$(tmutil status 2> /dev/null)" || return 2
# If we can't find a Running field at all, treat as unknown. # If we can't find a Running field at all, treat as unknown.
if ! grep -qE '(^|[[:space:]])("Running"|Running)[[:space:]]*=' <<<"$st"; then if ! grep -qE '(^|[[:space:]])("Running"|Running)[[:space:]]*=' <<< "$st"; then
return 2 return 2
fi fi
# Match: Running = 1; OR "Running" = 1 (with or without trailing ;) # Match: Running = 1; OR "Running" = 1 (with or without trailing ;)
grep -qE '(^|[[:space:]])("Running"|Running)[[:space:]]*=[[:space:]]*1([[:space:]]*;|$)' <<<"$st" grep -qE '(^|[[:space:]])("Running"|Running)[[:space:]]*=[[:space:]]*1([[:space:]]*;|$)' <<< "$st"
} }
# Local APFS snapshots (keep the most recent). # Local APFS snapshots (keep the most recent).
clean_local_snapshots() { clean_local_snapshots() {
if ! command -v tmutil >/dev/null 2>&1; then if ! command -v tmutil > /dev/null 2>&1; then
return 0 return 0
fi fi
@@ -292,7 +292,7 @@ clean_local_snapshots() {
start_section_spinner "Checking local snapshots..." start_section_spinner "Checking local snapshots..."
local snapshot_list local snapshot_list
snapshot_list=$(tmutil listlocalsnapshots / 2>/dev/null) snapshot_list=$(tmutil listlocalsnapshots / 2> /dev/null)
stop_section_spinner stop_section_spinner
[[ -z "$snapshot_list" ]] && return 0 [[ -z "$snapshot_list" ]] && return 0
local cleaned_count=0 local cleaned_count=0
@@ -305,14 +305,14 @@ clean_local_snapshots() {
local snap_name="${BASH_REMATCH[0]}" local snap_name="${BASH_REMATCH[0]}"
snapshots+=("$snap_name") snapshots+=("$snap_name")
local date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]:0:2}:${BASH_REMATCH[4]:2:2}:${BASH_REMATCH[4]:4:2}" local date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]:0:2}:${BASH_REMATCH[4]:2:2}:${BASH_REMATCH[4]:4:2}"
local snap_ts=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%s" 2>/dev/null || echo "0") local snap_ts=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%s" 2> /dev/null || echo "0")
[[ "$snap_ts" == "0" ]] && continue [[ "$snap_ts" == "0" ]] && continue
if [[ "$snap_ts" -gt "$newest_ts" ]]; then if [[ "$snap_ts" -gt "$newest_ts" ]]; then
newest_ts="$snap_ts" newest_ts="$snap_ts"
newest_name="$snap_name" newest_name="$snap_name"
fi fi
fi fi
done <<<"$snapshot_list" done <<< "$snapshot_list"
[[ ${#snapshots[@]} -eq 0 ]] && return 0 [[ ${#snapshots[@]} -eq 0 ]] && return 0
[[ -z "$newest_name" ]] && return 0 [[ -z "$newest_name" ]] && return 0
@@ -331,7 +331,7 @@ clean_local_snapshots() {
echo -e " ${GRAY}The most recent snapshot will be kept.${NC}" echo -e " ${GRAY}The most recent snapshot will be kept.${NC}"
echo -ne " ${PURPLE}${ICON_ARROW}${NC} Remove all local snapshots except the most recent one? ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: " echo -ne " ${PURPLE}${ICON_ARROW}${NC} Remove all local snapshots except the most recent one? ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: "
local choice local choice
if type read_key >/dev/null 2>&1; then if type read_key > /dev/null 2>&1; then
choice=$(read_key) choice=$(read_key)
else else
IFS= read -r -s -n 1 choice || choice="" IFS= read -r -s -n 1 choice || choice=""
@@ -356,7 +356,7 @@ clean_local_snapshots() {
((cleaned_count++)) ((cleaned_count++))
note_activity note_activity
else else
if sudo tmutil deletelocalsnapshots "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}" >/dev/null 2>&1; then if sudo tmutil deletelocalsnapshots "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}" > /dev/null 2>&1; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed snapshot: $snap_name" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed snapshot: $snap_name"
((cleaned_count++)) ((cleaned_count++))
note_activity note_activity

View File

@@ -20,7 +20,7 @@ clean_chrome_old_versions() {
) )
# Match the exact Chrome process name to avoid false positives # Match the exact Chrome process name to avoid false positives
if pgrep -x "Google Chrome" >/dev/null 2>&1; then if pgrep -x "Google Chrome" > /dev/null 2>&1; then
echo -e " ${YELLOW}${ICON_WARNING}${NC} Google Chrome running · old versions cleanup skipped" echo -e " ${YELLOW}${ICON_WARNING}${NC} Google Chrome running · old versions cleanup skipped"
return 0 return 0
fi fi
@@ -39,7 +39,7 @@ clean_chrome_old_versions() {
[[ -L "$current_link" ]] || continue [[ -L "$current_link" ]] || continue
local current_version local current_version
current_version=$(readlink "$current_link" 2>/dev/null || true) current_version=$(readlink "$current_link" 2> /dev/null || true)
current_version="${current_version##*/}" current_version="${current_version##*/}"
[[ -n "$current_version" ]] || continue [[ -n "$current_version" ]] || continue
@@ -69,9 +69,9 @@ clean_chrome_old_versions() {
cleaned_any=true cleaned_any=true
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
if has_sudo_session; then if has_sudo_session; then
safe_sudo_remove "$dir" >/dev/null 2>&1 || true safe_sudo_remove "$dir" > /dev/null 2>&1 || true
else else
safe_remove "$dir" true >/dev/null 2>&1 || true safe_remove "$dir" true > /dev/null 2>&1 || true
fi fi
fi fi
done done
@@ -100,7 +100,7 @@ clean_edge_old_versions() {
) )
# Match the exact Edge process name to avoid false positives (e.g., Microsoft Teams) # Match the exact Edge process name to avoid false positives (e.g., Microsoft Teams)
if pgrep -x "Microsoft Edge" >/dev/null 2>&1; then if pgrep -x "Microsoft Edge" > /dev/null 2>&1; then
echo -e " ${YELLOW}${ICON_WARNING}${NC} Microsoft Edge running · old versions cleanup skipped" echo -e " ${YELLOW}${ICON_WARNING}${NC} Microsoft Edge running · old versions cleanup skipped"
return 0 return 0
fi fi
@@ -119,7 +119,7 @@ clean_edge_old_versions() {
[[ -L "$current_link" ]] || continue [[ -L "$current_link" ]] || continue
local current_version local current_version
current_version=$(readlink "$current_link" 2>/dev/null || true) current_version=$(readlink "$current_link" 2> /dev/null || true)
current_version="${current_version##*/}" current_version="${current_version##*/}"
[[ -n "$current_version" ]] || continue [[ -n "$current_version" ]] || continue
@@ -149,9 +149,9 @@ clean_edge_old_versions() {
cleaned_any=true cleaned_any=true
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
if has_sudo_session; then if has_sudo_session; then
safe_sudo_remove "$dir" >/dev/null 2>&1 || true safe_sudo_remove "$dir" > /dev/null 2>&1 || true
else else
safe_remove "$dir" true >/dev/null 2>&1 || true safe_remove "$dir" true > /dev/null 2>&1 || true
fi fi
fi fi
done done
@@ -177,7 +177,7 @@ clean_edge_updater_old_versions() {
local updater_dir="$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-stable" local updater_dir="$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-stable"
[[ -d "$updater_dir" ]] || return 0 [[ -d "$updater_dir" ]] || return 0
if pgrep -x "Microsoft Edge" >/dev/null 2>&1; then if pgrep -x "Microsoft Edge" > /dev/null 2>&1; then
echo -e " ${YELLOW}${ICON_WARNING}${NC} Microsoft Edge running · updater cleanup skipped" echo -e " ${YELLOW}${ICON_WARNING}${NC} Microsoft Edge running · updater cleanup skipped"
return 0 return 0
fi fi
@@ -215,7 +215,7 @@ clean_edge_updater_old_versions() {
((cleaned_count++)) ((cleaned_count++))
cleaned_any=true cleaned_any=true
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
safe_remove "$dir" true >/dev/null 2>&1 || true safe_remove "$dir" true > /dev/null 2>&1 || true
fi fi
done done
@@ -242,20 +242,20 @@ scan_external_volumes() {
[[ -d "$volume" && -w "$volume" && ! -L "$volume" ]] || continue [[ -d "$volume" && -w "$volume" && ! -L "$volume" ]] || continue
[[ "$volume" == "/" || "$volume" == "/Volumes/Macintosh HD" ]] && continue [[ "$volume" == "/" || "$volume" == "/Volumes/Macintosh HD" ]] && continue
local protocol="" local protocol=""
protocol=$(run_with_timeout 1 command diskutil info "$volume" 2>/dev/null | grep -i "Protocol:" | awk '{print $2}' || echo "") protocol=$(run_with_timeout 1 command diskutil info "$volume" 2> /dev/null | grep -i "Protocol:" | awk '{print $2}' || echo "")
case "$protocol" in case "$protocol" in
SMB | NFS | AFP | CIFS | WebDAV) SMB | NFS | AFP | CIFS | WebDAV)
network_volumes+=("$volume") network_volumes+=("$volume")
continue continue
;; ;;
esac esac
local fs_type="" local fs_type=""
fs_type=$(run_with_timeout 1 command df -T "$volume" 2>/dev/null | tail -1 | awk '{print $2}' || echo "") fs_type=$(run_with_timeout 1 command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}' || echo "")
case "$fs_type" in case "$fs_type" in
nfs | smbfs | afpfs | cifs | webdav) nfs | smbfs | afpfs | cifs | webdav)
network_volumes+=("$volume") network_volumes+=("$volume")
continue continue
;; ;;
esac esac
candidate_volumes+=("$volume") candidate_volumes+=("$volume")
done done
@@ -275,7 +275,7 @@ scan_external_volumes() {
if [[ -d "$volume_trash" && "$DRY_RUN" != "true" ]] && ! is_path_whitelisted "$volume_trash"; then if [[ -d "$volume_trash" && "$DRY_RUN" != "true" ]] && ! is_path_whitelisted "$volume_trash"; then
while IFS= read -r -d '' item; do while IFS= read -r -d '' item; do
safe_remove "$item" true || true safe_remove "$item" true || true
done < <(command find "$volume_trash" -mindepth 1 -maxdepth 1 -print0 2>/dev/null || true) done < <(command find "$volume_trash" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
fi fi
if [[ "$PROTECT_FINDER_METADATA" != "true" ]]; then if [[ "$PROTECT_FINDER_METADATA" != "true" ]]; then
clean_ds_store_tree "$volume" "$(basename "$volume") volume (.DS_Store)" clean_ds_store_tree "$volume" "$(basename "$volume") volume (.DS_Store)"
@@ -362,7 +362,7 @@ clean_mail_downloads() {
((cleaned_kb += file_size_kb)) ((cleaned_kb += file_size_kb))
fi fi
fi fi
done < <(command find "$target_path" -type f -mtime +"$mail_age_days" -print0 2>/dev/null || true) done < <(command find "$target_path" -type f -mtime +"$mail_age_days" -print0 2> /dev/null || true)
fi fi
done done
if [[ $count -gt 0 ]]; then if [[ $count -gt 0 ]]; then
@@ -420,7 +420,7 @@ process_container_cache() {
local cache_dir="$container_dir/Data/Library/Caches" local cache_dir="$container_dir/Data/Library/Caches"
[[ -d "$cache_dir" ]] || return 0 [[ -d "$cache_dir" ]] || return 0
# Fast non-empty check. # Fast non-empty check.
if find "$cache_dir" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then if find "$cache_dir" -mindepth 1 -maxdepth 1 -print -quit 2> /dev/null | grep -q .; then
local size=$(get_path_size_kb "$cache_dir") local size=$(get_path_size_kb "$cache_dir")
((total_size += size)) ((total_size += size))
found_any=true found_any=true
@@ -451,7 +451,7 @@ clean_browsers() {
safe_clean ~/Library/Caches/company.thebrowser.dia/* "Dia cache" safe_clean ~/Library/Caches/company.thebrowser.dia/* "Dia cache"
safe_clean ~/Library/Caches/BraveSoftware/Brave-Browser/* "Brave cache" safe_clean ~/Library/Caches/BraveSoftware/Brave-Browser/* "Brave cache"
local firefox_running=false local firefox_running=false
if pgrep -x "Firefox" >/dev/null 2>&1; then if pgrep -x "Firefox" > /dev/null 2>&1; then
firefox_running=true firefox_running=true
fi fi
if [[ "$firefox_running" == "true" ]]; then if [[ "$firefox_running" == "true" ]]; then
@@ -504,7 +504,7 @@ clean_virtualization_tools() {
} }
# Application Support logs/caches. # Application Support logs/caches.
clean_application_support_logs() { clean_application_support_logs() {
if [[ ! -d "$HOME/Library/Application Support" ]] || ! ls "$HOME/Library/Application Support" >/dev/null 2>&1; then if [[ ! -d "$HOME/Library/Application Support" ]] || ! ls "$HOME/Library/Application Support" > /dev/null 2>&1; then
note_activity note_activity
echo -e " ${YELLOW}${ICON_WARNING}${NC} Skipped: No permission to access Application Support" echo -e " ${YELLOW}${ICON_WARNING}${NC} Skipped: No permission to access Application Support"
return 0 return 0
@@ -536,7 +536,7 @@ clean_application_support_logs() {
local -a start_candidates=("$app_dir/log" "$app_dir/logs" "$app_dir/activitylog" "$app_dir/Cache/Cache_Data" "$app_dir/Crashpad/completed") local -a start_candidates=("$app_dir/log" "$app_dir/logs" "$app_dir/activitylog" "$app_dir/Cache/Cache_Data" "$app_dir/Crashpad/completed")
for candidate in "${start_candidates[@]}"; do for candidate in "${start_candidates[@]}"; do
if [[ -d "$candidate" ]]; then if [[ -d "$candidate" ]]; then
if find "$candidate" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then if find "$candidate" -mindepth 1 -maxdepth 1 -print -quit 2> /dev/null | grep -q .; then
local size=$(get_path_size_kb "$candidate") local size=$(get_path_size_kb "$candidate")
((total_size += size)) ((total_size += size))
((cleaned_count++)) ((cleaned_count++))
@@ -544,7 +544,7 @@ clean_application_support_logs() {
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
for item in "$candidate"/*; do for item in "$candidate"/*; do
[[ -e "$item" ]] || continue [[ -e "$item" ]] || continue
safe_remove "$item" true >/dev/null 2>&1 || true safe_remove "$item" true > /dev/null 2>&1 || true
done done
fi fi
fi fi
@@ -560,7 +560,7 @@ clean_application_support_logs() {
local -a gc_candidates=("$container_path/Logs" "$container_path/Library/Logs") local -a gc_candidates=("$container_path/Logs" "$container_path/Library/Logs")
for candidate in "${gc_candidates[@]}"; do for candidate in "${gc_candidates[@]}"; do
if [[ -d "$candidate" ]]; then if [[ -d "$candidate" ]]; then
if find "$candidate" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then if find "$candidate" -mindepth 1 -maxdepth 1 -print -quit 2> /dev/null | grep -q .; then
local size=$(get_path_size_kb "$candidate") local size=$(get_path_size_kb "$candidate")
((total_size += size)) ((total_size += size))
((cleaned_count++)) ((cleaned_count++))
@@ -568,7 +568,7 @@ clean_application_support_logs() {
if [[ "$DRY_RUN" != "true" ]]; then if [[ "$DRY_RUN" != "true" ]]; then
for item in "$candidate"/*; do for item in "$candidate"/*; do
[[ -e "$item" ]] || continue [[ -e "$item" ]] || continue
safe_remove "$item" true >/dev/null 2>&1 || true safe_remove "$item" true > /dev/null 2>&1 || true
done done
fi fi
fi fi
@@ -597,7 +597,7 @@ check_ios_device_backups() {
if [[ -d "$backup_dir" ]]; then if [[ -d "$backup_dir" ]]; then
local backup_kb=$(get_path_size_kb "$backup_dir") local backup_kb=$(get_path_size_kb "$backup_dir")
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then
local backup_human=$(command du -sh "$backup_dir" 2>/dev/null | awk '{print $1}') local backup_human=$(command du -sh "$backup_dir" 2> /dev/null | awk '{print $1}')
if [[ -n "$backup_human" ]]; then if [[ -n "$backup_human" ]]; then
note_activity note_activity
echo -e " Found ${GREEN}${backup_human}${NC} iOS backups" echo -e " Found ${GREEN}${backup_human}${NC} iOS backups"

View File

@@ -168,47 +168,47 @@ read_key() {
return 0 return 0
} }
case "$key" in case "$key" in
$'\n' | $'\r') echo "ENTER" ;; $'\n' | $'\r') echo "ENTER" ;;
$'\x7f' | $'\x08') echo "DELETE" ;; $'\x7f' | $'\x08') echo "DELETE" ;;
$'\x1b') $'\x1b')
# Check if this is an escape sequence (arrow keys) or ESC key # Check if this is an escape sequence (arrow keys) or ESC key
if IFS= read -r -s -n 1 -t 0.1 rest 2>/dev/null; then if IFS= read -r -s -n 1 -t 0.1 rest 2> /dev/null; then
if [[ "$rest" == "[" ]]; then if [[ "$rest" == "[" ]]; then
if IFS= read -r -s -n 1 -t 0.1 rest2 2>/dev/null; then if IFS= read -r -s -n 1 -t 0.1 rest2 2> /dev/null; then
case "$rest2" in case "$rest2" in
"A") echo "UP" ;; "A") echo "UP" ;;
"B") echo "DOWN" ;; "B") echo "DOWN" ;;
"C") echo "RIGHT" ;; "C") echo "RIGHT" ;;
"D") echo "LEFT" ;; "D") echo "LEFT" ;;
"3") "3")
IFS= read -r -s -n 1 -t 0.1 rest3 2>/dev/null IFS= read -r -s -n 1 -t 0.1 rest3 2> /dev/null
[[ "$rest3" == "~" ]] && echo "DELETE" || echo "OTHER" [[ "$rest3" == "~" ]] && echo "DELETE" || echo "OTHER"
;; ;;
*) echo "OTHER" ;; *) echo "OTHER" ;;
esac esac
else echo "QUIT"; fi else echo "QUIT"; fi
elif [[ "$rest" == "O" ]]; then elif [[ "$rest" == "O" ]]; then
if IFS= read -r -s -n 1 -t 0.1 rest2 2>/dev/null; then if IFS= read -r -s -n 1 -t 0.1 rest2 2> /dev/null; then
case "$rest2" in case "$rest2" in
"A") echo "UP" ;; "A") echo "UP" ;;
"B") echo "DOWN" ;; "B") echo "DOWN" ;;
"C") echo "RIGHT" ;; "C") echo "RIGHT" ;;
"D") echo "LEFT" ;; "D") echo "LEFT" ;;
*) echo "OTHER" ;; *) echo "OTHER" ;;
esac esac
else echo "OTHER"; fi else echo "OTHER"; fi
else
# Not an escape sequence, it's ESC key
echo "QUIT"
fi
else else
# Not an escape sequence, it's ESC key # No following characters, it's ESC key
echo "QUIT" echo "QUIT"
fi fi
else ;;
# No following characters, it's ESC key ' ') echo "SPACE" ;; # Allow space in filter mode for selection
echo "QUIT" [[:print:]]) echo "CHAR:$key" ;;
fi *) echo "OTHER" ;;
;;
' ') echo "SPACE" ;; # Allow space in filter mode for selection
[[:print:]]) echo "CHAR:$key" ;;
*) echo "OTHER" ;;
esac esac
return 0 return 0
fi fi
@@ -218,53 +218,53 @@ read_key() {
return 0 return 0
} }
case "$key" in case "$key" in
$'\n' | $'\r') echo "ENTER" ;; $'\n' | $'\r') echo "ENTER" ;;
' ') echo "SPACE" ;; ' ') echo "SPACE" ;;
'/') echo "FILTER" ;; '/') echo "FILTER" ;;
'q' | 'Q') echo "QUIT" ;; 'q' | 'Q') echo "QUIT" ;;
'R') echo "RETRY" ;; 'R') echo "RETRY" ;;
'm' | 'M') echo "MORE" ;; 'm' | 'M') echo "MORE" ;;
'u' | 'U') echo "UPDATE" ;; 'u' | 'U') echo "UPDATE" ;;
't' | 'T') echo "TOUCHID" ;; 't' | 'T') echo "TOUCHID" ;;
'j' | 'J') echo "DOWN" ;; 'j' | 'J') echo "DOWN" ;;
'k' | 'K') echo "UP" ;; 'k' | 'K') echo "UP" ;;
'h' | 'H') echo "LEFT" ;; 'h' | 'H') echo "LEFT" ;;
'l' | 'L') echo "RIGHT" ;; 'l' | 'L') echo "RIGHT" ;;
$'\x03') echo "QUIT" ;; $'\x03') echo "QUIT" ;;
$'\x7f' | $'\x08') echo "DELETE" ;; $'\x7f' | $'\x08') echo "DELETE" ;;
$'\x1b') $'\x1b')
if IFS= read -r -s -n 1 -t 1 rest 2>/dev/null; then if IFS= read -r -s -n 1 -t 1 rest 2> /dev/null; then
if [[ "$rest" == "[" ]]; then if [[ "$rest" == "[" ]]; then
if IFS= read -r -s -n 1 -t 1 rest2 2>/dev/null; then if IFS= read -r -s -n 1 -t 1 rest2 2> /dev/null; then
case "$rest2" in case "$rest2" in
"A") echo "UP" ;; "B") echo "DOWN" ;; "A") echo "UP" ;; "B") echo "DOWN" ;;
"C") echo "RIGHT" ;; "D") echo "LEFT" ;; "C") echo "RIGHT" ;; "D") echo "LEFT" ;;
"3") "3")
IFS= read -r -s -n 1 -t 1 rest3 2>/dev/null IFS= read -r -s -n 1 -t 1 rest3 2> /dev/null
[[ "$rest3" == "~" ]] && echo "DELETE" || echo "OTHER" [[ "$rest3" == "~" ]] && echo "DELETE" || echo "OTHER"
;; ;;
*) echo "OTHER" ;; *) echo "OTHER" ;;
esac esac
else echo "QUIT"; fi else echo "QUIT"; fi
elif [[ "$rest" == "O" ]]; then elif [[ "$rest" == "O" ]]; then
if IFS= read -r -s -n 1 -t 1 rest2 2>/dev/null; then if IFS= read -r -s -n 1 -t 1 rest2 2> /dev/null; then
case "$rest2" in case "$rest2" in
"A") echo "UP" ;; "B") echo "DOWN" ;; "A") echo "UP" ;; "B") echo "DOWN" ;;
"C") echo "RIGHT" ;; "D") echo "LEFT" ;; "C") echo "RIGHT" ;; "D") echo "LEFT" ;;
*) echo "OTHER" ;; *) echo "OTHER" ;;
esac esac
else echo "OTHER"; fi
else echo "OTHER"; fi else echo "OTHER"; fi
else echo "OTHER"; fi else echo "QUIT"; fi
else echo "QUIT"; fi ;;
;; [[:print:]]) echo "CHAR:$key" ;;
[[:print:]]) echo "CHAR:$key" ;; *) echo "OTHER" ;;
*) echo "OTHER" ;;
esac esac
} }
drain_pending_input() { drain_pending_input() {
local drained=0 local drained=0
while IFS= read -r -s -n 1 -t 0.01 _ 2>/dev/null; do while IFS= read -r -s -n 1 -t 0.01 _ 2> /dev/null; do
((drained++)) ((drained++))
[[ $drained -gt 100 ]] && break [[ $drained -gt 100 ]] && break
done done
@@ -288,7 +288,7 @@ INLINE_SPINNER_PID=""
INLINE_SPINNER_STOP_FILE="" INLINE_SPINNER_STOP_FILE=""
start_inline_spinner() { start_inline_spinner() {
stop_inline_spinner 2>/dev/null || true stop_inline_spinner 2> /dev/null || true
local message="$1" local message="$1"
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
@@ -312,11 +312,11 @@ start_inline_spinner() {
done done
# Clean up stop file before exiting # Clean up stop file before exiting
rm -f "$stop_file" 2>/dev/null || true rm -f "$stop_file" 2> /dev/null || true
exit 0 exit 0
) & ) &
INLINE_SPINNER_PID=$! INLINE_SPINNER_PID=$!
disown 2>/dev/null || true disown 2> /dev/null || true
else else
echo -n " ${BLUE}|${NC} $message" >&2 || true echo -n " ${BLUE}|${NC} $message" >&2 || true
fi fi
@@ -326,25 +326,25 @@ stop_inline_spinner() {
if [[ -n "$INLINE_SPINNER_PID" ]]; then if [[ -n "$INLINE_SPINNER_PID" ]]; then
# Cooperative stop: create stop file to signal spinner to exit # Cooperative stop: create stop file to signal spinner to exit
if [[ -n "$INLINE_SPINNER_STOP_FILE" ]]; then if [[ -n "$INLINE_SPINNER_STOP_FILE" ]]; then
touch "$INLINE_SPINNER_STOP_FILE" 2>/dev/null || true touch "$INLINE_SPINNER_STOP_FILE" 2> /dev/null || true
fi fi
# Wait briefly for cooperative exit # Wait briefly for cooperative exit
local wait_count=0 local wait_count=0
while kill -0 "$INLINE_SPINNER_PID" 2>/dev/null && [[ $wait_count -lt 5 ]]; do while kill -0 "$INLINE_SPINNER_PID" 2> /dev/null && [[ $wait_count -lt 5 ]]; do
sleep 0.05 2>/dev/null || true sleep 0.05 2> /dev/null || true
((wait_count++)) ((wait_count++))
done done
# Only use SIGKILL as last resort if process is stuck # Only use SIGKILL as last resort if process is stuck
if kill -0 "$INLINE_SPINNER_PID" 2>/dev/null; then if kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then
kill -KILL "$INLINE_SPINNER_PID" 2>/dev/null || true kill -KILL "$INLINE_SPINNER_PID" 2> /dev/null || true
fi fi
wait "$INLINE_SPINNER_PID" 2>/dev/null || true wait "$INLINE_SPINNER_PID" 2> /dev/null || true
# Cleanup # Cleanup
rm -f "$INLINE_SPINNER_STOP_FILE" 2>/dev/null || true rm -f "$INLINE_SPINNER_STOP_FILE" 2> /dev/null || true
INLINE_SPINNER_PID="" INLINE_SPINNER_PID=""
INLINE_SPINNER_STOP_FILE="" INLINE_SPINNER_STOP_FILE=""
@@ -361,8 +361,8 @@ with_spinner() {
start_inline_spinner "$msg" start_inline_spinner "$msg"
local exit_code=0 local exit_code=0
if [[ -n "${MOLE_TIMEOUT_BIN:-}" ]]; then if [[ -n "${MOLE_TIMEOUT_BIN:-}" ]]; then
"$MOLE_TIMEOUT_BIN" "$timeout" "$@" >/dev/null 2>&1 || exit_code=$? "$MOLE_TIMEOUT_BIN" "$timeout" "$@" > /dev/null 2>&1 || exit_code=$?
else "$@" >/dev/null 2>&1 || exit_code=$?; fi else "$@" > /dev/null 2>&1 || exit_code=$?; fi
stop_inline_spinner "$msg" stop_inline_spinner "$msg"
return $exit_code return $exit_code
} }
@@ -379,14 +379,14 @@ format_last_used_summary() {
local value="$1" local value="$1"
case "$value" in case "$value" in
"" | "Unknown") "" | "Unknown")
echo "Unknown" echo "Unknown"
return 0 return 0
;; ;;
"Never" | "Recent" | "Today" | "Yesterday" | "This year" | "Old") "Never" | "Recent" | "Today" | "Yesterday" | "This year" | "Old")
echo "$value" echo "$value"
return 0 return 0
;; ;;
esac esac
if [[ $value =~ ^([0-9]+)[[:space:]]+days?\ ago$ ]]; then if [[ $value =~ ^([0-9]+)[[:space:]]+days?\ ago$ ]]; then
@@ -444,7 +444,7 @@ has_full_disk_access() {
if [[ -e "$test_path" ]]; then if [[ -e "$test_path" ]]; then
tested_count=$((tested_count + 1)) tested_count=$((tested_count + 1))
# Try to stat the ACTUAL protected path - this requires FDA # Try to stat the ACTUAL protected path - this requires FDA
if stat "$test_path" >/dev/null 2>&1; then if stat "$test_path" > /dev/null 2>&1; then
accessible_count=$((accessible_count + 1)) accessible_count=$((accessible_count + 1))
fi fi
fi fi