aspera-cli 4.25.6 → 4.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +74 -47
- data/CONTRIBUTING.md +1 -1
- data/lib/aspera/api/aoc.rb +118 -78
- data/lib/aspera/api/node.rb +101 -49
- data/lib/aspera/ascp/installation.rb +94 -30
- data/lib/aspera/cli/extended_value.rb +1 -0
- data/lib/aspera/cli/formatter.rb +47 -40
- data/lib/aspera/cli/manager.rb +30 -4
- data/lib/aspera/cli/plugins/aoc.rb +214 -136
- data/lib/aspera/cli/plugins/ats.rb +3 -3
- data/lib/aspera/cli/plugins/base.rb +17 -42
- data/lib/aspera/cli/plugins/config.rb +5 -3
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +5 -5
- data/lib/aspera/cli/plugins/faspex5.rb +20 -18
- data/lib/aspera/cli/plugins/node.rb +66 -70
- data/lib/aspera/cli/plugins/oauth.rb +5 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
- data/lib/aspera/cli/plugins/preview.rb +116 -80
- data/lib/aspera/cli/plugins/server.rb +2 -10
- data/lib/aspera/cli/plugins/shares.rb +7 -7
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/dot_container.rb +7 -3
- data/lib/aspera/environment.rb +3 -2
- data/lib/aspera/log.rb +1 -1
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/preview/generator.rb +146 -91
- data/lib/aspera/preview/options.rb +4 -1
- data/lib/aspera/preview/terminal.rb +50 -20
- data/lib/aspera/preview/utils.rb +76 -34
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/rest.rb +1 -0
- data/lib/aspera/rest_list.rb +23 -16
- data/lib/aspera/secret_hider.rb +3 -1
- data/lib/aspera/uri_reader.rb +17 -2
- data.tar.gz.sig +0 -0
- metadata +5 -5
- metadata.gz.sig +0 -0
data/lib/aspera/preview/utils.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# cspell:ignore ffprobe optipng unoconv
|
|
3
|
+
# cspell:ignore ffprobe optipng unoconv soffice
|
|
4
4
|
require 'aspera/log'
|
|
5
5
|
require 'aspera/assert'
|
|
6
6
|
require 'English'
|
|
@@ -11,32 +11,30 @@ require 'open3'
|
|
|
11
11
|
module Aspera
|
|
12
12
|
module Preview
|
|
13
13
|
class Utils
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
EXTERNAL_TOOLS = %i[ffmpeg ffprobe magick optipng unoconv].freeze
|
|
14
|
+
# External binaries used
|
|
15
|
+
EXTERNAL_TOOLS = %i[ffmpeg ffprobe magick optipng unoconv soffice].freeze
|
|
16
|
+
# File name format for temporary files, used by both ffmpeg and ruby(Kernel.format)
|
|
18
17
|
TEMP_FORMAT = 'img%04d.jpg'
|
|
18
|
+
# default parameters for ffmpeg
|
|
19
19
|
FFMPEG_DEFAULT_PARAMS = [
|
|
20
20
|
'-y', # overwrite output without asking
|
|
21
21
|
'-loglevel', 'error' # show only errors and up
|
|
22
22
|
].freeze
|
|
23
|
-
private_constant :
|
|
23
|
+
private_constant :EXTERNAL_TOOLS, :TEMP_FORMAT, :FFMPEG_DEFAULT_PARAMS
|
|
24
24
|
|
|
25
25
|
class << self
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# surround with single quotes, and escape single quotes
|
|
30
|
-
return %Q{'#{argument.gsub("'"){ |_s| %q{'"'"'}}}'}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# check that external tools can be executed
|
|
26
|
+
# Check that external tools can be executed
|
|
27
|
+
# @param skip_types [Array<Symbol>] list of tools to skip
|
|
28
|
+
# @return [nil]
|
|
34
29
|
def check_tools(skip_types = [])
|
|
35
30
|
tools_to_check = EXTERNAL_TOOLS.dup
|
|
36
|
-
|
|
31
|
+
if skip_types.include?(:office)
|
|
32
|
+
tools_to_check.delete(:unoconv)
|
|
33
|
+
tools_to_check.delete(:soffice)
|
|
34
|
+
end
|
|
37
35
|
# Check for binaries
|
|
38
36
|
tools_to_check.each do |command_sym|
|
|
39
|
-
|
|
37
|
+
silent_execute(command_sym, '-h')
|
|
40
38
|
rescue Errno::ENOENT => e
|
|
41
39
|
raise "missing #{command_sym} binary: #{e}"
|
|
42
40
|
rescue
|
|
@@ -44,42 +42,47 @@ module Aspera
|
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
# Execute external command
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
|
|
45
|
+
# Execute external command, verify it is in the supported list
|
|
46
|
+
def execute(*args, **kwargs)
|
|
47
|
+
Aspera.assert_values(args.first, EXTERNAL_TOOLS){'command'}
|
|
48
|
+
Environment.secure_execute(*args, **kwargs)
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
# Execute external command
|
|
55
|
-
# @return [
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
# Execute external command silently
|
|
52
|
+
# @return [nil]
|
|
53
|
+
def silent_execute(*args)
|
|
54
|
+
execute(*args, out: File::NULL, err: File::NULL)
|
|
55
|
+
nil
|
|
59
56
|
end
|
|
60
57
|
|
|
58
|
+
# Execute `ffmpeg`
|
|
59
|
+
# @return [nil]
|
|
61
60
|
def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
|
|
62
61
|
Aspera.assert_type(gl_p, Array)
|
|
63
62
|
Aspera.assert_type(in_p, Array)
|
|
64
63
|
Aspera.assert_type(out_p, Array)
|
|
65
|
-
|
|
64
|
+
silent_execute(:ffmpeg, *gl_p, *in_p, '-i', in_f, *out_p, out_f)
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
# @return Float in seconds
|
|
69
68
|
def video_get_duration(input_file)
|
|
70
|
-
return
|
|
69
|
+
return execute(
|
|
70
|
+
:ffprobe,
|
|
71
71
|
'-loglevel', 'error',
|
|
72
72
|
'-show_entries', 'format=duration',
|
|
73
73
|
'-print_format', 'default=noprint_wrappers=1:nokey=1', # cspell:disable-line
|
|
74
|
-
input_file
|
|
75
|
-
|
|
74
|
+
input_file,
|
|
75
|
+
mode: :capture
|
|
76
|
+
).first.to_f
|
|
76
77
|
end
|
|
77
78
|
|
|
79
|
+
# File output format, including temp folder
|
|
78
80
|
def ffmpeg_fmt(temp_folder)
|
|
79
81
|
return File.join(temp_folder, TEMP_FORMAT)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def get_tmp_num_filepath(temp_folder, file_number)
|
|
85
|
+
# Format using {Kernel.format}
|
|
83
86
|
return File.join(temp_folder, format(TEMP_FORMAT, file_number))
|
|
84
87
|
end
|
|
85
88
|
|
|
@@ -97,19 +100,58 @@ module Aspera
|
|
|
97
100
|
1.upto(count) do |i|
|
|
98
101
|
percent = i * 100 / (count + 1)
|
|
99
102
|
filename = get_tmp_num_filepath(temp_folder, index_begin + i)
|
|
100
|
-
|
|
103
|
+
silent_execute(:magick, 'composite', '-blend', percent, img2, img1, filename)
|
|
101
104
|
end
|
|
102
105
|
end
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
# Dump a frame from a video file
|
|
108
|
+
# @param input_file [String] the input file path
|
|
109
|
+
# @param offset_seconds [Integer] the offset in seconds
|
|
110
|
+
# @param scale [String] the scale of the output frame
|
|
111
|
+
# @param output_file [String] the output file path
|
|
112
|
+
# @return [nil]
|
|
113
|
+
def video_dump_frame(input_file, offset_seconds, scale, output_file)
|
|
106
114
|
ffmpeg(
|
|
107
115
|
in_f: input_file,
|
|
108
116
|
in_p: ['-ss', offset_seconds],
|
|
109
117
|
out_f: output_file,
|
|
110
118
|
out_p: ['-frames:v', 1, '-filter:v', "scale=#{scale}"]
|
|
111
119
|
)
|
|
112
|
-
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Parse the output of `magick identify -list font` command
|
|
123
|
+
# @param output [String] the output from `magick -list font`
|
|
124
|
+
# @return [Hash] with keys :path and :fonts
|
|
125
|
+
# :path [String] the path to the type.xml file
|
|
126
|
+
# :fonts [Array<Hash>] array of font hashes with keys:
|
|
127
|
+
# :name, :family, :style, :stretch, :weight, :metrics, :glyphs, :index
|
|
128
|
+
def parse_magick_fonts(output)
|
|
129
|
+
result = {path: nil, fonts: []}
|
|
130
|
+
current_font = nil
|
|
131
|
+
output.each_line do |line|
|
|
132
|
+
line = line.strip
|
|
133
|
+
# Parse the Path line
|
|
134
|
+
if line.start_with?('Path:')
|
|
135
|
+
result[:path] = line.sub(/^Path:\s*/, '')
|
|
136
|
+
# Parse Font name
|
|
137
|
+
elsif line.start_with?('Font:')
|
|
138
|
+
# Save previous font if exists
|
|
139
|
+
result[:fonts] << current_font if current_font
|
|
140
|
+
# Start new font
|
|
141
|
+
current_font = {name: line.sub(/^Font:\s*/, '')}
|
|
142
|
+
# Parse font properties
|
|
143
|
+
elsif current_font && line.include?(':')
|
|
144
|
+
key, value = line.split(':', 2)
|
|
145
|
+
key = key.strip.gsub(/\s+/, '_').to_sym
|
|
146
|
+
value = value.strip
|
|
147
|
+
# Convert numeric values
|
|
148
|
+
value = value.to_i if key == :weight || key == :index
|
|
149
|
+
current_font[key] = value
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
# Don't forget the last font
|
|
153
|
+
result[:fonts] << current_font if current_font
|
|
154
|
+
result
|
|
113
155
|
end
|
|
114
156
|
end
|
|
115
157
|
end
|
|
@@ -27,7 +27,7 @@ module Aspera
|
|
|
27
27
|
sdk_directory
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# @return the path to folder where SDK is installed
|
|
30
|
+
# @return 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
|
data/lib/aspera/rest.rb
CHANGED
|
@@ -78,6 +78,7 @@ module Aspera
|
|
|
78
78
|
|
|
79
79
|
# Indicate that the given Hash query uses php style for array parameters
|
|
80
80
|
# @param query [Hash] A key can have Array value and result will use PHP format: a[]=1&a[]=2
|
|
81
|
+
# @return [Hash] The query parameters.
|
|
81
82
|
def php_style(query)
|
|
82
83
|
Aspera.assert_type(query, Hash){'query'}
|
|
83
84
|
query[:x_array_php_style] = true
|
data/lib/aspera/rest_list.rb
CHANGED
|
@@ -11,33 +11,39 @@ module Aspera
|
|
|
11
11
|
|
|
12
12
|
# Query entity by general search (read with parameter `q`)
|
|
13
13
|
#
|
|
14
|
-
# @param
|
|
15
|
-
# @param
|
|
16
|
-
# @param
|
|
14
|
+
# @param entity [String] Path of entity in API
|
|
15
|
+
# @param value [String] Value of field of searched entity
|
|
16
|
+
# @param field [String] Field of searched entity
|
|
17
|
+
# @param query [Hash] Optional additional search query parameters
|
|
17
18
|
# @returns [Hash] A single entity matching the search, or an exception if not found or multiple found
|
|
18
|
-
def
|
|
19
|
-
query
|
|
19
|
+
def lookup_with_q(entity, value:, field: 'name', query: {})
|
|
20
|
+
Aspera.assert_type(query, Hash){'query'}
|
|
21
|
+
Aspera.assert_type(field, String){'field'}
|
|
20
22
|
# returns entities matching the query (it matches against several fields in case insensitive way)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
# We don't do paging, as anyway, we look for only one match
|
|
24
|
+
matching_items = read(entity, query.merge({'q' => value}))
|
|
25
|
+
# API style: {totalcount:, ...} cspell: disable-line: TODO: is that total_count ?
|
|
26
|
+
# @type [Array<Hash{String => String}>]
|
|
27
|
+
matching_items = matching_items[entity] if matching_items.is_a?(Hash)
|
|
24
28
|
Aspera.assert_type(matching_items, Array)
|
|
25
29
|
case matching_items.length
|
|
26
30
|
when 1 then return matching_items.first
|
|
27
|
-
when 0 then raise EntityNotFound, %Q{No such #{
|
|
31
|
+
when 0 then raise EntityNotFound, %Q{No such #{entity}: "#{value}"}
|
|
28
32
|
else
|
|
29
33
|
# multiple case insensitive partial matches, try case insensitive full match
|
|
30
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive
|
|
31
|
-
|
|
32
|
-
case
|
|
33
|
-
when 1 then return
|
|
34
|
-
when 0 then raise %Q(#{
|
|
35
|
-
else raise "Two entities cannot have the same case insensitive
|
|
34
|
+
# (anyway AoC does not allow creation of 2 entities with same case insensitive field value)
|
|
35
|
+
value_matches = matching_items.select{ |i| i[field].casecmp?(value)}
|
|
36
|
+
case value_matches.length
|
|
37
|
+
when 1 then return value_matches.first
|
|
38
|
+
when 0 then raise %Q(#{entity}: Multiple case insensitive partial match for: "#{value}" in #{matching_items.map{ |i| i[field]}.join(', ')} but no case insensitive full match. Please be more specific or give exact #{field}.)
|
|
39
|
+
else raise "Two entities cannot have the same case insensitive #{field}: #{value_matches.map{ |i| i[field]}}"
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
# Get a (full or partial) list of all entities of a given type
|
|
44
|
+
# Get a (full or partial) list of all entities of a given type.
|
|
45
|
+
# Using query: `offset` + `limit`
|
|
46
|
+
# And response `total_count`
|
|
41
47
|
# @param entity [String,Symbol] API endpoint of entity to list
|
|
42
48
|
# @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
|
|
43
49
|
# @param query [Hash,nil] Additional query parameters
|
|
@@ -114,6 +120,7 @@ module Aspera
|
|
|
114
120
|
return found.first if found.length.eql?(1)
|
|
115
121
|
raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
|
|
116
122
|
end
|
|
123
|
+
|
|
117
124
|
PER_PAGE_DEFAULT = 1000
|
|
118
125
|
private_constant :PER_PAGE_DEFAULT
|
|
119
126
|
module_function :lookup_entity_generic
|
data/lib/aspera/secret_hider.rb
CHANGED
|
@@ -21,6 +21,8 @@ module Aspera
|
|
|
21
21
|
ALL_SECRETS = (ASCP_ENV_SECRETS + KEY_SECRETS + HTTP_SECRETS).freeze
|
|
22
22
|
ALL_SECRETS2 = (KEY_SECRETS + HTTP_SECRETS).freeze
|
|
23
23
|
KEY_FALSE_POSITIVES = [/^access_key$/, /^fallback_private_key$/].freeze
|
|
24
|
+
# min length of hidden secrets, use `+` or `{n,}`
|
|
25
|
+
SECRET_LENGTH = '{5,}'
|
|
24
26
|
# regex that define named captures :begin and :end
|
|
25
27
|
REGEX_LOG_REPLACES = [
|
|
26
28
|
# private key values (place first)
|
|
@@ -32,7 +34,7 @@ module Aspera
|
|
|
32
34
|
# rendered JSON or Ruby
|
|
33
35
|
/(?<begin>(?:(?<quote>["'])|:)[^"':=]*(?:#{ALL_SECRETS.join('|')})[^"':=]*\k<quote>?(?:=>|:) *")[^"]+(?<end>")/,
|
|
34
36
|
# logged data
|
|
35
|
-
/(?<begin>(?:#{ALL_SECRETS2.join('|')})[ =:]+)
|
|
37
|
+
/(?<begin>(?:#{ALL_SECRETS2.join('|')})[ =:]+)[^ "]#{SECRET_LENGTH}(?<end>$)/,
|
|
36
38
|
# cred in http dump
|
|
37
39
|
/(?<begin>(?:#{HTTP_SECRETS.join('|')}): )[^\\]+(?<end>\\)/i
|
|
38
40
|
].freeze
|
data/lib/aspera/uri_reader.rb
CHANGED
|
@@ -14,6 +14,22 @@ module Aspera
|
|
|
14
14
|
SCHEME_FILE_PFX2 = "#{SCHEME_FILE_PFX1}///"
|
|
15
15
|
private_constant :SCHEME_FILE, :SCHEME_FILE_PFX1, :SCHEME_FILE_PFX2
|
|
16
16
|
class << self
|
|
17
|
+
# @return [Boolean] true if the URL is a file:// URL
|
|
18
|
+
def file?(url)
|
|
19
|
+
url.start_with?(SCHEME_FILE_PFX2)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [String] a file:// URL for the given path
|
|
23
|
+
def file_url(path)
|
|
24
|
+
return "#{SCHEME_FILE_PFX2}#{path}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [String] the path of a file:// URL
|
|
28
|
+
def file_path(url)
|
|
29
|
+
Aspera.assert(file?(url)){"use format: #{file_url('<path>')}"}
|
|
30
|
+
File.expand_path(url[SCHEME_FILE_PFX2.length..-1])
|
|
31
|
+
end
|
|
32
|
+
|
|
17
33
|
# Read some content from some URI, support file: , http: and https: schemes
|
|
18
34
|
def read(uri_to_read)
|
|
19
35
|
uri = URI.parse(uri_to_read)
|
|
@@ -41,8 +57,7 @@ module Aspera
|
|
|
41
57
|
if url.start_with?(SCHEME_FILE_PFX1)
|
|
42
58
|
# for file scheme, return directly the path
|
|
43
59
|
# require specific file scheme: the path part is "relative", or absolute if there are 4 slash
|
|
44
|
-
|
|
45
|
-
return File.expand_path(url[SCHEME_FILE_PFX2.length..-1])
|
|
60
|
+
return file_path(url)
|
|
46
61
|
elsif url.start_with?('data:')
|
|
47
62
|
# download to temp file
|
|
48
63
|
# auto-delete on exit
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aspera-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Laurent Martin
|
|
@@ -98,16 +98,16 @@ dependencies:
|
|
|
98
98
|
name: jwt
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- - "
|
|
101
|
+
- - ">="
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
103
|
+
version: '2.0'
|
|
104
104
|
type: :runtime
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- - "
|
|
108
|
+
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '
|
|
110
|
+
version: '2.0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: marcel
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
metadata.gz.sig
CHANGED
|
Binary file
|