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