aspera-cli 4.27.1 → 4.27.3

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 (133) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +67 -1
  4. data/bin/ascli +2 -1
  5. data/docs/README.md +805 -747
  6. data/lib/aspera/agent/connect.rb +6 -4
  7. data/lib/aspera/agent/desktop.rb +2 -2
  8. data/lib/aspera/agent/direct.rb +3 -1
  9. data/lib/aspera/agent/node.rb +3 -3
  10. data/lib/aspera/api/alee.rb +1 -1
  11. data/lib/aspera/api/aoc.rb +14 -12
  12. data/lib/aspera/api/ats.rb +1 -1
  13. data/lib/aspera/api/cos_node.rb +2 -2
  14. data/lib/aspera/api/faspex.rb +9 -7
  15. data/lib/aspera/api/httpgw.rb +37 -33
  16. data/lib/aspera/api/node.rb +38 -33
  17. data/lib/aspera/ascmd.rb +3 -1
  18. data/lib/aspera/ascp/installation.rb +62 -27
  19. data/lib/aspera/ascp/management.rb +1 -0
  20. data/lib/aspera/assert.rb +4 -0
  21. data/lib/aspera/cli/ascp_actions.rb +20 -41
  22. data/lib/aspera/cli/async_transfer_store.rb +2 -2
  23. data/lib/aspera/cli/bootstrapper.rb +11 -15
  24. data/lib/aspera/cli/command_line.rb +252 -0
  25. data/lib/aspera/cli/command_registry.rb +149 -33
  26. data/lib/aspera/cli/command_spec.rb +103 -14
  27. data/lib/aspera/cli/completion/ascli.bash +12 -0
  28. data/lib/aspera/cli/completion/ascli.fish +16 -0
  29. data/lib/aspera/cli/completion/ascli.zsh +19 -0
  30. data/lib/aspera/cli/context.rb +3 -0
  31. data/lib/aspera/cli/deprecation.rb +37 -0
  32. data/lib/aspera/cli/extended_value.rb +2 -0
  33. data/lib/aspera/cli/formatter.rb +87 -75
  34. data/lib/aspera/cli/gem_checker.rb +1 -1
  35. data/lib/aspera/cli/hints.rb +7 -6
  36. data/lib/aspera/cli/http.rb +21 -21
  37. data/lib/aspera/cli/info.rb +3 -0
  38. data/lib/aspera/cli/mcp_tool.rb +47 -83
  39. data/lib/aspera/cli/option_declarator.rb +33 -42
  40. data/lib/aspera/cli/option_registry.rb +69 -0
  41. data/lib/aspera/cli/option_types.rb +103 -0
  42. data/lib/aspera/cli/option_value.rb +281 -0
  43. data/lib/aspera/cli/options.schema.yaml +38 -5
  44. data/lib/aspera/cli/parser.rb +307 -848
  45. data/lib/aspera/cli/plugins/alee.rb +7 -4
  46. data/lib/aspera/cli/plugins/aoc.rb +435 -380
  47. data/lib/aspera/cli/plugins/ats.rb +58 -73
  48. data/lib/aspera/cli/plugins/base.rb +190 -240
  49. data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
  50. data/lib/aspera/cli/plugins/config.rb +244 -178
  51. data/lib/aspera/cli/plugins/console.rb +102 -38
  52. data/lib/aspera/cli/plugins/cos.rb +6 -23
  53. data/lib/aspera/cli/plugins/factory.rb +3 -0
  54. data/lib/aspera/cli/plugins/faspex5.rb +176 -173
  55. data/lib/aspera/cli/plugins/faspio.rb +5 -10
  56. data/lib/aspera/cli/plugins/httpgw.rb +8 -11
  57. data/lib/aspera/cli/plugins/mcp.rb +20 -55
  58. data/lib/aspera/cli/plugins/node.rb +277 -311
  59. data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
  60. data/lib/aspera/cli/plugins/preview.rb +79 -90
  61. data/lib/aspera/cli/plugins/server.rb +76 -50
  62. data/lib/aspera/cli/plugins/shares.rb +68 -116
  63. data/lib/aspera/cli/preset_actions.rb +17 -10
  64. data/lib/aspera/cli/preset_manager.rb +12 -2
  65. data/lib/aspera/cli/prompt.rb +35 -0
  66. data/lib/aspera/cli/result.rb +13 -18
  67. data/lib/aspera/cli/runner.rb +31 -54
  68. data/lib/aspera/cli/special_values.rb +5 -0
  69. data/lib/aspera/cli/sync_actions.rb +41 -37
  70. data/lib/aspera/cli/terminal_formatter.rb +9 -3
  71. data/lib/aspera/cli/transfer_actions.rb +0 -6
  72. data/lib/aspera/cli/transfer_agent.rb +29 -35
  73. data/lib/aspera/cli/vault_manager.rb +0 -17
  74. data/lib/aspera/cli/version.rb +1 -1
  75. data/lib/aspera/cli/wizard.rb +4 -2
  76. data/lib/aspera/command_line_builder.rb +1 -0
  77. data/lib/aspera/coverage.rb +1 -0
  78. data/lib/aspera/environment.rb +7 -1
  79. data/lib/aspera/faspex_gw.rb +2 -1
  80. data/lib/aspera/faspex_postproc.rb +1 -0
  81. data/lib/aspera/graphql.rb +5 -5
  82. data/lib/aspera/json_rpc/client.rb +5 -5
  83. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  84. data/lib/aspera/keychain/one_password_api.rb +1 -1
  85. data/lib/aspera/link_header.rb +2 -2
  86. data/lib/aspera/log.rb +22 -25
  87. data/lib/aspera/markdown.rb +2 -0
  88. data/lib/aspera/mime.rb +25 -0
  89. data/lib/aspera/node_simulator.rb +1 -0
  90. data/lib/aspera/oauth/base.rb +35 -25
  91. data/lib/aspera/oauth/factory.rb +1 -0
  92. data/lib/aspera/oauth/generic.rb +1 -1
  93. data/lib/aspera/oauth/jwt.rb +1 -1
  94. data/lib/aspera/oauth/web.rb +9 -8
  95. data/lib/aspera/preview/file_types.rb +4 -4
  96. data/lib/aspera/preview/generator.rb +7 -0
  97. data/lib/aspera/preview/options.rb +4 -4
  98. data/lib/aspera/preview/terminal.rb +4 -3
  99. data/lib/aspera/preview/utils.rb +9 -6
  100. data/lib/aspera/products/connect.rb +1 -1
  101. data/lib/aspera/rainbow.rb +7 -0
  102. data/lib/aspera/rest/aspera_errors.rb +60 -0
  103. data/lib/aspera/rest/call_error.rb +27 -0
  104. data/lib/aspera/rest/client.rb +514 -0
  105. data/lib/aspera/rest/error_analyzer.rb +113 -0
  106. data/lib/aspera/rest/list.rb +143 -0
  107. data/lib/aspera/rest/parameters.rb +55 -0
  108. data/lib/aspera/rest/util.rb +176 -0
  109. data/lib/aspera/rest.rb +7 -621
  110. data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
  111. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
  112. data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
  113. data/lib/aspera/schema/documentation.rb +13 -3
  114. data/lib/aspera/schema/registry.rb +18 -1
  115. data/lib/aspera/schema/validator.rb +92 -0
  116. data/lib/aspera/secret_hider.rb +36 -25
  117. data/lib/aspera/string_ext.rb +15 -0
  118. data/lib/aspera/temp_file_manager.rb +6 -5
  119. data/lib/aspera/transfer/parameters.rb +2 -0
  120. data/lib/aspera/transfer/spec.rb +1 -0
  121. data/lib/aspera/transfer/spec.schema.yaml +1 -0
  122. data/lib/aspera/uri_reader.rb +11 -11
  123. data/lib/aspera/web_auth/index.html +147 -0
  124. data/lib/aspera/web_auth/server.rb +81 -0
  125. data.tar.gz.sig +0 -0
  126. metadata +39 -7
  127. metadata.gz.sig +0 -0
  128. data/lib/aspera/colors.rb +0 -79
  129. data/lib/aspera/rest_call_error.rb +0 -25
  130. data/lib/aspera/rest_error_analyzer.rb +0 -111
  131. data/lib/aspera/rest_errors_aspera.rb +0 -58
  132. data/lib/aspera/rest_list.rb +0 -136
  133. data/lib/aspera/web_auth.rb +0 -211
@@ -4,6 +4,8 @@ require 'aspera/oauth/jwt'
4
4
  require 'aspera/assert'
5
5
  require 'aspera/cli/option_declarator'
6
6
  require 'aspera/cli/plugins/factory'
7
+ require 'aspera/rainbow'
8
+ using Rainbow
7
9
 
8
10
  module Aspera
9
11
  module Cli
@@ -60,7 +62,7 @@ module Aspera
60
62
  detection_info = nil
61
63
  begin
62
64
  Log.log.debug { "detecting #{plugin_name_sym} at #{app_url}" }
63
- RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
65
+ Rest::Parameters.instance.spinner_cb.call(plugin_name_sym.to_s)
64
66
  detection_info = plugin_klass.detect(app_url)
65
67
  rescue OpenSSL::SSL::SSLError => e
66
68
  Log.log.warn(e.message)
@@ -76,7 +78,7 @@ module Aspera
76
78
  # If there is a redirect, then the detector can override the url.
77
79
  found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
78
80
  end
79
- RestParameters.instance.spinner_cb.call(action: :success)
81
+ Rest::Parameters.instance.spinner_cb.call(action: :success)
80
82
  Aspera.assert(!found_apps.empty?) { "No known application found at #{app_url}" }
81
83
  Aspera.assert(found_apps.all? { |a| a.keys.all?(Symbol) }, 'all app info keys must be symbols')
82
84
  return found_apps
@@ -70,6 +70,7 @@ module Aspera
70
70
  Aspera.assert(unsupported_keys.empty?) { "Unsupported definition keys: #{unsupported_keys}" }
71
71
  Aspera.assert(node.key?('type') || node.key?('enum')) { "Missing type for #{name} in #{schema.current.dig('description').current}" }
72
72
  Aspera.assert(node['type'].eql?('boolean')) { "switch must be bool: #{name}" } if node['x-cli-switch'] && !node['x-cli-special']
73
+ Aspera.assert(node['x-cli-switch']) { "boolean direct option must have x-cli-switch: #{name}" } if node['type'].eql?('boolean') && node.key?('x-cli-option') && supported_by_agent(:direct, node) && !node['x-cli-special']
73
74
  node['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if node['x-cli-option'].eql?(true) || (node['x-cli-switch'].eql?(true) && !node.key?('x-cli-option'))
74
75
  Aspera.assert(DIRECT_PROPERTIES.any? { |i| node.key?(i) }, name, type: :warn) if ascp && supported_by_agent(:direct, node)
75
76
  node.freeze
@@ -4,6 +4,7 @@
4
4
  if ENV.key?('ENABLE_COVERAGE')
5
5
  require 'simplecov'
6
6
  require 'securerandom'
7
+ require 'fileutils'
7
8
  # compute development top folder based on this source location
8
9
  development_root = File.dirname(File.realpath(__FILE__), 3)
9
10
  coverage_dir = 'tmp/coverage'
@@ -8,6 +8,8 @@ require 'singleton'
8
8
  require 'open3'
9
9
  require 'English'
10
10
  require 'shellwords'
11
+ require 'aspera/rainbow'
12
+ using Rainbow
11
13
 
12
14
  # cspell:words MEBI mswin bccwin
13
15
 
@@ -201,12 +203,16 @@ module Aspera
201
203
  I18N_VARS.each { |var| ENV[var] = 'C' }
202
204
  end
203
205
 
206
+ # Override detection of Unicode support: `true` or `false`, or `nil` for auto-detection
207
+ attr_writer :unicode
208
+
204
209
  # @return [Boolean] true if we can display Unicode characters
205
210
  # Uses Encoding.locale_charmap for OS-independent detection.
206
211
  # Falls back to locale env vars for systems where charmap is not available.
207
212
  # https://www.gnu.org/software/libc/manual/html_node/Locale-Categories.html
208
213
  # https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
209
214
  def terminal_supports_unicode?
215
+ return @unicode unless @unicode.nil?
210
216
  return false unless terminal?
211
217
  locale_charmap_utf8? || I18N_VARS.any? { |var| ENV[var]&.include?('UTF-8') }
212
218
  end
@@ -237,7 +243,7 @@ module Aspera
237
243
  OS_WINDOWS
238
244
  when /darwin/, /mac os/
239
245
  OS_MACOS
240
- when /linux/
246
+ when /linux/, /cosmo/
241
247
  OS_LINUX
242
248
  when /aix/
243
249
  OS_AIX
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'aspera/log'
4
4
  require 'aspera/assert'
5
+ require 'aspera/mime'
5
6
  require 'webrick'
6
7
  require 'json'
7
8
 
@@ -10,7 +11,7 @@ module Aspera
10
11
  class Faspex4GWServlet < WEBrick::HTTPServlet::AbstractServlet
11
12
  AOC_API = 'Aspera::Api::AoC'
12
13
  FX_API = 'Aspera::Api::Faspex'
13
- # @param app_api [Rest] API object
14
+ # @param app_api [Rest::Client] API object
14
15
  # @param app_context [String] workspace id (aoc only)
15
16
  def initialize(server, app_api, app_context)
16
17
  Aspera.assert_values(app_api.class.name, [AOC_API, FX_API])
@@ -6,6 +6,7 @@ require 'English'
6
6
  require 'webrick'
7
7
  require 'aspera/log'
8
8
  require 'aspera/assert'
9
+ require 'aspera/mime'
9
10
 
10
11
  module Aspera
11
12
  # Start a Faspex-4 style post-processing script using Faspex-5 webhook
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/rest_call_error'
3
+ require 'aspera/rest/call_error'
4
4
 
5
5
  module Aspera
6
- # Helper to execute GraphQL queries via a Rest object.
6
+ # Helper to execute GraphQL queries via a Rest::Client object.
7
7
  # Follows the GraphQL-over-HTTP convention: POST {query, variables} to endpoint,
8
8
  # parse top-level `data` key, raise on top-level `errors` array.
9
9
  module GraphQL
@@ -12,16 +12,16 @@ module Aspera
12
12
 
13
13
  class << self
14
14
  # Execute a GraphQL query loaded from a .graphql file in lib/aspera/api/queries/
15
- # @param rest [Rest] API object pointing at the GraphQL endpoint
15
+ # @param rest [Rest::Client] API object pointing at the GraphQL endpoint
16
16
  # @param name [String] Base filename without extension (e.g. 'my_query')
17
17
  # @param variables [Hash] GraphQL variables
18
18
  # @return [Hash] The `data` object from the response
19
- # @raise [RestCallError] if the response contains GraphQL errors
19
+ # @raise [Rest::CallError] if the response contains GraphQL errors
20
20
  def execute(rest, name, variables = {})
21
21
  query = load_query(name)
22
22
  response = rest.create(nil, {query: query, variables: variables})
23
23
  errors = response['errors']
24
- raise RestCallError, errors.map { |e| e['message'] }.join("\n") if errors.is_a?(Array) && !errors.empty?
24
+ raise Rest::CallError, errors.map { |e| e['message'] }.join("\n") if errors.is_a?(Array) && !errors.empty?
25
25
  response['data']
26
26
  end
27
27
 
@@ -2,19 +2,19 @@
2
2
 
3
3
  # cspell:ignore blankslate jsonrpc
4
4
 
5
- require 'aspera/rest_error_analyzer'
5
+ require 'aspera/rest/error_analyzer'
6
6
  require 'aspera/assert'
7
7
  require 'aspera/json_rpc/version'
8
8
  require 'blankslate'
9
9
 
10
- Aspera::RestErrorAnalyzer.instance.add_simple_handler(name: 'JSON RPC', path: %w[error message], always: true)
10
+ Aspera::Rest::ErrorAnalyzer.instance.add_simple_handler(name: 'JSON RPC', path: %w[error message], always: true)
11
11
 
12
12
  module Aspera
13
13
  module JsonRpc
14
- # JSON-RPC 2.0 client over an Aspera::Rest HTTP endpoint.
14
+ # JSON-RPC 2.0 client over an Aspera::Rest::Client HTTP endpoint.
15
15
  # Methods are dispatched dynamically via method_missing.
16
16
  # Example:
17
- # client = JsonRpc::Client.new(Rest.new(base_url: 'http://127.0.0.1:33024'))
17
+ # client = JsonRpc::Client.new(Rest::Client.new(base_url: 'http://127.0.0.1:33024'))
18
18
  # client.get_info
19
19
  # client.start_transfer(app_id: '...', transfer_spec: {...})
20
20
  class Client < BlankSlate
@@ -22,7 +22,7 @@ module Aspera
22
22
  reveal :inspect
23
23
  reveal :to_s
24
24
 
25
- # @param api [Rest] Aspera REST object pointing at the JSON-RPC endpoint
25
+ # @param api [Rest::Client] Aspera REST object pointing at the JSON-RPC endpoint
26
26
  # @param namespace [String, nil] optional method prefix, e.g. "myns."
27
27
  def initialize(api, namespace = nil)
28
28
  super()
@@ -138,7 +138,7 @@ module Aspera
138
138
  end
139
139
  end
140
140
 
141
- # Derive an AES key from +new_password+.
141
+ # Derive an AES key from `new_password`.
142
142
  # @legacy_key is true for vaults created by v4.26.0 or earlier (no kdf field):
143
143
  # key = password truncated/zero-padded to key_bytes (same as symmetric-encryption did).
144
144
  # Otherwise PBKDF2 is used. When @kdf_params is nil (new vault or password change)
@@ -16,7 +16,7 @@ module Aspera
16
16
  def initialize(url:, token:, vault_id:)
17
17
  super()
18
18
  @vault_id = vault_id
19
- @api = Rest.new(
19
+ @api = Rest::Client.new(
20
20
  base_url: url,
21
21
  headers: {'Authorization' => "Bearer #{token}"}
22
22
  )
@@ -31,7 +31,7 @@ module Aspera
31
31
  @links = links
32
32
  end
33
33
 
34
- # Return the href of the first link whose +rel+ attribute matches +rel+.
34
+ # Return the href of the first link whose `rel` attribute matches `rel`.
35
35
  # Comparison is case-insensitive per RFC 7230 s.3.2 and RFC 8288 s.3.
36
36
  # Returns nil if no link with that relation exists.
37
37
  # @param rel [String]
@@ -41,7 +41,7 @@ module Aspera
41
41
  end
42
42
 
43
43
  class << self
44
- # Parse a raw Link header value into a +LinkHeader+ instance.
44
+ # Parse a raw Link header value into a `LinkHeader` instance.
45
45
  # Uses StringScanner so that commas inside <URI> are not treated as separators.
46
46
  # @param raw [String, nil]
47
47
  # @return [LinkHeader]
data/lib/aspera/log.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/assert'
4
- require 'aspera/colors'
5
4
  require 'aspera/secret_hider'
6
5
  require 'logger'
7
6
  require 'pp'
8
7
  require 'json'
9
8
  require 'singleton'
10
9
  require 'stringio'
10
+ require 'aspera/rainbow'
11
+ using Rainbow
11
12
 
12
13
  # Ignore warnings
13
14
  old_verbose = $VERBOSE
@@ -118,13 +119,13 @@ module Aspera
118
119
 
119
120
  # Returns the last 2 containers (module/class) and method caller
120
121
  def caller_method
121
- stack = caller
122
- i = stack.rindex { |line| line.include?('Logger') }
123
- frame = stack[i + 1] if i && stack[i + 1]
122
+ locations = caller_locations
123
+ # Outermost frame of logging code: Ruby logger, or this file (level methods, dump)
124
+ i = locations.rindex { |loc| loc.path.end_with?('/logger.rb') || loc.path.eql?(__FILE__) }
125
+ frame = locations[i + 1] if i
124
126
  return '???' unless frame
125
- # Extract the "Class::Module::Method" or "Class#method" part
126
- full = frame[/'([^']+)'/, 1]
127
- return '???' unless full
127
+ # "Class::Module#method" (Ruby >= 3.4) or "method" (Ruby < 3.4)
128
+ full = frame.label
128
129
  # Split into class/module and method parts
129
130
  parts = full.split(/(::|#)/)
130
131
  # Reconstruct keeping only last two class/module names + separator + method
@@ -138,6 +139,7 @@ module Aspera
138
139
  end
139
140
  class_parts = classes.split('::')
140
141
  selected_classes = class_parts.last(2).join('::')
142
+ return method if selected_classes.empty?
141
143
  "#{selected_classes}.#{method}"
142
144
  end
143
145
  end
@@ -230,34 +232,29 @@ module Aspera
230
232
  self.logger_type = @logger_type
231
233
  end
232
234
 
233
- # Define decoration of levels
234
- LVL_DECO = {
235
- TRACE2: %i{dim},
236
- TRACE1: %i{blue},
237
- DEBUG: %i{cyan},
238
- INFO: %i{green},
239
- WARN: %i{bg_brown black},
240
- ERROR: %i{bg_red blink},
241
- FATAL: %i{magenta},
242
- UNKNOWN: %i{blink}
235
+ # Short (4-letters) levels with color (evaluated on each call, as colors can be enabled or disabled by option)
236
+ LVL_COLOR = {
237
+ TRACE2: -> { short_levl(:TRACE2).faint },
238
+ TRACE1: -> { short_levl(:TRACE1).blue },
239
+ DEBUG: -> { short_levl(:DEBUG).cyan },
240
+ INFO: -> { short_levl(:INFO).green },
241
+ WARN: -> { short_levl(:WARN).bg(:yellow).black },
242
+ ERROR: -> { short_levl(:ERROR).bg(:red).blink },
243
+ FATAL: -> { short_levl(:FATAL).magenta },
244
+ UNKNOWN: -> { short_levl(:UNKNOWN).blink }
243
245
  }.freeze
244
246
 
245
- # Short (4-letters) levels with color
246
- LVL_COLOR = LVL_DECO.to_h do |k, v|
247
- [k, short_levl(k).apply(*v)]
248
- end.freeze
249
-
250
- DEFAULT_FORMATTER = ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{m}\n" }
247
+ DEFAULT_FORMATTER = ->(s, _d, _p, m) { "#{LVL_COLOR[s]&.call} #{m}\n" }
251
248
 
252
249
  # pre-defined formatters
253
250
  FORMATTER_LAMBDAS = {
254
251
  standard: Logger::Formatter.new,
255
252
  default: DEFAULT_FORMATTER,
256
- caller: ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{Log.caller_method}\n#{m}\n" }
253
+ caller: ->(s, _d, _p, m) { "#{LVL_COLOR[s]&.call} #{Log.caller_method}\n#{m}\n" }
257
254
  }.freeze
258
255
 
259
256
  FORMATTERS = FORMATTER_LAMBDAS.keys
260
257
 
261
- private_constant :LVL_DECO, :DEFAULT_FORMATTER, :FORMATTER_LAMBDAS
258
+ private_constant :DEFAULT_FORMATTER, :FORMATTER_LAMBDAS
262
259
  end
263
260
  end
@@ -82,6 +82,8 @@ module Aspera
82
82
  col_widths = table.transpose.map do |col|
83
83
  [col.flat_map { |c| c.to_s.delete('`').split(HTML_BREAK).map(&:size) }.max, COL_WIDTH].min
84
84
  end
85
+ # do not modify caller's table
86
+ table = table.dup
85
87
  headings = table.shift
86
88
  table.unshift(col_widths.map { |col_width| '-' * col_width })
87
89
  table.unshift(headings)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ # MIME types used in `Content-Type` and `Accept`
5
+ module Mime
6
+ # JSON body
7
+ JSON = 'application/json'
8
+ # URL encoded form body
9
+ WWW = 'application/x-www-form-urlencoded'
10
+ # Plain text body
11
+ TEXT = 'text/plain'
12
+ # JSON:API body (https://jsonapi.org)
13
+ JSON_API = 'application/vnd.api+json'
14
+ # Check if a media type is JSON: `application/json` or structured syntax suffix `+json` (RFC 6839)
15
+ # @param mime [String] Media type, without parameters (see `Rest.parse_header`)
16
+ # @return [Boolean] `true` if JSON
17
+ def json?(mime) = mime == JSON || mime.end_with?(JSON_SUFFIX) || mime == LEGACY_JSON
18
+ module_function :json?
19
+ # Structured syntax suffix for JSON
20
+ JSON_SUFFIX = '+json'
21
+ # Non standard type used by some servers for JSON
22
+ LEGACY_JSON = 'application/x-javascript'
23
+ private_constant :JSON_SUFFIX, :LEGACY_JSON
24
+ end
25
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/assert'
4
+ require 'aspera/mime'
4
5
  require 'aspera/ascp/installation'
5
6
  require 'aspera/agent/direct'
6
7
  require 'aspera/log'
@@ -3,7 +3,10 @@
3
3
  require 'aspera/oauth/factory'
4
4
  require 'aspera/log'
5
5
  require 'aspera/assert'
6
+ require 'aspera/mime'
6
7
  require 'date'
8
+ require 'aspera/rainbow'
9
+ using Rainbow
7
10
 
8
11
  module Aspera
9
12
  module OAuth
@@ -31,7 +34,7 @@ module Aspera
31
34
  Aspera.assert_type(params, Hash)
32
35
  Aspera.assert_type(cache_ids, Array)
33
36
  # This is the OAuth API
34
- @api = Rest.new(**rest_params)
37
+ @api = Rest::Client.new(**rest_params)
35
38
  @params = params.dup.freeze
36
39
  @path_token = path_token
37
40
  @token_field = token_field
@@ -47,36 +50,39 @@ module Aspera
47
50
  # Sub path to generate token
48
51
  attr_reader :path_token
49
52
 
53
+ # Parameters for token creation call
54
+ # @param include_secret [Boolean] Add secret in default call parameters
55
+ # @param other_params [Hash] Other parameters to merge
56
+ # @return [Hash] Token creation parameters
57
+ def base_parameters(include_secret: false, **other_params)
58
+ call_params = @params.dup
59
+ call_params.delete(:client_secret) unless include_secret
60
+ call_params.merge(other_params)
61
+ end
62
+
50
63
  # Helper method to create token as per RFC
51
- # @return [Net::HTTPResponse] raw HTTP response with token
52
- # @raise [RestCallError] if not 2XX code
53
- def create_token_call(creation_params)
54
- Log.log.debug { 'Generating a new token'.bg_green }
64
+ # @param other_params [Hash] Additional parameter to base parameters
65
+ # @return [Net::HTTPResponse] Raw HTTP response with token
66
+ # @raise [Rest::CallError] If not 2XX code
67
+ def create_token_base(include_secret: false, **other_params)
68
+ Log.log.debug { 'Generating a new token'.bg(:green) }
69
+ creation_params = base_parameters(include_secret: include_secret, **other_params)
55
70
  return @api.create(@path_token, nil, query: creation_params, ret: :resp) if @use_query
56
71
  return @api.create(@path_token, creation_params, content_type: Mime::WWW, ret: :resp)
57
72
  end
58
73
 
59
- # Create base parameters for token creation calls
60
- # @param add_secret [Boolean] Add secret in default call parameters
61
- # @return [Hash] Optional general parameters
62
- def base_params(add_secret: false)
63
- call_params = @params.dup
64
- call_params.delete(:client_secret) unless add_secret
65
- return call_params
66
- end
67
-
68
- # @return [String] value suitable for Authorization header
74
+ # @return [String] Value suitable for Authorization header: adds `Bearer `
69
75
  def authorization(**kwargs)
70
76
  return OAuth::Factory.bearer_authorization(token(**kwargs))
71
77
  end
72
78
 
73
- # get an OAuth v2 token (generated, cached, refreshed)
74
- # call token() to get a token.
75
- # if a token is expired (api returns 4xx), call again token(refresh: true)
79
+ # Get a OAuth v2 token (generated, cached, refreshed)
80
+ # If a token is expired (api returns 4xx), call again token(refresh: true)
76
81
  # @param cache [Boolean] set to false to disable cache
77
82
  # @param refresh [Boolean] set to true to force refresh or re-generation (if previous failed)
83
+ # @return [String] The bearer token
78
84
  def token(cache: true, refresh: false)
79
- # get token info from cache (or nil), decoded with date and expiration status
85
+ # Get token info from cache (or nil), decoded with date and expiration status
80
86
  token_info = Factory.instance.get_token_info(@token_cache_id) if cache
81
87
  token_data = nil
82
88
  unless token_info.nil?
@@ -90,17 +96,21 @@ module Aspera
90
96
  Log.log.trace1 { "refresh: #{refresh} expired: #{token_info[:expired]}" }
91
97
  refresh_token = nil
92
98
  if token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
93
- # save possible refresh token, before deleting the cache
99
+ # Save possible refresh token, before deleting the cache
94
100
  refresh_token = token_data['refresh_token']
95
101
  end
96
- # delete cache
102
+ # Delete cache
97
103
  Factory.instance.persist_mgr.delete(@token_cache_id)
98
104
  token_data = nil
99
- # lets try the existing refresh token
105
+ # Lets try the existing refresh token
100
106
  # NOTE: AoC admin token has no refresh, and lives by default 1800secs
101
107
  if !refresh_token.nil?
102
108
  begin
103
- http = create_token_call(base_params(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
109
+ http = create_token_base(
110
+ include_secret: true,
111
+ grant_type: 'refresh_token',
112
+ refresh_token: refresh_token
113
+ )
104
114
  # Save only if success
105
115
  json_data = http.body
106
116
  token_data = JSON.parse(json_data)
@@ -116,13 +126,13 @@ module Aspera
116
126
  # no cache, nor refresh: generate a token
117
127
  if token_data.nil?
118
128
  # Call the method-specific token creation
119
- # which returns the result of create_token_call
129
+ # which returns the result of create_token_base
120
130
  json_data = create_token.body
121
131
  token_data = JSON.parse(json_data)
122
132
  Factory.instance.persist_mgr.put(@token_cache_id, json_data)
123
133
  end
124
134
  Aspera.assert(token_data.key?(@token_field)) { "API error: No such field in answer: #{@token_field}" } unless token_data.nil?
125
- # ok we shall have a token here
135
+ # Ok we shall have a token here
126
136
  return token_data[@token_field]
127
137
  end
128
138
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'aspera/id_generator'
4
4
  require 'aspera/assert'
5
+ require 'aspera/string_ext'
5
6
  require 'singleton'
6
7
  require 'base64'
7
8
  module Aspera
@@ -23,7 +23,7 @@ module Aspera
23
23
  end
24
24
 
25
25
  def create_token
26
- return create_token_call(base_params.merge(@create_params))
26
+ create_token_base(**@create_params)
27
27
  end
28
28
  end
29
29
  Factory.instance.register_token_creator(Generic)
@@ -59,7 +59,7 @@ module Aspera
59
59
  }.merge(@additional_payload)
60
60
  Log.dump(:jwt_payload, jwt_payload)
61
61
  assertion = JWT.encode(jwt_payload, @private_key_obj, 'RS256', @headers)
62
- return create_token_call(base_params.merge(grant_type: GRANT_TYPE, assertion: assertion))
62
+ create_token_base(grant_type: GRANT_TYPE, assertion: assertion)
63
63
  end
64
64
  end
65
65
  Factory.instance.register_token_creator(Jwt)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'aspera/oauth/base'
4
4
  require 'aspera/environment'
5
- require 'aspera/web_auth'
5
+ require 'aspera/web_auth/server'
6
6
  require 'aspera/assert'
7
7
  module Aspera
8
8
  module OAuth
@@ -32,23 +32,24 @@ module Aspera
32
32
  random_state = SecureRandom.uuid
33
33
  login_page_url = Rest.build_uri(
34
34
  "#{api.base_url}/#{@path_authorize}",
35
- base_params.merge(response_type: 'code', redirect_uri: @redirect_uri, state: random_state)
35
+ base_parameters(response_type: 'code', redirect_uri: @redirect_uri, state: random_state)
36
36
  )
37
37
  # here, we need a human to authorize on a web page
38
38
  Log.log.debug { "login_page_url=#{login_page_url}" }
39
39
  # start a web server to receive request code
40
- web_server = WebAuth.new(@redirect_uri, self.class.additional_info)
40
+ web_server = WebAuth::Server.new(@redirect_uri, self.class.additional_info)
41
41
  # start browser on login page
42
42
  Environment.instance.open_uri(login_page_url)
43
43
  # wait for code in request
44
44
  received_params = web_server.received_request
45
45
  Aspera.assert(random_state.eql?(received_params['state'])) { 'wrong received state' }
46
46
  # exchange code for token
47
- return create_token_call(base_params(add_secret: true).merge(
48
- grant_type: 'authorization_code',
49
- code: received_params['code'],
50
- redirect_uri: @redirect_uri
51
- ))
47
+ create_token_base(
48
+ include_secret: true,
49
+ grant_type: 'authorization_code',
50
+ code: received_params['code'],
51
+ redirect_uri: @redirect_uri
52
+ )
52
53
  end
53
54
  end
54
55
  Factory.instance.register_token_creator(Web)
@@ -57,11 +57,11 @@ module Aspera
57
57
 
58
58
  private_constant :SUPPORTED_MIME_TYPES
59
59
 
60
- # @attr use_mimemagic [Boolean] `true` to use mimemagic to determine real mime type based on file content
61
- attr_accessor :use_mimemagic
60
+ # @attr detect_mime [Boolean] `true` to determine real mime type based on file content
61
+ attr_accessor :detect_mime
62
62
 
63
63
  def initialize
64
- @use_mimemagic = false
64
+ @detect_mime = false
65
65
  end
66
66
 
67
67
  # @param mimetype [String] mime type
@@ -81,7 +81,7 @@ module Aspera
81
81
  # @return [Symbol] file type, one of enum CONVERSION_TYPES
82
82
  # @raise [RuntimeError] if no conversion type found
83
83
  def conversion_type(filepath, mimetype)
84
- Log.log.debug { "conversion_type(#{filepath},mime=#{mimetype},magic=#{@use_mimemagic})" }
84
+ Log.log.debug { "conversion_type(#{filepath},mime=#{mimetype},magic=#{@detect_mime})" }
85
85
  # Default type or empty means no type
86
86
  mimetype = TYPE_NOT_FOUND if mimetype.nil? || (mimetype.is_a?(String) && mimetype.empty?)
87
87
  mimetype = Marcel::MimeType.for(Pathname.new(filepath), name: File.basename(filepath), declared_type: mimetype)
@@ -251,6 +251,7 @@ module Aspera
251
251
  tmp_pdf_file = File.join(this_tmpdir, "#{File.basename(@source, File.extname(@source))}.pdf")
252
252
  case @options.office_conversion
253
253
  when :unoconv
254
+ # `unoconv` is a python wrapper over `soffice`
254
255
  Utils.silent_execute(
255
256
  :unoconv,
256
257
  '-f', 'pdf',
@@ -261,6 +262,12 @@ module Aspera
261
262
  Utils.silent_execute(
262
263
  :soffice,
263
264
  '--headless',
265
+ '--invisible',
266
+ '--nocrashreport',
267
+ '--nodefault',
268
+ '--nofirststartwizard',
269
+ '--nologo',
270
+ '--norestore',
264
271
  '--convert-to', 'pdf',
265
272
  '--outdir', File.dirname(tmp_pdf_file),
266
273
  @source
@@ -10,19 +10,19 @@ module Aspera
10
10
  # types of generation for video files
11
11
  VIDEO_CONVERSION_METHODS = %i[reencode blend clips].freeze
12
12
  VIDEO_THUMBNAIL_METHODS = %i[fixed animated].freeze
13
- # methods for office document conversion
13
+ # @type [Array<Symbol>] methods for office document conversion
14
14
  OFFICE_CONVERSION_METHODS = %i[soffice unoconv].freeze
15
- # options used in generator
16
- # for scaling see: https://trac.ffmpeg.org/wiki/Scaling
15
+ # Options used in generator
16
+ # For scaling see: https://trac.ffmpeg.org/wiki/Scaling
17
17
  # iw/ih : input width or height
18
18
  # -x : keep aspect ratio, having value a multiple of x
19
19
  DESCRIPTIONS = [
20
+ {name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
20
21
  {name: :max_size, default: 1 << 24, description: 'maximum size (in bytes) of preview file'},
21
22
  {name: :thumb_vid_scale, default: '-1:min(ih,100)', description: 'png: video: size (ffmpeg scale argument)'},
22
23
  {name: :thumb_vid_fraction, default: 0.1, description: 'png: video: time percent position of snapshot'},
23
24
  {name: :thumb_img_size, default: 800, description: 'png: non-video: height (and width)'},
24
25
  {name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font for text rendering: `magick identify -list font`'},
25
- {name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
26
26
  {name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS},
27
27
  {name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS},
28
28
  {name: :video_scale, default: 'min(iw,360):-2', description: 'mp4: all: video scale (ffmpeg scale argument)'},
@@ -2,11 +2,12 @@
2
2
 
3
3
  # cspell:words Magick MAGICKCORE ITERM mintty winsize termcap
4
4
 
5
- require 'rainbow'
6
5
  require 'base64'
7
6
  require 'io/console'
8
7
  require 'aspera/log'
9
8
  require 'aspera/environment'
9
+ require 'aspera/rainbow'
10
+ using Rainbow
10
11
 
11
12
  module Aspera
12
13
  module Preview
@@ -155,9 +156,9 @@ module Aspera
155
156
  row_data.each_with_index do |pixel_rgb, col|
156
157
  text_pixels.push("\n") if col.eql?(0) && !row.eql?(0)
157
158
  if double
158
- text_pixels.push(Rainbow('▄').background(pixel_rgb).foreground(pixel_colors[row + 1][col]))
159
+ text_pixels.push('▄'.bg(pixel_rgb).fg(pixel_colors[row + 1][col]))
159
160
  else
160
- text_pixels.push(Rainbow(' ').background(pixel_rgb))
161
+ text_pixels.push(' '.bg(pixel_rgb))
161
162
  end
162
163
  end
163
164
  end