aspera-cli 4.12.0 → 4.13.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/CHANGELOG.md +17 -0
- data/CONTRIBUTING.md +97 -22
- data/README.md +548 -394
- data/bin/ascli +3 -3
- data/docs/test_env.conf +12 -5
- data/lib/aspera/aoc.rb +42 -42
- data/lib/aspera/ascmd.rb +4 -3
- data/lib/aspera/cli/extended_value.rb +24 -37
- data/lib/aspera/cli/formatter.rb +6 -0
- data/lib/aspera/cli/info.rb +2 -4
- data/lib/aspera/cli/main.rb +6 -0
- data/lib/aspera/cli/manager.rb +15 -6
- data/lib/aspera/cli/plugin.rb +1 -5
- data/lib/aspera/cli/plugins/aoc.rb +23 -6
- data/lib/aspera/cli/plugins/config.rb +13 -6
- data/lib/aspera/cli/plugins/faspex.rb +4 -3
- data/lib/aspera/cli/plugins/faspex5.rb +175 -42
- data/lib/aspera/cli/plugins/node.rb +107 -50
- data/lib/aspera/cli/plugins/preview.rb +3 -3
- data/lib/aspera/cli/plugins/server.rb +11 -1
- data/lib/aspera/cli/plugins/sync.rb +3 -3
- data/lib/aspera/cli/transfer_agent.rb +24 -10
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/command_line_builder.rb +2 -1
- data/lib/aspera/cos_node.rb +1 -1
- data/lib/aspera/fasp/agent_connect.rb +1 -1
- data/lib/aspera/fasp/agent_direct.rb +12 -12
- data/lib/aspera/fasp/agent_node.rb +14 -4
- data/lib/aspera/fasp/installation.rb +1 -0
- data/lib/aspera/fasp/parameters.rb +11 -3
- data/lib/aspera/fasp/parameters.yaml +3 -1
- data/lib/aspera/fasp/transfer_spec.rb +3 -1
- data/lib/aspera/faspex_gw.rb +1 -0
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/node.rb +11 -4
- data/lib/aspera/oauth.rb +3 -2
- data/lib/aspera/preview/file_types.rb +8 -6
- data/lib/aspera/preview/generator.rb +23 -11
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +34 -0
- data/lib/aspera/preview/utils.rb +8 -8
- data/lib/aspera/rest.rb +5 -4
- data/lib/aspera/rest_call_error.rb +3 -1
- data/lib/aspera/secret_hider.rb +4 -4
- data/lib/aspera/sync.rb +39 -33
- data/lib/aspera/web_server_simple.rb +22 -18
- data.tar.gz.sig +0 -0
- metadata +39 -46
- metadata.gz.sig +0 -0
- data/examples/aoc.rb +0 -30
- data/examples/faspex4.rb +0 -94
- data/examples/node.rb +0 -96
- data/examples/server.rb +0 -93
@@ -38,8 +38,9 @@ module Aspera
|
|
38
38
|
return @param_description_cache
|
39
39
|
end
|
40
40
|
|
41
|
+
# @param to_text [bool] replace HTML entities with text equivalent
|
41
42
|
# @return a table suitable to display in manual
|
42
|
-
def man_table
|
43
|
+
def man_table
|
43
44
|
result = []
|
44
45
|
description.each do |name, options|
|
45
46
|
param = {name: name, type: [options[:accepted_types]].flatten.join(','), description: options[:desc]}
|
@@ -70,10 +71,17 @@ module Aspera
|
|
70
71
|
if options.key?(:enum)
|
71
72
|
param[:description] += "\nAllowed values: #{options[:enum].join(', ')}"
|
72
73
|
end
|
73
|
-
|
74
|
+
# replace "solidus" HTML entity with its text value
|
75
|
+
param[:description] = param[:description].gsub('/', '\\')
|
74
76
|
result.push(param)
|
75
77
|
end
|
76
|
-
return result
|
78
|
+
return result.sort do |a, b|
|
79
|
+
if a[:name].start_with?('EX_').eql?(b[:name].start_with?('EX_'))
|
80
|
+
a[:name] <=> b[:name]
|
81
|
+
else
|
82
|
+
b[:name] <=> a[:name]
|
83
|
+
end
|
84
|
+
end
|
77
85
|
end
|
78
86
|
|
79
87
|
# special encoding methods used in YAML (key: :convert)
|
@@ -559,7 +559,8 @@ remove_empty_source_directory:
|
|
559
559
|
:cli:
|
560
560
|
:type: :opt_without_arg
|
561
561
|
EX_at_rest_password:
|
562
|
-
:desc:
|
562
|
+
:desc: Content protection password
|
563
|
+
:deprecation: "Use standard spec parameter: content_protection_password"
|
563
564
|
:agents:
|
564
565
|
- :direct
|
565
566
|
:cli:
|
@@ -703,6 +704,7 @@ EX_file_pair_list:
|
|
703
704
|
:switch: "--file-pair-list"
|
704
705
|
EX_ascp_args:
|
705
706
|
:desc: Add native command line arguments to ascp
|
707
|
+
:deprecation: Use parameter ascp_args in option transfer_info
|
706
708
|
:accepted_types: :array
|
707
709
|
:agents:
|
708
710
|
- :direct
|
@@ -15,7 +15,9 @@ module Aspera
|
|
15
15
|
'ssh_port' => SSH_PORT,
|
16
16
|
'fasp_port' => UDP_PORT
|
17
17
|
}.freeze
|
18
|
-
#
|
18
|
+
# reserved tag for Aspera
|
19
|
+
TAG_RESERVED = 'aspera'
|
20
|
+
# define constants for enums of parameters: <parameter>_<enum>, e.g. CIPHER_AES_128
|
19
21
|
Aspera::Fasp::Parameters.description.each do |k, v|
|
20
22
|
next unless v[:enum].is_a?(Array)
|
21
23
|
v[:enum].each do |enum|
|
data/lib/aspera/faspex_gw.rb
CHANGED
@@ -19,7 +19,7 @@ module Aspera
|
|
19
19
|
@parameters[:fail_on_error] ||= false
|
20
20
|
@parameters[:timeout_seconds] ||= 60
|
21
21
|
super(server)
|
22
|
-
Log.log.debug{
|
22
|
+
Log.log.debug{'Faspex4PostProcServlet initialized'}
|
23
23
|
end
|
24
24
|
|
25
25
|
def do_POST(request, response)
|
@@ -39,7 +39,7 @@ module Aspera
|
|
39
39
|
return
|
40
40
|
end
|
41
41
|
# build script path by removing domain, and adding script folder
|
42
|
-
script_file = request.path[@parameters[:root].size
|
42
|
+
script_file = request.path[@parameters[:root].size..]
|
43
43
|
Log.log.debug{"script file=#{script_file}"}
|
44
44
|
script_path = File.join(@parameters[:script_folder], script_file)
|
45
45
|
Log.log.debug{"script=#{script_path}"}
|
data/lib/aspera/node.rb
CHANGED
@@ -40,7 +40,9 @@ module Aspera
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
# fields in @app_info
|
44
|
+
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
45
|
+
# methods of @app_info[:api]
|
44
46
|
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
45
47
|
private_constant :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
46
48
|
|
@@ -71,8 +73,13 @@ module Aspera
|
|
71
73
|
|
72
74
|
# @returns [Aspera::Node] a Node or nil
|
73
75
|
def node_id_to_node(node_id)
|
74
|
-
|
75
|
-
|
76
|
+
if !@app_info.nil?
|
77
|
+
return self if node_id.eql?(@app_info[:node_info]['id'])
|
78
|
+
return @app_info[:api].node_api_from(
|
79
|
+
node_id: node_id,
|
80
|
+
workspace_id: @app_info[:workspace_id],
|
81
|
+
workspace_name: @app_info[:workspace_name])
|
82
|
+
end
|
76
83
|
Log.log.warn{"cannot resolve link with node id #{node_id}"}
|
77
84
|
return nil
|
78
85
|
end
|
@@ -216,7 +223,7 @@ module Aspera
|
|
216
223
|
'direction' => direction,
|
217
224
|
'token' => ak_token,
|
218
225
|
'tags' => {
|
219
|
-
|
226
|
+
Fasp::TransferSpec::TAG_RESERVED => {
|
220
227
|
'node' => {
|
221
228
|
'access_key' => ak_name,
|
222
229
|
'file_id' => file_id
|
data/lib/aspera/oauth.rb
CHANGED
@@ -87,6 +87,7 @@ module Aspera
|
|
87
87
|
# @param lambda_create called to create token
|
88
88
|
# @param id_create called to generate unique id for token, for cache
|
89
89
|
def register_token_creator(id, lambda_create, id_create)
|
90
|
+
Log.log.debug{"registering token creator #{id}"}
|
90
91
|
raise 'ERROR: requites Symbol and 2 lambdas' unless id.is_a?(Symbol) && lambda_create.is_a?(Proc) && id_create.is_a?(Proc)
|
91
92
|
@create_handlers[id] = lambda_create
|
92
93
|
@id_handlers[id] = id_create
|
@@ -94,7 +95,7 @@ module Aspera
|
|
94
95
|
|
95
96
|
# @return one of the registered creators for the given create type
|
96
97
|
def token_creator(id)
|
97
|
-
raise "token grant method unknown: #{id}
|
98
|
+
raise "token grant method unknown: '#{id}' (#{id.class})" unless @create_handlers.key?(id)
|
98
99
|
@create_handlers[id]
|
99
100
|
end
|
100
101
|
|
@@ -272,7 +273,7 @@ module Aspera
|
|
272
273
|
|
273
274
|
# an API was already called, but failed, we need to regenerate or refresh
|
274
275
|
if use_refresh_token
|
275
|
-
if token_data.is_a?(Hash) && token_data.key?('refresh_token')
|
276
|
+
if token_data.is_a?(Hash) && token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
|
276
277
|
# save possible refresh token, before deleting the cache
|
277
278
|
refresh_token = token_data['refresh_token']
|
278
279
|
end
|
@@ -12,6 +12,7 @@ module Aspera
|
|
12
12
|
CONVERSION_TYPES = %i[image office pdf plaintext video].freeze
|
13
13
|
|
14
14
|
# define how files are processed based on mime type
|
15
|
+
# spellchecker:disable
|
15
16
|
SUPPORTED_MIME_TYPES = {
|
16
17
|
'application/json' => :plaintext,
|
17
18
|
'application/mac-binhex40' => :office,
|
@@ -267,6 +268,7 @@ module Aspera
|
|
267
268
|
'ycbcra' => :image,
|
268
269
|
'yuv' => :image,
|
269
270
|
'zabw' => :office}.freeze
|
271
|
+
# spellchecker:enable
|
270
272
|
|
271
273
|
private_constant :SUPPORTED_MIME_TYPES, :SUPPORTED_EXTENSIONS
|
272
274
|
|
@@ -301,12 +303,12 @@ module Aspera
|
|
301
303
|
def conversion_type(filepath, mimetype)
|
302
304
|
Log.log.debug{"conversion_type(#{filepath},m=#{mimetype},t=#{@use_mimemagic})"}
|
303
305
|
# 1- get type from provided mime type, using local mapping
|
304
|
-
|
306
|
+
conversion_type = SUPPORTED_MIME_TYPES[mimetype] if !mimetype.nil?
|
305
307
|
# 2- else, from computed mime type (if available)
|
306
|
-
if
|
308
|
+
if conversion_type.nil? && @use_mimemagic
|
307
309
|
detected_mime = mime_from_file(filepath)
|
308
310
|
if !detected_mime.nil?
|
309
|
-
|
311
|
+
conversion_type = SUPPORTED_MIME_TYPES[detected_mime]
|
310
312
|
if !mimetype.nil?
|
311
313
|
if mimetype.eql?(detected_mime)
|
312
314
|
Log.log.debug('matching mime type per magic number')
|
@@ -319,9 +321,9 @@ module Aspera
|
|
319
321
|
end
|
320
322
|
# 3- else, from extensions, using local mapping
|
321
323
|
extension = File.extname(filepath.downcase)[1..-1]
|
322
|
-
|
323
|
-
Log.log.debug{"conversion_type(#{extension}): #{
|
324
|
-
return
|
324
|
+
conversion_type = SUPPORTED_EXTENSIONS[extension] if conversion_type.nil?
|
325
|
+
Log.log.debug{"conversion_type(#{extension}): #{conversion_type.class.name} [#{conversion_type}]"}
|
326
|
+
return conversion_type
|
325
327
|
end
|
326
328
|
end
|
327
329
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# ffmpeg options:
|
4
|
+
# spellchecker:ignore pauseframes libx264 trunc bufsize muxer apng libmp3lame maxrate posterize movflags faststart
|
5
|
+
# spellchecker:ignore palettegen paletteuse pointsize bordercolor repage lanczos
|
6
|
+
|
3
7
|
require 'open3'
|
4
8
|
require 'aspera/preview/options'
|
5
9
|
require 'aspera/preview/utils'
|
@@ -12,6 +16,8 @@ module Aspera
|
|
12
16
|
# values for preview_format : output format
|
13
17
|
PREVIEW_FORMATS = %i[png mp4].freeze
|
14
18
|
|
19
|
+
FFMPEG_OPTIONS_LIST = %w[in out].freeze
|
20
|
+
|
15
21
|
# CLI needs to know conversion type to know if need skip it
|
16
22
|
attr_reader :conversion_type
|
17
23
|
|
@@ -22,7 +28,6 @@ module Aspera
|
|
22
28
|
# supported preview type is one of Preview::PREVIEW_FORMATS
|
23
29
|
# the resulting preview file type is taken from destination file extension.
|
24
30
|
# conversion methods are provided by private methods: convert_<conversion_type>_to_<preview_format>
|
25
|
-
# (combi = combination of source file type and destination format)
|
26
31
|
# -> conversion_type is one of FileTypes::CONVERSION_TYPES
|
27
32
|
# -> preview_format is one of Generator::PREVIEW_FORMATS
|
28
33
|
# the conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
|
@@ -70,10 +75,10 @@ module Aspera
|
|
70
75
|
# check that generated size does not exceed maximum
|
71
76
|
result_size = File.size(@destination_file_path)
|
72
77
|
if result_size > @options.max_size
|
73
|
-
Log.log.warn{"preview size exceeds maximum #{result_size} > #{@options.max_size}"}
|
78
|
+
Log.log.warn{"preview size exceeds maximum allowed #{result_size} > #{@options.max_size}"}
|
74
79
|
end
|
75
80
|
rescue StandardError => e
|
76
|
-
Log.log.error{"
|
81
|
+
Log.log.error{"Ignoring: #{e.message}"}
|
77
82
|
Log.log.debug(e.backtrace.join("\n").red)
|
78
83
|
FileUtils.cp(File.expand_path(@preview_format_symb.eql?(:mp4) ? 'video_error.png' : 'image_error.png', File.dirname(__FILE__)), @destination_file_path)
|
79
84
|
ensure
|
@@ -102,11 +107,11 @@ module Aspera
|
|
102
107
|
def convert_video_to_mp4_using_blend
|
103
108
|
p_duration = Utils.video_get_duration(@source_file_path)
|
104
109
|
p_start_offset = @options.video_start_sec.to_i
|
105
|
-
|
110
|
+
p_key_frame_count = @options.blend_keyframes.to_i
|
106
111
|
last_keyframe = nil
|
107
112
|
current_index = 1
|
108
|
-
1.upto(
|
109
|
-
offset_seconds = get_offset(p_duration, p_start_offset,
|
113
|
+
1.upto(p_key_frame_count) do |i|
|
114
|
+
offset_seconds = get_offset(p_duration, p_start_offset, p_key_frame_count, i)
|
110
115
|
Utils.video_dump_frame(@source_file_path, offset_seconds, @options.video_scale, this_tmpdir, current_index)
|
111
116
|
Utils.video_dupe_frame(this_tmpdir, current_index, @options.blend_pauseframes)
|
112
117
|
Utils.video_blend_frames(this_tmpdir, last_keyframe, current_index) unless last_keyframe.nil?
|
@@ -133,17 +138,17 @@ module Aspera
|
|
133
138
|
File.open(filelist, 'w+') do |f|
|
134
139
|
1.upto(@options.clips_count.to_i) do |i|
|
135
140
|
offset_seconds = get_offset(p_duration, @options.video_start_sec.to_i, @options.clips_count.to_i, i)
|
136
|
-
|
141
|
+
tmp_file_name = format('clip%04d.mp4', i)
|
137
142
|
Utils.ffmpeg(
|
138
143
|
in_f: @source_file_path,
|
139
144
|
in_p: ['-ss', offset_seconds * 0.9],
|
140
|
-
out_f: File.join(this_tmpdir,
|
145
|
+
out_f: File.join(this_tmpdir, tmp_file_name),
|
141
146
|
out_p: [
|
142
147
|
'-ss', offset_seconds * 0.1,
|
143
148
|
'-t', @options.clips_length,
|
144
149
|
'-filter:v', "scale=#{@options.video_scale}",
|
145
150
|
'-codec:a', 'libmp3lame'])
|
146
|
-
f.puts("file '#{
|
151
|
+
f.puts("file '#{tmp_file_name}'")
|
147
152
|
end
|
148
153
|
end
|
149
154
|
# concat clips
|
@@ -154,12 +159,19 @@ module Aspera
|
|
154
159
|
out_p: ['-codec', 'copy'])
|
155
160
|
end
|
156
161
|
|
157
|
-
# do a simple
|
162
|
+
# do a simple re-encoding
|
158
163
|
def convert_video_to_mp4_using_reencode
|
164
|
+
options = @options.reencode_ffmpeg
|
165
|
+
raise 'reencode_ffmpeg must be a Hash' unless options.is_a?(Hash)
|
166
|
+
options.each do |k, v|
|
167
|
+
raise "Key not supported: #{k}. Use keys: #{FFMPEG_OPTIONS_LIST.join(',')}" unless FFMPEG_OPTIONS_LIST.include?(k)
|
168
|
+
raise "Value for #{k} must be Array" unless v.is_a?(Array)
|
169
|
+
end
|
159
170
|
Utils.ffmpeg(
|
160
171
|
in_f: @source_file_path,
|
172
|
+
in_p: options['in'] || ['-ss', @options.video_start_sec.to_i * 0.9],
|
161
173
|
out_f: @destination_file_path,
|
162
|
-
out_p: [
|
174
|
+
out_p: options['out'] || [
|
163
175
|
'-t', '60',
|
164
176
|
'-codec:v', 'libx264',
|
165
177
|
'-profile:v', 'high',
|
@@ -20,8 +20,9 @@ module Aspera
|
|
20
20
|
{ name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font to render text with imagemagick convert (identify -list font)'},
|
21
21
|
{ name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS },
|
22
22
|
{ name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS },
|
23
|
-
{ name: :
|
24
|
-
{ name: :
|
23
|
+
{ name: :video_scale, default: "'min(iw,360)':-2", description: 'mp4: all: video scale (ffmpeg)' },
|
24
|
+
{ name: :video_start_sec, default: 10, description: 'mp4: all: start offset (seconds) of video preview' },
|
25
|
+
{ name: :reencode_ffmpeg, default: {}, description: 'mp4: reencode: options to ffmpeg' },
|
25
26
|
{ name: :blend_keyframes, default: 30, description: 'mp4: blend: # key frames' },
|
26
27
|
{ name: :blend_pauseframes, default: 3, description: 'mp4: blend: # pause frames' },
|
27
28
|
{ name: :blend_transframes, default: 5, description: 'mp4: blend: # transition blend frames' },
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rmagick' # https://rmagick.github.io/index.html
|
4
|
+
require 'rainbow'
|
5
|
+
require 'io/console'
|
6
|
+
module Aspera
|
7
|
+
module Preview
|
8
|
+
# function conversion_type returns one of the types: CONVERSION_TYPES
|
9
|
+
class Terminal
|
10
|
+
class << self
|
11
|
+
def build(blob, reserved_lines: 0)
|
12
|
+
# TODO: retrieve terminal ratio using
|
13
|
+
font_ratio = 1.7
|
14
|
+
(term_rows, term_columns) = IO.console.winsize
|
15
|
+
term_rows -= reserved_lines
|
16
|
+
image = Magick::ImageList.new.from_blob(blob)
|
17
|
+
chosen_factor = [term_rows / image.rows.to_f, term_columns / image.columns.to_f].min
|
18
|
+
image = image.scale((image.columns * chosen_factor * font_ratio).to_i, (image.rows * chosen_factor).to_i)
|
19
|
+
text_pixels = []
|
20
|
+
image.each_pixel do |pixel, col, row|
|
21
|
+
text_pixels.push("\n") if col.eql?(0) && !row.eql?(0)
|
22
|
+
pixel_rgb = [pixel.red, pixel.green, pixel.blue].map do |color|
|
23
|
+
# quantum depth is 8 or 16: convert xc: -format "%q" info:
|
24
|
+
# Rainbow only supports 8-bit colors
|
25
|
+
color >> (Magick::MAGICKCORE_QUANTUM_DEPTH - 8)
|
26
|
+
end
|
27
|
+
text_pixels.push(Rainbow(' ').background(pixel_rgb))
|
28
|
+
end
|
29
|
+
return text_pixels.join
|
30
|
+
end
|
31
|
+
end # class << self
|
32
|
+
end # class Terminal
|
33
|
+
end # module Preview
|
34
|
+
end # module Aspera
|
data/lib/aspera/preview/utils.rb
CHANGED
@@ -14,9 +14,9 @@ module Aspera
|
|
14
14
|
# shell exit code when command is not found
|
15
15
|
BASH_EXIT_NOT_FOUND = 127
|
16
16
|
# external binaries used
|
17
|
-
|
18
|
-
|
19
|
-
private_constant :BASH_SPECIAL_CHARACTERS, :BASH_EXIT_NOT_FOUND, :
|
17
|
+
EXTERNAL_TOOLS = %i[ffmpeg ffprobe convert composite optipng unoconv].freeze
|
18
|
+
TEMP_FORMAT = 'img%04d.jpg'
|
19
|
+
private_constant :BASH_SPECIAL_CHARACTERS, :BASH_EXIT_NOT_FOUND, :EXTERNAL_TOOLS, :TEMP_FORMAT
|
20
20
|
|
21
21
|
class << self
|
22
22
|
# returns string with single quotes suitable for bash if there is any bash metacharacter
|
@@ -27,7 +27,7 @@ module Aspera
|
|
27
27
|
|
28
28
|
# check that external tools can be executed
|
29
29
|
def check_tools(skip_types=[])
|
30
|
-
tools_to_check =
|
30
|
+
tools_to_check = EXTERNAL_TOOLS.dup
|
31
31
|
tools_to_check.delete(:unoconv) if skip_types.include?(:office)
|
32
32
|
# Check for binaries
|
33
33
|
tools_to_check.each do |command_symb|
|
@@ -39,7 +39,7 @@ module Aspera
|
|
39
39
|
# one could use "system", but we would need to redirect stdout/err
|
40
40
|
# @return true if su
|
41
41
|
def external_command(command_symb, command_args)
|
42
|
-
raise "unexpected command #{command_symb}" unless
|
42
|
+
raise "unexpected command #{command_symb}" unless EXTERNAL_TOOLS.include?(command_symb)
|
43
43
|
# build command line, and quote special characters
|
44
44
|
command = command_args.clone.unshift(command_symb).map{|i| shell_quote(i.to_s)}.join(' ')
|
45
45
|
Log.log.debug{"cmd=#{command}".blue}
|
@@ -69,7 +69,7 @@ module Aspera
|
|
69
69
|
# input_file,input_args,output_file,output_args
|
70
70
|
a[:gl_p] ||= [
|
71
71
|
'-y', # overwrite output without asking
|
72
|
-
'-loglevel', 'error' # show only errors and up
|
72
|
+
'-loglevel', 'error' # show only errors and up
|
73
73
|
]
|
74
74
|
a[:in_p] ||= []
|
75
75
|
a[:out_p] ||= []
|
@@ -88,11 +88,11 @@ module Aspera
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def ffmpeg_fmt(temp_folder)
|
91
|
-
return File.join(temp_folder,
|
91
|
+
return File.join(temp_folder, TEMP_FORMAT)
|
92
92
|
end
|
93
93
|
|
94
94
|
def get_tmp_num_filepath(temp_folder, file_number)
|
95
|
-
return File.join(temp_folder, format(
|
95
|
+
return File.join(temp_folder, format(TEMP_FORMAT, file_number))
|
96
96
|
end
|
97
97
|
|
98
98
|
def video_dupe_frame(temp_folder, index, count)
|
data/lib/aspera/rest.rb
CHANGED
@@ -224,8 +224,9 @@ module Aspera
|
|
224
224
|
begin
|
225
225
|
# we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
|
226
226
|
oauth_tries ||= 2
|
227
|
-
|
228
|
-
|
227
|
+
# initialize with number of initial retries allowed, nil gives zero
|
228
|
+
tries_remain_redirect = call_data[:redirect_max].to_i if tries_remain_redirect.nil?
|
229
|
+
Log.log.debug("send request (retries=#{tries_remain_redirect})")
|
229
230
|
# make http request (pipelined)
|
230
231
|
http_session.request(req) do |response|
|
231
232
|
result[:http] = response
|
@@ -286,8 +287,8 @@ module Aspera
|
|
286
287
|
Log.log.debug{"using new token=#{call_data[:headers]['Authorization']}"}
|
287
288
|
retry unless (oauth_tries -= 1).zero?
|
288
289
|
end # if oauth
|
289
|
-
#
|
290
|
-
if e.response.is_a?(Net::HTTPRedirection)
|
290
|
+
# redirect ? (any code beginning with 3)
|
291
|
+
if tries_remain_redirect.positive? && e.response.is_a?(Net::HTTPRedirection)
|
291
292
|
tries_remain_redirect -= 1
|
292
293
|
current_uri = URI.parse(call_data[:base_url])
|
293
294
|
new_url = e.response['location']
|
@@ -5,7 +5,9 @@ module Aspera
|
|
5
5
|
class RestCallError < StandardError
|
6
6
|
attr_accessor :request, :response
|
7
7
|
|
8
|
-
# @param
|
8
|
+
# @param req HTTP Request object
|
9
|
+
# @param resp HTTP Response object
|
10
|
+
# @param msg Error message
|
9
11
|
def initialize(req, resp, msg)
|
10
12
|
@request = req
|
11
13
|
@response = resp
|
data/lib/aspera/secret_hider.rb
CHANGED
@@ -35,13 +35,13 @@ module Aspera
|
|
35
35
|
def log_formatter(original_formatter)
|
36
36
|
original_formatter ||= Logger::Formatter.new
|
37
37
|
# NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
|
38
|
-
return lambda do |severity,
|
38
|
+
return lambda do |severity, date_time, program_name, msg|
|
39
39
|
if msg.is_a?(String) && !@log_secrets
|
40
|
-
REGEX_LOG_REPLACES.each do |
|
41
|
-
msg = msg.gsub(
|
40
|
+
REGEX_LOG_REPLACES.each do |reg_ex|
|
41
|
+
msg = msg.gsub(reg_ex){"#{Regexp.last_match(:begin)}#{HIDDEN_PASSWORD}#{Regexp.last_match(:end)}"}
|
42
42
|
end
|
43
43
|
end
|
44
|
-
original_formatter.call(severity,
|
44
|
+
original_formatter.call(severity, date_time, program_name, msg)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
data/lib/aspera/sync.rb
CHANGED
@@ -8,6 +8,13 @@ require 'base64'
|
|
8
8
|
module Aspera
|
9
9
|
# builds command line arg for async
|
10
10
|
class Sync
|
11
|
+
# default is push
|
12
|
+
DIRECTIONS = %i[push pull bidi].freeze
|
13
|
+
DIRECTION_TO_REQUEST_TYPE = {
|
14
|
+
push: :sync_upload,
|
15
|
+
pull: :sync_download,
|
16
|
+
bidi: :sync
|
17
|
+
}.freeze
|
11
18
|
PARAMS_VX_INSTANCE =
|
12
19
|
{
|
13
20
|
'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
@@ -38,7 +45,7 @@ module Aspera
|
|
38
45
|
'cooloff' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
39
46
|
'pending_max' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
40
47
|
'scan_intensity' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
41
|
-
'cipher' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: true},
|
48
|
+
'cipher' => { cli: { type: :opt_with_arg, convert: 'Aspera::Fasp::Parameters.convert_remove_hyphen'}, accepted_types: :string, ts: true},
|
42
49
|
'transfer_threads' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
43
50
|
'preserve_time' => { cli: { type: :opt_without_arg}, ts: :preserve_times},
|
44
51
|
'preserve_access_time' => { cli: { type: :opt_without_arg}, ts: nil},
|
@@ -60,7 +67,7 @@ module Aspera
|
|
60
67
|
PARAMS_VX_KEYS = %w[instance sessions].freeze
|
61
68
|
|
62
69
|
# new API
|
63
|
-
|
70
|
+
TS_TO_PARAMS_V2 = {
|
64
71
|
'remote_host' => 'remote.host',
|
65
72
|
'remote_user' => 'remote.user',
|
66
73
|
'remote_password' => 'remote.pass',
|
@@ -74,14 +81,27 @@ module Aspera
|
|
74
81
|
|
75
82
|
ASYNC_EXECUTABLE = 'async'
|
76
83
|
|
77
|
-
private_constant :PARAMS_VX_INSTANCE, :PARAMS_VX_SESSION, :PARAMS_VX_KEYS, :ASYNC_EXECUTABLE
|
84
|
+
private_constant :PARAMS_VX_INSTANCE, :PARAMS_VX_SESSION, :PARAMS_VX_KEYS, :ASYNC_EXECUTABLE, :TS_TO_PARAMS_V2
|
85
|
+
|
86
|
+
attr_reader :env_args
|
78
87
|
|
79
|
-
|
80
|
-
|
81
|
-
|
88
|
+
# @param sync_params [Hash] sync parameters, old or new format
|
89
|
+
# @param node_sync [Object|nil]
|
90
|
+
def initialize(sync_params, node_sync)
|
91
|
+
raise StandardError, 'parameter must be Hash' unless sync_params.is_a?(Hash)
|
92
|
+
raise 'node_sync misses method transfer_spec' unless node_sync.nil? || node_sync.respond_to?(:transfer_spec)
|
93
|
+
@env_args = {
|
94
|
+
args: [],
|
95
|
+
env: {}
|
96
|
+
}
|
97
|
+
if sync_params.key?('local')
|
98
|
+
# async native JSON format (v2)
|
99
|
+
raise StandardError, 'remote must be Hash' unless sync_params['remote'].is_a?(Hash)
|
100
|
+
unless node_sync.nil?
|
101
|
+
transfer_spec = node_sync.transfer_spec(sync_params['direction'], sync_params['local']['path'], sync_params['remote']['path'])
|
82
102
|
# async native JSON format
|
83
103
|
raise StandardError, 'local must be Hash' unless sync_params['local'].is_a?(Hash)
|
84
|
-
|
104
|
+
TS_TO_PARAMS_V2.each do |ts_param, sy_path|
|
85
105
|
next unless transfer_spec.key?(ts_param)
|
86
106
|
sy_dig = sy_path.split('.')
|
87
107
|
param = sy_dig.pop
|
@@ -93,38 +113,23 @@ module Aspera
|
|
93
113
|
sync_params['remote']['connect_mode'] ||= sync_params['remote'].key?('ws_port') ? 'ws' : 'ssh'
|
94
114
|
sync_params['remote']['private_key_paths'] ||= Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
|
95
115
|
sync_params['remote']['path'] ||= '/' if transfer_spec.dig(*%w[tags aspera node file_id])
|
96
|
-
|
116
|
+
end
|
117
|
+
@env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"]
|
118
|
+
elsif sync_params.key?('sessions')
|
119
|
+
# ascli JSON format (v1)
|
120
|
+
unless node_sync.nil?
|
97
121
|
sync_params['sessions'].each do |session|
|
98
|
-
|
99
|
-
|
100
|
-
|
122
|
+
transfer_spec = node_sync.transfer_spec(session['direction'], session['local_dir'], session['remote_dir'])
|
123
|
+
PARAMS_VX_SESSION.each do |async_param, behavior|
|
124
|
+
if behavior.key?(:ts)
|
125
|
+
tspec_param = behavior[:ts].is_a?(TrueClass) ? async_param : behavior[:ts].to_s
|
101
126
|
session[async_param] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
|
102
127
|
end
|
103
128
|
end
|
104
129
|
session['private_key_paths'] = Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
|
105
130
|
session['remote_dir'] = '/' if transfer_spec.dig(*%w[tags aspera node file_id])
|
106
131
|
end
|
107
|
-
else
|
108
|
-
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
109
132
|
end
|
110
|
-
Log.dump(:sync, sync_params)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
attr_reader :env_args
|
115
|
-
|
116
|
-
def initialize(sync_params)
|
117
|
-
raise StandardError, 'parameter must be Hash' unless sync_params.is_a?(Hash)
|
118
|
-
@env_args = {
|
119
|
-
args: [],
|
120
|
-
env: {}
|
121
|
-
}
|
122
|
-
if sync_params.key?('local')
|
123
|
-
# async native JSON format
|
124
|
-
raise StandardError, 'remote must be Hash' unless sync_params['remote'].is_a?(Hash)
|
125
|
-
@env_args[:args] = "--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"
|
126
|
-
elsif sync_params.key?('sessions')
|
127
|
-
# ascli JSON format
|
128
133
|
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
129
134
|
sync_params.keys.push('instance').uniq.sort.eql?(PARAMS_VX_KEYS)
|
130
135
|
raise StandardError, 'sessions key must be Array' unless sync_params['sessions'].is_a?(Array)
|
@@ -139,7 +144,7 @@ module Aspera
|
|
139
144
|
|
140
145
|
sync_params['sessions'].each do |session_params|
|
141
146
|
raise StandardError, 'sessions must contain hashes' unless session_params.is_a?(Hash)
|
142
|
-
raise StandardError, 'session must contain at
|
147
|
+
raise StandardError, 'session must contain at least name' unless session_params.key?('name')
|
143
148
|
session_builder = Aspera::CommandLineBuilder.new(session_params, PARAMS_VX_SESSION)
|
144
149
|
session_builder.process_params
|
145
150
|
session_builder.add_env_args(@env_args[:env], @env_args[:args])
|
@@ -147,6 +152,7 @@ module Aspera
|
|
147
152
|
else
|
148
153
|
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
149
154
|
end
|
155
|
+
Log.dump(:sync, sync_params)
|
150
156
|
end
|
151
157
|
|
152
158
|
def start
|
@@ -160,7 +166,7 @@ module Aspera
|
|
160
166
|
else raise 'internal error: unspecified case'
|
161
167
|
end
|
162
168
|
end
|
163
|
-
end
|
169
|
+
end # end Sync
|
164
170
|
|
165
171
|
class SyncAdmin
|
166
172
|
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
@@ -8,24 +8,26 @@ require 'openssl'
|
|
8
8
|
module Aspera
|
9
9
|
class WebServerSimple < WEBrick::HTTPServer
|
10
10
|
CERT_PARAMETERS = %i[key cert chain].freeze
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
11
|
+
class << self
|
12
|
+
# generates and adds self signed cert to provided webrick options
|
13
|
+
def fill_self_signed_cert(cert, key)
|
14
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse('/C=FR/O=Test/OU=Test/CN=Test')
|
15
|
+
cert.not_before = Time.now
|
16
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
17
|
+
cert.public_key = key.public_key
|
18
|
+
cert.serial = 0x0
|
19
|
+
cert.version = 2
|
20
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
21
|
+
ef.issuer_certificate = cert
|
22
|
+
ef.subject_certificate = cert
|
23
|
+
cert.extensions = [
|
24
|
+
ef.create_extension('basicConstraints', 'CA:TRUE', true),
|
25
|
+
ef.create_extension('subjectKeyIdentifier', 'hash')
|
26
|
+
# ef.create_extension('keyUsage', 'cRLSign,keyCertSign', true),
|
27
|
+
]
|
28
|
+
cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always'))
|
29
|
+
cert.sign(key, OpenSSL::Digest.new('SHA256'))
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
# @param uri [URI]
|
@@ -66,6 +68,8 @@ module Aspera
|
|
66
68
|
end
|
67
69
|
# self signed certificate generates characters on STDERR, see create_self_signed_cert in webrick/ssl.rb
|
68
70
|
Log.capture_stderr { super(webrick_options) }
|
71
|
+
# kill -USR1 for graceful shutdown
|
72
|
+
Kernel.trap('USR1') { shutdown }
|
69
73
|
end
|
70
74
|
|
71
75
|
# log web server access ( option AccessLog )
|
data.tar.gz.sig
CHANGED
Binary file
|