mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 11:43:10 +02:00
175 lines
5.5 KiB
Bash
Executable File
175 lines
5.5 KiB
Bash
Executable File
#!/system/bin/sh
|
|
# shellcheck shell=ash
|
|
# ============================================================
|
|
# Workout detector for rooted Android.
|
|
#
|
|
# Why this exists:
|
|
# The user wants YouTube unblocked ONLY while a StrongLifts workout
|
|
# is currently in progress (i.e. started but not yet finished). This
|
|
# daemon writes a 1/0 flag to $WORKOUT_ACTIVE_FILE; hosts_enforcer.sh
|
|
# reads the flag and swaps the active canonical hosts file between
|
|
# the full block ($HOSTS_CANONICAL) and the workout-relaxed variant
|
|
# ($HOSTS_CANONICAL_WORKOUT) on transitions.
|
|
#
|
|
# Detection signal:
|
|
# StrongLifts persists every workout to $WORKOUT_DB_PATH (SQLite).
|
|
# The `workouts` table has columns `start` (epoch ms) and `finish`
|
|
# (epoch ms, NULL/0 while in progress). The single source of truth:
|
|
#
|
|
# SELECT COUNT(*) FROM workouts
|
|
# WHERE start > 0 AND (finish IS NULL OR finish = 0);
|
|
#
|
|
# Returns 1 during a workout, 0 otherwise. Verified empirically: every
|
|
# completed row in the user's history has both fields populated; only
|
|
# live workouts leave finish=NULL.
|
|
#
|
|
# Why other signals were rejected:
|
|
# * stronglifts_timer_running pref → only true between sets (rest
|
|
# timer); flips on/off every minute during a workout.
|
|
# * Foreground notification → posted only during rest timer.
|
|
# * Foreground activity → only true when actively staring at the app,
|
|
# which is rarely the case while lifting.
|
|
#
|
|
# Failure mode:
|
|
# Fail closed. Any error (sqlite3 missing, DB locked, query non-zero
|
|
# exit, malformed output) writes "0" so YouTube stays blocked. Stale
|
|
# data is preferred over an open door.
|
|
#
|
|
# Read-only DB access:
|
|
# Uses sqlite3's URI form `file:<path>?mode=ro` to avoid touching the
|
|
# app's WAL/SHM files or holding a write lock that StrongLifts could
|
|
# contend with.
|
|
# ============================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
# shellcheck source=config.sh
|
|
. "$SCRIPT_DIR/config.sh"
|
|
|
|
PIDFILE="$STATE_DIR/workout_detector.pid"
|
|
|
|
mkdir -p "$STATE_DIR"
|
|
touch "$WORKOUT_DETECTOR_LOG"
|
|
chmod 666 "$WORKOUT_DETECTOR_LOG" 2>/dev/null || true
|
|
|
|
log() {
|
|
local ts
|
|
ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "[$ts] $1" >> "$WORKOUT_DETECTOR_LOG"
|
|
}
|
|
|
|
rotate_log() {
|
|
local lines
|
|
lines="$(wc -l < "$WORKOUT_DETECTOR_LOG" 2>/dev/null || echo 0)"
|
|
if [ "$lines" -gt 500 ]; then
|
|
local tmp="$WORKOUT_DETECTOR_LOG.tmp"
|
|
tail -n 500 "$WORKOUT_DETECTOR_LOG" > "$tmp"
|
|
mv "$tmp" "$WORKOUT_DETECTOR_LOG"
|
|
fi
|
|
}
|
|
|
|
acquire_lock() {
|
|
if [ -f "$PIDFILE" ]; then
|
|
local old_pid
|
|
old_pid="$(cat "$PIDFILE")"
|
|
if kill -0 "$old_pid" 2>/dev/null; then
|
|
local cmdline
|
|
cmdline="$(tr '\0' ' ' < "/proc/$old_pid/cmdline" 2>/dev/null)"
|
|
if echo "$cmdline" | grep -q "workout_detector"; then
|
|
echo "workout_detector already running (PID $old_pid)"
|
|
exit 0
|
|
fi
|
|
fi
|
|
rm -f "$PIDFILE"
|
|
fi
|
|
echo $$ > "$PIDFILE"
|
|
}
|
|
|
|
# Write the flag atomically and chmod 666 so other daemons (running under
|
|
# different SELinux contexts) can read it. Returns 0 always; callers do not
|
|
# branch on success.
|
|
write_flag() {
|
|
local value="$1"
|
|
local tmp="$WORKOUT_ACTIVE_FILE.tmp"
|
|
printf '%s\n' "$value" > "$tmp"
|
|
mv "$tmp" "$WORKOUT_ACTIVE_FILE"
|
|
chmod 666 "$WORKOUT_ACTIVE_FILE" 2>/dev/null || true
|
|
}
|
|
|
|
# Query StrongLifts DB. On success echoes "0" or "1"; on failure echoes
|
|
# nothing and returns non-zero so the caller can fail closed.
|
|
query_workout_active() {
|
|
if [ ! -x "$WORKOUT_SQLITE3_BIN" ]; then
|
|
return 1
|
|
fi
|
|
if [ ! -f "$WORKOUT_DB_PATH" ]; then
|
|
# App not installed or DB not yet created → no workout possible.
|
|
echo 0
|
|
return 0
|
|
fi
|
|
|
|
local count
|
|
count="$(
|
|
"$WORKOUT_SQLITE3_BIN" "file:${WORKOUT_DB_PATH}?mode=ro" \
|
|
"SELECT COUNT(*) FROM workouts WHERE start>0 AND (finish IS NULL OR finish=0);" \
|
|
2>>"$WORKOUT_DETECTOR_LOG"
|
|
)" || return 1
|
|
|
|
case "$count" in
|
|
0) echo 0 ;;
|
|
[1-9]*) echo 1 ;;
|
|
*)
|
|
log "ERROR: unexpected sqlite output: '$count'"
|
|
return 1
|
|
;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
cleanup() {
|
|
log "workout_detector shutting down"
|
|
# Fail closed on shutdown — assume no workout so YouTube stays blocked.
|
|
write_flag 0
|
|
rm -f "$PIDFILE"
|
|
exit 0
|
|
}
|
|
|
|
trap cleanup INT TERM
|
|
|
|
main() {
|
|
acquire_lock
|
|
log "workout_detector started (PID=$$, db=$WORKOUT_DB_PATH, interval=${WORKOUT_DETECTOR_INTERVAL}s)"
|
|
|
|
local last_state="-1"
|
|
|
|
while true; do
|
|
local new_state
|
|
if new_state="$(query_workout_active)"; then
|
|
:
|
|
else
|
|
new_state=0
|
|
log "WARN: query failed, defaulting workout_active=0 (fail-closed)"
|
|
fi
|
|
|
|
if [ "$new_state" != "$last_state" ]; then
|
|
write_flag "$new_state"
|
|
if [ "$new_state" = "1" ]; then
|
|
log "STATE: workout STARTED → YouTube unblock requested"
|
|
else
|
|
# last_state="-1" is the very first iteration — log the
|
|
# initial baseline distinctly so it is obvious in the log.
|
|
if [ "$last_state" = "-1" ]; then
|
|
log "STATE: initial workout_active=0 (no in-progress workout)"
|
|
else
|
|
log "STATE: workout FINISHED → YouTube re-block requested"
|
|
fi
|
|
fi
|
|
last_state="$new_state"
|
|
fi
|
|
|
|
rotate_log
|
|
sleep "$WORKOUT_DETECTOR_INTERVAL"
|
|
done
|
|
}
|
|
|
|
main "$@"
|