From ebb3398716331e57970bb179d22528640137f2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=B6r=C3=B6g?= Date: Fri, 12 Jun 2020 16:56:21 +0200 Subject: [PATCH 1/3] Add simple-uploader.js driver --- README.md | 19 +- config/chunk-uploader.php | 24 +- src/Driver/SimpleUploaderJsUploadDriver.php | 30 ++ src/UploadManager.php | 6 + .../Driver/SimpleUploaderUploadDriverTest.php | 291 ++++++++++++++++++ 5 files changed, 363 insertions(+), 7 deletions(-) create mode 100644 src/Driver/SimpleUploaderJsUploadDriver.php create mode 100644 tests/Driver/SimpleUploaderUploadDriverTest.php diff --git a/README.md b/README.md index 84f0a73..6fe759e 100644 --- a/README.md +++ b/README.md @@ -143,12 +143,13 @@ If you wrote a custom driver that others might find useful, please consider addi Below is a list of available drivers along with their individual specs: -Service | Driver name | Chunk upload | Resumable --------------------------------------|----------------|--------------|----------- -[Monolith](#monolith-driver) | `monolith` | no | no -[Blueimp](#blueimp-driver) | `blueimp` | yes | yes -[DropzoneJS](#dropzonejs-driver) | `dropzone` | yes | no -[Resumable.js](#resumable-js-driver) | `resumable-js` | yes | yes +Service | Driver name | Chunk upload | Resumable +-------------------------------------------------|----------------------|--------------|----------- +[Monolith](#monolith-driver) | `monolith` | no | no +[Blueimp](#blueimp-driver) | `blueimp` | yes | yes +[DropzoneJS](#dropzonejs-driver) | `dropzone` | yes | no +[Resumable.js](#resumable-js-driver) | `resumable-js` | yes | yes +[simple-uploader.js](#simple-uploader-js-driver) | `simple-uploader-js` | yes | yes ### Monolith driver @@ -172,6 +173,12 @@ This driver handles requests made by the DropzoneJS client library. This driver handles requests made by the Resumable.js client library. +### simple-uploader.js driver + +[website](https://github.com/simple-uploader/Uploader) + +This driver handles requests made by the simple-uploader.js client library. + ## Identifiers In some cases an identifier is needed for the uploaded file when the client side library does not provide one. diff --git a/config/chunk-uploader.php b/config/chunk-uploader.php index 04c98f5..b491593 100644 --- a/config/chunk-uploader.php +++ b/config/chunk-uploader.php @@ -12,7 +12,8 @@ | throughout your application here. By default, the module is setup for | monolith upload. | - | Supported: "monolith", "blueimp", "dropzone", "resumable-js" + | Supported: "monolith", "blueimp", "dropzone", "resumable-js", + | "simple-uploader-js" | */ @@ -163,4 +164,25 @@ ], + /* + |-------------------------------------------------------------------------- + | simple-uploader.js Options + |-------------------------------------------------------------------------- + | + | Here you may configure the options for the simple-uploader.js driver. + | + */ + + 'simple-uploader-js' => [ + + // The name of the multipart request parameter to use for the file chunk + 'param' => 'file', + + // HTTP method for chunk test request. + 'test-method' => Illuminate\Http\Request::METHOD_GET, + // HTTP method to use when sending chunks to the server (POST, PUT, PATCH). + 'upload-method' => Illuminate\Http\Request::METHOD_POST, + + ], + ]; diff --git a/src/Driver/SimpleUploaderJsUploadDriver.php b/src/Driver/SimpleUploaderJsUploadDriver.php new file mode 100644 index 0000000..64a93d3 --- /dev/null +++ b/src/Driver/SimpleUploaderJsUploadDriver.php @@ -0,0 +1,30 @@ + 'chunkNumber', + // The name of the total number of chunks POST parameter to use for the file chunk. + 'total-chunks' => 'totalChunks', + // The name of the general chunk size POST parameter to use for the file chunk. + 'chunk-size' => 'chunkSize', + // The name of the total file size number POST parameter to use for the file chunk. + 'total-size' => 'totalSize', + // The name of the unique identifier POST parameter to use for the file chunk. + 'identifier' => 'identifier', + // The name of the original file name POST parameter to use for the file chunk. + 'file-name' => 'filename', + // The name of the file's relative path POST parameter to use for the file chunk. + 'relative-path' => 'relativePath', + // The name of the current chunk size POST parameter to use for the file chunk. + 'current-chunk-size' => 'currentChunkSize', + ]; + parent::__construct($config); + } +} diff --git a/src/UploadManager.php b/src/UploadManager.php index f53fac9..1023eb2 100644 --- a/src/UploadManager.php +++ b/src/UploadManager.php @@ -7,6 +7,7 @@ use LaraCrafts\ChunkUploader\Driver\DropzoneUploadDriver; use LaraCrafts\ChunkUploader\Driver\MonolithUploadDriver; use LaraCrafts\ChunkUploader\Driver\ResumableJsUploadDriver; +use LaraCrafts\ChunkUploader\Driver\SimpleUploaderJsUploadDriver; class UploadManager extends Manager { @@ -33,6 +34,11 @@ public function createResumableJsDriver() return new ResumableJsUploadDriver($this->app['config']['chunk-uploader.resumable-js']); } + public function createSimpleUploaderJsDriver() + { + return new SimpleUploaderJsUploadDriver($this->app['config']['chunk-uploader.simple-uploader-js']); + } + /** * Get the default driver name. * diff --git a/tests/Driver/SimpleUploaderUploadDriverTest.php b/tests/Driver/SimpleUploaderUploadDriverTest.php new file mode 100644 index 0000000..86204f8 --- /dev/null +++ b/tests/Driver/SimpleUploaderUploadDriverTest.php @@ -0,0 +1,291 @@ +app->make('config')->set('chunk-uploader.uploader', 'simple-uploader-js'); + $this->app->make('config')->set('chunk-uploader.sweep', false); + $this->handler = $this->app->make(UploadHandler::class); + + Storage::fake('local'); + Event::fake(); + } + + public function testDriverInstance() + { + $manager = $this->app->make('chunk-uploader.upload-manager'); + + $this->assertInstanceOf(SimpleUploaderJsUploadDriver::class, $manager->driver()); + } + + public function notAllowedRequestMethods() + { + return [ + 'HEAD' => [Request::METHOD_HEAD], + 'PUT' => [Request::METHOD_PUT], + 'PATCH' => [Request::METHOD_PATCH], + 'DELETE' => [Request::METHOD_DELETE], + 'PURGE' => [Request::METHOD_PURGE], + 'OPTIONS' => [Request::METHOD_OPTIONS], + 'TRACE' => [Request::METHOD_TRACE], + 'CONNECT' => [Request::METHOD_CONNECT], + ]; + } + + /** + * @dataProvider notAllowedRequestMethods + */ + public function testMethodNotAllowed($requestMethod) + { + $request = Request::create('', $requestMethod); + + $this->expectException(MethodNotAllowedHttpException::class); + + $this->createTestResponse($this->handler->handle($request)); + } + + public function testResumeWhenChunkDoesNotExists() + { + $this->createFakeLocalFile('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', '000-099'); + + $request = Request::create('', Request::METHOD_GET, [ + 'chunkNumber' => 2, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ]); + + $response = $this->createTestResponse($this->handler->handle($request)); + $response->assertStatus(Response::HTTP_NO_CONTENT); + } + + public function testResume() + { + $this->createFakeLocalFile('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', '000-099'); + + $request = Request::create('', Request::METHOD_GET, [ + 'chunkNumber' => 1, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ]); + + $response = $this->createTestResponse($this->handler->handle($request)); + $response->assertSuccessful(); + } + + public function testUploadWhenFileParameterIsEmpty() + { + $request = Request::create('', Request::METHOD_POST); + + $this->expectException(BadRequestHttpException::class); + + $this->handler->handle($request); + } + + public function testUploadWhenFileParameterIsInvalid() + { + $file = Mockery::mock(UploadedFile::class) + ->makePartial(); + $file->shouldReceive('isValid') + ->andReturn(false); + + $request = Request::create('', Request::METHOD_POST, [], [], [ + 'file' => $file, + ]); + + $this->expectException(InternalServerErrorHttpException::class); + + $this->handler->handle($request); + } + + public function excludedPostParameterProvider() + { + return [ + 'chunkNumber' => ['chunkNumber'], + 'totalChunks' => ['totalChunks'], + 'chunkSize' => ['chunkSize'], + 'totalSize' => ['totalSize'], + 'identifier' => ['identifier'], + 'filename' => ['filename'], + 'relativePath' => ['relativePath'], + 'currentChunkSize' => ['currentChunkSize'], + ]; + } + + /** + * @dataProvider excludedPostParameterProvider + */ + public function testPostParameterValidation($exclude) + { + $arr = [ + 'chunkNumber' => 1, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ]; + + unset($arr[$exclude]); + + $request = Request::create('', Request::METHOD_POST, $arr, [], [ + 'file' => UploadedFile::fake() + ->create('test.txt', 100), + ]); + + $this->expectException(ValidationException::class); + + $this->handler->handle($request); + } + + public function testUploadFirstChunk() + { + $file = UploadedFile::fake()->create('test.txt', 100); + $request = Request::create('', Request::METHOD_POST, [ + 'chunkNumber' => 1, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ], [], [ + 'file' => $file, + ]); + + /** @var \Illuminate\Foundation\Testing\TestResponse $response */ + $response = $this->createTestResponse($this->handler->handle($request)); + $response->assertSuccessful(); + $response->assertJson(['done' => 50]); + + Storage::disk('local')->assertExists('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt/000-099'); + + Event::assertNotDispatched(FileUploaded::class, function ($event) use ($file) { + return $event->file = $file->hashName('merged'); + }); + } + + public function testUploadFirstChunkWithCallback() + { + $file = UploadedFile::fake()->create('test.txt', 100); + $request = Request::create('', Request::METHOD_POST, [ + 'chunkNumber' => 1, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ], [], [ + 'file' => $file, + ]); + + $callback = $this->createClosureMock($this->never()); + + $this->handler->handle($request, $callback); + + Event::assertNotDispatched(FileUploaded::class, function ($event) use ($file) { + return $event->file = $file->hashName('merged'); + }); + } + + public function testUploadLastChunk() + { + $this->createFakeLocalFile('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', '000-099'); + + $file = UploadedFile::fake()->create('test.txt', 100); + $request = Request::create('', Request::METHOD_POST, [ + 'chunkNumber' => 2, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ], [], [ + 'file' => $file, + ]); + + /** @var \Illuminate\Foundation\Testing\TestResponse $response */ + $response = $this->createTestResponse($this->handler->handle($request)); + $response->assertSuccessful(); + $response->assertJson(['done' => 100]); + + Storage::disk('local')->assertExists('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt/100-199'); + Storage::disk('local')->assertExists($file->hashName('merged')); + + Event::assertDispatched(FileUploaded::class, function ($event) use ($file) { + return $event->file = $file->hashName('merged'); + }); + } + + public function testUploadLastChunkWithCallback() + { + $this->createFakeLocalFile('chunks/200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', '000-099'); + + $file = UploadedFile::fake()->create('test.txt', 100); + $request = Request::create('', Request::METHOD_POST, [ + 'chunkNumber' => 2, + 'totalChunks' => 2, + 'chunkSize' => 100, + 'totalSize' => 200, + 'identifier' => '200-0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zftxt', + 'filename' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'relativePath' => '0jWZTB1ZDfRQU6VTcXy0mJnL9xKMeEz3HoSPU0Zf.txt', + 'currentChunkSize' => 100, + ], [], [ + 'file' => $file, + ]); + + $callback = $this->createClosureMock( + $this->once(), + 'local', + $file->hashName('merged') + ); + + $this->handler->handle($request, $callback); + + Event::assertDispatched(FileUploaded::class, function ($event) use ($file) { + return $event->file = $file->hashName('merged'); + }); + } +} From c2ea4d1f50e0ed4a0353b67dbfcb1c77dcd84c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=B6r=C3=B6g?= Date: Fri, 12 Jun 2020 22:52:13 +0200 Subject: [PATCH 2/3] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6fe759e..5e5314c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ project at the moment is [tus](https://tus.io/). - [Blueimp](#blueimp-driver) - [DropzoneJS](#dropzonejs-driver) - [Resumable.js](#resumable-js-driver) + - [simple-uploader.js](#simple-uploader-js-driver) - [Identifiers](#identifiers) - [Session identifier](#session-identifier) - [Contribution](#contribution) From 1e1b45caedc5f0877afd84f78d7e2de239357ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=B6r=C3=B6g?= Date: Sat, 20 Jun 2020 17:03:52 +0200 Subject: [PATCH 3/3] Change namespace --- src/Driver/SimpleUploaderJsUploadDriver.php | 2 +- tests/Driver/SimpleUploaderUploadDriverTest.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Driver/SimpleUploaderJsUploadDriver.php b/src/Driver/SimpleUploaderJsUploadDriver.php index 64a93d3..70183f2 100644 --- a/src/Driver/SimpleUploaderJsUploadDriver.php +++ b/src/Driver/SimpleUploaderJsUploadDriver.php @@ -1,6 +1,6 @@