]>
Commit | Line | Data |
---|---|---|
940227cb MT |
1 | #!/usr/bin/python |
2 | ||
9068dba1 MT |
3 | from __future__ import division |
4 | ||
5 | import datetime | |
940227cb | 6 | import logging |
0673d1b0 | 7 | import math |
54af860e | 8 | import os.path |
0673d1b0 | 9 | import random |
940227cb MT |
10 | import socket |
11 | import time | |
12 | import tornado.httpclient | |
9068dba1 MT |
13 | import tornado.netutil |
14 | import urlparse | |
940227cb | 15 | |
9068dba1 | 16 | from misc import Object |
60024cc8 | 17 | |
9068dba1 | 18 | class Downloads(Object): |
60024cc8 MT |
19 | @property |
20 | def total(self): | |
21 | ret = self.db.get("SELECT COUNT(*) AS total FROM log_download") | |
22 | ||
23 | return ret.total | |
24 | ||
25 | @property | |
26 | def today(self): | |
9068dba1 | 27 | ret = self.db.get("SELECT COUNT(*) AS today FROM log_download WHERE date::date = NOW()::date") |
60024cc8 MT |
28 | |
29 | return ret.today | |
30 | ||
31 | @property | |
32 | def yesterday(self): | |
9068dba1 | 33 | ret = self.db.get("SELECT COUNT(*) AS yesterday FROM log_download WHERE date::date = (NOW() - INTERVAL '1 day')::date") |
60024cc8 MT |
34 | |
35 | return ret.yesterday | |
36 | ||
37 | @property | |
38 | def daily_map(self): | |
9068dba1 MT |
39 | ret = self.db.query("SELECT date::date AS date, COUNT(*) AS downloads FROM log_download" |
40 | " WHERE date::date BETWEEN (NOW() - INTERVAL '30 days')::date AND NOW()::date GROUP BY date::date") | |
60024cc8 MT |
41 | |
42 | return ret | |
43 | ||
44 | def get_countries(self, duration="all"): | |
45 | query = "SELECT country_code, count(country_code) AS count FROM log_download" | |
46 | ||
47 | if duration == "today": | |
9068dba1 | 48 | query += " WHERE date::date = NOW()::date" |
60024cc8 MT |
49 | |
50 | query += " GROUP BY country_code ORDER BY count DESC" | |
51 | ||
52 | results = self.db.query(query) | |
53 | ret = {} | |
54 | ||
55 | count = sum([o.count for o in results]) | |
9068dba1 MT |
56 | if count: |
57 | for res in results: | |
58 | ret[res.country_code] = res.count / count | |
60024cc8 MT |
59 | |
60 | return ret | |
61 | ||
62 | def get_mirror_load(self, duration="all"): | |
63 | query = "SELECT mirror, COUNT(mirror) AS count FROM log_download" | |
64 | ||
65 | if duration == "today": | |
9068dba1 | 66 | query += " WHERE date::date = NOW()::date" |
60024cc8 MT |
67 | |
68 | query += " GROUP BY mirror ORDER BY count DESC" | |
69 | ||
70 | results = self.db.query(query) | |
71 | ret = {} | |
72 | ||
73 | count = sum([o.count for o in results]) | |
9068dba1 MT |
74 | if count: |
75 | for res in results: | |
76 | mirror = self.mirrors.get(res.mirror) | |
77 | ret[mirror.hostname] = res.count / count | |
60024cc8 MT |
78 | |
79 | return ret | |
80 | ||
81 | ||
9068dba1 | 82 | class Mirrors(Object): |
940227cb | 83 | def check_all(self): |
9068dba1 | 84 | for mirror in self.get_all(): |
940227cb MT |
85 | mirror.check() |
86 | ||
87 | def get(self, id): | |
9068dba1 | 88 | return Mirror(self.backend, id) |
940227cb | 89 | |
0673d1b0 | 90 | def get_all(self): |
9068dba1 MT |
91 | res = self.db.query("SELECT * FROM mirrors WHERE enabled = %s", True) |
92 | ||
93 | mirrors = [] | |
94 | for row in res: | |
95 | mirror = Mirror(self.backend, row.id, row) | |
96 | mirrors.append(mirror) | |
97 | ||
98 | return MirrorSet(self.backend, sorted(mirrors)) | |
0673d1b0 | 99 | |
bd17b7d1 | 100 | def get_all_up(self): |
9068dba1 MT |
101 | res = self.db.query("SELECT * FROM mirrors WHERE enabled = %s AND state = %s \ |
102 | ORDER BY hostname", True, "UP") | |
bd17b7d1 MT |
103 | |
104 | mirrors = [] | |
105 | for row in res: | |
9068dba1 | 106 | m = Mirror(self.backend, row.id, row) |
bd17b7d1 MT |
107 | mirrors.append(m) |
108 | ||
9068dba1 | 109 | return MirrorSet(self.backend, mirrors) |
bd17b7d1 | 110 | |
940227cb MT |
111 | def get_by_hostname(self, hostname): |
112 | mirror = self.db.get("SELECT id FROM mirrors WHERE hostname=%s", hostname) | |
113 | ||
9068dba1 | 114 | return Mirror(self.backend, mirror.id) |
940227cb | 115 | |
54af860e MT |
116 | def get_with_file(self, filename, country=None): |
117 | # XXX quick and dirty solution - needs a performance boost | |
118 | mirror_ids = [m.mirror for m in self.db.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename)] | |
119 | ||
120 | #if country: | |
121 | # # Sort out all mirrors that are not preferred to the given country | |
122 | # for mirror in self.get_for_country(country): | |
123 | # if not mirror.id in mirror_ids: | |
124 | # mirror_ids.remove(mirror.id) | |
125 | ||
126 | mirrors = [] | |
127 | for mirror_id in mirror_ids: | |
128 | mirror = self.get(mirror_id) | |
129 | if not mirror.state == "UP": | |
130 | continue | |
131 | mirrors.append(mirror) | |
132 | ||
54af860e MT |
133 | return mirrors |
134 | ||
135 | def get_for_country(self, country): | |
136 | # XXX need option for random order | |
137 | mirrors = self.db.query("SELECT id FROM mirrors WHERE prefer_for_countries LIKE %s", country) | |
138 | ||
139 | for mirror in mirrors: | |
140 | yield self.get(mirror.id) | |
940227cb | 141 | |
9068dba1 | 142 | def get_for_location(self, location): |
119f55d7 MT |
143 | if not location: |
144 | return None | |
145 | ||
9068dba1 MT |
146 | distance = 2500 |
147 | ||
148 | mirrors = [] | |
149 | all_mirrors = self.get_all() | |
150 | ||
151 | while all_mirrors and len(mirrors) <= 3 and distance <= 8000: | |
0673d1b0 | 152 | for mirror in all_mirrors: |
9068dba1 MT |
153 | mirror_distance = mirror.distance_to(location) |
154 | if mirror_distance is None: | |
155 | continue | |
156 | ||
157 | if mirror_distance<= distance: | |
0673d1b0 MT |
158 | mirrors.append(mirror) |
159 | all_mirrors.remove(mirror) | |
160 | ||
161 | distance *= 1.2 | |
162 | ||
163 | return mirrors | |
164 | ||
edd297c4 MT |
165 | def get_all_files(self): |
166 | files = [] | |
167 | ||
9068dba1 | 168 | for mirror in self.get_all(): |
edd297c4 MT |
169 | if not mirror.state == "UP": |
170 | continue | |
171 | ||
172 | for file in mirror.filelist: | |
173 | if not file in files: | |
174 | files.append(file) | |
175 | ||
176 | return files | |
177 | ||
940227cb | 178 | |
9068dba1 MT |
179 | class MirrorSet(Object): |
180 | def __init__(self, backend, mirrors): | |
181 | Object.__init__(self, backend) | |
182 | ||
0673d1b0 MT |
183 | self._mirrors = mirrors |
184 | ||
185 | def __add__(self, other): | |
186 | mirrors = [] | |
187 | ||
188 | for mirror in self._mirrors + other._mirrors: | |
189 | if mirror in mirrors: | |
190 | continue | |
191 | ||
192 | mirrors.append(mirror) | |
193 | ||
9068dba1 | 194 | return MirrorSet(self.backend, mirrors) |
0673d1b0 MT |
195 | |
196 | def __sub__(self, other): | |
197 | mirrors = self._mirrors[:] | |
198 | ||
199 | for mirror in other._mirrors: | |
200 | if mirror in mirrors: | |
201 | mirrors.remove(mirror) | |
202 | ||
9068dba1 | 203 | return MirrorSet(self.backend, mirrors) |
0673d1b0 MT |
204 | |
205 | def __iter__(self): | |
206 | return iter(self._mirrors) | |
207 | ||
208 | def __len__(self): | |
209 | return len(self._mirrors) | |
210 | ||
211 | def __str__(self): | |
212 | return "<MirrorSet %s>" % ", ".join([m.hostname for m in self._mirrors]) | |
213 | ||
0673d1b0 MT |
214 | def get_with_file(self, filename): |
215 | with_file = [m.mirror for m in self.db.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename)] | |
216 | ||
217 | mirrors = [] | |
218 | for mirror in self._mirrors: | |
219 | if mirror.id in with_file: | |
220 | mirrors.append(mirror) | |
221 | ||
9068dba1 | 222 | return MirrorSet(self.backend, mirrors) |
0673d1b0 MT |
223 | |
224 | def get_random(self): | |
225 | mirrors = [] | |
226 | for mirror in self._mirrors: | |
f1f7eb7e | 227 | for i in range(0, mirror.priority): |
0673d1b0 MT |
228 | mirrors.append(mirror) |
229 | ||
230 | return random.choice(mirrors) | |
231 | ||
232 | def get_for_country(self, country): | |
233 | mirrors = [] | |
234 | ||
235 | for mirror in self._mirrors: | |
236 | if country in mirror.prefer_for_countries: | |
237 | mirrors.append(mirror) | |
238 | ||
9068dba1 | 239 | return MirrorSet(self.backend, mirrors) |
0673d1b0 | 240 | |
9068dba1 MT |
241 | def get_for_location(self, location): |
242 | distance = 2500 | |
0673d1b0 MT |
243 | mirrors = [] |
244 | ||
119f55d7 | 245 | if location: |
9068dba1 | 246 | while len(mirrors) <= 3 and distance <= 8000: |
119f55d7 MT |
247 | for mirror in self._mirrors: |
248 | if mirror in mirrors: | |
249 | continue | |
0673d1b0 | 250 | |
9068dba1 MT |
251 | mirror_distance = mirror.distance_to(location) |
252 | if mirror_distance is None: | |
253 | continue | |
254 | ||
255 | if mirror_distance <= distance: | |
119f55d7 | 256 | mirrors.append(mirror) |
0673d1b0 | 257 | |
119f55d7 | 258 | distance *= 1.2 |
0673d1b0 | 259 | |
9068dba1 | 260 | return MirrorSet(self.backend, mirrors) |
0673d1b0 MT |
261 | |
262 | def get_with_state(self, state): | |
263 | mirrors = [] | |
264 | ||
265 | for mirror in self._mirrors: | |
266 | if mirror.state == state: | |
267 | mirrors.append(mirror) | |
268 | ||
9068dba1 MT |
269 | return MirrorSet(self.backend, mirrors) |
270 | ||
0673d1b0 | 271 | |
9068dba1 MT |
272 | class Mirror(Object): |
273 | def __init__(self, backend, id, data=None): | |
274 | Object.__init__(self, backend) | |
0673d1b0 | 275 | |
940227cb MT |
276 | self.id = id |
277 | ||
bd17b7d1 MT |
278 | if data: |
279 | self._info = data | |
280 | else: | |
281 | self._info = self.db.get("SELECT * FROM mirrors WHERE id = %s", self.id) | |
282 | self._info["url"] = self.generate_url() | |
940227cb | 283 | |
119f55d7 MT |
284 | self.__location = None |
285 | self.__country_name = None | |
286 | ||
54af860e MT |
287 | def __repr__(self): |
288 | return "<%s %s>" % (self.__class__.__name__, self.url) | |
289 | ||
290 | def __cmp__(self, other): | |
9068dba1 | 291 | ret = cmp(self.country_code, other.country_code) |
54af860e | 292 | |
9068dba1 MT |
293 | if not ret: |
294 | ret = cmp(self.hostname, other.hostname) | |
295 | ||
296 | return ret | |
940227cb | 297 | |
940227cb MT |
298 | def generate_url(self): |
299 | url = "http://%s" % self.hostname | |
300 | if not self.path.startswith("/"): | |
301 | url += "/" | |
302 | url += "%s" % self.path | |
303 | if not self.path.endswith("/"): | |
304 | url += "/" | |
305 | return url | |
306 | ||
9068dba1 MT |
307 | @property |
308 | def hostname(self): | |
309 | return self._info.hostname | |
310 | ||
311 | @property | |
312 | def path(self): | |
313 | return self._info.path | |
940227cb MT |
314 | |
315 | @property | |
316 | def address(self): | |
317 | return socket.gethostbyname(self.hostname) | |
318 | ||
9068dba1 MT |
319 | @property |
320 | def owner(self): | |
321 | return self._info.owner | |
322 | ||
0673d1b0 MT |
323 | @property |
324 | def location(self): | |
119f55d7 | 325 | if self.__location is None: |
9068dba1 | 326 | self.__location = self.geoip.get_location(self.address) |
0673d1b0 MT |
327 | |
328 | return self.__location | |
329 | ||
330 | @property | |
331 | def latitude(self): | |
9068dba1 MT |
332 | if self.location: |
333 | return self.location.latitude | |
0673d1b0 MT |
334 | |
335 | @property | |
336 | def longitude(self): | |
9068dba1 MT |
337 | if self.location: |
338 | return self.location.longitude | |
0673d1b0 MT |
339 | |
340 | @property | |
341 | def coordinates(self): | |
342 | return (self.latitude, self.longitude) | |
343 | ||
344 | @property | |
345 | def coordiante_str(self): | |
346 | coordinates = [] | |
347 | ||
348 | for i in self.coordinates: | |
349 | coordinates.append("%s" % i) | |
350 | ||
351 | return ",".join(coordinates) | |
352 | ||
940227cb MT |
353 | @property |
354 | def country_code(self): | |
9068dba1 MT |
355 | if self.location: |
356 | return self.location.country | |
940227cb | 357 | |
0673d1b0 MT |
358 | @property |
359 | def country_name(self): | |
119f55d7 | 360 | if self.__country_name is None: |
9068dba1 | 361 | self.__country_name = self.geoip.get_country_name(self.country_code) |
119f55d7 MT |
362 | |
363 | return self.__country_name | |
0673d1b0 MT |
364 | |
365 | @property | |
9068dba1 MT |
366 | def location_str(self): |
367 | location = [] | |
368 | ||
369 | if self._info.location: | |
370 | location.append(self._info.location) | |
0673d1b0 | 371 | |
9068dba1 MT |
372 | elif self.location: |
373 | location.append(self.location.city) | |
374 | location.append(self.country_name) | |
375 | ||
376 | return ", ".join([s for s in location if s]) | |
0673d1b0 MT |
377 | |
378 | @property | |
9068dba1 MT |
379 | def asn(self): |
380 | if not hasattr(self, "__asn"): | |
381 | self.__asn = self.geoip.get_asn(self.address) | |
0673d1b0 | 382 | |
9068dba1 | 383 | return self.__asn |
0673d1b0 | 384 | |
940227cb MT |
385 | @property |
386 | def filelist(self): | |
387 | filelist = self.db.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self.id) | |
388 | return [f.filename for f in filelist] | |
389 | ||
54af860e MT |
390 | @property |
391 | def prefix(self): | |
54af860e MT |
392 | return "" |
393 | ||
9068dba1 MT |
394 | @property |
395 | def url(self): | |
396 | return self._info.url | |
397 | ||
398 | def build_url(self, filename): | |
399 | return urlparse.urljoin(self.url, filename) | |
400 | ||
401 | @property | |
402 | def last_update(self): | |
403 | return self._info.last_update | |
404 | ||
405 | @property | |
406 | def state(self): | |
407 | return self._info.state | |
408 | ||
940227cb MT |
409 | def set_state(self, state): |
410 | logging.info("Setting state of %s to %s" % (self.hostname, state)) | |
411 | ||
412 | if self.state == state: | |
413 | return | |
414 | ||
9068dba1 | 415 | self.db.execute("UPDATE mirrors SET state = %s WHERE id = %s", state, self.id) |
940227cb MT |
416 | |
417 | # Reload changed settings | |
bd17b7d1 MT |
418 | if hasattr(self, "_info"): |
419 | self._info["state"] = state | |
940227cb | 420 | |
9068dba1 MT |
421 | @property |
422 | def enabled(self): | |
423 | return self._info.enabled | |
424 | ||
425 | @property | |
426 | def disabled(self): | |
427 | return not self.enabled | |
428 | ||
940227cb MT |
429 | def check(self): |
430 | logging.info("Running check for mirror %s" % self.hostname) | |
431 | ||
432 | self.check_timestamp() | |
433 | self.check_filelist() | |
434 | ||
435 | def check_state(self): | |
436 | logging.debug("Checking state of mirror %s" % self.id) | |
437 | ||
9068dba1 | 438 | if not self.enabled: |
940227cb | 439 | self.set_state("DOWN") |
9068dba1 MT |
440 | return |
441 | ||
442 | now = datetime.datetime.utcnow() | |
443 | ||
444 | time_delta = now - self.last_update | |
445 | time_diff = time_delta.total_seconds() | |
940227cb | 446 | |
9068dba1 MT |
447 | time_down = self.settings.get_int("mirrors_time_down", 3*24*60*60) |
448 | if time_diff >= time_down: | |
940227cb | 449 | self.set_state("DOWN") |
9068dba1 | 450 | return |
940227cb | 451 | |
9068dba1 MT |
452 | time_outofsync = self.settings.get_int("mirrors_time_outofsync", 6*60*60) |
453 | if time_diff >= time_outofsync: | |
454 | self.set_state("OUTOFSYNC") | |
940227cb MT |
455 | return |
456 | ||
9068dba1 MT |
457 | self.set_state("UP") |
458 | ||
459 | def check_timestamp(self): | |
940227cb MT |
460 | http = tornado.httpclient.AsyncHTTPClient() |
461 | ||
462 | http.fetch(self.url + ".timestamp", | |
54af860e | 463 | headers={ "Pragma" : "no-cache" }, |
940227cb MT |
464 | callback=self.__check_timestamp_response) |
465 | ||
466 | def __check_timestamp_response(self, response): | |
467 | if response.error: | |
468 | logging.debug("Error getting timestamp from %s" % self.hostname) | |
a3ee39ce | 469 | self.set_state("DOWN") |
940227cb MT |
470 | return |
471 | ||
472 | try: | |
473 | timestamp = int(response.body.strip()) | |
474 | except ValueError: | |
475 | timestamp = 0 | |
476 | ||
9068dba1 MT |
477 | timestamp = datetime.datetime.fromtimestamp(timestamp) |
478 | ||
479 | self.db.execute("UPDATE mirrors SET last_update = %s WHERE id = %s", | |
940227cb MT |
480 | timestamp, self.id) |
481 | ||
482 | # Reload changed settings | |
bd17b7d1 MT |
483 | if hasattr(self, "_info"): |
484 | self._info["timestamp"] = timestamp | |
940227cb MT |
485 | |
486 | self.check_state() | |
487 | ||
488 | logging.info("Successfully updated timestamp from %s" % self.hostname) | |
489 | ||
490 | def check_filelist(self): | |
54af860e | 491 | # XXX need to remove data from disabled mirrors |
9068dba1 | 492 | if not self.enabled: |
940227cb MT |
493 | return |
494 | ||
495 | http = tornado.httpclient.AsyncHTTPClient() | |
496 | ||
497 | http.fetch(self.url + ".filelist", | |
54af860e | 498 | headers={ "Pragma" : "no-cache" }, |
940227cb MT |
499 | callback=self.__check_filelist_response) |
500 | ||
501 | def __check_filelist_response(self, response): | |
502 | if response.error: | |
503 | logging.debug("Error getting timestamp from %s" % self.hostname) | |
504 | return | |
505 | ||
56b9c1d8 | 506 | files = self.filelist |
940227cb MT |
507 | |
508 | for file in response.body.splitlines(): | |
56b9c1d8 MT |
509 | file = os.path.join(self.prefix, file) |
510 | ||
511 | if file in files: | |
512 | files.remove(file) | |
513 | continue | |
514 | ||
940227cb | 515 | self.db.execute("INSERT INTO mirror_files(mirror, filename) VALUES(%s, %s)", |
56b9c1d8 MT |
516 | self.id, file) |
517 | ||
518 | for file in files: | |
519 | self.db.execute("DELETE FROM mirror_files WHERE mirror=%s AND filename=%s", | |
520 | self.id, file) | |
940227cb MT |
521 | |
522 | logging.info("Successfully updated mirror filelist from %s" % self.hostname) | |
523 | ||
54af860e MT |
524 | @property |
525 | def prefer_for_countries(self): | |
0673d1b0 MT |
526 | countries = self._info.get("prefer_for_countries", "") |
527 | if countries: | |
528 | return sorted(countries.split(", ")) | |
54af860e | 529 | |
0673d1b0 MT |
530 | return [] |
531 | ||
532 | @property | |
533 | def prefer_for_countries_names(self): | |
9068dba1 MT |
534 | countries = [self.geoip.get_country_name(c.upper()) for c in self.prefer_for_countries] |
535 | ||
536 | return sorted(countries) | |
54af860e | 537 | |
119f55d7 | 538 | def distance_to(self, location, ignore_preference=False): |
0673d1b0 | 539 | if not location: |
9068dba1 | 540 | return None |
940227cb | 541 | |
9068dba1 MT |
542 | country_code = None |
543 | if location.country: | |
544 | country_code = location.country.lower() | |
545 | ||
546 | if not ignore_preference and country_code in self.prefer_for_countries: | |
0673d1b0 MT |
547 | return 0 |
548 | ||
9068dba1 MT |
549 | # http://www.movable-type.co.uk/scripts/latlong.html |
550 | ||
551 | if self.latitude is None: | |
552 | return None | |
553 | ||
554 | if self.longitude is None: | |
555 | return None | |
556 | ||
557 | earth = 6371 # km | |
558 | delta_lat = math.radians(self.latitude - location.latitude) | |
559 | delta_lon = math.radians(self.longitude - location.longitude) | |
560 | ||
561 | lat1 = math.radians(self.latitude) | |
562 | lat2 = math.radians(location.latitude) | |
563 | ||
564 | a = math.sin(delta_lat / 2) ** 2 | |
565 | a += math.cos(lat1) * math.cos(lat2) * (math.sin(delta_lon / 2) ** 2) | |
0673d1b0 | 566 | |
9068dba1 MT |
567 | b1 = math.sqrt(a) |
568 | b2 = math.sqrt(1 - a) | |
0673d1b0 | 569 | |
9068dba1 MT |
570 | c = 2 * math.atan2(b1, b2) |
571 | ||
572 | return c * earth | |
0673d1b0 MT |
573 | |
574 | def traffic(self, since): | |
575 | # XXX needs to be done better | |
576 | ||
577 | files = {} | |
578 | for entry in self.db.query("SELECT filename, filesize FROM files"): | |
579 | files[entry.filename] = entry.filesize | |
580 | ||
581 | query = "SELECT COUNT(filename) as count, filename FROM log_download WHERE mirror = %s" | |
582 | query += " AND date >= %s GROUP BY filename" | |
583 | ||
584 | traffic = 0 | |
585 | for entry in self.db.query(query, self.id, since): | |
586 | if files.has_key(entry.filename): | |
587 | traffic += entry.count * files[entry.filename] | |
588 | ||
589 | return traffic | |
590 | ||
591 | @property | |
592 | def priority(self): | |
f1f7eb7e | 593 | return self._info.get("priority", 10) |
940227cb | 594 | |
bd17b7d1 MT |
595 | @property |
596 | def development(self): | |
9068dba1 | 597 | return self._info.get("development", False) |
bd17b7d1 MT |
598 | |
599 | @property | |
600 | def mirrorlist(self): | |
9068dba1 MT |
601 | return self._info.get("mirrorlist", False) |
602 | ||
603 | @property | |
604 | def addresses(self): | |
605 | if not hasattr(self, "__addresses"): | |
606 | addrinfo = socket.getaddrinfo(self.hostname, 0, socket.AF_UNSPEC, socket.SOCK_STREAM) | |
607 | ||
608 | ret = [] | |
609 | for family, socktype, proto, canonname, address in addrinfo: | |
610 | if family == socket.AF_INET: | |
611 | address, port = address | |
612 | elif family == socket.AF_INET6: | |
613 | address, port, flowid, scopeid = address | |
614 | ret.append((family, address)) | |
615 | ||
616 | self.__addresses = ret | |
617 | ||
618 | return self.__addresses | |
619 | ||
620 | @property | |
621 | def addresses6(self): | |
622 | return [address for family, address in self.addresses if family == socket.AF_INET6] | |
623 | ||
624 | @property | |
625 | def addresses4(self): | |
626 | return [address for family, address in self.addresses if family == socket.AF_INET] |