filestack 2.0.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 +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +98 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.txt +201 -0
- data/README.md +148 -0
- data/Rakefile +10 -0
- data/VERSION +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/filestack-ruby.gemspec +33 -0
- data/lib/filestack.rb +5 -0
- data/lib/filestack/config.rb +36 -0
- data/lib/filestack/mixins/filestack_common.rb +93 -0
- data/lib/filestack/models/filelink.rb +89 -0
- data/lib/filestack/models/filestack_av.rb +32 -0
- data/lib/filestack/models/filestack_client.rb +55 -0
- data/lib/filestack/models/filestack_security.rb +82 -0
- data/lib/filestack/models/filestack_transform.rb +112 -0
- data/lib/filestack/ruby/version.rb +5 -0
- data/lib/filestack/utils/multipart_upload_utils.rb +223 -0
- data/lib/filestack/utils/utils.rb +107 -0
- metadata +168 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'filestack/config'
|
2
|
+
require 'filestack/models/filestack_av'
|
3
|
+
|
4
|
+
# Class for creating transformation chains and storing them to Filestack
|
5
|
+
class Transform
|
6
|
+
include TransformUtils
|
7
|
+
attr_reader :handle, :external_url, :security
|
8
|
+
|
9
|
+
def initialize(handle: nil, external_url: nil, security: nil, apikey: nil)
|
10
|
+
@apikey = apikey
|
11
|
+
@handle = handle
|
12
|
+
@external_url = external_url
|
13
|
+
@security = security
|
14
|
+
@transform_tasks = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Catches method calls and checks to see if they exist in transformation list
|
18
|
+
#
|
19
|
+
# This is to avoid rewriting the same code
|
20
|
+
# over and over for transform chaining
|
21
|
+
#
|
22
|
+
# @return [Filestack::Transform] or Error
|
23
|
+
def method_missing(method_name, **args)
|
24
|
+
if TransformConfig::TRANSFORMATIONS.include? method_name.to_s
|
25
|
+
@transform_tasks.push(
|
26
|
+
add_transform_task(method_name, args)
|
27
|
+
)
|
28
|
+
self
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Converts one filetype to the other
|
35
|
+
#
|
36
|
+
# @param options
|
37
|
+
#
|
38
|
+
# @return [Filestack::Transform]
|
39
|
+
def filetype_conversion(options)
|
40
|
+
@transform_tasks.push(
|
41
|
+
add_transform_task('output', options)
|
42
|
+
)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Converts video or audio based on user-provided parameters
|
47
|
+
#
|
48
|
+
# @param [Hash] options User-provided parameters
|
49
|
+
#
|
50
|
+
# @return [Filestack::AV]
|
51
|
+
def av_convert(options)
|
52
|
+
if @external_url
|
53
|
+
return 'av_convert does not support external URLs. Please upload file first.'
|
54
|
+
end
|
55
|
+
@transform_tasks.push(
|
56
|
+
add_transform_task('video_convert', options)
|
57
|
+
)
|
58
|
+
response = UploadUtils.make_call(url, 'post')
|
59
|
+
if response.code == 200
|
60
|
+
return AV.new(url, apikey: @apikey, security: @security)
|
61
|
+
end
|
62
|
+
response.body
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add debug parameter to get information on transformation image
|
66
|
+
#
|
67
|
+
# @return [Unirest::Response]
|
68
|
+
def debug
|
69
|
+
@transform_tasks.push(
|
70
|
+
add_transform_task('debug')
|
71
|
+
)
|
72
|
+
UploadUtils.make_call(url, 'get').body
|
73
|
+
end
|
74
|
+
|
75
|
+
# Stores a transformation URL and returns a filelink
|
76
|
+
#
|
77
|
+
# @return [Filestack::Filelink]
|
78
|
+
def store
|
79
|
+
@transform_tasks.push(
|
80
|
+
add_transform_task('store', {})
|
81
|
+
)
|
82
|
+
response = UploadUtils.make_call(url, 'get')
|
83
|
+
handle = response.body['url'].split('/').last
|
84
|
+
Filelink.new(handle, apikey: @apikey, security: @security)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Override default method (best practice when overriding method_missing)
|
88
|
+
def respond_to_missing?(method_name, *)
|
89
|
+
TransformConfig::TRANSFORMATIONS.include?(method_name.to_s || super)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Creates a URL based on transformation instance state
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
def url
|
96
|
+
base = [FilestackConfig::CDN_URL]
|
97
|
+
if @transform_tasks.include? 'debug'
|
98
|
+
@transform_tasks.delete('debug')
|
99
|
+
base.push('debug')
|
100
|
+
end
|
101
|
+
base.push(@apikey) if @apikey && @external_url
|
102
|
+
if @security
|
103
|
+
policy = @security.policy
|
104
|
+
signature = @security.signature
|
105
|
+
security_string = "security=policy:#{policy},signature:#{signature}"
|
106
|
+
base.push(security_string)
|
107
|
+
end
|
108
|
+
base += @transform_tasks
|
109
|
+
base.push(@handle || @external_url)
|
110
|
+
base.join('/')
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest'
|
3
|
+
require 'mimemagic'
|
4
|
+
require 'json'
|
5
|
+
require 'parallel'
|
6
|
+
require 'unirest'
|
7
|
+
|
8
|
+
require 'filestack/config'
|
9
|
+
require 'filestack/utils/utils'
|
10
|
+
|
11
|
+
include UploadUtils
|
12
|
+
|
13
|
+
# Includes all the utility functions for Filestack multipart uploads
|
14
|
+
module MultipartUploadUtils
|
15
|
+
def get_file_info(file)
|
16
|
+
filename = File.basename(file)
|
17
|
+
filesize = File.size(file)
|
18
|
+
mimetype = MimeMagic.by_magic(File.open(file))
|
19
|
+
[filename, filesize, mimetype.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Send start response to multipart endpoint
|
23
|
+
#
|
24
|
+
# @param [String] apikey Filestack API key
|
25
|
+
# @param [String] filename Name of incoming file
|
26
|
+
# @param [Int] filesize Size of incoming file
|
27
|
+
# @param [String] mimetype Mimetype of incoming file
|
28
|
+
# @param [FilestackSecurity] security Security object with
|
29
|
+
# policy/signature
|
30
|
+
# @param [Hash] options User-defined options for
|
31
|
+
# multipart uploads
|
32
|
+
#
|
33
|
+
# @return [Unirest::Response]
|
34
|
+
def multipart_start(apikey, filename, filesize, mimetype, security, options)
|
35
|
+
params = {
|
36
|
+
apikey: apikey,
|
37
|
+
filename: filename,
|
38
|
+
mimetype: mimetype,
|
39
|
+
size: filesize,
|
40
|
+
store_location: options.nil? ? 's3' : options[:store_location],
|
41
|
+
file: Tempfile.new(filename)
|
42
|
+
}
|
43
|
+
|
44
|
+
params = params.merge!(options) if options
|
45
|
+
|
46
|
+
unless security.nil?
|
47
|
+
params[:policy] = security.policy
|
48
|
+
params[:signature] = security.signature
|
49
|
+
end
|
50
|
+
|
51
|
+
response = Unirest.post(
|
52
|
+
FilestackConfig::MULTIPART_START_URL, parameters: params,
|
53
|
+
headers: FilestackConfig::HEADERS
|
54
|
+
)
|
55
|
+
if response.code == 200
|
56
|
+
response.body
|
57
|
+
else
|
58
|
+
raise Exception(response.body)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create array of jobs for parallel uploading
|
63
|
+
#
|
64
|
+
# @param [String] apikey Filestack API key
|
65
|
+
# @param [String] filename Name of incoming file
|
66
|
+
# @param [String] filepath Local path to file
|
67
|
+
# @param [Int] filesize Size of incoming file
|
68
|
+
# @param [Unirest::Response] start_response Response body from
|
69
|
+
# multipart_start
|
70
|
+
# @param [FilestackSecurity] security Security object with
|
71
|
+
# policy/signature
|
72
|
+
# @param [Hash] options User-defined options for
|
73
|
+
# multipart uploads
|
74
|
+
#
|
75
|
+
# @return [Array]
|
76
|
+
def create_upload_jobs(apikey, filename, filepath, filesize, start_response, options)
|
77
|
+
jobs = []
|
78
|
+
part = 1
|
79
|
+
seek_point = 0
|
80
|
+
while seek_point < filesize
|
81
|
+
jobs.push(
|
82
|
+
seek: seek_point,
|
83
|
+
filepath: filepath,
|
84
|
+
filename: filename,
|
85
|
+
apikey: apikey,
|
86
|
+
part: part,
|
87
|
+
uri: start_response['uri'],
|
88
|
+
region: start_response['region'],
|
89
|
+
upload_id: start_response['upload_id'],
|
90
|
+
location_url: start_response['location_url'],
|
91
|
+
store_location: options.nil? ? 's3' : options[:store_location]
|
92
|
+
)
|
93
|
+
part += 1
|
94
|
+
seek_point += FilestackConfig::DEFAULT_CHUNK_SIZE
|
95
|
+
end
|
96
|
+
jobs
|
97
|
+
end
|
98
|
+
|
99
|
+
# Uploads one chunk of the file
|
100
|
+
#
|
101
|
+
# @param [Hash] job Hash of options needed
|
102
|
+
# to upload a chunk
|
103
|
+
# @param [String] apikey Filestack API key
|
104
|
+
# @param [String] location_url Location url given back
|
105
|
+
# from endpoint
|
106
|
+
# @param [String] filepath Local path to file
|
107
|
+
# @param [Hash] options User-defined options for
|
108
|
+
# multipart uploads
|
109
|
+
#
|
110
|
+
# @return [Unirest::Response]
|
111
|
+
def upload_chunk(job, apikey, filepath, options)
|
112
|
+
file = File.open(filepath)
|
113
|
+
file.seek(job[:seek])
|
114
|
+
chunk = file.read(FilestackConfig::DEFAULT_CHUNK_SIZE)
|
115
|
+
md5 = Digest::MD5.new
|
116
|
+
md5 << chunk
|
117
|
+
data = {
|
118
|
+
apikey: apikey,
|
119
|
+
part: job[:part],
|
120
|
+
size: chunk.length,
|
121
|
+
md5: md5.base64digest,
|
122
|
+
uri: job[:uri],
|
123
|
+
region: job[:region],
|
124
|
+
upload_id: job[:upload_id],
|
125
|
+
store_location: options.nil? ? 's3' : options[:store_location],
|
126
|
+
file: Tempfile.new(job[:filename])
|
127
|
+
}
|
128
|
+
data = data.merge!(options) if options
|
129
|
+
fs_response = Unirest.post(
|
130
|
+
FilestackConfig::MULTIPART_UPLOAD_URL, parameters: data,
|
131
|
+
headers: FilestackConfig::HEADERS
|
132
|
+
).body
|
133
|
+
Unirest.put(
|
134
|
+
fs_response['url'], headers: fs_response['headers'], parameters: chunk
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Runs all jobs in parallel
|
139
|
+
#
|
140
|
+
# @param [Array] jobs Array of jobs to be run
|
141
|
+
# @param [String] apikey Filestack API key
|
142
|
+
# @param [String] filepath Local path to file
|
143
|
+
# @param [Hash] options User-defined options for
|
144
|
+
# multipart uploads
|
145
|
+
#
|
146
|
+
# @return [Array] Array of parts/etags strings
|
147
|
+
def run_uploads(jobs, apikey, filepath, options)
|
148
|
+
results = Parallel.map(jobs) do |job|
|
149
|
+
response = upload_chunk(
|
150
|
+
job, apikey, filepath, options
|
151
|
+
)
|
152
|
+
part = job[:part]
|
153
|
+
etag = response.headers[:etag]
|
154
|
+
"#{part}:#{etag}"
|
155
|
+
end
|
156
|
+
results
|
157
|
+
end
|
158
|
+
|
159
|
+
# Send complete call to multipart endpoint
|
160
|
+
#
|
161
|
+
# @param [String] apikey Filestack API key
|
162
|
+
# @param [String] filename Name of incoming file
|
163
|
+
# @param [Int] filesize Size of incoming file
|
164
|
+
# @param [String] mimetype Mimetype of incoming file
|
165
|
+
# @param [Unirest::Response] start_response Response body from
|
166
|
+
# multipart_start
|
167
|
+
# @param [FilestackSecurity] security Security object with
|
168
|
+
# policy/signature
|
169
|
+
# @param [Array] parts_and_etags Array of strings defining
|
170
|
+
# etags and their associated
|
171
|
+
# part numbers
|
172
|
+
# @param [Hash] options User-defined options for
|
173
|
+
# multipart uploads
|
174
|
+
#
|
175
|
+
# @return [Unirest::Response]
|
176
|
+
def multipart_complete(apikey, filename, filesize, mimetype, start_response, parts_and_etags, options)
|
177
|
+
data = {
|
178
|
+
apikey: apikey,
|
179
|
+
uri: start_response['uri'],
|
180
|
+
region: start_response['region'],
|
181
|
+
upload_id: start_response['upload_id'],
|
182
|
+
filename: filename,
|
183
|
+
size: filesize,
|
184
|
+
mimetype: mimetype,
|
185
|
+
parts: parts_and_etags.join(';'),
|
186
|
+
store_location: options.nil? ? 's3' : options[:store_location],
|
187
|
+
file: Tempfile.new(filename)
|
188
|
+
}
|
189
|
+
|
190
|
+
data = data.merge!(options) if options
|
191
|
+
|
192
|
+
Unirest.post(
|
193
|
+
FilestackConfig::MULTIPART_COMPLETE_URL, parameters: data,
|
194
|
+
headers: FilestackConfig::HEADERS
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Run entire multipart process through with file and options
|
199
|
+
#
|
200
|
+
# @param [String] apikey Filestack API key
|
201
|
+
# @param [String] filename Name of incoming file
|
202
|
+
# @param [FilestackSecurity] security Security object with
|
203
|
+
# policy/signature
|
204
|
+
# @param [Hash] options User-defined options for
|
205
|
+
# multipart uploads
|
206
|
+
#
|
207
|
+
# @return [Unirest::Response]
|
208
|
+
def multipart_upload(apikey, filepath, security, options)
|
209
|
+
filename, filesize, mimetype = get_file_info(filepath)
|
210
|
+
start_response = multipart_start(
|
211
|
+
apikey, filename, filesize, mimetype, security, options
|
212
|
+
)
|
213
|
+
jobs = create_upload_jobs(
|
214
|
+
apikey, filename, filepath, filesize, start_response, options
|
215
|
+
)
|
216
|
+
parts_and_etags = run_uploads(jobs, apikey, filepath, options)
|
217
|
+
response_complete = multipart_complete(
|
218
|
+
apikey, filename, filesize, mimetype,
|
219
|
+
start_response, parts_and_etags, options
|
220
|
+
)
|
221
|
+
response_complete.body
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest'
|
3
|
+
require 'mimemagic'
|
4
|
+
require 'json'
|
5
|
+
require 'unirest'
|
6
|
+
|
7
|
+
require 'filestack/config'
|
8
|
+
|
9
|
+
# Includes general utility functions for the Filestack Ruby SDK
|
10
|
+
module UploadUtils
|
11
|
+
# General request function
|
12
|
+
# @param [String] url The URL being called
|
13
|
+
# @param [String] action The specific HTTP action
|
14
|
+
# ('get', 'post', 'delete', 'put')
|
15
|
+
# @param [Hash] parameters The query and/or body parameters
|
16
|
+
#
|
17
|
+
# @return [Unirest::Request]
|
18
|
+
def make_call(url, action, parameters: nil, headers: nil)
|
19
|
+
headers = if headers
|
20
|
+
headers.merge!(FilestackConfig::HEADERS)
|
21
|
+
else
|
22
|
+
FilestackConfig::HEADERS
|
23
|
+
end
|
24
|
+
Unirest.public_send(
|
25
|
+
action, url, parameters: parameters, headers: headers
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Uploads to v1 REST API (for external URLs or if multipart is turned off)
|
30
|
+
#
|
31
|
+
# @param [String] apikey Filestack API key
|
32
|
+
# @param [String] filepath Local path to file
|
33
|
+
# @param [String] external_url External URL to be uploaded
|
34
|
+
# @param [FilestackSecurity] security Security object with
|
35
|
+
# policy/signature
|
36
|
+
# @param [Hash] options User-defined options for
|
37
|
+
# multipart uploads
|
38
|
+
# @param [String] storage Storage destination
|
39
|
+
# (s3, rackspace, etc)
|
40
|
+
# @return [Unirest::Response]
|
41
|
+
def send_upload(apikey, filepath: nil, external_url: nil, security: nil, options: nil, storage: 'S3')
|
42
|
+
data = if filepath
|
43
|
+
{ fileUpload: File.open(filepath) }
|
44
|
+
else
|
45
|
+
{ url: external_url }
|
46
|
+
end
|
47
|
+
|
48
|
+
# adds any user-defined upload options to request payload
|
49
|
+
data = data.merge!(options) unless options.nil?
|
50
|
+
base = "#{FilestackConfig::API_URL}/store/#{storage}?key=#{apikey}"
|
51
|
+
|
52
|
+
if security
|
53
|
+
policy = security.policy
|
54
|
+
signature = security.signature
|
55
|
+
base = "#{base}&signature=#{signature}&policy=#{policy}"
|
56
|
+
end
|
57
|
+
|
58
|
+
response = make_call(base, 'post', parameters: data)
|
59
|
+
if response.code == 200
|
60
|
+
handle = response.body['url'].split('/').last
|
61
|
+
return { 'handle' => handle }
|
62
|
+
end
|
63
|
+
raise response.body
|
64
|
+
end
|
65
|
+
|
66
|
+
# Generates the URL for a Filelink object
|
67
|
+
# @param [String] base The base Filestack URL
|
68
|
+
# @param [String] handle The Filelink handle (optional)
|
69
|
+
# @param [String] path The specific API path (optional)
|
70
|
+
# @param [String] security Security for the Filelink (optional)
|
71
|
+
#
|
72
|
+
# return [String]
|
73
|
+
def get_url(base, handle: nil, path: nil, security: nil)
|
74
|
+
url_components = [base]
|
75
|
+
|
76
|
+
url_components.push(path) unless path.nil?
|
77
|
+
url_components.push(handle) unless handle.nil?
|
78
|
+
url = url_components.join('/')
|
79
|
+
|
80
|
+
if security
|
81
|
+
policy = security.policy
|
82
|
+
signature = security.signature
|
83
|
+
security_path = "policy=#{policy}&signature=#{signature}"
|
84
|
+
url = "#{url}?#{security_path}"
|
85
|
+
end
|
86
|
+
url
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Utility functions for transformations
|
91
|
+
module TransformUtils
|
92
|
+
# Creates a transformation task to be sent back to transform object
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
def add_transform_task(transform, options = {})
|
96
|
+
options_list = []
|
97
|
+
if !options.empty?
|
98
|
+
options.each do |key, array|
|
99
|
+
options_list.push("#{key}:#{array}")
|
100
|
+
end
|
101
|
+
options_string = options_list.join(',')
|
102
|
+
"#{transform}=#{options_string}"
|
103
|
+
else
|
104
|
+
transform.to_s
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|