From 94cf3abd97350d5aa857722827157e4a9515d9a2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 8 Sep 2014 17:53:32 +0200 Subject: [PATCH] Add AtomicWriter.class.php --- .../lib/system/io/AtomicWriter.class.php | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/io/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); + } +} -- 2.20.1