filestack 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +2 -2
- data/VERSION +1 -1
- data/docs/AV.html +557 -0
- data/docs/Filestack.html +115 -0
- data/docs/Filestack/Ruby.html +115 -0
- data/docs/FilestackClient.html +702 -0
- data/docs/FilestackCommon.html +884 -0
- data/docs/FilestackConfig.html +197 -0
- data/docs/FilestackFilelink.html +1277 -0
- data/docs/FilestackSecurity.html +654 -0
- data/docs/IntelligentState.html +729 -0
- data/docs/IntelligentUtils.html +1639 -0
- data/docs/MultipartUploadUtils.html +1543 -0
- data/docs/Transform.html +1152 -0
- data/docs/TransformConfig.html +138 -0
- data/docs/TransformUtils.html +272 -0
- data/docs/UploadUtils.html +673 -0
- data/docs/_index.html +227 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +492 -0
- data/docs/file.README.html +210 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +210 -0
- data/docs/js/app.js +248 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +643 -0
- data/docs/top-level-namespace.html +145 -0
- data/examples/file_transformations.rb +6 -0
- data/examples/intelligent_upload.rb +7 -0
- data/examples/normal_upload.rb +5 -0
- data/examples/upload_external_url.rb +7 -0
- data/examples/upload_with_security.rb +6 -0
- data/examples/video_convert.rb +10 -0
- data/filestack-ruby.gemspec +1 -0
- data/lib/filestack/config.rb +9 -5
- data/lib/filestack/mixins/filestack_common.rb +17 -1
- data/lib/filestack/models/filelink.rb +8 -1
- data/lib/filestack/models/filestack_client.rb +17 -3
- data/lib/filestack/ruby/version.rb +1 -1
- data/lib/filestack/utils/multipart_upload_utils.rb +91 -36
- data/lib/filestack/utils/utils.rb +309 -1
- 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.
|
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-
|
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.
|
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
|