filestack 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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