Merge "Make chunkedUpload match upload behavior"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 14 Sep 2017 19:45:14 +0000 (19:45 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 14 Sep 2017 19:45:14 +0000 (19:45 +0000)
resources/src/mediawiki/api/upload.js

index 4a2895d..29bd59a 100644 (file)
                 * @return {jQuery.Promise}
                 */
                uploadChunk: function ( file, data, start, end, filekey, retries ) {
-                       var upload, retry,
+                       var upload,
                                api = this,
                                chunk = this.slice( file, start, end );
 
                        // In such case, it could be useful to try again: a network hickup
                        // doesn't necessarily have to result in upload failure...
                        retries = retries === undefined ? 1 : retries;
-                       retry = function ( code, result ) {
-                               var deferred = $.Deferred(),
-                                       callback = function () {
-                                               api.uploadChunk( file, data, start, end, filekey, retries - 1 )
-                                                       .then( deferred.resolve, deferred.reject );
-                                       };
-
-                               // Don't retry if the request failed because we aborted it (or
-                               // if it's another kind of request failure)
-                               if ( code !== 'http' || result.textStatus === 'abort' ) {
-                                       return deferred.reject( code, result );
-                               }
-
-                               setTimeout( callback, 1000 );
-                               return deferred.promise();
-                       };
 
                        data.filesize = file.size;
                        data.chunk = chunk;
                        upload = this.uploadWithFormData( file, data );
                        return upload.then(
                                null,
-                               // If the call fails, we may want to try again...
-                               retries === 0 ? null : retry,
+                               function ( code, result ) {
+                                       var retry;
+
+                                       // uploadWithFormData will reject uploads with warnings, but
+                                       // these warnings could be "harmless" or recovered from
+                                       // (e.g. exists-normalized, when it'll be renamed later)
+                                       // In the case of (only) a warning, we still want to
+                                       // continue the chunked upload until it completes: then
+                                       // reject it - at least it's been fully uploaded by then and
+                                       // failure handlers have a complete result object (including
+                                       // possibly more warnings, e.g. duplicate)
+                                       // This matches .upload, which also completes the upload.
+                                       if ( result.upload && result.upload.warnings && code in result.upload.warnings ) {
+                                               if ( end === file.size ) {
+                                                       // uploaded last chunk = reject with result data
+                                                       return $.Deferred().reject( code, result );
+                                               } else {
+                                                       // still uploading chunks = resolve to keep going
+                                                       return $.Deferred().resolve( result );
+                                               }
+                                       }
+
+                                       if ( retries === 0 ) {
+                                               return $.Deferred().reject( code, result );
+                                       }
+
+                                       // If the call flat out failed, we may want to try again...
+                                       retry = api.uploadChunk.bind( this, file, data, start, end, filekey, retries - 1 );
+                                       return api.retry( code, result, retry );
+                               },
                                function ( fraction ) {
                                        // Since we're only uploading small parts of a file, we
                                        // need to adjust the reported progress to reflect where
                        ).promise( { abort: upload.abort } );
                },
 
+               /**
+                * Launch the upload anew if it failed because of network issues.
+                *
+                * @private
+                * @param {string} code Error code
+                * @param {Object} result API result
+                * @param {Function} callable
+                * @return {jQuery.Promise}
+                */
+               retry: function ( code, result, callable ) {
+                       var uploadPromise,
+                               retryTimer,
+                               deferred = $.Deferred(),
+                               // Wrap around the callable, so that once it completes, it'll
+                               // resolve/reject the promise we'll return
+                               retry = function () {
+                                       uploadPromise = callable();
+                                       uploadPromise.then( deferred.resolve, deferred.reject );
+                               };
+
+                       // Don't retry if the request failed because we aborted it (or if
+                       // it's another kind of request failure)
+                       if ( code !== 'http' || result.textStatus === 'abort' ) {
+                               return deferred.reject( code, result );
+                       }
+
+                       retryTimer = setTimeout( retry, 1000 );
+                       return deferred.promise( { abort: function () {
+                               // Clear the scheduled upload, or abort if already in flight
+                               if ( retryTimer ) {
+                                       clearTimeout( retryTimer );
+                               }
+                               if ( uploadPromise.abort ) {
+                                       uploadPromise.abort();
+                               }
+                       } } );
+               },
+
                /**
                 * Slice a chunk out of a File object.
                 *