diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 5417095f44bee..c268352508575 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -17,15 +17,14 @@ use Psr\Log\LoggerInterface; /** - * Handle the mapping between the string and numeric storage ids + * Represents the persisted storage-id mapping for a single storage. * - * 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 storage has: + * - a backend-provided string identifier that reflects its configuration (e.g. 'smb://user@host/share') + * - a numeric identifier 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 + * This class resolves or creates that mapping, exposes the numeric id for the + * current storage, and updates persisted storage state such as availability. */ class Storage { private string $storageId; @@ -153,6 +152,8 @@ public function setAvailability(bool $isAvailable, int $delay = 0): void { ->set('last_checked', $query->createNamedParameter(time() + $delay)) ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId))); $query->executeStatement(); + + self::getGlobalCache()->clearStorageInfo($this->storageId); } /** diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 336f136509dfe..b7d128d00a0a0 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -12,15 +12,14 @@ use OCP\IDBConnection; /** - * Handle the mapping between the string and numeric storage ids + * Provides a process-local cache of persisted storage-id mappings. * - * 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 + * Caches lookups in both directions: + * - string storage id to storage record + * - numeric storage id to storage record * - * A mapping between the two storage ids is stored in the database and accessible through this class - * - * @package OC\Files\Cache + * This class reduces repeated database lookups for storage mapping metadata + * and provides invalidation helpers for callers that update those records. */ class StorageGlobal { /** @var array */ @@ -118,7 +117,26 @@ public function getStorageInfoByNumericId(int $numericId): ?array { return $this->numericIdCache[$numericId] ?? null; } + public function clearStorageInfo(string $storageId): void { + $row = $this->cache[$storageId] ?? null; + unset($this->cache[$storageId]); + + if ($row !== null) { + unset($this->numericIdCache[$row['numeric_id']]); + } + } + + public function clearStorageInfoByNumericId(int $numericId): void { + $row = $this->numericIdCache[$numericId] ?? null; + unset($this->numericIdCache[$numericId]); + + if ($row !== null) { + unset($this->cache[$row['id']]); + } + } + public function clearCache(): void { $this->cache = []; + $this->numericIdCache = []; } } diff --git a/tests/lib/Files/Cache/StorageTest.php b/tests/lib/Files/Cache/StorageTest.php new file mode 100644 index 0000000000000..75caecba31157 --- /dev/null +++ b/tests/lib/Files/Cache/StorageTest.php @@ -0,0 +1,96 @@ +connection = Server::get(IDBConnection::class); + Storage::getGlobalCache()->clearCache(); + } + + protected function tearDown(): void { + Storage::getGlobalCache()->clearCache(); + + if ($this->createdStorageId !== null) { + $query = $this->connection->getQueryBuilder(); + $query->delete('storages') + ->where( + $query->expr()->eq('id', $query->createNamedParameter($this->createdStorageId)) + ) + ->executeStatement(); + + $this->createdStorageId = null; + } + + parent::tearDown(); + } + + public function testSetAvailabilityInvalidatesGlobalCacheForStringAndNumericLookups(): void { + $storageId = 'test::availability-cache-' . uniqid('', true); + $this->createdStorageId = $storageId; + + $storage = new Storage($storageId, true, $this->connection); + $numericId = $storage->getNumericId(); + + $globalCache = Storage::getGlobalCache(); + + // Prime both process-local caches with the initial persisted state. + $initialById = $globalCache->getStorageInfo($storageId); + $initialByNumericId = $globalCache->getStorageInfoByNumericId($numericId); + + $this->assertIsArray($initialById); + $this->assertIsArray($initialByNumericId); + + $this->assertSame($storageId, $initialById['id']); + $this->assertSame($numericId, $initialById['numeric_id']); + $this->assertTrue($initialById['available']); + $this->assertSame(0, $initialById['last_checked']); + + $this->assertSame($storageId, $initialByNumericId['id']); + $this->assertSame($numericId, $initialByNumericId['numeric_id']); + $this->assertTrue($initialByNumericId['available']); + $this->assertSame(0, $initialByNumericId['last_checked']); + + $before = time(); + $delay = 600; + + $storage->setAvailability(false, $delay); + + // Both lookup directions must now observe fresh persisted state rather than + // stale data from the process-local cache. + $updatedById = $globalCache->getStorageInfo($storageId); + $updatedByNumericId = $globalCache->getStorageInfoByNumericId($numericId); + + $this->assertIsArray($updatedById); + $this->assertIsArray($updatedByNumericId); + + $this->assertSame($storageId, $updatedById['id']); + $this->assertSame($numericId, $updatedById['numeric_id']); + $this->assertFalse($updatedById['available']); + $this->assertGreaterThanOrEqual($before + $delay, $updatedById['last_checked']); + $this->assertLessThanOrEqual($before + $delay + 1, $updatedById['last_checked']); + + $this->assertSame($storageId, $updatedByNumericId['id']); + $this->assertSame($numericId, $updatedByNumericId['numeric_id']); + $this->assertFalse($updatedByNumericId['available']); + $this->assertSame($updatedById['last_checked'], $updatedByNumericId['last_checked']); + } +}