cantemo-portal-agent 1.0.9 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/cantemo-portal-agent +8 -6
- data/lib/cantemo/portal/agent/WatchFolderUtility/foreman.rb +59 -0
- data/lib/cantemo/portal/agent/cli/commands/watch_folders-working.rb +237 -0
- data/lib/cantemo/portal/agent/cli/commands/watch_folders.rb +63 -0
- data/lib/cantemo/portal/agent/version.rb +1 -1
- data/lib/envoi/aspera/watch_service/client.rb +397 -0
- data/lib/envoi/aspera/watch_service/snapshot.rb +11 -0
- data/lib/envoi/aspera/watch_service/subscription.rb +0 -0
- data/lib/envoi/aspera/watch_service/watch_folder.rb +322 -0
- data/lib/envoi/mam/agent/cli/commands/cantemo-agent.rb +62 -0
- data/lib/envoi/mam/agent/cli/commands/cantemo-watch_folders.rb +41 -0
- data/lib/envoi/mam/agent/cli/commands/cantemo.rb +5 -0
- data/lib/envoi/mam/agent/cli/commands/iconik.rb +21 -11
- data/lib/envoi/mam/agent/cli/commands/mediasilo.rb +1 -1
- data/lib/envoi/mam/agent/cli/commands/vidispine.rb +7 -4
- data/lib/envoi/mam/agent/cli/commands/wiredrive.rb +15 -2
- data/lib/envoi/mam/agent/cli/commands.rb +4 -4
- data/lib/envoi/mam/agent/cli.rb +3 -3
- data/lib/envoi/mam/agent/transfer_client/aspera.rb +145 -7
- data/lib/envoi/mam/agent/version.rb +1 -1
- data/lib/envoi/mam/agent/watch_folder_utility/foreman.rb +76 -0
- data/lib/envoi/mam/agent.rb +6 -1
- data/lib/envoi/mam/cantemo/agent/watch_folder_handler-working.rb +111 -0
- data/lib/envoi/mam/cantemo/agent/watch_folder_handler.rb +176 -0
- data/lib/envoi/mam/cantemo/agent/watch_folder_handler_aspera.rb +112 -0
- data/lib/envoi/mam/cantemo/agent.rb +288 -0
- data/lib/envoi/mam/iconik/agent.rb +15 -3
- data/lib/envoi/mam/vidispine/agent.rb +6 -1
- data/lib/envoi/watch_folder_utility/watch_folder/handler/listen.rb +189 -0
- metadata +48 -4
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'envoi/mam/cantemo/agent'
|
2
|
+
require 'envoi/aspera/watch_service/watch_folder'
|
3
|
+
|
4
|
+
module Envoi::Mam::Cantemo
|
5
|
+
|
6
|
+
class Agent
|
7
|
+
|
8
|
+
class WatchFolderHandler
|
9
|
+
|
10
|
+
AWF = Envoi::Aspera::WatchService::WatchFolder
|
11
|
+
LWF = Envoi::WatchFolderUtility::WatchFolder::Listen
|
12
|
+
|
13
|
+
attr_accessor :logger, :agent, :config, :watch_folders
|
14
|
+
|
15
|
+
def initialize(args = { })
|
16
|
+
initialize_logger(args)
|
17
|
+
|
18
|
+
@agent = Envoi::Mam::Cantemo::Agent.load_from_config_file(args)
|
19
|
+
@config = agent.config
|
20
|
+
cantemo_config = config[:cantemo] || config['cantemo']
|
21
|
+
watch_folder_defs = cantemo_config[:watch_folders] || cantemo_config['watch_folders']
|
22
|
+
|
23
|
+
@ignored_file_paths_by_watch_folder = Hash.new { |h, k| h[k] = [] }
|
24
|
+
|
25
|
+
@watch_folders = AWF.process_watch_folder_defs(watch_folder_defs)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize_logger(args = { })
|
29
|
+
@logger = args[:logger] ||= Logger.new(args[:log_to] || STDOUT)
|
30
|
+
log_level = args[:log_level]
|
31
|
+
if log_level
|
32
|
+
@logger.level = log_level
|
33
|
+
args[:logger] = @logger
|
34
|
+
end
|
35
|
+
@logger
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_to_ignore(wf, file)
|
39
|
+
@ignored_file_paths_by_watch_folder[wf] << file.path
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_file(watch_folder, file, storage_id = nil, quarantine_directory_path = nil)
|
43
|
+
full_file_path = File.join(watch_folder.path, file.path)
|
44
|
+
if storage_id && agent.upload(file_path: full_file_path, storage_id: storage_id)
|
45
|
+
FileUtils.rm full_file_path
|
46
|
+
else
|
47
|
+
if Dir.exist?(quarantine_directory_path)
|
48
|
+
FileUtils.mv full_file_path, quarantine_directory_path
|
49
|
+
else
|
50
|
+
add_to_ignore(watch_folder, file)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_watch_folder(wf)
|
56
|
+
storage_id = wf.definition['upload_to_storage_id'] || wf.definition['storage_id']
|
57
|
+
quarantine_directory_path = wf.definition['quarantine_path']
|
58
|
+
exclude = wf.definition['exclude']
|
59
|
+
min_stable_poll_count = wf.definition['stable_poll_count'] || 3
|
60
|
+
|
61
|
+
maps = wf.state.details[:maps]
|
62
|
+
stable_paths = maps[:stable]
|
63
|
+
ignored_files = @ignored_file_paths_by_watch_folder[wf]
|
64
|
+
stable_paths.each do |fp, file|
|
65
|
+
if exclude
|
66
|
+
next if ignored_files.include?(file.path)
|
67
|
+
if [*exclude].find { |ep| File.fnmatch(ep, file.path) }
|
68
|
+
logger.debug { "Adding File to Ignore Cache: '#{file.path}'"}
|
69
|
+
ignored_files << file.path
|
70
|
+
next
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# full_file_path = File.join(wf.path, file.path)
|
75
|
+
|
76
|
+
# pp file
|
77
|
+
# puts file_path
|
78
|
+
stable_poll_count = file[:stable_poll_count]
|
79
|
+
|
80
|
+
if stable_poll_count && stable_poll_count > min_stable_poll_count
|
81
|
+
process_file(wf, file, storage_id, quarantine_directory_path)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# process_watch_folder
|
86
|
+
end
|
87
|
+
|
88
|
+
def run
|
89
|
+
# AWF.run_once(watch_folders) { |wf| pp wf }
|
90
|
+
AWF.run(watch_folders) { |wf| process_watch_folder(wf) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_once
|
94
|
+
AWF.run_once(watch_folders) { |wf| process_watch_folder(wf) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.run(args)
|
98
|
+
w = self.new(args)
|
99
|
+
w.run
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.run_as_daemon(args)
|
103
|
+
# ARGV.unshift 'run' unless %w(start stop restart run zap killall status).include? ARGV.first
|
104
|
+
require 'daemons'
|
105
|
+
Daemons.run_proc('cantemo-portal-watch-folders') { self.run(args) }
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,288 @@
|
|
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
|
+
module Cantemo
|
11
|
+
|
12
|
+
class Agent < Envoi::Mam::Agent
|
13
|
+
|
14
|
+
DEFAULT_SHAPE_TAG = 'original'
|
15
|
+
DEFAULT_DESTINATION_PATH = '.'
|
16
|
+
|
17
|
+
attr_accessor :default_aspera_ascp_args,
|
18
|
+
:default_aspera_ascp_path,
|
19
|
+
:default_vidispine_shape_tag
|
20
|
+
|
21
|
+
def after_initialize
|
22
|
+
args = initial_args
|
23
|
+
@default_aspera_ascp_path = args[:default_aspera_ascp_path]
|
24
|
+
@default_aspera_args = args[:default_ascp_args] || Envoi::Mam::Agent::TransferClient::Aspera::DEFAULT_ASCP_ARGS
|
25
|
+
end
|
26
|
+
|
27
|
+
def dry_run?; @dry_run end
|
28
|
+
|
29
|
+
def agent_config
|
30
|
+
@agent_config ||= config['cantemo'] || config['vidispine'] || { }
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize_api_client(args = { })
|
34
|
+
api_config = agent_config
|
35
|
+
@api_client = args[:api_client] || begin
|
36
|
+
|
37
|
+
api_host = api_config['host']
|
38
|
+
api_host_use_ssl = api_config['use_ssl']
|
39
|
+
api_port = api_config['port']
|
40
|
+
api_username = api_config['username']
|
41
|
+
api_password = api_config['password']
|
42
|
+
api_auth_token = api_config['api_auth_token']
|
43
|
+
api_base_path = api_config['api_base_path']
|
44
|
+
api_default_query_data = api_config['default_query_data']
|
45
|
+
|
46
|
+
api_url = api_config['url'] || api_config['uri']
|
47
|
+
if api_url
|
48
|
+
api_uri = URI(api_url)
|
49
|
+
api_host ||= api_uri.host
|
50
|
+
api_port ||= api_uri.port
|
51
|
+
api_userinfo = api_uri.userinfo
|
52
|
+
if api_userinfo
|
53
|
+
_api_username, _api_password = api_userinfo.split(':')
|
54
|
+
api_username ||= _api_username
|
55
|
+
api_password ||= _api_password
|
56
|
+
end
|
57
|
+
api_uri_query = api_uri.query
|
58
|
+
api_default_query_data ||= Hash[api_uri_query.split('&').map { |kp| kp.split('=') }]
|
59
|
+
api_base_path ||= api_uri.path
|
60
|
+
end
|
61
|
+
|
62
|
+
api_port ||= (api_host_use_ssl ? 443 : 80)
|
63
|
+
api_base_path ||= 'VSAPI/'
|
64
|
+
|
65
|
+
client_args = { }
|
66
|
+
client_args[:http_host_address] = api_host if api_host
|
67
|
+
client_args[:http_host_port] = api_port if api_port
|
68
|
+
client_args[:http_host_use_ssl] = api_host_use_ssl if api_host_use_ssl
|
69
|
+
client_args[:username] = api_username if api_username
|
70
|
+
client_args[:password] = api_password if api_password
|
71
|
+
client_args[:default_base_path] = api_base_path if api_base_path
|
72
|
+
client_args[:default_query_data] = api_default_query_data if api_default_query_data
|
73
|
+
|
74
|
+
if api_auth_token
|
75
|
+
# Cantemo Portal supports an auth token for authentication, replace basic auth with auth-token
|
76
|
+
client_args[:authorization_header_key] = 'Auth-Token'
|
77
|
+
client_args[:authorization_header_value] = api_auth_token
|
78
|
+
end
|
79
|
+
|
80
|
+
_client = ::Vidispine::API::Utilities.new(client_args)
|
81
|
+
_client
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
@default_vidispine_shape_tag = args[:default_shape_tag] || api_config['default_shape_tag'] || api_config['shape_tag'] || DEFAULT_SHAPE_TAG
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def item_get_shape_by_tag(item_id, shape_tag)
|
91
|
+
item_shapes_get_response = api_client.item_shapes_get(:item_id => item_id, :tag => shape_tag)
|
92
|
+
shape_id = item_shapes_get_response['uri'].first
|
93
|
+
end
|
94
|
+
|
95
|
+
def download(args = { })
|
96
|
+
|
97
|
+
item_id = args[:item_id]
|
98
|
+
shape_id = args[:shape_id]
|
99
|
+
unless shape_id && !shape_id.empty?
|
100
|
+
shape_tag = args[:shape_tag] || default_vidispine_shape_tag
|
101
|
+
shape_id = item_get_shape_by_tag(item_id, shape_tag)
|
102
|
+
end
|
103
|
+
|
104
|
+
logger.info { "Getting file path for Item ID: #{item_id} Shape ID: #{shape_id}"}
|
105
|
+
item_shape_get_response = api_client.item_shape_get(:item_id => item_id, :shape_id => shape_id)
|
106
|
+
|
107
|
+
files = item_shape_get_response['containerComponent']['file']
|
108
|
+
logger.debug { "Files: #{files}"}
|
109
|
+
|
110
|
+
# file = files.first
|
111
|
+
files = [ files.first ] # just do the first file for now
|
112
|
+
files.each do |file|
|
113
|
+
begin
|
114
|
+
download_file(args, file)
|
115
|
+
rescue => e
|
116
|
+
logger.warn { "Exception: #{$!}" }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
logger.info { 'DONE' }
|
120
|
+
end
|
121
|
+
|
122
|
+
def download_file(args, file)
|
123
|
+
logger.debug { "File: #{file}"}
|
124
|
+
transfer_type = args[:transfer_type]
|
125
|
+
|
126
|
+
file_storage_id = file['storage']
|
127
|
+
file_path = file['path']
|
128
|
+
|
129
|
+
file_storage_config = agent_config['storages'][file_storage_id]
|
130
|
+
|
131
|
+
unless file_storage_config && !file_storage_config.empty?
|
132
|
+
raise Exception, "No configuration found for storage '#{file_storage_id}'"
|
133
|
+
end
|
134
|
+
|
135
|
+
logger.info { "Transferring File Path: '#{file_path}'" }
|
136
|
+
preserve_path = args.fetch(:preserve_path, file_storage_config.fetch('preserve_path', true))
|
137
|
+
|
138
|
+
destination_path = args[:destination_path] || file_storage_config['destination_path'] || DEFAULT_DESTINATION_PATH
|
139
|
+
relative_path = preserve_path ? File.dirname(file_path) : nil
|
140
|
+
relative_path = nil if relative_path == '.'
|
141
|
+
|
142
|
+
target_path = relative_path ? File.join(destination_path, relative_path) : destination_path
|
143
|
+
target_path = target_path[0..-1] if target_path.start_with?('/') && !destination_path.start_with?('/')
|
144
|
+
|
145
|
+
aspera_config = file_storage_config['aspera']
|
146
|
+
if (transfer_type.empty? || transfer_type == :aspera) && (aspera_config && !aspera_config.empty?)
|
147
|
+
client = Envoi::Mam::Agent::TransferClient::Aspera.new(agent: self)
|
148
|
+
return client.download(aspera_config, file_path, target_path)
|
149
|
+
# download_using_aspera(aspera_config, file_path, target_path)
|
150
|
+
end
|
151
|
+
|
152
|
+
s3_config = file_storage_config['s3']
|
153
|
+
if (transfer_type.empty? || transfer_type == :s3) && (s3_config && !s3_config.empty?)
|
154
|
+
target_path = File.expand_path(target_path) if target_path == '.'
|
155
|
+
target_path = File.join(target_path, File.basename(file_path))
|
156
|
+
client = Envoi::Mam::Agent::TransferClient::S3.new(agent: self)
|
157
|
+
return client.download(s3_config, file_path, target_path)
|
158
|
+
end
|
159
|
+
|
160
|
+
logger.warn { "No Supported TransferClient Configuration#{transfer_type && !transfer_type.empty? ? " for transfer type '#{transfer_type}' " : ''}Found in Storage Configuration." }
|
161
|
+
end
|
162
|
+
|
163
|
+
def upload(args = { })
|
164
|
+
file_path = args[:file_path]
|
165
|
+
raise ArgumentError, "Path not found: '#{file_path}'" unless File.exists?(file_path)
|
166
|
+
|
167
|
+
if File.directory?(file_path)
|
168
|
+
# Non-recursive directory upload
|
169
|
+
file_paths = Dir.glob(File.join(file_path, '*.*'))
|
170
|
+
logger.debug { "File Paths: #{file_paths}"}
|
171
|
+
file_paths.map { |fp| upload(args.merge(file_path: fp))}
|
172
|
+
return file_paths
|
173
|
+
end
|
174
|
+
logger.debug { "Preparing to upload '#{file_path}'" }
|
175
|
+
|
176
|
+
transfer_type = args[:transfer_type] || ''
|
177
|
+
storage_id = args[:storage_id]
|
178
|
+
vidispine_storage_config = agent_config['storages'][storage_id]
|
179
|
+
|
180
|
+
unless vidispine_storage_config && !vidispine_storage_config.empty?
|
181
|
+
raise "No configuration found for storage '#{storage_id}'"
|
182
|
+
end
|
183
|
+
|
184
|
+
should_import_file = args.fetch(:import_file, vidispine_storage_config.fetch('import', true))
|
185
|
+
|
186
|
+
should_preserve_path = args.fetch(:preserve_path, vidispine_storage_config.fetch('preserve_path', true))
|
187
|
+
|
188
|
+
destination_path = args[:destination_path] || vidispine_storage_config['destination_path'] || '/'
|
189
|
+
relative_path = should_preserve_path ? File.dirname(file_path) : nil
|
190
|
+
relative_path = File.expand_path(relative_path) if relative_path == '.'
|
191
|
+
|
192
|
+
target_path = relative_path ? File.join(destination_path, relative_path) : destination_path
|
193
|
+
target_path = target_path[0..-1] if target_path.start_with?('/') && !destination_path.start_with?('/')
|
194
|
+
|
195
|
+
|
196
|
+
# upload file
|
197
|
+
|
198
|
+
transfer_response = begin
|
199
|
+
response = nil
|
200
|
+
aspera_config = vidispine_storage_config['aspera']
|
201
|
+
begin
|
202
|
+
if (transfer_type.empty? || transfer_type == :aspera) && (aspera_config && !aspera_config.empty?)
|
203
|
+
client = Envoi::Mam::Agent::TransferClient::Aspera.new(agent: self)
|
204
|
+
response = client.upload(aspera_config, file_path, target_path)
|
205
|
+
end
|
206
|
+
rescue => e
|
207
|
+
logger.error { "Aspera Transfer Failed. '#{e.message}'" }
|
208
|
+
end
|
209
|
+
|
210
|
+
s3_config = vidispine_storage_config['s3']
|
211
|
+
begin
|
212
|
+
if !response && (transfer_type.empty? || transfer_type == :s3) && (s3_config && !s3_config.empty?)
|
213
|
+
_target_path = target_path
|
214
|
+
_target_path = File.expand_path(_target_path) if target_path == '.'
|
215
|
+
_target_path = File.join(_target_path, File.basename(file_path))
|
216
|
+
client = Envoi::Mam::Agent::TransferClient::S3.new(agent: self)
|
217
|
+
response = client.upload(s3_config, file_path, _target_path)
|
218
|
+
end
|
219
|
+
rescue => e
|
220
|
+
logger.error { "S3 Transfer Failed. '#{e.message}'" }
|
221
|
+
end
|
222
|
+
|
223
|
+
response
|
224
|
+
end
|
225
|
+
|
226
|
+
logger.warn { "No supported TransferClient configuration#{transfer_type && !transfer_type.empty? ? " for transfer type '#{transfer_type}' " : ''}found in storage configuration." } unless transfer_response != nil
|
227
|
+
|
228
|
+
_response = { transfer_response: transfer_response }
|
229
|
+
|
230
|
+
return _response unless should_import_file
|
231
|
+
|
232
|
+
item_id = args[:item_id]
|
233
|
+
shape_tag = args[:shape_tag] || default_vidispine_shape_tag
|
234
|
+
|
235
|
+
# attach file to item as shape
|
236
|
+
path_on_storage = File.join(target_path, File.basename(file_path))
|
237
|
+
file_create_response = api_client.storage_file_create storage_id: storage_id, path: path_on_storage, state: 'CLOSED'
|
238
|
+
file_id = file_create_response['id']
|
239
|
+
|
240
|
+
if item_id
|
241
|
+
item_shape_import_response = api_client.item_shape_import item_id: item_id, tag: shape_tag, fileId: file_id
|
242
|
+
else
|
243
|
+
item_shape_import_response = api_client.item_add_using_file_path storage_id: storage_id,
|
244
|
+
file_path: file_path,
|
245
|
+
file_id: file_id,
|
246
|
+
storage_path_map: { '/' => storage_id }
|
247
|
+
end
|
248
|
+
_response[:import_response] = item_shape_import_response
|
249
|
+
|
250
|
+
_response
|
251
|
+
end
|
252
|
+
|
253
|
+
def upload_transfer_spec_get(file_paths)
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
|
259
|
+
def ingest_file
|
260
|
+
# 1) export ASPERA_SCP_TOKEN="ATB3_AEA9dUI5dd2u1k8XBMVD0qInR-Lyylkz8AylTXLVt5_SMVGR8SO7Sdc0lj6RkoHK_DvAAEWac8bllF_Yan1NbbDTyPj_3BTA"
|
261
|
+
# 2) ascp -i asperaweb_id_dsa.openssh -P 33001 -l 20m /Users/nicholasstokes/Desktop/CantemoAgent.mov xfer@ats-aws-us-west-2.aspera.io:
|
262
|
+
#
|
263
|
+
# single line equivalent
|
264
|
+
# ascp -i asperaweb_id_dsa.openssh -W 'ATB3_AEAiBdrf-7sMQu782sPoQ4Q7Yh4LadgvYK9BP4Kt1hcVlZGR8SO7Sdc0lj6RkoHK_DvAAG5XlBBIZGEINicYOxMRE7g_3BTA' -P 33001 -l 20m /Users/nicholasstokes/Desktop/CantemoAgent.mov xfer@ats-aws-us-west-2.aspera.io:
|
265
|
+
|
266
|
+
# Aspera:
|
267
|
+
# `GET /aspera/ping/` - to check if Aspera upload is available
|
268
|
+
# `POST /vs/item/new/` to create a placeholder to upload to
|
269
|
+
# `POST /aspera/upload_setup/` - Aspera token
|
270
|
+
# ... upload the file with Aspera ...
|
271
|
+
# `POST /API/v1/placeholder/VX-3012/associatefile/?filepath=Screen%20Shot%202018-08-07%20at%2013.06.58.png&storageid=VX-11&platform=mac` - associate new file to placeholder
|
272
|
+
|
273
|
+
# HTTP upload:
|
274
|
+
# `POST /vs/item/new/` - create a placeholder
|
275
|
+
# then streamind POST (?) requests to `VSAPI/item/VX-3014/shape/essence/raw?transferId=2D30030A-6EC3-4364-9598-5BF20972A218&throttle=false&filename=Screen%20Shot%202018-08-07%20at%2014.34.15.png&jobmetadata=portal_groups:StringArray%3dAdmin` to upload the data.
|
276
|
+
# that end-point for HTTP is documented in http://apidoc.vidispine.com/latest/ref/item/shape.html#import-a-shape-using-the-request-body
|
277
|
+
#
|
278
|
+
end
|
279
|
+
|
280
|
+
# Agent
|
281
|
+
end
|
282
|
+
|
283
|
+
# Cantemo
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
@@ -112,11 +112,13 @@ module Envoi
|
|
112
112
|
if File.directory?(file_path)
|
113
113
|
file_paths = Dir.glob(File.join(file_path, '*.*'))
|
114
114
|
logger.debug { "File Paths: #{file_paths}"}
|
115
|
-
file_paths.map { |fp| upload(args.merge(file_path: fp))}
|
116
|
-
return file_paths
|
115
|
+
return file_paths.map { |fp| upload(args.merge(file_path: fp))}
|
117
116
|
end
|
118
117
|
logger.debug { "Preparing to upload '#{file_path}'" }
|
119
118
|
|
119
|
+
ingest_after_upload = args[:ingest_after_upload]
|
120
|
+
analyze_asset = args[:analyze_asset]
|
121
|
+
|
120
122
|
transfer_type = args[:transfer_type] || ''
|
121
123
|
storage_id = args[:storage_id]
|
122
124
|
path_on_storage = nil
|
@@ -170,7 +172,17 @@ module Envoi
|
|
170
172
|
file_size = File.exist?(file_path) ? File.size?(file_path) : 1024
|
171
173
|
path_on_storage ||= File.join(target_path, File.basename(file_path))
|
172
174
|
|
173
|
-
|
175
|
+
if ingest_after_upload
|
176
|
+
asset_create_response = api_client.asset_add_using_file_path(file_path: path_on_storage,
|
177
|
+
storage_id: storage_id,
|
178
|
+
file_size: file_size)
|
179
|
+
if analyze_asset
|
180
|
+
asset_id = asset_create_response[:asset]['id']
|
181
|
+
api_client.asset_analyze(:asset_id => asset_id)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
174
186
|
end
|
175
187
|
|
176
188
|
|
@@ -34,12 +34,17 @@ module Envoi
|
|
34
34
|
def initialize_api_client(args = { })
|
35
35
|
_vidispine_config = vidispine_config
|
36
36
|
@api_client = args[:vidispine_api_client] || begin
|
37
|
-
|
37
|
+
|
38
38
|
vidispine_host = _vidispine_config['host']
|
39
|
+
vidispine_port = _vidispine_config['port']
|
40
|
+
vidispine_host_use_ssl = _vidispine_config['use_ssl']
|
39
41
|
vidispine_username = _vidispine_config['username']
|
40
42
|
vidispine_password = _vidispine_config['password']
|
43
|
+
|
41
44
|
client_args = { }
|
42
45
|
client_args[:http_host_address] = vidispine_host if vidispine_host
|
46
|
+
client_args[:http_host_port] = vidispine_port if vidispine_port
|
47
|
+
client_args[:http_host_use_ssl] = vidispine_host_use_ssl if vidispine_host_use_ssl
|
43
48
|
client_args[:username] = vidispine_username if vidispine_username
|
44
49
|
client_args[:password] = vidispine_password if vidispine_password
|
45
50
|
::Vidispine::API::Utilities.new(client_args)
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'listen' # https://github.com/guard/listen
|
2
|
+
require 'pp'
|
3
|
+
module Envoi
|
4
|
+
module WatchFolderUtility
|
5
|
+
class WatchFolder
|
6
|
+
class Handler
|
7
|
+
class Listen
|
8
|
+
|
9
|
+
attr_accessor :logger, :definition,
|
10
|
+
:known_path_map,
|
11
|
+
:last_poll_time,
|
12
|
+
:min_stable_poll_count,
|
13
|
+
:paths,
|
14
|
+
:poll_interval,
|
15
|
+
|
16
|
+
:listener, :lock,
|
17
|
+
|
18
|
+
|
19
|
+
:quarantine_directory_path
|
20
|
+
|
21
|
+
def initialize(args = { })
|
22
|
+
initialize_logger(args)
|
23
|
+
logger.debug { "Initializing Work Folder. Args: #{args}" }
|
24
|
+
|
25
|
+
@definition = args[:definition]
|
26
|
+
raise ArgumentError, "Definition is a required argument." unless definition
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
# @paths = [ File.expand_path('~/watch_folders/aspera') ]
|
31
|
+
@paths = definition['paths'] || []
|
32
|
+
path = definition['path']
|
33
|
+
@paths << path if path
|
34
|
+
|
35
|
+
@known_path_map = { }
|
36
|
+
@ignored_files_map = { }
|
37
|
+
|
38
|
+
@lock = Mutex.new
|
39
|
+
# @quarantine_directory_path = definition['quarantine_directory_path'] || definition['quarantine_path']
|
40
|
+
@poll_interval = definition['poll_interval'] || 15
|
41
|
+
@min_stable_poll_count = 3
|
42
|
+
@processing_limit = 10
|
43
|
+
@processing = { }
|
44
|
+
|
45
|
+
initialize_listener
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize_logger(args = { })
|
49
|
+
@logger = args[:logger] ||= Logger.new(args[:log_to] || STDOUT)
|
50
|
+
log_level = args[:log_level] || Logger::DEBUG
|
51
|
+
if log_level
|
52
|
+
@logger.level = log_level
|
53
|
+
args[:logger] = @logger
|
54
|
+
end
|
55
|
+
@logger
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_add_or_modified_path(path, event_type)
|
59
|
+
return path.each { |path| process_add_or_modified_path(path, event_type) } if path.is_a?(Array)
|
60
|
+
logger.debug { "PATH #{event_type} '#{path}'" }
|
61
|
+
lock.synchronize { known_path_map[path] = OpenStruct.new({ type: event_type, path: path, stable_poll_count: 0, timestamp: Time.now}) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize_listener(args = { })
|
65
|
+
# @directories = directories.map do |directory|
|
66
|
+
# Pathname.new(directory.to_s).realpath
|
67
|
+
# end
|
68
|
+
|
69
|
+
|
70
|
+
@listener = ::Listen.to(*paths, {}) do |m, a, r|
|
71
|
+
if !r.empty?
|
72
|
+
log "#{r.length} file(s) removed."
|
73
|
+
lock.synchronize { r.each { |f| known_path_map.delete(f) } }
|
74
|
+
end
|
75
|
+
|
76
|
+
if !m.empty?
|
77
|
+
log "#{m.length} file(s) modified."
|
78
|
+
lock.synchronize { process_add_or_modified_path(m, :modified) }
|
79
|
+
end
|
80
|
+
|
81
|
+
if !a.empty?
|
82
|
+
log "#{a.length} file(s) added."
|
83
|
+
lock.synchronize { process_add_or_modified_path(a, :added) }
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
_config = @listener.instance_variable_get(:@backend).instance_variable_get(:@adapter).config
|
88
|
+
|
89
|
+
_snapshots ||= {}
|
90
|
+
# change_config = ::Listen::Change::Config.new(_config.queue, _config.silencer)
|
91
|
+
_config.directories.each do |dir|
|
92
|
+
record = ::Listen::Record.new(dir)
|
93
|
+
record.build
|
94
|
+
record.instance_variable_get(:@tree).each do |rel_path, fstats|
|
95
|
+
_path = File.join(dir, rel_path)
|
96
|
+
process_add_or_modified_path(_path, :added)
|
97
|
+
end
|
98
|
+
# snapshot = ::Listen::Change.new(change_config, record)
|
99
|
+
# _config.optimize_changes(snapshot)
|
100
|
+
# _snapshots[dir] = snapshot
|
101
|
+
end
|
102
|
+
|
103
|
+
# pp _snapshots
|
104
|
+
end
|
105
|
+
|
106
|
+
def log(message)
|
107
|
+
puts "#{Time.now} #{message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_to_ignore(file)
|
111
|
+
@ignored_files_map[file.path] = file
|
112
|
+
end
|
113
|
+
|
114
|
+
# def handle_stable_file(file, quarantine_directory_path = @quarantine_directory_path)
|
115
|
+
# full_file_path = file[:path]
|
116
|
+
# logger.info { "Processing File Path: #{full_file_path}..." }
|
117
|
+
# if true
|
118
|
+
# FileUtils.rm full_file_path
|
119
|
+
# else
|
120
|
+
# if quarantine_directory_path
|
121
|
+
# FileUtils.mv full_file_path, quarantine_directory_path
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
|
126
|
+
def poll
|
127
|
+
@previous_poll_time = @last_poll_time
|
128
|
+
@last_poll_time = Time.now
|
129
|
+
stable_files = []
|
130
|
+
lock.synchronize do
|
131
|
+
@known_path_map.each do |fp, f|
|
132
|
+
logger.debug { "Incrementing Stable Count: #{f.inspect}" }
|
133
|
+
f[:stable_poll_count] += 1
|
134
|
+
if f[:stable_poll_count] >= @min_stable_poll_count
|
135
|
+
stable_files << f
|
136
|
+
# handle_stable_file(f)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
stable_files
|
141
|
+
end
|
142
|
+
|
143
|
+
def run(options = { })
|
144
|
+
logger.info { "Starting handler for #{paths}" }
|
145
|
+
listener.start
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# watcher = Envoi::WatchFolderUtility::WatchFolder::Handler::Listen.new
|
156
|
+
# watcher.run
|
157
|
+
#
|
158
|
+
#
|
159
|
+
#
|
160
|
+
#
|
161
|
+
# class WatchFolder
|
162
|
+
#
|
163
|
+
# attr_accessor :definition, :state, :client, :watcher,
|
164
|
+
# :previous_poll_time, :last_poll_time
|
165
|
+
#
|
166
|
+
# def initialize(args = { })
|
167
|
+
#
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# def poll(&block)
|
171
|
+
#
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# def run_once(&block)
|
175
|
+
# poll(&block)
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# def run(poll_interval = 15, &block)
|
179
|
+
# loop do
|
180
|
+
# begin
|
181
|
+
# run_once(&block)
|
182
|
+
# sleep poll_interval
|
183
|
+
# rescue SystemExit, Interrupt
|
184
|
+
# break
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# end
|