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