nur-sery/nur_src/b/io/SharedFile.php

188 lines
4.9 KiB
PHP

<?php
namespace nur\b\io;
use nur\b\ICloseable;
use nur\os;
use nur\path;
/**
* Class SharedFile: un fichier accédé par plusieurs processus en même temps.
* le verrouillage est automatique en fonction de l'accès en lecture ou en
* écriture.
*/
class SharedFile implements ICloseable {
function __construct(string $file) {
$this->file = $file;
}
/** @var string chemin du fichier */
protected $file;
/** @var resource descripteur de fichier */
protected $fd;
/** @var int */
protected $serial;
/**
* @throws IOException
* @return resource
*/
protected function open() {
if ($this->fd === null) {
IOException::ensure_not_false(os::mkdirof($this->file));
$this->fd = IOException::ensure_not_false(fopen($this->file, "c+b"));
$this->serial = 0;
}
return $this->fd;
}
protected function lock(int $operation, ?int &$wouldBlock=null): bool {
$locked = flock($this->open(), $operation, $wouldBlock);
if ($locked) return true;
if ($operation & LOCK_NB) return false;
else throw IOException::error();
}
protected function unlock(bool $close=false): void {
if ($this->fd !== null) {
flock($this->fd, LOCK_UN);
if ($close) $this->close();
}
}
function size(): int {
$file = $this->file;
if (!file_exists($file)) return 0;
else return filesize($file);
}
/** @throws IOException */
function tell(): int {
return IOException::ensure_not_false(ftell($this->fd));
}
/**
* @return int la position après avoir déplacé le pointeur
* @throws IOException
*/
function seek(int $offset, int $whence=SEEK_SET): int {
$r = fseek($this->fd, $offset, $whence);
if ($r == -1) throw IOException::error();
return $this->tell();
}
/**
* 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
* true
*/
function canRead(): bool {
return $this->lock(LOCK_SH + LOCK_NB);
}
/**
* verrouiller en mode partagé puis retourner un objet permettant de lire le
* fichier.
*/
function getReader(bool $alreadyLockedByCanRead=false): IReader {
if (!$alreadyLockedByCanRead) $this->lock(LOCK_SH);
return new class($this->fd, ++$this->serial, $this) extends StreamReader {
function __construct($fd, int $serial, SharedFile $sf) {
$this->sf = $sf;
$this->serial = $serial;
parent::__construct($fd);
}
/** @var SharedFile */
private $sf;
/** @var int */
private $serial;
function close(bool $close=true): void {
if ($this->sf !== null && $close) {
$this->sf->close($this->serial);
$this->fd = null;
$this->sf = null;
}
}
};
}
/**
* 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
* true
*/
function canWrite(): bool {
return $this->lock(LOCK_EX + LOCK_NB);
}
/**
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
* dans le fichier
*/
function getWriter(bool $alreadyLockedByCanWrite=false): IWriter {
if (!$alreadyLockedByCanWrite) $this->lock(LOCK_EX);
return new class($this->fd, ++$this->serial, $this) extends StreamWriter {
function __construct($fd, int $serial, SharedFile $sf) {
$this->sf = $sf;
$this->serial = $serial;
parent::__construct($fd);
}
/** @var SharedFile */
private $sf;
/** @var int */
private $serial;
function close(bool $close=true): void {
if ($this->sf !== null && $close) {
$this->sf->close($this->serial);
$this->fd = null;
$this->sf = null;
}
}
};
}
function close(?int $serial=null): void {
if ($this->fd !== null && ($serial === null || $this->serial === $serial)) {
fclose($this->fd);
$this->fd = null;
}
}
/** retourner le contenu du fichier sous forme de chaine */
function getContents(bool $alreadyLockedByCanRead=false): string {
if (!$alreadyLockedByCanRead) $this->lock(LOCK_SH);
try {
return file_get_contents($this->file);
} finally {
$this->unlock(true);
}
}
function unserialize(?array $options=null, bool $alreadyLockedByCanRead=false) {
$args = [$this->getContents($alreadyLockedByCanRead)];
if ($options !== null) $args[] = $options;
return unserialize(...$args);
}
/** écrire le contenu spécifié dans le fichier */
function putContents(string $contents, bool $alreadyLockedByCanWrite=false): void {
if (!$alreadyLockedByCanWrite) $this->lock(LOCK_EX);
try {
file_put_contents($this->file, $contents);
} finally {
$this->unlock(true);
}
}
function serialize($object, bool $alreadyLockedByCanWrite=false): void {
$this->putContents(serialize($object), $alreadyLockedByCanWrite);
}
}