Class: Aspera::AsCmd

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/ascmd.rb

Overview

Run ascmd commands using specified executor (usually, remotely on transfer node) Equivalent of SDK “command client” execute: “ascmd -h” to get syntax Note: “ls” can take filters: as_ls -f *.txt -f *.bin /

Defined Under Namespace

Classes: Error

Constant Summary collapse

OPERATIONS =

list of supported actions

%i[ls rm mv du info mkdir cp df md5sum].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command_executor) ⇒ AsCmd

@param command_executor [Object] provides the “execute” method, taking a command to execute, and stdin to feed to it, typically: ssh or local



15
16
17
# File 'lib/aspera/ascmd.rb', line 15

def initialize(command_executor)
  @command_executor = command_executor
end

Class Method Details

.field_description(struct_name, typed_buffer) ⇒ Object

get description of structure’s field, @param struct_name, @param typed_buffer provides field name



108
109
110
111
112
# File 'lib/aspera/ascmd.rb', line 108

def field_description(struct_name, typed_buffer)
  result = TYPES_DESCR[struct_name][:fields][typed_buffer[:btype] - ENUM_START]
  raise "Unrecognized field for #{struct_name}: #{typed_buffer[:btype]}\n#{typed_buffer[:buffer]}" if result.nil?
  return result
end

.parse(buffer, type_name, indent_level = nil) ⇒ Object

decodes the provided buffer as provided type name :base : value, :buffer_list : an array of btype,buffer, :field_list : a hash, or array

Returns:

  • a decoded type.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/aspera/ascmd.rb', line 117

def parse(buffer, type_name, indent_level=nil)
  indent_level = (indent_level || -1) + 1
  type_descr = TYPES_DESCR[type_name]
  raise "Unexpected type #{type_name}" if type_descr.nil?
  Log.log.debug{"#{'   .' * indent_level}parse:#{type_name}:#{type_descr[:decode]}:#{buffer[0, 16]}...".red}
  result = nil
  case type_descr[:decode]
  when :base
    num_bytes = type_name.eql?(:zstr) ? buffer.length : type_descr[:size]
    raise 'ERROR:not enough bytes' if buffer.length < num_bytes
    byte_array = buffer.shift(num_bytes)
    byte_array = [byte_array] unless byte_array.is_a?(Array)
    result = byte_array.pack('C*').unpack1(type_descr[:unpack])
    result.force_encoding('UTF-8') if type_name.eql?(:zstr)
    Log.log.debug{"#{'   .' * indent_level}-> base:#{byte_array} -> #{result}"}
    result = Time.at(result) if type_name.eql?(:epoch)
  when :buffer_list
    result = []
    until buffer.empty?
      btype = parse(buffer, :int8, indent_level)
      length = parse(buffer, :int32, indent_level)
      raise 'ERROR:not enough bytes' if buffer.length < length
      value = buffer.shift(length)
      result.push({btype: btype, buffer: value})
      Log.log.debug{"#{'   .' * indent_level}:buffer_list[#{result.length - 1}] #{result.last}"}
    end
  when :field_list
    # by default the result is one struct
    result = {}
    # get individual binary fields
    parse(buffer, :blist, indent_level).each do |typed_buffer|
      # what type of field is it ?
      field_info = field_description(type_name, typed_buffer)
      Log.log.debug{"#{'   .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
      case field_info[:special]
      when nil
        result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
      when :return_true
        result[field_info[:name]] = true
      when :substruct
        result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
      when :multiple
        result[field_info[:name]] ||= []
        result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
      when :restart_on_first
        fl = result[field_info[:name]] = []
        parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
          fl.push({}) if tb[:btype].eql?(ENUM_START)
          fi = field_description(field_info[:is_a], tb)
          fl.last[fi[:name]] = parse(tb[:buffer], fi[:is_a], indent_level)
        end
      end
    end
  else raise "error: unknown decode:#{type_descr[:decode]}"
  end # is_a
  return result
end

Instance Method Details

#execute_single(action_sym, args = nil) ⇒ Object

execute an “as” command on a remote server

Parameters:

  • one (Symbol)

    of OPERATIONS

  • parameters (Array)

    for “as” command

Returns:

  • result of command, type depends on command (bool, array, hash)

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/aspera/ascmd.rb', line 23

def execute_single(action_sym, args=nil)
  # concatenate arguments, enclose in double quotes, protect backslash and double quotes, add "as_" command and as_exit
  stdin_input = (args || []).map{|v| '"' + v.gsub(/["\\]/n) {|s| '\\' + s } + '"'}.unshift('as_' + action_sym.to_s).join(' ') + "\nas_exit\n"
  # execute, get binary output
  byte_buffer = @command_executor.execute('ascmd', stdin_input).unpack('C*')
  raise 'ERROR: empty answer from server' if byte_buffer.empty?
  # get hash or table result
  result = self.class.parse(byte_buffer, :result)
  raise 'ERROR: unparsed bytes remaining' unless byte_buffer.empty?
  # get and delete info,always present in results
  system_info = result[:info]
  result.delete(:info)
  # make single file result like a folder
  result[:dir] = [result.delete(:file)] if result.key?(:file)
  # add type field for stats
  if result.key?(:dir)
    result[:dir].each do |file|
      if file.key?(:smode)
        # Converts the first character of the file mode (see 'man ls') into a type.
        file[:type] = case file[:smode][0, 1]; when 'd' then:directory; when '-' then:file; when 'l' then:link; else; :other; end # rubocop:disable Style/Semicolon
      end
    end
  end
  # for info, second overrides first, so restore it
  case result.keys.length; when 0 then result = system_info; when 1 then result = result[result.keys.first]; else raise 'error'; end
  # raise error as exception
  raise Error.new(result[:errno], result[:errstr], action_sym, args) if
    result.is_a?(Hash) && (result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort)
  return result
end