refactor: use new documents interfaces
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
||||||
|
|
||||||
use KTXF\Resource\Documents\Collection\CollectionPropertiesMutableAbstract;
|
use KTXF\Documents\Collection\CollectionPropertiesMutableAbstract;
|
||||||
|
|
||||||
class CollectionProperties extends CollectionPropertiesMutableAbstract {
|
class CollectionProperties extends CollectionPropertiesMutableAbstract {
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ class CollectionProperties extends CollectionPropertiesMutableAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['label'])) {
|
if (isset($data['label'])) {
|
||||||
$this->data[self::JSON_PROPERTY_LABEL] = (string) $data['label'];
|
$this->data[self::PROPERTY_LABEL] = (string) $data['label'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
||||||
|
|
||||||
use KTXF\Resource\Documents\Collection\CollectionMutableAbstract;
|
use KTXF\Utile\Timestamp;
|
||||||
|
use KTXF\Documents\Collection\CollectionMutableAbstract;
|
||||||
|
|
||||||
class CollectionResource extends CollectionMutableAbstract {
|
class CollectionResource extends CollectionMutableAbstract {
|
||||||
|
|
||||||
@@ -26,11 +27,11 @@ class CollectionResource extends CollectionMutableAbstract {
|
|||||||
$data = (array) $data;
|
$data = (array) $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->data[static::JSON_PROPERTY_COLLECTION] = $data['cid'] ?? null;
|
$this->data[static::PROPERTY_COLLECTION] = $data['cid'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_IDENTIFIER] = $data['nid'];
|
$this->data[static::PROPERTY_IDENTIFIER] = $data['nid'];
|
||||||
$this->data[static::JSON_PROPERTY_CREATED] = $data['created'] ?? null;
|
$this->data[static::PROPERTY_CREATED] = $data['created'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_MODIFIED] = $data['modified'] ?? null;
|
$this->data[static::PROPERTY_MODIFIED] = $data['modified'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_SIGNATURE] = $data['signature'] ?? null;
|
$this->data[static::PROPERTY_SIGNATURE] = $data['signature'] ?? null;
|
||||||
$this->getProperties()->fromStore($data['properties'] ?? []);
|
$this->getProperties()->fromStore($data['properties'] ?? []);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -41,11 +42,11 @@ class CollectionResource extends CollectionMutableAbstract {
|
|||||||
$properties = $this->getProperties();
|
$properties = $this->getProperties();
|
||||||
|
|
||||||
$data = array_filter([
|
$data = array_filter([
|
||||||
'cid' => $this->data[static::JSON_PROPERTY_COLLECTION] ?? null,
|
'cid' => $this->data[static::PROPERTY_COLLECTION] ?? null,
|
||||||
'nid' => $this->data[static::JSON_PROPERTY_IDENTIFIER] ?? null,
|
'nid' => $this->data[static::PROPERTY_IDENTIFIER] ?? null,
|
||||||
'created' => $this->data[static::JSON_PROPERTY_CREATED] ?? null,
|
'created' => Timestamp::normalize($this->data[static::PROPERTY_CREATED] ?? null),
|
||||||
'modified' => $this->data[static::JSON_PROPERTY_MODIFIED] ?? null,
|
'modified' => Timestamp::normalize($this->data[static::PROPERTY_MODIFIED] ?? null),
|
||||||
'signature' => $this->data[static::JSON_PROPERTY_SIGNATURE] ?? null,
|
'signature' => $this->data[static::PROPERTY_SIGNATURE] ?? null,
|
||||||
'properties' => $properties ? $properties->toStore() : null,
|
'properties' => $properties ? $properties->toStore() : null,
|
||||||
], static fn($value) => $value !== null);
|
], static fn($value) => $value !== null);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
||||||
|
|
||||||
use KTXF\Resource\Documents\Entity\EntityPropertiesMutableAbstract;
|
use KTXF\Documents\Entity\EntityPropertiesMutableAbstract;
|
||||||
|
|
||||||
class EntityProperties extends EntityPropertiesMutableAbstract {
|
class EntityProperties extends EntityPropertiesMutableAbstract {
|
||||||
|
|
||||||
@@ -23,23 +23,23 @@ class EntityProperties extends EntityPropertiesMutableAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['size'])) {
|
if (isset($data['size'])) {
|
||||||
$this->data[self::JSON_PROPERTY_SIZE] = (int) $data['size'];
|
$this->data[self::PROPERTY_SIZE] = (int) $data['size'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['label'])) {
|
if (isset($data['label'])) {
|
||||||
$this->data[self::JSON_PROPERTY_LABEL] = (string) $data['label'];
|
$this->data[self::PROPERTY_LABEL] = (string) $data['label'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['mime'])) {
|
if (isset($data['mime'])) {
|
||||||
$this->data[self::JSON_PROPERTY_MIME] = (string) $data['mime'];
|
$this->data[self::PROPERTY_MIME] = (string) $data['mime'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['format'])) {
|
if (isset($data['format'])) {
|
||||||
$this->data[self::JSON_PROPERTY_FORMAT] = (string) $data['format'];
|
$this->data[self::PROPERTY_FORMAT] = (string) $data['format'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($data['encoding'])) {
|
if (isset($data['encoding'])) {
|
||||||
$this->data[self::JSON_PROPERTY_ENCODING] = (string) $data['encoding'];
|
$this->data[self::PROPERTY_ENCODING] = (string) $data['encoding'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -50,10 +50,10 @@ class EntityProperties extends EntityPropertiesMutableAbstract {
|
|||||||
*/
|
*/
|
||||||
public function toStore(): array {
|
public function toStore(): array {
|
||||||
return array_filter([
|
return array_filter([
|
||||||
'label' => $this->data[self::JSON_PROPERTY_LABEL],
|
'label' => $this->data[self::PROPERTY_LABEL],
|
||||||
'mime' => $this->data[self::JSON_PROPERTY_MIME] ?? 'application/octet-stream',
|
'mime' => $this->data[self::PROPERTY_MIME] ?? 'application/octet-stream',
|
||||||
'format' => $this->data[self::JSON_PROPERTY_FORMAT] ?? null,
|
'format' => $this->data[self::PROPERTY_FORMAT] ?? null,
|
||||||
'encoding' => $this->data[self::JSON_PROPERTY_ENCODING] ?? null,
|
'encoding' => $this->data[self::PROPERTY_ENCODING] ?? null,
|
||||||
], static fn($value) => $value !== null);
|
], static fn($value) => $value !== null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
||||||
|
|
||||||
use KTXF\Resource\Documents\Entity\EntityMutableAbstract;
|
use KTXF\Utile\Timestamp;
|
||||||
|
use KTXF\Documents\Entity\EntityMutableAbstract;
|
||||||
|
|
||||||
class EntityResource extends EntityMutableAbstract {
|
class EntityResource extends EntityMutableAbstract {
|
||||||
|
|
||||||
@@ -26,11 +27,11 @@ class EntityResource extends EntityMutableAbstract {
|
|||||||
$data = (array) $data;
|
$data = (array) $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->data[static::JSON_PROPERTY_COLLECTION] = $data['cid'] ?? null;
|
$this->data[static::PROPERTY_COLLECTION] = $data['cid'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_IDENTIFIER] = $data['nid'];
|
$this->data[static::PROPERTY_IDENTIFIER] = $data['nid'];
|
||||||
$this->data[static::JSON_PROPERTY_CREATED] = $data['created'] ?? null;
|
$this->data[static::PROPERTY_CREATED] = $data['created'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_MODIFIED] = $data['modified'] ?? null;
|
$this->data[static::PROPERTY_MODIFIED] = $data['modified'] ?? null;
|
||||||
$this->data[static::JSON_PROPERTY_SIGNATURE] = $data['signature'] ?? null;
|
$this->data[static::PROPERTY_SIGNATURE] = $data['signature'] ?? null;
|
||||||
$this->getProperties()->fromStore($data['properties'] ?? []);
|
$this->getProperties()->fromStore($data['properties'] ?? []);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -41,11 +42,11 @@ class EntityResource extends EntityMutableAbstract {
|
|||||||
$properties = $this->getProperties();
|
$properties = $this->getProperties();
|
||||||
|
|
||||||
$data = array_filter([
|
$data = array_filter([
|
||||||
'cid' => $this->data[static::JSON_PROPERTY_COLLECTION] ?? null,
|
'cid' => $this->data[static::PROPERTY_COLLECTION] ?? null,
|
||||||
'nid' => $this->data[static::JSON_PROPERTY_IDENTIFIER] ?? null,
|
'nid' => $this->data[static::PROPERTY_IDENTIFIER] ?? null,
|
||||||
'created' => $this->data[static::JSON_PROPERTY_CREATED] ?? null,
|
'created' => Timestamp::normalize($this->data[static::PROPERTY_CREATED] ?? null),
|
||||||
'modified' => $this->data[static::JSON_PROPERTY_MODIFIED] ?? null,
|
'modified' => Timestamp::normalize($this->data[static::PROPERTY_MODIFIED] ?? null),
|
||||||
'signature' => $this->data[static::JSON_PROPERTY_SIGNATURE] ?? null,
|
'signature' => $this->data[static::PROPERTY_SIGNATURE] ?? null,
|
||||||
'properties' => $properties ? $properties->toStore() : null,
|
'properties' => $properties ? $properties->toStore() : null,
|
||||||
], static fn($value) => $value !== null);
|
], static fn($value) => $value !== null);
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,23 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
namespace KTXM\ProviderLocalDocuments\Providers\Personal;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
|
use KTXF\Blob\Signature;
|
||||||
|
use KTXF\Documents\Collection\CollectionBaseInterface;
|
||||||
|
use KTXF\Documents\Collection\CollectionPropertiesBaseInterface;
|
||||||
|
use KTXF\Documents\Entity\EntityPropertiesMutableInterface;
|
||||||
|
use KTXF\Documents\Service\ServiceBaseInterface;
|
||||||
|
use KTXF\Documents\Service\ServiceCollectionMutableInterface;
|
||||||
|
use KTXF\Documents\Service\ServiceEntityMutableInterface;
|
||||||
|
use KTXF\Files\Node\NodeType;
|
||||||
|
use KTXF\Resource\Delta\Delta;
|
||||||
use KTXF\Resource\Exceptions\InvalidParameterException;
|
use KTXF\Resource\Exceptions\InvalidParameterException;
|
||||||
use KTXF\Resource\Filter\Filter;
|
use KTXF\Resource\Filter\Filter;
|
||||||
use KTXF\Resource\Filter\IFilter;
|
use KTXF\Resource\Filter\IFilter;
|
||||||
|
use KTXF\Resource\Identifier\CollectionIdentifier;
|
||||||
|
use KTXF\Resource\Identifier\CollectionIdentifierInterface;
|
||||||
|
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||||
|
use KTXF\Resource\Identifier\EntityIdentifierInterface;
|
||||||
use KTXF\Resource\Range\IRange;
|
use KTXF\Resource\Range\IRange;
|
||||||
use KTXF\Resource\Range\RangeTally;
|
use KTXF\Resource\Range\RangeTally;
|
||||||
use KTXF\Resource\Range\RangeType;
|
use KTXF\Resource\Range\RangeType;
|
||||||
@@ -19,15 +33,6 @@ use KTXF\Resource\Sort\ISort;
|
|||||||
use KTXF\Resource\Sort\Sort;
|
use KTXF\Resource\Sort\Sort;
|
||||||
use KTXM\ProviderLocalDocuments\Store\BlobStore;
|
use KTXM\ProviderLocalDocuments\Store\BlobStore;
|
||||||
use KTXM\ProviderLocalDocuments\Store\MetaStore;
|
use KTXM\ProviderLocalDocuments\Store\MetaStore;
|
||||||
use KTXF\Blob\Signature;
|
|
||||||
use KTXF\Files\Node\NodeType;
|
|
||||||
use KTXF\Resource\Delta\Delta;
|
|
||||||
use KTXF\Resource\Documents\Collection\CollectionMutableInterface;
|
|
||||||
use KTXF\Resource\Documents\Entity\EntityMutableInterface;
|
|
||||||
use KTXF\Resource\Documents\Service\ServiceBaseInterface;
|
|
||||||
use KTXF\Resource\Documents\Service\ServiceCollectionMutableInterface;
|
|
||||||
use KTXF\Resource\Documents\Service\ServiceEntityMutableInterface;
|
|
||||||
use KTXM\ProviderLocalDocuments\Providers\Personal\CollectionResource;
|
|
||||||
|
|
||||||
class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableInterface, ServiceEntityMutableInterface {
|
class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableInterface, ServiceEntityMutableInterface {
|
||||||
|
|
||||||
@@ -58,6 +63,8 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
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_COPY => 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_ALL => 's:200:256:256',
|
self::CAPABILITY_ENTITY_FILTER_ALL => 's:200:256:256',
|
||||||
@@ -74,14 +81,15 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
self::CAPABILITY_ENTITY_RANGE_TALLY_ABSOLUTE,
|
self::CAPABILITY_ENTITY_RANGE_TALLY_ABSOLUTE,
|
||||||
self::CAPABILITY_ENTITY_RANGE_TALLY_RELATIVE
|
self::CAPABILITY_ENTITY_RANGE_TALLY_RELATIVE
|
||||||
],
|
],
|
||||||
self::CAPABILITY_ENTITY_RANGE_DATE => true,
|
|
||||||
],
|
],
|
||||||
self::CAPABILITY_ENTITY_DELTA => true,
|
self::CAPABILITY_ENTITY_DELTA => true,
|
||||||
self::CAPABILITY_ENTITY_EXTANT => true,
|
self::CAPABILITY_ENTITY_EXTANT => true,
|
||||||
self::CAPABILITY_ENTITY_FETCH => true,
|
self::CAPABILITY_ENTITY_FETCH => true,
|
||||||
self::CAPABILITY_ENTITY_CREATE => true,
|
self::CAPABILITY_ENTITY_CREATE => true,
|
||||||
self::CAPABILITY_ENTITY_UPDATE => true,
|
self::CAPABILITY_ENTITY_MODIFY => true,
|
||||||
self::CAPABILITY_ENTITY_DELETE => true,
|
self::CAPABILITY_ENTITY_DELETE => true,
|
||||||
|
self::CAPABILITY_ENTITY_MOVE => true,
|
||||||
|
self::CAPABILITY_ENTITY_COPY => true,
|
||||||
self::CAPABILITY_ENTITY_READ => true,
|
self::CAPABILITY_ENTITY_READ => true,
|
||||||
self::CAPABILITY_ENTITY_WRITE => true,
|
self::CAPABILITY_ENTITY_WRITE => true,
|
||||||
];
|
];
|
||||||
@@ -117,15 +125,15 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
|
|
||||||
public function jsonSerialize(): array {
|
public function jsonSerialize(): array {
|
||||||
return array_filter([
|
return array_filter([
|
||||||
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
|
self::PROPERTY_TYPE => self::JSON_TYPE,
|
||||||
self::JSON_PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
|
self::PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
|
||||||
self::JSON_PROPERTY_IDENTIFIER => self::SERVICE_IDENTIFIER,
|
self::PROPERTY_IDENTIFIER => self::SERVICE_IDENTIFIER,
|
||||||
self::JSON_PROPERTY_LABEL => self::SERVICE_LABEL,
|
self::PROPERTY_LABEL => self::SERVICE_LABEL,
|
||||||
self::JSON_PROPERTY_ENABLED => $this->serviceEnabled,
|
self::PROPERTY_ENABLED => $this->serviceEnabled,
|
||||||
self::JSON_PROPERTY_CAPABILITIES => $this->serviceAbilities,
|
self::PROPERTY_CAPABILITIES => $this->serviceAbilities,
|
||||||
self::JSON_PROPERTY_LOCATION => null,
|
self::PROPERTY_LOCATION => null,
|
||||||
self::JSON_PROPERTY_IDENTITY => null,
|
self::PROPERTY_IDENTITY => null,
|
||||||
self::JSON_PROPERTY_AUXILIARY => [],
|
self::PROPERTY_AUXILIARY => [],
|
||||||
], fn($v) => $v !== null);
|
], fn($v) => $v !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,9 +181,17 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a collection location, treating null and '' as the root collection
|
||||||
|
*/
|
||||||
|
private function normalizeLocation(string|int|null $location): string|int {
|
||||||
|
return ($location === null || $location === '') ? self::ROOT_ID : $location;
|
||||||
|
}
|
||||||
|
|
||||||
// Collection operations
|
// Collection operations
|
||||||
|
|
||||||
public function collectionList(string|int|null $location = null, ?IFilter $filter = null, ?ISort $sort = null): array {
|
public function collectionList(string|int|null $location, ?IFilter $filter = null, ?ISort $sort = null): array {
|
||||||
|
$location = $this->normalizeLocation($location);
|
||||||
$entries = $this->metaStore->collectionList($this->serviceTenantId, $this->serviceUserId, $location, $filter, $sort);
|
$entries = $this->metaStore->collectionList($this->serviceTenantId, $this->serviceUserId, $location, $filter, $sort);
|
||||||
// cache collections
|
// cache collections
|
||||||
foreach ($entries as $id => $collection) {
|
foreach ($entries as $id => $collection) {
|
||||||
@@ -209,11 +225,17 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return array_merge($cached, $fromStore);
|
return array_merge($cached, $fromStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a single collection exists and belongs to the current user
|
||||||
|
*/
|
||||||
|
private function collectionAccessible(string|int $collection): bool {
|
||||||
|
$response = $this->collectionExtant(null, $collection);
|
||||||
|
return ($response[$collection] ?? false) === true;
|
||||||
|
}
|
||||||
|
|
||||||
public function collectionFetch(string|int|null $identifier): ?CollectionResource {
|
public function collectionFetch(string|int|null $identifier): ?CollectionResource {
|
||||||
// null is root
|
// null is root
|
||||||
if ($identifier === null) {
|
$identifier = $this->normalizeLocation($identifier);
|
||||||
$identifier = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
// check cache first
|
// check cache first
|
||||||
if (isset($this->serviceCollectionCache[$identifier])) {
|
if (isset($this->serviceCollectionCache[$identifier])) {
|
||||||
return $this->serviceCollectionCache[$identifier];
|
return $this->serviceCollectionCache[$identifier];
|
||||||
@@ -242,11 +264,27 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $collection;
|
return $collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectionCreate(string|int|null $location, CollectionMutableInterface $collection, array $options = []): CollectionResource {
|
/**
|
||||||
// null is root
|
* Convert incoming collection properties into a native CollectionResource
|
||||||
if ($location === null) {
|
*/
|
||||||
$location = self::ROOT_ID;
|
private function collectionNative(CollectionPropertiesBaseInterface $properties, string|int|null $identifier = null): CollectionResource {
|
||||||
|
if ($properties instanceof CollectionResource) {
|
||||||
|
$native = clone $properties;
|
||||||
|
} else {
|
||||||
|
$native = $this->collectionFresh();
|
||||||
|
$native->jsonDeserialize([
|
||||||
|
CollectionResource::PROPERTY_IDENTIFIER => $identifier,
|
||||||
|
CollectionResource::PROPERTY_PROPERTIES => $properties->jsonSerialize(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
return $native;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectionCreate(CollectionIdentifierInterface|null $target, CollectionPropertiesBaseInterface $properties, array $options = []): CollectionResource {
|
||||||
|
// null target is root
|
||||||
|
$location = $this->normalizeLocation($target?->collection());
|
||||||
|
// convert properties to a native collection
|
||||||
|
$collection = $this->collectionNative($properties);
|
||||||
// Create in meta store
|
// Create in meta store
|
||||||
$node = $this->metaStore->collectionCreate($this->serviceTenantId, $this->serviceUserId, $location, $collection, $options);
|
$node = $this->metaStore->collectionCreate($this->serviceTenantId, $this->serviceUserId, $location, $collection, $options);
|
||||||
// cache collection
|
// cache collection
|
||||||
@@ -254,7 +292,10 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectionUpdate(string|int $identifier, CollectionMutableInterface $collection): CollectionResource {
|
public function collectionUpdate(CollectionIdentifierInterface $target, CollectionPropertiesBaseInterface $properties): CollectionResource {
|
||||||
|
$identifier = $target->collection();
|
||||||
|
// convert properties to a native collection
|
||||||
|
$collection = $this->collectionNative($properties, $identifier);
|
||||||
// Modify in meta store
|
// Modify in meta store
|
||||||
$node = $this->metaStore->collectionModify($this->serviceTenantId, $this->serviceUserId, $identifier, $collection);
|
$node = $this->metaStore->collectionModify($this->serviceTenantId, $this->serviceUserId, $identifier, $collection);
|
||||||
|
|
||||||
@@ -263,14 +304,15 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectionDelete(string|int $identifier, bool $force = false, bool $recursive = false): bool {
|
public function collectionDelete(CollectionIdentifierInterface $target, bool $force = false): CollectionBaseInterface | true {
|
||||||
|
$identifier = $target->collection();
|
||||||
// Protect root collection
|
// Protect root collection
|
||||||
if ($identifier === self::ROOT_ID) {
|
if ($identifier === self::ROOT_ID) {
|
||||||
throw new InvalidParameterException("Cannot destroy root collection");
|
throw new InvalidParameterException("Cannot destroy root collection");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not forcing and not recursive, ensure the collection is empty
|
// If not forcing, ensure the collection is empty
|
||||||
if (!$force && !$recursive) {
|
if (!$force) {
|
||||||
$children = $this->metaStore->nodeList($this->serviceTenantId, $this->serviceUserId, $identifier, false);
|
$children = $this->metaStore->nodeList($this->serviceTenantId, $this->serviceUserId, $identifier, false);
|
||||||
if (!empty($children)) {
|
if (!empty($children)) {
|
||||||
throw new InvalidParameterException("Collection is not empty: $identifier");
|
throw new InvalidParameterException("Collection is not empty: $identifier");
|
||||||
@@ -280,34 +322,32 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
// Delete blob files for all entities within the collection tree
|
// Delete blob files for all entities within the collection tree
|
||||||
$descendants = $this->metaStore->nodeList($this->serviceTenantId, $this->serviceUserId, $identifier, true);
|
$descendants = $this->metaStore->nodeList($this->serviceTenantId, $this->serviceUserId, $identifier, true);
|
||||||
foreach ($descendants as $nodeId => $node) {
|
foreach ($descendants as $nodeId => $node) {
|
||||||
if ($node->isEntity()) {
|
if ($node instanceof EntityResource) {
|
||||||
$this->blobStore->blobDelete((string)$nodeId);
|
$this->blobStore->blobDelete((string)$nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete from meta store (handles recursive collection/entity meta deletion internally)
|
// Delete from meta store (handles recursive collection/entity meta deletion internally)
|
||||||
$result = $this->metaStore->collectionDestroy($this->serviceTenantId, $this->serviceUserId, $identifier);
|
$this->metaStore->collectionDestroy($this->serviceTenantId, $this->serviceUserId, $identifier);
|
||||||
|
|
||||||
// remove from cache
|
// remove from cache
|
||||||
unset($this->serviceCollectionCache[$identifier]);
|
unset($this->serviceCollectionCache[$identifier]);
|
||||||
return $result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectionCopy(string|int $identifier, string|int|null $location): CollectionResource {
|
public function collectionCopy(CollectionIdentifierInterface|null $target, CollectionIdentifierInterface $source): CollectionResource {
|
||||||
|
$identifier = $source->collection();
|
||||||
// Protect root collection
|
// Protect root collection
|
||||||
if ($identifier === self::ROOT_ID) {
|
if ($identifier === self::ROOT_ID) {
|
||||||
throw new InvalidParameterException("Cannot copy root collection");
|
throw new InvalidParameterException("Cannot copy root collection");
|
||||||
}
|
}
|
||||||
// Verify collection exists
|
// Verify collection exists
|
||||||
$extant = $this->collectionExtant($identifier, $identifier);
|
if ($this->collectionAccessible($identifier) === false) {
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Collection not found: $identifier");
|
throw new InvalidParameterException("Collection not found: $identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
// null location is root
|
// null target is root
|
||||||
if ($location === null) {
|
$location = $this->normalizeLocation($target?->collection());
|
||||||
$location = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
// Copy in meta store
|
// Copy in meta store
|
||||||
$node = $this->metaStore->collectionCopy($this->serviceTenantId, $this->serviceUserId, $identifier, $location);
|
$node = $this->metaStore->collectionCopy($this->serviceTenantId, $this->serviceUserId, $identifier, $location);
|
||||||
|
|
||||||
@@ -316,21 +356,19 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectionMove(string|int $identifier, string|int|null $location): CollectionResource {
|
public function collectionMove(CollectionIdentifierInterface|null $target, CollectionIdentifierInterface $source): CollectionResource {
|
||||||
|
$identifier = $source->collection();
|
||||||
// Protect root collection
|
// Protect root collection
|
||||||
if ($identifier === self::ROOT_ID) {
|
if ($identifier === self::ROOT_ID) {
|
||||||
throw new InvalidParameterException("Cannot move root collection");
|
throw new InvalidParameterException("Cannot move root collection");
|
||||||
}
|
}
|
||||||
// Verify collection exists
|
// Verify collection exists
|
||||||
$extant = $this->collectionExtant($identifier, $identifier);
|
if ($this->collectionAccessible($identifier) === false) {
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Collection not found: $identifier");
|
throw new InvalidParameterException("Collection not found: $identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
// null location is root
|
// null target is root
|
||||||
if ($location === null) {
|
$location = $this->normalizeLocation($target?->collection());
|
||||||
$location = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
// Move in meta store
|
// Move in meta store
|
||||||
$node = $this->metaStore->collectionMove($this->serviceTenantId, $this->serviceUserId, $identifier, $location);
|
$node = $this->metaStore->collectionMove($this->serviceTenantId, $this->serviceUserId, $identifier, $location);
|
||||||
|
|
||||||
@@ -341,20 +379,22 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
|
|
||||||
// Entity operations
|
// Entity operations
|
||||||
|
|
||||||
public function entityList(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array {
|
public function entityListBulk(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array {
|
||||||
// null collection is root
|
// null collection is root
|
||||||
if ($collection === null) {
|
$collection = $this->normalizeLocation($collection);
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$entries = $this->metaStore->entityList($this->serviceTenantId, $this->serviceUserId, $collection, $filter, $sort, $range);
|
$entries = $this->metaStore->entityList($this->serviceTenantId, $this->serviceUserId, $collection, $filter, $sort, $range);
|
||||||
// cache entities
|
// cache entities
|
||||||
foreach ($entries as $id => $entity) {
|
foreach ($entries as $entity) {
|
||||||
$this->serviceEntityCache[$entity->identifier()] = $entity;
|
$this->serviceEntityCache[$entity->identifier()] = $entity;
|
||||||
}
|
}
|
||||||
return $entries ?? [];
|
return $entries ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function entityListStream(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator {
|
||||||
|
yield from $this->entityListBulk($collection, $filter, $sort, $range, $properties);
|
||||||
|
}
|
||||||
|
|
||||||
public function entityListFilter(): IFilter {
|
public function entityListFilter(): IFilter {
|
||||||
return new Filter($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_FILTER] ?? []);
|
return new Filter($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_FILTER] ?? []);
|
||||||
}
|
}
|
||||||
@@ -370,41 +410,37 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return new RangeTally();
|
return new RangeTally();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityFetch(string|int|null $collection, string|int ...$identifiers): array {
|
public function entityFetchBulk(EntityIdentifierInterface ...$identifiers): array {
|
||||||
// null collection is root
|
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
$toFetch = [];
|
// group identifiers by collection
|
||||||
|
$byCollection = [];
|
||||||
|
foreach ($identifiers as $identifier) {
|
||||||
|
$id = $identifier->entity();
|
||||||
// check cache first
|
// check cache first
|
||||||
foreach ($identifiers as $id) {
|
|
||||||
if (isset($this->serviceEntityCache[$id])) {
|
if (isset($this->serviceEntityCache[$id])) {
|
||||||
$result[$id] = $this->serviceEntityCache[$id];
|
$result[$id] = $this->serviceEntityCache[$id];
|
||||||
} else {
|
continue;
|
||||||
$toFetch[] = $id;
|
|
||||||
}
|
}
|
||||||
|
$byCollection[$this->normalizeLocation($identifier->collection())][] = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch remaining from store
|
// fetch remaining from store
|
||||||
if (!empty($toFetch)) {
|
foreach ($byCollection as $collection => $entityIds) {
|
||||||
$fetched = $this->metaStore->entityFetch($this->serviceTenantId, $this->serviceUserId, $collection, ...$toFetch);
|
$fetched = $this->metaStore->entityFetch($this->serviceTenantId, $this->serviceUserId, $collection, ...$entityIds);
|
||||||
foreach ($fetched as $id => $entity) {
|
foreach ($fetched as $id => $entity) {
|
||||||
$this->serviceEntityCache[$id] = $entity;
|
$this->serviceEntityCache[$id] = $entity;
|
||||||
$result[$id] = $entity;
|
$result[$id] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function entityFetchStream(EntityIdentifierInterface ...$identifiers): Generator {
|
||||||
|
yield from $this->entityFetchBulk(...$identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
public function entityExtant(string|int|null $collection, string|int ...$identifiers): array {
|
public function entityExtant(string|int|null $collection, string|int ...$identifiers): array {
|
||||||
// null collection is root
|
// null collection is root
|
||||||
if ($collection === null) {
|
$collection = $this->normalizeLocation($collection);
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($identifiers as $id) {
|
foreach ($identifiers as $id) {
|
||||||
@@ -427,17 +463,33 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
'uid' => $this->serviceUserId,
|
'uid' => $this->serviceUserId,
|
||||||
'cid' => null,
|
'cid' => null,
|
||||||
'nid' => null,
|
'nid' => null,
|
||||||
'created' => (new \DateTimeImmutable())->format('c'),
|
'created' => (int) round(microtime(true) * 1000),
|
||||||
'properties' => [],
|
'properties' => [],
|
||||||
]);
|
]);
|
||||||
return $entity;
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityCreate(string|int|null $collection, EntityMutableInterface $entity, array $options = []): EntityResource {
|
/**
|
||||||
// null collection is root
|
* Convert incoming entity properties into a native EntityResource
|
||||||
if ($collection === null) {
|
*/
|
||||||
$collection = self::ROOT_ID;
|
private function entityNative(EntityPropertiesMutableInterface $properties, string|int|null $identifier = null): EntityResource {
|
||||||
|
if ($properties instanceof EntityResource) {
|
||||||
|
$native = clone $properties;
|
||||||
|
} else {
|
||||||
|
$native = $this->entityFresh();
|
||||||
|
$native->jsonDeserialize([
|
||||||
|
EntityResource::PROPERTY_IDENTIFIER => $identifier,
|
||||||
|
EntityResource::PROPERTY_PROPERTIES => $properties->jsonSerialize(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
return $native;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function entityCreate(CollectionIdentifierInterface $target, EntityPropertiesMutableInterface $properties, array $options = []): EntityResource {
|
||||||
|
// null collection is root
|
||||||
|
$collection = $this->normalizeLocation($target->collection());
|
||||||
|
// convert properties to a native entity
|
||||||
|
$entity = $this->entityNative($properties);
|
||||||
|
|
||||||
// Create in meta store
|
// Create in meta store
|
||||||
$result = $this->metaStore->entityCreate($this->serviceTenantId, $this->serviceUserId, $collection, $entity, $options);
|
$result = $this->metaStore->entityCreate($this->serviceTenantId, $this->serviceUserId, $collection, $entity, $options);
|
||||||
@@ -450,11 +502,12 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityUpdate(string|int|null $collection, string|int $identifier, EntityMutableInterface $entity): EntityResource {
|
public function entityModify(EntityIdentifierInterface $target, EntityPropertiesMutableInterface $properties): EntityResource {
|
||||||
// null collection is root
|
// null collection is root
|
||||||
if ($collection === null) {
|
$collection = $this->normalizeLocation($target->collection());
|
||||||
$collection = self::ROOT_ID;
|
$identifier = $target->entity();
|
||||||
}
|
// convert properties to a native entity
|
||||||
|
$entity = $this->entityNative($properties, $identifier);
|
||||||
|
|
||||||
// Modify in meta store
|
// Modify in meta store
|
||||||
$result = $this->metaStore->entityModify($this->serviceTenantId, $this->serviceUserId, $collection, $identifier, $entity);
|
$result = $this->metaStore->entityModify($this->serviceTenantId, $this->serviceUserId, $collection, $identifier, $entity);
|
||||||
@@ -467,150 +520,140 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityDelete(string|int|null $collection, string|int $identifier): EntityResource {
|
public function entityDelete(EntityIdentifierInterface ...$targets): array {
|
||||||
// null collection is root
|
$results = [];
|
||||||
if ($collection === null) {
|
foreach ($targets as $target) {
|
||||||
$collection = self::ROOT_ID;
|
$key = (string) $target;
|
||||||
|
$collection = $this->normalizeLocation($target->collection());
|
||||||
|
$identifier = $target->entity();
|
||||||
|
// validate entity extant and ownership
|
||||||
|
$extant = $this->entityExtant($collection, $identifier);
|
||||||
|
if (($extant[$identifier] ?? false) === false) {
|
||||||
|
$results[$key] = ['disposition' => 'error', 'destination' => null, 'mutation' => $target];
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var EntityResource[] $entities */
|
|
||||||
$entities = $this->entityFetch($collection, $identifier);
|
|
||||||
|
|
||||||
if (!isset($entities[$identifier])) {
|
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
$entity = $entities[$identifier];
|
|
||||||
|
|
||||||
// Delete from blob store
|
// Delete from blob store
|
||||||
$this->blobStore->blobDelete($entity->identifier());
|
$this->blobStore->blobDelete((string)$identifier);
|
||||||
|
|
||||||
// Delete from meta store
|
// Delete from meta store
|
||||||
$this->metaStore->entityDestroy($this->serviceTenantId, $this->serviceUserId, $collection, $identifier);
|
$this->metaStore->entityDestroy($this->serviceTenantId, $this->serviceUserId, $collection, $identifier);
|
||||||
|
|
||||||
// remove from cache
|
// remove from cache
|
||||||
unset($this->serviceEntityCache[$entity->identifier()]);
|
unset($this->serviceEntityCache[$identifier]);
|
||||||
return $entity;
|
$results[$key] = ['disposition' => 'deleted', 'destination' => null, 'mutation' => $target];
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityDelta(string|int|null $collection, string $signature, string $detail = 'ids'): Delta {
|
public function entityDelta(string|int|null $collection, string $signature): Delta {
|
||||||
// null collection is root
|
// null collection is root
|
||||||
if ($collection === null) {
|
$collection = $this->normalizeLocation($collection);
|
||||||
$collection = self::ROOT_ID;
|
return new Delta(); // $this->metaStore->entityDelta($this->serviceTenantId, $this->serviceUserId, $collection, $signature);
|
||||||
}
|
|
||||||
return new Delta(); // $this->metaStore->entityDelta($this->serviceTenantId, $this->serviceUserId, $collection, $signature, $detail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityCopy(string|int|null $collection, string|int $identifier, string|int|null $destination): EntityResource {
|
public function entityMove(CollectionIdentifierInterface $target, EntityIdentifierInterface ...$sources): array {
|
||||||
// null collection is root
|
return $this->entityRelocate($target, true, ...$sources);
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
// null destination is root
|
|
||||||
if ($destination === null) {
|
|
||||||
$destination = self::ROOT_ID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify entity exists
|
public function entityCopy(CollectionIdentifierInterface $target, EntityIdentifierInterface ...$sources): array {
|
||||||
$extant = $this->entityExtant($collection, $identifier);
|
return $this->entityRelocate($target, false, ...$sources);
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy in meta store (creates new entity with new ID)
|
/**
|
||||||
$node = $this->metaStore->entityCopy($this->serviceTenantId, $this->serviceUserId, $collection, $identifier, $destination);
|
* Relocate (move or copy) entities into a target collection.
|
||||||
|
*
|
||||||
// Copy file content (new entity has new UUID = new file)
|
* Moves retain the entity identifier (blob path is keyed by entity id and never
|
||||||
|
* changes); copies receive a fresh identifier and a duplicated blob.
|
||||||
|
*
|
||||||
|
* @param CollectionIdentifierInterface $target destination collection identifier
|
||||||
|
* @param bool $remove true to move (relocate the source), false to copy
|
||||||
|
* @param EntityIdentifierInterface ...$sources source entity identifiers
|
||||||
|
*
|
||||||
|
* @return array<string, array{disposition: string, destination: ?CollectionIdentifierInterface, mutation: EntityIdentifierInterface}>
|
||||||
|
*/
|
||||||
|
private function entityRelocate(CollectionIdentifierInterface $target, bool $remove, EntityIdentifierInterface ...$sources): array {
|
||||||
|
$results = [];
|
||||||
|
$disposition = $remove ? 'moved' : 'copied';
|
||||||
|
$targetCollection = $this->normalizeLocation($target->collection());
|
||||||
|
// validate target collection extant and ownership
|
||||||
|
$targetValid = $targetCollection === self::ROOT_ID || $this->collectionAccessible($targetCollection);
|
||||||
|
foreach ($sources as $source) {
|
||||||
|
$key = (string) $source;
|
||||||
|
$sourceCollection = $this->normalizeLocation($source->collection());
|
||||||
|
$identifier = $source->entity();
|
||||||
|
// validate target collection
|
||||||
|
if ($targetValid === false) {
|
||||||
|
$results[$key] = ['disposition' => 'error', 'destination' => null, 'mutation' => $source];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// validate source entity extant and ownership
|
||||||
|
$extant = $this->entityExtant($sourceCollection, $identifier);
|
||||||
|
if (($extant[$identifier] ?? false) === false) {
|
||||||
|
$results[$key] = ['disposition' => 'error', 'destination' => null, 'mutation' => $source];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($remove) {
|
||||||
|
// Move in meta store (blob path never changes since it is keyed by entity id)
|
||||||
|
$node = $this->metaStore->entityMove($this->serviceTenantId, $this->serviceUserId, $sourceCollection, $identifier, $targetCollection);
|
||||||
|
} else {
|
||||||
|
// Copy in meta store (creates new entity with new id)
|
||||||
|
$node = $this->metaStore->entityCopy($this->serviceTenantId, $this->serviceUserId, $sourceCollection, $identifier, $targetCollection);
|
||||||
|
// Copy blob content (new entity has new id = new file)
|
||||||
$content = $this->blobStore->blobRead((string)$identifier);
|
$content = $this->blobStore->blobRead((string)$identifier);
|
||||||
if ($content !== null) {
|
if ($content !== null) {
|
||||||
$this->blobStore->blobWrite((string)$node->identifier(), $content);
|
$this->blobStore->blobWrite((string)$node->identifier(), $content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Write meta file for recovery
|
// Write meta file for recovery
|
||||||
$this->blobStore->metaWrite((string)$node->identifier(), $this->buildEntityMeta($node));
|
$this->blobStore->metaWrite((string)$node->identifier(), $this->buildEntityMeta($node));
|
||||||
|
|
||||||
// cache entity
|
// cache entity
|
||||||
$this->serviceEntityCache[$node->identifier()] = $node;
|
$this->serviceEntityCache[$node->identifier()] = $node;
|
||||||
return $node;
|
// construct destination and mutation identifiers
|
||||||
|
$destination = new CollectionIdentifier($this->provider(), $this->identifier(), (string)$targetCollection);
|
||||||
|
$mutation = new EntityIdentifier($this->provider(), $this->identifier(), (string)$targetCollection, (string)$node->identifier());
|
||||||
|
$results[$key] = ['disposition' => $disposition, 'destination' => $destination, 'mutation' => $mutation];
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityMove(string|int|null $collection, string|int $identifier, string|int|null $destination): EntityResource {
|
// Entity content (blob) operations
|
||||||
// null collection is root
|
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
// null destination is root
|
|
||||||
if ($destination === null) {
|
|
||||||
$destination = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify entity exists
|
/**
|
||||||
|
* Confirm an entity exists, returning its normalized collection identifier
|
||||||
|
*/
|
||||||
|
private function entityAccessible(EntityIdentifierInterface $target): string|int|false {
|
||||||
|
$collection = $this->normalizeLocation($target->collection());
|
||||||
|
$identifier = $target->entity();
|
||||||
$extant = $this->entityExtant($collection, $identifier);
|
$extant = $this->entityExtant($collection, $identifier);
|
||||||
if (!($extant[$identifier] ?? false)) {
|
return ($extant[$identifier] ?? false) ? $collection : false;
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move in meta store (file path never changes since it's based on entity ID)
|
public function entityRead(EntityIdentifierInterface $target): ?string {
|
||||||
$node = $this->metaStore->entityMove($this->serviceTenantId, $this->serviceUserId, $collection, $identifier, $destination);
|
if ($this->entityAccessible($target) === false) {
|
||||||
|
|
||||||
// Update meta file for recovery (parent changed)
|
|
||||||
$this->blobStore->metaWrite((string)$node->identifier(), $this->buildEntityMeta($node));
|
|
||||||
|
|
||||||
// update cache
|
|
||||||
$this->serviceEntityCache[$node->identifier()] = $node;
|
|
||||||
return $node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function entityRead(string|int|null $collection, string|int $identifier): ?string {
|
|
||||||
// null collection is root
|
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$entities = $this->entityFetch($collection, $identifier);
|
|
||||||
if (!isset($entities[$identifier])) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this->blobStore->blobRead((string)$identifier);
|
return $this->blobStore->blobRead((string)$target->entity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityReadStream(string|int|null $collection, string|int $identifier) {
|
public function entityReadStream(EntityIdentifierInterface $target) {
|
||||||
// null collection is root
|
if ($this->entityAccessible($target) === false) {
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$entities = $this->entityFetch($collection, $identifier);
|
|
||||||
if (!isset($entities[$identifier])) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return $this->blobStore->blobReadStream((string)$target->entity());
|
||||||
return $this->blobStore->blobReadStream((string)$identifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityReadChunk(string|int|null $collection, string|int $identifier, int $offset, int $length): ?string {
|
public function entityReadChunk(EntityIdentifierInterface $target, int $offset, int $length): ?string {
|
||||||
// null collection is root
|
if ($this->entityAccessible($target) === false) {
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
$entities = $this->entityFetch($collection, $identifier);
|
|
||||||
if (!isset($entities[$identifier])) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return $this->blobStore->blobReadChunk((string)$target->entity(), $offset, $length);
|
||||||
return $this->blobStore->blobReadChunk((string)$identifier, $offset, $length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityWrite(string|int|null $collection, string|int $identifier, string $data): int {
|
public function entityWrite(EntityIdentifierInterface $target, string $data): int {
|
||||||
// null collection is root
|
$collection = $this->entityAccessible($target);
|
||||||
if ($collection === null) {
|
$identifier = $target->entity();
|
||||||
$collection = self::ROOT_ID;
|
if ($collection === false) {
|
||||||
}
|
|
||||||
|
|
||||||
// verify entity exists
|
|
||||||
$extant = $this->entityExtant($collection, $identifier);
|
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
throw new InvalidParameterException("Entity not found: $identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,15 +684,10 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $size;
|
return $size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityWriteStream(string|int|null $collection, string|int $identifier) {
|
public function entityWriteStream(EntityIdentifierInterface $target) {
|
||||||
// null collection is root
|
$collection = $this->entityAccessible($target);
|
||||||
if ($collection === null) {
|
$identifier = $target->entity();
|
||||||
$collection = self::ROOT_ID;
|
if ($collection === false) {
|
||||||
}
|
|
||||||
|
|
||||||
// Verify entity exists
|
|
||||||
$extant = $this->entityExtant($collection, $identifier);
|
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
throw new InvalidParameterException("Entity not found: $identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,15 +714,10 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $stream;
|
return $stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function entityWriteChunk(string|int|null $collection, string|int $identifier, int $offset, string $data): int {
|
public function entityWriteChunk(EntityIdentifierInterface $target, int $offset, string $data): int {
|
||||||
// null collection is root
|
$collection = $this->entityAccessible($target);
|
||||||
if ($collection === null) {
|
$identifier = $target->entity();
|
||||||
$collection = self::ROOT_ID;
|
if ($collection === false) {
|
||||||
}
|
|
||||||
|
|
||||||
// Verify entity exists
|
|
||||||
$extant = $this->entityExtant($collection, $identifier);
|
|
||||||
if (!($extant[$identifier] ?? false)) {
|
|
||||||
throw new InvalidParameterException("Entity not found: $identifier");
|
throw new InvalidParameterException("Entity not found: $identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,34 +751,6 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
return $bytes;
|
return $bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node operations
|
|
||||||
|
|
||||||
public function nodeList(string|int|null $collection = null, bool $recursive = false, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array {
|
|
||||||
if ($collection === null) {
|
|
||||||
$collection = self::ROOT_ID;
|
|
||||||
}
|
|
||||||
return $this->metaStore->nodeList($this->serviceTenantId, $this->serviceUserId, $collection, $recursive, $filter, $sort, $range, $properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function nodeListFilter(): IFilter {
|
|
||||||
return new Filter($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_FILTER] ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function nodeListSort(): ISort {
|
|
||||||
return new Sort($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_SORT] ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function nodeListRange(RangeType $type): IRange {
|
|
||||||
if ($type !== RangeType::TALLY) {
|
|
||||||
throw new InvalidParameterException("Invalid: Node range of type '{$type->value}' is not supported");
|
|
||||||
}
|
|
||||||
return new RangeTally();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function nodeDelta(string|int|null $collection, string $signature, string $detail = 'ids'): Delta {
|
|
||||||
return new Delta();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build metadata array for an entity to store in .meta file
|
* Build metadata array for an entity to store in .meta file
|
||||||
*
|
*
|
||||||
@@ -758,8 +763,8 @@ class PersonalService implements ServiceBaseInterface, ServiceCollectionMutableI
|
|||||||
'uid' => $this->serviceUserId,
|
'uid' => $this->serviceUserId,
|
||||||
'cid' => $node->collection(),
|
'cid' => $node->collection(),
|
||||||
'nid' => $node->identifier(),
|
'nid' => $node->identifier(),
|
||||||
'created' => $node->created()?->format('c'),
|
'created' => $node->created()?->getTimestamp() !== null ? (((int) $node->created()?->format('U')) * 1000) + (int) $node->created()?->format('v') : null,
|
||||||
'modified' => $node->modified()?->format('c'),
|
'modified' => $node->modified()?->getTimestamp() !== null ? (((int) $node->modified()?->format('U')) * 1000) + (int) $node->modified()?->format('v') : null,
|
||||||
'size' => $node->getProperties()->size(),
|
'size' => $node->getProperties()->size(),
|
||||||
'label' => $node->getProperties()->getLabel(),
|
'label' => $node->getProperties()->getLabel(),
|
||||||
'mime' => $node->getProperties()->getMime(),
|
'mime' => $node->getProperties()->getMime(),
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ namespace KTXM\ProviderLocalDocuments\Providers;
|
|||||||
|
|
||||||
use DI\Attribute\Inject;
|
use DI\Attribute\Inject;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use KTXF\Resource\Documents\Provider\ProviderBaseInterface;
|
use KTXF\Documents\Provider\ProviderBaseInterface;
|
||||||
use KTXF\Resource\Documents\Service\ServiceBaseInterface;
|
use KTXF\Documents\Service\ServiceBaseInterface;
|
||||||
use KTXM\ProviderLocalDocuments\Module;
|
use KTXM\ProviderLocalDocuments\Module;
|
||||||
use KTXM\ProviderLocalDocuments\Providers\Personal\PersonalService;
|
use KTXM\ProviderLocalDocuments\Providers\Personal\PersonalService;
|
||||||
|
|
||||||
@@ -41,10 +41,10 @@ class Provider implements ProviderBaseInterface {
|
|||||||
public function jsonSerialize(): array
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
|
self::PROPERTY_TYPE => self::JSON_TYPE,
|
||||||
self::JSON_PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
|
self::PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
|
||||||
self::JSON_PROPERTY_LABEL => self::PROVIDER_LABEL,
|
self::PROPERTY_LABEL => self::PROVIDER_LABEL,
|
||||||
self::JSON_PROPERTY_CAPABILITIES => $this->providerAbilities,
|
self::PROPERTY_CAPABILITIES => $this->providerAbilities,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class Provider implements ProviderBaseInterface {
|
|||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return self::TYPE_CHRONO;
|
return self::TYPE_DOCUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function identifier(): string
|
public function identifier(): string
|
||||||
|
|||||||
+14
-17
@@ -220,8 +220,8 @@ class MetaStore {
|
|||||||
'cid' => empty($location) ? self::ROOT_COLLECTION_ID : $location,
|
'cid' => empty($location) ? self::ROOT_COLLECTION_ID : $location,
|
||||||
'nid' => UUID::v4(),
|
'nid' => UUID::v4(),
|
||||||
'type' => NodeType::Collection->value,
|
'type' => NodeType::Collection->value,
|
||||||
'created' => date('c'),
|
'created' => (int) round(microtime(true) * 1000),
|
||||||
'modified' => date('c'),
|
'modified' => (int) round(microtime(true) * 1000),
|
||||||
'properties' => $collection->getProperties()->toStore(),
|
'properties' => $collection->getProperties()->toStore(),
|
||||||
];
|
];
|
||||||
$data['signature'] = time();
|
$data['signature'] = time();
|
||||||
@@ -237,7 +237,7 @@ class MetaStore {
|
|||||||
*/
|
*/
|
||||||
public function collectionModify(string $tenantId, string $userId, string|int $identifier, CollectionResource $collection): CollectionResource {
|
public function collectionModify(string $tenantId, string $userId, string|int $identifier, CollectionResource $collection): CollectionResource {
|
||||||
$data = [
|
$data = [
|
||||||
'modified' => date('c'),
|
'modified' => (int) round(microtime(true) * 1000),
|
||||||
'properties' => $collection->getProperties()->toStore(),
|
'properties' => $collection->getProperties()->toStore(),
|
||||||
];
|
];
|
||||||
$data['signature'] = time();
|
$data['signature'] = time();
|
||||||
@@ -287,7 +287,7 @@ class MetaStore {
|
|||||||
public function collectionMove(string $tenantId, string $userId, string|int $identifier, string|int|null $location): CollectionResource {
|
public function collectionMove(string $tenantId, string $userId, string|int $identifier, string|int|null $location): CollectionResource {
|
||||||
$data = [
|
$data = [
|
||||||
'cid' => empty($location) ? self::ROOT_COLLECTION_ID : $location,
|
'cid' => empty($location) ? self::ROOT_COLLECTION_ID : $location,
|
||||||
'modified' => date('c'),
|
'modified' => (int) round(microtime(true) * 1000),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
||||||
@@ -311,7 +311,7 @@ class MetaStore {
|
|||||||
|
|
||||||
$source = $collections[$identifier];
|
$source = $collections[$identifier];
|
||||||
$newCollection = $this->collectionFresh();
|
$newCollection = $this->collectionFresh();
|
||||||
$newCollection->setLabel($source->getLabel());
|
$newCollection->getProperties()->fromStore($source->getProperties()->toStore());
|
||||||
|
|
||||||
$newNode = $this->collectionCreate($tenantId, $userId, $location, $newCollection);
|
$newNode = $this->collectionCreate($tenantId, $userId, $location, $newCollection);
|
||||||
|
|
||||||
@@ -420,8 +420,8 @@ class MetaStore {
|
|||||||
'cid' => empty($collection) ? self::ROOT_COLLECTION_ID : $collection,
|
'cid' => empty($collection) ? self::ROOT_COLLECTION_ID : $collection,
|
||||||
'nid' => UUID::v4(),
|
'nid' => UUID::v4(),
|
||||||
'type' => NodeType::Entity->value,
|
'type' => NodeType::Entity->value,
|
||||||
'created' => date('c'),
|
'created' => (int) round(microtime(true) * 1000),
|
||||||
'modified' => date('c'),
|
'modified' => (int) round(microtime(true) * 1000),
|
||||||
'properties' => $entity->getProperties()->toStore(),
|
'properties' => $entity->getProperties()->toStore(),
|
||||||
];
|
];
|
||||||
$entity['signature'] = time();
|
$entity['signature'] = time();
|
||||||
@@ -452,7 +452,7 @@ class MetaStore {
|
|||||||
$attributes['properties'] = $data['properties'] ?? [];
|
$attributes['properties'] = $data['properties'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes['modified'] = date('c');
|
$attributes['modified'] = (int) round(microtime(true) * 1000);
|
||||||
$attributes['signature'] = time();
|
$attributes['signature'] = time();
|
||||||
|
|
||||||
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
||||||
@@ -491,9 +491,9 @@ class MetaStore {
|
|||||||
*/
|
*/
|
||||||
public function entityMove(string $tenantId, string $userId, string|int|null $collection, string|int $identifier, string|int|null $destination): EntityResource {
|
public function entityMove(string $tenantId, string $userId, string|int|null $collection, string|int $identifier, string|int|null $destination): EntityResource {
|
||||||
$data = [
|
$data = [
|
||||||
'pid' => $destination,
|
'cid' => empty($destination) ? self::ROOT_COLLECTION_ID : $destination,
|
||||||
'modifiedOn' => date('c'),
|
'modified' => (int) round(microtime(true) * 1000),
|
||||||
'modifiedBy' => $userId,
|
'signature' => time(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
$this->_store->selectCollection($this->_NodeTable)->updateOne(
|
||||||
@@ -502,7 +502,7 @@ class MetaStore {
|
|||||||
);
|
);
|
||||||
$this->chronicleNode($tenantId, $userId, $identifier, 2);
|
$this->chronicleNode($tenantId, $userId, $identifier, 2);
|
||||||
|
|
||||||
$entities = $this->entityFetch($tenantId, $userId, $destination, $identifier);
|
$entities = $this->entityFetch($tenantId, $userId, empty($destination) ? self::ROOT_COLLECTION_ID : $destination, $identifier);
|
||||||
return $entities[$identifier];
|
return $entities[$identifier];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,10 +517,7 @@ class MetaStore {
|
|||||||
|
|
||||||
$source = $entities[$identifier];
|
$source = $entities[$identifier];
|
||||||
$newEntity = new EntityResource();
|
$newEntity = new EntityResource();
|
||||||
$newEntity->setLabel($source->getLabel());
|
$newEntity->getProperties()->fromStore($source->getProperties()->toStore());
|
||||||
$newEntity->setMime($source->getMime());
|
|
||||||
$newEntity->setFormat($source->getFormat());
|
|
||||||
$newEntity->setEncoding($source->getEncoding());
|
|
||||||
|
|
||||||
return $this->entityCreate($tenantId, $userId, $destination, $newEntity, ['path' => null]);
|
return $this->entityCreate($tenantId, $userId, $destination, $newEntity, ['path' => null]);
|
||||||
}
|
}
|
||||||
@@ -596,7 +593,7 @@ class MetaStore {
|
|||||||
$cursor = $this->_store->selectCollection($this->_NodeTable)->find([
|
$cursor = $this->_store->selectCollection($this->_NodeTable)->find([
|
||||||
'tid' => $tenantId,
|
'tid' => $tenantId,
|
||||||
'uid' => $userId,
|
'uid' => $userId,
|
||||||
'pid' => $parentId,
|
'cid' => $parentId,
|
||||||
'type' => NodeType::Collection->value,
|
'type' => NodeType::Collection->value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user