filestack 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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