287 lines
8.1 KiB
PHP
287 lines
8.1 KiB
PHP
<?php
|
|
namespace nur\sery\os\file;
|
|
|
|
use nulib\str;
|
|
use nulib\ValueException;
|
|
use nur\sery\os\EOFException;
|
|
use nur\sery\os\IOException;
|
|
use nur\sery\php\iter\AbstractIterator;
|
|
|
|
/**
|
|
* Class Stream: lecture/écriture générique dans un flux
|
|
*/
|
|
class Stream extends AbstractIterator implements IReader, IWriter {
|
|
use TStreamFilter;
|
|
|
|
/** @var bool les opérations de verrouillages sont-elle activées? */
|
|
const ALLOW_LOCKING = false;
|
|
|
|
/** @var resource */
|
|
protected $fd;
|
|
|
|
/** @var bool */
|
|
protected $close;
|
|
|
|
/** @var bool */
|
|
protected $throwOnError;
|
|
|
|
/** @var bool */
|
|
protected $allowLocking;
|
|
|
|
/** @var int|null */
|
|
protected $serial;
|
|
|
|
/** @var array */
|
|
protected $stat;
|
|
|
|
function __construct($fd, bool $close=true, bool $throwOnError=true, ?bool $allowLocking=null) {
|
|
if ($fd === null) throw ValueException::null("resource");
|
|
$this->fd = $fd;
|
|
$this->close = $close;
|
|
$this->throwOnError = $throwOnError;
|
|
if ($allowLocking === null) $allowLocking = static::ALLOW_LOCKING;
|
|
$this->allowLocking = $allowLocking;
|
|
}
|
|
|
|
#############################################################################
|
|
# File
|
|
|
|
/**
|
|
* @return resource|null retourner la resource associée à ce fichier si cela
|
|
* a du sens
|
|
*/
|
|
function getResource() {
|
|
IOException::ensure_open($this->fd === null);
|
|
$this->_streamAppendFilters($this->fd);
|
|
return $this->fd;
|
|
}
|
|
|
|
protected function lock(int $operation, ?int &$wouldBlock=null): bool {
|
|
$locked = flock($this->getResource(), $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 isatty(): bool {
|
|
return stream_isatty($this->getResource());
|
|
}
|
|
|
|
/** obtenir des informations sur le fichier */
|
|
function fstat(bool $reload=false): array {
|
|
if ($this->stat === null || $reload) {
|
|
$fd = $this->getResource();
|
|
$this->stat = IOException::ensure_value(fstat($fd), $this->throwOnError);
|
|
}
|
|
return $this->stat;
|
|
}
|
|
|
|
/** @throws IOException */
|
|
function ftell(): int {
|
|
$fd = $this->getResource();
|
|
return IOException::ensure_value(ftell($fd), $this->throwOnError);
|
|
}
|
|
|
|
/**
|
|
* @return int la position après avoir déplacé le pointeur
|
|
* @throws IOException
|
|
*/
|
|
function fseek(int $offset, int $whence=SEEK_SET): int {
|
|
$fd = $this->getResource();
|
|
IOException::ensure_value(fseek($fd, $offset, $whence), $this->throwOnError, -1);
|
|
return $this->ftell();
|
|
}
|
|
|
|
/** fermer le fichier si c'est nécessaire */
|
|
function close(bool $close=true, ?int $ifSerial=null): void {
|
|
AbstractIterator::rewind();
|
|
if ($this->fd !== null && $close && $this->close && ($ifSerial === null || $this->serial === $ifSerial)) {
|
|
fclose($this->fd);
|
|
$this->fd = null;
|
|
}
|
|
}
|
|
|
|
#############################################################################
|
|
# Reader
|
|
|
|
/** @throws IOException */
|
|
function fread(int $length): string {
|
|
$fd = $this->getResource();
|
|
return IOException::ensure_value(fread($fd, $length), $this->throwOnError);
|
|
}
|
|
|
|
/** @throws IOException */
|
|
function fgets(?int $length=null): string {
|
|
$fd = $this->getResource();
|
|
return EOFException::ensure_not_eof(fgets($fd, $length), $this->throwOnError);
|
|
}
|
|
|
|
/** @throws IOException */
|
|
function fpassthru(): int {
|
|
$fd = $this->getResource();
|
|
return IOException::ensure_value(fpassthru($fd), $this->throwOnError);
|
|
}
|
|
|
|
function readLine(): ?string {
|
|
return str::strip_nl($this->fgets());
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
if ($this->allowLocking) return $this->lock(LOCK_SH + LOCK_NB);
|
|
else return true;
|
|
}
|
|
|
|
/**
|
|
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
|
* fichier.
|
|
*/
|
|
function getReader(bool $lockedByCanRead=false): IReader {
|
|
if ($this->allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
|
function __construct($fd, int $serial, Stream $parent) {
|
|
$this->parent = $parent;
|
|
parent::__construct($fd);
|
|
}
|
|
|
|
/** @var Stream */
|
|
private $parent;
|
|
|
|
function close(bool $close=true): void {
|
|
if ($this->parent !== null && $close) {
|
|
$this->parent->close(true, $this->serial);
|
|
$this->fd = null;
|
|
$this->parent = null;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/** retourner le contenu du fichier sous forme de chaine */
|
|
function getContents(bool $close=true, bool $lockedByCanRead=false): string {
|
|
$allowLocking = $this->allowLocking;
|
|
if ($allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
|
try {
|
|
return IOException::ensure_value(stream_get_contents($this->fd), $this->throwOnError);
|
|
} finally {
|
|
if ($allowLocking) $this->unlock($close);
|
|
elseif ($close) $this->close();
|
|
}
|
|
}
|
|
|
|
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) {
|
|
$args = [$this->getContents($lockedByCanRead)];
|
|
if ($options !== null) $args[] = $options;
|
|
return unserialize(...$args);
|
|
}
|
|
|
|
#############################################################################
|
|
# Iterator
|
|
|
|
protected function _setup(): void {
|
|
}
|
|
|
|
protected function _next(&$key) {
|
|
return $this->fgets();
|
|
}
|
|
|
|
protected function _teardown(): void {
|
|
$this->fseek(0);
|
|
}
|
|
|
|
#############################################################################
|
|
# Writer
|
|
|
|
/** @throws IOException */
|
|
function fwrite(string $data, ?int $length=null): int {
|
|
$fd = $this->getResource();
|
|
if ($length === null) $r = fwrite($fd, $data);
|
|
else $r = fwrite($fd, $data, $length);
|
|
return IOException::ensure_value($r, $this->throwOnError);
|
|
}
|
|
|
|
/** @throws IOException */
|
|
function fflush(): void {
|
|
$fd = $this->getResource();
|
|
IOException::ensure_value(fflush($fd), $this->throwOnError);
|
|
}
|
|
|
|
/** @throws IOException */
|
|
function ftruncate(int $size): void {
|
|
$fd = $this->getResource();
|
|
IOException::ensure_value(ftruncate($fd, $size), $this->throwOnError);
|
|
}
|
|
|
|
function writeLines(?iterable $lines): IWriter {
|
|
if ($lines !== null) {
|
|
foreach ($lines as $line) {
|
|
$this->fwrite($line);
|
|
$this->fwrite("\n");
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
if ($this->allowLocking) return $this->lock(LOCK_EX + LOCK_NB);
|
|
else return true;
|
|
}
|
|
|
|
/**
|
|
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
|
* dans le fichier
|
|
*/
|
|
function getWriter(bool $lockedByCanWrite=false): IWriter {
|
|
if ($this->allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
|
function __construct($fd, int $serial, Stream $parent) {
|
|
$this->parent = $parent;
|
|
$this->serial = $serial;
|
|
parent::__construct($fd);
|
|
}
|
|
|
|
/** @var Stream */
|
|
private $parent;
|
|
|
|
function close(bool $close=true): void {
|
|
if ($this->parent !== null && $close) {
|
|
$this->parent->close(true, $this->serial);
|
|
$this->fd = null;
|
|
$this->parent = null;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void {
|
|
$allowLocking = $this->allowLocking;
|
|
if ($allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
|
try {
|
|
$this->fwrite($contents);
|
|
} finally {
|
|
if ($allowLocking) $this->unlock($close);
|
|
elseif ($close) $this->close();
|
|
}
|
|
}
|
|
|
|
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void {
|
|
$this->putContents(serialize($object), $lockedByCanWrite);
|
|
}
|
|
}
|