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