generated from Nodarx/template
feat: implement meta and content cache
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXM\ProviderImap\Stores;
|
||||
|
||||
use KTXC\Db\DataStore;
|
||||
use KTXM\ProviderImap\Providers\CollectionResource;
|
||||
|
||||
/**
|
||||
* IMAP Mailbox Meta Store
|
||||
*
|
||||
* One MongoDB document per cached mailbox in `provider_imap_mail_mailboxes`,
|
||||
* keyed by (sid, name). The document holds the mailbox itself and its
|
||||
* harmonization state.
|
||||
*/
|
||||
class MailboxStore
|
||||
{
|
||||
protected const COLLECTION_NAME = 'provider_imap_mail_mailboxes';
|
||||
|
||||
public function __construct(
|
||||
protected readonly DataStore $dataStore,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create the collection indexes.
|
||||
*
|
||||
* MongoDB createIndex is idempotent when the name and specification match.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function ensureIndexes(): array
|
||||
{
|
||||
$collection = $this->dataStore->selectCollection(self::COLLECTION_NAME);
|
||||
|
||||
return [
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'name' => 1],
|
||||
['name' => 'mailboxes_key', 'unique' => true]
|
||||
),
|
||||
$collection->createIndex(
|
||||
['tid' => 1, 'sid' => 1],
|
||||
['name' => 'mailboxes_by_tenant_service']
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update the mailbox fields of a document.
|
||||
*
|
||||
* Harmonization state on the same document is left untouched.
|
||||
*/
|
||||
public function upsert(string $tenantId, string $serviceId, CollectionResource $collection): void
|
||||
{
|
||||
$document = $collection->toCacheMeta();
|
||||
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne(
|
||||
['sid' => $serviceId, 'name' => $document['name']],
|
||||
['$set' => ['tid' => $tenantId, 'sid' => $serviceId, ...$document]],
|
||||
['upsert' => true],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the document of a mailbox.
|
||||
*/
|
||||
public function fetch(string $serviceId, string $name): ?array
|
||||
{
|
||||
return $this->dataStore->selectCollection(self::COLLECTION_NAME)->findOne([
|
||||
'sid' => $serviceId,
|
||||
'name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the documents of all mailboxes of a service.
|
||||
*
|
||||
* @return array<string, array> keyed by mailbox name
|
||||
*/
|
||||
public function list(string $serviceId): array
|
||||
{
|
||||
$cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find(['sid' => $serviceId]);
|
||||
|
||||
$list = [];
|
||||
foreach ($cursor as $document) {
|
||||
$list[(string) $document['name']] = $document;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the document of a mailbox.
|
||||
*/
|
||||
public function delete(string $serviceId, string $name): void
|
||||
{
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteOne([
|
||||
'sid' => $serviceId,
|
||||
'name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the documents of all mailboxes of a service.
|
||||
*/
|
||||
public function deleteByService(string $serviceId): void
|
||||
{
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteMany(['sid' => $serviceId]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXM\ProviderImap\Stores;
|
||||
|
||||
use KTXC\Db\DataStore;
|
||||
use KTXM\ProviderImap\Providers\EntityResource;
|
||||
|
||||
/**
|
||||
* IMAP Message Meta Store
|
||||
*
|
||||
* One MongoDB document per cached message in `provider_imap_mail_messages`,
|
||||
* keyed by (sid, mailbox, uidValidity, uid). Holds only what list, filter and
|
||||
* sort need; message content lives in the content store (message.json).
|
||||
*/
|
||||
class MessageStore
|
||||
{
|
||||
protected const COLLECTION_NAME = 'provider_imap_mail_messages';
|
||||
|
||||
public function __construct(
|
||||
protected readonly DataStore $dataStore,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create the collection indexes.
|
||||
*
|
||||
* MongoDB createIndex is idempotent when the name and specification match.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function ensureIndexes(): array
|
||||
{
|
||||
$collection = $this->dataStore->selectCollection(self::COLLECTION_NAME);
|
||||
|
||||
return [
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'mailbox' => 1, 'uidValidity' => 1, 'uid' => 1],
|
||||
['name' => 'messages_key', 'unique' => true]
|
||||
),
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'mailbox' => 1, 'received' => -1],
|
||||
['name' => 'messages_by_received']
|
||||
),
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'mailbox' => 1, 'sent' => -1],
|
||||
['name' => 'messages_by_sent']
|
||||
),
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'mailbox' => 1, 'flags' => 1],
|
||||
['name' => 'messages_by_flags']
|
||||
),
|
||||
$collection->createIndex(
|
||||
['sid' => 1, 'urid' => 1],
|
||||
['name' => 'messages_by_urid']
|
||||
),
|
||||
$collection->createIndex(
|
||||
['tid' => 1, 'sid' => 1],
|
||||
['name' => 'messages_by_tenant_service']
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or replace the meta document of a message.
|
||||
*
|
||||
* Fields owned by other writers (e.g. blobs) are left untouched.
|
||||
*/
|
||||
public function upsert(string $tenantId, string $serviceId, int $uidValidity, EntityResource $entity): void
|
||||
{
|
||||
$document = $entity->toCacheMeta();
|
||||
$key = [
|
||||
'sid' => $serviceId,
|
||||
'mailbox' => $document['mailbox'],
|
||||
'uidValidity' => $uidValidity,
|
||||
'uid' => $document['uid'],
|
||||
];
|
||||
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne(
|
||||
$key,
|
||||
['$set' => ['tid' => $tenantId, ...$key, ...$document]],
|
||||
['upsert' => true],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the meta document of a message.
|
||||
*/
|
||||
public function fetch(string $serviceId, string $mailbox, int $uidValidity, int $uid): ?array
|
||||
{
|
||||
return $this->dataStore->selectCollection(self::COLLECTION_NAME)->findOne([
|
||||
'sid' => $serviceId,
|
||||
'mailbox' => $mailbox,
|
||||
'uidValidity' => $uidValidity,
|
||||
'uid' => $uid,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the meta documents of several messages.
|
||||
*
|
||||
* @return array<int, array> keyed by UID; UIDs that are not cached are absent
|
||||
*/
|
||||
public function fetchMany(string $serviceId, string $mailbox, int $uidValidity, int ...$uids): array
|
||||
{
|
||||
if ($uids === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find([
|
||||
'sid' => $serviceId,
|
||||
'mailbox' => $mailbox,
|
||||
'uidValidity' => $uidValidity,
|
||||
'uid' => ['$in' => array_values($uids)],
|
||||
]);
|
||||
|
||||
$list = [];
|
||||
foreach ($cursor as $document) {
|
||||
$list[(int) $document['uid']] = $document;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the UIDs cached for a mailbox.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function uids(string $serviceId, string $mailbox, int $uidValidity): array
|
||||
{
|
||||
$cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find(
|
||||
['sid' => $serviceId, 'mailbox' => $mailbox, 'uidValidity' => $uidValidity],
|
||||
['projection' => ['uid' => 1]],
|
||||
);
|
||||
|
||||
$uids = [];
|
||||
foreach ($cursor as $document) {
|
||||
$uids[] = (int) $document['uid'];
|
||||
}
|
||||
return $uids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the flags of a message.
|
||||
*
|
||||
* @param string[] $flags set flags, e.g. ['seen', 'flagged']
|
||||
*/
|
||||
public function updateFlags(string $serviceId, string $mailbox, int $uidValidity, int $uid, array $flags): void
|
||||
{
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne(
|
||||
['sid' => $serviceId, 'mailbox' => $mailbox, 'uidValidity' => $uidValidity, 'uid' => $uid],
|
||||
['$set' => ['flags' => array_values($flags)]],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the meta documents of messages.
|
||||
*/
|
||||
public function delete(string $serviceId, string $mailbox, int $uidValidity, int ...$uids): void
|
||||
{
|
||||
if ($uids === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteMany([
|
||||
'sid' => $serviceId,
|
||||
'mailbox' => $mailbox,
|
||||
'uidValidity' => $uidValidity,
|
||||
'uid' => ['$in' => array_values($uids)],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all meta documents of a mailbox (any UIDVALIDITY).
|
||||
*/
|
||||
public function deleteByMailbox(string $serviceId, string $mailbox): void
|
||||
{
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteMany([
|
||||
'sid' => $serviceId,
|
||||
'mailbox' => $mailbox,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all meta documents of a service.
|
||||
*/
|
||||
public function deleteByService(string $serviceId): void
|
||||
{
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteMany(['sid' => $serviceId]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user