Browse Source

Merge pull request #390 from transloadit/full-retry

Restart the entire upload on retry, fixes #387.
Renée Kooi 7 years ago
parent
commit
8d9a1fa03d
5 changed files with 91 additions and 63 deletions
  1. 24 21
      src/core/Core.js
  2. 1 5
      src/plugins/Dashboard/index.js
  3. 3 1
      src/plugins/Transloadit/index.js
  4. 63 20
      src/plugins/Tus10.js
  5. 0 16
      src/plugins/XHRUpload.js

+ 24 - 21
src/core/Core.js

@@ -84,6 +84,7 @@ class Uppy {
     this.resumeAll = this.resumeAll.bind(this)
     this.retryAll = this.retryAll.bind(this)
     this.cancelAll = this.cancelAll.bind(this)
+    this.retryUpload = this.retryUpload.bind(this)
 
     // this.bus = this.emitter = ee()
     this.emitter = ee()
@@ -471,10 +472,6 @@ class Uppy {
 
   retryAll () {
     const updatedFiles = Object.assign({}, this.getState().files)
-    // const inProgressUpdatedFiles = Object.keys(updatedFiles).filter((file) => {
-    //   return !updatedFiles[file].progress.uploadComplete &&
-    //          updatedFiles[file].progress.uploadStarted
-    // })
     const filesToRetry = Object.keys(updatedFiles).filter(file => {
       return updatedFiles[file].error
     })
@@ -486,16 +483,35 @@ class Uppy {
       })
       updatedFiles[file] = updatedFile
     })
-    this.setState({files: updatedFiles})
+    this.setState({
+      files: updatedFiles,
+      error: null
+    })
 
     this.emit('core:retry-all', filesToRetry)
+
+    const uploadID = this.createUpload(filesToRetry)
+    return this.runUpload(uploadID)
+  }
+
+  retryUpload (fileID) {
+    const updatedFiles = Object.assign({}, this.state.files)
+    const updatedFile = Object.assign({}, updatedFiles[fileID],
+      { error: null, isPaused: false }
+    )
+    updatedFiles[fileID] = updatedFile
+    this.setState({
+      files: updatedFiles
+    })
+
+    this.emit('core:upload-retry', fileID)
+
+    const uploadID = this.createUpload([ fileID ])
+    return this.runUpload(uploadID)
   }
 
   reset () {
     this.cancelAll()
-    // this.pauseAll()
-    // this.emit('core:pause-all')
-    // this.emit('core:cancel-all')
   }
 
   cancelAll () {
@@ -594,19 +610,6 @@ class Uppy {
       this.info(message, 'error', 5000)
     })
 
-    this.on('core:upload-retry', (fileID) => {
-      const updatedFiles = Object.assign({}, this.state.files)
-      const updatedFile = Object.assign({}, updatedFiles[fileID],
-        { error: null, isPaused: false }
-      )
-      updatedFiles[fileID] = updatedFile
-      this.setState({files: updatedFiles})
-    })
-
-    this.on('core:retry-all', () => {
-      this.setState({ error: null })
-    })
-
     this.on('core:upload', () => {
       this.setState({ error: null })
     })

+ 1 - 5
src/plugins/Dashboard/index.js

@@ -327,10 +327,6 @@ module.exports = class DashboardUI extends Plugin {
       })
     }
 
-    const retryUpload = (fileID) => {
-      this.core.emit('core:upload-retry', fileID)
-    }
-
     const cancelUpload = (fileID) => {
       this.core.emit('core:upload-cancel', fileID)
       this.core.emit('core:file-remove', fileID)
@@ -378,7 +374,7 @@ module.exports = class DashboardUI extends Plugin {
       resumableUploads: this.core.state.capabilities.resumableUploads || false,
       startUpload: startUpload,
       pauseUpload: this.core.pauseResume,
-      retryUpload: retryUpload,
+      retryUpload: this.core.retryUpload,
       cancelUpload: cancelUpload,
       fileCardFor: pluginState.fileCardFor,
       showFileCard: showFileCard,

+ 3 - 1
src/plugins/Transloadit/index.js

@@ -156,7 +156,9 @@ module.exports = class Transloadit extends Plugin {
         const tus = Object.assign({}, file.tus, {
           endpoint: assembly.tus_url,
           // Only send assembly metadata to the tus endpoint.
-          metaFields: Object.keys(tlMeta)
+          metaFields: Object.keys(tlMeta),
+          // Make sure tus doesn't resume a previous upload.
+          uploadUrl: null
         })
         const transloadit = {
           assembly: assembly.assembly_id

+ 63 - 20
src/plugins/Tus10.js

@@ -26,6 +26,25 @@ const tusDefaultOptions = {
   retryDelays: null
 }
 
+/**
+ * Create a wrapper around an event emitter with a `remove` method to remove
+ * all events that were added using the wrapped emitter.
+ */
+function createEventTracker (emitter) {
+  const events = []
+  return {
+    on (event, fn) {
+      events.push([ event, fn ])
+      return emitter.on(event, fn)
+    },
+    remove () {
+      events.forEach(([ event, fn ]) => {
+        emitter.off(event, fn)
+      })
+    }
+  }
+}
+
 /**
  * Tus resumable file uploader
  *
@@ -47,6 +66,10 @@ module.exports = class Tus10 extends Plugin {
     // merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
 
+    this.uploaders = Object.create(null)
+    this.uploaderEvents = Object.create(null)
+    this.uploaderSockets = Object.create(null)
+
     this.handleResetProgress = this.handleResetProgress.bind(this)
     this.handleUpload = this.handleUpload.bind(this)
   }
@@ -65,6 +88,25 @@ module.exports = class Tus10 extends Plugin {
     this.core.setState({ files })
   }
 
+  /**
+   * Clean up all references for a file's upload: the tus.Upload instance,
+   * any events related to the file, and the uppy-server WebSocket connection.
+   */
+  resetUploaderReferences (fileID) {
+    if (this.uploaders[fileID]) {
+      this.uploaders[fileID].abort()
+      this.uploaders[fileID] = null
+    }
+    if (this.uploaderEvents[fileID]) {
+      this.uploaderEvents[fileID].remove()
+      this.uploaderEvents[fileID] = null
+    }
+    if (this.uploaderSockets[fileID]) {
+      this.uploaderSockets[fileID].close()
+      this.uploaderSockets[fileID] = null
+    }
+  }
+
   /**
    * Create a new Tus upload
    *
@@ -76,6 +118,8 @@ module.exports = class Tus10 extends Plugin {
   upload (file, current, total) {
     this.core.log(`uploading ${current} of ${total}`)
 
+    this.resetUploaderReferences(file.id)
+
     // Create a new tus upload
     return new Promise((resolve, reject) => {
       const optsTus = Object.assign(
@@ -90,6 +134,8 @@ module.exports = class Tus10 extends Plugin {
         this.core.log(err)
         this.core.emit('core:upload-error', file.id, err)
         err.message = `Failed because: ${err.message}`
+
+        this.resetUploaderReferences(file.id)
         reject(err)
       }
 
@@ -110,14 +156,17 @@ module.exports = class Tus10 extends Plugin {
           this.core.log('Download ' + upload.file.name + ' from ' + upload.url)
         }
 
+        this.resetUploaderReferences(file.id)
         resolve(upload)
       }
       optsTus.metadata = file.meta
 
       const upload = new tus.Upload(file.data, optsTus)
+      this.uploaders[file.id] = upload
+      this.uploaderEvents[file.id] = createEventTracker(this.core)
 
       this.onFileRemove(file.id, (targetFileID) => {
-        upload.abort()
+        this.resetUploaderReferences(file.id)
         resolve(`upload ${targetFileID} was removed`)
       })
 
@@ -125,22 +174,12 @@ module.exports = class Tus10 extends Plugin {
         isPaused ? upload.abort() : upload.start()
       })
 
-      this.onRetry(file.id, () => {
-        upload.abort()
-        upload.start()
-      })
-
-      this.onRetryAll(file.id, () => {
-        upload.abort()
-        upload.start()
-      })
-
       this.onPauseAll(file.id, () => {
         upload.abort()
       })
 
       this.onCancelAll(file.id, () => {
-        upload.abort()
+        this.resetUploaderReferences(file.id)
       })
 
       this.onResumeAll(file.id, () => {
@@ -156,6 +195,8 @@ module.exports = class Tus10 extends Plugin {
   }
 
   uploadRemote (file, current, total) {
+    this.resetUploaderReferences(file.id)
+
     return new Promise((resolve, reject) => {
       this.core.log(file.remote.url)
       if (file.serverToken) {
@@ -204,6 +245,8 @@ module.exports = class Tus10 extends Plugin {
     const token = file.serverToken
     const host = getSocketHost(file.remote.host)
     const socket = new UppySocket({ target: `${host}/api/${token}` })
+    this.uploaderSockets[file.id] = socket
+    this.uploaderEvents[file.id] = createEventTracker(this.core)
 
     this.onFileRemove(file.id, () => socket.send('pause', {}))
 
@@ -236,7 +279,7 @@ module.exports = class Tus10 extends Plugin {
 
     socket.on('success', (data) => {
       this.core.emitter.emit('core:upload-success', file.id, data, data.url)
-      socket.close()
+      this.resetUploaderReferences(file.id)
     })
   }
 
@@ -266,13 +309,13 @@ module.exports = class Tus10 extends Plugin {
   }
 
   onFileRemove (fileID, cb) {
-    this.core.on('core:file-removed', (targetFileID) => {
+    this.uploaderEvents[fileID].on('core:file-removed', (targetFileID) => {
       if (fileID === targetFileID) cb(targetFileID)
     })
   }
 
   onPause (fileID, cb) {
-    this.core.on('core:upload-pause', (targetFileID, isPaused) => {
+    this.uploaderEvents[fileID].on('core:upload-pause', (targetFileID, isPaused) => {
       if (fileID === targetFileID) {
         // const isPaused = this.core.pauseResume(fileID)
         cb(isPaused)
@@ -281,7 +324,7 @@ module.exports = class Tus10 extends Plugin {
   }
 
   onRetry (fileID, cb) {
-    this.core.on('core:upload-retry', (targetFileID) => {
+    this.uploaderEvents[fileID].on('core:upload-retry', (targetFileID) => {
       if (fileID === targetFileID) {
         cb()
       }
@@ -289,28 +332,28 @@ module.exports = class Tus10 extends Plugin {
   }
 
   onRetryAll (fileID, cb) {
-    this.core.on('core:retry-all', (filesToRetry) => {
+    this.uploaderEvents[fileID].on('core:retry-all', (filesToRetry) => {
       if (!this.core.getFile(fileID)) return
       cb()
     })
   }
 
   onPauseAll (fileID, cb) {
-    this.core.on('core:pause-all', () => {
+    this.uploaderEvents[fileID].on('core:pause-all', () => {
       if (!this.core.getFile(fileID)) return
       cb()
     })
   }
 
   onCancelAll (fileID, cb) {
-    this.core.on('core:cancel-all', () => {
+    this.uploaderEvents[fileID].on('core:cancel-all', () => {
       if (!this.core.getFile(fileID)) return
       cb()
     })
   }
 
   onResumeAll (fileID, cb) {
-    this.core.on('core:resume-all', () => {
+    this.uploaderEvents[fileID].on('core:resume-all', () => {
       if (!this.core.getFile(fileID)) return
       cb()
     })

+ 0 - 16
src/plugins/XHRUpload.js

@@ -34,8 +34,6 @@ module.exports = class XHRUpload extends Plugin {
     this.opts = Object.assign({}, defaultOptions, opts)
 
     this.handleUpload = this.handleUpload.bind(this)
-    this.handleRetry = this.handleRetry.bind(this)
-    this.handleRetryAll = this.handleRetryAll.bind(this)
   }
 
   getOptions (file) {
@@ -230,25 +228,11 @@ module.exports = class XHRUpload extends Plugin {
     return this.uploadFiles(files).then(() => null)
   }
 
-  handleRetry (targetFileID) {
-    this.handleUpload([targetFileID])
-  }
-
-  handleRetryAll (filesToRetry) {
-    this.handleUpload(filesToRetry)
-  }
-
   install () {
     this.core.addUploader(this.handleUpload)
-
-    this.core.on('core:upload-retry', this.handleRetry)
-    this.core.on('core:retry-all', this.handleRetryAll)
   }
 
   uninstall () {
     this.core.removeUploader(this.handleUpload)
-
-    this.core.off('core:upload-retry', this.handleRetry)
-    this.core.off('core:retry-all', this.handleRetryAll)
   }
 }