application: Add development vhosts.
[people/shoehn/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
9068dba1
MT
14class File(Object):
15 def __init__(self, backend, release, id, data=None):
16 Object.__init__(self, backend)
940227cb 17
938d083d
MT
18 self.id = id
19 self._release = release
940227cb
MT
20
21 # get all data from database
9068dba1 22 self.__data = data
940227cb 23
9068dba1
MT
24 def __cmp__(self, other):
25 return cmp(self.prio, other.prio)
938d083d
MT
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
940227cb
MT
42
43 @property
44 def type(self):
fadcfd00
MT
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
3230a47f
MT
65 elif "armv5tel" in filename and "scon" in filename:
66 return "armv5tel-scon"
67
fadcfd00
MT
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"
940227cb
MT
79
80 @property
81 def url(self):
9068dba1 82 baseurl = self.settings.get("download_url", "http://downloads.ipfire.org")
940227cb
MT
83
84 return urlparse.urljoin(baseurl, self.filename)
85
86 @property
87 def desc(self):
88 _ = lambda x: x
89
90 descriptions = {
fadcfd00 91 "armv5tel" : _("Image for the armv5tel architecture"),
3230a47f 92 "armv5tel-scon" : _("armv5tel image for boards with serial console"),
940227cb
MT
93 "iso" : _("Installable CD image"),
94 "torrent" : _("Torrent file"),
95 "flash" : _("Flash image"),
ec6ab977 96 "alix" : _("Flash image for devices with serial console"),
940227cb
MT
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,
3230a47f
MT
116 "armv5tel" : 40,
117 "armv5tel-scon" : 41,
940227cb
MT
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 = {
fadcfd00 131 "armv5tel" : _("This image runs on many ARM-based boards"),
3230a47f 132 "armv5tel-scon" : _("This image runs on ARM boards with a serial console"),
940227cb
MT
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):
938d083d 149 return self.data.get("sha1")
940227cb
MT
150
151 @property
152 def filename(self):
938d083d 153 return self.data.get("filename")
940227cb 154
c9698a0b
MT
155 @property
156 def basename(self):
157 return os.path.basename(self.filename)
158
60024cc8
MT
159 @property
160 def size(self):
938d083d 161 return self.data.get("filesize")
60024cc8
MT
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
fadcfd00
MT
171 return "N/A"
172
173 @property
174 def torrent_hash(self):
938d083d 175 return self.data.get("torrent_hash", None)
fadcfd00
MT
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
60024cc8 192
938d083d
MT
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
940227cb 207
9068dba1
MT
208class Release(Object):
209 def __init__(self, backend, id, data=None):
210 Object.__init__(self, backend)
940227cb
MT
211 self.id = id
212
213 # get all data from database
9068dba1 214 self.__data = data or self.db.get("SELECT * FROM releases WHERE id = %s", self.id)
940227cb
MT
215 assert self.__data
216
217 self.__files = []
218
219 def __repr__(self):
220 return "<%s %s>" % (self.__class__.__name__, self.name)
221
9068dba1
MT
222 def __cmp__(self, other):
223 return cmp(self.id, other.id)
224
940227cb
MT
225 @property
226 def files(self):
227 if not self.__files:
9068dba1
MT
228 files = self.db.query("SELECT * FROM files WHERE releases = %s \
229 AND NOT filename LIKE '%%.torrent'", self.id)
940227cb 230
9068dba1
MT
231 self.__files = [File(self.backend, self, f.id, f) for f in files]
232 self.__files.sort()
940227cb
MT
233
234 return self.__files
235
938d083d
MT
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
940227cb
MT
248 @property
249 def name(self):
9068dba1
MT
250 return self.__data.name
251
252 @property
253 def sname(self):
254 return self.__data.sname
940227cb
MT
255
256 @property
257 def stable(self):
9068dba1 258 return self.__data.stable
940227cb
MT
259
260 @property
261 def published(self):
9068dba1 262 return self.__data.published
940227cb 263
9068dba1 264 date = published
940227cb 265
3c4f2edc
MT
266 @property
267 def path(self):
9068dba1 268 return self.__data.path
3c4f2edc 269
940227cb
MT
270 def get_file(self, type):
271 for file in self.files:
272 if file.type == type:
273 return file
274
3c4f2edc
MT
275 def __file_hash(self, filename):
276 sha1 = hashlib.sha1()
277
278 with open(filename) as f:
b5f4eef0
MT
279 buf_size = 1024
280 buf = f.read(buf_size)
281 while buf:
282 sha1.update(buf)
283 buf = f.read(buf_size)
3c4f2edc
MT
284
285 return sha1.hexdigest()
286
3c4f2edc
MT
287 def scan_files(self, basepath="/srv/mirror0"):
288 if not self.path:
289 return
290
291 path = os.path.join(basepath, self.path)
921d98cc
MT
292 if not os.path.exists(path):
293 return
294
fadcfd00
MT
295 files = self.db.query("SELECT filename FROM files WHERE releases = %s", self.id)
296 files = [f.filename for f in files]
3c4f2edc
MT
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
9068dba1 317 logging.info("Hashing %s..." % filename)
3c4f2edc
MT
318 filehash = self.__file_hash(filename)
319 filesize = os.path.getsize(filename)
3c4f2edc 320
fadcfd00
MT
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()
3c4f2edc 361
9068dba1
MT
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
940227cb 368
9068dba1
MT
369 @property
370 def netboot_initrd(self):
371 return "http://downloads.ipfire.org/%s/images/instroot" % self.path
940227cb
MT
372
373 @property
9068dba1
MT
374 def netboot_append(self):
375 return "ro"
940227cb 376
940227cb 377
9068dba1 378class Releases(Object):
940227cb 379 def get_by_id(self, id):
9068dba1
MT
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)
940227cb 390
9068dba1
MT
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)
940227cb 394
9068dba1
MT
395 if ret:
396 return Release(self.backend, ret.id, data=ret)
940227cb 397
0aab96e0
MT
398 def get_latest_unstable(self):
399 ret = self.db.get("SELECT * FROM releases r1 \
400 WHERE r1.published IS NOT NULL AND r1.published <= NOW() \
401 AND stable = %s AND NOT EXISTS ( \
402 SELECT * FROM releases r2 WHERE r2.stable = %s AND \
403 r2.published IS NOT NULL AND r2.published >= r1.published \
404 ) ORDER BY r1.published DESC LIMIT 1", False, True)
405
406 if ret:
407 return Release(self.backend, ret.id, data=ret)
408
940227cb 409 def get_stable(self):
9068dba1
MT
410 query = self.db.query("SELECT * FROM releases \
411 WHERE published IS NOT NULL AND published <= NOW() AND stable = TRUE \
412 ORDER BY published DESC")
940227cb 413
9068dba1
MT
414 releases = []
415 for row in query:
416 release = Release(self.backend, row.id, data=row)
417 releases.append(release)
418
419 return releases
940227cb
MT
420
421 def get_unstable(self):
9068dba1
MT
422 query = self.db.query("SELECT * FROM releases \
423 WHERE published IS NOT NULL AND published <= NOW() AND stable = FALSE \
424 ORDER BY published DESC")
425
426 releases = []
427 for row in query:
428 release = Release(self.backend, row.id, data=row)
429 releases.append(release)
940227cb 430
9068dba1 431 return releases
940227cb
MT
432
433 def get_all(self):
9068dba1
MT
434 query = self.db.query("SELECT * FROM releases \
435 WHERE published IS NOT NULL AND published <= NOW() \
436 ORDER BY published DESC")
940227cb 437
9068dba1
MT
438 releases = []
439 for row in query:
440 release = Release(self.backend, row.id, data=row)
441 releases.append(release)
442
443 return releases
940227cb 444
d0eee7f8
MT
445 def _get_all(self):
446 query = self.db.query("SELECT * FROM releases ORDER BY published DESC")
447
448 releases = []
449 for row in query:
450 release = Release(self.backend, row.id, data=row)
451 releases.append(release)
452
453 return releases
454
938d083d
MT
455 def get_file_for_torrent_hash(self, torrent_hash):
456 file = self.db.get("SELECT id, releases FROM files WHERE torrent_hash = %s LIMIT 1",
fadcfd00
MT
457 torrent_hash)
458
459 if not file:
460 return
461
ea324f48
MT
462 release = Release(self.backend, file.releases)
463 file = File(self.backend, release, file.id)
938d083d
MT
464
465 return file