Merge pull request 'refactor: service entity list and fetch' (#27) from refactor/entity-list-fetch into main
Some checks failed
Renovate / renovate (push) Failing after 1m22s

Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
2026-05-17 21:50:00 +00:00
3 changed files with 57 additions and 39 deletions

View File

@@ -28,9 +28,9 @@ final class StoreCommand implements CommandInterface
*/
public function __construct(
FetchTarget|string|SequenceSet|null $target = null,
private readonly array $flags = [],
private readonly string $action = '',
private readonly bool $silent = true,
private array $flags = [],
private string $action = '',
private bool $silent = true,
) {
$resolvedTarget = match (true) {
$target instanceof FetchTarget => $target,

View File

@@ -27,7 +27,6 @@ use KTXM\ProviderImap\Stores\ServiceStore;
class Provider implements ProviderBaseInterface, ProviderServiceMutateInterface, ProviderServiceDiscoverInterface, ProviderServiceTestInterface
{
public const JSON_TYPE = ProviderBaseInterface::JSON_TYPE;
protected const PROVIDER_IDENTIFIER = 'imap';
protected const PROVIDER_LABEL = 'IMAP Mail Provider';
protected const PROVIDER_DESCRIPTION = 'Provides mail services via the IMAP protocol';

View File

@@ -11,7 +11,6 @@ namespace KTXM\ProviderImap\Providers;
use Generator;
use KTXF\Mail\Collection\CollectionBaseInterface;
use KTXF\Mail\Collection\CollectionMutableInterface;
use KTXF\Mail\Collection\CollectionPropertiesBaseInterface;
use KTXF\Mail\Object\Address;
use KTXF\Mail\Object\AddressInterface;
@@ -40,6 +39,7 @@ use KTXM\ProviderImap\Service\Remote\RemoteService;
use KTXM\ProviderImap\Providers\CollectionResource;
use KTXF\Mail\Collection\CollectionRoles;
use KTXF\Mail\Object\MessagePropertiesMutableInterface;
use KTXF\Resource\Identifier\EntityIdentifierInterface;
use KTXM\ProviderImap\Providers\EntityResource;
/**
@@ -498,7 +498,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
}
$result = match ($deleteMode) {
'soft' => $this->collectionMove($target, new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget)),
'soft' => $this->collectionMove(new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget), $target),
'hard' => $this->mailService->collectionDestroy((string) $target->collection()),
};
return $result;
@@ -520,8 +520,9 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
$sourceDelimiter = $sourceMailbox->delimiter() ?? '/';
$targetDelimiter = $targetMailbox->delimiter() ?? '/';
$targetPath = rtrim($targetMailbox->name(), $targetDelimiter) . $targetDelimiter . end(explode($sourceDelimiter, $sourceMailbox->name()));
$mutatedMailbox = $this->mailService->collectionRename($sourceMailbox->name(), $targetPath);
$extantPath = $sourceMailbox->name();
$freshPath = rtrim($targetMailbox->name(), $targetDelimiter) . $targetDelimiter . end(explode($sourceDelimiter, $extantPath));
$mutatedMailbox = $this->mailService->collectionRename($extantPath, $freshPath);
$collection = $this->collectionFresh();
$collection->fromImap($mutatedMailbox, ['delimiter' => $targetDelimiter]);
@@ -530,9 +531,9 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
// ── Entity operations ─────────────────────────────────────────────────────
public function entityList(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array
public function entityListBulk(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array
{
return iterator_to_array($this->entityList((string) $collection, $filter, $sort, $range), true);
return iterator_to_array($this->entityListStream((string) $collection, $filter, $sort, $range), true);
}
public function entityListStream(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator
@@ -542,7 +543,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
foreach ($this->mailService->entityList((string) $collection, $filter, $sort, $range) as $identifier => $message) {
$resource = $this->entityFresh();
$resource->fromImap($message, $collection);
yield $identifier => $resource;
yield $resource->urn() => $resource;
}
}
@@ -564,12 +565,25 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
};
}
public function entityFetch(string|int $collection, string|int ...$identifiers): array
public function entityFetchBulk(EntityIdentifierInterface ...$identifiers): array
{
return iterator_to_array($this->entityFetchStream(...$identifiers), true);
}
public function entityFetchStream(EntityIdentifierInterface ...$identifiers): Generator
{
$this->initialize();
$uids = array_map('intval', $identifiers);
return $this->mailService->entityFetch((string) $collection, ...$uids);
$identifiers = $this->groupEntitiesByCollection(...$identifiers);
foreach ($identifiers as $collection => $entities) {
$uids = array_keys($entities);
foreach ($this->mailService->entityFetch((string) $collection, ...$uids) as $uid => $message) {
$resource = $this->entityFresh();
$resource->fromImap($message, $collection);
yield $resource->urn() => $resource;
}
}
}
public function entityDelta(string|int $collection, string $signature, string $detail = 'ids'): Delta
@@ -605,6 +619,29 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
throw new \RuntimeException('Entity modification is not supported in this service');
}
public function entityPatch(MessagePropertiesMutableInterface $properties, EntityIdentifier ...$targets): array
{
// validate identifiers and group by collection
$targets = $this->groupEntitiesByCollection(...$targets);
// move entities on remote store and construct result map
$this->initialize();
$list = [];
foreach ($targets as $targetCollection => $targetIdentifiers) {
$uids = array_keys($targetIdentifiers);
$mutations = $this->mailService->entityPatch($targetCollection, $properties, ...$uids);
foreach ($uids as $uid) {
$list[(string)$targetIdentifiers[$uid]] = ['disposition' => 'patched'];
}
}
return $list;
}
public function entityDelete(EntityIdentifier ...$targets): array
{
// validate identifiers and group by collection
@@ -643,6 +680,11 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
$deleteTargetIdentifier = new CollectionIdentifier($this->provider(), (string) $this->identifier(), $deleteTargetNative);
}
// if all targets are already in the delete target collection, we should hard delete instead of moving to avoid duplicates in the trash
if (array_keys($targets) === [$deleteTargetNative]) {
$deleteMode = 'hard';
}
// entities need to be moved or deleted by collection
$list = [];
foreach ($targets as $sourceCollection => $sourceEntities) {
@@ -658,11 +700,11 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
};
foreach ($uids as $uid) {
$mutatedUid = $mutations[$uid] ?? null;
$mutatedUid = !isset($mutations[$uid]) || $mutations[$uid] === true ? null : $mutations[$uid];
$list[(string)$sourceEntities[$uid]] = [
'disposition' => $deleteMode === 'soft' ? 'moved' : 'deleted',
'destination' => $deleteMode === 'soft' ? $deleteTargetIdentifier : null,
'mutation' => $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $deleteTargetIdentifier->collection(), $mutatedUid) : null,
'mutation' => $deleteMode === 'soft' && $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $deleteTargetIdentifier->collection(), $mutatedUid) : null,
];
}
}
@@ -670,29 +712,6 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
return $list;
}
public function entityPatch(MessagePropertiesMutableInterface $properties, EntityIdentifier ...$targets): array
{
// validate identifiers and group by collection
$targets = $this->groupEntitiesByCollection(...$targets);
// move entities on remote store and construct result map
$this->initialize();
$list = [];
foreach ($targets as $targetCollection => $targetIdentifiers) {
$uids = array_keys($targetIdentifiers);
$mutations = $this->mailService->entityPatch($targetCollection, $properties, ...$uids);
foreach ($uids as $uid) {
$list[(string)$targetIdentifiers[$uid]] = ['disposition' => 'patched'];
}
}
return $list;
}
public function entityCopy(CollectionIdentifier $target, EntityIdentifier ...$sources): array
{
// validate target belongs to this service