aspera-cli 4.26.2 → 4.27.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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +281 -21
  4. data/CONTRIBUTING.md +98 -7
  5. data/bin/ascli +3 -3
  6. data/docs/README.md +10757 -0
  7. data/docs/test-mcp-with-ai.md +202 -0
  8. data/lib/aspera/agent/base.rb +20 -6
  9. data/lib/aspera/agent/connect.rb +53 -16
  10. data/lib/aspera/agent/desktop.rb +52 -17
  11. data/lib/aspera/agent/direct.rb +26 -24
  12. data/lib/aspera/agent/httpgw.rb +1 -1
  13. data/lib/aspera/agent/node.rb +32 -0
  14. data/lib/aspera/agent/transferd.rb +39 -3
  15. data/lib/aspera/api/alee.rb +1 -1
  16. data/lib/aspera/api/aoc.rb +14 -16
  17. data/lib/aspera/api/cos_node.rb +1 -1
  18. data/lib/aspera/api/faspex.rb +15 -14
  19. data/lib/aspera/api/httpgw.rb +4 -4
  20. data/lib/aspera/api/node.rb +26 -23
  21. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  22. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  23. data/lib/aspera/ascmd.rb +21 -19
  24. data/lib/aspera/ascp/installation.rb +37 -38
  25. data/lib/aspera/ascp/management.rb +2 -3
  26. data/lib/aspera/cli/ascp_actions.rb +155 -0
  27. data/lib/aspera/cli/async_transfer_store.rb +81 -0
  28. data/lib/aspera/cli/bootstrapper.rb +197 -0
  29. data/lib/aspera/cli/command_registry.rb +145 -0
  30. data/lib/aspera/cli/command_spec.rb +138 -0
  31. data/lib/aspera/cli/context.rb +16 -3
  32. data/lib/aspera/cli/error.rb +16 -0
  33. data/lib/aspera/cli/extended_value.rb +2 -1
  34. data/lib/aspera/cli/formatter.rb +41 -27
  35. data/lib/aspera/cli/gem_checker.rb +65 -0
  36. data/lib/aspera/cli/hints.rb +2 -2
  37. data/lib/aspera/cli/http.rb +70 -19
  38. data/lib/aspera/cli/info.rb +2 -0
  39. data/lib/aspera/cli/mailer.rb +97 -0
  40. data/lib/aspera/cli/mcp_tool.rb +198 -0
  41. data/lib/aspera/cli/options.schema.yaml +364 -7
  42. data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
  43. data/lib/aspera/cli/plugins/alee.rb +20 -22
  44. data/lib/aspera/cli/plugins/aoc.rb +1166 -853
  45. data/lib/aspera/cli/plugins/ats.rb +200 -161
  46. data/lib/aspera/cli/plugins/base.rb +457 -100
  47. data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
  48. data/lib/aspera/cli/plugins/config.rb +428 -754
  49. data/lib/aspera/cli/plugins/console.rb +106 -64
  50. data/lib/aspera/cli/plugins/cos.rb +44 -32
  51. data/lib/aspera/cli/plugins/factory.rb +4 -4
  52. data/lib/aspera/cli/plugins/faspex.rb +294 -257
  53. data/lib/aspera/cli/plugins/faspex5.rb +585 -357
  54. data/lib/aspera/cli/plugins/faspio.rb +49 -51
  55. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  56. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  57. data/lib/aspera/cli/plugins/node.rb +998 -794
  58. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  59. data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
  60. data/lib/aspera/cli/plugins/preview.rb +180 -139
  61. data/lib/aspera/cli/plugins/server.rb +124 -79
  62. data/lib/aspera/cli/plugins/shares.rb +301 -107
  63. data/lib/aspera/cli/preset_actions.rb +139 -0
  64. data/lib/aspera/cli/preset_manager.rb +13 -12
  65. data/lib/aspera/cli/result.rb +61 -11
  66. data/lib/aspera/cli/runner.rb +275 -141
  67. data/lib/aspera/cli/secret_finder.rb +40 -0
  68. data/lib/aspera/cli/special_values.rb +1 -0
  69. data/lib/aspera/cli/sync_actions.rb +83 -59
  70. data/lib/aspera/cli/terminal_formatter.rb +1 -1
  71. data/lib/aspera/cli/transfer_actions.rb +83 -0
  72. data/lib/aspera/cli/transfer_agent.rb +116 -51
  73. data/lib/aspera/cli/transfer_progress.rb +3 -3
  74. data/lib/aspera/cli/vault_manager.rb +57 -0
  75. data/lib/aspera/cli/version.rb +1 -1
  76. data/lib/aspera/cli/wizard.rb +21 -20
  77. data/lib/aspera/command_line_builder.rb +22 -22
  78. data/lib/aspera/data_repository.rb +2 -1
  79. data/lib/aspera/dot_container.rb +4 -4
  80. data/lib/aspera/environment.rb +18 -11
  81. data/lib/aspera/exec_spec.rb +13 -0
  82. data/lib/aspera/faspex_gw.rb +1 -1
  83. data/lib/aspera/faspex_postproc.rb +4 -3
  84. data/lib/aspera/graphql.rb +35 -0
  85. data/lib/aspera/hash_ext.rb +6 -0
  86. data/lib/aspera/json_rpc/client.rb +62 -0
  87. data/lib/aspera/json_rpc/version.rb +7 -0
  88. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  89. data/lib/aspera/keychain/factory.rb +3 -3
  90. data/lib/aspera/keychain/macos_security.rb +3 -3
  91. data/lib/aspera/link_header.rb +82 -0
  92. data/lib/aspera/log.rb +17 -3
  93. data/lib/aspera/markdown.rb +85 -2
  94. data/lib/aspera/node_simulator.rb +3 -1
  95. data/lib/aspera/oauth/base.rb +5 -5
  96. data/lib/aspera/oauth/boot.rb +2 -2
  97. data/lib/aspera/oauth/factory.rb +7 -5
  98. data/lib/aspera/oauth/jwt.rb +4 -4
  99. data/lib/aspera/oauth/web.rb +2 -2
  100. data/lib/aspera/persistency_action_once.rb +8 -8
  101. data/lib/aspera/persistency_folder.rb +10 -3
  102. data/lib/aspera/preview/file_types.rb +3 -3
  103. data/lib/aspera/preview/generator.rb +3 -3
  104. data/lib/aspera/preview/utils.rb +1 -1
  105. data/lib/aspera/products/connect.rb +1 -1
  106. data/lib/aspera/products/other.rb +1 -1
  107. data/lib/aspera/products/transferd.rb +1 -1
  108. data/lib/aspera/proxy_auto_config.rb +7 -6
  109. data/lib/aspera/rest.rb +10 -36
  110. data/lib/aspera/rest_error_analyzer.rb +3 -3
  111. data/lib/aspera/rest_errors_aspera.rb +0 -10
  112. data/lib/aspera/rest_list.rb +3 -3
  113. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
  114. data/lib/aspera/schema/documentation.rb +57 -26
  115. data/lib/aspera/schema/reader.rb +59 -10
  116. data/lib/aspera/schema/registry.rb +39 -6
  117. data/lib/aspera/secret_hider.rb +7 -2
  118. data/lib/aspera/ssl.rb +4 -4
  119. data/lib/aspera/sync/conf.schema.yaml +2 -2
  120. data/lib/aspera/sync/operations.rb +10 -13
  121. data/lib/aspera/temp_file_manager.rb +1 -1
  122. data/lib/aspera/timer_limiter.rb +1 -1
  123. data/lib/aspera/transfer/faux_file.rb +24 -11
  124. data/lib/aspera/transfer/parameters.rb +23 -21
  125. data/lib/aspera/transfer/result.rb +74 -0
  126. data/lib/aspera/transfer/resumer.rb +6 -6
  127. data/lib/aspera/transfer/spec.rb +18 -1
  128. data/lib/aspera/transfer/spec.schema.yaml +12 -3
  129. data/lib/aspera/uri_reader.rb +2 -2
  130. data/lib/aspera/web_auth.rb +2 -2
  131. data/lib/aspera/web_server_simple.rb +8 -5
  132. data/lib/aspera/yaml.rb +2 -1
  133. data.tar.gz.sig +0 -0
  134. metadata +25 -3
  135. metadata.gz.sig +0 -0
  136. data/lib/aspera/json_rpc.rb +0 -52
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module Aspera
6
+ # Parse and represent an HTTP Link header as per RFC 8288.
7
+ # Inspired by the link_header gem, with the following fixes:
8
+ # - rel lookup is case-insensitive (RFC 7230: parameter names are case-insensitive)
9
+ # - uses StringScanner so commas inside <URI> are never mistaken for entry separators
10
+ # - no external dependency
11
+ class LinkHeader
12
+ # A single link entry: one URI and its attribute pairs.
13
+ class Link
14
+ attr_reader :href, :attr_pairs
15
+
16
+ def initialize(href, attr_pairs)
17
+ @href = href
18
+ @attr_pairs = attr_pairs
19
+ end
20
+
21
+ # Retrieve an attribute value by name, case-insensitively.
22
+ def [](key)
23
+ pair = @attr_pairs.detect{ |k, _v| k.casecmp?(key)}
24
+ pair&.last
25
+ end
26
+ end
27
+
28
+ attr_reader :links
29
+
30
+ def initialize(links = [])
31
+ @links = links
32
+ end
33
+
34
+ # Return the href of the first link whose +rel+ attribute matches +rel+.
35
+ # Comparison is case-insensitive per RFC 7230 s.3.2 and RFC 8288 s.3.
36
+ # Returns nil if no link with that relation exists.
37
+ # @param rel [String]
38
+ # @return [String, nil]
39
+ def find_href(rel: 'next')
40
+ @links.detect{ |link| link['rel']&.casecmp?(rel)}&.href
41
+ end
42
+
43
+ class << self
44
+ # Parse a raw Link header value into a +LinkHeader+ instance.
45
+ # Uses StringScanner so that commas inside <URI> are not treated as separators.
46
+ # @param raw [String, nil]
47
+ # @return [LinkHeader]
48
+ def parse(raw)
49
+ return new unless raw && !raw.empty?
50
+
51
+ links = []
52
+ scanner = StringScanner.new(raw)
53
+
54
+ while scanner.scan(HREF_RE)
55
+ href = scanner[1].strip
56
+ attrs = []
57
+ while scanner.scan(ATTR_RE)
58
+ key = scanner[1]
59
+ # scanner[2] = full match (token or "quoted"), scanner[3] = content inside double-quotes
60
+ value = scanner[3] || scanner[2]
61
+ attrs << [key, value]
62
+ break unless scanner.scan(SEMI_RE)
63
+ end
64
+ links << Link.new(href, attrs)
65
+ break unless scanner.scan(COMMA_RE)
66
+ end
67
+
68
+ new(links)
69
+ end
70
+
71
+ private :new
72
+ end
73
+
74
+ # RFC 2616 token: any char except separators
75
+ TOKEN_RE = /[^()<>@,;:\"\[\]?={}\s]+/ # RFC 2616 token
76
+ QUOTED_RE = /"((?:[^"\\]|\\.)*)"/ # double-quoted string with backslash escapes
77
+ HREF_RE = /\s*<([^>]*)>\s*;?\s*/ # <URI> possibly followed by ;
78
+ ATTR_RE = /(#{TOKEN_RE})\s*=\s*(#{TOKEN_RE}|#{QUOTED_RE})\s*/ # key=value or key="value"
79
+ SEMI_RE = /;\s*/ # parameter separator
80
+ COMMA_RE = /,\s*/ # link entry separator
81
+ end
82
+ end
data/lib/aspera/log.rb CHANGED
@@ -36,13 +36,16 @@ class Logger
36
36
  end
37
37
 
38
38
  class << self
39
- # Define methods for a given log level
39
+ # Define logger methods for a given log level (e.g. `debug`, `debug?`, `debug!`)
40
+ # @param str_level [String] Log level name (e.g. "DEBUG", "TRACE1")
41
+ # @return [nil]
40
42
  def make_methods(str_level)
41
43
  int_level = ::Logger.const_get(str_level.upcase)
42
44
  method_base = str_level.downcase
43
45
  define_method(method_base, ->(message = nil, &block){add(int_level, message, &block)})
44
46
  define_method("#{method_base}?", ->{level <= int_level})
45
47
  define_method("#{method_base}!", ->{self.level = int_level})
48
+ nil
46
49
  end
47
50
  end
48
51
  # Declare methods for all levels
@@ -142,9 +145,13 @@ module Aspera
142
145
  attr_reader :logger_type, :logger
143
146
  attr_accessor :dump_format
144
147
 
148
+ # Set the program name used in log output
149
+ # @param value [String] Program name
150
+ # @return [nil]
145
151
  def program_name=(value)
146
152
  @program_name = value
147
153
  self.logger_type = @logger_type
154
+ nil
148
155
  end
149
156
 
150
157
  # Set log level of underlying logger given symbol level
@@ -154,15 +161,19 @@ module Aspera
154
161
  @logger.level = Logger::Severity.const_get(new_level.to_sym.upcase)
155
162
  end
156
163
 
164
+ # Set log formatter; accepts a symbol name, a Proc, or a Logger::Formatter instance
165
+ # @param formatter [String, Proc, Logger::Formatter] Formatter to use; one of: standard, default, caller
166
+ # @return [nil]
157
167
  def formatter=(formatter)
158
168
  if formatter.is_a?(String)
159
- raise Error, "Unknown formatter #{formatter}, use one of: #{FORMATTERS.join(', ')}" unless FORMATTER_LAMBDAS.key?(formatter.to_sym)
169
+ Aspera.assert(FORMATTER_LAMBDAS.key?(formatter.to_sym), type: Error){"Unknown formatter #{formatter}, use one of: #{FORMATTERS.join(', ')}"}
160
170
  formatter = FORMATTER_LAMBDAS[formatter.to_sym]
161
171
  elsif !formatter.respond_to?(:call) && !formatter.is_a?(Logger::Formatter)
162
172
  raise Error, 'Formatter must be a String, a Logger::Formatter or a Proc'
163
173
  end
164
174
  # Update formatter with password hiding
165
175
  @logger.formatter = SecretHider.instance.log_formatter(formatter)
176
+ nil
166
177
  end
167
178
 
168
179
  def formatter
@@ -176,7 +187,9 @@ module Aspera
176
187
  Logger::SEVERITY_LABEL[@logger.level].downcase
177
188
  end
178
189
 
179
- # Change underlying logger, but keep log level (default: INFO)
190
+ # Change underlying logger output destination, keeping current log level
191
+ # @param new_log_type [Symbol] Log destination; one of LOG_TYPES (:stderr, :stdout, :syslog)
192
+ # @return [nil]
180
193
  def logger_type=(new_log_type)
181
194
  # [Integer]
182
195
  current_severity_integer = @logger&.level || ENV['AS_LOG_LEVEL']&.to_i || Logger::Severity::INFO
@@ -203,6 +216,7 @@ module Aspera
203
216
  @logger_type = new_log_type
204
217
  # add secret hider to default logger
205
218
  self.formatter = @logger.formatter
219
+ nil
206
220
  end
207
221
 
208
222
  private
@@ -9,6 +9,71 @@ module Aspera
9
9
 
10
10
  class << self
11
11
  COL_WIDTH = 80
12
+
13
+ # Convert a Markdown heading text to a GitHub-flavoured anchor.
14
+ # Rules: downcase, keep letters/digits/spaces/hyphens, replace spaces with hyphens.
15
+ # Duplicate anchors are disambiguated by appending -1, -2, … (pass a seen Hash to track).
16
+ # @param text [String] raw heading text (without leading # and spaces)
17
+ # @param seen [Hash{String=>Integer}, nil] mutable counter; pass the same Hash across a document
18
+ # @return [String] anchor slug (without leading #)
19
+ def heading_to_anchor(text, seen: nil)
20
+ slug = text
21
+ .downcase
22
+ .gsub(/[`*_]/, '') # strip inline code/bold/italic markers
23
+ .gsub(/&[a-z]+;/, '') # strip HTML entities
24
+ .gsub(/[^\w\s-]/, '') # keep word chars, spaces, hyphens
25
+ .gsub(/\s+/, '-') # spaces → hyphens
26
+ .squeeze('-') # collapse consecutive hyphens
27
+ .strip
28
+ if seen
29
+ count = seen[slug].to_i
30
+ seen[slug] = count + 1
31
+ slug = "#{slug}-#{count}" if count > 0
32
+ end
33
+ slug
34
+ end
35
+
36
+ # Extract the table of contents from a Markdown document.
37
+ # @param content [String] full Markdown source
38
+ # @return [Array<Hash>] array of { level, title, anchor }
39
+ HEADING_RE = /^(\#{1,6})\s+(.+)$/
40
+
41
+ def toc(content)
42
+ seen = {}
43
+ content.each_line.filter_map do |line|
44
+ m = line.match(HEADING_RE)
45
+ next unless m
46
+ title = m[2].strip
47
+ {level: m[1].length, title: title, anchor: heading_to_anchor(title, seen: seen)}
48
+ end
49
+ end
50
+
51
+ # Extract the content of a single section (heading + body until next heading of same/higher level).
52
+ # @param content [String] full Markdown source
53
+ # @param anchor [String] GitHub anchor slug (without #)
54
+ # @return [String, nil] the section content, or nil if not found
55
+ def extract_section(content, anchor)
56
+ seen = {}
57
+ section_level = nil
58
+ result = []
59
+ content.each_line do |line|
60
+ m = line.match(HEADING_RE)
61
+ if m
62
+ slug = heading_to_anchor(m[2].strip, seen: seen)
63
+ if section_level.nil?
64
+ # not yet found: check if this heading matches
65
+ next unless slug == anchor
66
+ section_level = m[1].length
67
+ elsif m[1].length <= section_level
68
+ # already in section: stop at same/higher level heading
69
+ break
70
+ end
71
+ end
72
+ result << line if section_level
73
+ end
74
+ result.empty? ? nil : result.join
75
+ end
76
+
12
77
  # Generate markdown from the provided 2D table
13
78
  # @param table [Array<Array<String>>] 2D array of strings
14
79
  # @return [String] markdown table
@@ -26,28 +91,46 @@ module Aspera
26
91
  end
27
92
 
28
93
  # Generate markdown list from the provided list
94
+ # @param items [Array<String>] list of items
95
+ # @return [String] markdown unordered list
29
96
  def list(items)
30
97
  items.map{ |i| "- #{i}"}.join("\n")
31
98
  end
32
99
 
100
+ # Generate a markdown heading
101
+ # @param title [String] heading text
102
+ # @param level [Integer] heading level (1–6)
103
+ # @return [String] markdown heading
33
104
  def heading(title, level: 1)
34
105
  "#{'#' * level} #{title}\n\n"
35
106
  end
36
107
 
37
- # type: NOTE CAUTION WARNING IMPORTANT TIP INFO
108
+ # Generate a GitHub-flavoured admonition block
109
+ # @param lines [Array<String>] lines of the admonition body
110
+ # @param type [String] admonition type: NOTE, CAUTION, WARNING, IMPORTANT, TIP, INFO
111
+ # @return [String] markdown admonition block
38
112
  def admonition(lines, type: 'INFO')
39
113
  "> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
40
114
  end
41
115
 
116
+ # Generate a fenced code block
117
+ # @param lines [Array<String>] lines of code
118
+ # @param type [String] language identifier for syntax highlighting
119
+ # @return [String] markdown fenced code block
42
120
  def code(lines, type: 'shell')
43
121
  "```#{type}\n#{lines.join("\n")}\n```\n\n"
44
122
  end
45
123
 
46
- # inline code
124
+ # Wrap text in inline code backticks
125
+ # @param text [String] text to wrap
126
+ # @return [String] inline code span
47
127
  def icode(text)
48
128
  "`#{text}`"
49
129
  end
50
130
 
131
+ # Wrap text in a markdown paragraph (trailing blank line)
132
+ # @param text [String] paragraph content
133
+ # @return [String] paragraph with trailing newlines
51
134
  def paragraph(text)
52
135
  "#{text}\n\n"
53
136
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/assert'
3
4
  require 'aspera/ascp/installation'
4
5
  require 'aspera/agent/direct'
5
6
  require 'aspera/log'
@@ -128,6 +129,7 @@ module Aspera
128
129
 
129
130
  # Process event from management port
130
131
  def process_event(event)
132
+ # Code duplicated on purpose, as implementation goes, blocks will be replaced
131
133
  case event['Type']
132
134
  when 'NOP' then Aspera.Log.debug{"event not managed: #{event['Type']}"}
133
135
  # rubocop:disable Lint/DuplicateBranch
@@ -174,7 +176,7 @@ module Aspera
174
176
  require 'time'
175
177
 
176
178
  def folder_to_structure(folder_path)
177
- raise "Path does not exist or is not a directory: #{folder_path}" unless Dir.exist?(folder_path)
179
+ Aspera.assert(Dir.exist?(folder_path)){"Path does not exist or is not a directory: #{folder_path}"}
178
180
 
179
181
  # Build self structure
180
182
  folder_stat = File.stat(folder_path)
@@ -48,8 +48,8 @@ module Aspera
48
48
  attr_reader :path_token
49
49
 
50
50
  # Helper method to create token as per RFC
51
- # @return [HTTPResponse]
52
- # @raise RestError if not 2XX code
51
+ # @return [Net::HTTPResponse] raw HTTP response with token
52
+ # @raise [RestCallError] if not 2XX code
53
53
  def create_token_call(creation_params)
54
54
  Log.log.debug{'Generating a new token'.bg_green}
55
55
  return @api.create(@path_token, nil, query: creation_params, ret: :resp) if @use_query
@@ -73,8 +73,8 @@ module Aspera
73
73
  # get an OAuth v2 token (generated, cached, refreshed)
74
74
  # call token() to get a token.
75
75
  # if a token is expired (api returns 4xx), call again token(refresh: true)
76
- # @param cache set to false to disable cache
77
- # @param refresh set to true to force refresh or re-generation (if previous failed)
76
+ # @param cache [Boolean] set to false to disable cache
77
+ # @param refresh [Boolean] set to true to force refresh or re-generation (if previous failed)
78
78
  def token(cache: true, refresh: false)
79
79
  # get token info from cache (or nil), decoded with date and expiration status
80
80
  token_info = Factory.instance.get_token_info(@token_cache_id) if cache
@@ -106,7 +106,7 @@ module Aspera
106
106
  json_data = http.body
107
107
  token_data = JSON.parse(json_data)
108
108
  Factory.instance.persist_mgr.put(@token_cache_id, json_data)
109
- rescue => e
109
+ rescue StandardError => e
110
110
  # Refresh token can fail.
111
111
  Log.log.warn{"Refresh failed: #{e}"}
112
112
  end
@@ -8,11 +8,11 @@ module Aspera
8
8
  module OAuth
9
9
  # Token provider bootstrapped from an existing cookie (e.g. AoC browser cookie).
10
10
  # Injects the bearer token and optional refresh token directly into the cache.
11
- # Never generates a new token from scratch raises if cache+refresh are both exhausted.
11
+ # Never generates a new token from scratch - raises if cache+refresh are both exhausted.
12
12
  class Boot < Base
13
13
  # @param cookie [String, nil] Raw cookie string (--password), nil to rely on existing cache
14
14
  # @param username [String, nil] Expected subject; if provided, must match token's `sub` claim
15
- # @param **base_params Forwarded to Base (base_url:, params: {client_id:, scope:}, etc.)
15
+ # @param **base_params [Hash] Forwarded to Base (base_url:, params: {client_id:, scope:}, etc.)
16
16
  def initialize(cookie: nil, username: nil, **base_params)
17
17
  if cookie.nil?
18
18
  # No cookie: rely on existing cache, identified by username if provided
@@ -108,7 +108,7 @@ module Aspera
108
108
  end
109
109
 
110
110
  # Delete all existing tokens in cache
111
- # @return [void]
111
+ # @return [Array<String>] List of deleted file paths
112
112
  def flush_tokens
113
113
  persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN)
114
114
  end
@@ -119,7 +119,7 @@ module Aspera
119
119
  data = persist_mgr.current_items(PERSIST_CATEGORY_TOKEN)
120
120
  data.each.map do |k, v|
121
121
  info = {id: k}
122
- info.merge!(JSON.parse(v)) rescue nil
122
+ begin; info.merge!(JSON.parse(v)); rescue StandardError; nil; end
123
123
  d = decode_token(info.delete(TOKEN_FIELD))
124
124
  info.merge(d) if d
125
125
  info
@@ -155,9 +155,10 @@ module Aspera
155
155
 
156
156
  # Register a bearer token decoder for inspecting token properties
157
157
  # @param method [Proc] The decoder lambda/proc to register
158
- # @return [void]
158
+ # @return [nil]
159
159
  def register_decoder(method)
160
160
  @decoders.push(method)
161
+ nil
161
162
  end
162
163
 
163
164
  # Decode a token using all registered decoders
@@ -165,7 +166,7 @@ module Aspera
165
166
  # @return [Hash, nil] Decoded token data or nil if no decoder succeeded
166
167
  def decode_token(token)
167
168
  @decoders.each do |decoder|
168
- result = decoder.call(token) rescue nil
169
+ result = begin; decoder.call(token); rescue StandardError; nil; end
169
170
  return result unless result.nil?
170
171
  end
171
172
  return
@@ -173,12 +174,13 @@ module Aspera
173
174
 
174
175
  # Register a token creation method
175
176
  # @param creator_class [Class] The token creator class to register
176
- # @return [void]
177
+ # @return [nil]
177
178
  def register_token_creator(creator_class)
178
179
  Aspera.assert_type(creator_class, Class)
179
180
  id = Factory.class_to_id(creator_class)
180
181
  Log.log.debug{"registering creator for #{id}"}
181
182
  @token_type_classes[id] = creator_class
183
+ nil
182
184
  end
183
185
 
184
186
  # Create a token creator instance for the specified grant method
@@ -26,9 +26,9 @@ module Aspera
26
26
  end
27
27
  DEFAULT_PRIV_KEY_LENGTH = 4096
28
28
  GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
29
- # @param private_key_obj private key object
30
- # @param payload payload to be included in the JWT
31
- # @param headers headers to be included in the JWT
29
+ # @param private_key_obj [OpenSSL::PKey::RSA] private key object
30
+ # @param payload [Hash] payload to be included in the JWT
31
+ # @param headers [Hash] headers to be included in the JWT
32
32
  def initialize(
33
33
  private_key_obj:,
34
34
  payload:,
@@ -49,7 +49,7 @@ module Aspera
49
49
 
50
50
  def create_token
51
51
  require 'jwt'
52
- seconds_since_epoch = Time.new.to_i
52
+ seconds_since_epoch = Time.now.to_i
53
53
  Log.log.debug{"seconds_since_epoch=#{seconds_since_epoch}"}
54
54
  jwt_payload = {
55
55
  exp: seconds_since_epoch + OAuth::Factory.instance.parameters[:jwt_expiry_offset_sec], # expiration time
@@ -11,8 +11,8 @@ module Aspera
11
11
  class << self
12
12
  attr_accessor :additional_info
13
13
  end
14
- # @param redirect_uri url to receive the code after auth (to be exchanged for token)
15
- # @param path_authorize path to login page on web app
14
+ # @param redirect_uri [String] url to receive the code after auth (to be exchanged for token)
15
+ # @param path_authorize [String] path to login page on web app
16
16
  def initialize(
17
17
  redirect_uri:,
18
18
  path_authorize: 'authorize',
@@ -14,13 +14,13 @@ module Aspera
14
14
  MANAGER_METHODS = %i[get put delete]
15
15
  private_constant :DELETE_DEFAULT, :PARSE_DEFAULT, :FORMAT_DEFAULT, :MERGE_DEFAULT, :MANAGER_METHODS
16
16
 
17
- # @param :manager Mandatory Database
18
- # @param :data Mandatory object to persist, must be same object from begin to end (assume array by default)
19
- # @param :id Mandatory identifiers
20
- # @param :delete Optional delete persistency condition
21
- # @param :parse Optional parse method (default to JSON)
22
- # @param :format Optional dump method (default to JSON)
23
- # @param :merge Optional merge data from file to current data
17
+ # @param manager [Object] Mandatory Database (must respond to get/put/delete)
18
+ # @param data [Object] Mandatory object to persist, must be same object from begin to end (assume array by default)
19
+ # @param id [String] Mandatory identifier
20
+ # @param delete [Proc] Optional delete persistency condition
21
+ # @param parse [Proc] Optional parse method (default to JSON)
22
+ # @param format [Proc] Optional dump method (default to JSON)
23
+ # @param merge [Proc] Optional merge data from file to current data
24
24
  def initialize(manager:, data:, id:, delete: DELETE_DEFAULT, parse: PARSE_DEFAULT, format: FORMAT_DEFAULT, merge: MERGE_DEFAULT)
25
25
  Aspera.assert(MANAGER_METHODS.all?{ |i| manager.respond_to?(i)}){"Manager must answer to #{MANAGER_METHODS}"}
26
26
  Aspera.assert(!data.nil?, 'data must not be nil')
@@ -48,7 +48,7 @@ module Aspera
48
48
  end
49
49
  end
50
50
 
51
- # @return internal persisted object, in order to modify its content
51
+ # @return [Array, Hash] internal persisted object, in order to modify its content
52
52
  def data
53
53
  return @persisted_object
54
54
  end
@@ -19,7 +19,8 @@ module Aspera
19
19
  end
20
20
 
21
21
  # Get value of persisted item
22
- # @return [String,nil] Value of persisted id
22
+ # @param object_id [String] Identifier of persisted item
23
+ # @return [String, nil] Persisted value, or nil if not found
23
24
  def get(object_id)
24
25
  Log.log.debug{"persistency get: #{object_id}"}
25
26
  if @cache.key?(object_id)
@@ -59,7 +60,10 @@ module Aspera
59
60
  @cache.delete(object_id)
60
61
  end
61
62
 
62
- # Delete persisted items
63
+ # Delete persisted items matching the given category, optionally older than a given age
64
+ # @param persist_category [String] Prefix used to identify files belonging to the category
65
+ # @param max_age_seconds [Integer, nil] If set, only delete files older than this many seconds
66
+ # @return [Array<String>] List of deleted file paths
63
67
  def garbage_collect(persist_category, max_age_seconds = nil)
64
68
  garbage_files = current_files(persist_category)
65
69
  if !max_age_seconds.nil?
@@ -74,6 +78,9 @@ module Aspera
74
78
  return garbage_files
75
79
  end
76
80
 
81
+ # List all persisted files matching the given category prefix
82
+ # @param persist_category [String] Prefix identifying the category
83
+ # @return [Array<String>] List of matching file paths
77
84
  def current_files(persist_category)
78
85
  Dir[File.join(@folder, "#{persist_category}*#{FILE_SUFFIX}")]
79
86
  end
@@ -84,7 +91,7 @@ module Aspera
84
91
 
85
92
  private
86
93
 
87
- # @param object_id String or Array
94
+ # @param object_id [String, Array] identifier or list of identifiers
88
95
  def id_to_filepath(object_id)
89
96
  Aspera.assert_type(object_id, String)
90
97
  FileUtils.mkdir_p(@folder)
@@ -78,7 +78,7 @@ module Aspera
78
78
 
79
79
  # @param filepath [String] Full path to file
80
80
  # @param mimetype [String] MIME typre provided by node API
81
- # @return file type, one of enum CONVERSION_TYPES
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
84
  Log.log.debug{"conversion_type(#{filepath},mime=#{mimetype},magic=#{@use_mimemagic})"}
@@ -86,9 +86,9 @@ module Aspera
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)
88
88
  mimetype = 'text/plain' if mimetype.eql?(TYPE_NOT_FOUND) && ascii_text_file?(filepath)
89
- raise "no MIME type found for #{File.basename(filepath)}" if mimetype.eql?(TYPE_NOT_FOUND)
89
+ Aspera.assert(!mimetype.eql?(TYPE_NOT_FOUND)){"no MIME type found for #{File.basename(filepath)}"}
90
90
  conversion_type = mime_to_type(mimetype)
91
- raise "no conversion type found for #{File.basename(filepath)}" if conversion_type.nil?
91
+ Aspera.assert(!conversion_type.nil?){"no conversion type found for #{File.basename(filepath)}"}
92
92
  Log.log.trace1{"conversion_type(#{File.basename(filepath)}): #{conversion_type.class.name} [#{conversion_type}]"}
93
93
  return conversion_type
94
94
  end
@@ -27,10 +27,10 @@ module Aspera
27
27
  # Node API MIME types are from: http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types.
28
28
  # The resulting preview file type is taken from destination file extension.
29
29
  # Conversion methods are provided by private methods: convert_<conversion_type>_to_<preview_format>.
30
- # -> conversion_type is one of FileTypes::CONVERSION_TYPES.
31
- # -> preview_format is one of Generator::PREVIEW_FORMATS.
30
+ # conversion_type: one of FileTypes::CONVERSION_TYPES.
31
+ # preview_format: one of Generator::PREVIEW_FORMATS.
32
32
  # The conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>.
33
- # -> conversion method is one of Generator::VIDEO_CONVERSION_METHODS.
33
+ # conversion method: one of Generator::VIDEO_CONVERSION_METHODS.
34
34
  # @param src [String] Source file path.
35
35
  # @param dst [String] Destination file path.
36
36
  # @param options [Options] All conversion options.
@@ -64,7 +64,7 @@ module Aspera
64
64
  silent_execute(:ffmpeg, *gl_p, *in_p, '-i', in_f, *out_p, out_f)
65
65
  end
66
66
 
67
- # @return Float in seconds
67
+ # @return [Float] duration in seconds
68
68
  def video_get_duration(input_file)
69
69
  return execute(
70
70
  :ffprobe,
@@ -65,7 +65,7 @@ module Aspera
65
65
  Log.dump(:javascript, connect_versions_javascript)
66
66
  # get javascript object only
67
67
  found = connect_versions_javascript.match(/^.*? = (.*);/)
68
- raise Cli::Error, 'Problem when getting connect versions from internet' if found.nil?
68
+ Aspera.assert(!found.nil?, type: Cli::Error){'Problem when getting connect versions from internet'}
69
69
  all_data = JSON.parse(found[1])
70
70
  @connect_versions = all_data['entries']
71
71
  end
@@ -57,7 +57,7 @@ module Aspera
57
57
  # Find installed products and provide paths for it.
58
58
  # @param scan_locations [Array] Array of Hash with keys: expected, app_root, sub_bin, ascp_path, name, version
59
59
  # @return [Array] of products found, with filled missing fields
60
- # @raise Exception if no installed product found
60
+ # @raise [RuntimeError] if no installed product found
61
61
  def find(scan_locations)
62
62
  product_names = []
63
63
  found = scan_locations.select do |item|
@@ -27,7 +27,7 @@ module Aspera
27
27
  sdk_directory
28
28
  end
29
29
 
30
- # @return the path to folder where SDK is or should be installed
30
+ # @return [String] the path to folder where SDK is or should be installed
31
31
  def sdk_directory
32
32
  Aspera.assert(!@sdk_dir.nil?, 'SDK path was not initialized')
33
33
  @sdk_dir
@@ -44,7 +44,7 @@ module Aspera
44
44
  context_ip = r_addr.to_s if r_addr.is_a?(Resolv::IPv4)
45
45
  end
46
46
  end
47
- raise "DNS name not found: #{context_host}" if context_ip.nil?
47
+ Aspera.assert(!context_ip.nil?){"DNS name not found: #{context_host}"}
48
48
  # NOTE: Javascript code here with string inclusions
49
49
  javascript = <<END_OF_JAVASCRIPT
50
50
  function dnsResolve(host) {
@@ -63,7 +63,7 @@ END_OF_JAVASCRIPT
63
63
 
64
64
  attr_writer :proxy_user, :proxy_pass
65
65
 
66
- # @param proxy_auto_config the proxy auto config script to be evaluated
66
+ # @param proxy_auto_config [String] the proxy auto config script to be evaluated
67
67
  def initialize(proxy_auto_config)
68
68
  # user provided javascript with FindProxyForURL function
69
69
  @proxy_auto_config = proxy_auto_config
@@ -79,7 +79,7 @@ END_OF_JAVASCRIPT
79
79
  end
80
80
 
81
81
  # execute proxy auto config script for the given URL : https://en.wikipedia.org/wiki/Proxy_auto-config
82
- # @return either nil, or a String formatted following PAC standard
82
+ # @return [String, nil] either nil, or a String formatted following PAC standard
83
83
  def find_proxy_for_url(service_url)
84
84
  uri = URI.parse(service_url)
85
85
  simple_url = "#{uri.scheme}://#{uri.host}"
@@ -99,7 +99,7 @@ END_OF_JAVASCRIPT
99
99
  end
100
100
 
101
101
  # used to replace URI::Generic.find_proxy
102
- # @return Array of URI, possibly empty
102
+ # @return [Array<URI>] list of URIs, possibly empty
103
103
  def get_proxies(service_url)
104
104
  # prepare result
105
105
  uri_list = []
@@ -114,7 +114,8 @@ END_OF_JAVASCRIPT
114
114
  proxy_list_str.split(';').each do |item|
115
115
  # strip and split by space
116
116
  parts = item.strip.split
117
- case parts.shift
117
+ proxy_type = parts.shift
118
+ case proxy_type
118
119
  when 'DIRECT'
119
120
  Aspera.assert(parts.empty?, 'DIRECT has no param')
120
121
  Log.log.debug('ignoring proxy DIRECT')
@@ -136,7 +137,7 @@ END_OF_JAVASCRIPT
136
137
  rescue StandardError => e
137
138
  Log.log.warn{"PAC: cannot parse #{addr_port} #{e}"}
138
139
  end
139
- else Log.log.warn{"PAC: ignoring proxy type #{parts.first}: not supported"}
140
+ else Log.log.warn{"PAC: ignoring proxy type #{proxy_type}: not supported"}
140
141
  end
141
142
  end
142
143
  Log.log.debug{"Proxies: #{uri_list}"}