]>
git.ipfire.org Git - ipfire.org.git/blob - webapp/backend/mirrors.py
9 import tornado
.httpclient
11 from databases
import Databases
12 from geoip
import GeoIP
13 from memcached
import Memcached
14 from misc
import Singleton
16 class Downloads(object):
17 __metaclass__
= Singleton
21 return Databases().webapp
29 ret
= self
.db
.get("SELECT COUNT(*) AS total FROM log_download")
35 ret
= self
.db
.get("SELECT COUNT(*) AS today FROM log_download WHERE date >= NOW() - 1000000")
41 ret
= self
.db
.get("SELECT COUNT(*) AS yesterday FROM log_download WHERE DATE(date) = DATE(NOW())-1")
47 ret
= self
.db
.query("SELECT DATE(date) AS date, COUNT(*) AS downloads FROM log_download"
48 " WHERE DATE(date) BETWEEN DATE(NOW()) - 31 AND DATE(NOW()) GROUP BY DATE(date)")
52 def get_countries(self
, duration
="all"):
53 query
= "SELECT country_code, count(country_code) AS count FROM log_download"
55 if duration
== "today":
56 query
+= " WHERE date >= NOW() - 1000000"
58 query
+= " GROUP BY country_code ORDER BY count DESC"
60 results
= self
.db
.query(query
)
63 count
= sum([o
.count
for o
in results
])
65 ret
[res
.country_code
] = float(res
.count
) / count
69 def get_mirror_load(self
, duration
="all"):
70 query
= "SELECT mirror, COUNT(mirror) AS count FROM log_download"
72 if duration
== "today":
73 query
+= " WHERE date >= NOW() - 1000000"
75 query
+= " GROUP BY mirror ORDER BY count DESC"
77 results
= self
.db
.query(query
)
80 count
= sum([o
.count
for o
in results
])
82 mirror
= self
.mirrors
.get(res
.mirror
)
83 ret
[mirror
.hostname
] = float(res
.count
) / count
88 class Mirrors(object):
89 __metaclass__
= Singleton
93 return Databases().webapp
100 return [Mirror(m
.id) for m
in self
.db
.query("SELECT id FROM mirrors WHERE disabled = 'N' ORDER BY state,hostname")]
103 for mirror
in self
.list():
110 return MirrorSet(self
.list())
112 def get_by_hostname(self
, hostname
):
113 mirror
= self
.db
.get("SELECT id FROM mirrors WHERE hostname=%s", hostname
)
115 return Mirror(mirror
.id)
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
)
134 logging
.debug("%s" % mirrors
)
138 def get_for_country(self
, country
):
139 # XXX need option for random order
140 mirrors
= self
.db
.query("SELECT id FROM mirrors WHERE prefer_for_countries LIKE %s", country
)
142 for mirror
in mirrors
:
143 yield self
.get(mirror
.id)
145 def get_for_location(self
, addr
):
149 all_mirrors
= self
.list()
151 location
= GeoIP().get_all(addr
)
155 while all_mirrors
and len(mirrors
) <= 2 and distance
<= 270:
156 for mirror
in all_mirrors
:
157 if mirror
.distance_to(location
) <= distance
:
158 mirrors
.append(mirror
)
159 all_mirrors
.remove(mirror
)
165 def get_all_files(self
):
168 for mirror
in self
.list():
169 if not mirror
.state
== "UP":
172 for file in mirror
.filelist
:
173 if not file in files
:
179 class MirrorSet(object):
180 def __init__(self
, mirrors
):
181 self
._mirrors
= mirrors
183 def __add__(self
, other
):
186 for mirror
in self
._mirrors
+ other
._mirrors
:
187 if mirror
in mirrors
:
190 mirrors
.append(mirror
)
192 return MirrorSet(mirrors
)
194 def __sub__(self
, other
):
195 mirrors
= self
._mirrors
[:]
197 for mirror
in other
._mirrors
:
198 if mirror
in mirrors
:
199 mirrors
.remove(mirror
)
201 return MirrorSet(mirrors
)
204 return iter(self
._mirrors
)
207 return len(self
._mirrors
)
210 return "<MirrorSet %s>" % ", ".join([m
.hostname
for m
in self
._mirrors
])
216 def get_with_file(self
, filename
):
217 with_file
= [m
.mirror
for m
in self
.db
.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename
)]
220 for mirror
in self
._mirrors
:
221 if mirror
.id in with_file
:
222 mirrors
.append(mirror
)
224 return MirrorSet(mirrors
)
226 def get_random(self
):
228 for mirror
in self
._mirrors
:
229 for i
in range(0, mirror
.priority
):
230 mirrors
.append(mirror
)
232 return random
.choice(mirrors
)
234 def get_for_country(self
, country
):
237 for mirror
in self
._mirrors
:
238 if country
in mirror
.prefer_for_countries
:
239 mirrors
.append(mirror
)
241 return MirrorSet(mirrors
)
243 def get_for_location(self
, addr
):
248 location
= GeoIP().get_all(addr
)
250 while len(mirrors
) <= 2 and distance
<= 270:
251 for mirror
in self
._mirrors
:
252 if mirror
in mirrors
:
255 if mirror
.distance_to(location
) <= distance
:
256 mirrors
.append(mirror
)
260 return MirrorSet(mirrors
)
262 def get_with_state(self
, state
):
265 for mirror
in self
._mirrors
:
266 if mirror
.state
== state
:
267 mirrors
.append(mirror
)
269 return MirrorSet(mirrors
)
272 class Mirror(object):
273 def __init__(self
, id):
278 self
.__location
= None
279 self
.__country
_name
= None
282 return "<%s %s>" % (self
.__class
__.__name
__, self
.url
)
284 def __cmp__(self
, other
):
285 return cmp(self
.id, other
.id)
289 return Databases().webapp
291 def reload(self
, force
=False):
292 memcached
= Memcached()
293 mem_id
= "mirror-%s" % self
.id
296 memcached
.delete(mem_id
)
298 self
._info
= memcached
.get(mem_id
)
300 self
._info
= self
.db
.get("SELECT * FROM mirrors WHERE id=%s", self
.id)
301 self
._info
["url"] = self
.generate_url()
303 memcached
.set(mem_id
, self
._info
, 60)
305 def generate_url(self
):
306 url
= "http://%s" % self
.hostname
307 if not self
.path
.startswith("/"):
309 url
+= "%s" % self
.path
310 if not self
.path
.endswith("/"):
314 def __getattr__(self
, key
):
316 return self
._info
[key
]
318 raise AttributeError(key
)
322 return socket
.gethostbyname(self
.hostname
)
326 if self
.__location
is None:
327 self
.__location
= GeoIP().get_all(self
.address
)
329 return self
.__location
333 return self
.location
.latitude
337 return self
.location
.longitude
340 def coordinates(self
):
341 return (self
.latitude
, self
.longitude
)
344 def coordiante_str(self
):
347 for i
in self
.coordinates
:
348 coordinates
.append("%s" % i
)
350 return ",".join(coordinates
)
353 def country_code(self
):
354 return self
.location
.country_code
.lower() or "unknown"
357 def country_name(self
):
358 if self
.__country
_name
is None:
359 self
.__country
_name
= GeoIP().get_country_name(self
.country_code
)
361 return self
.__country
_name
365 if self
._info
["city"]:
366 return self
._info
["city"]
368 return self
.location
.city
371 def location_str(self
):
372 s
= self
.country_name
374 s
= "%s, %s" % (self
.city
, s
)
380 filelist
= self
.db
.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self
.id)
381 return [f
.filename
for f
in filelist
]
385 if self
.type.startswith("pakfire"):
390 def set_state(self
, state
):
391 logging
.info("Setting state of %s to %s" % (self
.hostname
, state
))
393 if self
.state
== state
:
396 self
.db
.execute("UPDATE mirrors SET state=%s WHERE id=%s",
399 # Reload changed settings
400 self
.reload(force
=True)
403 logging
.info("Running check for mirror %s" % self
.hostname
)
405 self
.check_timestamp()
406 self
.check_filelist()
408 def check_state(self
):
409 logging
.debug("Checking state of mirror %s" % self
.id)
411 if self
.disabled
== "Y":
412 self
.set_state("DOWN")
414 time_diff
= time
.time() - self
.last_update
415 if time_diff
> 3*24*60*60: # XXX get this into Settings
416 self
.set_state("DOWN")
417 elif time_diff
> 6*60*60:
418 self
.set_state("OUTOFSYNC")
422 def check_timestamp(self
):
423 if self
.releases
== "N":
426 http
= tornado
.httpclient
.AsyncHTTPClient()
428 http
.fetch(self
.url
+ ".timestamp",
429 headers
={ "Pragma" : "no-cache" },
430 callback
=self
.__check
_timestamp
_response
)
432 def __check_timestamp_response(self
, response
):
434 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
435 self
.set_state("DOWN")
439 timestamp
= int(response
.body
.strip())
443 self
.db
.execute("UPDATE mirrors SET last_update=%s WHERE id=%s",
446 # Reload changed settings
447 self
.reload(force
=True)
451 logging
.info("Successfully updated timestamp from %s" % self
.hostname
)
453 def check_filelist(self
):
454 # XXX need to remove data from disabled mirrors
455 if self
.releases
== "N" or self
.disabled
== "Y" or self
.type != "full":
458 http
= tornado
.httpclient
.AsyncHTTPClient()
460 http
.fetch(self
.url
+ ".filelist",
461 headers
={ "Pragma" : "no-cache" },
462 callback
=self
.__check
_filelist
_response
)
464 def __check_filelist_response(self
, response
):
466 logging
.debug("Error getting timestamp from %s" % self
.hostname
)
469 files
= self
.filelist
471 for file in response
.body
.splitlines():
472 file = os
.path
.join(self
.prefix
, file)
478 self
.db
.execute("INSERT INTO mirror_files(mirror, filename) VALUES(%s, %s)",
482 self
.db
.execute("DELETE FROM mirror_files WHERE mirror=%s AND filename=%s",
485 logging
.info("Successfully updated mirror filelist from %s" % self
.hostname
)
488 def prefer_for_countries(self
):
489 countries
= self
._info
.get("prefer_for_countries", "")
491 return sorted(countries
.split(", "))
496 def prefer_for_countries_names(self
):
497 return sorted([GeoIP().get_country_name(c
) for c
in self
.prefer_for_countries
])
499 def distance_to(self
, location
, ignore_preference
=False):
503 if not ignore_preference
and location
.country_code
.lower() in self
.prefer_for_countries
:
507 self
.latitude
- location
.latitude
,
508 self
.longitude
- location
.longitude
512 for i
in distance_vector
:
515 return math
.sqrt(distance
)
517 def traffic(self
, since
):
518 # XXX needs to be done better
521 for entry
in self
.db
.query("SELECT filename, filesize FROM files"):
522 files
[entry
.filename
] = entry
.filesize
524 query
= "SELECT COUNT(filename) as count, filename FROM log_download WHERE mirror = %s"
525 query
+= " AND date >= %s GROUP BY filename"
528 for entry
in self
.db
.query(query
, self
.id, since
):
529 if files
.has_key(entry
.filename
):
530 traffic
+= entry
.count
* files
[entry
.filename
]
536 return self
._info
.get("priority", 10)