filestack 2.7.0 → 2.9.2
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 +15 -0
- data/README.md +36 -3
- data/VERSION +1 -1
- data/docs/FilestackCommon.html +1 -1
- data/docs/MultipartUploadUtils.html +491 -491
- data/filestack-ruby.gemspec +1 -1
- data/lib/filestack/config.rb +2 -0
- data/lib/filestack/mixins/filestack_common.rb +2 -2
- data/lib/filestack/models/filestack_client.rb +11 -9
- data/lib/filestack/ruby/version.rb +1 -1
- data/lib/filestack/utils/multipart_upload_utils.rb +43 -23
- data/lib/filestack/utils/utils.rb +19 -17
- metadata +5 -5
data/filestack-ruby.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_dependency "typhoeus", "~> 1.1"
|
26
26
|
spec.add_dependency "parallel", "~> 1.11", ">= 1.11.2"
|
27
|
-
spec.add_dependency "
|
27
|
+
spec.add_dependency "mini_mime", "~> 1.0.2"
|
28
28
|
spec.add_dependency "progress_bar"
|
29
29
|
|
30
30
|
spec.add_development_dependency "bundler", "~> 1.7"
|
data/lib/filestack/config.rb
CHANGED
@@ -22,6 +22,8 @@ class FilestackConfig
|
|
22
22
|
'Accept-Encoding' => "application/json"
|
23
23
|
}.freeze
|
24
24
|
|
25
|
+
DEFAULT_UPLOAD_MIMETYPE = 'application/octet-stream'
|
26
|
+
|
25
27
|
INTELLIGENT_ERROR_MESSAGES = ['BACKEND_SERVER', 'BACKEND_NETWORK', 'S3_SERVER', 'S3_NETWORK']
|
26
28
|
|
27
29
|
def self.multipart_start_url
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'filestack/config'
|
2
2
|
require 'filestack/utils/utils'
|
3
3
|
require 'filestack/utils/multipart_upload_utils'
|
4
|
-
require '
|
4
|
+
require 'mini_mime'
|
5
5
|
require 'json'
|
6
6
|
|
7
7
|
# Module is mixin for common functionalities that all Filestack
|
@@ -58,7 +58,7 @@ module FilestackCommon
|
|
58
58
|
return 'Overwrite requires security' if security.nil?
|
59
59
|
|
60
60
|
file = File.open(filepath, 'r')
|
61
|
-
mimetype =
|
61
|
+
mimetype = MiniMime.lookup_by_filename(file).content_type
|
62
62
|
content = file.read
|
63
63
|
|
64
64
|
signature = security.signature
|
@@ -23,23 +23,25 @@ class FilestackClient
|
|
23
23
|
@security = security
|
24
24
|
end
|
25
25
|
|
26
|
-
# Upload a local file or
|
26
|
+
# Upload a local file, external url or IO object
|
27
27
|
# @param [String] filepath The path of a local file
|
28
28
|
# @param [String] external_url An external URL
|
29
|
+
# @param [StringIO] io The IO object
|
29
30
|
# @param [Hash] options User-supplied upload options
|
31
|
+
# @param [Boolean] intelligent Upload file using Filestack Intelligent Ingestion
|
32
|
+
# @param [String] storage Default storage to be used for uploads
|
30
33
|
#
|
31
34
|
# return [Filestack::FilestackFilelink]
|
32
|
-
def upload(filepath: nil, external_url: nil, options: {}, intelligent: false, timeout: 60, storage: 'S3')
|
33
|
-
return 'You cannot upload a URL and file at the same time' if filepath && external_url
|
35
|
+
def upload(filepath: nil, external_url: nil, io: nil, options: {}, intelligent: false, timeout: 60, storage: 'S3')
|
36
|
+
return 'You cannot upload a URL and file at the same time' if (filepath || io) && external_url
|
34
37
|
|
35
|
-
response = if
|
36
|
-
|
38
|
+
response = if external_url
|
39
|
+
send_upload(@apikey, external_url, @security, options)
|
37
40
|
else
|
38
|
-
|
39
|
-
|
40
|
-
options: options,
|
41
|
-
security: @security)
|
41
|
+
return 'You cannot upload IO object and file at the same time' if io && filepath
|
42
|
+
multipart_upload(@apikey, filepath, io, @security, options, timeout, storage, intelligent)
|
42
43
|
end
|
44
|
+
|
43
45
|
FilestackFilelink.new(response['handle'], security: @security, apikey: @apikey)
|
44
46
|
end
|
45
47
|
# Transform an external URL
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'timeout'
|
3
3
|
require 'digest'
|
4
|
-
require '
|
4
|
+
require 'mini_mime'
|
5
5
|
require 'json'
|
6
6
|
require 'parallel'
|
7
7
|
require 'typhoeus'
|
@@ -13,13 +13,22 @@ include UploadUtils
|
|
13
13
|
include IntelligentUtils
|
14
14
|
# Includes all the utility functions for Filestack multipart uploads
|
15
15
|
module MultipartUploadUtils
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
def get_file_attributes(file, options = {})
|
18
|
+
filename = options[:filename] || File.basename(file)
|
19
|
+
mimetype = options[:mimetype] || MiniMime.lookup_by_filename(File.open(file)).content_type || FilestackConfig::DEFAULT_UPLOAD_MIMETYPE
|
18
20
|
filesize = File.size(file)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
|
22
|
+
[filename, filesize, mimetype.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_io_attributes(io, options = {})
|
26
|
+
filename = options[:filename] || 'unnamed_file'
|
27
|
+
mimetype = options[:mimetype] || FilestackConfig::DEFAULT_UPLOAD_MIMETYPE
|
28
|
+
|
29
|
+
io.seek(0, IO::SEEK_END)
|
30
|
+
filesize = io.tell
|
31
|
+
|
23
32
|
[filename, filesize, mimetype.to_s]
|
24
33
|
end
|
25
34
|
|
@@ -31,8 +40,10 @@ module MultipartUploadUtils
|
|
31
40
|
# @param [String] mimetype Mimetype of incoming file
|
32
41
|
# @param [FilestackSecurity] security Security object with
|
33
42
|
# policy/signature
|
43
|
+
# @param [String] storage Default storage to be used for uploads
|
34
44
|
# @param [Hash] options User-defined options for
|
35
45
|
# multipart uploads
|
46
|
+
# @param [Bool] intelligent Upload file using Filestack Intelligent Ingestion
|
36
47
|
#
|
37
48
|
# @return [Typhoeus::Response]
|
38
49
|
def multipart_start(apikey, filename, filesize, mimetype, security, storage, options = {}, intelligent)
|
@@ -67,22 +78,21 @@ module MultipartUploadUtils
|
|
67
78
|
#
|
68
79
|
# @param [String] apikey Filestack API key
|
69
80
|
# @param [String] filename Name of incoming file
|
70
|
-
# @param [String] filepath Local path to file
|
71
81
|
# @param [Int] filesize Size of incoming file
|
72
82
|
# @param [Typhoeus::Response] start_response Response body from
|
73
83
|
# multipart_start
|
84
|
+
# @param [String] storage Default storage to be used for uploads
|
74
85
|
# @param [Hash] options User-defined options for
|
75
86
|
# multipart uploads
|
76
87
|
#
|
77
88
|
# @return [Array]
|
78
|
-
def create_upload_jobs(apikey, filename,
|
89
|
+
def create_upload_jobs(apikey, filename, filesize, start_response, storage, options)
|
79
90
|
jobs = []
|
80
91
|
part = 1
|
81
92
|
seek_point = 0
|
82
93
|
while seek_point < filesize
|
83
94
|
part_info = {
|
84
95
|
seek_point: seek_point,
|
85
|
-
filepath: filepath,
|
86
96
|
filename: filename,
|
87
97
|
apikey: apikey,
|
88
98
|
part: part,
|
@@ -92,7 +102,7 @@ module MultipartUploadUtils
|
|
92
102
|
upload_id: start_response['upload_id'],
|
93
103
|
location_url: start_response['location_url'],
|
94
104
|
start_response: start_response,
|
95
|
-
store: { location: storage }
|
105
|
+
store: { location: storage },
|
96
106
|
}
|
97
107
|
|
98
108
|
part_info[:store].merge!(options) if options
|
@@ -116,15 +126,16 @@ module MultipartUploadUtils
|
|
116
126
|
# @param [Hash] job Hash of options needed
|
117
127
|
# to upload a chunk
|
118
128
|
# @param [String] apikey Filestack API key
|
119
|
-
# @param [String]
|
129
|
+
# @param [String] filepath Location url given back
|
120
130
|
# from endpoint
|
121
|
-
# @param [
|
131
|
+
# @param [StringIO] io The IO object
|
122
132
|
# @param [Hash] options User-defined options for
|
123
133
|
# multipart uploads
|
134
|
+
# @param [String] storage Default storage to be used for uploads
|
124
135
|
#
|
125
136
|
# @return [Typhoeus::Response]
|
126
|
-
def upload_chunk(job, apikey, filepath, options, storage)
|
127
|
-
file = File.open(filepath)
|
137
|
+
def upload_chunk(job, apikey, filepath, io, options, storage)
|
138
|
+
file = filepath ? File.open(filepath) : io
|
128
139
|
file.seek(job[:seek_point])
|
129
140
|
chunk = file.read(FilestackConfig::DEFAULT_CHUNK_SIZE)
|
130
141
|
|
@@ -139,7 +150,6 @@ module MultipartUploadUtils
|
|
139
150
|
region: job[:region],
|
140
151
|
upload_id: job[:upload_id],
|
141
152
|
store: { location: storage },
|
142
|
-
file: Tempfile.new(job[:filename])
|
143
153
|
}
|
144
154
|
data = data.merge!(options) if options
|
145
155
|
|
@@ -158,13 +168,14 @@ module MultipartUploadUtils
|
|
158
168
|
# @param [String] filepath Local path to file
|
159
169
|
# @param [Hash] options User-defined options for
|
160
170
|
# multipart uploads
|
171
|
+
# @param [String] storage Default storage to be used for uploads
|
161
172
|
#
|
162
173
|
# @return [Array] Array of parts/etags strings
|
163
|
-
def run_uploads(jobs, apikey, filepath, options, storage)
|
174
|
+
def run_uploads(jobs, apikey, filepath, io, options, storage)
|
164
175
|
bar = ProgressBar.new(jobs.length)
|
165
176
|
results = Parallel.map(jobs, in_threads: 4) do |job|
|
166
177
|
response = upload_chunk(
|
167
|
-
job, apikey, filepath, options, storage
|
178
|
+
job, apikey, filepath, io, options, storage
|
168
179
|
)
|
169
180
|
if response.code == 200
|
170
181
|
bar.increment!
|
@@ -190,6 +201,8 @@ module MultipartUploadUtils
|
|
190
201
|
# part numbers
|
191
202
|
# @param [Hash] options User-defined options for
|
192
203
|
# multipart uploads
|
204
|
+
# @param [String] storage Default storage to be used for uploads
|
205
|
+
# @param [Boolean] intelligent Upload file using Filestack Intelligent Ingestion
|
193
206
|
#
|
194
207
|
# @return [Typhoeus::Response]
|
195
208
|
def multipart_complete(apikey, filename, filesize, mimetype, start_response, parts_and_etags, options, storage, intelligent = false)
|
@@ -215,32 +228,39 @@ module MultipartUploadUtils
|
|
215
228
|
#
|
216
229
|
# @param [String] apikey Filestack API key
|
217
230
|
# @param [String] filename Name of incoming file
|
231
|
+
# @param [StringIO] io The IO object
|
218
232
|
# @param [FilestackSecurity] security Security object with
|
219
233
|
# policy/signature
|
220
234
|
# @param [Hash] options User-defined options for
|
221
235
|
# multipart uploads
|
236
|
+
# @param [String] storage Default storage to be used for uploads
|
237
|
+
# @param [Boolean] intelligent Upload file using Filestack Intelligent Ingestion
|
222
238
|
#
|
223
239
|
# @return [Hash]
|
224
|
-
def multipart_upload(apikey, filepath, security, options, timeout, storage, intelligent
|
225
|
-
filename, filesize, mimetype =
|
240
|
+
def multipart_upload(apikey, filepath, io, security, options, timeout, storage, intelligent = false)
|
241
|
+
filename, filesize, mimetype = if filepath
|
242
|
+
get_file_attributes(filepath, options)
|
243
|
+
else
|
244
|
+
get_io_attributes(io, options)
|
245
|
+
end
|
226
246
|
|
227
247
|
start_response = multipart_start(
|
228
248
|
apikey, filename, filesize, mimetype, security, storage, options, intelligent
|
229
249
|
)
|
230
250
|
|
231
251
|
jobs = create_upload_jobs(
|
232
|
-
apikey, filename,
|
252
|
+
apikey, filename, filesize, start_response, storage, options
|
233
253
|
)
|
234
254
|
|
235
255
|
if intelligent
|
236
256
|
state = IntelligentState.new
|
237
|
-
run_intelligent_upload_flow(jobs, state, storage)
|
257
|
+
run_intelligent_upload_flow(jobs, filepath, io, state, storage)
|
238
258
|
response_complete = multipart_complete(
|
239
259
|
apikey, filename, filesize, mimetype,
|
240
260
|
start_response, nil, options, storage, intelligent
|
241
261
|
)
|
242
262
|
else
|
243
|
-
parts_and_etags = run_uploads(jobs, apikey, filepath, options, storage)
|
263
|
+
parts_and_etags = run_uploads(jobs, apikey, filepath, io, options, storage)
|
244
264
|
response_complete = multipart_complete(
|
245
265
|
apikey, filename, filesize, mimetype,
|
246
266
|
start_response, parts_and_etags, options, storage
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'digest'
|
3
3
|
require 'fiber'
|
4
|
-
require '
|
4
|
+
require 'mini_mime'
|
5
5
|
require 'json'
|
6
6
|
require 'typhoeus'
|
7
7
|
|
@@ -63,7 +63,7 @@ module UploadUtils
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def build_store_task(options = {})
|
66
|
-
return 'store' if options.empty?
|
66
|
+
return 'store' if options.nil? || options.empty?
|
67
67
|
tasks = []
|
68
68
|
options.each do |key, value|
|
69
69
|
value = case key
|
@@ -88,7 +88,7 @@ module UploadUtils
|
|
88
88
|
# @param [Hash] options User-defined options for
|
89
89
|
# multipart uploads
|
90
90
|
# @return [Hash]
|
91
|
-
def send_upload(apikey, external_url
|
91
|
+
def send_upload(apikey, external_url = nil, security = nil, options = nil)
|
92
92
|
base = "#{FilestackConfig::CDN_URL}/#{apikey}/#{build_store_task(options)}"
|
93
93
|
|
94
94
|
if security
|
@@ -100,9 +100,13 @@ module UploadUtils
|
|
100
100
|
response = Typhoeus.post("#{base}/#{external_url}", headers: FilestackConfig::HEADERS)
|
101
101
|
|
102
102
|
if response.code == 200
|
103
|
-
|
104
|
-
|
105
|
-
|
103
|
+
begin
|
104
|
+
response_body = JSON.parse(response.body)
|
105
|
+
handle = response_body['url'].split('/').last
|
106
|
+
return { 'handle' => handle }
|
107
|
+
rescue
|
108
|
+
raise response.body
|
109
|
+
end
|
106
110
|
end
|
107
111
|
raise response.body
|
108
112
|
end
|
@@ -199,7 +203,7 @@ module IntelligentUtils
|
|
199
203
|
# @param [IntelligentState] state An IntelligentState object
|
200
204
|
#
|
201
205
|
# @return [Array]
|
202
|
-
def run_intelligent_upload_flow(jobs, state, storage)
|
206
|
+
def run_intelligent_upload_flow(jobs, filepath, io, state, storage)
|
203
207
|
bar = ProgressBar.new(jobs.length)
|
204
208
|
generator = create_intelligent_generator(jobs)
|
205
209
|
working_offset = FilestackConfig::DEFAULT_OFFSET_SIZE
|
@@ -207,7 +211,7 @@ module IntelligentUtils
|
|
207
211
|
batch = get_generator_batch(generator)
|
208
212
|
# run parts
|
209
213
|
Parallel.map(batch, in_threads: 4) do |part|
|
210
|
-
state = run_intelligent_uploads(part, state, storage)
|
214
|
+
state = run_intelligent_uploads(part, filepath, io, state, storage)
|
211
215
|
# condition: a chunk has failed but we have not reached the maximum retries
|
212
216
|
while bad_state(state)
|
213
217
|
# condition: timeout to S3, requiring offset size to be changed
|
@@ -219,7 +223,7 @@ module IntelligentUtils
|
|
219
223
|
sleep(state.backoff)
|
220
224
|
end
|
221
225
|
state.add_retry
|
222
|
-
state = run_intelligent_uploads(part, state, storage)
|
226
|
+
state = run_intelligent_uploads(part, filepath, io, state, storage)
|
223
227
|
end
|
224
228
|
raise "Upload has failed. Please try again later." unless state.ok
|
225
229
|
bar.increment!
|
@@ -275,7 +279,7 @@ module IntelligentUtils
|
|
275
279
|
# multipart_start
|
276
280
|
#
|
277
281
|
# @return [Dict]
|
278
|
-
def chunk_job(job, state, apikey, filename,
|
282
|
+
def chunk_job(job, state, apikey, filename, filesize, start_response, storage)
|
279
283
|
offset = 0
|
280
284
|
seek_point = job[:seek_point]
|
281
285
|
chunk_list = []
|
@@ -283,7 +287,6 @@ module IntelligentUtils
|
|
283
287
|
while (offset < FilestackConfig::DEFAULT_CHUNK_SIZE) && (seek_point + offset) < filesize
|
284
288
|
chunk_list.push(
|
285
289
|
seek_point: seek_point,
|
286
|
-
filepath: filepath,
|
287
290
|
filename: filename,
|
288
291
|
apikey: apikey,
|
289
292
|
part: job[:part],
|
@@ -307,15 +310,14 @@ module IntelligentUtils
|
|
307
310
|
# @param [IntelligentState] state An IntelligentState object
|
308
311
|
#
|
309
312
|
# @return [IntelligentState]
|
310
|
-
def run_intelligent_uploads(part, state, storage)
|
313
|
+
def run_intelligent_uploads(part, filepath, io, state, storage)
|
311
314
|
failed = false
|
312
315
|
chunks = chunk_job(
|
313
|
-
part, state, part[:apikey], part[:filename], part[:
|
314
|
-
part[:filesize], part[:start_response], storage
|
316
|
+
part, state, part[:apikey], part[:filename], part[:filesize], part[:start_response], storage
|
315
317
|
)
|
316
318
|
Parallel.map(chunks, in_threads: 3) do |chunk|
|
317
319
|
begin
|
318
|
-
upload_chunk_intelligently(chunk, state, part[:apikey],
|
320
|
+
upload_chunk_intelligently(chunk, state, part[:apikey], filepath, io, part[:options], storage)
|
319
321
|
rescue => e
|
320
322
|
state.error_type = e.message
|
321
323
|
failed = true
|
@@ -364,8 +366,8 @@ module IntelligentUtils
|
|
364
366
|
# multipart uploads
|
365
367
|
#
|
366
368
|
# @return [Typhoeus::Response]
|
367
|
-
def upload_chunk_intelligently(job, state, apikey, filepath, options, storage)
|
368
|
-
file = File.open(filepath)
|
369
|
+
def upload_chunk_intelligently(job, state, apikey, filepath, io, options, storage)
|
370
|
+
file = filepath ? File.open(filepath) : io
|
369
371
|
file.seek(job[:seek_point] + job[:offset])
|
370
372
|
|
371
373
|
chunk = file.read(state.offset)
|
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.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Filestack
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typhoeus
|
@@ -45,19 +45,19 @@ dependencies:
|
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: 1.11.2
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
48
|
+
name: mini_mime
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.
|
53
|
+
version: 1.0.2
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: 0.
|
60
|
+
version: 1.0.2
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: progress_bar
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|