@@ -12,26 +12,20 @@ module RemoteInput
12
12
class Downloader
13
13
class TooManyRedirects < Error ; end
14
14
15
- def initialize ( url )
16
- if url . is_a? ( URI ::Generic )
17
- url = url . dup
18
- else
19
- url = URI . parse ( url )
20
- end
21
- @url = url
22
- unless @url . is_a? ( URI ::HTTP )
23
- raise ArgumentError , "download URL must be HTTP or HTTPS: <#{ @url } >"
24
- end
15
+ def initialize ( url , *fallback_urls , http_method : nil , http_parameters : nil )
16
+ @url = normalize_url ( url )
17
+ @fallback_urls = fallback_urls . collect { |fallback_url | normalize_url ( fallback_url ) }
18
+ @http_method = http_method
19
+ @http_parameters = http_parameters
25
20
end
26
21
27
22
def download ( output_path , &block )
28
- if output_path . exist?
29
- yield_chunks ( output_path , &block ) if block_given?
30
- return
31
- end
23
+ return if use_cache ( output_path , &block )
32
24
33
25
partial_output_path = Pathname . new ( "#{ output_path } .partial" )
34
26
synchronize ( output_path , partial_output_path ) do
27
+ return if use_cache ( output_path , &block )
28
+
35
29
output_path . parent . mkpath
36
30
37
31
n_retries = 0
@@ -47,7 +41,7 @@ def download(output_path, &block)
47
41
headers [ "Range" ] = "bytes=#{ start } -"
48
42
end
49
43
50
- start_http ( @url , headers ) do |response |
44
+ start_http ( @url , @fallback_urls , headers ) do |response |
51
45
if response . is_a? ( Net ::HTTPPartialContent )
52
46
mode = "ab"
53
47
else
@@ -87,6 +81,27 @@ def download(output_path, &block)
87
81
end
88
82
end
89
83
84
+ private def normalize_url ( url )
85
+ if url . is_a? ( URI ::Generic )
86
+ url = url . dup
87
+ else
88
+ url = URI . parse ( url )
89
+ end
90
+ unless url . is_a? ( URI ::HTTP )
91
+ raise ArgumentError , "download URL must be HTTP or HTTPS: <#{ url } >"
92
+ end
93
+ url
94
+ end
95
+
96
+ private def use_cache ( output_path , &block )
97
+ if output_path . exist?
98
+ yield_chunks ( output_path , &block ) if block_given?
99
+ true
100
+ else
101
+ false
102
+ end
103
+ end
104
+
90
105
private def synchronize ( output_path , partial_output_path )
91
106
begin
92
107
Process . getpgid ( Process . pid )
@@ -106,7 +121,8 @@ def download(output_path, &block)
106
121
rescue ArgumentError
107
122
# The process that acquired the lock will be exited before
108
123
# it stores its process ID.
109
- valid_lock_path = ( lock_path . mtime > 10 )
124
+ elapsed_time = Time . now - lock_path . mtime
125
+ valid_lock_path = ( elapsed_time > 10 )
110
126
else
111
127
begin
112
128
Process . getpgid ( pid )
@@ -135,7 +151,7 @@ def download(output_path, &block)
135
151
end
136
152
end
137
153
138
- private def start_http ( url , headers , limit = 10 , &block )
154
+ private def start_http ( url , fallback_urls , headers , limit = 10 , &block )
139
155
if limit == 0
140
156
raise TooManyRedirects , "too many redirections: #{ url } "
141
157
end
@@ -145,16 +161,47 @@ def download(output_path, &block)
145
161
http . start do
146
162
path = url . path
147
163
path += "?#{ url . query } " if url . query
148
- request = Net ::HTTP ::Get . new ( path , headers )
164
+ if @http_method == :post
165
+ # TODO: We may want to add @http_content_type, @http_body
166
+ # and so on.
167
+ if @http_parameters
168
+ body = URI . encode_www_form ( @http_parameters )
169
+ content_type = "application/x-www-form-urlencoded"
170
+ headers = { "Content-Type" => content_type } . merge ( headers )
171
+ else
172
+ body = ""
173
+ end
174
+ request = Net ::HTTP ::Post . new ( path , headers )
175
+ request . body = body
176
+ else
177
+ request = Net ::HTTP ::Get . new ( path , headers )
178
+ end
179
+ if url . scheme == "https" and url . host == "api.github.com"
180
+ gh_token = ENV [ "GH_TOKEN" ]
181
+ if gh_token
182
+ headers = headers . merge ( "Authorization" => "Bearer #{ gh_token } " )
183
+ end
184
+ end
149
185
http . request ( request ) do |response |
150
186
case response
151
187
when Net ::HTTPSuccess , Net ::HTTPPartialContent
152
188
return block . call ( response )
153
189
when Net ::HTTPRedirection
154
190
url = URI . parse ( response [ :location ] )
155
191
$stderr. puts "Redirect to #{ url } "
156
- return start_http ( url , headers , limit - 1 , &block )
192
+ return start_http ( url , fallback_urls , headers , limit - 1 , &block )
157
193
else
194
+ case response
195
+ when Net ::HTTPForbidden , Net ::HTTPNotFound
196
+ next_url , *rest_fallback_urls = fallback_urls
197
+ if next_url
198
+ message = "#{ response . code } : #{ response . message } : " +
199
+ "fallback: <#{ url } > -> <#{ next_url } >"
200
+ $stderr. puts ( message )
201
+ return start_http ( next_url , rest_fallback_urls , headers , &block )
202
+ end
203
+ end
204
+
158
205
message = response . code
159
206
if response . message and not response . message . empty?
160
207
message += ": #{ response . message } "
@@ -169,7 +216,7 @@ def download(output_path, &block)
169
216
private def yield_chunks ( path )
170
217
path . open ( "rb" ) do |output |
171
218
chunk_size = 1024 * 1024
172
- chunk = ""
219
+ chunk = + ""
173
220
while output . read ( chunk_size , chunk )
174
221
yield ( chunk )
175
222
end
0 commit comments