Skip to content
Merged
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
64 changes: 39 additions & 25 deletions lib/private/Files/Type/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use OCP\AppFramework\Db\TTransactional;
use OCP\DB\Exception as DBException;
use OCP\Files\IMimeTypeLoader;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IDBConnection;

/**
Expand All @@ -28,14 +30,16 @@ class Loader implements IMimeTypeLoader {
/** @psalm-var array<string, int> */
protected array $mimetypeIds;

/**
* @param IDBConnection $dbConnection
*/
private bool $loadedFromDatabase = false;
private ICache $cache;

public function __construct(
private IDBConnection $dbConnection,
ICacheFactory $cacheFactory,
) {
$this->mimetypes = [];
$this->mimetypeIds = [];
$this->cache = $cacheFactory->createLocal('mimetypes');
}

/**
Expand All @@ -46,24 +50,21 @@ public function getMimetypeById(int $id): ?string {
if (!$this->mimetypes) {
$this->loadMimetypes();
}
if (isset($this->mimetypes[$id])) {
return $this->mimetypes[$id];
if (!isset($this->mimetypes[$id]) && !$this->loadedFromDatabase) {
$this->loadMimetypes(true);
}
return null;
return $this->mimetypes[$id] ?? null;
}

/**
* Get a mimetype ID, adding the mimetype to the DB if it does not exist
*/
#[\Override]
public function getId(string $mimetype): int {
if (!$this->mimetypeIds) {
$this->loadMimetypes();
}
if (isset($this->mimetypeIds[$mimetype])) {
return $this->mimetypeIds[$mimetype];
if (!$this->exists($mimetype)) {
return $this->store($mimetype);
}
return $this->store($mimetype);
return $this->mimetypeIds[$mimetype];
}

/**
Expand All @@ -74,6 +75,9 @@ public function exists(string $mimetype): bool {
if (!$this->mimetypeIds) {
$this->loadMimetypes();
}
if (!isset($this->mimetypeIds[$mimetype]) && !$this->loadedFromDatabase) {
$this->loadMimetypes(true);
}
return isset($this->mimetypeIds[$mimetype]);
}

Expand All @@ -84,6 +88,8 @@ public function exists(string $mimetype): bool {
public function reset(): void {
$this->mimetypes = [];
$this->mimetypeIds = [];
$this->loadedFromDatabase = false;
$this->cache->remove('mimetypes');
}

/**
Expand Down Expand Up @@ -124,25 +130,33 @@ protected function store(string $mimetype): int {

$this->mimetypes[$mimetypeId] = $mimetype;
$this->mimetypeIds[$mimetype] = $mimetypeId;
$this->cache->remove('mimetypes');
return $mimetypeId;
}

/**
* Load all mimetypes from DB
* Load all mimetypes, from the cache unless $fromDatabase is set. Mimetypes
* are only ever added, so the cache can only miss new ones, which are
* then loaded from the database.
*/
private function loadMimetypes(): void {
$qb = $this->dbConnection->getQueryBuilder();
$qb->select('id', 'mimetype')
->from('mimetypes');

$result = $qb->executeQuery();
$results = $result->fetchAllAssociative();
$result->closeCursor();

foreach ($results as $row) {
$this->mimetypes[(int)$row['id']] = $row['mimetype'];
$this->mimetypeIds[$row['mimetype']] = (int)$row['id'];
private function loadMimetypes(bool $fromDatabase = false): void {
$mimetypes = $fromDatabase ? null : $this->cache->get('mimetypes');
if (!is_array($mimetypes)) {
$qb = $this->dbConnection->getQueryBuilder();
$qb->select('id', 'mimetype')
->from('mimetypes');
$result = $qb->executeQuery();
$mimetypes = [];
foreach ($result->fetchAllAssociative() as $row) {
$mimetypes[(int)$row['id']] = (string)$row['mimetype'];
}
$result->closeCursor();
$this->cache->set('mimetypes', $mimetypes);
$this->loadedFromDatabase = true;
}

$this->mimetypes = $mimetypes;
$this->mimetypeIds = array_flip($mimetypes);
}

/**
Expand Down
45 changes: 44 additions & 1 deletion tests/lib/Files/Type/LoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
namespace Test\Files\Type;

use OC\Files\Type\Loader;
use OC\Memcache\ArrayCache;
use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\Server;
use PHPUnit\Framework\Attributes\Group;
Expand All @@ -17,14 +19,16 @@
#[Group('DB')]
class LoaderTest extends TestCase {
protected IDBConnection $db;
protected ArrayCache $cache;
protected Loader $loader;

#[\Override]
protected function setUp(): void {
parent::setUp();

$this->db = Server::get(IDBConnection::class);
$this->loader = new Loader($this->db);
$this->cache = new ArrayCache('');
$this->loader = $this->createLoader();
}

#[\Override]
Expand All @@ -35,6 +39,7 @@ protected function tearDown(): void {
'mimetype', $deleteMimetypes->createPositionalParameter('testing/%')
));
$deleteMimetypes->executeStatement();
$this->loader->reset();
parent::tearDown();
}

Expand Down Expand Up @@ -84,4 +89,42 @@ public function testStoreExists(): void {

$this->assertEquals($mimetypeId, $mimetypeId2);
}

private function createLoader(): Loader {
$cacheFactory = $this->createMock(ICacheFactory::class);
$cacheFactory->method('createLocal')->willReturn($this->cache);
return new Loader($this->db, $cacheFactory);
}

private function insertMimetype(string $mimetype): int {
$qb = $this->db->getQueryBuilder();
$qb->insert('mimetypes')
->values(['mimetype' => $qb->createPositionalParameter($mimetype)])
->executeStatement();
return $qb->getLastInsertId();
}

public function testMimetypesAreLoadedFromTheCache(): void {
$this->cache->set('mimetypes', [123456 => 'testing/cached']);

$this->assertSame('testing/cached', $this->createLoader()->getMimetypeById(123456));
}

public function testMimetypeMissingInTheCacheIsLoadedFromTheDatabase(): void {
$this->loader->getMimetypeById(1);
$id = $this->insertMimetype('testing/added-elsewhere');

$loader = $this->createLoader();
$this->assertSame('testing/added-elsewhere', $loader->getMimetypeById($id));
$this->assertTrue($loader->exists('testing/added-elsewhere'));
$this->assertSame($id, $loader->getId('testing/added-elsewhere'));
}

public function testStoreInvalidatesTheCache(): void {
$this->loader->getMimetypeById(1);
$this->assertTrue($this->cache->hasKey('mimetypes'));

$this->loader->getId('testing/stored');
$this->assertFalse($this->cache->hasKey('mimetypes'));
}
}
Loading