Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 66 additions & 73 deletions lib/private/Cache/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OC\Cache;

use OC\Files\Filesystem;
use OC\Files\View;
use OC\ForbiddenException;
use OC\User\NoUserException;
use OCP\Files\LockNotAcquiredException;
use OCP\Files\File as FileNode;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\ICache;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\Lock\LockedException;
use OCP\Security\ISecureRandom;
use OCP\Server;
use Psr\Log\LoggerInterface;

class File implements ICache {
/** @var View */
protected $storage;
protected ?Folder $storage = null;

/**
* Returns the cache storage for the logged in user
* Returns the cache folder for the logged in user
*
* @return View cache storage
* @return Folder cache folder
* @throws ForbiddenException
* @throws NoUserException
*/
Expand All @@ -36,14 +36,19 @@ protected function getStorage() {
return $this->storage;
}
$session = Server::get(IUserSession::class);
if ($session->isLoggedIn()) {
$rootView = new View();
$userId = $session->getUser()->getUID();
Filesystem::initMountPoints($userId);
if (!$rootView->file_exists('/' . $userId . '/cache')) {
$rootView->mkdir('/' . $userId . '/cache');
$user = $session->getUser();
$rootFolder = Server::get(IRootFolder::class);
if ($user) {
$userId = $user->getUID();
try {
$cacheFolder = $rootFolder->get('/' . $userId . '/cache');
if (!$cacheFolder instanceof Folder) {
throw new \Exception('Cache folder is a file');
}
} catch (NotFoundException $e) {
$cacheFolder = $rootFolder->newFolder('/' . $userId . '/cache');
}
$this->storage = new View('/' . $userId . '/cache');
$this->storage = $cacheFolder;
Comment thread
icewind1991 marked this conversation as resolved.
return $this->storage;
} else {
Server::get(LoggerInterface::class)->error('Can\'t get cache storage, user not logged in', ['app' => 'core']);
Expand All @@ -58,27 +63,29 @@ protected function getStorage() {
*/
#[\Override]
public function get($key) {
$result = null;
if ($this->hasKey($key)) {
$storage = $this->getStorage();
$result = $storage->file_get_contents($key);
$storage = $this->getStorage();
try {
/** @var FileNode $item */
$item = $storage->get($key);
return $item->getContent();
} catch (NotFoundException $e) {
return null;
}
return $result;
}

/**
* Returns the size of the stored/cached data
*
* @param string $key
* @return int
* @return int|float
*/
public function size($key) {
$result = 0;
if ($this->hasKey($key)) {
$storage = $this->getStorage();
$result = $storage->filesize($key);
$storage = $this->getStorage();
try {
return $storage->get($key)->getSize();
} catch (NotFoundException $e) {
return 0;
}
return $result;
}

/**
Expand All @@ -91,24 +98,23 @@ public function size($key) {
#[\Override]
public function set($key, $value, $ttl = 0) {
$storage = $this->getStorage();
$result = false;
// unique id to avoid chunk collision, just in case
$uniqueId = Server::get(ISecureRandom::class)->generate(
16,
ISecureRandom::CHAR_ALPHANUMERIC
);

// use part file to prevent hasKey() to find the key
// use a temporary file to prevent hasKey() to find the key
// while it is being written
$keyPart = $key . '.' . $uniqueId . '.part';
if ($storage && $storage->file_put_contents($keyPart, $value)) {
if ($ttl === 0) {
$ttl = 86400; // 60*60*24
}
$result = $storage->touch($keyPart, time() + $ttl);
$result &= $storage->rename($keyPart, $key);
$keyPart = $key . '.' . $uniqueId;
$file = $storage->newFile($keyPart, $value);
if ($ttl === 0) {
$ttl = 86400; // 60*60*24
}
return $result;
$file->move($storage->getFullPath($key));
$file->touch(time() + $ttl);

return true;
}

/**
Expand All @@ -118,11 +124,7 @@ public function set($key, $value, $ttl = 0) {
*/
#[\Override]
public function hasKey($key) {
$storage = $this->getStorage();
if ($storage && $storage->is_file($key) && $storage->isReadable($key)) {
return true;
}
return false;
return $this->getStorage()->nodeExists($key);
}

/**
Expand All @@ -133,10 +135,12 @@ public function hasKey($key) {
#[\Override]
public function remove($key) {
$storage = $this->getStorage();
if (!$storage) {
try {
$storage->get($key)->delete();
return true;
} catch (NotFoundException $e) {
return false;
}
return $storage->unlink($key);
}

/**
Expand All @@ -147,49 +151,38 @@ public function remove($key) {
#[\Override]
public function clear($prefix = '') {
$storage = $this->getStorage();
if ($storage && $storage->is_dir('/')) {
$dh = $storage->opendir('/');
if (is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
if ($file !== '.' && $file !== '..' && ($prefix === '' || str_starts_with($file, $prefix))) {
$storage->unlink('/' . $file);
}
}
foreach ($storage->getDirectoryListing() as $file) {
if ($prefix === '' || str_starts_with($file->getName(), $prefix)) {
$file->delete();
}
}
return true;
}

/**
* Runs GC
*
* @throws ForbiddenException
*/
public function gc() {
$storage = $this->getStorage();
if ($storage) {
$ttl = Server::get(IConfig::class)->getSystemValueInt('cache_chunk_gc_ttl', 60 * 60 * 24);
$now = time() - $ttl;
$ttl = Server::get(IConfig::class)->getSystemValueInt('cache_chunk_gc_ttl', 60 * 60 * 24);
// extra hour safety, in case of stray part chunks that take longer to write,
// because touch() is only called after the chunk was finished

$dh = $storage->opendir('/');
if (!is_resource($dh)) {
return null;
}
while (($file = readdir($dh)) !== false) {
if ($file !== '.' && $file !== '..') {
try {
$mtime = $storage->filemtime('/' . $file);
if ($mtime < $now) {
$storage->unlink('/' . $file);
}
} catch (LockedException $e) {
// ignore locked chunks
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
} catch (\OCP\Files\ForbiddenException $e) {
Server::get(LoggerInterface::class)->debug('Could not cleanup forbidden chunk "' . $file . '"', ['app' => 'core']);
} catch (LockNotAcquiredException $e) {
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
}
$now = time() - $ttl;
foreach ($storage->getDirectoryListing() as $file) {
try {
if ($file->getMTime() < $now) {
$file->delete();
}
} catch (\OCP\Lock\LockedException $e) {
// ignore locked chunks
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file->getName() . '"', ['app' => 'core']);
} catch (\OCP\Files\ForbiddenException $e) {
Server::get(LoggerInterface::class)->debug('Could not cleanup forbidden chunk "' . $file->getName() . '"', ['app' => 'core']);
} catch (\OCP\Files\LockNotAcquiredException $e) {
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file->getName() . '"', ['app' => 'core']);
}
}
}
Expand Down
Loading
Loading