Split Flysystem extensions into their own standalone library.

This commit is contained in:
Buster "Silver Eagle" Neece 2021-04-05 18:31:29 -05:00
parent 10f6ba8df1
commit 286786978c
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
23 changed files with 273 additions and 622 deletions

View File

@ -18,6 +18,7 @@
"ext-xml": "*",
"ext-xmlwriter": "*",
"azuracast/azuraforms": "dev-main",
"azuracast/flysystem-v2-extensions": "dev-main",
"azuracast/nowplaying": "dev-main",
"bacon/bacon-qr-code": "^2.0",
"beberlei/doctrineextensions": "^1.2",
@ -36,7 +37,6 @@
"james-heinrich/getid3": "^1.9.9",
"laminas/laminas-config": "^3.3",
"league/csv": "^9.6",
"league/flysystem": "^2.0",
"league/flysystem-aws-s3-v3": "^2.0",
"league/mime-type-detection": "^1.7",
"league/plates": "^3.1",

232
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e7db03c2700220e7e79750abf73add84",
"content-hash": "26509ecde178c3363ca8a49fa1b33b3e",
"packages": [
{
"name": "aws/aws-sdk-php",
"version": "3.176.2",
"version": "3.176.8",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "cfd62d9d159df12e8e1fc3c486e0a010a4c39f80"
"reference": "776b944988167fa3d563d2cbc5fcb6763424a9f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/cfd62d9d159df12e8e1fc3c486e0a010a4c39f80",
"reference": "cfd62d9d159df12e8e1fc3c486e0a010a4c39f80",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/776b944988167fa3d563d2cbc5fcb6763424a9f0",
"reference": "776b944988167fa3d563d2cbc5fcb6763424a9f0",
"shasum": ""
},
"require": {
@ -92,9 +92,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.176.2"
"source": "https://github.com/aws/aws-sdk-php/tree/3.176.8"
},
"time": "2021-03-26T18:16:22+00:00"
"time": "2021-04-05T18:16:29+00:00"
},
{
"name": "azuracast/azuraforms",
@ -151,6 +151,69 @@
},
"time": "2021-02-24T03:51:40+00:00"
},
{
"name": "azuracast/flysystem-v2-extensions",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/AzuraCast/flysystem-v2-extensions.git",
"reference": "1032d9a8dfee321cdd36f1a6002560ecd53526ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AzuraCast/flysystem-v2-extensions/zipball/1032d9a8dfee321cdd36f1a6002560ecd53526ff",
"reference": "1032d9a8dfee321cdd36f1a6002560ecd53526ff",
"shasum": ""
},
"require": {
"league/flysystem": "^2.0",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"league/flysystem-aws-s3-v3": "^2.0",
"overtrue/phplint": "^2.0",
"phpstan/phpstan": "^0.12.26",
"roave/security-advisories": "dev-latest",
"spatie/flysystem-dropbox": "^2.0"
},
"suggest": {
"league/flysystem-aws-s3-v3": "AWS S3 API version 3 adapter for Flysystem V2.",
"spatie/flysystem-dropbox": "Dropbox adapter for Flysystem V2."
},
"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"
},
"time": "2021-04-05T23:13:55+00:00"
},
{
"name": "azuracast/nowplaying",
"version": "dev-main",
@ -934,16 +997,16 @@
},
{
"name": "doctrine/common",
"version": "3.1.1",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/common.git",
"reference": "2afde5a9844126bc311cd5f548b5475e75f800d3"
"reference": "a036d90c303f3163b5be8b8fde9b6755b2be4a3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/common/zipball/2afde5a9844126bc311cd5f548b5475e75f800d3",
"reference": "2afde5a9844126bc311cd5f548b5475e75f800d3",
"url": "https://api.github.com/repos/doctrine/common/zipball/a036d90c303f3163b5be8b8fde9b6755b2be4a3a",
"reference": "a036d90c303f3163b5be8b8fde9b6755b2be4a3a",
"shasum": ""
},
"require": {
@ -956,7 +1019,8 @@
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.0",
"symfony/phpunit-bridge": "^4.0.5"
"symfony/phpunit-bridge": "^4.0.5",
"vimeo/psalm": "^4.4"
},
"type": "library",
"autoload": {
@ -1003,7 +1067,7 @@
],
"support": {
"issues": "https://github.com/doctrine/common/issues",
"source": "https://github.com/doctrine/common/tree/3.1.1"
"source": "https://github.com/doctrine/common/tree/3.1.2"
},
"funding": [
{
@ -1019,7 +1083,7 @@
"type": "tidelift"
}
],
"time": "2021-01-20T19:58:05+00:00"
"time": "2021-02-10T20:18:51+00:00"
},
{
"name": "doctrine/data-fixtures",
@ -1100,32 +1164,32 @@
},
{
"name": "doctrine/dbal",
"version": "2.12.1",
"version": "2.13.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "adce7a954a1c2f14f85e94aed90c8489af204086"
"reference": "67d56d3203b33db29834e6b2fcdbfdc50535d796"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/adce7a954a1c2f14f85e94aed90c8489af204086",
"reference": "adce7a954a1c2f14f85e94aed90c8489af204086",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/67d56d3203b33db29834e6b2fcdbfdc50535d796",
"reference": "67d56d3203b33db29834e6b2fcdbfdc50535d796",
"shasum": ""
},
"require": {
"doctrine/cache": "^1.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.0",
"ext-pdo": "*",
"php": "^7.3 || ^8"
"php": "^7.1 || ^8"
},
"require-dev": {
"doctrine/coding-standard": "^8.1",
"jetbrains/phpstorm-stubs": "^2019.1",
"phpstan/phpstan": "^0.12.40",
"phpunit/phpunit": "^9.4",
"psalm/plugin-phpunit": "^0.10.0",
"doctrine/coding-standard": "8.2.0",
"jetbrains/phpstorm-stubs": "2020.2",
"phpstan/phpstan": "0.12.81",
"phpunit/phpunit": "^7.5.20|^8.5|9.5.0",
"symfony/console": "^2.0.5|^3.0|^4.0|^5.0",
"vimeo/psalm": "^3.17.2"
"vimeo/psalm": "4.6.4"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
@ -1134,11 +1198,6 @@
"bin/doctrine-dbal"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
@ -1191,7 +1250,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/2.12.1"
"source": "https://github.com/doctrine/dbal/tree/2.13.0"
},
"funding": [
{
@ -1207,7 +1266,50 @@
"type": "tidelift"
}
],
"time": "2020-11-14T20:26:58+00:00"
"time": "2021-03-28T18:10:53+00:00"
},
{
"name": "doctrine/deprecations",
"version": "v0.5.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
"reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0|^7.0|^8.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
},
"time": "2021-03-21T12:59:47+00:00"
},
{
"name": "doctrine/event-manager",
@ -1656,16 +1758,16 @@
},
{
"name": "doctrine/orm",
"version": "2.8.2",
"version": "2.8.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/orm.git",
"reference": "ebae57eb9637acd8252b398df3121b120688ed5c"
"reference": "657a30f8ceef2a78c2ff36a9fe6c6a6717c1c448"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/orm/zipball/ebae57eb9637acd8252b398df3121b120688ed5c",
"reference": "ebae57eb9637acd8252b398df3121b120688ed5c",
"url": "https://api.github.com/repos/doctrine/orm/zipball/657a30f8ceef2a78c2ff36a9fe6c6a6717c1c448",
"reference": "657a30f8ceef2a78c2ff36a9fe6c6a6717c1c448",
"shasum": ""
},
"require": {
@ -1698,11 +1800,6 @@
"bin/doctrine"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\ORM\\": "lib/Doctrine/ORM"
@ -1742,9 +1839,9 @@
],
"support": {
"issues": "https://github.com/doctrine/orm/issues",
"source": "https://github.com/doctrine/orm/tree/2.8.2"
"source": "https://github.com/doctrine/orm/tree/2.8.3"
},
"time": "2021-02-16T22:10:18+00:00"
"time": "2021-04-01T21:16:53+00:00"
},
{
"name": "doctrine/persistence",
@ -1832,16 +1929,16 @@
},
{
"name": "egulias/email-validator",
"version": "3.1.0",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "62c3b73c581c834885acf6e120b412b76acc495a"
"reference": "c81f18a3efb941d8c4d2e025f6183b5c6d697307"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/62c3b73c581c834885acf6e120b412b76acc495a",
"reference": "62c3b73c581c834885acf6e120b412b76acc495a",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/c81f18a3efb941d8c4d2e025f6183b5c6d697307",
"reference": "c81f18a3efb941d8c4d2e025f6183b5c6d697307",
"shasum": ""
},
"require": {
@ -1888,7 +1985,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/3.1.0"
"source": "https://github.com/egulias/EmailValidator/tree/3.1.1"
},
"funding": [
{
@ -1896,7 +1993,7 @@
"type": "github"
}
],
"time": "2021-03-07T14:33:28+00:00"
"time": "2021-04-01T18:37:14+00:00"
},
{
"name": "friendsofphp/proxy-manager-lts",
@ -9057,16 +9154,16 @@
},
{
"name": "codeception/codeception",
"version": "4.1.19",
"version": "4.1.20",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "138dc9345a81ec994dcd6b9680c501a752a37b00"
"reference": "d8b16e13e1781dbc3a7ae8292117d520c89a9c5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/138dc9345a81ec994dcd6b9680c501a752a37b00",
"reference": "138dc9345a81ec994dcd6b9680c501a752a37b00",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/d8b16e13e1781dbc3a7ae8292117d520c89a9c5a",
"reference": "d8b16e13e1781dbc3a7ae8292117d520c89a9c5a",
"shasum": ""
},
"require": {
@ -9140,7 +9237,7 @@
],
"support": {
"issues": "https://github.com/Codeception/Codeception/issues",
"source": "https://github.com/Codeception/Codeception/tree/4.1.19"
"source": "https://github.com/Codeception/Codeception/tree/4.1.20"
},
"funding": [
{
@ -9148,7 +9245,7 @@
"type": "open_collective"
}
],
"time": "2021-03-28T13:26:08+00:00"
"time": "2021-04-02T16:41:51+00:00"
},
{
"name": "codeception/lib-asserts",
@ -9691,16 +9788,16 @@
},
{
"name": "filp/whoops",
"version": "2.11.0",
"version": "2.12.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "f6e14679f948d8a5cfb866fa7065a30c66bd64d3"
"reference": "d501fd2658d55491a2295ff600ae5978eaad7403"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/f6e14679f948d8a5cfb866fa7065a30c66bd64d3",
"reference": "f6e14679f948d8a5cfb866fa7065a30c66bd64d3",
"url": "https://api.github.com/repos/filp/whoops/zipball/d501fd2658d55491a2295ff600ae5978eaad7403",
"reference": "d501fd2658d55491a2295ff600ae5978eaad7403",
"shasum": ""
},
"require": {
@ -9750,7 +9847,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.11.0"
"source": "https://github.com/filp/whoops/tree/2.12.0"
},
"funding": [
{
@ -9758,7 +9855,7 @@
"type": "github"
}
],
"time": "2021-03-19T12:00:00+00:00"
"time": "2021-03-30T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -10400,16 +10497,16 @@
},
{
"name": "phpstan/phpstan",
"version": "0.12.82",
"version": "0.12.83",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11"
"reference": "4a967cec6efb46b500dd6d768657336a3ffe699f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/3920f0fb0aff39263d3a4cb0bca120a67a1a6a11",
"reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a967cec6efb46b500dd6d768657336a3ffe699f",
"reference": "4a967cec6efb46b500dd6d768657336a3ffe699f",
"shasum": ""
},
"require": {
@ -10440,7 +10537,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/0.12.82"
"source": "https://github.com/phpstan/phpstan/tree/0.12.83"
},
"funding": [
{
@ -10456,7 +10553,7 @@
"type": "tidelift"
}
],
"time": "2021-03-19T06:08:17+00:00"
"time": "2021-04-03T15:35:45+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
@ -12845,6 +12942,7 @@
"minimum-stability": "dev",
"stability-flags": {
"azuracast/azuraforms": 20,
"azuracast/flysystem-v2-extensions": 20,
"azuracast/nowplaying": 20,
"lstrojny/fxmlrpc": 20,
"supervisorphp/supervisor": 20,

View File

@ -6,7 +6,6 @@ use App\Config;
use App\Controller\AbstractLogViewerController;
use App\Entity;
use App\Exception\NotFoundException;
use App\Flysystem\FilesystemInterface;
use App\Form\BackupSettingsForm;
use App\Form\Form;
use App\Http\Response;
@ -15,8 +14,9 @@ use App\Message\BackupMessage;
use App\Session\Flash;
use App\Sync\Task\RunBackupTask;
use App\Utilities\File;
use Azura\Files\Attributes\FileAttributes;
use Azura\Files\ExtendedFilesystemInterface;
use InvalidArgumentException;
use League\Flysystem\FileAttributes;
use League\Flysystem\StorageAttributes;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Messenger\MessageBus;
@ -184,8 +184,10 @@ class BackupsController extends AbstractLogViewerController
): ResponseInterface {
[$path, $fs] = $this->getFile($path);
/** @var FilesystemInterface $fs */
return $fs->streamToResponse($response->withNoCache(), $path);
/** @var ExtendedFilesystemInterface $fs */
return $response
->withNoCache()
->streamFilesystemFile($fs, $response, $path);
}
public function deleteAction(ServerRequest $request, Response $response, $path, $csrf): ResponseInterface
@ -194,7 +196,7 @@ class BackupsController extends AbstractLogViewerController
[$path, $fs] = $this->getFile($path);
/** @var FilesystemInterface $fs */
/** @var ExtendedFilesystemInterface $fs */
$fs->delete($path);
$request->getFlash()->addMessage('<b>' . __('Backup deleted.') . '</b>', Flash::SUCCESS);
@ -204,7 +206,7 @@ class BackupsController extends AbstractLogViewerController
/**
* @param string $rawPath
*
* @return array{0: string, 1: FilesystemInterface}
* @return array{0: string, 1: ExtendedFilesystemInterface}
*/
protected function getFile(string $rawPath): array
{

View File

@ -65,7 +65,7 @@ class GetArtAction
}
if ($fsMedia->fileExists($mediaPath)) {
return $fsMedia->streamToResponse($response, $mediaPath, null, 'inline');
return $response->streamFilesystemFile($fsMedia, $mediaPath, null, 'inline');
}
return $defaultArtRedirect;

View File

@ -4,7 +4,6 @@ namespace App\Controller\Api\Stations\Files;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Flysystem\FilesystemInterface;
use App\Flysystem\StationFilesystems;
use App\Http\Response;
use App\Http\ServerRequest;
@ -12,10 +11,9 @@ use App\Message;
use App\MessageQueue\QueueManager;
use App\Radio\Backend\Liquidsoap;
use App\Utilities\File;
use Azura\Files\ExtendedFilesystemInterface;
use DoctrineBatchUtils\BatchProcessing\SimpleBatchIteratorAggregate;
use Exception;
use Jhofm\FlysystemIterator\Filter\FilterFactory;
use Jhofm\FlysystemIterator\Options\Options;
use League\Flysystem\StorageAttributes;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Messenger\MessageBus;
@ -106,7 +104,7 @@ class BatchAction
ServerRequest $request,
Entity\Station $station,
Entity\StorageLocation $storageLocation,
FilesystemInterface $fs
ExtendedFilesystemInterface $fs
): Entity\Api\BatchResult {
$result = $this->parseRequest($request, $fs, true);
@ -167,7 +165,7 @@ class BatchAction
ServerRequest $request,
Entity\Station $station,
Entity\StorageLocation $storageLocation,
FilesystemInterface $fs
ExtendedFilesystemInterface $fs
): Entity\Api\BatchResult {
$result = $this->parseRequest($request, $fs, true);
@ -257,7 +255,7 @@ class BatchAction
ServerRequest $request,
Entity\Station $station,
Entity\StorageLocation $storageLocation,
FilesystemInterface $fs
ExtendedFilesystemInterface $fs
): Entity\Api\BatchResult {
$result = $this->parseRequest($request, $fs, false);
@ -317,7 +315,7 @@ class BatchAction
ServerRequest $request,
Entity\Station $station,
Entity\StorageLocation $storageLocation,
FilesystemInterface $fs
ExtendedFilesystemInterface $fs
): Entity\Api\BatchResult {
$result = $this->parseRequest($request, $fs, true);
@ -342,7 +340,7 @@ class BatchAction
ServerRequest $request,
Entity\Station $station,
Entity\StorageLocation $storageLocation,
FilesystemInterface $fs
ExtendedFilesystemInterface $fs
): Entity\Api\BatchResult {
$result = $this->parseRequest($request, $fs, true);
@ -390,7 +388,7 @@ class BatchAction
protected function parseRequest(
ServerRequest $request,
FilesystemInterface $fs,
ExtendedFilesystemInterface $fs,
bool $recursive = false
): Entity\Api\BatchResult {
$files = array_values((array)$request->getParam('files', []));

View File

@ -27,6 +27,6 @@ class DownloadAction
->withJson(new Error(404, 'File not found.'));
}
return $fsMedia->streamToResponse($response, $path);
return $response->streamFilesystemFile($fsMedia, $path);
}
}

View File

@ -3,7 +3,6 @@
namespace App\Controller\Api\Stations\Files;
use App\Entity;
use App\Flysystem\FilesystemManager;
use App\Flysystem\StationFilesystems;
use App\Http\Response;
use App\Http\ServerRequest;
@ -13,7 +12,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr;
use Jhofm\FlysystemIterator\Options\Options;
use League\Flysystem\StorageAttributes;
use Psr\Http\Message\ResponseInterface;
use Psr\SimpleCache\CacheInterface;

View File

@ -30,6 +30,6 @@ class PlayAction
$fsStation = new StationFilesystems($station);
$fsMedia = $fsStation->getMediaFilesystem();
return $fsMedia->streamToResponse($response, $media->getPath());
return $response->streamFilesystemFile($fsMedia, $media->getPath());
}
}

View File

@ -35,6 +35,6 @@ class DownloadAction
$fsMedia = $fsStation->getMediaFilesystem();
set_time_limit(600);
return $fsMedia->streamToResponse($response, $media->getPath());
return $response->streamFilesystemFile($fsMedia, $media->getPath());
}
}

View File

@ -131,8 +131,8 @@ class BroadcastsController extends AbstractApiCrudController
$fsStation = new StationFilesystems($station);
$fsRecordings = $fsStation->getRecordingsFilesystem();
return $fsRecordings->streamToResponse(
$response,
return $response->streamFilesystemFile(
$fsRecordings,
$recordingPath,
File::sanitizeFileName($broadcast->getStreamer()->getDisplayName()) . '_' . $filename
);

View File

@ -31,7 +31,7 @@ class GetWaveformAction
if (StationMedia::UNIQUE_ID_LENGTH === strlen($media_id)) {
$waveformPath = StationMedia::getWaveformPath($media_id);
if ($fsMedia->fileExists($waveformPath)) {
return $fsMedia->streamToResponse($response, $waveformPath, null, 'inline');
return $response->streamFilesystemFile($fsMedia, $waveformPath, null, 'inline');
}
}
@ -45,6 +45,6 @@ class GetWaveformAction
$mediaRepo->updateWaveform($media);
}
return $fsMedia->streamToResponse($response, $waveformPath, null, 'inline');
return $response->streamFilesystemFile($fsMedia, $waveformPath, null, 'inline');
}
}

View File

@ -8,9 +8,9 @@ use App\Entity;
use App\Entity\StationPlaylist;
use App\Environment;
use App\Exception\CannotProcessMediaException;
use App\Flysystem\FilesystemInterface;
use App\Media\MetadataManager;
use App\Service\AudioWaveform;
use Azura\Files\ExtendedFilesystemInterface;
use Exception;
use Generator;
use Intervention\Image\Constraint;
@ -254,12 +254,12 @@ class StationMediaRepository extends Repository
*
* @param Entity\StationMedia $media
* @param string $filePath
* @param FilesystemInterface|null $fs
* @param ExtendedFilesystemInterface|null $fs
*/
public function loadFromFile(
Entity\StationMedia $media,
string $filePath,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): void {
// Load metadata from supported files.
$metadata = $this->metadataManager->getMetadata($media, $filePath);
@ -337,7 +337,7 @@ class StationMediaRepository extends Repository
public function writeAlbumArt(
Entity\StationMedia $media,
string $rawArtString,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): void {
$fs ??= $this->getFilesystem($media);
@ -361,7 +361,7 @@ class StationMediaRepository extends Repository
public function removeAlbumArt(
Entity\StationMedia $media,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): void {
$fs ??= $this->getFilesystem($media);
@ -377,7 +377,7 @@ class StationMediaRepository extends Repository
public function writeToFile(
Entity\StationMedia $media,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): bool {
$fs ??= $this->getFilesystem($media);
@ -407,7 +407,7 @@ class StationMediaRepository extends Repository
public function updateWaveform(
Entity\StationMedia $media,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): void {
$fs ??= $this->getFilesystem($media);
$fs->withLocalFile(
@ -421,7 +421,7 @@ class StationMediaRepository extends Repository
public function writeWaveform(
Entity\StationMedia $media,
string $path,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): void {
$fs ??= $this->getFilesystem($media);
@ -440,14 +440,14 @@ class StationMediaRepository extends Repository
/**
* @param Entity\StationMedia $media
* @param bool $deleteFile Whether to remove the media file itself (disabled for batch operations).
* @param FilesystemInterface|null $fs
* @param ExtendedFilesystemInterface|null $fs
*
* @return StationPlaylist[] The IDs as keys and records as values for all affected playlists.
*/
public function remove(
Entity\StationMedia $media,
bool $deleteFile = false,
?FilesystemInterface $fs = null
?ExtendedFilesystemInterface $fs = null
): array {
$fs ??= $this->getFilesystem($media);
@ -476,7 +476,7 @@ class StationMediaRepository extends Repository
return $affectedPlaylists;
}
protected function getFilesystem(Entity\StationMedia $media): FilesystemInterface
protected function getFilesystem(Entity\StationMedia $media): ExtendedFilesystemInterface
{
return $media->getStorageLocation()->getFilesystem();
}

View File

@ -5,16 +5,17 @@
namespace App\Entity;
use App\Annotations\AuditLog;
use App\Flysystem\Adapter\AdapterInterface;
use App\Flysystem\Adapter\AwsS3Adapter;
use App\Flysystem\Adapter\DropboxAdapter;
use App\Flysystem\Adapter\LocalAdapter;
use App\Flysystem\FilesystemInterface;
use App\Flysystem\LocalFilesystem;
use App\Flysystem\RemoteFilesystem;
use App\Radio\Quota;
use App\Validator\Constraints as AppAssert;
use Aws\S3\S3Client;
use Azura\Files\Adapter\AwsS3\AwsS3Adapter;
use Azura\Files\Adapter\Dropbox\DropboxAdapter;
use Azura\Files\Adapter\ExtendedAdapterInterface;
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
use Azura\Files\Adapter\LocalAdapterInterface;
use Azura\Files\ExtendedFilesystemInterface;
use Azura\Files\LocalFilesystem;
use Azura\Files\RemoteFilesystem;
use Brick\Math\BigInteger;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@ -477,7 +478,7 @@ class StorageLocation
$adapter->fileExists('/test');
}
public function getStorageAdapter(): AdapterInterface
public function getStorageAdapter(): ExtendedAdapterInterface
{
switch ($this->adapter) {
case self::ADAPTER_S3:
@ -489,7 +490,7 @@ class StorageLocation
case self::ADAPTER_LOCAL:
default:
return new LocalAdapter($this->path);
return new LocalFilesystemAdapter($this->path);
}
}
@ -522,11 +523,11 @@ class StorageLocation
return new Client($this->dropboxAuthToken);
}
public function getFilesystem(): FilesystemInterface
public function getFilesystem(): ExtendedFilesystemInterface
{
$adapter = $this->getStorageAdapter();
return ($adapter instanceof LocalAdapter)
return ($adapter instanceof LocalAdapterInterface)
? new LocalFilesystem($adapter)
: new RemoteFilesystem($adapter);
}

View File

@ -1,83 +0,0 @@
<?php
namespace App\Flysystem;
use App\Flysystem\Adapter\AdapterInterface;
use App\Http\Response;
use League\Flysystem\Filesystem;
use League\Flysystem\PathNormalizer;
use League\Flysystem\StorageAttributes;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use Psr\Http\Message\ResponseInterface;
abstract class AbstractFilesystem extends Filesystem implements FilesystemInterface
{
protected AdapterInterface $adapter;
public function __construct(AdapterInterface $adapter, array $config = [], PathNormalizer $pathNormalizer = null)
{
$this->adapter = $adapter;
parent::__construct($adapter, $config, $pathNormalizer);
}
public function getAdapter(): AdapterInterface
{
return $this->adapter;
}
public function getMetadata(string $path): StorageAttributes
{
return $this->adapter->getMetadata($path);
}
public function uploadAndDeleteOriginal(string $localPath, string $to): void
{
$this->upload($localPath, $to);
@unlink($localPath);
}
public function streamToResponse(
Response $response,
string $path,
string $fileName = null,
string $disposition = 'attachment'
): ResponseInterface {
$localPath = $this->getLocalPath($path);
$mime = new FinfoMimeTypeDetector();
$mimeType = $mime->detectMimeTypeFromFile($localPath);
$fileName ??= basename($localPath);
if ('attachment' === $disposition) {
/*
* The regex used below is to ensure that the $fileName contains only
* characters ranging from ASCII 128-255 and ASCII 0-31 and 127 are replaced with an empty string
*/
$disposition .= '; filename="' . preg_replace('/[\x00-\x1F\x7F\"]/', ' ', $fileName) . '"';
$disposition .= "; filename*=UTF-8''" . rawurlencode($fileName);
}
$response = $response->withHeader('Content-Disposition', $disposition)
->withHeader('Content-Length', filesize($localPath))
->withHeader('X-Accel-Buffering', 'no');
// Special internal nginx routes to use X-Accel-Redirect for far more performant file serving.
$specialPaths = [
'/var/azuracast/backups' => '/internal/backups',
'/var/azuracast/stations' => '/internal/stations',
];
foreach ($specialPaths as $diskPath => $nginxPath) {
if (0 === strpos($localPath, $diskPath)) {
$accelPath = str_replace($diskPath, $nginxPath, $localPath);
return $response->withHeader('Content-Type', $mimeType)
->withHeader('X-Accel-Redirect', $accelPath);
}
}
return $response->withFile($localPath, $mimeType);
}
}

View File

@ -1,15 +0,0 @@
<?php
namespace App\Flysystem\Adapter;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\StorageAttributes;
interface AdapterInterface extends FilesystemAdapter
{
/**
* @param string $path
*
*/
public function getMetadata(string $path): StorageAttributes;
}

View File

@ -1,81 +0,0 @@
<?php
namespace App\Flysystem\Adapter;
use Aws\Api\DateTimeResult;
use Aws\S3\S3ClientInterface;
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use League\Flysystem\AwsS3V3\VisibilityConverter;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\PathPrefixer;
use League\Flysystem\StorageAttributes;
use League\Flysystem\UnableToRetrieveMetadata;
use League\MimeTypeDetection\MimeTypeDetector;
use Throwable;
class AwsS3Adapter extends AwsS3V3Adapter implements AdapterInterface
{
protected S3ClientInterface $client;
protected string $bucket;
protected PathPrefixer $prefixer;
/**
* @var string[]
*/
protected const EXTRA_METADATA_FIELDS = [
'Metadata',
'StorageClass',
'ETag',
'VersionId',
];
public function __construct(
S3ClientInterface $client,
string $bucket,
string $prefix = '',
VisibilityConverter $visibility = null,
MimeTypeDetector $mimeTypeDetector = null,
array $options = [],
bool $streamReads = true
) {
$this->client = $client;
$this->bucket = $bucket;
$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;
return new FileAttributes(
$path,
$fileSize,
null,
$lastModified,
$mimetype
);
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Flysystem\Adapter;
use League\Flysystem\StorageAttributes;
use League\Flysystem\UnableToRetrieveMetadata;
use Spatie\Dropbox\Exceptions\BadRequest;
class DropboxAdapter extends \Spatie\FlysystemDropbox\DropboxAdapter implements AdapterInterface
{
/** @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);
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace App\Flysystem\Adapter;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\Local\LocalFilesystemAdapter;
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;
class LocalAdapter extends LocalFilesystemAdapter implements AdapterInterface
{
protected PathPrefixer $pathPrefixer;
protected 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 getFullPath(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);
}
}

View File

@ -1,83 +0,0 @@
<?php
namespace App\Flysystem;
use App\Flysystem\Adapter\AdapterInterface;
use App\Http\Response;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\StorageAttributes;
use Psr\Http\Message\ResponseInterface;
interface FilesystemInterface extends FilesystemOperator
{
/**
* @return AdapterInterface The underlying filesystem adapter.
*/
public function getAdapter(): AdapterInterface;
/**
* @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;
/**
* 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;
/**
* Read a stream from the filesystem and directly write it to a PSR-7-compatible response object.
*
* @param Response $response The original PSR-7 response.
* @param string $path The path on the filesystem to stream.
* @param string|null $fileName
* @param string $disposition
*
* @return ResponseInterface The modified PSR-7 response.
*/
public function streamToResponse(
Response $response,
string $path,
string $fileName = null,
string $disposition = 'attachment'
): ResponseInterface;
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Flysystem;
use App\Flysystem\Adapter\LocalAdapter;
use League\Flysystem\PathNormalizer;
class LocalFilesystem extends AbstractFilesystem
{
protected LocalAdapter $localAdapter;
public function __construct(LocalAdapter $adapter, array $config = [], PathNormalizer $pathNormalizer = null)
{
$this->localAdapter = $adapter;
parent::__construct($adapter, $config, $pathNormalizer);
}
/** @inheritDoc */
public function isLocal(): bool
{
return true;
}
/** @inheritDoc */
public function getLocalPath(string $path): string
{
return $this->localAdapter->getFullPath($path);
}
/** @inheritDoc */
public function upload(string $localPath, string $to): void
{
$destPath = $this->getLocalPath($to);
copy($localPath, $destPath);
}
/** @inheritDoc */
public function download(string $from, string $localPath): void
{
$sourcePath = $this->getLocalPath($from);
copy($sourcePath, $localPath);
}
/** @inheritDoc */
public function withLocalFile(string $path, callable $function)
{
$localPath = $this->getLocalPath($path);
return $function($localPath);
}
}

View File

@ -1,97 +0,0 @@
<?php
namespace App\Flysystem;
use App\Flysystem\Adapter\AdapterInterface;
use League\Flysystem\PathNormalizer;
use League\Flysystem\PathPrefixer;
use Spatie\FlysystemDropbox\DropboxAdapter;
class RemoteFilesystem extends AbstractFilesystem
{
protected AdapterInterface $remoteAdapter;
protected PathPrefixer $localPath;
public function __construct(
AdapterInterface $remoteAdapter,
string $localPath = null,
array $config = [],
PathNormalizer $pathNormalizer = null
) {
if ($remoteAdapter instanceof DropboxAdapter) {
$config['case_sensitive'] = false;
}
$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 (!file_exists($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 (file_exists($localPath)) {
if (filemtime($localPath) >= $this->lastModified($from)) {
touch($localPath);
}
unlink($localPath);
}
$stream = $this->readStream($from);
file_put_contents($localPath, $stream);
if (is_resource($stream)) {
fclose($stream);
}
}
}

View File

@ -3,15 +3,19 @@
namespace App\Flysystem;
use App\Entity;
use App\Flysystem\Adapter\LocalAdapter;
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
use Azura\Files\Adapter\LocalAdapterInterface;
use Azura\Files\ExtendedFilesystemInterface;
use Azura\Files\LocalFilesystem;
use Azura\Files\RemoteFilesystem;
class StationFilesystems
{
protected Entity\Station $station;
protected FilesystemInterface $fsMedia;
protected ExtendedFilesystemInterface $fsMedia;
protected FilesystemInterface $fsRecordings;
protected ExtendedFilesystemInterface $fsRecordings;
protected LocalFilesystem $fsPlaylists;
@ -24,11 +28,11 @@ class StationFilesystems
$this->station = $station;
}
public function getMediaFilesystem(): FilesystemInterface
public function getMediaFilesystem(): ExtendedFilesystemInterface
{
if (!isset($this->fsMedia)) {
$mediaAdapter = $this->station->getMediaStorageLocation()->getStorageAdapter();
if ($mediaAdapter instanceof LocalAdapter) {
if ($mediaAdapter instanceof LocalAdapterInterface) {
$this->fsMedia = new LocalFilesystem($mediaAdapter);
} else {
$tempDir = $this->station->getRadioTempDir();
@ -39,11 +43,11 @@ class StationFilesystems
return $this->fsMedia;
}
public function getRecordingsFilesystem(): FilesystemInterface
public function getRecordingsFilesystem(): ExtendedFilesystemInterface
{
if (!isset($this->fsRecordings)) {
$recordingsAdapter = $this->station->getRecordingsStorageLocation()->getStorageAdapter();
if ($recordingsAdapter instanceof LocalAdapter) {
if ($recordingsAdapter instanceof LocalAdapterInterface) {
$this->fsRecordings = new LocalFilesystem($recordingsAdapter);
} else {
$tempDir = $this->station->getRadioTempDir();
@ -58,7 +62,7 @@ class StationFilesystems
{
if (!isset($this->fsPlaylists)) {
$playlistsDir = $this->station->getRadioPlaylistsDir();
$this->fsPlaylists = new LocalFilesystem(new LocalAdapter($playlistsDir));
$this->fsPlaylists = new LocalFilesystem(new LocalFilesystemAdapter($playlistsDir));
}
return $this->fsPlaylists;
@ -68,7 +72,7 @@ class StationFilesystems
{
if (!isset($this->fsConfig)) {
$configDir = $this->station->getRadioConfigDir();
$this->fsConfig = new LocalFilesystem(new LocalAdapter($configDir));
$this->fsConfig = new LocalFilesystem(new LocalFilesystemAdapter($configDir));
}
return $this->fsConfig;
@ -78,7 +82,7 @@ class StationFilesystems
{
if (!isset($this->fsTemp)) {
$tempDir = $this->station->getRadioTempDir();
$this->fsTemp = new LocalFilesystem(new LocalAdapter($tempDir));
$this->fsTemp = new LocalFilesystem(new LocalFilesystemAdapter($tempDir));
}
return $this->fsTemp;

View File

@ -2,6 +2,9 @@
namespace App\Http;
use Azura\Files\ExtendedFilesystemInterface;
use Azura\Files\FilesystemInterface;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
@ -160,4 +163,48 @@ final class Response extends \Slim\Http\Response
return new static($response, $this->streamFactory);
}
public function streamFilesystemFile(
ExtendedFilesystemInterface $filesystem,
string $path,
string $fileName = null,
string $disposition = 'attachment'
): ResponseInterface {
$localPath = $filesystem->getLocalPath($path);
$mime = new FinfoMimeTypeDetector();
$mimeType = $mime->detectMimeTypeFromFile($localPath);
$fileName ??= basename($localPath);
if ('attachment' === $disposition) {
/*
* The regex used below is to ensure that the $fileName contains only
* characters ranging from ASCII 128-255 and ASCII 0-31 and 127 are replaced with an empty string
*/
$disposition .= '; filename="' . preg_replace('/[\x00-\x1F\x7F\"]/', ' ', $fileName) . '"';
$disposition .= "; filename*=UTF-8''" . rawurlencode($fileName);
}
$response = $this->withHeader('Content-Disposition', $disposition)
->withHeader('Content-Length', filesize($localPath))
->withHeader('X-Accel-Buffering', 'no');
// Special internal nginx routes to use X-Accel-Redirect for far more performant file serving.
$specialPaths = [
'/var/azuracast/backups' => '/internal/backups',
'/var/azuracast/stations' => '/internal/stations',
];
foreach ($specialPaths as $diskPath => $nginxPath) {
if (0 === strpos($localPath, $diskPath)) {
$accelPath = str_replace($diskPath, $nginxPath, $localPath);
return $response->withHeader('Content-Type', $mimeType)
->withHeader('X-Accel-Redirect', $accelPath);
}
}
return $response->withFile($localPath, $mimeType);
}
}