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

perf(purge/uninstall): eliminate per-item subprocess forks in scan phase

purge:
- is_safe_project_artifact: replace echo|tr|wc depth calc with pure bash
  string arithmetic (2 forks → 0 per item)
- process_scan_results: replace dirname subprocess with ${item%/*}
- is_recently_modified: accept pre-computed epoch to avoid redundant
  get_epoch_seconds call (N date forks → 1 before loop)
- fd pattern construction: replace 45 per-target sed forks with single
  printf|sed pass (45 forks → 2)
- pre-compute _cached_project_paths alongside existing name/basename
  cache so display loop avoids get_project_path subshell per item

uninstall/app-selector:
- default size display: N/A → -- for apps without cached size data
  (cleaner UX on first scan; real sizes populate from background refresh)
- Pass 1 scan: replace basename/dirname subshells with parameter expansion
This commit is contained in:
Tw93
2026-03-21 15:33:57 +08:00
parent 4f07a5aa0a
commit c5d6cdc2f9
3 changed files with 26 additions and 26 deletions

View File

@@ -469,12 +469,11 @@ scan_applications() {
while IFS= read -r -d '' app_path; do
if [[ ! -e "$app_path" ]]; then continue; fi
local app_name
app_name=$(basename "$app_path" .app)
local app_name="${app_path##*/}"
app_name="${app_name%.app}"
# Skip nested apps inside another .app bundle.
local parent_dir
parent_dir=$(dirname "$app_path")
local parent_dir="${app_path%/*}"
if [[ "$parent_dir" == *".app" || "$parent_dir" == *".app/"* ]]; then
continue
fi
@@ -485,9 +484,8 @@ scan_applications() {
if [[ -n "$link_target" ]]; then
local resolved_target="$link_target"
if [[ "$link_target" != /* ]]; then
local link_dir
link_dir=$(dirname "$app_path")
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "$(dirname "$link_target")" 2> /dev/null && pwd)/$(basename "$link_target") 2> /dev/null || echo ""
local link_dir="${app_path%/*}"
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "${link_target%/*}" 2> /dev/null && pwd)/"${link_target##*/}" 2> /dev/null || echo ""
fi
case "$resolved_target" in
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
@@ -661,7 +659,7 @@ scan_applications() {
fi
local final_size_kb=0
local final_size="N/A"
local final_size="--"
if [[ "$cached_size_kb" =~ ^[0-9]+$ && $cached_size_kb -gt 0 ]]; then
final_size_kb="$cached_size_kb"
final_size=$(bytes_to_human "$((cached_size_kb * 1024))")

View File

@@ -285,7 +285,8 @@ is_safe_project_artifact() {
# Must not be a direct child of the search root.
local relative_path="${path#"$search_path"/}"
local depth=$(echo "$relative_path" | LC_ALL=C tr -cd '/' | wc -c)
local _rel_stripped="${relative_path//\//}"
local depth=$((${#relative_path} - ${#_rel_stripped}))
if [[ $depth -lt 1 ]]; then
# Allow direct-child artifacts only when the search path is itself
# a project root (single-project mode).
@@ -429,7 +430,7 @@ scan_purge_targets() {
if [[ -n "$item" ]] && is_safe_project_artifact "$item" "$search_path"; then
echo "$item"
# Update scanning path to show current project directory
local project_dir=$(dirname "$item")
local project_dir="${item%/*}"
echo "$project_dir" > "$stats_dir/purge_scanning" 2> /dev/null || true
fi
done < "$input_file" | filter_nested_artifacts | filter_protected_artifacts > "$output_file"
@@ -446,15 +447,11 @@ scan_purge_targets() {
debug_log "MO_USE_FIND=1: Forcing find instead of fd"
use_find=true
elif command -v fd > /dev/null 2>&1; then
# Escape regex special characters in target names for fd patterns
local escaped_targets=()
for target in "${PURGE_TARGETS[@]}"; do
escaped_targets+=("^$(printf '%s' "$target" | sed -e 's/[][(){}.^$*+?|\\]/\\&/g')\$")
done
local pattern="($(
IFS='|'
echo "${escaped_targets[*]}"
))"
# Escape regex special characters in target names for fd patterns (single sed pass)
local _escaped_lines
_escaped_lines=$(printf '%s\n' "${PURGE_TARGETS[@]}" | sed -e 's/[][(){}.^$*+?|\\]/\\&/g')
local pattern
pattern="($(printf '%s\n' "$_escaped_lines" | sed -e 's/^/^/' -e 's/$/$/' | paste -sd '|' -))"
local fd_args=(
"--absolute-path"
"--hidden"
@@ -546,14 +543,16 @@ filter_protected_artifacts() {
# Check if a path was modified recently (safety check).
is_recently_modified() {
local path="$1"
local current_time="${2:-}"
local age_days=$MIN_AGE_DAYS
if [[ ! -e "$path" ]]; then
return 1
fi
local mod_time
mod_time=$(get_file_mtime "$path")
local current_time
current_time=$(get_epoch_seconds)
if [[ -z "$current_time" || ! "$current_time" =~ ^[0-9]+$ ]]; then
current_time=$(get_epoch_seconds)
fi
local age_seconds=$((current_time - mod_time))
local age_in_days=$((age_seconds / 86400))
if [[ $age_in_days -lt $age_days ]]; then
@@ -1048,8 +1047,10 @@ clean_project_artifacts() {
return 2 # Special code: nothing to clean
fi
# Mark recently modified items (for default selection state)
local _now_epoch
_now_epoch=$(get_epoch_seconds)
for item in "${all_found_items[@]}"; do
if is_recently_modified "$item"; then
if is_recently_modified "$item" "$_now_epoch"; then
recently_modified+=("$item")
fi
# Add all items to safe_to_clean, let user choose
@@ -1332,10 +1333,12 @@ clean_project_artifacts() {
# can avoid repeated filesystem traversals during the O(N^2) duplicate check.
local -a _cached_basenames=()
local -a _cached_project_names=()
local -a _cached_project_paths=()
local _pre_idx
for _pre_idx in "${!safe_to_clean[@]}"; do
_cached_basenames[_pre_idx]="${safe_to_clean[$_pre_idx]##*/}"
_cached_project_names[_pre_idx]=$(get_project_name "${safe_to_clean[$_pre_idx]}")
_cached_project_paths[_pre_idx]=$(get_project_path "${safe_to_clean[$_pre_idx]}")
done
# Build menu options - one line per artifact
@@ -1346,8 +1349,7 @@ clean_project_artifacts() {
local -a item_display_paths=()
local _sz_idx=0
for item in "${safe_to_clean[@]}"; do
local project_path
project_path=$(get_project_path "$item")
local project_path="${_cached_project_paths[$_sz_idx]}"
local artifact_type
artifact_type=$(get_artifact_display_name "$item")
local size_raw

View File

@@ -17,8 +17,8 @@ format_app_display() {
fi
# Format size
local size_str="N/A"
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size"
local size_str="--"
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" && "$size" != "N/A" && "$size" != "--" ]] && size_str="$size"
# Calculate available width for app name based on terminal width
# Accept pre-calculated max_name_width (5th param) to avoid recalculation in loops