From ef765f4fa33b2695cba940a2157a4f7d6dd29a1f Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Tue, 2 May 2023 04:51:35 +0300 Subject: [PATCH 01/12] refactor: disable windows tests --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9e8befb..d8a2a42 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] php: [8.2] laravel: [10.*] stability: [prefer-stable] From fbb5d64decd26c6ba652be2f7c44f1172b6e2b2b Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Tue, 2 May 2023 05:06:00 +0300 Subject: [PATCH 02/12] refactor: extend readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2393eae..d9a91e4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ Laravel package for handling resumable file uploads with tus protocol and native > 100% Tus.io resumable upload [protocol](https://tus.io/protocols/resumable-upload.html) support +| Laravel Version | Package Version | Status | +|:---------------:|:---------------:|:------:| +| 10.x | ^1.0 | ✅ | + ## Installation You can install the package via composer: From 9887cdb75f4cc126f2ac21dca437ecaf029844b2 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Tue, 2 May 2023 20:51:59 +0300 Subject: [PATCH 03/12] refactor: extend functionality --- config/tus.php | 2 + .../TusClearExpiredUploadsCommand.php | 27 ++++++ src/Commands/TusCommand.php | 19 ----- src/Helpers/TusHeaderBuilder.php | 33 +++++--- src/Helpers/TusUploadMetadataManager.php | 57 +++++++++++++ src/Http/Controllers/TusUploadController.php | 60 +++++++++----- src/Tus.php | 82 ++++++++++++++++--- src/TusServiceProvider.php | 5 +- 8 files changed, 220 insertions(+), 65 deletions(-) create mode 100644 src/Commands/TusClearExpiredUploadsCommand.php delete mode 100644 src/Commands/TusCommand.php create mode 100644 src/Helpers/TusUploadMetadataManager.php diff --git a/config/tus.php b/config/tus.php index fa3d4ba..8c901b2 100644 --- a/config/tus.php +++ b/config/tus.php @@ -9,6 +9,8 @@ 'storage_disk' => env('TUS_STORAGE_DISK', 'local'), + 'storage_path' => env('TUS_STORAGE_PATH', 'tus'), + 'file_size_limit' => null, 'upload_expiration' => env('TUS_UPLOAD_EXPIRATION', 30), diff --git a/src/Commands/TusClearExpiredUploadsCommand.php b/src/Commands/TusClearExpiredUploadsCommand.php new file mode 100644 index 0000000..881a400 --- /dev/null +++ b/src/Commands/TusClearExpiredUploadsCommand.php @@ -0,0 +1,27 @@ +files(config('tus.storage_path')); + + $this->comment('All done'); + + return self::SUCCESS; + } +} diff --git a/src/Commands/TusCommand.php b/src/Commands/TusCommand.php deleted file mode 100644 index 8dba975..0000000 --- a/src/Commands/TusCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -comment('All done'); - - return self::SUCCESS; - } -} diff --git a/src/Helpers/TusHeaderBuilder.php b/src/Helpers/TusHeaderBuilder.php index 13acc99..a227deb 100644 --- a/src/Helpers/TusHeaderBuilder.php +++ b/src/Helpers/TusHeaderBuilder.php @@ -23,7 +23,7 @@ public function __construct(string $version) */ public function version(): static { - $this->headers['Tus-Version'] = $this->version; + $this->headers[ 'Tus-Version' ] = $this->version; return $this; } @@ -33,7 +33,7 @@ public function version(): static */ public function resumable(): static { - $this->headers['Tus-Resumable'] = $this->version; + $this->headers[ 'Tus-Resumable' ] = $this->version; return $this; } @@ -43,7 +43,7 @@ public function resumable(): static */ public function offset(int $offset): static { - $this->headers['Upload-Offset'] = $offset; + $this->headers[ 'Upload-Offset' ] = $offset; return $this; } @@ -53,7 +53,7 @@ public function offset(int $offset): static */ public function maxSize(): static { - $this->headers['Tus-Max-Size'] = config('tus.file_size_limit') ? (int) config('tus.file_size_limit') : (int) ini_get('post_max_size'); + $this->headers[ 'Tus-Max-Size' ] = Tus::maxFileSize(); return $this; } @@ -65,11 +65,11 @@ public function extensions(): static { $extensions = config('tus.extensions'); - if (! is_array($extensions) || empty($extensions)) { + if (!is_array($extensions) || empty($extensions)) { return $this; } - $this->headers['Tus-Extension'] = implode(',', $extensions); + $this->headers[ 'Tus-Extension' ] = implode(',', $extensions); return $this; } @@ -79,7 +79,7 @@ public function extensions(): static */ public function location(string $id): static { - $this->headers['Location'] = route('tus.patch', $id); + $this->headers[ 'Location' ] = route('tus.patch', $id); return $this; } @@ -89,11 +89,11 @@ public function location(string $id): static */ public function expires(int $lastModified): static { - if (! Tus::extensionIsActive('expiration')) { + if (!Tus::extensionIsActive('expiration')) { return $this; } - $this->headers['Upload-Expires'] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); + $this->headers[ 'Upload-Expires' ] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); return $this; } @@ -103,11 +103,18 @@ public function expires(int $lastModified): static */ public function checksumAlgorithm(): static { - if (! Tus::extensionIsActive('checksum')) { + if (!Tus::extensionIsActive('checksum')) { return $this; } - $this->headers['Tus-Checksum-Algorithm'] = implode(',', (array) config('tus.checksum_algorithm')); + $this->headers[ 'Tus-Checksum-Algorithm' ] = implode(',', (array) config('tus.checksum_algorithm')); + + return $this; + } + + public function length(?int $length = null): static + { + $this->headers[ 'Upload-Length' ] = $length; return $this; } @@ -135,9 +142,9 @@ public function forPost(string $id, int $offset, int $lastModified): static /** * @return $this */ - public function forHead(int $offset, int $lastModified): static + public function forHead(int $length, int $offset, int $lastModified): static { - $this->resumable()->offset($offset)->expires($lastModified)->maxSize(); + $this->resumable()->length($length)->offset($offset)->expires($lastModified)->maxSize(); return $this; } diff --git a/src/Helpers/TusUploadMetadataManager.php b/src/Helpers/TusUploadMetadataManager.php new file mode 100644 index 0000000..d31aa7b --- /dev/null +++ b/src/Helpers/TusUploadMetadataManager.php @@ -0,0 +1,57 @@ +explode(',') + ->mapWithKeys(function (string $data) { + $data = explode(' ', $data); + return [$data[ 0 ] => base64_decode($data[ 1 ])]; + }) + ->toArray(); + } + + public function store(string $id, ?string $rawMetadata = null, array $customMetadata = []): void + { + if (!empty($rawMetadata)) { + $customMetadata = [...$customMetadata, ...$this->parse($rawMetadata)]; + } + + match (config('tus.driver')) { + default => $this->storeInFile($id, $customMetadata), + }; + } + + protected function storeInFile(string $id, array $metadata): void + { + $path = sprintf("%s.json", Tus::path($id)); + + Tus::storage()->put($path, json_encode($metadata)); + } + + public function read(string $id): array + { + return match (config('tus.driver')) { + default => $this->readFromFile($id), + }; + } + + protected function readFromFile(string $id): array + { + $path = sprintf("%s.json", Tus::path($id)); + + return Tus::storage()->json($path) ?? []; + } + + public function readMeta(string $id, string $key): mixed + { + return $this->read($id)[ $key ] ?? null; + } + +} \ No newline at end of file diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index 30eb347..33fcd0f 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -5,8 +5,6 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller as BaseController; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; use KalynaSolutions\Tus\Facades\Tus; class TusUploadController extends BaseController @@ -21,20 +19,29 @@ public function options(): Response public function post(Request $request): Response { - $id = Str::random(40); + $id = Tus::id(); + $path = Tus::path($id); - Storage::disk(config('tus.storage_disk'))->put( - path: $id, + Tus::storage()->put( + path: $path, contents: Tus::extensionIsActive('creation-with-upload') && (int) $request->header('content-length') > 0 ? $request->getContent() : '' ); + Tus::metadata()->store( + id: $id, + rawMetadata: $request->header('upload-metadata'), + customMetadata: [ + 'size' => $request->header('upload-length'), + ] + ); + return response( status: 201, headers: Tus::headers() ->forPost( id: $id, - offset: Storage::disk(config('tus.storage_disk'))->size($id), - lastModified: Storage::disk(config('tus.storage_disk'))->lastModified($id) + offset: Tus::storage()->size($path), + lastModified: Tus::storage()->lastModified($path) ) ->toArray() ); @@ -42,7 +49,9 @@ public function post(Request $request): Response public function head(string $id): Response { - if (! Storage::disk(config('tus.storage_disk'))->exists($id)) { + $path = Tus::path($id); + + if (!Tus::storage()->exists($path)) { return response( status: 404, headers: Tus::headers()->default()->toArray() @@ -53,8 +62,9 @@ public function head(string $id): Response status: 200, headers: Tus::headers() ->forHead( - offset: Storage::disk(config('tus.storage_disk'))->size($id), - lastModified: Storage::disk(config('tus.storage_disk'))->lastModified($id) + length: Tus::metadata()->readMeta($id, 'size'), + offset: Tus::storage()->size($path), + lastModified: Tus::storage()->lastModified($path) ) ->toArray() ); @@ -62,16 +72,18 @@ public function head(string $id): Response public function patch(Request $request, string $id): Response { - if (! Storage::disk(config('tus.storage_disk'))->exists($id)) { + $path = Tus::path($id); + + if (!Tus::storage()->exists($path)) { return response( status: 404, headers: Tus::headers()->default()->toArray() ); } - if (Tus::extensionIsActive('expiration') && Tus::isUploadExpired(Storage::disk(config('tus.storage_disk'))->lastModified($id))) { + if (Tus::extensionIsActive('expiration') && Tus::isUploadExpired(Tus::storage()->lastModified($path))) { - Storage::disk(config('tus.storage_disk'))->delete($id); + Tus::storage()->delete($path); return response( status: 404, @@ -80,21 +92,27 @@ public function patch(Request $request, string $id): Response } - if ((int) $request->header('upload-offset') !== Storage::disk(config('tus.storage_disk'))->size($id)) { + if ((int) $request->header('upload-offset') !== Tus::storage()->size($path)) { return response( status: 409, headers: Tus::headers()->default()->toArray() ); } - Storage::disk(config('tus.storage_disk'))->append($id, $request->getContent(), null); + Tus::append($path, $request->getContent()); + + $offset = Tus::storage()->size($path); + + if ($offset === (int) Tus::metadata()->readMeta($id, 'size')) { + logs()->info('COMPLETED '.$id); + } return response( status: 204, headers: Tus::headers() ->forPatch( - offset: Storage::disk(config('tus.storage_disk'))->size($id), - lastModified: Storage::disk(config('tus.storage_disk'))->lastModified($id) + offset: $offset, + lastModified: Tus::storage()->lastModified($path) ) ->toArray() ); @@ -102,10 +120,12 @@ public function patch(Request $request, string $id): Response public function delete(string $id): Response { - if (! Tus::extensionIsActive('termination')) { + $path = Tus::path($id); + + if (!Tus::extensionIsActive('termination')) { $deleted = false; - } elseif (Storage::disk(config('tus.storage_disk'))->exists($id)) { - $deleted = Storage::disk(config('tus.storage_disk'))->delete($id); + } elseif (Tus::storage()->exists($path)) { + $deleted = Tus::storage()->delete($path); } else { $deleted = false; } diff --git a/src/Tus.php b/src/Tus.php index ec5c316..913880e 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -2,8 +2,14 @@ namespace KalynaSolutions\Tus; +use _PHPStan_532094bc1\Nette\Neon\Exception; +use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; use KalynaSolutions\Tus\Helpers\TusHeaderBuilder; +use KalynaSolutions\Tus\Helpers\TusUploadMetadataManager; class Tus { @@ -19,16 +25,9 @@ public function headers(): TusHeaderBuilder return new TusHeaderBuilder(static::VERSION); } - public function parseMetadata(string $rawMetadata): array + public function metadata(): TusUploadMetadataManager { - return str($rawMetadata) - ->explode(',') - ->mapWithKeys(function (string $data) { - $data = explode(' ', $data); - - return [$data[0] => $data[1]]; - }) - ->toArray(); + return new TusUploadMetadataManager; } public function isValidChecksum(string $algo, string $hash, string $payload): bool @@ -45,9 +44,23 @@ public function isUploadExpired(int $lastModified): bool return Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->isPast(); } + public function maxFileSize(): ?int + { + return match (true) { + (int) config('tus.file_size_limit') > 0 => (int) config('tus.file_size_limit'), + str_contains(ini_get('post_max_size'), 'M') => (int) ini_get('post_max_size') * 1000000, + str_contains(ini_get('post_max_size'), 'G') => (int) ini_get('post_max_size') * 1000000000, + default => null + }; + } + public function isInMaxFileSize(int $size): bool { - $limit = config('tus.file_size_limit') ? (int) config('tus.file_size_limit') : (int) ini_get('post_max_size'); + $limit = $this->maxFileSize(); + + if (is_null($limit)) { + return true; + } return $limit > $size; } @@ -56,4 +69,53 @@ public function extensionIsActive(string $extension): bool { return in_array($extension, (array) config('tus.extensions')); } + + public function id(): string + { + while (true) { + + $id = Str::random(40); + + if (!Tus::storage()->exists($this->path($id))) { + break; + } + + } + + return $id; + } + + public function path(string $id): string + { + if (!empty(config('tus.storage_path'))) { + return sprintf("%s/%s", config('tus.storage_path'), $id); + } + + return $id; + } + + public function storage(): Filesystem + { + return Storage::disk(config('tus.storage_disk')); + } + + public function append(string $path, string $data): void + { + $path = $this->storage()->path($path); + + if (!is_writable($path)) { + throw new Exception('writable'); + } + + + if (!$fp = fopen($path, 'a')) { + throw new Exception('open'); + } + + if (fwrite($fp, $data) === false) { + throw new Exception('write'); + } + + fclose($fp); + } } diff --git a/src/TusServiceProvider.php b/src/TusServiceProvider.php index 46568f4..83be16f 100644 --- a/src/TusServiceProvider.php +++ b/src/TusServiceProvider.php @@ -2,7 +2,7 @@ namespace KalynaSolutions\Tus; -use KalynaSolutions\Tus\Commands\TusCommand; +use KalynaSolutions\Tus\Commands\TusClearExpiredUploadsCommand; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -18,8 +18,7 @@ public function configurePackage(Package $package): void $package ->name('laravel-tus') ->hasConfigFile() - ->hasMigration('create_laravel-tus_table') ->hasRoute('tus') - ->hasCommand(TusCommand::class); + ->hasCommand(TusClearExpiredUploadsCommand::class); } } From 03b48ab45544b4dd663ef42e09d79d4878e7036e Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 13:33:38 +0300 Subject: [PATCH 04/12] refactor: fix file upload --- src/Exceptions/FileAppendException.php | 18 ++++++++++++++++ ...ption.php => VersionMismatchException.php} | 2 +- src/Http/Controllers/TusUploadController.php | 4 +--- .../Middleware/ValidateFileSizeMiddleware.php | 4 ++-- .../Middleware/ValidateVersionMiddleware.php | 6 +++--- src/Tus.php | 21 ++++++++++++------- 6 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 src/Exceptions/FileAppendException.php rename src/Exceptions/{TusVersionMismatchException.php => VersionMismatchException.php} (85%) diff --git a/src/Exceptions/FileAppendException.php b/src/Exceptions/FileAppendException.php new file mode 100644 index 0000000..a38c340 --- /dev/null +++ b/src/Exceptions/FileAppendException.php @@ -0,0 +1,18 @@ +default()->toArray() : $headers + ); + } +} diff --git a/src/Exceptions/TusVersionMismatchException.php b/src/Exceptions/VersionMismatchException.php similarity index 85% rename from src/Exceptions/TusVersionMismatchException.php rename to src/Exceptions/VersionMismatchException.php index c351b20..294ac87 100644 --- a/src/Exceptions/TusVersionMismatchException.php +++ b/src/Exceptions/VersionMismatchException.php @@ -5,7 +5,7 @@ use KalynaSolutions\Tus\Facades\Tus; use Symfony\Component\HttpKernel\Exception\HttpException; -class TusVersionMismatchException extends HttpException +class VersionMismatchException extends HttpException { public function __construct() { diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index 33fcd0f..f4bc85f 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -99,9 +99,7 @@ public function patch(Request $request, string $id): Response ); } - Tus::append($path, $request->getContent()); - - $offset = Tus::storage()->size($path); + $offset = Tus::storage()->size($path) + Tus::append($path, $request->getContent()); if ($offset === (int) Tus::metadata()->readMeta($id, 'size')) { logs()->info('COMPLETED '.$id); diff --git a/src/Http/Middleware/ValidateFileSizeMiddleware.php b/src/Http/Middleware/ValidateFileSizeMiddleware.php index 9483b1c..9ef831d 100644 --- a/src/Http/Middleware/ValidateFileSizeMiddleware.php +++ b/src/Http/Middleware/ValidateFileSizeMiddleware.php @@ -5,7 +5,7 @@ use Closure; use Illuminate\Http\Request; use KalynaSolutions\Tus\Exceptions\FileSizeLimitException; -use KalynaSolutions\Tus\Exceptions\TusVersionMismatchException; +use KalynaSolutions\Tus\Exceptions\VersionMismatchException; use KalynaSolutions\Tus\Facades\Tus; use Symfony\Component\HttpFoundation\Response; @@ -14,7 +14,7 @@ class ValidateFileSizeMiddleware /** * @param Closure(Request): (Response) $next * - * @throws TusVersionMismatchException + * @throws VersionMismatchException */ public function handle(Request $request, Closure $next): Response { diff --git a/src/Http/Middleware/ValidateVersionMiddleware.php b/src/Http/Middleware/ValidateVersionMiddleware.php index 490a88c..844e57c 100644 --- a/src/Http/Middleware/ValidateVersionMiddleware.php +++ b/src/Http/Middleware/ValidateVersionMiddleware.php @@ -4,7 +4,7 @@ use Closure; use Illuminate\Http\Request; -use KalynaSolutions\Tus\Exceptions\TusVersionMismatchException; +use KalynaSolutions\Tus\Exceptions\VersionMismatchException; use KalynaSolutions\Tus\Facades\Tus; use Symfony\Component\HttpFoundation\Response; @@ -13,12 +13,12 @@ class ValidateVersionMiddleware /** * @param Closure(Request): (Response) $next * - * @throws TusVersionMismatchException + * @throws VersionMismatchException */ public function handle(Request $request, Closure $next): Response { if ($request->header('tus-resumable') !== Tus::version()) { - throw new TusVersionMismatchException; + throw new VersionMismatchException; } return $next($request); diff --git a/src/Tus.php b/src/Tus.php index 913880e..81f7eae 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -2,12 +2,12 @@ namespace KalynaSolutions\Tus; -use _PHPStan_532094bc1\Nette\Neon\Exception; +use Exception; use Illuminate\Contracts\Filesystem\Filesystem; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use KalynaSolutions\Tus\Exceptions\FileAppendException; use KalynaSolutions\Tus\Helpers\TusHeaderBuilder; use KalynaSolutions\Tus\Helpers\TusUploadMetadataManager; @@ -99,23 +99,28 @@ public function storage(): Filesystem return Storage::disk(config('tus.storage_disk')); } - public function append(string $path, string $data): void + public function append(string $path, string $data): int { $path = $this->storage()->path($path); if (!is_writable($path)) { - throw new Exception('writable'); + throw new FileAppendException(message: 'File not exists or not writable'); } + $fp = fopen($path, 'a'); - if (!$fp = fopen($path, 'a')) { - throw new Exception('open'); + if ($fp === false) { + throw new FileAppendException(message: 'File open error'); } - if (fwrite($fp, $data) === false) { - throw new Exception('write'); + $fw = fwrite($fp, $data); + + if ($fw === false) { + throw new FileAppendException(message: 'File write error'); } fclose($fp); + + return $fw; } } From f909d4a8f5f83068e55fd8cce78fc785efe58ccd Mon Sep 17 00:00:00 2001 From: ArthurPatriot Date: Wed, 3 May 2023 10:34:06 +0000 Subject: [PATCH 05/12] Fix styling --- .../TusClearExpiredUploadsCommand.php | 1 - src/Helpers/TusHeaderBuilder.php | 24 +++++++++---------- src/Helpers/TusUploadMetadataManager.php | 14 +++++------ src/Http/Controllers/TusUploadController.php | 6 ++--- src/Tus.php | 9 ++++--- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/Commands/TusClearExpiredUploadsCommand.php b/src/Commands/TusClearExpiredUploadsCommand.php index 881a400..6955e8d 100644 --- a/src/Commands/TusClearExpiredUploadsCommand.php +++ b/src/Commands/TusClearExpiredUploadsCommand.php @@ -3,7 +3,6 @@ namespace KalynaSolutions\Tus\Commands; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Storage; use KalynaSolutions\Tus\Facades\Tus; class TusClearExpiredUploadsCommand extends Command diff --git a/src/Helpers/TusHeaderBuilder.php b/src/Helpers/TusHeaderBuilder.php index a227deb..89675b3 100644 --- a/src/Helpers/TusHeaderBuilder.php +++ b/src/Helpers/TusHeaderBuilder.php @@ -23,7 +23,7 @@ public function __construct(string $version) */ public function version(): static { - $this->headers[ 'Tus-Version' ] = $this->version; + $this->headers['Tus-Version'] = $this->version; return $this; } @@ -33,7 +33,7 @@ public function version(): static */ public function resumable(): static { - $this->headers[ 'Tus-Resumable' ] = $this->version; + $this->headers['Tus-Resumable'] = $this->version; return $this; } @@ -43,7 +43,7 @@ public function resumable(): static */ public function offset(int $offset): static { - $this->headers[ 'Upload-Offset' ] = $offset; + $this->headers['Upload-Offset'] = $offset; return $this; } @@ -53,7 +53,7 @@ public function offset(int $offset): static */ public function maxSize(): static { - $this->headers[ 'Tus-Max-Size' ] = Tus::maxFileSize(); + $this->headers['Tus-Max-Size'] = Tus::maxFileSize(); return $this; } @@ -65,11 +65,11 @@ public function extensions(): static { $extensions = config('tus.extensions'); - if (!is_array($extensions) || empty($extensions)) { + if (! is_array($extensions) || empty($extensions)) { return $this; } - $this->headers[ 'Tus-Extension' ] = implode(',', $extensions); + $this->headers['Tus-Extension'] = implode(',', $extensions); return $this; } @@ -79,7 +79,7 @@ public function extensions(): static */ public function location(string $id): static { - $this->headers[ 'Location' ] = route('tus.patch', $id); + $this->headers['Location'] = route('tus.patch', $id); return $this; } @@ -89,11 +89,11 @@ public function location(string $id): static */ public function expires(int $lastModified): static { - if (!Tus::extensionIsActive('expiration')) { + if (! Tus::extensionIsActive('expiration')) { return $this; } - $this->headers[ 'Upload-Expires' ] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); + $this->headers['Upload-Expires'] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); return $this; } @@ -103,18 +103,18 @@ public function expires(int $lastModified): static */ public function checksumAlgorithm(): static { - if (!Tus::extensionIsActive('checksum')) { + if (! Tus::extensionIsActive('checksum')) { return $this; } - $this->headers[ 'Tus-Checksum-Algorithm' ] = implode(',', (array) config('tus.checksum_algorithm')); + $this->headers['Tus-Checksum-Algorithm'] = implode(',', (array) config('tus.checksum_algorithm')); return $this; } public function length(?int $length = null): static { - $this->headers[ 'Upload-Length' ] = $length; + $this->headers['Upload-Length'] = $length; return $this; } diff --git a/src/Helpers/TusUploadMetadataManager.php b/src/Helpers/TusUploadMetadataManager.php index d31aa7b..2a7765f 100644 --- a/src/Helpers/TusUploadMetadataManager.php +++ b/src/Helpers/TusUploadMetadataManager.php @@ -12,14 +12,15 @@ public function parse(string $rawMetadata): array ->explode(',') ->mapWithKeys(function (string $data) { $data = explode(' ', $data); - return [$data[ 0 ] => base64_decode($data[ 1 ])]; + + return [$data[0] => base64_decode($data[1])]; }) ->toArray(); } public function store(string $id, ?string $rawMetadata = null, array $customMetadata = []): void { - if (!empty($rawMetadata)) { + if (! empty($rawMetadata)) { $customMetadata = [...$customMetadata, ...$this->parse($rawMetadata)]; } @@ -30,7 +31,7 @@ public function store(string $id, ?string $rawMetadata = null, array $customMeta protected function storeInFile(string $id, array $metadata): void { - $path = sprintf("%s.json", Tus::path($id)); + $path = sprintf('%s.json', Tus::path($id)); Tus::storage()->put($path, json_encode($metadata)); } @@ -44,14 +45,13 @@ public function read(string $id): array protected function readFromFile(string $id): array { - $path = sprintf("%s.json", Tus::path($id)); + $path = sprintf('%s.json', Tus::path($id)); return Tus::storage()->json($path) ?? []; } public function readMeta(string $id, string $key): mixed { - return $this->read($id)[ $key ] ?? null; + return $this->read($id)[$key] ?? null; } - -} \ No newline at end of file +} diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index f4bc85f..9f5e305 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -51,7 +51,7 @@ public function head(string $id): Response { $path = Tus::path($id); - if (!Tus::storage()->exists($path)) { + if (! Tus::storage()->exists($path)) { return response( status: 404, headers: Tus::headers()->default()->toArray() @@ -74,7 +74,7 @@ public function patch(Request $request, string $id): Response { $path = Tus::path($id); - if (!Tus::storage()->exists($path)) { + if (! Tus::storage()->exists($path)) { return response( status: 404, headers: Tus::headers()->default()->toArray() @@ -120,7 +120,7 @@ public function delete(string $id): Response { $path = Tus::path($id); - if (!Tus::extensionIsActive('termination')) { + if (! Tus::extensionIsActive('termination')) { $deleted = false; } elseif (Tus::storage()->exists($path)) { $deleted = Tus::storage()->delete($path); diff --git a/src/Tus.php b/src/Tus.php index 81f7eae..20954bb 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -2,7 +2,6 @@ namespace KalynaSolutions\Tus; -use Exception; use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Storage; @@ -76,7 +75,7 @@ public function id(): string $id = Str::random(40); - if (!Tus::storage()->exists($this->path($id))) { + if (! Tus::storage()->exists($this->path($id))) { break; } @@ -87,8 +86,8 @@ public function id(): string public function path(string $id): string { - if (!empty(config('tus.storage_path'))) { - return sprintf("%s/%s", config('tus.storage_path'), $id); + if (! empty(config('tus.storage_path'))) { + return sprintf('%s/%s', config('tus.storage_path'), $id); } return $id; @@ -103,7 +102,7 @@ public function append(string $path, string $data): int { $path = $this->storage()->path($path); - if (!is_writable($path)) { + if (! is_writable($path)) { throw new FileAppendException(message: 'File not exists or not writable'); } From 7c5552f9a5403d7387f50ccc58298504d6517115 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 15:30:34 +0300 Subject: [PATCH 06/12] feat: events support --- src/Events/FileUploadCreated.php | 16 +++++ src/Events/FileUploadFinished.php | 16 +++++ src/Events/FileUploadStarted.php | 16 +++++ src/Exceptions/FileNotFoundException.php | 17 +++++ src/Helpers/TusFile.php | 58 ++++++++++++++++ src/Helpers/TusHeaderBuilder.php | 23 +++++-- src/Helpers/TusUploadMetadataManager.php | 4 +- src/Http/Controllers/TusUploadController.php | 71 ++++++-------------- src/Tus.php | 2 +- 9 files changed, 165 insertions(+), 58 deletions(-) create mode 100644 src/Events/FileUploadCreated.php create mode 100644 src/Events/FileUploadFinished.php create mode 100644 src/Events/FileUploadStarted.php create mode 100644 src/Exceptions/FileNotFoundException.php create mode 100644 src/Helpers/TusFile.php diff --git a/src/Events/FileUploadCreated.php b/src/Events/FileUploadCreated.php new file mode 100644 index 0000000..09c5c79 --- /dev/null +++ b/src/Events/FileUploadCreated.php @@ -0,0 +1,16 @@ +default()->toArray() + ); + } +} diff --git a/src/Helpers/TusFile.php b/src/Helpers/TusFile.php new file mode 100644 index 0000000..2d40a30 --- /dev/null +++ b/src/Helpers/TusFile.php @@ -0,0 +1,58 @@ +id = $id; + $this->path = $path; + $this->disk = config('tus.storage_disk'); + $this->metadata = $metadata; + } + + public static function create(string $id = null, int $size = 0, ?string $rawMetadata = null): static + { + if (!$id) { + $id = Tus::id(); + } + + return new static( + id: $id, + path: Tus::path($id), + metadata: Tus::metadata()->store( + id: $id, + rawMetadata: $rawMetadata, + customMetadata: [ + 'size' => $size, + ] + ) + ); + } + + public static function find(string $id): static + { + $path = Tus::path($id); + + if (!Tus::storage()->exists($path)) { + throw new FileNotFoundException; + } + + return new static( + id: $id, + path: $path, + metadata: Tus::metadata()->read($id) + ); + } +} \ No newline at end of file diff --git a/src/Helpers/TusHeaderBuilder.php b/src/Helpers/TusHeaderBuilder.php index a227deb..4286445 100644 --- a/src/Helpers/TusHeaderBuilder.php +++ b/src/Helpers/TusHeaderBuilder.php @@ -112,8 +112,12 @@ public function checksumAlgorithm(): static return $this; } - public function length(?int $length = null): static + public function length(int $length): static { + if ($length === 0) { + return $this; + } + $this->headers[ 'Upload-Length' ] = $length; return $this; @@ -132,9 +136,13 @@ public function forOptions(): static /** * @return $this */ - public function forPost(string $id, int $offset, int $lastModified): static + public function forPost(TusFile $tusFile): static { - $this->resumable()->location($id)->offset($offset)->expires($lastModified)->maxSize(); + $this->resumable() + ->location($tusFile->id) + ->offset(Tus::storage()->size($tusFile->path)) + ->expires(Tus::storage()->lastModified($tusFile->path)) + ->maxSize(); return $this; } @@ -142,9 +150,12 @@ public function forPost(string $id, int $offset, int $lastModified): static /** * @return $this */ - public function forHead(int $length, int $offset, int $lastModified): static + public function forHead(TusFile $tusFile): static { - $this->resumable()->length($length)->offset($offset)->expires($lastModified)->maxSize(); + $this->resumable() + ->length($tusFile->metadata[ 'size' ]) + ->offset(Tus::storage()->size($tusFile->path)) + ->expires(Tus::storage()->lastModified($tusFile->path)); return $this; } @@ -154,7 +165,7 @@ public function forHead(int $length, int $offset, int $lastModified): static */ public function forPatch(int $offset, int $lastModified): static { - $this->resumable()->offset($offset)->expires($lastModified)->maxSize(); + $this->resumable()->offset($offset)->expires($lastModified); return $this; } diff --git a/src/Helpers/TusUploadMetadataManager.php b/src/Helpers/TusUploadMetadataManager.php index d31aa7b..9b3426b 100644 --- a/src/Helpers/TusUploadMetadataManager.php +++ b/src/Helpers/TusUploadMetadataManager.php @@ -17,7 +17,7 @@ public function parse(string $rawMetadata): array ->toArray(); } - public function store(string $id, ?string $rawMetadata = null, array $customMetadata = []): void + public function store(string $id, ?string $rawMetadata = null, array $customMetadata = []): array { if (!empty($rawMetadata)) { $customMetadata = [...$customMetadata, ...$this->parse($rawMetadata)]; @@ -26,6 +26,8 @@ public function store(string $id, ?string $rawMetadata = null, array $customMeta match (config('tus.driver')) { default => $this->storeInFile($id, $customMetadata), }; + + return $customMetadata; } protected function storeInFile(string $id, array $metadata): void diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index f4bc85f..3cf925e 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -5,7 +5,10 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller as BaseController; +use KalynaSolutions\Tus\Events\FileUploadCreated; +use KalynaSolutions\Tus\Events\FileUploadFinished; use KalynaSolutions\Tus\Facades\Tus; +use KalynaSolutions\Tus\Helpers\TusFile; class TusUploadController extends BaseController { @@ -19,71 +22,41 @@ public function options(): Response public function post(Request $request): Response { - $id = Tus::id(); - $path = Tus::path($id); + $tusFile = TusFile::create( + size: $request->header('upload-length', 0), + rawMetadata: $request->header('upload-metadata') + ); Tus::storage()->put( - path: $path, + path: $tusFile->path, contents: Tus::extensionIsActive('creation-with-upload') && (int) $request->header('content-length') > 0 ? $request->getContent() : '' ); - Tus::metadata()->store( - id: $id, - rawMetadata: $request->header('upload-metadata'), - customMetadata: [ - 'size' => $request->header('upload-length'), - ] - ); + event(new FileUploadCreated($tusFile)); return response( status: 201, - headers: Tus::headers() - ->forPost( - id: $id, - offset: Tus::storage()->size($path), - lastModified: Tus::storage()->lastModified($path) - ) - ->toArray() + headers: Tus::headers()->forPost($tusFile)->toArray() ); } public function head(string $id): Response { - $path = Tus::path($id); - - if (!Tus::storage()->exists($path)) { - return response( - status: 404, - headers: Tus::headers()->default()->toArray() - ); - } + $tusFile = TusFile::find($id); return response( status: 200, - headers: Tus::headers() - ->forHead( - length: Tus::metadata()->readMeta($id, 'size'), - offset: Tus::storage()->size($path), - lastModified: Tus::storage()->lastModified($path) - ) - ->toArray() + headers: Tus::headers()->forHead($tusFile)->toArray() ); } public function patch(Request $request, string $id): Response { - $path = Tus::path($id); - - if (!Tus::storage()->exists($path)) { - return response( - status: 404, - headers: Tus::headers()->default()->toArray() - ); - } + $tusFile = TusFile::find($id); - if (Tus::extensionIsActive('expiration') && Tus::isUploadExpired(Tus::storage()->lastModified($path))) { + if (Tus::extensionIsActive('expiration') && Tus::isUploadExpired(Tus::storage()->lastModified($tusFile->path))) { - Tus::storage()->delete($path); + Tus::storage()->delete($tusFile->path); return response( status: 404, @@ -92,17 +65,17 @@ public function patch(Request $request, string $id): Response } - if ((int) $request->header('upload-offset') !== Tus::storage()->size($path)) { + if ((int) $request->header('upload-offset') !== Tus::storage()->size($tusFile->path)) { return response( status: 409, headers: Tus::headers()->default()->toArray() ); } - $offset = Tus::storage()->size($path) + Tus::append($path, $request->getContent()); + $offset = Tus::storage()->size($tusFile->path) + Tus::append($tusFile->path, $request->getContent()); if ($offset === (int) Tus::metadata()->readMeta($id, 'size')) { - logs()->info('COMPLETED '.$id); + event(new FileUploadFinished($tusFile)); } return response( @@ -110,7 +83,7 @@ public function patch(Request $request, string $id): Response headers: Tus::headers() ->forPatch( offset: $offset, - lastModified: Tus::storage()->lastModified($path) + lastModified: Tus::storage()->lastModified($tusFile->path) ) ->toArray() ); @@ -118,14 +91,12 @@ public function patch(Request $request, string $id): Response public function delete(string $id): Response { - $path = Tus::path($id); + $tusFile = TusFile::find($id); if (!Tus::extensionIsActive('termination')) { $deleted = false; - } elseif (Tus::storage()->exists($path)) { - $deleted = Tus::storage()->delete($path); } else { - $deleted = false; + $deleted = Tus::storage()->delete($tusFile->path); } return response( diff --git a/src/Tus.php b/src/Tus.php index 81f7eae..4711548 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -76,7 +76,7 @@ public function id(): string $id = Str::random(40); - if (!Tus::storage()->exists($this->path($id))) { + if (!$this->storage()->exists($this->path($id))) { break; } From ab1e050d3abc9e161b7df48b99908251ff3a4148 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 16:46:59 +0300 Subject: [PATCH 07/12] feat: tus events --- src/Helpers/TusFile.php | 24 ++++++++++------- src/Helpers/TusUploadMetadataManager.php | 28 +++++++++++--------- src/Http/Controllers/TusUploadController.php | 16 +++++++++-- src/Tus.php | 21 ++++++++++----- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/Helpers/TusFile.php b/src/Helpers/TusFile.php index 2d40a30..ce3bd48 100644 --- a/src/Helpers/TusFile.php +++ b/src/Helpers/TusFile.php @@ -28,22 +28,26 @@ public static function create(string $id = null, int $size = 0, ?string $rawMeta $id = Tus::id(); } + $metadata = Tus::metadata()->store( + id: $id, + rawMetadata: $rawMetadata, + metadata: [ + 'size' => $size, + ] + ); + return new static( id: $id, - path: Tus::path($id), - metadata: Tus::metadata()->store( - id: $id, - rawMetadata: $rawMetadata, - customMetadata: [ - 'size' => $size, - ] - ) + path: Tus::path($id, $metadata['extension']), + metadata: $metadata ); } public static function find(string $id): static { - $path = Tus::path($id); + $metadata = Tus::metadata()->read($id); + + $path = Tus::path($id, $metadata['extension'] ?? null); if (!Tus::storage()->exists($path)) { throw new FileNotFoundException; @@ -52,7 +56,7 @@ public static function find(string $id): static return new static( id: $id, path: $path, - metadata: Tus::metadata()->read($id) + metadata: $metadata ); } } \ No newline at end of file diff --git a/src/Helpers/TusUploadMetadataManager.php b/src/Helpers/TusUploadMetadataManager.php index a115709..3b17383 100644 --- a/src/Helpers/TusUploadMetadataManager.php +++ b/src/Helpers/TusUploadMetadataManager.php @@ -2,6 +2,7 @@ namespace KalynaSolutions\Tus\Helpers; +use Illuminate\Support\Str; use KalynaSolutions\Tus\Facades\Tus; class TusUploadMetadataManager @@ -13,29 +14,32 @@ public function parse(string $rawMetadata): array ->mapWithKeys(function (string $data) { $data = explode(' ', $data); - return [$data[0] => base64_decode($data[1])]; + return [$data[ 0 ] => base64_decode($data[ 1 ])]; }) ->toArray(); } - public function store(string $id, ?string $rawMetadata = null, array $customMetadata = []): array + public function store(string $id, ?string $rawMetadata = null, array $metadata = []): array { - if (! empty($rawMetadata)) { - $customMetadata = [...$customMetadata, ...$this->parse($rawMetadata)]; + if (!empty($rawMetadata)) { + $metadata = [...$metadata, ...$this->parse($rawMetadata)]; + } + + if (!isset($metadata[ 'extension' ])) { + $extension = Str::afterLast($metadata[ 'name' ], '.'); + $metadata[ 'extension' ] = empty($extension) ? null : $extension; } match (config('tus.driver')) { - default => $this->storeInFile($id, $customMetadata), + default => $this->storeInFile($id, $metadata), }; - return $customMetadata; + return $metadata; } protected function storeInFile(string $id, array $metadata): void { - $path = sprintf('%s.json', Tus::path($id)); - - Tus::storage()->put($path, json_encode($metadata)); + Tus::storage()->put(Tus::path($id, 'json'), json_encode($metadata)); } public function read(string $id): array @@ -47,13 +51,11 @@ public function read(string $id): array protected function readFromFile(string $id): array { - $path = sprintf('%s.json', Tus::path($id)); - - return Tus::storage()->json($path) ?? []; + return Tus::storage()->json(Tus::path($id, 'json')) ?? []; } public function readMeta(string $id, string $key): mixed { - return $this->read($id)[$key] ?? null; + return $this->read($id)[ $key ] ?? null; } } diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index 3cf925e..4d2dee7 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -5,8 +5,10 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\Storage; use KalynaSolutions\Tus\Events\FileUploadCreated; use KalynaSolutions\Tus\Events\FileUploadFinished; +use KalynaSolutions\Tus\Events\FileUploadStarted; use KalynaSolutions\Tus\Facades\Tus; use KalynaSolutions\Tus\Helpers\TusFile; @@ -27,13 +29,19 @@ public function post(Request $request): Response rawMetadata: $request->header('upload-metadata') ); + $contents = Tus::extensionIsActive('creation-with-upload') && (int) $request->header('content-length') > 0 ? $request->getContent() : ''; + Tus::storage()->put( path: $tusFile->path, - contents: Tus::extensionIsActive('creation-with-upload') && (int) $request->header('content-length') > 0 ? $request->getContent() : '' + contents: $contents ); event(new FileUploadCreated($tusFile)); + if (!empty($contents)) { + event(new FileUploadStarted($tusFile)); + } + return response( status: 201, headers: Tus::headers()->forPost($tusFile)->toArray() @@ -72,10 +80,14 @@ public function patch(Request $request, string $id): Response ); } + if ((int) $request->header('upload-offset') === 0) { + event(new FileUploadStarted($tusFile)); + } + $offset = Tus::storage()->size($tusFile->path) + Tus::append($tusFile->path, $request->getContent()); if ($offset === (int) Tus::metadata()->readMeta($id, 'size')) { - event(new FileUploadFinished($tusFile)); + event(new FileUploadFinished($tusFile)); } return response( diff --git a/src/Tus.php b/src/Tus.php index 4711548..58c81d9 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use Illuminate\Support\Stringable; use KalynaSolutions\Tus\Exceptions\FileAppendException; use KalynaSolutions\Tus\Helpers\TusHeaderBuilder; use KalynaSolutions\Tus\Helpers\TusUploadMetadataManager; @@ -85,13 +86,19 @@ public function id(): string return $id; } - public function path(string $id): string + public function path(string $id, ?string $extension = null): string { - if (!empty(config('tus.storage_path'))) { - return sprintf("%s/%s", config('tus.storage_path'), $id); - } - - return $id; + return str('') + ->when( + value: !empty(config('tus.storage_path')), + callback: fn(Stringable $str) => $str->append(config('tus.storage_path'), '/') + ) + ->append($id) + ->when( + value: $extension, + callback: fn(Stringable $str) => $str->append('.', $extension) + ) + ->toString(); } public function storage(): Filesystem @@ -123,4 +130,6 @@ public function append(string $path, string $data): int return $fw; } + + public function appendExtension(string $id) {} } From e3e9a861109710484a08e02e6b27ee497d6da910 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 16:47:24 +0300 Subject: [PATCH 08/12] feat: tus events --- src/Tus.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tus.php b/src/Tus.php index 58c81d9..e8e86ff 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -130,6 +130,4 @@ public function append(string $path, string $data): int return $fw; } - - public function appendExtension(string $id) {} } From 77a82c7d9e8cf7d1d33c9b2c247c10322db46a28 Mon Sep 17 00:00:00 2001 From: ArthurPatriot Date: Wed, 3 May 2023 13:47:30 +0000 Subject: [PATCH 09/12] Fix styling --- src/Helpers/TusFile.php | 9 ++++--- src/Helpers/TusHeaderBuilder.php | 26 ++++++++++---------- src/Helpers/TusUploadMetadataManager.php | 12 ++++----- src/Http/Controllers/TusUploadController.php | 5 ++-- src/Tus.php | 15 +++++------ 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/Helpers/TusFile.php b/src/Helpers/TusFile.php index ce3bd48..d5df1b1 100644 --- a/src/Helpers/TusFile.php +++ b/src/Helpers/TusFile.php @@ -2,14 +2,15 @@ namespace KalynaSolutions\Tus\Helpers; -use Illuminate\Support\Str; use KalynaSolutions\Tus\Exceptions\FileNotFoundException; use KalynaSolutions\Tus\Facades\Tus; readonly class TusFile { public string $id; + public string $path; + public string $disk; public array $metadata; @@ -24,7 +25,7 @@ public function __construct(string $id, string $path, array $metadata) public static function create(string $id = null, int $size = 0, ?string $rawMetadata = null): static { - if (!$id) { + if (! $id) { $id = Tus::id(); } @@ -49,7 +50,7 @@ public static function find(string $id): static $path = Tus::path($id, $metadata['extension'] ?? null); - if (!Tus::storage()->exists($path)) { + if (! Tus::storage()->exists($path)) { throw new FileNotFoundException; } @@ -59,4 +60,4 @@ public static function find(string $id): static metadata: $metadata ); } -} \ No newline at end of file +} diff --git a/src/Helpers/TusHeaderBuilder.php b/src/Helpers/TusHeaderBuilder.php index 4286445..6112899 100644 --- a/src/Helpers/TusHeaderBuilder.php +++ b/src/Helpers/TusHeaderBuilder.php @@ -23,7 +23,7 @@ public function __construct(string $version) */ public function version(): static { - $this->headers[ 'Tus-Version' ] = $this->version; + $this->headers['Tus-Version'] = $this->version; return $this; } @@ -33,7 +33,7 @@ public function version(): static */ public function resumable(): static { - $this->headers[ 'Tus-Resumable' ] = $this->version; + $this->headers['Tus-Resumable'] = $this->version; return $this; } @@ -43,7 +43,7 @@ public function resumable(): static */ public function offset(int $offset): static { - $this->headers[ 'Upload-Offset' ] = $offset; + $this->headers['Upload-Offset'] = $offset; return $this; } @@ -53,7 +53,7 @@ public function offset(int $offset): static */ public function maxSize(): static { - $this->headers[ 'Tus-Max-Size' ] = Tus::maxFileSize(); + $this->headers['Tus-Max-Size'] = Tus::maxFileSize(); return $this; } @@ -65,11 +65,11 @@ public function extensions(): static { $extensions = config('tus.extensions'); - if (!is_array($extensions) || empty($extensions)) { + if (! is_array($extensions) || empty($extensions)) { return $this; } - $this->headers[ 'Tus-Extension' ] = implode(',', $extensions); + $this->headers['Tus-Extension'] = implode(',', $extensions); return $this; } @@ -79,7 +79,7 @@ public function extensions(): static */ public function location(string $id): static { - $this->headers[ 'Location' ] = route('tus.patch', $id); + $this->headers['Location'] = route('tus.patch', $id); return $this; } @@ -89,11 +89,11 @@ public function location(string $id): static */ public function expires(int $lastModified): static { - if (!Tus::extensionIsActive('expiration')) { + if (! Tus::extensionIsActive('expiration')) { return $this; } - $this->headers[ 'Upload-Expires' ] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); + $this->headers['Upload-Expires'] = Date::createFromTimestamp($lastModified)->addMinutes((int) config('tus.upload_expiration'))->toRfc7231String(); return $this; } @@ -103,11 +103,11 @@ public function expires(int $lastModified): static */ public function checksumAlgorithm(): static { - if (!Tus::extensionIsActive('checksum')) { + if (! Tus::extensionIsActive('checksum')) { return $this; } - $this->headers[ 'Tus-Checksum-Algorithm' ] = implode(',', (array) config('tus.checksum_algorithm')); + $this->headers['Tus-Checksum-Algorithm'] = implode(',', (array) config('tus.checksum_algorithm')); return $this; } @@ -118,7 +118,7 @@ public function length(int $length): static return $this; } - $this->headers[ 'Upload-Length' ] = $length; + $this->headers['Upload-Length'] = $length; return $this; } @@ -153,7 +153,7 @@ public function forPost(TusFile $tusFile): static public function forHead(TusFile $tusFile): static { $this->resumable() - ->length($tusFile->metadata[ 'size' ]) + ->length($tusFile->metadata['size']) ->offset(Tus::storage()->size($tusFile->path)) ->expires(Tus::storage()->lastModified($tusFile->path)); diff --git a/src/Helpers/TusUploadMetadataManager.php b/src/Helpers/TusUploadMetadataManager.php index 3b17383..62b0093 100644 --- a/src/Helpers/TusUploadMetadataManager.php +++ b/src/Helpers/TusUploadMetadataManager.php @@ -14,20 +14,20 @@ public function parse(string $rawMetadata): array ->mapWithKeys(function (string $data) { $data = explode(' ', $data); - return [$data[ 0 ] => base64_decode($data[ 1 ])]; + return [$data[0] => base64_decode($data[1])]; }) ->toArray(); } public function store(string $id, ?string $rawMetadata = null, array $metadata = []): array { - if (!empty($rawMetadata)) { + if (! empty($rawMetadata)) { $metadata = [...$metadata, ...$this->parse($rawMetadata)]; } - if (!isset($metadata[ 'extension' ])) { - $extension = Str::afterLast($metadata[ 'name' ], '.'); - $metadata[ 'extension' ] = empty($extension) ? null : $extension; + if (! isset($metadata['extension'])) { + $extension = Str::afterLast($metadata['name'], '.'); + $metadata['extension'] = empty($extension) ? null : $extension; } match (config('tus.driver')) { @@ -56,6 +56,6 @@ protected function readFromFile(string $id): array public function readMeta(string $id, string $key): mixed { - return $this->read($id)[ $key ] ?? null; + return $this->read($id)[$key] ?? null; } } diff --git a/src/Http/Controllers/TusUploadController.php b/src/Http/Controllers/TusUploadController.php index 4d2dee7..1d27d28 100644 --- a/src/Http/Controllers/TusUploadController.php +++ b/src/Http/Controllers/TusUploadController.php @@ -5,7 +5,6 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller as BaseController; -use Illuminate\Support\Facades\Storage; use KalynaSolutions\Tus\Events\FileUploadCreated; use KalynaSolutions\Tus\Events\FileUploadFinished; use KalynaSolutions\Tus\Events\FileUploadStarted; @@ -38,7 +37,7 @@ public function post(Request $request): Response event(new FileUploadCreated($tusFile)); - if (!empty($contents)) { + if (! empty($contents)) { event(new FileUploadStarted($tusFile)); } @@ -105,7 +104,7 @@ public function delete(string $id): Response { $tusFile = TusFile::find($id); - if (!Tus::extensionIsActive('termination')) { + if (! Tus::extensionIsActive('termination')) { $deleted = false; } else { $deleted = Tus::storage()->delete($tusFile->path); diff --git a/src/Tus.php b/src/Tus.php index 58c81d9..18acdc7 100644 --- a/src/Tus.php +++ b/src/Tus.php @@ -2,7 +2,6 @@ namespace KalynaSolutions\Tus; -use Exception; use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Storage; @@ -77,7 +76,7 @@ public function id(): string $id = Str::random(40); - if (!$this->storage()->exists($this->path($id))) { + if (! $this->storage()->exists($this->path($id))) { break; } @@ -90,13 +89,13 @@ public function path(string $id, ?string $extension = null): string { return str('') ->when( - value: !empty(config('tus.storage_path')), - callback: fn(Stringable $str) => $str->append(config('tus.storage_path'), '/') + value: ! empty(config('tus.storage_path')), + callback: fn (Stringable $str) => $str->append(config('tus.storage_path'), '/') ) ->append($id) ->when( value: $extension, - callback: fn(Stringable $str) => $str->append('.', $extension) + callback: fn (Stringable $str) => $str->append('.', $extension) ) ->toString(); } @@ -110,7 +109,7 @@ public function append(string $path, string $data): int { $path = $this->storage()->path($path); - if (!is_writable($path)) { + if (! is_writable($path)) { throw new FileAppendException(message: 'File not exists or not writable'); } @@ -131,5 +130,7 @@ public function append(string $path, string $data): int return $fw; } - public function appendExtension(string $id) {} + public function appendExtension(string $id) + { + } } From d8489cabe2f05acd607d8814d3e46cefd5c0d62f Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 16:48:28 +0300 Subject: [PATCH 10/12] fix: phpstan --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a91953b..e005ac7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -6,7 +6,6 @@ parameters: paths: - src - config - - database tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true From 014c097a06b32244fe54e0b5cff5ea2fc852be33 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 17:26:35 +0300 Subject: [PATCH 11/12] refactor: describe config --- README.md | 28 ++++++++++++---------- config/tus.php | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d9a91e4..8e2fc5e 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,11 @@ [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/kalynasolutions/laravel-tus/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/kalynasolutions/laravel-tus/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/kalynasolutions/laravel-tus.svg?style=flat-square)](https://packagist.org/packages/kalynasolutions/laravel-tus) -Laravel package for handling resumable file uploads with tus protocol and native [Uppy.js](https://uppy.io) support **without** additional tus servers. +![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/kalynasolutions/laravel-tus/php?style=flat-square) +![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/kalynasolutions/laravel-tus/illuminate/contracts?color=%23F05340&label=laravel&style=flat-square) +[![Tus.io Protocol Version](https://img.shields.io/badge/tus.io_protocol-1.0.0-brightgreen?style=flat-square)](https://tus.io/protocols/resumable-upload.html) -> 100% Tus.io resumable upload [protocol](https://tus.io/protocols/resumable-upload.html) support - -| Laravel Version | Package Version | Status | -|:---------------:|:---------------:|:------:| -| 10.x | ^1.0 | ✅ | +Laravel package for handling resumable file uploads with tus protocol and native [Uppy.js](https://uppy.io) support **without** additional Tus servers. ## Installation @@ -21,13 +19,6 @@ You can install the package via composer: composer require kalynasolutions/laravel-tus ``` -You can publish and run the migrations with (optional): - -```bash -php artisan vendor:publish --tag="laravel-tus-migrations" -php artisan migrate -``` - You can publish the config file with (optional): ```bash @@ -52,6 +43,17 @@ const uppy = new Uppy({ logger: debugLogger }); uppy.use(Tus, { endpoint: TUS_ENDPOINT, limit: TUS_LIMIT, chunkSize: TUS_CHUNK_SIZE }) ``` +## Tus Extensions + +| Extension | Supported | +|----------------------|:---------------------------:| +| creation | ✅ | +| creation-with-upload | ✅ | +| expiration | ✅ | +| checksum | ✅ | +| termination | ✅ | +| concatenation | ❌ (will be added on future) | + ## Testing ```bash diff --git a/config/tus.php b/config/tus.php index 8c901b2..05f7816 100644 --- a/config/tus.php +++ b/config/tus.php @@ -1,22 +1,87 @@ env('TUS_DRIVER', 'file'), + /* + |-------------------------------------------------------------------------- + | Default endpoint path + |-------------------------------------------------------------------------- + */ 'path' => env('TUS_PATH', 'tus'), + /* + |-------------------------------------------------------------------------- + | Default endpoint middlewares + |-------------------------------------------------------------------------- + */ 'middleware' => ['web'], + /* + |-------------------------------------------------------------------------- + | Default storage for files + |-------------------------------------------------------------------------- + */ 'storage_disk' => env('TUS_STORAGE_DISK', 'local'), + /* + |-------------------------------------------------------------------------- + | Default tus directory path + |-------------------------------------------------------------------------- + */ 'storage_path' => env('TUS_STORAGE_PATH', 'tus'), + /* + |-------------------------------------------------------------------------- + | Configure upload file size limit + |-------------------------------------------------------------------------- + | + | Set max file upload size limit in bytes. If `null` will be user php ini value from `max_post_size`. + | + */ 'file_size_limit' => null, + /* + |-------------------------------------------------------------------------- + | Default upload expiration + |-------------------------------------------------------------------------- + | + | This values used by `expiration` extension if it is enabled. If the file has expired, it will be deleted automatically. + | If you not need file deletion after given time, set this value to `null`. + | + */ 'upload_expiration' => env('TUS_UPLOAD_EXPIRATION', 30), + /* + |-------------------------------------------------------------------------- + | Supported checksum algorithms for checksum extension + |-------------------------------------------------------------------------- + | + | All supported algos: https://www.php.net/manual/ru/function.hash-algos.php + | + */ 'checksum_algorithm' => ['crc32', 'md5', 'sha1'], + /* + |-------------------------------------------------------------------------- + | Tus extensions + |-------------------------------------------------------------------------- + | + | You can enable or disable specific tus.io protocol extensions on server. + | + | Supported extensions: "creation", "creation-with-upload", "expiration", "checksum", "termination" + | + */ 'extensions' => [ 'creation', 'creation-with-upload', From 3982be154efc4a05271106b640992c363796e574 Mon Sep 17 00:00:00 2001 From: Artur Khylskyi Date: Wed, 3 May 2023 17:30:21 +0300 Subject: [PATCH 12/12] fix: static fix --- src/Helpers/TusFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helpers/TusFile.php b/src/Helpers/TusFile.php index d5df1b1..4375220 100644 --- a/src/Helpers/TusFile.php +++ b/src/Helpers/TusFile.php @@ -5,7 +5,7 @@ use KalynaSolutions\Tus\Exceptions\FileNotFoundException; use KalynaSolutions\Tus\Facades\Tus; -readonly class TusFile +final readonly class TusFile { public string $id;