From: Tim Düsterhus Date: Mon, 8 Sep 2014 15:53:32 +0000 (+0200) Subject: Add AtomicWriter.class.php X-Git-Tag: 2.1.0_Alpha_1~344^2~32^2~1^2~4 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=94cf3abd97350d5aa857722827157e4a9515d9a2;p=GitHub%2FWoltLab%2FWCF.git Add AtomicWriter.class.php --- diff --git a/wcfsetup/install/files/lib/system/io/AtomicWriter.class.php b/wcfsetup/install/files/lib/system/io/AtomicWriter.class.php new file mode 100644 index 0000000000..9fecdac719 --- /dev/null +++ b/wcfsetup/install/files/lib/system/io/AtomicWriter.class.php @@ -0,0 +1,119 @@ + + * @package com.woltlab.wcf + * @subpackage system.io + * @category Community Framework + */ +class AtomicWriter extends File { + /** + * The file the data should be flushed into. + * @var string + */ + protected $targetFilename = ''; + + /** + * AtomicWriter will be unusable, once this flag is true. + * @var boolean + */ + protected $isFlushed = false; + + /** + * Opens a new file. The file is always opened in binary mode. + * + * @param string $filename + */ + public function __construct($filename) { + $this->targetFilename = $filename; + + $i = 0; + while (true) { + try { + parent::__construct(FileUtil::getTemporaryFilename('atomic_'), 'xb'); + break; + } + catch (SystemException $e) { + // allow at most 5 failures + if (++$i == 5) { + throw $e; + } + } + } + + if (!flock($this->resource, LOCK_EX)) throw new SystemException('Could not get lock on temporary file'); + } + + /** + * @see \wcf\system\io\AtomicWriter::close() + */ + public function __destruct() { + $this->close(); + } + + /** + * Closes the file, while discarding any written data, noop if the + * file is already closed or flushed. + */ + public function close() { + if (!$this->isFlushed) { + $this->isFlushed = true; + + flock($this->resource, LOCK_UN); + fclose($this->resource); + @unlink($this->filename); + } + } + + /** + * Persists the written data into the target file. The flush is atomic + * if the underlying storage supports an atomic rename. + */ + public function flush() { + $this->isFlushed = true; + + fflush($this->resource); + flock($this->resource, LOCK_UN); + fclose($this->resource); + + rename($this->filename, $this->targetFilename); + } + + /** + * @see \wcf\system\io\File::__call($function, $arguments) + */ + public function __call($function, $arguments) { + if ($this->isFlushed) { + throw new SystemException('AtomicWriter for '.$this->targetFilename.' was already flushed.'); + } + + switch ($function) { + case 'write': + case 'puts': + case 'seek': + case 'tell': + case 'rewind': + case 'truncate': + // these are fine + break; + default: + throw new SystemException("AtomicWriter does not allow '".$function."'"); + } + + return parent::__call($function, $arguments); + } +}