feat: implement entiry read and write

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-10-01 21:31:00 -04:00
parent dbfb5f8b37
commit cc1deb4c07
3 changed files with 335 additions and 67 deletions
+125 -61
View File
@@ -649,42 +649,35 @@ class RemoteFilesService {
/**
* create entity in remote storage
*
* a file node must reference a blob, a node without one is a folder
*
* @since Release 1.0.0
*/
public function entityCreate(string $location, array $so): ?array {
public function entityCreate(string|null $location, array $so): ?array {
if (empty($so['blobId'])) {
throw new Exception('invalidArguments: A file node requires a blob identifier', 1);
}
// convert entity
$to = new NodeParametersRequest();
$to->parametersRaw($so);
$to->in($location);
// define location
$location = $this->locationNormalize($location);
if ($location !== null) {
$to->in($location);
}
$id = uniqid();
// construct request
$r0 = new NodeSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
$r0->create($id, $to);
// transceive
$bundle = $this->dataStore->perform([$r0]);
// extract response
$response = $bundle->response(0);
// 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);
}
}
$response = $this->performSet($r0);
// check for success
$result = $response->createSuccess($id);
if ($result !== null) {
return array_merge($so, $result);
return array_merge($so, ['parentId' => $location], $result);
}
// check for failure
$result = $response->createFailure($id);
if ($result !== null) {
$type = $result['type'] ?? 'unknownError';
$description = $result['description'] ?? 'An unknown error occurred during collection creation.';
throw new Exception("$type: $description", 1);
}
// return null if creation failed without failure reason
$this->throwSetFailure($response->createFailure($id), 'entity creation');
return null;
}
@@ -693,26 +686,22 @@ class RemoteFilesService {
*
* @since Release 1.0.0
*/
public function entityModify(array $so): ?array {
// extract entity id
$id = $so['id'];
public function entityModify(string $identifier, array $so): ?array {
// convert entity
$to = new NodeParametersRequest();
$to->parametersRaw($so);
// construct request
$r0 = new NodeSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
$r0->update($id, $to);
// transmit request and receive response
$bundle = $this->dataStore->perform([$r0]);
// extract response
$response = $bundle->response(0);
// determine if command succeeded
if (array_key_exists($id, $response->updated())) {
// update entity
$ro = $response->updated()[$id];
$so = array_merge($so, $ro);
return $so;
$r0->update($identifier, $to);
// transceive
$response = $this->performSet($r0);
// check for success
$result = $response->updateSuccess($identifier);
if ($result !== null) {
return array_merge($so, $result, ['id' => $identifier]);
}
// check for failure
$this->throwSetFailure($response->updateFailure($identifier), 'entity modification');
return null;
}
@@ -720,55 +709,130 @@ class RemoteFilesService {
* delete entity from remote storage
*
* @since Release 1.0.0
*
* @return string|null new state on success
*/
public function entityDelete(string $id): ?string {
// construct set request
public function entityDelete(string $identifier): ?string {
// construct request
$r0 = new NodeSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
// construct object
$r0->delete($id);
// transmit request and receive response
$bundle = $this->dataStore->perform([$r0]);
// extract response
$response = $bundle->response(0);
// determine if command succeeded
if (array_search($id, $response->deleted()) !== false) {
$r0->delete($identifier);
// transceive
$response = $this->performSet($r0);
// check for success
if ($response->deleteSuccess($identifier) !== null) {
return $response->stateNew();
}
// check for failure
$this->throwSetFailure($response->deleteFailure($identifier), 'entity deletion');
return null;
}
/**
* copy entity in remote storage
*
* @since Release 1.0.0
* the copy references the same blob as the source, blobs are immutable
*
* @since Release 1.0.0
*/
public function entityCopy(string $location, MailMessageObject $so): ?MailMessageObject {
return null;
public function entityCopy(string|null $location, string $identifier): ?array {
$source = $this->entityFetch($identifier)[$identifier] ?? null;
if ($source === null) {
return null;
}
return $this->entityCreate($location, array_intersect_key($source, array_flip(['blobId', 'name', 'type', 'executable'])));
}
/**
* move entity in remote storage
*
* @since Release 1.0.0
*
*/
public function entityMove(string $location, array $so): ?array {
// extract entity id
$id = $so['id'];
public function entityMove(string|null $location, string $identifier): ?array {
$location = $this->locationNormalize($location);
// construct request
$r0 = new NodeSet($this->dataAccount, null, $this->resourceNamespace, $this->resourceEntityLabel);
$r0->update($id)->in($location);
// transmit request and receive response
$bundle = $this->dataStore->perform([$r0]);
// extract response
$response = $bundle->response(0);
// determine if command succeeded
if (array_key_exists($id, $response->updated())) {
$so = array_merge($so, ['mailboxIds' => [$location => true]]);
return $so;
$r0->update($identifier)->in($location);
// transceive
$response = $this->performSet($r0);
// check for success
$result = $response->updateSuccess($identifier);
if ($result !== null) {
return array_merge($result, ['id' => $identifier, 'parentId' => $location]);
}
// check for failure
$this->throwSetFailure($response->updateFailure($identifier), 'entity move');
return null;
}
/**
* upload blob to remote storage
*
* @since 2026.10.01
*
* @return array{blobId: string, type?: string, size?: int}
*/
public function blobUpload(string $type, string &$data): array {
$response = json_decode($this->dataStore->upload($this->dataAccount, $type, $data), true);
if (!is_array($response) || empty($response['blobId'])) {
throw new Exception('serverFail: Blob upload did not return a blob identifier', 1);
}
return $response;
}
/**
* download blob from remote storage
*
* @since 2026.10.01
*/
public function blobDownload(string $identifier, string $type = 'application/octet-stream'): string {
$data = '';
$this->dataStore->download($this->dataAccount, $identifier, $data, $type);
return $data;
}
/**
* download blob from remote storage as stream
*
* @since 2026.10.01
*
* @return resource
*/
public function blobDownloadStream(string $identifier, string $type = 'application/octet-stream') {
$stream = $this->dataStore->downloadStream($this->dataAccount, $identifier, $type);
return \GuzzleHttp\Psr7\StreamWrapper::getResource($stream);
}
/**
* root location is represented by a null parent in remote storage
*/
private function locationNormalize(string|null $location): ?string {
return (empty($location) || $location === self::ROOT_ID) ? null : $location;
}
/**
* transceive a set request and return its response, throwing on command error
*/
private function performSet(NodeSet $request): mixed {
$response = $this->dataStore->perform([$request])->response(0);
if ($response instanceof ResponseException) {
if ($response->type() === 'unknownMethod') {
throw new JmapUnknownMethod($response->description(), 1);
} else {
throw new Exception($response->type() . ': ' . $response->description(), 1);
}
}
return $response;
}
/**
* throw set failure reason if present
*/
private function throwSetFailure(?array $failure, string $operation): void {
if ($failure !== null) {
$type = $failure['type'] ?? 'unknownError';
$description = $failure['description'] ?? "An unknown error occurred during $operation.";
throw new Exception("$type: $description", 1);
}
}
}