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