class Aspera::AsCmd

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 /

Constants

ENUM_START

protocol enum start at one, but array index start at zero

OPERATIONS

list of supported actions

TYPES_DESCR

description of result structures (see ascmdtypes.h). Base types are big endian key = name of type

Public Class Methods

new(command_executor) click to toggle source

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

# File lib/aspera/ascmd.rb, line 13
def initialize(command_executor)
  @command_executor = command_executor
end

Private Class Methods

field_description(struct_name,typed_buffer) click to toggle source

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

# File lib/aspera/ascmd.rb, line 86
def self.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) click to toggle source

decodes the provided buffer as provided type name @return a decoded type. :base : value, :buffer_list : an array of {btype,buffer}, :field_list : a hash, or array

# File lib/aspera/ascmd.rb, line 95
def self.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*').unpack(type_descr[:unpack]).first
    Log.log.debug("#{"   ."*indent_level}-> base:#{byte_array} -> #{result}")
    result=Time.at(result) if type_name.eql?(:epoch)
  when :buffer_list
    result = []
    while !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

Public Instance Methods

execute_single(action_sym,args=nil) click to toggle source

execute an “as” command on a remote server @param [Symbol] one of OPERATIONS @param [Array] parameters for “as” command @return result of command, type depends on command (bool, array, hash)

# File lib/aspera/ascmd.rb, line 21
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
  bytebuffer=@command_executor.execute('ascmd',stdin_input).unpack('C*')
  # get hash or table result
  result=self.class.parse(bytebuffer,:result)
  raise "ERROR: unparsed bytes remaining" unless bytebuffer.empty?
  # get and delete info,always present in results
  system_info=result[:info]
  result.delete(:info)
  # make single file result like a folder
  if result.has_key?(:file);result[:dir]=[result[:file]];result.delete(:file);end
  # add type field for stats
  if result.has_key?(:dir)
    result[:dir].each do |file|
      if file.has_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';:directory;when'-';:file;when'l';:link;else;:other;end
      end
    end
  end
  # for info, second overrides first, so restore it
  case result.keys.length;when 0;result=system_info;when 1;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) and result.keys.sort == TYPES_DESCR[:error][:fields].map{|i|i[:name]}.sort
  return result
end