Initial commit

This commit is contained in:
root
2025-12-21 09:57:43 -05:00
committed by Sebastian Krupinski
commit db42b6699c
35 changed files with 6458 additions and 0 deletions

748
lib/Manager.php Normal file
View File

@@ -0,0 +1,748 @@
<?php
declare(strict_types=1);
namespace KTXM\FileManager;
use InvalidArgumentException;
use KTXC\Resource\ProviderManager;
use KTXF\Files\Node\INodeBase;
use KTXF\Files\Node\INodeCollectionBase;
use KTXF\Files\Node\INodeCollectionMutable;
use KTXF\Files\Node\INodeEntityBase;
use KTXF\Files\Node\INodeEntityMutable;
use KTXF\Files\Provider\IProviderBase;
use KTXF\Files\Service\IServiceBase;
use KTXF\Files\Service\IServiceCollectionMutable;
use KTXF\Files\Service\IServiceEntityMutable;
use KTXF\Resource\Provider\ProviderInterface;
use KTXF\Resource\Range\IRangeTally;
use KTXF\Resource\Range\RangeAnchorType;
use KTXF\Resource\Range\RangeType;
use KTXF\Resource\Selector\ServiceSelector;
use KTXF\Resource\Selector\SourceSelector;
use Psr\Log\LoggerInterface;
class Manager {
public function __construct(
private LoggerInterface $logger,
private ProviderManager $providerManager,
) { }
// ==================== Provider Operations ====================
/**
* Retrieve available providers
*
* @param SourceSelector|null $sources collection of provider identifiers
*
* @return array<string,IProviderBase> collection of available providers
*/
public function providerList(string $tenantId, string $userId, ?SourceSelector $sources = null): array {
// determine filter from sources
$filter = ($sources !== null && $sources->identifiers() !== []) ? $sources->identifiers() : null;
// retrieve providers from provider manager
return $this->providerManager->providers(ProviderInterface::TYPE_FILES, $filter);
}
/**
* Confirm which providers are available
*
* @param SourceSelector $sources collection of provider identifiers to confirm
*
* @return array<string,bool> collection of providers and their availability status
*/
public function providerExtant(string $tenantId, string $userId, SourceSelector $sources): array {
$providerFilter = $sources?->identifiers() ?? [];
$providersResolved = $this->providerManager->providers(ProviderInterface::TYPE_FILES, $providerFilter);
$providersAvailable = array_keys($providersResolved);
$providersUnavailable = array_diff($providerFilter, $providersAvailable);
$responseData = array_merge(
array_fill_keys($providersAvailable, true),
array_fill_keys($providersUnavailable, false)
);
return $responseData;
}
/**
* Retrieve specific provider for specific user
*
* @param string $tenantId tenant identifier
* @param string $userId user identifier
* @param string $provider provider identifier
*
* @return IProviderBase
* @throws InvalidArgumentException
*/
public function providerFetch(string $tenantId, string $userId, string $provider): IProviderBase {
// retrieve provider
$providers = $this->providerList($tenantId, $userId, new SourceSelector([$provider => true]));
if (!isset($providers[$provider])) {
throw new InvalidArgumentException("Provider '$provider' not found");
}
return $providers[$provider];
}
// ==================== Service Operations ====================
/**
* Retrieve available services for specific user
*
* @param string $tenantId tenant identifier
* @param string $userId user identifier
* @param SourceSelector|null $sources list of provider and service identifiers
*
* @return array<string,array<string,IServiceBase>> collections of available services
*/
public function serviceList(string $tenantId, string $userId, ?SourceSelector $sources = null): array {
// retrieve providers
$providers = $this->providerList($tenantId, $userId, $sources);
// retrieve services for each provider
$responseData = [];
foreach ($providers as $provider) {
$serviceFilter = $sources[$provider->id()] instanceof ServiceSelector ? $sources[$provider->id()]->identifiers() : [];
$services = $provider->serviceList($tenantId, $userId, $serviceFilter);
$responseData[$provider->id()] = $services;
}
return $responseData;
}
/**
* Confirm which services are available
*
* @param string $tenantId tenant identifier
* @param string $userId user identifier
* @param SourceSelector|null $sources collection of provider and service identifiers to confirm
*
* @return array<string,bool> collection of providers and their availability status
*/
public function serviceExtant(string $tenantId, string $userId, SourceSelector $sources): array {
// retrieve providers
$providers = $this->providerList($tenantId, $userId, $sources);
$providersRequested = $sources->identifiers();
$providersUnavailable = array_diff($providersRequested, array_keys($providers));
// initialize response with unavailable providers
$responseData = array_fill_keys($providersUnavailable, false);
// retrieve services for each available provider
foreach ($providers as $provider) {
$serviceSelector = $sources[$provider->id()];
$serviceAvailability = $provider->serviceExtant($tenantId, $userId, ...$serviceSelector->identifiers());
$responseData[$provider->id()] = $serviceAvailability;
}
return $responseData;
}
/**
* Retrieve service for specific user
*
* @param string $tenantId tenant identifier
* @param string $userId user identifier
* @param string $providerId provider identifier
* @param string|int $serviceId service identifier
*
* @return IServiceBase
* @throws InvalidArgumentException
*/
public function serviceFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId): IServiceBase {
// retrieve provider and service
$service = $this->providerFetch($tenantId, $userId, $providerId)->serviceFetch($tenantId, $userId, $serviceId);
if ($service === null) {
throw new InvalidArgumentException("Service '$serviceId' not found for provider '$providerId'");
}
return $service;
}
// ==================== Collection Operations ====================
/**
* List collections at a location
*
* @return array<string|int,INodeCollectionBase>
*/
public function collectionList(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $location = null,
?array $filter = null,
?array $sort = null
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
// construct filter
$collectionFilter = null;
if ($filter !== null && $filter !== []) {
$collectionFilter = $service->collectionListFilter();
foreach ($filter as $attribute => $value) {
$collectionFilter->condition($attribute, $value);
}
}
// construct sort
$collectionSort = null;
if ($sort !== null && $sort !== []) {
$collectionSort = $service->collectionListSort();
foreach ($sort as $attribute => $direction) {
$collectionSort->condition($attribute, $direction);
}
}
return $service->collectionList($location, $collectionFilter, $collectionSort);
}
/**
* Check if collection exists
*/
public function collectionExtant(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier
): bool {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->collectionExtant($identifier);
}
/**
* Fetch a specific collection
*/
public function collectionFetch(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier
): ?INodeCollectionBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->collectionFetch($identifier);
}
/**
* Create a new collection
*/
public function collectionCreate(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $location,
INodeCollectionMutable|array $collection,
array $options = []
): INodeCollectionBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceCollectionMutable) {
throw new InvalidArgumentException('Service does not support collection creation');
}
if (is_array($collection)) {
$collectionObject = $service->collectionFresh();
$collectionObject->jsonDeserialize($collection);
$collection = $collectionObject;
}
return $service->collectionCreate($location, $collection, $options);
}
/**
* Modify an existing collection
*/
public function collectionModify(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier,
INodeCollectionMutable|array $collection
): INodeCollectionBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceCollectionMutable) {
throw new InvalidArgumentException('Service does not support collection modification');
}
if (is_array($collection)) {
$collectionObject = $service->collectionFresh();
$collectionObject->jsonDeserialize($collection);
$collection = $collectionObject;
}
return $service->collectionModify($identifier, $collection);
}
/**
* Destroy a collection
*/
public function collectionDestroy(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier
): bool {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceCollectionMutable) {
throw new InvalidArgumentException('Service does not support collection deletion');
}
return $service->collectionDestroy($identifier);
}
/**
* Copy a collection
*/
public function collectionCopy(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier,
string|int|null $location
): INodeCollectionBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceCollectionMutable) {
throw new InvalidArgumentException('Service does not support collection copy');
}
return $service->collectionCopy($identifier, $location);
}
/**
* Move a collection
*/
public function collectionMove(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $identifier,
string|int|null $location
): INodeCollectionBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceCollectionMutable) {
throw new InvalidArgumentException('Service does not support collection move');
}
return $service->collectionMove($identifier, $location);
}
// ==================== Entity Operations ====================
/**
* List entities in a collection
*
* @return array<string|int,INodeEntityBase>
*/
public function entityList(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
?array $filter = null,
?array $sort = null,
?array $range = null
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
// construct filter
$entityFilter = null;
if ($filter !== null && $filter !== []) {
$entityFilter = $service->entityListFilter();
foreach ($filter as $attribute => $value) {
$entityFilter->condition($attribute, $value);
}
}
// construct sort
$entitySort = null;
if ($sort !== null && $sort !== []) {
$entitySort = $service->entityListSort();
foreach ($sort as $attribute => $direction) {
$entitySort->condition($attribute, $direction);
}
}
// construct range
$entityRange = null;
if ($range !== null && $range !== [] && isset($range['type'])) {
$entityRange = $service->entityListRange(RangeType::from($range['type']));
if ($entityRange instanceof IRangeTally) {
if (isset($range['anchor'])) {
$entityRange->setAnchor(RangeAnchorType::from($range['anchor']));
}
if (isset($range['position'])) {
$entityRange->setPosition($range['position']);
}
if (isset($range['tally'])) {
$entityRange->setTally($range['tally']);
}
}
}
return $service->entityList($collection, $entityFilter, $entitySort, $entityRange);
}
/**
* Get entity delta/changes since a signature
*/
public function entityDelta(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string $signature,
string $detail = 'ids'
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityDelta($collection, $signature, $detail);
}
/**
* Check if entities exist
*
* @return array<string|int,bool>
*/
public function entityExtant(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
array $identifiers
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityExtant($collection, ...$identifiers);
}
/**
* Fetch specific entities
*
* @return array<string|int,INodeEntityBase>
*/
public function entityFetch(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
array $identifiers
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityFetch($collection, ...$identifiers);
}
/**
* Read entity content
*/
public function entityRead(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string|int $identifier
): ?string {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityRead($collection, $identifier);
}
/**
* Read entity content as stream
*
* @return resource|null
*/
public function entityReadStream(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string|int $identifier
) {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityReadStream($collection, $identifier);
}
/**
* Read entity content chunk
*/
public function entityReadChunk(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string|int $identifier,
int $offset,
int $length
): ?string {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->entityReadChunk($collection, $identifier, $offset, $length);
}
/**
* Create a new entity
*/
public function entityCreate(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
INodeEntityMutable|array $entity,
array $options = []
): INodeEntityBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity creation');
}
if (is_array($entity)) {
$entityObject = $service->entityFresh();
$entityObject->jsonDeserialize($entity);
$entity = $entityObject;
}
return $service->entityCreate($collection, $entity, $options);
}
/**
* Modify an existing entity
*/
public function entityModify(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
string|int $identifier,
INodeEntityMutable|array $entity
): INodeEntityBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity modification');
}
if (is_array($entity)) {
$entityObject = $service->entityFresh();
$entityObject->jsonDeserialize($entity);
$entity = $entityObject;
}
return $service->entityModify($collection, $identifier, $entity);
}
/**
* Destroy an entity
*/
public function entityDestroy(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
string|int $identifier
): bool {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity deletion');
}
return $service->entityDestroy($collection, $identifier);
}
/**
* Copy an entity
*/
public function entityCopy(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
string|int $identifier,
string|int|null $destination
): INodeEntityBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity copy');
}
return $service->entityCopy($collection, $identifier, $destination);
}
/**
* Move an entity
*/
public function entityMove(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
string|int $identifier,
string|int|null $destination
): INodeEntityBase {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity move');
}
return $service->entityMove($collection, $identifier, $destination);
}
/**
* Write entity content
*/
public function entityWrite(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $collection,
string|int $identifier,
string $data
): int {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity write');
}
return $service->entityWrite($collection, $identifier, $data);
}
/**
* Write entity content from stream
*
* @return resource|null
*/
public function entityWriteStream(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string|int $identifier
) {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity write stream');
}
return $service->entityWriteStream($collection, $identifier);
}
/**
* Write entity content chunk
*/
public function entityWriteChunk(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int $collection,
string|int $identifier,
int $offset,
string $data
): int {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
if (!$service instanceof IServiceEntityMutable) {
throw new InvalidArgumentException('Service does not support entity write chunk');
}
return $service->entityWriteChunk($collection, $identifier, $offset, $data);
}
// ==================== Node Operations (Unified/Recursive) ====================
/**
* List nodes (collections and entities) at a location
*
* @return array<string|int,INodeBase>
*/
public function nodeList(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $location = null,
bool $recursive = false,
?array $filter = null,
?array $sort = null,
?array $range = null
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
// construct filter
$nodeFilter = null;
if ($filter !== null && $filter !== []) {
$nodeFilter = $service->nodeListFilter();
foreach ($filter as $attribute => $value) {
$nodeFilter->condition($attribute, $value);
}
}
// construct sort
$nodeSort = null;
if ($sort !== null && $sort !== []) {
$nodeSort = $service->nodeListSort();
foreach ($sort as $attribute => $direction) {
$nodeSort->condition($attribute, $direction);
}
}
// construct range
$nodeRange = null;
if ($range !== null && $range !== [] && isset($range['type'])) {
$nodeRange = $service->nodeListRange(RangeType::from($range['type']));
if ($nodeRange instanceof IRangeTally) {
if (isset($range['anchor'])) {
$nodeRange->setAnchor(RangeAnchorType::from($range['anchor']));
}
if (isset($range['position'])) {
$nodeRange->setPosition($range['position']);
}
if (isset($range['tally'])) {
$nodeRange->setTally($range['tally']);
}
}
}
return $service->nodeList($location, $recursive, $nodeFilter, $nodeSort, $nodeRange);
}
/**
* Get node delta/changes since a signature
*/
public function nodeDelta(
string $tenantId,
string $userId,
string $providerId,
string|int $serviceId,
string|int|null $location,
string $signature,
bool $recursive = false,
string $detail = 'ids'
): array {
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
return $service->nodeDelta($location, $signature, $recursive, $detail);
}
}