]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/python | |
2 | ||
3 | import hashlib | |
4 | import logging | |
5 | import os | |
6 | import re | |
7 | import urllib | |
8 | import urlparse | |
9 | ||
10 | import tracker | |
11 | ||
12 | from databases import Databases | |
13 | from misc import Singleton | |
14 | from settings import Settings | |
15 | ||
16 | class File(object): | |
17 | def __init__(self, release, id): | |
18 | self.id = id | |
19 | self._release = release | |
20 | ||
21 | # get all data from database | |
22 | self.__data = None | |
23 | ||
24 | @property | |
25 | def db(self): | |
26 | return Databases().webapp | |
27 | ||
28 | @property | |
29 | def tracker(self): | |
30 | return self.release.tracker | |
31 | ||
32 | @property | |
33 | def data(self): | |
34 | if self.__data is None: | |
35 | self.__data = self.db.get("SELECT * FROM files WHERE id = %s", self.id) | |
36 | assert self.__data | |
37 | ||
38 | return self.__data | |
39 | ||
40 | @property | |
41 | def release(self): | |
42 | if not self._release: | |
43 | release_id = self.data.get("releases") | |
44 | self._release = Release(release_id) | |
45 | ||
46 | return self._release | |
47 | ||
48 | @property | |
49 | def type(self): | |
50 | filename = self.filename | |
51 | ||
52 | if filename.endswith(".iso"): | |
53 | return "iso" | |
54 | ||
55 | elif filename.endswith(".torrent"): | |
56 | return "torrent" | |
57 | ||
58 | elif "xen" in filename: | |
59 | return "xen" | |
60 | ||
61 | elif "sources" in filename: | |
62 | return "source" | |
63 | ||
64 | elif "usb-fdd" in filename: | |
65 | return "usbfdd" | |
66 | ||
67 | elif "usb-hdd" in filename: | |
68 | return "usbhdd" | |
69 | ||
70 | elif "armv5tel" in filename and "scon" in filename: | |
71 | return "armv5tel-scon" | |
72 | ||
73 | elif "armv5tel" in filename: | |
74 | return "armv5tel" | |
75 | ||
76 | elif "scon" in filename: | |
77 | return "alix" | |
78 | ||
79 | elif filename.endswith(".img.gz"): | |
80 | return "flash" | |
81 | ||
82 | else: | |
83 | return "unknown" | |
84 | ||
85 | @property | |
86 | def url(self): | |
87 | baseurl = Settings().get("download_url") | |
88 | ||
89 | return urlparse.urljoin(baseurl, self.filename) | |
90 | ||
91 | @property | |
92 | def desc(self): | |
93 | _ = lambda x: x | |
94 | ||
95 | descriptions = { | |
96 | "armv5tel" : _("Image for the armv5tel architecture"), | |
97 | "armv5tel-scon" : _("armv5tel image for boards with serial console"), | |
98 | "iso" : _("Installable CD image"), | |
99 | "torrent" : _("Torrent file"), | |
100 | "flash" : _("Flash image"), | |
101 | "alix" : _("Alix image"), | |
102 | "usbfdd" : _("USB FDD Image"), | |
103 | "usbhdd" : _("USB HDD Image"), | |
104 | "xen" : _("Pregenerated Xen image"), | |
105 | } | |
106 | ||
107 | try: | |
108 | return descriptions[self.type] | |
109 | except KeyError: | |
110 | return _("Unknown image type") | |
111 | ||
112 | @property | |
113 | def prio(self): | |
114 | priorities = { | |
115 | "iso" : 10, | |
116 | "torrent" : 20, | |
117 | "flash" : 40, | |
118 | "alix" : 41, | |
119 | "usbfdd" : 31, | |
120 | "usbhdd" : 30, | |
121 | "armv5tel" : 40, | |
122 | "armv5tel-scon" : 41, | |
123 | "xen" : 50, | |
124 | } | |
125 | ||
126 | try: | |
127 | return priorities[self.type] | |
128 | except KeyError: | |
129 | return 999 | |
130 | ||
131 | @property | |
132 | def rem(self): | |
133 | _ = lambda x: x | |
134 | ||
135 | remarks = { | |
136 | "armv5tel" : _("This image runs on many ARM-based boards"), | |
137 | "armv5tel-scon" : _("This image runs on ARM boards with a serial console"), | |
138 | "iso" : _("Use this image to burn a CD and install IPFire from it."), | |
139 | "torrent" : _("Download the CD image from the torrent network."), | |
140 | "flash" : _("An image that is meant to run on embedded devices."), | |
141 | "alix" : _("Flash image where a serial console is enabled by default."), | |
142 | "usbfdd" : _("Install IPFire from a floppy-formated USB key."), | |
143 | "usbhdd" : _("If the floppy image doesn't work, use this image instead."), | |
144 | "xen" : _("A ready-to-run image for Xen."), | |
145 | } | |
146 | ||
147 | try: | |
148 | return remarks[self.type] | |
149 | except KeyError: | |
150 | return _("Unknown image type") | |
151 | ||
152 | @property | |
153 | def sha1(self): | |
154 | return self.data.get("sha1") | |
155 | ||
156 | @property | |
157 | def filename(self): | |
158 | return self.data.get("filename") | |
159 | ||
160 | @property | |
161 | def basename(self): | |
162 | return os.path.basename(self.filename) | |
163 | ||
164 | @property | |
165 | def size(self): | |
166 | return self.data.get("filesize") | |
167 | ||
168 | @property | |
169 | def arch(self): | |
170 | known_arches = ("i586", "arm") | |
171 | ||
172 | for arch in known_arches: | |
173 | if arch in self.basename: | |
174 | return arch | |
175 | ||
176 | return "N/A" | |
177 | ||
178 | @property | |
179 | def torrent_hash(self): | |
180 | return self.data.get("torrent_hash", None) | |
181 | ||
182 | @property | |
183 | def magnet_link(self): | |
184 | # Don't return anything if we have no torrent hash. | |
185 | if self.torrent_hash is None: | |
186 | return | |
187 | ||
188 | s = "magnet:?xt=urn:btih:%s" % self.torrent_hash | |
189 | ||
190 | #s += "&xl=%d" % self.size | |
191 | s += "&dn=%s" % urllib.quote(self.basename) | |
192 | ||
193 | # Add our tracker. | |
194 | s += "&tr=http://tracker.ipfire.org:6969/announce" | |
195 | ||
196 | return s | |
197 | ||
198 | @property | |
199 | def seeders(self): | |
200 | if not self.torrent_hash: | |
201 | return | |
202 | ||
203 | return self.tracker.get_seeds(self.torrent_hash) | |
204 | ||
205 | @property | |
206 | def peers(self): | |
207 | if not self.torrent_hash: | |
208 | return | |
209 | ||
210 | return self.tracker.get_peers(self.torrent_hash) | |
211 | ||
212 | @property | |
213 | def completed(self): | |
214 | if not self.torrent_hash: | |
215 | return | |
216 | ||
217 | return self.tracker.complete(self.torrent_hash) | |
218 | ||
219 | ||
220 | class Release(object): | |
221 | @property | |
222 | def db(self): | |
223 | return Releases().db | |
224 | ||
225 | @property | |
226 | def tracker(self): | |
227 | return tracker.Tracker() | |
228 | ||
229 | def __init__(self, id): | |
230 | self.id = id | |
231 | ||
232 | # get all data from database | |
233 | self.__data = \ | |
234 | self.db.get("SELECT * FROM releases WHERE id = %s", self.id) | |
235 | assert self.__data | |
236 | ||
237 | self.__files = [] | |
238 | ||
239 | def __repr__(self): | |
240 | return "<%s %s>" % (self.__class__.__name__, self.name) | |
241 | ||
242 | @property | |
243 | def files(self): | |
244 | if not self.__files: | |
245 | files = self.db.query("SELECT id, filename FROM files WHERE releases = %s \ | |
246 | AND loadable = 'Y' AND NOT filename LIKE '%%.torrent'", self.id) | |
247 | ||
248 | self.__files = [File(self, f.id) for f in files] | |
249 | self.__files.sort(lambda a, b: cmp(a.prio, b.prio)) | |
250 | ||
251 | return self.__files | |
252 | ||
253 | @property | |
254 | def torrents(self): | |
255 | torrents = [] | |
256 | ||
257 | for file in self.files: | |
258 | if not file.torrent_hash: | |
259 | continue | |
260 | ||
261 | torrents.append(file) | |
262 | ||
263 | return torrents | |
264 | ||
265 | @property | |
266 | def name(self): | |
267 | return self.__data.get("name") | |
268 | ||
269 | @property | |
270 | def stable(self): | |
271 | return self.__data.get("stable") == "Y" | |
272 | ||
273 | @property | |
274 | def published(self): | |
275 | return self.__data.get("published") == "Y" | |
276 | ||
277 | @property | |
278 | def date(self): | |
279 | return self.__data.get("date") | |
280 | ||
281 | @property | |
282 | def path(self): | |
283 | return self.__data.get("path") | |
284 | ||
285 | def get_file(self, type): | |
286 | for file in self.files: | |
287 | if file.type == type: | |
288 | return file | |
289 | ||
290 | def __file_hash(self, filename): | |
291 | sha1 = hashlib.sha1() | |
292 | ||
293 | with open(filename) as f: | |
294 | buf_size = 1024 | |
295 | buf = f.read(buf_size) | |
296 | while buf: | |
297 | sha1.update(buf) | |
298 | buf = f.read(buf_size) | |
299 | ||
300 | return sha1.hexdigest() | |
301 | ||
302 | def scan_files(self, basepath="/srv/mirror0"): | |
303 | if not self.path: | |
304 | return | |
305 | ||
306 | path = os.path.join(basepath, self.path) | |
307 | if not os.path.exists(path): | |
308 | return | |
309 | ||
310 | files = self.db.query("SELECT filename FROM files WHERE releases = %s", self.id) | |
311 | files = [f.filename for f in files] | |
312 | ||
313 | # Make files that do not exists not loadable. | |
314 | for filename in files: | |
315 | _filename = os.path.join(basepath, filename) | |
316 | if not os.path.exists(_filename): | |
317 | self.db.execute("UPDATE files SET loadable='N' WHERE filename = %s", filename) | |
318 | ||
319 | for filename in os.listdir(path): | |
320 | filename = os.path.join(path, filename) | |
321 | ||
322 | if os.path.isdir(filename): | |
323 | continue | |
324 | ||
325 | _filename = re.match(".*(releases/.*)", filename).group(1) | |
326 | if _filename in files: | |
327 | continue | |
328 | ||
329 | if filename.endswith(".md5"): | |
330 | continue | |
331 | ||
332 | filehash = self.__file_hash(filename) | |
333 | filesize = os.path.getsize(filename) | |
334 | ||
335 | # Check if there is a torrent download available for this file: | |
336 | torrent_hash = "" | |
337 | torrent_file = "%s.torrent" % filename | |
338 | if os.path.exists(torrent_file): | |
339 | torrent_hash = self.torrent_read_hash(torrent_file) | |
340 | ||
341 | self.db.execute("INSERT INTO files(releases, filename, filesize, \ | |
342 | sha1, torrent_hash) VALUES(%s, %s, %s, %s, %s)", | |
343 | self.id, _filename, filesize, filehash, torrent_hash) | |
344 | ||
345 | # Search for all files that miss a torrent hash. | |
346 | files = self.db.query("SELECT id, filename FROM files \ | |
347 | WHERE releases = %s AND torrent_hash IS NULL", self.id) | |
348 | ||
349 | for file in files: | |
350 | path = os.path.join(basepath, file.filename) | |
351 | ||
352 | torrent_file = "%s.torrent" % path | |
353 | if os.path.exists(torrent_file): | |
354 | torrent_hash = self.torrent_read_hash(torrent_file) | |
355 | ||
356 | self.db.execute("UPDATE files SET torrent_hash = %s WHERE id = %s", | |
357 | torrent_hash, file.id) | |
358 | ||
359 | def torrent_read_hash(self, filename): | |
360 | f = None | |
361 | try: | |
362 | f = open(filename, "rb") | |
363 | ||
364 | metainfo = tracker.bdecode(f.read()) | |
365 | metainfo = tracker.bencode(metainfo["info"]) | |
366 | ||
367 | hash = hashlib.sha1() | |
368 | hash.update(metainfo) | |
369 | ||
370 | return hash.hexdigest() | |
371 | ||
372 | finally: | |
373 | if f: | |
374 | f.close() | |
375 | ||
376 | ||
377 | class Releases(object): | |
378 | __metaclass__ = Singleton | |
379 | ||
380 | @property | |
381 | def db(self): | |
382 | return Databases().webapp | |
383 | ||
384 | def list(self): | |
385 | return [Release(r.id) for r in self.db.query("SELECT id FROM releases ORDER BY date DESC")] | |
386 | ||
387 | def get_by_id(self, id): | |
388 | id = int(id) | |
389 | if id in [r.id for r in self.db.query("SELECT id FROM releases")]: | |
390 | return Release(id) | |
391 | ||
392 | def get_latest(self, stable=1): | |
393 | query = "SELECT id FROM releases WHERE published='Y' AND" | |
394 | if stable: | |
395 | query += " stable='Y'" | |
396 | else: | |
397 | query += " stable='N'" | |
398 | ||
399 | query += " ORDER BY date DESC LIMIT 1" | |
400 | ||
401 | release = self.db.get(query) | |
402 | if release: | |
403 | return Release(release.id) | |
404 | ||
405 | def get_stable(self): | |
406 | releases = self.db.query("""SELECT id FROM releases | |
407 | WHERE published='Y' AND stable='Y' | |
408 | ORDER BY date DESC""") | |
409 | ||
410 | return [Release(r.id) for r in releases] | |
411 | ||
412 | def get_unstable(self): | |
413 | releases = self.db.query("""SELECT id FROM releases | |
414 | WHERE published='Y' AND stable='N' | |
415 | ORDER BY date DESC""") | |
416 | ||
417 | return [Release(r.id) for r in releases] | |
418 | ||
419 | def get_all(self): | |
420 | releases = self.db.query("""SELECT id FROM releases | |
421 | WHERE published='Y' ORDER BY date DESC""") | |
422 | ||
423 | return [Release(r.id) for r in releases] | |
424 | ||
425 | def get_file_for_torrent_hash(self, torrent_hash): | |
426 | file = self.db.get("SELECT id, releases FROM files WHERE torrent_hash = %s LIMIT 1", | |
427 | torrent_hash) | |
428 | ||
429 | if not file: | |
430 | return | |
431 | ||
432 | release = Release(file.releases) | |
433 | file = File(release, file.id) | |
434 | ||
435 | return file | |
436 | ||
437 | ||
438 | if __name__ == "__main__": | |
439 | r = Releases() | |
440 | ||
441 | for release in r.get_all(): | |
442 | print release.name | |
443 | ||
444 | print r.get_latest() |