aspera-cli 4.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +3592 -0
- data/bin/ascli +7 -0
- data/bin/asession +89 -0
- data/docs/Makefile +59 -0
- data/docs/README.erb.md +3012 -0
- data/docs/README.md +13 -0
- data/docs/diagrams.txt +49 -0
- data/docs/secrets.make +38 -0
- data/docs/test_env.conf +117 -0
- data/docs/transfer_spec.html +99 -0
- data/examples/aoc.rb +17 -0
- data/examples/proxy.pac +60 -0
- data/examples/transfer.rb +115 -0
- data/lib/aspera/api_detector.rb +60 -0
- data/lib/aspera/ascmd.rb +151 -0
- data/lib/aspera/ats_api.rb +43 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
- data/lib/aspera/cli/extended_value.rb +88 -0
- data/lib/aspera/cli/formater.rb +238 -0
- data/lib/aspera/cli/listener/line_dump.rb +17 -0
- data/lib/aspera/cli/listener/logger.rb +20 -0
- data/lib/aspera/cli/listener/progress.rb +52 -0
- data/lib/aspera/cli/listener/progress_multi.rb +91 -0
- data/lib/aspera/cli/main.rb +304 -0
- data/lib/aspera/cli/manager.rb +440 -0
- data/lib/aspera/cli/plugin.rb +90 -0
- data/lib/aspera/cli/plugins/alee.rb +24 -0
- data/lib/aspera/cli/plugins/ats.rb +231 -0
- data/lib/aspera/cli/plugins/bss.rb +71 -0
- data/lib/aspera/cli/plugins/config.rb +806 -0
- data/lib/aspera/cli/plugins/console.rb +62 -0
- data/lib/aspera/cli/plugins/cos.rb +106 -0
- data/lib/aspera/cli/plugins/faspex.rb +377 -0
- data/lib/aspera/cli/plugins/faspex5.rb +93 -0
- data/lib/aspera/cli/plugins/node.rb +438 -0
- data/lib/aspera/cli/plugins/oncloud.rb +937 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
- data/lib/aspera/cli/plugins/preview.rb +464 -0
- data/lib/aspera/cli/plugins/server.rb +216 -0
- data/lib/aspera/cli/plugins/shares.rb +63 -0
- data/lib/aspera/cli/plugins/shares2.rb +114 -0
- data/lib/aspera/cli/plugins/sync.rb +65 -0
- data/lib/aspera/cli/plugins/xnode.rb +115 -0
- data/lib/aspera/cli/transfer_agent.rb +251 -0
- data/lib/aspera/cli/version.rb +5 -0
- data/lib/aspera/colors.rb +39 -0
- data/lib/aspera/command_line_builder.rb +137 -0
- data/lib/aspera/fasp/aoc.rb +24 -0
- data/lib/aspera/fasp/connect.rb +99 -0
- data/lib/aspera/fasp/error.rb +21 -0
- data/lib/aspera/fasp/error_info.rb +60 -0
- data/lib/aspera/fasp/http_gw.rb +81 -0
- data/lib/aspera/fasp/installation.rb +240 -0
- data/lib/aspera/fasp/listener.rb +11 -0
- data/lib/aspera/fasp/local.rb +377 -0
- data/lib/aspera/fasp/manager.rb +69 -0
- data/lib/aspera/fasp/node.rb +88 -0
- data/lib/aspera/fasp/parameters.rb +235 -0
- data/lib/aspera/fasp/resume_policy.rb +76 -0
- data/lib/aspera/fasp/uri.rb +51 -0
- data/lib/aspera/faspex_gw.rb +196 -0
- data/lib/aspera/hash_ext.rb +28 -0
- data/lib/aspera/log.rb +80 -0
- data/lib/aspera/nagios.rb +71 -0
- data/lib/aspera/node.rb +14 -0
- data/lib/aspera/oauth.rb +319 -0
- data/lib/aspera/on_cloud.rb +421 -0
- data/lib/aspera/open_application.rb +72 -0
- data/lib/aspera/persistency_action_once.rb +42 -0
- data/lib/aspera/persistency_folder.rb +91 -0
- data/lib/aspera/preview/file_types.rb +300 -0
- data/lib/aspera/preview/generator.rb +258 -0
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +35 -0
- data/lib/aspera/preview/utils.rb +131 -0
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.erb.js +287 -0
- data/lib/aspera/proxy_auto_config.rb +34 -0
- data/lib/aspera/rest.rb +296 -0
- data/lib/aspera/rest_call_error.rb +13 -0
- data/lib/aspera/rest_error_analyzer.rb +98 -0
- data/lib/aspera/rest_errors_aspera.rb +58 -0
- data/lib/aspera/ssh.rb +53 -0
- data/lib/aspera/sync.rb +82 -0
- data/lib/aspera/temp_file_manager.rb +37 -0
- data/lib/aspera/uri_reader.rb +25 -0
- 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
|