aspera-cli 4.24.2 → 4.25.0.pre2

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1067 -758
  4. data/CONTRIBUTING.md +93 -120
  5. data/README.md +817 -510
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/transferd.rb +4 -4
  8. data/lib/aspera/api/aoc.rb +71 -43
  9. data/lib/aspera/api/cos_node.rb +3 -2
  10. data/lib/aspera/api/faspex.rb +6 -5
  11. data/lib/aspera/api/node.rb +10 -12
  12. data/lib/aspera/ascmd.rb +1 -2
  13. data/lib/aspera/ascp/installation.rb +55 -41
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +28 -6
  16. data/lib/aspera/cli/error.rb +4 -2
  17. data/lib/aspera/cli/extended_value.rb +94 -62
  18. data/lib/aspera/cli/formatter.rb +55 -22
  19. data/lib/aspera/cli/main.rb +21 -14
  20. data/lib/aspera/cli/manager.rb +349 -248
  21. data/lib/aspera/cli/plugins/alee.rb +3 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +94 -51
  23. data/lib/aspera/cli/plugins/base.rb +62 -49
  24. data/lib/aspera/cli/plugins/config.rb +85 -96
  25. data/lib/aspera/cli/plugins/console.rb +15 -9
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +34 -27
  28. data/lib/aspera/cli/plugins/faspex5.rb +47 -44
  29. data/lib/aspera/cli/plugins/faspio.rb +7 -6
  30. data/lib/aspera/cli/plugins/httpgw.rb +3 -2
  31. data/lib/aspera/cli/plugins/node.rb +132 -120
  32. data/lib/aspera/cli/plugins/oauth.rb +1 -1
  33. data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
  34. data/lib/aspera/cli/plugins/preview.rb +26 -46
  35. data/lib/aspera/cli/plugins/server.rb +9 -10
  36. data/lib/aspera/cli/plugins/shares.rb +77 -43
  37. data/lib/aspera/cli/sync_actions.rb +49 -38
  38. data/lib/aspera/cli/transfer_agent.rb +16 -34
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/cli/wizard.rb +8 -5
  41. data/lib/aspera/command_line_builder.rb +20 -17
  42. data/lib/aspera/coverage.rb +6 -2
  43. data/lib/aspera/environment.rb +71 -84
  44. data/lib/aspera/faspex_gw.rb +1 -1
  45. data/lib/aspera/faspex_postproc.rb +1 -1
  46. data/lib/aspera/keychain/factory.rb +1 -2
  47. data/lib/aspera/keychain/macos_security.rb +2 -2
  48. data/lib/aspera/log.rb +2 -1
  49. data/lib/aspera/markdown.rb +31 -0
  50. data/lib/aspera/nagios.rb +6 -5
  51. data/lib/aspera/oauth/base.rb +17 -27
  52. data/lib/aspera/oauth/factory.rb +1 -1
  53. data/lib/aspera/oauth/url_json.rb +2 -1
  54. data/lib/aspera/preview/file_types.rb +23 -37
  55. data/lib/aspera/preview/terminal.rb +95 -29
  56. data/lib/aspera/preview/utils.rb +6 -5
  57. data/lib/aspera/products/connect.rb +3 -3
  58. data/lib/aspera/rest.rb +51 -39
  59. data/lib/aspera/rest_error_analyzer.rb +4 -4
  60. data/lib/aspera/ssh.rb +5 -2
  61. data/lib/aspera/ssl.rb +41 -0
  62. data/lib/aspera/sync/conf.schema.yaml +182 -34
  63. data/lib/aspera/sync/database.rb +2 -1
  64. data/lib/aspera/sync/operations.rb +128 -72
  65. data/lib/aspera/transfer/parameters.rb +3 -4
  66. data/lib/aspera/transfer/spec.rb +2 -3
  67. data/lib/aspera/transfer/spec.schema.yaml +49 -19
  68. data/lib/aspera/transfer/spec_doc.rb +14 -14
  69. data/lib/aspera/uri_reader.rb +1 -1
  70. data/lib/transferd_pb.rb +2 -2
  71. data.tar.gz.sig +0 -0
  72. metadata +33 -6
  73. metadata.gz.sig +0 -0
@@ -49,7 +49,7 @@ module Aspera
49
49
  options[:path] = uri.path unless ['', '/'].include?(uri.path)
50
50
  options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
51
51
  end
52
- command_args = [command]
52
+ command_args = [SECURITY_UTILITY, command]
53
53
  options&.each do |k, v|
54
54
  Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
55
55
  next if v.nil?
@@ -57,7 +57,7 @@ module Aspera
57
57
  command_args.push(v.shellescape) unless v.empty?
58
58
  end
59
59
  command_args.push(last_opt) unless last_opt.nil?
60
- return Environment.secure_capture(exec: SECURITY_UTILITY, args: command_args)
60
+ return Environment.secure_execute(*command_args, mode: :capture)
61
61
  end
62
62
 
63
63
  def key_chains(output)
data/lib/aspera/log.rb CHANGED
@@ -81,8 +81,9 @@ module Aspera
81
81
  # @param object [Hash, nil] Data to dump
82
82
  # @param level [Symbol] Debug level
83
83
  # @param block [Proc, nil] Give computed object
84
- def dump(name, object = nil, level: :debug)
84
+ def dump(name, object = nil, level: :debug, &block)
85
85
  return unless instance.logger.send(:"#{level}?")
86
+ Aspera.assert(object.nil? || block.nil?){'Use either object, or block, not both'}
86
87
  object = yield if block_given?
87
88
  instance.logger.send(level, obj_dump(name, object))
88
89
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ # Formatting for Markdown
5
+ class Markdown
6
+ # Matches: **bold**, `code`, or an HTML entity (&, ©, 💩)
7
+ FORMATS = /(?:\*\*(?<bold>[^*]+?)\*\*)|(?:`(?<code>[^`]+)`)|&(?<entity>(?:[A-Za-z][A-Za-z0-9]{1,31}|#\d{1,7}|#x[0-9A-Fa-f]{1,6}));/m
8
+ HTML_BREAK = '<br/>'
9
+
10
+ class << self
11
+ # Generate markdown from the provided 2D table
12
+ def table(table)
13
+ # get max width of each columns
14
+ col_widths = table.transpose.map do |col|
15
+ [col.flat_map{ |c| c.to_s.delete('`').split(HTML_BREAK).map(&:size)}.max, 80].min
16
+ end
17
+ headings = table.shift
18
+ table.unshift(col_widths.map{ |col_width| '-' * col_width})
19
+ table.unshift(headings)
20
+ lines = table.map{ |line| "| #{line.map{ |i| i.to_s.gsub('\\', '\\\\').gsub('|', '\|')}.join(' | ')} |\n"}
21
+ lines[1] = lines[1].tr(' ', '-')
22
+ return lines.join.chomp
23
+ end
24
+
25
+ # Generate markdown list from the provided list
26
+ def list(items)
27
+ items.map{ |i| "- #{i}"}.join("\n")
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/aspera/nagios.rb CHANGED
@@ -21,7 +21,7 @@ module Aspera
21
21
  end
22
22
 
23
23
  class << self
24
- # process results of a analysis and display status and exit with code
24
+ # Process results of a analysis and display status and exit with code
25
25
  def process(data)
26
26
  Aspera.assert_type(data, Array)
27
27
  Aspera.assert(!data.empty?){'data is empty'}
@@ -54,7 +54,7 @@ module Aspera
54
54
  @data = []
55
55
  end
56
56
 
57
- # compare remote time with local time
57
+ # Compare remote time with local time
58
58
  def check_time_offset(remote_date, component)
59
59
  # check date if specified : 2015-10-13T07:32:01Z
60
60
  remote_time = Time.parse(remote_date)
@@ -76,10 +76,11 @@ module Aspera
76
76
  # TODO: check on database if latest version
77
77
  end
78
78
 
79
- # translate for display
80
- def result
79
+ # Readable status list
80
+ # @return [Array] of Hash
81
+ def status_list
81
82
  Aspera.assert(!@data.empty?){'missing result'}
82
- {type: :object_list, data: @data.map{ |i| {'status' => LEVELS[i[:code]].to_s, 'component' => i[:comp], 'message' => i[:msg]}}}
83
+ @data.map{ |i| {'status' => LEVELS[i[:code]].to_s, 'component' => i[:comp], 'message' => i[:msg]}}
83
84
  end
84
85
  end
85
86
  end
@@ -59,25 +59,13 @@ module Aspera
59
59
 
60
60
  attr_reader :scope, :api, :path_token, :client_id
61
61
 
62
- # helper method to create token as per RFC
62
+ # Helper method to create token as per RFC
63
+ # @return [HTTPResponse]
64
+ # @raise RestError if not 2XX code
63
65
  def create_token_call(creation_params)
64
66
  Log.log.debug{'Generating a new token'.bg_green}
65
- payload = if @use_query
66
- {
67
- query: creation_params
68
- }
69
- else
70
- {
71
- content_type: Rest::MIME_WWW,
72
- body: creation_params
73
- }
74
- end
75
- return @api.call(
76
- operation: 'POST',
77
- subpath: @path_token,
78
- headers: {'Accept' => Rest::MIME_JSON},
79
- **payload
80
- )
67
+ return @api.create(@path_token, nil, query: creation_params, ret: :resp) if @use_query
68
+ return @api.create(@path_token, creation_params, content_type: Rest::MIME_WWW, ret: :resp)
81
69
  end
82
70
 
83
71
  # @param add_secret [Boolean] Add secret in default call parameters
@@ -122,17 +110,18 @@ module Aspera
122
110
  Factory.instance.persist_mgr.delete(@token_cache_id)
123
111
  token_data = nil
124
112
  # lets try the existing refresh token
113
+ # NOTE: AoC admin token has no refresh, and lives by default 1800secs
125
114
  if !refresh_token.nil?
126
- Log.log.info{"refresh=[#{refresh_token}]".bg_green}
127
- # NOTE: AoC admin token has no refresh, and lives by default 1800secs
128
- resp = create_token_call(optional_scope_client_id(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
129
- if resp[:http].code.start_with?('2')
130
- # save only if success
131
- json_data = resp[:http].body
115
+ Log.log.info{"refresh token=[#{refresh_token}]".bg_green}
116
+ begin
117
+ http = create_token_call(optional_scope_client_id(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
118
+ # Save only if success
119
+ json_data = http.body
132
120
  token_data = JSON.parse(json_data)
133
121
  Factory.instance.persist_mgr.put(@token_cache_id, json_data)
134
- else
135
- Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
122
+ rescue => e
123
+ # Refresh token can fail.
124
+ Log.log.warn{"Refresh failed: #{e}"}
136
125
  end
137
126
  end
138
127
  end
@@ -140,8 +129,9 @@ module Aspera
140
129
 
141
130
  # no cache, nor refresh: generate a token
142
131
  if token_data.nil?
143
- resp = create_token
144
- json_data = resp[:http].body
132
+ # Call the method-specific token creation
133
+ # which returns the result of create_token_call
134
+ json_data = create_token.body
145
135
  token_data = JSON.parse(json_data)
146
136
  Factory.instance.persist_mgr.put(@token_cache_id, json_data)
147
137
  end
@@ -156,7 +156,7 @@ module Aspera
156
156
  def register_token_creator(creator_class)
157
157
  Aspera.assert_type(creator_class, Class)
158
158
  id = Factory.class_to_id(creator_class)
159
- Log.log.debug{"registering token creator #{id}"}
159
+ Log.log.debug{"registering creator for #{id}"}
160
160
  @token_type_classes[id] = creator_class
161
161
  end
162
162
 
@@ -25,7 +25,8 @@ module Aspera
25
25
  query: @query.merge(scope: scope), # scope is here because it may change over time (node)
26
26
  content_type: Rest::MIME_JSON,
27
27
  body: @body,
28
- headers: {'Accept' => Rest::MIME_JSON}
28
+ headers: {'Accept' => Rest::MIME_JSON},
29
+ ret: :resp
29
30
  )
30
31
  end
31
32
  end
@@ -3,7 +3,7 @@
3
3
  require 'aspera/log'
4
4
  require 'aspera/assert'
5
5
  require 'singleton'
6
- require 'mime/types'
6
+ require 'marcel'
7
7
 
8
8
  module Aspera
9
9
  module Preview
@@ -61,7 +61,7 @@ module Aspera
61
61
  end
62
62
 
63
63
  # @param mimetype [String] mime type
64
- # @return file type, one of enum CONVERSION_TYPES, or nil if not found
64
+ # @return [NilClass,Symbol] file type, one of enum CONVERSION_TYPES, or nil if not found
65
65
  def mime_to_type(mimetype)
66
66
  Aspera.assert_type(mimetype, String)
67
67
  return SUPPORTED_MIME_TYPES[mimetype] if SUPPORTED_MIME_TYPES.key?(mimetype)
@@ -72,19 +72,17 @@ module Aspera
72
72
  return
73
73
  end
74
74
 
75
- # @param filepath [String] full path to file
76
- # @param mimetype [String] provided by node API
75
+ # @param filepath [String] Full path to file
76
+ # @param mimetype [String] MIME typre provided by node API
77
77
  # @return file type, one of enum CONVERSION_TYPES
78
78
  # @raise [RuntimeError] if no conversion type found
79
79
  def conversion_type(filepath, mimetype)
80
80
  Log.log.debug{"conversion_type(#{filepath},mime=#{mimetype},magic=#{@use_mimemagic})"}
81
- mimetype = nil if mimetype.is_a?(String) && (mimetype == 'application/octet-stream' || mimetype.empty?)
82
- # Use mimemagic if available
83
- mimetype ||= mime_using_mimemagic(filepath)
84
- mimetype ||= mime_using_file(filepath)
85
- # from extensions, using local mapping
86
- mimetype ||= MIME::Types.of(File.basename(filepath)).first
87
- raise "no MIME type found for #{File.basename(filepath)}" if mimetype.nil?
81
+ # Default type or empty means no type
82
+ mimetype = TYPE_NOT_FOUND if mimetype.nil? || (mimetype.is_a?(String) && mimetype.empty?)
83
+ mimetype = Marcel::MimeType.for(Pathname.new(filepath), name: File.basename(filepath), declared_type: mimetype)
84
+ mimetype = 'text/plain' if mimetype.eql?(TYPE_NOT_FOUND) && ascii_text_file?(filepath)
85
+ raise "no MIME type found for #{File.basename(filepath)}" if mimetype.eql?(TYPE_NOT_FOUND)
88
86
  conversion_type = mime_to_type(mimetype)
89
87
  raise "no conversion type found for #{File.basename(filepath)}" if conversion_type.nil?
90
88
  Log.log.trace1{"conversion_type(#{File.basename(filepath)}): #{conversion_type.class.name} [#{conversion_type}]"}
@@ -93,33 +91,21 @@ module Aspera
93
91
 
94
92
  private
95
93
 
96
- # Use mime magic to find mime type based on file content (magic numbers)
97
- # @param filepath [String] full path to file
98
- # @return [String] mime type, or nil if not found
99
- def mime_using_mimemagic(filepath)
100
- return unless @use_mimemagic
101
- # moved here, as `mimemagic` can cause installation issues
102
- require 'mimemagic'
103
- require 'mimemagic/version'
104
- require 'mimemagic/overlay' if MimeMagic::VERSION.start_with?('0.3.')
105
- # check magic number inside file (empty string if not found)
106
- detected_mime = MimeMagic.by_magic(File.open(filepath)).to_s
107
- # check extension only
108
- if mime_to_type(detected_mime).nil?
109
- Log.log.debug{"no conversion for #{detected_mime}, trying extension"}
110
- detected_mime = MimeMagic.by_extension(File.extname(filepath)).to_s
111
- end
112
- detected_mime = nil if detected_mime.empty?
113
- Log.log.debug{"mimemagic: #{detected_mime.class.name} [#{detected_mime}]"}
114
- return detected_mime
115
- end
94
+ TYPE_NOT_FOUND = 'application/octet-stream'
95
+ ACCEPT_CTRL_CHARS = [9, 10, 13]
116
96
 
117
- # Use 'file' command to find mime type based on file content (Unix)
118
- def mime_using_file(filepath)
119
- return Environment.secure_capture(exec: 'file', args: ['--mime-type', '--brief', filepath]).strip
120
- rescue => e
121
- Log.log.error{"error using 'file' command: #{e.message}"}
122
- return
97
+ # Returns true if the file looks like ASCII text (printable ASCII + \t, \r, \n, space).
98
+ # It reads only a small prefix (default: 64KB) and fails fast on the first bad byte.
99
+ def ascii_text_file?(path, sample_size: 64 * 1024)
100
+ File.open(path, 'rb') do |f|
101
+ sample = f.read(sample_size) || ''.b
102
+ sample.each_byte do |b|
103
+ next if b.between?(32, 126) || ACCEPT_CTRL_CHARS.include?(b)
104
+ # Any other control character => not ASCII text
105
+ return false
106
+ end
107
+ true
108
+ end
123
109
  end
124
110
  end
125
111
  end
@@ -3,11 +3,87 @@
3
3
  # cspell:words Magick MAGICKCORE ITERM mintty winsize termcap
4
4
 
5
5
  require 'rainbow'
6
+ require 'base64'
6
7
  require 'io/console'
7
8
  require 'aspera/log'
8
9
  require 'aspera/environment'
10
+
9
11
  module Aspera
10
12
  module Preview
13
+ module Backend
14
+ # provides image pixels scaled to terminal
15
+ class Base
16
+ def initialize(reserve:, double:, font_ratio:)
17
+ @reserve = reserve
18
+ @height_ratio = double ? 2.0 : 1.0
19
+ @font_ratio = font_ratio
20
+ end
21
+ Aspera.require_method!(:terminal_pixels)
22
+ # compute scaling to fit terminal
23
+ def terminal_scaling(rows, columns)
24
+ (term_rows, term_columns) = IO.console.winsize || [24, 80]
25
+ term_rows = [term_rows - @reserve, 2].max
26
+ fit_term_ratio = [term_rows.to_f * @font_ratio / rows.to_f, term_columns.to_f / columns.to_f].min
27
+ [(columns * fit_term_ratio).to_i, (rows * fit_term_ratio * @height_ratio / @font_ratio).to_i]
28
+ end
29
+ end
30
+
31
+ class RMagick < Base
32
+ def initialize(blob, **kwargs)
33
+ super(**kwargs)
34
+ # do not require statically, as the package is optional
35
+ require 'rmagick' # https://rmagick.github.io/index.html
36
+ @image = Magick::ImageList.new.from_blob(blob)
37
+ end
38
+
39
+ def terminal_pixels
40
+ # quantum depth is 8 or 16, see: `magick xc: -format "%q" info:`
41
+ shift_for_8_bit = Magick::MAGICKCORE_QUANTUM_DEPTH - 8
42
+ # get all pixel colors, adjusted for Rainbow
43
+ pixel_colors = []
44
+ @image.scale(*terminal_scaling(@image.rows, @image.columns)).each_pixel do |pixel, col, row|
45
+ pixel_rgb = [pixel.red, pixel.green, pixel.blue]
46
+ pixel_rgb = pixel_rgb.map{ |color| color >> shift_for_8_bit} unless shift_for_8_bit.eql?(0)
47
+ # init 2-dim array
48
+ pixel_colors[row] ||= []
49
+ pixel_colors[row][col] = pixel_rgb
50
+ end
51
+ pixel_colors
52
+ end
53
+ end
54
+
55
+ class ChunkyPNG < Base
56
+ def initialize(blob, **kwargs)
57
+ super(**kwargs)
58
+ require 'chunky_png'
59
+ @png = ::ChunkyPNG::Image.from_blob(blob)
60
+ end
61
+
62
+ def terminal_pixels
63
+ src_w = @png.width
64
+ src_h = @png.height
65
+ dst_w, dst_h = terminal_scaling(src_h, src_w)
66
+ dst_w = [dst_w, 1].max
67
+ dst_h = [dst_h, 1].max
68
+ pixel_colors = Array.new(dst_h){Array.new(dst_w)}
69
+ x_ratio = src_w.to_f / dst_w
70
+ y_ratio = src_h.to_f / dst_h
71
+ dst_h.times do |dy|
72
+ sy = (dy * y_ratio).floor
73
+ sy = src_h - 1 if sy >= src_h
74
+ dst_w.times do |dx|
75
+ sx = (dx * x_ratio).floor
76
+ sx = src_w - 1 if sx >= src_w
77
+ rgba = @png.get_pixel(sx, sy)
78
+ # ChunkyPNG stores as 0xRRGGBBAA; extract 8-bit channels
79
+ pixel_colors[dy][dx] = %i[r g b].map{ |i| ::ChunkyPNG::Color.send(i, rgba)}
80
+ end
81
+ end
82
+ pixel_colors
83
+ end
84
+ end
85
+ end
86
+
11
87
  # Display a picture in the terminal.
12
88
  # Either use coloured characters or iTerm2 protocol.
13
89
  class Terminal
@@ -22,41 +98,31 @@ module Aspera
22
98
  private_constant :TERM_ENV_VARS, :ITERM_NAMES, :DEFAULT_FONT_RATIO
23
99
  class << self
24
100
  # @param blob [String] The image as a binary string
25
- # @param reserve [Integer] Number of lines to reserve for other text than the image
26
101
  # @param text [Boolean] `true` to display the image as text, `false` to use iTerm2 if supported
102
+ # @param reserve [Integer] Number of lines to reserve for other text than the image
27
103
  # @param double [Boolean] `true` to use colors on half lines, `false` to use colors on full lines
28
104
  # @param font_ratio [Float] ratio = font height / font width
29
105
  # @return [String] The image as text, or the iTerm2 escape sequence
30
- def build(blob, reserve: 3, text: false, double: true, font_ratio: DEFAULT_FONT_RATIO)
106
+ def build(blob, text: false, reserve: 3, double: true, font_ratio: DEFAULT_FONT_RATIO)
31
107
  return '[Image display requires a terminal]' unless Environment.terminal?
32
108
  return iterm_display_image(blob) if iterm_supported? && !text
33
- begin
34
- # do not require statically, as the package is optional
35
- require 'rmagick' # https://rmagick.github.io/index.html
36
- rescue LoadError => e
37
- Log.log.error('Install missing gem: gem install rmagick')
38
- # fallback to iterm, if supported
109
+ pixel_colors =
110
+ begin
111
+ Log.log.debug('Trying chunky_png')
112
+ Backend::ChunkyPNG.new(blob, reserve: reserve, double: double, font_ratio: font_ratio).terminal_pixels
113
+ rescue => e
114
+ Log.log.debug(e.message)
115
+ begin
116
+ Log.log.debug('Trying rmagick')
117
+ Backend::RMagick.new(blob, reserve: reserve, double: double, font_ratio: font_ratio).terminal_pixels
118
+ rescue => e
119
+ Log.log.debug(e.message)
120
+ nil
121
+ end
122
+ end
123
+ if pixel_colors.nil?
39
124
  return iterm_display_image(blob) if iterm_supported?
40
- Log.log.error('Cant display picture.')
41
- raise e
42
- end
43
- image = Magick::ImageList.new.from_blob(blob)
44
- (term_rows, term_columns) = IO.console.winsize
45
- term_rows -= reserve
46
- # compute scaling to fit terminal
47
- fit_term_ratio = [term_rows.to_f * font_ratio / image.rows.to_f, term_columns.to_f / image.columns.to_f].min
48
- height_ratio = double ? 2.0 : 1.0
49
- image = image.scale((image.columns * fit_term_ratio).to_i, (image.rows * fit_term_ratio * height_ratio / font_ratio).to_i)
50
- # quantum depth is 8 or 16, see: `magick xc: -format "%q" info:`
51
- shift_for_8_bit = Magick::MAGICKCORE_QUANTUM_DEPTH - 8
52
- # get all pixel colors, adjusted for Rainbow
53
- pixel_colors = []
54
- image.each_pixel do |pixel, col, row|
55
- pixel_rgb = [pixel.red, pixel.green, pixel.blue]
56
- pixel_rgb = pixel_rgb.map{ |color| color >> shift_for_8_bit} unless shift_for_8_bit.eql?(0)
57
- # init 2-dim array
58
- pixel_colors[row] ||= []
59
- pixel_colors[row][col] = pixel_rgb
125
+ raise 'Cannot decode picture.'
60
126
  end
61
127
  # now generate text
62
128
  text_pixels = []
@@ -88,7 +154,7 @@ module Aspera
88
154
  }.map{ |k, v| "#{k}=#{v}"}.join(';')
89
155
  # \a is BEL, \e is ESC : https://github.com/ruby/ruby/blob/master/doc/syntax/literals.rdoc#label-Strings
90
156
  # escape sequence for iTerm2 image display
91
- return "\e]1337;File=#{arguments}:#{Base64.encode64(blob)}\a"
157
+ return "\e]1337;File=#{arguments}:#{Base64.strict_encode64(blob)}\a"
92
158
  end
93
159
 
94
160
  # @return [Boolean] true if the terminal supports iTerm2 image display
@@ -44,17 +44,18 @@ module Aspera
44
44
  end
45
45
  end
46
46
 
47
- # execute external command
48
- # one could use "system", but we would need to redirect stdout/err
49
- # @return nil
47
+ # Execute external command
48
+ # @return [nil]
50
49
  def external_command(command_sym, command_args)
51
50
  Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
52
- Environment.secure_execute(exec: command_sym.to_s, args: command_args.map(&:to_s), out: File::NULL, err: File::NULL)
51
+ Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
53
52
  end
54
53
 
54
+ # Execute external command and capture output
55
+ # @return [String]
55
56
  def external_capture(command_sym, command_args)
56
57
  Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
57
- return Environment.secure_capture(exec: command_sym.to_s, args: command_args.map(&:to_s))
58
+ return Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture)
58
59
  end
59
60
 
60
61
  def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
@@ -56,10 +56,10 @@ module Aspera
56
56
  # Retrieve structure from cloud (CDN) with all versions available
57
57
  def versions
58
58
  if @connect_versions.nil?
59
- javascript = cdn_api.call(operation: 'GET', subpath: VERSION_INFO_FILE)
59
+ http = cdn_api.read(VERSION_INFO_FILE, ret: :resp)
60
60
  # get result on one line
61
- connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/, '')
62
- Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
61
+ connect_versions_javascript = http.body.gsub(/\r?\n\s*/, '')
62
+ Log.dump(:javascript, connect_versions_javascript)
63
63
  # get javascript object only
64
64
  found = connect_versions_javascript.match(/^.*? = (.*);/)
65
65
  raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?