laravel-chunk-upload icon indicating copy to clipboard operation
laravel-chunk-upload copied to clipboard

What's your recommendation to resumable uploads with this package?

Open chrisbbreuer opened this issue 8 years ago • 12 comments

Hi,

thanks for this amazing package. Saved me tons of time, but I have been wondering about implementing a resumable upload option. What's your recommendation to resumable uploads with this package?

Would love to hear it :-)

chrisbbreuer avatar Mar 06 '17 01:03 chrisbbreuer

Hi @Chinese1904 , thank you, glad to hear that it helped.

I think it is possible to implement it. It seems that pUpload library doesn't have any easy way how enable override of starting byte. Based on the jQuery file upload example: https://github.com/blueimp/jQuery-File-Upload/wiki/Chunked-file-uploads#automatic-resume

Here small guide hot we should achieve it, sorry for the english. Limited time.

  1. I would abstract the construct of FileReceiver with the same construct parameters (chunkStorage and config) - AbstractReceiver. Instead of passing the $fileIndex we would pass the UploadFile object. There would be a method to create a save object createSaveHandler that is used in receive method:
if ($this->handler->isChunkedUpload()) {
            return new ChunkSave($this->file, $this->handler, $this->chunkStorage, $this->config);
        } else {
            return new SingleSave($this->file, $this->handler, $this->config);
        }

The receive would be an abstract method. The FileReceiver would extend the newly create abstract class and would create a $file object before calling parent construct.

 $file = is_object($fileIndexOrFile) ? $fileIndexOrFile : $request->file($fileIndexOrFile);
parent::__construct(...);
  1. I would add a new abstract method into AbstractSave -> exists, that would check if the file exists. Build the ChunkSave
  2. Implement the new method in both Save classes. In ChunkSave use already existing check $this->chunkDisk()->exists($file) and update the line at 142.
  3. Then we would need move methods for getting the getChunkFilePath etc into the AbstractSave (all abstract and renamed to getFilePath and etc).
  4. We could add a new class for detecting current sent bytes ProgressReceiver($fileName, $handlerClass, $chunkStorage = null, $config = null) that would extend the AbstractReceiver. Before calling parents construct, we would need to create a new UploadFile from a $fileName.
  5. After construct i would check if the file exists with the new method, if yes, then i would create a ChunkSave (default is null).
  6. The progress would then have implemented the receive method thet would check if the ChunkSave is not null, if yes it will return null as not uploaded. If the file exists we would build a fully new UploadFile object with the path to current chunk file. The file size would measure the amount of sent bytes.
 // build the new UploadedFile
$finalPath = $this->save->getFullFilePath(); // cached value from `$chunkFullFilePath` property - moved methods
        return new UploadedFile(
            $finalPath, // new method
            null,
            null,
            filesize($finalPath), null,
            true // we must pass the true as test to force the upload file
        );
  1. Then we would create a new method getSentBytes that would call the receive and check if no null, if yes it will return zero bytes. If not, we would return the file size.
  2. Create a new method in your controller that would accept a file name in request. It would create the ProgressReceiver and call the receive method that would return the sent bytes
  3. Implement the Resuming file uploads code from jQuery File Upload

What do you think?

pionl avatar Mar 07 '17 12:03 pionl

I love the idea and that implementation! I wish I could help you with the plupload, but I have never worked with it. Only jQuery File Upload :-(

That definitely would be a great add on for this package.

chrisbbreuer avatar Mar 07 '17 17:03 chrisbbreuer

I think it's not necessary to implement it for plupload. I agree its a good idea, thanks for the proposal. Do you have a free time to make the implementation? I'm quite swamped right now.

Is there any question about the implementation? It would be great to update the example too.

Only one downside at this moment, when a user want's to upload file with same name (the previous file didn't finished - there is chunk file). At this moment, the chunk file is deleted if started again (start index starts at 0).

pionl avatar Mar 08 '17 18:03 pionl

I can't guarantee working on it anytime soon due to me being quite busy as well. Now, within the next few months, if this has not been implemented - I, for sure, would. I will need the feature sometime soon, so I might as well add it into this package. But I can't guarantee.

If anyone beats me to it, that would be perfect :-)

chrisbbreuer avatar Mar 09 '17 00:03 chrisbbreuer

At this moment this feature is not important for me (don't have the time to make it happen). Closing but any one can make it happen :)

pionl avatar Jan 23 '18 15:01 pionl

In February I will have some free time to start working on this feature. I'm going to notify you when I start.

nerg4l avatar Jan 23 '18 15:01 nerg4l

Thanks @Nerg4l, if you need any assistance contact me (here or mail [email protected])

pionl avatar Jan 23 '18 15:01 pionl

Finally I have some free time (a bit later than February).

Since some of the supported libraries did not documented anything about their implementation of this feature I have to reverse engineer it from their source.

As I know you not checking on GitHub 24/7 I have to ask where should I put the example JavaScript code when I'm ready. Here as a comment, to the pull request as a comment, into the Wiki of my fork or ...?

nerg4l avatar Sep 07 '18 20:09 nerg4l

Hi, great info! I've unfortunately added lint fixes, there will be conflicts :(

Add example to pull request, or (better) update the example project 👍 Thank you!

pionl avatar Sep 10 '18 19:09 pionl

Since GH-51 is still not merged, I managed to make a simple code to temporarily enable resumable uploads with resumable.js.

According to resumable.js's docs:

testChunks Make a GET request to the server for each chunk to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: true)

So setting testChunks to true means that the library will make a GET call to the same URL it uses to make the POST calls for the uploads, and ask if the chunk that is about to be uploaded already exists in the server.

So in your web.php:

Route::get('/files/upload', 'UploadController@checkChunk');
Route::post('/files/upload', 'UploadController@upload');

In the upload functon just write your upload logic, I'll not get into that. The important part is in the checkChunk function.

In your UploadController.php:

public function checkChunk(Request $request){
        // The frontend library resumable.js calls this function before uploading every chunk to know if
        // it already exists. This is useful in case the upload was interrupted for some reason.

        // Get the path where the chunks are stored
        $path = storage_path('app/chunks/');

        // The last part of the chunks are formed by an identifier and the chunk number
        $fileName = '*' . $request->resumableIdentifier . '.' . $request->resumableChunkNumber . '.part';
        $fullName = $path . $fileName;

        // Search for a file that ends with the file name we defined
        $chunk = glob($fullName);

        if(count($chunk)){
            // Let resumable.js know that the chunk exists
            return response('ok', 200); // The chunk will not be re-uploaded
        } else {
            // Chunk not found
            return response('ko', 204); // The chunk will be uploaded
        }
    }

I have implemented and tested and it's working great. Maybe it's not the best approach, but it's a temporary fix until the pull request is merged.

I hope it helps someone!

mattogodoy avatar Dec 19 '18 14:12 mattogodoy

@mattogodoy thanks a lot, this is awesome !!!!

rycks avatar Feb 26 '20 16:02 rycks

Hi guys, just a note on the above solution provided by @mattogodoy. For long file names, the check could fails due to a substr applied on resumableIdentifier in the resumablejs handler.

The following fixes this issue:

$fileName = '*' . substr($request->resumableIdentifier, 0, 40) . '.' . $request->resumableChunkNumber . '.part';

Hope this helps someone!

stefanomarra avatar Feb 21 '24 16:02 stefanomarra