Skip to content

Commit 8bea54f

Browse files
authored
Add ng-file-upload driver (#61)
* Add ng-file-upload driver * Re-arrange use * Remove unnecessary line
1 parent dd47027 commit 8bea54f

File tree

8 files changed

+755
-2
lines changed

8 files changed

+755
-2
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ project at the moment is [tus](https://tus.io/).
3737
- [Blueimp](#blueimp-driver)
3838
- [DropzoneJS](#dropzonejs-driver)
3939
- [Flow.js](#flow-js-driver)
40+
- [ng-file-upload](#ng-file-upload-driver)
4041
- [Plupload](#plupload-driver)
4142
- [Resumable.js](#resumable-js-driver)
4243
- [simple-uploader.js](#simple-uploader-js-driver)
@@ -153,6 +154,7 @@ Service | Driver name | Chunk
153154
[Blueimp](#blueimp-driver) | `blueimp` | yes | yes
154155
[DropzoneJS](#dropzonejs-driver) | `dropzone` | yes | no
155156
[Flow.js](#flow-js-driver) | `flow-js` | yes | yes
157+
[ng-file-upload](#ng-file-upload-driver) | `ng-file-upload` | yes | no
156158
[Plupload](#plupload-driver) | `plupload` | yes | no
157159
[Resumable.js](#resumable-js-driver) | `resumable-js` | yes | yes
158160
[simple-uploader.js](#simple-uploader-js-driver) | `simple-uploader-js` | yes | yes
@@ -179,6 +181,12 @@ This driver handles requests made by the DropzoneJS client library.
179181

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

184+
### ng-file-upload driver
185+
186+
[website](https://github.com/danialfarid/ng-file-upload)
187+
188+
This driver handles requests made by the ng-file-upload client library.
189+
182190
### Plupload driver
183191

184192
[website](https://github.com/moxiecode/plupload)

config/chunk-uploader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
| throughout your application here. By default, the module is setup for
1313
| monolith upload.
1414
|
15-
| Supported: "monolith", "blueimp", "dropzone", "flow-js", "resumable-js",
16-
| "simple-uploader-js"
15+
| Supported: "monolith", "blueimp", "dropzone", "flow-js",
16+
| "ng-file-upload", "resumable-js", "simple-uploader-js"
1717
|
1818
*/
1919

examples/ng-file-upload.blade.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
7+
<title>ng-file-upload</title>
8+
</head>
9+
<body>
10+
<h1>ng-file-upload</h1>
11+
12+
<div ng-app="fileUpload" ng-controller="MyCtrl">
13+
<form name="myForm">
14+
<input type="file" ngf-select ng-model="picFile" name="file" required>
15+
<br/>
16+
17+
<button ng-disabled="!myForm.$valid" ng-click="uploadPic(picFile)">Submit</button>
18+
<span ng-show="picFile.result">Upload Successful</span>
19+
</form>
20+
</div>
21+
22+
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
23+
<script src="//cdn.jsdelivr.net/gh/danialfarid/ng-file-upload/dist/ng-file-upload.min.js"></script>
24+
<script>
25+
const uploadUrl = "{{ url('upload') }}";
26+
const app = angular.module('fileUpload', ['ngFileUpload']);
27+
28+
app.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) {
29+
$scope.uploadPic = function (file) {
30+
$scope.formUpload = true;
31+
if (file != null) {
32+
$scope.upload(file);
33+
}
34+
};
35+
36+
$scope.upload = function (file) {
37+
$scope.errorMsg = null;
38+
file.upload = Upload.upload({
39+
url: uploadUrl,
40+
resumeSizeUrl: uploadUrl + '?file=' + encodeURIComponent(file.name) + '&totalSize=' + file.size,
41+
resumeSizeResponseReader: function(data) {return data.size;},
42+
resumeChunkSize: 1024 * 1024,
43+
data: {file: file}
44+
});
45+
};
46+
}]);
47+
</script>
48+
</body>
49+
</html>

src/Driver/NgFileUploadDriver.php

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<?php
2+
3+
namespace CodingSocks\ChunkUploader\Driver;
4+
5+
use Closure;
6+
use CodingSocks\ChunkUploader\Helper\ChunkHelpers;
7+
use CodingSocks\ChunkUploader\Identifier\Identifier;
8+
use CodingSocks\ChunkUploader\Range\NgFileUploadRange;
9+
use CodingSocks\ChunkUploader\Response\PercentageJsonResponse;
10+
use CodingSocks\ChunkUploader\StorageConfig;
11+
use Illuminate\Http\JsonResponse;
12+
use Illuminate\Http\Request;
13+
use Illuminate\Http\UploadedFile;
14+
use Illuminate\Support\Arr;
15+
use InvalidArgumentException;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
18+
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
19+
20+
class NgFileUploadDriver extends UploadDriver
21+
{
22+
use ChunkHelpers;
23+
24+
/**
25+
* @var \CodingSocks\ChunkUploader\Identifier\Identifier
26+
*/
27+
private $identifier;
28+
29+
/**
30+
* NgFileUploadDriver constructor.
31+
*
32+
* @param \CodingSocks\ChunkUploader\Identifier\Identifier $identifier
33+
*/
34+
public function __construct(Identifier $identifier)
35+
{
36+
$this->identifier = $identifier;
37+
}
38+
39+
/**
40+
* @inheritDoc
41+
*/
42+
public function handle(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
43+
{
44+
if ($this->isRequestMethodIn($request, [Request::METHOD_GET])) {
45+
return $this->resume($request, $config);
46+
}
47+
48+
if ($this->isRequestMethodIn($request, [Request::METHOD_POST])) {
49+
return $this->save($request, $config, $fileUploaded);
50+
}
51+
52+
throw new MethodNotAllowedHttpException([
53+
Request::METHOD_GET,
54+
Request::METHOD_POST,
55+
]);
56+
}
57+
58+
private function resume(Request $request, StorageConfig $config): Response
59+
{
60+
$request->validate([
61+
'file' => 'required',
62+
'totalSize' => 'required',
63+
]);
64+
65+
$originalFilename = $request->get('file');
66+
$totalSize = $request->get('totalSize');
67+
$uuid = $this->identifier->generateFileIdentifier($totalSize, $originalFilename);
68+
69+
if (!$this->chunkExists($config, $uuid)) {
70+
return new JsonResponse([
71+
'file' => $originalFilename,
72+
'size' => 0,
73+
]);
74+
}
75+
76+
$chunk = Arr::last($this->chunks($config, $uuid));
77+
$size = explode('-', basename($chunk))[1] + 1;
78+
79+
return new JsonResponse([
80+
'file' => $originalFilename,
81+
'size' => $size,
82+
]);
83+
}
84+
85+
private function save(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
86+
{
87+
$file = $request->file('file');
88+
89+
$this->validateUploadedFile($file);
90+
91+
if ($this->isMonolithRequest($request)) {
92+
return $this->saveMonolith($file, $config, $fileUploaded);
93+
}
94+
95+
$this->validateChunkRequest($request);
96+
97+
return $this->saveChunk($file, $request, $config, $fileUploaded);
98+
}
99+
100+
private function isMonolithRequest(Request $request)
101+
{
102+
return empty($request->post());
103+
}
104+
105+
/**
106+
* @param \Illuminate\Http\Request $request
107+
*/
108+
private function validateChunkRequest(Request $request): void
109+
{
110+
$request->validate([
111+
'_chunkNumber' => 'required|numeric',
112+
'_chunkSize' => 'required|numeric',
113+
'_totalSize' => 'required|numeric',
114+
'_currentChunkSize' => 'required|numeric',
115+
]);
116+
}
117+
118+
/**
119+
* @param \Illuminate\Http\UploadedFile $file
120+
* @param \CodingSocks\ChunkUploader\StorageConfig $config
121+
* @param \Closure|null $fileUploaded
122+
*
123+
* @return \Symfony\Component\HttpFoundation\Response
124+
*/
125+
private function saveMonolith(UploadedFile $file, StorageConfig $config, Closure $fileUploaded = null): Response
126+
{
127+
$path = $file->store($config->getMergedDirectory(), [
128+
'disk' => $config->getDisk(),
129+
]);
130+
131+
$this->triggerFileUploadedEvent($config->getDisk(), $path, $fileUploaded);
132+
133+
return new PercentageJsonResponse(100);
134+
}
135+
136+
/**
137+
* @param \Illuminate\Http\UploadedFile $file
138+
* @param \Illuminate\Http\Request $request
139+
* @param \CodingSocks\ChunkUploader\StorageConfig $config
140+
* @param \Closure|null $fileUploaded
141+
*
142+
* @return \Symfony\Component\HttpFoundation\Response
143+
*/
144+
private function saveChunk(UploadedFile $file, Request $request, StorageConfig $config, Closure $fileUploaded = null): Response
145+
{
146+
try {
147+
$range = new NgFileUploadRange($request);
148+
} catch (InvalidArgumentException $e) {
149+
throw new BadRequestHttpException($e->getMessage(), $e);
150+
}
151+
152+
$originalFilename = $file->getClientOriginalName();
153+
$totalSize = $request->get('_totalSize');
154+
$uuid = $this->identifier->generateFileIdentifier($totalSize, $originalFilename);
155+
156+
$chunks = $this->storeChunk($config, $range, $file, $uuid);
157+
158+
if (!$range->isLast()) {
159+
return new PercentageJsonResponse($range->getPercentage());
160+
}
161+
162+
$targetFilename = $file->hashName();
163+
164+
$path = $this->mergeChunks($config, $chunks, $targetFilename);
165+
166+
if ($config->sweep()) {
167+
$this->deleteChunkDirectory($config, $uuid);
168+
}
169+
170+
$this->triggerFileUploadedEvent($config->getDisk(), $path, $fileUploaded);
171+
172+
return new PercentageJsonResponse(100);
173+
}
174+
}

src/Range/NgFileUploadRange.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
namespace CodingSocks\ChunkUploader\Range;
4+
5+
use InvalidArgumentException;
6+
7+
class NgFileUploadRange implements Range
8+
{
9+
private static $CHUNK_NUMBER_PARAMETER_NAME = '_chunkNumber';
10+
private static $CHUNK_SIZE_PARAMETER_NAME = '_chunkSize';
11+
private static $CURRENT_CHUNK_SIZE_PARAMETER_NAME = '_currentChunkSize';
12+
private static $TOTAL_SIZE_PARAMETER_NAME = '_totalSize';
13+
14+
private $chunkNumber;
15+
16+
private $chunkSize;
17+
18+
private $currentChunkSize;
19+
20+
private $totalSize;
21+
22+
/**
23+
* NgFileUploadRange constructor.
24+
*
25+
* @param \Illuminate\Http\Request|\Symfony\Component\HttpFoundation\ParameterBag $request
26+
*/
27+
public function __construct($request)
28+
{
29+
$this->chunkNumber = (int) $request->get(self::$CHUNK_NUMBER_PARAMETER_NAME);
30+
$this->chunkSize = (int) $request->get(self::$CHUNK_SIZE_PARAMETER_NAME);
31+
$this->currentChunkSize = (int) $request->get(self::$CURRENT_CHUNK_SIZE_PARAMETER_NAME);
32+
$this->totalSize = (double) $request->get(self::$TOTAL_SIZE_PARAMETER_NAME);
33+
34+
if ($this->chunkNumber < 0) {
35+
throw new InvalidArgumentException(sprintf('`%s` must be greater than or equal to zero', self::$CHUNK_NUMBER_PARAMETER_NAME));
36+
}
37+
if ($this->chunkSize < 1) {
38+
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$CHUNK_SIZE_PARAMETER_NAME));
39+
}
40+
if ($this->currentChunkSize < 1) {
41+
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$CURRENT_CHUNK_SIZE_PARAMETER_NAME));
42+
}
43+
if ($this->totalSize < 1) {
44+
throw new InvalidArgumentException(sprintf('`%s` must be greater than zero', self::$TOTAL_SIZE_PARAMETER_NAME));
45+
}
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
public function getStart(): float
52+
{
53+
return $this->chunkNumber * $this->chunkSize;
54+
}
55+
56+
/**
57+
* {@inheritDoc}
58+
*/
59+
public function getEnd(): float
60+
{
61+
return $this->getStart() + $this->currentChunkSize - 1;
62+
}
63+
64+
/**
65+
* {@inheritDoc}
66+
*/
67+
public function getTotal(): float
68+
{
69+
return $this->totalSize;
70+
}
71+
72+
/**
73+
* {@inheritDoc}
74+
*/
75+
public function isFirst(): bool
76+
{
77+
return $this->chunkNumber === 0;
78+
}
79+
80+
/**
81+
* {@inheritDoc}
82+
*/
83+
public function isLast(): bool
84+
{
85+
return $this->getEnd() === ($this->getTotal() - 1);
86+
}
87+
88+
/**
89+
* @return float
90+
*/
91+
public function getPercentage(): float
92+
{
93+
return floor(($this->getEnd() + 1) / $this->getTotal() * 100);
94+
}
95+
}

src/UploadManager.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use CodingSocks\ChunkUploader\Driver\DropzoneUploadDriver;
77
use CodingSocks\ChunkUploader\Driver\FlowJsUploadDriver;
88
use CodingSocks\ChunkUploader\Driver\MonolithUploadDriver;
9+
use CodingSocks\ChunkUploader\Driver\NgFileUploadDriver;
910
use CodingSocks\ChunkUploader\Driver\PluploadUploadDriver;
1011
use CodingSocks\ChunkUploader\Driver\ResumableJsUploadDriver;
1112
use CodingSocks\ChunkUploader\Driver\SimpleUploaderJsUploadDriver;
@@ -36,6 +37,14 @@ public function createFlowJsDriver()
3637
return new FlowJsUploadDriver($this->app['config']['chunk-uploader.resumable-js']);
3738
}
3839

40+
public function createNgFileUploadDriver()
41+
{
42+
/** @var \Illuminate\Support\Manager $identityManager */
43+
$identityManager = $this->app['chunk-uploader.identity-manager'];
44+
45+
return new NgFileUploadDriver($identityManager->driver());
46+
}
47+
3948
public function createPluploadDriver()
4049
{
4150
/** @var \Illuminate\Support\Manager $identityManager */

0 commit comments

Comments
 (0)