aspera-cli 4.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3592 -0
  3. data/bin/ascli +7 -0
  4. data/bin/asession +89 -0
  5. data/docs/Makefile +59 -0
  6. data/docs/README.erb.md +3012 -0
  7. data/docs/README.md +13 -0
  8. data/docs/diagrams.txt +49 -0
  9. data/docs/secrets.make +38 -0
  10. data/docs/test_env.conf +117 -0
  11. data/docs/transfer_spec.html +99 -0
  12. data/examples/aoc.rb +17 -0
  13. data/examples/proxy.pac +60 -0
  14. data/examples/transfer.rb +115 -0
  15. data/lib/aspera/api_detector.rb +60 -0
  16. data/lib/aspera/ascmd.rb +151 -0
  17. data/lib/aspera/ats_api.rb +43 -0
  18. data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
  19. data/lib/aspera/cli/extended_value.rb +88 -0
  20. data/lib/aspera/cli/formater.rb +238 -0
  21. data/lib/aspera/cli/listener/line_dump.rb +17 -0
  22. data/lib/aspera/cli/listener/logger.rb +20 -0
  23. data/lib/aspera/cli/listener/progress.rb +52 -0
  24. data/lib/aspera/cli/listener/progress_multi.rb +91 -0
  25. data/lib/aspera/cli/main.rb +304 -0
  26. data/lib/aspera/cli/manager.rb +440 -0
  27. data/lib/aspera/cli/plugin.rb +90 -0
  28. data/lib/aspera/cli/plugins/alee.rb +24 -0
  29. data/lib/aspera/cli/plugins/ats.rb +231 -0
  30. data/lib/aspera/cli/plugins/bss.rb +71 -0
  31. data/lib/aspera/cli/plugins/config.rb +806 -0
  32. data/lib/aspera/cli/plugins/console.rb +62 -0
  33. data/lib/aspera/cli/plugins/cos.rb +106 -0
  34. data/lib/aspera/cli/plugins/faspex.rb +377 -0
  35. data/lib/aspera/cli/plugins/faspex5.rb +93 -0
  36. data/lib/aspera/cli/plugins/node.rb +438 -0
  37. data/lib/aspera/cli/plugins/oncloud.rb +937 -0
  38. data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
  39. data/lib/aspera/cli/plugins/preview.rb +464 -0
  40. data/lib/aspera/cli/plugins/server.rb +216 -0
  41. data/lib/aspera/cli/plugins/shares.rb +63 -0
  42. data/lib/aspera/cli/plugins/shares2.rb +114 -0
  43. data/lib/aspera/cli/plugins/sync.rb +65 -0
  44. data/lib/aspera/cli/plugins/xnode.rb +115 -0
  45. data/lib/aspera/cli/transfer_agent.rb +251 -0
  46. data/lib/aspera/cli/version.rb +5 -0
  47. data/lib/aspera/colors.rb +39 -0
  48. data/lib/aspera/command_line_builder.rb +137 -0
  49. data/lib/aspera/fasp/aoc.rb +24 -0
  50. data/lib/aspera/fasp/connect.rb +99 -0
  51. data/lib/aspera/fasp/error.rb +21 -0
  52. data/lib/aspera/fasp/error_info.rb +60 -0
  53. data/lib/aspera/fasp/http_gw.rb +81 -0
  54. data/lib/aspera/fasp/installation.rb +240 -0
  55. data/lib/aspera/fasp/listener.rb +11 -0
  56. data/lib/aspera/fasp/local.rb +377 -0
  57. data/lib/aspera/fasp/manager.rb +69 -0
  58. data/lib/aspera/fasp/node.rb +88 -0
  59. data/lib/aspera/fasp/parameters.rb +235 -0
  60. data/lib/aspera/fasp/resume_policy.rb +76 -0
  61. data/lib/aspera/fasp/uri.rb +51 -0
  62. data/lib/aspera/faspex_gw.rb +196 -0
  63. data/lib/aspera/hash_ext.rb +28 -0
  64. data/lib/aspera/log.rb +80 -0
  65. data/lib/aspera/nagios.rb +71 -0
  66. data/lib/aspera/node.rb +14 -0
  67. data/lib/aspera/oauth.rb +319 -0
  68. data/lib/aspera/on_cloud.rb +421 -0
  69. data/lib/aspera/open_application.rb +72 -0
  70. data/lib/aspera/persistency_action_once.rb +42 -0
  71. data/lib/aspera/persistency_folder.rb +91 -0
  72. data/lib/aspera/preview/file_types.rb +300 -0
  73. data/lib/aspera/preview/generator.rb +258 -0
  74. data/lib/aspera/preview/image_error.png +0 -0
  75. data/lib/aspera/preview/options.rb +35 -0
  76. data/lib/aspera/preview/utils.rb +131 -0
  77. data/lib/aspera/preview/video_error.png +0 -0
  78. data/lib/aspera/proxy_auto_config.erb.js +287 -0
  79. data/lib/aspera/proxy_auto_config.rb +34 -0
  80. data/lib/aspera/rest.rb +296 -0
  81. data/lib/aspera/rest_call_error.rb +13 -0
  82. data/lib/aspera/rest_error_analyzer.rb +98 -0
  83. data/lib/aspera/rest_errors_aspera.rb +58 -0
  84. data/lib/aspera/ssh.rb +53 -0
  85. data/lib/aspera/sync.rb +82 -0
  86. data/lib/aspera/temp_file_manager.rb +37 -0
  87. data/lib/aspera/uri_reader.rb +25 -0
  88. metadata +288 -0
@@ -0,0 +1,421 @@
1
+ require 'aspera/log'
2
+ require 'aspera/rest'
3
+ require 'aspera/hash_ext'
4
+ require 'base64'
5
+
6
+ module Aspera
7
+ class OnCloud < Rest
8
+ private
9
+ @@use_standard_ports = true
10
+
11
+ PRODUCT_NAME='Aspera on Cloud'
12
+ # Production domain of AoC
13
+ PROD_DOMAIN='ibmaspera.com'
14
+ # to avoid infinite loop in pub link redirection
15
+ MAX_REDIRECT=10
16
+ DEFAULT_CLIENT='aspera.global-cli-client'
17
+ # Random generator seed
18
+ # strings /Applications/Aspera\ Drive.app/Contents/MacOS/AsperaDrive|grep -E '.{100}==$'|base64 --decode
19
+ CLIENT_RANDOM={
20
+ 'aspera.drive' => '1FwelGbL9xsv3M8H-VXPs5k69OdbaMgfB66qfBqlELFPk6r9ANztmGMOSLqaXEWXEPwk-6JnMZ7-RaXAYLd5thLbcL3QzgeU',
21
+ 'aspera.global-cli-client' => 'bK_qpqFpP-OPEuRJ9mnmdw_ebLtpSqCnqhuAKfKdoLXC6OF2yLMgsfAMBmXg7XI_zplV4gBqNOvlJdgCxlP0Zjm4GsRsmprf'
22
+ }
23
+ # path in URL of public links
24
+ PATHS_PUBLIC_LINK=['/packages/public/receive','/packages/public/send','/files/public']
25
+ JWT_AUDIENCE='https://api.asperafiles.com/api/v1/oauth2/token'
26
+ OAUTH_API_SUBPATH='api/v1/oauth2'
27
+ DEFAULT_TSPEC_INFO={
28
+ 'remote_user' => 'xfer',
29
+ 'ssh_port' => 33001,
30
+ 'fasp_port' => 33001
31
+ }
32
+
33
+ private_constant :PRODUCT_NAME,:PROD_DOMAIN,:MAX_REDIRECT,:DEFAULT_CLIENT,:CLIENT_RANDOM,:PATHS_PUBLIC_LINK,:JWT_AUDIENCE,:OAUTH_API_SUBPATH,:DEFAULT_TSPEC_INFO
34
+
35
+ public
36
+ # various API scopes supported
37
+ SCOPE_FILES_SELF='self'
38
+ SCOPE_FILES_USER='user:all'
39
+ SCOPE_FILES_ADMIN='admin:all'
40
+ SCOPE_FILES_ADMIN_USER='admin-user:all'
41
+ SCOPE_FILES_ADMIN_USER_USER=SCOPE_FILES_ADMIN_USER+'+'+SCOPE_FILES_USER
42
+ SCOPE_NODE_USER='user:all'
43
+ SCOPE_NODE_ADMIN='admin:all'
44
+ PATH_SEPARATOR='/'
45
+ FILES_APP='files'
46
+ PACKAGES_APP='packages'
47
+
48
+ def self.get_client_ids(id,secret,client_name=DEFAULT_CLIENT)
49
+ return id,secret unless id.nil? and secret.nil?
50
+ return client_name,CLIENT_RANDOM[client_name].reverse
51
+ end
52
+
53
+ # @param url of AoC instance
54
+ # @return organization id in url and AoC domain: ibmaspera.com, asperafiles.com or qa.asperafiles.com, etc...
55
+ def self.parse_url(aoc_org_url)
56
+ uri=URI.parse(aoc_org_url.gsub(/\/+$/,''))
57
+ instance_fqdn=uri.host
58
+ Log.log.debug("instance_fqdn=#{instance_fqdn}")
59
+ raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if instance_fqdn.nil?
60
+ organization,instance_domain=instance_fqdn.split('.',2)
61
+ Log.log.debug("instance_domain=#{instance_domain}")
62
+ Log.log.debug("organization=#{organization}")
63
+ raise "expecting a public FQDN for #{PRODUCT_NAME}" if instance_domain.nil?
64
+ return organization,instance_domain
65
+ end
66
+
67
+ def self.metering_api(entitlement_id,customer_id,api_domain=PROD_DOMAIN)
68
+ return Rest.new({
69
+ :base_url => "https://api.#{api_domain}/metering/v1",
70
+ :headers => {'X-Aspera-Entitlement-Authorization' => Rest.basic_creds(entitlement_id,customer_id)}
71
+ })
72
+ end
73
+
74
+ # node API scopes
75
+ def self.node_scope(access_key,scope)
76
+ return 'node.'+access_key+':'+scope
77
+ end
78
+
79
+ def self.set_use_default_ports(val)
80
+ @@use_standard_ports=val
81
+ end
82
+
83
+ # check option "link"
84
+ # if present try to get token value (resolve redirection if short links used)
85
+ # then set options url/token/auth
86
+ def self.resolve_pub_link(rest_opts,public_link_url)
87
+ return if public_link_url.nil?
88
+ # set to token if available after redirection
89
+ url_param_token_pair=nil
90
+ redirect_count=0
91
+ loop do
92
+ uri=URI.parse(public_link_url)
93
+ if PATHS_PUBLIC_LINK.include?(uri.path)
94
+ url_param_token_pair=URI::decode_www_form(uri.query).select{|e|e.first.eql?('token')}.first
95
+ if url_param_token_pair.nil?
96
+ raise ArgumentError,"link option must be URL with 'token' parameter"
97
+ end
98
+ # ok we get it !
99
+ rest_opts[:org_url]='https://'+uri.host
100
+ rest_opts[:auth][:grant]=:url_token
101
+ rest_opts[:auth][:url_token]=url_param_token_pair.last
102
+ return
103
+ end
104
+ Log.log.debug("no expected format: #{public_link_url}")
105
+ raise "exceeded max redirection: #{MAX_REDIRECT}" if redirect_count > MAX_REDIRECT
106
+ r = Net::HTTP.get_response(uri)
107
+ if r.code.start_with?("3")
108
+ public_link_url = r['location']
109
+ raise "no location in redirection" if public_link_url.nil?
110
+ Log.log.debug("redirect to: #{public_link_url}")
111
+ else
112
+ # not a redirection
113
+ raise ArgumentError,'link option must be redirect or have token parameter'
114
+ end
115
+ end # loop
116
+
117
+ raise RuntimeError,'too many redirections'
118
+ end
119
+
120
+ # @param :link,:url,:auth,:client_id,:client_secret,:scope,:redirect_uri,:private_key,:username,:subpath
121
+ def initialize(opt)
122
+ # access key secrets are provided out of band to get node api access
123
+ # key: access key
124
+ # value: associated secret
125
+ @secrets={}
126
+
127
+ # init rest params
128
+ aoc_rest_p={:auth=>{:type =>:oauth2}}
129
+ # shortcut to auth section
130
+ aoc_auth_p=aoc_rest_p[:auth]
131
+
132
+ # sets [:org_url], [:auth][:grant], [:auth][:url_token]
133
+ self.class.resolve_pub_link(aoc_rest_p,opt[:link])
134
+
135
+ if aoc_rest_p.has_key?(:org_url)
136
+ # Pub Link only: get org url from pub link
137
+ opt[:url] = aoc_rest_p[:org_url]
138
+ aoc_rest_p.delete(:org_url)
139
+ else
140
+ # else url is mandatory
141
+ raise ArgumentError,"Missing mandatory option: url" if opt[:url].nil?
142
+ end
143
+
144
+ # get org name and domain from url
145
+ organization,instance_domain=self.class.parse_url(opt[:url])
146
+ # this is the base API url (depends on domain, which could be "qa.xxx")
147
+ api_base_url="https://api.#{instance_domain}"
148
+ # base API URL
149
+ aoc_rest_p[:base_url]="#{api_base_url}/#{opt[:subpath]}"
150
+ # base auth URL
151
+ aoc_auth_p[:base_url] = "#{api_base_url}/#{OAUTH_API_SUBPATH}/#{organization}"
152
+
153
+ if !aoc_auth_p.has_key?(:grant)
154
+ raise ArgumentError,"Missing mandatory option: auth" if opt[:auth].nil?
155
+ aoc_auth_p[:grant] = opt[:auth]
156
+ end
157
+
158
+ aoc_auth_p[:client_id],aoc_auth_p[:client_secret] = self.class.get_client_ids(opt[:client_id],opt[:client_secret])
159
+ raise ArgumentError,"Missing mandatory option: scope" if opt[:scope].nil?
160
+ aoc_auth_p[:scope] = opt[:scope]
161
+
162
+ # fill other auth parameters based on Oauth method
163
+ case aoc_auth_p[:grant]
164
+ when :web
165
+ raise ArgumentError,"Missing mandatory option: redirect_uri" if opt[:redirect_uri].nil?
166
+ aoc_auth_p[:redirect_uri] = opt[:redirect_uri]
167
+ when :jwt
168
+ # add jwt payload for global ids
169
+ if CLIENT_RANDOM.keys.include?(aoc_auth_p[:client_id])
170
+ aoc_auth_p.merge!({:jwt_add=>{org: organization}})
171
+ end
172
+ raise ArgumentError,"Missing mandatory option: private_key" if opt[:private_key].nil?
173
+ raise ArgumentError,"Missing mandatory option: username" if opt[:username].nil?
174
+ private_key_PEM_string=opt[:private_key]
175
+ aoc_auth_p[:jwt_audience] = JWT_AUDIENCE
176
+ aoc_auth_p[:jwt_subject] = opt[:username]
177
+ aoc_auth_p[:jwt_private_key_obj] = OpenSSL::PKey::RSA.new(private_key_PEM_string)
178
+ when :url_token
179
+ # nothing more
180
+ else raise "ERROR: unsupported auth method: #{aoc_auth_p[:grant]}"
181
+ end
182
+ super(aoc_rest_p)
183
+ end
184
+
185
+ def add_secrets(secrets)
186
+ @secrets.merge!(secrets)
187
+ Log.log.debug("now secrets:#{secrets}")
188
+ nil
189
+ end
190
+
191
+ def has_secret(ak)
192
+ Log.log.debug("has key:#{ak} -> #{@secrets.has_key?(ak)}")
193
+ return @secrets.has_key?(ak)
194
+ end
195
+
196
+ # additional transfer spec (tags) for package information
197
+ def self.package_tags(package_info,operation)
198
+ return {'tags'=>{'aspera'=>{'files'=>{
199
+ 'package_id' => package_info['id'],
200
+ 'package_name' => package_info['name'],
201
+ 'package_operation' => operation
202
+ }}}}
203
+ end
204
+
205
+ # add details to show in analytics
206
+ def self.analytics_ts(app,direction,ws_id,ws_name)
207
+ # translate transfer to operation
208
+ operation=case direction
209
+ when 'send'; 'upload'
210
+ when 'receive'; 'download'
211
+ else raise "ERROR: unexpected value: #{direction}"
212
+ end
213
+
214
+ return {
215
+ 'tags' => {
216
+ 'aspera' => {
217
+ 'usage_id' => "aspera.files.workspace.#{ws_id}", # activity tracking
218
+ 'files' => {
219
+ 'files_transfer_action' => "#{operation}_#{app.gsub(/s$/,'')}",
220
+ 'workspace_name' => ws_name, # activity tracking
221
+ 'workspace_id' => ws_id,
222
+ }
223
+ }
224
+ }
225
+ }
226
+ end
227
+
228
+ # build ts addon for IBM Aspera Console (cookie)
229
+ def self.console_ts(app,user_name,user_email)
230
+ elements=[app,user_name,user_email].map{|e|Base64.strict_encode64(e)}
231
+ elements.unshift('aspera.aoc')
232
+ #Log.dump('elem1'.bg_red,elements[1])
233
+ return {
234
+ 'cookie'=>elements.join(':')
235
+ }
236
+ end
237
+
238
+ # build "transfer info", 2 elements array with:
239
+ # - transfer spec for aspera on cloud, based on node information and file id
240
+ # - source and token regeneration method
241
+ def tr_spec(app,direction,node_file,ts_add)
242
+ # prepare the rest end point is used to generate the bearer token
243
+ token_generation_method=lambda {|do_refresh|self.oauth_token(scope: self.class.node_scope(node_file[:node_info]['access_key'],SCOPE_NODE_USER), refresh: do_refresh)}
244
+ # prepare transfer specification
245
+ # note xfer_id and xfer_retry are set by the transfer agent itself
246
+ transfer_spec={
247
+ 'direction' => direction,
248
+ 'token' => token_generation_method.call(false), # first time, use cache
249
+ 'tags' => {
250
+ 'aspera' => {
251
+ 'app' => app,
252
+ 'files' => {
253
+ 'node_id' => node_file[:node_info]['id'],
254
+ }, # files
255
+ 'node' => {
256
+ 'access_key' => node_file[:node_info]['access_key'],
257
+ #'file_id' => ts_add['source_root_id']
258
+ 'file_id' => node_file[:file_id]
259
+ } # node
260
+ } # aspera
261
+ } # tags
262
+ }
263
+ # add remote host info
264
+ if @@use_standard_ports
265
+ transfer_spec.merge!(DEFAULT_TSPEC_INFO)
266
+ transfer_spec['remote_host']=node_file[:node_info]['host']
267
+ else
268
+ # retrieve values from API
269
+ std_t_spec=get_node_api(node_file[:node_info],SCOPE_NODE_USER).create('files/download_setup',{:transfer_requests => [ { :transfer_request => {:paths => [ {"source"=>'/'} ] } } ] } )[:data]['transfer_specs'].first['transfer_spec']
270
+ ['remote_host','remote_user','ssh_port','fasp_port'].each {|i| transfer_spec[i]=std_t_spec[i]}
271
+ end
272
+ # add caller provided transfer spec
273
+ transfer_spec.deep_merge!(ts_add)
274
+ # additional information for transfer agent
275
+ source_and_token_generator={
276
+ :src => :node_gen4,
277
+ :regenerate_token => token_generation_method
278
+ }
279
+ return transfer_spec,source_and_token_generator
280
+ end
281
+
282
+ # returns a node API for access key
283
+ # @param scope e.g. SCOPE_NODE_USER
284
+ # no scope: requires secret
285
+ # if secret provided beforehand: use it
286
+ def get_node_api(node_info,node_scope=nil)
287
+ node_rest_params={
288
+ :base_url => node_info['url'],
289
+ :headers => {'X-Aspera-AccessKey'=>node_info['access_key']},
290
+ }
291
+ ak_secret=@secrets[node_info['access_key']]
292
+ if ak_secret.nil? and node_scope.nil?
293
+ raise 'There must be at least one of: secret, node scope'
294
+ end
295
+ # if secret provided on command line or if there is no scope
296
+ if !ak_secret.nil? or node_scope.nil?
297
+ node_rest_params[:auth]={
298
+ :type => :basic,
299
+ :username => node_info['access_key'],
300
+ :password => ak_secret
301
+ }
302
+ else
303
+ node_rest_params[:auth]=self.params[:auth].clone
304
+ node_rest_params[:auth][:scope]=self.class.node_scope(node_info['access_key'],node_scope)
305
+ end
306
+ return Rest.new(node_rest_params)
307
+ end
308
+
309
+ # check that parameter has necessary types
310
+ # @return split values
311
+ def check_get_node_file(node_file)
312
+ raise "node_file must be Hash (got #{node_file.class})" unless node_file.is_a?(Hash)
313
+ raise "node_file must have 2 keys: :file_id and :node_info" unless node_file.keys.sort.eql?([:file_id,:node_info])
314
+ node_info=node_file[:node_info]
315
+ file_id=node_file[:file_id]
316
+ raise "node_info must be Hash (got #{node_info.class}: #{node_info})" unless node_info.is_a?(Hash)
317
+ raise 'node_info must have id' unless node_info.has_key?('id')
318
+ raise 'file_id is empty' if file_id.to_s.empty?
319
+ return node_info,file_id
320
+ end
321
+
322
+ # returns node api and folder_id from soft link
323
+ def read_asplnk(current_file_info)
324
+ new_node_api=get_node_api(self.read("nodes/#{current_file_info['target_node_id']}")[:data],SCOPE_NODE_USER)
325
+ return {:node_api=>new_node_api,:folder_id=>current_file_info['target_id']}
326
+ end
327
+
328
+ # @returns list of file paths that match given regex
329
+ def find_files( top_node_file, test_block )
330
+ top_node_info,top_file_id=check_get_node_file(top_node_file)
331
+ Log.log.debug("find_files: node_info=#{top_node_info}, fileid=#{top_file_id}")
332
+ result=[]
333
+ top_node_api=get_node_api(top_node_info,SCOPE_NODE_USER)
334
+ # initialize loop elements : list of folders to scan
335
+ # Note: top file id is necessarily a folder
336
+ items_to_explore=[{:node_api=>top_node_api,:folder_id=>top_file_id,:path=>''}]
337
+
338
+ while !items_to_explore.empty? do
339
+ current_item = items_to_explore.shift
340
+ Log.log.debug("searching #{current_item[:path]}".bg_green)
341
+ # get folder content
342
+ begin
343
+ folder_contents = current_item[:node_api].read("files/#{current_item[:folder_id]}/files")[:data]
344
+ rescue => e
345
+ Log.log.warn("#{current_item[:path]}: #{e.message}")
346
+ folder_contents=[]
347
+ end
348
+ # TODO: check if this is a folder or file ?
349
+ Log.dump(:folder_contents,folder_contents)
350
+ folder_contents.each do |current_file_info|
351
+ item_path=File.join(current_item[:path],current_file_info['name'])
352
+ Log.log.debug("looking #{item_path}".bg_green)
353
+ begin
354
+ # does item match ?
355
+ result.push(current_file_info.merge({'path'=>item_path})) if test_block.call(current_file_info)
356
+ # does it need further processing ?
357
+ case current_file_info['type']
358
+ when 'file'
359
+ Log.log.debug("testing : #{current_file_info['name']}")
360
+ when 'folder'
361
+ items_to_explore.push({:node_api=>current_item[:node_api],:folder_id=>current_file_info['id'],:path=>item_path})
362
+ when 'link' # .*.asp-lnk
363
+ items_to_explore.push(read_asplnk(current_file_info).merge({:path=>item_path}))
364
+ else
365
+ Log.log.error("unknown folder item type: #{current_file_info['type']}")
366
+ end
367
+ rescue => e
368
+ Log.log.error("#{item_path}: #{e.message}")
369
+ end
370
+ end
371
+ end
372
+ return result
373
+ end
374
+
375
+ # @return node information (returned by API) and file id, from a "/" based path
376
+ # supports links to secondary nodes
377
+ # input: Array(root node,file id), String path
378
+ # output: Array(node_info,file_id) for the given path
379
+ def resolve_node_file( top_node_file, element_path_string='' )
380
+ Log.log.debug("resolve_node_file: top_node_file=#{top_node_file}, path=#{element_path_string}")
381
+ # initialize loop invariants
382
+ current_node_info,current_file_id=check_get_node_file(top_node_file)
383
+ items_to_explore=element_path_string.split(PATH_SEPARATOR).select{|i| !i.empty?}
384
+
385
+ while !items_to_explore.empty? do
386
+ current_item = items_to_explore.shift
387
+ Log.log.debug "searching #{current_item}".bg_green
388
+ # get API if changed
389
+ current_node_api=get_node_api(current_node_info,SCOPE_NODE_USER) if current_node_api.nil?
390
+ # get folder content
391
+ folder_contents = current_node_api.read("files/#{current_file_id}/files")
392
+ Log.dump(:folder_contents,folder_contents)
393
+ matching_folders = folder_contents[:data].select { |i| i['name'].eql?(current_item)}
394
+ #Log.log.debug "matching_folders: #{matching_folders}"
395
+ raise "no such folder: #{current_item} in #{folder_contents[:data].map { |i| i['name']}}" if matching_folders.empty?
396
+ current_file_info = matching_folders.first
397
+ # process type of file
398
+ case current_file_info['type']
399
+ when 'file'
400
+ current_file_id=current_file_info['id']
401
+ # a file shall be terminal
402
+ if !items_to_explore.empty? then
403
+ raise "#{current_item} is a file, expecting folder to find: #{items_to_explore}"
404
+ end
405
+ when 'link'
406
+ current_node_info=self.read("nodes/#{current_file_info['target_node_id']}")[:data]
407
+ current_file_id=current_file_info['target_id']
408
+ # need to switch node
409
+ current_node_api=nil
410
+ when 'folder'
411
+ current_file_id=current_file_info['id']
412
+ else
413
+ Log.log.warn("unknown element type: #{current_file_info['type']}")
414
+ end
415
+ end
416
+ Log.log.info("resolve_node_file(#{element_path_string}): file_id=#{current_file_id},node_info=#{current_node_info}")
417
+ return {node_info: current_node_info, file_id: current_file_id}
418
+ end
419
+
420
+ end # OnCloud
421
+ end # Aspera
@@ -0,0 +1,72 @@
1
+ require 'aspera/log'
2
+ require 'rbconfig'
3
+ require 'singleton'
4
+
5
+ module Aspera
6
+ # Allows a user to open a Url
7
+ # if method is "text", then URL is displayed on terminal
8
+ # if method is "graphical", then the URL will be opened with the default browser.
9
+ class OpenApplication
10
+ include Singleton
11
+ # User Interfaces
12
+ def self.user_interfaces; [ :text, :graphical ]; end
13
+
14
+ def self.default_gui_mode
15
+ case current_os_type
16
+ when :windows,:mac
17
+ return :graphical
18
+ else # unix family
19
+ if ENV.has_key?("DISPLAY") and !ENV["DISPLAY"].empty?
20
+ return :graphical
21
+ end
22
+ return :text
23
+ end
24
+ end
25
+
26
+ def self.current_os_type
27
+ case RbConfig::CONFIG['host_os']
28
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
29
+ return :windows
30
+ when /darwin|mac os/
31
+ return :mac
32
+ else # unix family
33
+ return :unix
34
+ end
35
+ end
36
+
37
+ # command must be non blocking
38
+ def self.uri_graphical(uri)
39
+ case current_os_type
40
+ when :mac
41
+ return system('open',uri.to_s)
42
+ when :windows
43
+ return system('start explorer "'+uri.to_s+'"')
44
+ else # unix family
45
+ return system("xdg-open '#{uri.to_s}'")
46
+ end
47
+ end
48
+
49
+ attr_accessor :url_method
50
+
51
+ def initialize
52
+ @url_method=self.class.default_gui_mode
53
+ end
54
+
55
+ # this is non blocking
56
+ def uri(the_url)
57
+ case @url_method
58
+ when :graphical
59
+ self.class.uri_graphical(the_url)
60
+ when :text
61
+ case the_url.to_s
62
+ when /^http/
63
+ puts "USER ACTION: please enter this url in a browser:\n"+the_url.to_s.red()+"\n"
64
+ else
65
+ puts "USER ACTION: open this:\n"+the_url.to_s.red()+"\n"
66
+ end
67
+ else
68
+ raise StandardError,"unsupported url open method: #{@url_method}"
69
+ end
70
+ end
71
+ end # OpenApplication
72
+ end # Aspera