*/ #[ORM\OneToMany(mappedBy: 'storage_location', targetEntity: StationMedia::class)] protected Collection $media; public function __construct( string|StorageLocationTypes $type, string|StorageLocationAdapters $adapter ) { if (!($type instanceof StorageLocationTypes)) { $type = StorageLocationTypes::from($type); } if (!($adapter instanceof StorageLocationAdapters)) { $adapter = StorageLocationAdapters::from($adapter); } $this->type = $type->value; $this->adapter = $adapter->value; $this->media = new ArrayCollection(); } public function getType(): string { return $this->type; } public function getTypeEnum(): StorageLocationTypes { return StorageLocationTypes::from($this->type); } public function getAdapter(): string { return $this->adapter; } public function getAdapterEnum(): StorageLocationAdapters { return StorageLocationAdapters::from($this->adapter); } public function getPath(): string { return $this->path; } public function getFilteredPath(): string { return match ($this->getAdapterEnum()) { StorageLocationAdapters::S3, StorageLocationAdapters::Dropbox => trim($this->path, '/'), default => rtrim($this->path, '/') }; } public function applyPath(?string $suffix = null): string { $suffix = (null !== $suffix) ? '/' . ltrim($suffix, '/') : ''; return $this->path . $suffix; } public function setPath(string $path): void { $this->path = $this->truncateString($path); } public function getS3CredentialKey(): ?string { return $this->s3CredentialKey; } public function setS3CredentialKey(?string $s3CredentialKey): void { $this->s3CredentialKey = $this->truncateNullableString($s3CredentialKey); } public function getS3CredentialSecret(): ?string { return $this->s3CredentialSecret; } public function setS3CredentialSecret(?string $s3CredentialSecret): void { $this->s3CredentialSecret = $this->truncateNullableString($s3CredentialSecret); } public function getS3Region(): ?string { return $this->s3Region; } public function setS3Region(?string $s3Region): void { $this->s3Region = $s3Region; } public function getS3Version(): ?string { return $this->s3Version; } public function setS3Version(?string $s3Version): void { $this->s3Version = $s3Version; } public function getS3Bucket(): ?string { return $this->s3Bucket; } public function setS3Bucket(?string $s3Bucket): void { $this->s3Bucket = $s3Bucket; } public function getS3Endpoint(): ?string { return $this->s3Endpoint; } public function setS3Endpoint(?string $s3Endpoint): void { $this->s3Endpoint = $this->truncateNullableString($s3Endpoint); } public function getDropboxAuthToken(): ?string { return $this->dropboxAuthToken; } public function setDropboxAuthToken(?string $dropboxAuthToken): void { $this->dropboxAuthToken = $dropboxAuthToken; } public function getSftpHost(): ?string { return $this->sftpHost; } public function setSftpHost(?string $sftpHost): void { $this->sftpHost = $sftpHost; } public function getSftpUsername(): ?string { return $this->sftpUsername; } public function setSftpUsername(?string $sftpUsername): void { $this->sftpUsername = $sftpUsername; } public function getSftpPassword(): ?string { return $this->sftpPassword; } public function setSftpPassword(?string $sftpPassword): void { $this->sftpPassword = $sftpPassword; } public function getSftpPort(): ?int { return $this->sftpPort; } public function setSftpPort(?int $sftpPort): void { $this->sftpPort = $sftpPort; } public function getSftpPrivateKey(): ?string { return $this->sftpPrivateKey; } public function setSftpPrivateKey(?string $sftpPrivateKey): void { $this->sftpPrivateKey = $sftpPrivateKey; } public function getSftpPrivateKeyPassPhrase(): ?string { return $this->sftpPrivateKeyPassPhrase; } public function setSftpPrivateKeyPassPhrase(?string $sftpPrivateKeyPassPhrase): void { $this->sftpPrivateKeyPassPhrase = $sftpPrivateKeyPassPhrase; } public function isLocal(): bool { return $this->getAdapterEnum()->isLocal(); } public function getStorageQuota(): ?string { $raw_quota = $this->getStorageQuotaBytes(); return ($raw_quota instanceof BigInteger) ? Quota::getReadableSize($raw_quota) : ''; } /** * @param string|BigInteger|null $storageQuota */ public function setStorageQuota(BigInteger|string|null $storageQuota): void { $storageQuota = (string)Quota::convertFromReadableSize($storageQuota); $this->storageQuota = !empty($storageQuota) ? $storageQuota : null; } public function getStorageQuotaBytes(): ?BigInteger { $size = $this->storageQuota; return (null !== $size && '' !== $size) ? BigInteger::of($size) : null; } public function getStorageUsed(): ?string { $raw_size = $this->getStorageUsedBytes(); return Quota::getReadableSize($raw_size); } /** * @param string|BigInteger|null $storageUsed */ public function setStorageUsed(BigInteger|string|null $storageUsed): void { $storageUsed = (string)Quota::convertFromReadableSize($storageUsed); $this->storageUsed = !empty($storageUsed) ? $storageUsed : null; } public function getStorageUsedBytes(): BigInteger { $size = $this->storageUsed; return (null !== $size && '' !== $size) ? BigInteger::of($size) : BigInteger::zero(); } /** * Increment the current used storage total. * * @param int|string|BigInteger $newStorageAmount */ public function addStorageUsed(BigInteger|int|string $newStorageAmount): void { if (empty($newStorageAmount)) { return; } $currentStorageUsed = $this->getStorageUsedBytes(); $this->storageUsed = (string)$currentStorageUsed->plus($newStorageAmount); } /** * Decrement the current used storage total. * * @param int|string|BigInteger $amountToRemove */ public function removeStorageUsed(BigInteger|int|string $amountToRemove): void { if (empty($amountToRemove)) { return; } $storageUsed = $this->getStorageUsedBytes()->minus($amountToRemove); if ($storageUsed->isLessThan(0)) { $storageUsed = BigInteger::zero(); } $this->storageUsed = (string)$storageUsed; } public function getStorageAvailable(): string { $raw_size = $this->getStorageAvailableBytes(); return ($raw_size instanceof BigInteger) ? Quota::getReadableSize($raw_size) : ''; } public function getStorageAvailableBytes(): ?BigInteger { $quota = $this->getStorageQuotaBytes(); if ($this->isLocal()) { $localPath = $this->getPath(); $totalSpaceFloat = disk_total_space($localPath); if (is_float($totalSpaceFloat)) { $totalSpace = BigInteger::of($totalSpaceFloat); if (null === $quota || $quota->isGreaterThan($totalSpace)) { return $totalSpace; } } } return $quota ?? null; } public function isStorageFull(): bool { $available = $this->getStorageAvailableBytes(); if ($available === null) { return false; } $used = $this->getStorageUsedBytes(); return ($used->compareTo($available) !== -1); } public function canHoldFile(BigInteger|int|string $size): bool { if (empty($size)) { return true; } $available = $this->getStorageAvailableBytes(); if ($available === null) { return true; } $newStorageUsed = $this->getStorageUsedBytes()->plus($size); return ($newStorageUsed->compareTo($available) === -1); } public function errorIfFull(): void { if ($this->isStorageFull()) { throw new StorageLocationFullException(); } } public function getStorageUsePercentage(): int { $storageUsed = $this->getStorageUsedBytes(); $storageAvailable = $this->getStorageAvailableBytes(); if (null === $storageAvailable) { return 0; } return Quota::getPercentage($storageUsed, $storageAvailable); } /** * @return Collection */ public function getMedia(): Collection { return $this->media; } public function getUri(?string $suffix = null): string { $path = $this->applyPath($suffix); return match ($this->getAdapterEnum()) { StorageLocationAdapters::S3 => $this->getS3ObjectUri($suffix), StorageLocationAdapters::Dropbox => 'dropbox://' . $this->dropboxAuthToken . ltrim($path, '/'), default => $path, }; } protected function getS3ObjectUri(?string $suffix = null): string { $path = $this->applyPath($suffix); $bucket = $this->s3Bucket; if (null === $bucket) { return 'No S3 Bucket Specified'; } try { $client = $this->getS3Client(); if (empty($path)) { $objectUrl = $client->getObjectUrl($bucket, '/'); return rtrim($objectUrl, '/'); } return $client->getObjectUrl($bucket, ltrim($path, '/')); } catch (InvalidArgumentException $e) { return 'Invalid URI (' . $e->getMessage() . ')'; } } public function validate(): void { if (StorageLocationAdapters::S3 === $this->getAdapterEnum()) { $client = $this->getS3Client(); $client->listObjectsV2( [ 'Bucket' => $this->s3Bucket, 'max-keys' => 1, ] ); } $adapter = $this->getStorageAdapter(); $adapter->fileExists('/test'); } public function getStorageAdapter(): ExtendedAdapterInterface { $filteredPath = $this->getFilteredPath(); switch ($this->getAdapterEnum()) { case StorageLocationAdapters::S3: $bucket = $this->s3Bucket; if (null === $bucket) { throw new RuntimeException('Amazon S3 bucket is empty.'); } return new AwsS3Adapter($this->getS3Client(), $bucket, $filteredPath); case StorageLocationAdapters::Dropbox: return new DropboxAdapter($this->getDropboxClient(), $filteredPath); case StorageLocationAdapters::Sftp: return new SftpAdapter($this->getSftpConnectionProvider(), $filteredPath); default: return new LocalFilesystemAdapter($filteredPath); } } protected function getS3Client(): S3Client { if (StorageLocationAdapters::S3 !== $this->getAdapterEnum()) { throw new InvalidArgumentException('This storage location is not using the S3 adapter.'); } $s3Options = array_filter( [ 'credentials' => [ 'key' => $this->s3CredentialKey, 'secret' => $this->s3CredentialSecret, ], 'region' => $this->s3Region, 'version' => $this->s3Version, 'endpoint' => $this->s3Endpoint, ] ); return new S3Client($s3Options); } protected function getDropboxClient(): Client { if (StorageLocationAdapters::Dropbox !== $this->getAdapterEnum()) { throw new InvalidArgumentException('This storage location is not using the Dropbox adapter.'); } return new Client($this->dropboxAuthToken); } protected function getSftpConnectionProvider(): SftpConnectionProvider { if (StorageLocationAdapters::Sftp !== $this->getAdapterEnum()) { throw new InvalidArgumentException('This storage location is not using the SFTP adapter.'); } return new SftpConnectionProvider( $this->sftpHost ?? '', $this->sftpUsername ?? '', $this->sftpPassword, $this->sftpPrivateKey, $this->sftpPrivateKeyPassPhrase, $this->sftpPort ?? 22 ); } public function getFilesystem(): ExtendedFilesystemInterface { $adapter = $this->getStorageAdapter(); return ($adapter instanceof LocalAdapterInterface) ? new LocalFilesystem( adapter: $adapter, visibilityConverter: new PortableVisibilityConverter(defaultForDirectories: Visibility::PUBLIC) ) : new RemoteFilesystem($adapter); } public function __toString(): string { return $this->getAdapterEnum()->getName() . ': ' . $this->getUri(); } }