Module: Down

Defined in:
lib/down.rb,
lib/down/version.rb

Defined Under Namespace

Modules: DownloadedFile Classes: ChunkedIO, Error, NotFound, TooLarge

Constant Summary collapse

VERSION =
"2.3.5"

Class Method Summary collapse

Class Method Details

.copy_to_tempfile(basename, io) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/down.rb', line 123

def copy_to_tempfile(basename, io)
  tempfile = Tempfile.new(["down", File.extname(basename)], binmode: true)
  if io.is_a?(OpenURI::Meta) && io.is_a?(Tempfile)
    io.close
    tempfile.close
    FileUtils.mv io.path, tempfile.path
  else
    IO.copy_stream(io, tempfile)
    io.rewind
  end
  tempfile.open
  tempfile
end

.download(url, options = {}) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/down.rb', line 16

def download(url, options = {})
  warn "Passing :timeout option to `Down.download` is deprecated and will be removed in Down 3. You should use open-uri's :open_timeout and/or :read_timeout." if options.key?(:timeout)
  warn "Passing :progress option to `Down.download` is deprecated and will be removed in Down 3. You should use open-uri's :progress_proc." if options.key?(:progress)

  max_size            = options.delete(:max_size)
  max_redirects       = options.delete(:max_redirects) || 2
  progress_proc       = options.delete(:progress_proc) || options.delete(:progress)
  content_length_proc = options.delete(:content_length_proc)
  timeout             = options.delete(:timeout)

  tries = max_redirects + 1

  begin
    uri = URI.parse(url)

    open_uri_options = {
      "User-Agent" => "Down/#{VERSION}",
      content_length_proc: proc { |size|
        if size && max_size && size > max_size
          raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
        end
        content_length_proc.call(size) if content_length_proc
      },
      progress_proc: proc { |current_size|
        if max_size && current_size > max_size
          raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
        end
        progress_proc.call(current_size) if progress_proc
      },
      read_timeout: timeout,
      redirect: false,
    }

    if uri.user || uri.password
      open_uri_options[:http_basic_authentication] = [uri.user, uri.password]
      uri.user = nil
      uri.password = nil
    end

    open_uri_options.update(options)

    downloaded_file = uri.open(open_uri_options)
  rescue OpenURI::HTTPRedirect => redirect
    url = redirect.uri.to_s
    retry if (tries -= 1) > 0
    raise Down::NotFound, "too many redirects"
  rescue => error
    raise if error.is_a?(Down::Error)
    raise Down::NotFound, "file not found"
  end

  # open-uri will return a StringIO instead of a Tempfile if the filesize is
  # less than 10 KB, so if it happens we convert it back to Tempfile. We want
  # to do this with a Tempfile as well, because open-uri doesn't preserve the
  # file extension, so we want to run it against #copy_to_tempfile which
  # does.
  open_uri_file = downloaded_file
  downloaded_file = copy_to_tempfile(uri.path, open_uri_file)
  OpenURI::Meta.init downloaded_file, open_uri_file

  downloaded_file.extend DownloadedFile
  downloaded_file
end

.open(url, options = {}) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/down.rb', line 86

def open(url, options = {})
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port)

  # taken from open-uri implementation
  if uri.is_a?(URI::HTTPS)
    require "net/https"
    http.use_ssl = true
    http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
    store = OpenSSL::X509::Store.new
    if options[:ssl_ca_cert]
      Array(options[:ssl_ca_cert]).each do |cert|
        File.directory?(cert) ? store.add_path(cert) : store.add_file(cert)
      end
    else
      store.set_default_paths
    end
    http.cert_store = store
  end

  request = Fiber.new do
    http.start do
      http.request_get(url) do |response|
        Fiber.yield response
        response.instance_variable_set("@read", true)
      end
    end
  end

  response = request.resume
  content_length = Integer(response["Content-Length"]) if response["Content-Length"]
  chunks = response.to_enum(:read_body)
  close_connection = -> { request.resume }

  ChunkedIO.new(size: content_length, chunks: chunks, on_close: close_connection)
end

.stream(url, options = {}) ⇒ Object



80
81
82
83
84
# File 'lib/down.rb', line 80

def stream(url, options = {})
  warn "Down.stream is deprecated and will be removed in Down 3. Use Down.open instead."
  io = open(url, options)
  io.each_chunk { |chunk| yield chunk, io.size }
end