aspera-cli 4.0.0.pre3 → 4.2.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +695 -205
  3. data/bin/dascli +13 -0
  4. data/docs/README.erb.md +615 -157
  5. data/docs/test_env.conf +23 -5
  6. data/docs/transfer_spec.html +1 -1
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +78 -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 +46 -34
  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 +207 -182
  15. data/lib/aspera/cli/plugins/ats.rb +2 -2
  16. data/lib/aspera/cli/plugins/config.rb +173 -117
  17. data/lib/aspera/cli/plugins/console.rb +2 -2
  18. data/lib/aspera/cli/plugins/faspex.rb +51 -36
  19. data/lib/aspera/cli/plugins/faspex5.rb +82 -41
  20. data/lib/aspera/cli/plugins/node.rb +3 -3
  21. data/lib/aspera/cli/plugins/preview.rb +35 -25
  22. data/lib/aspera/cli/plugins/server.rb +23 -8
  23. data/lib/aspera/cli/transfer_agent.rb +7 -6
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/cos_node.rb +33 -28
  26. data/lib/aspera/environment.rb +2 -2
  27. data/lib/aspera/fasp/connect.rb +28 -21
  28. data/lib/aspera/fasp/http_gw.rb +140 -28
  29. data/lib/aspera/fasp/installation.rb +101 -53
  30. data/lib/aspera/fasp/local.rb +88 -45
  31. data/lib/aspera/fasp/manager.rb +15 -0
  32. data/lib/aspera/fasp/node.rb +4 -4
  33. data/lib/aspera/fasp/parameters.rb +6 -18
  34. data/lib/aspera/fasp/resume_policy.rb +13 -12
  35. data/lib/aspera/log.rb +1 -1
  36. data/lib/aspera/node.rb +61 -1
  37. data/lib/aspera/oauth.rb +49 -46
  38. data/lib/aspera/persistency_folder.rb +9 -4
  39. data/lib/aspera/preview/file_types.rb +53 -21
  40. data/lib/aspera/preview/generator.rb +3 -3
  41. data/lib/aspera/rest.rb +29 -18
  42. data/lib/aspera/secrets.rb +20 -0
  43. data/lib/aspera/temp_file_manager.rb +19 -0
  44. data/lib/aspera/web_auth.rb +105 -0
  45. metadata +42 -22
data/docs/test_env.conf CHANGED
@@ -5,8 +5,8 @@ default:
5
5
  config: cli_default
6
6
  aoc: tst_aoc1
7
7
  faspex: tst_faspex
8
- faspex5: tst_faspex5
9
- shares: tst_shares
8
+ faspex5: tst_faspex5_web
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,10 +47,24 @@ tst_node_faspex:
48
47
  url: your value here
49
48
  username: your value here
50
49
  password: your value here
51
- tst_faspex5:
50
+ tst_faspex5_boot:
52
51
  url: your value here
52
+ auth: your value here
53
53
  username: your value here
54
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
61
+ tst_faspex5:
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
67
+ username: your value here
55
68
  tst_shares:
56
69
  url: your value here
57
70
  username: your value here
@@ -94,6 +107,7 @@ tst_ak_preview:
94
107
  url: your value here
95
108
  username: your value here
96
109
  password: your value here
110
+ mimemagic: your value here
97
111
  tst_node_preview:
98
112
  url: your value here
99
113
  username: your value here
@@ -110,6 +124,8 @@ misc:
110
124
  faspex_publink_send_to_fxuser: your value here
111
125
  faspex_publink_send_to_dropbox: your value here
112
126
  shares_upload: your value here
127
+ console_smart_id: your value here
128
+ console_smart_file: your value here
113
129
  orch_workflow_id: your value here
114
130
  file_dcm: your value here
115
131
  file_pdf: your value here
@@ -122,6 +138,7 @@ misc:
122
138
  aoc_publink_recv_from_aocuser: your value here
123
139
  aoc_publink_send_shd_inbox: your value here
124
140
  aoc_publink_send_aoc_user: your value here
141
+ aoc_publink_send_use_pass: your value here
125
142
  aoc_publink_folder: your value here
126
143
  aoc_shbx_ws: your value here
127
144
  aoc_shbx_name: your value here
@@ -137,4 +154,5 @@ misc:
137
154
  email_internal: your value here
138
155
  email_external: your value here
139
156
  aoc_org: your value here
140
- aoc_user: your value here
157
+ aoc_user_email: your value here
158
+ http_gw_fqdn_port: your value here
@@ -28,7 +28,7 @@ arg: related ascp argument or env var suffix (PASS for ASPERA_SCP_PASS)
28
28
  </p>
29
29
  <p>
30
30
  UNDER CONSTRUCTION<br/>
31
- <a href="https://developer.ibm.com/api/view/aspera-prod:ibm-aspera:title-IBM_Aspera#id90944">Documentation&rarr;Node API&rarr;/opt/transfers</a><br/>
31
+ <a href="https://developer.ibm.com/apis/catalog/?search=aspera">Aspera API Documentation</a>&rarr;Node API&rarr;/opt/transfers<br/>
32
32
  </p>
33
33
 
34
34
  <table>
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,78 @@
1
+ #!/usr/bin/env ruby
2
+ require 'aspera/rest'
3
+ require 'aspera/log'
4
+ require 'aspera/fasp/local'
5
+ require 'aspera/cli/listener/line_dump'
6
+ require 'aspera/cli/extended_value'
7
+
8
+ # set high log level for the example
9
+ Aspera::Log.instance.level=:debug
10
+
11
+ # set folder where SDK is installed
12
+ # (if ascp is not there, the lib will try to find in usual locations)
13
+ Aspera::Fasp::Installation.instance.folder='.'
14
+
15
+ if ! ARGV.length.eql?(3)
16
+ Aspera::Log.log.error("wrong number of args: #{ARGV.length}")
17
+ Aspera::Log.log.error("Usage: #{$0} <faspex URL> <faspex username> <faspex password>")
18
+ Aspera::Log.log.error("Example: #{$0} https://faspex.com/aspera/faspex john p@sSw0rd")
19
+ Process.exit(1)
20
+ end
21
+
22
+ faspex_url=ARGV[0]
23
+ faspex_user=ARGV[1]
24
+ faspex_pass=ARGV[2]
25
+
26
+ # 1: demo API v3
27
+ #---------------
28
+
29
+ # create REST API object
30
+ api_v3=Aspera::Rest.new({
31
+ :base_url => faspex_url,
32
+ :auth => {
33
+ :type => :basic,
34
+ :username => faspex_user,
35
+ :password => faspex_pass
36
+ }})
37
+
38
+ api_v3.read('me')
39
+
40
+ # 2: send a package
41
+ #---------------
42
+
43
+ # create a sample file to send
44
+ file_to_send='./myfile.bin'
45
+ File.open(file_to_send, "w") {|f| f.write("sample data") }
46
+ # package creation parameters
47
+ package_create_params={'delivery'=>{'title'=>'test package','recipients'=>['aspera.user1@gmail.com'],'sources'=>[{'paths'=>[file_to_send]}]}}
48
+ pkg_created=api_v3.create('send',package_create_params)[:data]
49
+ # get transfer specification
50
+ transfer_spec=pkg_created['xfer_sessions'].first
51
+ # set paths of files to send
52
+ transfer_spec['paths']=['source'=>file_to_send]
53
+ # get the local agent (i.e. ascp)
54
+ client=Aspera::Fasp::Local.new
55
+ # disable ascp output on stdout to not mix with JSON events
56
+ client.quiet=true
57
+ # start transfer (asynchronous)
58
+ job_id=client.start_transfer(transfer_spec)
59
+ result=client.wait_for_transfers_completion
60
+ # notify of any transfer error
61
+ result.select{|i|!i.eql?(:success)}.each do |e|
62
+ Aspera::Log.log.error("A transfer error occured: #{e.message}")
63
+ end
64
+
65
+ # 1: demo API v4
66
+ #---------------
67
+ api_v4=Aspera::Rest.new({
68
+ :base_url => faspex_url+'/api',
69
+ :auth => {
70
+ :type => :oauth2,
71
+ :base_url => faspex_url+'/auth/oauth2',
72
+ :grant => :header_userpass,
73
+ :user_name => faspex_user,
74
+ :user_pass => faspex_pass,
75
+ :scope => 'admin'
76
+ }})
77
+
78
+ 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