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