]> git.ipfire.org Git - ipfire.org.git/blame - www/webapp/backend/mirrors.py
Text improvements (english).
[ipfire.org.git] / www / webapp / backend / mirrors.py
CommitLineData
940227cb
MT
1#!/usr/bin/python
2
3import logging
54af860e 4import os.path
940227cb
MT
5import socket
6import time
7import tornado.httpclient
8
9from databases import Databases
10from geoip import GeoIP
11from misc import Singleton
12
13class Mirrors(object):
14 __metaclass__ = Singleton
15
16 @property
17 def db(self):
18 return Databases().webapp
19
20 def list(self):
21 return [Mirror(m.id) for m in self.db.query("SELECT id FROM mirrors ORDER BY state")]
22
23 def check_all(self):
24 for mirror in self.list():
25 mirror.check()
26
27 def get(self, id):
54af860e 28 return Mirror(id)
940227cb
MT
29
30 def get_by_hostname(self, hostname):
31 mirror = self.db.get("SELECT id FROM mirrors WHERE hostname=%s", hostname)
32
33 return Mirror(mirror.id)
34
54af860e
MT
35 def get_with_file(self, filename, country=None):
36 # XXX quick and dirty solution - needs a performance boost
37 mirror_ids = [m.mirror for m in self.db.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename)]
38
39 #if country:
40 # # Sort out all mirrors that are not preferred to the given country
41 # for mirror in self.get_for_country(country):
42 # if not mirror.id in mirror_ids:
43 # mirror_ids.remove(mirror.id)
44
45 mirrors = []
46 for mirror_id in mirror_ids:
47 mirror = self.get(mirror_id)
48 if not mirror.state == "UP":
49 continue
50 mirrors.append(mirror)
51
52 logging.debug("%s" % mirrors)
53
54 return mirrors
55
56 def get_for_country(self, country):
57 # XXX need option for random order
58 mirrors = self.db.query("SELECT id FROM mirrors WHERE prefer_for_countries LIKE %s", country)
59
60 for mirror in mirrors:
61 yield self.get(mirror.id)
940227cb 62
edd297c4
MT
63 def get_all_files(self):
64 files = []
65
66 for mirror in self.list():
67 if not mirror.state == "UP":
68 continue
69
70 for file in mirror.filelist:
71 if not file in files:
72 files.append(file)
73
74 return files
75
940227cb
MT
76
77class Mirror(object):
78 def __init__(self, id):
79 self.id = id
80
81 self.reload()
82
54af860e
MT
83 def __repr__(self):
84 return "<%s %s>" % (self.__class__.__name__, self.url)
85
86 def __cmp__(self, other):
87 return cmp(self.id, other.id)
88
940227cb
MT
89 @property
90 def db(self):
91 return Databases().webapp
92
93 def reload(self):
94 self._info = self.db.get("SELECT * FROM mirrors WHERE id=%s", self.id)
95 self._info["url"] = self.generate_url()
96
97 def generate_url(self):
98 url = "http://%s" % self.hostname
99 if not self.path.startswith("/"):
100 url += "/"
101 url += "%s" % self.path
102 if not self.path.endswith("/"):
103 url += "/"
104 return url
105
106 def __getattr__(self, key):
107 try:
108 return self._info[key]
109 except KeyError:
110 raise AttributeError(key)
111
112 @property
113 def address(self):
114 return socket.gethostbyname(self.hostname)
115
116 @property
117 def country_code(self):
118 return GeoIP().get_country(self.address).lower() or "unknown"
119
120 @property
121 def filelist(self):
122 filelist = self.db.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self.id)
123 return [f.filename for f in filelist]
124
54af860e
MT
125 @property
126 def prefix(self):
127 if self.type.startswith("pakfire"):
128 return self.type
129
130 return ""
131
940227cb
MT
132 def set_state(self, state):
133 logging.info("Setting state of %s to %s" % (self.hostname, state))
134
135 if self.state == state:
136 return
137
138 self.db.execute("UPDATE mirrors SET state=%s WHERE id=%s",
139 state, self.id)
140
141 # Reload changed settings
142 self.reload()
143
144 def check(self):
145 logging.info("Running check for mirror %s" % self.hostname)
146
147 self.check_timestamp()
148 self.check_filelist()
149
150 def check_state(self):
151 logging.debug("Checking state of mirror %s" % self.id)
152
153 if self.disabled == "Y":
154 self.set_state("DOWN")
155
156 time_diff = time.time() - self.last_update
157 if time_diff > 3*24*60*60: # XXX get this into Settings
158 self.set_state("DOWN")
159 elif time_diff > 6*60*60:
160 self.set_state("OUTOFSYNC")
161 else:
162 self.set_state("UP")
163
164 def check_timestamp(self):
165 if self.releases == "N":
166 return
167
168 http = tornado.httpclient.AsyncHTTPClient()
169
170 http.fetch(self.url + ".timestamp",
54af860e 171 headers={ "Pragma" : "no-cache" },
940227cb
MT
172 callback=self.__check_timestamp_response)
173
174 def __check_timestamp_response(self, response):
175 if response.error:
176 logging.debug("Error getting timestamp from %s" % self.hostname)
177 return
178
179 try:
180 timestamp = int(response.body.strip())
181 except ValueError:
182 timestamp = 0
183
184 self.db.execute("UPDATE mirrors SET last_update=%s WHERE id=%s",
185 timestamp, self.id)
186
187 # Reload changed settings
188 self.reload()
189
190 self.check_state()
191
192 logging.info("Successfully updated timestamp from %s" % self.hostname)
193
194 def check_filelist(self):
54af860e
MT
195 # XXX need to remove data from disabled mirrors
196 if self.releases == "N" or self.disabled == "Y" or self.type != "full":
940227cb
MT
197 return
198
199 http = tornado.httpclient.AsyncHTTPClient()
200
201 http.fetch(self.url + ".filelist",
54af860e 202 headers={ "Pragma" : "no-cache" },
940227cb
MT
203 callback=self.__check_filelist_response)
204
205 def __check_filelist_response(self, response):
206 if response.error:
207 logging.debug("Error getting timestamp from %s" % self.hostname)
208 return
209
56b9c1d8 210 files = self.filelist
940227cb
MT
211
212 for file in response.body.splitlines():
56b9c1d8
MT
213 file = os.path.join(self.prefix, file)
214
215 if file in files:
216 files.remove(file)
217 continue
218
940227cb 219 self.db.execute("INSERT INTO mirror_files(mirror, filename) VALUES(%s, %s)",
56b9c1d8
MT
220 self.id, file)
221
222 for file in files:
223 self.db.execute("DELETE FROM mirror_files WHERE mirror=%s AND filename=%s",
224 self.id, file)
940227cb
MT
225
226 logging.info("Successfully updated mirror filelist from %s" % self.hostname)
227
54af860e
MT
228 @property
229 def prefer_for_countries(self):
230 return self._info.get("prefer_for_countries", "").split()
231
232
940227cb
MT
233
234if __name__ == "__main__":
235 m = Mirrors()
236
237 for mirror in m.list():
238 print mirror.hostname, mirror.country_code