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);
 | 
						|
  }
 | 
						|
}
 |