You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nextcloud-server/lib/private/Files/Cache/Storage.php

202 lines
5.9 KiB

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Cache;
use OC\DB\Exceptions\DbalException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Storage\IStorage;
use OCP\IDBConnection;
use OCP\Server;
use Psr\Log\LoggerInterface;
/**
* Handle the mapping between the string and numeric storage ids
*
* Each storage has 2 different ids
* a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share')
* and a numeric storage id which is referenced in the file cache
*
* A mapping between the two storage ids is stored in the database and accessible through this class
*
* @package OC\Files\Cache
*/
class Storage {
private static ?StorageGlobal $globalCache = null;
private string $storageId;
private int $numericId;
public static function getGlobalCache(): StorageGlobal {
if (is_null(self::$globalCache)) {
self::$globalCache = new StorageGlobal(Server::get(IDBConnection::class));
}
return self::$globalCache;
}
/**
* @throws \RuntimeException
*/
public function __construct(
IStorage|string $storage,
bool $isAvailable,
IDBConnection $connection,
) {
$this->storageId = $storage instanceof IStorage ? $storage->getId() : $storage;
$this->storageId = self::adjustStorageId($this->storageId);
if ($row = self::getStorageById($this->storageId)) {
$this->numericId = $row['numeric_id'];
} else {
$available = $isAvailable ? 1 : 0;
try {
$qb = $connection->getQueryBuilder();
$qb->insert('storages')
->values([
'id' => $qb->createNamedParameter($this->storageId),
'available' => $qb->createNamedParameter($available),
])
->executeStatement();
$this->numericId = $qb->getLastInsertId();
} catch (DbalException $e) {
if ($e->getReason() === DbalException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
$qb = $connection->getQueryBuilder();
$result = $qb->select('numeric_id')
->from('storages')
->where($qb->expr()->eq('id', $qb->createNamedParameter($this->storageId)))
->executeQuery();
/** @var false|string|int $row */
$row = $result->fetchOne();
if ($row === false) {
throw new \RuntimeException('Storage could neither be inserted nor be selected from the database: ' . $this->storageId, $e->getCode(), $e);
}
$this->numericId = (int)$row;
}
throw $e;
}
}
}
/**
* @return array{id: string, numeric_id: int, available: bool, last_checked: int}|null
*/
public static function getStorageById(string $storageId): ?array {
return self::getGlobalCache()->getStorageInfo($storageId);
}
/**
* Adjusts the storage id to use md5 if too long
* @param string $storageId storage id
* @return string unchanged $storageId if its length is less than 64 characters,
* else returns the md5 of $storageId
*/
public static function adjustStorageId(string $storageId): string {
if (strlen($storageId) > 64) {
return md5($storageId);
}
return $storageId;
}
/**
* Get the numeric id for the storage
*/
public function getNumericId(): int {
return $this->numericId;
}
/**
* Get the string id for the storage
*
* @return string|null either the storage id string or null if the numeric id is not known
*/
public static function getStorageId(int $numericId): ?string {
$storage = self::getGlobalCache()->getStorageInfoByNumericId($numericId);
return $storage['id'] ?? null;
}
/**
* @return array{available: bool, last_checked: int}
*/
public function getAvailability(): array {
if ($row = self::getStorageById($this->storageId)) {
return [
'available' => (int)$row['available'] === 1,
'last_checked' => $row['last_checked']
];
}
return [
'available' => true,
'last_checked' => time(),
];
}
/**
* @param int $delay amount of seconds to delay reconsidering that storage further
*/
public function setAvailability(bool $isAvailable, int $delay = 0): void {
$available = $isAvailable ? 1 : 0;
if (!$isAvailable) {
Server::get(LoggerInterface::class)->info('Storage with ' . $this->storageId . ' marked as unavailable', ['app' => 'lib']);
}
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->update('storages')
->set('available', $query->createNamedParameter($available))
->set('last_checked', $query->createNamedParameter(time() + $delay))
->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
$query->executeStatement();
}
/**
* Remove the entry for the storage by the mount id.
*/
public static function cleanByMountId(int $mountId): void {
$db = Server::get(IDBConnection::class);
try {
$db->beginTransaction();
$query = $db->getQueryBuilder();
$query->select('storage_id')
->from('mounts')
->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
$storageIds = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
$storageIds = array_unique($storageIds);
$query = $db->getQueryBuilder();
$query->delete('filecache')
->where($query->expr()->in('storage', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
->runAcrossAllShards()
->executeStatement();
$query = $db->getQueryBuilder();
$query->delete('storages')
->where($query->expr()->in('numeric_id', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
->executeStatement();
$query = $db->getQueryBuilder();
$query->delete('mounts')
->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
->executeStatement();
$db->commit();
} catch (\Exception $exception) {
$db->rollBack();
throw $exception;
}
}
}