aspera-cli 4.14.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# cspell:ignore precalc
|
|
3
4
|
require 'aspera/fasp/agent_base'
|
|
4
5
|
require 'aspera/fasp/transfer_spec'
|
|
5
6
|
require 'aspera/node'
|
|
6
7
|
require 'aspera/log'
|
|
7
|
-
require '
|
|
8
|
+
require 'aspera/assert'
|
|
9
|
+
require 'aspera/oauth'
|
|
8
10
|
|
|
9
11
|
module Aspera
|
|
10
12
|
module Fasp
|
|
11
13
|
# this singleton class is used by the CLI to provide a common interface to start a transfer
|
|
12
14
|
# before using it, the use must set the `node_api` member.
|
|
13
15
|
class AgentNode < Aspera::Fasp::AgentBase
|
|
16
|
+
DEFAULT_OPTIONS = {
|
|
17
|
+
url: :required,
|
|
18
|
+
username: :required,
|
|
19
|
+
password: :required,
|
|
20
|
+
root_id: nil
|
|
21
|
+
}.freeze
|
|
14
22
|
# option include: root_id if the node is an access key
|
|
15
|
-
attr_writer :options
|
|
23
|
+
# attr_writer :options
|
|
16
24
|
|
|
17
|
-
def initialize(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
def initialize(opts)
|
|
26
|
+
assert_type(opts, Hash){'node agent options'}
|
|
27
|
+
super(opts)
|
|
28
|
+
options = AgentBase.options(default: DEFAULT_OPTIONS, options: opts)
|
|
21
29
|
# root id is required for access key
|
|
22
30
|
@root_id = options[:root_id]
|
|
23
31
|
rest_params = { base_url: options[:url]}
|
|
24
|
-
if
|
|
25
|
-
rest_params[:headers] = {
|
|
26
|
-
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => options[:username],
|
|
27
|
-
'Authorization' => options[:password]
|
|
28
|
-
}
|
|
32
|
+
if Oauth.bearer?(options[:password])
|
|
29
33
|
raise 'root_id is required for access key' if @root_id.nil?
|
|
34
|
+
rest_params[:headers] = Aspera::Node.bearer_headers(options[:password], access_key: options[:username])
|
|
30
35
|
else
|
|
31
36
|
rest_params[:auth] = {
|
|
32
37
|
type: :basic,
|
|
@@ -37,6 +42,7 @@ module Aspera
|
|
|
37
42
|
@node_api = Rest.new(rest_params)
|
|
38
43
|
# TODO: currently only supports one transfer. This is bad shortcut. but ok for CLI.
|
|
39
44
|
@transfer_id = nil
|
|
45
|
+
# Log.log.debug{Log.dump(:agent_options, @options)}
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# used internally to ensure node api is set before using.
|
|
@@ -45,7 +51,7 @@ module Aspera
|
|
|
45
51
|
return @node_api
|
|
46
52
|
end
|
|
47
53
|
# use this to read the node_api end point.
|
|
48
|
-
attr_reader :node_api
|
|
54
|
+
# attr_reader :node_api
|
|
49
55
|
|
|
50
56
|
# use this to set the node_api end point before using the class.
|
|
51
57
|
def node_api=(new_value)
|
|
@@ -62,7 +68,7 @@ module Aspera
|
|
|
62
68
|
case transfer_spec['direction']
|
|
63
69
|
when Fasp::TransferSpec::DIRECTION_SEND then transfer_spec['source_root_id'] = @root_id
|
|
64
70
|
when Fasp::TransferSpec::DIRECTION_RECEIVE then transfer_spec['destination_root_id'] = @root_id
|
|
65
|
-
else
|
|
71
|
+
else error_unexpected_value(transfer_spec['direction'])
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
# manage special additional parameter
|
|
@@ -94,43 +100,45 @@ module Aspera
|
|
|
94
100
|
|
|
95
101
|
# generic method
|
|
96
102
|
def wait_for_transfers_completion
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
# set to true when we know the total size of the transfer
|
|
104
|
+
session_started = false
|
|
105
|
+
bytes_expected = nil
|
|
99
106
|
# lets emulate management events to display progress bar
|
|
100
107
|
loop do
|
|
101
108
|
# status is empty sometimes with status 200...
|
|
102
|
-
transfer_data = node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {'status' => 'unknown'} rescue {'status' => 'waiting(
|
|
109
|
+
transfer_data = node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {'status' => 'unknown'} rescue {'status' => 'waiting(api error)'}
|
|
103
110
|
case transfer_data['status']
|
|
104
|
-
when 'completed'
|
|
105
|
-
notify_end(@transfer_id)
|
|
106
|
-
break
|
|
107
111
|
when 'waiting', 'partially_completed', 'unknown', 'waiting(read error)'
|
|
108
|
-
|
|
109
|
-
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
|
110
|
-
spinner.start
|
|
111
|
-
end
|
|
112
|
-
spinner.update(title: transfer_data['status'])
|
|
113
|
-
spinner.spin
|
|
112
|
+
notify_progress(session_id: nil, type: :pre_start, info: transfer_data['status'])
|
|
114
113
|
when 'running'
|
|
115
|
-
if !
|
|
114
|
+
if !session_started
|
|
115
|
+
notify_progress(session_id: @transfer_id, type: :session_start)
|
|
116
|
+
session_started = true
|
|
117
|
+
end
|
|
118
|
+
message = transfer_data['status']
|
|
119
|
+
message = "#{message} (#{transfer_data['error_desc']})" if !transfer_data['error_desc']&.empty?
|
|
120
|
+
notify_progress(session_id: nil, type: :pre_start, info: message)
|
|
121
|
+
if bytes_expected.nil? &&
|
|
122
|
+
transfer_data['precalc'].is_a?(Hash) &&
|
|
116
123
|
transfer_data['precalc']['status'].eql?('ready')
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else
|
|
120
|
-
notify_progress(@transfer_id, transfer_data['bytes_transferred'])
|
|
124
|
+
bytes_expected = transfer_data['precalc']['bytes_expected']
|
|
125
|
+
notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
|
|
121
126
|
end
|
|
127
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: transfer_data['bytes_transferred'])
|
|
128
|
+
when 'completed'
|
|
129
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
|
|
130
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
|
131
|
+
break
|
|
122
132
|
when 'failed'
|
|
133
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
|
123
134
|
# Bug in HSTS ? transfer is marked failed, but there is no reason
|
|
124
|
-
if transfer_data['error_code'].eql?(0) && transfer_data['error_desc'].empty?
|
|
125
|
-
notify_end(@transfer_id)
|
|
126
|
-
break
|
|
127
|
-
end
|
|
135
|
+
break if transfer_data['error_code'].eql?(0) && transfer_data['error_desc'].empty?
|
|
128
136
|
raise Fasp::Error, "status: #{transfer_data['status']}. code: #{transfer_data['error_code']}. description: #{transfer_data['error_desc']}"
|
|
129
137
|
else
|
|
130
138
|
Log.log.warn{"transfer_data -> #{transfer_data}"}
|
|
131
139
|
raise Fasp::Error, "status: #{transfer_data['status']}. code: #{transfer_data['error_code']}. description: #{transfer_data['error_desc']}"
|
|
132
140
|
end
|
|
133
|
-
sleep(1)
|
|
141
|
+
sleep(1.0)
|
|
134
142
|
end
|
|
135
143
|
# TODO: get status of sessions
|
|
136
144
|
return []
|
|
@@ -1,61 +1,121 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# cspell:words Transfersdk
|
|
4
|
-
|
|
5
3
|
require 'aspera/fasp/agent_base'
|
|
6
4
|
require 'aspera/fasp/installation'
|
|
5
|
+
require 'aspera/temp_file_manager'
|
|
6
|
+
require 'aspera/log'
|
|
7
|
+
require 'aspera/assert'
|
|
7
8
|
require 'json'
|
|
9
|
+
require 'uri'
|
|
8
10
|
|
|
9
11
|
module Aspera
|
|
10
12
|
module Fasp
|
|
11
13
|
class AgentTrsdk < Aspera::Fasp::AgentBase
|
|
14
|
+
# see https://github.com/grpc/grpc/blob/master/doc/naming.md
|
|
15
|
+
# https://grpc.io/docs/guides/custom-name-resolution/
|
|
16
|
+
LOCAL_SOCKET_ADDR = '127.0.0.1'
|
|
17
|
+
PORT_SEP = ':'
|
|
18
|
+
# port zero means select a random available high port
|
|
19
|
+
AUTO_LOCAL_TCP_PORT = "#{PORT_SEP}0"
|
|
12
20
|
DEFAULT_OPTIONS = {
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
url: AUTO_LOCAL_TCP_PORT,
|
|
22
|
+
external: false, # expect that an external daemon is already running
|
|
23
|
+
keep: false # do not shutdown daemon on exit
|
|
15
24
|
}.freeze
|
|
16
25
|
private_constant :DEFAULT_OPTIONS
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
class << self
|
|
28
|
+
# Well, the port number is only in log file
|
|
29
|
+
def daemon_port_from_log(log_file)
|
|
30
|
+
result = nil
|
|
31
|
+
# if port is zero, a dynamic port was created, get it
|
|
32
|
+
File.open(log_file, 'r') do |file|
|
|
33
|
+
file.each_line do |line|
|
|
34
|
+
# Well, it's tricky to depend on log
|
|
35
|
+
if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
|
|
36
|
+
result = m[2].to_i
|
|
37
|
+
# no "break" , need to read last matching log line
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
raise 'Port not found in daemon logs' if result.nil?
|
|
42
|
+
Log.log.debug{"Got port #{result} from log"}
|
|
43
|
+
return result
|
|
26
44
|
end
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# options come from transfer_info
|
|
48
|
+
def initialize(user_opts={})
|
|
49
|
+
super(user_opts)
|
|
50
|
+
@options = AgentBase.options(default: DEFAULT_OPTIONS, options: user_opts)
|
|
51
|
+
is_local_auto_port = @options[:url].eql?(AUTO_LOCAL_TCP_PORT)
|
|
52
|
+
raise 'Cannot use options `keep` or `external` with port zero' if is_local_auto_port && (@options[:keep] || @options[:external])
|
|
53
|
+
Log.log.debug{Log.dump(:agent_options, @options)}
|
|
54
|
+
# load SDK stub class on demand, as it's an optional gem
|
|
30
55
|
$LOAD_PATH.unshift(Installation.instance.sdk_ruby_folder)
|
|
31
56
|
require 'transfer_services_pb'
|
|
32
|
-
|
|
57
|
+
# keep PID for optional shutdown
|
|
58
|
+
@daemon_pid = nil
|
|
59
|
+
daemon_endpoint = @options[:url]
|
|
60
|
+
Log.log.debug{Log.dump(:daemon_endpoint, daemon_endpoint)}
|
|
61
|
+
# retry loop
|
|
33
62
|
begin
|
|
63
|
+
# no address: local bind
|
|
64
|
+
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{daemon_endpoint}" if daemon_endpoint.match?(/^#{PORT_SEP}[0-9]+$/o)
|
|
65
|
+
# Create stub (without credentials)
|
|
66
|
+
@transfer_client = Transfersdk::TransferService::Stub.new(daemon_endpoint, :this_channel_is_insecure)
|
|
67
|
+
# Initiate actual connection
|
|
34
68
|
get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
|
|
35
|
-
Log.log.debug{"
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
Log.log.debug{"Daemon info: #{get_info_response}"}
|
|
70
|
+
Log.log.warn{'Attached to existing daemon'} unless @daemon_pid || @options[:external] || @options[:keep]
|
|
71
|
+
at_exit{shutdown}
|
|
72
|
+
rescue GRPC::Unavailable => e
|
|
73
|
+
# if transferd is external: do not start it, or other error
|
|
74
|
+
raise if @options[:external] || !e.message.include?('failed to connect')
|
|
75
|
+
# we already tried to start a daemon, but it failed
|
|
76
|
+
assert(@daemon_pid.nil?){"Daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_endpoint}}"}
|
|
77
|
+
Log.log.warn('no daemon present, starting daemon...') if @options[:external]
|
|
38
78
|
# location of daemon binary
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
79
|
+
sdk_folder = File.realpath(File.join(Installation.instance.sdk_ruby_folder, '..'))
|
|
80
|
+
# transferd only supports local ip and port
|
|
81
|
+
daemon_uri = URI.parse("ipv4://#{daemon_endpoint}")
|
|
82
|
+
assert(daemon_uri.scheme.eql?('ipv4')){"Invalid scheme daemon URI #{daemon_endpoint}"}
|
|
43
83
|
# create a config file for daemon
|
|
44
84
|
config = {
|
|
45
|
-
address:
|
|
46
|
-
port:
|
|
85
|
+
address: daemon_uri.host,
|
|
86
|
+
port: daemon_uri.port,
|
|
47
87
|
fasp_runtime: {
|
|
48
88
|
use_embedded: false,
|
|
49
89
|
user_defined: {
|
|
50
|
-
bin:
|
|
51
|
-
etc:
|
|
90
|
+
bin: sdk_folder,
|
|
91
|
+
etc: sdk_folder
|
|
52
92
|
}
|
|
53
93
|
}
|
|
54
94
|
}
|
|
95
|
+
# config file and logs are created in same folder
|
|
96
|
+
transferd_base_tmp = TempFileManager.instance.new_file_path_global('transferd')
|
|
97
|
+
Log.log.debug{"transferd base tmp #{transferd_base_tmp}"}
|
|
98
|
+
conf_file = "#{transferd_base_tmp}.conf"
|
|
99
|
+
log_stdout = "#{transferd_base_tmp}.out"
|
|
100
|
+
log_stderr = "#{transferd_base_tmp}.err"
|
|
55
101
|
File.write(conf_file, config.to_json)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
102
|
+
@daemon_pid = Process.spawn(Installation.instance.path(:transferd), '--config', conf_file, out: log_stdout, err: log_stderr)
|
|
103
|
+
begin
|
|
104
|
+
# wait for process to initialize, max 2 seconds
|
|
105
|
+
Timeout.timeout(2.0) do
|
|
106
|
+
# this returns if process dies (within 2 seconds)
|
|
107
|
+
_, status = Process.wait2(@daemon_pid)
|
|
108
|
+
raise "Transfer daemon exited with status #{status.exitstatus}. Check files: #{log_stdout} and #{log_stderr}"
|
|
109
|
+
end
|
|
110
|
+
rescue Timeout::Error
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
Log.log.debug{"Daemon started with pid #{@daemon_pid}"}
|
|
114
|
+
Process.detach(@daemon_pid) if @options[:keep]
|
|
115
|
+
at_exit {shutdown}
|
|
116
|
+
# update port for next connection attempt (if auto high port was requested)
|
|
117
|
+
daemon_endpoint = "#{LOCAL_SOCKET_ADDR}#{PORT_SEP}#{self.class.daemon_port_from_log(log_stdout)}" if is_local_auto_port
|
|
118
|
+
# local daemon started, try again
|
|
59
119
|
retry
|
|
60
120
|
end
|
|
61
121
|
end
|
|
@@ -74,25 +134,34 @@ module Aspera
|
|
|
74
134
|
end
|
|
75
135
|
|
|
76
136
|
def wait_for_transfers_completion
|
|
77
|
-
|
|
137
|
+
# set to true when we know the total size of the transfer
|
|
138
|
+
session_started = false
|
|
139
|
+
bytes_expected = nil
|
|
78
140
|
# monitor transfer status
|
|
79
141
|
@transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
|
|
80
|
-
Log.dump(:response, response.to_h)
|
|
142
|
+
Log.log.debug{Log.dump(:response, response.to_h)}
|
|
81
143
|
# Log.log.debug{"#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}"}
|
|
82
144
|
case response.status
|
|
83
145
|
when :RUNNING
|
|
84
|
-
if !
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
elsif started
|
|
88
|
-
notify_progress(@transfer_id, response.transferInfo.bytesTransferred)
|
|
146
|
+
if !session_started
|
|
147
|
+
notify_progress(session_id: @transfer_id, type: :session_start)
|
|
148
|
+
session_started = true
|
|
89
149
|
end
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
150
|
+
if bytes_expected.nil? &&
|
|
151
|
+
!response.sessionInfo.preTransferBytes.eql?(0)
|
|
152
|
+
bytes_expected = response.sessionInfo.preTransferBytes
|
|
153
|
+
notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
|
|
154
|
+
end
|
|
155
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: response.transferInfo.bytesTransferred)
|
|
156
|
+
when :COMPLETED
|
|
157
|
+
notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
|
|
158
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
|
93
159
|
break
|
|
160
|
+
when :FAILED, :CANCELED
|
|
161
|
+
notify_progress(type: :end, session_id: @transfer_id)
|
|
162
|
+
raise Fasp::Error, JSON.parse(response.message)['Description']
|
|
94
163
|
when :QUEUED, :UNKNOWN_STATUS, :PAUSED, :ORPHANED
|
|
95
|
-
|
|
164
|
+
notify_progress(session_id: nil, type: :pre_start, info: response.status.to_s.downcase)
|
|
96
165
|
else
|
|
97
166
|
Log.log.error{"unknown status#{response.status}"}
|
|
98
167
|
end
|
|
@@ -100,6 +169,20 @@ module Aspera
|
|
|
100
169
|
# TODO: return status
|
|
101
170
|
return []
|
|
102
171
|
end
|
|
172
|
+
|
|
173
|
+
def shutdown
|
|
174
|
+
stop_daemon unless @options[:keep]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def stop_daemon
|
|
178
|
+
if !@daemon_pid.nil?
|
|
179
|
+
Log.log.debug("Stopping daemon #{@daemon_pid}")
|
|
180
|
+
Process.kill('INT', @daemon_pid)
|
|
181
|
+
_, status = Process.wait2(@daemon_pid)
|
|
182
|
+
Log.log.debug("daemon stopped #{status}")
|
|
183
|
+
@daemon_pid = nil
|
|
184
|
+
end
|
|
185
|
+
end
|
|
103
186
|
end
|
|
104
187
|
end
|
|
105
188
|
end
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
module Aspera
|
|
6
6
|
module Fasp
|
|
7
7
|
# from https://www.google.com/search?q=FASP+error+codes
|
|
8
|
-
# Note that the fact that an error is
|
|
8
|
+
# Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
|
|
9
9
|
# rubocop:disable Layout/MultilineHashKeyLineBreaks
|
|
10
10
|
# rubocop:disable Layout/FirstHashElementLineBreak
|
|
11
11
|
ERROR_INFO = {
|
|
12
|
-
# id
|
|
12
|
+
# id retry-able mnemo message additional info
|
|
13
13
|
1 => { r: false, c: 'FASP_PROTO', m: 'Generic fasp(tm) protocol error', a: 'fasp(tm) error'},
|
|
14
14
|
2 => { r: false, c: 'ASCP', m: 'Generic SCP error', a: 'ASCP error'},
|
|
15
15
|
3 => { r: false, c: 'AMBIGUOUS_TARGET', m: 'Target incorrectly specified', a: 'Ambiguous target'},
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aspera
|
|
4
|
+
module Fasp
|
|
5
|
+
# generates a pseudo file stream
|
|
6
|
+
class FauxFile
|
|
7
|
+
# marker for faux file
|
|
8
|
+
PREFIX = 'faux:///'
|
|
9
|
+
# size suffix
|
|
10
|
+
SUFFIX = %w[k m g t p e]
|
|
11
|
+
class << self
|
|
12
|
+
def open(name)
|
|
13
|
+
return nil unless name.start_with?(PREFIX)
|
|
14
|
+
parts = name[PREFIX.length..-1].split('?')
|
|
15
|
+
raise 'Format: #{PREFIX}<file path>?<size>' unless parts.length.eql?(2)
|
|
16
|
+
raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = parts[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
|
|
17
|
+
size = m[1].to_i
|
|
18
|
+
suffix = m[2]
|
|
19
|
+
SUFFIX.each do |s|
|
|
20
|
+
size *= 1024
|
|
21
|
+
break if s.eql?(suffix)
|
|
22
|
+
end
|
|
23
|
+
return FauxFile.new(parts[0], size)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
attr_reader :path, :size
|
|
27
|
+
|
|
28
|
+
def initialize(path, size)
|
|
29
|
+
@path = path
|
|
30
|
+
@size = size
|
|
31
|
+
@offset = 0
|
|
32
|
+
# we cache large chunks, anyway most of them will be the same size
|
|
33
|
+
@chunk_by_size = {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def read(chunk_size)
|
|
37
|
+
return nil if eof?
|
|
38
|
+
bytes_to_read = [chunk_size, @size - @offset].min
|
|
39
|
+
@offset += bytes_to_read
|
|
40
|
+
@chunk_by_size[bytes_to_read] = "\x00" * bytes_to_read unless @chunk_by_size.key?(bytes_to_read)
|
|
41
|
+
return @chunk_by_size[bytes_to_read]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def eof?
|
|
48
|
+
return @offset >= @size
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|