UMI samples

Ruby

You will need to install farcall gem first. The sample assumes UMI archive is unpacked into ./umi directory.

Simple UMI connection

This sample generates new private key and saves it to a file:

require 'farcall'
require 'open3'
require 'base64'

# Start UMI in default mode. We need only stdin/out
UMI_in,UMI_out = Open3.popen3('./umi/bin/umi')

# Now create endpoint with it. Our UMI uses \n as delimiter, so we should specify it:
UMI = Farcall::Endpoint.new(
    Farcall::JsonTransport.create(delimiter: "\n", input: UMI_out,output: UMI_in)
)

# call key creation
private_key_ref = UMI.sync_call("instantiate", "PrivateKey", 2048)
# call pack for created key object
packed_key_64 = UMI.sync_call("invoke", private_key_ref.id, "pack")[:base64]
# And save in universa binary format
open('new_key.private.unikey', 'wb') { |f| f << Base64.decode64(packed_key_64) }

Very simple, but it could be ever simpler if we create some class to incapsulate UMI boilerplate and reference lifetime tracking. For example:

u = UMI.new
pk = u.instantiate "PrivateKey", 2048
contract = u.instantiate "Contract", pk
contract.seal()
p contract.getCreatedAt() # => ruby Time instance
p contract.check()        # => true
p contract.isOk()         # => true

# we can chain Java method calls:
address_str1 = contract.getOwner().getAllAddresses()[0]
address_str2 = pk.getPublicKey().getShortAddress().toString()
assert address_str1 == address_str2

pk2 = contract.getOwner().getKeys()[0]

Here is a smaple code that incapulates remote kitchen for the sample above:

require 'open3'
require 'farcall'
require 'base64'

class UMI

  class Error < IOError
  end

  def initialize(path = "./umi")
    @in, @out, @wtr = Open3.popen2("#{path}/bin/umi")
    @endpoint = UMI = Farcall::Endpoint.new(
        Farcall::JsonTransport.create(delimiter: "\n", input: @out, output: @in)
    )
    @closed = false
  end

  def new *args

  end

  def instantiate object_class, *args
    ensure_open
    Ref.new(self, call("instantiate", object_class, *prepare_args(args)))
  end

  def invoke ref, method, *args
    ensure_open
    encode_result call("invoke", ref.id, method, *prepare_args(args))
  end

  def close
    # Process.kill("INT", @wtr.pid)
    # p @wtr.join
    @endpoint.close
    @in.close
    @out.close
    @closed = true
    @wtr.value.exited?
  end

  private

  def prepare_args args
    args.map {|x| x.respond_to?(:as_UMI_arg) ? x.as_UMI_arg : x}
  end

  def encode_result value
    case value
      when Hashie::Mash
        type = value.__type
        case type
          when 'RemoteObject';
            Ref.new(self, value)
          when 'binary';
            Base64.decode64(value.base64)
          when 'unixtime';
            Time.at(value.seconds)
          else
            value
        end
      when Hashie::Array
        value.map {|x| encode_result x}
      else
        value
    end
  end

  def ensure_open
    raise Error, "UMI interface is closed" if @closed
  end


  def call(command, *args)
    @endpoint.sync_call(command, *args)
  end
end

# A reference to any Java-object that can call its methods direcly
# as if these are local ones:
class Ref
  def initialize UMI, ref
    @UMI, @ref = UMI, ref
    @id = ref.id
  end

  def respond_to_missing?(method_name, include_private = false)
    true
  end

  # this is a magick: call remote method instead of the local one
  def method_missing(method_name, *args, &block)
    @UMI.invoke @ref, method_name, *args
  end

  # This allow Ref instance to be an argument to the remote call 
  def as_UMI_arg
    @ref
  end

  def inspect
    "<umiRef:#{@UMI.__id__}:#{@ref.className}:#{@id})>"
  end

  def ==(other)
    other.is_a?(Ref) && other.UMI == @UMI && other.id == id
  end
end

More samples are under way with.