Skip to content

Instantly share code, notes, and snippets.

@tompng
Last active August 12, 2019 08:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tompng/b7503c766980e3e34d5f70e733aa7079 to your computer and use it in GitHub Desktop.
Save tompng/b7503c766980e3e34d5f70e733aa7079 to your computer and use it in GitHub Desktop.
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