@@ -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 );
0 commit comments