Last active
August 12, 2019 08:32
-
-
Save tompng/b7503c766980e3e34d5f70e733aa7079 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'io/console' | |
require 'net/http' | |
require 'digest' | |
class Loader | |
def initialize(url, download_dir) | |
@url = url | |
uri = URI.parse url | |
@file_name = File.basename(url).gsub(/[^a-zA-Z0-9_.]/, '').gsub(/^\./, 'file.') | |
@file_path = "#{download_dir}/#{@file_name}" | |
@loaded_size = File.exist?(@file_path) ? File.size(@file_path) : 0 | |
@content_length = Net::HTTP.new(uri.hostname, uri.port).head(uri.path).header['content-length'].to_i | |
p @url | |
p @content_length | |
@missings = [[@loaded_size, @content_length]] | |
@mutex = Mutex.new | |
@partials = [] | |
end | |
def take(size, &block) | |
@mutex.synchronize do | |
segment = @missings.shift | |
return nil unless segment | |
from, to = segment | |
if to - from >= 2 * size | |
@missings.unshift [from + size, to] | |
[from, from + size] | |
else | |
[from, to] | |
end | |
end | |
end | |
def flush | |
@partials.sort_by!(&:first) | |
while true do | |
from, to, data = @partials.first | |
break unless from == @loaded_size | |
File.open @file_path, 'a' do |f| | |
f.write data | |
end | |
@partials.shift | |
@loaded_size = to | |
end | |
end | |
def loaded from, to, data | |
@mutex.synchronize do | |
@partials << [from, to, data] | |
flush | |
show_progress | |
end | |
end | |
def failed from, to | |
@mutex.synchronize do | |
@missings.unshift [from, to] | |
end | |
end | |
def show_progress | |
@loaded_size | |
@content_length | |
width = [IO.console.winsize.last, 40].max | |
progress = [' '] * width | |
(width * @loaded_size / @content_length).times { |i| progress[i] = 'a' } | |
@partials.map do |from, to| | |
from_idx = width * from / @content_length | |
to_idx = width * to / @content_length | |
(from_idx...to_idx).each { |i| progress[i] = 'b' } | |
end | |
progress_text = progress.join | |
{ | |
' ' => ["\e[47m", ' '], | |
'a' => ["\e[42;32m", '#'], | |
'b' => ["\e[47;32m", '|'] | |
}.each do |c, (esc, c2)| | |
progress_text.gsub!(/#{c}+/) { |s| esc + c2 * s.size + "\e[m" } | |
end | |
stat = "%#{@content_length.to_s.size}d / %d %02.2f%%" % [ | |
@loaded_size, | |
@content_length, | |
100.0 * @loaded_size / @content_length | |
] | |
message = "#{@file_name[0, width - stat.size - 2]} #{stat}" | |
STDOUT.write "\n" + progress_text + "\eM\r\e[K" + message | |
end | |
def partial from, to | |
range = "#{from}-#{to - 1}" | |
# data = HTTP::Client.get(@url, headers: { range: range }).response.body | |
data = IO.popen ['curl', '-r', range, @url], err: File::NULL do |io| | |
io.binmode.read | |
end | |
raise "wrong size #{data.size} #{to - from}" if data.size != to - from | |
data | |
end | |
def run partial_size: 500_000, num: 16 | |
threads = num.times.map do | |
Thread.new do | |
loop do | |
segment = take partial_size | |
break unless segment | |
begin | |
data = partial(*segment) | |
loaded(*segment, data) | |
rescue => e | |
p e | |
puts e.backtrace | |
failed(*segment) | |
sleep 1 | |
end | |
end | |
end | |
end | |
threads.map(&:join) | |
end | |
end | |
download_dir = 'downloads' | |
Dir.mkdir download_dir unless Dir.exist? download_dir | |
gitignore_file = '.gitignore' | |
File.write gitignore_file, "#{download_dir}/\n#{gitignore_file}\n" unless File.exist? gitignore_file | |
url = ARGV[0] | |
Loader.new(url, download_dir).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment