envoi-mam-agent 1.1.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,286 @@
1
+ require 'net/https'
2
+ require 'faraday'
3
+
4
+ require 'ubiquity/mediasilo/api/v3/utilities'
5
+ require 'envoi/mam/agent/transfer_client/aspera'
6
+
7
+ module Envoi
8
+
9
+ module Mam
10
+
11
+ class MediaSilo
12
+
13
+ attr_accessor :logger, :config, :api_client
14
+
15
+ class Agent < Envoi::Mam::Agent
16
+
17
+ DEFAULT_DOWNLOAD_DESTINATION_PATH = '.'
18
+
19
+ class CaseSensitiveHeaderKey < String
20
+ # def downcase; self end
21
+ def capitalize; self end
22
+ end
23
+
24
+ class File < ::File
25
+ def bytesize; size end
26
+ end
27
+
28
+ def initialize_api_client(args = { })
29
+ @api_client = args[:api_client] || begin
30
+ ms_config = config['mediasilo']
31
+ mediasilo_hostname = ms_config['hostname']
32
+ mediasilo_username = ms_config['username']
33
+ mediasilo_password = ms_config['password']
34
+ mediasilo_api_key = ms_config['api_key']
35
+
36
+ client_args = { }
37
+ client_args[:hostname] = mediasilo_hostname if mediasilo_hostname
38
+ client_args[:username] = mediasilo_username if mediasilo_username
39
+ client_args[:password] = mediasilo_password if mediasilo_password
40
+ client_args[:api_key] = mediasilo_api_key if mediasilo_api_key
41
+ Ubiquity::MediaSilo::API::V3::Utilities.new(client_args)
42
+ end
43
+
44
+ end
45
+
46
+ def download(args = { })
47
+ transfer_type = args[:transfer_type]
48
+
49
+ file_path = args[:file_path]
50
+ project_id = args[:project_id]
51
+ folder_id = args[:folder_id]
52
+ asset_id = args[:asset_id]
53
+
54
+ mediasilo_path = args[:mediasilo_path]
55
+ project_name = args[:project_name]
56
+ folder_name = args[:folder_name]
57
+
58
+ if mediasilo_path || project_name || folder_name
59
+ check_path_result = api_client.check_path(mediasilo_path)
60
+ found = check_path_result[:existing]
61
+
62
+ project = found[:project] || { }
63
+ project_id = project['id']
64
+
65
+ folders = found[:folders]
66
+ folder_id = (folders.last || { })['id']
67
+
68
+ asset = found[:asset] || { }
69
+ asset_id = asset['id']
70
+ end
71
+
72
+ asset ||= asset_id
73
+
74
+ destination_file_path = args[:destination_path] || DEFAULT_DOWNLOAD_DESTINATION_PATH
75
+
76
+ case transfer_type
77
+ when :aspera; download_using_aspera(asset_id, destination_file_path, args)
78
+ else
79
+ do_upload_response = download_using_http(asset, destination_file_path, args)
80
+ asset_url = do_upload_response[:asset_url]
81
+ end
82
+
83
+ end
84
+
85
+ def download_using_aspera(asset_id, destination_file_path, args = { })
86
+ derivative_type = args[:asset_derivative_type]
87
+ derivative_type = derivative_type.downcase == 'source' ? 'source' : 'proxy'
88
+ ticket = api_client.aspera_file_download_ticket_create(:asset_id => asset_id, :target => derivative_type)
89
+ return false unless ticket.is_a?(Hash)
90
+
91
+ host = ticket['server']
92
+ username = ticket['username']
93
+ password = ticket['password']
94
+ token = ticket['token']
95
+
96
+ original_file_name = ticket['fileName']
97
+
98
+ file_path = ticket['path']
99
+ target_path = destination_file_path
100
+
101
+ FileUtils.mkdir_p(target_path) if target_path.end_with?('/') && !File.directory?(target_path)
102
+ if target_path.end_with?('/') || File.directory?(target_path)
103
+ target_path = File.join(target_path, original_file_name)
104
+ end
105
+
106
+ aspera_config = { }
107
+ aspera_config['host'] = host
108
+ aspera_config['username'] = username
109
+ aspera_config['password'] = password
110
+ aspera_config['token'] = token
111
+
112
+ client = Envoi::Mam::Agent::TransferClient::Aspera.new(agent: self)
113
+ client.download(aspera_config, file_path, target_path)
114
+ end
115
+
116
+ def download_using_http(asset, destination_file_path, args)
117
+ overwrite = args.fetch(:overwrite, false)
118
+ derivative_type = args[:asset_derivative_type]
119
+ api_client.asset_download_derivative(derivative_type, asset, destination_file_path, overwrite)
120
+ end
121
+
122
+
123
+ def upload(args = { })
124
+ file_path = args[:file_path]
125
+ raise "File not found: '#{file_path}'" unless File.exists?(file_path)
126
+
127
+ if File.directory?(file_path)
128
+ file_paths = Dir.glob(File.join(file_path, '*.*'))
129
+ logger.debug { "File Paths: #{file_paths}"}
130
+ file_paths.map { |fp| upload(args.merge(file_path: fp))}
131
+ return file_paths
132
+ end
133
+ logger.debug { "Preparing to upload '#{file_path}'" }
134
+
135
+ transfer_type = args[:transfer_type]
136
+ project_id = args[:project_id]
137
+ folder_id = args[:folder_id]
138
+ # asset_id = args[:asset_id]
139
+
140
+ mediasilo_path = args[:mediasilo_path]
141
+ project_name = args[:project_name]
142
+ folder_name = args[:folder_name]
143
+
144
+ if mediasilo_path || project_name || folder_name
145
+ mediasilo_path ||= File.join(project_name, folder_name)
146
+
147
+ check_path_result = api_client.check_path(mediasilo_path, false)
148
+ found = check_path_result[:existing]
149
+
150
+ project = found[:project] || { }
151
+ project_id = project['id']
152
+
153
+ folders = found[:folders]
154
+ folder_id = (folders.last || { })['id']
155
+ end
156
+
157
+ # # preserve_path = args.fetch(:preserve_path, vidispine_storage_true)
158
+ # preserve_path = args.fetch(:preserve_path, true)
159
+ #
160
+ # destination_path = args[:destination_path] || DEFAULT_DESTINATION_PATH
161
+ # relative_path = preserve_path ? File.dirname(file_path) : nil
162
+ # relative_path = nil if relative_path == '.'
163
+ #
164
+ # target_path = relative_path ? File.join(destination_path, relative_path) : destination_path
165
+ # target_path = target_path[0..-1] if target_path.start_with?('/') && !destination_path.start_with?('/')
166
+
167
+ # upload file
168
+ case transfer_type
169
+ when :aspera; do_upload_response = upload_using_aspera(file_path, args)
170
+ else
171
+ do_upload_response = upload_using_http(file_path)
172
+ asset_url = do_upload_response[:asset_url]
173
+ end
174
+
175
+ return false unless do_upload_response[:success]
176
+
177
+ asset_create_args = {
178
+ 'projectId' => project_id,
179
+ 'folderId' => folder_id,
180
+ :source_url => asset_url
181
+ }
182
+ response = api_client.asset_create(asset_create_args)
183
+ end
184
+
185
+ def upload_using_aspera(file_path, args = { })
186
+ raise 'Upload using Aspera not yet Implemented.'
187
+
188
+ file_name = File.basename(file_path)
189
+
190
+ ticket = api_client.aspera_file_upload_ticket_create(:file_name => file_name)
191
+
192
+ host = ticket['server']
193
+ username = ticket['username']
194
+ password = ticket['password']
195
+ token = ticket['token']
196
+ destination = ticket['destination']
197
+
198
+ target_path = destination
199
+
200
+ aspera_config = { }
201
+ aspera_config['host'] = host
202
+ aspera_config['username'] = username
203
+ aspera_config['password'] = password
204
+ aspera_config['token'] = token
205
+
206
+ client = AsperaTransferClient.new(agent: self)
207
+ client.upload(aspera_config, file_path, target_path)
208
+ { :success => true }
209
+ end
210
+
211
+ def upload_using_faraday(url, file_path, options = { })
212
+ headers = options[:headers]
213
+
214
+ uri = URI(url)
215
+ connection_url = "#{uri.scheme}://#{uri.host}:#{uri.port}"
216
+ conn = Faraday.new(connection_url, { headers: headers, ssl: { } }) do |f|
217
+ f.adapter :net_http
218
+ end
219
+ payload = Faraday::UploadIO.new(file_path, headers['Content-Type'])
220
+ response = conn.put do |req|
221
+ req.url uri.path
222
+ req.body = payload
223
+ req.headers = headers
224
+ end
225
+ end
226
+
227
+ def upload_using_http(file_path)
228
+ file_name = File.basename(file_path)
229
+
230
+ res = api_client.asset_upload_ticket_create(:file_name => file_name)
231
+ uri_str = res['assetUrl']
232
+
233
+ uri = URI.parse(uri_str)
234
+
235
+ amz_date = res['amzDate']
236
+ amz_acl = res['amzAcl']
237
+ content_type = res['contentType']
238
+ authorization = res['authorization']
239
+
240
+ # file = FileToSend.open(file_path)
241
+ #
242
+ file = File.open(file_path)
243
+
244
+ headers = {
245
+ 'x-amz-date' => amz_date,
246
+ 'x-amz-acl' => amz_acl,
247
+ 'Content-Type' => content_type,
248
+ 'Authorization' => authorization,
249
+ 'Content-Length' => file.size.to_s
250
+ }
251
+
252
+
253
+ response = upload_using_faraday(uri_str, file_path, { headers: headers })
254
+
255
+ unless response
256
+ req = Net::HTTP::Put.new(uri.request_uri)
257
+ req['x-amz-date'] = amz_date
258
+ req['x-amz-acl'] = amz_acl
259
+ req[CaseSensitiveHeaderKey.new('Content-Type')] = content_type
260
+ res[CaseSensitiveHeaderKey.new('Authorization')] = authorization
261
+ # req[CaseSensitiveHeaderKey.new('Transfer-Encoding')] = 'chunked'
262
+ req[CaseSensitiveHeaderKey.new('Content-Length')] = file.size
263
+
264
+ req.body_stream = file
265
+ # req.body = file
266
+
267
+ request = req
268
+ http = Net::HTTP.new(uri.host, uri.port)
269
+ http.use_ssl = true
270
+ logger.debug { %(REQUEST: #{request.method} https://#{http.address}:#{http.port}#{request.path} HEADERS: #{request.to_hash.inspect} #{api_client.http_client.log_request_body and request.request_body_permitted? ? "BODY: #{api_client.http_client.format_body_for_log_output(request)}" : ''}) }
271
+
272
+ response = res = http.request(req)
273
+ end
274
+ logger.debug { %(RESPONSE: #{response.inspect})} #HEADERS: #{response.to_hash.inspect} #{api_client.http_client.log_response_body and response.respond_to?(:body) ? "BODY: #{api_client.http_client.format_body_for_log_output(response)}" : ''}) }
275
+ response_code = response.respond_to?(:status) ? response.status.to_s : response.code
276
+ { :success => response_code.start_with?('2'), asset_url: uri_str }
277
+ end
278
+
279
+
280
+ end
281
+
282
+ end
283
+
284
+ end
285
+
286
+ end
@@ -0,0 +1,214 @@
1
+ require 'envoi/mam/agent/transfer_client/aspera'
2
+ require 'envoi/mam/agent/transfer_client/s3'
3
+
4
+ require 'vidispine/api/utilities'
5
+
6
+ module Envoi
7
+
8
+ module Mam
9
+
10
+ class Vidispine
11
+
12
+ class Agent < Envoi::Mam::Agent
13
+
14
+ DEFAULT_SHAPE_TAG = 'original'
15
+ DEFAULT_ASPERA_ARGS = '-v -k3 --overwrite=diff -P 33001'
16
+ DEFAULT_DESTINATION_PATH = '.'
17
+
18
+ attr_accessor :default_aspera_ascp_args,
19
+ :default_aspera_ascp_path,
20
+ :default_vidispine_shape_tag
21
+
22
+ def after_initialize
23
+ args = initial_args
24
+ @default_aspera_ascp_path = args[:default_aspera_ascp_path]
25
+ @default_aspera_args = args[:default_ascp_args] || DEFAULT_ASPERA_ARGS
26
+ end
27
+
28
+ def dry_run?; @dry_run end
29
+
30
+ def vidispine_config
31
+ config['vidispine'] || { }
32
+ end
33
+
34
+ def initialize_api_client(args = { })
35
+ _vidispine_config = vidispine_config
36
+ @api_client = args[:vidispine_api_client] || begin
37
+
38
+ vidispine_host = _vidispine_config['host']
39
+ vidispine_username = _vidispine_config['username']
40
+ vidispine_password = _vidispine_config['password']
41
+ client_args = { }
42
+ client_args[:http_host_address] = vidispine_host if vidispine_host
43
+ client_args[:username] = vidispine_username if vidispine_username
44
+ client_args[:password] = vidispine_password if vidispine_password
45
+ ::Vidispine::API::Utilities.new(client_args)
46
+ end
47
+
48
+ @default_vidispine_shape_tag = args[:default_shape_tag] || _vidispine_config['default_shape_tag'] || _vidispine_config['shape_tag'] || DEFAULT_SHAPE_TAG
49
+
50
+ end
51
+
52
+ def item_get_shape_by_tag(item_id, shape_tag)
53
+ item_shapes_get_response = api_client.item_shapes_get(:item_id => item_id, :tag => shape_tag)
54
+ shape_id = item_shapes_get_response['uri'].first
55
+ end
56
+
57
+ def download(args = { })
58
+
59
+ item_id = args[:item_id]
60
+ shape_id = args[:shape_id]
61
+ unless shape_id && !shape_id.empty?
62
+ shape_tag = args[:shape_tag] || default_vidispine_shape_tag
63
+ shape_id = item_get_shape_by_tag(item_id, shape_tag)
64
+ end
65
+
66
+ logger.info { "Getting file path for Item ID: #{item_id} Shape ID: #{shape_id}"}
67
+ item_shape_get_response = api_client.item_shape_get(:item_id => item_id, :shape_id => shape_id)
68
+
69
+ files = item_shape_get_response['containerComponent']['file']
70
+ logger.debug { "Files: #{files}"}
71
+
72
+ # file = files.first
73
+ files = [ files.first ] # just do the first file for now
74
+ files.each do |file|
75
+ begin
76
+ download_file(args, file)
77
+ rescue => e
78
+ logger.warn { "Exception: #{$!}" }
79
+ end
80
+ end
81
+ logger.info { 'DONE' }
82
+ end
83
+
84
+ def download_file(args, file)
85
+ logger.debug { "File: #{file}"}
86
+ transfer_type = args[:transfer_type]
87
+
88
+ file_storage_id = file['storage']
89
+ file_path = file['path']
90
+
91
+ file_storage_config = vidispine_config['storages'][file_storage_id]
92
+
93
+ unless file_storage_config && !file_storage_config.empty?
94
+ raise Exception, "No configuration found for storage '#{file_storage_id}'"
95
+ end
96
+
97
+ logger.info { "Transferring File Path: '#{file_path}'" }
98
+ preserve_path = args.fetch(:preserve_path, file_storage_config.fetch('preserve_path', true))
99
+
100
+ destination_path = args[:destination_path] || file_storage_config['destination_path'] || DEFAULT_DESTINATION_PATH
101
+ relative_path = preserve_path ? File.dirname(file_path) : nil
102
+ relative_path = nil if relative_path == '.'
103
+
104
+ target_path = relative_path ? File.join(destination_path, relative_path) : destination_path
105
+ target_path = target_path[0..-1] if target_path.start_with?('/') && !destination_path.start_with?('/')
106
+
107
+ aspera_config = file_storage_config['aspera']
108
+ if (transfer_type.empty? || transfer_type == :aspera) && (aspera_config && !aspera_config.empty?)
109
+ client = Envoi::Mam::Agent::TransferClient::Aspera.new(agent: self)
110
+ return client.download(aspera_config, file_path, target_path)
111
+ # download_using_aspera(aspera_config, file_path, target_path)
112
+ end
113
+
114
+ s3_config = file_storage_config['s3']
115
+ if (transfer_type.empty? || transfer_type == :s3) && (s3_config && !s3_config.empty?)
116
+ target_path = File.expand_path(target_path) if target_path == '.'
117
+ target_path = File.join(target_path, File.basename(file_path))
118
+ client = Envoi::Mam::Agent::TransferClient::S3.new(agent: self)
119
+ return client.download(s3_config, file_path, target_path)
120
+ end
121
+
122
+ logger.warn { "No Supported TransferClient Configuration#{transfer_type && !transfer_type.empty? ? " for transfer type '#{transfer_type}' " : ''}Found in Storage Configuration." }
123
+ end
124
+
125
+ def upload(args = { })
126
+ file_path = args[:file_path]
127
+ raise ArgumentError, "Path not found: '#{file_path}'" unless File.exists?(file_path)
128
+
129
+ if File.directory?(file_path)
130
+ # Non-recursive directory upload
131
+ file_paths = Dir.glob(File.join(file_path, '*.*'))
132
+ logger.debug { "File Paths: #{file_paths}"}
133
+ file_paths.map { |fp| upload(args.merge(file_path: fp))}
134
+ return file_paths
135
+ end
136
+ logger.debug { "Preparing to upload '#{file_path}'" }
137
+
138
+ transfer_type = args[:transfer_type] || ''
139
+ storage_id = args[:storage_id]
140
+ vidispine_storage_config = vidispine_config['storages'][storage_id]
141
+
142
+ unless vidispine_storage_config && !vidispine_storage_config.empty?
143
+ raise "No configuration found for storage '#{storage_id}'"
144
+ end
145
+
146
+ should_import_file = args.fetch(:import_file, vidispine_storage_config.fetch('import', true))
147
+
148
+ should_preserve_path = args.fetch(:preserve_path, vidispine_storage_config.fetch('preserve_path', true))
149
+
150
+ destination_path = args[:destination_path] || vidispine_storage_config['destination_path'] || '/'
151
+ relative_path = should_preserve_path ? File.dirname(file_path) : nil
152
+ relative_path = File.expand_path(relative_path) if relative_path == '.'
153
+
154
+ target_path = relative_path ? File.join(destination_path, relative_path) : destination_path
155
+ target_path = target_path[0..-1] if target_path.start_with?('/') && !destination_path.start_with?('/')
156
+
157
+
158
+ # upload file
159
+
160
+ transfer_response = begin
161
+ aspera_config = vidispine_storage_config['aspera']
162
+ if (transfer_type.empty? || transfer_type == :aspera) && (aspera_config && !aspera_config.empty?)
163
+ client = Envoi::Mam::Agent::TransferClient::Aspera.new(agent: self)
164
+ response = client.upload(aspera_config, file_path, target_path)
165
+ end
166
+
167
+ s3_config = vidispine_storage_config['s3']
168
+ if !response && (transfer_type.empty? || transfer_type == :s3) && (s3_config && !s3_config.empty?)
169
+ _target_path = target_path
170
+ _target_path = File.expand_path(_target_path) if target_path == '.'
171
+ _target_path = File.join(_target_path, File.basename(file_path))
172
+ client = Envoi::Mam::Agent::TransferClient::S3.new(agent: self)
173
+ response = client.upload(s3_config, file_path, _target_path)
174
+ end
175
+
176
+ response
177
+ end
178
+
179
+ logger.warn { "No supported TransferClient configuration#{transfer_type && !transfer_type.empty? ? " for transfer type '#{transfer_type}' " : ''}found in storage configuration." } unless transfer_response != nil
180
+
181
+ _response = { transfer_response: transfer_response }
182
+
183
+ return _response unless should_import_file
184
+
185
+ item_id = args[:item_id]
186
+ shape_tag = args[:shape_tag] || default_vidispine_shape_tag
187
+
188
+ # attach file to item as shape
189
+ path_on_storage = File.join(target_path, File.basename(file_path))
190
+ file_create_response = api_client.storage_file_create storage_id: storage_id, path: path_on_storage, state: 'CLOSED'
191
+ file_id = file_create_response['id']
192
+
193
+ if item_id
194
+ item_shape_import_response = vs_api.item_shape_import item_id: item_id, tag: shape_tag, fileId: file_id
195
+ else
196
+ item_shape_import_response = vs_api.item_add_using_file_path storage_id: storage_id,
197
+ file_path: file_path,
198
+ file_id: file_id,
199
+ storage_path_map: { '/' => storage_id }
200
+ end
201
+ _response[:import_response] = item_shape_import_response
202
+
203
+ _response
204
+ end
205
+
206
+ # Agent
207
+ end
208
+
209
+ # Vidispine
210
+ end
211
+
212
+ end
213
+
214
+ end