summaryrefslogtreecommitdiff
path: root/lib/net/ssh/multi/pending_connection.rb
blob: 5faaf705e4e38bc8ff945dfe8fe0ad42a7996176 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
require 'net/ssh/multi/channel_proxy'

module Net; module SSH; module Multi

  # A PendingConnection instance mimics a Net::SSH::Connection::Session instance,
  # without actually being an open connection to a server. It is used by
  # Net::SSH::Multi::Session when a concurrent connection limit is in effect,
  # so that a server can hang on to a "connection" that isn't really a connection.
  #
  # Any requests against this connection (like #open_channel or #send_global_request)
  # are not actually sent, but are added to a list of recordings. When the real
  # session is opened and replaces this pending connection, all recorded actions
  # will be replayed against that session.
  #
  # You'll never need to initialize one of these directly, and (if all goes well!)
  # should never even notice that one of these is in use. Net::SSH::Multi::Session
  # will instantiate these as needed, and only when there is a concurrent
  # connection limit.
  class PendingConnection
    # Represents a #open_channel action.
    class ChannelOpenRecording #:nodoc:
      attr_reader :type, :extras, :channel

      def initialize(type, extras, channel)
        @type, @extras, @channel = type, extras, channel
      end

      def replay_on(session)
        real_channel = session.open_channel(type, *extras, &channel.on_confirm)
        channel.delegate_to(real_channel)
      end
    end

    # Represents a #forward action.
    class ForwardRecording
      def initialize
        @recordings = []
      end

      def remote(port, host, remote_port, remote_host="127.0.0.1")
        @recordings << [:remote, port, host, remote_port, remote_host]
      end

      def replay_on(session)
        forward = session.forward
        @recordings.each { |args| forward.send(*args) }
      end
    end

    def forward
      forward = ForwardRecording.new
      @recordings << forward
      forward
    end

    # Represents a #send_global_request action.
    class SendGlobalRequestRecording #:nodoc:
      attr_reader :type, :extra, :callback

      def initialize(type, extra, callback)
        @type, @extra, @callback = type, extra, callback
      end

      def replay_on(session)
        session.send_global_request(type, *extra, &callback)
      end
    end

    # The Net::SSH::Multi::Server object that "owns" this pending connection.
    attr_reader :server

    # Instantiates a new pending connection for the given Net::SSH::Multi::Server
    # object.
    def initialize(server)
      @server = server
      @recordings = []
    end

    # Instructs the pending session to replay all of its recordings against the
    # given +session+, and to then replace itself with the given session.
    def replace_with(session)
      @recordings.each { |recording| recording.replay_on(session) }
      @server.replace_session(session)
    end

    # Records that a channel open request has been made, and returns a new
    # Net::SSH::Multi::ChannelProxy object to represent the (as yet unopened)
    # channel.
    def open_channel(type="session", *extras, &on_confirm)
      channel = ChannelProxy.new(&on_confirm)
      @recordings << ChannelOpenRecording.new(type, extras, channel)
      return channel
    end

    # Records that a global request has been made. The request is not actually
    # sent, and won't be until #replace_with is called.
    def send_global_request(type, *extra, &callback)
      @recordings << SendGlobalRequestRecording.new(type, extra, callback)
      self
    end

    # Always returns +true+, so that the pending connection looks active until
    # it can be truly opened and replaced with a real connection.
    def busy?(include_invisible=false)
      true
    end

    # Does nothing, except to make a pending connection quack like a real connection.
    def close
      self
    end

    # Returns an empty array, since a pending connection cannot have any real channels.
    def channels
      []
    end

    # Returns +true+, and does nothing else.
    def preprocess
      true
    end

    # Returns +true+, and does nothing else.
    def postprocess(readers, writers)
      true
    end

    # Returns an empty hash, since a pending connection has no real listeners.
    def listeners
      {}
    end
  end

end; end; end