diff --git a/src/Driver/BlueimpUploadDriver.php b/src/Driver/BlueimpUploadDriver.php index aaa29ea..01f5ab2 100644 --- a/src/Driver/BlueimpUploadDriver.php +++ b/src/Driver/BlueimpUploadDriver.php @@ -136,7 +136,7 @@ public function save(Request $request, StorageConfig $config, Closure $fileUploa try { $range = new ContentRange($request->headers); } catch (InvalidArgumentException $e) { - throw new BadRequestHttpException($e->getMessage()); + throw new BadRequestHttpException($e->getMessage(), $e); } $filename = $this->identifier->generateUploadedFileIdentifierName($file); diff --git a/src/Driver/DropzoneUploadDriver.php b/src/Driver/DropzoneUploadDriver.php index 75ee11e..cbdaa67 100644 --- a/src/Driver/DropzoneUploadDriver.php +++ b/src/Driver/DropzoneUploadDriver.php @@ -6,12 +6,14 @@ use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; +use InvalidArgumentException; use LaraCrafts\ChunkUploader\Helper\ChunkHelpers; use LaraCrafts\ChunkUploader\Identifier\Identifier; -use LaraCrafts\ChunkUploader\Range\RequestRange; +use LaraCrafts\ChunkUploader\Range\RequestBodyRange; use LaraCrafts\ChunkUploader\Response\PercentageJsonResponse; use LaraCrafts\ChunkUploader\StorageConfig; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; class DropzoneUploadDriver extends UploadDriver @@ -124,14 +126,17 @@ private function saveMonolith(UploadedFile $file, StorageConfig $config, Closure */ private function saveChunk(UploadedFile $file, Request $request, StorageConfig $config, Closure $fileUploaded = null): Response { - $numberOfChunks = $request->post('dztotalchunkcount'); - - $range = new RequestRange( - $request->post('dzchunkindex'), - $numberOfChunks, - $request->post('dzchunksize'), - $request->post('dztotalfilesize') - ); + try { + $range = new RequestBodyRange( + $request, + 'dzchunkindex', + 'dztotalchunkcount', + 'dzchunksize', + 'dztotalfilesize' + ); + } catch (InvalidArgumentException $e) { + throw new BadRequestHttpException($e->getMessage(), $e); + } $filename = $request->post('dzuuid'); @@ -142,8 +147,8 @@ private function saveChunk(UploadedFile $file, Request $request, StorageConfig $ $chunks = $this->storeChunk($config, $range, $file, $filename); - if (!$this->isFinished($numberOfChunks, $chunks)) { - return new PercentageJsonResponse($this->getPercentage($chunks, $numberOfChunks)); + if (!$range->isFinished($chunks)) { + return new PercentageJsonResponse($range->getPercentage($chunks)); } $path = $this->mergeChunks($config, $chunks, $filename); @@ -156,26 +161,4 @@ private function saveChunk(UploadedFile $file, Request $request, StorageConfig $ return new PercentageJsonResponse(100); } - - /** - * @param $numberOfChunks - * @param $chunks - * - * @return bool - */ - private function isFinished($numberOfChunks, $chunks) - { - return $numberOfChunks === count($chunks); - } - - /** - * @param array $chunks - * @param $numberOfChunks - * - * @return float|int - */ - private function getPercentage(array $chunks, $numberOfChunks) - { - return floor(count($chunks) / $numberOfChunks * 100); - } } diff --git a/src/Range/RequestBodyRange.php b/src/Range/RequestBodyRange.php new file mode 100644 index 0000000..448affe --- /dev/null +++ b/src/Range/RequestBodyRange.php @@ -0,0 +1,129 @@ +request; + } + + $this->index = $request->get($indexKey); + $this->numberOfChunks = $request->get($numberOfChunksKey); + $this->chunkSize = $request->get($chunkSizeKey); + $this->totalSize = $request->get($totalSizeKey); + + if ($this->numberOfChunks <= 0) { + throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', $numberOfChunksKey)); + } + if ($this->index < 0) { + throw new InvalidArgumentException(sprintf('`%s` must be greater than or equal to zero', $indexKey)); + } + if ($this->index >= $this->numberOfChunks) { + throw new InvalidArgumentException(sprintf('`%s` must be smaller than `%s`', $indexKey, $numberOfChunksKey)); + } + if ($this->chunkSize < 1) { + throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', $chunkSizeKey)); + } + if ($this->totalSize < 1) { + throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', $totalSizeKey)); + } elseif ($this->totalSize <= $this->index * $this->chunkSize) { + throw new InvalidArgumentException( + sprintf('`%s` must be greater than the multiple of `%s` and `%s`', $totalSizeKey, $chunkSizeKey, $indexKey) + ); + } elseif ($this->totalSize > $this->numberOfChunks * $this->chunkSize) { + throw new InvalidArgumentException( + sprintf('`%s` must be smaller than or equal to the multiple of `%s` and `%s`', $totalSizeKey, $chunkSizeKey, $numberOfChunksKey) + ); + } + } + + /** + * {@inheritDoc} + */ + public function getStart(): float + { + return $this->index * $this->chunkSize; + } + + /** + * {@inheritDoc} + */ + public function getEnd(): float + { + $end = (($this->index + 1) * $this->chunkSize) - 1; + + $sizeIndex = $this->totalSize - 1; + if ($end > ($sizeIndex)) { + return $sizeIndex; + } + + return $end; + } + + /** + * {@inheritDoc} + */ + public function getTotal(): float + { + return $this->totalSize; + } + + /** + * {@inheritDoc} + */ + public function isFirst(): bool + { + return $this->index === 0; + } + + /** + * {@inheritDoc} + */ + public function isLast(): bool + { + return $this->index === $this->numberOfChunks - 1; + } + + /** + * @param $uploadedChunks + * + * @return float + */ + public function getPercentage($uploadedChunks): float + { + return floor(count($uploadedChunks) / $this->numberOfChunks * 100); + } + + /** + * @param $uploadedChunks + * + * @return bool + */ + public function isFinished($uploadedChunks): bool + { + return $this->numberOfChunks === count($uploadedChunks); + } +} diff --git a/src/Range/RequestRange.php b/src/Range/RequestRange.php deleted file mode 100644 index 782ebe8..0000000 --- a/src/Range/RequestRange.php +++ /dev/null @@ -1,76 +0,0 @@ -index = $index; - $this->numberOfChunks = $numberOfChunks; - $this->totalSize = $totalSize; - $this->chunkSize = $chunkSize; - } - - /** - * {@inheritDoc} - */ - public function getStart(): float - { - return $this->index * $this->chunkSize; - } - - /** - * {@inheritDoc} - */ - public function getEnd(): float - { - $end = (($this->index + 1) * $this->chunkSize) - 1; - - if ($end > ($this->totalSize - 1)) { - return $this->totalSize - 1; - } - - return $end; - } - - /** - * {@inheritDoc} - */ - public function getTotal(): float - { - return $this->totalSize; - } - - /** - * {@inheritDoc} - */ - public function isFirst(): bool - { - return $this->index === 0; - } - - /** - * {@inheritDoc} - */ - public function isLast(): bool - { - return $this->index === $this->numberOfChunks - 1; - } -} diff --git a/tests/Range/RequestBodyRangeTest.php b/tests/Range/RequestBodyRangeTest.php new file mode 100644 index 0000000..33262e7 --- /dev/null +++ b/tests/Range/RequestBodyRangeTest.php @@ -0,0 +1,146 @@ + [4, 0, 20, 190, '`numberOfChunks` must be greater than zero'], + 'Number of chunks size smaller than zero' => [4, -1, 20, 190, '`numberOfChunks` must be greater than zero'], + 'Index smaller than zero' => [-1, 10, 20, 190, '`index` must be greater than or equal to zero'], + 'Index equal to the number of chunks' => [10, 10, 20, 190, '`index` must be smaller than `numberOfChunks`'], + 'Index greater than the number of chunks' => [14, 10, 20, 190, '`index` must be smaller than `numberOfChunks`'], + 'Chunk size equal to zero' => [4, 10, 0, 190, '`chunkSize` must be greater than zero'], + 'Chunk size smaller than zero' => [4, 10, -1, 190, '`chunkSize` must be greater than zero'], + 'Total size equal to zero' => [4, 10, 20, 0, '`totalSize` must be greater than zero'], + 'Total size smaller than zero' => [4, 10, 20, -1, '`totalSize` must be greater than zero'], + 'Total size too small' => [4, 10, 20, 80, '`totalSize` must be greater than the multiple of `chunkSize` and `index`'], + 'Total size too big' => [4, 10, 20, 201, '`totalSize` must be smaller than or equal to the multiple of `chunkSize` and `numberOfChunks`'], + ]; + } + + /** + * @dataProvider invalidArgumentProvider + * + * @param $index + * @param $numberOfChunks + * @param $chunkSize + * @param $totalSize + * @param $expectedExceptionMessage + */ + public function testArgumentValidation($index, $numberOfChunks, $chunkSize, $totalSize, $expectedExceptionMessage) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->createRequestBodyRange($index, $numberOfChunks, $chunkSize, $totalSize); + } + + public function testIsFirst() + { + $range = $this->createRequestBodyRange(0, 2, 1, 2); + $this->assertTrue($range->isFirst()); + + $range = $this->createRequestBodyRange(1, 2, 1, 2); + $this->assertFalse($range->isFirst()); + } + + public function testIsLast() + { + $range = $this->createRequestBodyRange(1, 2, 1, 2); + $this->assertTrue($range->isLast()); + + $range = $this->createRequestBodyRange(0, 2, 1, 2); + $this->assertFalse($range->isLast()); + } + + public function testIsFirstAndIsLast() + { + $range = $this->createRequestBodyRange(0, 1, 1, 1); + $this->assertTrue($range->isLast()); + $this->assertTrue($range->isLast()); + } + + public function testGetTotal() + { + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertEquals(190, $range->getTotal()); + } + + public function testGetStart() + { + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertEquals(80, $range->getStart()); + } + + public function testGetEnd() + { + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertEquals(99, $range->getEnd()); + + $range = $this->createRequestBodyRange(9, 10, 20, 190); + $this->assertEquals(189, $range->getEnd()); + } + + public function testGetPercentage() + { + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertEquals(100, $range->getPercentage(range(0, 9))); + + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertEquals(90, $range->getPercentage(range(0, 8))); + } + + public function testIsFinished() + { + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertTrue($range->isFinished(range(0, 9))); + + $range = $this->createRequestBodyRange(4, 10, 20, 190); + $this->assertFalse($range->isFinished(range(0, 8))); + } + + public function testCreateFromRequest() + { + $request = new Request([], [ + 'index' => 4, + 'numberOfChunks' => 10, + 'chunkSize' => 20, + 'totalSize' => 190, + ]); + + $range = new RequestBodyRange($request, 'index', 'numberOfChunks', 'chunkSize', 'totalSize'); + + $this->assertEquals(80, $range->getStart()); + $this->assertEquals(99, $range->getEnd()); + $this->assertEquals(190, $range->getTotal()); + } + + /** + * @param $index + * @param $numberOfChunks + * @param $chunkSize + * @param $totalSize + * + * @return \LaraCrafts\ChunkUploader\Range\RequestBodyRange + */ + private function createRequestBodyRange($index, $numberOfChunks, $chunkSize, $totalSize) + { + $request = new ParameterBag([ + 'index' => $index, + 'numberOfChunks' => $numberOfChunks, + 'chunkSize' => $chunkSize, + 'totalSize' => $totalSize, + ]); + + return new RequestBodyRange($request, 'index', 'numberOfChunks', 'chunkSize', 'totalSize'); + } +}