mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 20:15: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:
@@ -469,12 +469,11 @@ scan_applications() {
|
|||||||
while IFS= read -r -d '' app_path; do
|
while IFS= read -r -d '' app_path; do
|
||||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||||
|
|
||||||
local app_name
|
local app_name="${app_path##*/}"
|
||||||
app_name=$(basename "$app_path" .app)
|
app_name="${app_name%.app}"
|
||||||
|
|
||||||
# Skip nested apps inside another .app bundle.
|
# Skip nested apps inside another .app bundle.
|
||||||
local parent_dir
|
local parent_dir="${app_path%/*}"
|
||||||
parent_dir=$(dirname "$app_path")
|
|
||||||
if [[ "$parent_dir" == *".app" || "$parent_dir" == *".app/"* ]]; then
|
if [[ "$parent_dir" == *".app" || "$parent_dir" == *".app/"* ]]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -485,9 +484,8 @@ scan_applications() {
|
|||||||
if [[ -n "$link_target" ]]; then
|
if [[ -n "$link_target" ]]; then
|
||||||
local resolved_target="$link_target"
|
local resolved_target="$link_target"
|
||||||
if [[ "$link_target" != /* ]]; then
|
if [[ "$link_target" != /* ]]; then
|
||||||
local link_dir
|
local link_dir="${app_path%/*}"
|
||||||
link_dir=$(dirname "$app_path")
|
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "${link_target%/*}" 2> /dev/null && pwd)/"${link_target##*/}" 2> /dev/null || echo ""
|
||||||
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "$(dirname "$link_target")" 2> /dev/null && pwd)/$(basename "$link_target") 2> /dev/null || echo ""
|
|
||||||
fi
|
fi
|
||||||
case "$resolved_target" in
|
case "$resolved_target" in
|
||||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
||||||
@@ -661,7 +659,7 @@ scan_applications() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local final_size_kb=0
|
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
|
if [[ "$cached_size_kb" =~ ^[0-9]+$ && $cached_size_kb -gt 0 ]]; then
|
||||||
final_size_kb="$cached_size_kb"
|
final_size_kb="$cached_size_kb"
|
||||||
final_size=$(bytes_to_human "$((cached_size_kb * 1024))")
|
final_size=$(bytes_to_human "$((cached_size_kb * 1024))")
|
||||||
|
|||||||
@@ -285,7 +285,8 @@ is_safe_project_artifact() {
|
|||||||
|
|
||||||
# Must not be a direct child of the search root.
|
# Must not be a direct child of the search root.
|
||||||
local relative_path="${path#"$search_path"/}"
|
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
|
if [[ $depth -lt 1 ]]; then
|
||||||
# Allow direct-child artifacts only when the search path is itself
|
# Allow direct-child artifacts only when the search path is itself
|
||||||
# a project root (single-project mode).
|
# a project root (single-project mode).
|
||||||
@@ -429,7 +430,7 @@ scan_purge_targets() {
|
|||||||
if [[ -n "$item" ]] && is_safe_project_artifact "$item" "$search_path"; then
|
if [[ -n "$item" ]] && is_safe_project_artifact "$item" "$search_path"; then
|
||||||
echo "$item"
|
echo "$item"
|
||||||
# Update scanning path to show current project directory
|
# 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
|
echo "$project_dir" > "$stats_dir/purge_scanning" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
done < "$input_file" | filter_nested_artifacts | filter_protected_artifacts > "$output_file"
|
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"
|
debug_log "MO_USE_FIND=1: Forcing find instead of fd"
|
||||||
use_find=true
|
use_find=true
|
||||||
elif command -v fd > /dev/null 2>&1; then
|
elif command -v fd > /dev/null 2>&1; then
|
||||||
# Escape regex special characters in target names for fd patterns
|
# Escape regex special characters in target names for fd patterns (single sed pass)
|
||||||
local escaped_targets=()
|
local _escaped_lines
|
||||||
for target in "${PURGE_TARGETS[@]}"; do
|
_escaped_lines=$(printf '%s\n' "${PURGE_TARGETS[@]}" | sed -e 's/[][(){}.^$*+?|\\]/\\&/g')
|
||||||
escaped_targets+=("^$(printf '%s' "$target" | sed -e 's/[][(){}.^$*+?|\\]/\\&/g')\$")
|
local pattern
|
||||||
done
|
pattern="($(printf '%s\n' "$_escaped_lines" | sed -e 's/^/^/' -e 's/$/$/' | paste -sd '|' -))"
|
||||||
local pattern="($(
|
|
||||||
IFS='|'
|
|
||||||
echo "${escaped_targets[*]}"
|
|
||||||
))"
|
|
||||||
local fd_args=(
|
local fd_args=(
|
||||||
"--absolute-path"
|
"--absolute-path"
|
||||||
"--hidden"
|
"--hidden"
|
||||||
@@ -546,14 +543,16 @@ filter_protected_artifacts() {
|
|||||||
# Check if a path was modified recently (safety check).
|
# Check if a path was modified recently (safety check).
|
||||||
is_recently_modified() {
|
is_recently_modified() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
local current_time="${2:-}"
|
||||||
local age_days=$MIN_AGE_DAYS
|
local age_days=$MIN_AGE_DAYS
|
||||||
if [[ ! -e "$path" ]]; then
|
if [[ ! -e "$path" ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local mod_time
|
local mod_time
|
||||||
mod_time=$(get_file_mtime "$path")
|
mod_time=$(get_file_mtime "$path")
|
||||||
local current_time
|
if [[ -z "$current_time" || ! "$current_time" =~ ^[0-9]+$ ]]; then
|
||||||
current_time=$(get_epoch_seconds)
|
current_time=$(get_epoch_seconds)
|
||||||
|
fi
|
||||||
local age_seconds=$((current_time - mod_time))
|
local age_seconds=$((current_time - mod_time))
|
||||||
local age_in_days=$((age_seconds / 86400))
|
local age_in_days=$((age_seconds / 86400))
|
||||||
if [[ $age_in_days -lt $age_days ]]; then
|
if [[ $age_in_days -lt $age_days ]]; then
|
||||||
@@ -1048,8 +1047,10 @@ clean_project_artifacts() {
|
|||||||
return 2 # Special code: nothing to clean
|
return 2 # Special code: nothing to clean
|
||||||
fi
|
fi
|
||||||
# Mark recently modified items (for default selection state)
|
# Mark recently modified items (for default selection state)
|
||||||
|
local _now_epoch
|
||||||
|
_now_epoch=$(get_epoch_seconds)
|
||||||
for item in "${all_found_items[@]}"; do
|
for item in "${all_found_items[@]}"; do
|
||||||
if is_recently_modified "$item"; then
|
if is_recently_modified "$item" "$_now_epoch"; then
|
||||||
recently_modified+=("$item")
|
recently_modified+=("$item")
|
||||||
fi
|
fi
|
||||||
# Add all items to safe_to_clean, let user choose
|
# 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.
|
# can avoid repeated filesystem traversals during the O(N^2) duplicate check.
|
||||||
local -a _cached_basenames=()
|
local -a _cached_basenames=()
|
||||||
local -a _cached_project_names=()
|
local -a _cached_project_names=()
|
||||||
|
local -a _cached_project_paths=()
|
||||||
local _pre_idx
|
local _pre_idx
|
||||||
for _pre_idx in "${!safe_to_clean[@]}"; do
|
for _pre_idx in "${!safe_to_clean[@]}"; do
|
||||||
_cached_basenames[_pre_idx]="${safe_to_clean[$_pre_idx]##*/}"
|
_cached_basenames[_pre_idx]="${safe_to_clean[$_pre_idx]##*/}"
|
||||||
_cached_project_names[_pre_idx]=$(get_project_name "${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
|
done
|
||||||
|
|
||||||
# Build menu options - one line per artifact
|
# Build menu options - one line per artifact
|
||||||
@@ -1346,8 +1349,7 @@ clean_project_artifacts() {
|
|||||||
local -a item_display_paths=()
|
local -a item_display_paths=()
|
||||||
local _sz_idx=0
|
local _sz_idx=0
|
||||||
for item in "${safe_to_clean[@]}"; do
|
for item in "${safe_to_clean[@]}"; do
|
||||||
local project_path
|
local project_path="${_cached_project_paths[$_sz_idx]}"
|
||||||
project_path=$(get_project_path "$item")
|
|
||||||
local artifact_type
|
local artifact_type
|
||||||
artifact_type=$(get_artifact_display_name "$item")
|
artifact_type=$(get_artifact_display_name "$item")
|
||||||
local size_raw
|
local size_raw
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ format_app_display() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Format size
|
# Format size
|
||||||
local size_str="N/A"
|
local size_str="--"
|
||||||
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size"
|
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" && "$size" != "N/A" && "$size" != "--" ]] && size_str="$size"
|
||||||
|
|
||||||
# Calculate available width for app name based on terminal width
|
# Calculate available width for app name based on terminal width
|
||||||
# Accept pre-calculated max_name_width (5th param) to avoid recalculation in loops
|
# Accept pre-calculated max_name_width (5th param) to avoid recalculation in loops
|
||||||
|
|||||||
Reference in New Issue
Block a user