diff --git a/packages/@uppy/transloadit/src/index.js b/packages/@uppy/transloadit/src/index.js index 5d7d05dc7..4c734b2db 100644 --- a/packages/@uppy/transloadit/src/index.js +++ b/packages/@uppy/transloadit/src/index.js @@ -1,4 +1,5 @@ const Translator = require('@uppy/utils/lib/Translator') +const hasProperty = require('@uppy/utils/lib/hasProperty') const { Plugin } = require('@uppy/core') const Tus = require('@uppy/tus') const Assembly = require('./Assembly') @@ -84,6 +85,10 @@ module.exports = class Transloadit extends Plugin { }) // Contains Assembly instances for in-progress Assemblies. this.activeAssemblies = {} + // Contains a mapping of uploadID to AssemblyWatcher + this.assemblyWatchers = {} + // Contains a file IDs that have completed postprocessing before the upload they belong to has entered the postprocess stage. + this.completedFiles = Object.create(null) } setOptions (newOpts) { @@ -208,20 +213,21 @@ module.exports = class Transloadit extends Plugin { }).then((newAssembly) => { const assembly = new Assembly(newAssembly) const status = assembly.status + const assemblyID = status.assembly_id const { assemblies, uploadsAssemblies } = this.getPluginState() this.setPluginState({ // Store the Assembly status. assemblies: { ...assemblies, - [status.assembly_id]: status + [assemblyID]: status }, // Store the list of Assemblies related to this upload. uploadsAssemblies: { ...uploadsAssemblies, [uploadID]: [ ...uploadsAssemblies[uploadID], - status.assembly_id + assemblyID ] } }) @@ -240,9 +246,7 @@ module.exports = class Transloadit extends Plugin { this.uppy.emit('transloadit:assembly-created', status, fileIDs) - this._connectAssembly(assembly) - - this.uppy.log(`[Transloadit] Created Assembly ${status.assembly_id}`) + this.uppy.log(`[Transloadit] Created Assembly ${assemblyID}`) return assembly }).catch((err) => { err.message = `${this.i18n('creatingAssemblyFailed')}: ${err.message}` @@ -251,6 +255,32 @@ module.exports = class Transloadit extends Plugin { }) } + _createAssemblyWatcher (assemblyID, fileIDs, uploadID) { + // AssemblyWatcher tracks completion states of all Assemblies in this upload. + const watcher = new AssemblyWatcher(this.uppy, assemblyID) + + watcher.on('assembly-complete', (id) => { + const files = this.getAssemblyFiles(id) + files.forEach((file) => { + this.completedFiles[file.id] = true + this.uppy.emit('postprocess-complete', file) + }) + }) + + watcher.on('assembly-error', (id, error) => { + // Clear postprocessing state for all our files. + const files = this.getAssemblyFiles(id) + files.forEach((file) => { + // TODO Maybe make a postprocess-error event here? + this.uppy.emit('upload-error', file, error) + + this.uppy.emit('postprocess-complete', file) + }) + }) + + this.assemblyWatchers[uploadID] = watcher + } + _shouldWaitAfterUpload () { return this.opts.waitForEncoding || this.opts.waitForMetadata } @@ -360,11 +390,12 @@ module.exports = class Transloadit extends Plugin { _onAssemblyFinished (status) { const url = status.assembly_ssl_url this.client.getAssemblyStatus(url).then((finalStatus) => { + const assemblyId = finalStatus.assemblyId const state = this.getPluginState() this.setPluginState({ assemblies: { ...state.assemblies, - [finalStatus.assembly_id]: finalStatus + [assemblyId]: finalStatus } }) this.uppy.emit('transloadit:complete', finalStatus) @@ -462,10 +493,23 @@ module.exports = class Transloadit extends Plugin { }) } - // Set up the Assembly instances for existing Assemblies. + // Set up the Assembly instances and AssemblyWatchers for existing Assemblies. const restoreAssemblies = () => { - const { assemblies } = this.getPluginState() - Object.keys(assemblies).forEach((id) => { + const { assemblies, uploadsAssemblies } = this.getPluginState() + + // Set up the assembly watchers again for all the ongoing uploads. + Object.keys(uploadsAssemblies).forEach((uploadID) => { + const assemblyIDs = uploadsAssemblies[uploadID] + const fileIDsInUpload = assemblyIDs.reduce((acc, assemblyID) => { + const fileIDsInAssembly = this.getAssemblyFiles(assemblyID).map((file) => file.id) + acc.push(...fileIDsInAssembly) + return acc + }, []) + this._createAssemblyWatcher(assemblyIDs, fileIDsInUpload, uploadID) + }) + + const allAssemblyIDs = Object.keys(assemblies) + allAssemblyIDs.forEach((id) => { const assembly = new Assembly(assemblies[id]) this._connectAssembly(assembly) }) @@ -569,7 +613,9 @@ module.exports = class Transloadit extends Plugin { }) const createAssembly = ({ fileIDs, options }) => { + let createdAssembly return this._createAssembly(fileIDs, uploadID, options).then((assembly) => { + createdAssembly = assembly if (this.opts.importFromUploadURLs) { return this._reserveFiles(assembly, fileIDs) } @@ -578,6 +624,7 @@ module.exports = class Transloadit extends Plugin { const file = this.uppy.getFile(fileID) this.uppy.emit('preprocess-complete', file) }) + return createdAssembly }).catch((err) => { fileIDs.forEach((fileID) => { const file = this.uppy.getFile(fileID) @@ -604,7 +651,11 @@ module.exports = class Transloadit extends Plugin { return assemblyOptions.build().then( (assemblies) => Promise.all( assemblies.map(createAssembly) - ), + ).then((createdAssemblies) => { + const assemblyIDs = createdAssemblies.map(assembly => assembly.status.assembly_id) + this._createAssemblyWatcher(assemblyIDs, fileIDs, uploadID) + createdAssemblies.map(assembly => this._connectAssembly(assembly)) + }), // If something went wrong before any Assemblies could be created, // clear all processing state. (err) => { @@ -619,8 +670,9 @@ module.exports = class Transloadit extends Plugin { } _afterUpload (fileIDs, uploadID) { + const files = fileIDs.map(fileID => this.uppy.getFile(fileID)) // Only use files without errors - fileIDs = fileIDs.filter((file) => !file.error) + fileIDs = files.filter((file) => !file.error).map(file => file.id) const state = this.getPluginState() @@ -653,35 +705,15 @@ module.exports = class Transloadit extends Plugin { return Promise.resolve() } - // AssemblyWatcher tracks completion states of all Assemblies in this upload. - const watcher = new AssemblyWatcher(this.uppy, assemblyIDs) - - fileIDs.forEach((fileID) => { - const file = this.uppy.getFile(fileID) + const incompleteFiles = files.filter(file => !hasProperty(this.completedFiles, file.id)) + incompleteFiles.forEach((file) => { this.uppy.emit('postprocess-progress', file, { mode: 'indeterminate', message: this.i18n('encoding') }) }) - watcher.on('assembly-complete', (id) => { - const files = this.getAssemblyFiles(id) - files.forEach((file) => { - this.uppy.emit('postprocess-complete', file) - }) - }) - - watcher.on('assembly-error', (id, error) => { - // Clear postprocessing state for all our files. - const files = this.getAssemblyFiles(id) - files.forEach((file) => { - // TODO Maybe make a postprocess-error event here? - this.uppy.emit('upload-error', file, error) - - this.uppy.emit('postprocess-complete', file) - }) - }) - + const watcher = this.assemblyWatchers[uploadID] return watcher.promise.then(() => { const assemblies = assemblyIDs.map((id) => this.getAssembly(id))