Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ project at the moment is [tus](https://tus.io/).
- [Blueimp](#blueimp-driver)
- [DropzoneJS](#dropzonejs-driver)
- [Flow.js](#flow-js-driver)
- [ng-file-upload](#ng-file-upload-driver)
- [Plupload](#plupload-driver)
- [Resumable.js](#resumable-js-driver)
- [simple-uploader.js](#simple-uploader-js-driver)
Expand Down Expand Up @@ -153,6 +154,7 @@ Service | Driver name | Chunk
[Blueimp](#blueimp-driver) | `blueimp` | yes | yes
[DropzoneJS](#dropzonejs-driver) | `dropzone` | yes | no
[Flow.js](#flow-js-driver) | `flow-js` | yes | yes
[ng-file-upload](#ng-file-upload-driver) | `ng-file-upload` | yes | no
[Plupload](#plupload-driver) | `plupload` | yes | no
[Resumable.js](#resumable-js-driver) | `resumable-js` | yes | yes
[simple-uploader.js](#simple-uploader-js-driver) | `simple-uploader-js` | yes | yes
Expand All @@ -179,6 +181,12 @@ This driver handles requests made by the DropzoneJS client library.

This driver handles requests made by the Flow.js client library.

### ng-file-upload driver

[website](https://github.com/danialfarid/ng-file-upload)

This driver handles requests made by the ng-file-upload client library.

### Plupload driver

[website](https://github.com/moxiecode/plupload)
Expand Down
4 changes: 2 additions & 2 deletions config/chunk-uploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
| throughout your application here. By default, the module is setup for
| monolith upload.
|
| Supported: "monolith", "blueimp", "dropzone", "flow-js", "resumable-js",
| "simple-uploader-js"
| Supported: "monolith", "blueimp", "dropzone", "flow-js",
| "ng-file-upload", "resumable-js", "simple-uploader-js"
|
*/

Expand Down
49 changes: 49 additions & 0 deletions examples/ng-file-upload.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>ng-file-upload</title>
</head>
<body>
<h1>ng-file-upload</h1>

<div ng-app="fileUpload" ng-controller="MyCtrl">
<form name="myForm">
<input type="file" ngf-select ng-model="picFile" name="file" required>
<br/>

<button ng-disabled="!myForm.$valid" ng-click="uploadPic(picFile)">Submit</button>
<span ng-show="picFile.result">Upload Successful</span>
</form>
</div>

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
<script src="//cdn.jsdelivr.net/gh/danialfarid/ng-file-upload/dist/ng-file-upload.min.js"></script>
<script>
const uploadUrl = "{{ url('upload') }}";
const app = angular.module('fileUpload', ['ngFileUpload']);

app.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) {
$scope.uploadPic = function (file) {
$scope.formUpload = true;
if (file != null) {
$scope.upload(file);
}
};

$scope.upload = function (file) {
$scope.errorMsg = null;
file.upload = Upload.upload({
url: uploadUrl,
resumeSizeUrl: uploadUrl + '?file=' + encodeURIComponent(file.name) + '&totalSize=' + file.size,
resumeSizeResponseReader: function(data) {return data.size;},
resumeChunkSize: 1024 * 1024,
data: {file: file}
});
};
}]);
</script>
</body>
</html>
174 changes: 174 additions & 0 deletions src/Driver/NgFileUploadDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace CodingSocks\ChunkUploader\Driver;

use Closure;
use CodingSocks\ChunkUploader\Helper\ChunkHelpers;
use CodingSocks\ChunkUploader\Identifier\Identifier;
use CodingSocks\ChunkUploader\Range\NgFileUploadRange;
use CodingSocks\ChunkUploader\Response\PercentageJsonResponse;
use CodingSocks\ChunkUploader\StorageConfig;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;

class NgFileUploadDriver extends UploadDriver
{
use ChunkHelpers;

/**
* @var \CodingSocks\ChunkUploader\Identifier\Identifier
*/
private $identifier;

/**
* NgFileUploadDriver constructor.
*
* @param \CodingSocks\ChunkUploader\Identifier\Identifier $identifier
*/
public function __construct(Identifier $identifier)
{
$this->identifier = $identifier;
}

/**
* @inheritDoc
*/
public function handle(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
{
if ($this->isRequestMethodIn($request, [Request::METHOD_GET])) {
return $this->resume($request, $config);
}

if ($this->isRequestMethodIn($request, [Request::METHOD_POST])) {
return $this->save($request, $config, $fileUploaded);
}

throw new MethodNotAllowedHttpException([
Request::METHOD_GET,
Request::METHOD_POST,
]);
}

private function resume(Request $request, StorageConfig $config): Response
{
$request->validate([
'file' => 'required',
'totalSize' => 'required',
]);

$originalFilename = $request->get('file');
$totalSize = $request->get('totalSize');
$uuid = $this->identifier->generateFileIdentifier($totalSize, $originalFilename);

if (!$this->chunkExists($config, $uuid)) {
return new JsonResponse([
'file' => $originalFilename,
'size' => 0,
]);
}

$chunk = Arr::last($this->chunks($config, $uuid));
$size = explode('-', basename($chunk))[1] + 1;

return new JsonResponse([
'file' => $originalFilename,
'size' => $size,
]);
}

private function save(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
{
$file = $request->file('file');

$this->validateUploadedFile($file);

if ($this->isMonolithRequest($request)) {
return $this->saveMonolith($file, $config, $fileUploaded);
}

$this->validateChunkRequest($request);

return $this->saveChunk($file, $request, $config, $fileUploaded);
}

private function isMonolithRequest(Request $request)
{
return empty($request->post());
}

/**
* @param \Illuminate\Http\Request $request
*/
private function validateChunkRequest(Request $request): void
{
$request->validate([
'_chunkNumber' => 'required|numeric',
'_chunkSize' => 'required|numeric',
'_totalSize' => 'required|numeric',
'_currentChunkSize' => 'required|numeric',
]);
}

/**
* @param \Illuminate\Http\UploadedFile $file
* @param \CodingSocks\ChunkUploader\StorageConfig $config
* @param \Closure|null $fileUploaded
*
* @return \Symfony\Component\HttpFoundation\Response
*/
private function saveMonolith(UploadedFile $file, StorageConfig $config, Closure $fileUploaded = null): Response
{
$path = $file->store($config->getMergedDirectory(), [
'disk' => $config->getDisk(),
]);

$this->triggerFileUploadedEvent($config->getDisk(), $path, $fileUploaded);

return new PercentageJsonResponse(100);
}

/**
* @param \Illuminate\Http\UploadedFile $file
* @param \Illuminate\Http\Request $request
* @param \CodingSocks\ChunkUploader\StorageConfig $config
* @param \Closure|null $fileUploaded
*
* @return \Symfony\Component\HttpFoundation\Response
*/
private function saveChunk(UploadedFile $file, Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
{
try {
$range = new NgFileUploadRange($request);
} catch (InvalidArgumentException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}

$originalFilename = $file->getClientOriginalName();
$totalSize = $request->get('_totalSize');
$uuid = $this->identifier->generateFileIdentifier($totalSize, $originalFilename);

$chunks = $this->storeChunk($config, $range, $file, $uuid);

if (!$range->isLast()) {
return new PercentageJsonResponse($range->getPercentage());
}

$targetFilename = $file->hashName();

$path = $this->mergeChunks($config, $chunks, $targetFilename);

if ($config->sweep()) {
$this->deleteChunkDirectory($config, $uuid);
}

$this->triggerFileUploadedEvent($config->getDisk(), $path, $fileUploaded);

return new PercentageJsonResponse(100);
}
}
95 changes: 95 additions & 0 deletions src/Range/NgFileUploadRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace CodingSocks\ChunkUploader\Range;

use InvalidArgumentException;

class NgFileUploadRange implements Range
{
private static $CHUNK_NUMBER_PARAMETER_NAME = '_chunkNumber';
private static $CHUNK_SIZE_PARAMETER_NAME = '_chunkSize';
private static $CURRENT_CHUNK_SIZE_PARAMETER_NAME = '_currentChunkSize';
private static $TOTAL_SIZE_PARAMETER_NAME = '_totalSize';

private $chunkNumber;

private $chunkSize;

private $currentChunkSize;

private $totalSize;

/**
* NgFileUploadRange constructor.
*
* @param \Illuminate\Http\Request|\Symfony\Component\HttpFoundation\ParameterBag $request
*/
public function __construct($request)
{
$this->chunkNumber = (int) $request->get(self::$CHUNK_NUMBER_PARAMETER_NAME);
$this->chunkSize = (int) $request->get(self::$CHUNK_SIZE_PARAMETER_NAME);
$this->currentChunkSize = (int) $request->get(self::$CURRENT_CHUNK_SIZE_PARAMETER_NAME);
$this->totalSize = (double) $request->get(self::$TOTAL_SIZE_PARAMETER_NAME);

if ($this->chunkNumber < 0) {
throw new InvalidArgumentException(sprintf('`%s` must be greater than or equal to zero', self::$CHUNK_NUMBER_PARAMETER_NAME));
}
if ($this->chunkSize < 1) {
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$CHUNK_SIZE_PARAMETER_NAME));
}
if ($this->currentChunkSize < 1) {
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$CURRENT_CHUNK_SIZE_PARAMETER_NAME));
}
if ($this->totalSize < 1) {
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$TOTAL_SIZE_PARAMETER_NAME));
}
}

/**
* {@inheritDoc}
*/
public function getStart(): float
{
return $this->chunkNumber * $this->chunkSize;
}

/**
* {@inheritDoc}
*/
public function getEnd(): float
{
return $this->getStart() + $this->currentChunkSize - 1;
}

/**
* {@inheritDoc}
*/
public function getTotal(): float
{
return $this->totalSize;
}

/**
* {@inheritDoc}
*/
public function isFirst(): bool
{
return $this->chunkNumber === 0;
}

/**
* {@inheritDoc}
*/
public function isLast(): bool
{
return $this->getEnd() === ($this->getTotal() - 1);
}

/**
* @return float
*/
public function getPercentage(): float
{
return floor(($this->getEnd() + 1) / $this->getTotal() * 100);
}
}
9 changes: 9 additions & 0 deletions src/UploadManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use CodingSocks\ChunkUploader\Driver\DropzoneUploadDriver;
use CodingSocks\ChunkUploader\Driver\FlowJsUploadDriver;
use CodingSocks\ChunkUploader\Driver\MonolithUploadDriver;
use CodingSocks\ChunkUploader\Driver\NgFileUploadDriver;
use CodingSocks\ChunkUploader\Driver\PluploadUploadDriver;
use CodingSocks\ChunkUploader\Driver\ResumableJsUploadDriver;
use CodingSocks\ChunkUploader\Driver\SimpleUploaderJsUploadDriver;
Expand Down Expand Up @@ -36,6 +37,14 @@ public function createFlowJsDriver()
return new FlowJsUploadDriver($this->app['config']['chunk-uploader.resumable-js']);
}

public function createNgFileUploadDriver()
{
/** @var \Illuminate\Support\Manager $identityManager */
$identityManager = $this->app['chunk-uploader.identity-manager'];

return new NgFileUploadDriver($identityManager->driver());
}

public function createPluploadDriver()
{
/** @var \Illuminate\Support\Manager $identityManager */
Expand Down
Loading