files.com 1.0.412 → 1.0.414

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. package/_VERSION +1 -1
  2. package/lib/Api.js +30 -18
  3. package/lib/Files.js +1 -1
  4. package/lib/isomorphic/File.node.js +21 -13
  5. package/lib/models/ActionNotificationExport.js +8 -5
  6. package/lib/models/ApiKey.js +8 -5
  7. package/lib/models/As2Partner.js +8 -5
  8. package/lib/models/As2Station.js +8 -5
  9. package/lib/models/Automation.js +8 -5
  10. package/lib/models/AutomationRun.js +8 -5
  11. package/lib/models/Behavior.js +15 -9
  12. package/lib/models/Bundle.js +8 -5
  13. package/lib/models/BundleNotification.js +8 -5
  14. package/lib/models/Clickwrap.js +8 -5
  15. package/lib/models/ExternalEvent.js +8 -5
  16. package/lib/models/File.js +212 -131
  17. package/lib/models/FileComment.js +8 -5
  18. package/lib/models/FileMigration.js +8 -5
  19. package/lib/models/Folder.js +14 -9
  20. package/lib/models/FormFieldSet.js +8 -5
  21. package/lib/models/GpgKey.js +8 -5
  22. package/lib/models/Group.js +8 -5
  23. package/lib/models/History.js +22 -13
  24. package/lib/models/HistoryExport.js +8 -5
  25. package/lib/models/Invoice.js +8 -5
  26. package/lib/models/Lock.js +15 -9
  27. package/lib/models/Message.js +8 -5
  28. package/lib/models/MessageComment.js +8 -5
  29. package/lib/models/MessageCommentReaction.js +8 -5
  30. package/lib/models/MessageReaction.js +8 -5
  31. package/lib/models/Notification.js +8 -5
  32. package/lib/models/Payment.js +8 -5
  33. package/lib/models/Priority.js +8 -5
  34. package/lib/models/Project.js +8 -5
  35. package/lib/models/PublicKey.js +8 -5
  36. package/lib/models/RemoteServer.js +15 -9
  37. package/lib/models/Request.js +8 -5
  38. package/lib/models/SftpHostKey.js +8 -5
  39. package/lib/models/ShareGroup.js +8 -5
  40. package/lib/models/Snapshot.js +8 -5
  41. package/lib/models/SsoStrategy.js +8 -5
  42. package/lib/models/Style.js +8 -5
  43. package/lib/models/User.js +8 -5
  44. package/lib/models/UserRequest.js +8 -5
  45. package/package.json +1 -1
  46. package/src/Files.js +1 -1
  47. package/src/models/File.js +86 -44
  48. package/test/src/index.js +30 -3
@@ -97,79 +97,121 @@ class File {
97
97
  let length = 0
98
98
  const concurrentUploads = []
99
99
 
100
+ let chunkBuffer = null
101
+ let streamEnded = false
102
+
103
+ const handleStreamEnd = async () => {
104
+ if (chunkBuffer !== null || !streamEnded) {
105
+ return
106
+ }
107
+
108
+ try {
109
+ if (chunks.length > 0) {
110
+ const buffer = Buffer.concat(chunks)
111
+ const nextFileUploadPart = await File._continueUpload(destinationPath, ++part, firstFileUploadPart, options)
112
+
113
+ const upload_uri = determinePartUploadUri(nextFileUploadPart)
114
+
115
+ // instantiate an httpsAgent dynamically if needed
116
+ const agent = options.getAgentForUrl?.(upload_uri) || options?.agent
117
+
118
+ concurrentUploads.push(Api.sendFilePart(upload_uri, 'PUT', buffer, { agent }))
119
+ }
120
+
121
+ await Promise.all(concurrentUploads)
122
+
123
+ const response = await File._completeUpload(firstFileUploadPart, options)
124
+ const createdFile = new File(response.data, options)
125
+
126
+ resolve(createdFile)
127
+ } catch (error) {
128
+ reject(error)
129
+ }
130
+ }
131
+
100
132
  readableStream.on('error', error => { reject(error) })
101
133
 
134
+ // note that for a network stream, each chunk is typically less than partsize * 2, but
135
+ // if a stream has been created based on very large data, it's possible for a chunk to
136
+ // contain the entire file and we could get a single chunk with length >= partsize * 3
102
137
  readableStream.on('data', async chunk => {
103
138
  try {
104
- const nextLength = length + chunk.length
105
- const excessLength = nextLength - firstFileUploadPart.partsize
139
+ let excessLength = (length + chunk.length) - firstFileUploadPart.partsize
106
140
 
107
- const chunkBuffer = Buffer.from(chunk)
141
+ chunkBuffer = Buffer.from(chunk)
108
142
 
109
143
  if (excessLength > 0) {
110
144
  readableStream.pause()
111
145
 
112
- // the amount to append this last part with to make it exactly the full partsize
113
- const tailLength = chunkBuffer.length - excessLength
146
+ while (chunkBuffer) {
147
+ // the amount to append this last part with to make it exactly the full partsize
148
+ const lengthForEndOfCurrentPart = chunkBuffer.length - excessLength
114
149
 
115
- const lastChunkForPart = chunkBuffer.subarray(0, tailLength)
116
- const firstChunkForNextPart = chunkBuffer.subarray(tailLength)
150
+ const lastChunkForCurrentPart = chunkBuffer.subarray(0, lengthForEndOfCurrentPart)
151
+ const chunkBufferAfterCurrentPart = chunkBuffer.subarray(lengthForEndOfCurrentPart)
117
152
 
118
- chunks.push(lastChunkForPart)
153
+ chunks.push(lastChunkForCurrentPart)
119
154
 
120
- const buffer = Buffer.concat(chunks)
121
- const nextFileUploadPart = await File._continueUpload(destinationPath, ++part, firstFileUploadPart, options)
155
+ const buffer = Buffer.concat(chunks)
156
+ const nextFileUploadPart = await File._continueUpload(destinationPath, ++part, firstFileUploadPart, options)
122
157
 
123
- const upload_uri = determinePartUploadUri(nextFileUploadPart)
158
+ const upload_uri = determinePartUploadUri(nextFileUploadPart)
124
159
 
125
- // instantiate an httpsAgent dynamically if needed
126
- const agent = options.getAgentForUrl?.(upload_uri) || options?.agent
160
+ // instantiate an httpsAgent dynamically if needed
161
+ const agent = options.getAgentForUrl?.(upload_uri) || options?.agent
127
162
 
128
- const uploadPromise = Api.sendFilePart(upload_uri, 'PUT', buffer, { agent })
163
+ const uploadPromise = Api.sendFilePart(upload_uri, 'PUT', buffer, { agent })
129
164
 
130
- if (firstFileUploadPart.parallel_parts) {
131
- concurrentUploads.push(uploadPromise)
132
- } else {
133
- await uploadPromise
134
- }
165
+ if (firstFileUploadPart.parallel_parts) {
166
+ concurrentUploads.push(uploadPromise)
167
+ } else {
168
+ await uploadPromise
169
+ }
170
+
171
+ // determine if the remainder of the excess chunk data is too large to be a single part
172
+ const isNextChunkAtLeastOnePart = chunkBufferAfterCurrentPart.length >= firstFileUploadPart.partsize
173
+
174
+ // the excess data contains >= 1 full part, so we'll loop again to enqueue
175
+ // the next part for upload and continue processing any excess beyond that
176
+ if (isNextChunkAtLeastOnePart) {
177
+ chunks = []
178
+ length = 0
135
179
 
136
- chunks = [firstChunkForNextPart]
137
- length = firstChunkForNextPart.length
180
+ chunkBuffer = chunkBufferAfterCurrentPart
181
+ excessLength = chunkBuffer.length - firstFileUploadPart.partsize
182
+ // the excess data is less than a full part, so we'll enqueue it
183
+ } else if (chunkBufferAfterCurrentPart.length > 0) {
184
+ chunks = [chunkBufferAfterCurrentPart]
185
+ length = chunkBufferAfterCurrentPart.length
186
+
187
+ chunkBuffer = null
188
+ } else {
189
+ chunkBuffer = null
190
+ }
191
+ }
138
192
 
139
193
  readableStream.resume()
140
194
  } else {
141
195
  chunks.push(chunkBuffer)
142
196
  length += chunk.length
143
- }
144
- } catch (error) {
145
- reject(error)
146
- }
147
- })
148
-
149
- readableStream.on('end', async () => {
150
- try {
151
- if (chunks.length > 0) {
152
- const buffer = Buffer.concat(chunks)
153
- const nextFileUploadPart = await File._continueUpload(destinationPath, ++part, firstFileUploadPart, options)
154
-
155
- const upload_uri = determinePartUploadUri(nextFileUploadPart)
156
197
 
157
- // instantiate an httpsAgent dynamically if needed
158
- const agent = options.getAgentForUrl?.(upload_uri) || options?.agent
159
-
160
- concurrentUploads.push(Api.sendFilePart(upload_uri, 'PUT', buffer, { agent }))
198
+ chunkBuffer = null
161
199
  }
162
200
 
163
- await Promise.all(concurrentUploads)
164
-
165
- const response = await File._completeUpload(firstFileUploadPart, options)
166
- const createdFile = new File(response.data, options)
167
-
168
- resolve(createdFile)
201
+ if (streamEnded) {
202
+ handleStreamEnd()
203
+ }
169
204
  } catch (error) {
170
205
  reject(error)
171
206
  }
172
207
  })
208
+
209
+ // note that this event may occur while there is still data being processed above
210
+ readableStream.on('end', () => {
211
+ streamEnded = true
212
+
213
+ handleStreamEnd()
214
+ })
173
215
  })
174
216
 
175
217
  return file
package/test/src/index.js CHANGED
@@ -142,7 +142,7 @@ const testSuite = async () => {
142
142
  }
143
143
 
144
144
  /* to run this test, put a file (or symlink) at huge-file.ext * /
145
- const testUploadHugeFile = async () => {
145
+ const testUploadFileForHugeFile = async () => {
146
146
  const sourceFilePath = '../huge-file.ext'
147
147
 
148
148
  const displayName = `huge-file__${nonce}.ext`
@@ -161,7 +161,33 @@ const testSuite = async () => {
161
161
 
162
162
  await file.delete()
163
163
 
164
- Logger.info('***** testUploadHugeFile() succeeded! *****')
164
+ Logger.info('***** testUploadFileForHugeFile() succeeded! *****')
165
+ }
166
+
167
+ /* to run this test, put a file (or symlink) at huge-file.ext * /
168
+ const testUploadDataForHugeFile = async () => {
169
+ const sourceFilePath = '../huge-file.ext'
170
+
171
+ const displayName = `huge-file__${nonce}.ext`
172
+ const destinationPath = `${SDK_TEST_ROOT_FOLDER}/${displayName}`
173
+
174
+ const fs = require('fs/promises')
175
+ const data = await fs.readFile(sourceFilePath, { encoding: "utf8" })
176
+
177
+ const file = await File.uploadData(destinationPath, data)
178
+
179
+ invariant(!!file.path, 'Uploaded file response object should have a path')
180
+ invariant(file.display_name === displayName, 'Uploaded file response object should have the same display_name as the file we uploaded')
181
+
182
+ const foundFile = await File.find(destinationPath)
183
+
184
+ invariant(foundFile.path === destinationPath, 'Found file should have the same path as the file we uploaded')
185
+ invariant(foundFile.display_name === displayName, 'Found file should have the same display_name as the file we uploaded')
186
+ invariant(typeof foundFile.getDownloadUri() === 'undefined', 'Found file should not have a download uri yet')
187
+
188
+ await file.delete()
189
+
190
+ Logger.info('***** testUploadDataForHugeFile() succeeded! *****')
165
191
  }
166
192
  /**/
167
193
 
@@ -242,7 +268,8 @@ const testSuite = async () => {
242
268
  await testFolderListAutoPagination()
243
269
  await testUploadAndDownloadToFile()
244
270
  await testUploadAndDownloadToString()
245
- // await testUploadHugeFile() // to run this test, put a file (or symlink) at huge-file.ext
271
+ // await testUploadDataForHugeFile() // to run this test, put a file (or symlink) at huge-file.ext
272
+ // await testUploadFileForHugeFile() // to run this test, put a file (or symlink) at huge-file.ext
246
273
  await testSession()
247
274
  await testFailure()
248
275
  await testUserListAndUpdate()