net-sftp 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. data/CHANGELOG.rdoc +23 -0
  2. data/Manifest +55 -0
  3. data/README.rdoc +96 -0
  4. data/Rakefile +30 -0
  5. data/lib/net/sftp.rb +53 -38
  6. data/lib/net/sftp/constants.rb +187 -0
  7. data/lib/net/sftp/errors.rb +34 -20
  8. data/lib/net/sftp/operations/dir.rb +93 -0
  9. data/lib/net/sftp/operations/download.rb +364 -0
  10. data/lib/net/sftp/operations/file.rb +176 -0
  11. data/lib/net/sftp/operations/file_factory.rb +60 -0
  12. data/lib/net/sftp/operations/upload.rb +387 -0
  13. data/lib/net/sftp/packet.rb +21 -0
  14. data/lib/net/sftp/protocol.rb +32 -0
  15. data/lib/net/sftp/protocol/01/attributes.rb +265 -96
  16. data/lib/net/sftp/protocol/01/base.rb +268 -0
  17. data/lib/net/sftp/protocol/01/name.rb +43 -0
  18. data/lib/net/sftp/protocol/02/base.rb +31 -0
  19. data/lib/net/sftp/protocol/03/base.rb +35 -0
  20. data/lib/net/sftp/protocol/04/attributes.rb +120 -195
  21. data/lib/net/sftp/protocol/04/base.rb +94 -0
  22. data/lib/net/sftp/protocol/04/name.rb +67 -0
  23. data/lib/net/sftp/protocol/05/base.rb +66 -0
  24. data/lib/net/sftp/protocol/06/attributes.rb +107 -0
  25. data/lib/net/sftp/protocol/06/base.rb +63 -0
  26. data/lib/net/sftp/protocol/base.rb +50 -0
  27. data/lib/net/sftp/request.rb +91 -0
  28. data/lib/net/sftp/response.rb +76 -0
  29. data/lib/net/sftp/session.rb +914 -238
  30. data/lib/net/sftp/version.rb +14 -21
  31. data/net-sftp.gemspec +60 -0
  32. data/setup.rb +1331 -0
  33. data/test/common.rb +173 -0
  34. data/test/protocol/01/test_attributes.rb +97 -0
  35. data/test/protocol/01/test_base.rb +210 -0
  36. data/test/protocol/01/test_name.rb +27 -0
  37. data/test/protocol/02/test_base.rb +26 -0
  38. data/test/protocol/03/test_base.rb +27 -0
  39. data/test/protocol/04/test_attributes.rb +148 -0
  40. data/test/protocol/04/test_base.rb +74 -0
  41. data/test/protocol/04/test_name.rb +49 -0
  42. data/test/protocol/05/test_base.rb +62 -0
  43. data/test/protocol/06/test_attributes.rb +124 -0
  44. data/test/protocol/06/test_base.rb +51 -0
  45. data/test/protocol/test_base.rb +42 -0
  46. data/test/test_all.rb +3 -0
  47. data/test/test_dir.rb +47 -0
  48. data/test/test_download.rb +252 -0
  49. data/test/test_file.rb +159 -0
  50. data/test/test_file_factory.rb +48 -0
  51. data/test/test_packet.rb +9 -0
  52. data/test/test_protocol.rb +17 -0
  53. data/test/test_request.rb +71 -0
  54. data/test/test_response.rb +53 -0
  55. data/test/test_session.rb +741 -0
  56. data/test/test_upload.rb +219 -0
  57. metadata +59 -111
  58. data/doc/LICENSE-BSD +0 -27
  59. data/doc/LICENSE-GPL +0 -280
  60. data/doc/LICENSE-RUBY +0 -56
  61. data/doc/faq/faq.html +0 -298
  62. data/doc/faq/faq.rb +0 -154
  63. data/doc/faq/faq.yml +0 -183
  64. data/examples/asynchronous.rb +0 -57
  65. data/examples/get-put.rb +0 -45
  66. data/examples/sftp-open-uri.rb +0 -30
  67. data/examples/ssh-service.rb +0 -30
  68. data/examples/synchronous.rb +0 -131
  69. data/lib/net/sftp/operations/abstract.rb +0 -108
  70. data/lib/net/sftp/operations/close.rb +0 -31
  71. data/lib/net/sftp/operations/errors.rb +0 -76
  72. data/lib/net/sftp/operations/fsetstat.rb +0 -36
  73. data/lib/net/sftp/operations/fstat.rb +0 -32
  74. data/lib/net/sftp/operations/lstat.rb +0 -31
  75. data/lib/net/sftp/operations/mkdir.rb +0 -33
  76. data/lib/net/sftp/operations/open.rb +0 -32
  77. data/lib/net/sftp/operations/opendir.rb +0 -32
  78. data/lib/net/sftp/operations/read.rb +0 -88
  79. data/lib/net/sftp/operations/readdir.rb +0 -55
  80. data/lib/net/sftp/operations/realpath.rb +0 -37
  81. data/lib/net/sftp/operations/remove.rb +0 -31
  82. data/lib/net/sftp/operations/rename.rb +0 -32
  83. data/lib/net/sftp/operations/rmdir.rb +0 -31
  84. data/lib/net/sftp/operations/services.rb +0 -42
  85. data/lib/net/sftp/operations/setstat.rb +0 -33
  86. data/lib/net/sftp/operations/stat.rb +0 -31
  87. data/lib/net/sftp/operations/write.rb +0 -63
  88. data/lib/net/sftp/protocol/01/impl.rb +0 -251
  89. data/lib/net/sftp/protocol/01/packet-assistant.rb +0 -82
  90. data/lib/net/sftp/protocol/01/services.rb +0 -47
  91. data/lib/net/sftp/protocol/02/impl.rb +0 -39
  92. data/lib/net/sftp/protocol/02/packet-assistant.rb +0 -32
  93. data/lib/net/sftp/protocol/02/services.rb +0 -44
  94. data/lib/net/sftp/protocol/03/impl.rb +0 -42
  95. data/lib/net/sftp/protocol/03/packet-assistant.rb +0 -35
  96. data/lib/net/sftp/protocol/03/services.rb +0 -44
  97. data/lib/net/sftp/protocol/04/impl.rb +0 -86
  98. data/lib/net/sftp/protocol/04/packet-assistant.rb +0 -45
  99. data/lib/net/sftp/protocol/04/services.rb +0 -44
  100. data/lib/net/sftp/protocol/05/impl.rb +0 -90
  101. data/lib/net/sftp/protocol/05/packet-assistant.rb +0 -34
  102. data/lib/net/sftp/protocol/05/services.rb +0 -44
  103. data/lib/net/sftp/protocol/constants.rb +0 -60
  104. data/lib/net/sftp/protocol/driver.rb +0 -235
  105. data/lib/net/sftp/protocol/packet-assistant.rb +0 -84
  106. data/lib/net/sftp/protocol/services.rb +0 -55
  107. data/lib/uri/open-sftp.rb +0 -54
  108. data/lib/uri/sftp.rb +0 -42
  109. data/test/ALL-TESTS.rb +0 -23
  110. data/test/operations/tc_abstract.rb +0 -124
  111. data/test/operations/tc_close.rb +0 -40
  112. data/test/operations/tc_fsetstat.rb +0 -48
  113. data/test/operations/tc_fstat.rb +0 -40
  114. data/test/operations/tc_lstat.rb +0 -40
  115. data/test/operations/tc_mkdir.rb +0 -48
  116. data/test/operations/tc_open.rb +0 -42
  117. data/test/operations/tc_opendir.rb +0 -40
  118. data/test/operations/tc_read.rb +0 -103
  119. data/test/operations/tc_readdir.rb +0 -88
  120. data/test/operations/tc_realpath.rb +0 -54
  121. data/test/operations/tc_remove.rb +0 -40
  122. data/test/operations/tc_rmdir.rb +0 -40
  123. data/test/operations/tc_setstat.rb +0 -48
  124. data/test/operations/tc_stat.rb +0 -40
  125. data/test/operations/tc_write.rb +0 -91
  126. data/test/protocol/01/tc_attributes.rb +0 -138
  127. data/test/protocol/01/tc_impl.rb +0 -294
  128. data/test/protocol/01/tc_packet_assistant.rb +0 -81
  129. data/test/protocol/02/tc_impl.rb +0 -41
  130. data/test/protocol/02/tc_packet_assistant.rb +0 -31
  131. data/test/protocol/03/tc_impl.rb +0 -48
  132. data/test/protocol/03/tc_packet_assistant.rb +0 -34
  133. data/test/protocol/04/tc_attributes.rb +0 -174
  134. data/test/protocol/04/tc_impl.rb +0 -91
  135. data/test/protocol/04/tc_packet_assistant.rb +0 -38
  136. data/test/protocol/05/tc_impl.rb +0 -61
  137. data/test/protocol/05/tc_packet_assistant.rb +0 -32
  138. data/test/protocol/tc_driver.rb +0 -219
@@ -0,0 +1,91 @@
1
+ require 'net/sftp/constants'
2
+ require 'net/sftp/response'
3
+
4
+ module Net; module SFTP
5
+
6
+ # Encapsulates a single active SFTP request. This is instantiated
7
+ # automatically by the Net::SFTP::Session class when an operation is
8
+ # executed.
9
+ #
10
+ # request = sftp.open("/path/to/file")
11
+ # puts request.pending? #-> true
12
+ # request.wait
13
+ # puts request.pending? #-> false
14
+ # result = request.response
15
+ class Request
16
+ include Constants::PacketTypes
17
+
18
+ # The Net::SFTP session object that is servicing this request
19
+ attr_reader :session
20
+
21
+ # The SFTP packet identifier for this request
22
+ attr_reader :id
23
+
24
+ # The type of this request (e.g., :open, :symlink, etc.)
25
+ attr_reader :type
26
+
27
+ # The callback (if any) associated with this request. When the response
28
+ # is recieved for this request, the callback will be invoked.
29
+ attr_reader :callback
30
+
31
+ # The hash of properties associated with this request. Properties allow
32
+ # programmers to associate arbitrary data with a request, making state
33
+ # machines richer.
34
+ attr_reader :properties
35
+
36
+ # The response that was received for this request (see Net::SFTP::Response)
37
+ attr_reader :response
38
+
39
+ # Instantiate a new Request object, serviced by the given +session+, and
40
+ # being of the given +type+. The +id+ is the packet identifier for this
41
+ # request.
42
+ def initialize(session, type, id, &callback) #:nodoc:
43
+ @session, @id, @type, @callback = session, id, type, callback
44
+ @response = nil
45
+ @properties = {}
46
+ end
47
+
48
+ # Returns the value of property with the given +key+. If +key+ is not a
49
+ # symbol, it will be converted to a symbol before lookup.
50
+ def [](key)
51
+ properties[key.to_sym]
52
+ end
53
+
54
+ # Sets the value of the property with name +key+ to +value+. If +key+ is
55
+ # not a symbol, it will be converted to a symbol before lookup.
56
+ def []=(key, value)
57
+ properties[key.to_sym] = value
58
+ end
59
+
60
+ # Returns +true+ if the request is still waiting for a response from the
61
+ # server, and +false+ otherwise. The SSH event loop must be run in order
62
+ # for a request to be processed; see #wait.
63
+ def pending?
64
+ session.pending_requests.key?(id)
65
+ end
66
+
67
+ # Waits (blocks) until the server responds to this packet. If prior
68
+ # SFTP packets were also pending, they will be processed as well (since
69
+ # SFTP packets are processed in the order in which they are received by
70
+ # the server). Returns the request object itself.
71
+ def wait
72
+ session.loop { pending? }
73
+ self
74
+ end
75
+
76
+ public # but not "published". Internal use only
77
+
78
+ # When the server responds to this request, the packet is passed to
79
+ # this method, which parses the packet and builds a Net::SFTP::Response
80
+ # object to encapsulate it. If a #callback has been provided for this
81
+ # request, the callback is invoked with the new response object.
82
+ def respond_to(packet) #:nodoc:
83
+ data = session.protocol.parse(packet)
84
+ data[:type] = packet.type
85
+ @response = Response.new(self, data)
86
+
87
+ callback.call(@response) if callback
88
+ end
89
+ end
90
+
91
+ end; end
@@ -0,0 +1,76 @@
1
+ require 'net/sftp/constants'
2
+
3
+ module Net; module SFTP
4
+
5
+ # Encapsulates a response from the remote server, to a specific client
6
+ # request. Response objects are passed as parameters to callbacks when you
7
+ # are performing asynchronous operations; when you call Net::SFTP::Request#wait,
8
+ # you can get the corresponding response object via Net::SFTP::Request#response.
9
+ #
10
+ # sftp.open("/path/to/file") do |response|
11
+ # p response.ok?
12
+ # p response[:handle]
13
+ # end
14
+ #
15
+ # sftp.loop
16
+ class Response
17
+ include Net::SFTP::Constants::StatusCodes
18
+
19
+ # The request object that this object is in response to
20
+ attr_reader :request
21
+
22
+ # A hash of request-specific data, such as a file handle or attribute information
23
+ attr_reader :data
24
+
25
+ # The numeric code, one of the FX_* constants
26
+ attr_reader :code
27
+
28
+ # The textual message for this response (possibly blank)
29
+ attr_reader :message
30
+
31
+ # Create a new Response object for the given Net::SFTP::Request instance,
32
+ # and with the given data. If there is no :code key in the data, the
33
+ # code is assumed to be FX_OK.
34
+ def initialize(request, data={}) #:nodoc:
35
+ @request, @data = request, data
36
+ @code, @message = data[:code] || FX_OK, data[:message]
37
+ end
38
+
39
+ # Retrieve the data item with the given +key+. The key is converted to a
40
+ # symbol before being used to lookup the value.
41
+ def [](key)
42
+ data[key.to_sym]
43
+ end
44
+
45
+ # Returns a textual description of this response, including the status
46
+ # code and name.
47
+ def to_s
48
+ if message && !message.empty? && message.downcase != MAP[code]
49
+ "#{message} (#{MAP[code]}, #{code})"
50
+ else
51
+ "#{MAP[code]} (#{code})"
52
+ end
53
+ end
54
+
55
+ alias :to_str :to_s
56
+
57
+ # Returns +true+ if the status code is FX_OK; +false+ otherwise.
58
+ def ok?
59
+ code == FX_OK
60
+ end
61
+
62
+ # Returns +true+ if the status code is FX_EOF; +false+ otherwise.
63
+ def eof?
64
+ code == FX_EOF
65
+ end
66
+
67
+ #--
68
+ MAP = constants.inject({}) do |memo, name|
69
+ next memo unless name =~ /^FX_(.*)/
70
+ memo[const_get(name)] = $1.downcase.tr("_", " ")
71
+ memo
72
+ end
73
+ #++
74
+ end
75
+
76
+ end; end
@@ -1,275 +1,951 @@
1
- #--
2
- # =============================================================================
3
- # Copyright (c) 2004, Jamis Buck (jamis@37signals.com)
4
- # All rights reserved.
5
- #
6
- # This source file is distributed as part of the Net::SFTP Secure FTP Client
7
- # library for Ruby. This file (and the library as a whole) may be used only as
8
- # allowed by either the BSD license, or the Ruby license (or, by association
9
- # with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SFTP
10
- # distribution for the texts of these licenses.
11
- # -----------------------------------------------------------------------------
12
- # net-sftp website: http://net-ssh.rubyforge.org/sftp
13
- # project website : http://rubyforge.org/projects/net-ssh
14
- # =============================================================================
15
- #++
16
-
1
+ require 'net/ssh'
2
+ require 'net/sftp/constants'
17
3
  require 'net/sftp/errors'
18
- require 'net/sftp/protocol/constants'
4
+ require 'net/sftp/protocol'
5
+ require 'net/sftp/request'
6
+ require 'net/sftp/operations/dir'
7
+ require 'net/sftp/operations/upload'
8
+ require 'net/sftp/operations/download'
9
+ require 'net/sftp/operations/file_factory'
10
+
11
+ module Net; module SFTP
12
+
13
+ # The Session class encapsulates a single SFTP channel on a Net::SSH
14
+ # connection. Instances of this class are what most applications will
15
+ # interact with most, as it provides access to both low-level (mkdir,
16
+ # rename, remove, symlink, etc.) and high-level (upload, download, etc.)
17
+ # SFTP operations.
18
+ #
19
+ # Although Session makes it easy to do SFTP operations serially, you can
20
+ # also set up multiple operations to be done in parallel, too, without
21
+ # needing to resort to threading. You merely need to fire off the requests,
22
+ # and then run the event loop until all of the requests have completed:
23
+ #
24
+ # handle1 = sftp.open!("/path/to/file1")
25
+ # handle2 = sftp.open!("/path/to/file2")
26
+ #
27
+ # r1 = sftp.read(handle1, 0, 1024)
28
+ # r2 = sftp.read(handle2, 0, 1024)
29
+ # sftp.loop { [r1, r2].any? { |r| r.pending? } }
30
+ #
31
+ # puts "chunk #1: #{r1.response[:data]}"
32
+ # puts "chunk #2: #{r2.response[:data]}"
33
+ #
34
+ # By passing blocks to the operations, you can set up powerful state
35
+ # machines, to fire off subsequent operations. In fact, the Net::SFTP::Operations::Upload
36
+ # and Net::SFTP::Operations::Download classes set up such state machines, so that
37
+ # multiple uploads and/or downloads can be running simultaneously.
38
+ #
39
+ # The convention with the names of the operations is as follows: if the method
40
+ # name ends with an exclamation mark, like #read!, it will be synchronous
41
+ # (e.g., it will block until the server responds). Methods without an
42
+ # exclamation mark (e.g. #read) are asynchronous, and return before the
43
+ # server has responded. You will need to make sure the SSH event loop is
44
+ # run in order to process these requests. (See #loop.)
45
+ class Session
46
+ include Net::SSH::Loggable
47
+ include Net::SFTP::Constants::PacketTypes
19
48
 
20
- module Net ; module SFTP
49
+ # The highest protocol version supported by the Net::SFTP library.
50
+ HIGHEST_PROTOCOL_VERSION_SUPPORTED = 6
21
51
 
22
- # Represents a single SFTP session, running atop an SSH session.
23
- class Session
24
- include Protocol::Constants
25
-
26
- # remove this, inherited from Kernel, so that sftp.open will go via
27
- # method_missing
28
- undef :open
29
-
30
- # The status of the last synchronously executed operation. This is either
31
- # +nil+, or an object that responds to <tt>:code</tt>, <tt>:message</tt>,
32
- # and <tt>:language</tt>.
33
- attr_accessor :status
34
-
35
- # Create a new SFTP session on top of the given SSH session.
36
- def initialize( session )
37
- @session = session
38
- @log = @session.registry.log_for( "sftp.session" )
39
-
40
- @session.registry.namespace_define :sftp do |ns|
41
- ns.require "net/sftp/protocol/services", "Net::SFTP::Protocol"
42
- ns.require "net/sftp/operations/services", "Net::SFTP::Operations"
43
-
44
- # register a reference to myself for other services to be able to
45
- # access me.
46
- ns.session( :pipeline => [] ) { self }
47
-
48
- @driver = ns.protocol.driver
49
- @driver.on_open do |d|
50
- d.on_attrs( &method( :do_attrs ))
51
- d.on_data( &method( :do_data ))
52
- d.on_handle(&method( :do_handle ))
53
- d.on_name( &method( :do_name ))
54
- d.on_status(&method( :do_status ))
55
-
56
- if block_given?
57
- begin
58
- yield self
59
- ensure
60
- d.close
61
- end
62
- end
63
- end
52
+ # A reference to the Net::SSH session object that powers this SFTP session.
53
+ attr_reader :session
64
54
 
65
- @operations = ns.operations
66
- end
55
+ # The Net::SSH::Connection::Channel object that the SFTP session is being
56
+ # processed by.
57
+ attr_reader :channel
67
58
 
68
- @requests = Hash.new
59
+ # The state of the SFTP connection. It will be :opening, :subsystem, :init,
60
+ # :open, or :closed.
61
+ attr_reader :state
69
62
 
70
- @session.loop if block_given?
71
- end
63
+ # The protocol instance being used by this SFTP session. Useful for
64
+ # querying the protocol version in effect.
65
+ attr_reader :protocol
72
66
 
73
- #--
74
- # ====================================================================
75
- # QUERIES/GETTERS/SETTERS/ACTIONS
76
- # ====================================================================
77
- #++
67
+ # The hash of pending requests. Any requests that have been sent and which
68
+ # the server has not yet responded to will be represented here.
69
+ attr_reader :pending_requests
78
70
 
79
- # Return the state of the SFTP connection. (See
80
- # Net::SFTP::Protocol::Driver#state.)
81
- def state
82
- @driver.state
71
+ # Creates a new Net::SFTP instance atop the given Net::SSH connection.
72
+ # This will return immediately, before the SFTP connection has been properly
73
+ # initialized. Once the connection is ready, the given block will be called.
74
+ # If you want to block until the connection has been initialized, try this:
75
+ #
76
+ # sftp = Net::SFTP::Session.new(ssh)
77
+ # sftp.loop { sftp.opening? }
78
+ def initialize(session, &block)
79
+ @session = session
80
+ @input = Net::SSH::Buffer.new
81
+ self.logger = session.logger
82
+ @state = :closed
83
+
84
+ connect(&block)
83
85
  end
84
86
 
85
- # Return the underlying SSH channel that supports this SFTP connection.
86
- # Useful for adding custom callbacks for some events, or for accessing
87
- # the underlying connection beneath the channel.
88
- def channel
89
- @driver.channel
90
- end
87
+ public # high-level SFTP operations
88
+
89
+ # Initiates an upload from +local+ to +remote+, asynchronously. This
90
+ # method will return a new Net::SFTP::Operations::Upload instance, and requires
91
+ # the event loop to be run in order for the upload to progress. See
92
+ # Net::SFTP::Operations::Upload for a full discussion of how this method can be
93
+ # used.
94
+ #
95
+ # uploader = sftp.upload("/local/path", "/remote/path")
96
+ # uploader.wait
97
+ def upload(local, remote, options={}, &block)
98
+ Operations::Upload.new(self, local, remote, options, &block)
99
+ end
91
100
 
92
- # Closes the SFTP connection, but leaves the SSH connection open.
93
- def close_channel
94
- @driver.close
95
- end
101
+ # Identical to #upload, but blocks until the upload is complete.
102
+ def upload!(local, remote, options={}, &block)
103
+ upload(local, remote, options, &block).wait
104
+ end
96
105
 
97
- # Closes the underlying SSH connection.
98
- def close
99
- @session.close
100
- end
106
+ # Initiates a download from +remote+ to +local+, asynchronously. This
107
+ # method will return a new Net::SFTP::Operations::Download instance, and requires
108
+ # that the event loop be run in order for the download to progress. See
109
+ # Net::SFTP::Operations::Download for a full discussion of hos this method can be
110
+ # used.
111
+ #
112
+ # download = sftp.download("/remote/path", "/local/path")
113
+ # download.wait
114
+ def download(remote, local, options={}, &block)
115
+ Operations::Download.new(self, local, remote, options, &block)
116
+ end
101
117
 
102
- # Registers the given handler with the given request id. This is used
103
- # internally by the operations, so that the session knows who to delegate
104
- # a response to that has been received from the server.
105
- def register( id, handler )
106
- @requests[ id ] = handler
107
- end
118
+ # Identical to #download, but blocks until the download is complete.
119
+ # If +local+ is omitted, downloads the file to an in-memory buffer
120
+ # and returns the result as a string; otherwise, returns the
121
+ # Net::SFTP::Operations::Download instance.
122
+ def download!(remote, local=nil, options={}, &block)
123
+ destination = local || StringIO.new
124
+ result = download(remote, destination, options, &block).wait
125
+ local ? result : destination.string
126
+ end
108
127
 
109
- # Delegates to Net::SSH::Session#loop. Causes the underlying SSH
110
- # connection to process events as long as the given block returns +true+,
111
- # or (if no block is given) until there are no more open channels.
112
- def loop( &block )
113
- @session.loop( &block )
114
- end
128
+ # Returns an Net::SFTP::Operations::FileFactory instance, which can be used to
129
+ # mimic synchronous, IO-like file operations on a remote file via
130
+ # SFTP.
131
+ #
132
+ # sftp.file.open("/path/to/file") do |file|
133
+ # while line = file.gets
134
+ # puts line
135
+ # end
136
+ # end
137
+ #
138
+ # See Net::SFTP::Operations::FileFactory and Net::SFTP::Operations::File for more details.
139
+ def file
140
+ @file ||= Operations::FileFactory.new(self)
141
+ end
115
142
 
116
- # Waits for the underlying driver to reach a state of :open (or :closed).
117
- # This makes it easier to use the SFTP routines synchronously without using
118
- # the block form:
119
- #
120
- # sftp = Net::SFTP::Session.new( ssh_session )
121
- # sftp.connect
122
- # puts sftp.realpath( "." )
123
- #
124
- # Without the call to #connect, the call to #realpath would fail because
125
- # the SFTP protocol has not yet been negotiated and no underlying driver has
126
- # been selected.
127
- #
128
- # If no block is given, it returns +self+, so it can be chained easily to
129
- # other method calls. If a block _is_ given, the session is yielded to the
130
- # block as soon as the driver successfully reports it's state as +open+,
131
- # with the session's channel being closed automatically when the block
132
- # finishes.
133
- #
134
- # require 'net/ssh'
135
- # require 'net/sftp'
136
- #
137
- # Net::SSH.start( 'localhost' ) do |session|
138
- # session.sftp.connect do |sftp|
139
- # puts sftp.realpath( "." )
140
- # end
141
- # end
142
- def connect
143
- @session.loop do
144
- @driver.state != :open &&
145
- @driver.state != :closed
146
- end
147
- if @driver.state == :open && block_given?
148
- begin
149
- yield self
150
- ensure
151
- close_channel
143
+ # Returns a Net::SFTP::Operations::Dir instance, which can be used to
144
+ # conveniently iterate over and search directories on the remote server.
145
+ #
146
+ # sftp.dir.glob("/base/path", "*/**/*.rb") do |entry|
147
+ # p entry.name
148
+ # end
149
+ #
150
+ # See Net::SFTP::Operations::Dir for a more detailed discussion of how
151
+ # to use this.
152
+ def dir
153
+ @dir ||= Operations::Dir.new(self)
154
+ end
155
+
156
+ public # low-level SFTP operations
157
+
158
+ # :call-seq:
159
+ # open(path, flags="r", options={}) -> request
160
+ # open(path, flags="r", options={}) { |response| ... } -> request
161
+ #
162
+ # Opens a file on the remote server. The +flags+ parameter determines
163
+ # how the flag is open, and accepts the same format as IO#open (e.g.,
164
+ # either a string like "r" or "w", or a combination of the IO constants).
165
+ # The +options+ parameter is a hash of attributes to be associated
166
+ # with the file, and varies greatly depending on the SFTP protocol
167
+ # version in use, but some (like :permissions) are always available.
168
+ #
169
+ # Returns immediately with a Request object. If a block is given, it will
170
+ # be invoked when the server responds, with a Response object as the only
171
+ # parameter. The :handle property of the response is the handle of the
172
+ # opened file, and may be passed to other methods (like #close, #read,
173
+ # #write, and so forth).
174
+ #
175
+ # sftp.open("/path/to/file") do |response|
176
+ # raise "fail!" unless response.ok?
177
+ # sftp.close(response[:handle])
178
+ # end
179
+ # sftp.loop
180
+ def open(path, flags="r", options={}, &callback)
181
+ request :open, path, flags, options, &callback
182
+ end
183
+
184
+ # Identical to #open, but blocks until the server responds. It will raise
185
+ # a StatusException if the request was unsuccessful. Otherwise, it will
186
+ # return the handle of the newly opened file.
187
+ #
188
+ # handle = sftp.open!("/path/to/file")
189
+ def open!(path, flags="r", options={}, &callback)
190
+ wait_for(open(path, flags, options, &callback), :handle)
191
+ end
192
+
193
+ # :call-seq:
194
+ # close(handle) -> request
195
+ # close(handle) { |response| ... } -> request
196
+ #
197
+ # Closes an open handle, whether obtained via #open, or #opendir. Returns
198
+ # immediately with a Request object. If a block is given, it will be
199
+ # invoked when the server responds.
200
+ #
201
+ # sftp.open("/path/to/file") do |response|
202
+ # raise "fail!" unless response.ok?
203
+ # sftp.close(response[:handle])
204
+ # end
205
+ # sftp.loop
206
+ def close(handle, &callback)
207
+ request :close, handle, &callback
208
+ end
209
+
210
+ # Identical to #close, but blocks until the server responds. It will
211
+ # raise a StatusException if the request was unsuccessful. Otherwise,
212
+ # it returns the Response object for this request.
213
+ #
214
+ # sftp.close!(handle)
215
+ def close!(handle, &callback)
216
+ wait_for(close(handle, &callback))
217
+ end
218
+
219
+ # :call-seq:
220
+ # read(handle, offset, length) -> request
221
+ # read(handle, offset, length) { |response| ... } -> request
222
+ #
223
+ # Requests that +length+ bytes, starting at +offset+ bytes from the
224
+ # beginning of the file, be read from the file identified by
225
+ # +handle+. (The +handle+ should be a value obtained via the #open
226
+ # method.) Returns immediately with a Request object. If a block is
227
+ # given, it will be invoked when the server responds.
228
+ #
229
+ # The :data property of the response will contain the requested data,
230
+ # assuming the call was successful.
231
+ #
232
+ # request = sftp.read(handle, 0, 1024) do |response|
233
+ # if response.eof?
234
+ # puts "end of file reached before reading any data"
235
+ # elsif !response.ok?
236
+ # puts "error (#{response})"
237
+ # else
238
+ # print(response[:data])
239
+ # end
240
+ # end
241
+ # request.wait
242
+ #
243
+ # To read an entire file will usually require multiple calls to #read,
244
+ # unless you know in advance how large the file is.
245
+ def read(handle, offset, length, &callback)
246
+ request :read, handle, offset, length, &callback
247
+ end
248
+
249
+ # Identical to #read, but blocks until the server responds. It will raise
250
+ # a StatusException if the request was unsuccessful. If the end of the file
251
+ # was reached, +nil+ will be returned. Otherwise, it returns the data that
252
+ # was read, as a String.
253
+ #
254
+ # data = sftp.read!(handle, 0, 1024)
255
+ def read!(handle, offset, length, &callback)
256
+ wait_for(read(handle, offset, length, &callback), :data)
257
+ end
258
+
259
+ # :call-seq:
260
+ # write(handle, offset, data) -> request
261
+ # write(handle, offset, data) { |response| ... } -> request
262
+ #
263
+ # Requests that +data+ be written to the file identified by +handle+,
264
+ # starting at +offset+ bytes from the start of the file. The file must
265
+ # have been opened for writing via #open. Returns immediately with a
266
+ # Request object. If a block is given, it will be invoked when the
267
+ # server responds.
268
+ #
269
+ # request = sftp.write(handle, 0, "hello, world!\n")
270
+ # request.wait
271
+ def write(handle, offset, data, &callback)
272
+ request :write, handle, offset, data, &callback
273
+ end
274
+
275
+ # Identical to #write, but blocks until the server responds. It will raise
276
+ # a StatusException if the request was unsuccessful, or the end of the file
277
+ # was reached. Otherwise, it returns the Response object for this request.
278
+ #
279
+ # sftp.write!(handle, 0, "hello, world!\n")
280
+ def write!(handle, offset, data, &callback)
281
+ wait_for(write(handle, offset, data, &callback))
282
+ end
283
+
284
+ # :call-seq:
285
+ # lstat(path, flags=nil) -> request
286
+ # lstat(path, flags=nil) { |response| ... } -> request
287
+ #
288
+ # This method is identical to the #stat method, with the exception that
289
+ # it will not follow symbolic links (thus allowing you to stat the
290
+ # link itself, rather than what it refers to). The +flags+ parameter
291
+ # is not used in SFTP protocol versions prior to 4, and will be ignored
292
+ # in those versions of the protocol that do not use it. For those that
293
+ # do, however, you may provide hints as to which file proprties you wish
294
+ # to query (e.g., if all you want is permissions, you could pass the
295
+ # Net::SFTP::Protocol::V04::Attributes::F_PERMISSIONS flag as the value
296
+ # for the +flags+ parameter).
297
+ #
298
+ # The method returns immediately with a Request object. If a block is given,
299
+ # it will be invoked when the server responds. The :attrs property of
300
+ # the response will contain an Attributes instance appropriate for the
301
+ # the protocol version (see Protocol::V01::Attributes, Protocol::V04::Attributes,
302
+ # and Protocol::V06::Attributes).
303
+ #
304
+ # request = sftp.lstat("/path/to/file") do |response|
305
+ # raise "fail!" unless response.ok?
306
+ # puts "permissions: %04o" % response[:attrs].permissions
307
+ # end
308
+ # request.wait
309
+ def lstat(path, flags=nil, &callback)
310
+ request :lstat, path, flags, &callback
311
+ end
312
+
313
+ # Identical to the #lstat method, but blocks until the server responds.
314
+ # It will raise a StatusException if the request was unsuccessful.
315
+ # Otherwise, it will return the attribute object describing the path.
316
+ #
317
+ # puts sftp.lstat!("/path/to/file").permissions
318
+ def lstat!(path, flags=nil, &callback)
319
+ wait_for(lstat(path, flags, &callback), :attrs)
320
+ end
321
+
322
+ # The fstat method is identical to the #stat and #lstat methods, with
323
+ # the exception that it takes a +handle+ as the first parameter, such
324
+ # as would be obtained via the #open or #opendir methods. (See the #lstat
325
+ # method for full documentation).
326
+ def fstat(handle, flags=nil, &callback)
327
+ request :fstat, handle, flags, &callback
328
+ end
329
+
330
+ # Identical to the #fstat method, but blocks until the server responds.
331
+ # It will raise a StatusException if the request was unsuccessful.
332
+ # Otherwise, it will return the attribute object describing the path.
333
+ #
334
+ # puts sftp.fstat!(handle).permissions
335
+ def fstat!(handle, flags=nil, &callback)
336
+ wait_for(fstat(handle, flags, &callback), :attrs)
337
+ end
338
+
339
+ # :call-seq:
340
+ # setstat(path, attrs) -> request
341
+ # setstat(path, attrs) { |response| ... } -> request
342
+ #
343
+ # This method may be used to set file metadata (such as permissions, or
344
+ # user/group information) on a remote file. The exact metadata that may
345
+ # be tweaked is dependent on the SFTP protocol version in use, but in
346
+ # general you may set at least the permissions, user, and group. (See
347
+ # Protocol::V01::Attributes, Protocol::V04::Attributes, and Protocol::V06::Attributes
348
+ # for the full lists of attributes that may be set for the different
349
+ # protocols.)
350
+ #
351
+ # The +attrs+ parameter is a hash, where the keys are symbols identifying
352
+ # the attributes to set.
353
+ #
354
+ # The method returns immediately with a Request object. If a block is given,
355
+ # it will be invoked when the server responds.
356
+ #
357
+ # request = sftp.setstat("/path/to/file", :permissions => 0644)
358
+ # request.wait
359
+ # puts "success: #{request.response.ok?}"
360
+ def setstat(path, attrs, &callback)
361
+ request :setstat, path, attrs, &callback
362
+ end
363
+
364
+ # Identical to the #setstat method, but blocks until the server responds.
365
+ # It will raise a StatusException if the request was unsuccessful.
366
+ # Otherwise, it will return the Response object for the request.
367
+ #
368
+ # sftp.setstat!("/path/to/file", :permissions => 0644)
369
+ def setstat!(path, attrs, &callback)
370
+ wait_for(setstat(path, attrs, &callback))
371
+ end
372
+
373
+ # The fsetstat method is identical to the #setstat method, with the
374
+ # exception that it takes a +handle+ as the first parameter, such as
375
+ # would be obtained via the #open or #opendir methods. (See the
376
+ # #setstat method for full documentation.)
377
+ def fsetstat(handle, attrs, &callback)
378
+ request :fsetstat, handle, attrs, &callback
379
+ end
380
+
381
+ # Identical to the #fsetstat method, but blocks until the server responds.
382
+ # It will raise a StatusException if the request was unsuccessful.
383
+ # Otherwise, it will return the Response object for the request.
384
+ #
385
+ # sftp.fsetstat!(handle, :permissions => 0644)
386
+ def fsetstat!(handle, attrs, &callback)
387
+ wait_for(fsetstat(handle, attrs, &callback))
388
+ end
389
+
390
+ # :call-seq:
391
+ # opendir(path) -> request
392
+ # opendir(path) { |response| ... } -> request
393
+ #
394
+ # Attempts to open a directory on the remote host for reading. Once the
395
+ # handle is obtained, directory entries may be retrieved using the
396
+ # #readdir method. The method returns immediately with a Request object.
397
+ # If a block is given, it will be invoked when the server responds.
398
+ #
399
+ # sftp.opendir("/path/to/directory") do |response|
400
+ # raise "fail!" unless response.ok?
401
+ # sftp.close(response[:handle])
402
+ # end
403
+ # sftp.loop
404
+ def opendir(path, &callback)
405
+ request :opendir, path, &callback
406
+ end
407
+
408
+ # Identical to #opendir, but blocks until the server responds. It will raise
409
+ # a StatusException if the request was unsuccessful. Otherwise, it will
410
+ # return a handle to the given path.
411
+ #
412
+ # handle = sftp.opendir!("/path/to/directory")
413
+ def opendir!(path, &callback)
414
+ wait_for(opendir(path, &callback), :handle)
415
+ end
416
+
417
+ # :call-seq:
418
+ # readdir(handle) -> request
419
+ # raeddir(handle) { |response| ... } -> request
420
+ #
421
+ # Reads a set of entries from the given directory handle (which must
422
+ # have been obtained via #opendir). If the response is EOF, then there
423
+ # are no more entries in the directory. Otherwise, the entries will be
424
+ # in the :names property of the response:
425
+ #
426
+ # loop do
427
+ # request = sftp.readdir(handle).wait
428
+ # break if request.response.eof?
429
+ # raise "fail!" unless request.response.ok?
430
+ # request.response[:names].each do |entry|
431
+ # puts entry.name
432
+ # end
433
+ # end
434
+ #
435
+ # See also Protocol::V01::Name and Protocol::V04::Name for the specific
436
+ # properties of each individual entry (which vary based on the SFTP
437
+ # protocol version in use).
438
+ def readdir(handle, &callback)
439
+ request :readdir, handle, &callback
440
+ end
441
+
442
+ # Identical to #readdir, but blocks until the server responds. It will raise
443
+ # a StatusException if the request was unsuccessful. Otherwise, it will
444
+ # return nil if there were no more names to read, or an array of name
445
+ # entries.
446
+ #
447
+ # while (entries = sftp.readdir!(handle)) do
448
+ # entries.each { |entry| puts(entry.name) }
449
+ # end
450
+ def readdir!(handle, &callback)
451
+ wait_for(readdir(handle, &callback), :names)
452
+ end
453
+
454
+ # :call-seq:
455
+ # remove(filename) -> request
456
+ # remove(filename) { |response| ... } -> request
457
+ #
458
+ # Attempts to remove the given file from the remote file system. Returns
459
+ # immediately with a Request object. If a block is given, the block will
460
+ # be invoked when the server responds, and will be passed a Response
461
+ # object.
462
+ #
463
+ # sftp.remove("/path/to/file").wait
464
+ def remove(filename, &callback)
465
+ request :remove, filename, &callback
466
+ end
467
+
468
+ # Identical to #remove, but blocks until the server responds. It will raise
469
+ # a StatusException if the request was unsuccessful. Otherwise, it will
470
+ # return the Response object for the request.
471
+ #
472
+ # sftp.remove!("/path/to/file")
473
+ def remove!(filename, &callback)
474
+ wait_for(remove(filename, &callback))
475
+ end
476
+
477
+ # :call-seq:
478
+ # mkdir(path, attrs={}) -> request
479
+ # mkdir(path, attrs={}) { |response| ... } -> request
480
+ #
481
+ # Creates the named directory on the remote server. If an attribute hash
482
+ # is given, it must map to the set of attributes supported by the version
483
+ # of the SFTP protocol in use. (See Protocol::V01::Attributes,
484
+ # Protocol::V04::Attributes, and Protocol::V06::Attributes.)
485
+ #
486
+ # sftp.mkdir("/path/to/directory", :permissions => 0550).wait
487
+ def mkdir(path, attrs={}, &callback)
488
+ request :mkdir, path, attrs, &callback
489
+ end
490
+
491
+ # Identical to #mkdir, but blocks until the server responds. It will raise
492
+ # a StatusException if the request was unsuccessful. Otherwise, it will
493
+ # return the Response object for the request.
494
+ #
495
+ # sftp.mkdir!("/path/to/directory", :permissions => 0550)
496
+ def mkdir!(path, attrs={}, &callback)
497
+ wait_for(mkdir(path, attrs, &callback))
498
+ end
499
+
500
+ # :call-seq:
501
+ # rmdir(path) -> request
502
+ # rmdir(path) { |response| ... } -> request
503
+ #
504
+ # Removes the named directory on the remote server. The directory must
505
+ # be empty before it can be removed.
506
+ #
507
+ # sftp.rmdir("/path/to/directory").wait
508
+ def rmdir(path, &callback)
509
+ request :rmdir, path, &callback
510
+ end
511
+
512
+ # Identical to #rmdir, but blocks until the server responds. It will raise
513
+ # a StatusException if the request was unsuccessful. Otherwise, it will
514
+ # return the Response object for the request.
515
+ #
516
+ # sftp.rmdir!("/path/to/directory")
517
+ def rmdir!(path, &callback)
518
+ wait_for(rmdir(path, &callback))
519
+ end
520
+
521
+ # :call-seq:
522
+ # realpath(path) -> request
523
+ # realpath(path) { |response| ... } -> request
524
+ #
525
+ # Tries to canonicalize the given path, turning any given path into an
526
+ # absolute path. This is primarily useful for converting a path with
527
+ # ".." or "." segments into an identical path without those segments.
528
+ # The answer will be in the response's :names attribute, as a
529
+ # one-element array.
530
+ #
531
+ # request = sftp.realpath("/path/../to/../directory").wait
532
+ # puts request[:names].first.name
533
+ def realpath(path, &callback)
534
+ request :realpath, path, &callback
535
+ end
536
+
537
+ # Identical to #realpath, but blocks until the server responds. It will raise
538
+ # a StatusException if the request was unsuccessful. Otherwise, it will
539
+ # return a name object identifying the path.
540
+ #
541
+ # puts(sftp.realpath!("/path/../to/../directory"))
542
+ def realpath!(path, &callback)
543
+ wait_for(realpath(path, &callback), :names).first
544
+ end
545
+
546
+ # Identical to the #lstat method, except that it follows symlinks
547
+ # (e.g., if you give it the path to a symlink, it will stat the target
548
+ # of the symlink rather than the symlink itself). See the #lstat method
549
+ # for full documentation.
550
+ def stat(path, flags=nil, &callback)
551
+ request :stat, path, flags, &callback
552
+ end
553
+
554
+ # Identical to #stat, but blocks until the server responds. It will raise
555
+ # a StatusException if the request was unsuccessful. Otherwise, it will
556
+ # return an attribute object for the named path.
557
+ #
558
+ # attrs = sftp.stat!("/path/to/file")
559
+ def stat!(path, flags=nil, &callback)
560
+ wait_for(stat(path, flags, &callback), :attrs)
561
+ end
562
+
563
+ # :call-seq:
564
+ # rename(name, new_name, flags=nil) -> request
565
+ # rename(name, new_name, flags=nil) { |response| ... } -> request
566
+ #
567
+ # Renames the given file. This operation is only available in SFTP
568
+ # protocol versions two and higher. The +flags+ parameter is ignored
569
+ # in versions prior to 5. In versions 5 and higher, the +flags+
570
+ # parameter can be used to specify how the rename should be performed
571
+ # (atomically, etc.).
572
+ #
573
+ # The following flags are defined in protocol version 5:
574
+ #
575
+ # * 0x0001 - overwrite an existing file if the new name specifies a file
576
+ # that already exists.
577
+ # * 0x0002 - perform the rewrite atomically.
578
+ # * 0x0004 - allow the server to perform the rename as it prefers.
579
+ def rename(name, new_name, flags=nil, &callback)
580
+ request :rename, name, new_name, flags, &callback
581
+ end
582
+
583
+ # Identical to #rename, but blocks until the server responds. It will raise
584
+ # a StatusException if the request was unsuccessful. Otherwise, it will
585
+ # return the Response object for the request.
586
+ #
587
+ # sftp.rename!("/path/to/old", "/path/to/new")
588
+ def rename!(name, new_name, flags=nil, &callback)
589
+ wait_for(rename(name, new_name, flags, &callback))
590
+ end
591
+
592
+ # :call-seq:
593
+ # readlink(path) -> request
594
+ # readlink(path) { |response| ... } -> request
595
+ #
596
+ # Queries the server for the target of the specified symbolic link.
597
+ # This operation is only available in protocol versions 3 and higher.
598
+ # The response to this request will include a names property, a one-element
599
+ # array naming the target of the symlink.
600
+ #
601
+ # request = sftp.readlink("/path/to/symlink").wait
602
+ # puts request.response[:names].first.name
603
+ def readlink(path, &callback)
604
+ request :readlink, path, &callback
605
+ end
606
+
607
+ # Identical to #readlink, but blocks until the server responds. It will raise
608
+ # a StatusException if the request was unsuccessful. Otherwise, it will
609
+ # return the Name object for the path that the symlink targets.
610
+ #
611
+ # item = sftp.readlink!("/path/to/symlink")
612
+ def readlink!(path, &callback)
613
+ wait_for(readlink(path, &callback), :names).first
614
+ end
615
+
616
+ # :call-seq:
617
+ # symlink(path, target) -> request
618
+ # symlink(path, target) { |response| ... } -> request
619
+ #
620
+ # Attempts to create a symlink to +path+ at +target+. This operation
621
+ # is only available in protocol versions 3, 4, and 5, but the Net::SFTP
622
+ # library mimics the symlink behavior in protocol version 6 using the
623
+ # #link method, so it is safe to use this method in protocol version 6.
624
+ #
625
+ # sftp.symlink("/path/to/file", "/path/to/symlink").wait
626
+ def symlink(path, target, &callback)
627
+ request :symlink, path, target, &callback
628
+ end
629
+
630
+ # Identical to #symlink, but blocks until the server responds. It will raise
631
+ # a StatusException if the request was unsuccessful. Otherwise, it will
632
+ # return the Response object for the request.
633
+ #
634
+ # sftp.symlink!("/path/to/file", "/path/to/symlink")
635
+ def symlink!(path, target, &callback)
636
+ wait_for(symlink(path, target, &callback))
637
+ end
638
+
639
+ # :call-seq:
640
+ # link(new_link_path, existing_path, symlink=true) -> request
641
+ # link(new_link_path, existing_path, symlink=true) { |response| ... } -> request
642
+ #
643
+ # Attempts to create a link, either hard or symbolic. This operation is
644
+ # only available in SFTP protocol versions 6 and higher. If the +symlink+
645
+ # paramter is true, a symbolic link will be created, otherwise a hard
646
+ # link will be created. The link will be named +new_link_path+, and will
647
+ # point to the path +existing_path+.
648
+ #
649
+ # sftp.link("/path/to/symlink", "/path/to/file", true).wait
650
+ #
651
+ # Note that #link is only available for SFTP protocol 6 and higher. You
652
+ # can use #symlink for protocols 3 and higher.
653
+ def link(new_link_path, existing_path, symlink=true, &callback)
654
+ request :link, new_link_path, existing_path, symlink, &callback
655
+ end
656
+
657
+ # Identical to #link, but blocks until the server responds. It will raise
658
+ # a StatusException if the request was unsuccessful. Otherwise, it will
659
+ # return the Response object for the request.
660
+ #
661
+ # sftp.link!("/path/to/symlink", "/path/to/file", true)
662
+ def link!(new_link_path, existing_path, symlink=true, &callback)
663
+ wait_for(link(new_link_path, existing_path, symlink, &callback))
664
+ end
665
+
666
+ # :call-seq:
667
+ # block(handle, offset, length, mask) -> request
668
+ # block(handle, offset, length, mask) { |response| ... } -> request
669
+ #
670
+ # Creates a byte-range lock on the file specified by the given +handle+.
671
+ # This operation is only available in SFTP protocol versions 6 and
672
+ # higher. The lock may be either mandatory or advisory.
673
+ #
674
+ # The +handle+ parameter is a file handle, as obtained by the #open method.
675
+ #
676
+ # The +offset+ and +length+ parameters describe the location and size of
677
+ # the byte range.
678
+ #
679
+ # The +mask+ describes how the lock should be defined, and consists of
680
+ # some combination of the following bit masks:
681
+ #
682
+ # * 0x0040 - Read lock. The byte range may not be accessed for reading
683
+ # by via any other handle, though it may be written to.
684
+ # * 0x0080 - Write lock. The byte range may not be written to via any
685
+ # other handle, though it may be read from.
686
+ # * 0x0100 - Delete lock. No other handle may delete this file.
687
+ # * 0x0200 - Advisory lock. The server need not honor the lock instruction.
688
+ #
689
+ # Once created, the lock may be removed via the #unblock method.
690
+ def block(handle, offset, length, mask, &callback)
691
+ request :block, handle, offset, length, mask, &callback
692
+ end
693
+
694
+ # Identical to #block, but blocks until the server responds. It will raise
695
+ # a StatusException if the request was unsuccessful. Otherwise, it will
696
+ # return the Response object for the request.
697
+ def block!(handle, offset, length, mask, &callback)
698
+ wait_for(block(handle, offset, length, mask, &callback))
699
+ end
700
+
701
+ # :call-seq:
702
+ # unblock(handle, offset, length) -> request
703
+ # unblock(handle, offset, length) { |response| ... } -> request
704
+ #
705
+ # Removes a previously created byte-range lock. This operation is only
706
+ # available in protocol versions 6 and higher. The +offset+ and +length+
707
+ # parameters must exactly match those that were given to #block when the
708
+ # lock was acquired.
709
+ def unblock(handle, offset, length, &callback)
710
+ request :unblock, handle, offset, length, &callback
711
+ end
712
+
713
+ # Identical to #unblock, but blocks until the server responds. It will raise
714
+ # a StatusException if the request was unsuccessful. Otherwise, it will
715
+ # return the Response object for the request.
716
+ def unblock!(handle, offset, length, &callback)
717
+ wait_for(unblock(handle, offset, length, &callback))
718
+ end
719
+
720
+ public # miscellaneous methods
721
+
722
+ # Closes the SFTP connection, but not the SSH connection. Blocks until the
723
+ # session has terminated. Once the session has terminated, further operations
724
+ # on this object will result in errors. You can reopen the SFTP session
725
+ # via the #connect method.
726
+ def close_channel
727
+ return unless open?
728
+ channel.close
729
+ loop { !closed? }
730
+ end
731
+
732
+ # Returns true if the connection has been initialized.
733
+ def open?
734
+ state == :open
735
+ end
736
+
737
+ # Returns true if the connection has been closed.
738
+ def closed?
739
+ state == :closed
740
+ end
741
+
742
+ # Returns true if the connection is in the process of being initialized
743
+ # (e.g., it is not closed, but is not yet fully open).
744
+ def opening?
745
+ !(open? || closed?)
746
+ end
747
+
748
+ # Attempts to establish an SFTP connection over the SSH session given when
749
+ # this object was instantiated. If the object is already open, this will
750
+ # simply execute the given block (if any), passing the SFTP session itself
751
+ # as argument. If the session is currently being opened, this will add
752
+ # the given block to the list of callbacks, to be executed when the session
753
+ # is fully open.
754
+ #
755
+ # This method does not block, and will return immediately. If you pass a
756
+ # block to it, that block will be invoked when the connection has been
757
+ # fully established. Thus, you can do something like this:
758
+ #
759
+ # sftp.connect do
760
+ # puts "open!"
761
+ # end
762
+ #
763
+ # If you just want to block until the connection is ready, see the #connect!
764
+ # method.
765
+ def connect(&block)
766
+ case state
767
+ when :open
768
+ block.call(self) if block
769
+ when :closed
770
+ @state = :opening
771
+ @channel = session.open_channel(&method(:when_channel_confirmed))
772
+ @packet_length = nil
773
+ @protocol = nil
774
+ @on_ready = Array(block)
775
+ else # opening
776
+ @on_ready << block if block
152
777
  end
153
- else
778
+
154
779
  self
155
780
  end
156
- end
157
781
 
158
- # Opens the given remote file and returns a handle to it, which may be used
159
- # with other operations (read, write, etc.). If a block is given, the handle
160
- # will be yielded to it and closed when the block finishes, otherwise the
161
- # handle will be returned. If the flags parameter is a numeric value, it
162
- # must be a combination of IO constants, otherwise, it should be a string
163
- # such as given to File.open.
164
- def open_handle( path, flags=IO::RDONLY, mode=0660 )
165
- if String === flags
166
- flags = case flags
167
- when "r" then IO::RDONLY
168
- when "r+" then IO:RDWR
169
- when "w" then IO::WRONLY | IO::CREAT | IO::TRUNC
170
- when "w+" then IO::RDWR | IO::CREAT | IO::TRUNC
171
- when "a" then IO::APPEND | IO::CREAT
172
- when "a+" then IO::APPEND | IO::CREAT
173
- else IO::RDONLY
174
- end
782
+ # Same as the #connect method, but blocks until the SFTP connection has
783
+ # been fully initialized.
784
+ def connect!(&block)
785
+ connect(&block)
786
+ loop { opening? }
787
+ self
788
+ end
789
+
790
+ alias :loop_forever :loop
791
+
792
+ # Runs the SSH event loop while the given block returns true. This lets
793
+ # you set up a state machine and then "fire it off". If you do not specify
794
+ # a block, the event loop will run for as long as there are any pending
795
+ # SFTP requests. This makes it easy to do thing like this:
796
+ #
797
+ # sftp.remove("/path/to/file")
798
+ # sftp.loop
799
+ def loop(&block)
800
+ block ||= Proc.new { pending_requests.any? }
801
+ session.loop(&block)
802
+ end
803
+
804
+ # Formats, constructs, and sends an SFTP packet of the given type and with
805
+ # the given data. This does not block, but merely enqueues the packet for
806
+ # sending and returns.
807
+ #
808
+ # You should probably use the operation methods, rather than building and
809
+ # sending the packet directly. (See #open, #close, etc.)
810
+ def send_packet(type, *args)
811
+ data = Net::SSH::Buffer.from(*args)
812
+ msg = Net::SSH::Buffer.from(:long, data.length+1, :byte, type, :raw, data)
813
+ channel.send_data(msg.to_s)
814
+ end
815
+
816
+ private
817
+
818
+ #--
819
+ # "ruby -w" hates private attributes, so we have to do this longhand
820
+ #++
821
+
822
+ # The input buffer used to accumulate packet data
823
+ def input; @input; end
824
+
825
+ # Create and enqueue a new SFTP request of the given type, with the
826
+ # given arguments. Returns a new Request instance that encapsulates the
827
+ # request.
828
+ def request(type, *args, &callback)
829
+ request = Request.new(self, type, protocol.send(type, *args), &callback)
830
+ info { "sending #{type} packet (#{request.id})" }
831
+ pending_requests[request.id] = request
175
832
  end
176
833
 
177
- handle = self.open( path, flags, mode )
178
- if block_given?
179
- begin
180
- yield handle
181
- ensure
182
- close_handle( handle )
834
+ # Waits for the given request to complete. If the response is
835
+ # EOF, nil is returned. If the response was not successful
836
+ # (e.g., !response.ok?), a StatusException will be raised.
837
+ # If +property+ is given, the corresponding property from the response
838
+ # will be returned; otherwise, the response object itself will be
839
+ # returned.
840
+ def wait_for(request, property=nil)
841
+ request.wait
842
+ if request.response.eof?
843
+ nil
844
+ elsif !request.response.ok?
845
+ raise StatusException.new(request.response)
846
+ elsif property
847
+ request.response[property.to_sym]
848
+ else
849
+ request.response
183
850
  end
184
- else
185
- return handle
186
851
  end
187
- end
188
852
 
189
- # Retrieves the given remote file to the given local path. This will
190
- # overwrite any file at the local path name. The remote file must
191
- # exist.
192
- def get_file( remote_path, local_path )
193
- open_handle( remote_path ) do |handle|
194
- contents = read( handle )
195
- File.open( local_path, "wb" ) { |f| f.write contents }
853
+ # Called when the SSH channel is confirmed as "open" by the server.
854
+ # This is one of the states of the SFTP state machine, and is followed
855
+ # by the #when_subsystem_started state.
856
+ def when_channel_confirmed(channel)
857
+ debug { "requesting sftp subsystem" }
858
+ @state = :subsystem
859
+ channel.subsystem("sftp", &method(:when_subsystem_started))
196
860
  end
197
- end
198
861
 
199
- # This stores the given local file at the given remote path. This will
200
- # overwrite any file at the remote path name. The local file must exist.
201
- def put_file( local_path, remote_path )
202
- contents = File.open( local_path, "rb" ) { |f| f.read }
203
- open_handle( remote_path, "w" ) { |handle| write( handle, contents ) }
204
- end
205
-
206
- #--
207
- # ====================================================================
208
- # CALLBACKS
209
- # ====================================================================
210
- #++
211
-
212
- # Invoked by the underlying SFTP protocol layer when a SFTP data packet
213
- # is received.
214
- def do_data( driver, id, data )
215
- @requests.delete( id ).do_data( data )
216
- end
862
+ # Called when the SSH server confirms that the SFTP subsystem was
863
+ # successfully started. This sets up the appropriate callbacks on the
864
+ # SSH channel and then starts the SFTP protocol version negotiation
865
+ # process.
866
+ def when_subsystem_started(channel, success)
867
+ raise Net::SFTP::Exception, "could not start SFTP subsystem" unless success
217
868
 
218
- # Invoked by the underlying SFTP protocol layer when a SFTP status packet
219
- # is received.
220
- def do_status( driver, id, code, message, language )
221
- @requests.delete( id ).do_status( code, message, language )
222
- end
869
+ debug { "sftp subsystem successfully started" }
870
+ @state = :init
223
871
 
224
- # Invoked by the underlying SFTP protocol layer when a SFTP handle packet
225
- # is received.
226
- def do_handle( driver, id, handle )
227
- @requests.delete( id ).do_handle( handle )
228
- end
872
+ channel.on_data { |c,data| input.append(data) }
873
+ channel.on_extended_data { |c,t,data| debug { data } }
229
874
 
230
- # Invoked by the underlying SFTP protocol layer when a SFTP name packet
231
- # is received.
232
- def do_name( driver, id, items )
233
- @requests.delete( id ).do_name( items )
234
- end
875
+ channel.on_close(&method(:when_channel_closed))
876
+ channel.on_process(&method(:when_channel_polled))
235
877
 
236
- # Invoked by the underlying SFTP protocol layer when a SFTP attrs packet
237
- # is received.
238
- def do_attrs( driver, id, attributes )
239
- @requests.delete( id ).do_attrs( attributes )
240
- end
878
+ send_packet(FXP_INIT, :long, HIGHEST_PROTOCOL_VERSION_SUPPORTED)
879
+ end
241
880
 
242
- #--
243
- # ====================================================================
244
- # DELEGATION
245
- # ====================================================================
246
- #++
247
-
248
- # Delegates the message to the operation that has been registered with
249
- # the given name. If no such operation exists, control is passed to the
250
- # superclass' implementation of #method_missing.
251
- def method_missing( sym, *args, &block )
252
- if @operations.has_key?( sym )
253
- connect
254
- @operations[ sym ].execute( *args, &block )
255
- else
256
- super
881
+ # Called when the SSH server closes the underlying channel.
882
+ def when_channel_closed(channel)
883
+ debug { "sftp channel closed" }
884
+ @channel = nil
885
+ @state = :closed
257
886
  end
258
- end
259
887
 
260
- # Returns +true+ if the object responds to the given symbol, or if there is
261
- # an operation registered under the given symbol.
262
- def respond_to?( sym )
263
- super || @operations.has_key?( sym )
264
- end
888
+ # Called whenever Net::SSH polls the SFTP channel for pending activity.
889
+ # This basically checks the input buffer to see if enough input has been
890
+ # accumulated to handle. If there has, the packet is parsed and
891
+ # dispatched, according to its type (see #do_version and #dispatch_request).
892
+ def when_channel_polled(channel)
893
+ while input.length > 0
894
+ if @packet_length.nil?
895
+ # make sure we've read enough data to tell how long the packet is
896
+ return unless input.length >= 4
897
+ @packet_length = input.read_long
898
+ end
265
899
 
266
- # Returns +true+ if the underlying driver responds to the given symbol. This
267
- # can be used by clients to determine whether the SFTP protocol version in
268
- # use supports a particular operation.
269
- def support?( sym )
270
- @driver.respond_to?( sym )
271
- end
900
+ return unless input.length >= @packet_length
901
+ packet = Net::SFTP::Packet.new(input.read(@packet_length))
902
+ input.consume!
903
+ @packet_length = nil
904
+
905
+ debug { "received sftp packet #{packet.type} len #{packet.length}" }
906
+
907
+ if packet.type == FXP_VERSION
908
+ do_version(packet)
909
+ else
910
+ dispatch_request(packet)
911
+ end
912
+ end
913
+ end
272
914
 
915
+ # Called to handle FXP_VERSION packets. This performs the SFTP protocol
916
+ # version negotiation, instantiating the appropriate Protocol instance
917
+ # and invoking the callback given to #connect, if any.
918
+ def do_version(packet)
919
+ debug { "negotiating sftp protocol version, mine is #{HIGHEST_PROTOCOL_VERSION_SUPPORTED}" }
920
+
921
+ server_version = packet.read_long
922
+ debug { "server reports sftp version #{server_version}" }
923
+
924
+ negotiated_version = [server_version, HIGHEST_PROTOCOL_VERSION_SUPPORTED].min
925
+ info { "negotiated version is #{negotiated_version}" }
926
+
927
+ extensions = {}
928
+ until packet.eof?
929
+ name = packet.read_string
930
+ data = packet.read_string
931
+ extensions[name] = data
932
+ end
933
+
934
+ @protocol = Protocol.load(self, negotiated_version)
935
+ @pending_requests = {}
936
+
937
+ @state = :open
938
+ @on_ready.each { |callback| callback.call(self) }
939
+ @on_ready = nil
940
+ end
941
+
942
+ # Parses the packet, finds the associated Request instance, and tells
943
+ # the Request instance to respond to the packet (see Request#respond_to).
944
+ def dispatch_request(packet)
945
+ id = packet.read_long
946
+ request = pending_requests.delete(id) or raise Net::SFTP::Exception, "no such request `#{id}'"
947
+ request.respond_to(packet)
948
+ end
273
949
  end
274
950
 
275
- end ; end
951
+ end; end