ajouter RunFile et LockFile
This commit is contained in:
parent
502331664e
commit
096bfe91f8
|
@ -37,7 +37,7 @@ interface IReader extends _IFile {
|
||||||
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||||
* fichier.
|
* 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,
|
* 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
|
* @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 */
|
/** 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,11 @@ interface IWriter extends _IFile {
|
||||||
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||||
* dans le fichier
|
* dans le fichier
|
||||||
*/
|
*/
|
||||||
function getWriter(bool $lockedByCanWrite=false): IWriter;
|
function getWriter(bool $alreadyLocked=false): IWriter;
|
||||||
|
|
||||||
/** écrire le contenu spécifié dans le fichier */
|
/** é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 */
|
/** 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\file\app;
|
||||||
|
|
||||||
|
use nur\sery\cl;
|
||||||
|
use nur\sery\file\base\SharedFile;
|
||||||
|
use nur\sery\output\msg;
|
||||||
|
use nur\sery\php\time\DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class LockFile: une classe qui permet à une application de verrouiller
|
||||||
|
* certaines actions
|
||||||
|
*/
|
||||||
|
class LockFile {
|
||||||
|
const NAME = null;
|
||||||
|
|
||||||
|
const TITLE = null;
|
||||||
|
|
||||||
|
function __construct($file, ?string $name=null, ?string $title=null) {
|
||||||
|
$this->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(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\file\app;
|
||||||
|
|
||||||
|
use nur\sery\cl;
|
||||||
|
use nur\sery\file\base\SharedFile;
|
||||||
|
use nur\sery\os\path;
|
||||||
|
use nur\sery\php\time\DateTime;
|
||||||
|
use nur\sery\str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RunFile: une classe permettant de suivre le fonctionnement d'une
|
||||||
|
* application qui tourne en tâche de fond
|
||||||
|
*/
|
||||||
|
class RunFile {
|
||||||
|
const RUN_EXT = ".run";
|
||||||
|
const LOCK_EXT = ".lock";
|
||||||
|
|
||||||
|
const NAME = null;
|
||||||
|
|
||||||
|
function __construct(string $file, ?string $name=null) {
|
||||||
|
$file = path::ensure_ext($file, self::RUN_EXT);
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -234,6 +234,14 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
return iterator_to_array($this);
|
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
|
* 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
|
* 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
|
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||||
* fichier.
|
* fichier.
|
||||||
*/
|
*/
|
||||||
function getReader(bool $lockedByCanRead=false): IReader {
|
function getReader(bool $alreadyLocked=false): IReader {
|
||||||
if ($this->useLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_SH);
|
||||||
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||||
function __construct($fd, int $serial, Stream $parent) {
|
function __construct($fd, int $serial, Stream $parent) {
|
||||||
$this->parent = $parent;
|
$this->parent = $parent;
|
||||||
|
@ -270,9 +278,9 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** retourner le contenu du fichier sous forme de chaine */
|
/** 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;
|
$useLocking = $this->useLocking;
|
||||||
if ($useLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_SH);
|
||||||
try {
|
try {
|
||||||
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -281,8 +289,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) {
|
function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false) {
|
||||||
$args = [$this->getContents($lockedByCanRead)];
|
$args = [$this->getContents($alreadyLocked)];
|
||||||
if ($options !== null) $args[] = $options;
|
if ($options !== null) $args[] = $options;
|
||||||
return unserialize(...$args);
|
return unserialize(...$args);
|
||||||
}
|
}
|
||||||
|
@ -331,9 +339,10 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @throws IOException */
|
/** @throws IOException */
|
||||||
function ftruncate(int $size): self {
|
function ftruncate(int $size=0, bool $rewind=true): self {
|
||||||
$fd = $this->getResource();
|
$fd = $this->getResource();
|
||||||
IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError);
|
IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError);
|
||||||
|
if ($rewind) rewind($fd);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,6 +356,14 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
return $this;
|
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
|
* 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
|
* 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
|
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||||
* dans le fichier
|
* dans le fichier
|
||||||
*/
|
*/
|
||||||
function getWriter(bool $lockedByCanWrite=false): IWriter {
|
function getWriter(bool $alreadyLocked=false): IWriter {
|
||||||
if ($this->useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
if ($this->useLocking && !$alreadyLocked) $this->lock(LOCK_EX);
|
||||||
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||||
function __construct($fd, int $serial, Stream $parent) {
|
function __construct($fd, int $serial, Stream $parent) {
|
||||||
$this->parent = $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;
|
$useLocking = $this->useLocking;
|
||||||
if ($useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_EX);
|
||||||
try {
|
try {
|
||||||
$this->fwrite($contents);
|
$this->fwrite($contents);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -394,7 +411,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void {
|
function serialize($object, bool $close=true, bool $alreadyLocked=false): void {
|
||||||
$this->putContents(serialize($object), $lockedByCanWrite);
|
$this->putContents(serialize($object), $alreadyLocked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,17 @@ abstract class _File extends Stream {
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $file;
|
protected $file;
|
||||||
|
|
||||||
|
function getFile(): ?string {
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
protected $mode;
|
protected $mode;
|
||||||
|
|
||||||
|
function getMode(): string {
|
||||||
|
return $this->mode;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return resource */
|
/** @return resource */
|
||||||
protected function open() {
|
protected function open() {
|
||||||
return IOException::ensure_valid(@fopen($this->file, $this->mode));
|
return IOException::ensure_valid(@fopen($this->file, $this->mode));
|
||||||
|
|
Loading…
Reference in New Issue