1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{Entropy Package Manager Client Packages retrieval Interface}.
10
11 """
12 import os
13 from entropy.i18n import _
14 from entropy.const import *
15 from entropy.exceptions import *
16 from entropy.output import purple, bold, red, blue, darkgreen, darkred, brown, darkblue
17
19
21
22 if os.path.isfile(etpConst['entropyworkdir']+"/"+filepath):
23 if checksum is None:
24 return 0
25 else:
26
27 md5res = self.entropyTools.compare_md5(etpConst['entropyworkdir']+"/"+filepath, checksum)
28 if (md5res):
29 return 0
30 else:
31 return -2
32 else:
33 return -1
34
35 - def fetch_files(self, url_data_list, checksum = True, resume = True, fetch_file_abort_function = None):
36 """
37 Fetch multiple files simultaneously on URLs.
38
39 @param url_data_list list
40 [(url,dest_path [or None],checksum ['ab86fff46f6ec0f4b1e0a2a4a82bf323' or None],branch,),..]
41 @param digest bool, digest check (checksum)
42 @param resume bool enable resume support
43 @param fetch_file_abort_function callable method that could raise exceptions
44 @return general_status_code, {'url': (status_code,checksum,resumed,)}, data_transfer
45 """
46 pkgs_bindir = etpConst['packagesbindir']
47 url_path_list = []
48 checksum_map = {}
49 count = 0
50 for url, dest_path, cksum, branch in url_data_list:
51 count += 1
52 filename = os.path.basename(url)
53 if dest_path == None:
54 dest_path = os.path.join(pkgs_bindir, branch, filename,)
55
56 dest_dir = os.path.dirname(dest_path)
57 if not os.path.isdir(dest_dir):
58 os.makedirs(dest_dir, 0o755)
59
60 url_path_list.append((url, dest_path,))
61 if cksum != None: checksum_map[count] = cksum
62
63
64 fetchConn = self.MultipleUrlFetcher(url_path_list, resume = resume,
65 abort_check_func = fetch_file_abort_function, OutputInterface = self,
66 UrlFetcherClass = self.urlFetcher, checksum = checksum)
67 try:
68 data = fetchConn.download()
69 except KeyboardInterrupt:
70 return -100, {}, 0
71
72 diff_map = {}
73 if checksum_map and checksum:
74 diff_map = dict((url_path_list[x-1][0], checksum_map.get(x)) for x in checksum_map \
75 if checksum_map.get(x) != data.get(x))
76
77 data_transfer = fetchConn.get_data_transfer()
78 if diff_map:
79 defval = -1
80 for key, val in list(diff_map.items()):
81 if val == "-1":
82 diff_map[key] = -1
83 elif val == "-2":
84 diff_map[key] = -2
85 elif val == "-4":
86 diff_map[key] = -4
87 elif val == "-3":
88 diff_map[key] = -3
89 elif val == -100:
90 defval = -100
91 return defval, diff_map, data_transfer
92
93 return 0, diff_map, data_transfer
94
96 """
97 @param download_map list [(repository,branch,filename,checksum (digest),signatures,),..]
98 @param checksum bool verify checksum?
99 @param fetch_abort_function callable method that could raise exceptions
100 """
101 repo_uris = dict(((x[0], self.SystemSettings['repositories']['available'][x[0]]['packages'][::-1],) for x in download_list))
102 remaining = repo_uris.copy()
103 my_download_list = download_list[:]
104
105 def get_best_mirror(repository):
106 try:
107 return remaining[repository][0]
108 except IndexError:
109 return None
110
111 def update_download_list(down_list, failed_down):
112 newlist = []
113 for repo, branch, fname, cksum, signatures in down_list:
114 myuri = get_best_mirror(repo)
115 myuri = os.path.join(myuri, fname)
116 if myuri not in failed_down:
117 continue
118 newlist.append((repo, branch, fname, cksum, signatures,))
119 return newlist
120
121
122 def mirror_fail_check(repository, best_mirror):
123
124 if not self.MirrorStatus.get_failing_mirror_status(best_mirror) >= 30:
125 return False
126
127 self.MirrorStatus.set_failing_mirror_status(best_mirror, 30)
128 mirrorcount = repo_uris[repo].index(best_mirror)+1
129 mytxt = "( mirror #%s ) " % (mirrorcount,)
130 mytxt += blue(" %s: ") % (_("Mirror"),)
131 mytxt += red(self.entropyTools.spliturl(best_mirror)[1])
132 mytxt += " - %s." % (_("maximum failure threshold reached"),)
133 self.updateProgress(
134 mytxt,
135 importance = 1,
136 type = "warning",
137 header = red(" ## ")
138 )
139
140 if self.MirrorStatus.get_failing_mirror_status(best_mirror) == 30:
141 self.MirrorStatus.add_failing_mirror(best_mirror, 45)
142 elif self.MirrorStatus.get_failing_mirror_status(best_mirror) > 31:
143 self.MirrorStatus.add_failing_mirror(best_mirror, -4)
144 else:
145 self.MirrorStatus.set_failing_mirror_status(best_mirror, 0)
146
147 remaining[repository].discard(best_mirror)
148 return True
149
150 def show_download_summary(down_list):
151
152 for repo, branch, fname, cksum, signatures in down_list:
153 best_mirror = get_best_mirror(repo)
154 mirrorcount = repo_uris[repo].index(best_mirror)+1
155 mytxt = "( mirror #%s ) " % (mirrorcount,)
156 basef = os.path.basename(fname)
157 mytxt += "[%s] %s " % (brown(basef), blue("@"),)
158 mytxt += red(self.entropyTools.spliturl(best_mirror)[1])
159
160 self.updateProgress(
161 mytxt,
162 importance = 1,
163 type = "info",
164 header = red(" ## ")
165 )
166
167 def show_successful_download(down_list, data_transfer):
168 for repo, branch, fname, cksum, signatures in down_list:
169 best_mirror = get_best_mirror(repo)
170 mirrorcount = repo_uris[repo].index(best_mirror)+1
171 mytxt = "( mirror #%s ) " % (mirrorcount,)
172 basef = os.path.basename(fname)
173 mytxt += "[%s] %s %s " % (brown(basef), darkred(_("success")), blue("@"),)
174 mytxt += red(self.entropyTools.spliturl(best_mirror)[1])
175 self.updateProgress(
176 mytxt,
177 importance = 1,
178 type = "info",
179 header = red(" ## ")
180 )
181 mytxt = " %s: %s%s%s" % (
182 blue(_("Aggregated transfer rate")),
183 bold(self.entropyTools.bytes_into_human(data_transfer)),
184 darkred("/"),
185 darkblue(_("second")),
186 )
187 self.updateProgress(
188 mytxt,
189 importance = 1,
190 type = "info",
191 header = red(" ## ")
192 )
193
194 def show_download_error(down_list, rc):
195 for repo, branch, fname, cksum, signatures in down_list:
196 best_mirror = get_best_mirror(repo)
197 mirrorcount = repo_uris[repo].index(best_mirror)+1
198 mytxt = "( mirror #%s ) " % (mirrorcount,)
199 mytxt += blue("%s: %s") % (
200 _("Error downloading from"),
201 red(self.entropyTools.spliturl(best_mirror)[1]),
202 )
203 if rc == -1:
204 mytxt += " - %s." % (_("data not available on this mirror"),)
205 elif rc == -2:
206 self.MirrorStatus.add_failing_mirror(best_mirror, 1)
207 mytxt += " - %s." % (_("wrong checksum"),)
208 elif rc == -3:
209 mytxt += " - %s." % (_("not found"),)
210 elif rc == -4:
211 mytxt += " - %s." % (_("timeout error"),)
212 elif rc == -100:
213 mytxt += " - %s." % (_("discarded download"),)
214 else:
215 self.MirrorStatus.add_failing_mirror(best_mirror, 5)
216 mytxt += " - %s." % (_("unknown reason"),)
217 self.updateProgress(
218 mytxt,
219 importance = 1,
220 type = "warning",
221 header = red(" ## ")
222 )
223
224 def remove_failing_mirrors(repos):
225 for repo in repos:
226 best_mirror = get_best_mirror(repo)
227 if remaining[repo]:
228 remaining[repo].pop(0)
229
230 def check_remaining_mirror_failure(repos):
231 return [x for x in repos if not remaining.get(x)]
232
233 while True:
234
235 do_resume = True
236 timeout_try_count = 50
237 while True:
238
239 fetch_files_list = []
240 for repo, branch, fname, cksum, signatures in my_download_list:
241 best_mirror = get_best_mirror(repo)
242
243 self.MirrorStatus.set_working_mirror(best_mirror)
244 if best_mirror != None:
245 mirror_fail_check(repo, best_mirror)
246 best_mirror = get_best_mirror(repo)
247 if best_mirror == None:
248
249
250 return 3, my_download_list
251 myuri = os.path.join(best_mirror, fname)
252 fetch_files_list.append((myuri, None, cksum, branch,))
253
254 try:
255
256 show_download_summary(my_download_list)
257 rc, failed_downloads, data_transfer = self.fetch_files(
258 fetch_files_list, checksum = checksum,
259 fetch_file_abort_function = fetch_abort_function,
260 resume = do_resume
261 )
262 if rc == 0:
263 show_successful_download(my_download_list, data_transfer)
264 return 0, []
265
266
267 my_download_list = update_download_list(my_download_list, failed_downloads)
268 if rc not in (-3, -4, -100,) and failed_downloads and do_resume:
269
270 do_resume = False
271 continue
272 else:
273 show_download_error(my_download_list, rc)
274 if rc == -4:
275 timeout_try_count -= 1
276 if timeout_try_count > 0:
277 continue
278 elif rc == -100:
279 return 1, []
280 myrepos = set([x[0] for x in my_download_list])
281 remove_failing_mirrors(myrepos)
282
283 remaining_failure = check_remaining_mirror_failure(myrepos)
284 if remaining_failure:
285 return 3, my_download_list
286 break
287 except KeyboardInterrupt:
288 return 1, []
289 return 0, []
290
291
292 - def fetch_file(self, url, branch, digest = None, resume = True,
293 fetch_file_abort_function = None, filepath = None):
294
295 def do_stfu_rm(xpath):
296 try:
297 os.remove(xpath)
298 except OSError:
299 pass
300
301 filename = os.path.basename(url)
302 if not filepath:
303 filepath = os.path.join(etpConst['packagesbindir'], branch, filename)
304 filepath_dir = os.path.dirname(filepath)
305
306 if not os.path.isdir(os.path.realpath(filepath_dir)):
307 try:
308 os.remove(filepath_dir)
309 except OSError:
310 pass
311 os.makedirs(filepath_dir, 0o755)
312
313 existed_before = False
314 if os.path.isfile(filepath) and os.path.exists(filepath):
315 existed_before = True
316
317
318 fetchConn = self.urlFetcher(url, filepath, resume = resume,
319 abort_check_func = fetch_file_abort_function, OutputInterface = self)
320 fetchConn.progress = self.progress
321
322
323 data_transfer = 0
324 resumed = False
325 try:
326 fetchChecksum = fetchConn.download()
327 data_transfer = fetchConn.get_transfer_rate()
328 resumed = fetchConn.is_resumed()
329 except KeyboardInterrupt:
330 return -100, data_transfer, resumed
331 except NameError:
332 raise
333 except:
334 if etpUi['debug']:
335 self.updateProgress(
336 "fetch_file:",
337 importance = 1,
338 type = "warning",
339 header = red(" ## ")
340 )
341 self.entropyTools.print_traceback()
342 if (not existed_before) or (not resume):
343 do_stfu_rm(filepath)
344 return -1, data_transfer, resumed
345 if fetchChecksum == "-3":
346
347 return -3, data_transfer, resumed
348 elif fetchChecksum == "-4":
349
350 return -4, data_transfer, resumed
351
352 del fetchConn
353
354 if digest and (fetchChecksum != digest):
355
356 if (not existed_before) or (not resume):
357 do_stfu_rm(filepath)
358 return -2, data_transfer, resumed
359
360 return 0, data_transfer, resumed
361
362
363 - def fetch_file_on_mirrors(self, repository, branch, filename,
364 digest = False, fetch_abort_function = None):
365
366 uris = self.SystemSettings['repositories']['available'][repository]['packages'][::-1]
367 remaining = set(uris)
368
369 mirrorcount = 0
370 for uri in uris:
371
372 if not remaining:
373
374 self.MirrorStatus.set_working_mirror(None)
375 return 3
376
377 self.MirrorStatus.set_working_mirror(uri)
378 mirrorcount += 1
379 mirrorCountText = "( mirror #%s ) " % (mirrorcount,)
380 url = uri + "/" + filename
381
382
383 if self.MirrorStatus.get_failing_mirror_status(uri) >= 30:
384
385
386 self.MirrorStatus.set_failing_mirror_status(uri, 30)
387 mytxt = mirrorCountText
388 mytxt += blue(" %s: ") % (_("Mirror"),)
389 mytxt += red(self.entropyTools.spliturl(uri)[1])
390 mytxt += " - %s." % (_("maximum failure threshold reached"),)
391 self.updateProgress(
392 mytxt,
393 importance = 1,
394 type = "warning",
395 header = red(" ## ")
396 )
397
398 if self.MirrorStatus.get_failing_mirror_status(uri) == 30:
399
400
401 self.MirrorStatus.add_failing_mirror(uri, 45)
402 elif self.MirrorStatus.get_failing_mirror_status(uri) > 31:
403
404
405 self.MirrorStatus.add_failing_mirror(uri, -4)
406 else:
407
408 self.MirrorStatus.set_failing_mirror_status(uri, 0)
409
410 remaining.discard(uri)
411 continue
412
413 do_resume = True
414 timeout_try_count = 50
415 while True:
416 try:
417 mytxt = mirrorCountText
418 mytxt += blue("%s: ") % (_("Downloading from"),)
419 mytxt += red(self.entropyTools.spliturl(uri)[1])
420
421 self.updateProgress(
422 mytxt,
423 importance = 1,
424 type = "warning",
425 header = red(" ## ")
426 )
427 rc, data_transfer, resumed = self.fetch_file(
428 url,
429 branch,
430 digest,
431 do_resume,
432 fetch_file_abort_function = fetch_abort_function
433 )
434 if rc == 0:
435 mytxt = mirrorCountText
436 mytxt += blue("%s: ") % (_("Successfully downloaded from"),)
437 mytxt += red(self.entropyTools.spliturl(uri)[1])
438 human_bytes = self.entropyTools.bytes_into_human(data_transfer)
439 mytxt += " %s %s/%s" % (_("at"), human_bytes, _("second"),)
440 self.updateProgress(
441 mytxt,
442 importance = 1,
443 type = "info",
444 header = red(" ## ")
445 )
446
447 self.MirrorStatus.set_working_mirror(None)
448 return 0
449 elif resumed and (rc not in (-3, -4, -100,)):
450 do_resume = False
451 continue
452 else:
453 error_message = mirrorCountText
454 error_message += blue("%s: %s") % (
455 _("Error downloading from"),
456 red(self.entropyTools.spliturl(uri)[1]),
457 )
458
459 if rc == -1:
460 error_message += " - %s." % (_("file not available on this mirror"),)
461 elif rc == -2:
462 self.MirrorStatus.add_failing_mirror(uri, 1)
463 error_message += " - %s." % (_("wrong checksum"),)
464
465
466 if (data_transfer < 1) and do_resume:
467 error_message += " %s." % (_("Disabling resume"),)
468 do_resume = False
469 continue
470 elif rc == -3:
471 self.MirrorStatus.add_failing_mirror(uri, 3)
472 error_message += " - %s." % (_("not found"),)
473 elif rc == -4:
474 timeout_try_count -= 1
475 if timeout_try_count > 0:
476 error_message += " - %s." % (_("timeout, retrying on this mirror"),)
477 else:
478 error_message += " - %s." % (_("timeout, giving up"),)
479 elif rc == -100:
480 error_message += " - %s." % (_("discarded download"),)
481 else:
482 self.MirrorStatus.add_failing_mirror(uri, 5)
483 error_message += " - %s." % (_("unknown reason"),)
484 self.updateProgress(
485 error_message,
486 importance = 1,
487 type = "warning",
488 header = red(" ## ")
489 )
490 if rc == -4:
491 if timeout_try_count > 0:
492 continue
493 elif rc == -100:
494 self.MirrorStatus.set_working_mirror(None)
495 return 1
496 remaining.discard(uri)
497
498 if not remaining:
499 self.MirrorStatus.set_working_mirror(None)
500 return 3
501 break
502 except KeyboardInterrupt:
503 self.MirrorStatus.set_working_mirror(None)
504 return 1
505
506 self.MirrorStatus.set_working_mirror(None)
507 return 0
508