cantemo-portal-agent 1.0.9 → 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.
- 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
|