aspera-cli 4.0.0 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +843 -304
  3. data/bin/dascli +13 -0
  4. data/docs/Makefile +4 -4
  5. data/docs/README.erb.md +805 -172
  6. data/docs/test_env.conf +22 -3
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +89 -0
  9. data/lib/aspera/aoc.rb +87 -108
  10. data/lib/aspera/cli/formater.rb +2 -0
  11. data/lib/aspera/cli/main.rb +89 -49
  12. data/lib/aspera/cli/plugin.rb +9 -4
  13. data/lib/aspera/cli/plugins/alee.rb +1 -1
  14. data/lib/aspera/cli/plugins/aoc.rb +188 -173
  15. data/lib/aspera/cli/plugins/ats.rb +2 -2
  16. data/lib/aspera/cli/plugins/config.rb +218 -145
  17. data/lib/aspera/cli/plugins/console.rb +2 -2
  18. data/lib/aspera/cli/plugins/faspex.rb +114 -61
  19. data/lib/aspera/cli/plugins/faspex5.rb +85 -43
  20. data/lib/aspera/cli/plugins/node.rb +3 -3
  21. data/lib/aspera/cli/plugins/preview.rb +59 -45
  22. data/lib/aspera/cli/plugins/server.rb +23 -8
  23. data/lib/aspera/cli/transfer_agent.rb +77 -49
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/command_line_builder.rb +49 -31
  26. data/lib/aspera/cos_node.rb +33 -28
  27. data/lib/aspera/environment.rb +2 -2
  28. data/lib/aspera/fasp/connect.rb +28 -21
  29. data/lib/aspera/fasp/http_gw.rb +140 -28
  30. data/lib/aspera/fasp/installation.rb +93 -46
  31. data/lib/aspera/fasp/local.rb +88 -45
  32. data/lib/aspera/fasp/manager.rb +15 -0
  33. data/lib/aspera/fasp/node.rb +4 -4
  34. data/lib/aspera/fasp/parameters.rb +59 -101
  35. data/lib/aspera/fasp/parameters.yaml +531 -0
  36. data/lib/aspera/fasp/resume_policy.rb +13 -12
  37. data/lib/aspera/fasp/uri.rb +1 -1
  38. data/lib/aspera/log.rb +1 -1
  39. data/lib/aspera/node.rb +61 -1
  40. data/lib/aspera/oauth.rb +49 -46
  41. data/lib/aspera/persistency_folder.rb +9 -4
  42. data/lib/aspera/preview/file_types.rb +53 -21
  43. data/lib/aspera/preview/generator.rb +3 -3
  44. data/lib/aspera/rest.rb +29 -18
  45. data/lib/aspera/secrets.rb +20 -0
  46. data/lib/aspera/sync.rb +40 -35
  47. data/lib/aspera/temp_file_manager.rb +19 -0
  48. data/lib/aspera/web_auth.rb +105 -0
  49. metadata +54 -20
  50. data/docs/transfer_spec.html +0 -99
data/docs/test_env.conf CHANGED
@@ -6,7 +6,7 @@ default:
6
6
  aoc: tst_aoc1
7
7
  faspex: tst_faspex
8
8
  faspex5: tst_faspex5
9
- shares: tst_shares
9
+ shares: tst_shares_1
10
10
  shares2: tst_shares2
11
11
  node: tst_node
12
12
  server: tst_server
@@ -19,7 +19,6 @@ default:
19
19
  cli_default:
20
20
  interactive: your value here
21
21
  smtp: your value here
22
- ascp_path: your value here
23
22
  local_user:
24
23
  ssh_keys: your value here
25
24
  smtp_config:
@@ -48,8 +47,23 @@ tst_node_faspex:
48
47
  url: your value here
49
48
  username: your value here
50
49
  password: your value here
50
+ tst_faspex5_boot:
51
+ url: your value here
52
+ auth: your value here
53
+ username: your value here
54
+ password: your value here
55
+ tst_faspex5_web:
56
+ url: your value here
57
+ auth: your value here
58
+ redirect_uri: your value here
59
+ client_id: your value here
60
+ client_secret: your value here
51
61
  tst_faspex5:
52
62
  url: your value here
63
+ auth: your value here
64
+ client_id: your value here
65
+ client_secret: your value here
66
+ private_key: your value here
53
67
  username: your value here
54
68
  password: your value here
55
69
  tst_shares:
@@ -94,6 +108,7 @@ tst_ak_preview:
94
108
  url: your value here
95
109
  username: your value here
96
110
  password: your value here
111
+ mimemagic: your value here
97
112
  tst_node_preview:
98
113
  url: your value here
99
114
  username: your value here
@@ -110,6 +125,8 @@ misc:
110
125
  faspex_publink_send_to_fxuser: your value here
111
126
  faspex_publink_send_to_dropbox: your value here
112
127
  shares_upload: your value here
128
+ console_smart_id: your value here
129
+ console_smart_file: your value here
113
130
  orch_workflow_id: your value here
114
131
  file_dcm: your value here
115
132
  file_pdf: your value here
@@ -122,6 +139,7 @@ misc:
122
139
  aoc_publink_recv_from_aocuser: your value here
123
140
  aoc_publink_send_shd_inbox: your value here
124
141
  aoc_publink_send_aoc_user: your value here
142
+ aoc_publink_send_use_pass: your value here
125
143
  aoc_publink_folder: your value here
126
144
  aoc_shbx_ws: your value here
127
145
  aoc_shbx_name: your value here
@@ -137,4 +155,5 @@ misc:
137
155
  email_internal: your value here
138
156
  email_external: your value here
139
157
  aoc_org: your value here
140
- aoc_user: your value here
158
+ aoc_user_email: your value here
159
+ http_gw_fqdn_port: your value here
data/examples/aoc.rb CHANGED
@@ -4,11 +4,22 @@ require 'aspera/log'
4
4
 
5
5
  Aspera::Log.instance.level=:debug
6
6
 
7
+ if ! ARGV.length.eql?(3)
8
+ Aspera::Log.log.error("wrong number of args: #{ARGV.length}")
9
+ Aspera::Log.log.error("Usage: #{$0} <aoc URL> <aoc username> <aoc private key content>")
10
+ Aspera::Log.log.error("Example: #{$0} https://myorg.ibmaspera.com john@example.com $(cat /home/john/my_key.pem)")
11
+ Process.exit(1)
12
+ end
13
+
14
+ aoc_url=ARGV[0]
15
+ aoc_user=ARGV[1]
16
+ aoc_key_value=ARGV[2]
17
+
7
18
  aocapi=Aspera::AoC.new(
8
- url: 'https://myorg.ibmaspera.com',
19
+ url: aoc_url,
9
20
  auth: :jwt,
10
- private_key: File.read('path/to_your_private_key.pem'),
11
- username: 'my.email@example.com',
21
+ private_key: aoc_key_value,
22
+ username: aoc_user,
12
23
  scope: 'user:all',
13
24
  subpath: 'api/v1')
14
25
 
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # find Faspex API here: https://developer.ibm.com/apis/catalog/?search=faspex
3
+ # this example makes use of class Aspera::Rest for REST calls, alternatively class RestClient of gem rest-client could be used
4
+ # this example makes use of class Aspera::Fasp::Local for transfers, alternatively the official "Transfer SDK" could be used
5
+ # Aspera SDK can be downloaded with: `ascli conf ascp install` , it installs in $HOME/.aspera/ascli/sdk
6
+ require 'aspera/rest'
7
+ require 'aspera/log'
8
+ require 'aspera/fasp/local'
9
+
10
+ tmpdir=ENV['tmp']||Dir.tmpdir || '.'
11
+
12
+ # Set high log level for the example, decrease to :warn usually
13
+ Aspera::Log.instance.level=:debug
14
+
15
+ # Set folder where SDK is installed (mandatory)
16
+ # (if ascp is not there, the lib will try to find in usual locations)
17
+ # (if data files are not there, they will be created)
18
+ Aspera::Fasp::Installation.instance.folder = tmpdir
19
+
20
+ if ! ARGV.length.eql?(3)
21
+ Aspera::Log.log.error("Wrong number of args: #{ARGV.length}")
22
+ Aspera::Log.log.error("Usage: #{$0} <faspex URL> <faspex username> <faspex password>")
23
+ Aspera::Log.log.error("Example: #{$0} https://faspex.com/aspera/faspex john p@sSw0rd")
24
+ Process.exit(1)
25
+ end
26
+
27
+ faspex_url=ARGV[0] # typically: https://faspex.example.com/aspera/faspex
28
+ faspex_user=ARGV[1]
29
+ faspex_pass=ARGV[2]
30
+
31
+ # comment out this if certificate is valid, keep line to ignore certificate
32
+ Aspera::Rest.insecure=true
33
+
34
+ # 1: Faspex 4 API v3
35
+ #---------------
36
+
37
+ # create REST API object
38
+ api_v3=Aspera::Rest.new({
39
+ :base_url => faspex_url,
40
+ :auth => {
41
+ :type => :basic,
42
+ :username => faspex_user,
43
+ :password => faspex_pass
44
+ }})
45
+
46
+ # very simple api call
47
+ api_v3.read('me')
48
+
49
+ # 2: send a package
50
+ #---------------
51
+
52
+ # create a sample file to send
53
+ file_to_send=File.join(tmpdir,'myfile.bin')
54
+ File.open(file_to_send, "w") {|f| f.write("sample data") }
55
+ # package creation parameters
56
+ package_create_params={'delivery'=>{'title'=>'test package','recipients'=>['aspera.user1@gmail.com'],'sources'=>[{'paths'=>[file_to_send]}]}}
57
+ pkg_created=api_v3.create('send',package_create_params)[:data]
58
+ # get transfer specification (normally: only one)
59
+ transfer_spec=pkg_created['xfer_sessions'].first
60
+ # set paths of files to send
61
+ transfer_spec['paths']=[{'source'=>file_to_send}]
62
+ # get the local agent (i.e. ascp)
63
+ transfer_client=Aspera::Fasp::Local.new
64
+ # disable ascp output on stdout (optional)
65
+ transfer_client.quiet=true
66
+ # start transfer (asynchronous)
67
+ job_id=transfer_client.start_transfer(transfer_spec)
68
+ # wait for all transfer completion (for the example)
69
+ result=transfer_client.wait_for_transfers_completion
70
+ # notify of any transfer error
71
+ result.select{|i|!i.eql?(:success)}.each do |e|
72
+ Aspera::Log.log.error("A transfer error occured: #{e.message}")
73
+ end
74
+
75
+ # 3: Faspex 4 API v4
76
+ #---------------
77
+ api_v4=Aspera::Rest.new({
78
+ :base_url => faspex_url+'/api',
79
+ :auth => {
80
+ :type => :oauth2,
81
+ :base_url => faspex_url+'/auth/oauth2',
82
+ :grant => :header_userpass,
83
+ :user_name => faspex_user,
84
+ :user_pass => faspex_pass,
85
+ :scope => 'admin'
86
+ }})
87
+
88
+ # Use it. Note that Faspex 4 API v4 is totally different from Faspex 4 v3 APIs, see ref on line 2
89
+ Aspera::Log.dump('users',api_v4.read('users')[:data])
data/lib/aspera/aoc.rb CHANGED
@@ -9,6 +9,8 @@ module Aspera
9
9
  private
10
10
  @@use_standard_ports = true
11
11
 
12
+ API_V1='api/v1'
13
+
12
14
  PRODUCT_NAME='Aspera on Cloud'
13
15
  # Production domain of AoC
14
16
  PROD_DOMAIN='ibmaspera.com'
@@ -42,11 +44,6 @@ module Aspera
42
44
  FILES_APP='files'
43
45
  PACKAGES_APP='packages'
44
46
 
45
- CLIENT_RANDOM={
46
- 'aspera.global-cli-client' => 'frpmsRsG4mjZ0PlxCgdJlvONqBg4Vlpz_IX7gXmBMAfsgMLy2FO6CXLodKfKAuhqnCqSptLbe_wdmnm9JRuEPO-PpFqpq_Kb',
47
- 'aspera.drive' => 'UegzQ3LcbLht5dLYAXaR-7ZMnJ6-kwPEXWEXaqLSOMGmtzNA9r6kPFLElqBfq66BfgMabdO96k5sPXV-H8M3vsx9LbGlewF1'
48
- }
49
-
50
47
  def self.get_client_info(client_name=CLIENT_APPS.first)
51
48
  client_index=CLIENT_APPS.index(client_name)
52
49
  raise "no such pre-defined client: #{client_name}" if client_index.nil?
@@ -68,9 +65,14 @@ module Aspera
68
65
  return organization,instance_domain
69
66
  end
70
67
 
68
+ # base API url depends on domain, which could be "qa.xxx"
69
+ def self.api_base_url(api_domain=PROD_DOMAIN)
70
+ return "https://api.#{api_domain}"
71
+ end
72
+
71
73
  def self.metering_api(entitlement_id,customer_id,api_domain=PROD_DOMAIN)
72
74
  return Rest.new({
73
- :base_url => "https://api.#{api_domain}/metering/v1",
75
+ :base_url => "#{api_base_url(api_domain)}/metering/v1",
74
76
  :headers => {'X-Aspera-Entitlement-Authorization' => Rest.basic_creds(entitlement_id,customer_id)}
75
77
  })
76
78
  end
@@ -121,12 +123,12 @@ module Aspera
121
123
  raise RuntimeError,'too many redirections'
122
124
  end
123
125
 
124
- # @param :link,:url,:auth,:client_id,:client_secret,:scope,:redirect_uri,:private_key,:username,:subpath
126
+ # @param :link,:url,:auth,:client_id,:client_secret,:scope,:redirect_uri,:private_key,:username,:subpath,:password (for pub link)
125
127
  def initialize(opt)
126
128
  # access key secrets are provided out of band to get node api access
127
129
  # key: access key
128
130
  # value: associated secret
129
- @secrets={}
131
+ @key_chain=nil
130
132
 
131
133
  # init rest params
132
134
  aoc_rest_p={:auth=>{:type =>:oauth2}}
@@ -147,12 +149,12 @@ module Aspera
147
149
 
148
150
  # get org name and domain from url
149
151
  organization,instance_domain=self.class.parse_url(opt[:url])
150
- # this is the base API url (depends on domain, which could be "qa.xxx")
151
- api_base_url="https://api.#{instance_domain}"
152
- # base API URL
153
- aoc_rest_p[:base_url]="#{api_base_url}/#{opt[:subpath]}"
152
+ # this is the base API url
153
+ api_url_base=self.class.api_base_url(instance_domain)
154
+ # API URL, including subpath (version ...)
155
+ aoc_rest_p[:base_url]="#{api_url_base}/#{opt[:subpath]}"
154
156
  # base auth URL
155
- aoc_auth_p[:base_url] = "#{api_base_url}/#{OAUTH_API_SUBPATH}/#{organization}"
157
+ aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{organization}"
156
158
  aoc_auth_p[:client_id]=opt[:client_id]
157
159
  aoc_auth_p[:client_secret] = opt[:client_secret]
158
160
 
@@ -185,23 +187,19 @@ module Aspera
185
187
  aoc_auth_p[:jwt_subject] = opt[:username]
186
188
  aoc_auth_p[:jwt_private_key_obj] = OpenSSL::PKey::RSA.new(private_key_PEM_string)
187
189
  when :url_token
190
+ aoc_auth_p[:password]=opt[:password] unless opt[:password].nil?
188
191
  # nothing more
189
192
  else raise "ERROR: unsupported auth method: #{aoc_auth_p[:grant]}"
190
193
  end
191
194
  super(aoc_rest_p)
192
195
  end
193
196
 
194
- def add_secrets(secrets)
195
- @secrets.merge!(secrets)
196
- Log.log.debug("now secrets:#{secrets}")
197
+ def key_chain=(keychain)
198
+ raise "keychain already set" unless @key_chain.nil?
199
+ @key_chain=keychain
197
200
  nil
198
201
  end
199
202
 
200
- def has_secret(ak)
201
- Log.log.debug("has key:#{ak} -> #{@secrets.has_key?(ak)}")
202
- return @secrets.has_key?(ak)
203
- end
204
-
205
203
  # additional transfer spec (tags) for package information
206
204
  def self.package_tags(package_info,operation)
207
205
  return {'tags'=>{'aspera'=>{'files'=>{
@@ -293,13 +291,14 @@ module Aspera
293
291
  # no scope: requires secret
294
292
  # if secret provided beforehand: use it
295
293
  def get_node_api(node_info,node_scope=nil)
294
+ # X-Aspera-AccessKey required for bearer token only
296
295
  node_rest_params={
297
296
  :base_url => node_info['url'],
298
297
  :headers => {'X-Aspera-AccessKey'=>node_info['access_key']},
299
298
  }
300
- ak_secret=@secrets[node_info['access_key']]
299
+ ak_secret=@key_chain.get_secret(node_info['access_key'],false)
301
300
  if ak_secret.nil? and node_scope.nil?
302
- raise 'There must be at least one of: secret, node scope'
301
+ raise "There must be at least one of: 'secret' or 'scope' for access key #{node_info['access_key']}"
303
302
  end
304
303
  # if secret provided on command line or if there is no scope
305
304
  if !ak_secret.nil? or node_scope.nil?
@@ -312,7 +311,7 @@ module Aspera
312
311
  node_rest_params[:auth]=self.params[:auth].clone
313
312
  node_rest_params[:auth][:scope]=self.class.node_scope(node_info['access_key'],node_scope)
314
313
  end
315
- return Rest.new(node_rest_params)
314
+ return Node.new(node_rest_params)
316
315
  end
317
316
 
318
317
  # check that parameter has necessary types
@@ -328,102 +327,82 @@ module Aspera
328
327
  return node_info,file_id
329
328
  end
330
329
 
331
- # returns node api and folder_id from soft link
332
- def read_asplnk(current_file_info)
333
- new_node_api=get_node_api(self.read("nodes/#{current_file_info['target_node_id']}")[:data],SCOPE_NODE_USER)
334
- return {:node_api=>new_node_api,:folder_id=>current_file_info['target_id']}
330
+ # add entry to list if test block is success
331
+ def process_find_files(entry,path)
332
+ begin
333
+ # add to result if match filter
334
+ @find_state[:found].push(entry.merge({'path'=>path})) if @find_state[:test_block].call(entry)
335
+ # process link
336
+ if entry[:type].eql?('link')
337
+ sub_node_info=self.read("nodes/#{entry['target_node_id']}")[:data]
338
+ sub_opt={method: process_find_files, top_file_id: entry['target_id'], top_file_path: path}
339
+ get_node_api(sub_node_info,SCOPE_NODE_USER).crawl(self,sub_opt)
340
+ end
341
+ rescue => e
342
+ Log.log.error("#{path}: #{e.message}")
343
+ end
344
+ # process all folders
345
+ return true
335
346
  end
336
347
 
337
- # @returns list of file paths that match given regex
338
348
  def find_files( top_node_file, test_block )
339
349
  top_node_info,top_file_id=check_get_node_file(top_node_file)
340
350
  Log.log.debug("find_files: node_info=#{top_node_info}, fileid=#{top_file_id}")
341
- result=[]
342
- top_node_api=get_node_api(top_node_info,SCOPE_NODE_USER)
343
- # initialize loop elements : list of folders to scan
344
- # Note: top file id is necessarily a folder
345
- items_to_explore=[{:node_api=>top_node_api,:folder_id=>top_file_id,:path=>''}]
346
-
347
- while !items_to_explore.empty? do
348
- current_item = items_to_explore.shift
349
- Log.log.debug("searching #{current_item[:path]}".bg_green)
350
- # get folder content
351
- begin
352
- folder_contents = current_item[:node_api].read("files/#{current_item[:folder_id]}/files")[:data]
353
- rescue => e
354
- Log.log.warn("#{current_item[:path]}: #{e.message}")
355
- folder_contents=[]
351
+ @find_state={found: [], test_block: test_block}
352
+ get_node_api(top_node_info,SCOPE_NODE_USER).crawl(self,{method: :process_find_files, top_file_id: top_file_id})
353
+ result=@find_state[:found]
354
+ @find_state=nil
355
+ return result
356
+ end
357
+
358
+ def process_resolve_node_file(entry,path)
359
+ # stop digging here if not in right path
360
+ return false unless entry['name'].eql?(@resolve_state[:path].first)
361
+ # ok it matches, so we remove the match
362
+ @resolve_state[:path].shift
363
+ case entry['type']
364
+ when 'file'
365
+ # file must be terminal
366
+ raise "#{entry['name']} is a file, expecting folder to find: #{@resolve_state[:path]}" unless @resolve_state[:path].empty?
367
+ @resolve_state[:result][:file_id]=entry['id']
368
+ when 'link'
369
+ @resolve_state[:result][:node_info]=self.read("nodes/#{entry['target_node_id']}")[:data]
370
+ if @resolve_state[:path].empty?
371
+ @resolve_state[:result][:file_id]=entry['target_id']
372
+ else
373
+ get_node_api(@resolve_state[:result][:node_info],SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: entry['target_id']})
356
374
  end
357
- # TODO: check if this is a folder or file ?
358
- Log.dump(:folder_contents,folder_contents)
359
- folder_contents.each do |current_file_info|
360
- item_path=File.join(current_item[:path],current_file_info['name'])
361
- Log.log.debug("looking #{item_path}".bg_green)
362
- begin
363
- # does item match ?
364
- result.push(current_file_info.merge({'path'=>item_path})) if test_block.call(current_file_info)
365
- # does it need further processing ?
366
- case current_file_info['type']
367
- when 'file'
368
- Log.log.debug("testing : #{current_file_info['name']}")
369
- when 'folder'
370
- items_to_explore.push({:node_api=>current_item[:node_api],:folder_id=>current_file_info['id'],:path=>item_path})
371
- when 'link' # .*.asp-lnk
372
- items_to_explore.push(read_asplnk(current_file_info).merge({:path=>item_path}))
373
- else
374
- Log.log.error("unknown folder item type: #{current_file_info['type']}")
375
- end
376
- rescue => e
377
- Log.log.error("#{item_path}: #{e.message}")
378
- end
375
+ when 'folder'
376
+ if @resolve_state[:path].empty?
377
+ # found: store
378
+ @resolve_state[:result][:file_id]=entry['id']
379
+ return false
379
380
  end
381
+ else
382
+ Log.log.warn("unknown element type: #{entry['type']}")
380
383
  end
381
- return result
384
+ # continue to dig folder
385
+ return true
382
386
  end
383
387
 
384
- # @return node information (returned by API) and file id, from a "/" based path
388
+ # @return Array(node_info,file_id) for the given path
389
+ # @param top_node_file Array [root node,file id]
390
+ # @param element_path_string String path of element
385
391
  # supports links to secondary nodes
386
- # input: Array(root node,file id), String path
387
- # output: Array(node_info,file_id) for the given path
388
- def resolve_node_file( top_node_file, element_path_string='' )
389
- Log.log.debug("resolve_node_file: top_node_file=#{top_node_file}, path=#{element_path_string}")
390
- # initialize loop invariants
391
- current_node_info,current_file_id=check_get_node_file(top_node_file)
392
- items_to_explore=element_path_string.split(PATH_SEPARATOR).select{|i| !i.empty?}
393
-
394
- while !items_to_explore.empty? do
395
- current_item = items_to_explore.shift
396
- Log.log.debug "searching #{current_item}".bg_green
397
- # get API if changed
398
- current_node_api=get_node_api(current_node_info,SCOPE_NODE_USER) if current_node_api.nil?
399
- # get folder content
400
- folder_contents = current_node_api.read("files/#{current_file_id}/files")
401
- Log.dump(:folder_contents,folder_contents)
402
- matching_folders = folder_contents[:data].select { |i| i['name'].eql?(current_item)}
403
- #Log.log.debug "matching_folders: #{matching_folders}"
404
- raise "no such folder: #{current_item} in #{folder_contents[:data].map { |i| i['name']}}" if matching_folders.empty?
405
- current_file_info = matching_folders.first
406
- # process type of file
407
- case current_file_info['type']
408
- when 'file'
409
- current_file_id=current_file_info['id']
410
- # a file shall be terminal
411
- if !items_to_explore.empty? then
412
- raise "#{current_item} is a file, expecting folder to find: #{items_to_explore}"
413
- end
414
- when 'link'
415
- current_node_info=self.read("nodes/#{current_file_info['target_node_id']}")[:data]
416
- current_file_id=current_file_info['target_id']
417
- # need to switch node
418
- current_node_api=nil
419
- when 'folder'
420
- current_file_id=current_file_info['id']
421
- else
422
- Log.log.warn("unknown element type: #{current_file_info['type']}")
423
- end
392
+ def resolve_node_file( top_node_file, element_path_string )
393
+ top_node_info,top_file_id=check_get_node_file(top_node_file)
394
+ path_elements=element_path_string.split(PATH_SEPARATOR).select{|i| !i.empty?}
395
+ result={node_info: top_node_info, file_id: nil}
396
+ if path_elements.empty?
397
+ result[:file_id]=top_file_id
398
+ else
399
+ @resolve_state={path: path_elements, result: result}
400
+ get_node_api(top_node_info,SCOPE_NODE_USER).crawl(self,{method: :process_resolve_node_file, top_file_id: top_file_id})
401
+ not_found=@resolve_state[:path]
402
+ @resolve_state=nil
403
+ raise "entry not found: #{not_found}" if result[:file_id].nil?
424
404
  end
425
- Log.log.info("resolve_node_file(#{element_path_string}): file_id=#{current_file_id},node_info=#{current_node_info}")
426
- return {node_info: current_node_info, file_id: current_file_id}
405
+ return result
427
406
  end
428
407
 
429
408
  end # AoC
@@ -151,6 +151,8 @@ module Aspera
151
151
  else
152
152
  if user_asked_fields_list_str.start_with?('+')
153
153
  result_default_fields(results,table_rows_hash_val).push(*user_asked_fields_list_str.gsub(/^\+/,'').split(','))
154
+ elsif user_asked_fields_list_str.start_with?('-')
155
+ result_default_fields(results,table_rows_hash_val).select{|i| ! user_asked_fields_list_str.gsub(/^\-/,'').split(',').include?(i)}
154
156
  else
155
157
  user_asked_fields_list_str.split(',')
156
158
  end