From fadcfd00c248a5d175eefe37bdde303753184729 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Thu, 26 Jul 2012 12:29:23 +0200 Subject: [PATCH] Add torrent and magnet links for all downloads. --- www/templates/modules/release-item.html | 16 ++ www/templates/tracker-torrents.html | 1 - www/translations/de_DE/LC_MESSAGES/webapp.po | 54 +++++-- www/translations/webapp.pot | 54 +++++-- www/webapp/__init__.py | 2 + www/webapp/backend/releases.py | 159 +++++++++++++------ www/webapp/handlers_tracker.py | 11 ++ 7 files changed, 215 insertions(+), 82 deletions(-) diff --git a/www/templates/modules/release-item.html b/www/templates/modules/release-item.html index c0df5a0f..1c3dfcff 100644 --- a/www/templates/modules/release-item.html +++ b/www/templates/modules/release-item.html @@ -96,6 +96,16 @@ {% for file in files[arch] %} + {% if file.torrent_hash %} +

+ + + + + + +

+ {% end %} {{ _(file.desc) }}
{{ _(file.rem) }} @@ -106,6 +116,12 @@ {% end %} + +

+ {{ _("Legend:") }} + {{ _("Magnet link") }}, + {{ _("Torrent download") }} +

{% end %} diff --git a/www/templates/tracker-torrents.html b/www/templates/tracker-torrents.html index 3a6ec13a..8364ee1b 100644 --- a/www/templates/tracker-torrents.html +++ b/www/templates/tracker-torrents.html @@ -40,5 +40,4 @@ {% end %} - {% end block %} diff --git a/www/translations/de_DE/LC_MESSAGES/webapp.po b/www/translations/de_DE/LC_MESSAGES/webapp.po index 06c97274..74a128be 100644 --- a/www/translations/de_DE/LC_MESSAGES/webapp.po +++ b/www/translations/de_DE/LC_MESSAGES/webapp.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-25 20:13+0200\n" +"POT-Creation-Date: 2012-07-26 12:27+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -865,7 +865,19 @@ msgstr "Imagetyp" msgid "Size" msgstr "Größe" -#: templates/modules/release-item.html:115 +#: templates/modules/release-item.html:121 +msgid "Legend:" +msgstr "Legende:" + +#: templates/modules/release-item.html:122 +msgid "Magnet link" +msgstr "Magnet-Link" + +#: templates/modules/release-item.html:123 +msgid "Torrent download" +msgstr "Torrent-Download" + +#: templates/modules/release-item.html:131 #: templates/modules/release-item-short.html:13 msgid "There are no downloads available for this release." msgstr "Es gibt keine Downloads für dieses Release." @@ -1281,65 +1293,73 @@ msgstr "Unbekannter Autor" msgid "%s to %s" msgstr "%s nach %s" -#: webapp/backend/releases.py:39 +#: webapp/backend/releases.py:73 +msgid "Image for the armv5tel architecture" +msgstr "Image für die armv5tel-Architektur" + +#: webapp/backend/releases.py:74 msgid "Installable CD image" msgstr "Bootbares CD-Image" -#: webapp/backend/releases.py:40 +#: webapp/backend/releases.py:75 msgid "Torrent file" msgstr "Torrentdatei" -#: webapp/backend/releases.py:41 +#: webapp/backend/releases.py:76 msgid "Flash image" msgstr "Flash-Image" -#: webapp/backend/releases.py:42 +#: webapp/backend/releases.py:77 msgid "Alix image" msgstr "Alix-Image" -#: webapp/backend/releases.py:43 +#: webapp/backend/releases.py:78 msgid "USB FDD Image" msgstr "USB-FDD-Image" -#: webapp/backend/releases.py:44 +#: webapp/backend/releases.py:79 msgid "USB HDD Image" msgstr "USB-HDD-Image" -#: webapp/backend/releases.py:45 +#: webapp/backend/releases.py:80 msgid "Pregenerated Xen image" msgstr "Vorgefertigtes Xen-Image" -#: webapp/backend/releases.py:51 webapp/backend/releases.py:88 +#: webapp/backend/releases.py:86 webapp/backend/releases.py:124 msgid "Unknown image type" msgstr "Unbekanntes Imageformat" -#: webapp/backend/releases.py:76 +#: webapp/backend/releases.py:111 +msgid "This image runs on many ARM-based boards" +msgstr "Dieses Image läuft auf vielen ARM-basierten Boards" + +#: webapp/backend/releases.py:112 msgid "Use this image to burn a CD and install IPFire from it." msgstr "" "Nutze dieses Image um eine CD zu erstellen und IPFire von dieser zu " "installieren." -#: webapp/backend/releases.py:77 +#: webapp/backend/releases.py:113 msgid "Download the CD image from the torrent network." msgstr "CD-Image mit aus dem Torrentnetzwerk laden" -#: webapp/backend/releases.py:78 +#: webapp/backend/releases.py:114 msgid "An image that is meant to run on embedded devices." msgstr "Ein Image, das für eingebettete Systeme optimiert ist." -#: webapp/backend/releases.py:79 +#: webapp/backend/releases.py:115 msgid "Flash image where a serial console is enabled by default." msgstr "Image, bei welchem die serielle Konsole eingeschaltet ist." -#: webapp/backend/releases.py:80 +#: webapp/backend/releases.py:116 msgid "Install IPFire from a floppy-formated USB key." msgstr "IPFire von einem Floppy-formatiertem USB-Stick installieren." -#: webapp/backend/releases.py:81 +#: webapp/backend/releases.py:117 msgid "If the floppy image doesn't work, use this image instead." msgstr "Wenn das Floppy-Image nicht funktioniert, dieses verwenden." -#: webapp/backend/releases.py:82 +#: webapp/backend/releases.py:118 msgid "A ready-to-run image for Xen." msgstr "Ein fertiges Image für Xen." diff --git a/www/translations/webapp.pot b/www/translations/webapp.pot index 23a93995..fcc3385d 100644 --- a/www/translations/webapp.pot +++ b/www/translations/webapp.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-25 20:13+0200\n" +"POT-Creation-Date: 2012-07-26 12:27+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -857,7 +857,19 @@ msgstr "" msgid "Size" msgstr "" -#: templates/modules/release-item.html:115 +#: templates/modules/release-item.html:121 +msgid "Legend:" +msgstr "" + +#: templates/modules/release-item.html:122 +msgid "Magnet link" +msgstr "" + +#: templates/modules/release-item.html:123 +msgid "Torrent download" +msgstr "" + +#: templates/modules/release-item.html:131 #: templates/modules/release-item-short.html:13 msgid "There are no downloads available for this release." msgstr "" @@ -1273,63 +1285,71 @@ msgstr "" msgid "%s to %s" msgstr "" -#: webapp/backend/releases.py:39 +#: webapp/backend/releases.py:73 +msgid "Image for the armv5tel architecture" +msgstr "" + +#: webapp/backend/releases.py:74 msgid "Installable CD image" msgstr "" -#: webapp/backend/releases.py:40 +#: webapp/backend/releases.py:75 msgid "Torrent file" msgstr "" -#: webapp/backend/releases.py:41 +#: webapp/backend/releases.py:76 msgid "Flash image" msgstr "" -#: webapp/backend/releases.py:42 +#: webapp/backend/releases.py:77 msgid "Alix image" msgstr "" -#: webapp/backend/releases.py:43 +#: webapp/backend/releases.py:78 msgid "USB FDD Image" msgstr "" -#: webapp/backend/releases.py:44 +#: webapp/backend/releases.py:79 msgid "USB HDD Image" msgstr "" -#: webapp/backend/releases.py:45 +#: webapp/backend/releases.py:80 msgid "Pregenerated Xen image" msgstr "" -#: webapp/backend/releases.py:51 webapp/backend/releases.py:88 +#: webapp/backend/releases.py:86 webapp/backend/releases.py:124 msgid "Unknown image type" msgstr "" -#: webapp/backend/releases.py:76 +#: webapp/backend/releases.py:111 +msgid "This image runs on many ARM-based boards" +msgstr "" + +#: webapp/backend/releases.py:112 msgid "Use this image to burn a CD and install IPFire from it." msgstr "" -#: webapp/backend/releases.py:77 +#: webapp/backend/releases.py:113 msgid "Download the CD image from the torrent network." msgstr "" -#: webapp/backend/releases.py:78 +#: webapp/backend/releases.py:114 msgid "An image that is meant to run on embedded devices." msgstr "" -#: webapp/backend/releases.py:79 +#: webapp/backend/releases.py:115 msgid "Flash image where a serial console is enabled by default." msgstr "" -#: webapp/backend/releases.py:80 +#: webapp/backend/releases.py:116 msgid "Install IPFire from a floppy-formated USB key." msgstr "" -#: webapp/backend/releases.py:81 +#: webapp/backend/releases.py:117 msgid "If the floppy image doesn't work, use this image instead." msgstr "" -#: webapp/backend/releases.py:82 +#: webapp/backend/releases.py:118 msgid "A ready-to-run image for Xen." msgstr "" diff --git a/www/webapp/__init__.py b/www/webapp/__init__.py index 69e03e30..e23b68fc 100644 --- a/www/webapp/__init__.py +++ b/www/webapp/__init__.py @@ -149,6 +149,8 @@ class Application(tornado.web.Application): (r"/a.*", TrackerAnnounceHandler), (r"/scrape", TrackerScrapeHandler), (r"/torrent/([0-9a-f]+)", TrackerDetailHandler), + (r"/([0-9a-f]{40})", TrackerDetailHandler), + (r"/([0-9a-f]{40})/download", TrackerDownloadHandler), ] + static_handlers) # boot.ipfire.org diff --git a/www/webapp/backend/releases.py b/www/webapp/backend/releases.py index b03f21f6..6701f755 100644 --- a/www/webapp/backend/releases.py +++ b/www/webapp/backend/releases.py @@ -4,8 +4,11 @@ import hashlib import logging import os import re +import urllib import urlparse +import tracker + from databases import Databases from misc import Singleton from settings import Settings @@ -23,7 +26,37 @@ class File(object): @property def type(self): - return self.__data.get("filetype") + filename = self.filename + + if filename.endswith(".iso"): + return "iso" + + elif filename.endswith(".torrent"): + return "torrent" + + elif "xen" in filename: + return "xen" + + elif "sources" in filename: + return "source" + + elif "usb-fdd" in filename: + return "usbfdd" + + elif "usb-hdd" in filename: + return "usbhdd" + + elif "armv5tel" in filename: + return "armv5tel" + + elif "scon" in filename: + return "alix" + + elif filename.endswith(".img.gz"): + return "flash" + + else: + return "unknown" @property def url(self): @@ -36,6 +69,7 @@ class File(object): _ = lambda x: x descriptions = { + "armv5tel" : _("Image for the armv5tel architecture"), "iso" : _("Installable CD image"), "torrent" : _("Torrent file"), "flash" : _("Flash image"), @@ -59,7 +93,7 @@ class File(object): "alix" : 41, "usbfdd" : 31, "usbhdd" : 30, - "arm" : 40, + "armv5tel" : 40, "xen" : 50, } @@ -73,6 +107,7 @@ class File(object): _ = lambda x: x remarks = { + "armv5tel" : _("This image runs on many ARM-based boards"), "iso" : _("Use this image to burn a CD and install IPFire from it."), "torrent" : _("Download the CD image from the torrent network."), "flash" : _("An image that is meant to run on embedded devices."), @@ -111,7 +146,27 @@ class File(object): if arch in self.basename: return arch - return "N/A" + return "N/A" + + @property + def torrent_hash(self): + return self.__data.get("torrent_hash", None) + + @property + def magnet_link(self): + # Don't return anything if we have no torrent hash. + if self.torrent_hash is None: + return + + s = "magnet:?xt=urn:btih:%s" % self.torrent_hash + + #s += "&xl=%d" % self.size + s += "&dn=%s" % urllib.quote(self.basename) + + # Add our tracker. + s += "&tr=http://tracker.ipfire.org:6969/announce" + + return s class Release(object): @@ -135,10 +190,10 @@ class Release(object): @property def files(self): if not self.__files: - files = self.db.query("SELECT id FROM files WHERE releases = %s \ - AND loadable = 'Y' AND NOT filetype = 'torrent'", self.id) + files = self.db.query("SELECT id, filename FROM files WHERE releases = %s \ + AND loadable = 'Y'", self.id) - self.__files = [File(self, f.id) for f in files] + self.__files = [File(self, f.id) for f in files if not f.filename.endswith(".torrent")] self.__files.sort(lambda a, b: cmp(a.prio, b.prio)) return self.__files @@ -163,12 +218,6 @@ class Release(object): def path(self): return self.__data.get("path") - @property - def torrent_hash(self): - h = self.__data.get("torrent_hash") - if h: - return h.lower() - def get_file(self, type): for file in self.files: if file.type == type: @@ -186,46 +235,16 @@ class Release(object): return sha1.hexdigest() - def __guess_filetype(self, filename): - if filename.endswith(".iso"): - return "iso" - - if filename.endswith(".torrent"): - return "torrent" - - if "xen" in filename: - return "xen" - - if "sources" in filename: - return "source" - - if "usb-fdd" in filename: - return "usbfdd" - - if "usb-hdd" in filename: - return "usbhdd" - - if "arm" in filename: - return "arm" - - if "scon" in filename: - return "alix" - - if filename.endswith(".img.gz"): - return "flash" - - return "unknown" - def scan_files(self, basepath="/srv/mirror0"): if not self.path: return path = os.path.join(basepath, self.path) - if not os.path.exists(path): return - files = [f.filename for f in self.files] + files = self.db.query("SELECT filename FROM files WHERE releases = %s", self.id) + files = [f.filename for f in files] # Make files that do not exists not loadable. for filename in files: @@ -248,10 +267,47 @@ class Release(object): filehash = self.__file_hash(filename) filesize = os.path.getsize(filename) - filetype = self.__guess_filetype(filename) - self.db.execute("""INSERT INTO files(releases, filename, filesize, filetype, sha1) - VALUES(%s, %s, %s, %s, %s)""", self.id, _filename, filesize, filetype, filehash) + # Check if there is a torrent download available for this file: + torrent_hash = "" + torrent_file = "%s.torrent" % filename + if os.path.exists(torrent_file): + torrent_hash = self.torrent_read_hash(torrent_file) + + self.db.execute("INSERT INTO files(releases, filename, filesize, \ + sha1, torrent_hash) VALUES(%s, %s, %s, %s, %s)", + self.id, _filename, filesize, filehash, torrent_hash) + + # Search for all files that miss a torrent hash. + files = self.db.query("SELECT id, filename FROM files \ + WHERE releases = %s AND torrent_hash IS NULL", self.id) + + for file in files: + path = os.path.join(basepath, file.filename) + + torrent_file = "%s.torrent" % path + if os.path.exists(torrent_file): + torrent_hash = self.torrent_read_hash(torrent_file) + + self.db.execute("UPDATE files SET torrent_hash = %s WHERE id = %s", + torrent_hash, file.id) + + def torrent_read_hash(self, filename): + f = None + try: + f = open(filename, "rb") + + metainfo = tracker.bdecode(f.read()) + metainfo = tracker.bencode(metainfo["info"]) + + hash = hashlib.sha1() + hash.update(metainfo) + + return hash.hexdigest() + + finally: + if f: + f.close() class Releases(object): @@ -302,6 +358,15 @@ class Releases(object): return [Release(r.id) for r in releases] + def get_filename_for_torrent_hash(self, torrent_hash): + file = self.db.get("SELECT filename FROM files WHERE torrent_hash = %s LIMIT 1", + torrent_hash) + + if not file: + return + + return file.filename + if __name__ == "__main__": r = Releases() diff --git a/www/webapp/handlers_tracker.py b/www/webapp/handlers_tracker.py index c57dc7fb..184ea505 100644 --- a/www/webapp/handlers_tracker.py +++ b/www/webapp/handlers_tracker.py @@ -48,6 +48,17 @@ class TrackerDetailHandler(BaseHandler): self.render("tracker-torrent-detail.html", release=release, torrent=torrent) +class TrackerDownloadHandler(BaseHandler): + def get(self, torrent_hash): + file = self.releases.get_filename_for_torrent_hash(torrent_hash) + + if not file: + raise tornado.web.HTTPError(404, "Could not find torrent file for hash: %s" % hash) + + # Redirect the user to the download redirector. + self.redirect("http://downloads.ipfire.org/%s.torrent" % file) + + #class TrackerTorrentsHandler(BaseHandler): # @property # def tracker(self): -- 2.47.3