aspera-cli 4.20.0 → 4.21.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +20 -2
- data/README.md +281 -156
- data/bin/asession +2 -2
- data/lib/aspera/agent/alpha.rb +7 -12
- data/lib/aspera/agent/connect.rb +19 -1
- data/lib/aspera/agent/direct.rb +20 -29
- data/lib/aspera/agent/node.rb +1 -11
- data/lib/aspera/agent/trsdk.rb +4 -25
- data/lib/aspera/api/aoc.rb +5 -0
- data/lib/aspera/api/node.rb +45 -28
- data/lib/aspera/ascp/installation.rb +69 -38
- data/lib/aspera/ascp/management.rb +27 -6
- data/lib/aspera/cli/formatter.rb +149 -141
- data/lib/aspera/cli/info.rb +1 -1
- data/lib/aspera/cli/manager.rb +1 -0
- data/lib/aspera/cli/plugin.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +27 -17
- data/lib/aspera/cli/plugins/config.rb +31 -21
- data/lib/aspera/cli/plugins/faspex.rb +1 -1
- data/lib/aspera/cli/plugins/faspex5.rb +11 -3
- data/lib/aspera/cli/plugins/node.rb +44 -38
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +1 -1
- data/lib/aspera/environment.rb +5 -6
- data/lib/aspera/node_simulator.rb +228 -112
- data/lib/aspera/oauth/base.rb +31 -42
- data/lib/aspera/oauth/factory.rb +41 -2
- data/lib/aspera/persistency_folder.rb +20 -2
- data/lib/aspera/preview/generator.rb +1 -1
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/alpha.rb +30 -0
- data/lib/aspera/products/connect.rb +48 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/trsdk.rb +54 -0
- data/lib/aspera/rest.rb +18 -13
- data/lib/aspera/ssh.rb +28 -24
- data/lib/aspera/transfer/spec.yaml +22 -20
- data.tar.gz.sig +0 -0
- metadata +21 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/ascp/products.rb +0 -168
data/bin/asession
CHANGED
@@ -6,7 +6,7 @@ Kernel.load(File.join(gem_lib_folder, 'aspera/coverage.rb'))
|
|
6
6
|
$LOAD_PATH.unshift(gem_lib_folder)
|
7
7
|
require 'aspera/agent/direct'
|
8
8
|
require 'aspera/cli/extended_value'
|
9
|
-
require 'aspera/
|
9
|
+
require 'aspera/products/trsdk'
|
10
10
|
require 'aspera/log'
|
11
11
|
require 'json'
|
12
12
|
# extended transfer spec parameter (only used in asession)
|
@@ -77,7 +77,7 @@ if session_spec.key?(PARAM_TMP_FILE_LIST_FOLDER)
|
|
77
77
|
Aspera::Transfer::Parameters.file_list_folder = session_spec[PARAM_TMP_FILE_LIST_FOLDER]
|
78
78
|
end
|
79
79
|
session_spec[PARAM_SDK] = File.join(Dir.home, '.aspera', 'sdk') unless session_spec.key?(PARAM_SDK)
|
80
|
-
Aspera::
|
80
|
+
Aspera::Products::Trsdk.sdk_directory = session_spec[PARAM_SDK]
|
81
81
|
session_spec[PARAM_AGENT] = {} unless session_spec.key?(PARAM_AGENT)
|
82
82
|
agent_params = session_spec[PARAM_AGENT]
|
83
83
|
agent_params['quiet'] = true
|
data/lib/aspera/agent/alpha.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'aspera/agent/base'
|
4
4
|
require 'aspera/rest'
|
5
|
-
require 'aspera/log'
|
6
|
-
require 'aspera/json_rpc'
|
7
5
|
require 'aspera/environment'
|
6
|
+
require 'aspera/json_rpc'
|
7
|
+
require 'aspera/products/alpha'
|
8
8
|
require 'securerandom'
|
9
9
|
|
10
10
|
module Aspera
|
@@ -15,9 +15,8 @@ module Aspera
|
|
15
15
|
START_URIS = ['aspera://', 'aspera://', 'aspera://']
|
16
16
|
# delay between each try to start the app
|
17
17
|
SLEEP_SEC_BETWEEN_RETRY = 5
|
18
|
-
APP_IDENTIFIER = 'com.ibm.software.aspera.desktop'
|
19
|
-
APP_NAME = 'Aspera Desktop Alpha Client'
|
20
18
|
private_constant :START_URIS, :SLEEP_SEC_BETWEEN_RETRY
|
19
|
+
|
21
20
|
def initialize(**base_options)
|
22
21
|
@application_id = SecureRandom.uuid
|
23
22
|
@xfer_id = nil
|
@@ -34,23 +33,19 @@ module Aspera
|
|
34
33
|
rescue Errno::ECONNREFUSED => e
|
35
34
|
start_url = START_URIS[method_index]
|
36
35
|
method_index += 1
|
37
|
-
raise StandardError, "Unable to start #{APP_NAME} #{method_index} times" if start_url.nil?
|
38
|
-
Log.log.warn{"#{APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
|
36
|
+
raise StandardError, "Unable to start #{Products::Alpha::APP_NAME} #{method_index} times" if start_url.nil?
|
37
|
+
Log.log.warn{"#{Products::Alpha::APP_NAME} is not started (#{e}). Trying to start it ##{method_index}..."}
|
39
38
|
if !Environment.open_uri_graphical(start_url)
|
40
39
|
Environment.open_uri_graphical('https://www.ibm.com/aspera/connect/')
|
41
|
-
raise StandardError, "#{APP_NAME} is not installed"
|
40
|
+
raise StandardError, "#{Products::Alpha::APP_NAME} is not installed"
|
42
41
|
end
|
43
42
|
sleep(SLEEP_SEC_BETWEEN_RETRY)
|
44
43
|
retry
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
48
|
-
def sdk_log_file
|
49
|
-
File.join(Dir.home, 'Library', 'Logs', APP_IDENTIFIER, 'ibm-aspera-desktop.log')
|
50
|
-
end
|
51
|
-
|
52
47
|
def aspera_client_api_url
|
53
|
-
log_file =
|
48
|
+
log_file = Products::Alpha.log_file
|
54
49
|
url = nil
|
55
50
|
File.open(log_file, 'r') do |file|
|
56
51
|
file.each_line do |line|
|
data/lib/aspera/agent/connect.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/agent/base'
|
4
|
+
require 'aspera/products/connect'
|
5
|
+
require 'aspera/products/other'
|
4
6
|
require 'aspera/rest'
|
7
|
+
require 'aspera/environment'
|
5
8
|
require 'securerandom'
|
6
9
|
|
7
10
|
module Aspera
|
@@ -20,7 +23,7 @@ module Aspera
|
|
20
23
|
raise 'Using connect requires a graphical environment' if !Environment.default_gui_mode.eql?(:graphical)
|
21
24
|
method_index = 0
|
22
25
|
begin
|
23
|
-
connect_url =
|
26
|
+
connect_url = connect_api_url
|
24
27
|
Log.log.debug{"found: #{connect_url}"}
|
25
28
|
@connect_api = Rest.new(
|
26
29
|
base_url: "#{connect_url}/v5/connect", # could use v6 also now
|
@@ -43,6 +46,21 @@ module Aspera
|
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
49
|
+
# @return the file path of local connect where API's URI can be read
|
50
|
+
def connect_api_url
|
51
|
+
connect_locations = Products::Other.find(Products::Connect.locations).first
|
52
|
+
raise "Product: #{name} not found, please install." if connect_locations.nil?
|
53
|
+
folder = File.join(connect_locations[:run_root], 'var', 'run')
|
54
|
+
['', 's'].each do |ext|
|
55
|
+
uri_file = File.join(folder, "http#{ext}.uri")
|
56
|
+
Log.log.debug{"checking connect port file: #{uri_file}"}
|
57
|
+
if File.exist?(uri_file)
|
58
|
+
return File.open(uri_file, &:gets).strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
raise "no connect uri file found in #{folder}"
|
62
|
+
end
|
63
|
+
|
46
64
|
def start_transfer(transfer_spec, token_regenerator: nil)
|
47
65
|
if transfer_spec['direction'] == 'send'
|
48
66
|
Log.log.warn{"Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red}
|
data/lib/aspera/agent/direct.rb
CHANGED
@@ -72,7 +72,7 @@ module Aspera
|
|
72
72
|
session = {
|
73
73
|
id: nil, # SessionId from INIT message in mgt port
|
74
74
|
job_id: SecureRandom.uuid, # job id (regroup sessions)
|
75
|
-
ts: transfer_spec, # transfer spec
|
75
|
+
ts: transfer_spec, # global transfer spec
|
76
76
|
thread: nil, # Thread object monitoring management port, not nil when pushed to :sessions
|
77
77
|
error: nil, # exception if failed
|
78
78
|
io: nil, # management port server socket
|
@@ -84,7 +84,7 @@ module Aspera
|
|
84
84
|
if multi_session_info.nil?
|
85
85
|
Log.log.debug('Starting single session thread')
|
86
86
|
# single session for transfer : simple
|
87
|
-
session[:thread] = Thread.new
|
87
|
+
session[:thread] = Thread.new {transfer_thread_entry(session)}
|
88
88
|
@sessions.push(session)
|
89
89
|
else
|
90
90
|
Log.log.debug('Starting multi session threads')
|
@@ -101,7 +101,7 @@ module Aspera
|
|
101
101
|
# option: increment (default as per ascp manual) or not (cluster on other side ?)
|
102
102
|
args.unshift('-O', (multi_session_info[:udp_base] + i - 1).to_s) if @multi_incr_udp
|
103
103
|
# finally start the thread
|
104
|
-
this_session[:thread] = Thread.new
|
104
|
+
this_session[:thread] = Thread.new {transfer_thread_entry(this_session)}
|
105
105
|
@sessions.push(this_session)
|
106
106
|
end
|
107
107
|
end
|
@@ -132,18 +132,18 @@ module Aspera
|
|
132
132
|
|
133
133
|
# @return [Array] list of sessions for a job
|
134
134
|
def sessions_by_job(job_id)
|
135
|
-
@sessions.select{|
|
135
|
+
@sessions.select{|session| session[:job_id].eql?(job_id)}
|
136
136
|
end
|
137
137
|
|
138
|
-
# This is the low level method to start the transfer process
|
138
|
+
# This is the low level method to start the transfer process.
|
139
|
+
# Typically started in a thread.
|
139
140
|
# Start process with management port.
|
140
|
-
# returns
|
141
|
-
# raises FaspError on error
|
142
141
|
# @param session this session information, keys :io and :token_regenerator
|
143
|
-
# @param env [Hash] environment variables
|
144
|
-
# @param name [Symbol] name of executable: :ascp, :ascp4 or :async
|
145
|
-
# @param args [Array] command line arguments
|
142
|
+
# @param env [Hash] environment variables (comes from ascp_args)
|
143
|
+
# @param name [Symbol] name of executable: :ascp, :ascp4 or :async (comes from ascp_args)
|
144
|
+
# @param args [Array] command line arguments (comes from ascp_args)
|
146
145
|
# @return [nil] when process has exited
|
146
|
+
# @throw FaspError on error
|
147
147
|
def start_and_monitor_process(
|
148
148
|
session:,
|
149
149
|
env:,
|
@@ -159,6 +159,8 @@ module Aspera
|
|
159
159
|
mgt_server_socket = socket_class.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
160
160
|
# open any available (0) local TCP port for use as management port
|
161
161
|
mgt_server_socket.bind(Addrinfo.tcp(LISTEN_LOCAL_ADDRESS, SELECT_AVAILABLE_PORT))
|
162
|
+
# make port ready to accept connections, before starting ascp
|
163
|
+
mgt_server_socket.listen(1)
|
162
164
|
# build arguments and add mgt port
|
163
165
|
command_arguments = if name.eql?(:async)
|
164
166
|
["--exclusive-mgmt-port=#{mgt_server_socket.local_address.ip_port}"]
|
@@ -170,8 +172,8 @@ module Aspera
|
|
170
172
|
command_path = Ascp::Installation.instance.path(name)
|
171
173
|
command_pid = Environment.secure_spawn(env: env, exec: command_path, args: command_arguments)
|
172
174
|
notify_progress(:pre_start, session_id: nil, info: "waiting for #{name} to start")
|
173
|
-
mgt_server_socket.listen(1)
|
174
175
|
# TODO: timeout does not work when Process.spawn is used... until process exits, then it works
|
176
|
+
# So we use select to detect that anything happens on the socket (connection)
|
175
177
|
Log.log.debug{"before select, timeout: #{@spawn_timeout_sec}"}
|
176
178
|
readable, _, _ = IO.select([mgt_server_socket], nil, nil, @spawn_timeout_sec)
|
177
179
|
Log.log.debug('after select, before accept')
|
@@ -192,6 +194,8 @@ module Aspera
|
|
192
194
|
next unless event
|
193
195
|
# event is ready
|
194
196
|
Log.log.trace1{Log.dump(:management_port, event)}
|
197
|
+
# store latest event by type
|
198
|
+
session[:id] = event['SessionId'] if event['Type'].eql?('INIT')
|
195
199
|
@management_cb&.call(event)
|
196
200
|
process_progress(event)
|
197
201
|
Log.log.error((event['Description']).to_s) if event['Type'].eql?('FILEERROR') # cspell:disable-line
|
@@ -242,10 +246,13 @@ module Aspera
|
|
242
246
|
nil
|
243
247
|
end
|
244
248
|
|
249
|
+
attr_reader :sessions
|
250
|
+
|
245
251
|
private
|
246
252
|
|
247
253
|
# notify progress to callback
|
248
254
|
# @param event management port event
|
255
|
+
# @param session sessin object
|
249
256
|
def process_progress(event)
|
250
257
|
session_id = event['SessionId']
|
251
258
|
case event['Type']
|
@@ -282,14 +289,6 @@ module Aspera
|
|
282
289
|
end
|
283
290
|
end
|
284
291
|
|
285
|
-
# @return [Hash] session information
|
286
|
-
def session_by_id(id)
|
287
|
-
matches = @sessions.select{|session_info| session_info[:id].eql?(id)}
|
288
|
-
raise 'no such session' if matches.empty?
|
289
|
-
raise 'more than one session' if matches.length > 1
|
290
|
-
return matches.first
|
291
|
-
end
|
292
|
-
|
293
292
|
# send command to management port of command (used in `asession)
|
294
293
|
# @param job_id identified transfer process
|
295
294
|
# @param session_index index of session (for multi session)
|
@@ -297,18 +296,10 @@ module Aspera
|
|
297
296
|
# {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
298
297
|
# {'type'=>'DONE'}
|
299
298
|
def send_command(job_id, data)
|
300
|
-
session =
|
299
|
+
session = @sessions.find{|session| session[:job_id].eql?(job_id)}
|
301
300
|
Log.log.debug{"command: #{data}"}
|
302
|
-
|
303
|
-
command = data
|
304
|
-
.keys
|
305
|
-
.map{|k|"#{k.capitalize}: #{data[k]}"}
|
306
|
-
.unshift(MGT_HEADER)
|
307
|
-
.push('', '')
|
308
|
-
.join("\n")
|
309
|
-
session[:io].puts(command)
|
301
|
+
session[:io].puts(Ascp::Management.command_to_stream(data))
|
310
302
|
end
|
311
|
-
attr_reader :sessions
|
312
303
|
|
313
304
|
# options for initialize (same as values in option transfer_info)
|
314
305
|
# @param ascp_args [Array] additional arguments to ascp
|
data/lib/aspera/agent/node.rb
CHANGED
@@ -50,14 +50,6 @@ module Aspera
|
|
50
50
|
return @node_api
|
51
51
|
end
|
52
52
|
|
53
|
-
# use this to set the node_api end point before using the class.
|
54
|
-
def node_api=(new_value)
|
55
|
-
if !@node_api.nil? && !new_value.nil?
|
56
|
-
Log.log.warn('overriding existing node api value')
|
57
|
-
end
|
58
|
-
@node_api = new_value
|
59
|
-
end
|
60
|
-
|
61
53
|
# generic method
|
62
54
|
def start_transfer(transfer_spec, token_regenerator: nil)
|
63
55
|
# add root id if access key
|
@@ -120,9 +112,7 @@ module Aspera
|
|
120
112
|
# Bug in HSTS ? transfer is marked failed, but there is no reason
|
121
113
|
break if transfer_data['error_code'].eql?(0) && transfer_data['error_desc'].empty?
|
122
114
|
raise Transfer::Error, "status: #{transfer_data['status']}. code: #{transfer_data['error_code']}. description: #{transfer_data['error_desc']}"
|
123
|
-
else
|
124
|
-
Log.log.warn{"transfer_data -> #{transfer_data}"}
|
125
|
-
raise Transfer::Error, "status: #{transfer_data['status']}. code: #{transfer_data['error_code']}. description: #{transfer_data['error_desc']}"
|
115
|
+
else Aspera.error_unexpected_value(transfer_data['status']){"transfer_data -> #{transfer_data}"}
|
126
116
|
end
|
127
117
|
sleep(1.0)
|
128
118
|
end
|
data/lib/aspera/agent/trsdk.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/agent/base'
|
4
|
-
require 'aspera/
|
4
|
+
require 'aspera/products/trsdk'
|
5
5
|
require 'aspera/temp_file_manager'
|
6
|
-
require 'aspera/log'
|
7
|
-
require 'aspera/assert'
|
8
6
|
require 'json'
|
9
7
|
require 'uri'
|
10
8
|
require 'transfer_services_pb'
|
@@ -18,25 +16,6 @@ module Aspera
|
|
18
16
|
PORT_SEP = ':'
|
19
17
|
# port zero means select a random available high port
|
20
18
|
AUTO_LOCAL_TCP_PORT = "#{PORT_SEP}0"
|
21
|
-
class << self
|
22
|
-
# Well, the port number is only in log file
|
23
|
-
def daemon_port_from_log(log_file)
|
24
|
-
result = nil
|
25
|
-
# if port is zero, a dynamic port was created, get it
|
26
|
-
File.open(log_file, 'r') do |file|
|
27
|
-
file.each_line do |line|
|
28
|
-
# Well, it's tricky to depend on log
|
29
|
-
if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
|
30
|
-
result = m[2].to_i
|
31
|
-
# no "break" , need to read last matching log line
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
raise 'Port not found in daemon logs' if result.nil?
|
36
|
-
Log.log.debug{"Got port #{result} from log"}
|
37
|
-
return result
|
38
|
-
end
|
39
|
-
end
|
40
19
|
|
41
20
|
# @param url [String] URL of the transfer manager daemon
|
42
21
|
# @param external [Boolean] if true, expect that an external daemon is already running
|
@@ -83,8 +62,8 @@ module Aspera
|
|
83
62
|
fasp_runtime: {
|
84
63
|
use_embedded: false,
|
85
64
|
user_defined: {
|
86
|
-
bin:
|
87
|
-
etc:
|
65
|
+
bin: Products::Trsdk.sdk_directory,
|
66
|
+
etc: Products::Trsdk.sdk_directory
|
88
67
|
}
|
89
68
|
}
|
90
69
|
}
|
@@ -110,7 +89,7 @@ module Aspera
|
|
110
89
|
Process.detach(@daemon_pid) if @keep
|
111
90
|
at_exit {shutdown}
|
112
91
|
# update port for next connection attempt (if auto high port was requested)
|
113
|
-
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{
|
92
|
+
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{Products::Trsdk.daemon_port_from_log(log_stdout)}" if is_local_auto_port
|
114
93
|
# local daemon started, try again
|
115
94
|
retry
|
116
95
|
end
|
data/lib/aspera/api/aoc.rb
CHANGED
@@ -70,6 +70,7 @@ module Aspera
|
|
70
70
|
|
71
71
|
# split host of http://myorg.asperafiles.com in org and domain
|
72
72
|
def split_org_domain(uri)
|
73
|
+
Aspera.assert_type(uri, URI)
|
73
74
|
raise "No host found in URL.Please check URL format: https://myorg.#{SAAS_DOMAIN_PROD}" if uri.host.nil?
|
74
75
|
parts = uri.host.split('.', 2)
|
75
76
|
Aspera.assert(parts.length == 2){"expecting a public FQDN for #{PRODUCT_NAME}"}
|
@@ -77,6 +78,10 @@ module Aspera
|
|
77
78
|
return %i{organization domain}.zip(parts).to_h
|
78
79
|
end
|
79
80
|
|
81
|
+
def saas_url?(url)
|
82
|
+
url.include?(SAAS_DOMAIN_PROD)
|
83
|
+
end
|
84
|
+
|
80
85
|
# @param url [String] URL of AoC public link
|
81
86
|
# @return [Hash] information about public link, or nil if not a public link
|
82
87
|
def link_info(url)
|
data/lib/aspera/api/node.rb
CHANGED
@@ -16,8 +16,6 @@ module Aspera
|
|
16
16
|
class Node < Aspera::Rest
|
17
17
|
SCOPE_SEPARATOR = ':'
|
18
18
|
SCOPE_NODE_PREFIX = 'node.'
|
19
|
-
# prefix for ruby code for filter (deprecated)
|
20
|
-
MATCH_EXEC_PREFIX = 'exec:'
|
21
19
|
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
22
20
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
23
21
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
@@ -25,7 +23,7 @@ module Aspera
|
|
25
23
|
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
26
24
|
# methods of @app_info[:api]
|
27
25
|
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
28
|
-
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :
|
26
|
+
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
|
29
27
|
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
|
30
28
|
:REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
31
29
|
|
@@ -47,27 +45,20 @@ module Aspera
|
|
47
45
|
@use_node_cache = true
|
48
46
|
|
49
47
|
class << self
|
48
|
+
# set to false to read transfer parameters from download_setup
|
50
49
|
attr_accessor :use_standard_ports
|
50
|
+
# set to false to bypass cache in redis
|
51
51
|
attr_accessor :use_node_cache
|
52
52
|
|
53
|
-
def cache_control_headers
|
54
|
-
h = {'Accept' => 'application/json'}
|
55
|
-
h[HEADER_X_CACHE_CONTROL] = 'no-cache' unless use_node_cache
|
56
|
-
h
|
57
|
-
end
|
58
|
-
|
59
53
|
# For access keys: provide expression to match entry in folder
|
54
|
+
# @param match_expression one of supported types
|
55
|
+
# @return lambda function
|
60
56
|
def file_matcher(match_expression)
|
61
57
|
case match_expression
|
62
58
|
when Proc then return match_expression
|
63
59
|
when Regexp then return ->(f){f['name'].match?(match_expression)}
|
64
60
|
when String
|
65
|
-
|
66
|
-
code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
|
67
|
-
Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
|
68
|
-
return Environment.secure_eval(code, __FILE__, __LINE__)
|
69
|
-
end
|
70
|
-
return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
61
|
+
return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
71
62
|
when NilClass then return ->(_){true}
|
72
63
|
else Aspera.error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
|
73
64
|
end
|
@@ -158,7 +149,13 @@ module Aspera
|
|
158
149
|
|
159
150
|
# Call node API, possibly adding cache control header, as globally specified
|
160
151
|
def read_with_cache(subpath, query=nil)
|
161
|
-
|
152
|
+
headers = {'Accept' => 'application/json'}
|
153
|
+
headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
|
154
|
+
return call(
|
155
|
+
operation: 'GET',
|
156
|
+
subpath: subpath,
|
157
|
+
headers: headers,
|
158
|
+
query: query)[:data]
|
162
159
|
end
|
163
160
|
|
164
161
|
# update transfer spec with special additional tags
|
@@ -198,11 +195,11 @@ module Aspera
|
|
198
195
|
# Recursively browse in a folder (with non-recursive method)
|
199
196
|
# sub folders are processed if the processing method returns true
|
200
197
|
# links are processed on the respective node
|
198
|
+
# @param method_sym [Symbol] processing method, arguments: entry, path, state
|
201
199
|
# @param state [Object] state object sent to processing method
|
202
200
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
203
201
|
# @param top_file_path [String] path of top folder (default = /)
|
204
|
-
|
205
|
-
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
|
202
|
+
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
|
206
203
|
Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
|
207
204
|
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
208
205
|
# start at top folder
|
@@ -215,7 +212,8 @@ module Aspera
|
|
215
212
|
# get folder content
|
216
213
|
folder_contents =
|
217
214
|
begin
|
218
|
-
|
215
|
+
# TODO: use header
|
216
|
+
read_with_cache("files/#{current_item[:id]}/files")
|
219
217
|
rescue StandardError => e
|
220
218
|
Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
|
221
219
|
[]
|
@@ -228,21 +226,21 @@ module Aspera
|
|
228
226
|
end
|
229
227
|
next
|
230
228
|
end
|
231
|
-
|
232
|
-
Log.log.debug{"process_folder_tree: checking #{
|
229
|
+
current_path = File.join(current_item[:path], entry['name'])
|
230
|
+
Log.log.debug{"process_folder_tree: checking #{current_path}"}
|
233
231
|
# call block, continue only if method returns true
|
234
|
-
next unless send(method_sym, entry,
|
232
|
+
next unless send(method_sym, entry, current_path, state)
|
235
233
|
# entry type is file, folder or link
|
236
234
|
case entry['type']
|
237
235
|
when 'folder'
|
238
|
-
folders_to_explore.push({id: entry['id'], path:
|
236
|
+
folders_to_explore.push({id: entry['id'], path: current_path})
|
239
237
|
when 'link'
|
240
238
|
if entry_has_link_information(entry)
|
241
239
|
node_id_to_node(entry['target_node_id'])&.process_folder_tree(
|
242
240
|
method_sym: method_sym,
|
243
241
|
state: state,
|
244
242
|
top_file_id: entry['target_id'],
|
245
|
-
top_file_path:
|
243
|
+
top_file_path: current_path)
|
246
244
|
end
|
247
245
|
end
|
248
246
|
end
|
@@ -268,13 +266,19 @@ module Aspera
|
|
268
266
|
return resolve_state[:result]
|
269
267
|
end
|
270
268
|
|
271
|
-
def find_files(top_file_id,
|
269
|
+
def find_files(top_file_id, test_lambda)
|
272
270
|
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
273
|
-
find_state = {found: [],
|
271
|
+
find_state = {found: [], test_lambda: test_lambda}
|
274
272
|
process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
|
275
273
|
return find_state[:found]
|
276
274
|
end
|
277
275
|
|
276
|
+
def list_files(top_file_id)
|
277
|
+
find_state = {found: []}
|
278
|
+
process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
|
279
|
+
return find_state[:found]
|
280
|
+
end
|
281
|
+
|
278
282
|
def refreshed_transfer_token
|
279
283
|
return oauth.token(refresh: true)
|
280
284
|
end
|
@@ -296,6 +300,9 @@ module Aspera
|
|
296
300
|
end
|
297
301
|
|
298
302
|
# Create transfer spec for gen4
|
303
|
+
# @param file_id destination or source folder (id)
|
304
|
+
# @param direction one of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
|
305
|
+
# @param ts_merge additional transfer spec to merge
|
299
306
|
def transfer_spec_gen4(file_id, direction, ts_merge=nil)
|
300
307
|
ak_name = nil
|
301
308
|
ak_token = nil
|
@@ -309,6 +316,9 @@ module Aspera
|
|
309
316
|
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
|
310
317
|
# get bearer token, possibly use cache
|
311
318
|
ak_token = oauth.token
|
319
|
+
when :none
|
320
|
+
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
321
|
+
ak_token = params[:headers]['Authorization']
|
312
322
|
else Aspera.error_unexpected_value(auth_params[:type])
|
313
323
|
end
|
314
324
|
transfer_spec = {
|
@@ -357,8 +367,8 @@ module Aspera
|
|
357
367
|
|
358
368
|
private
|
359
369
|
|
370
|
+
# method called in loop for each entry for `resolve_api_fid`
|
360
371
|
def process_api_fid(entry, path, state)
|
361
|
-
# this block is called recursively for each entry in folder
|
362
372
|
# stop digging here if not in right path
|
363
373
|
return false unless entry['name'].eql?(state[:path].first)
|
364
374
|
# ok it matches, so we remove the match, and continue digging
|
@@ -401,11 +411,18 @@ module Aspera
|
|
401
411
|
return true
|
402
412
|
end
|
403
413
|
|
414
|
+
# method called in loop for each entry for `find_files`
|
404
415
|
def process_find_files(entry, path, state)
|
405
|
-
state[:found].push(entry.merge({'path' => path})) if state[:
|
416
|
+
state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
|
406
417
|
# test all files deeply
|
407
418
|
return true
|
408
419
|
end
|
420
|
+
|
421
|
+
# method called in loop for each entry for `list_files`
|
422
|
+
def process_list_files(entry, path, state)
|
423
|
+
state[:found].push(entry.merge({'path' => path}))
|
424
|
+
return false
|
425
|
+
end
|
409
426
|
end
|
410
427
|
end
|
411
428
|
end
|