1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 20:15:07 +00:00

fix(clean): avoid container cache scan stalls

Add a lightweight top-level entry cap to Containers and Group
Containers scanning.  When a cache directory exceeds the threshold,
skip the expensive per-item du/find size scan and take a partial-size
/ "cleaned" output path instead.  Replace find-piped-to-read loops
with pure-bash glob iteration to cut external process overhead.

Closes #586
This commit is contained in:
Tw93
2026-03-18 14:49:12 +08:00
parent f88b2116ab
commit b8f3a0ecd3
2 changed files with 180 additions and 40 deletions

View File

@@ -484,6 +484,32 @@ clean_support_app_data() {
}
# App caches (merged: macOS system caches + Sandboxed apps).
cache_top_level_entry_count_capped() {
local dir="$1"
local cap="${2:-101}"
local count=0
local _nullglob_state
local _dotglob_state
_nullglob_state=$(shopt -p nullglob || true)
_dotglob_state=$(shopt -p dotglob || true)
shopt -s nullglob dotglob
local item
for item in "$dir"/*; do
[[ -e "$item" ]] || continue
count=$((count + 1))
if ((count >= cap)); then
break
fi
done
eval "$_nullglob_state"
eval "$_dotglob_state"
[[ "$count" =~ ^[0-9]+$ ]] || count=0
printf '%s\n' "$count"
}
clean_app_caches() {
start_section_spinner "Scanning app caches..."
@@ -516,6 +542,7 @@ clean_app_caches() {
[[ ! -d "$containers_dir" ]] && return 0
start_section_spinner "Scanning sandboxed apps..."
local total_size=0
local total_size_partial=false
local cleaned_count=0
local found_any=false
@@ -529,12 +556,22 @@ clean_app_caches() {
stop_section_spinner
if [[ "$found_any" == "true" ]]; then
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Sandboxed app caches${NC}, ${YELLOW}$size_human dry${NC}"
if [[ "$total_size_partial" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Sandboxed app caches${NC}, ${YELLOW}dry${NC}"
else
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Sandboxed app caches${NC}, ${YELLOW}$size_human dry${NC}"
fi
else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${GREEN}$size_human${NC}"
if [[ "$total_size_partial" == "true" ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${GREEN}cleaned${NC}"
else
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${GREEN}$size_human${NC}"
fi
fi
files_cleaned=$((files_cleaned + cleaned_count))
total_size_cleaned=$((total_size_cleaned + total_size))
@@ -561,20 +598,35 @@ process_container_cache() {
local cache_dir="$container_dir/Data/Library/Caches"
[[ -d "$cache_dir" ]] || return 0
[[ -L "$cache_dir" ]] && return 0
# Fast non-empty check.
if find "$cache_dir" -mindepth 1 -maxdepth 1 -print -quit 2> /dev/null | grep -q .; then
local item_count
item_count=$(cache_top_level_entry_count_capped "$cache_dir" 101)
[[ "$item_count" =~ ^[0-9]+$ ]] || item_count=0
[[ "$item_count" -eq 0 ]] && return 0
if [[ "$item_count" -le 100 ]]; then
local size
size=$(get_path_size_kb "$cache_dir")
size=$(get_path_size_kb "$cache_dir" 2> /dev/null || echo "0")
[[ "$size" =~ ^[0-9]+$ ]] || size=0
total_size=$((total_size + size))
found_any=true
cleaned_count=$((cleaned_count + 1))
if [[ "$DRY_RUN" != "true" ]]; then
local item
while IFS= read -r -d '' item; do
[[ -e "$item" ]] || continue
safe_remove "$item" true || true
done < <(command find "$cache_dir" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
fi
else
total_size_partial=true
fi
found_any=true
cleaned_count=$((cleaned_count + 1))
if [[ "$DRY_RUN" != "true" ]]; then
local _nullglob_state
local _dotglob_state
_nullglob_state=$(shopt -p nullglob || true)
_dotglob_state=$(shopt -p dotglob || true)
shopt -s nullglob dotglob
local item
for item in "$cache_dir"/*; do
[[ -e "$item" ]] || continue
safe_remove "$item" true || true
done
eval "$_nullglob_state"
eval "$_dotglob_state"
fi
}
@@ -588,6 +640,7 @@ clean_group_container_caches() {
start_section_spinner "Scanning Group Containers..."
local total_size=0
local total_size_partial=false
local cleaned_count=0
local found_any=false
@@ -642,42 +695,56 @@ clean_group_container_caches() {
continue
fi
# Build non-protected candidate items for cleanup.
local -a items_to_clean=()
local item
while IFS= read -r -d '' item; do
[[ -e "$item" ]] || continue
[[ -L "$item" ]] && continue
if should_protect_path "$item" 2> /dev/null || is_path_whitelisted "$item" 2> /dev/null; then
continue
else
items_to_clean+=("$item")
fi
done < <(command find "$candidate" -mindepth 1 -maxdepth 1 -print0 2> /dev/null || true)
[[ ${#items_to_clean[@]} -gt 0 ]] || continue
local quick_count
quick_count=$(cache_top_level_entry_count_capped "$candidate" 101)
[[ "$quick_count" =~ ^[0-9]+$ ]] || quick_count=0
[[ "$quick_count" -eq 0 ]] && continue
local candidate_size_kb=0
local candidate_changed=false
if [[ "$DRY_RUN" == "true" ]]; then
for item in "${items_to_clean[@]}"; do
local item_size
item_size=$(get_path_size_kb "$item" 2> /dev/null) || item_size=0
[[ "$item_size" =~ ^[0-9]+$ ]] || item_size=0
local _nullglob_state
local _dotglob_state
_nullglob_state=$(shopt -p nullglob || true)
_dotglob_state=$(shopt -p dotglob || true)
shopt -s nullglob dotglob
if [[ "$quick_count" -gt 100 ]]; then
total_size_partial=true
for item in "$candidate"/*; do
[[ -e "$item" ]] || continue
[[ -L "$item" ]] && continue
if should_protect_path "$item" 2> /dev/null || is_path_whitelisted "$item" 2> /dev/null; then
continue
fi
candidate_changed=true
candidate_size_kb=$((candidate_size_kb + item_size))
if [[ "$DRY_RUN" != "true" ]]; then
safe_remove "$item" true 2> /dev/null || true
fi
done
else
for item in "${items_to_clean[@]}"; do
for item in "$candidate"/*; do
[[ -e "$item" ]] || continue
[[ -L "$item" ]] && continue
if should_protect_path "$item" 2> /dev/null || is_path_whitelisted "$item" 2> /dev/null; then
continue
fi
local item_size
item_size=$(get_path_size_kb "$item" 2> /dev/null) || item_size=0
[[ "$item_size" =~ ^[0-9]+$ ]] || item_size=0
if [[ "$DRY_RUN" == "true" ]]; then
candidate_changed=true
candidate_size_kb=$((candidate_size_kb + item_size))
continue
fi
if safe_remove "$item" true 2> /dev/null; then
candidate_changed=true
candidate_size_kb=$((candidate_size_kb + item_size))
fi
done
fi
eval "$_nullglob_state"
eval "$_dotglob_state"
if [[ "$candidate_changed" == "true" ]]; then
total_size=$((total_size + candidate_size_kb))
@@ -690,12 +757,22 @@ clean_group_container_caches() {
stop_section_spinner
if [[ "$found_any" == "true" ]]; then
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ "$DRY_RUN" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Group Containers logs/caches${NC}, ${YELLOW}$size_human dry${NC}"
if [[ "$total_size_partial" == "true" ]]; then
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Group Containers logs/caches${NC}, ${YELLOW}dry${NC}"
else
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Group Containers logs/caches${NC}, ${YELLOW}$size_human dry${NC}"
fi
else
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Group Containers logs/caches${NC}, ${GREEN}$size_human${NC}"
if [[ "$total_size_partial" == "true" ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Group Containers logs/caches${NC}, ${GREEN}cleaned${NC}"
else
local size_human
size_human=$(bytes_to_human "$((total_size * 1024))")
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Group Containers logs/caches${NC}, ${GREEN}$size_human${NC}"
fi
fi
files_cleaned=$((files_cleaned + cleaned_count))
total_size_cleaned=$((total_size_cleaned + total_size))