]>
git.ipfire.org Git - people/shoehn/ipfire.org.git/blob - webapp/backend/mirrors.py
68b58023b99a855675828c6a033976a46fad8e38
3 from __future__
import division
12 import tornado
.httpclient
13 import tornado
.netutil
16 from misc
import Object
18 class Downloads(Object
):
21 ret
= self
.db
.get("SELECT COUNT(*) AS total FROM log_download")
27 ret
= self
.db
.get("SELECT COUNT(*) AS today FROM log_download WHERE date::date = NOW()::date")
33 ret
= self
.db
.get("SELECT COUNT(*) AS yesterday FROM log_download WHERE date::date = (NOW() - INTERVAL '1 day')::date")
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")
44 def get_countries(self
, duration
="all"):
45 query
= "SELECT country_code, count(country_code) AS count FROM log_download"
47 if duration
== "today":
48 query
+= " WHERE date::date = NOW()::date"
50 query
+= " GROUP BY country_code ORDER BY count DESC"
52 results
= self
.db
.query(query
)
55 count
= sum([o
.count
for o
in results
])
58 ret
[res
.country_code
] = res
.count
/ count
62 def get_mirror_load(self
, duration
="all"):
63 query
= "SELECT mirror, COUNT(mirror) AS count FROM log_download"
65 if duration
== "today":
66 query
+= " WHERE date::date = NOW()::date"
68 query
+= " GROUP BY mirror ORDER BY count DESC"
70 results
= self
.db
.query(query
)
73 count
= sum([o
.count
for o
in results
])
76 mirror
= self
.mirrors
.get(res
.mirror
)
77 ret
[mirror
.hostname
] = res
.count
/ count
82 class Mirrors(Object
):
84 for mirror
in self
.get_all():
88 return Mirror(self
.backend
, id)
91 res
= self
.db
.query("SELECT * FROM mirrors WHERE enabled = %s", True)
95 mirror
= Mirror(self
.backend
, row
.id, row
)
96 mirrors
.append(mirror
)
98 return MirrorSet(self
.backend
, sorted(mirrors
))
100 def get_all_up(self
):
101 res
= self
.db
.query("SELECT * FROM mirrors WHERE enabled = %s AND state = %s \
102 ORDER BY hostname", True, "UP")
106 m
= Mirror(self
.backend
, row
.id, row
)
109 return MirrorSet(self
.backend
, mirrors
)
111 def get_by_hostname(self
, hostname
):
112 ret
= self
.db
.get("SELECT * FROM mirrors WHERE hostname = %s", hostname
)
115 return Mirror(self
.backend
, ret
.id, ret
)
117 def get_with_file(self
, filename
, country
=None):
118 # XXX quick and dirty solution - needs a performance boost
119 mirror_ids
= [m
.mirror
for m
in self
.db
.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename
)]
122 # # Sort out all mirrors that are not preferred to the given country
123 # for mirror in self.get_for_country(country):
124 # if not mirror.id in mirror_ids:
125 # mirror_ids.remove(mirror.id)
128 for mirror_id
in mirror_ids
:
129 mirror
= self
.get(mirror_id
)
130 if not mirror
.state
== "UP":
132 mirrors
.append(mirror
)
136 def get_for_location(self
, location
, max_distance
=4000, filename
=None):
141 res
= self
.db
.query("\
142 WITH client AS (SELECT point(%s, %s) AS location) \
143 SELECT * FROM mirrors WHERE mirrors.state = %s \
144 AND mirrors.id IN ( \
145 SELECT mirror FROM mirror_files WHERE filename = %s \
146 ) AND mirrors.id IN ( \
147 SELECT id FROM mirrors_locations, client \
148 WHERE geodistance(mirrors_locations.location, client.location) <= %s \
150 location
.latitude
, location
.longitude
, "UP", filename
, max_distance
)
152 res
= self
.db
.query("\
153 WITH client AS (SELECT point(%s, %s) AS location) \
154 SELECT * FROM mirrors WHERE mirrors.state = %s AND mirrors.id IN ( \
155 SELECT id FROM mirrors_locations, client \
156 WHERE geodistance(mirrors_locations.location, client.location) <= %s \
158 location
.latitude
, location
.longitude
, "UP", max_distance
)
162 mirror
= Mirror(self
.backend
, row
.id, row
)
163 mirrors
.append(mirror
)
165 return sorted(mirrors
)
167 def get_all_files(self
):
170 for mirror
in self
.get_all():
171 if not mirror
.state
== "UP":
174 for file in mirror
.filelist
:
175 if not file in files
:
180 def get_random(self
, filename
=None):
182 ret
= self
.db
.get("SELECT * FROM mirrors WHERE state = %s \
183 AND mirrors.id IN (SELECT mirror FROM mirror_files \
184 WHERE filename = %s) ORDER BY RANDOM() LIMIT 1", "UP", filename
)
186 ret
= self
.db
.get("SELECT * FROM mirrors WHERE state = %s \
187 ORDER BY RANDOM() LIMIT 1", "UP")
190 return Mirror(self
.backend
, ret
.id, ret
)
192 def file_exists(self
, filename
):
193 ret
= self
.db
.get("SELECT 1 FROM mirror_files \
194 WHERE filename = %s LIMIT 1", filename
)
202 class MirrorSet(Object
):
203 def __init__(self
, backend
, mirrors
):
204 Object
.__init
__(self
, backend
)
206 self
._mirrors
= mirrors
208 def __add__(self
, other
):
211 for mirror
in self
._mirrors
+ other
._mirrors
:
212 if mirror
in mirrors
:
215 mirrors
.append(mirror
)
217 return MirrorSet(self
.backend
, mirrors
)
219 def __sub__(self
, other
):
220 mirrors
= self
._mirrors
[:]
222 for mirror
in other
._mirrors
:
223 if mirror
in mirrors
:
224 mirrors
.remove(mirror
)
226 return MirrorSet(self
.backend
, mirrors
)
229 return iter(self
._mirrors
)
232 return len(self
._mirrors
)
235 return "<MirrorSet %s>" % ", ".join([m
.hostname
for m
in self
._mirrors
])
237 def get_with_file(self
, filename
):
238 with_file
= [m
.mirror
for m
in self
.db
.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename
)]
241 for mirror
in self
._mirrors
:
242 if mirror
.id in with_file
:
243 mirrors
.append(mirror
)
245 return MirrorSet(self
.backend
, mirrors
)
247 def get_random(self
):
249 for mirror
in self
._mirrors
:
250 for i
in range(0, mirror
.priority
):
251 mirrors
.append(mirror
)
253 return random
.choice(mirrors
)
255 def get_for_location(self
, location
):
260 while len(mirrors
) <= 3 and distance
<= 8000:
261 for mirror
in self
._mirrors
:
262 if mirror
in mirrors
:
265 mirror_distance
= mirror
.distance_to(location
)
266 if mirror_distance
is None:
269 if mirror_distance
<= distance
:
270 mirrors
.append(mirror
)
274 return MirrorSet(self
.backend
, mirrors
)
276 def get_with_state(self
, state
):
279 for mirror
in self
._mirrors
:
280 if mirror
.state
== state
:
281 mirrors
.append(mirror
)
283 return MirrorSet(self
.backend
, mirrors
)
286 class Mirror(Object
):
287 def __init__(self
, backend
, id, data
=None):
288 Object
.__init
__(self
, backend
)
295 self
._info
= self
.db
.get("SELECT * FROM mirrors WHERE id = %s", self
.id)
296 self
._info
["url"] = self
.generate_url()
298 self
.__location
= None
299 self
.__country
_name
= None
302 return "<%s %s>" % (self
.__class
__.__name
__, self
.url
)
304 def __cmp__(self
, other
):
305 ret
= cmp(self
.country_code
, other
.country_code
)
308 ret
= cmp(self
.hostname
, other
.hostname
)
312 def generate_url(self
):
313 url
= "http://%s" % self
.hostname
314 if not self
.path
.startswith("/"):
316 url
+= "%s" % self
.path
317 if not self
.path
.endswith("/"):
323 return self
._info
.hostname
327 return self
._info
.path
331 for addr
in self
.addresses6
:
334 for addr
in self
.addresses4
:
339 return self
._info
.owner
343 if self
.__location
is None:
344 self
.__location
= self
.geoip
.get_location(self
.address
)
346 return self
.__location
351 return self
.location
.latitude
356 return self
.location
.longitude
359 def coordinates(self
):
360 return (self
.latitude
, self
.longitude
)
363 def coordiante_str(self
):
366 for i
in self
.coordinates
:
367 coordinates
.append("%s" % i
)
369 return ",".join(coordinates
)
372 def country_code(self
):
374 return self
.location
.country
377 def country_name(self
):
378 if self
.__country
_name
is None:
379 self
.__country
_name
= self
.geoip
.get_country_name(self
.country_code
)
381 return self
.__country
_name
384 def location_str(self
):
387 if self
._info
.location
:
388 location
.append(self
._info
.location
)
391 location
.append(self
.location
.city
)
392 location
.append(self
.country_name
)
394 return ", ".join([s
for s
in location
if s
])
398 if not hasattr(self
, "__asn"):
399 self
.__asn
= self
.geoip
.get_asn(self
.address
)
405 filelist
= self
.db
.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self
.id)
406 return [f
.filename
for f
in filelist
]
414 return self
._info
.url
416 def build_url(self
, filename
):
417 return urlparse
.urljoin(self
.url
, filename
)
420 def last_update(self
):
421 return self
._info
.last_update
425 return self
._info
.state
427 def set_state(self
, state
):
428 logging
.info("Setting state of %s to %s" % (self
.hostname
, state
))
430 if self
.state
== state
:
433 self
.db
.execute("UPDATE mirrors SET state = %s WHERE id = %s", state
, self
.id)
435 # Reload changed settings
436 if hasattr(self
, "_info"):
437 self
._info
["state"] = state
441 return self
._info
.enabled
445 return not self
.enabled
448 logging
.info("Running check for mirror %s" % self
.hostname
)
450 self
.db
.execute("UPDATE mirrors SET address = %s WHERE id = %s",
451 self
.address
, self
.id)
453 self
.check_timestamp()
454 self
.check_filelist()
456 def check_state(self
):
457 logging
.debug("Checking state of mirror %s" % self
.id)
460 self
.set_state("DOWN")
463 now
= datetime
.datetime
.utcnow()
465 time_delta
= now
- self
.last_update
466 time_diff
= time_delta
.total_seconds()
468 time_down
= self
.settings
.get_int("mirrors_time_down", 3*24*60*60)
469 if time_diff
>= time_down
:
470 self
.set_state("DOWN")
473 time_outofsync
= self
.settings
.get_int("mirrors_time_outofsync", 6*60*60)
474 if time_diff
>= time_outofsync
:
475 self
.set_state("OUTOFSYNC")
480 def check_timestamp(self
):
481 http
= tornado
.httpclient
.AsyncHTTPClient()
483 http
.fetch(self
.url
+ ".timestamp",
484 headers
={ "Pragma" : "no-cache" },
485 callback
=self
.__check
_timestamp
_response
)
487 def __check_timestamp_response(self
, response
):
489 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
490 self
.set_state("DOWN")
494 timestamp
= int(response
.body
.strip())
498 timestamp
= datetime
.datetime
.utcfromtimestamp(timestamp
)
500 self
.db
.execute("UPDATE mirrors SET last_update = %s WHERE id = %s",
503 # Reload changed settings
504 if hasattr(self
, "_info"):
505 self
._info
["timestamp"] = timestamp
509 logging
.info("Successfully updated timestamp from %s" % self
.hostname
)
511 def check_filelist(self
):
512 # XXX need to remove data from disabled mirrors
516 http
= tornado
.httpclient
.AsyncHTTPClient()
518 http
.fetch(self
.url
+ ".filelist",
519 headers
={ "Pragma" : "no-cache" },
520 callback
=self
.__check
_filelist
_response
)
522 def __check_filelist_response(self
, response
):
524 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
527 files
= self
.filelist
529 for file in response
.body
.splitlines():
530 file = os
.path
.join(self
.prefix
, file)
536 self
.db
.execute("INSERT INTO mirror_files(mirror, filename) VALUES(%s, %s)",
540 self
.db
.execute("DELETE FROM mirror_files WHERE mirror=%s AND filename=%s",
543 logging
.info("Successfully updated mirror filelist from %s" % self
.hostname
)
546 def prefer_for_countries(self
):
547 countries
= self
._info
.get("prefer_for_countries", "")
549 return sorted(countries
.split(", "))
554 def prefer_for_countries_names(self
):
555 countries
= [self
.geoip
.get_country_name(c
.upper()) for c
in self
.prefer_for_countries
]
557 return sorted(countries
)
559 def distance_to(self
, location
, ignore_preference
=False):
565 country_code
= location
.country
.lower()
567 if not ignore_preference
and country_code
in self
.prefer_for_countries
:
570 # http://www.movable-type.co.uk/scripts/latlong.html
572 if self
.latitude
is None:
575 if self
.longitude
is None:
579 delta_lat
= math
.radians(self
.latitude
- location
.latitude
)
580 delta_lon
= math
.radians(self
.longitude
- location
.longitude
)
582 lat1
= math
.radians(self
.latitude
)
583 lat2
= math
.radians(location
.latitude
)
585 a
= math
.sin(delta_lat
/ 2) ** 2
586 a
+= math
.cos(lat1
) * math
.cos(lat2
) * (math
.sin(delta_lon
/ 2) ** 2)
589 b2
= math
.sqrt(1 - a
)
591 c
= 2 * math
.atan2(b1
, b2
)
595 def traffic(self
, since
):
596 # XXX needs to be done better
599 for entry
in self
.db
.query("SELECT filename, filesize FROM files"):
600 files
[entry
.filename
] = entry
.filesize
602 query
= "SELECT COUNT(filename) as count, filename FROM log_download WHERE mirror = %s"
603 query
+= " AND date >= %s GROUP BY filename"
606 for entry
in self
.db
.query(query
, self
.id, since
):
607 if files
.has_key(entry
.filename
):
608 traffic
+= entry
.count
* files
[entry
.filename
]
614 return self
._info
.get("priority", 10)
617 def development(self
):
618 return self
._info
.get("mirrorlist_devel", False)
621 def mirrorlist(self
):
622 return self
._info
.get("mirrorlist", False)
626 if not hasattr(self
, "__addresses"):
628 addrinfo
= socket
.getaddrinfo(self
.hostname
, 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
)
630 raise Exception("Could not resolve %s" % self
.hostname
)
633 for family
, socktype
, proto
, canonname
, address
in addrinfo
:
634 if family
== socket
.AF_INET
:
635 address
, port
= address
636 elif family
== socket
.AF_INET6
:
637 address
, port
, flowid
, scopeid
= address
638 ret
.append((family
, address
))
640 self
.__addresses
= ret
642 return self
.__addresses
645 def addresses6(self
):
646 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET6
]
649 def addresses4(self
):
650 return [address
for family
, address
in self
.addresses
if family
== socket
.AF_INET
]