class Rock::WebApp::Syskit::AppClient

An interface client using TCP that provides reconnection capabilities as well as proper formatting of the information

Attributes

client[R]

@return [Client,nil] the socket used to communicate to the server,

or nil if we have not managed to connect yet
connection_method[R]

@return [#call] an object that can create a Client instance

mutex[R]

@return [Mutex] the shell requires multi-threading access, this is

the mutex to protect when required
remote_name[R]

@return [String] a string that describes the remote host

Public Class Methods

new(remote_name, &connection_method) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 18
def initialize(remote_name, &connection_method)
    @connection_method = connection_method
    @remote_name = remote_name
    @mutex = Mutex.new
    connect
end

Public Instance Methods

actions(regex = nil, verbose = false) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 51
def actions(regex = nil, verbose = false)
    actions = client.actions.sort_by {|act| act.name }
    if regex
        regex = Regexp.new(regex)
    else
        regex = Regexp.new(".*")
    end
    returnedactions = {}
    actions.each do |action|
        if regex.match(action.name)
                arguments = action.arguments.sort_by {|arg| arg.name }
                required_arguments = []
                optional_arguments = []
                arguments.each do |argument|
                    if argument.required
                        required_arguments << Hash[name: argument.name, default: argument.default, doc: argument.doc]
                    else
                        optional_arguments << Hash[name: argument.name, default: argument.default, doc: argument.doc]
                    end
                end
            actionhash = Hash[required_arguments: required_arguments, optional_arguments: optional_arguments, doc: action.doc]
            returnedactions[action.name] = actionhash
        end
    end
    returnedactions
end
call(options, path, m, *args) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 113
def call(options, path, m, *args)
    options = Kernel.validate_options options, :retry => false
    if options[:retry]
        options = options.merge(:retry => false)
        retry_on_com_error do
            return call options, path, m, *args
        end
    else
        @mutex.synchronize do
            client.call(path, m, *args)
        end
    end
end
close() click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 46
def close
    client.close
    @client = nil
end
connect(retry_period = 0.5) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 27
def connect(retry_period = 0.5)
    retry_warning = false
    begin
        @client = connection_method.call
    rescue Roby::Interface::ConnectionError, Roby::Interface::ComError => e
        if retry_period
            if e.kind_of?(Roby::Interface::ComError)
                Roby::Interface.warn "failed handshake with #{remote_name}, retrying ..."
            elsif !retry_warning
                Roby::Interface.warn "cannot connect to #{remote_name}, retrying every #{retry_period} seconds..."
                retry_warning = true
            end
            sleep retry_period
            retry
        else raise
        end
    end
end
format_arguments(hash) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 78
def format_arguments(hash)
    hash.keys.map do |k|
        v = hash[k]
        v = if !v || v.respond_to?(:to_str) then v.inspect
            else v
            end
        "#{k} => #{v}"
    end.join(", ")
end
format_exception(kind, error, *args) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 143
def format_exception(kind, error, *args)
    color = if kind == ExecutionEngine::EXCEPTION_FATAL then [:red]
            elsif kind == ExecutionEngine::EXCEPTION_NONFATAL then [:magenta]
            else []
            end
    if error
        msg = Roby.format_exception(error.exception)
        if msg[0]
            msg[0] = Roby.console.color(msg[0], *color)
        end
    else
        msg = ["<something wrong happened in transmission of exception information>"]
    end
    return msg
end
format_job_progress(kind, job_id, job_name, *args) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 135
def format_job_progress(kind, job_id, job_name, *args)
    ["[#{job_id}] #{job_name}: #{kind}"]
end
format_notification(source, level, message) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 127
def format_notification(source, level, message)
    ["[#{level}] #{source}: #{message}"]
end
help(subcommand = client) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 220
def help(subcommand = client)
    puts
    if subcommand.respond_to?(:description)
        puts Roby.console.color(subcommand.description.join("\n"), :bold)
        puts
    end

    commands = subcommand.commands[''].commands
    if !commands.empty?
        puts Roby.console.color("Commands", :bold)
        puts Roby.console.color("--------", :bold)
        commands.keys.sort.each do |command_name|
            cmd = commands[command_name]
            puts "#{command_name}(#{cmd.arguments.keys.map(&:to_s).join(", ")}): #{cmd.description.first}"
        end
    end
    if subcommand.commands.size > 1
        puts if !commands.empty?
        puts Roby.console.color("Subcommands (use help <subcommand name> for more details)", :bold)
        puts Roby.console.color("-----------", :bold)
        subcommand.commands.keys.sort.each do |subcommand_name|
            next if subcommand_name.empty?
            puts "#{subcommand_name}: #{subcommand.commands[subcommand_name].description.first}"
        end
    end
    nil
end
jobs() click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 88
def jobs
    returnedjobs = Array.new
    jobs = call Hash[:retry => true], [], :jobs
    jobs.each do |id, (state, task, planning_task)|
        jobhash = nil
        if planning_task.respond_to?(:action_model) && planning_task.action_model
          name = "#{planning_task.action_model.to_s}"
          jobhash = Hash[name: name, id: id, state: state.to_s, arguments: planning_task.action_arguments]
        else
          name = task.to_s
          jobhash = Hash[name: name, id: id, state: state.to_s, arguments: planning_task.action_arguments]
        end
        returnedjobs << jobhash
    end
    returnedjobs
end
method_missing(m, *args, &block) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 190
def method_missing(m, *args, &block)
    if sub = client.find_subcommand_by_name(m.to_s)
        ShellSubcommand.new(self, m.to_s, sub.description, sub.commands)
    elsif act = client.find_action_by_name(m.to_s)
        Roby::Actions::Action.new(act, *args)
    else
        begin
            call Hash[], [], m, *args
        rescue NoMethodError => e
            if e.message =~ /undefined method .#{m}./
                puts "invalid command name #{m}, call 'help' for more information"
            else raise
            end
        rescue ArgumentError => e
            if e.message =~ /wrong number of arguments/ && e.backtrace.first =~ /#{m.to_s}/
                puts e.message
            else raise
            end
        end
    end
rescue ComError
    Roby::Interface.warn "Lost communication with remote, will not retry the command after reconnection"
    mutex.synchronize do
        connect
    end
rescue Interrupt
    Roby::Interface.warn "Interrupted"
end
notification_loop(period = 0.1) { |id, msg| ... } click to toggle source

Polls for messages from the remote interface and yields them. It handles automatic reconnection, when applicable, as well

It is meant to be called in a separate thread

@yieldparam [String] msg messages for the user @param [Float] period the polling period in seconds

# File lib/rock/webapp/syskit/app_client.rb, line 314
def notification_loop(period = 0.1)
    already_summarized = Set.new
    was_connected = nil
    while true
        mutex.synchronize do
            has_valid_connection =
                begin
                    client.poll
                    true
                rescue Exception
                    begin
                        connect(nil)
                        true
                    rescue Exception
                    end
                end
    
            already_summarized = 
                summarize_pending_messages(already_summarized) do |id, msg|
                    yield id, msg
                end
            if has_valid_connection
                was_connected = true
            end
    
            if has_valid_connection && !was_connected
                Readline.puts "reconnected"
            elsif !has_valid_connection && was_connected
                Readline.puts "lost connection, reconnecting ..."
            end
            was_connected = has_valid_connection
        end
        sleep period
    end
end
path() click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 25
def path; [] end
retry_on_com_error() { || ... } click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 105
def retry_on_com_error
    yield
rescue Roby::Interface::ComError
    Roby::Interface.warn "Lost communication with remote, retrying command after reconnection"
    connect
    retry
end
summarize_exception(kind, error, *args) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 159
def summarize_exception(kind, error, *args)
    msg = "(#{kind}) #{format_exception(kind, error, *args).first}"
    return msg, false
end
summarize_job_progress(kind, job_id, job_name, *args) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 139
def summarize_job_progress(kind, job_id, job_name, *args)
    return format_job_progress(kind, job_id, job_name, *args).first, true
end
summarize_notification(source, level, message) click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 131
def summarize_notification(source, level, message)
    return format_notification(source, level, message).first, true
end
summarize_pending_messages(already_summarized = Set.new) { |id, msg| ... } click to toggle source

Processes the exception and job_progress queues, and yields with a message that summarizes the new ones

@param [Set] already_summarized the set of IDs of messages that

have already been summarized. This should be the value returned by
the last call to {#summarize_pending_messages}

@yieldparam [String] msg the message that summarizes the new

exception/job progress

@return [Set] the set of notifications still in the queues that

have already been summarized. Pass to the next call to
{#summarize_exception}
# File lib/rock/webapp/syskit/app_client.rb, line 260
def summarize_pending_messages(already_summarized = Set.new)
    summarized = Set.new
    queues = {:exception => client.exception_queue,
              :job_progress => client.job_progress_queue,
              :notification => client.notification_queue}
    queues.each do |type, q|
        q.delete_if do |id, args|
            summarized << id
            if !already_summarized.include?(id)
                msg, complete = send("summarize_#{type}", *args)
                yield id, msg
                complete
            end
        end
    end
    summarized
end
wtf?() click to toggle source
# File lib/rock/webapp/syskit/app_client.rb, line 164
def wtf?
    msg = []
    @mutex.synchronize do
        client.notification_queue.each do |id, level, message|
            msg << Roby.console.color("-- ##{id} (notification) --", :bold)
            msg.concat format_message(kind, level, message)
            msg << "\n"
        end
        client.job_progress_queue.each do |id, (kind, job_id, job_name, *args)|
            msg << Roby.console.color("-- ##{id} (job progress) --", :bold)
            msg.concat format_job_progress(kind, job_id, job_name, *args)
            msg << "\n"
        end
        client.exception_queue.each do |id, (kind, exception, tasks)|
            msg << Roby.console.color("-- ##{id} (#{kind} exception) --", :bold)
            msg.concat format_exception(kind, exception, tasks)
            msg << "\n"
        end
        client.job_progress_queue.clear
        client.exception_queue.clear
        client.notification_queue.clear
    end
    puts msg.join("\n")
    nil
end