class Abalone::Terminal
Public Class Methods
new(settings, ws, params)
click to toggle source
# File lib/abalone/terminal.rb, line 6 def initialize(settings, ws, params) @settings = settings @ws = ws @params = params @modes = nil @buffer = Abalone::Buffer.new ENV['TERM'] ||= 'xterm-256color' # make sure we've got a somewhat sane environment if settings.respond_to?(:bannerfile) @ws.send({'data' => File.read(settings.bannerfile).encode(crlf_newline: true)}.to_json) @ws.send({'data' => "\r\n\r\n"}.to_json) end reader, @writer, @pid = PTY.spawn(*shell_command) @writer.winsize = [24,80] # there must be some form of event driven pty interaction, EM or some gem maybe? reader.sync = true @term = Thread.new do carry = [] loop do begin PTY.check(@pid, true) output = reader.read_nonblock(512).unpack('C*') # we read non-blocking to stream data as quickly as we can last_low = output.rindex { |x| x < 128 } # find the last low bit trailing = last_low +1 # use inclusive slices here data = (carry + output[0..last_low]).pack('C*').force_encoding('UTF-8') # repack into a string up until the last low bit carry = output[trailing..-1] # save the any remaining high bits and partial chars for next go-round @ws.send({'data' => data}.to_json) rescue IO::WaitReadable IO.select([reader]) retry rescue PTY::ChildExited => e warn('Terminal has exited!') @ws.close_connection @timer.terminate rescue nil @timer.join rescue nil Thread.exit end sleep(0.05) end end if @settings.respond_to? :timeout @timer = Thread.new do expiration = Time.now + @settings.timeout loop do remaining = expiration - Time.now if remaining < 0 terminate! Thread.exit end format = (remaining > 3600) ? "%H:%M:%S" : "%M:%S" time = { 'event' => 'time', 'data' => Time.at(remaining).utc.strftime(format), } @ws.send(time.to_json) sleep 1 end end end end
Public Instance Methods
alive?()
click to toggle source
# File lib/abalone/terminal.rb, line 79 def alive? @term.alive? end
modes=(message)
click to toggle source
# File lib/abalone/terminal.rb, line 109 def modes=(message) raise 'Invalid modes data type' unless message.is_a? Hash @modes = message.select do |key, val| ['cursorBlink', 'cursorVisible', 'bracketedPaste', 'applicationCursor'].include? key end end
reconnect(ws)
click to toggle source
# File lib/abalone/terminal.rb, line 83 def reconnect(ws) if @ttl # stop the countdown warn "Stopping timeout" @ttl.terminate rescue nil @ttl.join rescue nil @ttl = nil end @ws.close_connection if @ws @ws = ws sleep 0.25 # allow the terminal to finish initialization before we blast it. if @modes @ws.send({ 'event' => 'modes', 'data' => @modes }.to_json) end @ws.send({'data' => @buffer.replay}.to_json) @writer.write "\cl" # ctrl-l forces a screen redraw end
resize(rows, cols)
click to toggle source
# File lib/abalone/terminal.rb, line 141 def resize(rows, cols) @writer.winsize = [rows, cols] end
stop!()
click to toggle source
# File lib/abalone/terminal.rb, line 116 def stop! if @settings.respond_to? :ttl @ws = @buffer @ttl = Thread.new do warn "Providing a shutdown grace period of #{@settings.ttl} seconds." sleep @settings.ttl terminate! end else terminate! end end
terminate!()
click to toggle source
# File lib/abalone/terminal.rb, line 129 def terminate! warn "Terminating session." Process.kill('TERM', @pid) rescue nil sleep 1 Process.kill('KILL', @pid) rescue nil [@ttl, @timer, @term].each do |thread| thread.terminate rescue nil thread.join rescue nil end end
write(message)
click to toggle source
# File lib/abalone/terminal.rb, line 105 def write(message) @writer.write message end
Private Instance Methods
shell_command()
click to toggle source
# File lib/abalone/terminal.rb, line 146 def shell_command if @settings.respond_to? :command return @settings.command unless @settings.respond_to? :params command = @settings.command command = command.split if command.class == String @params.each do |param, value| if @settings.params.is_a? Array command << "--#{param}" << value else config = @settings.params[param] case config when nil command << "--#{param}" << value when Hash command << (config[:map] || "--#{param}") command << value end end end return command end if @settings.respond_to? :ssh config = @settings.ssh.dup config[:user] ||= @params['user'] # if not in the config file, it must come from the user if config[:user].nil? warn "SSH configuration must include the user" return ['echo', 'no username provided'] end command = ['ssh', config[:host] ] command << '-l' << config[:user] if config.include? :user command << '-p' << config[:port] if config.include? :port command << '-i' << config[:cert] if config.include? :cert return command end # default just to running login 'login' end