Files
brain-template/scripts/file-lock.sh

189 lines
5.7 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
# file-lock.sh — Mutex fichier BSI-v3-7 (ADR-036 : brain.db)
# Empêche deux satellites d'écrire simultanément dans le même fichier.
# Source : table locks dans brain.db (ex : locks/*.lock)
#
# Usage :
# file-lock.sh acquire <filepath> <sess-id> [ttl_minutes] → acquiert le lock
# file-lock.sh release <filepath> <sess-id> → libère le lock
# file-lock.sh check <filepath> → qui détient le lock ?
# file-lock.sh list → tous les locks actifs
# file-lock.sh cleanup → supprime les locks expirés
#
# Exit codes :
# 0 = succès
# 1 = lock déjà détenu par une autre session (acquire)
# 2 = erreur (sess-id incorrect pour release, fichier introuvable)
set -euo pipefail
BRAIN_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
DB_PATH="$BRAIN_ROOT/brain.db"
DEFAULT_TTL=60 # minutes
# Init table si absente
python3 "$BRAIN_ROOT/scripts/bsi-db.py" -script "
CREATE TABLE IF NOT EXISTS locks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filepath TEXT NOT NULL UNIQUE,
holder TEXT NOT NULL,
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT NOT NULL,
ttl_min INTEGER NOT NULL DEFAULT 60
);
"
# --- ACQUIRE ---
cmd_acquire() {
local filepath="$1"
local sess_id="$2"
local ttl="${3:-$DEFAULT_TTL}"
# Check existing active lock held by someone else
local existing
existing=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "
SELECT holder, expires_at FROM locks
WHERE filepath = '$filepath'
AND julianday('now') < julianday(expires_at)
AND holder != '$sess_id'
LIMIT 1;
")
if [ -n "$existing" ]; then
local holder expires
holder=$(echo "$existing" | cut -d'|' -f1)
expires=$(echo "$existing" | cut -d'|' -f2)
echo "🔴 LOCK — $filepath"
echo " Détenu par : $holder"
echo " Expire à : $expires"
echo ""
echo " Attendre le release ou contacter : $holder"
exit 1
fi
# Upsert — remplace si même holder ou expiré
python3 "$BRAIN_ROOT/scripts/bsi-db.py" -script "
DELETE FROM locks WHERE filepath = '$filepath';
INSERT INTO locks (filepath, holder, claimed_at, expires_at, ttl_min)
VALUES ('$filepath', '$sess_id', datetime('now'), datetime('now', '+$ttl minutes'), $ttl);
"
local expires_at
expires_at=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "SELECT expires_at FROM locks WHERE filepath = '$filepath';")
echo "✅ Lock acquis : $filepath"
echo " Session : $sess_id"
echo " Expire : $expires_at"
}
# --- RELEASE ---
cmd_release() {
local filepath="$1"
local sess_id="$2"
local holder
holder=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "SELECT holder FROM locks WHERE filepath = '$filepath';")
if [ -z "$holder" ]; then
echo " Pas de lock actif sur : $filepath"
exit 0
fi
if [ "$holder" != "$sess_id" ]; then
echo "🚨 Release refusé — lock détenu par : $holder (pas $sess_id)"
exit 2
fi
python3 "$BRAIN_ROOT/scripts/bsi-db.py" -exec "DELETE FROM locks WHERE filepath = '$filepath' AND holder = '$sess_id'"
echo "✅ Lock libéré : $filepath"
}
# --- CHECK ---
cmd_check() {
local filepath="$1"
local row
row=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "
SELECT holder, expires_at,
CASE WHEN julianday('now') < julianday(expires_at) THEN 'active' ELSE 'expired' END
FROM locks WHERE filepath = '$filepath';
")
if [ -z "$row" ]; then
echo "✅ Libre : $filepath"
exit 0
fi
local holder expires status
holder=$(echo "$row" | cut -d'|' -f1)
expires=$(echo "$row" | cut -d'|' -f2)
status=$(echo "$row" | cut -d'|' -f3)
if [ "$status" = "active" ]; then
echo "🔴 Locké : $filepath"
echo " Holder : $holder"
echo " Expire : $expires"
else
echo "⚠️ Lock expiré (nettoyable) : $filepath"
echo " Ancien holder : $holder"
fi
}
# --- LIST ---
cmd_list() {
local rows
rows=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "
SELECT filepath, holder, expires_at,
CASE WHEN julianday('now') < julianday(expires_at) THEN 'actif' ELSE 'expiré' END
FROM locks ORDER BY claimed_at DESC;
")
if [ -z "$rows" ]; then
echo "✅ Aucun lock actif"
exit 0
fi
echo "Locks actifs :"
echo ""
while IFS='|' read -r filepath holder expires status; do
local icon="🔴"
[ "$status" = "expiré" ] && icon="⚠️ "
echo " $icon $status | $filepath | $holder | exp: $expires"
done <<< "$rows"
}
# --- CLEANUP ---
cmd_cleanup() {
local count
count=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "
SELECT COUNT(*) FROM locks WHERE julianday('now') >= julianday(expires_at);
")
if [ "$count" -eq 0 ]; then
echo "✅ Aucun lock expiré à nettoyer"
else
python3 "$BRAIN_ROOT/scripts/bsi-db.py" -exec "DELETE FROM locks WHERE julianday('now') >= julianday(expires_at)"
echo "$count lock(s) nettoyé(s)"
fi
}
# --- Router ---
CMD="${1:-}"
case "$CMD" in
acquire) cmd_acquire "${2:-}" "${3:-}" "${4:-}" ;;
release) cmd_release "${2:-}" "${3:-}" ;;
check) cmd_check "${2:-}" ;;
list) cmd_list ;;
cleanup) cmd_cleanup ;;
*)
echo "Usage : file-lock.sh <acquire|release|check|list|cleanup>"
echo ""
echo " acquire <filepath> <sess-id> [ttl_min] → acquiert le lock (défaut: 60min)"
echo " release <filepath> <sess-id> → libère le lock"
echo " check <filepath> → état du lock"
echo " list → tous les locks actifs"
echo " cleanup → supprime les locks expirés"
exit 1
;;
esac