aspera-cli 4.27.3 → 4.27.5
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 +118 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +1 -0
- data/docs/README.md +224 -42
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +0 -4
- data/lib/aspera/agent/desktop.rb +0 -4
- data/lib/aspera/agent/direct.rb +49 -21
- data/lib/aspera/agent/node.rb +6 -5
- data/lib/aspera/agent/transferd.rb +2 -2
- data/lib/aspera/api/faspex.rb +2 -0
- data/lib/aspera/api/httpgw.rb +1 -2
- data/lib/aspera/api/node.rb +3 -3
- data/lib/aspera/ascp/installation.rb +1 -1
- data/lib/aspera/cli/async_transfer_store.rb +10 -9
- data/lib/aspera/cli/bootstrapper.rb +3 -1
- data/lib/aspera/cli/command_registry.rb +69 -7
- data/lib/aspera/cli/command_spec.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +4 -3
- data/lib/aspera/cli/formatter.rb +10 -8
- data/lib/aspera/cli/http.rb +8 -20
- data/lib/aspera/cli/option_types.rb +3 -1
- data/lib/aspera/cli/option_value.rb +16 -19
- data/lib/aspera/cli/options.schema.yaml +86 -10
- data/lib/aspera/cli/parser.rb +30 -18
- data/lib/aspera/cli/plugins/aoc.rb +133 -156
- data/lib/aspera/cli/plugins/ats.rb +1 -7
- data/lib/aspera/cli/plugins/base.rb +40 -34
- data/lib/aspera/cli/plugins/config.rb +24 -11
- data/lib/aspera/cli/plugins/console.rb +1 -1
- data/lib/aspera/cli/plugins/faspex5.rb +30 -11
- data/lib/aspera/cli/plugins/faspio.rb +1 -1
- data/lib/aspera/cli/plugins/node.rb +26 -19
- data/lib/aspera/cli/plugins/orchestrator.rb +73 -44
- data/lib/aspera/cli/plugins/preview.rb +21 -19
- data/lib/aspera/cli/plugins/server.rb +3 -4
- data/lib/aspera/cli/plugins/shares.rb +21 -24
- data/lib/aspera/cli/preset_actions.rb +28 -18
- data/lib/aspera/cli/preset_manager.rb +34 -19
- data/lib/aspera/cli/prompt.rb +2 -1
- data/lib/aspera/cli/result.rb +33 -22
- data/lib/aspera/cli/runner.rb +1 -5
- data/lib/aspera/cli/sync_actions.rb +17 -16
- data/lib/aspera/cli/transfer_actions.rb +14 -3
- data/lib/aspera/cli/transfer_agent.rb +6 -4
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +1 -1
- data/lib/aspera/coverage.rb +0 -1
- data/lib/aspera/environment.rb +29 -5
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/log.rb +25 -2
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +2 -1
- data/lib/aspera/oauth/factory.rb +6 -3
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +3 -4
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/generator.rb +4 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- data/lib/aspera/rest/aspera_errors.rb +12 -0
- data/lib/aspera/rest/client.rb +28 -19
- data/lib/aspera/rest/list.rb +14 -8
- data/lib/aspera/rest/parameters.rb +2 -2
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +6 -1
- data.tar.gz.sig +0 -0
- metadata +5 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/node_simulator.rb +0 -345
- data/lib/aspera/preview/options.rb +0 -45
data/lib/aspera/oauth/base.rb
CHANGED
|
@@ -83,7 +83,7 @@ module Aspera
|
|
|
83
83
|
# @return [String] The bearer token
|
|
84
84
|
def token(cache: true, refresh: false)
|
|
85
85
|
# Get token info from cache (or nil), decoded with date and expiration status
|
|
86
|
-
token_info = Factory.instance.get_token_info(@token_cache_id) if cache
|
|
86
|
+
token_info = Factory.instance.get_token_info(@token_cache_id, token_field: @token_field) if cache
|
|
87
87
|
token_data = nil
|
|
88
88
|
unless token_info.nil?
|
|
89
89
|
token_data = token_info[:data]
|
|
@@ -130,6 +130,7 @@ module Aspera
|
|
|
130
130
|
json_data = create_token.body
|
|
131
131
|
token_data = JSON.parse(json_data)
|
|
132
132
|
Factory.instance.persist_mgr.put(@token_cache_id, json_data)
|
|
133
|
+
Factory.instance.get_token_info(@token_cache_id, token_field: @token_field)
|
|
133
134
|
end
|
|
134
135
|
Aspera.assert(token_data.key?(@token_field)) { "API error: No such field in answer: #{@token_field}" } unless token_data.nil?
|
|
135
136
|
# Ok we shall have a token here
|
data/lib/aspera/oauth/factory.rb
CHANGED
|
@@ -121,7 +121,8 @@ module Aspera
|
|
|
121
121
|
data.each.map do |k, v|
|
|
122
122
|
info = {id: k}
|
|
123
123
|
begin; info.merge!(JSON.parse(v)); rescue StandardError; nil; end
|
|
124
|
-
|
|
124
|
+
token_val = info.delete(TOKEN_FIELD) || info.delete('token')
|
|
125
|
+
d = decode_token(token_val)
|
|
125
126
|
info.merge(d) if d
|
|
126
127
|
info
|
|
127
128
|
end
|
|
@@ -129,13 +130,15 @@ module Aspera
|
|
|
129
130
|
|
|
130
131
|
# Get token information from cache
|
|
131
132
|
# @param id [String] identifier of token
|
|
133
|
+
# @param token_field [String, nil] Field containing the token, or nil to auto-detect
|
|
132
134
|
# @return [Hash] token internal information , including Date object for `expiration_date`
|
|
133
|
-
def get_token_info(id)
|
|
135
|
+
def get_token_info(id, token_field: nil)
|
|
134
136
|
token_raw_string = persist_mgr.get(id)
|
|
135
137
|
return if token_raw_string.nil?
|
|
136
138
|
token_data = JSON.parse(token_raw_string)
|
|
137
139
|
Aspera.assert_type(token_data, Hash)
|
|
138
|
-
|
|
140
|
+
token_value = token_data[token_field] || token_data[TOKEN_FIELD] || token_data['token']
|
|
141
|
+
decoded_token = decode_token(token_value)
|
|
139
142
|
info = {data: token_data}
|
|
140
143
|
if decoded_token.is_a?(Hash)
|
|
141
144
|
info[:decoded] = decoded_token
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/oauth/base'
|
|
4
|
+
require 'digest'
|
|
5
|
+
|
|
6
|
+
module Aspera
|
|
7
|
+
module OAuth
|
|
8
|
+
# Token creator using JSON credentials (e.g., username/password or apikey) sent to a login endpoint
|
|
9
|
+
class JsonCredentials < Base
|
|
10
|
+
# @param json [Hash] Body parameters to send as JSON (e.g. {username:, password:} or {apikey:})
|
|
11
|
+
# @param generic_params [Hash] Generic parameters for OAuth::Base
|
|
12
|
+
def initialize(
|
|
13
|
+
json:,
|
|
14
|
+
**generic_params
|
|
15
|
+
)
|
|
16
|
+
cache_id = json[:username] || Digest::SHA256.hexdigest(json[:apikey] || json.to_s)[0..23]
|
|
17
|
+
super(**generic_params, cache_ids: [cache_id])
|
|
18
|
+
@body = json
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_token
|
|
22
|
+
api.call(
|
|
23
|
+
operation: 'POST',
|
|
24
|
+
subpath: path_token,
|
|
25
|
+
content_type: Mime::JSON,
|
|
26
|
+
body: @body,
|
|
27
|
+
headers: {'Accept' => Mime::JSON},
|
|
28
|
+
ret: :resp
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
Factory.instance.register_token_creator(JsonCredentials)
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/aspera/oauth/jwt.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/oauth/base'
|
|
4
|
+
require 'aspera/environment'
|
|
4
5
|
require 'aspera/assert'
|
|
5
6
|
require 'securerandom'
|
|
6
7
|
require 'openssl'
|
|
@@ -17,10 +18,8 @@ module Aspera
|
|
|
17
18
|
class << self
|
|
18
19
|
def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
|
|
19
20
|
priv_key = OpenSSL::PKey::RSA.new(length)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Environment.restrict_file_access(path)
|
|
23
|
-
Environment.restrict_file_access("#{path}.pub")
|
|
21
|
+
Environment.write_file_restricted(path, force: true) { priv_key.to_s }
|
|
22
|
+
Environment.write_file_restricted("#{path}.pub", force: true) { priv_key.public_key.to_s }
|
|
24
23
|
nil
|
|
25
24
|
end
|
|
26
25
|
end
|
data/lib/aspera/oauth.rb
CHANGED
|
@@ -44,9 +44,7 @@ module Aspera
|
|
|
44
44
|
Aspera.assert_type(value, String)
|
|
45
45
|
persist_filepath = id_to_filepath(object_id)
|
|
46
46
|
Log.log.debug { "persistency saving: #{persist_filepath}" }
|
|
47
|
-
|
|
48
|
-
File.write(persist_filepath, value)
|
|
49
|
-
Environment.restrict_file_access(persist_filepath)
|
|
47
|
+
Environment.write_file_restricted(persist_filepath, force: true) { value }
|
|
50
48
|
@cache[object_id] = value
|
|
51
49
|
nil
|
|
52
50
|
end
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# spellchecker:ignore soffice pauseframes libx264 trunc bufsize muxer apng libmp3lame maxrate posterize movflags faststart
|
|
5
5
|
# spellchecker:ignore palettegen paletteuse pointsize bordercolor repage lanczos unoconv optipng reencode conv transframes
|
|
6
6
|
|
|
7
|
-
require 'aspera/preview/options'
|
|
8
7
|
require 'aspera/preview/utils'
|
|
9
8
|
require 'aspera/preview/file_types'
|
|
9
|
+
require 'aspera/schema/registry'
|
|
10
10
|
require 'aspera/log'
|
|
11
11
|
require 'aspera/assert'
|
|
12
12
|
|
|
@@ -20,6 +20,9 @@ module Aspera
|
|
|
20
20
|
# List of valid ffmpeg option keys for reencode configuration.
|
|
21
21
|
FFMPEG_OPTIONS_LIST = %w[in out].freeze
|
|
22
22
|
|
|
23
|
+
# Generator options, members defined in `options.schema.yaml`.
|
|
24
|
+
Options = Struct.new(*Schema::Registry.instance.reader(Schema::Registry::PREVIEW_OPTIONS).current['properties'].keys.map(&:to_sym))
|
|
25
|
+
|
|
23
26
|
# CLI needs to know conversion type to know if need skip it.
|
|
24
27
|
# One of CONVERSION_TYPES.
|
|
25
28
|
attr_reader :conversion_type, :destination
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
$schema: https://json-schema.org/draft/2020-12/schema
|
|
2
|
+
$id: https://github.com/IBM/aspera-cli/tree/main/lib/aspera/preview/options.schema.yaml
|
|
3
|
+
title: Preview generator options
|
|
4
|
+
description: >-
|
|
5
|
+
Options of the preview generator: tools and parameters used to generate PNG thumbnails and MP4 video previews.
|
|
6
|
+
$comment: >-
|
|
7
|
+
Used to declare the options of the `preview` plugin: values of `enum` properties are `Symbol`.
|
|
8
|
+
Scaling uses `ffmpeg` scale filter arguments, see: https://trac.ffmpeg.org/wiki/Scaling
|
|
9
|
+
(`iw`/`ih`: input width/height, `-x`: keep aspect ratio, with a value multiple of `x`).
|
|
10
|
+
type: object
|
|
11
|
+
additionalProperties: false
|
|
12
|
+
properties:
|
|
13
|
+
office_conversion:
|
|
14
|
+
description: 'PNG thumbnail of office document: conversion tool.'
|
|
15
|
+
type: string
|
|
16
|
+
enum: [soffice, unoconv]
|
|
17
|
+
default: soffice
|
|
18
|
+
max_size:
|
|
19
|
+
description: 'Maximum size (in bytes) of a preview file: a warning is logged if exceeded.'
|
|
20
|
+
type: integer
|
|
21
|
+
minimum: 1
|
|
22
|
+
default: 16777216
|
|
23
|
+
thumb_vid_scale:
|
|
24
|
+
description: 'PNG thumbnail of video: frame size, as `ffmpeg` scale filter argument.'
|
|
25
|
+
type: string
|
|
26
|
+
default: '-1:min(ih,100)'
|
|
27
|
+
thumb_vid_fraction:
|
|
28
|
+
description: 'PNG thumbnail of video (`fixed`): position of the snapshot, as a fraction of the video duration.'
|
|
29
|
+
type: number
|
|
30
|
+
minimum: 0
|
|
31
|
+
maximum: 1
|
|
32
|
+
default: 0.1
|
|
33
|
+
thumb_img_size:
|
|
34
|
+
description: 'PNG thumbnail of image, PDF, office document or text: size (in pixels).'
|
|
35
|
+
type: integer
|
|
36
|
+
minimum: 1
|
|
37
|
+
default: 800
|
|
38
|
+
thumb_text_font:
|
|
39
|
+
description: 'PNG thumbnail of text: font name, as listed by `magick identify -list font`.'
|
|
40
|
+
type: string
|
|
41
|
+
default: Courier
|
|
42
|
+
video_conversion:
|
|
43
|
+
description: >-
|
|
44
|
+
MP4 video preview: generation method.
|
|
45
|
+
`reencode`: re-encode the beginning of the video,
|
|
46
|
+
`blend`: key frames with transitions,
|
|
47
|
+
`clips`: concatenation of short clips.
|
|
48
|
+
type: string
|
|
49
|
+
enum: [reencode, blend, clips]
|
|
50
|
+
default: reencode
|
|
51
|
+
video_png_conv:
|
|
52
|
+
description: >-
|
|
53
|
+
PNG thumbnail of video: generation method.
|
|
54
|
+
`fixed`: single frame,
|
|
55
|
+
`animated`: animated PNG.
|
|
56
|
+
type: string
|
|
57
|
+
enum: [fixed, animated]
|
|
58
|
+
default: fixed
|
|
59
|
+
video_scale:
|
|
60
|
+
description: 'MP4 video preview: frame size, as `ffmpeg` scale filter argument.'
|
|
61
|
+
type: string
|
|
62
|
+
default: 'min(iw,360):-2'
|
|
63
|
+
video_start_sec:
|
|
64
|
+
description: 'Video: start offset (in seconds) of MP4 video preview and of `animated` PNG thumbnail.'
|
|
65
|
+
type: integer
|
|
66
|
+
minimum: 0
|
|
67
|
+
default: 10
|
|
68
|
+
video_codec:
|
|
69
|
+
description: >-
|
|
70
|
+
MP4 video preview (`reencode`): `ffmpeg` video codec, e.g. `libx264`, `h264_videotoolbox`, `h264_nvenc`.
|
|
71
|
+
Default: first available H.264 encoder.
|
|
72
|
+
type: string
|
|
73
|
+
reencode_ffmpeg:
|
|
74
|
+
description: >-
|
|
75
|
+
MP4 video preview (`reencode`): `ffmpeg` options replacing the default ones.
|
|
76
|
+
type: object
|
|
77
|
+
additionalProperties: false
|
|
78
|
+
properties:
|
|
79
|
+
in:
|
|
80
|
+
description: Input options.
|
|
81
|
+
type: array
|
|
82
|
+
items:
|
|
83
|
+
type: [string, number]
|
|
84
|
+
out:
|
|
85
|
+
description: Output options.
|
|
86
|
+
type: array
|
|
87
|
+
items:
|
|
88
|
+
type: [string, number]
|
|
89
|
+
default: {}
|
|
90
|
+
blend_keyframes:
|
|
91
|
+
description: 'MP4 video preview (`blend`): number of key frames.'
|
|
92
|
+
type: integer
|
|
93
|
+
minimum: 1
|
|
94
|
+
default: 30
|
|
95
|
+
blend_pauseframes:
|
|
96
|
+
description: 'MP4 video preview (`blend`): number of pause frames (repetitions of each key frame).'
|
|
97
|
+
type: integer
|
|
98
|
+
minimum: 0
|
|
99
|
+
default: 3
|
|
100
|
+
blend_transframes:
|
|
101
|
+
description: 'MP4 video preview (`blend`): number of transition frames between key frames.'
|
|
102
|
+
type: integer
|
|
103
|
+
minimum: 0
|
|
104
|
+
default: 5
|
|
105
|
+
blend_fps:
|
|
106
|
+
description: 'MP4 video preview (`blend`): frames per second.'
|
|
107
|
+
type: integer
|
|
108
|
+
minimum: 1
|
|
109
|
+
default: 15
|
|
110
|
+
clips_count:
|
|
111
|
+
description: 'MP4 video preview (`clips`): number of clips.'
|
|
112
|
+
type: integer
|
|
113
|
+
minimum: 1
|
|
114
|
+
default: 5
|
|
115
|
+
clips_length:
|
|
116
|
+
description: 'Video: length (in seconds) of each clip of MP4 video preview (`clips`), and of `animated` PNG thumbnail.'
|
|
117
|
+
type: integer
|
|
118
|
+
minimum: 1
|
|
119
|
+
default: 5
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'aspera/rest/error_analyzer'
|
|
4
4
|
require 'aspera/log'
|
|
5
|
+
require 'xmlsimple'
|
|
5
6
|
|
|
6
7
|
module Aspera
|
|
7
8
|
module Rest
|
|
@@ -45,6 +46,17 @@ module Aspera
|
|
|
45
46
|
ErrorAnalyzer.instance.add_handler('Orchestrator') do |type, context|
|
|
46
47
|
next if context[:response].code.start_with?('2')
|
|
47
48
|
data = context[:data]
|
|
49
|
+
if data.is_a?(String) && data.start_with?('<?xml')
|
|
50
|
+
parsed = XmlSimple.xml_in(data, {'ForceArray' => false}) rescue nil
|
|
51
|
+
if parsed.is_a?(Hash) && parsed['content'].is_a?(String)
|
|
52
|
+
ErrorAnalyzer.add_error(context, type, parsed['content'].gsub(/\A"|"\z/, ''))
|
|
53
|
+
parsed.each do |k, v|
|
|
54
|
+
next if k.eql?('content')
|
|
55
|
+
ErrorAnalyzer.add_error(context, "#{type}(sub)", "#{k}: #{v}") if [String, Integer].include?(v.class)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
next
|
|
59
|
+
end
|
|
48
60
|
next unless data.is_a?(Hash)
|
|
49
61
|
work_order = data['work_order']
|
|
50
62
|
next unless work_order.is_a?(Hash)
|
data/lib/aspera/rest/client.rb
CHANGED
|
@@ -401,31 +401,39 @@ module Aspera
|
|
|
401
401
|
Log.log.debug { "saving to: #{target_file}" }
|
|
402
402
|
written_size = 0
|
|
403
403
|
session_id = SecureRandom.uuid.freeze
|
|
404
|
-
Parameters.instance.progress_bar
|
|
405
|
-
|
|
404
|
+
progress = Parameters.instance.progress_bar
|
|
405
|
+
progress&.event(:session_start, session_id: session_id)
|
|
406
|
+
progress&.event(:session_size, session_id: session_id, info: total_size) if total_size
|
|
406
407
|
limiter = TimerLimiter.new(0.5)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
written_size += fragment.length
|
|
412
|
-
Parameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
|
413
|
-
end
|
|
414
|
-
else
|
|
415
|
-
# file path: download to partial name first, then rename atomically
|
|
416
|
-
target_file_tmp = "#{target_file}#{Parameters.instance.download_partial_suffix}"
|
|
417
|
-
FileUtils.mkdir_p(File.dirname(target_file_tmp))
|
|
418
|
-
File.open(target_file_tmp, 'wb') do |file|
|
|
408
|
+
success = false
|
|
409
|
+
begin
|
|
410
|
+
if target_file.respond_to?(:write)
|
|
411
|
+
# IO object: stream directly into it
|
|
419
412
|
response.read_body do |fragment|
|
|
420
|
-
|
|
413
|
+
target_file.write(fragment)
|
|
421
414
|
written_size += fragment.length
|
|
422
|
-
|
|
415
|
+
progress&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
|
416
|
+
end
|
|
417
|
+
else
|
|
418
|
+
# file path: download to partial name first, then rename atomically
|
|
419
|
+
target_file_tmp = "#{target_file}#{Parameters.instance.download_partial_suffix}"
|
|
420
|
+
FileUtils.mkdir_p(File.dirname(target_file_tmp))
|
|
421
|
+
File.open(target_file_tmp, 'wb') do |file|
|
|
422
|
+
response.read_body do |fragment|
|
|
423
|
+
file.write(fragment)
|
|
424
|
+
written_size += fragment.length
|
|
425
|
+
progress&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
|
|
426
|
+
end
|
|
423
427
|
end
|
|
428
|
+
File.rename(target_file_tmp, target_file)
|
|
424
429
|
end
|
|
425
|
-
|
|
430
|
+
success = true
|
|
431
|
+
ensure
|
|
432
|
+
# Last progress may have been skipped by limiter
|
|
433
|
+
progress&.event(:transfer, session_id: session_id, info: written_size)
|
|
434
|
+
progress&.event(:session_end, session_id: session_id)
|
|
435
|
+
progress&.event(:end, info: success)
|
|
426
436
|
end
|
|
427
|
-
Parameters.instance.progress_bar&.event(:session_end, session_id: session_id)
|
|
428
|
-
Parameters.instance.progress_bar&.event(:end)
|
|
429
437
|
true
|
|
430
438
|
end
|
|
431
439
|
|
|
@@ -439,6 +447,7 @@ module Aspera
|
|
|
439
447
|
# TODO: Remove next 2 lines when bug in async node api is fixed. (Aspera/core/issues/4490)
|
|
440
448
|
node_api_bug = result_data&.index('}HTTP/1.1 400 Bad Request') if result_data.is_a?(String)
|
|
441
449
|
result_data = result_data[0..node_api_bug] if node_api_bug
|
|
450
|
+
result_mime = 'application/xml' if result_data.is_a?(String) && result_data.start_with?('<?xml')
|
|
442
451
|
result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
|
|
443
452
|
Log.dump(:result_data, result_data)
|
|
444
453
|
result_data
|
data/lib/aspera/rest/list.rb
CHANGED
|
@@ -70,9 +70,11 @@ module Aspera
|
|
|
70
70
|
Log.dump(:query, query)
|
|
71
71
|
result = []
|
|
72
72
|
offset = 0
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
# Special parameters are not sent to the API (the caller's query is not modified)
|
|
74
|
+
query = query.dup
|
|
75
|
+
max_items = query.delete(MAX_ITEMS)&.to_i
|
|
76
|
+
remain_pages = query.delete(MAX_PAGES)&.to_i
|
|
77
|
+
# Merge default parameters
|
|
76
78
|
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
77
79
|
total_count = nil
|
|
78
80
|
call_args = {
|
|
@@ -84,18 +86,22 @@ module Aspera
|
|
|
84
86
|
loop do
|
|
85
87
|
query['offset'] = offset
|
|
86
88
|
page_result = call(**call_args)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
page_items = page_result[items_key]
|
|
90
|
+
Aspera.assert_type(page_items, Array)
|
|
91
|
+
result.concat(page_items)
|
|
89
92
|
# Reach the limit set by user ?
|
|
90
93
|
if !max_items.nil? && (result.length >= max_items)
|
|
91
94
|
result = result.slice(0, max_items)
|
|
92
95
|
break
|
|
93
96
|
end
|
|
94
97
|
total_count ||= page_result['total_count']
|
|
95
|
-
|
|
98
|
+
# An empty page ends the list, even if the total count is not reached (e.g. items deleted meanwhile)
|
|
99
|
+
break if page_items.empty?
|
|
100
|
+
# Without total count, a partial page is the last one
|
|
101
|
+
break if total_count.nil? ? page_items.length < query['limit'].to_i : result.length >= total_count
|
|
96
102
|
remain_pages -= 1 unless remain_pages.nil?
|
|
97
|
-
break if remain_pages
|
|
98
|
-
offset +=
|
|
103
|
+
break if !remain_pages.nil? && remain_pages <= 0
|
|
104
|
+
offset += page_items.length
|
|
99
105
|
Parameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
|
|
100
106
|
end
|
|
101
107
|
Parameters.instance.spinner_cb.call(action: :success)
|
|
@@ -32,7 +32,7 @@ module Aspera
|
|
|
32
32
|
attr_accessor :session_cb
|
|
33
33
|
# @return [Object, nil] Progress bar, receives `event` calls during download
|
|
34
34
|
attr_accessor :progress_bar
|
|
35
|
-
# @return [Proc
|
|
35
|
+
# @return [Proc] Called with `(title = nil, action: :spin)` to display progress of long operations (default: no display)
|
|
36
36
|
attr_accessor :spinner_cb
|
|
37
37
|
|
|
38
38
|
private
|
|
@@ -48,7 +48,7 @@ module Aspera
|
|
|
48
48
|
@retry_sleep = 4
|
|
49
49
|
@session_cb = nil
|
|
50
50
|
@progress_bar = nil
|
|
51
|
-
@spinner_cb = nil
|
|
51
|
+
@spinner_cb = ->(_title = nil, action: :spin) {}
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -3840,12 +3840,6 @@ paths:
|
|
|
3840
3840
|
required: false
|
|
3841
3841
|
schema:
|
|
3842
3842
|
type: boolean
|
|
3843
|
-
- name: get_package_download_link
|
|
3844
|
-
in: query
|
|
3845
|
-
description: Specifies whether to return the package download context to download the package as an external user.
|
|
3846
|
-
required: false
|
|
3847
|
-
schema:
|
|
3848
|
-
type: boolean
|
|
3849
3843
|
responses:
|
|
3850
3844
|
'200':
|
|
3851
3845
|
description: ' - OK - Successful response'
|
|
@@ -30208,13 +30202,6 @@ components:
|
|
|
30208
30202
|
- eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
|
|
30209
30203
|
maxLength: 255
|
|
30210
30204
|
minLength: 1
|
|
30211
|
-
pkg_download_context:
|
|
30212
|
-
description: The package download context value used for navigating to the package download view for external users. Only included in responses when `get_package_download_link` is set to `true` and the package in question was not sent to a shared inbox or workgroup.
|
|
30213
|
-
type: string
|
|
30214
|
-
examples:
|
|
30215
|
-
- eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
|
|
30216
|
-
maxLength: 255
|
|
30217
|
-
minLength: 1
|
|
30218
30205
|
PutPackage:
|
|
30219
30206
|
description: Update a package
|
|
30220
30207
|
type: object
|
|
@@ -32779,13 +32766,6 @@ components:
|
|
|
32779
32766
|
- eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
|
|
32780
32767
|
maxLength: 255
|
|
32781
32768
|
minLength: 1
|
|
32782
|
-
pkg_download_context:
|
|
32783
|
-
description: The package download context value used for navigating to the package download view for external users. Only included in responses when `get_package_download_link` is set to `true` and the package in question was not sent to a shared inbox or workgroup.
|
|
32784
|
-
type: string
|
|
32785
|
-
examples:
|
|
32786
|
-
- eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
|
|
32787
|
-
maxLength: 255
|
|
32788
|
-
minLength: 1
|
|
32789
32769
|
relays_count:
|
|
32790
32770
|
description: Package relays count
|
|
32791
32771
|
type: object
|