diff --git a/lib/Providers/Mail/Service.php b/lib/Providers/Mail/Service.php
index 39db91f..5d5a683 100644
--- a/lib/Providers/Mail/Service.php
+++ b/lib/Providers/Mail/Service.php
@@ -11,6 +11,7 @@ namespace KTXM\ProviderJmapc\Providers\Mail;
use Generator;
use KTXF\Mail\Collection\CollectionBaseInterface;
+use KTXF\Mail\Collection\CollectionRoles;
use KTXF\Mail\Collection\CollectionMutableInterface;
use KTXF\Mail\Object\Address;
use KTXF\Mail\Object\AddressInterface;
@@ -24,6 +25,8 @@ use KTXF\Resource\Provider\ResourceServiceLocationInterface;
use KTXF\Resource\Delta\Delta;
use KTXF\Resource\Filter\Filter;
use KTXF\Resource\Filter\IFilter;
+use KTXF\Resource\Identifier\CollectionIdentifier;
+use KTXF\Resource\Identifier\EntityIdentifier;
use KTXF\Resource\Range\IRange;
use KTXF\Resource\Range\Range;
use KTXF\Resource\Range\RangeType;
@@ -95,6 +98,8 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
self::CAPABILITY_ENTITY_DELTA => true,
self::CAPABILITY_ENTITY_EXTANT => true,
self::CAPABILITY_ENTITY_FETCH => true,
+ //self::CAPABILITY_ENTITY_DELETE => true,
+ //self::CAPABILITY_ENTITY_MOVE => true,
];
private readonly RemoteMailService $mailService;
@@ -535,12 +540,75 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
return $entities;
}
- public function entityMove(string|int $target, array $sources): array
+ public function entityDelete(EntityIdentifier ...$identifiers): array
{
+ // validate identifiers and construct ID list
+ $ids = [];
+ foreach ($identifiers as $identifier) {
+ if ($identifier->provider() !== $this->provider() || $identifier->service() !== (string)$this->identifier()) {
+ throw new \InvalidArgumentException('Entity identifier does not belong to this service: ' . (string)$identifier);
+ }
+ $ids[] = $identifier->entity();
+ }
+
$this->initialize();
- $result = $this->mailService->entityMove($target, $sources);
+ $deleteMode = strtolower(trim((string) ($this->getAuxiliary()['deleteMode'] ?? 'soft')));
+ if ($deleteMode === 'soft') {
+ $deleteDestination = $this->getAuxiliary()['deleteDestination'] ?? null;
+ if (is_string($deleteDestination) || is_int($deleteDestination)) {
+ $deleteDestination = trim((string) $deleteDestination);
+ if ($deleteDestination === '') {
+ $deleteDestination = null;
+ }
+ } else {
+ $deleteDestination = null;
+ }
- return $result;
+ if ($deleteDestination === null) {
+ $filter = $this->collectionListFilter();
+ $filter->condition(self::CAPABILITY_COLLECTION_FILTER_ROLE, CollectionRoles::Trash->value);
+
+ foreach ($this->collectionList(null, $filter) as $collection) {
+ if (!$collection instanceof CollectionBaseInterface) {
+ continue;
+ }
+
+ if ($collection->getProperties()->getRole() === CollectionRoles::Trash) {
+ $deleteDestination = (string) $collection->identifier();
+ break;
+ }
+ }
+ }
+
+ if ($deleteDestination === null) {
+ throw new \RuntimeException('Soft delete is enabled but no trash collection could be resolved.');
+ }
+
+ return $this->mailService->entityMove($deleteDestination, ...$ids);
+ }
+
+ return $this->mailService->entityDelete(...$ids);
+ }
+
+ public function entityMove(CollectionIdentifier $target, EntityIdentifier ...$identifiers): array
+ {
+ // validate target belongs to this service
+ if ($target->provider() !== $this->provider() || $target->service() !== (string)$this->identifier()) {
+ throw new \InvalidArgumentException('Target collection does not belong to this service');
+ }
+
+ // validate identifiers and construct ID list
+ $ids = [];
+ foreach ($identifiers as $identifier) {
+ if ($identifier->provider() !== $this->provider() || $identifier->service() !== (string)$this->identifier()) {
+ throw new \InvalidArgumentException('Entity identifier does not belong to this service: ' . (string)$identifier);
+ }
+ $ids[] = $identifier->entity();
+ }
+
+ $this->initialize();
+
+ return $this->mailService->entityMove($target->collection(), ...$ids);
}
}
diff --git a/lib/Service/Remote/RemoteMailService.php b/lib/Service/Remote/RemoteMailService.php
index 51e396c..e2d30a1 100644
--- a/lib/Service/Remote/RemoteMailService.php
+++ b/lib/Service/Remote/RemoteMailService.php
@@ -725,24 +725,40 @@ class RemoteMailService {
}
/**
- * delete entity from remote storage
+ * delete entities from remote storage
*
* @since Release 1.0.0
*/
- public function entityDelete(string $id): ?string {
+ public function entityDelete(string ...$identifiers): array {
// construct set request
$r0 = new MailSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
- // construct object
- $r0->delete($id);
+ foreach ($identifiers as $id) {
+ $r0->delete($id);
+ }
// transceive
$bundle = $this->dataStore->perform([$r0]);
// extract response
$response = $bundle->response(0);
- // determine if command succeeded
- if (array_search($id, $response->deleted()) !== false) {
- return $response->stateNew();
+ // check for command error
+ if ($response instanceof ResponseException) {
+ if ($response->type() === 'unknownMethod') {
+ throw new JmapUnknownMethod($response->description(), 1);
+ } else {
+ throw new Exception($response->type() . ': ' . $response->description(), 1);
+ }
}
- return null;
+
+ $results = [];
+ // check for success
+ foreach ($response->deleteSuccesses() as $id) {
+ $results[$id] = true;
+ }
+ // check for failure
+ foreach ($response->deleteFailures() as $id => $data) {
+ $results[$id] = $data['type'] ?? 'unknownError';
+ }
+
+ return $results;
}
/**
@@ -751,7 +767,7 @@ class RemoteMailService {
* @since Release 1.0.0
*
*/
- public function entityCopy(string $target, array $sources): array {
+ public function entityCopy(string $target, string ...$identifiers): array {
return [];
}
@@ -759,15 +775,8 @@ class RemoteMailService {
* move entity in remote storage
*
* @since Release 1.0.0
- *
*/
- public function entityMove(string $target, array $sources): array {
-
- // extract identifiers from sources
- $identifiers = [];
- foreach ($sources as $source) {
- $identifiers = array_merge($identifiers, $source);
- }
+ public function entityMove(string $target, string ...$identifiers): array {
// construct request
$r0 = new MailSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
foreach ($identifiers as $id) {
diff --git a/lib/Service/Remote/RemoteService.php b/lib/Service/Remote/RemoteService.php
index 48d1360..f762e8a 100644
--- a/lib/Service/Remote/RemoteService.php
+++ b/lib/Service/Remote/RemoteService.php
@@ -69,10 +69,10 @@ class RemoteService {
}
// debugging
if ($service->getDebug()) {
+ $logDir = Server::getInstance()?->logDir();
+ $logDir .= '/jmap/' . $service->identifier() . '.json';
$client->configureTransportLogState(true);
- $client->configureTransportLogLocation(
- sys_get_temp_dir() . '/' . $location->getHost() . '-' . $identity->getIdentity() . '.log'
- );
+ $client->configureTransportLogLocation($logDir);
}
// return
return $client;
diff --git a/src/components/JmapAuthPanel.vue b/src/components/JmapAuthPanel.vue
index e15546d..742c898 100644
--- a/src/components/JmapAuthPanel.vue
+++ b/src/components/JmapAuthPanel.vue
@@ -1,33 +1,31 @@
@@ -189,7 +203,7 @@ const jmapCapabilities = [
-
-
-
-
- mdi-cog
- Advanced Settings
-
-
-
-
-
-
-
-
-
-
+
import('@/components/JmapConfigPanel.vue'),
+ label: 'JMAP Protocol Panel',
+ component: () => import('@/components/JmapProtocolPanel.vue'),
priority: 10,
}
],
mail_account_auth_panels: [
{
id: 'jmap',
+ label: 'JMAP Authentication Panel',
component: () => import('@/components/JmapAuthPanel.vue'),
}
],
diff --git a/src/models/JmapServiceObject.ts b/src/models/JmapServiceObject.ts
index 9956831..651b643 100644
--- a/src/models/JmapServiceObject.ts
+++ b/src/models/JmapServiceObject.ts
@@ -51,5 +51,13 @@ export class JmapServiceObject extends ServiceObject {
get accountId(): string | undefined {
return this.jmapAuxiliary.accountId
}
+
+ get deleteMode(): 'soft' | 'hard' {
+ return this.jmapAuxiliary.deleteMode === 'hard' ? 'hard' : 'soft'
+ }
+
+ get deleteDestination(): string | undefined {
+ return this.jmapAuxiliary.deleteDestination
+ }
}
diff --git a/src/types/auxiliary.ts b/src/types/auxiliary.ts
index 94c06fd..58f3e9f 100644
--- a/src/types/auxiliary.ts
+++ b/src/types/auxiliary.ts
@@ -22,6 +22,12 @@ export interface JmapAuxiliary {
/** JMAP account ID */
accountId?: string;
+
+ /** Message delete behavior */
+ deleteMode?: 'soft' | 'hard';
+
+ /** Optional destination mailbox identifier for soft delete */
+ deleteDestination?: string;
/** Allow additional custom fields */
[key: string]: any;