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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +20 -2
  4. data/README.md +281 -156
  5. data/bin/asession +2 -2
  6. data/lib/aspera/agent/alpha.rb +7 -12
  7. data/lib/aspera/agent/connect.rb +19 -1
  8. data/lib/aspera/agent/direct.rb +20 -29
  9. data/lib/aspera/agent/node.rb +1 -11
  10. data/lib/aspera/agent/trsdk.rb +4 -25
  11. data/lib/aspera/api/aoc.rb +5 -0
  12. data/lib/aspera/api/node.rb +45 -28
  13. data/lib/aspera/ascp/installation.rb +69 -38
  14. data/lib/aspera/ascp/management.rb +27 -6
  15. data/lib/aspera/cli/formatter.rb +149 -141
  16. data/lib/aspera/cli/info.rb +1 -1
  17. data/lib/aspera/cli/manager.rb +1 -0
  18. data/lib/aspera/cli/plugin.rb +2 -2
  19. data/lib/aspera/cli/plugins/aoc.rb +27 -17
  20. data/lib/aspera/cli/plugins/config.rb +31 -21
  21. data/lib/aspera/cli/plugins/faspex.rb +1 -1
  22. data/lib/aspera/cli/plugins/faspex5.rb +11 -3
  23. data/lib/aspera/cli/plugins/node.rb +44 -38
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/command_line_builder.rb +1 -1
  26. data/lib/aspera/environment.rb +5 -6
  27. data/lib/aspera/node_simulator.rb +228 -112
  28. data/lib/aspera/oauth/base.rb +31 -42
  29. data/lib/aspera/oauth/factory.rb +41 -2
  30. data/lib/aspera/persistency_folder.rb +20 -2
  31. data/lib/aspera/preview/generator.rb +1 -1
  32. data/lib/aspera/preview/utils.rb +1 -1
  33. data/lib/aspera/products/alpha.rb +30 -0
  34. data/lib/aspera/products/connect.rb +48 -0
  35. data/lib/aspera/products/other.rb +82 -0
  36. data/lib/aspera/products/trsdk.rb +54 -0
  37. data/lib/aspera/rest.rb +18 -13
  38. data/lib/aspera/ssh.rb +28 -24
  39. data/lib/aspera/transfer/spec.yaml +22 -20
  40. data.tar.gz.sig +0 -0
  41. metadata +21 -4
  42. metadata.gz.sig +0 -0
  43. 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/ascp/installation'
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::Ascp::Installation.instance.sdk_folder = session_spec[PARAM_SDK]
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
@@ -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 = sdk_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|
@@ -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 = Ascp::Products.connect_uri
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}
@@ -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(session) {|session_info|transfer_thread_entry(session_info)}
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(this_session) {|session_info|transfer_thread_entry(session_info)}
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{|session_info| session_info[:job_id].eql?(job_id)}
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 = session_by_id(job_id)
299
+ session = @sessions.find{|session| session[:job_id].eql?(job_id)}
301
300
  Log.log.debug{"command: #{data}"}
302
- # build command
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
@@ -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
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/agent/base'
4
- require 'aspera/ascp/installation'
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: Ascp::Installation.instance.sdk_folder,
87
- etc: Ascp::Installation.instance.sdk_folder
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}#{self.class.daemon_port_from_log(log_stdout)}" if is_local_auto_port
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
@@ -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)
@@ -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, :MATCH_EXEC_PREFIX, :MATCH_TYPES,
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
- if match_expression.start_with?(MATCH_EXEC_PREFIX)
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
- return call(operation: 'GET', subpath: subpath, headers: self.class.cache_control_headers, query: query)[:data]
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
- # @param block [Proc] processing method, arguments: entry, path, state
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
- read("files/#{current_item[:id]}/files")
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
- relative_path = File.join(current_item[:path], entry['name'])
232
- Log.log.debug{"process_folder_tree: checking #{relative_path}"}
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, relative_path, state)
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: relative_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: relative_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, test_block)
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: [], test_block: test_block}
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[:test_block].call(entry)
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