From 6a4e38e72fc6255619ad1f3d6c395ce72f58ed2b Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 11 Jun 2024 12:06:54 +0400 Subject: [PATCH] modifs.mineures sans commentaires --- nur_src/cli/Application.php | 63 ++++++++ src/{file => }/app/LockFile.php | 6 +- src/{file => }/app/RunFile.php | 27 +++- src/app/app.php | 267 ++++++++++++++++++++++++++++++++ src/file/csv/CsvBuilder.php | 1 - 5 files changed, 360 insertions(+), 4 deletions(-) rename src/{file => }/app/LockFile.php (94%) rename src/{file => }/app/RunFile.php (83%) create mode 100644 src/app/app.php diff --git a/nur_src/cli/Application.php b/nur_src/cli/Application.php index 91962f4..2ad278d 100644 --- a/nur_src/cli/Application.php +++ b/nur_src/cli/Application.php @@ -9,6 +9,7 @@ use nur\config\ArrayConfig; use nur\msg; use nur\os; use nur\path; +use nur\sery\app\app; use nur\sery\output\log as nlog; use nur\sery\output\msg as nmsg; use nur\sery\output\say as nsay; @@ -18,7 +19,44 @@ use nur\sery\output\std\StdMessenger as nStdMessenger; * Class Application: application de base */ abstract class Application { + /** @var string répertoire du projet (celui qui contient composer.json */ + const PROJDIR = null; + + /** + * @var string code du projet, utilisé pour dériver le noms de certains des + * paramètres extraits de l'environnement, e.g XXX_DATADIR si le projet a pour + * code xxx + */ + const APPCODE = null; + + /** + * @var string code de l'application, utilisé pour inférer le nom de certains + * fichiers spécifiques à l'application + */ + const NAME = null; + + /** @var string description courte de l'application */ + const TITLE = null; + + const DATADIR = null; + const ETCDIR = null; + const VARDIR = null; + const LOGDIR = null; + + /** @var bool faut-il maintenir un fichier de suivi du process? */ + const USE_RUNFILE = false; + + /** + * @var bool faut-il empêcher deux instances de cette application de se lancer + * en même temps? + * + * nécessite USE_RUNFILE==true + */ + const USE_RUNLOCK = false; + protected static function _app_init(): void { + app::init(static::class); + config::set_fact(config::FACT_CLI_APP); nmsg::set_messenger_class(nStdMessenger::class); @@ -68,8 +106,33 @@ abstract class Application { } static function run(?Application $app=null): void { + $unlock = false; + $stop = false; + register_shutdown_function(function () use (&$unlock, &$stop) { + $self = app::get(); + if ($unlock) $self->getLockfile()->release(); + if ($stop) $self->getRunfile()->stop(); + }); try { static::_app_init(); + if (static::USE_RUNFILE) { + $self = app::get(); + global $argc, $argv; + if ($argc === 2 && $argv[1] === "--Application-Runlock-release") { + $self->getLockfile()->release(); + exit(0); + } + $useRunlock = static::USE_RUNLOCK; + if ($useRunlock && $self->getLockfile()->warnIfLocked()) { + exit(1); + } + $self->getRunfile()->start(); + $stop = true; + if ($useRunlock) { + $self->getLockfile()->lock(); + $unlock = true; + } + } if ($app === null) $app = new static(); static::_app_configure($app); static::_app_main($app); diff --git a/src/file/app/LockFile.php b/src/app/LockFile.php similarity index 94% rename from src/file/app/LockFile.php rename to src/app/LockFile.php index a5f0ecd..9f64228 100644 --- a/src/file/app/LockFile.php +++ b/src/app/LockFile.php @@ -1,5 +1,5 @@ read(); if ($data["locked"]) { msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]"); + return true; } + return false; } function lock(?array &$data=null): bool { diff --git a/src/file/app/RunFile.php b/src/app/RunFile.php similarity index 83% rename from src/file/app/RunFile.php rename to src/app/RunFile.php index f9f0573..531a7ec 100644 --- a/src/file/app/RunFile.php +++ b/src/app/RunFile.php @@ -1,5 +1,5 @@ read(); + if ($data["date_start"] === null) return false; + if ($data["date_stop"] !== null) return false; + if (!posix_kill($data["pid"], 0)) { + switch (posix_get_last_error()) { + case PCNTL_ESRCH: + # process inexistant + return false; + case PCNTL_EPERM: + # process auquel on n'a pas accès?! est-ce un autre process qui a + # réutilisé le PID? + return false; + case PCNTL_EINVAL: + # ne devrait pas se produire + return false; + } + } + # process existant auquel on a accès + return true; + } + /** tester si l'application est arrêtée */ function isStopped(): bool { $data = $this->read(); diff --git a/src/app/app.php b/src/app/app.php new file mode 100644 index 0000000..336c439 --- /dev/null +++ b/src/app/app.php @@ -0,0 +1,267 @@ + $projdir, + "appcode" => $appcode, + "apptype" => "cli", + "name" => $name, + "title" => $title, + "datadir" => $datadir, + "etcdir" => $etcdir, + "vardir" => $vardir, + "logdir" => $logdir, + ]); + } + + protected static ?app $app = null; + + static function init($app): void { + self::$app = static::with($app); + } + + static function get(): self { + return self::$app ??= new self(null); + } + + function __construct(?array $params) { + $this->projdir = $projdir = path::abspath($params["projdir"] ?? "."); + $this->appcode = $appcode = $params["appcode"] ?? "app"; + $this->apptype = $apptype = $params["apptype"] ?? "cli"; + $this->name = $params["name"] ?? $appcode; + $this->title = $params["title"] ?? null; + $appcode = strtoupper($appcode); + # profile + $profile = getenv("${appcode}_PROFILE"); + if ($profile === false) $profile = getenv("APP_PROFILE"); + if ($profile === false) $profile = $params["profile"] ?? null; + if ($profile === null) { + if (file_exists("$projdir/.default-profile-devel")) $profile = "devel"; + else $profile = "prod"; + } + $this->profile = $profile; + # cwd + $this->cwd = getcwd(); + # datadir + $datadir = getenv("${appcode}_DATADIR"); + if ($datadir === false) $datadir = $params["datadir"] ?? null; + if ($datadir === null) $datadir = "devel/$apptype"; + $this->datadir = $datadir = path::reljoin($projdir, $datadir); + # etcdir + $etcdir = getenv("${appcode}_ETCDIR"); + if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; + if ($etcdir === null) $etcdir = "etc"; + $this->etcdir = $etcdir = path::reljoin($datadir, $etcdir); + # vardir + $vardir = getenv("${appcode}_VARDIR"); + if ($vardir === false) $vardir = $params["vardir"] ?? null; + if ($vardir === null) $vardir = "var"; + $this->vardir = $vardir = path::reljoin($datadir, $vardir); + # logdir + $logdir = getenv("${appcode}_LOGDIR"); + if ($logdir === false) $logdir = $params["logdir"] ?? null; + if ($logdir === null) $logdir = "log"; + $this->logdir = $logdir = path::reljoin($datadir, $logdir); + } + + protected string $projdir; + + function getProjdir(): string { + return $this->projdir; + } + + protected string $appcode; + + function getAppcode(): string { + return $this->appcode; + } + + protected string $apptype; + + function getApptype(): string { + return $this->apptype; + } + + protected string $name; + + function getName(): ?string { + return $this->name; + } + + protected ?string $title; + + function getTitle(): ?string { + return $this->title; + } + + protected string $profile; + + function getProfile(): string { + return $this->profile; + } + + /** + * @param ?string|false $profile + */ + function withProfile(string $file, $profile): string { + if ($profile !== false) { + if ($profile === null) $profile = $this->getProfile(); + [$dir, $filename] = path::split($file); + $basename = path::basename($filename); + $ext = path::ext($file); + $file = path::join($dir, "$basename.$profile$ext"); + } + return $file; + } + + function findFile(array $dirs, array $names, $profile=null): string { + # d'abord chercher avec le profil + if ($profile !== false) { + foreach ($dirs as $dir) { + foreach ($names as $name) { + $file = path::join($dir, $name); + $file = $this->withProfile($file, $profile); + if (file_exists($file)) return $file; + } + } + } + # puis sans profil + foreach ($dirs as $dir) { + foreach ($names as $name) { + $file = path::join($dir, $name); + if (file_exists($file)) return $file; + } + } + # la valeur par défaut est avec profil + return $this->withProfile(path::join($dirs[0], $names[0]), $profile); + } + + function fencedJoin(string $basedir, string $path): string { + $path = path::reljoin($basedir, $path); + if (!path::is_within($path, $basedir)) { + throw ValueException::invalid_value($path, "path"); + } + return $path; + } + + protected string $cwd; + + function getCwd(): string { + return $this->cwd; + } + + protected string $datadir; + + function getDatadir(): string { + return $this->datadir; + } + + protected string $etcdir; + + function getEtcdir(): string { + return $this->etcdir; + } + + function getEtcfile(string $name, $profile=null): string { + return $this->findFile([$this->etcdir], [$name], $profile); + } + + protected string $vardir; + + function getVardir(): string { + return $this->vardir; + } + + function getVarfile(string $name, $profile=null): string { + return $this->withProfile($this->fencedJoin($this->vardir, $name), $profile); + } + + protected string $logdir; + + function getLogdir(): string { + return $this->logdir; + } + + function getLogfile(string $name, $profile=null): string { + return $this->withProfile($this->fencedJoin($this->logdir, $name), $profile); + } + + /** + * obtenir le chemin absolu vers un fichier de travail + * - si le chemin est absolu, il est inchangé + * - si le chemin est qualifié (commence par ./ ou ../) ou sans chemin, il est + * exprimé par rapport à $vardir + * - sinon le chemin est exprimé par rapport au répertoire de travail de base + * $datadir + * + * is $ensure_dir, créer le répertoire du fichier s'il n'existe pas déjà + */ + function getWorkfile(?string $file, $profile=null, bool $ensureDir=true): ?string { + if ($file === null) return null; + if (path::is_qualified($file) || !path::have_dir($file)) { + $file = path::reljoin($this->vardir, $file); + } else { + $file = path::reljoin($this->datadir, $file); + } + $file = $this->withProfile($file, $profile); + if ($ensureDir) os::mkdirof($file); + return $file; + } + + /** + * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. + * - si le chemin commence par /, il est laissé en l'état + * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd + * - sinon le chemin est exprimé par rapport au répertoire de travail $vardir + */ + function getUserfile(?string $file): ?string { + if ($file === null) return null; + if (path::is_qualified($file)) { + return path::reljoin($this->cwd, $file); + } else { + return path::reljoin($this->vardir, $file); + } + } + + protected ?RunFile $runfile = null; + + function getRunfile(): RunFile { + $name = $this->name; + return $this->runfile ??= new RunFile($this->getWorkfile($name), $name); + } + + protected ?LockFile $lockFile = null; + + function getLockfile(): LockFile { + return $this->lockFile ??= $this->getRunfile()->getLockFile(null, $this->title); + } +} diff --git a/src/file/csv/CsvBuilder.php b/src/file/csv/CsvBuilder.php index 07f0df6..37c23f6 100644 --- a/src/file/csv/CsvBuilder.php +++ b/src/file/csv/CsvBuilder.php @@ -1,7 +1,6 @@