files.com 1.0.412 → 1.0.414

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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()