187 lines
4.9 KiB
PHP
187 lines
4.9 KiB
PHP
|
<?php
|
||
|
namespace nur\b\io;
|
||
|
|
||
|
use nur\b\ICloseable;
|
||
|
use nur\os;
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
}
|