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.
- 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
|