Files
dotfiles/.config/waybar/scripts/check-updates.sh

238 lines
7.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ==============================================================================
# 功能Waybar 更新检测后台守护脚本
# 特性:定期检查 Pacman 和 AUR 更新,生成 JSON 和 txt 供前端极速读取。
# 修复:引入动态信号屏蔽与 FORCE_UPDATE 标志,完美解决高频触发导致的并发地狱。
# ==============================================================================
set -euo pipefail
# === 配置区域 ===
CACHE_DIR="$HOME/.cache/shorin-check-arch-updates"
CACHE_FILE="$CACHE_DIR/updates.json"
LOCK_FILE="/tmp/waybar-updates.lock"
MAX_LINES=50
CHECK_INTERVAL=3600
PACMAN_LOG="/var/log/pacman.log"
# 确保缓存目录存在
mkdir -p "$CACHE_DIR"
# 全局状态标志0=按需检查1=强制无视缓存检查
FORCE_UPDATE=0
# === 自动检测 AUR Helper ===
if command -v paru &> /dev/null; then
AUR_HELPER="paru"
elif command -v yay &> /dev/null; then
AUR_HELPER="yay"
else
AUR_HELPER=""
fi
# === 信号处理函数 ===
# 不再粗暴删除文件,仅修改标志位
on_sigusr1() {
FORCE_UPDATE=1
}
# 初始绑定信号
trap 'on_sigusr1' SIGUSR1
# === 上次系统更新时间 ===
format_age() {
local seconds=$1
if [[ "$seconds" -lt 0 ]]; then
seconds=0
fi
if [[ "$seconds" -lt 60 ]]; then
printf '刚刚'
elif [[ "$seconds" -lt 3600 ]]; then
printf '%d 分钟前' $((seconds / 60))
elif [[ "$seconds" -lt 86400 ]]; then
printf '%d 小时前' $((seconds / 3600))
elif [[ "$seconds" -lt 2592000 ]]; then
printf '%d 天前' $((seconds / 86400))
elif [[ "$seconds" -lt 31536000 ]]; then
printf '%d 个月前' $((seconds / 2592000))
else
printf '%d 年前' $((seconds / 31536000))
fi
}
get_last_system_update_info() {
if [[ ! -r "$PACMAN_LOG" ]]; then
printf '上次系统更新:未知'
return
fi
local last_line timestamp last_update current_time age
# Count only full system upgrade commands, not AUR/local package upgrades.
last_line=$(tac "$PACMAN_LOG" 2>/dev/null | grep -m1 -E "\[PACMAN\] Running 'pacman ([^']*-S[^[:space:]']*u|[^']*-S[[:space:]][^']*[[:space:]](-u|--sysupgrade)|[^']*--sync[^']*[[:space:]](-u|--sysupgrade))" || true)
if [[ -z "$last_line" ]]; then
printf '上次系统更新:无记录'
return
fi
timestamp=${last_line%%]*}
timestamp=${timestamp#\[}
last_update=$(date -d "$timestamp" +%s 2>/dev/null || true)
if [[ -z "$last_update" ]]; then
printf '上次系统更新:未知'
return
fi
current_time=$(date +%s)
age=$((current_time - last_update))
printf '上次系统更新:%s' "$(format_age "$age")"
}
# === 生成 JSON 函数 ===
generate_json() {
local updates=$1
local count
local last_update_info
last_update_info=$(get_last_system_update_info)
updates=$(echo "$updates" | grep -v '^\s*$' || true)
if [[ -z "$updates" ]]; then
count=0
printf '{"text": "", "alt": "updated", "tooltip": "System is up to date\\n----------------\\n%s"}\n' "$last_update_info"
return
else
count=$(echo "$updates" | wc -l)
fi
local tooltip_text=""
if [[ "$count" -gt "$MAX_LINES" ]]; then
local remainder=$((count - MAX_LINES))
local top_list=$(echo "$updates" | head -n "$MAX_LINES")
local escaped_list=$(echo "$top_list" | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}' )
tooltip_text="${escaped_list}----------------\\n<b>⚠️ ... and ${remainder} more updates</b>"
else
tooltip_text=$(echo "$updates" | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}' | head -c -2 || true)
fi
tooltip_text="${tooltip_text}\\n----------------\\n${last_update_info}"
printf '{"text": "%s", "alt": "has-updates", "tooltip": "%s"}\n' "$count" "$tooltip_text"
}
# === 从缓存文本重新生成输出,避免 tooltip 的相对时间被 JSON 缓存卡住 ===
emit_cached_json() {
local repo_cache="${CACHE_FILE%.json}-repo.txt"
local aur_cache="${CACHE_FILE%.json}-aur.txt"
local REPO_UPDATES=""
local AUR_UPDATES=""
local ALL_UPDATES=""
if [[ ! -f "$repo_cache" && ! -f "$aur_cache" ]]; then
cat "$CACHE_FILE"
return
fi
if [[ -f "$repo_cache" ]]; then
REPO_UPDATES=$(<"$repo_cache")
fi
if [[ -f "$aur_cache" ]]; then
AUR_UPDATES=$(<"$aur_cache")
fi
if [[ -n "$REPO_UPDATES" ]] && [[ -n "$AUR_UPDATES" ]]; then
ALL_UPDATES="$REPO_UPDATES"$'\n'"$AUR_UPDATES"
elif [[ -n "$REPO_UPDATES" ]]; then
ALL_UPDATES="$REPO_UPDATES"
else
ALL_UPDATES="$AUR_UPDATES"
fi
generate_json "$ALL_UPDATES"
}
# === 真正的检查逻辑 ===
perform_update_check() {
local REPO_UPDATES=""
local STATUS=0
REPO_UPDATES=$(checkupdates 2>/dev/null) || STATUS=$?
local AUR_UPDATES=""
if [[ -n "$AUR_HELPER" ]]; then
AUR_UPDATES=$("$AUR_HELPER" -Qua 2>/dev/null || true)
fi
local ALL_UPDATES=""
if [[ $STATUS -eq 0 ]] || [[ $STATUS -eq 2 ]]; then
if [[ -n "$REPO_UPDATES" ]] && [[ -n "$AUR_UPDATES" ]]; then
ALL_UPDATES="$REPO_UPDATES"$'\n'"$AUR_UPDATES"
elif [[ -n "$REPO_UPDATES" ]]; then
ALL_UPDATES="$REPO_UPDATES"
else
ALL_UPDATES="$AUR_UPDATES"
fi
echo "$REPO_UPDATES" > "${CACHE_FILE%.json}-repo.txt"
echo "$AUR_UPDATES" > "${CACHE_FILE%.json}-aur.txt"
generate_json "$ALL_UPDATES" > "$CACHE_FILE"
else
return 1
fi
}
# === 主控制逻辑 ===
run_check() {
# 1. 检查缓存是否新鲜 (如果收到刷新信号 FORCE_UPDATE=1则跳过判断)
if [[ $FORCE_UPDATE -eq 0 ]] && [[ -f "$CACHE_FILE" ]]; then
local current_time file_time age
current_time=$(date +%s)
file_time=$(stat -c %Y "$CACHE_FILE")
age=$((current_time - file_time))
if [[ $age -lt $((CHECK_INTERVAL - 10)) ]]; then
emit_cached_json
return
fi
fi
# 准备干脏活累活前,重置标志位
FORCE_UPDATE=0
# 【核心修复】:动态屏蔽 SIGUSR1 信号!
# 在执行耗时的网络和数据库操作时,无视一切 Ctrl+R 带来的外部干扰。
trap '' SIGUSR1
# 2. 获取锁进行更新
(
if flock -x -n 9; then
perform_update_check || true
else
flock -x -w 120 9 || true
fi
) 9>"$LOCK_FILE" || true
# 【核心修复】:干完活了,重新恢复对 SIGUSR1 信号的监听
trap 'on_sigusr1' SIGUSR1
# 3. 最终输出缓存内容
if [[ -f "$CACHE_FILE" ]]; then
emit_cached_json
else
printf '{"text": "...", "alt": "updated", "tooltip": "Checking...\\n----------------\\n%s"}\n' "$(get_last_system_update_info)"
fi
}
# === 主循环 ===
while true; do
run_check
sleep "$CHECK_INTERVAL" &
# 即使 sleep 被信号强行打断,|| true 也能保住脚本的命
wait $! || true
done