redcar 0.3.7.1 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (246) hide show
  1. data/CHANGES +16 -0
  2. data/README.md +5 -0
  3. data/ROADMAP.md +7 -9
  4. data/Rakefile +55 -22
  5. data/lib/redcar.rb +12 -6
  6. data/lib/redcar/installer.rb +119 -44
  7. data/plugins/application/features/step_definitions/speedbar_steps.rb +62 -0
  8. data/plugins/application/features/step_definitions/tree_steps.rb +3 -3
  9. data/plugins/application/features/support/env.rb +2 -1
  10. data/plugins/application/lib/application/notebook.rb +53 -7
  11. data/plugins/application/lib/application/speedbar.rb +12 -0
  12. data/plugins/application/lib/application/tab.rb +16 -5
  13. data/plugins/application/lib/application/treebook.rb +21 -1
  14. data/plugins/application_swt/lib/application_swt.rb +12 -2
  15. data/plugins/application_swt/lib/application_swt/html_tab.rb +4 -0
  16. data/plugins/application_swt/lib/application_swt/notebook.rb +5 -3
  17. data/plugins/application_swt/lib/application_swt/speedbar.rb +15 -27
  18. data/plugins/application_swt/lib/application_swt/swt_wrapper.rb +1 -0
  19. data/plugins/application_swt/lib/application_swt/tab.rb +1 -0
  20. data/plugins/application_swt/lib/application_swt/treebook.rb +36 -8
  21. data/plugins/application_swt/lib/application_swt/window.rb +24 -18
  22. data/plugins/auto_completer/lib/auto_completer.rb +5 -20
  23. data/plugins/core/lib/core.rb +7 -4
  24. data/plugins/core/lib/core/observable_struct.rb +2 -2
  25. data/plugins/declarations/lib/declarations.rb +3 -17
  26. data/plugins/document_search/features/search.feature +163 -0
  27. data/plugins/document_search/features/support/env.rb +4 -0
  28. data/plugins/document_search/lib/document_search.rb +121 -0
  29. data/plugins/document_search/plugin.rb +8 -0
  30. data/plugins/edit_view/features/line_delimiter.feature +7 -0
  31. data/plugins/edit_view/features/step_definitions/editing_steps.rb +49 -1
  32. data/plugins/edit_view/features/step_definitions/tab_steps.rb +10 -11
  33. data/plugins/edit_view/features/switch_tabs.feature +61 -10
  34. data/plugins/edit_view/lib/edit_view.rb +4 -0
  35. data/plugins/edit_view/lib/edit_view/document.rb +56 -0
  36. data/plugins/edit_view/lib/edit_view/document/command.rb +1 -1
  37. data/plugins/edit_view/lib/edit_view/edit_tab.rb +0 -8
  38. data/plugins/edit_view_swt/lib/edit_view_swt.rb +4 -0
  39. data/plugins/html_view/lib/html_controller.rb +19 -1
  40. data/plugins/html_view/lib/html_view.rb +28 -1
  41. data/plugins/html_view/lib/html_view/html_tab.rb +10 -1
  42. data/plugins/plugin_manager_ui/lib/plugin_manager_ui.rb +3 -1
  43. data/plugins/project/features/open_directory_tree.feature +3 -3
  44. data/plugins/project/lib/project.rb +4 -6
  45. data/plugins/project/lib/project/commands.rb +17 -0
  46. data/plugins/project/lib/project/dir_controller.rb +2 -1
  47. data/plugins/project/lib/project/dir_mirror.rb +8 -0
  48. data/plugins/project/lib/project/manager.rb +2 -0
  49. data/plugins/project/plugin.rb +2 -1
  50. data/plugins/project/vendor/net-sftp/Manifest +55 -0
  51. data/plugins/project/vendor/net-sftp/Rakefile +30 -0
  52. data/plugins/project/vendor/net-sftp/lib/net/sftp.rb +70 -0
  53. data/plugins/project/vendor/net-sftp/lib/net/sftp/constants.rb +187 -0
  54. data/plugins/project/vendor/net-sftp/lib/net/sftp/errors.rb +39 -0
  55. data/plugins/project/vendor/net-sftp/lib/net/sftp/operations/dir.rb +93 -0
  56. data/plugins/project/vendor/net-sftp/lib/net/sftp/operations/download.rb +364 -0
  57. data/plugins/project/vendor/net-sftp/lib/net/sftp/operations/file.rb +176 -0
  58. data/plugins/project/vendor/net-sftp/lib/net/sftp/operations/file_factory.rb +60 -0
  59. data/plugins/project/vendor/net-sftp/lib/net/sftp/operations/upload.rb +387 -0
  60. data/plugins/project/vendor/net-sftp/lib/net/sftp/packet.rb +21 -0
  61. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol.rb +32 -0
  62. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/01/attributes.rb +315 -0
  63. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/01/base.rb +268 -0
  64. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/01/name.rb +43 -0
  65. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/02/base.rb +31 -0
  66. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/03/base.rb +35 -0
  67. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/04/attributes.rb +152 -0
  68. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/04/base.rb +94 -0
  69. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/04/name.rb +67 -0
  70. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/05/base.rb +66 -0
  71. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/06/attributes.rb +107 -0
  72. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/06/base.rb +63 -0
  73. data/plugins/project/vendor/net-sftp/lib/net/sftp/protocol/base.rb +50 -0
  74. data/plugins/project/vendor/net-sftp/lib/net/sftp/request.rb +91 -0
  75. data/plugins/project/vendor/net-sftp/lib/net/sftp/response.rb +76 -0
  76. data/plugins/project/vendor/net-sftp/lib/net/sftp/session.rb +951 -0
  77. data/plugins/project/vendor/net-sftp/lib/net/sftp/version.rb +18 -0
  78. data/plugins/project/vendor/net-sftp/setup.rb +1331 -0
  79. data/plugins/project/vendor/net-sftp/test/common.rb +171 -0
  80. data/plugins/project/vendor/net-sftp/test/protocol/01/test_attributes.rb +97 -0
  81. data/plugins/project/vendor/net-sftp/test/protocol/01/test_base.rb +210 -0
  82. data/plugins/project/vendor/net-sftp/test/protocol/01/test_name.rb +27 -0
  83. data/plugins/project/vendor/net-sftp/test/protocol/02/test_base.rb +26 -0
  84. data/plugins/project/vendor/net-sftp/test/protocol/03/test_base.rb +27 -0
  85. data/plugins/project/vendor/net-sftp/test/protocol/04/test_attributes.rb +148 -0
  86. data/plugins/project/vendor/net-sftp/test/protocol/04/test_base.rb +74 -0
  87. data/plugins/project/vendor/net-sftp/test/protocol/04/test_name.rb +53 -0
  88. data/plugins/project/vendor/net-sftp/test/protocol/05/test_base.rb +62 -0
  89. data/plugins/project/vendor/net-sftp/test/protocol/06/test_attributes.rb +124 -0
  90. data/plugins/project/vendor/net-sftp/test/protocol/06/test_base.rb +51 -0
  91. data/plugins/project/vendor/net-sftp/test/protocol/test_base.rb +42 -0
  92. data/plugins/project/vendor/net-sftp/test/test_all.rb +7 -0
  93. data/plugins/project/vendor/net-sftp/test/test_dir.rb +47 -0
  94. data/plugins/project/vendor/net-sftp/test/test_download.rb +252 -0
  95. data/plugins/project/vendor/net-sftp/test/test_file.rb +159 -0
  96. data/plugins/project/vendor/net-sftp/test/test_file_factory.rb +48 -0
  97. data/plugins/project/vendor/net-sftp/test/test_packet.rb +9 -0
  98. data/plugins/project/vendor/net-sftp/test/test_protocol.rb +17 -0
  99. data/plugins/project/vendor/net-sftp/test/test_request.rb +71 -0
  100. data/plugins/project/vendor/net-sftp/test/test_response.rb +53 -0
  101. data/plugins/project/vendor/net-sftp/test/test_session.rb +741 -0
  102. data/plugins/project/vendor/net-sftp/test/test_upload.rb +219 -0
  103. data/plugins/project/vendor/net-ssh/Manifest +110 -0
  104. data/plugins/project/vendor/net-ssh/Rakefile +85 -0
  105. data/plugins/project/vendor/net-ssh/Rudyfile +96 -0
  106. data/plugins/project/vendor/net-ssh/lib/net/ssh.rb +215 -0
  107. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/agent.rb +179 -0
  108. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/constants.rb +18 -0
  109. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/key_manager.rb +193 -0
  110. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  111. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  112. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  113. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/methods/password.rb +39 -0
  114. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  115. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/pageant.rb +183 -0
  116. data/plugins/project/vendor/net-ssh/lib/net/ssh/authentication/session.rb +134 -0
  117. data/plugins/project/vendor/net-ssh/lib/net/ssh/buffer.rb +340 -0
  118. data/plugins/project/vendor/net-ssh/lib/net/ssh/buffered_io.rb +198 -0
  119. data/plugins/project/vendor/net-ssh/lib/net/ssh/config.rb +202 -0
  120. data/plugins/project/vendor/net-ssh/lib/net/ssh/connection/channel.rb +630 -0
  121. data/plugins/project/vendor/net-ssh/lib/net/ssh/connection/constants.rb +33 -0
  122. data/plugins/project/vendor/net-ssh/lib/net/ssh/connection/session.rb +597 -0
  123. data/plugins/project/vendor/net-ssh/lib/net/ssh/connection/term.rb +178 -0
  124. data/plugins/project/vendor/net-ssh/lib/net/ssh/errors.rb +85 -0
  125. data/plugins/project/vendor/net-ssh/lib/net/ssh/key_factory.rb +102 -0
  126. data/plugins/project/vendor/net-ssh/lib/net/ssh/known_hosts.rb +129 -0
  127. data/plugins/project/vendor/net-ssh/lib/net/ssh/loggable.rb +61 -0
  128. data/plugins/project/vendor/net-ssh/lib/net/ssh/packet.rb +102 -0
  129. data/plugins/project/vendor/net-ssh/lib/net/ssh/prompt.rb +93 -0
  130. data/plugins/project/vendor/net-ssh/lib/net/ssh/proxy/command.rb +75 -0
  131. data/plugins/project/vendor/net-ssh/lib/net/ssh/proxy/errors.rb +14 -0
  132. data/plugins/project/vendor/net-ssh/lib/net/ssh/proxy/http.rb +94 -0
  133. data/plugins/project/vendor/net-ssh/lib/net/ssh/proxy/socks4.rb +70 -0
  134. data/plugins/project/vendor/net-ssh/lib/net/ssh/proxy/socks5.rb +142 -0
  135. data/plugins/project/vendor/net-ssh/lib/net/ssh/ruby_compat.rb +43 -0
  136. data/plugins/project/vendor/net-ssh/lib/net/ssh/service/forward.rb +288 -0
  137. data/plugins/project/vendor/net-ssh/lib/net/ssh/test.rb +89 -0
  138. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/channel.rb +129 -0
  139. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/extensions.rb +152 -0
  140. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/kex.rb +44 -0
  141. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/local_packet.rb +51 -0
  142. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/packet.rb +81 -0
  143. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/remote_packet.rb +38 -0
  144. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/script.rb +157 -0
  145. data/plugins/project/vendor/net-ssh/lib/net/ssh/test/socket.rb +64 -0
  146. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/algorithms.rb +384 -0
  147. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/cipher_factory.rb +97 -0
  148. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/constants.rb +30 -0
  149. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac.rb +31 -0
  150. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  151. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/md5.rb +12 -0
  152. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  153. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/none.rb +15 -0
  154. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  155. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  156. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/identity_cipher.rb +55 -0
  157. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/kex.rb +13 -0
  158. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  159. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  160. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/openssl.rb +128 -0
  161. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/packet_stream.rb +235 -0
  162. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/server_version.rb +71 -0
  163. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/session.rb +276 -0
  164. data/plugins/project/vendor/net-ssh/lib/net/ssh/transport/state.rb +206 -0
  165. data/plugins/project/vendor/net-ssh/lib/net/ssh/verifiers/lenient.rb +30 -0
  166. data/plugins/project/vendor/net-ssh/lib/net/ssh/verifiers/null.rb +12 -0
  167. data/plugins/project/vendor/net-ssh/lib/net/ssh/verifiers/strict.rb +53 -0
  168. data/plugins/project/vendor/net-ssh/lib/net/ssh/version.rb +62 -0
  169. data/plugins/project/vendor/net-ssh/net-ssh.gemspec +138 -0
  170. data/plugins/project/vendor/net-ssh/setup.rb +1585 -0
  171. data/plugins/project/vendor/net-ssh/support/arcfour_check.rb +20 -0
  172. data/plugins/project/vendor/net-ssh/support/ssh_tunnel_bug.rb +65 -0
  173. data/plugins/project/vendor/net-ssh/test/README.txt +42 -0
  174. data/plugins/project/vendor/net-ssh/test/authentication/methods/common.rb +28 -0
  175. data/plugins/project/vendor/net-ssh/test/authentication/methods/test_abstract.rb +51 -0
  176. data/plugins/project/vendor/net-ssh/test/authentication/methods/test_hostbased.rb +114 -0
  177. data/plugins/project/vendor/net-ssh/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  178. data/plugins/project/vendor/net-ssh/test/authentication/methods/test_password.rb +50 -0
  179. data/plugins/project/vendor/net-ssh/test/authentication/methods/test_publickey.rb +127 -0
  180. data/plugins/project/vendor/net-ssh/test/authentication/test_agent.rb +205 -0
  181. data/plugins/project/vendor/net-ssh/test/authentication/test_key_manager.rb +105 -0
  182. data/plugins/project/vendor/net-ssh/test/authentication/test_session.rb +93 -0
  183. data/plugins/project/vendor/net-ssh/test/common.rb +107 -0
  184. data/plugins/project/vendor/net-ssh/test/configs/eqsign +3 -0
  185. data/plugins/project/vendor/net-ssh/test/configs/exact_match +8 -0
  186. data/plugins/project/vendor/net-ssh/test/configs/host_plus +10 -0
  187. data/plugins/project/vendor/net-ssh/test/configs/multihost +4 -0
  188. data/plugins/project/vendor/net-ssh/test/configs/nohost +19 -0
  189. data/plugins/project/vendor/net-ssh/test/configs/numeric_host +4 -0
  190. data/plugins/project/vendor/net-ssh/test/configs/wild_cards +14 -0
  191. data/plugins/project/vendor/net-ssh/test/connection/test_channel.rb +467 -0
  192. data/plugins/project/vendor/net-ssh/test/connection/test_session.rb +488 -0
  193. data/plugins/project/vendor/net-ssh/test/manual/test_forward.rb +185 -0
  194. data/plugins/project/vendor/net-ssh/test/test_all.rb +9 -0
  195. data/plugins/project/vendor/net-ssh/test/test_buffer.rb +336 -0
  196. data/plugins/project/vendor/net-ssh/test/test_buffered_io.rb +63 -0
  197. data/plugins/project/vendor/net-ssh/test/test_config.rb +117 -0
  198. data/plugins/project/vendor/net-ssh/test/test_key_factory.rb +67 -0
  199. data/plugins/project/vendor/net-ssh/test/transport/hmac/test_md5.rb +39 -0
  200. data/plugins/project/vendor/net-ssh/test/transport/hmac/test_md5_96.rb +25 -0
  201. data/plugins/project/vendor/net-ssh/test/transport/hmac/test_none.rb +34 -0
  202. data/plugins/project/vendor/net-ssh/test/transport/hmac/test_sha1.rb +34 -0
  203. data/plugins/project/vendor/net-ssh/test/transport/hmac/test_sha1_96.rb +25 -0
  204. data/plugins/project/vendor/net-ssh/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  205. data/plugins/project/vendor/net-ssh/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  206. data/plugins/project/vendor/net-ssh/test/transport/test_algorithms.rb +302 -0
  207. data/plugins/project/vendor/net-ssh/test/transport/test_cipher_factory.rb +213 -0
  208. data/plugins/project/vendor/net-ssh/test/transport/test_hmac.rb +34 -0
  209. data/plugins/project/vendor/net-ssh/test/transport/test_identity_cipher.rb +40 -0
  210. data/plugins/project/vendor/net-ssh/test/transport/test_packet_stream.rb +441 -0
  211. data/plugins/project/vendor/net-ssh/test/transport/test_server_version.rb +78 -0
  212. data/plugins/project/vendor/net-ssh/test/transport/test_session.rb +315 -0
  213. data/plugins/project/vendor/net-ssh/test/transport/test_state.rb +173 -0
  214. data/plugins/redcar/plugin.rb +3 -1
  215. data/plugins/redcar/redcar.rb +108 -121
  216. data/plugins/redcar_debug/lib/redcar_debug.rb +2 -0
  217. data/plugins/runnables/lib/runnables.rb +80 -23
  218. data/plugins/runnables/lib/runnables/command_output_controller.rb +102 -0
  219. data/plugins/runnables/lib/runnables/running_process_checker.rb +34 -0
  220. data/plugins/runnables/{plugin_no_load.rb → plugin.rb} +2 -1
  221. data/plugins/runnables/vendor/session-2.4.0/HISTORY +73 -0
  222. data/plugins/runnables/vendor/session-2.4.0/README +89 -0
  223. data/plugins/runnables/vendor/session-2.4.0/TODO +3 -0
  224. data/plugins/runnables/vendor/session-2.4.0/VERSION +1 -0
  225. data/plugins/runnables/vendor/session-2.4.0/gemspec.rb +22 -0
  226. data/plugins/runnables/vendor/session-2.4.0/install.rb +143 -0
  227. data/plugins/runnables/vendor/session-2.4.0/lib/session.rb +757 -0
  228. data/plugins/runnables/vendor/session-2.4.0/sample/bash.rb +60 -0
  229. data/plugins/runnables/vendor/session-2.4.0/sample/driver.rb +47 -0
  230. data/plugins/runnables/vendor/session-2.4.0/sample/session_idl.rb +25 -0
  231. data/plugins/runnables/vendor/session-2.4.0/sample/session_sh.rb +29 -0
  232. data/plugins/runnables/vendor/session-2.4.0/sample/sh0.rb +23 -0
  233. data/plugins/runnables/vendor/session-2.4.0/sample/stdin.rb +17 -0
  234. data/plugins/runnables/vendor/session-2.4.0/sample/threadtest.rb +42 -0
  235. data/plugins/runnables/vendor/session-2.4.0/session-2.4.0.gem +0 -0
  236. data/plugins/runnables/vendor/session-2.4.0/test/session.rb +227 -0
  237. data/plugins/runnables/views/command_output.html.erb +32 -0
  238. data/plugins/task_manager/lib/task_manager.rb +2 -0
  239. data/plugins/textmate/lib/textmate/plist.rb +1 -1
  240. data/plugins/textmate/lib/textmate/snippet.rb +1 -0
  241. data/plugins/tree/lib/tree/mirror.rb +8 -1
  242. data/plugins/tree_view_swt/lib/tree_view_swt.rb +29 -2
  243. metadata +191 -6
  244. data/plugins/declarations/TODO +0 -3
  245. data/plugins/redcar/spec/redcar/redcar_spec.rb +0 -70
  246. data/plugins/redcar/spec/spec_helper.rb +0 -4
@@ -0,0 +1,107 @@
1
+ require 'net/sftp/protocol/04/attributes'
2
+
3
+ module Net; module SFTP; module Protocol; module V06
4
+
5
+ # A class representing the attributes of a file or directory on the server.
6
+ # It may be used to specify new attributes, or to query existing attributes.
7
+ # This particular class is specific to versions 6 and higher of the SFTP
8
+ # protocol.
9
+ #
10
+ # To specify new attributes, just pass a hash as the argument to the
11
+ # constructor. The following keys are supported:
12
+ #
13
+ # * :type:: the type of the item (integer, one of the T_ constants)
14
+ # * :size:: the size of the item (integer)
15
+ # * :allocation_size:: the actual number of bytes that the item uses on disk (integer)
16
+ # * :uid:: the user-id that owns the file (integer)
17
+ # * :gid:: the group-id that owns the file (integer)
18
+ # * :owner:: the name of the user that owns the file (string)
19
+ # * :group:: the name of the group that owns the file (string)
20
+ # * :permissions:: the permissions on the file (integer, e.g. 0755)
21
+ # * :atime:: the access time of the file (integer, seconds since epoch)
22
+ # * :atime_nseconds:: the nanosecond component of atime (integer)
23
+ # * :createtime:: the time at which the file was created (integer, seconds since epoch)
24
+ # * :createtime_nseconds:: the nanosecond component of createtime (integer)
25
+ # * :mtime:: the modification time of the file (integer, seconds since epoch)
26
+ # * :mtime_nseconds:: the nanosecond component of mtime (integer)
27
+ # * :ctime:: the time that the file's attributes were last changed (integer)
28
+ # * :ctime_nseconds:: the nanosecond component of ctime (integer)
29
+ # * :acl:: an array of ACL entries for the item
30
+ # * :attrib_bits:: other attributes of the file or directory (as a bit field) (integer)
31
+ # * :attrib_bits_valid:: a mask describing which bits in attrib_bits are valid (integer)
32
+ # * :text_hint:: whether the file may or may not contain textual data (integer)
33
+ # * :mime_type:: the mime type of the file (string)
34
+ # * :link_count:: the hard link count of the file (integer)
35
+ # * :untranslated_name:: the value of the filename before filename translation was attempted (string)
36
+ # * :extended:: a hash of name/value pairs identifying extended info
37
+ #
38
+ # Likewise, when the server sends an Attributes object, all of the
39
+ # above attributes are exposed as methods (though not all will be set with
40
+ # non-nil values from the server).
41
+ class Attributes < V04::Attributes
42
+ F_BITS = 0x00000200
43
+ F_ALLOCATION_SIZE = 0x00000400
44
+ F_TEXT_HINT = 0x00000800
45
+ F_MIME_TYPE = 0x00001000
46
+ F_LINK_COUNT = 0x00002000
47
+ F_UNTRANSLATED_NAME = 0x00004000
48
+ F_CTIME = 0x00008000
49
+
50
+ # The array of elements that describe this structure, in order. Used when
51
+ # parsing and serializing attribute objects.
52
+ def self.elements #:nodoc:
53
+ @elements ||= [
54
+ [:type, :byte, 0],
55
+ [:size, :int64, F_SIZE],
56
+ [:allocation_size, :int64, F_ALLOCATION_SIZE],
57
+ [:owner, :string, F_OWNERGROUP],
58
+ [:group, :string, F_OWNERGROUP],
59
+ [:permissions, :long, F_PERMISSIONS],
60
+ [:atime, :int64, F_ACCESSTIME],
61
+ [:atime_nseconds, :long, F_ACCESSTIME | F_SUBSECOND_TIMES],
62
+ [:createtime, :int64, F_CREATETIME],
63
+ [:createtime_nseconds, :long, F_CREATETIME | F_SUBSECOND_TIMES],
64
+ [:mtime, :int64, F_MODIFYTIME],
65
+ [:mtime_nseconds, :long, F_MODIFYTIME | F_SUBSECOND_TIMES],
66
+ [:ctime, :int64, F_CTIME],
67
+ [:ctime_nseconds, :long, F_CTIME | F_SUBSECOND_TIMES],
68
+ [:acl, :special, F_ACL],
69
+ [:attrib_bits, :long, F_BITS],
70
+ [:attrib_bits_valid, :long, F_BITS],
71
+ [:text_hint, :byte, F_TEXT_HINT],
72
+ [:mime_type, :string, F_MIME_TYPE],
73
+ [:link_count, :long, F_LINK_COUNT],
74
+ [:untranslated_name, :string, F_UNTRANSLATED_NAME],
75
+ [:extended, :special, F_EXTENDED]
76
+ ]
77
+ end
78
+
79
+ # The size on-disk of the file
80
+ attr_accessor :allocation_size
81
+
82
+ # The time at which the file's attributes were last changed
83
+ attr_accessor :ctime
84
+
85
+ # The nanosecond component of #ctime
86
+ attr_accessor :ctime_nseconds
87
+
88
+ # Other attributes of this file or directory (as a bit field)
89
+ attr_accessor :attrib_bits
90
+
91
+ # A bit mask describing which bits in #attrib_bits are valid
92
+ attr_accessor :attrib_bits_valid
93
+
94
+ # Describes whether the file may or may not contain textual data
95
+ attr_accessor :text_hint
96
+
97
+ # The mime-type of the file
98
+ attr_accessor :mime_type
99
+
100
+ # The hard link count for the file
101
+ attr_accessor :link_count
102
+
103
+ # The value of the file name before filename translation was attempted
104
+ attr_accessor :untranslated_name
105
+ end
106
+
107
+ end; end; end; end
@@ -0,0 +1,63 @@
1
+ require 'net/sftp/protocol/05/base'
2
+ require 'net/sftp/protocol/06/attributes'
3
+
4
+ module Net; module SFTP; module Protocol; module V06
5
+
6
+ # Wraps the low-level SFTP calls for version 6 of the SFTP protocol.
7
+ #
8
+ # None of these protocol methods block--all of them return immediately,
9
+ # requiring the SSH event loop to be run while the server response is
10
+ # pending.
11
+ #
12
+ # You will almost certainly never need to use this driver directly. Please
13
+ # see Net::SFTP::Session for the recommended interface.
14
+ class Base < V05::Base
15
+
16
+ # Returns the protocol version implemented by this driver. (6, in this
17
+ # case)
18
+ def version
19
+ 6
20
+ end
21
+
22
+ # Sends a FXP_LINK packet to the server to request that a link be created
23
+ # at +new_link_path+, pointing to +existing_path+. If +symlink+ is true, a
24
+ # symbolic link will be created; otherwise a hard link will be created.
25
+ def link(new_link_path, existing_path, symlink)
26
+ send_request(FXP_LINK, :string, new_link_path, :string, existing_path, :bool, symlink)
27
+ end
28
+
29
+ # Provided for backwards compatibility; v6 of the SFTP protocol removes the
30
+ # older FXP_SYMLINK packet type, so this method simply calls the #link
31
+ # method.
32
+ def symlink(path, target)
33
+ link(path, target, true)
34
+ end
35
+
36
+ # Sends a FXP_BLOCK packet to the server to request that a byte-range lock
37
+ # be obtained on the given +handle+, for the given byte +offset+ and
38
+ # +length+. The +mask+ parameter is a bitfield indicating what kind of
39
+ # lock to acquire, and must be a combination of one or more of the
40
+ # Net::SFTP::Constants::LockTypes constants.
41
+ def block(handle, offset, length, mask)
42
+ send_request(FXP_BLOCK, :string, handle, :int64, offset, :int64, length, :long, mask)
43
+ end
44
+
45
+ # Sends a FXP_UNBLOCK packet to the server to request that a previously
46
+ # acquired byte-range lock be released on the given +handle+, for the
47
+ # given byte +offset+ and +length+. The +handle+, +offset+, and +length+
48
+ # must all exactly match the parameters that were given when the lock was
49
+ # originally acquired (see #block).
50
+ def unblock(handle, offset, length)
51
+ send_request(FXP_UNBLOCK, :string, handle, :int64, offset, :int64, length)
52
+ end
53
+
54
+ protected
55
+
56
+ # Returns the Attributes class used by this version of the protocol
57
+ # (Net::SFTP::Protocol::V06::Attributes, in this case)
58
+ def attribute_factory
59
+ V06::Attributes
60
+ end
61
+ end
62
+
63
+ end; end; end; end
@@ -0,0 +1,50 @@
1
+ require 'net/ssh/loggable'
2
+ require 'net/sftp/constants'
3
+
4
+ module Net; module SFTP; module Protocol
5
+
6
+ # The abstract superclass of the specific implementations for each supported
7
+ # SFTP protocol version. It implements general packet parsing logic, and
8
+ # provides a way for subclasses to send requests.
9
+ class Base
10
+ include Net::SSH::Loggable
11
+ include Net::SFTP::Constants
12
+ include Net::SFTP::Constants::PacketTypes
13
+
14
+ # The SFTP session object that acts as client to this protocol instance
15
+ attr_reader :session
16
+
17
+ # Create a new instance of a protocol driver, servicing the given session.
18
+ def initialize(session)
19
+ @session = session
20
+ self.logger = session.logger
21
+ @request_id_counter = -1
22
+ end
23
+
24
+ # Attept to parse the given packet. If the packet is of an unsupported
25
+ # type, an exception will be raised. Returns the parsed data as a hash
26
+ # (the keys in the hash are packet-type specific).
27
+ def parse(packet)
28
+ case packet.type
29
+ when FXP_STATUS then parse_status_packet(packet)
30
+ when FXP_HANDLE then parse_handle_packet(packet)
31
+ when FXP_DATA then parse_data_packet(packet)
32
+ when FXP_NAME then parse_name_packet(packet)
33
+ when FXP_ATTRS then parse_attrs_packet(packet)
34
+ else raise NotImplementedError, "unknown packet type: #{packet.type}"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Send a new packet of the given type, and with the given data arguments.
41
+ # A new request identifier will be allocated to this request, and will
42
+ # be returned.
43
+ def send_request(type, *args)
44
+ @request_id_counter += 1
45
+ session.send_packet(type, :long, @request_id_counter, *args)
46
+ return @request_id_counter
47
+ end
48
+ end
49
+
50
+ end; end; end
@@ -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
@@ -0,0 +1,951 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp/constants'
3
+ require 'net/sftp/errors'
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
48
+
49
+ # The highest protocol version supported by the Net::SFTP library.
50
+ HIGHEST_PROTOCOL_VERSION_SUPPORTED = 6
51
+
52
+ # A reference to the Net::SSH session object that powers this SFTP session.
53
+ attr_reader :session
54
+
55
+ # The Net::SSH::Connection::Channel object that the SFTP session is being
56
+ # processed by.
57
+ attr_reader :channel
58
+
59
+ # The state of the SFTP connection. It will be :opening, :subsystem, :init,
60
+ # :open, or :closed.
61
+ attr_reader :state
62
+
63
+ # The protocol instance being used by this SFTP session. Useful for
64
+ # querying the protocol version in effect.
65
+ attr_reader :protocol
66
+
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
70
+
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)
85
+ end
86
+
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
100
+
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
105
+
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
117
+
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
127
+
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
142
+
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
777
+ end
778
+
779
+ self
780
+ end
781
+
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
832
+ end
833
+
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
850
+ end
851
+ end
852
+
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))
860
+ end
861
+
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
868
+
869
+ debug { "sftp subsystem successfully started" }
870
+ @state = :init
871
+
872
+ channel.on_data { |c,data| input.append(data) }
873
+ channel.on_extended_data { |c,t,data| debug { data } }
874
+
875
+ channel.on_close(&method(:when_channel_closed))
876
+ channel.on_process(&method(:when_channel_polled))
877
+
878
+ send_packet(FXP_INIT, :long, HIGHEST_PROTOCOL_VERSION_SUPPORTED)
879
+ end
880
+
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
886
+ end
887
+
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
899
+
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
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
949
+ end
950
+
951
+ end; end