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