generated from Nodarx/template
Merge pull request 'feat: mail entity download' (#32) from feat/mail-entity-download into main
Some checks failed
Renovate / renovate (push) Failing after 2m0s
Some checks failed
Renovate / renovate (push) Failing after 2m0s
Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
@@ -19,7 +19,7 @@ use KTXF\Mail\Service\ServiceCollectionMutableInterface;
|
|||||||
use KTXF\Mail\Service\ServiceEntityMutableInterface;
|
use KTXF\Mail\Service\ServiceEntityMutableInterface;
|
||||||
use KTXF\Mail\Service\ServiceConfigurableInterface;
|
use KTXF\Mail\Service\ServiceConfigurableInterface;
|
||||||
use KTXF\Mail\Service\ServiceMutableInterface;
|
use KTXF\Mail\Service\ServiceMutableInterface;
|
||||||
use KTXF\Mail\Service\DownloadResult;
|
use KTXF\Resource\BinaryResource;
|
||||||
use KTXF\Resource\Provider\ResourceServiceIdentityInterface;
|
use KTXF\Resource\Provider\ResourceServiceIdentityInterface;
|
||||||
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
|
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
|
||||||
use KTXF\Resource\Delta\Delta;
|
use KTXF\Resource\Delta\Delta;
|
||||||
@@ -42,6 +42,7 @@ use KTXF\Mail\Collection\CollectionRoles;
|
|||||||
use KTXF\Mail\Object\MessagePropertiesMutableInterface;
|
use KTXF\Mail\Object\MessagePropertiesMutableInterface;
|
||||||
use KTXF\Resource\Identifier\EntityIdentifierInterface;
|
use KTXF\Resource\Identifier\EntityIdentifierInterface;
|
||||||
use KTXM\ProviderImap\Providers\EntityResource;
|
use KTXM\ProviderImap\Providers\EntityResource;
|
||||||
|
use KTXM\ProviderImap\Client\Mailbox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMAP Mail Service
|
* IMAP Mail Service
|
||||||
@@ -62,23 +63,23 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
private array $auxiliary = [];
|
private array $auxiliary = [];
|
||||||
|
|
||||||
private array $serviceAbilities = [
|
private array $serviceAbilities = [
|
||||||
self::CAPABILITY_COLLECTION_LIST => true,
|
self::CAPABILITY_COLLECTION_LIST => true,
|
||||||
self::CAPABILITY_COLLECTION_LIST_FILTER => [
|
self::CAPABILITY_COLLECTION_LIST_FILTER => [
|
||||||
self::CAPABILITY_COLLECTION_FILTER_LABEL => 's:128:256:256',
|
self::CAPABILITY_COLLECTION_FILTER_LABEL => 's:128:256:256',
|
||||||
self::CAPABILITY_COLLECTION_FILTER_ROLE => 's:32:1:1',
|
self::CAPABILITY_COLLECTION_FILTER_ROLE => 's:32:1:1',
|
||||||
self::CAPABILITY_COLLECTION_FILTER_SUBSCRIBED => 'b:0:1:1',
|
self::CAPABILITY_COLLECTION_FILTER_SUBSCRIBED => 'b:0:1:1',
|
||||||
],
|
],
|
||||||
self::CAPABILITY_COLLECTION_LIST_SORT => [
|
self::CAPABILITY_COLLECTION_LIST_SORT => [
|
||||||
self::CAPABILITY_COLLECTION_SORT_LABEL,
|
self::CAPABILITY_COLLECTION_SORT_LABEL,
|
||||||
self::CAPABILITY_COLLECTION_SORT_RANK,
|
self::CAPABILITY_COLLECTION_SORT_RANK,
|
||||||
],
|
],
|
||||||
self::CAPABILITY_COLLECTION_EXTANT => true,
|
|
||||||
self::CAPABILITY_COLLECTION_FETCH => true,
|
self::CAPABILITY_COLLECTION_FETCH => true,
|
||||||
|
self::CAPABILITY_COLLECTION_EXTANT => true,
|
||||||
self::CAPABILITY_COLLECTION_CREATE => true,
|
self::CAPABILITY_COLLECTION_CREATE => true,
|
||||||
self::CAPABILITY_COLLECTION_UPDATE => true,
|
self::CAPABILITY_COLLECTION_UPDATE => true,
|
||||||
self::CAPABILITY_COLLECTION_DELETE => true,
|
self::CAPABILITY_COLLECTION_DELETE => true,
|
||||||
self::CAPABILITY_COLLECTION_MOVE => true,
|
self::CAPABILITY_COLLECTION_MOVE => true,
|
||||||
self::CAPABILITY_ENTITY_LIST => true,
|
self::CAPABILITY_ENTITY_LIST => true,
|
||||||
self::CAPABILITY_ENTITY_LIST_FILTER => [
|
self::CAPABILITY_ENTITY_LIST_FILTER => [
|
||||||
self::CAPABILITY_ENTITY_FILTER_FROM => 's:100:256:256',
|
self::CAPABILITY_ENTITY_FILTER_FROM => 's:100:256:256',
|
||||||
self::CAPABILITY_ENTITY_FILTER_TO => 's:100:256:256',
|
self::CAPABILITY_ENTITY_FILTER_TO => 's:100:256:256',
|
||||||
@@ -89,7 +90,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
self::CAPABILITY_ENTITY_FILTER_SIZE_MIN => 'i:0:16:16',
|
self::CAPABILITY_ENTITY_FILTER_SIZE_MIN => 'i:0:16:16',
|
||||||
self::CAPABILITY_ENTITY_FILTER_SIZE_MAX => 'i:0:32:32',
|
self::CAPABILITY_ENTITY_FILTER_SIZE_MAX => 'i:0:32:32',
|
||||||
],
|
],
|
||||||
self::CAPABILITY_ENTITY_LIST_SORT => [
|
self::CAPABILITY_ENTITY_LIST_SORT => [
|
||||||
self::CAPABILITY_ENTITY_SORT_FROM,
|
self::CAPABILITY_ENTITY_SORT_FROM,
|
||||||
self::CAPABILITY_ENTITY_SORT_TO,
|
self::CAPABILITY_ENTITY_SORT_TO,
|
||||||
self::CAPABILITY_ENTITY_SORT_SUBJECT,
|
self::CAPABILITY_ENTITY_SORT_SUBJECT,
|
||||||
@@ -100,25 +101,25 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
self::CAPABILITY_ENTITY_LIST_RANGE => [
|
self::CAPABILITY_ENTITY_LIST_RANGE => [
|
||||||
'tally' => ['absolute', 'relative']
|
'tally' => ['absolute', 'relative']
|
||||||
],
|
],
|
||||||
self::CAPABILITY_ENTITY_FETCH => true,
|
self::CAPABILITY_ENTITY_FETCH => true,
|
||||||
self::CAPABILITY_ENTITY_EXTANT => true,
|
self::CAPABILITY_ENTITY_EXTANT => true,
|
||||||
self::CAPABILITY_ENTITY_CREATE => false,
|
self::CAPABILITY_ENTITY_CREATE => false,
|
||||||
self::CAPABILITY_ENTITY_MODIFY => false,
|
self::CAPABILITY_ENTITY_MODIFY => false,
|
||||||
self::CAPABILITY_ENTITY_PATCH => true,
|
self::CAPABILITY_ENTITY_PATCH => true,
|
||||||
self::CAPABILITY_ENTITY_DELETE => true,
|
self::CAPABILITY_ENTITY_DELETE => true,
|
||||||
self::CAPABILITY_ENTITY_MOVE => true,
|
self::CAPABILITY_ENTITY_MOVE => true,
|
||||||
self::CAPABILITY_ENTITY_COPY => false,
|
self::CAPABILITY_ENTITY_COPY => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
private RemoteMailService $mailService;
|
private RemoteMailService $remoteService;
|
||||||
|
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
|
|
||||||
private function initialize(): void
|
private function initialize(): void
|
||||||
{
|
{
|
||||||
if (!isset($this->mailService)) {
|
if (!isset($this->remoteService)) {
|
||||||
$wrapper = RemoteService::freshClient($this);
|
$wrapper = RemoteService::freshClient($this);
|
||||||
$this->mailService = RemoteService::mailService($this, $wrapper);
|
$this->remoteService = RemoteService::mailService($this, $wrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +363,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
|
|
||||||
$list = [];
|
$list = [];
|
||||||
|
|
||||||
foreach ($this->mailService->collectionList($location, $filter, $sort) as $mailbox) {
|
foreach ($this->remoteService->collectionList($location, $filter, $sort) as $mailbox) {
|
||||||
$resource = $this->collectionFresh();
|
$resource = $this->collectionFresh();
|
||||||
$resource->fromImap($mailbox);
|
$resource->fromImap($mailbox);
|
||||||
$list[$mailbox->name()] = $resource;
|
$list[$mailbox->name()] = $resource;
|
||||||
@@ -389,7 +390,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
|
|
||||||
foreach ($identifiers as $identifier) {
|
foreach ($identifiers as $identifier) {
|
||||||
$key = (string) $identifier;
|
$key = (string) $identifier;
|
||||||
$result = $this->mailService->collectionFetch($key);
|
$result = $this->remoteService->collectionFetch($key);
|
||||||
|
|
||||||
$list[$key] = $result !== false;
|
$list[$key] = $result !== false;
|
||||||
}
|
}
|
||||||
@@ -401,7 +402,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
{
|
{
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
|
|
||||||
$mailbox = $this->mailService->collectionFetch((string) $identifier);
|
$mailbox = $this->remoteService->collectionFetch((string) $identifier);
|
||||||
if ($mailbox === null) {
|
if ($mailbox === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -430,13 +431,13 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
if ($target !== null) {
|
if ($target !== null) {
|
||||||
$path = $target->collection();
|
$path = $target->collection();
|
||||||
// Determine the hierarchy delimiter from an existing mailbox, default to '/'
|
// Determine the hierarchy delimiter from an existing mailbox, default to '/'
|
||||||
$mailboxes = iterator_to_array($this->mailService->collectionList(null, null, null, ''));
|
$mailboxes = iterator_to_array($this->remoteService->collectionList(null, null, null, ''));
|
||||||
$rootMailbox = $mailboxes === [] ? null : reset($mailboxes);
|
$rootMailbox = $mailboxes === [] ? null : reset($mailboxes);
|
||||||
$delimiter = $rootMailbox === false ? '/' : ($rootMailbox?->delimiter() ?? '/');
|
$delimiter = $rootMailbox === false ? '/' : ($rootMailbox?->delimiter() ?? '/');
|
||||||
$label = rtrim((string) $path, $delimiter) . $delimiter . ltrim($label, $delimiter);
|
$label = rtrim((string) $path, $delimiter) . $delimiter . ltrim($label, $delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailbox = $this->mailService->collectionCreate($label);
|
$mailbox = $this->remoteService->collectionCreate($label);
|
||||||
|
|
||||||
$collection = $this->collectionFresh();
|
$collection = $this->collectionFresh();
|
||||||
$collection->fromImap($mailbox, ['delimiter' => $delimiter ?? null]);
|
$collection->fromImap($mailbox, ['delimiter' => $delimiter ?? null]);
|
||||||
@@ -456,7 +457,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
// In IMAP, "update" = rename to the new label
|
// In IMAP, "update" = rename to the new label
|
||||||
$oldPath = (string) $target->collection();
|
$oldPath = (string) $target->collection();
|
||||||
$newName = $properties->getLabel();
|
$newName = $properties->getLabel();
|
||||||
$mailbox = $this->mailService->collectionRename($oldPath, $newName);
|
$mailbox = $this->remoteService->collectionRename($oldPath, $newName);
|
||||||
|
|
||||||
$collection = $this->collectionFresh();
|
$collection = $this->collectionFresh();
|
||||||
$collection->fromImap($mailbox);
|
$collection->fromImap($mailbox);
|
||||||
@@ -476,14 +477,14 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
|
|
||||||
// Move to target collection (e.g. Trash) instead of deleting
|
// Move to target collection (e.g. Trash) instead of deleting
|
||||||
if ($deleteMode === 'soft' && $deleteTarget !== null) {
|
if ($deleteMode === 'soft' && $deleteTarget !== null) {
|
||||||
return $this->collectionMove($target, new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget));
|
return $this->collectionMove(new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget), $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($deleteMode === 'soft' && $deleteTarget === null) {
|
if ($deleteMode === 'soft' && $deleteTarget === null) {
|
||||||
$filter = $this->collectionListFilter();
|
$filter = $this->collectionListFilter();
|
||||||
$filter->condition('role', CollectionRoles::Trash->value);
|
$filter->condition('role', CollectionRoles::Trash->value);
|
||||||
|
|
||||||
$mailboxes = iterator_to_array($this->mailService->collectionList(null, $filter, null));
|
$mailboxes = iterator_to_array($this->remoteService->collectionList(null, $filter, null));
|
||||||
if (empty($mailboxes)) {
|
if (empty($mailboxes)) {
|
||||||
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
||||||
}
|
}
|
||||||
@@ -499,7 +500,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
|
|
||||||
$result = match ($deleteMode) {
|
$result = match ($deleteMode) {
|
||||||
'soft' => $this->collectionMove(new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget), $target),
|
'soft' => $this->collectionMove(new CollectionIdentifier($target->provider(), $target->service(), $deleteTarget), $target),
|
||||||
'hard' => $this->mailService->collectionDestroy((string) $target->collection()),
|
'hard' => $this->remoteService->collectionDestroy((string) $target->collection()),
|
||||||
};
|
};
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
@@ -508,8 +509,8 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
{
|
{
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
|
|
||||||
$sourceMailbox = $this->mailService->collectionFetch((string) $source->collection());
|
$sourceMailbox = $this->remoteService->collectionFetch((string) $source->collection());
|
||||||
$targetMailbox = $this->mailService->collectionFetch((string) $target->collection());
|
$targetMailbox = $this->remoteService->collectionFetch((string) $target->collection());
|
||||||
if ($sourceMailbox === null) {
|
if ($sourceMailbox === null) {
|
||||||
throw new \RuntimeException('Source collection not found for move operation');
|
throw new \RuntimeException('Source collection not found for move operation');
|
||||||
}
|
}
|
||||||
@@ -521,8 +522,11 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
$targetDelimiter = $targetMailbox->delimiter() ?? '/';
|
$targetDelimiter = $targetMailbox->delimiter() ?? '/';
|
||||||
|
|
||||||
$extantPath = $sourceMailbox->name();
|
$extantPath = $sourceMailbox->name();
|
||||||
$freshPath = rtrim($targetMailbox->name(), $targetDelimiter) . $targetDelimiter . end(explode($sourceDelimiter, $extantPath));
|
$extantPathLeafs = explode($sourceDelimiter, rtrim($extantPath, $sourceDelimiter));
|
||||||
$mutatedMailbox = $this->mailService->collectionRename($extantPath, $freshPath);
|
|
||||||
|
$freshPath = rtrim($targetMailbox->name(), $targetDelimiter) . $targetDelimiter . end($extantPathLeafs);
|
||||||
|
|
||||||
|
$mutatedMailbox = $this->remoteService->collectionRename($extantPath, $freshPath);
|
||||||
|
|
||||||
$collection = $this->collectionFresh();
|
$collection = $this->collectionFresh();
|
||||||
$collection->fromImap($mutatedMailbox, ['delimiter' => $targetDelimiter]);
|
$collection->fromImap($mutatedMailbox, ['delimiter' => $targetDelimiter]);
|
||||||
@@ -538,7 +542,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
{
|
{
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
|
|
||||||
foreach ($this->mailService->entityList((string) $collection, $filter, $sort, $range) as $identifier => $message) {
|
foreach ($this->remoteService->entityList((string) $collection, $filter, $sort, $range) as $identifier => $message) {
|
||||||
$resource = $this->entityFresh();
|
$resource = $this->entityFresh();
|
||||||
$resource->fromImap($message, $collection);
|
$resource->fromImap($message, $collection);
|
||||||
yield $resource->urn() => $resource;
|
yield $resource->urn() => $resource;
|
||||||
@@ -576,7 +580,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
|
|
||||||
foreach ($identifiers as $collection => $entities) {
|
foreach ($identifiers as $collection => $entities) {
|
||||||
$uids = array_keys($entities);
|
$uids = array_keys($entities);
|
||||||
foreach ($this->mailService->entityFetch((string) $collection, ...$uids) as $uid => $message) {
|
foreach ($this->remoteService->entityFetch((string) $collection, ...$uids) as $uid => $message) {
|
||||||
$resource = $this->entityFresh();
|
$resource = $this->entityFresh();
|
||||||
$resource->fromImap($message, $collection);
|
$resource->fromImap($message, $collection);
|
||||||
yield $resource->urn() => $resource;
|
yield $resource->urn() => $resource;
|
||||||
@@ -584,6 +588,16 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function entityDownload(EntityIdentifierInterface $target, array|null $part): BinaryResource {
|
||||||
|
$this->initialize();
|
||||||
|
|
||||||
|
$collection = $target->collection();
|
||||||
|
$uid = (int) $target->entity();
|
||||||
|
$partId = isset($part['partId']) ? (string) $part['partId'] : null;
|
||||||
|
|
||||||
|
return $this->remoteService->entityDownload($collection, $uid, $partId);
|
||||||
|
}
|
||||||
|
|
||||||
public function entityDelta(string|int $collection, string $signature, string $detail = 'ids'): Delta
|
public function entityDelta(string|int $collection, string $signature, string $detail = 'ids'): Delta
|
||||||
{
|
{
|
||||||
return new Delta(signature: $signature);
|
return new Delta(signature: $signature);
|
||||||
@@ -593,7 +607,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
{
|
{
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
|
|
||||||
$allUids = $this->mailService->entityList((string) $collection);
|
$allUids = $this->remoteService->entityList((string) $collection);
|
||||||
$uidSet = array_flip($allUids); // int[] → [uid => index]
|
$uidSet = array_flip($allUids); // int[] → [uid => index]
|
||||||
$extant = [];
|
$extant = [];
|
||||||
foreach ($identifiers as $id) {
|
foreach ($identifiers as $id) {
|
||||||
@@ -640,7 +654,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mutations = $this->mailService->entityPatch($targetCollection, $flagsAdd, $flagsRemove, ...$uids);
|
$mutations = $this->remoteService->entityPatch($targetCollection, $flagsAdd, $flagsRemove, ...$uids);
|
||||||
|
|
||||||
foreach ($uids as $uid) {
|
foreach ($uids as $uid) {
|
||||||
$list[(string)$targetIdentifiers[$uid]] = ['disposition' => 'patched'];
|
$list[(string)$targetIdentifiers[$uid]] = ['disposition' => 'patched'];
|
||||||
@@ -671,17 +685,18 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
$filter = $this->collectionListFilter();
|
$filter = $this->collectionListFilter();
|
||||||
$filter->condition('role', CollectionRoles::Trash->value);
|
$filter->condition('role', CollectionRoles::Trash->value);
|
||||||
|
|
||||||
$mailboxes = iterator_to_array($this->mailService->collectionList(null, $filter, null));
|
/** @var Mailbox[] $mailboxes */
|
||||||
|
$mailboxes = iterator_to_array($this->remoteService->collectionList(null, $filter, null));
|
||||||
if (empty($mailboxes)) {
|
if (empty($mailboxes)) {
|
||||||
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
||||||
}
|
}
|
||||||
|
|
||||||
$rootMailbox = reset($mailboxes);
|
$targetMailbox = reset($mailboxes);
|
||||||
if ($rootMailbox === false) {
|
if ($targetMailbox === false) {
|
||||||
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
throw new \RuntimeException('No Trash collection configured or found for deletion');
|
||||||
}
|
}
|
||||||
|
|
||||||
$deleteTargetNative = $rootMailbox->name();
|
$deleteTargetNative = $targetMailbox->name();
|
||||||
$deleteTargetIdentifier = new CollectionIdentifier($this->provider(), (string) $this->identifier(), $deleteTargetNative);
|
$deleteTargetIdentifier = new CollectionIdentifier($this->provider(), (string) $this->identifier(), $deleteTargetNative);
|
||||||
} else {
|
} else {
|
||||||
$deleteTargetNative = $deleteTarget;
|
$deleteTargetNative = $deleteTarget;
|
||||||
@@ -703,8 +718,8 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
$uids = array_keys($sourceEntities);
|
$uids = array_keys($sourceEntities);
|
||||||
|
|
||||||
$mutations = match ($deleteMode) {
|
$mutations = match ($deleteMode) {
|
||||||
'soft' => $this->mailService->entityMove($deleteTargetNative, $sourceCollection, ...$uids),
|
'soft' => $this->remoteService->entityMove($deleteTargetNative, $sourceCollection, ...$uids),
|
||||||
'hard' => $this->mailService->entityDestroy($sourceCollection, ...$uids),
|
'hard' => $this->remoteService->entityDestroy($sourceCollection, ...$uids),
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach ($uids as $uid) {
|
foreach ($uids as $uid) {
|
||||||
@@ -720,39 +735,6 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityCopy(CollectionIdentifier $target, EntityIdentifier ...$sources): array
|
|
||||||
{
|
|
||||||
// validate target belongs to this service
|
|
||||||
if ($target->provider() !== $this->provider() || $target->service() !== $this->identifier()) {
|
|
||||||
throw new \InvalidArgumentException('Target collection does not belong to this service: ' . $target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate identifiers and group by collection
|
|
||||||
$sources = $this->groupEntitiesByCollection(...$sources);
|
|
||||||
|
|
||||||
// copy entities on remote store and construct result map
|
|
||||||
$this->initialize();
|
|
||||||
|
|
||||||
$list = [];
|
|
||||||
|
|
||||||
foreach ($sources as $sourceCollection => $sourceEntities) {
|
|
||||||
$uids = array_keys($sourceEntities);
|
|
||||||
|
|
||||||
$mutations = $this->mailService->entityCopy($target->collection(), $sourceCollection, ...$uids);
|
|
||||||
|
|
||||||
foreach ($uids as $uid) {
|
|
||||||
$mutatedUid = $mutations[$uid] ?? null;
|
|
||||||
$list[(string)$sourceEntities[$uid]] = [
|
|
||||||
'disposition' => $mutatedUid !== null ? 'copied' : 'error',
|
|
||||||
'destination' => $target,
|
|
||||||
'mutation' => $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $target->collection(), $mutatedUid) : null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function entityMove(CollectionIdentifier $target, EntityIdentifier ...$sources): array
|
public function entityMove(CollectionIdentifier $target, EntityIdentifier ...$sources): array
|
||||||
{
|
{
|
||||||
// validate target belongs to this service
|
// validate target belongs to this service
|
||||||
@@ -771,7 +753,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
foreach ($sources as $sourceCollection => $sourceEntities) {
|
foreach ($sources as $sourceCollection => $sourceEntities) {
|
||||||
$uids = array_keys($sourceEntities);
|
$uids = array_keys($sourceEntities);
|
||||||
|
|
||||||
$mutations = $this->mailService->entityMove($target->collection(), $sourceCollection, ...$uids);
|
$mutations = $this->remoteService->entityMove($target->collection(), $sourceCollection, ...$uids);
|
||||||
|
|
||||||
foreach ($uids as $uid) {
|
foreach ($uids as $uid) {
|
||||||
$mutatedUid = $mutations[$uid] ?? null;
|
$mutatedUid = $mutations[$uid] ?? null;
|
||||||
@@ -780,20 +762,44 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
|||||||
'destination' => $target,
|
'destination' => $target,
|
||||||
'mutation' => $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $target->collection(), $mutatedUid) : null,
|
'mutation' => $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $target->collection(), $mutatedUid) : null,
|
||||||
];
|
];
|
||||||
|
unset($sourceEntities[$uid]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityDownload(EntityIdentifier $target, array|null $part): DownloadResult {
|
public function entityCopy(CollectionIdentifier $target, EntityIdentifier ...$sources): array
|
||||||
|
{
|
||||||
|
// validate target belongs to this service
|
||||||
|
if ($target->provider() !== $this->provider() || $target->service() !== $this->identifier()) {
|
||||||
|
throw new \InvalidArgumentException('Target collection does not belong to this service: ' . $target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate identifiers and group by collection
|
||||||
|
$sources = $this->groupEntitiesByCollection(...$sources);
|
||||||
|
|
||||||
|
// copy entities on remote store and construct result map
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
|
|
||||||
$collection = $target->collection();
|
$list = [];
|
||||||
$uid = (int) $target->entity();
|
|
||||||
$partId = isset($part['partId']) ? (string) $part['partId'] : null;
|
foreach ($sources as $sourceCollection => $sourceEntities) {
|
||||||
|
$uids = array_keys($sourceEntities);
|
||||||
|
|
||||||
return $this->mailService->entityDownload($collection, $uid, $partId);
|
$mutations = $this->remoteService->entityCopy($target->collection(), $sourceCollection, ...$uids);
|
||||||
|
|
||||||
|
foreach ($uids as $uid) {
|
||||||
|
$mutatedUid = $mutations[$uid] ?? null;
|
||||||
|
$list[(string)$sourceEntities[$uid]] = [
|
||||||
|
'disposition' => $mutatedUid !== null ? 'copied' : 'error',
|
||||||
|
'destination' => $target,
|
||||||
|
'mutation' => $mutatedUid !== null ? new EntityIdentifier($this->provider(), $this->identifier(), $target->collection(), $mutatedUid) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function groupEntitiesByCollection(EntityIdentifier ...$identifiers): array
|
private function groupEntitiesByCollection(EntityIdentifier ...$identifiers): array
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ use KTXF\Resource\Range\IRangeTally;
|
|||||||
use KTXF\Resource\Range\RangeAnchorType;
|
use KTXF\Resource\Range\RangeAnchorType;
|
||||||
use KTXF\Resource\Range\RangeTally;
|
use KTXF\Resource\Range\RangeTally;
|
||||||
use KTXF\Resource\Sort\ISort;
|
use KTXF\Resource\Sort\ISort;
|
||||||
use KTXF\Mail\Service\DownloadResult;
|
use KTXF\Resource\BinaryResource;
|
||||||
use KTXM\ProviderImap\Providers\CollectionResource;
|
|
||||||
use KTXM\ProviderImap\Providers\EntityResource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMAP Remote Mail Service
|
* IMAP Remote Mail Service
|
||||||
@@ -307,7 +305,7 @@ class RemoteMailService
|
|||||||
* @param int $uid Message UID
|
* @param int $uid Message UID
|
||||||
* @param string|null $partId MIME section (e.g. '1', '1.2'); null = full RFC 822
|
* @param string|null $partId MIME section (e.g. '1', '1.2'); null = full RFC 822
|
||||||
*/
|
*/
|
||||||
public function entityDownload(string $collection, int $uid, ?string $partId = null): DownloadResult
|
public function entityDownload(string $collection, int $uid, ?string $partId = null): BinaryResource
|
||||||
{
|
{
|
||||||
$this->client->perform(new SelectCommand($collection, true));
|
$this->client->perform(new SelectCommand($collection, true));
|
||||||
|
|
||||||
@@ -338,7 +336,7 @@ class RemoteMailService
|
|||||||
$encoding
|
$encoding
|
||||||
);
|
);
|
||||||
|
|
||||||
return new DownloadResult($filename, $mimeType, $stream);
|
return new BinaryResource($filename, $mimeType, $stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findBodyPart(MessagePart $root, string $partId): ?MessagePart
|
private function findBodyPart(MessagePart $root, string $partId): ?MessagePart
|
||||||
@@ -537,6 +535,36 @@ class RemoteMailService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function entityCopy(string $targetCollection, string $sourceCollection, int ...$uids): array
|
||||||
|
{
|
||||||
|
if (empty($uids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client->perform(new SelectCommand($sourceCollection, false));
|
||||||
|
$response = $this->client->perform(new CopyCommand(
|
||||||
|
FetchTarget::uid(SequenceSet::items(...array_values($uids))),
|
||||||
|
$targetCollection,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$response->isOk()) {
|
||||||
|
throw new ImapException('Failed to copy messages: ' . implode(', ', $response->responseCodes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct operation result as a map of source UID to boolean or destination UID, depending on server support
|
||||||
|
$map = $response->copyUidMap();
|
||||||
|
if ($map === []) {
|
||||||
|
$result = array_fill_keys(array_map('strval', $uids), true);
|
||||||
|
} else {
|
||||||
|
$result = array_fill_keys(array_map('strval', $uids), false);
|
||||||
|
foreach ($uids as $uid) {
|
||||||
|
$result[$uid] = $map[$uid] ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
private function buildEntitySearchCriteria(?IFilter $filter): SearchCriteriaBuilder
|
private function buildEntitySearchCriteria(?IFilter $filter): SearchCriteriaBuilder
|
||||||
{
|
{
|
||||||
if ($filter === null || $filter->conditions() === []) {
|
if ($filter === null || $filter->conditions() === []) {
|
||||||
|
|||||||
Reference in New Issue
Block a user