From 096bfe91f83af8652c2556c369624628f850ba97 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 17 May 2024 09:51:31 +0400 Subject: [PATCH] ajouter RunFile et LockFile --- src/file/IReader.php | 6 +- src/file/IWriter.php | 6 +- src/file/app/LockFile.php | 87 +++++++++++++++++++++++++ src/file/app/RunFile.php | 129 ++++++++++++++++++++++++++++++++++++++ src/file/base/Stream.php | 43 +++++++++---- src/file/base/_File.php | 8 +++ 6 files changed, 260 insertions(+), 19 deletions(-) create mode 100644 src/file/app/LockFile.php create mode 100644 src/file/app/RunFile.php diff --git a/src/file/IReader.php b/src/file/IReader.php index 5d38fc5..3936d3f 100644 --- a/src/file/IReader.php +++ b/src/file/IReader.php @@ -37,7 +37,7 @@ interface IReader extends _IFile { * verrouiller en mode partagé puis retourner un objet permettant de lire le * fichier. */ - function getReader(bool $lockedByCanRead=false): IReader; + function getReader(bool $alreadyLocked=false): IReader; /** * lire tout le contenu du fichier en une seule fois, puis, si $close==true, @@ -45,8 +45,8 @@ interface IReader extends _IFile { * * @throws IOException si une erreur se produit */ - function getContents(bool $close=true, bool $lockedByCanRead=false): string; + function getContents(bool $close=true, bool $alreadyLocked=false): string; /** désérialiser le contenu du fichier, puis, si $close===true, le fermer */ - function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false); + function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false); } diff --git a/src/file/IWriter.php b/src/file/IWriter.php index 7cf2d13..afc7f5c 100644 --- a/src/file/IWriter.php +++ b/src/file/IWriter.php @@ -30,11 +30,11 @@ interface IWriter extends _IFile { * verrouiller en mode exclusif puis retourner un objet permettant d'écrire * dans le fichier */ - function getWriter(bool $lockedByCanWrite=false): IWriter; + function getWriter(bool $alreadyLocked=false): IWriter; /** écrire le contenu spécifié dans le fichier */ - function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void; + function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void; /** sérialiser l'objet dans la destination */ - function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void; + function serialize($object, bool $close=true, bool $alreadyLocked=false): void; } diff --git a/src/file/app/LockFile.php b/src/file/app/LockFile.php new file mode 100644 index 0000000..67b4cab --- /dev/null +++ b/src/file/app/LockFile.php @@ -0,0 +1,87 @@ +file = new SharedFile($file); + $this->name = $name ?? static::NAME; + $this->title = $title ?? static::TITLE; + } + + /** @var SharedFile */ + protected $file; + + /** @var ?string */ + protected $name; + + /** @var ?string */ + protected $title; + + protected function initData(): array { + return [ + "name" => $this->name, + "title" => $this->title, + "locked" => false, + "date_lock" => null, + "date_release" => null, + ]; + } + + function read(bool $close=true): array { + $data = $this->file->unserialize(null, $close); + if (!is_array($data)) $data = $this->initData(); + return $data; + } + + function isLocked(?array &$data=null): bool { + $data = $this->read(); + return $data["locked"]; + } + + function warnIfLocked(?array $data=null): void { + if ($data === null) $data = $this->read(); + if ($data["locked"]) { + msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]"); + } + } + + function lock(?array &$data=null): bool { + $file = $this->file; + $data = $this->read(false); + if ($data["locked"]) { + $file->close(); + return false; + } else { + $file->ftruncate(); + $file->serialize(cl::merge($data, [ + "locked" => true, + "date_lock" => new DateTime(), + "date_release" => null, + ])); + return true; + } + } + + function release(?array &$data=null): void { + $file = $this->file; + $data = $this->read(false); + $file->ftruncate(); + $file->serialize(cl::merge($data, [ + "locked" => false, + "date_release" => new DateTime(), + ])); + } +} diff --git a/src/file/app/RunFile.php b/src/file/app/RunFile.php new file mode 100644 index 0000000..34c90b1 --- /dev/null +++ b/src/file/app/RunFile.php @@ -0,0 +1,129 @@ +file = new SharedFile($file); + $this->name = $name ?? static::NAME; + } + + /** @var SharedFile */ + protected $file; + + /** @var ?string */ + protected $name; + + protected static function merge(array $data, array $merge): array { + return cl::merge($data, [ + "serial" => $data["serial"] + 1, + ], $merge); + } + + protected function initData(bool $withDateStart=true): array { + $dateStart = $withDateStart? new DateTime(): null; + return [ + "name" => $this->name, + "serial" => 0, + "date_start" => $dateStart, + "date_stop" => null, + "action" => null, + "action_date_start" => null, + "action_max_step" => null, + "action_current_step" => null, + "action_date_step" => null, + ]; + } + + function read(): array { + $data = $this->file->unserialize(); + if (!is_array($data)) $data = $this->initData(false); + return $data; + } + + /** tester si l'application est démarrée */ + function isStarted(): bool { + $data = $this->read(); + return $data["date_start"] !== null; + } + + /** tester si l'application est arrêtée */ + function isStopped(): bool { + $data = $this->read(); + return $data["date_stop"] !== null; + } + + function haveWorked(int $serial, ?int &$currentSerial=null): bool { + $data = $this->read(); + return $serial !== $data["serial"]; + } + + protected function willWrite(): array { + $file = $this->file; + $file->lockWrite(); + $data = $file->unserialize(null, false, true); + if (!is_array($data)) { + $data = $this->initData(); + $file->ftruncate(); + $file->serialize($data, false, true); + } + $file->ftruncate(); + return [$file, $data]; + } + + /** indiquer que l'application démarre */ + function start(): void { + $this->file->serialize($this->initData()); + } + + /** indiquer le début d'une action */ + function action(?string $title, ?int $maxSteps=null): void { + [$file, $data] = $this->willWrite(); + $file->serialize(self::merge($data, [ + "action" => $title, + "action_date_start" => new DateTime(), + "action_max_step" => $maxSteps, + "action_current_step" => 0, + ])); + } + + /** indiquer qu'une étape est franchie dans l'action en cours */ + function step(int $nbSteps=1): void { + [$file, $data] = $this->willWrite(); + $file->serialize(self::merge($data, [ + "action_date_step" => new DateTime(), + "action_current_step" => $data["action_current_step"] + $nbSteps, + ])); + } + + /** indiquer que l'application s'arrête */ + function stop(): void { + [$file, $data] = $this->willWrite(); + $file->serialize(self::merge($data, [ + "date_stop" => new DateTime(), + ])); + } + + function getLockFile(?string $name=null, ?string $title=null): LockFile { + $ext = self::LOCK_EXT; + if ($name !== null) $ext = ".$name$ext"; + $file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT); + $name = str::join("/", [$this->name, $name]); + return new LockFile($file, $name, $title); + } +} diff --git a/src/file/base/Stream.php b/src/file/base/Stream.php index 51fbb0d..5963a9d 100644 --- a/src/file/base/Stream.php +++ b/src/file/base/Stream.php @@ -234,6 +234,14 @@ class Stream extends AbstractIterator implements IReader, IWriter { return iterator_to_array($this); } + /** + * verrouiller le fichier en lecture de façon inconditionelle (ignorer la + * valeur de $useLocking). bloquer jusqu'à ce que le verrou soit disponible + */ + function lockRead(): void { + $this->lock(LOCK_SH); + } + /** * essayer de verrouiller le fichier en lecture. retourner true si l'opération * réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument @@ -248,8 +256,8 @@ class Stream extends AbstractIterator implements IReader, IWriter { * verrouiller en mode partagé puis retourner un objet permettant de lire le * fichier. */ - function getReader(bool $lockedByCanRead=false): IReader { - if ($this->useLocking && !$lockedByCanRead) $this->lock(LOCK_SH); + function getReader(bool $alreadyLocked=false): IReader { + if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_SH); return new class($this->fd, ++$this->serial, $this) extends Stream { function __construct($fd, int $serial, Stream $parent) { $this->parent = $parent; @@ -270,9 +278,9 @@ class Stream extends AbstractIterator implements IReader, IWriter { } /** retourner le contenu du fichier sous forme de chaine */ - function getContents(bool $close=true, bool $lockedByCanRead=false): string { + function getContents(bool $close=true, bool $alreadyLocked=false): string { $useLocking = $this->useLocking; - if ($useLocking && !$lockedByCanRead) $this->lock(LOCK_SH); + if ($useLocking && !$alreadyLocked) $this->lock(LOCK_SH); try { return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError); } finally { @@ -281,8 +289,8 @@ class Stream extends AbstractIterator implements IReader, IWriter { } } - function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) { - $args = [$this->getContents($lockedByCanRead)]; + function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false) { + $args = [$this->getContents($alreadyLocked)]; if ($options !== null) $args[] = $options; return unserialize(...$args); } @@ -331,9 +339,10 @@ class Stream extends AbstractIterator implements IReader, IWriter { } /** @throws IOException */ - function ftruncate(int $size): self { + function ftruncate(int $size=0, bool $rewind=true): self { $fd = $this->getResource(); IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError); + if ($rewind) rewind($fd); return $this; } @@ -347,6 +356,14 @@ class Stream extends AbstractIterator implements IReader, IWriter { return $this; } + /** + * verrouiller le fichier en écriture de façon inconditionelle (ignorer la + * valeur de $useLocking). bloquer jusqu'à ce que le verrou soit disponible + */ + function lockWrite(): void { + $this->lock(LOCK_EX); + } + /** * essayer de verrouiller le fichier en écriture. retourner true si l'opération * réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument @@ -361,8 +378,8 @@ class Stream extends AbstractIterator implements IReader, IWriter { * verrouiller en mode exclusif puis retourner un objet permettant d'écrire * dans le fichier */ - function getWriter(bool $lockedByCanWrite=false): IWriter { - if ($this->useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX); + function getWriter(bool $alreadyLocked=false): IWriter { + if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_EX); return new class($this->fd, ++$this->serial, $this) extends Stream { function __construct($fd, int $serial, Stream $parent) { $this->parent = $parent; @@ -383,9 +400,9 @@ class Stream extends AbstractIterator implements IReader, IWriter { }; } - function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void { + function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void { $useLocking = $this->useLocking; - if ($useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX); + if ($useLocking && !$alreadyLocked) $this->lock(LOCK_EX); try { $this->fwrite($contents); } finally { @@ -394,7 +411,7 @@ class Stream extends AbstractIterator implements IReader, IWriter { } } - function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void { - $this->putContents(serialize($object), $lockedByCanWrite); + function serialize($object, bool $close=true, bool $alreadyLocked=false): void { + $this->putContents(serialize($object), $alreadyLocked); } } diff --git a/src/file/base/_File.php b/src/file/base/_File.php index 69be147..b9a0710 100644 --- a/src/file/base/_File.php +++ b/src/file/base/_File.php @@ -12,9 +12,17 @@ abstract class _File extends Stream { /** @var string */ protected $file; + function getFile(): ?string { + return $this->file; + } + /** @var string */ protected $mode; + function getMode(): string { + return $this->mode; + } + /** @return resource */ protected function open() { return IOException::ensure_valid(@fopen($this->file, $this->mode));