#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire base pretty

function display_help() {
    uecho "$scriptname: lancer une commande en différé

USAGE
    $scriptname [options] -- command [args]

La commande est lancée après un certain temps, sauf si ce script est rappelé
(auquel cas le compte est réinitialisé), ou si l'opération est annulée.
Attention! La commande est lancée en tâche de fond, et son entrée standard est
connectée à un fichier qui peut être provisionné avec l'option -a

note: ce script ne fonctionne que sous Linux puisqu'il utilise la commande flock

OPTIONS
    -n, --name NAME
        Spécifier un nom identifiant la tâche. Par défaut, le nom est généré à
        partir des détails de la tâche à lancer. Ce nom est utilisé pour
        identifier les invocations successives.
    -f, --cmdfile CMDFILE
        Spécifier un fichier contenant les commandes à lancer. Le fichier est
        sourcé dans un sous-shell. Utiliser - pour lire les commandes depuis
        l'entrée standard.
    --rundelay RUNDELAY[=$RUNDELAY]
        Nombre de secondes au bout desquelles la commande est lancée. Si ce
        script est relancé avant la fin de ce décompte, le compte est remis à
        zéro.
        Utiliser --rundelay '' pour désactiver cette fonctionnalité, auquel cas
        la commande est lancée immédiatement.
    -s, --sudo
        Forcer l'exécution de la commande avec l'utilisateur root si ce n'est
        pas déjà le cas
    -a, --datafile DATAFILE
        Accumuler des données à fournir à la commande. Les informations du
        fichier DATAFILE (utiliser - pour l'entrée standard) sont ajoutées à un
        fichier temporaires, et sont fournies en une seule fois à la commande
        sur son entrée standard.
    -A, --data DATA
        Variante de --datafile où les données sont fournies sur la ligne de
        commande au lieu d'un fichier externe. Si les deux options -a et -A sont
        spécifiées, les données sont accumulées dans l'ordre --datafile puis
        --data
    -k, --cancel
        Annuler le lancement planifié d'une commande. Si la commande est déjà en
        train de tourner, cette option n'a aucun effet."
}

RUNDELAY=5

function acquire_lock() {
    eval "exec $1>'$2'"
    flock $3 "$1"
}
function release_lock() {
    rm "$2"
    eval "exec $1>&-"
}

name=
cmdfile=
rundelay="$RUNDELAY"
run_as_root=
datafile=
data=
cancel=
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    -n:,--name: name= \
    -f:,--cmdfile: cmdfile= \
    --rundelay: rundelay= \
    -s,--sudo run_as_root=1 \
    -a:,--datafile: datafile= \
    -A:,--data: data= \
    -k,--cancel cancel=1 \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

if [ -n "$run_as_root" ]; then
    args=(
        -n "$name"
        -f "$cmdfile"
        --rundelay "$rundelay"
        -a "$datafile"
        -A "$data"
        ${cancel:+-k}
        -- "$@"
    )
    run_as_root "${args[@]}"
fi

[ -n "$cmdfile" -o $# -gt 0 ] || die "Vous devez spécifier la commande à lancer"
[ -n "$cmdfile" -a $# -gt 0 ] && ewarn "Vous avez spécifié une commande et un fichier de commandes. La commande '$*' sera ignorée"

if [ -n "$name" ]; then
    if is_root; then
        base="/var/run/$scriptname/name/$name"
    else
        base="/tmp/$scriptname/name/$name"
    fi
else
    if is_root; then
        base="/var/run/$scriptname/cmd"
    else
        base="/tmp/$scriptname/cmd"
    fi
    [ "$cmdfile" == "-" ] && cmdfile=/dev/stdin
    if [ -n "$cmdfile" ]; then
        # fichier de commande
        cmdfile="$(abspath "$cmdfile")"
        base="$base$cmdfile"
    elif [ "${1#/}" != "$1" ]; then
        # commande avec un chemin absolu
        base="$base$1"
    else
        # commande avec un chemin relatif: il faut calculer le chemin absolu
        if progexists "$1"; then
            base="$base$(which "$1")"
        else
            base="$base/$1"
        fi
    fi
fi
mkdirof "$base"

if [ "$cmdfile" == "/dev/stdin" ]; then
    # commandes lues depuis stdin
    cmdfile="$base"
    cat >"$cmdfile"
fi

# données en cours de collecte
podata="$base.podata"
polock="$base.polock"
function acquire_po() { acquire_lock 7 "$polock" "$*"; }
function release_po() { release_lock 7 "$polock" "$*"; }
# données à traiter
codata="$base.codata"
# commande à lancer
uslock="$base.uslock"
function acquire_us() { acquire_lock 8 "$uslock" "$*"; }
function release_us() { release_lock 8 "$uslock" "$*"; }
uspid="$base.uspid"

function __update_pending() {
    touch "$podata"
    [ "$datafile" == "-" ] && datafile=/dev/stdin
    [ -n "$datafile" ] && stdredir "$datafile" ">>$podata" "" cat
    [ -n "$data" ] && echo "$data" >>"$podata"
}

function __run_command() { (
    exec <"$codata"
    if [ -n "$cmdfile" ]; then
        source "$cmdfile"
    else
        "$@"
    fi
); }

acquire_po
__update_pending
release_po

if acquire_us -n; then
    (
        [ -f "$uspid" ] && {
            pid=$(<"$uspid")
            kill -0 "$pid" >&/dev/null && kill "$pid"
            rm "$uspid"
        }
        if [ -n "$cancel" ]; then
            release_us
            exit 0
        fi
        echo $BASHPID >"$uspid"
        release_us

        [ -n "$rundelay" ] && sleep "$rundelay"

        acquire_us
        while [ -f "$podata" ]; do
            acquire_po
            mv "$podata" "$codata"
            release_po
            __run_command "$@"
            rm "$codata"
        done
        rm "$uspid"
        release_us
    ) &
fi
