wip os/proc et php/coll
This commit is contained in:
parent
b39ff95639
commit
38e90f752f
|
@ -0,0 +1,576 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\proc;
|
||||||
|
|
||||||
|
use nur\sery\php\coll\AutoArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ManagedTask: une tâche de fond
|
||||||
|
*
|
||||||
|
* --autogen-properties-and-methods--
|
||||||
|
*/
|
||||||
|
class ManagedTask extends AutoArray {
|
||||||
|
const LOCK = "task";
|
||||||
|
|
||||||
|
const DEFINITION_SCHEMA = [
|
||||||
|
"id" => ["string", null, "identifiant de la tâche"],
|
||||||
|
"serial" => ["int", null, "numéro de série permettant de distinguer deux occurrences de la tâche"],
|
||||||
|
"title" => ["?string", null, "description de la tâche"],
|
||||||
|
"valid" => ["bool", false, "la tâche est-elle valide?"],
|
||||||
|
"owner_login" => ["?string", null, "compte de la personne qui a lancé la tâche"],
|
||||||
|
"owner_name" => ["?string", null, "nom de la personne qui a lancé la tâche"],
|
||||||
|
"owner_page" => ["?string", null, "page qui a créé cette tâche"],
|
||||||
|
"owner_params" => ["?array", null, "paramètres à passer à la page"],
|
||||||
|
"cmd" => [null, null, "commande à lancer"],
|
||||||
|
"logfile" => ["?string", null, "sortie de la commande"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const SCHEMA = [
|
||||||
|
"definition" => [
|
||||||
|
"?array", null, "définition de la tâche",
|
||||||
|
"schema" => self::DEFINITION_SCHEMA,
|
||||||
|
],
|
||||||
|
"state" => [
|
||||||
|
"?array", null, "instance de la tâche",
|
||||||
|
"schema" => [
|
||||||
|
"definition" => [
|
||||||
|
"array", null, "copie de la définition de la tâche",
|
||||||
|
"schema" => self::DEFINITION_SCHEMA,
|
||||||
|
],
|
||||||
|
"started" => ["bool", false, "la tâche a-t-elle été démarrée?"],
|
||||||
|
"date_start" => ["?datetime", null, "date du démarrage de la tâche"],
|
||||||
|
"pid" => ["?int", null, "PID du process contrôleur"],
|
||||||
|
"status" => ["?string", null, "Message de statut indiqué par la tâche"],
|
||||||
|
"stopped" => ["bool", false, "la tâche est-elle terminée?"],
|
||||||
|
"date_stop" => ["?datetime", null, "date de l'arrêt de la tâche"],
|
||||||
|
"retcode" => ["?int", null, "code de retour de la commande"],
|
||||||
|
"done" => ["bool", false, "la fin de la tâche a-t-elle été prise en compte?"],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"" => [
|
||||||
|
"auto_properties" => [
|
||||||
|
"id" => "definition.id",
|
||||||
|
"serial" => "definition.serial",
|
||||||
|
"title" => "definition.title",
|
||||||
|
"valid" => "definition.valid",
|
||||||
|
"owner_login" => "definition.owner_login",
|
||||||
|
"owner_name" => "definition.owner_name",
|
||||||
|
"owner_page" => "definition.owner_page",
|
||||||
|
"owner_params" => "definition.owner_params",
|
||||||
|
"cmd" => "definition.cmd",
|
||||||
|
"logfile" => "definition.logfile",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const _AUTO_PROPERTIES = self::SCHEMA[""]["auto_properties"];
|
||||||
|
|
||||||
|
const _SCHEMA = [
|
||||||
|
"id" => ["string", null, "identifiant de la tâche"],
|
||||||
|
"serial" => ["string", null, "numéro de série permettant de distinguer deux occurrences de la tâche"],
|
||||||
|
"title" => ["?string", null, "description de la tâche"],
|
||||||
|
"valid" => ["bool", false, "la tâche est-elle valide?"],
|
||||||
|
"owner_login" => ["?string", null, "compte de la personne qui a lancé la tâche"],
|
||||||
|
"owner_name" => ["?string", null, "nom de la personne qui a lancé la tâche"],
|
||||||
|
"page" => ["?array", null, "page qui a créé cette tâche et paramètres à passer à la page"],
|
||||||
|
"cmd" => [null, null, "commande à lancer"],
|
||||||
|
"logfile" => ["?string", null, "sortie de commande"],
|
||||||
|
"started" => ["bool", false, "la tâche a-t-elle été démarrée?"],
|
||||||
|
"date_start" => ["?datetime", null, "date du démarrage de la tâche"],
|
||||||
|
"pid" => ["?int", null, "PID du process contrôleur"],
|
||||||
|
"stopped" => ["bool", false, "la tâche est-elle terminée?"],
|
||||||
|
"date_stop" => ["?datetime", null, "date de l'arrêt de la tâche"],
|
||||||
|
"retcode" => ["?int", null, "code de retour de la commande"],
|
||||||
|
"done" => ["bool", false, "la fin de la tâche a-t-elle été prise en compte?"],
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct(string $id, bool $autoUpdate=false, ?callable $init=null) {
|
||||||
|
# ne pas appeler parent::__construct()
|
||||||
|
if (file_exists($id)) {
|
||||||
|
$file = $id;
|
||||||
|
} else {
|
||||||
|
$authz = authz::get();
|
||||||
|
$this->data = [
|
||||||
|
"id" => $id,
|
||||||
|
"serial" => 0,
|
||||||
|
"owner_login" => $authz->getUsername(),
|
||||||
|
"owner_name" => $authz->getDisplayName(),
|
||||||
|
];
|
||||||
|
$file = tasks::pf("$id.task");
|
||||||
|
}
|
||||||
|
$this->init = $init;
|
||||||
|
$this->file = $file;
|
||||||
|
$this->ensureTask($autoUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ?callable */
|
||||||
|
protected $init;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $file;
|
||||||
|
|
||||||
|
private function ensureTask(bool $autoUpdate): void {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
try {
|
||||||
|
if (is_file($this->file)) {
|
||||||
|
$this->_reload();
|
||||||
|
} else {
|
||||||
|
$this->_reset();
|
||||||
|
$this->_save();
|
||||||
|
}
|
||||||
|
$logfile = $this->getLogfile();
|
||||||
|
if ($logfile !== null) os::mkdirof($logfile);
|
||||||
|
} finally {
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
if ($autoUpdate) $this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _init(): bool {
|
||||||
|
$init = $this->init;
|
||||||
|
if ($init !== null) {
|
||||||
|
func::call($init, $this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(): void {
|
||||||
|
if ($this->init !== null) {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
try {
|
||||||
|
$this->_init();
|
||||||
|
$this->_save();
|
||||||
|
} finally {
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _reset(): void {
|
||||||
|
$authz = authz::get();
|
||||||
|
$id = $this->data["id"];
|
||||||
|
$serial = A::get($this->data, "serial", 0);
|
||||||
|
$this->data = $this->ensureData([
|
||||||
|
"id" => $id,
|
||||||
|
"serial" => $serial + 1,
|
||||||
|
"owner_login" => $authz->getUsername(),
|
||||||
|
"owner_name" => $authz->getDisplayName(),
|
||||||
|
"logfile" => logs::pf("$id/latest.log"),
|
||||||
|
]);
|
||||||
|
$this->_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(): void {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
try {
|
||||||
|
$this->_reset();
|
||||||
|
$this->_save();
|
||||||
|
} finally {
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _reload(): void {
|
||||||
|
$this->data = unserialize(file_get_contents($this->file));
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload(): void {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
try {
|
||||||
|
$this->_reload();
|
||||||
|
} finally {
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _save(): void {
|
||||||
|
os::mkdirof($this->file);
|
||||||
|
$outf = fopen($this->file, "w+");
|
||||||
|
fwrite($outf, serialize($this->data));
|
||||||
|
fclose($outf);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(): void {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
try {
|
||||||
|
$this->_save();
|
||||||
|
} finally {
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** vérifier que l'objet est bien initialisé */
|
||||||
|
function validate(): void {
|
||||||
|
if (!$this->isValid()) {
|
||||||
|
if ($this->getCmd() === null) {
|
||||||
|
throw new ValueException("cmd is required");
|
||||||
|
}
|
||||||
|
$this->setValid(true);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _launch(): void {
|
||||||
|
$args = [
|
||||||
|
__DIR__.'/../../lib/launch_task.php',
|
||||||
|
"--envname", envs::get(),
|
||||||
|
$this->getId(),
|
||||||
|
];
|
||||||
|
$logfile = $this->getLogfile();
|
||||||
|
if ($logfile !== null) A::merge($args, ["--logfile", $logfile]);
|
||||||
|
$cmd = new Cmd($args);
|
||||||
|
$cmd->addRedir("null");
|
||||||
|
$cmd->passthru();
|
||||||
|
$this->reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLaunchable(): bool {
|
||||||
|
return $this->isStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch(): void {
|
||||||
|
if (!$this->isStartable()) return;
|
||||||
|
if ($this->isDone()) $this->reset();
|
||||||
|
$this->_launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUpdatable(): bool {
|
||||||
|
return $this->isLaunchable() && !$this->isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(): void {
|
||||||
|
if ($this->isUpdatable()) $this->_launch();
|
||||||
|
else $this->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function kill(): void {
|
||||||
|
if (!$this->isStarted() || $this->isStopped()) return;
|
||||||
|
$args = [
|
||||||
|
__DIR__.'/../../lib/launch_task.php',
|
||||||
|
"-e", envs::get(),
|
||||||
|
"--kill",
|
||||||
|
$this->getId(),
|
||||||
|
];
|
||||||
|
$logfile = $this->getLogfile();
|
||||||
|
if ($logfile !== null) A::merge($args, ["-L", $logfile]);
|
||||||
|
$cmd = new Cmd($args);
|
||||||
|
$cmd->addRedir("null");
|
||||||
|
$cmd->passthru();
|
||||||
|
$this->reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartable(): bool {
|
||||||
|
return !$this->isStarted() || $this->isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* démarrer la commande. doit être lancé depuis launch_task.php
|
||||||
|
*/
|
||||||
|
function ltStart(?string $logfile): void {
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if ($pid == -1) {
|
||||||
|
# parent, impossible de forker
|
||||||
|
throw new IllegalAccessException("unable to fork");
|
||||||
|
} elseif ($pid) {
|
||||||
|
# parent, fork ok
|
||||||
|
$this->setStarted(true);
|
||||||
|
$this->setDateStart(date::datetime());
|
||||||
|
$this->setPid($pid);
|
||||||
|
$this->save();
|
||||||
|
} else {
|
||||||
|
## child, fork ok
|
||||||
|
# Créer un groupe de process, pour pouvoir les tuer toutes en même temps
|
||||||
|
posix_setsid();
|
||||||
|
msg::push($oldMsg, null, [
|
||||||
|
"output" => $logfile,
|
||||||
|
]);
|
||||||
|
$retcode = -776;
|
||||||
|
try {
|
||||||
|
# tout d'abord synchroniser les fichiers le cas échéant
|
||||||
|
$command = $this->get("command");
|
||||||
|
$append = false;
|
||||||
|
if ($command !== null) {
|
||||||
|
$files = $command["files"];
|
||||||
|
$forceSync = $this->get("force_sync");
|
||||||
|
files::sync($files, $forceSync, $logfile, "wb");
|
||||||
|
$append = true;
|
||||||
|
}
|
||||||
|
# puis lancer la commande
|
||||||
|
$cmd = Cmd::with($this->getCmd());
|
||||||
|
if ($logfile !== null) $cmd->addRedir("both", $logfile, $append);
|
||||||
|
$cmd->fork_exec($retcode);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
msg::error($e);
|
||||||
|
} finally {
|
||||||
|
$this->reload();
|
||||||
|
$this->setStopped(true);
|
||||||
|
$this->setDateStop(date::datetime());
|
||||||
|
$this->setRetcode($retcode);
|
||||||
|
$this->save();
|
||||||
|
msg::pop($oldMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** arrêter la commande. doit être lancé depuis launch_task.php */
|
||||||
|
function ltKill(?string $logfile): void {
|
||||||
|
msg::push($oldMsg, null, $logfile);
|
||||||
|
try {
|
||||||
|
$id = $this->getId();
|
||||||
|
$pid = $this->getPid();
|
||||||
|
msg::action("$id: $pid");
|
||||||
|
if (!posix_kill(-$pid, SIGKILL)) {
|
||||||
|
switch (posix_get_last_error()) {
|
||||||
|
case PCNTL_ESRCH:
|
||||||
|
msg::afailure("process inexistant");
|
||||||
|
break;
|
||||||
|
case PCNTL_EPERM:
|
||||||
|
msg::afailure("process non accessible");
|
||||||
|
break;
|
||||||
|
case PCNTL_EINVAL:
|
||||||
|
msg::afailure("signal invalide");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$timeout = 10;
|
||||||
|
while ($this->ltIsUndead()) {
|
||||||
|
sleep(1);
|
||||||
|
if (--$timeout == 0) {
|
||||||
|
msg::afailure("tentative d'arrêt de la tâche");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg::asuccess("tâche arrêtée");
|
||||||
|
$this->setStopped(true);
|
||||||
|
$this->setDateStop(date::datetime());
|
||||||
|
$this->setRetcode(-787);
|
||||||
|
$this->setDone(true);
|
||||||
|
$this->save();
|
||||||
|
} finally {
|
||||||
|
msg::pop($oldMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReapable(): bool {
|
||||||
|
return $this->isStopped() && !$this->isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* marquer la commande comme terminée. doit être lancé depuis launch_task.php
|
||||||
|
*/
|
||||||
|
function ltReap(): void {
|
||||||
|
pcntl_waitpid($this->getPid(), $status);
|
||||||
|
$this->setDone(true);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si on est dans le cas où la tâche est censée tourner mais en
|
||||||
|
* réalité ce n'est pas le cas. doit être lancé depuis launch_task.php
|
||||||
|
*/
|
||||||
|
function ltIsUndead(): bool {
|
||||||
|
if (!posix_kill($this->getPid(), 0)) {
|
||||||
|
switch (posix_get_last_error()) {
|
||||||
|
case PCNTL_ESRCH:
|
||||||
|
# process inexistant
|
||||||
|
return true;
|
||||||
|
case PCNTL_EPERM:
|
||||||
|
# process auquel on n'a pas accès: ce doit être un autre process qui a
|
||||||
|
# réutilisé le PID
|
||||||
|
return true;
|
||||||
|
case PCNTL_EINVAL:
|
||||||
|
# ne devrait pas se produire
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# process existant
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* marquer la tâche comme terminée avec un code d'erreur si elle n'existe
|
||||||
|
* plus. doit être lancé depuis launch_task.php
|
||||||
|
*/
|
||||||
|
function ltCleanUndead(): void {
|
||||||
|
if (!$this->isStopped()) {
|
||||||
|
$this->setStopped(true);
|
||||||
|
$this->setDateStop(date::datetime());
|
||||||
|
$this->setRetcode(-777);
|
||||||
|
}
|
||||||
|
$this->setDone(true);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIdTitle(): string {
|
||||||
|
$idTitle = $this->getId();
|
||||||
|
$title = $this->getTitle();
|
||||||
|
if ($title) $idTitle .= " -- $title";
|
||||||
|
return $idTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNameOrLogin(): string {
|
||||||
|
$nameOrLogin = $this->getOwnerName();
|
||||||
|
if ($nameOrLogin === null) $nameOrLogin = $this->getOwnerLogin();
|
||||||
|
if ($nameOrLogin === null) $nameOrLogin = "(unknown)";
|
||||||
|
return $nameOrLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_LOG_SIZE = 256 * 1024;
|
||||||
|
const CACTION_NONE = "n";
|
||||||
|
const CACTION_REPLACE = "r";
|
||||||
|
const CACTION_UPDATE = "u";
|
||||||
|
|
||||||
|
function export(?int $serial=null, ?int $cs=null, ?int $ce=null): array {
|
||||||
|
$task = $this->array();
|
||||||
|
$dateStart = new Datetime($this->getDateStart());
|
||||||
|
$dateStop = new Datetime($this->getDateStop());
|
||||||
|
# $ca = action à faire par le client: replace ou update
|
||||||
|
# ls = local start, le = local end (local === server en l'occurrence)
|
||||||
|
# cs = client start, ce = client end (pour CACTION_REPLACE)
|
||||||
|
# $ps = plus start, $pe = plus end (pour CACTION_UPDATE)
|
||||||
|
# $rs = read start, $re = read end
|
||||||
|
if ($serial !== null && $cs !== null && $ce !== null && $this->isStarted()) {
|
||||||
|
lock::exlusive(self::LOCK);
|
||||||
|
$inf = false;
|
||||||
|
try {
|
||||||
|
$logfile = $this->getLogfile();
|
||||||
|
if (!file_exists($logfile)) {
|
||||||
|
# s'assurer que le fichier existe (il peut avoir été nettoyé entre temps)
|
||||||
|
f::close(f::open($logfile, "cb"));
|
||||||
|
}
|
||||||
|
$inf = f::open($logfile, "rb");
|
||||||
|
$le = f::seek($inf, 0, SEEK_END);
|
||||||
|
$ls = $le - self::MAX_LOG_SIZE;
|
||||||
|
if ($ls <= 0) {
|
||||||
|
$ls = 0;
|
||||||
|
} else {
|
||||||
|
# trouver le premier saut de ligne
|
||||||
|
$ls = f::find_nl($inf, $ls);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serial != $this->getSerial()) {
|
||||||
|
# nouvelle tâche, on recommence tout
|
||||||
|
$rs = $cs = $ls;
|
||||||
|
$re = $ce = $le;
|
||||||
|
$ca = self::CACTION_REPLACE;
|
||||||
|
} elseif ($ls <= $cs) {
|
||||||
|
# cas courant, on rajoute du contenu, mais pas plus que MAX_LOG_SIZE
|
||||||
|
$ls = $cs;
|
||||||
|
$ps = 0;
|
||||||
|
$pe = $le - $ce;
|
||||||
|
$rs = $ce;
|
||||||
|
$re = $le;
|
||||||
|
$ca = self::CACTION_UPDATE;
|
||||||
|
$cs = $ps;
|
||||||
|
$ce = $pe;
|
||||||
|
} elseif ($ls <= $ce) {
|
||||||
|
# on a dépassé MAX_LOG_SIZE, il faut recalculer
|
||||||
|
# garder une partie des logs précédents
|
||||||
|
$ps = $ls - $cs;
|
||||||
|
$pe = $le - $ce;
|
||||||
|
$rs = $ce;
|
||||||
|
$re = $le;
|
||||||
|
$ca = self::CACTION_UPDATE;
|
||||||
|
$cs = $ps;
|
||||||
|
$ce = $pe;
|
||||||
|
} else {
|
||||||
|
# ne rien garder des logs précédents
|
||||||
|
$rs = $cs = $ls;
|
||||||
|
$re = $ce = $le;
|
||||||
|
$ca = self::CACTION_REPLACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$logSize = $re - $rs;
|
||||||
|
if ($logSize > 0) {
|
||||||
|
f::seek($inf, $rs, SEEK_SET);
|
||||||
|
$log = f::read($inf, $logSize);
|
||||||
|
$lf = new BaseF(); #XXX
|
||||||
|
$lf->formatContent($log);
|
||||||
|
} elseif ($ca == self::CACTION_REPLACE) {
|
||||||
|
$log = "";
|
||||||
|
} else {
|
||||||
|
$log = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if ($inf) f::close($inf);
|
||||||
|
lock::release(self::LOCK);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$cs = $ce = false;
|
||||||
|
$ca = self::CACTION_NONE;
|
||||||
|
$log = false;
|
||||||
|
}
|
||||||
|
$page = $this->getPage();
|
||||||
|
if ($page !== null) {
|
||||||
|
$dest = A::get($page, 0);
|
||||||
|
$params = A::get($page, 1);
|
||||||
|
$pageUrl = page::bu($dest, $params);
|
||||||
|
} else {
|
||||||
|
$pageUrl = false;
|
||||||
|
}
|
||||||
|
A::merge($task, [
|
||||||
|
"id_title" => $this->getIdTitle(),
|
||||||
|
"name_or_login" => $this->getNameOrLogin(),
|
||||||
|
"page_url" => $pageUrl,
|
||||||
|
"elapsed_start" => $dateStart->getElapsed()->formatAt(),
|
||||||
|
"elapsed_stop" => $dateStop->getElapsed()->formatSince(),
|
||||||
|
"elapsed_total" => $dateStart->getElapsed($dateStop)->formatDelay(),
|
||||||
|
"launchable" => $this->isLaunchable(),
|
||||||
|
"updatable" => $this->isUpdatable(),
|
||||||
|
"startable" => $this->isStartable(),
|
||||||
|
"reapable" => $this->isReapable(),
|
||||||
|
"working" => $this->isStarted() && !$this->isDone(),
|
||||||
|
"ok" => $this->isDone() && $this->getRetcode() == 0,
|
||||||
|
"ko" => $this->isDone() && $this->getRetcode() != 0,
|
||||||
|
"log" => $log,
|
||||||
|
]);
|
||||||
|
return [$task, $ca, $cs, $ce];
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
const _AUTOGEN_CONSTS = [
|
||||||
|
"_AUTO_GETTERS" => [Autogen::class, "auto_getters", self::SCHEMA],
|
||||||
|
"_AUTO_SETTERS" => [Autogen::class, "auto_setters", self::SCHEMA],
|
||||||
|
];
|
||||||
|
const _AUTOGEN_METHODS = [
|
||||||
|
[Autogen::class, "auto_getters_methods", self::SCHEMA],
|
||||||
|
[Autogen::class, "auto_setters_methods", self::SCHEMA],
|
||||||
|
];
|
||||||
|
const _AUTO_GETTERS = /*autogen*/[
|
||||||
|
'getId' => 'id',
|
||||||
|
'getSerial' => 'serial',
|
||||||
|
'getTitle' => 'title',
|
||||||
|
'isValid' => 'valid',
|
||||||
|
'getOwnerLogin' => 'owner_login',
|
||||||
|
'getOwnerName' => 'owner_name',
|
||||||
|
'getPage' => 'page',
|
||||||
|
'getCmd' => 'cmd',
|
||||||
|
'getLogfile' => 'logfile',
|
||||||
|
'isStarted' => 'started',
|
||||||
|
'getDateStart' => 'date_start',
|
||||||
|
'getPid' => 'pid',
|
||||||
|
'isStopped' => 'stopped',
|
||||||
|
'getDateStop' => 'date_stop',
|
||||||
|
'getRetcode' => 'retcode',
|
||||||
|
'isDone' => 'done',
|
||||||
|
];
|
||||||
|
const _AUTO_SETTERS = /*autogen*/[
|
||||||
|
'setId' => 'id',
|
||||||
|
'setSerial' => 'serial',
|
||||||
|
'setTitle' => 'title',
|
||||||
|
'setValid' => 'valid',
|
||||||
|
'setOwnerLogin' => 'owner_login',
|
||||||
|
'setOwnerName' => 'owner_name',
|
||||||
|
'setPage' => 'page',
|
||||||
|
'setCmd' => 'cmd',
|
||||||
|
'setLogfile' => 'logfile',
|
||||||
|
'setStarted' => 'started',
|
||||||
|
'setDateStart' => 'date_start',
|
||||||
|
'setPid' => 'pid',
|
||||||
|
'setStopped' => 'stopped',
|
||||||
|
'setDateStop' => 'date_stop',
|
||||||
|
'setRetcode' => 'retcode',
|
||||||
|
'setDone' => 'done',
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\proc;
|
||||||
|
|
||||||
|
class tasks {
|
||||||
|
static function pf(string $name): string {
|
||||||
|
$envname = envs::get();
|
||||||
|
return "/tasks/$envname/$name";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** le verrou doit être posé avant l'appel de cette méthode */
|
||||||
|
private static function _list(bool $include_invalids, bool $sort): array {
|
||||||
|
$tmpfiles = glob(self::pf("*.task"));
|
||||||
|
if ($tmpfiles === false) return [];
|
||||||
|
$tasks = [];
|
||||||
|
foreach ($tmpfiles as $taskfile) {
|
||||||
|
$task = new ManagedTask($taskfile);
|
||||||
|
if (!$include_invalids && !$task->isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$tasks[] = [$taskfile, $task];
|
||||||
|
}
|
||||||
|
if ($sort) {
|
||||||
|
clearstatcache();
|
||||||
|
usort($tasks, function ($fta, $ftb) {
|
||||||
|
/**
|
||||||
|
* @var ManagedTask $ta
|
||||||
|
* @var ManagedTask $tb
|
||||||
|
*/
|
||||||
|
[$fa, $ta] = $fta;
|
||||||
|
[$fb, $tb] = $ftb;
|
||||||
|
# comparer l'état "running"
|
||||||
|
$wa = $ta->isStarted() && !$ta->isDone();
|
||||||
|
$wb = $tb->isStarted() && !$tb->isDone();
|
||||||
|
$c = -base::compare($wa, $wb);
|
||||||
|
if ($c != 0) return $c;
|
||||||
|
# comparer la date de dernière modification du fichier
|
||||||
|
$mta = filemtime($fa);
|
||||||
|
$mtb = filemtime($fb);
|
||||||
|
return -base::compare($mta, $mtb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** supprimer toutes les tâches */
|
||||||
|
static function delete_all(): void {
|
||||||
|
lock::exlusive(ManagedTask::LOCK);
|
||||||
|
try {
|
||||||
|
foreach (self::_list(true, false) as [$taskfile, $task]) {
|
||||||
|
unlink($taskfile);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock::release(ManagedTask::LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner la liste des tâches valides
|
||||||
|
* @return ManagedTask[]
|
||||||
|
*/
|
||||||
|
static function list(?string $selectId=null): array {
|
||||||
|
$tasks = [];
|
||||||
|
lock::exlusive(ManagedTask::LOCK);
|
||||||
|
try {
|
||||||
|
foreach (self::_list(false, true) as [$taskfile, $task]) {
|
||||||
|
$id = $task->getId();
|
||||||
|
if ($selectId !== null && $id !== $selectId) continue;
|
||||||
|
$tasks[$id] = $task;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock::release(ManagedTask::LOCK);
|
||||||
|
}
|
||||||
|
return $tasks;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php\coll;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AutoArray: un tableau dont les éléments peuvent être accédés comme des
|
||||||
|
* propriétés
|
||||||
|
*/
|
||||||
|
class AutoArray extends BaseArray {
|
||||||
|
const _AUTO_PROPERTIES = null;
|
||||||
|
static function _AUTO_PROPERTIES(): ?array { return static::_AUTO_PROPERTIES; }
|
||||||
|
|
||||||
|
function __isset($name) {
|
||||||
|
if ($this->has($name)) return true;
|
||||||
|
$properties = self::_AUTO_PROPERTIES();
|
||||||
|
if ($properties === null) return false;
|
||||||
|
return array_key_exists($name, $properties);
|
||||||
|
}
|
||||||
|
function __get($name) {
|
||||||
|
$properties = self::_AUTO_PROPERTIES();
|
||||||
|
if ($this->has($name)) return $this->get($name);
|
||||||
|
$pkey = cl::get($properties, $name, $name);
|
||||||
|
return cl::pget($this->data, $pkey);
|
||||||
|
}
|
||||||
|
function __set($name, $value) {
|
||||||
|
$properties = self::_AUTO_PROPERTIES();
|
||||||
|
if ($this->has($name)) {
|
||||||
|
$this->set($name, $value);
|
||||||
|
} else {
|
||||||
|
$pkey = cl::get($properties, $name, $name);
|
||||||
|
cl::pset($this->data, $pkey, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __unset($name) {
|
||||||
|
$properties = self::_AUTO_PROPERTIES();
|
||||||
|
if ($this->has($name)) {
|
||||||
|
$this->del($name);
|
||||||
|
} else {
|
||||||
|
$pkey = cl::get($properties, $name, $name);
|
||||||
|
cl::pdel($this->data, $pkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php\coll;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Countable;
|
||||||
|
use Iterator;
|
||||||
|
use nulib\cl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BaseArray: implémentation de base d'un objet array-like, qui peut aussi
|
||||||
|
* servir comme front-end pour un array
|
||||||
|
*/
|
||||||
|
class BaseArray implements ArrayAccess, Countable, Iterator {
|
||||||
|
function __construct(?array &$data=null) {
|
||||||
|
$this->reset($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
function __toString(): string { return var_export($this->data, true); }
|
||||||
|
#function __debugInfo() { return $this->data; }
|
||||||
|
function reset(?array &$data): void { $this->data =& $data; }
|
||||||
|
function &array(): ?array { return $this->data; }
|
||||||
|
function count(): int { return $this->data !== null? count($this->data): 0; }
|
||||||
|
function keys(): array { return $this->data !== null? array_keys($this->data): []; }
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# base
|
||||||
|
|
||||||
|
function has($key): bool {
|
||||||
|
return $this->data !== null && array_key_exists($key, $this->data);
|
||||||
|
}
|
||||||
|
function &get($key, $default=null) {
|
||||||
|
if ($this->data !== null && array_key_exists($key, $this->data)) {
|
||||||
|
return $this->data[$key];
|
||||||
|
} else return $default;
|
||||||
|
}
|
||||||
|
function set($key, $value): void {
|
||||||
|
if ($key === null) $this->data[] = $value;
|
||||||
|
else $this->data[$key] = $value;
|
||||||
|
}
|
||||||
|
function del($key): void {
|
||||||
|
unset($this->data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function offsetExists($offset): bool { return $this->has($offset); }
|
||||||
|
function &offsetGet($offset) { return $this->get($offset); }
|
||||||
|
function offsetSet($offset, $value) { $this->set($offset, $value); }
|
||||||
|
function offsetUnset($offset) { $this->del($offset); }
|
||||||
|
|
||||||
|
function __isset($name) { return $this->has($name); }
|
||||||
|
function &__get($name) { return $this->get($name); }
|
||||||
|
function __set($name, $value) { $this->set($name, $value); }
|
||||||
|
function __unset($name) { $this->del($name); }
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# iterator
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $valid = false;
|
||||||
|
|
||||||
|
function rewind() {
|
||||||
|
if ($this->data !== null) {
|
||||||
|
$first = reset($this->data);
|
||||||
|
$this->valid = $first !== false || key($this->data) !== null;
|
||||||
|
} else {
|
||||||
|
$this->valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function valid(): bool { return $this->valid; }
|
||||||
|
function key() { return key($this->data); }
|
||||||
|
function current() { return current($this->data); }
|
||||||
|
function next() {
|
||||||
|
$next = next($this->data);
|
||||||
|
$this->valid = $next !== false || key($this->data) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# divers
|
||||||
|
|
||||||
|
function phas($pkey): bool { return cl::phas($this->data, $pkey); }
|
||||||
|
function pget($pkey, $default=null): bool { return cl::pget($this->data, $pkey, $default); }
|
||||||
|
function pset($pkey, $value): void { cl::pset($this->data, $pkey, $value); }
|
||||||
|
function pdel($pkey): void { cl::pdel($this->data, $pkey); }
|
||||||
|
|
||||||
|
function contains($value, bool $strict=false): bool {
|
||||||
|
if ($value === null || $this->data === null) return false;
|
||||||
|
return in_array($value, $this->data, $strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add($value, bool $unique=true, bool $strict=false): bool {
|
||||||
|
if ($unique && $this->contains($value, $strict)) return false;
|
||||||
|
$this->set(null, $value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAll(?array $values, bool $unique=true, bool $strict=false): void {
|
||||||
|
if ($values === null) return;
|
||||||
|
$index = 0;
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$this->add($value, $unique, $strict);
|
||||||
|
$index++;
|
||||||
|
} else {
|
||||||
|
$this->set($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAll(?array $values): void {
|
||||||
|
$this->data = null;
|
||||||
|
$this->addAll($values);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ namespace nur\sery\php\iter;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Iterator;
|
use Iterator;
|
||||||
use nur\sery\os\EOFException;
|
use nulib\DataException;
|
||||||
use nur\sery\php\ICloseable;
|
use nur\sery\php\ICloseable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,12 +29,12 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
||||||
protected function beforeIter() {}
|
protected function beforeIter() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner le prochain élément. lancer l'exception {@link EOFException} pour
|
* retourner le prochain élément. lancer l'exception {@link DataException} pour
|
||||||
* indiquer que plus aucun élément n'est disponible
|
* indiquer que plus aucun élément n'est disponible
|
||||||
*
|
*
|
||||||
* le cas échéant, initialiser $key
|
* le cas échéant, initialiser $key
|
||||||
*
|
*
|
||||||
* @throws EOFException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
abstract protected function _next(&$key);
|
abstract protected function _next(&$key);
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
||||||
$this->valid = false;
|
$this->valid = false;
|
||||||
try {
|
try {
|
||||||
$item = $this->_next($key);
|
$item = $this->_next($key);
|
||||||
} catch (EOFException $e) {
|
} catch (DataException $e) {
|
||||||
$this->beforeClose();
|
$this->beforeClose();
|
||||||
try {
|
try {
|
||||||
$this->_teardown();
|
$this->_teardown();
|
||||||
|
|
|
@ -136,7 +136,7 @@ nature liste si:
|
||||||
|
|
||||||
Un tableau associatif est modélisée de cette manière:
|
Un tableau associatif est modélisée de cette manière:
|
||||||
~~~php
|
~~~php
|
||||||
const LIST_SCHEMA = [
|
const ASSOC_SCHEMA = [
|
||||||
KEY => VALUE_SCHEMA,
|
KEY => VALUE_SCHEMA,
|
||||||
...
|
...
|
||||||
"" => ["assoc"],
|
"" => ["assoc"],
|
||||||
|
|
Loading…
Reference in New Issue