Skip to content

Instantly share code, notes, and snippets.

@floehopper
Created November 16, 2024 10:52
Show Gist options
  • Select an option

  • Save floehopper/fe59485e96c6f856a9c060b892d03f00 to your computer and use it in GitHub Desktop.

Select an option

Save floehopper/fe59485e96c6f856a9c060b892d03f00 to your computer and use it in GitHub Desktop.

Revisions

  1. floehopper created this gist Nov 16, 2024.
    77 changes: 77 additions & 0 deletions download
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    #!/usr/bin/env ruby

    require "bundler/inline"

    gemfile do
    source "https://rubygems.org"
    gem "typhoeus"
    end

    require "uri"
    require "typhoeus"

    abort "Usage: ruby download.rb <url>" if ARGV.empty?
    url = ARGV.first

    response = Typhoeus.head(url)

    if response.success?
    if content_length = response.headers["Content-Length"]
    total_size = content_length.to_i
    else
    abort "Content-Length header is missing, unable to determine file size."
    end
    if content_disposition = response.headers["Content-Disposition"]
    match = response.headers["Content-Disposition"].match(/filename="([^"]+)"/)
    filename = match[1] if match
    end
    else
    abort "HEAD request failed: #{response.code}"
    end

    unless filename
    uri = URI.parse(url)
    filename = File.basename(uri.path)
    end

    num_parts = 4
    part_size = (total_size / num_parts.to_f).ceil

    ranges = Array.new(num_parts) do |i|
    start_byte = i * part_size
    end_byte = [start_byte + part_size - 1, total_size - 1].min
    "bytes=#{start_byte}-#{end_byte}"
    end

    hydra = Typhoeus::Hydra.new
    parts = []

    ranges.each_with_index do |range, index|
    request = Typhoeus::Request.new(url, headers: { "Range" => range })
    request.on_complete do |response|
    if response.success?
    file_name = "part_#{index}"
    File.write(file_name, response.body)
    parts << file_name
    puts response.headers["Content-Range"]
    puts "Downloaded part #{index}: #{response.code}"
    else
    puts "Failed to download part #{index}: #{response.code}"
    end
    end
    hydra.queue(request)
    end

    hydra.run

    File.open(filename, "wb") do |output|
    parts.each do |part|
    File.open(part, "rb") do |input|
    IO.copy_stream(input, output)
    end
    end
    end

    parts.each { |part| File.delete(part) }

    puts "Download complete: #{filename}"