wip os/proc et php/coll
This commit is contained in:
		
							parent
							
								
									b39ff95639
								
							
						
					
					
						commit
						38e90f752f
					
				
							
								
								
									
										576
									
								
								src/os/proc/ManagedTask.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										576
									
								
								src/os/proc/ManagedTask.php
									
									
									
									
									
										Normal file
									
								
							@ -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',
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								src/os/proc/tasks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/os/proc/tasks.php
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/php/coll/AutoArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/php/coll/AutoArray.php
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								src/php/coll/BaseArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/php/coll/BaseArray.php
									
									
									
									
									
										Normal file
									
								
							@ -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 Iterator;
 | 
			
		||||
use nur\sery\os\EOFException;
 | 
			
		||||
use nulib\DataException;
 | 
			
		||||
use nur\sery\php\ICloseable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -29,12 +29,12 @@ abstract class AbstractIterator implements Iterator, ICloseable {
 | 
			
		||||
  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
 | 
			
		||||
   *
 | 
			
		||||
   * le cas échéant, initialiser $key
 | 
			
		||||
   *
 | 
			
		||||
   * @throws EOFException
 | 
			
		||||
   * @throws DataException
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function _next(&$key);
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,7 @@ abstract class AbstractIterator implements Iterator, ICloseable {
 | 
			
		||||
    $this->valid = false;
 | 
			
		||||
    try {
 | 
			
		||||
      $item = $this->_next($key);
 | 
			
		||||
    } catch (EOFException $e) {
 | 
			
		||||
    } catch (DataException $e) {
 | 
			
		||||
      $this->beforeClose();
 | 
			
		||||
      try {
 | 
			
		||||
        $this->_teardown();
 | 
			
		||||
 | 
			
		||||
@ -136,7 +136,7 @@ nature liste si:
 | 
			
		||||
 | 
			
		||||
Un tableau associatif est modélisée de cette manière:
 | 
			
		||||
~~~php
 | 
			
		||||
const LIST_SCHEMA = [
 | 
			
		||||
const ASSOC_SCHEMA = [
 | 
			
		||||
  KEY => VALUE_SCHEMA,
 | 
			
		||||
  ...
 | 
			
		||||
  "" => ["assoc"],
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user