Class: Aspera::Cli::Plugins::Faspex

Inherits:
BasicAuthPlugin show all
Defined in:
lib/aspera/cli/plugins/faspex.rb

Constant Summary collapse

KEY_NODE =

required hash key for source in config

'node'
KEY_PATH =

value must be hash with url, username, password

'path'
PACKAGE_MATCH_FIELD =

added field in result that identifies the package

'package_id'
ATOM_MAILBOXES =

list of supported atoms

%i[inbox archive sent].freeze
ATOM_PARAMS =

allowed parameters for inbox.atom

%w[page count startIndex].freeze
ATOM_EXT_PARAMS =

with special parameters (from Plugin class) : max and pmax (from Plugin)

[MAX_ITEMS, MAX_PAGES].concat(ATOM_PARAMS).freeze
'external_deliveries/'
STANDARD_PATH =
'/aspera/faspex'
ACTIONS =
%i[health package source me dropbox v4 address_book login_methods].freeze

Constants inherited from Aspera::Cli::Plugin

Aspera::Cli::Plugin::ALL_OPS, Aspera::Cli::Plugin::GLOBAL_OPS, Aspera::Cli::Plugin::INIT_PARAMS, Aspera::Cli::Plugin::INSTANCE_OPS, Aspera::Cli::Plugin::MAX_ITEMS, Aspera::Cli::Plugin::MAX_PAGES, Aspera::Cli::Plugin::REGEX_LOOKUP_ID_BY_FIELD

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BasicAuthPlugin

#basic_auth_api, #basic_auth_params, declare_options

Methods inherited from Aspera::Cli::Plugin

declare_generic_options, #do_bulk_operation, #entity_action, #entity_command, #init_params, #instance_identifier, #query_option, #query_read_delete, #value_create_modify

Constructor Details

#initialize(**env) ⇒ Faspex

Returns a new instance of Faspex.



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/aspera/cli/plugins/faspex.rb', line 122

def initialize(**env)
  super
  @api_v3 = nil
  @api_v4 = nil
  options.declare(:link, 'Public link for specific operation')
  options.declare(:delivery_info, 'Package delivery information', types: Hash)
  options.declare(:remote_source, 'Remote source for package send (id or %name:)')
  options.declare(:storage, 'Faspex local storage definition (for browsing source)')
  options.declare(:recipient, 'Use if recipient is a dropbox (with *)')
  options.declare(:box, 'Package box', values: ATOM_MAILBOXES, default: :inbox)
  options.parse_options!
end

Class Method Details

.detect(address_or_url) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/aspera/cli/plugins/faspex.rb', line 43

def detect(address_or_url)
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
  urls = [address_or_url]
  urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
  error = nil
  urls.each do |base_url|
    next unless base_url.start_with?('https://')
    api = Rest.new(base_url: base_url, redirect_max: 1)
    result = api.call(
      operation:        'POST',
      headers:          {
        'Content-type' => 'text/plain',
        'Accept'       => 'application/xrds+xml'
      }
    )
    # 4.x
    next unless result[:http].body.start_with?('<?xml')
    res_s = XmlSimple.xml_in(result[:http].body, {'ForceArray' => false})
    Log.log.debug{"version: #{result[:http]['X-IBM-Aspera']}"}
    version = res_s['XRD']['application']['version']
    # take redirect if any
    return {
      version: version,
      url:     result[:http].uri.to_s
    }
  rescue StandardError => e
    error = e
    Log.log.debug{"detect error: #{e}"}
  end
  raise error if error
  return nil
end

.get_fasp_uri_from_entry(entry, raise_no_link: true) ⇒ Object

get Transfer::Uri::SCHEME URI from entry in xml, and fix problems.



105
106
107
108
109
110
111
112
# File 'lib/aspera/cli/plugins/faspex.rb', line 105

def get_fasp_uri_from_entry(entry, raise_no_link: true)
  unless entry.key?('link')
    raise Cli::BadArgument, 'package has no link (deleted?)' if raise_no_link
    return nil
  end
  result = entry['link'].find{|e| e['rel'].eql?('package')}['href']
  return result
end

extract elements from faspex public link



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/aspera/cli/plugins/faspex.rb', line 89

def get_link_data(public_url)
  public_uri = URI.parse(public_url)
  Aspera.assert((m = public_uri.path.match(%r{^(.*)/(external.*)$})), exception_class: Cli::BadArgument){'Public link does not match Faspex format'}
  base = m[1]
  subpath = m[2]
  port_add = public_uri.port.eql?(public_uri.default_port) ? '' : ":#{public_uri.port}"
  result = {
    base_url: "#{public_uri.scheme}://#{public_uri.host}#{port_add}#{base}",
    subpath:  subpath,
    query:    Rest.decode_query(public_uri.query)
  }
  Log.log.debug{Log.dump('link data', result)}
  return result
end

.get_source_id_by_name(source_name, source_list) ⇒ Integer

Returns identifier of source.

Returns:

  • (Integer)

    identifier of source

Raises:



115
116
117
118
119
# File 'lib/aspera/cli/plugins/faspex.rb', line 115

def get_source_id_by_name(source_name, source_list)
  match_source = source_list.find { |i| i['name'].eql?(source_name) }
  return match_source['id'] unless match_source.nil?
  raise Cli::Error, %Q(No such Faspex source: "#{source_name}" in [#{source_list.map{|i| %Q("#{i['name']}")}.join(', ')}])
end

.wizard(object:, private_key_path: nil, pub_key_pem: nil) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/aspera/cli/plugins/faspex.rb', line 76

def wizard(object:, private_key_path: nil, pub_key_pem: nil)
  options = object.options
  return {
    preset_value: {
      url:      options.get_option(:url, mandatory: true),
      username: options.get_option(:username, mandatory: true),
      password: options.get_option(:password, mandatory: true)
    },
    test_args:    'me'
  }
end

Instance Method Details

#api_v3Object



135
136
137
138
139
140
# File 'lib/aspera/cli/plugins/faspex.rb', line 135

def api_v3
  if @api_v3.nil?
    @api_v3 = basic_auth_api
  end
  return @api_v3
end

#api_v4Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/aspera/cli/plugins/faspex.rb', line 142

def api_v4
  if @api_v4.nil?
    faspex_api_base = options.get_option(:url, mandatory: true)
    @api_v4 = Rest.new(
      base_url: "#{faspex_api_base}/api",
      auth:     {
        type:         :oauth2,
        grant_method: :generic,
        base_url:     "#{faspex_api_base}/auth/oauth2",
        auth:         {type: :basic, username: options.get_option(:username, mandatory: true), password: options.get_option(:password, mandatory: true)},
        scope:        'admin',
        grant_type:   'password'
      })
  end
  return @api_v4
end

#execute_actionObject



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/aspera/cli/plugins/faspex.rb', line 273

def execute_action
  command = options.get_next_command(ACTIONS)
  case command
  when :health
    nagios = Nagios.new
    begin
      api_v3.read('me')
      nagios.add_ok('faspex api', 'accessible')
    rescue StandardError => e
      nagios.add_critical('faspex api', e.to_s)
    end
    return nagios.result
  when :package
    command_pkg = options.get_next_command(%i[send receive list show], aliases: {recv: :receive})
    case command_pkg
    when :show
      delivery_id = instance_identifier
      return {type: :single_object, data: mailbox_filtered_entries(stop_at_id: delivery_id).find{|p|p[PACKAGE_MATCH_FIELD].eql?(delivery_id)} }
    when :list
      return {
        type:   :object_list,
        data:   mailbox_filtered_entries,
        fields: [PACKAGE_MATCH_FIELD, 'title', 'items']
      }
    when :send
      delivery_info = options.get_option(:delivery_info, mandatory: true)
      Aspera.assert_type(delivery_info, Hash, exception_class: Cli::BadArgument){'delivery_info'}
      # actual parameter to faspex API
      package_create_params = {'delivery' => delivery_info}
      public_link_url = options.get_option(:link)
      if public_link_url.nil?
        # authenticated user
        delivery_info['sources'] ||= [{'paths' => []}]
        first_source = delivery_info['sources'].first
        first_source['paths'].push(*transfer.source_list)
        source_id = instance_identifier(as_option: :remote_source) do |field, value|
          Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
          source_list = api_v3.call(operation: 'GET', subpath: 'source_shares', headers: {'Accept' => 'application/json'})[:data]['items']
          self.class.get_source_id_by_name(value, source_list)
        end
        first_source['id'] = source_id.to_i unless source_id.nil?
        pkg_created = api_v3.call(
          operation:   'POST',
          subpath:     'send',
          headers:     {'Accept' => 'application/json'},
          body:        package_create_params,
          body_type:   :json
        )[:data]
        if first_source.key?('id')
          # no transfer spec if remote source: handled by faspex
          return {data: [pkg_created['links']['status']], type: :value_list, name: 'link'}
        end
        raise Cli::BadArgument, 'expecting one session exactly' if pkg_created['xfer_sessions'].length != 1
        transfer_spec = pkg_created['xfer_sessions'].first
        # use source from cmd line, this one only contains destination (already in dest root)
        transfer_spec.delete('paths')
      else # public link
        transfer_spec = send_public_link_to_ts(public_link_url, package_create_params)
      end
      # Log.log.debug{Log.dump('transfer_spec',transfer_spec)}
      return Main.result_transfer(transfer.start(transfer_spec))
    when :receive
      link_url = options.get_option(:link)
      # list of faspex ID/URI to download
      pkg_id_uri = nil
      skip_ids_data = []
      skip_ids_persistency = nil
      case link_url
      when nil # usual case: no link
        if options.get_option(:once_only, mandatory: true)
          skip_ids_persistency = PersistencyActionOnce.new(
            manager: persistency,
            data:    skip_ids_data,
            id:      IdGenerator.from_list([
              'faspex_recv',
              options.get_option(:url, mandatory: true),
              options.get_option(:username, mandatory: true),
              options.get_option(:box, mandatory: true).to_s
            ]))
        end
        # get command line parameters
        delivery_id = instance_identifier
        raise 'empty id' if delivery_id.empty?
        recipient = options.get_option(:recipient)
        if delivery_id.eql?(SpecialValues::ALL)
          pkg_id_uri = mailbox_filtered_entries.map{|i|{id: i[PACKAGE_MATCH_FIELD], uri: self.class.get_fasp_uri_from_entry(i, raise_no_link: false)}}
        elsif delivery_id.eql?(SpecialValues::INIT)
          Aspera.assert(skip_ids_persistency){'Only with option once_only'}
          skip_ids_persistency.data.clear.concat(mailbox_filtered_entries.map{|i|{id: i[PACKAGE_MATCH_FIELD]}})
          skip_ids_persistency.save
          return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
        elsif !recipient.nil? && recipient.start_with?('*')
          found_package_link = mailbox_filtered_entries(stop_at_id: delivery_id).find{|p|p[PACKAGE_MATCH_FIELD].eql?(delivery_id)}['link'].first['href']
          raise "Not Found. Dropbox and Workgroup packages can use the link option with #{Transfer::Uri::SCHEME}" if found_package_link.nil?
          pkg_id_uri = [{id: delivery_id, uri: found_package_link}]
        else
          # TODO: delivery id is the right one if package was receive by workgroup
          endpoint =
            case options.get_option(:box, mandatory: true)
            when :inbox, :archive then'received'
            when :sent then 'sent'
            end
          entry_xml = api_v3.call(operation: 'GET', subpath: "#{endpoint}/#{delivery_id}", headers: {'Accept' => 'application/xml'})[:http].body
          package_entry = XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
          pkg_id_uri = [{id: delivery_id, uri: self.class.get_fasp_uri_from_entry(package_entry)}]
        end
      when /^#{Transfer::Uri::SCHEME}:/o
        pkg_id_uri = [{id: 'package', uri: link_url}]
      else
        link_data = self.class.get_link_data(link_url)
        if !link_data[:subpath].start_with?(PUB_LINK_EXTERNAL_MATCH)
          raise Cli::BadArgument, "Pub link is #{link_data[:subpath]}. Expecting #{PUB_LINK_EXTERNAL_MATCH}"
        end
        # NOTE: unauthenticated API (authorization is in url params)
        api_public_link = Rest.new(base_url: link_data[:base_url])
        package_creation_data = api_public_link.call(
          operation: 'GET',
          subpath:   link_data[:subpath],
          headers:   {'Accept' => 'application/xml'},
          query:     {passcode: link_data[:query]['passcode']})
        if !package_creation_data[:http].body.start_with?('<?xml ')
          Environment.instance.open_uri(link_url)
          raise Cli::Error, 'Unexpected response: package not found ?'
        end
        package_entry = XmlSimple.xml_in(package_creation_data[:http].body, {'ForceArray' => false})
        Log.log.debug{Log.dump(:package_entry, package_entry)}
        transfer_uri = self.class.get_fasp_uri_from_entry(package_entry)
        pkg_id_uri = [{id: package_entry['id'], uri: transfer_uri}]
      end
      # prune packages already downloaded
      # TODO : remove ids from skip not present in inbox to avoid growing too big
      # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
      pkg_id_uri.reject!{|i|skip_ids_data.include?(i[:id])}
      Log.log.debug{Log.dump(:pkg_id_uri, pkg_id_uri)}
      return Main.result_status('no new package') if pkg_id_uri.empty?
      result_transfer = []
      pkg_id_uri.each do |id_uri|
        if id_uri[:uri].nil?
          # skip package with no link: empty or content deleted
          statuses = [:success]
        else
          transfer_spec = Transfer::Uri.new(id_uri[:uri]).transfer_spec
          # NOTE: only external users have token in Transfer::Uri::SCHEME link !
          if !transfer_spec.key?('token')
            sanitized = id_uri[:uri].gsub('&', '&amp;')
            xml_payload =
              %Q(<?xml version="1.0" encoding="UTF-8"?><url-list xmlns="http://schemas.asperasoft.com/xml/url-list"><url href="#{sanitized}"/></url-list>)
            transfer_spec['token'] = api_v3.call(
              operation:  'POST',
              subpath:    'issue-token',
              headers:    {'Accept' => 'text/plain', 'Content-Type' => 'application/vnd.aspera.url-list+xml'},
              query:      {'direction' => 'down'},
              body:       xml_payload,
              body_type:  :text)[:http].body
          end
          transfer_spec['direction'] = Transfer::Spec::DIRECTION_RECEIVE
          statuses = transfer.start(transfer_spec)
        end
        result_transfer.push({'package' => id_uri[:id], Main::STATUS_FIELD => statuses})
        # skip only if all sessions completed
        skip_ids_data.push(id_uri[:id]) if TransferAgent.session_status(statuses).eql?(:success)
      end
      skip_ids_persistency&.save
      return Main.result_transfer_multiple(result_transfer)
    end
  when :source
    command_source = options.get_next_command(%i[list info node])
    source_list = api_v3.call(operation: 'GET', subpath: 'source_shares', headers: {'Accept' => 'application/json'})[:data]['items']
    case command_source
    when :list
      return {type: :object_list, data: source_list}
    else # :info :node
      source_id = instance_identifier do |field, value|
        Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only name as selector, or give id'}
        self.class.get_source_id_by_name(value, source_list)
      end.to_i
      source_name = source_list.find{|i|i['id'].eql?(source_id)}['name']
      source_hash = options.get_option(:storage, mandatory: true)
      # check value of option
      Aspera.assert_type(source_hash, Hash, exception_class: Cli::Error){'storage option'}
      source_hash.each do |name, storage|
        Aspera.assert_type(storage, Hash, exception_class: Cli::Error){"storage '#{name}'"}
        [KEY_NODE, KEY_PATH].each do |key|
          Aspera.assert(storage.key?(key), exception_class: Cli::Error){"storage '#{name}' must have a '#{key}'"}
        end
      end
      if !source_hash.key?(source_name)
        raise Cli::Error, "No such storage in config file: \"#{source_name}\" in [#{source_hash.keys.join(', ')}]"
      end
      source_info = source_hash[source_name]
      Log.log.debug{Log.dump(:source_info, source_info)}
      case command_source
      when :info
        return {data: source_info, type: :single_object}
      when :node
        node_config = ExtendedValue.instance.evaluate(source_info[KEY_NODE])
        Log.log.debug{"node=#{node_config}"}
        Aspera.assert_type(node_config, Hash, exception_class: Cli::Error){source_info[KEY_NODE]}
        api_node = Rest.new(
          base_url: node_config['url'],
          auth:     {
            type:     :basic,
            username: node_config['username'],
            password: node_config['password']})
        command = options.get_next_command(Node::COMMANDS_FASPEX)
        return Node.new(**init_params, api: api_node).execute_action(command, source_info[KEY_PATH])
      end
    end
  when :me
    my_info = api_v3.call(operation: 'GET', subpath: 'me', headers: {'Accept' => 'application/json'})[:data]
    return {data: my_info, type: :single_object}
  when :dropbox
    command_pkg = options.get_next_command([:list])
    case command_pkg
    when :list
      dropbox_list = api_v3.call(operation: 'GET', subpath: 'dropboxes', headers: {'Accept' => 'application/json'})[:data]
      return {type: :object_list, data: dropbox_list['items'], fields: %w[name id description can_read can_write]}
    end
  when :v4
    command = options.get_next_command(%i[package dropbox dmembership workgroup wmembership user metadata_profile])
    case command
    when :dropbox
      return entity_action(api_v4, 'admin/dropboxes', display_fields: %w[id e_wg_name e_wg_desc created_at])
    when :dmembership
      return entity_action(api_v4, 'dropbox_memberships')
    when :workgroup
      return entity_action(api_v4, 'admin/workgroups', display_fields: %w[id e_wg_name e_wg_desc created_at])
    when :wmembership
      return entity_action(api_v4, 'workgroup_memberships')
    when :user
      return entity_action(api_v4, 'users', display_fields: %w[id name first_name last_name])
    when :metadata_profile
      return entity_action(api_v4, 'metadata_profiles')
    when :package
      pkg_box_type = options.get_next_command([:users])
      pkg_box_id = instance_identifier
      return entity_action(api_v4, "#{pkg_box_type}/#{pkg_box_id}/packages")
    end
  when :address_book
    result = api_v3.call(
      operation: 'GET',
      subpath:   'address-book',
      headers:   {'Accept' => 'application/json'},
      query:     {'format' => 'json', 'count' => 100_000}
    )[:data]
    formatter.display_status("users: #{result['itemsPerPage']}/#{result['totalResults']}, start:#{result['startIndex']}")
    users = result['entry']
    # add missing entries
    users.each do |u|
      unless u['emails'].nil?
        email = u['emails'].find{|i|i['primary'].eql?('true')}
        u['email'] = email['value'] unless email.nil?
      end
      if u['email'].nil?
        Log.log.warn{"Skip user without email: #{u}"}
        next
      end
      u['first_name'], u['last_name'] = u['displayName'].split(' ', 2)
      u['x'] = true
    end
    return {type: :object_list, data: users}
  when :login_methods
     = api_v3.call(operation: 'GET', subpath: 'login/new', headers: {'Accept' => 'application/xrds+xml'})[:http].body
     = XmlSimple.xml_in(, {'ForceArray' => false})
    return {type: :object_list, data: ['XRD']['Service']}
  end
end

#mailbox_filtered_entries(stop_at_id: nil) ⇒ Object

query supports : “startIndex”:10,“count”:1,“page”:109,“max”:2,“pmax”:1



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/aspera/cli/plugins/faspex.rb', line 160

def mailbox_filtered_entries(stop_at_id: nil)
  recipient_names = [options.get_option(:recipient) || options.get_option(:username, mandatory: true)]
  # some workgroup messages have no star in recipient name
  recipient_names.push(recipient_names.first[1..-1]) if recipient_names.first.start_with?('*')
  # mailbox is in ATOM_MAILBOXES
  mailbox = options.get_option(:box, mandatory: true)
  # parameters
  mailbox_query = options.get_option(:query)
  max_items = nil
  max_pages = nil
  result = []
  if !mailbox_query.nil?
    Aspera.assert_type(mailbox_query, Hash){'query'}
    Aspera.assert((mailbox_query.keys - ATOM_EXT_PARAMS).empty?){"query: supported params: #{ATOM_EXT_PARAMS}"}
    Aspera.assert(!(mailbox_query.key?('startIndex') && mailbox_query.key?('page'))){'query: startIndex and page are exclusive'}
    max_items = mailbox_query[MAX_ITEMS]
    mailbox_query.delete(MAX_ITEMS)
    max_pages = mailbox_query[MAX_PAGES]
    mailbox_query.delete(MAX_PAGES)
  end
  loop do
    # get a batch of package information
    # order: first batch is latest packages, and then in a batch ids are increasing
    atom_xml = api_v3.call(
      operation: 'GET',
      subpath:   "#{mailbox}.atom",
      headers:   {'Accept' => 'application/xml'},
      query:     mailbox_query)[:http].body
    box_data = XmlSimple.xml_in(atom_xml, {'ForceArray' => %w[entry field link to]})
    Log.log.debug{Log.dump(:box_data, box_data)}
    items = box_data.key?('entry') ? box_data['entry'] : []
    Log.log.debug{"new items: #{items.count}"}
    # it is the end if page is empty
    break if items.empty?
    stop_condition = false
    # results will be sorted in reverse id
    items.reverse_each do |package|
      # create the package id, based on recipient's box
      package[PACKAGE_MATCH_FIELD] =
        case mailbox
        when :inbox, :archive
          recipient = package['to'].find{|i|recipient_names.include?(i['name'])}
          recipient.nil? ? nil : recipient['recipient_delivery_id']
        else # :sent
          package['delivery_id']
        end
      # add special key
      package['items'] = package['link'].is_a?(Array) ? package['link'].length : 0
      package['metadata'] = package['metadata']['field'].each_with_object({}){|i, m| m[i['name']] = i['content'] }
      # if we look for a specific package
      stop_condition = true if !stop_at_id.nil? && stop_at_id.eql?(package[PACKAGE_MATCH_FIELD])
      # keep only those for the specified recipient
      result.push(package) unless package[PACKAGE_MATCH_FIELD].nil?
    end
    break if stop_condition
    # result.push({PACKAGE_MATCH_FIELD=>'======'})
    Log.log.debug{"total items: #{result.count}"}
    # reach the limit ?
    if !max_items.nil? && (result.count >= max_items)
      result = result.slice(0, max_items) if result.count > max_items
      break
    end
    link = box_data['link'].find{|i|i['rel'].eql?('next')}
    Log.log.debug{"link: #{link}"}
    # no next link
    break if link.nil?
    # replace parameters with the ones from next link
    params = CGI.parse(URI.parse(link['href']).query)
    mailbox_query = params.keys.each_with_object({}){|i, m| m[i] = params[i].first }
    Log.log.debug{"query: #{mailbox_query}"}
    break if !max_pages.nil? && (mailbox_query['page'].to_i > max_pages)
  end
  return result
end

retrieve transfer spec from pub link for send package



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/aspera/cli/plugins/faspex.rb', line 236

def send_public_link_to_ts(public_link_url, package_create_params)
  delivery_info = package_create_params['delivery']
  # pub link user
  link_data = self.class.get_link_data(public_link_url)
  if !['external/submissions/new', 'external/dropbox_submissions/new'].include?(link_data[:subpath])
    raise Cli::BadArgument, "pub link is #{link_data[:subpath]}, expecting external/submissions/new"
  end
  create_path = link_data[:subpath].split('/')[0..-2].join('/')
  package_create_params[:passcode] = link_data[:query]['passcode']
  delivery_info[:transfer_type] = 'connect'
  delivery_info[:source_paths_list] = transfer.source_list.join("\r\n")
  api_public_link = Rest.new(base_url: link_data[:base_url])
  # Hum, as this does not always work (only user, but not dropbox), we get the javascript and need hack
  # pkg_created=api_public_link.create(create_path,package_create_params)[:data]
  # so extract data from javascript
  package_creation_data = api_public_link.call(
    operation:   'POST',
    subpath:     create_path,
    headers:     {'Accept' => 'text/javascript'},
    body:        package_create_params,
    body_type:   :json)[:http].body
  # get arguments of function call
  package_creation_data.delete!("\n") # one line
  package_creation_data.gsub!(/^[^"]+\("\{/, '{') # delete header
  package_creation_data.gsub!(/"\);[^"]+$/, '"') # delete trailer
  package_creation_data.gsub!(/\}", *"/, '},"') # between two arguments
  package_creation_data.gsub!('\\"', '"') # remove protecting quote
  begin
    package_creation_data = JSON.parse("[#{package_creation_data}]")
  rescue JSON::ParserError # => e
    raise 'Unexpected response: missing metadata ?'
  end
  return package_creation_data.first
end