Skip to content

Commit 27fa0c5

Browse files
authored
Merge pull request dilab#36 from abilogos/improved
Improved
2 parents eeb8298 + 72f438c commit 27fa0c5

File tree

9 files changed

+128
-77
lines changed

9 files changed

+128
-77
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
vendor/*
22
composer.lock
3-
.idea
3+
.idea
4+
.phpunit.cache/*
5+
.phpunit.*.cache

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
}
99
],
1010
"require": {
11-
"php": ">=5.3.0",
11+
"php": ">=8.1.0",
1212
"cakephp/filesystem": "^3.0",
13-
"monolog/monolog": "^1.17"
13+
"monolog/monolog": "^2.0"
1414
},
1515
"require-dev": {
16-
"phpunit/phpunit": "~4.0"
16+
"phpunit/phpunit": "~10.0"
1717
},
1818
"license": "MIT",
1919
"autoload": {

phpunit.xml

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit backupGlobals="false"
3-
backupStaticAttributes="false"
4-
bootstrap="./vendor/autoload.php"
5-
colors="true"
6-
convertErrorsToExceptions="true"
7-
convertNoticesToExceptions="true"
8-
convertWarningsToExceptions="true"
9-
processIsolation="false"
10-
stopOnFailure="true"
11-
syntaxCheck="false"
12-
>
13-
<testsuites>
14-
<testsuite name="Package Test Suite">
15-
<directory suffix=".php">./test/</directory>
16-
</testsuite>
17-
</testsuites>
18-
</phpunit>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
3+
<testsuites>
4+
<testsuite name="Package Test Suite">
5+
<directory suffix=".php">./test/</directory>
6+
</testsuite>
7+
</testsuites>
8+
</phpunit>

readme.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# PHP backend for resumable.js
22

3-
43
## Installation
54

65
To install, use composer:
@@ -21,11 +20,23 @@ use Dilab\Resumable;
2120
2221
$request = new SimpleRequest();
2322
$response = new SimpleResponse();
23+
// optional instanceId to seperate uploads from diffrent users like if two users want to upload untitled.jpg there would be no conflict anymore
24+
$instanceId = session_id();
2425
25-
$resumable = new Resumable($request, $response);
26+
$resumable = new Resumable($request, $response, $instanceId);
2627
$resumable->tempFolder = 'tmps';
2728
$resumable->uploadFolder = 'uploads';
28-
$resumable->process();
29+
$status = $resumable->process();
30+
31+
return match ($status){
32+
200 => ['message' => 'OK'], // Uploading of chunk is complete.
33+
201 => [
34+
'message' => 'File uploaded',
35+
'file' => $_REQUEST['resumableFilename']
36+
],// Uploading of whole file is complete.
37+
204 => ['message' => 'Chunk not found'],
38+
default => ['message' => 'An error occurred'] //status => 404
39+
};
2940
3041
```
3142

src/Network/SimpleResponse.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ class SimpleResponse implements Response
1111
*/
1212
public function header($statusCode)
1313
{
14-
if (200==$statusCode) {
15-
return header($_SERVER["SERVER_PROTOCOL"]." 200 Ok");
16-
} else if (404==$statusCode) {
17-
return header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
14+
if($statusCode >= 500) {
15+
$statusCode = 204;
1816
}
19-
return header($_SERVER["SERVER_PROTOCOL"]." 204 No Content");
17+
if (!in_array($statusCode, [200,201,204,404])) {
18+
$statusCode = 404;
19+
}
20+
http_response_code($statusCode);
21+
return $statusCode;
2022
}
2123

2224
}

src/Resumable.php

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Resumable
2424

2525
protected $response;
2626

27+
protected $instanceId;
28+
2729
protected $params;
2830

2931
protected $chunkFile;
@@ -45,15 +47,17 @@ class Resumable
4547
'filename' => 'filename',
4648
'chunkNumber' => 'chunkNumber',
4749
'chunkSize' => 'chunkSize',
48-
'totalSize' => 'totalSize'
50+
'totalSize' => 'totalSize',
51+
'totalChunks' => 'totalChunks'
4952
];
5053

5154
const WITHOUT_EXTENSION = true;
5255

53-
public function __construct(Request $request, Response $response)
56+
public function __construct(Request $request, Response $response, string|null $instanceId = null)
5457
{
5558
$this->request = $request;
5659
$this->response = $response;
60+
$this->instanceId = $instanceId;
5761

5862
$this->log = new Logger('debug');
5963
$this->log->pushHandler(new StreamHandler('debug.log', Logger::DEBUG));
@@ -81,9 +85,9 @@ public function process()
8185
{
8286
if (!empty($this->resumableParams())) {
8387
if (!empty($this->request->file())) {
84-
$this->handleChunk();
88+
return $this->handleChunk();
8589
} else {
86-
$this->handleTestChunk();
90+
return $this->handleTestChunk();
8791
}
8892
}
8993
}
@@ -174,10 +178,17 @@ public function handleTestChunk()
174178
$identifier = $this->resumableParam($this->resumableOption['identifier']);
175179
$filename = $this->resumableParam($this->resumableOption['filename']);
176180
$chunkNumber = $this->resumableParam($this->resumableOption['chunkNumber']);
181+
$chunkSize = $this->resumableParam($this->resumableOption['chunkSize']);
182+
$totalChunks = $this->resumableParam($this->resumableOption['totalChunks']);
177183

178184
if (!$this->isChunkUploaded($identifier, $filename, $chunkNumber)) {
179185
return $this->response->header(204);
180186
} else {
187+
if ($this->isFileUploadComplete($filename, $identifier, $totalChunks)) {
188+
$this->isUploadComplete = true;
189+
$this->createFileAndDeleteTmp($identifier, $filename);
190+
return $this->response->header(201);
191+
}
181192
return $this->response->header(200);
182193
}
183194

@@ -190,16 +201,17 @@ public function handleChunk()
190201
$filename = $this->resumableParam($this->resumableOption['filename']);
191202
$chunkNumber = $this->resumableParam($this->resumableOption['chunkNumber']);
192203
$chunkSize = $this->resumableParam($this->resumableOption['chunkSize']);
193-
$totalSize = $this->resumableParam($this->resumableOption['totalSize']);
204+
$totalChunks = $this->resumableParam($this->resumableOption['totalChunks']);
194205

195206
if (!$this->isChunkUploaded($identifier, $filename, $chunkNumber)) {
196207
$chunkFile = $this->tmpChunkDir($identifier) . DIRECTORY_SEPARATOR . $this->tmpChunkFilename($filename, $chunkNumber);
197208
$this->moveUploadedFile($file['tmp_name'], $chunkFile);
198209
}
199210

200-
if ($this->isFileUploadComplete($filename, $identifier, $chunkSize, $totalSize)) {
211+
if ($this->isFileUploadComplete($filename, $identifier, $totalChunks)) {
201212
$this->isUploadComplete = true;
202213
$this->createFileAndDeleteTmp($identifier, $filename);
214+
return $this->response->header(201);
203215
}
204216

205217
return $this->response->header(200);
@@ -221,7 +233,12 @@ private function createFileAndDeleteTmp($identifier, $filename)
221233
}
222234

223235
// replace filename reference by the final file
224-
$this->filepath = $this->uploadFolder . DIRECTORY_SEPARATOR . $finalFilename;
236+
$this->filepath = $this->uploadFolder . DIRECTORY_SEPARATOR;
237+
if (!empty($this->instanceId)) {
238+
$this->filepath .= $this->instanceId . DIRECTORY_SEPARATOR;
239+
}
240+
$this->filepath .= $finalFilename;
241+
225242
$this->extension = $this->findExtension($this->filepath);
226243

227244
if ($this->createFileFromChunks($chunkFiles, $this->filepath) && $this->deleteTmpFolder) {
@@ -249,13 +266,9 @@ public function resumableParams()
249266
}
250267
}
251268

252-
public function isFileUploadComplete($filename, $identifier, $chunkSize, $totalSize)
269+
public function isFileUploadComplete($filename, $identifier, $totalChunks)
253270
{
254-
if ($chunkSize <= 0) {
255-
return false;
256-
}
257-
$numOfChunks = intval($totalSize / $chunkSize) + ($totalSize % $chunkSize == 0 ? 0 : 1);
258-
for ($i = 1; $i < $numOfChunks; $i++) {
271+
for ($i = 1; $i <= $totalChunks; $i++) {
259272
if (!$this->isChunkUploaded($identifier, $filename, $i)) {
260273
return false;
261274
}
@@ -271,13 +284,38 @@ public function isChunkUploaded($identifier, $filename, $chunkNumber)
271284

272285
public function tmpChunkDir($identifier)
273286
{
274-
$tmpChunkDir = $this->tempFolder . DIRECTORY_SEPARATOR . $identifier;
275-
if (!file_exists($tmpChunkDir)) {
276-
mkdir($tmpChunkDir);
287+
$tmpChunkDir = $this->tempFolder. DIRECTORY_SEPARATOR;
288+
if (!empty($this->instanceId)){
289+
$tmpChunkDir .= $this->instanceId . DIRECTORY_SEPARATOR;
277290
}
291+
$tmpChunkDir .= $identifier;
292+
$this->ensureDirExists($tmpChunkDir);
278293
return $tmpChunkDir;
279294
}
280295

296+
/**
297+
* make directory if it doesn't exists (Immune against the race condition)
298+
*
299+
*
300+
* since the resuamble is usually used with simultaneously uploads,
301+
* this sometimes resulted in directory creation btween the *is_dir* check
302+
* and *mkdir* then following race condition.
303+
* in this setup it will shut down the mkdir error
304+
* then try to check if directory is created after that
305+
*
306+
* @param string $path the directoryPath to ensure
307+
* @return void
308+
* @throws \Exception
309+
*/
310+
private function ensureDirExists($path)
311+
{
312+
umask(0);
313+
if ( is_dir($path) || @mkdir($path, 0775, true) || is_dir($path)) {
314+
return;
315+
}
316+
throw new \Exception("could not mkdir $path");
317+
}
318+
281319
public function tmpChunkFilename($filename, $chunkNumber)
282320
{
283321
return $filename . '.' . str_pad($chunkNumber, 4, 0, STR_PAD_LEFT);
@@ -299,6 +337,10 @@ public function createFileFromChunks($chunkFiles, $destFile)
299337

300338
natsort($chunkFiles);
301339

340+
if (!empty($this->instanceId)) {
341+
$this->ensureDirExists(dirname($destFile));
342+
}
343+
302344
$handle = $this->getExclusiveFileHandle($destFile);
303345
if (!$handle) {
304346
return false;
@@ -319,6 +361,9 @@ public function createFileFromChunks($chunkFiles, $destFile)
319361

320362
public function moveUploadedFile($file, $destFile)
321363
{
364+
//workaround cakephp error regarding: TMP not defined
365+
define("TMP",sys_get_temp_dir());
366+
322367
$file = new File($file);
323368
if ($file->exists()) {
324369
return $file->copy($destFile);

test/src/Network/SimpleRequestTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
* @package Dilab\Network
99
* @property $request Request
1010
*/
11-
class SimpleRequestTest extends \PHPUnit_Framework_TestCase
11+
class SimpleRequestTest extends \PHPUnit\Framework\TestCase
1212
{
13-
protected function setUp()
13+
protected function setUp() : void
1414
{
1515
$this->request = new SimpleRequest();
1616
}
1717

18-
public function tearDown()
18+
public function tearDown() : void
1919
{
2020
unset($this->request);
2121
parent::tearDown();

test/src/Network/SimpleResponseTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@
88
* @package Dilab\Network
99
* @property $response Response
1010
*/
11-
class SimpleResponseTest extends \PHPUnit_Framework_TestCase
11+
class SimpleResponseTest extends \PHPUnit\Framework\TestCase
1212
{
13-
protected function setUp()
13+
protected function setUp() : void
1414
{
1515
$this->response = new SimpleResponse();
1616
}
1717

18-
public function tearDown()
18+
public function tearDown() : void
1919
{
2020
unset($this->response);
2121
parent::tearDown();
2222
}
2323

2424

25-
public function headerProvider()
25+
public static function headerProvider()
2626
{
27-
return array(
28-
array(404,404),
29-
array(204,204),
30-
array(200,200),
31-
array(500,204),
32-
);
27+
return [
28+
[404,404],
29+
[204,204],
30+
[200,200],
31+
[500,204],
32+
];
3333
}
3434

3535
/**

0 commit comments

Comments
 (0)