瀏覽代碼

Merge pull request #290 from goto-bus-stop/feature/tl-assembly-without-files

transloadit: Add `alwaysRunAssembly` option
Artur Paikin 7 年之前
父節點
當前提交
2bd4364968
共有 3 個文件被更改,包括 103 次插入30 次删除
  1. 3 1
      src/core/Core.js
  2. 84 29
      src/plugins/Transloadit/index.js
  3. 16 0
      test/unit/Transloadit.spec.js

+ 3 - 1
src/core/Core.js

@@ -870,7 +870,9 @@ class Uppy {
             [uploadID]: currentUpload
             [uploadID]: currentUpload
           })
           })
         })
         })
-        return fn(fileIDs)
+        // TODO give this the `currentUpload` object as its only parameter maybe?
+        // Otherwise when more metadata may be added to the upload this would keep getting more parameters
+        return fn(fileIDs, uploadID)
       })
       })
     })
     })
 
 

+ 84 - 29
src/plugins/Transloadit/index.js

@@ -23,6 +23,7 @@ module.exports = class Transloadit extends Plugin {
     const defaultOptions = {
     const defaultOptions = {
       waitForEncoding: false,
       waitForEncoding: false,
       waitForMetadata: false,
       waitForMetadata: false,
+      alwaysRunAssembly: false, // TODO name
       signature: null,
       signature: null,
       params: null,
       params: null,
       fields: {},
       fields: {},
@@ -109,7 +110,7 @@ module.exports = class Transloadit extends Plugin {
     return Object.keys(dedupeMap).map((id) => dedupeMap[id])
     return Object.keys(dedupeMap).map((id) => dedupeMap[id])
   }
   }
 
 
-  createAssembly (fileIDs, options) {
+  createAssembly (fileIDs, uploadID, options) {
     this.core.log('Transloadit: create assembly')
     this.core.log('Transloadit: create assembly')
 
 
     return this.client.createAssembly({
     return this.client.createAssembly({
@@ -118,10 +119,15 @@ module.exports = class Transloadit extends Plugin {
       expectedFiles: fileIDs.length,
       expectedFiles: fileIDs.length,
       signature: options.signature
       signature: options.signature
     }).then((assembly) => {
     }).then((assembly) => {
+      // Store the list of assemblies related to this upload.
+      const uploadsAssemblies = Object.assign({}, this.state.uploadsAssemblies)
+      uploadsAssemblies[uploadID] = (uploadsAssemblies[uploadID] || []).concat([ assembly.assembly_id ])
+
       this.updateState({
       this.updateState({
         assemblies: Object.assign(this.state.assemblies, {
         assemblies: Object.assign(this.state.assemblies, {
           [assembly.assembly_id]: assembly
           [assembly.assembly_id]: assembly
-        })
+        }),
+        uploadsAssemblies
       })
       })
 
 
       function attachAssemblyMetadata (file, assembly) {
       function attachAssemblyMetadata (file, assembly) {
@@ -255,7 +261,7 @@ module.exports = class Transloadit extends Plugin {
     })
     })
   }
   }
 
 
-  prepareUpload (fileIDs) {
+  prepareUpload (fileIDs, uploadID) {
     fileIDs.forEach((fileID) => {
     fileIDs.forEach((fileID) => {
       this.core.emit('core:preprocess-progress', fileID, {
       this.core.emit('core:preprocess-progress', fileID, {
         mode: 'indeterminate',
         mode: 'indeterminate',
@@ -264,33 +270,51 @@ module.exports = class Transloadit extends Plugin {
     })
     })
 
 
     const createAssembly = ({ fileIDs, options }) => {
     const createAssembly = ({ fileIDs, options }) => {
-      return this.createAssembly(fileIDs, options).then(() => {
+      return this.createAssembly(fileIDs, uploadID, options).then(() => {
         fileIDs.forEach((fileID) => {
         fileIDs.forEach((fileID) => {
           this.core.emit('core:preprocess-complete', fileID)
           this.core.emit('core:preprocess-complete', fileID)
         })
         })
       })
       })
     }
     }
 
 
-    return this.getAssemblyOptions(fileIDs)
-      .then((allOptions) => this.dedupeAssemblyOptions(allOptions))
-      .then((assemblies) => Promise.all(
-        assemblies.map(createAssembly)
-      ))
+    let optionsPromise
+    if (fileIDs.length > 0) {
+      optionsPromise = this.getAssemblyOptions(fileIDs)
+        .then((allOptions) => this.dedupeAssemblyOptions(allOptions))
+    } else if (this.opts.alwaysRunAssembly) {
+      optionsPromise = Promise.resolve().then(() => {
+        const options = this.opts.getAssemblyOptions(null, this.opts)
+        this.validateParams(options.params)
+        return [
+          { fileIDs, options }
+        ]
+      })
+    } else {
+      // If there are no files and we do not `alwaysRunAssembly`,
+      // don't do anything.
+      return Promise.resolve()
+    }
+
+    return optionsPromise.then((assemblies) => Promise.all(
+      assemblies.map(createAssembly)
+    ))
   }
   }
 
 
-  afterUpload (fileIDs) {
-    // A file ID that is part of this assembly...
-    const fileID = fileIDs[0]
+  afterUpload (fileIDs, uploadID) {
+    const assemblyIDs = this.state.uploadsAssemblies[uploadID]
 
 
     // If we don't have to wait for encoding metadata or results, we can close
     // If we don't have to wait for encoding metadata or results, we can close
     // the socket immediately and finish the upload.
     // the socket immediately and finish the upload.
     if (!this.shouldWait()) {
     if (!this.shouldWait()) {
-      const file = this.core.getFile(fileID)
-      const socket = this.sockets[file.transloadit.assembly]
-      socket.close()
+      assemblyIDs.forEach((assemblyID) => {
+        const socket = this.sockets[assemblyID]
+        socket.close()
+      })
       return Promise.resolve()
       return Promise.resolve()
     }
     }
 
 
+    let finishedAssemblies = 0
+
     return new Promise((resolve, reject) => {
     return new Promise((resolve, reject) => {
       fileIDs.forEach((fileID) => {
       fileIDs.forEach((fileID) => {
         this.core.emit('core:postprocess-progress', fileID, {
         this.core.emit('core:postprocess-progress', fileID, {
@@ -300,45 +324,62 @@ module.exports = class Transloadit extends Plugin {
       })
       })
 
 
       const onAssemblyFinished = (assembly) => {
       const onAssemblyFinished = (assembly) => {
-        const file = this.core.getFile(fileID)
         // An assembly for a different upload just finished. We can ignore it.
         // An assembly for a different upload just finished. We can ignore it.
-        if (assembly.assembly_id !== file.transloadit.assembly) {
+        if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
           return
           return
         }
         }
-        // Remove this handler once we find the assembly we needed.
-        this.core.emitter.off('transloadit:complete', onAssemblyFinished)
 
 
         // TODO set the `file.uploadURL` to a result?
         // TODO set the `file.uploadURL` to a result?
         // We will probably need an option here so the plugin user can tell us
         // We will probably need an option here so the plugin user can tell us
         // which result to pick…?
         // which result to pick…?
 
 
-        fileIDs.forEach((fileID) => {
-          this.core.emit('core:postprocess-complete', fileID)
+        const files = this.getAssemblyFiles(assembly.assembly_id)
+        files.forEach((file) => {
+          this.core.emit('core:postprocess-complete', file.id)
         })
         })
 
 
-        resolve()
+        finishedAssemblies += 1
+        if (finishedAssemblies === assemblyIDs.length) {
+          // We're done, these listeners can be removed
+          removeListeners()
+          resolve()
+        }
       }
       }
 
 
       const onAssemblyError = (assembly, error) => {
       const onAssemblyError = (assembly, error) => {
-        const file = this.core.getFile(fileID)
-        // An assembly for a different upload just errored. We can ignore it.
-        if (assembly.assembly_id !== file.transloadit.assembly) {
+        // An assembly for a different upload just finished. We can ignore it.
+        if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
           return
           return
         }
         }
-        // Remove this handler once we find the assembly we needed.
-        this.core.emitter.off('transloadit:assembly-error', onAssemblyError)
 
 
         // Clear postprocessing state for all our files.
         // Clear postprocessing state for all our files.
-        fileIDs.forEach((fileID) => {
-          this.core.emit('core:postprocess-complete', fileID)
+        const files = this.getAssemblyFiles(assembly.assembly_id)
+        files.forEach((file) => {
+          this.core.emit('core:postprocess-complete', file.id)
         })
         })
 
 
+        // Should we remove the listeners here or should we keep handling finished
+        // assemblies?
+        // Doing this for now so that it's not possible to receive more postprocessing
+        // events once the upload has failed.
+        removeListeners()
+
         // Reject the `afterUpload()` promise.
         // Reject the `afterUpload()` promise.
         reject(error)
         reject(error)
       }
       }
 
 
+      const removeListeners = () => {
+        this.core.off('transloadit:complete', onAssemblyFinished)
+        this.core.off('transloadit:assembly-error', onAssemblyError)
+      }
+
       this.core.on('transloadit:complete', onAssemblyFinished)
       this.core.on('transloadit:complete', onAssemblyFinished)
       this.core.on('transloadit:assembly-error', onAssemblyError)
       this.core.on('transloadit:assembly-error', onAssemblyError)
+    }).then(() => {
+      // Clean up uploadID → assemblyIDs, they're no longer going to be used anywhere.
+      const uploadsAssemblies = Object.assign({}, this.state.uploadsAssemblies)
+      delete uploadsAssemblies[uploadID]
+      this.updateState({ uploadsAssemblies })
     })
     })
   }
   }
 
 
@@ -347,8 +388,13 @@ module.exports = class Transloadit extends Plugin {
     this.core.addPostProcessor(this.afterUpload)
     this.core.addPostProcessor(this.afterUpload)
 
 
     this.updateState({
     this.updateState({
+      // Contains assembly status objects, indexed by their ID.
       assemblies: {},
       assemblies: {},
+      // Contains arrays of assembly IDs, indexed by the upload ID that they belong to.
+      uploadsAssemblies: {},
+      // Contains file data from Transloadit, indexed by their Transloadit-assigned ID.
       files: {},
       files: {},
+      // Contains result data from Transloadit.
       results: []
       results: []
     })
     })
   }
   }
@@ -362,6 +408,15 @@ module.exports = class Transloadit extends Plugin {
     return this.state.assemblies[id]
     return this.state.assemblies[id]
   }
   }
 
 
+  getAssemblyFiles (assemblyID) {
+    const fileIDs = Object.keys(this.core.state.files)
+    return fileIDs.map((fileID) => {
+      return this.core.getFile(fileID)
+    }).filter((file) => {
+      return file && file.transloadit && file.transloadit.assembly === assemblyID
+    })
+  }
+
   get state () {
   get state () {
     return this.core.state.transloadit || {}
     return this.core.state.transloadit || {}
   }
   }

+ 16 - 0
test/unit/Transloadit.spec.js

@@ -149,3 +149,19 @@ test('Transloadit: Should merge files with same parameters into one assembly', (
     })
     })
   }, t.fail)
   }, t.fail)
 })
 })
+
+test('Does not create an assembly if no files are being uploaded', (t) => {
+  t.plan(0)
+
+  const uppy = new Core()
+  uppy.use(Transloadit, {
+    getAssemblyOptions () {
+      t.fail('should not create assembly')
+    }
+  })
+  uppy.run()
+
+  uppy.upload().then(() => {
+    t.end()
+  }).catch(t.fail)
+})