aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|