mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-15 21:56:36 +00:00
Re-internalize Flysystem extensions.
This commit is contained in:
parent
0ba1556a95
commit
c69ed7570e
|
@ -28,7 +28,6 @@
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"azuracast/doctrine-batch-utilities": "dev-main",
|
"azuracast/doctrine-batch-utilities": "dev-main",
|
||||||
"azuracast/doctrine-entity-normalizer": "dev-main",
|
"azuracast/doctrine-entity-normalizer": "dev-main",
|
||||||
"azuracast/flysystem-v2-extensions": "dev-main",
|
|
||||||
"azuracast/nowplaying": "dev-main",
|
"azuracast/nowplaying": "dev-main",
|
||||||
"azuracast/slim-callable-eventdispatcher": "dev-main",
|
"azuracast/slim-callable-eventdispatcher": "dev-main",
|
||||||
"bacon/bacon-qr-code": "^2.0",
|
"bacon/bacon-qr-code": "^2.0",
|
||||||
|
|
83
composer.lock
generated
83
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "1cbe0493b1bb2b62ab667d12fec4d738",
|
"content-hash": "418f33f513ae89bfcf89c29142408445",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
@ -264,86 +264,6 @@
|
||||||
],
|
],
|
||||||
"time": "2022-06-12T14:29:46+00:00"
|
"time": "2022-06-12T14:29:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "azuracast/flysystem-v2-extensions",
|
|
||||||
"version": "dev-main",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/AzuraCast/flysystem-v2-extensions.git",
|
|
||||||
"reference": "44915dea8668d120e540bd1acfdd4d3bd40eee20"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/AzuraCast/flysystem-v2-extensions/zipball/44915dea8668d120e540bd1acfdd4d3bd40eee20",
|
|
||||||
"reference": "44915dea8668d120e540bd1acfdd4d3bd40eee20",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"league/flysystem": "^3.1",
|
|
||||||
"php": "^8.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
|
||||||
"league/flysystem-sftp-v3": "^3.0",
|
|
||||||
"php-parallel-lint/php-console-highlighter": "^0.5.0",
|
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
|
||||||
"phpstan/phpstan": "^1.0",
|
|
||||||
"roave/security-advisories": "dev-latest",
|
|
||||||
"spatie/flysystem-dropbox": ">2.0.5"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"league/flysystem-aws-s3-v3": "AWS S3 API version 3 adapter for Flysystem V3.",
|
|
||||||
"league/flysystem-sftp-v3": "SFTP adapter for Flysystem V3",
|
|
||||||
"spatie/flysystem-dropbox": "Dropbox adapter for Flysystem V3."
|
|
||||||
},
|
|
||||||
"default-branch": true,
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Azura\\Files\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Buster Neece",
|
|
||||||
"email": "buster@busterneece.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Extensions to Flysystem V2 functionality.",
|
|
||||||
"keywords": [
|
|
||||||
"aws",
|
|
||||||
"cloud",
|
|
||||||
"file",
|
|
||||||
"files",
|
|
||||||
"filesystem",
|
|
||||||
"filesystems",
|
|
||||||
"s3",
|
|
||||||
"storage"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/AzuraCast/flysystem-v2-extensions/issues",
|
|
||||||
"source": "https://github.com/AzuraCast/flysystem-v2-extensions/tree/main"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/AzuraCast",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://opencollective.com/azuracast",
|
|
||||||
"type": "open_collective"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://www.patreon.com/AzuraCast",
|
|
||||||
"type": "patreon"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2022-10-08T22:14:27+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "azuracast/nowplaying",
|
"name": "azuracast/nowplaying",
|
||||||
"version": "dev-main",
|
"version": "dev-main",
|
||||||
|
@ -13978,7 +13898,6 @@
|
||||||
"stability-flags": {
|
"stability-flags": {
|
||||||
"azuracast/doctrine-batch-utilities": 20,
|
"azuracast/doctrine-batch-utilities": 20,
|
||||||
"azuracast/doctrine-entity-normalizer": 20,
|
"azuracast/doctrine-entity-normalizer": 20,
|
||||||
"azuracast/flysystem-v2-extensions": 20,
|
|
||||||
"azuracast/nowplaying": 20,
|
"azuracast/nowplaying": 20,
|
||||||
"azuracast/slim-callable-eventdispatcher": 20,
|
"azuracast/slim-callable-eventdispatcher": 20,
|
||||||
"lstrojny/fxmlrpc": 20,
|
"lstrojny/fxmlrpc": 20,
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace App\Controller\Api\Admin\Backups;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
final class DeleteAction extends AbstractFileAction
|
final class DeleteAction extends AbstractFileAction
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace App\Controller\Api\Admin\Backups;
|
||||||
|
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
final class DownloadAction extends AbstractFileAction
|
final class DownloadAction extends AbstractFileAction
|
||||||
|
|
|
@ -8,7 +8,7 @@ use App\Entity;
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use App\Paginator;
|
use App\Paginator;
|
||||||
use Azura\Files\Attributes\FileAttributes;
|
use App\Flysystem\Attributes\FileAttributes;
|
||||||
use League\Flysystem\StorageAttributes;
|
use League\Flysystem\StorageAttributes;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use App\Flysystem\StationFilesystems;
|
||||||
use App\Http\Response;
|
use App\Http\Response;
|
||||||
use App\Http\ServerRequest;
|
use App\Http\ServerRequest;
|
||||||
use App\OpenApi;
|
use App\OpenApi;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ use App\Radio\Backend\Liquidsoap;
|
||||||
use App\Radio\Enums\BackendAdapters;
|
use App\Radio\Enums\BackendAdapters;
|
||||||
use App\Radio\Enums\LiquidsoapQueues;
|
use App\Radio\Enums\LiquidsoapQueues;
|
||||||
use App\Utilities\File;
|
use App\Utilities\File;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use League\Flysystem\StorageAttributes;
|
use League\Flysystem\StorageAttributes;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use App\Exception\InvalidPodcastMediaFileException;
|
||||||
use App\Exception\StorageLocationFullException;
|
use App\Exception\StorageLocationFullException;
|
||||||
use App\Media\AlbumArt;
|
use App\Media\AlbumArt;
|
||||||
use App\Media\MetadataManager;
|
use App\Media\MetadataManager;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use League\Flysystem\UnableToDeleteFile;
|
use League\Flysystem\UnableToDeleteFile;
|
||||||
use League\Flysystem\UnableToRetrieveMetadata;
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use App\Doctrine\Repository;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Exception\StorageLocationFullException;
|
use App\Exception\StorageLocationFullException;
|
||||||
use App\Media\AlbumArt;
|
use App\Media\AlbumArt;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use League\Flysystem\UnableToDeleteFile;
|
use League\Flysystem\UnableToDeleteFile;
|
||||||
use League\Flysystem\UnableToRetrieveMetadata;
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ use App\Media\MetadataManager;
|
||||||
use App\Media\RemoteAlbumArt;
|
use App\Media\RemoteAlbumArt;
|
||||||
use App\Service\AudioWaveform;
|
use App\Service\AudioWaveform;
|
||||||
use App\Utilities\Logger;
|
use App\Utilities\Logger;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Generator;
|
use Generator;
|
||||||
use League\Flysystem\FilesystemException;
|
use League\Flysystem\FilesystemException;
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace App\Entity\Repository;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Flysystem\StationFilesystems;
|
use App\Flysystem\StationFilesystems;
|
||||||
use App\Service\Flow\UploadedFile;
|
use App\Service\Flow\UploadedFile;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends AbstractStationBasedRepository<Entity\StationMount>
|
* @extends AbstractStationBasedRepository<Entity\StationMount>
|
||||||
|
|
|
@ -8,10 +8,10 @@ use App\Assets\AlbumArtCustomAsset;
|
||||||
use App\Doctrine\ReloadableEntityManagerInterface;
|
use App\Doctrine\ReloadableEntityManagerInterface;
|
||||||
use App\Doctrine\Repository;
|
use App\Doctrine\Repository;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use App\Flysystem\StationFilesystems;
|
use App\Flysystem\StationFilesystems;
|
||||||
use App\Radio\Enums\StreamFormats;
|
use App\Radio\Enums\StreamFormats;
|
||||||
use App\Service\Flow\UploadedFile;
|
use App\Service\Flow\UploadedFile;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ use App\Entity\StorageLocationAdapter\StorageLocationAdapterInterface;
|
||||||
use App\Exception\StorageLocationFullException;
|
use App\Exception\StorageLocationFullException;
|
||||||
use App\Radio\Quota;
|
use App\Radio\Quota;
|
||||||
use App\Validator\Constraints as AppAssert;
|
use App\Validator\Constraints as AppAssert;
|
||||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Brick\Math\BigInteger;
|
use Brick\Math\BigInteger;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\StorageLocation;
|
use App\Entity\StorageLocation;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Azura\Files\RemoteFilesystem;
|
use App\Flysystem\RemoteFilesystem;
|
||||||
|
|
||||||
abstract class AbstractStorageLocationLocationAdapter implements StorageLocationAdapterInterface
|
abstract class AbstractStorageLocationLocationAdapter implements StorageLocationAdapterInterface
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\Enums\StorageLocationAdapters;
|
use App\Entity\Enums\StorageLocationAdapters;
|
||||||
use Azura\Files\Adapter\Dropbox\DropboxAdapter;
|
use App\Flysystem\Adapter\DropboxAdapter;
|
||||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
use Spatie\Dropbox\Client;
|
use Spatie\Dropbox\Client;
|
||||||
|
|
||||||
final class DropboxStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
final class DropboxStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||||
|
|
|
@ -5,10 +5,10 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\Enums\StorageLocationAdapters;
|
use App\Entity\Enums\StorageLocationAdapters;
|
||||||
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
|
use App\Flysystem\Adapter\LocalAdapterInterface;
|
||||||
use Azura\Files\Adapter\LocalAdapterInterface;
|
use App\Flysystem\Adapter\LocalFilesystemAdapter;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Azura\Files\LocalFilesystem;
|
use App\Flysystem\LocalFilesystem;
|
||||||
|
|
||||||
final class LocalStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
final class LocalStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,9 +5,9 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\Enums\StorageLocationAdapters;
|
use App\Entity\Enums\StorageLocationAdapters;
|
||||||
|
use App\Flysystem\Adapter\AwsS3Adapter;
|
||||||
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
use Aws\S3\S3Client;
|
use Aws\S3\S3Client;
|
||||||
use Azura\Files\Adapter\AwsS3\AwsS3Adapter;
|
|
||||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
final class S3StorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
final class S3StorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||||
|
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\Enums\StorageLocationAdapters;
|
use App\Entity\Enums\StorageLocationAdapters;
|
||||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
use Azura\Files\Adapter\Sftp\SftpAdapter;
|
use App\Flysystem\Adapter\SftpAdapter;
|
||||||
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
|
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
|
||||||
|
|
||||||
final class SftpStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
final class SftpStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||||
|
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Entity\StorageLocationAdapter;
|
namespace App\Entity\StorageLocationAdapter;
|
||||||
|
|
||||||
use App\Entity\Enums\StorageLocationAdapters;
|
use App\Entity\Enums\StorageLocationAdapters;
|
||||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
|
|
||||||
interface StorageLocationAdapterInterface
|
interface StorageLocationAdapterInterface
|
||||||
{
|
{
|
||||||
|
|
61
src/Flysystem/AbstractFilesystem.php
Normal file
61
src/Flysystem/AbstractFilesystem.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem;
|
||||||
|
|
||||||
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
|
use App\Flysystem\Normalizer\WhitespacePathNormalizer;
|
||||||
|
use League\Flysystem\Filesystem;
|
||||||
|
use League\Flysystem\PathNormalizer;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
abstract class AbstractFilesystem extends Filesystem implements ExtendedFilesystemInterface
|
||||||
|
{
|
||||||
|
protected ExtendedAdapterInterface $adapter;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ExtendedAdapterInterface $adapter,
|
||||||
|
array $config = [],
|
||||||
|
PathNormalizer $pathNormalizer = null
|
||||||
|
) {
|
||||||
|
$this->adapter = $adapter;
|
||||||
|
|
||||||
|
$pathNormalizer = $pathNormalizer ?: new WhitespacePathNormalizer();
|
||||||
|
|
||||||
|
parent::__construct($adapter, $config, $pathNormalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdapter(): ExtendedAdapterInterface
|
||||||
|
{
|
||||||
|
return $this->adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadata(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
return $this->adapter->getMetadata($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDir(string $path): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->getMetadata($path)->isDir();
|
||||||
|
} catch (UnableToRetrieveMetadata $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFile(string $path): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->getMetadata($path)->isFile();
|
||||||
|
} catch (UnableToRetrieveMetadata $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadAndDeleteOriginal(string $localPath, string $to): void
|
||||||
|
{
|
||||||
|
$this->upload($localPath, $to);
|
||||||
|
@unlink($localPath);
|
||||||
|
}
|
||||||
|
}
|
68
src/Flysystem/Adapter/AwsS3Adapter.php
Normal file
68
src/Flysystem/Adapter/AwsS3Adapter.php
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
use App\Flysystem\Attributes\DirectoryAttributes;
|
||||||
|
use App\Flysystem\Attributes\FileAttributes;
|
||||||
|
use Aws\Api\DateTimeResult;
|
||||||
|
use Aws\S3\S3ClientInterface;
|
||||||
|
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
|
||||||
|
use League\Flysystem\AwsS3V3\VisibilityConverter;
|
||||||
|
use League\Flysystem\PathPrefixer;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
use League\MimeTypeDetection\MimeTypeDetector;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
final class AwsS3Adapter extends AwsS3V3Adapter implements ExtendedAdapterInterface
|
||||||
|
{
|
||||||
|
private readonly PathPrefixer $prefixer;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly S3ClientInterface $client,
|
||||||
|
private readonly string $bucket,
|
||||||
|
string $prefix = '',
|
||||||
|
VisibilityConverter $visibility = null,
|
||||||
|
MimeTypeDetector $mimeTypeDetector = null,
|
||||||
|
array $options = [],
|
||||||
|
bool $streamReads = true
|
||||||
|
) {
|
||||||
|
$this->prefixer = new PathPrefixer($prefix);
|
||||||
|
|
||||||
|
parent::__construct($client, $bucket, $prefix, $visibility, $mimeTypeDetector, $options, $streamReads);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getMetadata(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
$arguments = ['Bucket' => $this->bucket, 'Key' => $this->prefixer->prefixPath($path)];
|
||||||
|
$command = $this->client->getCommand('HeadObject', $arguments);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$metadata = $this->client->execute($command);
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
throw UnableToRetrieveMetadata::create($path, 'metadata', '', $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($path, -1) === '/') {
|
||||||
|
return new DirectoryAttributes(rtrim($path, '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimetype = $metadata['ContentType'] ?? null;
|
||||||
|
$fileSize = $metadata['ContentLength'] ?? $metadata['Size'] ?? null;
|
||||||
|
$fileSize = $fileSize === null ? null : (int)$fileSize;
|
||||||
|
$dateTime = $metadata['LastModified'] ?? null;
|
||||||
|
$lastModified = $dateTime instanceof DateTimeResult ? $dateTime->getTimeStamp() : null;
|
||||||
|
$visibility = function ($path) {
|
||||||
|
return $this->visibility($path)->visibility();
|
||||||
|
};
|
||||||
|
|
||||||
|
return new FileAttributes(
|
||||||
|
$path,
|
||||||
|
$fileSize,
|
||||||
|
$visibility,
|
||||||
|
$lastModified,
|
||||||
|
$mimetype
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
src/Flysystem/Adapter/DropboxAdapter.php
Normal file
25
src/Flysystem/Adapter/DropboxAdapter.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
use Spatie\Dropbox\Exceptions\BadRequest;
|
||||||
|
use Spatie\FlysystemDropbox\DropboxAdapter as SpatieDropboxAdapter;
|
||||||
|
|
||||||
|
final class DropboxAdapter extends SpatieDropboxAdapter implements ExtendedAdapterInterface
|
||||||
|
{
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getMetadata(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
$location = $this->applyPathPrefix($path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->client->getMetadata($location);
|
||||||
|
} catch (BadRequest $e) {
|
||||||
|
throw UnableToRetrieveMetadata::create($location, 'metadata', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->normalizeResponse($response);
|
||||||
|
}
|
||||||
|
}
|
19
src/Flysystem/Adapter/ExtendedAdapterInterface.php
Normal file
19
src/Flysystem/Adapter/ExtendedAdapterInterface.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
use League\Flysystem\FilesystemAdapter;
|
||||||
|
use League\Flysystem\FilesystemException;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
interface ExtendedAdapterInterface extends FilesystemAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws UnableToRetrieveMetadata
|
||||||
|
* @throws FilesystemException
|
||||||
|
*/
|
||||||
|
public function getMetadata(string $path): StorageAttributes;
|
||||||
|
}
|
8
src/Flysystem/Adapter/LocalAdapterInterface.php
Normal file
8
src/Flysystem/Adapter/LocalAdapterInterface.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
interface LocalAdapterInterface extends ExtendedAdapterInterface
|
||||||
|
{
|
||||||
|
public function getLocalPath(string $path): string;
|
||||||
|
}
|
69
src/Flysystem/Adapter/LocalFilesystemAdapter.php
Normal file
69
src/Flysystem/Adapter/LocalFilesystemAdapter.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
use App\Flysystem\Attributes\DirectoryAttributes;
|
||||||
|
use App\Flysystem\Attributes\FileAttributes;
|
||||||
|
use League\Flysystem\Local\LocalFilesystemAdapter as LeagueLocalFilesystemAdapter;
|
||||||
|
use League\Flysystem\PathPrefixer;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||||
|
use League\Flysystem\UnixVisibility\VisibilityConverter;
|
||||||
|
use League\MimeTypeDetection\MimeTypeDetector;
|
||||||
|
|
||||||
|
final class LocalFilesystemAdapter extends LeagueLocalFilesystemAdapter implements LocalAdapterInterface
|
||||||
|
{
|
||||||
|
private readonly PathPrefixer $pathPrefixer;
|
||||||
|
|
||||||
|
private readonly VisibilityConverter $visibility;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $location,
|
||||||
|
VisibilityConverter $visibility = null,
|
||||||
|
int $writeFlags = LOCK_EX,
|
||||||
|
int $linkHandling = self::DISALLOW_LINKS,
|
||||||
|
MimeTypeDetector $mimeTypeDetector = null
|
||||||
|
) {
|
||||||
|
$this->pathPrefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR);
|
||||||
|
|
||||||
|
$this->visibility = $visibility ?: new PortableVisibilityConverter();
|
||||||
|
|
||||||
|
parent::__construct($location, $visibility, $writeFlags, $linkHandling, $mimeTypeDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocalPath(string $path): string
|
||||||
|
{
|
||||||
|
return $this->pathPrefixer->prefixPath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getMetadata(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
$location = $this->pathPrefixer->prefixPath($path);
|
||||||
|
|
||||||
|
if (!file_exists($location)) {
|
||||||
|
throw UnableToRetrieveMetadata::create($location, 'metadata', 'File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileInfo = new \SplFileInfo($location);
|
||||||
|
|
||||||
|
$lastModified = $fileInfo->getMTime();
|
||||||
|
$isDirectory = $fileInfo->isDir();
|
||||||
|
|
||||||
|
$permissions = $fileInfo->getPerms();
|
||||||
|
$visibility = $isDirectory
|
||||||
|
? $this->visibility->inverseForDirectory($permissions)
|
||||||
|
: $this->visibility->inverseForFile($permissions);
|
||||||
|
|
||||||
|
return $isDirectory
|
||||||
|
? new DirectoryAttributes($path, $visibility, $lastModified)
|
||||||
|
: new FileAttributes(
|
||||||
|
$path,
|
||||||
|
$fileInfo->getSize(),
|
||||||
|
$visibility,
|
||||||
|
$lastModified,
|
||||||
|
fn() => $this->mimeType($path)->mimeType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
79
src/Flysystem/Adapter/SftpAdapter.php
Normal file
79
src/Flysystem/Adapter/SftpAdapter.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Adapter;
|
||||||
|
|
||||||
|
use App\Flysystem\Attributes\DirectoryAttributes;
|
||||||
|
use App\Flysystem\Attributes\FileAttributes;
|
||||||
|
use League\Flysystem\PathPrefixer;
|
||||||
|
use League\Flysystem\PhpseclibV3\ConnectionProvider;
|
||||||
|
use League\Flysystem\PhpseclibV3\SftpAdapter as LeagueSftpAdapter;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||||
|
use League\Flysystem\UnixVisibility\VisibilityConverter;
|
||||||
|
use League\MimeTypeDetection\FinfoMimeTypeDetector;
|
||||||
|
use League\MimeTypeDetection\MimeTypeDetector;
|
||||||
|
|
||||||
|
final class SftpAdapter extends LeagueSftpAdapter implements ExtendedAdapterInterface
|
||||||
|
{
|
||||||
|
private const NET_SFTP_TYPE_DIRECTORY = 2;
|
||||||
|
|
||||||
|
private readonly VisibilityConverter $visibilityConverter;
|
||||||
|
|
||||||
|
private readonly PathPrefixer $prefixer;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ConnectionProvider $connectionProvider,
|
||||||
|
string $root,
|
||||||
|
VisibilityConverter $visibilityConverter = null,
|
||||||
|
MimeTypeDetector $mimeTypeDetector = null
|
||||||
|
) {
|
||||||
|
$this->visibilityConverter = $visibilityConverter ?: new PortableVisibilityConverter();
|
||||||
|
$this->prefixer = new PathPrefixer($root);
|
||||||
|
|
||||||
|
$mimeTypeDetector ??= new FinfoMimeTypeDetector();
|
||||||
|
|
||||||
|
parent::__construct($connectionProvider, $root, $visibilityConverter, $mimeTypeDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getMetadata(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
$location = $this->prefixer->prefixPath($path);
|
||||||
|
$connection = $this->connectionProvider->provideConnection();
|
||||||
|
$stat = $connection->stat($location);
|
||||||
|
|
||||||
|
if (!is_array($stat)) {
|
||||||
|
throw UnableToRetrieveMetadata::create($path, 'metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
$attributes = $this->convertListingToAttributes($path, $stat);
|
||||||
|
|
||||||
|
if (!$attributes instanceof FileAttributes) {
|
||||||
|
throw UnableToRetrieveMetadata::create($path, 'metadata', 'path is not a file');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertListingToAttributes(string $path, array $attributes): StorageAttributes
|
||||||
|
{
|
||||||
|
$permissions = $attributes['mode'] & 0777;
|
||||||
|
$lastModified = $attributes['mtime'] ?? null;
|
||||||
|
|
||||||
|
if ($attributes['type'] === self::NET_SFTP_TYPE_DIRECTORY) {
|
||||||
|
return new DirectoryAttributes(
|
||||||
|
ltrim($path, '/'),
|
||||||
|
$this->visibilityConverter->inverseForDirectory($permissions),
|
||||||
|
$lastModified
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileAttributes(
|
||||||
|
$path,
|
||||||
|
$attributes['size'],
|
||||||
|
$this->visibilityConverter->inverseForFile($permissions),
|
||||||
|
$lastModified
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
83
src/Flysystem/Attributes/AbstractAttributes.php
Normal file
83
src/Flysystem/Attributes/AbstractAttributes.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem\Attributes;
|
||||||
|
|
||||||
|
use League\Flysystem\ProxyArrayAccessToProperties;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
abstract class AbstractAttributes implements StorageAttributes
|
||||||
|
{
|
||||||
|
use ProxyArrayAccessToProperties;
|
||||||
|
|
||||||
|
protected string $type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param string|callable|null $visibility
|
||||||
|
* @param int|callable|null $lastModified
|
||||||
|
* @param array $extraMetadata
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
protected string $path,
|
||||||
|
protected $visibility = null,
|
||||||
|
protected $lastModified = null,
|
||||||
|
protected array $extraMetadata = []
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function path(): string
|
||||||
|
{
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function visibility(): ?string
|
||||||
|
{
|
||||||
|
$visibility = (is_callable($this->visibility))
|
||||||
|
? ($this->visibility)($this->path)
|
||||||
|
: $this->visibility;
|
||||||
|
|
||||||
|
return $visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastModified(): ?int
|
||||||
|
{
|
||||||
|
$lastModified = is_callable($this->lastModified)
|
||||||
|
? ($this->lastModified)($this->path)
|
||||||
|
: $this->lastModified;
|
||||||
|
|
||||||
|
if (null === $lastModified) {
|
||||||
|
throw UnableToRetrieveMetadata::lastModified($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extraMetadata(): array
|
||||||
|
{
|
||||||
|
return $this->extraMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFile(): bool
|
||||||
|
{
|
||||||
|
return (StorageAttributes::TYPE_FILE === $this->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDir(): bool
|
||||||
|
{
|
||||||
|
return (StorageAttributes::TYPE_DIRECTORY === $this->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPath(string $path): StorageAttributes
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
$clone->path = $path;
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
}
|
46
src/Flysystem/Attributes/DirectoryAttributes.php
Normal file
46
src/Flysystem/Attributes/DirectoryAttributes.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Flysystem\Attributes;
|
||||||
|
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
|
||||||
|
final class DirectoryAttributes extends AbstractAttributes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param string|callable|null $visibility
|
||||||
|
* @param int|callable|null $lastModified
|
||||||
|
* @param array $extraMetadata
|
||||||
|
*/
|
||||||
|
public function __construct(string $path, $visibility = null, $lastModified = null, array $extraMetadata = [])
|
||||||
|
{
|
||||||
|
$this->type = StorageAttributes::TYPE_DIRECTORY;
|
||||||
|
parent::__construct($path, $visibility, $lastModified, $extraMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $attributes): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_PATH],
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
StorageAttributes::ATTRIBUTE_TYPE => $this->type,
|
||||||
|
StorageAttributes::ATTRIBUTE_PATH => $this->path,
|
||||||
|
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
|
||||||
|
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
|
||||||
|
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
83
src/Flysystem/Attributes/FileAttributes.php
Normal file
83
src/Flysystem/Attributes/FileAttributes.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Flysystem\Attributes;
|
||||||
|
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
use League\Flysystem\UnableToRetrieveMetadata;
|
||||||
|
|
||||||
|
final class FileAttributes extends AbstractAttributes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int|callable|null $fileSize
|
||||||
|
* @param string|callable|null $visibility
|
||||||
|
* @param int|callable|null $lastModified
|
||||||
|
* @param string|callable|null $mimeType
|
||||||
|
* @param array $extraMetadata
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $path,
|
||||||
|
private $fileSize = null,
|
||||||
|
$visibility = null,
|
||||||
|
$lastModified = null,
|
||||||
|
private $mimeType = null,
|
||||||
|
array $extraMetadata = []
|
||||||
|
) {
|
||||||
|
$this->type = StorageAttributes::TYPE_FILE;
|
||||||
|
|
||||||
|
parent::__construct($path, $visibility, $lastModified, $extraMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileSize(): ?int
|
||||||
|
{
|
||||||
|
$fileSize = is_callable($this->fileSize)
|
||||||
|
? ($this->fileSize)($this->path)
|
||||||
|
: $this->fileSize;
|
||||||
|
|
||||||
|
if (null === $fileSize) {
|
||||||
|
throw UnableToRetrieveMetadata::fileSize($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mimeType(): ?string
|
||||||
|
{
|
||||||
|
$mimeType = is_callable($this->mimeType)
|
||||||
|
? ($this->mimeType)($this->path)
|
||||||
|
: $this->mimeType;
|
||||||
|
|
||||||
|
if (null === $mimeType) {
|
||||||
|
throw UnableToRetrieveMetadata::mimeType($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $attributes): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_PATH],
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null,
|
||||||
|
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE,
|
||||||
|
StorageAttributes::ATTRIBUTE_PATH => $this->path,
|
||||||
|
StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize,
|
||||||
|
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
|
||||||
|
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
|
||||||
|
StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType,
|
||||||
|
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
68
src/Flysystem/ExtendedFilesystemInterface.php
Normal file
68
src/Flysystem/ExtendedFilesystemInterface.php
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem;
|
||||||
|
|
||||||
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
|
use League\Flysystem\FilesystemOperator;
|
||||||
|
use League\Flysystem\StorageAttributes;
|
||||||
|
|
||||||
|
interface ExtendedFilesystemInterface extends FilesystemOperator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return ExtendedAdapterInterface The underlying filesystem adapter.
|
||||||
|
*/
|
||||||
|
public function getAdapter(): ExtendedAdapterInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool Whether this filesystem is directly located on disk.
|
||||||
|
*/
|
||||||
|
public function isLocal(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path The original path of the file on the filesystem.
|
||||||
|
*
|
||||||
|
* @return string A path that will be guaranteed to be local to the filesystem.
|
||||||
|
*/
|
||||||
|
public function getLocalPath(string $path): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return StorageAttributes Metadata for the specified path.
|
||||||
|
*/
|
||||||
|
public function getMetadata(string $path): StorageAttributes;
|
||||||
|
|
||||||
|
public function isDir(string $path): bool;
|
||||||
|
|
||||||
|
public function isFile(string $path): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a callable function with a path that is guaranteed to be a local path, even if
|
||||||
|
* this filesystem is a remote one, by copying to a temporary directory first in the
|
||||||
|
* case of remote filesystems.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param callable $function
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function withLocalFile(string $path, callable $function);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $localPath
|
||||||
|
* @param string $to
|
||||||
|
*/
|
||||||
|
public function uploadAndDeleteOriginal(string $localPath, string $to): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $localPath
|
||||||
|
* @param string $to
|
||||||
|
*/
|
||||||
|
public function upload(string $localPath, string $to): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $from
|
||||||
|
* @param string $localPath
|
||||||
|
*/
|
||||||
|
public function download(string $from, string $localPath): void;
|
||||||
|
}
|
99
src/Flysystem/LocalFilesystem.php
Normal file
99
src/Flysystem/LocalFilesystem.php
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem;
|
||||||
|
|
||||||
|
use App\Flysystem\Adapter\LocalAdapterInterface;
|
||||||
|
use League\Flysystem\PathNormalizer;
|
||||||
|
use League\Flysystem\UnableToCopyFile;
|
||||||
|
use League\Flysystem\UnableToCreateDirectory;
|
||||||
|
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||||
|
use League\Flysystem\UnixVisibility\VisibilityConverter;
|
||||||
|
|
||||||
|
final class LocalFilesystem extends AbstractFilesystem
|
||||||
|
{
|
||||||
|
private readonly LocalAdapterInterface $localAdapter;
|
||||||
|
|
||||||
|
private readonly VisibilityConverter $visibilityConverter;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
LocalAdapterInterface $adapter,
|
||||||
|
array $config = [],
|
||||||
|
PathNormalizer $pathNormalizer = null,
|
||||||
|
VisibilityConverter $visibilityConverter = null
|
||||||
|
) {
|
||||||
|
$this->localAdapter = $adapter;
|
||||||
|
$this->visibilityConverter = $visibilityConverter ?? new PortableVisibilityConverter();
|
||||||
|
|
||||||
|
parent::__construct($adapter, $config, $pathNormalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function isLocal(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getLocalPath(string $path): string
|
||||||
|
{
|
||||||
|
return $this->localAdapter->getLocalPath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function upload(string $localPath, string $to): void
|
||||||
|
{
|
||||||
|
$destPath = $this->getLocalPath($to);
|
||||||
|
|
||||||
|
$this->ensureDirectoryExists(
|
||||||
|
dirname($destPath),
|
||||||
|
$this->visibilityConverter->defaultForDirectories()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!@copy($localPath, $destPath)) {
|
||||||
|
throw UnableToCopyFile::fromLocationTo($localPath, $destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function download(string $from, string $localPath): void
|
||||||
|
{
|
||||||
|
$sourcePath = $this->getLocalPath($from);
|
||||||
|
|
||||||
|
$this->ensureDirectoryExists(
|
||||||
|
dirname($localPath),
|
||||||
|
$this->visibilityConverter->defaultForDirectories()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!@copy($sourcePath, $localPath)) {
|
||||||
|
throw UnableToCopyFile::fromLocationTo($sourcePath, $localPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function withLocalFile(string $path, callable $function)
|
||||||
|
{
|
||||||
|
$localPath = $this->getLocalPath($path);
|
||||||
|
return $function($localPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureDirectoryExists(string $dirname, int $visibility): void
|
||||||
|
{
|
||||||
|
if (is_dir($dirname)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_clear_last();
|
||||||
|
|
||||||
|
if (!@mkdir($dirname, $visibility, true)) {
|
||||||
|
$mkdirError = error_get_last();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearstatcache(false, $dirname);
|
||||||
|
|
||||||
|
if (!is_dir($dirname)) {
|
||||||
|
$errorMessage = $mkdirError['message'] ?? '';
|
||||||
|
|
||||||
|
throw UnableToCreateDirectory::atLocation($dirname, $errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
src/Flysystem/Normalizer/WhitespacePathNormalizer.php
Normal file
60
src/Flysystem/Normalizer/WhitespacePathNormalizer.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Flysystem\Normalizer;
|
||||||
|
|
||||||
|
use League\Flysystem\PathNormalizer;
|
||||||
|
use League\Flysystem\PathTraversalDetected;
|
||||||
|
|
||||||
|
final class WhitespacePathNormalizer implements PathNormalizer
|
||||||
|
{
|
||||||
|
public function normalizePath(string $path): string
|
||||||
|
{
|
||||||
|
$path = str_replace('\\', '/', $path);
|
||||||
|
$path = $this->removeFunkyWhiteSpace($path);
|
||||||
|
|
||||||
|
return $this->normalizeRelativePath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeFunkyWhiteSpace(string $path): string
|
||||||
|
{
|
||||||
|
// Remove unprintable characters and invalid unicode characters.
|
||||||
|
// We do this check in a loop, since removing invalid unicode characters
|
||||||
|
// can lead to new characters being created.
|
||||||
|
//
|
||||||
|
// Customized regex for zero-width chars
|
||||||
|
// @see https://github.com/thephpleague/flysystem/issues/1157
|
||||||
|
while (preg_match('#\p{C}-[\x{200C}-\x{200D}]+|^\./#u', $path)) {
|
||||||
|
$path = (string) preg_replace('#\p{C}-[\x{200C}-\x{200D}]+|^\./#u', '', $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeRelativePath(string $path): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
|
||||||
|
foreach (explode('/', $path) as $part) {
|
||||||
|
switch ($part) {
|
||||||
|
case '':
|
||||||
|
case '.':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '..':
|
||||||
|
if (empty($parts)) {
|
||||||
|
throw PathTraversalDetected::forPath($path);
|
||||||
|
}
|
||||||
|
array_pop($parts);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$parts[] = $part;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('/', $parts);
|
||||||
|
}
|
||||||
|
}
|
92
src/Flysystem/RemoteFilesystem.php
Normal file
92
src/Flysystem/RemoteFilesystem.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Flysystem;
|
||||||
|
|
||||||
|
use App\Flysystem\Adapter\ExtendedAdapterInterface;
|
||||||
|
use League\Flysystem\PathNormalizer;
|
||||||
|
use League\Flysystem\PathPrefixer;
|
||||||
|
|
||||||
|
final class RemoteFilesystem extends AbstractFilesystem
|
||||||
|
{
|
||||||
|
private readonly PathPrefixer $localPath;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ExtendedAdapterInterface $remoteAdapter,
|
||||||
|
string $localPath = null,
|
||||||
|
array $config = [],
|
||||||
|
PathNormalizer $pathNormalizer = null
|
||||||
|
) {
|
||||||
|
$this->localPath = new PathPrefixer($localPath ?? sys_get_temp_dir());
|
||||||
|
parent::__construct($remoteAdapter, $config, $pathNormalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function isLocal(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function getLocalPath(string $path): string
|
||||||
|
{
|
||||||
|
$tempLocalPath = $this->localPath->prefixPath(
|
||||||
|
substr(md5($path), 0, 10) . '_' . basename($path),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->download($path, $tempLocalPath);
|
||||||
|
return $tempLocalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function withLocalFile(string $path, callable $function)
|
||||||
|
{
|
||||||
|
$localPath = $this->getLocalPath($path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$returnVal = $function($localPath);
|
||||||
|
} finally {
|
||||||
|
unlink($localPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function upload(string $localPath, string $to): void
|
||||||
|
{
|
||||||
|
if (!is_file($localPath)) {
|
||||||
|
throw new \RuntimeException(sprintf('Source upload file not found at path: %s', $localPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stream = fopen($localPath, 'rb');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->writeStream($to, $stream);
|
||||||
|
} finally {
|
||||||
|
if (is_resource($stream)) {
|
||||||
|
fclose($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function download(string $from, string $localPath): void
|
||||||
|
{
|
||||||
|
if (is_file($localPath)) {
|
||||||
|
if (filemtime($localPath) >= $this->lastModified($from)) {
|
||||||
|
touch($localPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($localPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stream = $this->readStream($from);
|
||||||
|
|
||||||
|
file_put_contents($localPath, $stream);
|
||||||
|
|
||||||
|
if (is_resource($stream)) {
|
||||||
|
fclose($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Flysystem;
|
namespace App\Flysystem;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
|
use App\Flysystem\Adapter\LocalAdapterInterface;
|
||||||
use Azura\Files\Adapter\LocalAdapterInterface;
|
use App\Flysystem\Adapter\LocalFilesystemAdapter;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
|
||||||
use Azura\Files\LocalFilesystem;
|
|
||||||
use Azura\Files\RemoteFilesystem;
|
|
||||||
|
|
||||||
final class StationFilesystems
|
final class StationFilesystems
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace App\Http;
|
namespace App\Http;
|
||||||
|
|
||||||
use App\Nginx\CustomUrls;
|
use App\Nginx\CustomUrls;
|
||||||
use Azura\Files\Adapter\LocalAdapterInterface;
|
use App\Flysystem\Adapter\LocalAdapterInterface;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use League\Flysystem\FileAttributes;
|
use League\Flysystem\FileAttributes;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace App\Media;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Utilities\File;
|
use App\Utilities\File;
|
||||||
use Azura\DoctrineBatchUtils\ReadWriteBatchIteratorAggregate;
|
use Azura\DoctrineBatchUtils\ReadWriteBatchIteratorAggregate;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace App\Sync\Task;
|
||||||
use App\Doctrine\ReloadableEntityManagerInterface;
|
use App\Doctrine\ReloadableEntityManagerInterface;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Flysystem\StationFilesystems;
|
use App\Flysystem\StationFilesystems;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ use App\Message\ProcessCoverArtMessage;
|
||||||
use App\Message\ReprocessMediaMessage;
|
use App\Message\ReprocessMediaMessage;
|
||||||
use App\MessageQueue\QueueManagerInterface;
|
use App\MessageQueue\QueueManagerInterface;
|
||||||
use App\Radio\Quota;
|
use App\Radio\Quota;
|
||||||
use Azura\Files\Attributes\FileAttributes;
|
use App\Flysystem\Attributes\FileAttributes;
|
||||||
use Azura\Files\ExtendedFilesystemInterface;
|
use App\Flysystem\ExtendedFilesystemInterface;
|
||||||
use Brick\Math\BigInteger;
|
use Brick\Math\BigInteger;
|
||||||
use Doctrine\ORM\AbstractQuery;
|
use Doctrine\ORM\AbstractQuery;
|
||||||
use League\Flysystem\FilesystemException;
|
use League\Flysystem\FilesystemException;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user