docs.ruby-lang.org か rubygems.org に良い感じの実装が転がっている気がするんだけど、みつけられてない。
-
Net::FTP#retrbinary
を使ってオフセットを指定-
Net::FTP#resume=
で resume モードにしておく必要あり
-
-
IO.copy_stream
- 良い感じの file 結合処理を思いつけず
IO.copy_stream
を使った - 並行分割ダウンロード結果を統合するロジックを分かっていない
- 良い感じの file 結合処理を思いつけず
- 同時接続数を制限しているサーバ相手には機能しない
require 'net/ftp'
require 'tmpdir'
require 'thwait'
DEBUG = !ENV.fetch('DEBUG', '').empty?
CONCURRENT = ENV.fetch('CONCURRENT').to_i
FTP_HOST = ENV.fetch('FTP_HOST')
FTP_PORT = ENV.fetch('FTP_PORT').to_i
FTP_USER = ENV.fetch('FTP_USER')
FTP_PASS = ENV.fetch('FTP_PASS')
FTP_PATH = ENV.fetch('FTP_PATH')
def establish_connection
Net::FTP.new(FTP_HOST, port: FTP_PORT, username: FTP_USER, password: FTP_PASS, passive: true, debug_mode: DEBUG)
end
content_size = establish_connection.size(FTP_PATH)
page_size = (content_size.to_f / CONCURRENT).ceil
Dir.mktmpdir do |dir|
threads = []
CONCURRENT.times do |i|
offset = page_size * i
next if offset >= content_size
threads << Thread.start(i) do |t|
File.open("#{dir}/#{t}", 'wb') do |page|
ftp = establish_connection
ftp.resume = true
retrieved = 0
ftp.retrbinary("RETR #{FTP_PATH}", Net::FTP::DEFAULT_BLOCKSIZE, offset) do |chunk|
rest = page_size - retrieved
retrievable = [rest, chunk.bytesize].min
break if retrievable <= 0
retrieved += page.write(chunk[0, retrievable])
end
end
end
end
ThreadsWait.all_waits(*threads)
File.open('downloaded', 'wb') do |output|
CONCURRENT.times do |i|
path = "#{dir}/#{i}"
next unless File.exist?(path)
IO.copy_stream(path, output)
end
end
end
__END__
### e.g. 495MB CentOS ISO file
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
1: 165.29 real 5.34 user 4.74 sys
3: 99.68 real 5.23 user 6.12 sys
5: 88.19 real 5.56 user 7.43 sys