Skip to content

Commit 86c2b04

Browse files
committed
Merge pull request #11 from thedarkone/large_read_size
Properly handle receiving less data than requested
2 parents a9a8064 + 73b9ab1 commit 86c2b04

File tree

2 files changed

+35
-14
lines changed

2 files changed

+35
-14
lines changed

lib/net/sftp/operations/download.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ def download_next_chunk(entry)
318318
request = sftp.read(entry.handle, entry.offset, read_size, &method(:on_read))
319319
request[:entry] = entry
320320
request[:offset] = entry.offset
321-
entry.offset += read_size
322321
end
323322

324323
# Called when a read from a file finishes. If the read was successful
@@ -335,6 +334,7 @@ def on_read(response)
335334
elsif !response.ok?
336335
raise "read #{entry.remote}: #{response}"
337336
else
337+
entry.offset += response[:data].bytesize
338338
update_progress(:get, entry, response.request[:offset], response[:data])
339339
entry.sink.write(response[:data])
340340
download_next_chunk(entry)

test/test_download.rb

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require "common"
22

33
class DownloadTest < Net::SFTP::TestCase
4+
FXP_DATA_CHUNK_SIZE = 1024
5+
46
def setup
57
prepare_progress!
68
end
@@ -30,6 +32,19 @@ def test_download_large_file_should_transfer_remote_to_local
3032
assert_equal text, file.string
3133
end
3234

35+
def test_download_large_file_should_handle_too_large_read_size
36+
local = "/path/to/local"
37+
remote = "/path/to/remote"
38+
text = "0123456789" * 1024
39+
40+
# some servers put upper bound on the max read_size value and send less data than requested
41+
too_large_read_size = FXP_DATA_CHUNK_SIZE + 1
42+
file = prepare_large_file_download(local, remote, text, too_large_read_size)
43+
44+
assert_scripted_command { sftp.download(remote, local, :read_size => too_large_read_size) }
45+
assert_equal text, file.string
46+
end
47+
3348
def test_download_large_file_with_progress_should_report_progress
3449
local = "/path/to/local"
3550
remote = "/path/to/remote"
@@ -121,25 +136,29 @@ def expect_file_transfer(remote, text)
121136
channel.gets_packet(FXP_HANDLE, :long, 0, :string, "handle")
122137
channel.sends_packet(FXP_READ, :long, 1, :string, "handle", :int64, 0, :long, 32_000)
123138
channel.gets_packet(FXP_DATA, :long, 1, :string, text)
124-
channel.sends_packet(FXP_READ, :long, 2, :string, "handle", :int64, 32_000, :long, 32_000)
139+
channel.sends_packet(FXP_READ, :long, 2, :string, "handle", :int64, text.bytesize, :long, 32_000)
125140
channel.gets_packet(FXP_STATUS, :long, 2, :long, 1)
126141
channel.sends_packet(FXP_CLOSE, :long, 3, :string, "handle")
127142
channel.gets_packet(FXP_STATUS, :long, 3, :long, 0)
128143
end
129144
end
130145

131-
def prepare_large_file_download(local, remote, text)
146+
def prepare_large_file_download(local, remote, text, requested_chunk_size = FXP_DATA_CHUNK_SIZE)
132147
expect_sftp_session :server_version => 3 do |channel|
133148
channel.sends_packet(FXP_OPEN, :long, 0, :string, remote, :long, 0x01, :long, 0)
134149
channel.gets_packet(FXP_HANDLE, :long, 0, :string, "handle")
135-
10.times do |n|
136-
channel.sends_packet(FXP_READ, :long, n+1, :string, "handle", :int64, n*1024, :long, 1024)
137-
channel.gets_packet(FXP_DATA, :long, n+1, :string, text[n*1024,1024])
150+
offset = 0
151+
data_packet_count = (text.bytesize / FXP_DATA_CHUNK_SIZE.to_f).ceil
152+
data_packet_count.times do |n|
153+
payload = text[n*FXP_DATA_CHUNK_SIZE,FXP_DATA_CHUNK_SIZE]
154+
channel.sends_packet(FXP_READ, :long, n+1, :string, "handle", :int64, offset, :long, requested_chunk_size)
155+
offset += payload.bytesize
156+
channel.gets_packet(FXP_DATA, :long, n+1, :string, payload)
138157
end
139-
channel.sends_packet(FXP_READ, :long, 11, :string, "handle", :int64, 10240, :long, 1024)
140-
channel.gets_packet(FXP_STATUS, :long, 11, :long, 1)
141-
channel.sends_packet(FXP_CLOSE, :long, 12, :string, "handle")
142-
channel.gets_packet(FXP_STATUS, :long, 12, :long, 0)
158+
channel.sends_packet(FXP_READ, :long, data_packet_count + 1, :string, "handle", :int64, offset, :long, requested_chunk_size)
159+
channel.gets_packet(FXP_STATUS, :long, data_packet_count + 1, :long, 1)
160+
channel.sends_packet(FXP_CLOSE, :long, data_packet_count + 2, :string, "handle")
161+
channel.gets_packet(FXP_STATUS, :long, data_packet_count + 2, :long, 0)
143162
end
144163

145164
file = StringIO.new
@@ -182,6 +201,8 @@ def prepare_large_file_download(local, remote, text)
182201
# <- 15:STATUS(0)
183202

184203
def prepare_directory_tree_download(local, remote)
204+
file1_contents = "contents of file1"
205+
file2_contents = "contents of file2"
185206
expect_sftp_session :server_version => 3 do |channel|
186207
channel.sends_packet(FXP_OPENDIR, :long, 0, :string, remote)
187208
channel.gets_packet(FXP_HANDLE, :long, 0, :string, "dir1")
@@ -214,8 +235,8 @@ def prepare_directory_tree_download(local, remote)
214235
channel.sends_packet(FXP_OPEN, :long, 8, :string, File.join(remote, "subdir1", "file2"), :long, 0x01, :long, 0)
215236
channel.sends_packet(FXP_READDIR, :long, 9, :string, "dir2")
216237

217-
channel.gets_packet(FXP_DATA, :long, 6, :string, "contents of file1")
218-
channel.sends_packet(FXP_READ, :long, 10, :string, "file1", :int64, 32_000, :long, 32_000)
238+
channel.gets_packet(FXP_DATA, :long, 6, :string, file1_contents)
239+
channel.sends_packet(FXP_READ, :long, 10, :string, "file1", :int64, file1_contents.bytesize, :long, 32_000)
219240

220241
channel.gets_packet(FXP_STATUS, :long, 7, :long, 0)
221242
channel.gets_packet(FXP_HANDLE, :long, 8, :string, "file2")
@@ -227,8 +248,8 @@ def prepare_directory_tree_download(local, remote)
227248
channel.gets_packet(FXP_STATUS, :long, 10, :long, 1)
228249
channel.sends_packet(FXP_CLOSE, :long, 13, :string, "file1")
229250

230-
channel.gets_packet(FXP_DATA, :long, 11, :string, "contents of file2")
231-
channel.sends_packet(FXP_READ, :long, 14, :string, "file2", :int64, 32_000, :long, 32_000)
251+
channel.gets_packet(FXP_DATA, :long, 11, :string, file2_contents)
252+
channel.sends_packet(FXP_READ, :long, 14, :string, "file2", :int64, file2_contents.bytesize, :long, 32_000)
232253

233254
channel.gets_packet(FXP_STATUS, :long, 12, :long, 0)
234255
channel.gets_packet(FXP_STATUS, :long, 13, :long, 0)

0 commit comments

Comments
 (0)