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