refactor: split service into ServiceBase and LiveService

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-10-07 21:49:31 -04:00
parent eea9b5630a
commit 4e12d41ee9
18 changed files with 649 additions and 516 deletions
+98 -22
View File
@@ -33,6 +33,8 @@ use KTXM\ProviderImap\Client\Protocol\IdentifierMode;
use KTXM\ProviderImap\Client\ImapException;
use KTXM\ProviderImap\Client\Protocol\Command\Argument\ListReturnOptions;
use KTXM\ProviderImap\Client\Mailbox;
use KTXM\ProviderImap\Providers\CollectionResource;
use KTXM\ProviderImap\Providers\EntityResource;
use KTXM\ProviderImap\Client\Message;
use KTXM\ProviderImap\Client\MessageAddress;
use KTXM\ProviderImap\Client\MessagePart;
@@ -49,7 +51,7 @@ use KTXF\Resource\Range\RangeAnchorType;
use KTXF\Resource\Range\RangeTally;
use KTXF\Resource\Sort\ISort;
use KTXF\Resource\BinaryResource;
use KTXM\ProviderImap\Providers\Service;
use KTXM\ProviderImap\Providers\ServiceBase;
use KTXM\ProviderImap\Smtp\Client as SmtpClient;
use RuntimeException;
@@ -69,7 +71,7 @@ class LiveMailService
private ?Mailbox $selected = null;
public function __construct(
private readonly Service $service,
private readonly ServiceBase $service,
) {}
/**
@@ -140,11 +142,48 @@ class LiveMailService
}
/**
* list of collections in remote storage
*
* @since Release 1.0.0
*/
public function collectionList(?string $location = null, IFilter|null $filter = null, ISort|null $sort = null, string $depth = '*'): Generator
* List the collections of the account, keyed by mailbox name.
*
* @return array<string, CollectionResource>
*/
public function collectionList(?string $location = null, ?IFilter $filter = null, ?ISort $sort = null): array
{
$list = [];
foreach ($this->mailboxList($location, $filter, $sort) as $name => $mailbox) {
$list[(string) $name] = $this->collectionResource($mailbox);
}
return $list;
}
/**
* Fetch a single collection by its mailbox name; null when it does not exist.
*/
public function collectionFetch(string $identifier): ?CollectionResource
{
$mailbox = $this->mailboxFetch($identifier);
return $mailbox === null ? null : $this->collectionResource($mailbox);
}
/**
* Hierarchy delimiter of the account, from `LIST "" ""` (no mailboxes are listed); "/" when unknown.
*/
public function collectionDelimiter(): string
{
foreach ($this->imapClient()->perform(new ListCommand('', '')) as $mailbox) {
return $mailbox->delimiter() ?: '/';
}
return '/';
}
/**
* List the IMAP mailboxes of the account (with status), keyed by name.
*
* @return Generator<string, Mailbox>
*/
public function mailboxList(?string $location = null, IFilter|null $filter = null, ISort|null $sort = null, string $depth = '*'): Generator
{
// Prepare location filter
if (!empty($location)) {
@@ -196,11 +235,11 @@ class LiveMailService
}
/**
* Fetch a single mailbox by its full name.
* Fetch a single IMAP mailbox (with status) by its full name.
*
* Returns null when no mailbox matching $identifier is found.
*/
public function collectionFetch(string $identifier): ?Mailbox
public function mailboxFetch(string $identifier): ?Mailbox
{
// LIST-STATUS (RFC 5819) returns the status with the LIST response in one round trip
$listStatus = $this->imapClient()->hasCapability('LIST-STATUS');
@@ -225,38 +264,38 @@ class LiveMailService
}
/**
* Create a new IMAP mailbox and return it.
* Create a new IMAP mailbox and return it as a collection resource.
*
* If the server-side LIST cannot confirm the new mailbox (e.g., immediate
* consistency), a lightweight stub resource is returned instead.
*/
public function collectionCreate(string $name): Mailbox
public function collectionCreate(string $name, ?string $delimiter = null): CollectionResource
{
$this->imapClient()->perform(new CreateCommand($name));
// Attempt to refetch the new mailbox from the server
$mailbox = $this->collectionFetch($name);
$mailbox = $this->mailboxFetch($name);
if ($mailbox === null) {
throw new ImapException('Failed to create mailbox: ' . $name);
}
return $mailbox;
return $this->collectionResource($mailbox, $delimiter);
}
/**
* Rename a mailbox and return the updated resource.
*/
public function collectionRename(string $oldName, string $newName): Mailbox
public function collectionRename(string $oldName, string $newName, ?string $delimiter = null): CollectionResource
{
$this->imapClient()->perform(new RenameCommand($oldName, $newName));
$mailbox = $this->collectionFetch($newName);
$mailbox = $this->mailboxFetch($newName);
if ($mailbox === null) {
throw new ImapException('Failed to rename mailbox: ' . $oldName . ' to ' . $newName);
}
return $mailbox;
return $this->collectionResource($mailbox, $delimiter);
}
/**
@@ -354,11 +393,36 @@ class LiveMailService
}
/**
* Retrieve a list of messages in a mailbox matching the given filter, sorted and paginated as requested.
* List the entities of a mailbox matching the given filter, sorted and paginated as requested.
*
* @return Message[] list of messages matching the filter, sorted and paginated as requested
* @return Generator<int, EntityResource> keyed by UID
*/
public function entityList(string $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null): Generator
{
foreach ($this->messageList($collection, $filter, $sort, $range) as $message) {
$entity = $this->entityResource($message, $collection);
yield (int) $entity->identifier() => $entity;
}
}
/**
* Fetch entities by UID (full TEXT).
*
* @return Generator<int, EntityResource> keyed by UID
*/
public function entityFetch(string $collection, int ...$uids): Generator
{
foreach ($this->messageFetch($collection, null, ...$uids) as $uid => $message) {
yield $uid => $this->entityResource($message, $collection);
}
}
/**
* List IMAP messages of a mailbox matching the given filter, sorted and paginated as requested.
*
* @return Generator<Message>
*/
public function messageList(string $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null): Generator
{
// text parts normally precede attachments, so capping TEXT avoids transferring attachment bytes
$options = FetchOptions::message()->withBodyText(self::LIST_BODY_TEXT_LIMIT);
@@ -387,16 +451,16 @@ class LiveMailService
return [];
}
yield from $this->entityFetch($collection, $options, ...$uids);
yield from $this->messageFetch($collection, $options, ...$uids);
}
/**
* Fetch one or more messages by UID and return EntityResource objects.
* Fetch one or more messages by UID as IMAP messages.
*
* @param int ...$uids
* @return Message[] keyed by UID
* @return Generator<int, Message> keyed by UID
*/
public function entityFetch(string $collection, ?FetchOptions $options = null, int ...$uids): Generator
public function messageFetch(string $collection, ?FetchOptions $options = null, int ...$uids): Generator
{
if (empty($uids)) {
return [];
@@ -1181,6 +1245,18 @@ class LiveMailService
return $normalized;
}
private function collectionResource(Mailbox $mailbox, ?string $delimiter = null): CollectionResource
{
return (new CollectionResource($this->service->provider(), $this->service->identifier()))
->fromImap($mailbox, ['delimiter' => $delimiter]);
}
private function entityResource(Message $message, string $collection): EntityResource
{
return (new EntityResource($this->service->provider(), $this->service->identifier()))
->fromImap($message, $collection);
}
/**
* Select a mailbox, reusing the current selection when possible.
*