migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
use bantu\IniGetWrapper\IniGetWrapper;
use OCP\IMemcache;
class APCu extends Cache implements IMemcache {
use CASTrait {
cas as casEmulated;
}
use CADTrait;
public function get($key) {
$result = apcu_fetch($this->getPrefix() . $key, $success);
if (!$success) {
return null;
}
return $result;
}
public function set($key, $value, $ttl = 0) {
return apcu_store($this->getPrefix() . $key, $value, $ttl);
}
public function hasKey($key) {
return apcu_exists($this->getPrefix() . $key);
}
public function remove($key) {
return apcu_delete($this->getPrefix() . $key);
}
public function clear($prefix = '') {
$ns = $this->getPrefix() . $prefix;
$ns = preg_quote($ns, '/');
if (class_exists('\APCIterator')) {
$iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY);
} else {
$iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY);
}
return apcu_delete($iter);
}
/**
* Set a value in the cache if it's not already stored
*
* @param string $key
* @param mixed $value
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
* @return bool
*/
public function add($key, $value, $ttl = 0) {
return apcu_add($this->getPrefix() . $key, $value, $ttl);
}
/**
* Increase a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function inc($key, $step = 1) {
$this->add($key, 0);
/**
* TODO - hack around a PHP 7 specific issue in APCu
*
* on PHP 7 the apcu_inc method on a non-existing object will increment
* "0" and result in "1" as value - therefore we check for existence
* first
*
* on PHP 5.6 this is not the case
*
* see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221
* for details
*/
return apcu_exists($this->getPrefix() . $key)
? apcu_inc($this->getPrefix() . $key, $step)
: false;
}
/**
* Decrease a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function dec($key, $step = 1) {
/**
* TODO - hack around a PHP 7 specific issue in APCu
*
* on PHP 7 the apcu_dec method on a non-existing object will decrement
* "0" and result in "-1" as value - therefore we check for existence
* first
*
* on PHP 5.6 this is not the case
*
* see https://github.com/krakjoe/apcu/issues/183#issuecomment-244038221
* for details
*/
return apcu_exists($this->getPrefix() . $key)
? apcu_dec($this->getPrefix() . $key, $step)
: false;
}
/**
* Compare and set
*
* @param string $key
* @param mixed $old
* @param mixed $new
* @return bool
*/
public function cas($key, $old, $new) {
// apc only does cas for ints
if (is_int($old) and is_int($new)) {
return apcu_cas($this->getPrefix() . $key, $old, $new);
} else {
return $this->casEmulated($key, $old, $new);
}
}
public static function isAvailable(): bool {
if (!extension_loaded('apcu')) {
return false;
} elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) {
return false;
} elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enable_cli') && \OC::$CLI) {
return false;
} elseif (version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1) {
return false;
} else {
return true;
}
}
}
@@ -0,0 +1,159 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
use OCP\IMemcache;
class ArrayCache extends Cache implements IMemcache {
/** @var array Array with the cached data */
protected $cachedData = [];
use CADTrait;
/**
* {@inheritDoc}
*/
public function get($key) {
if ($this->hasKey($key)) {
return $this->cachedData[$key];
}
return null;
}
/**
* {@inheritDoc}
*/
public function set($key, $value, $ttl = 0) {
$this->cachedData[$key] = $value;
return true;
}
/**
* {@inheritDoc}
*/
public function hasKey($key) {
return isset($this->cachedData[$key]);
}
/**
* {@inheritDoc}
*/
public function remove($key) {
unset($this->cachedData[$key]);
return true;
}
/**
* {@inheritDoc}
*/
public function clear($prefix = '') {
if ($prefix === '') {
$this->cachedData = [];
return true;
}
foreach ($this->cachedData as $key => $value) {
if (str_starts_with($key, $prefix)) {
$this->remove($key);
}
}
return true;
}
/**
* Set a value in the cache if it's not already stored
*
* @param string $key
* @param mixed $value
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
* @return bool
*/
public function add($key, $value, $ttl = 0) {
// since this cache is not shared race conditions aren't an issue
if ($this->hasKey($key)) {
return false;
} else {
return $this->set($key, $value, $ttl);
}
}
/**
* Increase a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function inc($key, $step = 1) {
$oldValue = $this->get($key);
if (is_int($oldValue)) {
$this->set($key, $oldValue + $step);
return $oldValue + $step;
} else {
$success = $this->add($key, $step);
return $success ? $step : false;
}
}
/**
* Decrease a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function dec($key, $step = 1) {
$oldValue = $this->get($key);
if (is_int($oldValue)) {
$this->set($key, $oldValue - $step);
return $oldValue - $step;
} else {
return false;
}
}
/**
* Compare and set
*
* @param string $key
* @param mixed $old
* @param mixed $new
* @return bool
*/
public function cas($key, $old, $new) {
if ($this->get($key) === $old) {
return $this->set($key, $new);
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public static function isAvailable(): bool {
return true;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
trait CADTrait {
abstract public function get($key);
abstract public function remove($key);
abstract public function add($key, $value, $ttl = 0);
/**
* Compare and delete
*
* @param string $key
* @param mixed $old
* @return bool
*/
public function cad($key, $old) {
//no native cas, emulate with locking
if ($this->add($key . '_lock', true)) {
if ($this->get($key) === $old) {
$this->remove($key);
$this->remove($key . '_lock');
return true;
} else {
$this->remove($key . '_lock');
return false;
}
} else {
return false;
}
}
}
@@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
trait CASTrait {
abstract public function get($key);
abstract public function set($key, $value, $ttl = 0);
abstract public function remove($key);
abstract public function add($key, $value, $ttl = 0);
/**
* Compare and set
*
* @param string $key
* @param mixed $old
* @param mixed $new
* @return bool
*/
public function cas($key, $old, $new) {
//no native cas, emulate with locking
if ($this->add($key . '_lock', true)) {
if ($this->get($key) === $old) {
$this->set($key, $new);
$this->remove($key . '_lock');
return true;
} else {
$this->remove($key . '_lock');
return false;
}
} else {
return false;
}
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
abstract class Cache implements \ArrayAccess, \OCP\ICache {
/**
* @var string $prefix
*/
protected $prefix;
/**
* @param string $prefix
*/
public function __construct($prefix = '') {
$this->prefix = $prefix;
}
/**
* @return string Prefix used for caching purposes
*/
public function getPrefix() {
return $this->prefix;
}
/**
* @param string $key
* @return mixed
*/
abstract public function get($key);
/**
* @param string $key
* @param mixed $value
* @param int $ttl
* @return mixed
*/
abstract public function set($key, $value, $ttl = 0);
/**
* @param string $key
* @return mixed
*/
abstract public function hasKey($key);
/**
* @param string $key
* @return mixed
*/
abstract public function remove($key);
/**
* @param string $prefix
* @return mixed
*/
abstract public function clear($prefix = '');
//implement the ArrayAccess interface
public function offsetExists($offset): bool {
return $this->hasKey($offset);
}
public function offsetSet($offset, $value): void {
$this->set($offset, $value);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
return $this->get($offset);
}
public function offsetUnset($offset): void {
$this->remove($offset);
}
}
@@ -0,0 +1,200 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Markus Goetz <markus@woboq.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Richard Steinmetz <richard@steinmetz.cloud>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
use OCP\Cache\CappedMemoryCache;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IMemcache;
use OCP\Profiler\IProfiler;
use Psr\Log\LoggerInterface;
class Factory implements ICacheFactory {
public const NULL_CACHE = NullCache::class;
private string $globalPrefix;
private LoggerInterface $logger;
/**
* @var ?class-string<ICache> $localCacheClass
*/
private ?string $localCacheClass;
/**
* @var ?class-string<ICache> $distributedCacheClass
*/
private ?string $distributedCacheClass;
/**
* @var ?class-string<IMemcache> $lockingCacheClass
*/
private ?string $lockingCacheClass;
private string $logFile;
private IProfiler $profiler;
/**
* @param string $globalPrefix
* @param LoggerInterface $logger
* @param ?class-string<ICache> $localCacheClass
* @param ?class-string<ICache> $distributedCacheClass
* @param ?class-string<IMemcache> $lockingCacheClass
* @param string $logFile
*/
public function __construct(string $globalPrefix, LoggerInterface $logger, IProfiler $profiler,
?string $localCacheClass = null, ?string $distributedCacheClass = null, ?string $lockingCacheClass = null, string $logFile = '') {
$this->logFile = $logFile;
$this->globalPrefix = $globalPrefix;
if (!$localCacheClass) {
$localCacheClass = self::NULL_CACHE;
}
$localCacheClass = ltrim($localCacheClass, '\\');
if (!$distributedCacheClass) {
$distributedCacheClass = $localCacheClass;
}
$distributedCacheClass = ltrim($distributedCacheClass, '\\');
$missingCacheMessage = 'Memcache {class} not available for {use} cache';
$missingCacheHint = 'Is the matching PHP module installed and enabled?';
if (!class_exists($localCacheClass) || !$localCacheClass::isAvailable()) {
throw new \OCP\HintException(strtr($missingCacheMessage, [
'{class}' => $localCacheClass, '{use}' => 'local'
]), $missingCacheHint);
}
if (!class_exists($distributedCacheClass) || !$distributedCacheClass::isAvailable()) {
throw new \OCP\HintException(strtr($missingCacheMessage, [
'{class}' => $distributedCacheClass, '{use}' => 'distributed'
]), $missingCacheHint);
}
if (!($lockingCacheClass && class_exists($lockingCacheClass) && $lockingCacheClass::isAvailable())) {
// don't fall back since the fallback might not be suitable for storing lock
$lockingCacheClass = self::NULL_CACHE;
}
$lockingCacheClass = ltrim($lockingCacheClass, '\\');
$this->localCacheClass = $localCacheClass;
$this->distributedCacheClass = $distributedCacheClass;
$this->lockingCacheClass = $lockingCacheClass;
$this->profiler = $profiler;
}
/**
* create a cache instance for storing locks
*
* @param string $prefix
* @return IMemcache
*/
public function createLocking(string $prefix = ''): IMemcache {
assert($this->lockingCacheClass !== null);
$cache = new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix);
if ($this->lockingCacheClass === Redis::class && $this->profiler->isEnabled()) {
// We only support the profiler with Redis
$cache = new ProfilerWrapperCache($cache, 'Locking');
$this->profiler->add($cache);
}
if ($this->lockingCacheClass === Redis::class &&
$this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
$cache = new LoggerWrapperCache($cache, $this->logFile);
}
return $cache;
}
/**
* create a distributed cache instance
*
* @param string $prefix
* @return ICache
*/
public function createDistributed(string $prefix = ''): ICache {
assert($this->distributedCacheClass !== null);
$cache = new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix);
if ($this->distributedCacheClass === Redis::class && $this->profiler->isEnabled()) {
// We only support the profiler with Redis
$cache = new ProfilerWrapperCache($cache, 'Distributed');
$this->profiler->add($cache);
}
if ($this->distributedCacheClass === Redis::class && $this->logFile !== ''
&& is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
$cache = new LoggerWrapperCache($cache, $this->logFile);
}
return $cache;
}
/**
* create a local cache instance
*
* @param string $prefix
* @return ICache
*/
public function createLocal(string $prefix = ''): ICache {
assert($this->localCacheClass !== null);
$cache = new $this->localCacheClass($this->globalPrefix . '/' . $prefix);
if ($this->localCacheClass === Redis::class && $this->profiler->isEnabled()) {
// We only support the profiler with Redis
$cache = new ProfilerWrapperCache($cache, 'Local');
$this->profiler->add($cache);
}
if ($this->localCacheClass === Redis::class && $this->logFile !== ''
&& is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
$cache = new LoggerWrapperCache($cache, $this->logFile);
}
return $cache;
}
/**
* check memcache availability
*
* @return bool
*/
public function isAvailable(): bool {
return $this->distributedCacheClass !== self::NULL_CACHE;
}
public function createInMemory(int $capacity = 512): ICache {
return new CappedMemoryCache($capacity);
}
/**
* Check if a local memory cache backend is available
*
* @return bool
*/
public function isLocalCacheAvailable(): bool {
return $this->localCacheClass !== self::NULL_CACHE;
}
}
@@ -0,0 +1,185 @@
<?php
declare(strict_types = 1);
/**
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
*
* @author Carl Schwan <carl@carlschwan.eu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Memcache;
use OCP\IMemcacheTTL;
/**
* Cache wrapper that logs the cache operation in a log file
*/
class LoggerWrapperCache extends Cache implements IMemcacheTTL {
/** @var Redis */
protected $wrappedCache;
/** @var string $logFile */
private $logFile;
/** @var string $prefix */
protected $prefix;
public function __construct(Redis $wrappedCache, string $logFile) {
parent::__construct($wrappedCache->getPrefix());
$this->wrappedCache = $wrappedCache;
$this->logFile = $logFile;
}
/**
* @return string Prefix used for caching purposes
*/
public function getPrefix() {
return $this->prefix;
}
protected function getNameSpace() {
return $this->prefix;
}
/** @inheritDoc */
public function get($key) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::get::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->get($key);
}
/** @inheritDoc */
public function set($key, $value, $ttl = 0) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n",
FILE_APPEND
);
return $this->wrappedCache->set($key, $value, $$ttl);
}
/** @inheritDoc */
public function hasKey($key) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::hasKey::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->hasKey($key);
}
/** @inheritDoc */
public function remove($key) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::remove::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->remove($key);
}
/** @inheritDoc */
public function clear($prefix = '') {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::clear::' . $prefix . "\n",
FILE_APPEND
);
return $this->wrappedCache->clear($prefix);
}
/** @inheritDoc */
public function add($key, $value, $ttl = 0) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::add::' . $key . '::' . $value . "\n",
FILE_APPEND
);
return $this->wrappedCache->add($key, $value, $ttl);
}
/** @inheritDoc */
public function inc($key, $step = 1) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::inc::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->inc($key, $step);
}
/** @inheritDoc */
public function dec($key, $step = 1) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::dec::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->dec($key, $step);
}
/** @inheritDoc */
public function cas($key, $old, $new) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::cas::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->cas($key, $old, $new);
}
/** @inheritDoc */
public function cad($key, $old) {
file_put_contents(
$this->logFile,
$this->getNameSpace() . '::cad::' . $key . "\n",
FILE_APPEND
);
return $this->wrappedCache->cad($key, $old);
}
/** @inheritDoc */
public function setTTL(string $key, int $ttl) {
$this->wrappedCache->setTTL($key, $ttl);
}
public function getTTL(string $key): int|false {
return $this->wrappedCache->getTTL($key);
}
public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
}
public static function isAvailable(): bool {
return true;
}
}
@@ -0,0 +1,196 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
use OCP\HintException;
use OCP\IMemcache;
class Memcached extends Cache implements IMemcache {
use CASTrait;
/**
* @var \Memcached $cache
*/
private static $cache = null;
use CADTrait;
public function __construct($prefix = '') {
parent::__construct($prefix);
if (is_null(self::$cache)) {
self::$cache = new \Memcached();
$defaultOptions = [
\Memcached::OPT_CONNECT_TIMEOUT => 50,
\Memcached::OPT_RETRY_TIMEOUT => 50,
\Memcached::OPT_SEND_TIMEOUT => 50,
\Memcached::OPT_RECV_TIMEOUT => 50,
\Memcached::OPT_POLL_TIMEOUT => 50,
// Enable compression
\Memcached::OPT_COMPRESSION => true,
// Turn on consistent hashing
\Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
// Enable Binary Protocol
\Memcached::OPT_BINARY_PROTOCOL => true,
];
/**
* By default enable igbinary serializer if available
*
* Psalm checks depend on if igbinary is installed or not with memcached
* @psalm-suppress RedundantCondition
* @psalm-suppress TypeDoesNotContainType
*/
if (\Memcached::HAVE_IGBINARY) {
$defaultOptions[\Memcached::OPT_SERIALIZER] =
\Memcached::SERIALIZER_IGBINARY;
}
$options = \OC::$server->getConfig()->getSystemValue('memcached_options', []);
if (is_array($options)) {
$options = $options + $defaultOptions;
self::$cache->setOptions($options);
} else {
throw new HintException("Expected 'memcached_options' config to be an array, got $options");
}
$servers = \OC::$server->getSystemConfig()->getValue('memcached_servers');
if (!$servers) {
$server = \OC::$server->getSystemConfig()->getValue('memcached_server');
if ($server) {
$servers = [$server];
} else {
$servers = [['localhost', 11211]];
}
}
self::$cache->addServers($servers);
}
}
/**
* entries in XCache gets namespaced to prevent collisions between owncloud instances and users
*/
protected function getNameSpace() {
return $this->prefix;
}
public function get($key) {
$result = self::$cache->get($this->getNameSpace() . $key);
if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) {
return null;
} else {
return $result;
}
}
public function set($key, $value, $ttl = 0) {
if ($ttl > 0) {
$result = self::$cache->set($this->getNameSpace() . $key, $value, $ttl);
} else {
$result = self::$cache->set($this->getNameSpace() . $key, $value);
}
return $result || $this->isSuccess();
}
public function hasKey($key) {
self::$cache->get($this->getNameSpace() . $key);
return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
}
public function remove($key) {
$result = self::$cache->delete($this->getNameSpace() . $key);
return $result || $this->isSuccess() || self::$cache->getResultCode() === \Memcached::RES_NOTFOUND;
}
public function clear($prefix = '') {
// Newer Memcached doesn't like getAllKeys(), flush everything
self::$cache->flush();
return true;
}
/**
* Set a value in the cache if it's not already stored
*
* @param string $key
* @param mixed $value
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
* @return bool
*/
public function add($key, $value, $ttl = 0) {
$result = self::$cache->add($this->getPrefix() . $key, $value, $ttl);
return $result || $this->isSuccess();
}
/**
* Increase a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function inc($key, $step = 1) {
$this->add($key, 0);
$result = self::$cache->increment($this->getPrefix() . $key, $step);
if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
return false;
}
return $result;
}
/**
* Decrease a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function dec($key, $step = 1) {
$result = self::$cache->decrement($this->getPrefix() . $key, $step);
if (self::$cache->getResultCode() !== \Memcached::RES_SUCCESS) {
return false;
}
return $result;
}
public static function isAvailable(): bool {
return extension_loaded('memcached');
}
private function isSuccess(): bool {
return self::$cache->getResultCode() === \Memcached::RES_SUCCESS;
}
}
@@ -0,0 +1,73 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
class NullCache extends Cache implements \OCP\IMemcache {
public function get($key) {
return null;
}
public function set($key, $value, $ttl = 0) {
return true;
}
public function hasKey($key) {
return false;
}
public function remove($key) {
return true;
}
public function add($key, $value, $ttl = 0) {
return true;
}
public function inc($key, $step = 1) {
return true;
}
public function dec($key, $step = 1) {
return true;
}
public function cas($key, $old, $new) {
return true;
}
public function cad($key, $old) {
return true;
}
public function clear($prefix = '') {
return true;
}
public static function isAvailable(): bool {
return true;
}
}
@@ -0,0 +1,229 @@
<?php
declare(strict_types = 1);
/**
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
*
* @author Carl Schwan <carl@carlschwan.eu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Memcache;
use OC\AppFramework\Http\Request;
use OCP\AppFramework\Http\Response;
use OCP\DataCollector\AbstractDataCollector;
use OCP\IMemcacheTTL;
/**
* Cache wrapper that logs profiling information
*/
class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess {
/** @var Redis $wrappedCache*/
protected $wrappedCache;
/** @var string $prefix */
protected $prefix;
/** @var string $type */
private $type;
public function __construct(Redis $wrappedCache, string $type) {
$this->prefix = $wrappedCache->getPrefix();
$this->wrappedCache = $wrappedCache;
$this->type = $type;
$this->data['queries'] = [];
$this->data['cacheHit'] = 0;
$this->data['cacheMiss'] = 0;
}
public function getPrefix(): string {
return $this->prefix;
}
/** @inheritDoc */
public function get($key) {
$start = microtime(true);
$ret = $this->wrappedCache->get($key);
if ($ret === null) {
$this->data['cacheMiss']++;
} else {
$this->data['cacheHit']++;
}
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::get::' . $key,
'hit' => $ret !== null,
];
return $ret;
}
/** @inheritDoc */
public function set($key, $value, $ttl = 0) {
$start = microtime(true);
$ret = $this->wrappedCache->set($key, $value, $ttl);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::set::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function hasKey($key) {
$start = microtime(true);
$ret = $this->wrappedCache->hasKey($key);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::hasKey::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function remove($key) {
$start = microtime(true);
$ret = $this->wrappedCache->remove($key);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::remove::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function clear($prefix = '') {
$start = microtime(true);
$ret = $this->wrappedCache->clear($prefix);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::clear::' . $prefix,
];
return $ret;
}
/** @inheritDoc */
public function add($key, $value, $ttl = 0) {
$start = microtime(true);
$ret = $this->wrappedCache->add($key, $value, $ttl);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::add::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function inc($key, $step = 1) {
$start = microtime(true);
$ret = $this->wrappedCache->inc($key, $step);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::inc::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function dec($key, $step = 1) {
$start = microtime(true);
$ret = $this->wrappedCache->dec($key, $step);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::dev::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function cas($key, $old, $new) {
$start = microtime(true);
$ret = $this->wrappedCache->cas($key, $old, $new);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::cas::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function cad($key, $old) {
$start = microtime(true);
$ret = $this->wrappedCache->cad($key, $old);
$this->data['queries'][] = [
'start' => $start,
'end' => microtime(true),
'op' => $this->getPrefix() . '::cad::' . $key,
];
return $ret;
}
/** @inheritDoc */
public function setTTL(string $key, int $ttl) {
$this->wrappedCache->setTTL($key, $ttl);
}
public function getTTL(string $key): int|false {
return $this->wrappedCache->getTTL($key);
}
public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
}
public function offsetExists($offset): bool {
return $this->hasKey($offset);
}
public function offsetSet($offset, $value): void {
$this->set($offset, $value);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
return $this->get($offset);
}
public function offsetUnset($offset): void {
$this->remove($offset);
}
public function collect(Request $request, Response $response, \Throwable $exception = null): void {
// Nothing to do here $data is already ready
}
public function getName(): string {
return 'cache/' . $this->type . '/' . $this->prefix;
}
public static function isAvailable(): bool {
return true;
}
}
+223
View File
@@ -0,0 +1,223 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Memcache;
use OCP\IMemcacheTTL;
class Redis extends Cache implements IMemcacheTTL {
/** name => [script, sha1] */
public const LUA_SCRIPTS = [
'dec' => [
'if redis.call("exists", KEYS[1]) == 1 then return redis.call("decrby", KEYS[1], ARGV[1]) else return "NEX" end',
'720b40cb66cef1579f2ef16ec69b3da8c85510e9',
],
'cas' => [
'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("set", KEYS[1], ARGV[2]) return 1 else return 0 end',
'94eac401502554c02b811e3199baddde62d976d4',
],
'cad' => [
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end',
'cf0e94b2e9ffc7e04395cf88f7583fc309985910',
],
'caSetTtl' => [
'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("expire", KEYS[1], ARGV[2]) return 1 else return 0 end',
'fa4acbc946d23ef41d7d3910880b60e6e4972d72',
],
];
/**
* @var \Redis|\RedisCluster $cache
*/
private static $cache = null;
public function __construct($prefix = '', string $logFile = '') {
parent::__construct($prefix);
}
/**
* @return \Redis|\RedisCluster|null
* @throws \Exception
*/
public function getCache() {
if (is_null(self::$cache)) {
self::$cache = \OC::$server->getGetRedisFactory()->getInstance();
}
return self::$cache;
}
public function get($key) {
$result = $this->getCache()->get($this->getPrefix() . $key);
if ($result === false) {
return null;
}
return self::decodeValue($result);
}
public function set($key, $value, $ttl = 0) {
$value = self::encodeValue($value);
if ($ttl > 0) {
return $this->getCache()->setex($this->getPrefix() . $key, $ttl, $value);
} else {
return $this->getCache()->set($this->getPrefix() . $key, $value);
}
}
public function hasKey($key) {
return (bool)$this->getCache()->exists($this->getPrefix() . $key);
}
public function remove($key) {
if ($this->getCache()->unlink($this->getPrefix() . $key)) {
return true;
} else {
return false;
}
}
public function clear($prefix = '') {
// TODO: this is slow and would fail with Redis cluster
$prefix = $this->getPrefix() . $prefix . '*';
$keys = $this->getCache()->keys($prefix);
$deleted = $this->getCache()->del($keys);
return (is_array($keys) && (count($keys) === $deleted));
}
/**
* Set a value in the cache if it's not already stored
*
* @param string $key
* @param mixed $value
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
* @return bool
*/
public function add($key, $value, $ttl = 0) {
$value = self::encodeValue($value);
$args = ['nx'];
if ($ttl !== 0 && is_int($ttl)) {
$args['ex'] = $ttl;
}
return $this->getCache()->set($this->getPrefix() . $key, $value, $args);
}
/**
* Increase a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function inc($key, $step = 1) {
return $this->getCache()->incrBy($this->getPrefix() . $key, $step);
}
/**
* Decrease a stored number
*
* @param string $key
* @param int $step
* @return int | bool
*/
public function dec($key, $step = 1) {
$res = $this->evalLua('dec', [$key], [$step]);
return ($res === 'NEX') ? false : $res;
}
/**
* Compare and set
*
* @param string $key
* @param mixed $old
* @param mixed $new
* @return bool
*/
public function cas($key, $old, $new) {
$old = self::encodeValue($old);
$new = self::encodeValue($new);
return $this->evalLua('cas', [$key], [$old, $new]) > 0;
}
/**
* Compare and delete
*
* @param string $key
* @param mixed $old
* @return bool
*/
public function cad($key, $old) {
$old = self::encodeValue($old);
return $this->evalLua('cad', [$key], [$old]) > 0;
}
public function setTTL($key, $ttl) {
$this->getCache()->expire($this->getPrefix() . $key, $ttl);
}
public function getTTL(string $key): int|false {
$ttl = $this->getCache()->ttl($this->getPrefix() . $key);
return $ttl > 0 ? (int)$ttl : false;
}
public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
$value = self::encodeValue($value);
return $this->evalLua('caSetTtl', [$key], [$value, $ttl]) > 0;
}
public static function isAvailable(): bool {
return \OC::$server->getGetRedisFactory()->isAvailable();
}
protected function evalLua(string $scriptName, array $keys, array $args) {
$keys = array_map(fn ($key) => $this->getPrefix() . $key, $keys);
$args = array_merge($keys, $args);
$script = self::LUA_SCRIPTS[$scriptName];
$result = $this->getCache()->evalSha($script[1], $args, count($keys));
if (false === $result) {
$result = $this->getCache()->eval($script[0], $args, count($keys));
}
return $result;
}
protected static function encodeValue(mixed $value): string {
return is_int($value) ? (string) $value : json_encode($value);
}
protected static function decodeValue(string $value): mixed {
return is_numeric($value) ? (int) $value : json_decode($value, true);
}
}
@@ -0,0 +1,54 @@
<?php
namespace OC\Memcache;
use OCP\Cache\CappedMemoryCache;
use OCP\ICache;
/**
* Wrap a cache instance with an extra later of local, in-memory caching
*/
class WithLocalCache implements ICache {
private ICache $inner;
private CappedMemoryCache $cached;
public function __construct(ICache $inner, int $localCapacity = 512) {
$this->inner = $inner;
$this->cached = new CappedMemoryCache($localCapacity);
}
public function get($key) {
if (isset($this->cached[$key])) {
return $this->cached[$key];
} else {
$value = $this->inner->get($key);
if (!is_null($value)) {
$this->cached[$key] = $value;
}
return $value;
}
}
public function set($key, $value, $ttl = 0) {
$this->cached[$key] = $value;
return $this->inner->set($key, $value, $ttl);
}
public function hasKey($key) {
return isset($this->cached[$key]) || $this->inner->hasKey($key);
}
public function remove($key) {
unset($this->cached[$key]);
return $this->inner->remove($key);
}
public function clear($prefix = '') {
$this->cached->clear();
return $this->inner->clear($prefix);
}
public static function isAvailable(): bool {
return false;
}
}