modifs.mineures sans commentaires
This commit is contained in:
parent
bc527bfe7a
commit
6a4e38e72f
|
@ -9,6 +9,7 @@ use nur\config\ArrayConfig;
|
||||||
use nur\msg;
|
use nur\msg;
|
||||||
use nur\os;
|
use nur\os;
|
||||||
use nur\path;
|
use nur\path;
|
||||||
|
use nur\sery\app\app;
|
||||||
use nur\sery\output\log as nlog;
|
use nur\sery\output\log as nlog;
|
||||||
use nur\sery\output\msg as nmsg;
|
use nur\sery\output\msg as nmsg;
|
||||||
use nur\sery\output\say as nsay;
|
use nur\sery\output\say as nsay;
|
||||||
|
@ -18,7 +19,44 @@ use nur\sery\output\std\StdMessenger as nStdMessenger;
|
||||||
* Class Application: application de base
|
* Class Application: application de base
|
||||||
*/
|
*/
|
||||||
abstract class Application {
|
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 {
|
protected static function _app_init(): void {
|
||||||
|
app::init(static::class);
|
||||||
|
|
||||||
config::set_fact(config::FACT_CLI_APP);
|
config::set_fact(config::FACT_CLI_APP);
|
||||||
|
|
||||||
nmsg::set_messenger_class(nStdMessenger::class);
|
nmsg::set_messenger_class(nStdMessenger::class);
|
||||||
|
@ -68,8 +106,33 @@ abstract class Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
static function run(?Application $app=null): void {
|
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 {
|
try {
|
||||||
static::_app_init();
|
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();
|
if ($app === null) $app = new static();
|
||||||
static::_app_configure($app);
|
static::_app_configure($app);
|
||||||
static::_app_main($app);
|
static::_app_main($app);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\file\app;
|
namespace nur\sery\app;
|
||||||
|
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\file\SharedFile;
|
use nur\sery\file\SharedFile;
|
||||||
|
@ -51,11 +51,13 @@ class LockFile {
|
||||||
return $data["locked"];
|
return $data["locked"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function warnIfLocked(?array $data=null): void {
|
function warnIfLocked(?array $data=null): bool {
|
||||||
if ($data === null) $data = $this->read();
|
if ($data === null) $data = $this->read();
|
||||||
if ($data["locked"]) {
|
if ($data["locked"]) {
|
||||||
msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]");
|
msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function lock(?array &$data=null): bool {
|
function lock(?array &$data=null): bool {
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\file\app;
|
namespace nur\sery\app;
|
||||||
|
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\file\SharedFile;
|
use nur\sery\file\SharedFile;
|
||||||
|
@ -65,6 +65,31 @@ class RunFile {
|
||||||
return $data["date_start"] !== null;
|
return $data["date_start"] !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si la tâche tourne et est accessible
|
||||||
|
*/
|
||||||
|
function isRunning(?array &$data=null): bool {
|
||||||
|
$data = $this->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 */
|
/** tester si l'application est arrêtée */
|
||||||
function isStopped(): bool {
|
function isStopped(): bool {
|
||||||
$data = $this->read();
|
$data = $this->read();
|
|
@ -0,0 +1,267 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\app;
|
||||||
|
|
||||||
|
use nur\cli\Application;
|
||||||
|
use nur\os;
|
||||||
|
use nur\sery\os\path;
|
||||||
|
use nur\sery\ValueException;
|
||||||
|
|
||||||
|
class app {
|
||||||
|
/** @param Application|string */
|
||||||
|
static function with($app): self {
|
||||||
|
if ($app instanceof Application) {
|
||||||
|
$projdir = $app::PROJDIR;
|
||||||
|
$appcode = $app::APPCODE;
|
||||||
|
$name = $app::NAME;
|
||||||
|
$title = $app::TITLE;
|
||||||
|
$datadir = $app::DATADIR;
|
||||||
|
$etcdir = $app::ETCDIR;
|
||||||
|
$vardir = $app::VARDIR;
|
||||||
|
$logdir = $app::LOGDIR;
|
||||||
|
} elseif (is_string($app) && is_subclass_of($app, Application::class)) {
|
||||||
|
$projdir = constant("$app::PROJDIR");
|
||||||
|
$appcode = constant("$app::APPCODE");
|
||||||
|
$name = constant("$app::NAME");
|
||||||
|
$title = constant("$app::TITLE");
|
||||||
|
$datadir = constant("$app::DATADIR");
|
||||||
|
$etcdir = constant("$app::ETCDIR");
|
||||||
|
$vardir = constant("$app::VARDIR");
|
||||||
|
$logdir = constant("$app::LOGDIR");
|
||||||
|
} else {
|
||||||
|
throw ValueException::invalid_type($app, Application::class);
|
||||||
|
}
|
||||||
|
return new static([
|
||||||
|
"projdir" => $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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\file\csv;
|
namespace nur\sery\file\csv;
|
||||||
|
|
||||||
use nur\sery\ValueException;
|
|
||||||
use nur\sery\web\http;
|
use nur\sery\web\http;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue