Class: MaxCube::Network::TCP::Client
- Inherits:
-
Object
- Object
- MaxCube::Network::TCP::Client
- Includes:
- Commands
- Defined in:
- lib/maxcube/network/tcp/client.rb,
lib/maxcube/network/tcp/client/commands.rb
Overview
Fundamental class that provides TCP communication with Cube gateway and connected devices. After connecting to Cube (#connect), interactive shell is launched.
Communication with Cube is performed via messages, whereas client works with hashes, which have particular message contents divided and is human readable. An issue is how to pass contents of hashes as arguments of message serialization. For simple hashes client provides and option to pass arguments explicitly on command line. This would be difficult to accomplish for large hashes with subhashes, so YAML files are used in these cases, which are able to be generated both automatically and manually. This file has to be loaded into internal hash before each such message.
Client interactive shell contains quite detailed usage message.
Defined Under Namespace
Modules: Commands
Constant Summary
- DEFAULT_VERBOSE =
Default verbose mode on startup.
true
- DEFAULT_PERSIST =
Default persist mode on startup.
true
- ARGS_FROM_HASH =
Command line token that enables loading arguments (hash) from file.
'-'.freeze
Constants included from Commands
Instance Method Summary collapse
-
#args_from_hash?(args) ⇒ Boolean
private
Whether to enable loading arguments (hash) from file.
-
#buffer(dir_key, data_key, history = false) ⇒ Array<Hash>, String
private
Returns only current or all (without or with history) collected part of buffer and history (contents of buffer is moved to history on clear command).
-
#close ⇒ Object
Closes client gracefully.
-
#command(line) ⇒ Object
private
Executes command from shell command line.
-
#connect(host = LOCALHOST, port = PORT) ⇒ Object
Connects to concrete address and starts interactive shell (#shell).
-
#initialize(verbose: DEFAULT_VERBOSE, persist: DEFAULT_PERSIST) ⇒ Client
constructor
Creates all necessary internal variables.
-
#print_hash(hash) ⇒ Object
private
Prints hash in human readable way.
-
#receiver ⇒ Object
Routine started in separate thread that receives and parses all incoming messages in loop and stores them info thread-safe queue.
-
#refresh_buffer ⇒ Object
private
Moves contents of receiver's queue to internal buffer.
-
#send_msg(type, *args, **opts) ⇒ Object
private
Performs message serialization and sends it to Cube.
-
#send_msg_hash(type, *args, **opts) ⇒ Hash
private
Returns hash with contents necessary for serialization of message of given message type.
-
#send_msg_hash_from_internal(*args, **_opts) ⇒ Hash?
private
Returns hash via Commands#cmd_load.
-
#send_msg_hash_from_keys_args(type, *args, **opts) ⇒ Hash?
private
Zips
args
with appropriate keys according to Messages::Handler#msg_type_hash_keys and Messages::Handler#msg_type_hash_opt_keys. -
#shell ⇒ Object
Interactive shell that maintains all operations with Cube.
Methods included from Commands
#assign_hash, #cmd_clear, #cmd_config, #cmd_data, #cmd_delete, #cmd_dump, #cmd_history, #cmd_list, #cmd_load, #cmd_metadata, #cmd_ntp, #cmd_pair, #cmd_persist, #cmd_quit, #cmd_reset, #cmd_save, #cmd_send, #cmd_url, #cmd_usage, #cmd_verbose, #cmd_wake, #list_hashes, #load_hash, #parse_hash, #toggle, #usage_cmd
Constructor Details
#initialize(verbose: DEFAULT_VERBOSE, persist: DEFAULT_PERSIST) ⇒ Client
Creates all necessary internal variables. Internal hash is invalid on startup.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/maxcube/network/tcp/client.rb', line 37 def initialize(verbose: DEFAULT_VERBOSE, persist: DEFAULT_PERSIST) @parser = Messages::TCP::Parser.new @serializer = Messages::TCP::Serializer.new @queue = Queue.new @buffer = { recv: { hashes: [], data: [] }, sent: { hashes: [], data: [] } } @history = { recv: { hashes: [], data: [] }, sent: { hashes: [], data: [] } } @hash = nil @hash_set = false @data_dir = Pathname.new(MaxCube.data_dir) @load_data_dir = @data_dir + 'load' @save_data_dir = @data_dir + 'save' @verbose = verbose @persist = persist end |
Instance Method Details
#args_from_hash?(args) ⇒ Boolean (private)
Returns whether to enable loading arguments (hash) from file.
218 219 220 |
# File 'lib/maxcube/network/tcp/client.rb', line 218 def args_from_hash?(args) args.first == ARGS_FROM_HASH end |
#buffer(dir_key, data_key, history = false) ⇒ Array<Hash>, String (private)
Returns only current or all (without or with history) collected part of buffer and history (contents of buffer is moved to history on clear command).
146 147 148 149 |
# File 'lib/maxcube/network/tcp/client.rb', line 146 def buffer(dir_key, data_key, history = false) return @buffer[dir_key][data_key] unless history @history[dir_key][data_key] + @buffer[dir_key][data_key] end |
#close ⇒ Object
Closes client gracefully.
119 120 121 122 123 124 |
# File 'lib/maxcube/network/tcp/client.rb', line 119 def close STDIN.close send_msg('q') @socket.close @thread.join end |
#command(line) ⇒ Object (private)
Executes command from shell command line. It calls a method dynamically according to MaxCube::Network::TCP::Client::Commands::COMMANDS, or displays usage message MaxCube::Network::TCP::Client::Commands#cmd_usage.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/maxcube/network/tcp/client.rb', line 155 def command(line) cmd, *args = line.chomp.split return nil unless cmd return send("cmd_#{cmd}", *args) if COMMANDS.key?(cmd) keys = COMMANDS.find { |_, v| v.include?(cmd) } return send("cmd_#{keys.first}", *args) if keys puts "Unrecognized command: '#{cmd}'" cmd_usage rescue ArgumentError puts "Invalid arguments: #{args}" cmd_usage end |
#connect(host = LOCALHOST, port = PORT) ⇒ Object
62 63 64 65 66 |
# File 'lib/maxcube/network/tcp/client.rb', line 62 def connect(host = LOCALHOST, port = PORT) @socket = TCPSocket.new(host, port) @thread = Thread.new(self, &:receiver) shell end |
#print_hash(hash) ⇒ Object (private)
Prints hash in human readable way.
282 283 284 |
# File 'lib/maxcube/network/tcp/client.rb', line 282 def print_hash(hash) puts hash.to_yaml end |
#receiver ⇒ Object
Routine started in separate thread that receives and parses all incoming
messages in loop and stores them info thread-safe queue. Parsing is done
via Messages::TCP::Parser#parse_tcp_msg. It should close gracefully on
any IOError
or on shell's initiative.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/maxcube/network/tcp/client.rb', line 75 def receiver puts '<Starting receiver thread ...>' while (data = @socket.gets) hashes = @parser.parse_tcp_data(data) if @verbose hashes.each { |h| print_hash(h) } puts end @queue << [data, hashes] end raise IOError rescue IOError STDIN.close puts '<Closing receiver thread ...>' rescue Messages::InvalidMessage => e puts e.to_s.capitalize end |
#refresh_buffer ⇒ Object (private)
Moves contents of receiver's queue to internal buffer. Queue is being filled from #receiver. Operation is thread-safe.
131 132 133 134 135 136 137 |
# File 'lib/maxcube/network/tcp/client.rb', line 131 def refresh_buffer until @queue.empty? data, hashes = @queue.pop @buffer[:recv][:data] << data @buffer[:recv][:hashes] << hashes end end |
#send_msg(type, *args, **opts) ⇒ Object (private)
Performs message serialization and sends it to Cube. It builds the hash to serialize from by #send_msg_hash, and serializes it with Messages::TCP::Serializer#serialize_tcp_hash.
Both sent message and built hash are buffered.
It catches all Messages::InvalidMessage exceptions.
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/maxcube/network/tcp/client.rb', line 258 def send_msg(type, *args, **opts) hash = send_msg_hash(type, *args, **opts) return unless hash if hash.key?(:type) unless type == hash[:type] puts "\nInternal hash message type mismatch: '#{hash[:type]}'" \ " (should be '#{type}')" return end else hash[:type] = type end msg = @serializer.serialize_tcp_hash(hash) @buffer[:sent][:data] << msg @buffer[:sent][:hashes] << [hash] @socket.write(msg) rescue Messages::InvalidMessage => e puts e.to_s.capitalize end |
#send_msg_hash(type, *args, **opts) ⇒ Hash (private)
Returns hash with contents necessary for serialization of message of given
message type. It is either built from command line args
(#send_msg_hash_from_keys_args), or loaded from YAML file
(#send_msg_hash_from_internal).
234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/maxcube/network/tcp/client.rb', line 234 def send_msg_hash(type, *args, **opts) if opts[:load_only] && !args_from_hash?(args) args.unshift(ARGS_FROM_HASH) end return {} if args.empty? if args_from_hash?(args) return send_msg_hash_from_internal(*args, **opts) end send_msg_hash_from_keys_args(type, *args, **opts) end |
#send_msg_hash_from_internal(*args, **_opts) ⇒ Hash? (private)
Returns hash via MaxCube::Network::TCP::Client::Commands#cmd_load. It is used to combine sending a message with loading a hash from file. On success and in non-persistive mode, it simultaneously invalidates internal hash flag.
206 207 208 209 210 |
# File 'lib/maxcube/network/tcp/client.rb', line 206 def send_msg_hash_from_internal(*args, **_opts) return nil unless cmd_load(*args.drop(1)) @hash_set = false unless @persist @hash end |
#send_msg_hash_from_keys_args(type, *args, **opts) ⇒ Hash? (private)
Zips args
with appropriate keys according to
Messages::Handler#msg_type_hash_keys and
Messages::Handler#msg_type_hash_opt_keys.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/maxcube/network/tcp/client.rb', line 184 def send_msg_hash_from_keys_args(type, *args, **opts) keys = @serializer.msg_type_hash_keys(type) + @serializer.msg_type_hash_opt_keys(type) if opts[:last_array] hash_args = args.first(keys.size - 1) ary_args = args.drop(keys.size - 1) ary_args = nil if opts[:array_nonempty] && ary_args.empty? args = hash_args << ary_args end if keys.size < args.size return puts 'Additional arguments: ' \ "#{args.last(args.size - keys.size)}" end keys.zip(args).to_h.reject { |_, v| v.nil? } end |
#shell ⇒ Object
Interactive shell that maintains all operations with Cube. It is yet only
simple STDIN
parser without any command history and other
features that possess all decent shells. It calls #command on every
input. It provides quite detailed usage message (MaxCube::Network::TCP::Client::Commands#cmd_usage).
It should close gracefully from user's will, when connection closes, or when soft interrupt appears. Calls #close when closing.
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/maxcube/network/tcp/client.rb', line 104 def shell puts "Welcome to interactive shell!\n" \ "Type 'help' for list of commands.\n\n" STDIN.each do |line| refresh_buffer command(line) puts end raise Interrupt rescue IOError, Interrupt puts "\nClosing shell ..." close end |