Initial commit
This commit is contained in:
commit
73c6b816c0
716 changed files with 170045 additions and 0 deletions
228
kirby/src/Cms/ContentLocks.php
Normal file
228
kirby/src/Cms/ContentLocks.php
Normal file
|
@ -0,0 +1,228 @@
|
|||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
|
||||
/**
|
||||
* Manages all content lock files
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Nico Hoffmann <nico@getkirby.com>,
|
||||
* Lukas Bestle <lukas@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class ContentLocks
|
||||
{
|
||||
/**
|
||||
* Data from the `.lock` files
|
||||
* that have been read so far
|
||||
* cached by `.lock` file path
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* PHP file handles for all currently
|
||||
* open `.lock` files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $handles = [];
|
||||
|
||||
/**
|
||||
* Closes the open file handles
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
foreach ($this->handles as $file => $handle) {
|
||||
$this->closeHandle($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file lock and closes the file handle
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
protected function closeHandle(string $file)
|
||||
{
|
||||
if (isset($this->handles[$file]) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handle = $this->handles[$file];
|
||||
$result = flock($handle, LOCK_UN) && fclose($handle);
|
||||
|
||||
if ($result !== true) {
|
||||
throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
unset($this->handles[$file]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to a model's lock file
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return string
|
||||
*/
|
||||
public static function file(ModelWithContent $model): string
|
||||
{
|
||||
return $model->contentFileDirectory() . '/.lock';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lock/unlock data for the specified model
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return array
|
||||
*/
|
||||
public function get(ModelWithContent $model): array
|
||||
{
|
||||
$file = static::file($model);
|
||||
$id = static::id($model);
|
||||
|
||||
// return from cache if file was already loaded
|
||||
if (isset($this->data[$file]) === true) {
|
||||
return $this->data[$file][$id] ?? [];
|
||||
}
|
||||
|
||||
// first get a handle to ensure a file system lock
|
||||
$handle = $this->handle($file);
|
||||
|
||||
if (is_resource($handle) === true) {
|
||||
// read data from file
|
||||
clearstatcache();
|
||||
$filesize = filesize($file);
|
||||
|
||||
if ($filesize > 0) {
|
||||
// always read the whole file
|
||||
rewind($handle);
|
||||
$string = fread($handle, $filesize);
|
||||
$data = Data::decode($string, 'yaml');
|
||||
}
|
||||
}
|
||||
|
||||
$this->data[$file] = $data ?? [];
|
||||
|
||||
return $this->data[$file][$id] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file handle to a `.lock` file
|
||||
*
|
||||
* @param string $file
|
||||
* @param bool $create Whether to create the file if it does not exist
|
||||
* @return resource|null File handle
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
protected function handle(string $file, bool $create = false)
|
||||
{
|
||||
// check for an already open handle
|
||||
if (isset($this->handles[$file]) === true) {
|
||||
return $this->handles[$file];
|
||||
}
|
||||
|
||||
// don't create a file if not requested
|
||||
if (is_file($file) !== true && $create !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$handle = @fopen($file, 'c+b');
|
||||
if (is_resource($handle) === false) {
|
||||
throw new Exception('Lock file ' . $file . ' could not be opened.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// lock the lock file exclusively to prevent changes by other threads
|
||||
$result = flock($handle, LOCK_EX);
|
||||
if ($result !== true) {
|
||||
throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $this->handles[$file] = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns model ID used as the key for the data array;
|
||||
* prepended with a slash because the $site otherwise won't have an ID
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @return string
|
||||
*/
|
||||
public static function id(ModelWithContent $model): string
|
||||
{
|
||||
return '/' . $model->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets and writes the lock/unlock data for the specified model
|
||||
*
|
||||
* @param \Kirby\Cms\ModelWithContent $model
|
||||
* @param array $data
|
||||
* @return bool
|
||||
* @throws \Kirby\Exception\Exception
|
||||
*/
|
||||
public function set(ModelWithContent $model, array $data): bool
|
||||
{
|
||||
$file = static::file($model);
|
||||
$id = static::id($model);
|
||||
$handle = $this->handle($file, true);
|
||||
|
||||
$this->data[$file][$id] = $data;
|
||||
|
||||
// make sure to unset model id entries,
|
||||
// if no lock data for the model exists
|
||||
foreach ($this->data[$file] as $id => $data) {
|
||||
// there is no data for that model whatsoever
|
||||
if (
|
||||
isset($data['lock']) === false &&
|
||||
(isset($data['unlock']) === false ||
|
||||
count($data['unlock']) === 0)
|
||||
) {
|
||||
unset($this->data[$file][$id]);
|
||||
|
||||
// there is empty unlock data, but still lock data
|
||||
} elseif (
|
||||
isset($data['unlock']) === true &&
|
||||
count($data['unlock']) === 0
|
||||
) {
|
||||
unset($this->data[$file][$id]['unlock']);
|
||||
}
|
||||
}
|
||||
|
||||
// there is no data left in the file whatsoever, delete the file
|
||||
if (count($this->data[$file]) === 0) {
|
||||
unset($this->data[$file]);
|
||||
|
||||
// close the file handle, otherwise we can't delete it on Windows
|
||||
$this->closeHandle($file);
|
||||
|
||||
return F::remove($file);
|
||||
}
|
||||
|
||||
$yaml = Data::encode($this->data[$file], 'yaml');
|
||||
|
||||
// delete all file contents first
|
||||
if (rewind($handle) !== true || ftruncate($handle, 0) !== true) {
|
||||
throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// write the new contents
|
||||
$result = fwrite($handle, $yaml);
|
||||
if (is_int($result) === false || $result === 0) {
|
||||
throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue