mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 09:46:44 +00:00
- Replace parentheses with commas for supplementary info - Use commas instead of em-dashes for separators - Update bullet points from - to * in some contexts - Improve version extraction regex with fallback logic
218 lines
7.9 KiB
Bash
218 lines
7.9 KiB
Bash
#!/bin/bash
|
|
# Cache Cleanup Module
|
|
set -euo pipefail
|
|
# Preflight TCC prompts once to avoid mid-run interruptions.
|
|
check_tcc_permissions() {
|
|
[[ -t 1 ]] || return 0
|
|
local permission_flag="$HOME/.cache/mole/permissions_granted"
|
|
[[ -f "$permission_flag" ]] && return 0
|
|
local -a tcc_dirs=(
|
|
"$HOME/Library/Caches"
|
|
"$HOME/Library/Logs"
|
|
"$HOME/Library/Application Support"
|
|
"$HOME/Library/Containers"
|
|
"$HOME/.cache"
|
|
)
|
|
# Quick permission probe (avoid deep scans).
|
|
local needs_permission_check=false
|
|
if ! ls "$HOME/Library/Caches" > /dev/null 2>&1; then
|
|
needs_permission_check=true
|
|
fi
|
|
if [[ "$needs_permission_check" == "true" ]]; then
|
|
echo ""
|
|
echo -e "${BLUE}First-time setup${NC}"
|
|
echo -e "${GRAY}macOS will request permissions to access Library folders.${NC}"
|
|
echo -e "${GRAY}You may see ${GREEN}${#tcc_dirs[@]} permission dialogs${NC}${GRAY}, please approve them all.${NC}"
|
|
echo ""
|
|
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to continue: "
|
|
read -r
|
|
MOLE_SPINNER_PREFIX="" start_inline_spinner "Requesting permissions..."
|
|
# Touch each directory to trigger prompts without deep scanning.
|
|
for dir in "${tcc_dirs[@]}"; do
|
|
[[ -d "$dir" ]] && command find "$dir" -maxdepth 1 -type d > /dev/null 2>&1
|
|
done
|
|
stop_inline_spinner
|
|
echo ""
|
|
fi
|
|
# Mark as granted to avoid repeat prompts.
|
|
ensure_user_file "$permission_flag"
|
|
return 0
|
|
}
|
|
# Args: $1=browser_name, $2=cache_path
|
|
# Clean Service Worker cache while protecting critical web editors.
|
|
clean_service_worker_cache() {
|
|
local browser_name="$1"
|
|
local cache_path="$2"
|
|
[[ ! -d "$cache_path" ]] && return 0
|
|
local cleaned_size=0
|
|
local protected_count=0
|
|
while IFS= read -r cache_dir; do
|
|
[[ ! -d "$cache_dir" ]] && continue
|
|
# Extract a best-effort domain name from cache folder.
|
|
local domain=$(basename "$cache_dir" | grep -oE '[a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z]{2,}' | head -1 || echo "")
|
|
local size=$(run_with_timeout 5 get_path_size_kb "$cache_dir")
|
|
local is_protected=false
|
|
for protected_domain in "${PROTECTED_SW_DOMAINS[@]}"; do
|
|
if [[ "$domain" == *"$protected_domain"* ]]; then
|
|
is_protected=true
|
|
protected_count=$((protected_count + 1))
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$is_protected" == "false" ]]; then
|
|
if [[ "$DRY_RUN" != "true" ]]; then
|
|
safe_remove "$cache_dir" true || true
|
|
fi
|
|
cleaned_size=$((cleaned_size + size))
|
|
fi
|
|
done < <(run_with_timeout 10 sh -c "find '$cache_path' -type d -depth 2 2> /dev/null || true")
|
|
if [[ $cleaned_size -gt 0 ]]; then
|
|
local spinner_was_running=false
|
|
if [[ -t 1 && -n "${INLINE_SPINNER_PID:-}" ]]; then
|
|
stop_inline_spinner
|
|
spinner_was_running=true
|
|
fi
|
|
local cleaned_mb=$((cleaned_size / 1024))
|
|
if [[ "$DRY_RUN" != "true" ]]; then
|
|
if [[ $protected_count -gt 0 ]]; then
|
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker, ${cleaned_mb}MB, ${protected_count} protected"
|
|
else
|
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $browser_name Service Worker, ${cleaned_mb}MB"
|
|
fi
|
|
else
|
|
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $browser_name Service Worker, would clean ${cleaned_mb}MB, ${protected_count} protected"
|
|
fi
|
|
note_activity
|
|
if [[ "$spinner_was_running" == "true" ]]; then
|
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning browser Service Worker caches..."
|
|
fi
|
|
fi
|
|
}
|
|
# Next.js/Python project caches with tight scan bounds and timeouts.
|
|
clean_project_caches() {
|
|
stop_inline_spinner 2> /dev/null || true
|
|
# Fast pre-check before scanning the whole home dir.
|
|
local has_dev_projects=false
|
|
local -a common_dev_dirs=(
|
|
"$HOME/Code"
|
|
"$HOME/Projects"
|
|
"$HOME/workspace"
|
|
"$HOME/github"
|
|
"$HOME/dev"
|
|
"$HOME/work"
|
|
"$HOME/src"
|
|
"$HOME/repos"
|
|
"$HOME/Development"
|
|
"$HOME/www"
|
|
"$HOME/golang"
|
|
"$HOME/go"
|
|
"$HOME/rust"
|
|
"$HOME/python"
|
|
"$HOME/ruby"
|
|
"$HOME/java"
|
|
"$HOME/dotnet"
|
|
"$HOME/node"
|
|
)
|
|
for dir in "${common_dev_dirs[@]}"; do
|
|
if [[ -d "$dir" ]]; then
|
|
has_dev_projects=true
|
|
break
|
|
fi
|
|
done
|
|
# Fallback: look for project markers near $HOME.
|
|
if [[ "$has_dev_projects" == "false" ]]; then
|
|
local -a project_markers=(
|
|
"node_modules"
|
|
".git"
|
|
"target"
|
|
"go.mod"
|
|
"Cargo.toml"
|
|
"package.json"
|
|
"pom.xml"
|
|
"build.gradle"
|
|
)
|
|
local spinner_active=false
|
|
if [[ -t 1 ]]; then
|
|
MOLE_SPINNER_PREFIX=" "
|
|
start_inline_spinner "Detecting dev projects..."
|
|
spinner_active=true
|
|
fi
|
|
for marker in "${project_markers[@]}"; do
|
|
if run_with_timeout 3 sh -c "find '$HOME' -maxdepth 2 -name '$marker' -not -path '*/Library/*' -not -path '*/.Trash/*' 2>/dev/null | head -1" | grep -q .; then
|
|
has_dev_projects=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$spinner_active" == "true" ]]; then
|
|
stop_inline_spinner 2> /dev/null || true
|
|
fi
|
|
[[ "$has_dev_projects" == "false" ]] && return 0
|
|
fi
|
|
if [[ -t 1 ]]; then
|
|
MOLE_SPINNER_PREFIX=" "
|
|
start_inline_spinner "Searching project caches..."
|
|
fi
|
|
local nextjs_tmp_file
|
|
nextjs_tmp_file=$(create_temp_file)
|
|
local pycache_tmp_file
|
|
pycache_tmp_file=$(create_temp_file)
|
|
local find_timeout=10
|
|
# Parallel scans (Next.js and __pycache__).
|
|
(
|
|
command find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \
|
|
-not -path "*/Library/*" \
|
|
-not -path "*/.Trash/*" \
|
|
-not -path "*/node_modules/*" \
|
|
-not -path "*/.*" \
|
|
2> /dev/null || true
|
|
) > "$nextjs_tmp_file" 2>&1 &
|
|
local next_pid=$!
|
|
(
|
|
command find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \
|
|
-not -path "*/Library/*" \
|
|
-not -path "*/.Trash/*" \
|
|
-not -path "*/node_modules/*" \
|
|
-not -path "*/.*" \
|
|
2> /dev/null || true
|
|
) > "$pycache_tmp_file" 2>&1 &
|
|
local py_pid=$!
|
|
local elapsed=0
|
|
local check_interval=0.2 # Check every 200ms instead of 1s for smoother experience
|
|
while [[ $(echo "$elapsed < $find_timeout" | awk '{print ($1 < $2)}') -eq 1 ]]; do
|
|
if ! kill -0 $next_pid 2> /dev/null && ! kill -0 $py_pid 2> /dev/null; then
|
|
break
|
|
fi
|
|
sleep $check_interval
|
|
elapsed=$(echo "$elapsed + $check_interval" | awk '{print $1 + $2}')
|
|
done
|
|
# Kill stuck scans after timeout.
|
|
for pid in $next_pid $py_pid; do
|
|
if kill -0 "$pid" 2> /dev/null; then
|
|
kill -TERM "$pid" 2> /dev/null || true
|
|
local grace_period=0
|
|
while [[ $grace_period -lt 20 ]]; do
|
|
if ! kill -0 "$pid" 2> /dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
((grace_period++))
|
|
done
|
|
if kill -0 "$pid" 2> /dev/null; then
|
|
kill -KILL "$pid" 2> /dev/null || true
|
|
fi
|
|
wait "$pid" 2> /dev/null || true
|
|
else
|
|
wait "$pid" 2> /dev/null || true
|
|
fi
|
|
done
|
|
if [[ -t 1 ]]; then
|
|
stop_inline_spinner
|
|
fi
|
|
while IFS= read -r next_dir; do
|
|
[[ -d "$next_dir/cache" ]] && safe_clean "$next_dir/cache"/* "Next.js build cache" || true
|
|
done < "$nextjs_tmp_file"
|
|
while IFS= read -r pycache; do
|
|
[[ -d "$pycache" ]] && safe_clean "$pycache"/* "Python bytecode cache" || true
|
|
done < "$pycache_tmp_file"
|
|
}
|