|
@@ -3,6 +3,14 @@ const Plugin = require('../../core/Plugin')
|
|
const Client = require('./Client')
|
|
const Client = require('./Client')
|
|
const StatusSocket = require('./Socket')
|
|
const StatusSocket = require('./Socket')
|
|
|
|
|
|
|
|
+function defaultGetAssemblyOptions (file, options) {
|
|
|
|
+ return {
|
|
|
|
+ params: options.params,
|
|
|
|
+ signature: options.signature,
|
|
|
|
+ fields: options.fields
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Upload files to Transloadit using Tus.
|
|
* Upload files to Transloadit using Tus.
|
|
*/
|
|
*/
|
|
@@ -29,13 +37,7 @@ module.exports = class Transloadit extends Plugin {
|
|
signature: null,
|
|
signature: null,
|
|
params: null,
|
|
params: null,
|
|
fields: {},
|
|
fields: {},
|
|
- getAssemblyOptions (file, options) {
|
|
|
|
- return {
|
|
|
|
- params: options.params,
|
|
|
|
- signature: options.signature,
|
|
|
|
- fields: options.fields
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ getAssemblyOptions: defaultGetAssemblyOptions,
|
|
locale: defaultLocale
|
|
locale: defaultLocale
|
|
}
|
|
}
|
|
|
|
|
|
@@ -50,6 +52,8 @@ module.exports = class Transloadit extends Plugin {
|
|
this.prepareUpload = this.prepareUpload.bind(this)
|
|
this.prepareUpload = this.prepareUpload.bind(this)
|
|
this.afterUpload = this.afterUpload.bind(this)
|
|
this.afterUpload = this.afterUpload.bind(this)
|
|
this.onFileUploadURLAvailable = this.onFileUploadURLAvailable.bind(this)
|
|
this.onFileUploadURLAvailable = this.onFileUploadURLAvailable.bind(this)
|
|
|
|
+ this.onRestored = this.onRestored.bind(this)
|
|
|
|
+ this.getPersistentData = this.getPersistentData.bind(this)
|
|
|
|
|
|
if (this.opts.params) {
|
|
if (this.opts.params) {
|
|
this.validateParams(this.opts.params)
|
|
this.validateParams(this.opts.params)
|
|
@@ -120,7 +124,7 @@ module.exports = class Transloadit extends Plugin {
|
|
createAssembly (fileIDs, uploadID, options) {
|
|
createAssembly (fileIDs, uploadID, options) {
|
|
const pluginOptions = this.opts
|
|
const pluginOptions = this.opts
|
|
|
|
|
|
- this.uppy.log('Transloadit: create assembly')
|
|
|
|
|
|
+ this.uppy.log('[Transloadit] create assembly')
|
|
|
|
|
|
return this.client.createAssembly({
|
|
return this.client.createAssembly({
|
|
params: options.params,
|
|
params: options.params,
|
|
@@ -187,7 +191,7 @@ module.exports = class Transloadit extends Plugin {
|
|
return this.connectSocket(assembly)
|
|
return this.connectSocket(assembly)
|
|
.then(() => assembly)
|
|
.then(() => assembly)
|
|
}).then((assembly) => {
|
|
}).then((assembly) => {
|
|
- this.uppy.log('Transloadit: Created assembly')
|
|
|
|
|
|
+ this.uppy.log('[Transloadit] Created assembly')
|
|
return assembly
|
|
return assembly
|
|
}).catch((err) => {
|
|
}).catch((err) => {
|
|
this.uppy.info(this.i18n('creatingAssemblyFailed'), 'error', 0)
|
|
this.uppy.info(this.i18n('creatingAssemblyFailed'), 'error', 0)
|
|
@@ -237,9 +241,20 @@ module.exports = class Transloadit extends Plugin {
|
|
if (!files.hasOwnProperty(id)) {
|
|
if (!files.hasOwnProperty(id)) {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
|
|
+ // Completed file upload.
|
|
if (files[id].uploadURL === uploadedFile.tus_upload_url) {
|
|
if (files[id].uploadURL === uploadedFile.tus_upload_url) {
|
|
return files[id]
|
|
return files[id]
|
|
}
|
|
}
|
|
|
|
+ // In-progress file upload.
|
|
|
|
+ if (files[id].tus && files[id].tus.uploadUrl === uploadedFile.tus_upload_url) {
|
|
|
|
+ return files[id]
|
|
|
|
+ }
|
|
|
|
+ if (!uploadedFile.is_tus_file) {
|
|
|
|
+ // Fingers-crossed check for non-tus uploads, eg imported from S3.
|
|
|
|
+ if (files[id].name === uploadedFile.name && files[id].size === uploadedFile.size) {
|
|
|
|
+ return files[id]
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -249,6 +264,7 @@ module.exports = class Transloadit extends Plugin {
|
|
this.setPluginState({
|
|
this.setPluginState({
|
|
files: Object.assign({}, state.files, {
|
|
files: Object.assign({}, state.files, {
|
|
[uploadedFile.id]: {
|
|
[uploadedFile.id]: {
|
|
|
|
+ assembly: assemblyId,
|
|
id: file.id,
|
|
id: file.id,
|
|
uploadedFile
|
|
uploadedFile
|
|
}
|
|
}
|
|
@@ -263,8 +279,15 @@ module.exports = class Transloadit extends Plugin {
|
|
// The `file` may not exist if an import robot was used instead of a file upload.
|
|
// The `file` may not exist if an import robot was used instead of a file upload.
|
|
result.localId = file ? file.id : null
|
|
result.localId = file ? file.id : null
|
|
|
|
|
|
|
|
+ const entry = {
|
|
|
|
+ result,
|
|
|
|
+ stepName,
|
|
|
|
+ id: result.id,
|
|
|
|
+ assembly: assemblyId
|
|
|
|
+ }
|
|
|
|
+
|
|
this.setPluginState({
|
|
this.setPluginState({
|
|
- results: state.results.concat(result)
|
|
|
|
|
|
+ results: [...state.results, entry]
|
|
})
|
|
})
|
|
this.uppy.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
|
|
this.uppy.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
|
|
}
|
|
}
|
|
@@ -281,6 +304,200 @@ module.exports = class Transloadit extends Plugin {
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ getPersistentData (setData) {
|
|
|
|
+ const state = this.getPluginState()
|
|
|
|
+ const assemblies = state.assemblies
|
|
|
|
+ const uploadsAssemblies = state.uploadsAssemblies
|
|
|
|
+ const uploads = Object.keys(state.files)
|
|
|
|
+ const results = state.results.map((result) => result.id)
|
|
|
|
+
|
|
|
|
+ setData({
|
|
|
|
+ [this.id]: {
|
|
|
|
+ assemblies,
|
|
|
|
+ uploadsAssemblies,
|
|
|
|
+ uploads,
|
|
|
|
+ results
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Emit the necessary events that must have occured to get from the `prevState`,
|
|
|
|
+ * to the current state.
|
|
|
|
+ * For completed uploads, `transloadit:upload` is emitted.
|
|
|
|
+ * For new results, `transloadit:result` is emitted.
|
|
|
|
+ * For completed or errored assemblies, `transloadit:complete` or `transloadit:assembly-error` is emitted.
|
|
|
|
+ */
|
|
|
|
+ emitEventsDiff (prevState) {
|
|
|
|
+ const opts = this.opts
|
|
|
|
+ const state = this.getPluginState()
|
|
|
|
+
|
|
|
|
+ const emitMissedEvents = () => {
|
|
|
|
+ // Emit events for completed uploads and completed results
|
|
|
|
+ // that we've missed while we were away.
|
|
|
|
+ const newUploads = Object.keys(state.files).filter((fileID) => {
|
|
|
|
+ return !prevState.files.hasOwnProperty(fileID)
|
|
|
|
+ }).map((fileID) => state.files[fileID])
|
|
|
|
+ const newResults = state.results.filter((result) => {
|
|
|
|
+ return !prevState.results.some((prev) => prev.id === result.id)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ this.uppy.log('[Transloadit] New fully uploaded files since restore:')
|
|
|
|
+ this.uppy.log(newUploads)
|
|
|
|
+ newUploads.forEach(({ assembly, uploadedFile }) => {
|
|
|
|
+ this.uppy.log(`[Transloadit] emitting transloadit:upload ${uploadedFile.id}`)
|
|
|
|
+ this.uppy.emit('transloadit:upload', uploadedFile, this.getAssembly(assembly))
|
|
|
|
+ })
|
|
|
|
+ this.uppy.log('[Transloadit] New results since restore:')
|
|
|
|
+ this.uppy.log(newResults)
|
|
|
|
+ newResults.forEach(({ assembly, stepName, result, id }) => {
|
|
|
|
+ this.uppy.log(`[Transloadit] emitting transloadit:result ${stepName}, ${id}`)
|
|
|
|
+ this.uppy.emit('transloadit:result', stepName, result, this.getAssembly(assembly))
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const newAssemblies = state.assemblies
|
|
|
|
+ const previousAssemblies = prevState.assemblies
|
|
|
|
+ this.uppy.log('[Transloadit] Current assembly status after restore')
|
|
|
|
+ this.uppy.log(newAssemblies)
|
|
|
|
+ this.uppy.log('[Transloadit] Assembly status before restore')
|
|
|
|
+ this.uppy.log(previousAssemblies)
|
|
|
|
+ Object.keys(newAssemblies).forEach((assemblyId) => {
|
|
|
|
+ const oldAssembly = previousAssemblies[assemblyId]
|
|
|
|
+ diffAssemblyStatus(oldAssembly, newAssemblies[assemblyId])
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Emit events for assemblies that have completed or errored while we were away.
|
|
|
|
+ const diffAssemblyStatus = (prev, next) => {
|
|
|
|
+ this.uppy.log('[Transloadit] Diff assemblies')
|
|
|
|
+ this.uppy.log(prev)
|
|
|
|
+ this.uppy.log(next)
|
|
|
|
+
|
|
|
|
+ if (opts.waitForEncoding && next.ok === 'ASSEMBLY_COMPLETED' && prev.ok !== 'ASSEMBLY_COMPLETED') {
|
|
|
|
+ this.uppy.log(`[Transloadit] Emitting transloadit:complete for ${next.assembly_id}`)
|
|
|
|
+ this.uppy.log(next)
|
|
|
|
+ this.uppy.emit('transloadit:complete', next)
|
|
|
|
+ } else if (opts.waitForMetadata && next.upload_meta_data_extracted && !prev.upload_meta_data_extracted) {
|
|
|
|
+ this.uppy.log(`[Transloadit] Emitting transloadit:complete after metadata extraction for ${next.assembly_id}`)
|
|
|
|
+ this.uppy.log(next)
|
|
|
|
+ this.uppy.emit('transloadit:complete', next)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (next.error && !prev.error) {
|
|
|
|
+ this.uppy.log(`[Transloadit] !!! Emitting transloadit:assembly-error for ${next.assembly_id}`)
|
|
|
|
+ this.uppy.log(next)
|
|
|
|
+ this.uppy.emit('transloadit:assembly-error', next, new Error(next.message))
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ emitMissedEvents()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ onRestored (pluginData) {
|
|
|
|
+ const savedState = pluginData && pluginData[this.id] ? pluginData[this.id] : {}
|
|
|
|
+ const knownUploads = savedState.files || []
|
|
|
|
+ const knownResults = savedState.results || []
|
|
|
|
+ const previousAssemblies = savedState.assemblies || {}
|
|
|
|
+ const uploadsAssemblies = savedState.uploadsAssemblies || {}
|
|
|
|
+
|
|
|
|
+ if (Object.keys(uploadsAssemblies).length === 0) {
|
|
|
|
+ // Nothing to restore.
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Fetch up-to-date assembly statuses.
|
|
|
|
+ const loadAssemblies = () => {
|
|
|
|
+ const assemblyIDs = []
|
|
|
|
+ Object.keys(uploadsAssemblies).forEach((uploadID) => {
|
|
|
|
+ assemblyIDs.push(...uploadsAssemblies[uploadID])
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ return Promise.all(
|
|
|
|
+ assemblyIDs.map((assemblyID) => {
|
|
|
|
+ const url = `https://api2.transloadit.com/assemblies/${assemblyID}`
|
|
|
|
+ return this.client.getAssemblyStatus(url)
|
|
|
|
+ })
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const reconnectSockets = (assemblies) => {
|
|
|
|
+ return Promise.all(assemblies.map((assembly) => {
|
|
|
|
+ // No need to connect to the socket if the assembly has completed by now.
|
|
|
|
+ if (assembly.ok === 'ASSEMBLY_COMPLETE') {
|
|
|
|
+ return null
|
|
|
|
+ }
|
|
|
|
+ return this.connectSocket(assembly)
|
|
|
|
+ }))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Convert loaded assembly statuses to a Transloadit plugin state object.
|
|
|
|
+ const restoreState = (assemblies) => {
|
|
|
|
+ const assembliesById = {}
|
|
|
|
+ const files = {}
|
|
|
|
+ const results = []
|
|
|
|
+ assemblies.forEach((assembly) => {
|
|
|
|
+ assembliesById[assembly.assembly_id] = assembly
|
|
|
|
+
|
|
|
|
+ assembly.uploads.forEach((uploadedFile) => {
|
|
|
|
+ const file = this.findFile(uploadedFile)
|
|
|
|
+ files[uploadedFile.id] = {
|
|
|
|
+ id: file.id,
|
|
|
|
+ assembly: assembly.assembly_id,
|
|
|
|
+ uploadedFile
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const state = this.getPluginState()
|
|
|
|
+ Object.keys(assembly.results).forEach((stepName) => {
|
|
|
|
+ assembly.results[stepName].forEach((result) => {
|
|
|
|
+ const file = state.files[result.original_id]
|
|
|
|
+ result.localId = file ? file.id : null
|
|
|
|
+ results.push({
|
|
|
|
+ id: result.id,
|
|
|
|
+ result,
|
|
|
|
+ stepName,
|
|
|
|
+ assembly: assembly.assembly_id
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ this.setPluginState({
|
|
|
|
+ assemblies: assembliesById,
|
|
|
|
+ files: files,
|
|
|
|
+ results: results,
|
|
|
|
+ uploadsAssemblies: uploadsAssemblies
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Restore all assembly state.
|
|
|
|
+ this.restored = Promise.resolve()
|
|
|
|
+ .then(loadAssemblies)
|
|
|
|
+ .then((assemblies) => {
|
|
|
|
+ restoreState(assemblies)
|
|
|
|
+ return reconnectSockets(assemblies)
|
|
|
|
+ })
|
|
|
|
+ .then(() => {
|
|
|
|
+ // Return a callback that will be called by `afterUpload`
|
|
|
|
+ // once it has attached event listeners etc.
|
|
|
|
+ const newState = this.getPluginState()
|
|
|
|
+ const previousFiles = {}
|
|
|
|
+ knownUploads.forEach((id) => {
|
|
|
|
+ previousFiles[id] = newState.files[id]
|
|
|
|
+ })
|
|
|
|
+ return () => this.emitEventsDiff({
|
|
|
|
+ assemblies: previousAssemblies,
|
|
|
|
+ files: previousFiles,
|
|
|
|
+ results: newState.results.filter(({ id }) => knownResults.indexOf(id) !== -1),
|
|
|
|
+ uploadsAssemblies
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ this.restored.then(() => {
|
|
|
|
+ this.restored = null
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
connectSocket (assembly) {
|
|
connectSocket (assembly) {
|
|
const socket = new StatusSocket(
|
|
const socket = new StatusSocket(
|
|
assembly.websocket_url,
|
|
assembly.websocket_url,
|
|
@@ -304,7 +521,6 @@ module.exports = class Transloadit extends Plugin {
|
|
} else if (this.opts.waitForMetadata) {
|
|
} else if (this.opts.waitForMetadata) {
|
|
socket.on('metadata', () => {
|
|
socket.on('metadata', () => {
|
|
this.onAssemblyFinished(assembly.assembly_ssl_url)
|
|
this.onAssemblyFinished(assembly.assembly_ssl_url)
|
|
- this.uppy.emit('transloadit:complete', assembly)
|
|
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -312,7 +528,7 @@ module.exports = class Transloadit extends Plugin {
|
|
socket.on('connect', resolve)
|
|
socket.on('connect', resolve)
|
|
socket.on('error', reject)
|
|
socket.on('error', reject)
|
|
}).then(() => {
|
|
}).then(() => {
|
|
- this.uppy.log('Transloadit: Socket is ready')
|
|
|
|
|
|
+ this.uppy.log('[Transloadit] Socket is ready')
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -374,6 +590,16 @@ module.exports = class Transloadit extends Plugin {
|
|
fileIDs = fileIDs.filter((file) => !file.error)
|
|
fileIDs = fileIDs.filter((file) => !file.error)
|
|
|
|
|
|
const state = this.getPluginState()
|
|
const state = this.getPluginState()
|
|
|
|
+
|
|
|
|
+ // If we're still restoring state, wait for that to be done.
|
|
|
|
+ if (this.restored) {
|
|
|
|
+ return this.restored.then((emitMissedEvents) => {
|
|
|
|
+ const promise = this.afterUpload(fileIDs, uploadID)
|
|
|
|
+ emitMissedEvents()
|
|
|
|
+ return promise
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
const assemblyIDs = state.uploadsAssemblies[uploadID]
|
|
const assemblyIDs = 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
|
|
@@ -405,8 +631,10 @@ module.exports = class Transloadit extends Plugin {
|
|
const onAssemblyFinished = (assembly) => {
|
|
const onAssemblyFinished = (assembly) => {
|
|
// 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 (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
|
|
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
|
|
|
|
+ this.uppy.log(`[Transloadit] afterUpload(): Ignoring finished assembly ${assembly.assembly_id}`)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+ this.uppy.log(`[Transloadit] afterUpload(): Got assembly finish ${assembly.assembly_id}`)
|
|
|
|
|
|
// 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
|
|
@@ -421,10 +649,13 @@ module.exports = class Transloadit extends Plugin {
|
|
}
|
|
}
|
|
|
|
|
|
const onAssemblyError = (assembly, error) => {
|
|
const onAssemblyError = (assembly, error) => {
|
|
- // An assembly for a different upload just finished. We can ignore it.
|
|
|
|
|
|
+ // An assembly for a different upload just errored. We can ignore it.
|
|
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
|
|
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
|
|
|
|
+ this.uppy.log(`[Transloadit] afterUpload(): Ignoring errored assembly ${assembly.assembly_id}`)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+ this.uppy.log(`[Transloadit] afterUpload(): Got assembly error ${assembly.assembly_id}`)
|
|
|
|
+ this.uppy.log(error)
|
|
|
|
|
|
// Clear postprocessing state for all our files.
|
|
// Clear postprocessing state for all our files.
|
|
const files = this.getAssemblyFiles(assembly.assembly_id)
|
|
const files = this.getAssemblyFiles(assembly.assembly_id)
|
|
@@ -486,6 +717,9 @@ module.exports = class Transloadit extends Plugin {
|
|
this.uppy.on('upload-success', this.onFileUploadURLAvailable)
|
|
this.uppy.on('upload-success', this.onFileUploadURLAvailable)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ this.uppy.on('restore:get-data', this.getPersistentData)
|
|
|
|
+ this.uppy.on('restored', this.onRestored)
|
|
|
|
+
|
|
this.setPluginState({
|
|
this.setPluginState({
|
|
// Contains assembly status objects, indexed by their ID.
|
|
// Contains assembly status objects, indexed by their ID.
|
|
assemblies: {},
|
|
assemblies: {},
|