filestack 2.7.0 → 2.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 "mimemagic", "~> 0.3.2"
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"
@@ -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 'mimemagic'
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 = MimeMagic.by_magic(file)
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 external url
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 filepath
36
- multipart_upload(@apikey, filepath, @security, options, timeout, storage, intelligent: intelligent)
38
+ response = if external_url
39
+ send_upload(@apikey, external_url, @security, options)
37
40
  else
38
- send_upload(@apikey,
39
- external_url: external_url,
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,5 +1,5 @@
1
1
  module Filestack
2
2
  module Ruby
3
- VERSION = '2.7.0'.freeze
3
+ VERSION = '2.9.2'.freeze
4
4
  end
5
5
  end
@@ -1,7 +1,7 @@
1
1
  require 'base64'
2
2
  require 'timeout'
3
3
  require 'digest'
4
- require 'mimemagic'
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
- def get_file_info(file)
17
- filename = File.basename(file)
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
- mimetype = MimeMagic.by_magic(File.open(file))
20
- if mimetype.nil?
21
- mimetype = 'application/octet-stream'
22
- end
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, filepath, filesize, start_response, storage, options)
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] location_url Location url given back
129
+ # @param [String] filepath Location url given back
120
130
  # from endpoint
121
- # @param [String] filepath Local path to file
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: false)
225
- filename, filesize, mimetype = get_file_info(filepath)
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, filepath, filesize, start_response, storage, options
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 'mimemagic'
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: nil, security: nil, options: nil)
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
- response_body = JSON.parse(response.body)
104
- handle = response_body['url'].split('/').last
105
- return { 'handle' => handle }
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, filepath, filesize, start_response, storage)
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[:filepath],
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], part[:filepath], part[:options], storage)
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.7.0
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: 2020-09-28 00:00:00.000000000 Z
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: mimemagic
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.3.2
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.3.2
60
+ version: 1.0.2
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: progress_bar
63
63
  requirement: !ruby/object:Gem::Requirement