class Rock::WebApp::Syskit::AppClient
An interface client using TCP that provides reconnection capabilities as well as proper formatting of the information
Attributes
@return [Client,nil] the socket used to communicate to the server,
or nil if we have not managed to connect yet
@return [#call] an object that can create a Client instance
@return [Mutex] the shell requires multi-threading access, this is
the mutex to protect when required
@return [String] a string that describes the remote host
Public Class Methods
# 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
# 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
# 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
# File lib/rock/webapp/syskit/app_client.rb, line 46 def close client.close @client = nil end
# 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
# 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
# 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
# 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
# File lib/rock/webapp/syskit/app_client.rb, line 127 def format_notification(source, level, message) ["[#{level}] #{source}: #{message}"] end
# 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
# 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
# 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
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
# File lib/rock/webapp/syskit/app_client.rb, line 25 def path; [] end
# 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
# 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
# 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
# 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
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
# 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