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