lib/net/ssh/shell.rb in net-ssh-shell-0.1.0 vs lib/net/ssh/shell.rb in net-ssh-shell-0.2.0

- old
+ new

@@ -9,25 +9,29 @@ attr_reader :session attr_reader :channel attr_reader :state attr_reader :shell attr_reader :processes + attr_accessor :default_process_class def initialize(session, shell=:default) @session = session @shell = shell @state = :closed @processes = [] @when_open = [] + @on_process_run = nil + @default_process_class = Net::SSH::Shell::Process open end def open(&callback) if closed? @state = :opening @channel = session.open_channel(&method(:open_succeeded)) @channel.on_open_failed(&method(:open_failed)) + @channel.on_request('exit-status', &method(:on_exit_status)) end when_open(&callback) if callback self end @@ -58,25 +62,37 @@ def opening? !open? && !closed? end - def execute(command, klass=Net::SSH::Shell::Process, &callback) - process = klass.new(self, command, callback) - process.run if processes.empty? + def on_process_run(&callback) + @on_process_run = callback + end + + def execute(command, *args, &callback) + # The class is an optional second argument. + klass = default_process_class + klass = args.shift if args.first.is_a?(Class) + + # The properties are expected to be the next argument. + props = {} + props = args.shift if args.first.is_a?(Hash) + + process = klass.new(self, command, props, callback) processes << process - return process + run_next_process if processes.length == 1 + process end def subshell(command, &callback) execute(command, Net::SSH::Shell::Subshell, &callback) end def execute!(command, &callback) - execute(command, &callback) + process = execute(command, &callback) wait! - return process + process end def busy? opening? || processes.any? end @@ -90,11 +106,11 @@ end def child_finished(child) channel.on_close(&method(:on_channel_close)) if !channel.nil? processes.delete(child) - processes.first.run if processes.any? + run_next_process end def separator @separator ||= begin s = Digest::SHA1.hexdigest([session.object_id, object_id, Time.now.to_i, Time.now.usec, rand(0xFFFFFFFF)].join(":")) @@ -107,45 +123,59 @@ @channel = nil end private - def open_succeeded(channel) - @state = :pty - channel.on_close(&method(:on_channel_close)) - channel.request_pty(:modes => { Net::SSH::Connection::Term::ECHO => 0 }, &method(:pty_requested)) + def run_next_process + if processes.any? + process = processes.first + @on_process_run.call(self, process) if @on_process_run + process.run end + end - def open_failed(channel, code, description) - @state = :closed - raise "could not open channel for process manager (#{description}, ##{code})" - end + def open_succeeded(channel) + @state = :pty + channel.on_close(&method(:on_channel_close)) + channel.request_pty(:modes => { Net::SSH::Connection::Term::ECHO => 0 }, &method(:pty_requested)) + end - def pty_requested(channel, success) - @state = :shell - raise "could not request pty for process manager" unless success - if shell == :default - channel.send_channel_request("shell", &method(:shell_requested)) - else - channel.exec(shell, &method(:shell_requested)) - end + def open_failed(channel, code, description) + @state = :closed + raise "could not open channel for process manager (#{description}, ##{code})" + end + + def on_exit_status(channel, data) + unless data.read_long == 0 + raise "the shell exited unexpectedly" end + end - def shell_requested(channel, success) - @state = :initializing - raise "could not request shell for process manager" unless success - channel.on_data(&method(:look_for_initialization_done)) - channel.send_data "export PS1=; echo #{separator} $?\n" + def pty_requested(channel, success) + @state = :shell + raise "could not request pty for process manager" unless success + if shell == :default + channel.send_channel_request("shell", &method(:shell_requested)) + else + channel.exec(shell, &method(:shell_requested)) end + end - def look_for_initialization_done(channel, data) - if data.include?(separator) - @state = :open - @when_open.each { |callback| callback.call(self) } - @when_open.clear - end + def shell_requested(channel, success) + @state = :initializing + raise "could not request shell for process manager" unless success + channel.on_data(&method(:look_for_initialization_done)) + channel.send_data "export PS1=; echo #{separator} $?\n" + end + + def look_for_initialization_done(channel, data) + if data.include?(separator) + @state = :open + @when_open.each { |callback| callback.call(self) } + @when_open.clear end + end end end end class Net::SSH::Connection::Session @@ -153,8 +183,8 @@ # session. Yields the new shell if a block is given. Returns the shell # instance. def shell(*args) shell = Net::SSH::Shell.new(self, *args) yield shell if block_given? - return shell + shell end end