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.
@@ -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,5 @@
1
+ module Filestack
2
+ module Ruby
3
+ VERSION = '2.0.0'.freeze
4
+ end
5
+ 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