filestack 2.1.0 → 2.2.0

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. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +2 -2
  4. data/VERSION +1 -1
  5. data/docs/AV.html +557 -0
  6. data/docs/Filestack.html +115 -0
  7. data/docs/Filestack/Ruby.html +115 -0
  8. data/docs/FilestackClient.html +702 -0
  9. data/docs/FilestackCommon.html +884 -0
  10. data/docs/FilestackConfig.html +197 -0
  11. data/docs/FilestackFilelink.html +1277 -0
  12. data/docs/FilestackSecurity.html +654 -0
  13. data/docs/IntelligentState.html +729 -0
  14. data/docs/IntelligentUtils.html +1639 -0
  15. data/docs/MultipartUploadUtils.html +1543 -0
  16. data/docs/Transform.html +1152 -0
  17. data/docs/TransformConfig.html +138 -0
  18. data/docs/TransformUtils.html +272 -0
  19. data/docs/UploadUtils.html +673 -0
  20. data/docs/_index.html +227 -0
  21. data/docs/class_list.html +51 -0
  22. data/docs/css/common.css +1 -0
  23. data/docs/css/full_list.css +58 -0
  24. data/docs/css/style.css +492 -0
  25. data/docs/file.README.html +210 -0
  26. data/docs/file_list.html +56 -0
  27. data/docs/frames.html +17 -0
  28. data/docs/index.html +210 -0
  29. data/docs/js/app.js +248 -0
  30. data/docs/js/full_list.js +216 -0
  31. data/docs/js/jquery.js +4 -0
  32. data/docs/method_list.html +643 -0
  33. data/docs/top-level-namespace.html +145 -0
  34. data/examples/file_transformations.rb +6 -0
  35. data/examples/intelligent_upload.rb +7 -0
  36. data/examples/normal_upload.rb +5 -0
  37. data/examples/upload_external_url.rb +7 -0
  38. data/examples/upload_with_security.rb +6 -0
  39. data/examples/video_convert.rb +10 -0
  40. data/filestack-ruby.gemspec +1 -0
  41. data/lib/filestack/config.rb +9 -5
  42. data/lib/filestack/mixins/filestack_common.rb +17 -1
  43. data/lib/filestack/models/filelink.rb +8 -1
  44. data/lib/filestack/models/filestack_client.rb +17 -3
  45. data/lib/filestack/ruby/version.rb +1 -1
  46. data/lib/filestack/utils/multipart_upload_utils.rb +91 -36
  47. data/lib/filestack/utils/utils.rb +309 -1
  48. metadata +52 -3
@@ -1,10 +1,48 @@
1
1
  require 'base64'
2
2
  require 'digest'
3
+ require 'fiber'
3
4
  require 'mimemagic'
4
5
  require 'json'
5
6
  require 'unirest'
6
7
 
7
8
  require 'filestack/config'
9
+ # set timeout for all requests to be 30 seconds
10
+ Unirest.timeout(30)
11
+ class IntelligentState
12
+ attr_accessor :offset, :ok, :error_type
13
+ def initialize
14
+ @offset = 524288
15
+ @ok = true
16
+ @alive = true
17
+ @retries = 0
18
+ @backoff = 1
19
+ @offset_index = 0
20
+ @offset_sizes = [524288, 262144, 131072, 65536, 32768]
21
+ end
22
+
23
+ def alive?
24
+ @alive
25
+ end
26
+
27
+ def add_retry
28
+ @retries += 1
29
+ @alive = false if @retries >= 5
30
+ end
31
+
32
+ def backoff
33
+ @backoff = 2 ** retries
34
+ end
35
+
36
+ def next_offset
37
+ current_offset = @offset_sizes[@offset_index]
38
+ @offset_index += 1
39
+ return current_offset
40
+ end
41
+
42
+ def reset
43
+ @retries = 0
44
+ end
45
+ end
8
46
 
9
47
  # Includes general utility functions for the Filestack Ruby SDK
10
48
  module UploadUtils
@@ -69,7 +107,7 @@ module UploadUtils
69
107
  # @param [String] path The specific API path (optional)
70
108
  # @param [String] security Security for the FilestackFilelink (optional)
71
109
  #
72
- # return [String]
110
+ # @return [String]
73
111
  def get_url(base, handle: nil, path: nil, security: nil)
74
112
  url_components = [base]
75
113
 
@@ -91,6 +129,9 @@ end
91
129
  module TransformUtils
92
130
  # Creates a transformation task to be sent back to transform object
93
131
  #
132
+ # @param [String] transform The task to be added
133
+ # @param [Dict] options A dictionary representing the options for that task
134
+ #
94
135
  # @return [String]
95
136
  def add_transform_task(transform, options = {})
96
137
  options_list = []
@@ -105,3 +146,270 @@ module TransformUtils
105
146
  end
106
147
  end
107
148
  end
149
+
150
+ module IntelligentUtils
151
+ # Generates a batch given a Fiber
152
+ #
153
+ # @param [Fiber] generator A living Fiber object
154
+ #
155
+ # @return [Array]
156
+ def get_generator_batch(generator)
157
+ batch = []
158
+ 4.times do
159
+ batch.push(generator.resume) if generator.alive?
160
+ end
161
+ return batch
162
+ end
163
+
164
+ # Check if state is in error state
165
+ # or has reached maximum retries
166
+ #
167
+ # @param [IntelligentState] state An IntelligentState object
168
+ #
169
+ # @return [Boolean]
170
+ def bad_state(state)
171
+ !state.ok && state.alive?
172
+ end
173
+
174
+ # Return current working offest if state
175
+ # has not tried it. Otherwise, return the next
176
+ # offset of the state
177
+ #
178
+ # @param [Integer] working_offset The current offset
179
+ # @param [IntelligentState] state An IntelligentState object
180
+ #
181
+ # @return [Integer]
182
+ def change_offset(working_offset, state)
183
+ if state.offset > working_offset
184
+ working_offset
185
+ else
186
+ state.offset = state.next_offset
187
+ end
188
+ end
189
+
190
+ # Runs the intelligent upload flow, from start to finish
191
+ #
192
+ # @param [Array] jobs A list of file parts
193
+ # @param [IntelligentState] state An IntelligentState object
194
+ #
195
+ # @return [Array]
196
+ def run_intelligent_upload_flow(jobs, state)
197
+ bar = ProgressBar.new(jobs.length)
198
+ generator = create_intelligent_generator(jobs)
199
+ working_offset = FilestackConfig::DEFAULT_OFFSET_SIZE
200
+ while generator.alive?
201
+ batch = get_generator_batch(generator)
202
+ # run parts
203
+ Parallel.map(batch, in_threads: 4) do |part|
204
+ state = run_intelligent_uploads(part, state)
205
+ # condition: a chunk has failed but we have not reached the maximum retries
206
+ while bad_state(state)
207
+ # condition: timeout to S3, requiring offset size to be changed
208
+ if state.error_type == 'S3_NETWORK'
209
+ sleep(5)
210
+ state.offset = working_offset = change_offset(working_offset, state)
211
+ # condition: timeout to backend, requiring only backoff
212
+ elsif ['S3_SERVER', 'BACKEND_SERVER'].include? state.error_type
213
+ sleep(state.backoff)
214
+ end
215
+ state.add_retry
216
+ state = run_intelligent_uploads(part, state)
217
+ end
218
+ raise "Upload has failed. Please try again later." unless state.ok
219
+ bar.increment!
220
+ end
221
+ end
222
+ end
223
+
224
+ # Creates a generator of part jobs
225
+ #
226
+ # @param [Array] jobs A list of file parts
227
+ #
228
+ # @return [Fiber]
229
+ def create_intelligent_generator(jobs)
230
+ jobs_gen = jobs.lazy.each
231
+ Fiber.new do
232
+ (jobs.length-1).times do
233
+ Fiber.yield jobs_gen.next
234
+ end
235
+ jobs_gen.next
236
+ end
237
+ end
238
+
239
+ # Loop and run chunks for each offset
240
+ #
241
+ # @param [Array] jobs A list of file parts
242
+ # @param [IntelligentState] state An IntelligentState object
243
+ # @param [String] apikey Filestack API key
244
+ # @param [String] filename Name of incoming file
245
+ # @param [String] filepath Local path to the file
246
+ # @param [Int] filesize Size of incoming file
247
+ # @param [Unirest::Response] start_response Response body from
248
+ # multipart_start
249
+ #
250
+ # @return [Array]
251
+ def create_upload_job_chunks(jobs, state, apikey, filename, filepath, filesize, start_response)
252
+ jobs.each { |job|
253
+ job[:chunks] = chunk_job(
254
+ job, state, apikey, filename, filepath, filesize, start_response
255
+ )
256
+ }
257
+ jobs
258
+ end
259
+
260
+ # Chunk a specific job into offests
261
+ #
262
+ # @param [Dict] job Dictionary with all job options
263
+ # @param [IntelligentState] state An IntelligentState object
264
+ # @param [String] apikey Filestack API key
265
+ # @param [String] filename Name of incoming file
266
+ # @param [String] filepath Local path to the file
267
+ # @param [Int] filesize Size of incoming file
268
+ # @param [Unirest::Response] start_response Response body from
269
+ # multipart_start
270
+ #
271
+ # @return [Dict]
272
+ def chunk_job(job, state, apikey, filename, filepath, filesize, start_response)
273
+ offset = 0
274
+ seek_point = job[:seek]
275
+ chunk_list = []
276
+ while (offset < FilestackConfig::DEFAULT_CHUNK_SIZE) && (seek_point + offset) < filesize
277
+ chunk_list.push(
278
+ seek: seek_point,
279
+ filepath: filepath,
280
+ filename: filename,
281
+ apikey: apikey,
282
+ part: job[:part],
283
+ size: job[:size],
284
+ uri: start_response['uri'],
285
+ region: start_response['region'],
286
+ upload_id: start_response['upload_id'],
287
+ location_url: start_response['location_url'],
288
+ store_location: job[:store_location],
289
+ offset: offset
290
+ )
291
+ offset += state.offset
292
+ end
293
+ chunk_list
294
+ end
295
+
296
+ # Send a job's chunks in parallel and commit
297
+ #
298
+ # @param [Dict] part A dictionary representing the information
299
+ # for a single part
300
+ # @param [IntelligentState] state An IntelligentState object
301
+ #
302
+ # @return [IntelligentState]
303
+ def run_intelligent_uploads(part, state)
304
+ failed = false
305
+ chunks = chunk_job(
306
+ part, state, part[:apikey], part[:filename], part[:filepath],
307
+ part[:filesize], part[:start_response]
308
+ )
309
+ Parallel.map(chunks, in_threads: 3) do |chunk|
310
+ begin
311
+ upload_chunk_intelligently(chunk, state, part[:apikey], part[:filepath], part[:options])
312
+ rescue => e
313
+ state.error_type = e.message
314
+ failed = true
315
+ Parallel::Kill
316
+ end
317
+ end
318
+
319
+ if failed
320
+ state.ok = false
321
+ return state
322
+ else
323
+ state.ok = true
324
+ end
325
+ commit_params = {
326
+ apikey: part[:apikey],
327
+ uri: part[:uri],
328
+ region: part[:region],
329
+ upload_id: part[:upload_id],
330
+ size: part[:filesize],
331
+ part: part[:part],
332
+ location_url: part[:location_url],
333
+ store_location: part[:store_location],
334
+ file: Tempfile.new(part[:filename])
335
+ }
336
+ response = Unirest.post(FilestackConfig::MULTIPART_COMMIT_URL, parameters: commit_params,
337
+ headers: FilestackConfig::HEADERS)
338
+ if response.code == 200
339
+ state.reset
340
+ else
341
+ state.ok = false
342
+ end
343
+ state
344
+ end
345
+
346
+ # Upload a single chunk
347
+ #
348
+ # @param [Dict] job Dictionary with all job options
349
+ # @param [IntelligentState] state An IntelligentState object
350
+ # @param [String] apikey Filestack API key
351
+ # @param [String] filename Name of incoming file
352
+ # @param [String] filepath Local path to the file
353
+ # @param [Hash] options User-defined options for
354
+ # multipart uploads
355
+ #
356
+ # @return [Unirest::Response]
357
+ def upload_chunk_intelligently(job, state, apikey, filepath, options)
358
+ file = File.open(filepath)
359
+ file.seek(job[:seek] + job[:offset])
360
+ chunk = file.read(state.offset)
361
+ md5 = Digest::MD5.new
362
+ md5 << chunk
363
+ data = {
364
+ apikey: apikey,
365
+ part: job[:part],
366
+ size: chunk.length,
367
+ md5: md5.base64digest,
368
+ uri: job[:uri],
369
+ region: job[:region],
370
+ upload_id: job[:upload_id],
371
+ store_location: job[:store_location],
372
+ offset: job[:offset],
373
+ file: Tempfile.new(job[:filename]),
374
+ 'multipart' => 'true'
375
+ }
376
+
377
+ data = data.merge!(options) if options
378
+ fs_response = Unirest.post(
379
+ FilestackConfig::MULTIPART_UPLOAD_URL, parameters: data,
380
+ headers: FilestackConfig::HEADERS
381
+ )
382
+ # POST to multipart/upload
383
+ begin
384
+ unless fs_response.code == 200
385
+ if [400, 403, 404].include? fs_response.code
386
+ raise 'FAILURE'
387
+ else
388
+ raise 'BACKEND_SERVER'
389
+ end
390
+ end
391
+
392
+ rescue
393
+ raise 'BACKEND_NETWORK'
394
+ end
395
+ fs_response = fs_response.body
396
+
397
+ # PUT to S3
398
+ begin
399
+ amazon_response = Unirest.put(
400
+ fs_response['url'], headers: fs_response['headers'], parameters: chunk
401
+ )
402
+ unless amazon_response.code == 200
403
+ if [400, 403, 404].include? amazon_response.code
404
+ raise 'FAILURE'
405
+ else
406
+ raise 'S3_SERVER'
407
+ end
408
+ end
409
+
410
+ rescue
411
+ raise 'S3_NETWORK'
412
+ end
413
+ amazon_response
414
+ end
415
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filestack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Filestack
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-04 00:00:00.000000000 Z
11
+ date: 2017-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: unirest
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.3.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: progress_bar
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +142,41 @@ files:
128
142
  - VERSION
129
143
  - bin/console
130
144
  - bin/setup
145
+ - docs/AV.html
146
+ - docs/Filestack.html
147
+ - docs/Filestack/Ruby.html
148
+ - docs/FilestackClient.html
149
+ - docs/FilestackCommon.html
150
+ - docs/FilestackConfig.html
151
+ - docs/FilestackFilelink.html
152
+ - docs/FilestackSecurity.html
153
+ - docs/IntelligentState.html
154
+ - docs/IntelligentUtils.html
155
+ - docs/MultipartUploadUtils.html
156
+ - docs/Transform.html
157
+ - docs/TransformConfig.html
158
+ - docs/TransformUtils.html
159
+ - docs/UploadUtils.html
160
+ - docs/_index.html
161
+ - docs/class_list.html
162
+ - docs/css/common.css
163
+ - docs/css/full_list.css
164
+ - docs/css/style.css
165
+ - docs/file.README.html
166
+ - docs/file_list.html
167
+ - docs/frames.html
168
+ - docs/index.html
169
+ - docs/js/app.js
170
+ - docs/js/full_list.js
171
+ - docs/js/jquery.js
172
+ - docs/method_list.html
173
+ - docs/top-level-namespace.html
174
+ - examples/file_transformations.rb
175
+ - examples/intelligent_upload.rb
176
+ - examples/normal_upload.rb
177
+ - examples/upload_external_url.rb
178
+ - examples/upload_with_security.rb
179
+ - examples/video_convert.rb
131
180
  - filestack-ruby.gemspec
132
181
  - lib/filestack.rb
133
182
  - lib/filestack/config.rb
@@ -160,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
209
  version: '0'
161
210
  requirements: []
162
211
  rubyforge_project:
163
- rubygems_version: 2.5.1
212
+ rubygems_version: 2.6.12
164
213
  signing_key:
165
214
  specification_version: 4
166
215
  summary: Official Ruby SDK for the Filestack API