generated from Nodarx/template
feat: harmonize mailbox list and track harmonization state
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXM\ProviderImap\Service\Cache;
|
||||
|
||||
use KTXM\ProviderImap\Providers\CollectionResource;
|
||||
use KTXM\ProviderImap\Providers\Service;
|
||||
use KTXM\ProviderImap\Service\Live\LiveMailService;
|
||||
use KTXM\ProviderImap\Stores\MailboxStore;
|
||||
use KTXM\ProviderImap\Stores\MessageFileStore;
|
||||
use KTXM\ProviderImap\Stores\MessageStore;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Brings the cache of one service in line with its IMAP server.
|
||||
*
|
||||
* Created by the container without a service; call for() to bind one. Only ever
|
||||
* talks to LiveMailService, never to the cache it maintains.
|
||||
*/
|
||||
class HarmonizationService
|
||||
{
|
||||
private ?Service $service = null;
|
||||
private ?LiveMailService $live = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly MailboxStore $mailboxStore,
|
||||
private readonly MessageStore $messageStore,
|
||||
private readonly MessageFileStore $fileStore,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Bind a service; returns a new instance so the shared one stays unbound.
|
||||
*/
|
||||
public function for(Service $service, ?LiveMailService $live = null): static
|
||||
{
|
||||
$bound = clone $this;
|
||||
$bound->service = $service;
|
||||
$bound->live = $live ?? new LiveMailService($service);
|
||||
|
||||
return $bound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Harmonize the mailbox list of the service with the server.
|
||||
*
|
||||
* New mailboxes are added (not yet harmonized), existing ones updated, and
|
||||
* mailboxes gone from the server purged with their messages. A rename shows
|
||||
* up as one mailbox gone and one new.
|
||||
*
|
||||
* @return array{added: string[], updated: string[], removed: string[]}
|
||||
*/
|
||||
public function harmonizeMailboxes(): array
|
||||
{
|
||||
[$service, $live] = $this->bound();
|
||||
$tenantId = (string) $service->tenantIdentifier();
|
||||
$serviceId = (string) $service->identifier();
|
||||
|
||||
$remote = [];
|
||||
foreach ($live->collectionList() as $name => $mailbox) {
|
||||
$remote[(string) $name] = (new CollectionResource($service->provider(), $serviceId))->fromImap($mailbox);
|
||||
}
|
||||
$cached = $this->mailboxStore->list($serviceId);
|
||||
|
||||
$result = ['added' => [], 'updated' => [], 'removed' => []];
|
||||
|
||||
foreach ($remote as $name => $collection) {
|
||||
$this->mailboxStore->upsert($tenantId, $serviceId, $collection);
|
||||
$result[isset($cached[$name]) ? 'updated' : 'added'][] = $name;
|
||||
}
|
||||
|
||||
// every IMAP account has an INBOX; a list without one is incomplete, so nothing is purged
|
||||
if (!self::containsInbox(array_keys($remote))) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach (array_keys(array_diff_key($cached, $remote)) as $name) {
|
||||
$this->purgeMailbox($tenantId, $serviceId, (string) $name);
|
||||
$result['removed'][] = (string) $name;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a mailbox and everything cached for it: meta documents first, then files, then the mailbox.
|
||||
*/
|
||||
private function purgeMailbox(string $tenantId, string $serviceId, string $name): void
|
||||
{
|
||||
$this->messageStore->deleteByMailbox($serviceId, $name);
|
||||
$this->fileStore->deleteByMailbox($tenantId, $serviceId, $name);
|
||||
$this->mailboxStore->delete($serviceId, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: Service, 1: LiveMailService}
|
||||
*/
|
||||
private function bound(): array
|
||||
{
|
||||
if ($this->service === null || $this->live === null) {
|
||||
throw new LogicException('HarmonizationService is not bound to a service; call for() first');
|
||||
}
|
||||
|
||||
return [$this->service, $this->live];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $names
|
||||
*/
|
||||
private static function containsInbox(array $names): bool
|
||||
{
|
||||
foreach ($names as $name) {
|
||||
if (strcasecmp($name, 'INBOX') === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user