]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blob - webapp/backend/releases.py
Major update of the webapp.
[people/shoehn/ipfire.org.git] / webapp / backend / releases.py
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_file_for_torrent_hash(self, torrent_hash):
435 file = self.db.get("SELECT id, releases FROM files WHERE torrent_hash = %s LIMIT 1",
436 torrent_hash)
437
438 if not file:
439 return
440
441 release = Release(file.releases)
442 file = File(release, file.id)
443
444 return file