]> git.ipfire.org Git - ipfire.org.git/commitdiff
Import of new website.
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 11 Dec 2010 10:33:34 +0000 (11:33 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 11 Dec 2010 10:33:34 +0000 (11:33 +0100)
105 files changed:
www/banners.json [deleted file]
www/info.json [deleted file]
www/manager.py [new file with mode: 0644]
www/menu.json [deleted file]
www/mirrors.json [deleted file]
www/news.json [deleted file]
www/redirect.py [deleted file]
www/releases.json [deleted file]
www/static/css/style.css
www/static/images/banners/hetzner_hosted_by_1.jpg [new file with mode: 0644]
www/static/images/flags/unknown.png [new symlink]
www/templates/admin-accounts.html
www/templates/admin-base.html
www/templates/admin-login.html [new file with mode: 0644]
www/templates/admin-mirrors-create.html [new file with mode: 0644]
www/templates/admin-mirrors-details.html [new file with mode: 0644]
www/templates/admin-mirrors.html [new file with mode: 0644]
www/templates/admin-planet-compose.html
www/templates/admin-planet.html
www/templates/base-1.html [new file with mode: 0644]
www/templates/base-2.html [new file with mode: 0644]
www/templates/base.html
www/templates/download-mirror-detail.html [new file with mode: 0644]
www/templates/downloads-all.html
www/templates/downloads-development.html
www/templates/downloads-index.html [new file with mode: 0644]
www/templates/downloads-item.html [new file with mode: 0644]
www/templates/downloads-mirrors.html
www/templates/downloads-older.html [new file with mode: 0644]
www/templates/downloads.html
www/templates/error.html
www/templates/index.html
www/templates/mirrors-item.html [new file with mode: 0644]
www/templates/mirrors.html [new file with mode: 0644]
www/templates/modules/menu-item.html [deleted file]
www/templates/modules/menu.html
www/templates/modules/news-item.html
www/templates/modules/news-line.html [new file with mode: 0644]
www/templates/modules/news-preview.html [new file with mode: 0644]
www/templates/modules/planet-entry.html
www/templates/modules/release-item-short.html [new file with mode: 0644]
www/templates/modules/release-item.html
www/templates/modules/sidebar-banner.html
www/templates/modules/sidebar-release.html
www/templates/modules/tracker-peerlist.html [new file with mode: 0644]
www/templates/news-author.html [new file with mode: 0644]
www/templates/news-item.html [new file with mode: 0644]
www/templates/news.html
www/templates/planet-main.html
www/templates/planet-user.html
www/templates/stasy-base-1.html [new file with mode: 0644]
www/templates/stasy-base-2.html [new file with mode: 0644]
www/templates/stasy-index.html [new file with mode: 0644]
www/templates/stasy-profile.html [new file with mode: 0644]
www/templates/static/about.html [new file with mode: 0644]
www/templates/static/artwork.html
www/templates/static/cebit.html
www/templates/static/development.html
www/templates/static/donation.html
www/templates/static/features.html
www/templates/static/imprint.html
www/templates/static/screenshots.html
www/templates/static/support.html [new file with mode: 0644]
www/templates/tracker-torrent-detail.html [new file with mode: 0644]
www/templates/tracker-torrents.html [moved from www/templates/downloads-torrents.html with 66% similarity]
www/translations/de_DE.csv
www/webapp.py
www/webapp/__init__.py
www/webapp/backend/__init__.py [new file with mode: 0644]
www/webapp/backend/accounts.py [new file with mode: 0644]
www/webapp/backend/banners.py [new file with mode: 0644]
www/webapp/backend/databases.py [new file with mode: 0644]
www/webapp/backend/geoip.py [new file with mode: 0644]
www/webapp/backend/menu.py [new file with mode: 0644]
www/webapp/backend/mirrors.py [new file with mode: 0644]
www/webapp/backend/misc.py [new file with mode: 0644]
www/webapp/backend/news.py [new file with mode: 0644]
www/webapp/backend/planet.py [new file with mode: 0644]
www/webapp/backend/releases.py [new file with mode: 0644]
www/webapp/backend/settings.py [new file with mode: 0644]
www/webapp/backend/tracker.py [moved from www/webapp/datastore/tracker.py with 93% similarity]
www/webapp/datastore/__init__.py [deleted file]
www/webapp/datastore/banners.py [deleted file]
www/webapp/datastore/builds.py [deleted file]
www/webapp/datastore/config.py [deleted file]
www/webapp/datastore/connections.py [deleted file]
www/webapp/datastore/info.py [deleted file]
www/webapp/datastore/menu.py [deleted file]
www/webapp/datastore/mirrors.py [deleted file]
www/webapp/datastore/news.py [deleted file]
www/webapp/datastore/releases.py [deleted file]
www/webapp/db.py [deleted file]
www/webapp/handlers.py
www/webapp/handlers_admin.py [new file with mode: 0644]
www/webapp/handlers_base.py [new file with mode: 0644]
www/webapp/handlers_download.py [new file with mode: 0644]
www/webapp/handlers_mirrors.py [new file with mode: 0644]
www/webapp/handlers_news.py [new file with mode: 0644]
www/webapp/handlers_planet.py [new file with mode: 0644]
www/webapp/handlers_stasy.py [new file with mode: 0644]
www/webapp/handlers_tracker.py [new file with mode: 0644]
www/webapp/helpers.py [deleted file]
www/webapp/markdown.py [deleted file]
www/webapp/stasy.py [new file with mode: 0644]
www/webapp/ui_modules.py

diff --git a/www/banners.json b/www/banners.json
deleted file mode 100644 (file)
index fe51488..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/www/info.json b/www/info.json
deleted file mode 100644 (file)
index dd07e2f..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-       "cluster"  : { "hostname" : "minerva.ipfire.org" },
-       "nightly_builds" : [{ "url" : "http://ftp.ipfire.org/pub/nightly-builds/",
-                                                 "path" : "/srv/anonftp/pub/nightly-builds" },
-                                               { "url" : "http://www.rowie.at/upload/ipfire/builds/" }],
-       "pakfire3" : { "path" : "/srv/pakfire3/" }
-}
diff --git a/www/manager.py b/www/manager.py
new file mode 100644 (file)
index 0000000..5c2299a
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+
+import logging
+import time
+import tornado.ioloop
+
+import webapp.backend as backend
+
+class Daemon(object):
+       def __init__(self):
+               self._managers = []
+               
+               self.ioloop.set_blocking_log_threshold(900)
+
+       @property
+       def ioloop(self):
+               return tornado.ioloop.IOLoop.instance()
+
+       def add(self, manager_cls):
+               manager = manager_cls(self)
+               self._managers.append(manager)
+
+       def run(self):
+               """
+                       Main loop.
+               """
+               for manager in self._managers:
+                       manager.pc.start()
+
+               self.ioloop.start()
+
+       def shutdown(self):
+               self.ioloop.stop()
+
+
+class Manager(object):
+       def __init__(self, daemon):
+               self.daemon = daemon
+
+               self.pc = tornado.ioloop.PeriodicCallback(self, self.timeout * 1000)
+               
+               logging.info("%s was initialized." % self.__class__.__name__)
+
+               self()
+
+       def __call__(self):
+               logging.info("%s main method was called." % self.__class__.__name__)
+
+               self.do()
+
+               # Update callback_time.
+               self.pc.callback_time = self.timeout * 1000
+               logging.debug("Next call will be in %.2f seconds." % \
+                       (self.pc.callback_time / 1000))
+
+       @property
+       def timeout(self):
+               """
+                       Return a new callback timeout in seconds.
+               """
+               raise NotImplementedError
+
+       def do(self):
+               raise NotImplementedError
+
+
+
+class MirrorManager(Manager):
+       @property
+       def mirrors(self):
+               return backend.Mirrors()
+
+       @property
+       def timeout(self):
+               return backend.Config().get_int("mirror_check_interval")
+
+       def do(self):
+               # Check status of all mirror servers.
+               self.mirrors.check_all()
+
+
+if __name__ == "__main__":
+       d = Daemon()
+       d.add(MirrorManager)
+
+       d.run()
diff --git a/www/menu.json b/www/menu.json
deleted file mode 100644 (file)
index 20574ad..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-       "www.ipfire.org" : [
-               { "uri"  : "/index",
-                 "name" : "Home" },
-               { "uri"  : "/features",
-                 "name" : { "de" : "Funktionen", "en" : "Features" }},
-               { "uri"  : "/screenshots",
-                 "name" : "Screenshots" },
-               { "uri"  : "/download",
-                 "name" : "Downloads" },
-               { "uri"  : "http://wiki.ipfire.org/",
-                 "name" : { "de" : "Wiki", "en" : "Wiki" }},
-               { "uri"  : "http://forum.ipfire.org/",
-                 "name" : "Forum" },
-               { "uri"  : "/development",
-                 "name" : { "de" : "Entwicklung", "en" : "Development" }},
-               { "uri"  : "/cebit",
-                 "name" : "CeBIT 2010" }
-       ],
-
-       "planet.ipfire.org" : [
-               { "uri"  : "/index",
-                 "name" : "Home" }
-       ]
-}
diff --git a/www/mirrors.json b/www/mirrors.json
deleted file mode 100644 (file)
index 073eae1..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-[
-       {
-               "owner" : "IPFire Project",
-               "location" : {
-                       "city" : "Cologne",
-                       "country" : "Germany",
-                       "country_code" : "de"
-               },
-               "hostname" : "mirror1.ipfire.org",
-               "path" : "",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : true,
-                       "pakfire3" : true
-               }
-       },
-
-       {
-               "name" : "Ronald Wiesinger",
-               "location" : {
-                       "city" : "Vienna",
-                       "country" : "Austria",
-                       "country_code" : "at"
-               },
-               "hostname" : "www.rowie.at",
-               "path" : "/ipfire",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : false,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "Jan Paul Tücking",
-               "location" : {
-                       "city" : "Falkenstein/Vogtland",
-                       "country" : "Germany",
-                       "country_code" : "de"
-               },
-               "hostname" : "ipfire.earl-net.com",
-               "path" : "",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : true,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "Markus Villwock",
-               "location" : {
-                       "city" : "Hannover",
-                       "country" : "Germany",
-                       "country_code" : "de"
-               },
-               "hostname" : "kraefte.net",
-               "path" : "/ipfire",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : true,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "Kim Barthel",
-               "location" : {
-                       "city" : "Stuttgart",
-                       "country" : "Germany",
-                       "country_code" : "de"
-               },
-               "hostname" : "ipfire.b56.eu",
-               "path" : "",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : false,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "Robert Möker",
-               "location" : {
-                       "city" : "Frankfurt am Main",
-                       "country" : "Germany",
-                       "country_code" : "de"
-               },
-               "hostname" : "datenklatscher.net",
-               "path" : "/ipfire",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : false,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "SWITCH",
-               "location" : {
-                       "city" : "Zurich",
-                       "country" : "Switzerland",
-                       "country_code" : "ch"
-               },
-               "hostname" : "mirror.switch.ch",
-               "path" : "/ftp/mirror/ipfire/pakfire2",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : false,
-                       "pakfire3" : false
-               }
-       },
-
-       {
-               "owner" : "Peter Schälchli",
-               "location" : {
-                       "city" : "Paris",
-                       "country" : "France",
-                       "country_code" : "fr"
-               },
-               "hostname" : "mirror6.ipfire.org",
-               "path" : "",
-               "serves" : {
-                       "isos" : true,
-                       "pakfire2" : true,
-                       "pakfire3" : false
-               }
-       }
-]
diff --git a/www/news.json b/www/news.json
deleted file mode 100644 (file)
index 801bc1f..0000000
+++ /dev/null
@@ -1,1272 +0,0 @@
-{
-               "01" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "IPFire 2.1 Final",
-                               "date"     : "2007-08-11",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the final version of <strong>IPFire 2.1</strong>.</p>",
-                                         "de" : "<p>Heute wurde die finale Version von <strong>IPFire 2.1</strong> herausgegeben.</p>"}},
-
-               "02" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "IPFire's first rss feed", "de" : "IPFires RSS-Feed" },
-                               "date"     : "2008-07-24",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Now, the ipfire project has got it's own rss feed. This feed is for you, to keep you up on latest
-                                                               security updates and releases.</strong>.</p>",
-                                         "de" : "<p>Ab heute hat das IPFire-Projekt seinen eigenen RSS-Feed. Dieser soll die Möglichkeit bieten
-                                                               auf dem Stand der Dinge zu bleiben.</p>"}},
-
-               "03" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 15",
-                               "date"     : "2008-07-24",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #15</strong>.<br />
-                                                               This is an important update because it will fix
-                                                               the latest dns vulnerabilities.<br />
-                                                               <a href=\"http://www.heise-online.co.uk/news/DNS-security-problem-details-released--/111145\">Read this for more information.</a><br />
-                                                               Please install this update as soon as possible.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #15</strong> herausgegeben.<br />
-                                                               Dieses Update wird als kritisch eingestuft, da es die Sicherheitslücke des
-                                                               DNS behebt.<br />
-                                                               <a href=\"http://www.heise-online.co.uk/news/DNS-security-problem-details-released--/111145\">Nähere Informationen dazu gibt es hier.</a><br />
-                                                               Bitte installieren Sie dieses Update möglichst schnell.</p>"}},
-
-               "04" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "LUG Lünen",
-                               "date"     : "2008-07-30",
-                               "link"     : "http://www.linux-luenen.de/?q=node/15",
-                               "content"  :
-                                       { "en" : "<p>The linux user group from Lünen
-                                                               released an article about ipfire.</p>",
-                                         "de" : "<p>Die Linux-User-Group aus Lünen hat einen Artikel
-                                                               Ã¼ber IPFire auf Ihrer Webseite veröffentlicht.</p>"}},
-
-               "05" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 16",
-                               "date"     : "2008-08-17",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #16</strong>.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #16</strong> herausgegeben.</p>"}},
-
-               "06" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "IPFire 2.3 Beta 3",
-                               "date"     : "2008-08-20",
-                               "link"     : "http://forum.ipfire.org/index.php/topic,709.0.html",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the beta version of <strong>IPFire 2.3</strong>.</p>",
-                                         "de" : "<p>Heute wurde die Beta-Version von <strong>IPFire 2.3</strong> herausgegeben.</p>"}},
-
-               "07" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "Presentation of our project on kbarthel.de",
-                                                          "de" : "Projektpräsentation auf kbarthel.de" },
-                               "date"     : "2008-08-22",
-                               "link"     : "http://blog.kbarthel.de/?p=148",
-                               "content"  :
-                                       { "en" : "<p>Kim Barthel published on his blog a text about the project ipfire itself.</p>",
-                                         "de" : "<p>Kim Barthel veröffentlichte in seinem Blog einen umfassenden Bericht Ã¼ber IPFire.</p>"}},
-
-               "08" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "IPFire 2.3 Beta 5",
-                               "date"     : "2008-10-13",
-                               "link"     : "http://forum.ipfire.org/index.php/topic,788.0.html",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the beta version of <strong>IPFire 2.3</strong>.</p>",
-                                         "de" : "<p>Heute wurde die Beta-Version von <strong>IPFire 2.3</strong> herausgegeben.</p>"}},
-
-               "09" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "IPFire 2.3 Final",
-                               "date"     : "2008-11-08",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the final version of <strong>IPFire 2.3</strong>.</p>",
-                                         "de" : "<p>Heute wurde die finale Version von <strong>IPFire 2.3</strong> herausgegeben.</p>"}},
-
-               "10" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 24",
-                               "date"     : "2008-12-12",
-                               "link"     : "http://forum.ipfire.org/index.php?topic=882.0",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #24</strong>.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #24</strong> herausgegeben.</p>"}},
-
-               "11" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 25",
-                               "date"     : "2008-12-21",
-                               "link"     : "http://forum.ipfire.org/index.php?topic=951.0",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #25</strong>.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #25</strong> herausgegeben.</p>"}},
-
-               "12" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 26",
-                               "date"     : "2009-02-11",
-                               "link"     : "http://forum.ipfire.org/index.php?topic=991.0",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #26</strong>.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #26</strong> herausgegeben.</p>"}},
-
-               "13" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Core Update 27",
-                               "date"     : "2009-03-10",
-                               "link"     : "http://forum.ipfire.org/index.php?topic=1018.0",
-                               "content"  :
-                                       { "en" : "<p>Today, we released the <strong>core update number #27</strong>.</p>",
-                                         "de" : "<p>Heute wurde das <strong>Core Update #27</strong> herausgegeben.</p>"}},
-
-               "14" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : "Hosting Partner",
-                               "date"     : "2009-03-28",
-                               "link"     : "http://wiki.ipfire.org/de/project/contribute",
-                               "content"  :
-                                       { "en" : "<p>We would like to welcome to our hosting partner <strong>ISP42</strong> that
-                                                               has given a server to the project for free.</p>",
-                                         "de" : "<p>Wir möchten unseren neuen Hosting-Partner <strong>ISP42</strong>
-                                                               willkommen heißen, der uns einen Server konstenfrei zu Verfügung stellt.</p>"}},
-
-               "15" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "rss feed url changed", "de" : "RSS-Feed-URL geändert" },
-                               "date"     : "2009-03-28",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>The url of our rss feed has changed. Please re-add it from the link in the sidebar.</p>",
-                                         "de" : "<p>Wir möchten darauf hinweisen, dass sich die URL des RSS-Feeds geändert hat. Es ist nötig
-                                                               den Feed neu hinzuzufügen von dem Link in der Sidebar.</p>"}},
-
-               "16" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "experimental xen-images", "de" : "Experimentelle Xen-Images verfügbar" },
-                               "date"     : "2009-06-11",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Ben Schweikert has published an experimental xen-image. See
-                                                               <a href=\"http://people.ipfire.org/~trikolon/\" target=\"_blank\">http://people.ipfire.org/~trikolon/</a>
-                                                               for more information.</p><p>Instructions are included in the tarball.</p>",
-                                         "de" : "<p>Ben Schweikert hat ein experimentelles Image für IPFire als Gast in Xen erstellt.</p>
-                                                               <p>Der Download ist auf <a href=\"http://people.ipfire.org/~trikolon/\" target=\"_blank\">http://people.ipfire.org/~trikolon/</a>
-                                                               zu finden. Installationsanweisungen sind in dem Tarball enthalten.</p>"}},
-
-               "17" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.5 has arrived! - Core 28", "de" : "IPFire 2.5 - Core 28" },
-                               "date"     : "2009-06-22",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Finally, <strong>IPFire 2.5</strong> has arrived! With this release,
-                                                               there are several new features:</p>
-                                                               <ul><li>More/better hardware support (with Linux kernel 2.6.27.25 LTS)</li>
-                                                                       <li>Dial-in via PPTP and VDSL</li>
-                                                                       <li>A lot of minor updates: openssl, dnsmasq, iptables, snort.</li>
-                                                               </ul>
-                                                               <p>There is also an experimental <strong>Xen</strong>-kernel available.</p>",
-                                         "de" : "<p>Ab heute ist <strong>IPFire 2.5</strong> verfügbar. Es gibt folgende Ã„nderungen:</p>
-                                                               <ul><li>Bessere Hardwareunterstützung (Linux 2.6.27.25 LTS).</li>
-                                                                       <li>Einwahl Ã¼ber PPTP und VDSL möglich.</li>
-                                                                       <li>Eine Zahl kleinerer Updates: openssl, dnsmasq, iptables, snort.</li>
-                                                               </ul>
-                                                               <p>Ein experimenteller <strong>Xen</strong>-Kernel ist verfügbar.</p>"}},
-                                                               
-               "18" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 29", "de" : "Core Update 29" },
-                               "date"     : "2009-08-05",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>From today, <strong>Core 29</strong> will be available. 
-                                                               It comes with the following Changes:</p>
-                                                               <ul>
-                                                                       <li>Deleted unneeded collectd partition data.</li>
-                                                                       <li>Removed networks CGI page.</li>
-                                                                       <li>Deleted all snort community rules, because they are no longer maintained.</li>
-                                                                       <li>There were updates for: compat wireless, fcron, kudzu, iw</li>
-                                                                       <li>Changed visual style and made some bugfixes for some CGIs.</li>
-                                                                       <li>Added ability to change red mac address.</li>
-                                                                       <li>Added ability to change red dns servers for dhcp.</li>
-                                                                       <li>Added av7110 firmware.</li>
-                                                                       <li>Added nsupdate from bind package.</li>
-                                                                       <li>Added iptables CGI to firewall menu.</li>
-                                                               </ul><br />",
-                                                               
-                                         "de" : "<p>Ab heute ist <strong>Core 29</strong> verfügbar. Es kommt mit folgenden Ã„nderungen:</p>
-                                                               <ul>
-                                                                       <li>Nicht benötigte Partitionsstatistiken wurden gelöscht.</li>
-                                                                       <li>Die networks.cgi wurde entfernt.</li>
-                                                                       <li>Snort Community Rules wurden gelöscht, da diese teilweise inkompatibel mit der aktuellen Snort Version waren.</li>
-                                                                       <li>Updates gab es unter anderem für: compat wireless, fcron, kudzu, iw.</li>
-                                                                       <li>Es wurden einige Fehler und kosmetische Korrekturen/Verbesserungen and den CGIs vorgenommen.</li>
-                                                                       <li>Die MAC Adresse des roten Interfaces kann ab sofort per Webinterface geändert werden.</li>
-                                                                       <li>Die DNS Server für DHCP auf rot können nun statisch gesetzt werden.</li>
-                                                                       <li>Die Firmware für av7110 wurde hinzugefügt.</li>
-                                                                       <li>nsupdate aus dem Bind Paket wurde hinzugefügt.</li>
-                                                                       <li>Die vorhandene IPTables CGI wurde in das Network Menu eingehangen.</li>
-                                                               </ul><br />"}},
-
-
-               "19" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 30", "de" : "Core Update 30" },
-                               "date"     : "2009-08-30",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 30</strong>. 
-                                                               It comes with the following Changes:</p>
-                                                               <ul>
-                                                                       <li>Added a warning that qos rules are deleted if you go back without saving</li>
-                                                                       <li>Added a question if you really want to shutdown/reboot</li>
-                                                                       <li>New: Added support for Vodafone K3565-Z umts stick</li>
-                                                                       <li>New: Added support for Installation on virtio block devices</li>
-                                                                       <li>Updated driver for: Intel e1000e and High-Speed-Option 3g Driver</li>
-                                                                       <li>Fixed pakfire configuration settings</li>
-                                                                       <li>Fixed pakfire install dependencies more than once</li>
-                                                                       <li>Fixed /etc/host gateway setup</li>
-                                                               </ul><br />
-                                                               <p>Special fixes for c't-Server:</p>
-                                                               <ul>
-                                                                       <li>Fixed network card assignment (setup corrupted it)</li>
-                                                                       <li>Fixed empty pakfire uuid</li>
-                                                                       <li>Removed autoload of acpi modules</li>
-                                                                       <li>Switched pakfire trunk to 2.5-ct</li>                                                               
-                                                               </ul><br />",
-                                                                                       
-                                         "de" : "<p>Heute werden wir <strong>Core 30</strong> veröffentlichen. 
-                                                               Es kommt mit folgenden Ã„nderungen:</p>
-                                                               <ul>
-                                                                       <li>Warnung hinzugefügt, dass QoS-Regeln bei \"zurück\" gelöscht werden</li>
-                                                                       <li>Sicherheitsabfrage, ob man wirklich herunterfahren/neustarten will</li>
-                                                                       <li>Neu: Unterstützung für Vodafone K3565-Z UMTS-Stick</li>
-                                                                       <li>Neu: Installation auf \"virtio block devices\"</li>
-                                                                       <li>Treiberupdate für Intel e1000e und High-Speed-Option UMTS</li>
-                                                                       <li>Fix: Pakfire-Einstellungsdialog</li>
-                                                                       <li>Fix: Pakfire installiert Abhängigkeiten mehrfach</li>
-                                                                       <li>Fix: /etc/host gateway-Eintrag</li>
-                                                               </ul><br />
-                                                               <p>Spezielle Korrekturen für den c't-Server:</p>
-                                                               <ul>
-                                                                       <li>Fix: Netwerkartenzuordnung (setup zerstörte diese)</li>
-                                                                       <li>Fix: Pakfire UUID ist leer</li>
-                                                                       <li>ACPI Module werden nicht mehr versucht zu laden</li>
-                                                                       <li>Pakfire-Trunk nach 2.5-ct gewechselt</li>
-                                                               </ul><br />"}},
-                                                               
-                                                               
-               "20" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 31", "de" : "Core Update 31" },
-                               "date"     : "2009-10-04",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 31</strong>. 
-                                                                       It implicates following changes:</p>
-                                                               <ul>
-                                                                       <li>Upgrade Kernel to stable 2.6.27.31 due to a security vulnerability</li>
-                                                                       <li>Added drivers for alix led support</li>
-                                                                       <li>Updated openswan to 2.6.23</li>
-                                                                       <li>Updated smartctl to latest svn r2877 (usb support)</li>
-                                                                       <li>Updated openvpn to 2.9 rc19</li>
-                                                                       <li>The mac address und dns server adjustments can now be deleted</li>
-                                                                       <li>Fixed a few bugs on some cgi: urlfilter.cgi constraints, chpasswd.cgi</li>
-                                                               </ul>
-                                                               <p>Because of the security vulnerability in the old kernel, we recommend all users 
-                                                               <br />to install this core update!</p>
-                                                               <br />",
-                                                                                       
-                                         "de" : "<p>Heute wurde <strong>Core 31</strong> veröffentlicht. Es bringt die folgenden Ã„nderungen mit sich:</p>
-                                                               
-                                                               <ul>                                                            
-                                                                       <li>Kernel Update auf 2.6.27.31 wegen einer Sicherheitslücke</li>
-                                                                       <li>Treiber für Alix Led's unterstützung hinzugefügt</li>
-                                                                       <li>Update von Openswan auf 2.6.23</li>
-                                                                       <li>Update von smartctl auf SVN r2877 (USB-Support)</li>
-                                                                       <li>Update von Openvpn auf 2.9 rc19</li>
-                                                                       <li>Die MAC Adresse und DNS Server Einstellungen können nun auch gelöscht werden</li>
-                                                                       <li>Es wurden ein paar Fehler an CGIs behoben: urlfilter.cgi constraints, chpasswd.cgi</li>             
-                                                               </ul>
-                                                               <p>Aufgrund der Sicherheitslücke im alten Kernel empfehlen wir allen Benutzern
-                                                               <br />dieses Core-Update zu installieren!</p>
-                                                               <br />"}},
-                                                               
-               "21" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 32", "de" : "Core Update 32" },
-                               "date"     : "2009-11-04",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 32</strong>. 
-                                                                       It implicates following changes:</p>
-                                                               <ul>                    
-                                                               <li>Update of openvpn to version 2.1rc20</li>
-                                                                   <li>Update of setdns.pl now it updates correctly regfish.de accounts</li>
-                                                                   <li>Update of squid to version 2.7.STABLE7</li>
-                                                                   <li>Update of ntfs3g, maybe this fixes some mount problems</li>
-                                                                   <li>Update of hardware database</li>
-                                                                   <li>Update of realtek network drivers</li>  
-                                                                   <li>Update of GeoIP database</li>
-                                                                   <li>Update of Net::SSLeay</li>
-                                                                   <li>Downgrade dnsmasq to version 2.45</li>
-                                                                   <li>Fix on logrotate the postrotate script of snort logs</li>
-                                                                   <li>Fix tmpfs backup cronjob entry</li>
-                                                                   <li>Fix dhcpclient for IPTV</li>
-                                                                   <li>Add vdsl-iptv mac address change</li>
-                                                                   <li>Add 'all option' to outgoing firewall</li>
-                                                                   <li>Add S.M.A.R.T. fail warning to index.cgi</li>
-                                                                   <li>Add a core-update notification to index.cgi when there will be available</li>
-                                                                   <li>Changed probenic.sh to detect virtio and ssb interfaces</li>
-                                                                   <li>Translated some german phrases on en.pl</li>
-                                                                   <li>Prepared PPTP over DHCP internet connection support</li>
-                                                               </ul>
-                                                               <br />",
-                                                                                       
-                                         "de" : "<p>Heute werden wir <strong>Core 32</strong> veröffentlichen. Es bringt die folgenden Ã„nderungen mit sich:</p>                                                      
-                                                               <ul>                                                            
-                                                                   <li>Update von openvpn auf Version 2.1rc20</li>
-                                                                   <li>Update von setdns.pl nun werden auch regfish.de Account richtig aktualisiert</li>
-                                                                   <li>Update von squid auf Version 2.7.STABLE7</li>
-                                                                   <li>Update von ntfs3g, vielleicht behebt dieses ein paar mount Probleme</li>
-                                                                   <li>Update der Hardware Datenbank</li>
-                                                                   <li>Update von Realtek Netzwerkkartentreibern</li>  
-                                                                   <li>Update der GeoIP Datenbank</li>
-                                                                   <li>Update von Net::SSLeay</li>
-                                                                   <li>Downgrade dnsmasq auf Version 2.45</li>
-                                                                   <li>Beim Logrotate wurden am postrotate Script von den Snort Logs Fehler behoben</li>
-                                                                   <li>Fix tmpfs Backup Cronjob Eintrag</li>
-                                                                   <li>Fix dhcpclient für IPTV</li>
-                                                                   <li>Es gibt nun die Möglichkeit bei vdsl-iptv die Mac Adresse zu Ã¤ndern</li>
-                                                                   <li>Der Ausgehenden Firewall wurde die Option 'all options' hinzugefügt</li>
-                                                                   <li>Eine S.M.A.R.T. Fehlerwarung wurder der index.cgi hinzugefügt</li>
-                                                                   <li>Eine Core-Update Meldung wird nun auf der index.cgi angezeigt wenn Updates vorhanden sind</li>
-                                                                   <li>Änderungen an der probenic.sh um Virtio und SSB Interfaces zu erkennen</li>
-                                                                   <li>Überseztung von ein paar deutschen Sätze in der en.pl</li>
-                                                                   <li>Vorbereitung für die Unterstützung von PPTP over DHCP Internetverbindungen</li>       
-                                                               </ul>
-                                                               <br />"}},
-                               
-               "22" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 33", "de" : "Core Update 33" },
-                               "date"     : "2009-11-05",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p><strong>Due to a security reason</strong> we are going to release <strong>Core 33</strong> today. 
-                                                               </p>
-                                                               <ul>                                                            
-                                                                   <li>Set vm.mmap_min_addr to 4096 to workaround 
-                                                                   <a href=\"http://www.h-online.com/open/news/item/Hole-in-the-Linux-kernel-allows-root-access-850016.html\" 
-                                                                   target=\"_blank\">this security issue</a>.</li>
-                                                                   <li>Fix some typos (Device xxx reports S.M.A.R.T. etc.)</li>
-                                                                   <li>Update Net-Server to 0.97</li>
-                                                                       <li>Pakfire: Update also the meta db at package list update.<br />
-                                                                   This prevent using old dependencies at package update.</li>
-                                                               </ul>
-                                                               <br />",
-                                                                                       
-                                         "de" : "<p><strong>Aus sicherheitstechnischen Gründen</strong> werden wir heute direkt den <strong>Core 33</strong> 
-                                                               veröffentlichen:</p>                                                   
-                                                               <ul>                                                            
-                                                                   <li>Wert in vm.mmap_min_addr auf 4096 gesetzt, um 
-                                                                   <a href=\"http://www.heise.de/security/meldung/Luecke-im-Linux-Kernel-erlaubt-Root-Zugriff-Update-849799.html\"
-                                                                   target=\"_blank\">dieses Sicherheitsproblem</a> zu blockieren.</li>
-                                                                   <li>Fix von einigen Tippfehler (Device xxx reports S.M.A.R.T. etc.)</li>
-                                                                   <li>Update Net-Server auf Version 0.97</li>
-                                                                       <li>Pakfire: AktualiSiert nun auch die meta db beim \"package list\" Update.<br />
-                                                                   Dieses verhindert die Benutzung von alten Abhängigkeiten bei der PaketaktualiSierung.</li>
-                                                               </ul>
-                                                               <br />"}},
-
-               "23" : {"author"   : "Michael Tremer",
-                               "mail"     : "ms@ipfire.org",
-                               "subject"  : { "en" : "Group of Core Developers grows", "de" : "Neue Core-Developer ernannt" },
-                               "date"     : "2009-11-07",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>The IPFire Core Developers are proud to announce to new members of them.
-                                                               <strong>Stefan Schantl</strong> and <strong>Jan Paul Tücking</strong> were incorporated
-                                                               into the group to honour their continuous contribution and giving their support to the project.</p>
-                                                               <p>To get out how you can join the project have a look at:
-                                                                 <a href=\"http://wiki.ipfire.org/en/project/join\" target=\"_blank\">http://wiki.ipfire.org/en/project/join</a>.
-                                                               </p>",
-
-                                         "de" : "<p>Das IPFire Core Development Team ist stolz zwei neue Mitglieder vorzustellen.
-                                                               <strong>Stefan Schantl</strong> und <strong>Jan Paul Tücking</strong> sind von
-                                                               nun an Teil des Teams aufgrund Ihrer beständigen und langen Mitarbeit am Projekt.</p>
-                                                               <p>
-                                                                       <a href=\"http://wiki.ipfire.org/de/project/contribute\" target=\"_blank\">Wie kann ich selber zum Projekt beitragen?</a>
-                                                               </p>"}},
-                                                                               
-               "24" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "talk about IPFire", "de" : "Vortrag zum IPFire" },
-                               "date"     : "2009-12-06",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Marcus Scholz did a <a href=\"http://www.commander1024.de/wordpress/2009/12/ipfire-talk-in-der-warpzone/\">talk</a> about
-                                                        IPFire at the <a href=\"http://warpzone.ms/\" target=\"_blank\">Warpzone</a>, a 
-                                                        <a href=\"http://www.commander1024.de/wordpress/wp-content/uploads/2009/12/IPFire-Talk.pdf\" target=\"_blank\">draft</a> 
-                                                        is available as well as a <a href=\"http://qik.com/video/3807703\" target=\"_blank\">video</a>.
-                                                         </p>",
-
-                                         "de" : "<p>Marcus Scholz hat einen <a href=\"http://www.commander1024.de/wordpress/2009/12/ipfire-talk-in-der-warpzone/\" target=\"_blank\">Vortrag</a> Ã¼ber
-                                                         IPFire in der <a href=\"http://warpzone.ms/\" target=\"_blank\">Warpzone</a> gehalten. Die dazugehörigen  
-                                                         <a href=\"http://www.commander1024.de/wordpress/wp-content/uploads/2009/12/IPFire-Talk.pdf\" target=\"_blank\">Folien</a> 
-                                                         so wie ein <a href=\"http://qik.com/video/3807703\" target=\"_blank\">Video</a> von seiner Präsentation stehen zur Verfügung.
-                                                               </p>"}},
-
-               "25" : {"author"   : "Michael Tremer",
-                               "mail"     : "michael.tremer@ipfire.org",
-                               "subject"  : { "en" : "IPFire on CeBIT 2010", "de" : "IPFire auf der CeBIT 2010" },
-                               "date"     : "2009-12-28",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>At next year's CeBIT, the world's largest computer expo, <strong>IPFire</strong> will,
-                                                               for the first time, get its own booth in the <strong>Open Source Project Lounge</strong>.</p>
-                                                               <p>That's why we need your <a href=\"cebit#Your%20Donations%20are%20Needed!\">donations!</a><br />
-                                                               We would like to say thanks to all the people who have already donated.</p>
-                                                               <p>For further information: Read our <a href=\"cebit\">CeBIT Special</a>.</p>",
-
-                                         "de" : "<p>Dank der <strong>Linux New Media AG</strong> wird das <strong>IPFire</strong>-Projekt auf der CeBIT,
-                                                               der weltweit größten Messe für Informationstechnik, im nächsten Jahr mit einem eigenen Stand
-                                                               in der <strong>Open Source Project Lounge</strong> vertreten sein.</p>
-                                                               <p>
-                                                               Daher möchten wir hiermit einen <strong>Spendenaufruf</strong> starten und bedanken uns schon
-                                                               im Voraus bei der Vielzahl an freiwilligen Helfern.</p>
-                                                               <p>Weitere Informationen zum Thema gibt es auf der <a href=\"cebit\">CeBIT-Special-Seite</a>.</p>"}},
-                                                               
-               "26" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 34", "de" : "Core Update 34" },
-                               "date"     : "2010-01-09",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release the first update in 2010. 
-                                                                 <strong>Core 34</strong> comes with the following changes:</p>
-                                                               <p>
-                                                               <ul>
-                                                                 <li><strong>Kernel update to 2.6.27.42</strong></li>
-                                                                 <ul>
-                                                                   <li>Update of mISDN, mISDNuser</li>
-                                                                   <li>Update of alsa to 1.0.22(.1)</li>
-                                                                   <li>Update of v4l-dvb</li>
-                                                                   <li>Update of compat-wireless to 2.6.32.2</li>
-                                                                   <li>Switch of atheros default driver to ath5k</li>
-                                                                   <li>Add kvm-kmod to update the kvm api for qemu</li>
-                                                                   <li>Add Atheros/Attansic atl1c ethernet driver</li>
-                                                                   <li>Add patches to detect Realtek 8102/8103 to r8101 driver</li>
-                                                                   <li>Add a patch to fix Intel E100 wake-on-lan problems</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>Add usb_modeswitch 1.0.6</li>
-                                                                 <li>Add compile options for DCF77-clocks</li>
-                                                                 <li>Update of smartmontools to 5.39 stable</li>
-                                                                 <li>Ensure that ehci_hci module are loading at first (fix USB2.0 detection)</li>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>IPSEC:</li>
-                                                                 <ul>
-                                                                   <li>Make ipsec peers reachable from the IPFire</li>
-                                                                   <li>Remove not working cryptomodes from ipsec config</li>
-                                                                 </ul>
-                                                                 <li>IPTV fixes:</li>
-                                                                 <ul>
-                                                                   <li>Update of dhcpcd</li>
-                                                                   <li>Fix the iqmpproxy install check (pppsetup.cgi)</li>
-                                                                   <li>Improve the mac.cgi page. Fixed table and added some comments.</li>
-                                                                 </ul>
-                                                                 <li>CGI changes:</li>
-                                                                 <ul>
-                                                                   <li>Add a donation button to credit page</li>
-                                                                   <li>Add a patch to change oinkmaster tmp to /var/tmp</li>
-                                                                   <li>Add time constraints feature and local port adressing to outgoing firewall</li>
-                                                                   <li>Redesign of chpasswd.cgi</li>
-                                                                   <li>Improve the pakfire \"ping\" message on pakfire.cgi</li>
-                                                                   <li>ExtraHD page is now multilingual</li>
-                                                                   <li>Fix that crontab was replaced at ramdisk restore</li>
-                                                                 </ul>
-                                                                 <li>Installer:</li>
-                                                                 <ul>
-                                                                   <li>Change installer device from /dev/tty1 to /dev/console</li>
-                                                                   <li>Update of memtest86+ to 4.00</li>
-                                                                 </ul>
-                                                                 <li>Languages:</li>
-                                                                 <ul>
-                                                                  <li>Add french language support to installer</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               </p>
-                                                               <p>
-                                                               Due to the <strong>kernel update</strong> a <strong>reboot</strong> of IPFire is necessary!<br />
-                                                               <strong>Warning</strong>: Grub is being reinstalled during the update. If you have applied manual 
-                                                               changes, you have to reapply them first!<br />
-                                                               <strong>Xen</strong> user have to update linux-xen bevor the reboot of the machine!
-                                                               </p>",
-
-                                         "de" : "<p>Heute werden wir das erste Update im Jahr 2010 veröffentlichen.  
-                                                                 Das <strong>Core 34</strong> Update weist folgende Ã„nderungen auf:</p>
-                                                               <p>
-                                                               <ul>
-                                                                 <li><strong>Kernel Update auf 2.6.27.42</strong></li>
-                                                                 <ul>
-                                                                   <li>Update von mISDN, mISDNuser</li>
-                                                                   <li>Update von alsa auf 1.0.22(.1)</li>
-                                                                   <li>Update von v4l-dvb</li>
-                                                                   <li>Update von compat-wireless auf 2.6.32.2</li>
-                                                                   <li>Wechsel des Standardtreibers von atheros auf ath5k</li>
-                                                                   <li>Ergänzung von kvm-kmod zwecks AktualiSierung der kvm api für qemu</li>
-                                                                   <li>Erweiterung um Atheros/Attansic atl1c Netzwerktreiber</li>
-                                                                   <li>Ergänzung um Patches für die Erkennung von Realtek 8102/8103 zu r8101 Treibern</li>
-                                                                   <li>Behebung der Wake-on-Lan Probleme der Intel E100 NIC durch das Hinzufügen eines Patches</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>Erweiterung um usb_modeswitch 1.0.6</li>
-                                                                 <li>NTP: Unterstützung für DCF77-Uhren</li>
-                                                                 <li>Update von smartmontools auf 5.39 stable</li>
-                                                                 <li>Sicherstellung, dass das ehci_hci Modul zuerst geladen wird (behebt USB2.0-Erkennung)</li>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>IPSEC-Verbesserungen:</li>
-                                                                 <ul>
-                                                                   <li>IPSec Peers sind nun ebenfalls durch IPFire erreichbar</li>
-                                                                   <li>Nicht funktionierende Cryptomodule wurden aus der IPSec-Konfiguration gelöscht.</li>
-                                                                 </ul>
-                                                                 <li>IPTV Verbesserungen:</li>
-                                                                 <ul>
-                                                                   <li>Update von dhcpcd</li>
-                                                                   <li>Fix für den iqmpproxy Installationscheck (pppsetup.cgi)</li>
-                                                                   <li>Verbesserung an der mac.cgi Seite, Ã„nderungen am Tabellenlayout und Ergänzungen von Beschreibungen.</li>
-                                                                 </ul>
-                                                                 <li>CGI Ã„nderungen:</li>
-                                                                 <ul>
-                                                                   <li>Hinzufügen eines Spendenbuttons auf der Credits Seite</li>
-                                                                   <li>Erweiterung um ein Patch, zwecks Ã„nderung des oinkmaster Verzeichnisses tmp auf /var/tmp</li>
-                                                                   <li>Erweiterung um ein \"time constraints feature\" und \"local port adressing\" zur ausgehenden Firewall</li>
-                                                                   <li>Neugestaltung der chpasswd.cgi</li>
-                                                                   <li>Verbesserung der Pakfire \"Ping\" Beschreibung auf der pakfire.cgi</li>
-                                                                   <li>Erweiterung auf eine mehrsprachige ExtraHD-Seite</li>
-                                                                   <li>Beseitigung des Fehlers, dass beim RAM-Disk Restore Crontab ersetzt wurde</li>
-                                                                 </ul>
-                                                                 <li>Installer:</li>
-                                                                 <ul>
-                                                                   <li>Änderung des Installer device von /dev/tty1 nach /dev/console geändert</li>
-                                                                   <li>Update von memtest86+ auf 4.00</li>
-                                                                 </ul>
-                                                                 <li>Sprachen:</li>
-                                                                 <ul>
-                                                                  <li>Unterstützung der französischen Sprache beim Installer</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               </p>
-                                                               <p>
-                                                               Durch das <strong>Update</strong> des <strong>Kernels</strong> ist ein <strong>Neustart</strong> 
-                                                               von IPFire nötig! <br/>
-                                                               <strong>Achtung</strong>: Grub wird neu installiert. Wenn die Konfiguration manuell geändert 
-                                                               wurde muss diese zuerst wieder hergestellt werden!<br />
-                                                               <strong>Xen</strong> User müssen vor dem Neustart der Maschine zuerst linux-xen updaten, 
-                                                               da es sonst zu Fehlern kommt!
-                                                               </p>"}},
-                                                               
-               "27" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 35", "de" : "Core Update 35" },
-                               "date"     : "2010-01-28",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 35</strong>. 
-                                                                       It implicates following changes:</p>    
-                                                               <p>
-                                                               <ul>
-                                                             <li>Applied the latest <strong>security patches</strong> to squidGuard to prevent buffer overflows and 
-                                                                from going into emergency mode when overlong URLs are encountered</li>
-                                                             <li>Add missing modules for intersil/prism (disabled) </li>
-                                                             <li>Disabled connscheduler on general (*)</li>
-                                                             <li>Add zerofree to cleanup flash and xenimages (*)</li>
-                                                             <li>Fix for countrycode list (thx to m.a.d)</li>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>Webinterface:</li>
-                                                                 <ul>
-                                                                   <li>Fix the link to external Snort SID page</li>
-                                                                 </ul>
-                                                                 <li>Outgoing Firewall:</li>
-                                                                 <ul>
-                                                                   <li>Parse pre Core34 rules correct and enabled change logging in mode 1</li>
-                                                                   <li>Added GRE and ESP protocol to outgoing firewall</li>
-                                                                 </ul>
-                                                                 <li>Xen:</li>
-                                                                 <ul>
-                                                                   <li>Fix klogd fails on xen-kernel</li>
-                                                                   <li>Disabled start of console initscript on xen-image (*)</li>
-                                                                   <li>Change xen-image boot fstype to ext2 (*)</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               </p>
-                                                               <p>
-                                                               (*) These changes have only effect on new installations of IPFire.
-                                                       </p>",
-
-
-                                         "de" : "<p>Heute werden wir <strong>Core 35</strong> veröffentlichen. 
-                                                                       Es bringt die folgenden Ã„nderungen mit sich:</p>       
-                                                               <p>
-                                                               <ul>
-                                                             <li>Die neuesten <strong>Sicherheitspatches</strong> für squidGuard wurden eingespielt, 
-                                                               diese beheben ein Pufferüberlauf und verhindert das squidGuard in den 
-                                                               \"Emergency-Mode\" bei Ã¼berlangen URLs geht</li>
-                                                             <li>Fehlende Module für intersil/prism wurden hinzugefügt.</li>
-                                                             <li>Der connscheduler ist nun immer zuerst abgeschaltet (*)</li>
-                                                             <li>Unbenutzte Blöcke in den Flash und Xen-Images vor dem komprimieren gelöscht (*)</li>
-                                                             <li>Korrektur der Ländercodeliste (Danke m.a.d)</li>
-                                                               </ul>
-                                                               <ul>
-                                                                 <li>Webinterface:</li>
-                                                                 <ul>
-                                                                   <li>Der Link für die externe Snort SID Seite wurde korrigiert</li>
-                                                                 </ul>
-                                                                 <li>Ausgehende Firewall:</li>
-                                                                 <ul>
-                                                                   <li>Alte Regeln (vor Core34) werden nun korrekt geparst und für den Modus 1 wurde das Logging aktiviert</li>
-                                                                   <li>Die Protokolle GRE und ESP wurden der Ausgehenden Firewall hinzugefügt</li>
-                                                                 </ul>
-                                                                 <li>Xen:</li>
-                                                                 <ul>
-                                                                   <li>Die Fehler vom klogd Demon im Xen-Kernel wurden behoben</li>
-                                                                   <li>Der Start der Konsolen Initskripten auf dem Xen-Image wurde herausgenommen (*)</li>
-                                                                   <li>Filesystemtyp der Bootpartition des Xen-Images wurde auf ext2 geändert (*)</li>
-                                                                 </ul>
-                                                               </ul>
-                                                               </p>
-                                                               <p>
-                                                               (*) Diese Ã„nderungen betreffen nur Neuinstallationen von IPFire.
-                                               </p>"}},
-                                               
-                                               
-               "28" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 36", "de" : "Core Update 36" },
-                               "date"     : "2010-02-01",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 36</strong>. 
-                                                               It implicates following changes:</p>    
-                                                               <p>
-                                                                       <ul>
-                                                                               <li>Update of openssh to 5.3p1</li>
-                                                                               <li>Update of openssl to 0.9.8l</li>
-                                                                               <li>Change cyrus-sasl from package to common</li>
-                                                                       </ul>
-                                                                       <ul>
-                                                                               <li>Webinterface:</li>
-                                                                               <ul>
-                                                                                       <li>Fixed some typos (thx to lavaguy)</li>
-                                                                                       <li>Add a delay to pakfire.cgi to prevent pakfire run's twice.</li>
-                                                                               </ul>
-                                                                               <li>IPTV:</li>
-                                                                               <ul>
-                                                                                       <li>Add IPTV chains for IGMPPROXY to firewall.</li>
-                                                                               </ul>
-                                                                               <li>Addon:</li>
-                                                                               <ul>
-                                                                                       <li>Update of nmap to 5.20</li>
-                                                                                       <li>Update of rsync to 3.0.7</li>
-                                                                               </ul>
-                                                                       </ul>
-                                                               </p>
-                                                               <p>
-                                                                       Because of the security updates of ssh and ssl, we 
-                                                                       recommend all users to install this core update!
-                                                               </p>",
-
-
-                                               "de" : "<p>Heute werden wir <strong>Core 36</strong> veröffentlichen. 
-                                                               Es bringt die folgenden Ã„nderungen mit sich:</p>
-                                                               <p>
-                                                                       <ul>
-                                                                               <li>Update von openssh auf 5.3p1</li>
-                                                                               <li>Update von openssl auf 0.9.8l</li>
-                                                                               <li>Cyrus-sasl ist nun direkt integriert und kein extra Paket mehr</li>
-                                                                       </ul>
-                                                                       <ul>
-                                                                               <li>Webinterface:</li>
-                                                                               <ul>
-                                                                                       <li>Einige Tippfehler wurden korrigiert (Danke lavaguy)</li>
-                                                                                       <li>Es wurde eine Verzögerung der pakfire.cgi Seite hinzugefügt, 
-                                                                                       welche verhindern soll das Pakfire zwei mal gestartet wird</li>
-                                                                               </ul>
-                                                                               <li>IPTV:</li>
-                                                                               <ul>
-                                                                                       <li>Extra IPTV Chains für den IGMPPROXY wurden der Firewall hinzugefügt</li>
-                                                                               </ul>
-                                                                               <li>Addon:</li>
-                                                                               <ul>
-                                                                                       <li>Update von nmap auf 5.20</li>
-                                                                                       <li>Update von rsync auf 3.0.7 </li>
-                                                                               </ul>
-                                                                       </ul>
-                                                               </p>
-                                                               <p>
-                                                                       Auf Grund der Sicheitsupdates von ssh und ssl, empfehlen wir allen Benutzern dieses 
-                                                                       Core Update zu installieren!
-                                                               </p>"}},
-
-               "29" : {"author"   : "Michael Tremer",
-                               "mail"     : "ms@ipfire.org",
-                               "subject"  : "CeBIT Update #1",
-                               "date"     : "2010-02-24",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "
-                                               <p>I would like you to get a short update for our plannings on CeBIT 2010.</p>
-                                               <p>
-                                                       There are only a few days left and we are quite finished. But
-                                                       as an exhibitor, we got a bunch of free tickets. If you would like
-                                                       to get one, please send an email to
-                                                       <a href=\"mailto:tickets@ipfire.org\">tickets@ipfire.org</a>.
-                                               </p>
-                                               <p>
-                                                       We hope to see you at the IPFire booth in
-                                                       <strong>Hall 2 - F34</strong>.
-                                               </p>",
-
-                                         "de" : "
-                                               <p>Dies ist ein kleines Update zur CeBIT:</p>
-                                               <p>
-                                                       Die Vorbereitungen sind weitestgehend beendet und wir
-                                                       bedanken uns bei allen Helfern.
-                                               </p>
-                                               <p>
-                                                       Als Austeller haben wir eine Zahl kostenloser Tickets bekommen,
-                                                       die wir nun an die Community (vorrangig die Spender)
-                                                       verteilen möchten. Bitte sendet dazu eine kurze Email
-                                                       an <a href=\"mailto:tickets@ipfire.org\">tickets@ipfire.org</a>.
-                                               </p>
-                                               <p>
-                                                       Wir hoffen euch begrüßen zu dürfen in der <strong>Halle 2,
-                                                       Stand F34</strong>.
-                                               </p>"}},
-                                                                               
-               "30" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 37", "de" : "Core Update 37" },
-                               "date"     : "2010-04-04",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 37</strong>. 
-                                                               It implicates following changes:</p>    
-                                                                       <ul>
-                                                                               <li>Update of openssh to 5.4p1</li>
-                                                                               <li>Update of openssl to 0.9.8n</li>
-                                                                               <li>Update of apache to 2.2.15</li>
-                                                                               <li>Update of sslh to current stable</li>
-                                                                               <li>Update of madwifi to lastest stable</li>
-                                                                               <li>Update of lm_sensors to current stable</li>
-                                                                               <li>Enabled identd lookup for squid</li>
-                                                                               <li>Fix cyrus-sasl autorun</li>
-                                                                               <li>Fix pakfire pingtest to use icmp again</li>
-                                                                               <li>Fix ath5k (no txbuf available)</li>
-                                                                               <li>Fix disk (media/hardware) graphs with xen</li>
-                                                                               <li>Fix temp readings for some Atom boards</li>
-                                                                               <li>Fix mISDN-hfcusb driver (reportl1down and bchannel endpoint)</li>
-                                                                               <li>Fix english qos page</li>
-                                                                               <li>Fix urlfilter wasting much memory</li>
-                                                                               <li>Add eject commandline tool</li>
-                                                                               <li>Add possibility to change the ssh port from 222 back to standart port 22</li>
-                                                                               <li>Add Atom cpu to p4-clockmod</li>
-                                                                               <li>Add ET131x ethernet driver</li>
-                                                                       </ul>                                                           
-                                                               <p>
-                                                                       Because of the security updates of ssh and ssl, we 
-                                                                       recommend all users to install this core update!
-                                                               </p>
-                                                               <p>
-                                                                       If you had installed this core update from \"testing tree\" 
-                                                                       before 01.04.2010 check the forum how to reinstall this update because the 
-                                                                       openssl update was added in last minute.
-                                                               </p>
-                                                               <p>
-                                                                       We plan to change the ipsec vpn software from openswan to strongswan in one of the next updates. 
-                                                                       To prepare this you schould check the settings and fill the \"Local ID\" and \"Remote ID\" field.
-                                                                       The webif marks this as \"can be blank\" but this will change with strongswan!
-                                                               </p>",
-
-                                               "de" : "<p>Heute werden wir <strong>Core 37</strong> veröffentlichen. 
-                                                               Es bringt die folgenden Ã„nderungen mit sich:</p>
-                                                                       <ul>
-                                                                               <li>Update von openssl auf 0.9.8n</li>
-                                                                               <li>Update von apache auf 2.2.15</li>
-                                                                               <li>Update von sslh</li>
-                                                                               <li>Update von madwifi</li>
-                                                                               <li>Update von lm_sensors</li>
-                                                                               <li>identd lookup für squid aktiviert</li>
-                                                                               <li>Pakfire pingtest vewendet wieder icmp</li>
-                                                                               <li>Bugfix: cyrus-sasl autostart</li>
-                                                                               <li>Bugfix ath5k (no txbuf available)</li>
-                                                                               <li>Bugfix: disk (media/hardware) Graphen unter xen</li>
-                                                                               <li>Bugfix: Temperatursensoren von einigen Atom Boards</li>
-                                                                               <li>Bugfix: mISDN-hfcusb Treiber (reportl1down and bchannel endpoint)</li>
-                                                                               <li>Bugfix: Englische QoS Seite</li>
-                                                                               <li>Bugfix: urlfilter verschwendete Speicher</li>
-                                                                               <li> Neu: eject Kommandozeilentool</li>
-                                                                               <li>Der ssh port kann von 222 auf den Standardport 22 gestellt werden</li>
-                                                                               <li>Atom support für P4-clockmod</li>
-                                                                               <li>ET131x ethernet driver</li>
-                                                                       </ul>                                                           
-                                                               <p>
-                                                                       Auf Grund der Sicheitsupdates von ssh und ssl, empfehlen wir allen 
-                                                                       Benutzern dieses Core Update zu installieren!
-                                                               </p>
-                                                               <p>
-                                                                       Wenn Sie das Core 37 schon vor dem 01.04.2010 aus dem \"testing tree\" 
-                                                                       installiert haben bitte schauen Sie ins Forum wie das Update nochmal installiert 
-                                                                       wird da das openssl update in letzter Minute hinzugefügt wurde.
-                                                               </p>
-                                                               <p>
-                                                                       Wir Planen in einem der nächsten Updates die Software für das IPSEC VPN 
-                                                                       von openswan auf strongswan zu wechseln. Um auf diesen Wechsel vorbereitet zu sein 
-                                                                       sollten Sie Ihre IPSEC Einstellungen prüfen und die Felder \"Local ID\" und \"Remote ID\" 
-                                                                       ausfüllen. Im Moment sind diese Felder optional aber mit strongswan sind diese erforderlich.
-                                                               </p>"}},
-                                                               
-               "31" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire on LWN.net", "de" : "IPFire auf LWN.net" },
-                               "date"     : "2010-05-15",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Linux Weekly News, one of the most important information portals of free software and linux, 
-                                                                has released a detailed article about IPFire that we want to share with our users:
-                                                                <br />
-                                                            <a href=\"http://lwn.net/Articles/384419/\" target=\"_blank\">IPFire 2.5: Firewalls and more</a>
-                                                         </p>",
-
-
-                                         "de" : "<p>Linux Weekly News, einer der wichtigsten Informationsportale Ã¼ber Freie Software und Linux,
-                                                            hat einen ausführlichen Artikel Ã¼ber IPFire herausgebracht den wir unseren Benutzern nicht 
-                                                            vorenthalten wollen:
-                                                            <br />
-                                                            <a href=\"http://lwn.net/Articles/384419/\" target=\"_blank\">IPFire 2.5: Firewalls and more</a>   
-                                                         </p>"}},
-                                                         
-               "32" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 release candidate 1", "de" : "IPFire 2.7 release candidate 1" },
-                               "date"     : "2010-06-10",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>After the approval by the release manager we are going to release <strong>IPFire 2.7 RC1</strong> today. 
-                                                               This version is only suitable for testing and should not be used in productive environments.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                           <br />
-                                                           Please help us to test the new version of IPFire. If you have experienced any bugs please report 
-                                                           them to the <a href=\"http://bugtracker.ipfire.org/\" 
-                                                               target=\"_blank\">bugtracker</a>. Thank you!
-                                                           <br />
-                                                           More information is available on the 
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire forum</a>.
-                                                         </p>",
-
-                                         "de" : "<p>Nach der Freigabe durch den Releasemanager werden wir heute <strong>IPFire 2.7 RC1</strong> veröffentlichen. 
-                                                               Diese Version ist ausschließlich zu Testzwecken geeignet und sollte nicht in Produktivumgebungen benutzt werden.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                           <br />
-                                                               Helft uns und testet die neue IPFire-Version. Gefundene Fehler bitte im <a href=\"http://bugtracker.ipfire.org/\" 
-                                                               target=\"_blank\">Bugtracker</a> melden. Vielen Dank!
-                                                           <br />
-                                                           Mehr Informationen gibt es hier im
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire Forum</a>.
-                                                         </p>"}},
-                                                         
-               "33" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 release candidate 2", "de" : "IPFire 2.7 release candidate 2" },
-                               "date"     : "2010-06-16",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today <strong>IPFire 2.7 RC2</strong> is available.
-                                                                <br />
-                                                                Again this version is only suitable for testing and should not be used in productive environments.
-                                                                <br />
-                                                                Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                                <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                         </p>",
-
-                                         "de" : "<p>Heute steht <strong>IPFire 2.7 RC2</strong> zur Verfügung. 
-                                                                <br />Diese Version ist wieder ausschließlich zu Testzwecken geeignet und sollte nicht in Produktivumgebungen benutzt werden.
-                                                                <br />
-                                                                Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                                <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                         </p>"}},
-               
-               
-               "35" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 release candidate 3", "de" : "IPFire 2.7 release candidate 3" },
-                               "date"     : "2010-06-18",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are gonig to release <strong>IPFire 2.7 RC3</strong>.
-                                                               <br />
-                                                               This version is only suitable for testing and should not be used in productive environments.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               If you have experienced any bugs please report them to the <a href=\"http://bugtracker.ipfire.org/\" 
-                                                               target=\"_blank\">bugtracker</a>. Thank you!
-                                                               <br />
-                                                           Changes are available on the 
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire forum</a>.
-                                                         </p>",
-
-                                         "de" : "<p>Heute werden wir <strong>IPFire 2.7 RC3</strong> veröffentlichen. 
-                                                               <br />Diese Version ist ausschließlich zu Testzwecken geeignet und sollte nicht in Produktivumgebungen benutzt werden.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               Gefundene Fehler bitte im <a href=\"http://bugtracker.ipfire.org/\" target=\"_blank\">Bugtracker</a> melden. Vielen Dank!
-                                                               <br />
-                                                           Ã„nderungen gibt es hier im
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire Forum</a>.
-                                                         </p>"}},
-
-               "36" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 release candidate 4", "de" : "IPFire 2.7 release candidate 4" },
-                               "date"     : "2010-06-22",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>News for all our tester <strong>IPFire 2.7 RC4</strong> is released.
-                                                               <br />
-                                                               This version is only suitable for testing and should not be used in productive environments.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               If you have experienced any bugs please report them to the <a href=\"http://bugtracker.ipfire.org/\" 
-                                                               target=\"_blank\">bugtracker</a>. Thank you!
-                                                               <br />
-                                                           All changes are available on the 
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire forum</a>.
-                                                         </p>",
-
-                                         "de" : "<p>Neuigkeiten für alle unsere Tester <strong>IPFire 2.7 RC4</strong> ist da. 
-                                                               <br />Diese Version ist ausschließlich zu Testzwecken geeignet und sollte nicht in Produktivumgebungen benutzt werden.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               Gefundene Fehler bitte im <a href=\"http://bugtracker.ipfire.org/\" target=\"_blank\">Bugtracker</a> melden. Vielen Dank!
-                                                               <br />
-                                                           Alle Ã„nderungen gibt es hier im
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire Forum</a>.
-                                                         </p>"}},
-                                                         
-               "37" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 release candidate 5", "de" : "IPFire 2.7 release candidate 5" },
-                               "date"     : "2010-06-27",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Probably this will be the last release candidate, <strong>IPFire 2.7 RC5</strong> is avaiable.
-                                                               <br />
-                                                               This version is only suitable for testing and should not be used in productive environments.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               If you have experienced any bugs please report them to the <a href=\"http://bugtracker.ipfire.org/\" 
-                                                               target=\"_blank\">bugtracker</a>. Thank you!
-                                                               <br />
-                                                           Changes you will find at the 
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire forum</a>.
-                                                         </p>",
-
-                                         "de" : "<p>Wahrscheinlich wird dies der letzte Release Candidate, <strong>IPFire 2.7 RC5</strong> 
-                                                               steht absofort bereit. 
-                                                               <br />Diese Version ist ausschließlich zu Testzwecken geeignet und sollte nicht in Produktivumgebungen benutzt werden.
-                                                               <br />
-                                                               Download: <a href=\"http://people.ipfire.org/~arne_f/testing/\" target=\"_blank\">Mirror 1</a> | 
-                                                               <a href=\"http://testing.ipfire.at/\" target=\"_blank\">Mirror 2</a>
-                                                               <br />
-                                                               Gefundene Fehler bitte im <a href=\"http://bugtracker.ipfire.org/\" target=\"_blank\">Bugtracker</a> melden. Vielen Dank!
-                                                               <br />
-                                                           Ã„nderungen findet man im
-                                                           <a href=\"http://forum.ipfire.org/index.php?topic=2436.0\" target=\"_blank\">IPFire Forum</a>.
-                                                         </p>"}},
-                                                         
-               "38" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 final", "de" : "IPFire 2.7 final" },
-                               "date"     : "2010-07-02",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>It is time - today we are going to release <strong>IPFire 2.7</strong>.
-                                                               At first we will <strong>only</strong> release the ISO files, the update is not <strong>yet</strong> available via pakfire.<br />
-                                                               The reason for this is the change of the IPSec software from OpenSwan to StrongSwan and the mandatory changes in the configuration                                                              of net2net connections. The update on pakfire will be released next friday 2010-07-09, 
-                                                               so there is enough time to change the IPSec tunnels, more information about this is avaiable at the
-                                                               <a href=\"http://wiki.ipfire.org/en/configuration/services/ipsec_update\" target=\"_blank\">Wiki</a>.
-                                                               <br />There are about 400 changes in the new IPFire Version. All commits can be found at the 
-                                                               <a href=\"http://git.ipfire.org/?p=ipfire-2.x.git;a=log;h=refs/heads/core38\" target=\"_blank\">GIT (changelog)</a>.
-                                                               <br />
-                                                               Changes among others are:
-                                                               <ul>
-                                                                 <li>Updates</li>
-                                                                 <ul>
-                                                                   <li>Updated <strong>Kernel</strong> to stable lts (2.6.32.15)</li>
-                                                                   <li>Updated openssl to version 0.9.8o</li>
-                                                                   <li>Updated Net-SSLeay to version 1.36</li>
-                                                                   <li>Updated smartmontools to version 5.39.1</li>
-                                                                   <li>Updated usb-modeswitch to version 1.1.2</li>
-                                                                   <li>Updated alsa to version 1.0.23</li>
-                                                                   <li>Updated memtest to version 4.10</li>
-                                                                   <li>Updated v4l-dvb (2010-05-20)</li>
-                                                                   <li>Updated kvm-kmod to version 2.6.33.1</li>
-                                                                   <li>Updated compat-wireless to version 2.6.34</li>
-                                                                   <li>Updated hardware and GeoIP database</li>
-                                                                   <li>Updated squid to current stable version</li>
-                                                                   <li>Updated mISDN, mISDNuser (25.5.2010) and lcr to version 1.7</li>
-                                                                 </ul>
-                                                                 <li>VPN</li>
-                                                                 <ul>
-                                                                   <li>Switched IPSec from OpenSwan to StrongSwan version 4.4.0</li>
-                                                                   <li>Fixed vpn-watch hang at connection restart</li>
-                                                                   <li>Many other IPSec fixes</li>
-                                                                   <li>Updated OpenVPN to current stable version</li>
-                                                                   <li>New advanced settings for OpenVPN avaiable [bug #490]</li>
-                                                                   <li>Removed not working tap device</li>
-                                                                   <li>Load cryptodev modules by default</li>
-                                                                 </ul>
-                                                                 <li>Snort</li>
-                                                                 <ul>
-                                                                   <li>Updated snort to stable 2.8.6</li>
-                                                                   <li>Removed snort md5 check, added free space check</li>
-                                                                   <li>Fixed Snort init script, added sleep before chmod</li>
-                                                                   <li>Many snort config and script changes</li>
-                                                                   <li>Fixed detection of snort descriptions</li>
-                                                                   <li>Replaced snort gpl community rules by emergingthreats</li>
-                                                                   <li>Many Guardian fixes like ignore file handling and linefead detection</li>
-                                                                 </ul>
-                                                                 <li>Hardware</li>
-                                                                 <ul>
-                                                                   <li>Added support for alix2 leds</li>
-                                                                   <li>Added Vodafone K3765 and K4505 usbids to option driver</li>
-                                                                 </ul>
-                                                                 <li>Webinterface</li>
-                                                                 <ul>
-                                                                   <li>Cosmetic change for the swap and load graphs</li>
-                                                                   <li>Fixed some naming and length problems in the outgoing firewall</li>
-                                                                   <li>Fixed naming of firewall groups for webinterface</li>
-                                                                   <li>Added clearer description for P2P block</li>
-                                                                   <li>Added links for services on services.cgi [bug #617]</li>
-                                                                   <li>Added clearer button for stopping services</li>
-                                                                   <li>Added new iptables GUI</li>
-                                                                   <li>Fixed white page at first start of ids.cgi</li>
-                                                                   <li>Fixed update acclerator file download</li>
-                                                                 </ul>
-                                                                 <li>Firewall</li>
-                                                                 <ul>
-                                                                   <li>Added grouping option to the outgoing firewall - multiport and network group rules</li>
-                                                                   <li>Added space to logging entries by outgoing firewall</li>
-                                                                 </ul>
-                                                                 <li>Language</li>
-                                                                 <ul>
-                                                                   <li>Added spanish translation of installer and setup</li>
-                                                                   <li>Added spanish webif translation</li>
-                                                                 </ul>
-                                                                 <li>Others</li>
-                                                                       <ul>
-                                                                         <li>Added an config setting to remove netfilter sip modules</li>
-                                                                         <li>Syslog async logging feature</li>
-                                                                         <li>Resized /var/log/rrd in fstab</li>
-                                                                         <li>Changed size of the swapfile</li>
-                                                                         <li>Done a whole rework on the collectd config</li>
-                                                                         <li>misc-progs: Cleanup chain creation of wirelessctrl</li>
-                                                                         <li>Modified modules initscript to softly fail module loads</li>
-                                                                         <li>Added new led triggers: netdev</li>
-                                                                         <li>Added e2fsck.conf, this should fix manual superblock checls</li>
-                                                                         <li>Enabled force setting system time on boot</li>
-                                                                         <li>Fixed url filter repository for local redirects</li>
-                                                                         <li>Fixed squidclamav logging [bug #639]</li>
-                                                                         <li>Increase length of the password dialog to 50 chars</li>
-                                                                         <li>Added bootoption to skip an initskript</li>
-                                                                         <li>Blacklistet all framebuffer modules</li>
-                                                                         <li>Fixed rebuildhost [bug #509]</li>
-                                                                         <li>Allow also ip/netmask for blue access</li>
-                                                                         <li>Fixed grub installation on virtio hdd</li>
-                                                                         <li>Changed the flash serialcon image</li>
-                                                                       </ul>
-                                                               </ul>
-                                                         </p>",
-
-                                         "de" : "<p>Es ist so weit - heute werden wir <strong>IPFire 2.7</strong> freigeben.<br />
-                                                               Zuerst veröffentlichen wir <strong>nur</strong> die Installations-ISOs. Ãœber Pakfire ist das Update auf 2.7 <strong>noch</strong> nicht
-                                                               verfügbar.<br />
-                                                               Der Grund hierfür liegt an der Umstellung für IPSec von OpenSwan auf StrongSwan und die damit verbunden zwingenden Ã„nderungen
-                                                               bei laufenden Net2Net-Verbindungen. Das Update Ã¼ber Pakfire wird am Freitag, 09.07.2010 freigegeben. So bleibt genügend Zeit
-                                                               die IPSec Tunnel auf Strongwan anzupassen. Mehr Informationen hierüber findet man im 
-                                                               <a href=\"http://wiki.ipfire.org/de/configuration/services/ipsec_update\" target=\"_blank\">Wiki</a>.
-                                                               <br />Insgesammt sind an die 400 Ã„nderungen in die neue IPFire Version eingeflossen, alle Commits hierzu findet man im 
-                                                               <a href=\"http://git.ipfire.org/?p=ipfire-2.x.git;a=log;h=refs/heads/core38\" target=\"_blank\">GIT (Changelog)</a>.
-                                                               <br />
-                                                               Ã„nderungen sind unter anderem:
-                                                               <ul>
-                                                                 <li>Updates</li>
-                                                                 <ul>
-                                                                   <li>Update vom <strong>Kernel</strong> auf stabile Version mit LTS (2.6.32.15)</li>
-                                                                   <li>Update von openssl auf Version 0.9.8o</li>
-                                                                   <li>Update von Net-SSLeay auf Version 1.36</li>
-                                                                   <li>Update von smartmontools auf Version 5.39.1</li>
-                                                                   <li>Update von usb-modeswitch auf Version 1.1.2</li>
-                                                                   <li>Update von alsa auf Version 1.0.23</li>
-                                                                   <li>Update von memtest auf Version 4.10</li>
-                                                                   <li>Update von v4l-dvb (2010-05-20)</li>
-                                                                   <li>Update von kvm-kmod auf Version 2.6.33.1</li>
-                                                                   <li>Update von compat-wireless auf Version 2.6.34</li>
-                                                                   <li>Update von Hardware und GeoIP Datenbank</li>
-                                                                   <li>Update von squid auf die letzte stabile Version</li>
-                                                                   <li>Update von mISDN, mISDNuser (25.5.2010) und lcr auf Version 1.7</li>
-                                                                 </ul>
-                                                                 <li>VPN</li>
-                                                                 <ul>
-                                                                   <li>Änderung der IPSec Software von OpenSwan nach StrongSwan Version 4.4.0</li>
-                                                                   <li>'vpn-watch hing nach einem Verbindungsneustart' behoben</li>
-                                                                   <li>Viele kleine IPSec-Fehler behoben</li>
-                                                                   <li>Update von OpenVPN auf die letzte stabile Version</li>
-                                                                   <li>Neue Möglichkeiten unter 'Erweiterte Einstellungen' für OpenVPN [Bug #490]</li>
-                                                                   <li>Das nicht funktionierende tap-Device wurde entfernt</li>
-                                                                   <li>cryptodev-Module werden nun geladen</li>
-                                                                 </ul>
-                                                                 <li>Snort</li>
-                                                                 <ul>
-                                                                   <li>Update von Snort auf die stabile Version 2.8.6</li>
-                                                                   <li>MD5-Check wurde entfernt - Ãœberprüfung von freien Speicher wurde hinzugefügt</li>
-                                                                   <li>Fehler in init-Skript wurden behoben</li>
-                                                                   <li>Es wurden mehrere Snort Konfigurations- und Skriptanpassungen gemacht</li>
-                                                                   <li>Fehler in der Erkennung von Snort Beschreibungen wurde behoben</li>
-                                                                   <li>Austausch der Snort GPL-Community-Rule gegen Regeln von Emergingthreats</li>
-                                                                   <li>Mehere Guardian-Fehler wurden behoben</li>
-                                                                 </ul>
-                                                                 <li>Hardware</li>
-                                                                 <ul>
-                                                                   <li>Support für Alix2-LEDs wurde hinzugefügt</li>
-                                                                   <li>Treiber für Vodafone K3765 and K4505 wurden hinzugefügt</li>
-                                                                 </ul>
-                                                                 <li>Webinterface</li>
-                                                                 <ul>
-                                                                   <li>Kosmetische Ã„nderungen am Swap- und Load-Graph</li>
-                                                                   <li>Fehler bei der Bennenung und Längenproblem in der ausgehenden Firewall behoben</li>
-                                                                   <li>Fehler bei der Bennenung von Firewall-Gruppen behoben</li>
-                                                                   <li>Eine bessere Beschreibung der P2P-Optionen wurde hinzugefügt</li>
-                                                                   <li>Auf der services.cgi werden die Diensten nun mit entsprechenden Links dargestellt [Bug #617]</li>
-                                                                   <li>Verbesserter Button für das Stoppen von Diensten</li>
-                                                                   <li>Neue iptables-GUI</li>
-                                                                   <li>Der Fehler, dass die ids.cgi-Seite beim ersten laden leer blieb, wurde behoben</li>
-                                                                   <li>Update-Acclerator: Dateidownload Fehler wurde behoben</li>
-                                                                 </ul>
-                                                                 <li>Firewall</li>
-                                                                 <ul>
-                                                                   <li>Neue Gruppenoption wurde der ausgehenden Firewall hinzugefügt, für Multiport- und Netzwerk-Gruppen-Regeln</li>
-                                                                   <li>Ein Leerzeichen zur verbesserten Lesbarkeit der ausgehenden Firewall-Logs wurde hinzugefügt</li>
-                                                                 </ul>
-                                                                 <li>Sprachen</li>
-                                                                 <ul>
-                                                                   <li>Spanische Ãœbersetzung für Installer und Setupmenü wurde hinzugefügt</li>
-                                                                   <li>Spanische Ãœbersetzung des Webinterfaces hinzugefügt</li>
-                                                                 </ul>
-                                                                 <li>Sonstiges</li>
-                                                                       <ul>
-                                                                         <li>Eine Konfigurationoption wurde hinzugefügt um die Netfilter-SIP-Module zu deaktivieren</li>
-                                                                         <li>Syslog-async-Loggingfunktion hinzugefügt</li>
-                                                                         <li>Größe von /var/log/rrd in fstab geändert</li>
-                                                                         <li>Größe der Swapdatei geändert</li>
-                                                                         <li>Komplette Ãœberarbeitung der collectd-Konfiguration</li>
-                                                                         <li>Die Erstellung der Chains in der wirelessctrl wurde bereinigt</li>
-                                                                         <li>Neue LED-Trigger hinzugefügt: netdev</li>
-                                                                         <li>Eine e2fsck.conf wurde hinzugefügt um den Fehler der Superblock-Checks zu beheben</li>
-                                                                         <li>Fehler im squidclamav-Logging wurde behoben [Bug #639]</li>
-                                                                         <li>Erhöhung der Passwortlänge im Installer auf 50 Zeichen</li>
-                                                                         <li>Es wurde eine Bootoption hinzugefügt, welche bestimmte Init-Skripte Ã¼berspringen kann</li>
-                                                                         <li>Es wurden alle Frambuffer-Module der Blacklist hinzugefügt</li>
-                                                                         <li>Fehler im rebuildhost behoben [Bug #509]</li>
-                                                                         <li>Fehler in GRUB bei der Installation auf Virtio-Blockdevices wurde behoben</li>
-                                                                       </ul>
-                                                               </ul>
-                                                         </p>"}},
-                                                         
-           "39" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "IPFire 2.7 final via pakfire", "de" : "IPFire 2.7 final via pakfire" },
-                               "date"     : "2010-07-09",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>From today it will be possible to update old IPFire systems via the package manager 
-                                                               to the brand new version <strong>IPFire 2.7</strong>.
-                                                               <br />We want to point out again that the update to StrongSwan comes with 
-                                                               <a href=\"http://wiki.ipfire.org/en/configuration/services/ipsec_update\" target=\"_blank\">mandatory changes</a> 
-                                                               for existing IPSec tunnels with PSK.
-                                                               <br />
-                                                               <br />Because of to the kernel update, a reboot will be needed.<br />
-                                                         </p>",
-
-                                         "de" : "<p>Ab heute wird es nun auch möglich sein, Ã¼ber den Paketmanager alte IPFire-Systeme 
-                                                               auf die Version <strong>IPFire 2.7</strong> zu aktuallsieren. 
-                                                               <br />Wir wollen erneut darauf hinweisen, dass durch das Update auf StrongSwan 
-                                                               <a href=\"http://wiki.ipfire.org/de/configuration/services/ipsec_update\" target=\"_blank\">zwingend eine Ã„nderung</a> an den
-                                                               bestehenden IPSec-Tunneln mit PSK vorgenommen werden muss.
-                                                               <br />
-                                                               <br />Durch das Update des Kernels ist ein Neustart von IPFire nötig.<br/>
-                                                         </p>"}},
-                                                         
-           "40" : {"author"   : "Jan Paul Tuecking",
-                               "mail"     : "earl@ipfire.org",
-                               "subject"  : { "en" : "Core Update 39", "de" : "Core Update 39" },
-                               "date"     : "2010-08-01",
-                               "link"     : "",
-                               "content"  :
-                                       { "en" : "<p>Today we are going to release <strong>Core 39</strong>.<br />
-                                                               The most significant changes are the fixed download of snort rules and the update of the ppp daemon to version 2.4.5.<br />
-                                                               Despite of that it comes with the following changes:<br />
-                                                               <ul>
-                                                                       <li>Updated libpng to version 1.2.44 (critical vulnerability)</li>
-                                                                       <li>Fixed the creating of the backupiso</li>
-                                                                       <li>Fixed dhcp client helper script to handle multiple dns servers</li>
-                                                                       <li>Fixed blacklist update and cache cleanup of the Update-Accelerator/URL filter</li>
-                                                                       <li>Added missing terminfo file for screen</li>
-                                                                       <li>Enabled support for Cisco VPN clients</li>
-                                                               </ul>
-                                                               </p>",
-
-                                         "de" : "<p>Heute werden wir <strong>Core 39</strong> veröffentlichen.<br />
-                                                               Die wichtigsten Ã„nderungen sind insbesondere die Aktualisierung des Snort-Regeldownloads
-                                                               und des ppp-Daemon auf die Version 2.4.5.<br />
-                                                               Desweiteren bringt es folgende Ã„nderungen mit sich:<br />
-                                                               <ul>
-                                                                       <li>Update der libpng auf Version 1.2.44 (kritische Lücke)</li>
-                                                                       <li>Fehler beim Erstellen der Backup-ISO behoben</li>
-                                                                       <li>Fehler im DHCP Client-Helper-Script behoben (Multiple DNS-Server wurden falsch verarbeitet)</li>
-                                                                       <li>Fehler im Blacklistenupdate und Cachecleanup vom Update-Accelerator/URL-Filter behoben</li>
-                                                                       <li>Die fehlende terminfo für Screen wurde hinzugefügt</li>
-                                                                       <li>Support für Cisco VPN-Clients hinzugefügt</li>
-                                                               </ul>
-                                                       </p>"}}
-}
diff --git a/www/redirect.py b/www/redirect.py
deleted file mode 100644 (file)
index c8a1fc3..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/python
-
-import os
-import cgi
-
-from web.http import HTTPResponse
-
-for language in ("de", "en",):
-       if os.environ.has_key("HTTP_ACCEPT_LANGUAGE") and \
-                       os.environ["HTTP_ACCEPT_LANGUAGE"].startswith(language):
-               break
-
-site = cgi.FieldStorage().getfirst("site") or "index"
-
-sites = {      "ipfire.org"                    : "http://www.ipfire.org",
-            "admin.ipfire.org"         : "/%s/%s" % (language, site,),
-                       "www.ipfire.org"                : "/%s/%s" % (language, site,),
-                       "source.ipfire.org"             : "http://www.ipfire.org/%s/source" % language,
-                       "tracker.ipfire.org"    : "http://www.ipfire.org/%s/torrent" % language,
-                       "torrent.ipfire.org"    : "http://www.ipfire.org/%s/torrent" % language,
-                       "download.ipfire.org"   : "http://www.ipfire.org/%s/download" % language,
-                       "people.ipfire.org"             : "http://wiki.ipfire.org/%s/people/start" % language,
-                       "pxe.ipfire.org"                : "http://www.ipfire.org/%s/pxe" % language, }
-
-httpheader = []
-
-try:
-       httpheader.append(("Location", sites[os.environ["SERVER_NAME"]]))
-except KeyError:
-       httpheader.append(("Location", sites["www.ipfire.org"]))
-
-h = HTTPResponse(302, httpheader, None)
-h.execute()
diff --git a/www/releases.json b/www/releases.json
deleted file mode 100644 (file)
index b0e699e..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-[
-       { "name" : "IPFire 2.7 - Core 39",
-         "status"    : "stable",
-         "online"    : 1,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.7.1gb-ext2-scon.i586-full-core39.img.gz",
-                 "sha1" : "a567fc27f4d85ac7c18fa52fbfcf8539eb34bc85"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.7.1gb-ext2.i586-full-core39.img.gz",
-                 "sha1" : "14c746d895f2400ea7ffaff54a01630b55f62ba9"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.7.i586-full-core39.iso",
-                 "sha1" : "fff98747a60981f7f9db642ffd8984fa459ffd44"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.7.i586-full-core39.iso.torrent",
-                 "hash" : "915E1F25919C8CD9D651CB250CEC00362B41C580"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.7-install-usb-fdd.i586-full-core39.img.gz",
-                 "sha1" : "dbba94f216632105b3591f04feada0f4fa74edc8"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.7-install-usb-hdd.i586-full-core39.img.gz",
-                 "sha1" : "6c806d017336e4349938c3a0aa765142cb3fb184"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.7.xen.i586-full-core39.tar.bz2",
-                 "sha1" : "7eb2f700659be180f1f7195fd7abb1f5b61ede96"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 2.7 - Core 38",
-         "status"    : "stable",
-         "online"    : 1,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.7.1gb-ext2-scon.i586-full-core38.img.gz",
-                 "sha1" : "1b2a5795da9f7c7e8e276f5f9a3e77843ea612cb"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.7.1gb-ext2.i586-full-core38.img.gz",
-                 "sha1" : "db546eced1c9b07df9ce196c9642874371cb19df"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.7.i586-full-core38.iso",
-                 "sha1" : "9ad97d399824772b01aef20d2f9ca0bb39972f19"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.7.i586-full-core38.iso.torrent",
-                 "hash" : "9A1BEEEDB8ADC094683B5E268C201FDA43702379"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.7-install-usb-fdd.i586-full-core38.img.gz",
-                 "sha1" : "56a8685614eefce7c5cce1302cc99687f00afebc"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.7-install-usb-hdd.i586-full-core38.img.gz",
-                 "sha1" : "6f3dfa23e7fe7176fccf78907ccd3dd3a4a74a0b"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.7.xen.i586-full-core38.tar.bz2",
-                 "sha1" : "bdefe67bf1d06d59a8d61e6212836cccb8fecca6"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 2.5 - Core 37",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.5.1gb-ext2-scon.i586-full-core37.img.gz",
-                 "sha1" : "896fb84f0b058cb4272c66eedfd74d38"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.5.1gb-ext2.i586-full-core37.img.gz",
-                 "sha1" : "1c1bce1d7fdaf33162391566f3ed262b"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.5.i586-full-core37.iso",
-                 "sha1" : "8d8380be4434709c8901bf1e925b3442"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.5.i586-full-core37.iso.torrent",
-                 "hash" : "AFF48B9C13FBF74D5F65A8EC7C7E257DBA532A61"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.5-install-usb-fdd.i586-full-core37.img.gz",
-                 "sha1" : "10dfe27792d0f440a67342747d93d72a"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.5-install-usb-hdd.i586-full-core37.img.gz",
-                 "sha1" : "4b0f89c05fd64b0ac63cf2dc2f01a1d5"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.5.xen.i586-full-core37.tar.bz2",
-                 "sha1" : "3c75f1508ac2ed038178ef508a496e6d"
-               }
-         ]
-       },
-       
-       { "name" : "IPFire 2.5 - Core 36",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.5.1gb-ext2-scon.i586-full-core36.img.gz",
-                 "sha1" : "a85466380a93977eb5c738f234d52524"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.5.1gb-ext2.i586-full-core36.img.gz",
-                 "sha1" : "729478943e1d4e49f44a42ab9fe8b336"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.5.i586-full-core36.iso",
-                 "sha1" : "bd4f0dbd8be79e3caf1a94b4778dc69a"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.5.i586-full-core36.iso.torrent",
-                 "hash" : "64B24734C43144DFA2CED2DEF1A5FAAB996A42A1"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.5-install-usb-fdd.i586-full-core36.img.gz",
-                 "sha1" : "9837d8d7689034b26ef547b6dc6468e3"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.5-install-usb-hdd.i586-full-core36.img.gz",
-                 "sha1" : "c654443842d1f95cbae1e426111e668c"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.5.xen.i586-full-core36.tar.bz2",
-                 "sha1" : "ec333151180a84ffb66c674a9284097f"
-               }
-         ]
-       },
-       
-       { "name" : "IPFire 2.5 - Core 35",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.5.1gb-ext2-scon.i586-full-core35.img.gz",
-                 "sha1" : "658d588d2bd1cec21819989e4b3ff8593742b0df"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.5.1gb-ext2.i586-full-core35.img.gz",
-                 "sha1" : "633d6864df15a5487bdbf132eed683e4c3a58fd0"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.5.i586-full-core35.iso",
-                 "sha1" : "5bd50e7c39fad9e4e75ba62d369674610d427914"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.5.i586-full-core35.iso.torrent",
-                 "hash" : "50DC8E935D63ACAC02E5A3C21477EE59D7F1A06D"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.5-install-usb-fdd.i586-full-core35.img.gz",
-                 "sha1" : "edcedabea75117978fa4374004450d2fb3d8ae01"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.5-install-usb-hdd.i586-full-core35.img.gz",
-                 "sha1" : "4f24b0e6edcfab60af016495735a0bdba6007ada"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.5.xen.i586-full-core35.tar.bz2",
-                 "sha1" : "d777c02dd5b4109a4f6c6414093b41694c213822"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 2.5 - Core 34",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.5.1gb-ext2-scon.i586-full-core34.img.gz",
-                 "sha1" : "7f1fbea61842ff89197aede66da29a5e436543c3"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.5.1gb-ext2.i586-full-core34.img.gz",
-                 "sha1" : "26e60ee522b8a006f9c3e26e0077446d444cf27a"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.5.i586-full-core34.iso",
-                 "sha1" : "91ad4fb774bf14b4eb2e13c26caf066438b281aa"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.5.i586-full-core34.iso.torrent",
-                 "hash" : "50DC8E935D63ACAC02E5A3C21477EE59D7F1A06D"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.5-install-usb-fdd.i586-full-core34.img.gz",
-                 "sha1" : "a038d5af7e6e052f5df0199c729316e19ce202d1"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.5-install-usb-hdd.i586-full-core34.img.gz",
-                 "sha1" : "5a53677b757fa5515ecb686ac4b22739e11da7c1"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.5.xen.i586-full-core34.tar.bz2",
-                 "sha1" : "05f062b1c0499a0d949127f7d048dda959feb9ac"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 2.5 - Core 33",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "alix",
-                 "name" : "ipfire-2.5.1gb-ext2-scon.i586-full-core33.img.gz",
-                 "sha1" : "04e0634ce5e0445c77a8278e254ca7084f68e30a"
-               },
-               {
-                 "type" : "flash",
-                 "name" : "ipfire-2.5.1gb-ext2.i586-full-core33.img.gz",
-                 "sha1" : "1e3a8b0594fe92bb237bfabe10a8a10d687773f2"
-               },
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-2.5.i586-full-core33.iso",
-                 "sha1" : "c60c7bbe625044e96790c4ee3a30eaffb89aa379"
-               },
-               {
-                 "type" : "torrent",
-                 "name" : "ipfire-2.5.i586-full-core33.iso.torrent",
-                 "hash" : "D9992ED673EC9D92774452962F4445993ECA730E"
-               },
-               {
-                 "type" : "usbfdd",
-                 "name" : "ipfire-2.5-install-usb-fdd.i586-full-core33.img.gz",
-                 "sha1" : "5e8888afb035881f07ef318fc75dda7a3ab16840"
-               },
-               {
-                 "type" : "usbhdd",
-                 "name" : "ipfire-2.5-install-usb-hdd.i586-full-core33.img.gz",
-                 "sha1" : "6b1e702d5cf2f317ff52371ecd7e68828b56f262"
-               },
-               {
-                 "type" : "xen",
-                 "name" : "ipfire-2.5.xen.i586-full-core33.tar.bz2",
-                 "sha1" : "04e0634ce5e0445c77a8278e254ca7084f68e30a"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 3.0 alpha 1",
-         "status"    : "development",
-         "online"    : 1,
-         "files" : [
-               {
-                 "type" : "iso",
-                 "name" : "ipfire-3.0-alpha1.i686.iso",
-                 "sha1" : "e5d6c236a8997de8f1718f1c44f5a9d8f7d90af6"
-               }
-         ]
-       },
-
-       { "name" : "IPFire 1.4.9 - FINAL",
-         "status"    : "stable",
-         "online"    : 0,
-         "files" : [
-               {
-                 "type" : "iso",
-                 "name" : "IPFire-1.4.9-FINAL.iso",
-                 "sha1" : "ba40ca010f6c3069f110f6b7d99e3524bd81d367"
-               }
-         ]
-       }
-]
index c38b3326dcc9d85d7079d39160cbf3e4210a07b2..dc37c8a2ff11e3c76d4211f4ea05c3171e7ff3bc 100644 (file)
-/*
-
-       Nonzero1.0 by nodethirtythree design
-       http://www.nodethirtythree.com
-       missing in a maze
-
-*/
 
 /* This controls the width of the fluid width layouts */
 
-div.fluid
-{
-width: 90% !important;
+div.fluid {
+       width: 90% !important;
 }
 
 /* This controls the width of the fixed width layouts */
-
-div.fixed
-{
-width: 980px !important;
-margin: 0;
+div.fixed {
+       width: 980px !important;
+       margin: 0;
 }
 
 /* Basic Stuff */
 
-*
-{
-margin: 0em;
-padding: 0em;
+* {
+       margin: 0em;
+       padding: 0em;
 }
 
-body
-{
+body {
   font-family: "Verdana", "Deja-Vu Sans", "Bitstream Vera Sans", sans-serif;
   font-size: 0.9em;
-  background: #880400; /* url(../images/bg.png) repeat;*/
+  background: #880400;
   color: #585858;
 }
 
-h1,h2,h3,h4,h5,h6
-{
-font-weight: normal;
-letter-spacing: -1px;
-text-transform: lowercase;
+h1,h2,h3,h4,h5,h6 {
+       font-weight: normal;
+       letter-spacing: -1px;
+       text-transform: lowercase;
 }
 
-h3,h4,h5,h6
-{
-color: #66000F;
+h3,h4,h5,h6 {
+       color: #66000F;
 }
 
-h1 span
-{
-font-weight: bold;
+h1 span {
+       font-weight: bold;
 }
 
-h3 span
-{
-font-weight: bold;
+h3 span {
+       font-weight: bold;
 }
 
-h4 span
-{
-font-weight: bold;
+h4 span {
+       font-weight: bold;
 }
 
-br.clear
-{
-clear: both;
+br.clear {
+       clear: both;
 }
 
-img
-{
-padding: 3px;
+img {
+       padding: 3px;
 }
 
-img.floatTL
-{
-float: left;
-margin-right: 1.5em;
-margin-bottom: 1.5em;
-margin-top: 0.5em;
+img.floatTL {
+       float: left;
+       margin-right: 1.5em;
+       margin-bottom: 1.5em;
+       margin-top: 0.5em;
 }
 
-img.floatTR
-{
-float: right;
-margin-left: 1.5em;
-margin-bottom: 1.5em;
-margin-top: 0.5em;
+img.floatTR {
+       float: right;
+       margin-left: 1.5em;
+       margin-bottom: 1.5em;
+       margin-top: 0.5em;
 }
 
-img.symbol
-{
-float: left;
-margin-bottom: 0em;
-border: 0;
+img.symbol {
+       float: left;
+       margin-bottom: 0em;
+       border: 0;
 }
 
-a
-{
-text-decoration: underline;
-color: #D90000;
-border-style: none;
+a {
+       text-decoration: underline;
+       color: #D90000;
+       border-style: none;
 }
 
-a:hover
-{
-text-decoration: none;
+a:hover {
+       text-decoration: none;
 }
 
-ul.links
-{
-  /* list-style: none; */
-  padding-left: 1em;
+ul.links {
+       /* list-style: none; */
+       padding-left: 1em;
 }
 
-ul.links li
-{
-line-height: 1.5em;
-font-size: 0.9em;
-/* display: inline; */
+ul.links li {
+       line-height: 1.5em;
+       font-size: 0.9em;
+       /* display: inline; */
 }
 
-ul.links li.first
-{
+ul.links li.first {
 }
 
-p
-{
-line-height: 1.5em;
+p {
+       line-height: 1.5em;
+}
+
+p.left {
+       float: left;
 }
 
 p.right {
@@ -137,290 +113,187 @@ p.right {
 
 /* Header */
 
-#header
-{
-width:100%;
-height:102px;
-background: url('../images/bg-menu99.png') repeat-x;
+#header {
+       width:100%;
+       height:102px;
+       background: url('../images/bg-menu99.png') repeat-x;
 }
 
-#header_inner
-{
-position: relative;
-width: 980px;
-height:102px;
-margin: 0 auto;
+#header_inner {
+       position: relative;
+       width: 980px;
+       height:102px;
+       margin: 0 auto;
 }
 
-img.symbol
-{
-margin: 0;
-padding: 0;
+img.symbol {
+       margin: 0;
+       padding: 0;
 }
 
 /* Logo */
 
-#logo
-{
-position: absolute;
-top: 0;
-float: left;
+#logo {
+       position: absolute;
+       top: 0;
+       float: left;
 }
 
-#header h1
-{
-float: left;
-margin-left: 105px;
-color: #eee;
-font-size: 150%;
+#header h1 {
+       float: left;
+       margin-left: 105px;
+       color: #eee;
+       font-size: 150%;
 }
 
-#header h2
-{
-float: left;
-margin-left: 105px;
-color: #E5CCD0;
-font-size: 1.0em;
-vertical-align: bottom;
+#header h2 {
+       float: left;
+       margin-left: 105px;
+       color: #E5CCD0;
+       font-size: 1.0em;
+       vertical-align: bottom;
 }
 
 /* Header Line's */
-#line1
-{
-height: 37px;
+#header_menu {
+       height: 37px;
 }
 
-#line2
-{
-height: 32px;
-}
-
-#line3
-{
-height: 31px;
-}
-
-/* Lang */
-
-#lang
-{
-position: absolute;
-right: 0em;
-top: 0em;
+#header_hostname {
+       height: 32px;
 }
 
-#lang img
-{
-float: left;
-border: none;
-margin-right: 0.5em;
-margin-bottom: 1.5em;
-margin-top: 0.5em;
-width: 30px;
+#header_slogan {
+       height: 31px;
 }
 
 /* Menu */
 
-#menu
-{
-position: absolute;
-left: 105px;
-top: 0em;
-}
-
-#menu ul
-{
-list-style: none;
-}
-
-#menu li
-{
-float: left;
-}
-#menu li
-{
-vertical-align: middle;
-background: #333 url('../images/btn-break.png') center;
-}
-#menu li a
-{
-margin-left: 1px; /*0.5em;*/
-margin-right: 1px;
-display: block;
-padding: 10px 5px 0 8px;
-height: 26px;
-background: #333 url('../images/btn-empty.png') repeat-x center;
-color: #ddd;
-font-weight: bolder;
-vertical-align: middle;
-font-size: 0.8em;
-text-decoration: none;
-}
-
-#menu li a.active
-{
-background: #CA2F2F url('../images/btn-red2.png') repeat-x center;
-color: #ddd;
-}
-
-#menu li a:hover
-{
-background: #333 url('../images/btn-red2.png') repeat-x center;
-color: #fff;
-}
-
-/* Main */
-
-#main
-{
-/* background: #fff url('../images/n2.gif') 0px 1px repeat-x; */
-}
-
-#main_inner p
-{
-text-align: justify;
-margin-bottom: 0.5em;
-font-size: 0.9em;
+#menu {
+       position: absolute;
+       left: 105px;
+       top: 0em;
 }
 
-#main_inner .post a
-{
-font-size: 0.9em;
+#menu ul {
+       list-style: none;
 }
 
-#main_inner ul
-{
-margin-bottom: 2.0em;
-font-size: 0.9em;
+#menu li {
+       float: left;
 }
 
-#main_inner
-{
-position: relative;
-width: 950px;
-margin: 0 auto;
+#menu li {
+       vertical-align: middle;
+       background: #333 url('../images/btn-break.png') center;
 }
 
-#main_inner h3,h4
-{
-border-bottom: dotted 1px #E1E1E1;
-position: relative;
+#menu li a {
+       margin-left: 1px; /*0.5em;*/
+       margin-right: 1px;
+       display: block;
+       padding: 10px 5px 0 8px;
+       height: 26px;
+       background: #333 url('../images/btn-empty.png') repeat-x center;
+       color: #ddd;
+       font-weight: bolder;
+       vertical-align: middle;
+       font-size: 0.8em;
+       text-decoration: none;
 }
 
-#main_inner h3
-{
-font-size: 2.1em;
-padding-bottom: 0.1em;
-margin-bottom: 0.5em;
+#menu li a.active {
+       background: #CA2F2F url('../images/btn-red2.png') repeat-x center;
+       color: #ddd;
 }
 
-#main_inner h4
-{
-font-size: 1.1em;
-padding-bottom: 0.175em;
-margin-bottom: 0.25em;
-margin-top: 0.95em;
+#menu li a:hover {
+       background: #333 url('../images/btn-red2.png') repeat-x center;
+       color: #fff;
 }
 
-#main_inner .post
-{
-position: relative;
-margin-bottom: 2.0em;
-}
+/* Main */
 
-#main_inner .post h3
-{
-position: relative;
-font-size: 1.4em;
-padding-bottom: 0.25em;
+#main_inner p {
+       text-align: justify;
+       margin-bottom: 0.5em;
 }
 
-#main_inner .post ul.post_info , #main_inner .post .post_info
-{
-list-style: none;
-position: absolute;
-top: 3em;
-font-size: 0.7em;
+#main_inner ul {
+       padding-left: 1.5em;
 }
 
-#main_inner .post .post_info
-{
-width: 100%;
-text-align: right;
-margin-top: -1.5em;
+#main_inner {
+       position: relative;
+       width: 950px;
+       margin: 0 auto;
 }
 
-#main_inner .post ul
-{
-padding-left: 1.5em; 
+#main_inner h3,h4 {
+       border-bottom: dotted 1px #E1E1E1;
+       position: relative;
 }
 
-#main_inner .post ul a
-{
-font-size: 1.0em;
+#main_inner h3 {
+       font-size: 1.5em;
+       padding-bottom: 0.1em;
+       margin-bottom: 0.5em;
 }
 
-#main_inner .post ul.post_info li
-{
-background-position: 0em center;
-background-repeat: no-repeat;
-display: inline;
-padding-left: 15px;
+#main_inner h4 {
+       font-size: 1.2em;
+       padding-bottom: 0.175em;
+       margin-bottom: 0.25em;
+       margin-top: 0.95em;
 }
 
-#main_inner .post ul.post_info li.date
-{
-background-image: url('../images/n5.gif');
+#main_frame {
+       border: 0px;
+       border-collapse: collapse;
+       border-spacing: 0px;
+       margin: 0em;
+       padding: 0em;
+       width: 980px;
+       max-width: 980px;
 }
 
-#main_inner .post ul.post_info li.comments
-{
-background-image: url('../images/n6.gif');
-margin-left: 1.1em;
-}
-
-table {
-  border: 0px;
-  border-collapse: collapse;
-  border-spacing: 0px;
-  margin: 0em;
-  padding: 0em;
-  width: 980px;
-  max-width: 980px;
-}
-.post table {
-  width: 900px;
-  max-width: 900px;
-  font-size: 0.9em;
-}
 #sh-tl {
-  background: url("../images/sh-tl.png") no-repeat right bottom;
+       background: url("../images/sh-tl.png") no-repeat right bottom;
 }
+
 #sh-top {
-  background: url("../images/sh-top.png") repeat-x bottom;
+       background: url("../images/sh-top.png") repeat-x bottom;
 }
+
 #sh-tr {
-  background: url("../images/sh-tr.png") no-repeat left bottom;
+       background: url("../images/sh-tr.png") no-repeat left bottom;
 }
+
 #sh-lft {
-  background: url("../images/sh-lft.png") repeat-y right;
+       background: url("../images/sh-lft.png") repeat-y right;
 }
+
 #sh-rgt {
-  background: url("../images/sh-rgt.png") repeat-y left;
+       background: url("../images/sh-rgt.png") repeat-y left;
 }
+
 #sh-bl {
-  background: url("../images/sh-bl.png") no-repeat right top;
+       background: url("../images/sh-bl.png") no-repeat right top;
 }
+
 #sh-btn {
-  background: url("../images/sh-btn.png") repeat-x top;
+       background: url("../images/sh-btn.png") repeat-x top;
 }
+
 #sh-br {
-  background: url("../images/sh-br.png") no-repeat left top;
+       background: url("../images/sh-br.png") no-repeat left top;
 }
+
 #no-sh {
-  background-color: #f5f5f5;
-  width: 980px;
+       background-color: #f9f9f9;
+       width: 980px;
 }
+
 #sh-tl, #sh-top, #sh-tr, #sh-lft, #sh-rgt, #sh-bl, #sh-btn, #sh-br {
   width: 16px;
   height: 16px;
@@ -428,300 +301,143 @@ table {
   border-width: 0px;
   border-style: none;
 }
+
 .banner {
   border: 0;
 }
 
-/* Cluster */
-
-#nodes {
-  width: 100%;
-  font-size: 0.9em;
-}
-#nodes th, #nodes td {
-  text-align: left;
-}
-#nodes th.hostname {
-  width: 40%;
-}
-#nodes th.arch {
-  width: 8%;
-}
-#nodes th.jobs {
-  width: 24%;
-}
-#nodes th.speed {
-  width: 10%;
+.post {
+       margin-bottom: 40px;
 }
-#nodes th.load {
-  width: 18%;
-}
-
-/* Builds */
 
-#builds {
-  width: 100%;
-  font-size: 0.9em;
-}
-#builds img {
-       border: 0;
-}
-#builds td {
-  text-align: left;
+.post .title {
+       text-transform: lowercase;
+       font-size: 1.5em;
+       font-weight: normal;
+       color: #606060;
 }
-#builds tr.headline td {
-       text-align: center;
-       font-weight: bold;
-}
-#builds td.packages {
-       text-align: right;
-}
-
-/* Footer */
 
-#footer
-{
-clear: both;
-height: 26px;
-color: #ddd;
-text-align: center;
-background: url("../images/ft.png") left top;
-margin-top: 0em;
-margin-bottom: 0em;
-padding-top: 0.5em;
-padding-bottom: 0.5em;
-text-transform: lowercase;
+.post .title a {
+       border: none;
+       color: #606060;
 }
 
-/* Search */
-
-input.button
-{
-background: #CA2F2F url("../images/n3.gif") repeat-x;
-color: #fff;
-border: solid 1px #A94B4B;
-font-weight: bold;
-text-transform: lowercase;
-font-size: 0.8em;
-height: 2.0em;
+.post .meta {
+       padding-left: 2px;
+       padding-bottom: 2px;
+       text-align: left;
+       text-transform: lowercase;
 }
 
-input.text
-{
-border: solid 1px #F1F1F1;
-font-size: 1.0em;
-padding: 0.25em 0.25em 0.25em 0.25em;
+.post .meta a {
 }
 
-#search
-{
-position: relative;
-width: 100%;
-margin-bottom: 2.0em;
+.post .entry {
+       padding: 1.2em 1.2em;
+       border: 1px dotted #E4E4E4;
+       text-align: justify;
 }
 
-#search input.text
-{
-position: absolute;
-top: 0em;
-left: 0em;
-width: 9.5em;
+.links {
+       padding-top: 20px;
+       text-transform: lowercase;
 }
 
-#search input.button
-{
-position: absolute;
-top: 0em;
-right: 0em;
-min-width: 2.0em;
-max-width: 2.5em;
+/* News */
+
+ul.news {
+       list-style: none;
 }
 
-.thumbnail{
-position: relative;
-z-index: 0;
+/* Footer */
+
+#footer {
+       clear: both;
+       height: 26px;
+       color: #ddd;
+       text-align: center;
+       background: url("../images/ft.png") left top;
+       margin-top: 0em;
+       margin-bottom: 0em;
+       padding-top: 0.5em;
+       padding-bottom: 0.5em;
+       text-transform: lowercase;
 }
 
-.thumbnail:hover{
-background-color: transparent;
-z-index: 50;
+.thumbnail {
+       position: relative;
+       z-index: 0;
 }
 
-.thumbnail span{ /*CSS for enlarged image*/
-position: absolute;
-background-color: #ffffe0; /*lightyellow;*/
-padding: 5px;
-left: -1000px;
-border: 1px dashed gray;
-visibility: hidden;
-color: black;
-text-decoration: none;
+.thumbnail:hover {
+       background-color: transparent;
+       z-index: 50;
 }
 
-.thumbnail span img{ /*CSS for enlarged image*/
-border-width: 0;
-padding: 2px;
+.thumbnail span {
+       position: absolute;
+       background-color: #ffffe0;
+       padding: 5px;
+       left: -1000px;
+       border: 1px dashed gray;
+       visibility: hidden;
+       color: black;
+       text-decoration: none;
 }
 
-.thumbnail:hover span{ /*CSS for enlarged image on hover*/
-visibility: visible;
-top: 0;
-left: 60px; /*position where enlarged image should offset horizontally */
+.thumbnail span img {
+       border-width: 0;
+       padding: 2px;
+}
 
+.thumbnail:hover span {
+       visibility: visible;
+       top: 0;
+       left: 60px;
 }
 
-.feed {
+a.feed {
   margin-left: 3px;
   padding: 0 0 0 19px;
   background: url("../images/feed.png") no-repeat 0 50%;
 }
 
-/* LAYOUT - 3 COLUMNS */
-
-       /* Primary content */
-       
-       #primaryContent_3columns
-       {
-       position: relative;
-       margin-right: 34em;
-       }
-       
-       #columnA_3columns
-       {
-       position: relative;
-       float: left;
-       width: 100%;
-       margin-right: -34em;
-       padding-right: 2em;
-       }
-       
-       /* Secondary Content */
-       
-       #secondaryContent_3columns
-       {
-       float: right;
-       }
-       
-       #columnB_3columns
-       {
-       width: 13.0em;
-       float: left;
-       padding: 0em 2em 0.5em 2em;
-       border-left: dotted 1px #E1E1E1;
-       }
-       
-       #columnC_3columns
-       {
-       width: 13.0em;
-       float: left;
-       padding: 0em 0em 0.5em 2em;
-       border-left: dotted 1px #E1E1E1;
-       }
-       
 /* LAYOUT - 2 COLUMNS */
 
        /* Primary content */
-       
-       #primaryContent_2columns
-       {
+
+#columns2_primary {    
        position: relative;
        /* margin-right: 17em; */
-       }
-       
-       #columnA_2columns
-       {
+}
+
+#columns2_columnA {
        position: relative;
        float: left;
        padding: 1em 0.5em 0.5em 1em;
        width: 710px;
-       }
-       
-       /* Secondary Content */
-       
-       #secondaryContent_2columns
-       {
+}
+
+#columns2_secondary {  
        float: right;
-       }
+}
        
-       #columnC_2columns
-       {
+#columns2_columnB
+{
        width: 12.0em;
        float: left;
        padding: 0em 1em 0.5em 1em;
        border-left: dotted 1px #E1E1E1;
-       }
-
-/* LAYOUT - COLUMNLESS */
+}
 
-       /* Primary content */
-       
-       #primaryContent_columnless
-       {
+#columns1_primary {
        position: relative;
-       }
+}
        
-       #columnA_columnless
-       {
+#columns1_columnA {
        position: relative;
-       width: 100%;
-       }
-
-
-/* URIEL */
-table.uriel {
-       width: 100%;
-       /* border: 1px solid #880400; */
-       font-size: 0.9em;
-       
-}
-table.uriel td.header {
-       width: 80px;    
-}
-table.uriel td {
-       padding: 4px;
-}
-table.uriel td.item {
-       /* font-weight: bold; */
-}
-table.uriel td.value {
-       text-align: right;
-}
-table.uriel td.footer {
-       text-align: right;
-       background-color: #dedede;
-       border: 1px solid #999;
-}
-
-/* TRANSLATION */
-table.translate {
-       width: 100%;
-       /* border: 1px solid #880400; */
-       font-size: 0.9em;
-}
-
-table.translate td {
-       padding: 4px;
-       text-align: center;
-}
-
-table.translate td.lang {
-       text-align: left;
-}
-
-/* pakfire3 */
-#repos .leaf {
-       border: 1px solid #000;
-       padding: 5px;
-       margin-top: 5px;
-}
-
-#repos .leaf p.header {
-       background-color: #E1E1E1;
-       padding-left: 5px;
-}
-
-#repos .leaf p.footer {
-       text-align: right;
+       padding: 1em 1em 0.5em 1em;
+       border: 1px solid white;
 }
 
 /* Tabs */
@@ -1060,11 +776,15 @@ table.download-torrents tr {
        height: 24px;
 }
 
-table.download-torrents td {
-       text-align: center;
+table.download-torrents th {
+       text-align: left;
 }
 
-table.download-torrents th.seeds, th.peers, td.seeds, td.peers {
+table.download-torrents th.peers, td.peers {
+       text-align: right;
+}
+
+table.download-torrents th.seeds, td.seeds {
        text-align: right;
 }
 
@@ -1072,6 +792,23 @@ table.download-torrents th.peers,td.peers {
        padding-left: 5px;
 }
 
+table.tracker-peerlist {
+       width: 85%;
+}
+
+table.tracker-peerlist th {
+       text-align: left;
+}
+
+table.download-torrents td {
+       padding: 1em;
+}
+
+table.tracker-peerlist td.ip {
+       text-align: right;
+       padding-right: 1em;
+}
+
 table.download-mirrors {
        margin-bottom: 25px;
        margin-left: 15px;
@@ -1118,3 +855,63 @@ ul.sources li {
        font-family: courier;
        list-style-type: none;
 }
+
+table.mirrors {
+       /* border: 1px solid #606060; */
+}
+
+table.mirrors tr {
+       line-height: 2em;
+}
+
+table.mirrors td {
+       padding-left: 0.5em;
+       padding-right: 0.5em;
+}
+
+table.mirrors td.hostname {
+       text-align: right;
+       padding-left: 2em;
+}
+
+table.mirrors td.down {
+       border: 1px solid #ff8888;
+       background-color: #ff9999;
+}
+
+table.mirrors td.outofsync {
+       border: 1px solid #ffffaa;
+       background-color: #ffff99;
+}
+
+table.mirrors td.up {
+       border: 1px solid #88ff88;
+       background-color: #aaffaa;
+}
+
+table.blocks {
+       width: 100%;
+       border: 1px solid #E4E4E4;
+}
+
+table.blocks td.block {
+       width: 33%;
+       padding: 1.5em;
+}
+
+table.blocks td.block p {
+       text-align: justify;
+}
+
+table.blocks td.block span {
+       text-align: center;
+       font-size: 1.2em;
+}
+
+table.blocks td.block1,td.block3 {
+       background-color: white;
+}
+
+table.blocks td.block2 {
+       background-color: #f5f5f5;
+}
diff --git a/www/static/images/banners/hetzner_hosted_by_1.jpg b/www/static/images/banners/hetzner_hosted_by_1.jpg
new file mode 100644 (file)
index 0000000..a796748
Binary files /dev/null and b/www/static/images/banners/hetzner_hosted_by_1.jpg differ
diff --git a/www/static/images/flags/unknown.png b/www/static/images/flags/unknown.png
new file mode 120000 (symlink)
index 0000000..d74f919
--- /dev/null
@@ -0,0 +1 @@
+europeanunion.png
\ No newline at end of file
index cd3224b215bc83a82041034711bbcf801a4a8b5f..ab8fe3a57bf13fabc2a8c847fc41343f640868f2 100644 (file)
@@ -8,12 +8,20 @@
                        <a href="/accounts/create">{{ _("Create new account") }}</a>
                </p>
 
-               <ul>
+               <table>
+                       <tr>
+                               <th>{{ _("Name (Nickname)") }}</th>
+                               <th>&nbsp;</th>
+                       </tr>
                        {% for account in accounts %}
-                               <li>
-                                       <a href="/accounts/edit/{{ account.id }}">{{ account.realname }} ({{ account.name }})</a>
-                               </li>
+                               <tr>
+                                       <td>{{ account.cn }} ({{ account.uid }})</td>
+                                       <td>
+                                               <a href="/accounts/edit/{{ account.uid }}">{{ _("Edit") }}</a>
+                                               <a href="/accounts/delete/{{ account.uid }}">{{ _("Delete") }}</a>
+                                       </td>
+                               </tr>
                        {% end %}
-               </ul>
+               </table>
        </div>
 {% end %}
index 5303fca88493222fc76666977200ee609201dab5..de3b12a347dfc85ab114cadc6c27bc157472c74c 100644 (file)
@@ -1,14 +1,13 @@
-{% extends "base.html" %}
+{% extends "base-2.html" %}
  
 {% block title %}{{ _("IPFire Admin Area") }}{% end %}
 
-{% block languages %}{% end %}
-
 {% block sidebar %}
        <h4>{{ _("Options") }}</h4>
 
        <ul>
                <li><a href="/accounts">{{ _("Accounts") }}</a></li>
+               <li><a href="/mirrors">{{ _("Mirrors") }}</a></li>
                <li><a href="/planet">{{ _("Planet") }}</a></li>
        </ul>
 {% end %}
diff --git a/www/templates/admin-login.html b/www/templates/admin-login.html
new file mode 100644 (file)
index 0000000..581a799
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends "base-1.html" %}
+
+{% block title %}{{ _("Please login") }}{% end block %}
+
+{% block content %}
+       <!-- XXX needs styling -->
+       <form name="login" method="POST">
+               <input type="text" name="name" />
+               <input type="password" name="password" />
+               <input type="submit" value="{{ _("Log in") }}" />
+               {{ xsrf_form_html() }}
+       </form>
+{% end block %}
diff --git a/www/templates/admin-mirrors-create.html b/www/templates/admin-mirrors-create.html
new file mode 100644 (file)
index 0000000..2b9b460
--- /dev/null
@@ -0,0 +1,71 @@
+{% extends "admin-base.html" %}
+
+{% block content %}
+       <div class="post">
+               <h3>{{ _("Create new mirror") }}</h3>
+               <form name="mirror" method="post">
+                       {{ xsrf_form_html() }}
+                       <input type="hidden" name="id" value="{{ mirror.id }}">
+
+                       <table>
+                               <tr>
+                                       <td>{{ _("Hostname") }}</td>
+                                       <td><input type="text" name="hostname" value="{{ mirror.hostname }}" /></td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Path") }}</td>
+                                       <td><input type="text" name="path" value="{{ mirror.path }}" /></td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Owner") }}</td>
+                                       <td><input type="text" name="owner" value="{{ mirror.owner }}" /></td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Location") }}</td>
+                                       <td><input type="text" name="location" value="{{ mirror.location }}" /></td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("File mirror") }}</td>
+                                       <td>
+                                               <select name="releases">
+                                                       <option value="Y" {% if mirror.releases == "Y" %}selected{% end %}>{{ _("yes") }}</option>
+                                                       <option value="N" {% if mirror.releases == "N" %}selected{% end %}>{{ _("no") }}</option>
+                                               </select>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Pakfire 2 mirror") }}</td>
+                                       <td>
+                                               <select name="pakfire2">
+                                                       <option value="Y" {% if mirror.pakfire2 == "Y" %}selected{% end %}>{{ _("yes") }}</option>
+                                                       <option value="N" {% if mirror.pakfire2 == "N" %}selected{% end %}>{{ _("no") }}</option>
+                                               </select>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Pakfire 3 mirror") }}</td>
+                                       <td>
+                                               <select name="pakfire3">
+                                                       <option value="Y" {% if mirror.pakfire3 == "Y" %}selected{% end %}>{{ _("yes") }}</option>
+                                                       <option value="N" {% if mirror.pakfire3 == "N" %}selected{% end %}>{{ _("no") }}</option>
+                                               </select>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <td>{{ _("Disabled?") }}</td>
+                                       <td>
+                                               <select name="disabled">
+                                                       <option value="Y" {% if mirror.disabled == "Y" %}selected{% end %}>{{ _("yes") }}</option>
+                                                       <option value="N" {% if mirror.disabled == "N" %}selected{% end %}>{{ _("no") }}</option>
+                                               </select>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <td colspan="2">
+                                               <input type="submit" value="{{ _("Save") }}" />
+                                       </td>
+                               </tr>
+                       </table>
+               </form>
+       </div>
+{% end block %}
diff --git a/www/templates/admin-mirrors-details.html b/www/templates/admin-mirrors-details.html
new file mode 100644 (file)
index 0000000..d7b544a
--- /dev/null
@@ -0,0 +1,61 @@
+{% extends "admin-base.html" %}
+
+{% block content %}
+       <div class="post">
+               <h3>{{ mirror.hostname }} - {{ _("Details") }}</h3>
+               
+               <p>
+                       <a href="/mirrors">{{ _("All mirrors") }}</a>
+               </p>
+               
+               <table>
+                       <tr>
+                               <td>{{ _("Hostname") }}</td>
+                               <td><a href="{{ mirror.url }}" target="_blank">{{ mirror.url }}</a></td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Status") }}</td>
+                               <td>{{ mirror.state }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Owner") }}</td>
+                               <td>{{ mirror.owner }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Location") }}</td>
+                               <td>{{ mirror.location }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("GeoIP Location") }}</td>
+                               <td>{{ mirror.country_code }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("File mirror") }}</td>
+                               <td>{{ mirror.releases }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Pakfire 2 mirror") }}</td>
+                               <td>{{ mirror.pakfire2 }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Pakfire 3 mirror") }}</td>
+                               <td>{{ mirror.pakfire3 }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Disabled?") }}</td>
+                               <td>{{ mirror.disabled }}</td>
+                       </tr>
+               </table>
+
+               {% if mirror.releases == "Y" %}
+                       <h4>{{ _("Filelist") }}</h4>
+                       <table class="filelist">
+                               {% for file in mirror.filelist %}
+                                       <tr>
+                                               <td>{{ file }}</td>
+                                       </tr>
+                               {% end %}
+                       </table>
+               {% end %}
+       </div>
+{% end block %}
diff --git a/www/templates/admin-mirrors.html b/www/templates/admin-mirrors.html
new file mode 100644 (file)
index 0000000..7220df0
--- /dev/null
@@ -0,0 +1,31 @@
+{% extends "admin-base.html" %}
+
+{% block content %}
+       <div id="post">
+               <h3>{{ _("Mirror Administrator") }}</h3>
+               
+               <p>
+                       <a href="/mirrors/create">{{ _("Create new mirror") }}</a> |
+                       <a href="/mirrors/update">{{ _("Re-check now") }}</a>
+               </p>
+
+               <table>
+                       <tr>
+                               <th>{{ _("Hostname") }}</th>
+                               <th>{{ _("Last update") }}</th>
+                               <th>&nbsp;</th>
+                       </tr>
+                       {% for mirror in mirrors %}
+                               <tr class="mirror-{{ mirror.state.lower() }}">
+                                       <td>{{ mirror.hostname }}</td>
+                                       <td>{{ locale.format_date(mirror.last_update) }}</td>
+                                       <td>
+                                               <a href="/mirrors/details/{{ mirror.id }}">{{ _("Details") }}</a>
+                                               <a href="/mirrors/edit/{{ mirror.id }}">{{ _("Edit") }}</a>
+                                               <a href="/mirrors/delete/{{ mirror.id }}">{{ _("Delete") }}</a>
+                                       </td>
+                               </tr>
+                       {% end %}
+               </table>
+       </div>
+{% end %}
index 4999bcd28334b596521171eea83860a72e4b3598..ee9295057a0a4e7d1ca83254ab8854f4a91db867 100644 (file)
@@ -16,7 +16,7 @@
                                <tr>
                                        <td>&nbsp;</td>
                                        <td>
-                                               <textarea id="text" name="text" rows="20" cols="80">{{ entry.text }}</textarea>
+                                               <textarea id="markdown" name="markdown" rows="20" cols="80">{{ entry.markdown }}</textarea>
                                        </td>
                                </tr>
                                <tr>
@@ -35,7 +35,7 @@
 {% block javascript %}
        <script type="text/javascript">
                preview = function() {
-                       $.get("/api/planet/render", { text : $("#text").val() },
+                       $.get("/api/planet/render", { text : $("#markdown").val() },
                                function(data) {
                                        $("#preview").html(data);
                                }
index d26caa4a850923c4fbba5a724c342c6d82080fad..6a225d3398c6a0300ed5051d7f15d9aa2f59c154 100644 (file)
@@ -16,7 +16,7 @@
                        </tr>
                        {% for entry in entries %}
                                <tr>
-                                       <td>{{ entry.author.realname }}</td>
+                                       <td>{{ entry.author.cn }}</td>
                                        <td><a href="http://planet.ipfire.org/post/{{ entry.slug }}" target="_blank">{{ entry.title }}</a></td>
                                        <td><a href="/planet/edit/{{ entry.id }}">{{ _("Edit") }}</a></td>
                                </tr>
diff --git a/www/templates/base-1.html b/www/templates/base-1.html
new file mode 100644 (file)
index 0000000..f3ad838
--- /dev/null
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+
+{% block columns %}
+       <div id="columns1_primary">
+               <div id="columns1_columnA">
+                       {% block content %}{% end block %}
+                       <br class="clear" />
+               </div>
+       </div>
+{% end block %}
diff --git a/www/templates/base-2.html b/www/templates/base-2.html
new file mode 100644 (file)
index 0000000..5fdf682
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+
+{% block columns %}
+       <div id="columns2_primary">
+               <div id="columns2_columnA">
+                       {% block content %}{% end block %}
+                       <br class="clear" />
+               </div>
+       </div>
+       <div id="columns2_secondary">
+               <div id="columns2_columnB">
+                       {% block sidebar %}
+                               {{ modules.SidebarBanner() }}
+                       {% end block %}
+                       <br class="clear" />
+               </div>
+       </div>
+{% end block %}
index 9996822f435442581148939f29e3d2db5ea76357..bd8d225d7e91f04825de45c3d57dcfacbcb196fe 100644 (file)
@@ -1,54 +1,55 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
        <head>
-               <meta http-equiv="content-type" content="text/html; charset=utf-8" />
-               <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
-               <title>{{ hostname }} - {% block title %}{{ title }}{% end block %}</title>
+               <title>{{ hostname }} - {% block title %}{% end block %}</title>
+
+               <!-- Your locale is {{ locale.code }} -->
+
+               <!-- Some meta information -->
                <meta name="keywords" content="Linux, Firewall, IPFire, Security" />
-               <meta name="description" content="" />
+               <meta name="description" content="{{ _("IPFire is a free firewall distribution based on Linux.") }}" />
+
+               <!-- Some browser specific stuff -->
                <meta name="verify-v1" content="2LEf3W8naILGWVy2dhedSVEHPXTpp2xFNVsHXZMH1JI=" />
+               <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+               <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
+
+               <!-- Style information and javascript -->
                <link rel="stylesheet" type="text/css" href="{{ static_url("css/style.css") }}" />
                <script type="text/javascript" src="{{ static_url("js/jquery.js") }}"></script>
                <script type="text/javascript" src="{{ static_url("js/jquery-ui.js") }}"></script>
-               <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.ipfire.org/{{ lang }}/news.rss" />
                <!--[if lt IE 7]>
                        <link rel="stylesheet" type="text/css" href="{{ static_url("css/ie6.css") }}" />
                        <script src="{{ static_url("js/correctpng.js") }}" type="text/javascript"></script>
                <![endif]-->
+
+               <!-- RSS -->
+               <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.ipfire.org/{{ lang }}/news.rss" />
        </head>
        <body>
                <div id="header">
                        <div id="header_inner" class="fixed">
                                <div id="logo">
-                                       <a href="{{ link("") }}">
+                                       <a href="http://www.ipfire.org/">
                                                <img src="{{ static_url("images/tux_menu_99x100.png") }}" class="symbol" alt="IPFire" />
                                        </a>
                                </div>
-                               <div id="line1">
+                               <div id="header_menu">
                                        {% block menu %}
                                                {{ modules.Menu() }}
                                        {% end block %}
-                                       <div id="lang">
-                                               {% block languages %}
-                                                       {% for lng in langs %}
-                                                               <a href="{{ lang_link(lng) }}"><img src="{{ static_url("images/%s.gif" % lng) }}" alt="{{ lng }}" /></a>
-                                                       {% end %}
-                                               {% end block %}
-                                       </div>
                                </div>
-                               <div id="line2">
-                                       <h1>{{ server }}</h1>
+                               <div id="header_hostname">
+                                       <h1>{{ hostname }}</h1>
                                </div>
-                               <div id="line3">
-                                       <h2>{{ _("Security gone easy!") }}</h2>
+                               <div id="header_slogan">
+                                       <h2>{{ _("An Open Source Firewall Solution") }}</h2>
                                </div>
-                               
                        </div>
                </div>
                <div id="main">
                    <div id="main_inner" class="fixed">
-                               <table>
+                               <table id="main_frame">
                                <tr>
                                                <td id="sh-tl"></td>
                                                <td id="sh-top"></td>
                                        <tr>
                                                <td id="sh-lft"></td>
                                                <td id="no-sh">
-                                                       <div id="primaryContent_2columns">
-                                                               <div id="columnA_2columns">
-                                                                       {% block content %}
-                                                                       {% end block %}
-                                                                       <br class="clear" />
-                                                               </div>
-                                                       </div>
-                                                       <div id="secondaryContent_2columns">
-                                                               <div id="columnC_2columns">
-                                                                       {% block sidebar %}
-                                                                               {{ modules.SidebarBanner(banner) }}
-                                                                       {% end block %}
-                                                                       <br class="clear" />
-                                                               </div>
-                                                       </div>
+                                                       {% block columns %}{% end block %}
                                                </td>
                                                <td id="sh-rgt"></td>
                                        </tr>
-                                       
                                        <tr>
                                                <td id="sh-lft"></td>
                                                <td id="footer" class="fixed2">
-                                                       Copyright &copy; {{ year }} IPFire.org. All rights reserved. <a href="{{ link("imprint") }}">{{ _("imprint") }}</a>
+                                                       Copyright &copy; {{ year }} IPFire.org. {{ _("All rights reserved.") }} <a href="http://www.ipfire.org/imprint">{{ _("imprint") }}</a>
                                                </td>
                                                <td id="sh-rgt"></td>
                                        </tr>
-                                       
                                        <tr>
                                                <td id="sh-bl"></td>
                                                <td id="sh-btn"></td>
diff --git a/www/templates/download-mirror-detail.html b/www/templates/download-mirror-detail.html
new file mode 100644 (file)
index 0000000..5110546
--- /dev/null
@@ -0,0 +1,37 @@
+{% extends "base.html" %}
+
+{% block title %}{{ _("Mirror-Server") }}{% end block %}
+
+{% block content %}
+       <div class="post">
+               <a name="latest"></a>
+               <h3>{{ _("IPFire Mirrors") }}</h3>
+               <img src="{{ static_url("images/page_icons/download-mirrors.png") }}" class="floatTR" border="0" alt="{{ _("IPFire Torrent Tracker") }}" />
+
+               <table class="download-mirror-detail">
+                       <tr>
+                               <td>{{ _("Hostname") }}</td>
+                               <td><a href="{{ mirror.url }}">{{ mirror.hostname }}</a></td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Owner") }}</td>
+                               <td>{{ mirror.owner }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Last update") }}</td>
+                               <td>{{ locale.format_date(mirror.last_update) }}</td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Number of files") }}</td>
+                               <td>{{ len(mirror.filelist) }}</td>
+                       </tr>
+               </table>
+
+               <br class="clear" />
+
+               <p>
+                       <a href="all">{{ _("View list of all mirror servers.") }}</a>
+               </p>
+       </div>
+
+{% end block %}
index 00d858695f8b5bc182d5076e314b1397c0105a8e..4dd6f8910a92ded2b25eb48f321e511a87528ca5 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-1.html" %}
 
 {% block title %}{{ _("Ancient Downloads") }}{% end block %}
 
                </p>
                
                <p>
-                       <a href="{{ link("downloads") }}">{{ _("Get back to latest stable downloads.") }}</a>
+                       <a href="/downloads">{{ _("Go back to latest stable downloads.") }}</a>
                </p>
 
                <br class="clear" />
        </div>
        
-       {% for release in releases.stable %}
-               {% if not release == releases.latest %}
-                       {{ modules.ReleaseItem(release) }}
-               {% end %}
+       {% for release in releases %}
+               {{ modules.ReleaseItemShort(release) }}
        {% end %}
 {% end block %}
index 24acd0ff702901fa46c0598429e4c9a5a9983362..85b8edf830499007a49f7532660be212ce9d7dae 100644 (file)
@@ -1,37 +1,26 @@
-{% extends "base.html" %}
+{% extends "downloads-older.html" %}
 
 {% block title %}{{ _("Development Downloads") }}{% end block %}
 
-{% block content %}
-       <div class="post">
-               <h3>{{ _("Development Downloads") }}</h3>
-               <img src="{{ static_url("images/page_icons/download-developmemt.png") }}"
-                       class="floatTR" border="0" alt="{{ _("Development Downloads") }}" />
-               
-               {% if lang == "de" %}
-                       <p>
-                               In regelmäßigen Abständen wird aus dem aktuellen Quellcode der
-                               Entwicklungsversion ein installationsfähiges Image als
-                               technologische Vorschau erstellt.<br />
-                               Diese Versionen dienen dazu einen stabilen Stand einer größeren
-                               Menge an Testern zukommen zu lassen und sind <strong>nicht</strong>
-                               für den Produktiveinsatz bestimmt.
-                       </p>
-        {% else %}
-               <p>
-                       The developers do create a snapshort version from the current 
-                               development sources to get this distributed to a higher number
-                               of testers.<br />
-                               These versions are <strong>not</strong> intended to be used in a
-                               productive environment.
-               {% end %}               
+{% block information %}
+       <h3>{{ _("Development Downloads") }}</h3>
 
+       {% if lang == "de" %}
                <p>
-                       <a href="{{ link("downloads") }}">{{ _("Get back to latest stable downloads.") }}</a>
+                       In regelmäßigen Abständen wird aus dem aktuellen Quellcode der
+                       Entwicklungsversion ein installationsfähiges Image als
+                       technologische Vorschau erstellt.<br />
+                       Diese Versionen dienen dazu einen stabilen Stand einer größeren
+                       Menge an Testern zukommen zu lassen und sind <strong>nicht</strong>
+                       für den Produktiveinsatz bestimmt.
+               </p>
+    {% else %}
+       <p>
+               The developers do create a snapshort version from the current 
+                       development sources to get this distributed to a higher number
+                       of testers.<br />
+                       These versions are <strong>not</strong> intended to be used in a
+                       productive environment.
                </p>
-       </div>
-
-       {% for release in releases.development %}
-               {{ modules.ReleaseItem(release) }}
        {% end %}
 {% end block %}
diff --git a/www/templates/downloads-index.html b/www/templates/downloads-index.html
new file mode 100644 (file)
index 0000000..df1893e
--- /dev/null
@@ -0,0 +1,49 @@
+{% extends "base-2.html" %}
+
+{% block title %}Index{% end block %}
+
+{% block content %}
+       <h3>{{ _("IPFire Download Center") }}</h3>
+
+       <!-- <img src="{{ static_url("images/box_ipfire.png") }}" alt="{{ _("CD-Box") }}" class="floatTR" /> -->
+
+       <p>
+               Welcome to the downloads section of the IPFire project.
+               <br />
+               At this place you can download every release of IPFire that was published
+               in the last years.
+       </p>
+
+       <p>
+               Please note that older or development releases could bring serious
+               security and stability issues that were solved in a more recent version.
+       <p>
+
+       <h4>Downloads on this site</h4>
+       <ul>
+               <li><a href="/latest">{{ _("Latest release") }}</a></li>
+               <li><a href="/older">{{ _("List of older downloads") }}</a></li>
+               <li><a href="/development">{{ _("Development releases") }}</a></li>
+               <li><a href="/source">{{ _("Source code") }}</a></li>
+       </ul>
+
+       <br class="clear" />
+
+       {{ modules.ReleaseItem(release) }}
+
+{% end block %}
+
+{% block sidebar %}
+       <h4>{{ _("Download options") }}</h4>
+       <ul>
+               <li><a href="/latest">{{ _("Latest release") }}</a></li>
+               <li><a href="/older">{{ _("Older downloads") }}</a></li>
+               <li><a href="/development">{{ _("Development releases") }}</a></li>
+               <li><a href="/source">{{ _("Source code") }}</a></li>
+       </ul>
+
+       <h4>{{ _("Other options") }}</h4>
+       <ul>
+               <li><a href="/mirrors">{{ _("Mirror list") }}</a></li>
+       </ul>
+{% end %}
diff --git a/www/templates/downloads-item.html b/www/templates/downloads-item.html
new file mode 100644 (file)
index 0000000..00a3042
--- /dev/null
@@ -0,0 +1,7 @@
+{% extends "downloads-index.html" %}
+
+{% block title %}Download {{ item.name }}{% end block %}
+
+{% block content %}
+       {{ modules.ReleaseItem(item) }}
+{% end block %}
index 6e1b80567de5b8a828b379d26d4c0cb0671115f0..96cf434689b7173d2c8ecc5f7a6ac694528e17a2 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-1.html" %}
 
 {% block title %}{{ _("Mirror-Server") }}{% end block %}
 
                
                <table class="download-mirrors">
                        <tr>
-                               <th>{{ _("Owner (Hostname)") }}</th>
+                               <th>{{ _("Owner") }}</th>
+                               <th>{{ _("Hostname") }}</th>
                                <th>{{ _("Location") }}</th>
                                <th>{{ _("Last update") }}</th>
-                               <th>{{ _("Contains") }}</th>
+                               <th>&nbsp;</th>
                        </tr>
-                       {% for mirror in mirrors.all %}
-                               <tr class="{{ mirror.html_class() }}">
-                                       <td><a href="{{ mirror.url }}" target="_blank">{{ mirror.owner }} ({{ mirror.hostname }})</a></td>
+                       {% for mirror in mirrors %}
+                               <tr class="{{ mirror.state.lower() }}">
+                                       <td>{{ mirror.owner }}</td>
+                                       <td><a href="{{ mirror.url }}" target="_blank">{{ mirror.hostname }}</a></td>
                                        <td>
-                                               <img src="{{ static_url("images/flags/%s.png" % mirror.location["country_code"]) }}"
-                                                        align="absmiddle" alt="{{ mirror.location["country_code"] }}" />
-                                               {{ mirror.location["country"] }}, {{ mirror.location["city"] }}
+                                               <img src="{{ static_url("images/flags/%s.png" % mirror.country_code) }}"
+                                                        align="absmiddle" alt="{{ mirror.country_code }}" />
+                                               {{ mirror.location }}
                                        </td>
-
-                                       <td class="latency"></td>
-                                       <td class="percentage">{{ mirror.filelist_compare(mirrors.master.files) }}%</td>
+                                       <td>{{ locale.format_date(mirror.last_update) }}</td>
+                                       <td><a href="{{ mirror.id }}">{{ _("details") }}</a></td>
                                </tr>
                        {% end %}
-                       <tr class="legend">
-                               <td colspan="4">&nbsp;</td>
-                       </tr>
-                       <tr class="legend">
-                               <td><strong>{{ _("Legend") }}:</strong></td>
-                               <td colspan="3" class="ok">{{ _("Server is okay") }}</td>
-                       </tr>
-                       <tr class="legend">
-                               <td>&nbsp;</td>
-                               <td colspan="3" class="outdated">{{ _("Server is outdated") }}</td>
-                       </tr>
                </table>
 
                <br class="clear" />
diff --git a/www/templates/downloads-older.html b/www/templates/downloads-older.html
new file mode 100644 (file)
index 0000000..c72981e
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "downloads-index.html" %}
+
+{% block title %}{{ _("Ancient downloads") }}{% end block %}
+
+{% block content %}
+       {% block information %}
+               <h3>{{ _("Ancient downloads") }}</h3>
+               <img src="{{ static_url("images/page_icons/download-all.png") }}" class="floatTR" border="0" alt="{{ _("Download IPFire") }}" />
+               <p>
+                       {{ _("These are the ancient downloads of IPFire. They are just saved for historical reasons and should not be used in a productive environment.") }}
+               </p>
+
+               <p class="warning">
+                       {{ _("Beware that these releases could lack possible security-fixes     and so it is recommended to use the <em>latest</em> version.") }}
+               </p>
+               
+               <p>
+                       <a href="/">{{ _("Go back to latest stable downloads.") }}</a>
+               </p>
+
+               <br class="clear" />
+       {% end block %}
+
+       <table>
+               <tr>
+                       <th>{{ _("Release") }}</th>
+                       <th>{{ _("Published on") }}</th>
+               </tr>
+               {% for release in releases %}
+               <tr>
+                       <td><a href="/release/{{ release.id }}">{{ release.name }}</a></td>
+                       <td>{{ locale.format_date(release.date, shorter=True, relative=False) }}</td>
+               </tr>
+               {% end %}
+       </table>
+{% end block %}
index 8fe582a8275fd0c531382c015b5a3d065c47fc08..647f18c77861248ca8cc913e6468e91a73d647ff 100644 (file)
@@ -1,89 +1,83 @@
-{% extends "base.html" %}
+{% extends "base-2.html" %}
 
 {% block title %}{{ _("Downloads") }}{% end block %}
 
 {% block content %}
-       <div class="post">
-               <a name="latest"></a>
-               <h3>{{ _("Download IPFire") }}</h3>
+       <h3>{{ _("Download IPFire") }}</h3>
 
-               <img src="{{ static_url("images/box_ipfire.png") }}" alt="{{ _("CD-Box") }}" class="floatTR" />
+       <img src="{{ static_url("images/box_ipfire.png") }}" alt="{{ _("CD-Box") }}" class="floatTR" />
 
-               {% if lang == "de" %}
-                       <p>
-                               Auf dieser Seite können Sie <strong>kostenlos</strong> die neueste
-                               Version von IPFire herunterladen. Ã„ltere Versionen oder
-                               andere Downloadoptionen finden Sie weiter unten auf der Seite.
-                       </p>
+       {% if lang == "de" %}
+               <p>
+                       Auf dieser Seite können Sie <strong>kostenlos</strong> die neueste
+                       Version von IPFire herunterladen. Ã„ltere Versionen oder
+                       andere Downloadoptionen finden Sie weiter unten auf der Seite.
+               </p>
 
-                       <p>
-                               IPFire ist innerhalb von 15 bis 20 Minuten eingerichtet.
-                               Eine <a href="http://wiki.ipfire.org/{{ lang }}/installation/start"
-                               target="_blank">Installationsanleitung</a>
-                               ist in unserem Wiki zu finden.
-                       </p>
+               <p>
+                       IPFire ist innerhalb von 15 bis 20 Minuten eingerichtet.
+                       Eine <a href="http://wiki.ipfire.org/{{ lang }}/installation/start"
+                       target="_blank">Installationsanleitung</a>
+                       ist in unserem Wiki zu finden.
+               </p>
+       {% else %}
+               <p>
+                       On this page one can download the latest version of IPFire
+                       <strong>for free</strong>. Older versions and other downloads
+                       can be retrieved on the linked pages below.
+               </p>
 
-                       <p>
-                               Sollte Ihnen diese Software gefallen, würden sich die Betreiber
-                               Ã¼ber ein kleines <a href="{{ link("donation") }}">Dankeschön</a> freuen.
-                       </p>
-               {% else %}
-                       <p>
-                               On this page one can download the latest version of IPFire
-                               <strong>for free</strong>. Older versions and other downloads
-                               can be retrieved on the linked pages below.
-                       </p>
+               <!-- <p>
+                       IPFire can be installed within 15 to 20 minutes. An
+                       <a href="http://wiki.ipfire.org/{{ lang }}/installation/start"
+                       target="_blank">installation guide</a> can be found on our wiki.
+               </p> -->
 
-                       <p>
-                               IPFire can be installed within 15 to 20 minutes. An
-                               <a href="http://wiki.ipfire.org/{{ lang }}/installation/start"
-                               target="_blank">installation guide</a> can be found on our wiki.
-                       </p>
+               <p>
+                       Some other things you might be interested in as well:
+               </p>
+               <ul>
+                       <li>
+                               <a href="http://wiki.ipfire.org/{{ lang }}/installation/start">{{ _("Installation guide") }}</a>
+                       </li>
+               </ul>
+       {% end %}
+       
+       <p>
+               Older downloads can be found in the
+               <a href="http://downloads.ipfire.org/">Download Center</a>.
+       </p>
 
-                       <p>
-                               If you like this piece of software, developers appreciate your
-                               <a href="{{ link("donation") }}">donations</a>.
-                       </p>
-               {% end %}
+       <br class="clear" />
 
-               <div class="bigdownload">
-                       <a href="{{ release.iso.url }}">
-                               {{ _("Begin download") }}<br />
-                               {{ release.name }}
-                       </a>
-               </div>
-
-               <table class="download">
-                       {% for download in release.downloads %}
-                               <tr class="{{ download.type }}">
-                                       <td class="icon">
-                                               <img src="{{ static_url("images/download_type_%s.png" % download.type) }}"
-                                                       alt="{{ download.type }}" />
-                                       </td>
-                                       <td class="link">
-                                               <a href="{{ download.url }}">{{ _(download.desc) }}</a>
-                                       </td>
-                                       <td>
-                                               {{ _(download.rem) }}
-                                       </td>
-                               </tr>
-                       {% end %}
-               </table>
+       {{ modules.ReleaseItem(release) }}
 
-               <br class="clear" />
-       </div>
+       <br class="clear" />
 
-       <div class="post">
-               <a name="other"></a>
-               <h3>{{ _("Other download options") }}</h3>
-               <div class=pr_li>
-                       <ul>
-                               <li><a href="{{ link("downloads/all") }}">{{ _("See older downloads...") }}</a></li>
-                               <li><a href="{{ link("downloads/development") }}">{{ _("See development releases...") }}</a></li>
-                               <li><a href="{{ link("downloads/torrents") }}">{{ _("See all torrents...") }}</a></li>
-                               <li><a href="{{ link("downloads/mirrors") }}">{{ _("See a list of mirrors...") }}</a></li>
-                       </ul>
+       <h3>{{ _("Donation") }}</h3>
+       <p>
+               If you like IPFire, there is the opportunity to donate a small amount
+               of money to the project. This will help the people that are running
+               this project very much.
+       </p>
+       <p>
+               <div align="center">
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+                               <input type="hidden" name="cmd" value="_s-xclick">
+                               <input type="hidden" name="hosted_button_id" value="10781833">
+                               <input type="image" src="https://www.paypal.com/de_DE/DE/i/btn/btn_donateCC_LG.gif"
+                                       border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
+                               <img alt="" border="0" src="https://www.paypal.com/de_DE/i/scr/pixel.gif" width="1" height="1">
+                       </form>
                </div>
                <br class="clear" />
-       </div>
+       </p>
+       <p>
+               If you want to know more about how you can help the IPFire project
+               read <a href="/donation">this</a>.
+       </p>
+{% end block %}
+
+{% block sidebar %}
+       {{ modules.SidebarBanner() }}
 {% end block %}
index bc587d1780718411793a44033afad688f5f00fde..aeaf0f2cfa58df4fddead8fec963178f225e846c 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-1.html" %}
 
 {% block title %}{{ _("Error") }} {{ code }}{% end block %}
 
index 417b855dfc68b7ce4bb97ee983193b520e72a047..e81d55657e8cf7c1f5299ac1489c22d8a405f244 100644 (file)
-{% extends "base.html" %}
+{% extends "base-2.html" %}
 
 {% block title %}{{ _("Home") }}{% end block %}
 
 {% block content %}
-       <div class=post>
-               <h3>{{ _("More security for your network") }}</h3>
+       <!--
+               <img src="{{ static_url("images/Network-1.png") }}") }}" alt="IPFire Network" />
+               <br class="clear" />
+       -->
+
+       <h3>IPFire - {{ _("An open source firewall distribution") }}</h3>
+       {% if lang == "de" %}
                <p>
-                       {% if lang == "de" %}
-                               Das <strong>IPFire</strong>-System ist eine Linux-Distribution, mit der Zielsetzung ein einfach zu 
-                               installierendes Grundsystem zu bieten und dabei ein <strong>hohes Sicherheitsniveau</strong> zu gewährleisten.
-                               <strong>IPFire</strong> ist komplett Ã¼ber sein intuitiv zu bedienendes Webinterface zu konfigurieren 
-                               und bietet sowohl <strong>Anfängern</strong>, als auch erfahrenen <strong>Administratoren</strong> eine Fülle von 
-                               Einstellungsmöglichkeiten. Einen <strong>Schwerpunkt</strong> bei der Weiterentwicklung legen die erfahrenen Entwickler 
-                               auf regelmäßige System- und vor allem <strong>Sicherheitsupdates</strong>.<br /><br />                         
-                               Durch den integrierten Paketmanager <strong>Pakfire</strong> lässt sich der <strong>IPFire</strong> mit diversen 
-                               <a href="http://wiki.ipfire.org/de/addons/start" target="_blank">Addons</a>, 
-                               zu einem Server-System erweitern.
-                       {% else %}
-                               <strong>IPFire</strong> is a linux-distribution that focusses on easy setup, good handling and a 
-                               <strong>high niveau of security</strong>.<br /> It is operable via an intuitive webinterface, which offers a
-                               lot of playground for <strong>beginners</strong> and even experienced <strong>administrators</strong>.
-                               <strong>IPFire</strong> is maintained by experienced developers, who are really concerned about security and regulary 
-                               updates to <strong>keep it secure</strong>.<br /><br />                  
-                               <strong>IPFire</strong> ships with a custom built paket-manager called <strong>Pakfire</strong>, so the system can be 
-                               expanded with various <a href="http://wiki.ipfire.org/en/addons/start" target="_blank">addons</a>.                               
-                       {% end %}
+                       <strong>IPFire</strong> ist eine Firewall-Distribution für den optimalen Einsatz
+                       in mittleren bis kleinen Unternehmensnetzwerken und Heimnetzwerken.
                </p>
-               <img src="{{ static_url("images/Network-1.png") }}") }}" alt="IPFire Network" />
-               <div class="button">
-                       <div class="button_text">
-                                       <a href="download">Download&nbsp;&nbsp;IPFire 2.7</a>
-                       </div>
-               </div>
-       </div>
-       
-       <div class=post>
-               <h3>{{ _("News") }}</h3>
-       
-               {% for item in news.get(2) %}
-                       {{ modules.NewsItem(item) }}
-               {% end %}
-       </div>
+               <p>
+                       Obwohl das System extrem schlank gehalten und gehärtet wurde und können
+                       Addons mit nur einem Klick installiert werden. Diese Eigenschaft unterscheidet
+                       IPFire von anderen Distributionen: IPFire ist einfach zu administrieren
+                       aber hat genug Leistung allen Anforderungen gewachsen zu sein.
+               </p>
+       {% else %}
+               <p>
+                       <strong>IPFire</strong> is a server distribution with primary task as
+                       a firewall. It also focuses on flexibility and scales very well from small to
+                       middle-sized buisiness networks and home networks.
+               </p>
+               <p>
+                       Along with this very shrinked and hardened system come lots of addons that
+                       can be installed with a simple click. That's what makes IPFire different
+                       from other distributions: It is easy to administer and has lots of power
+                       for every challenge there might be.
+               </p>
+       {% end %}
+
+       <br class="clear" />
+
+       <p>
+               {{ _("See what makes IPFire so great:") }}
+       </p>
+
+       <table class="blocks">
+               <tr>
+                       <td class="block block1">
+                               <span>{{ _("Security") }}</span>
+                               <p>
+                                       As the most important issue: Security updates
+                                       are deployed regularly and in a timely manner.
+                               </p>
+                               <a href="/about#security">{{ _("Learn more.") }}</a>
+                       </td>
+                       <td class="block block2">
+                               <span>{{ _("Flexibility") }}</span>
+                               <p>
+                                       Nowadays it is also important to act flexible and fit
+                                       as best as possible into the environment. So does IPFire.
+                               </p>
+                               <p>
+                                       <a href="/about#concept">{{ _("Learn more.") }}</a>
+                               </p>
+                       </td>
+                       <td class="block block3">
+                               <span>{{ _("Community") }}</span>
+                               <p>
+                                       The success of IPFire is based on the community that
+                                       is working on the code an spreading IPFire to the world.
+                               </p>
+                               <p>
+                                       <a href="/about#community">{{ _("Get involved.") }}</a>
+                               </p>
+                       </td>
+               </tr>
+       </table>
+
+       <p class="links">
+               {{ _("Quick links") }} &bull;
+               <a href="/about">{{ _("About IPFire") }}</a>
+               &bull;
+               <a href="/screenshots">{{ _("Screenshots") }}</a>
+       </p>
+
+       <br class="clear" />
+
+       {% for item in latest_news %}
+               {{ modules.NewsItem(item) }}
+       {% end %}
+
+       <h4>{{ _("Recent news") }}:</h4>
+       <ul class="news">
+       {% for item in recent_news %}
+               <li>{{ modules.NewsLine(item) }}</li>
+       {% end %}
+       </ul>
+
+       <br class="clear" />
+
        <p class="right">
-               <a href="{{ link("news") }}">{{ _("Previous posts >>") }}</a>
+               <a class="feed" href="/news.rss">{{ _("Subscribe to the latest news of IPFire") }} (RSS)</a>
+               &bull;
+               <a href="/news">{{ _("All posts") }} &gt;&gt;</a>
        </p>
 {% end block %}
 
 {% block sidebar %}
        {{ modules.SidebarRelease() }}
-       
-       <h4><span>Internet Relay</span> Chat</h4>
-       <p>
-               Server: irc.freenode.net<br />
-               Channel: #ipfire<br />
-               <a href="http://webchat.freenode.net/?channels=ipfire" target="_blank">Web-Chat</a>
-       </p>
-       
-       {{ modules.SidebarBanner(banner) }}
-       
-       <h4><span>RSS</span> feed</h4>
-       <p>
-               {% if lang == "de" %}
-                       <a class="feed" href="news.rss">IPFire - News</a><br />
-               {% else %}              
-                       <a class="feed" href="news.rss">IPFire - News</a><br />
-               {% end %}
-       </p>
-
+       {{ modules.SidebarBanner() }}
 {% end block %}
diff --git a/www/templates/mirrors-item.html b/www/templates/mirrors-item.html
new file mode 100644 (file)
index 0000000..f81ed7c
--- /dev/null
@@ -0,0 +1,53 @@
+{% extends "mirrors.html" %}
+
+{% block title %}Mirror {{ item.hostname }}{% end block %}
+
+{% block content %}
+       <h3>{{ item.hostname }}</h3>
+
+       <table class="mirrors">
+               <tr>
+                       <td>{{ _("State") }}</td>
+                       <td class="{{ item.state.lower() }}">
+                               {% if item.state.lower() == "up" %}
+                                       {{ _("Up") }}
+                               {% elif item.state.lower() == "down" %}
+                                       {{ _("Down") }}
+                               {% elif item.state.lower() == "outofsync" %}
+                                       {{ _("Out of synchronization") }}
+                               {% else %}
+                                       {{ _("Unknown") }}
+                               {% end %}
+                       </td>
+               </tr>
+               <tr>
+                       <td>{{ _("Last update") }}</td>
+                       <td>{{ locale.format_date(item.last_update, full_format=True) }}</td>
+               </tr>
+               <tr>
+                       <td>{{ _("Owner") }}</td>
+                       <td>{{ item.owner }}</td>
+               </tr>
+       </table>
+
+       <p class="links">
+               <a href="{{ item.url }}" target="_blank">{{ _("Open mirror") }}</a>
+               &bull;
+               <a href="/">{{ _("List of all mirror servers") }}</a>
+       </p>
+
+       <br class="clear" />
+
+       <h3>{{ _("Mirror location") }}</h3>
+       <p>
+               {{ _("The mirror <em>%s</em> is located in %s.") % (item.hostname, item.location_str) }}
+               <br class="clear" />
+               <img class="map"
+                       src="http://maps.google.com/maps/api/staticmap?center={{ item.coordiantes }}&size=640x280&zoom=6&markers=color:blue|label:.|{{ item.coordiantes }}&sensor=false"
+                       alt="{{ _("Location of the server") }}" />
+       </p>
+{% end block %}
+
+{% block sidebar %}
+       {{ modules.SidebarBanner() }}
+{% end block %}
diff --git a/www/templates/mirrors.html b/www/templates/mirrors.html
new file mode 100644 (file)
index 0000000..0ec796a
--- /dev/null
@@ -0,0 +1,38 @@
+{% extends "base-2.html" %}
+
+{% block title %}Mirrors{% end block %}
+
+{% block content %}
+       <h3>mirrors.ipfire.org</h3>
+       <p>
+               The IPFire project is hosted on several servers to provide a high
+               availabilty of service.
+       </p>
+       <p>
+               This is the list of the download mirrors. Most of them are not owned
+               and managed by the IPFire project. So please do not call us to get them
+               work again if there is a problem.
+       </p>
+
+       <br class="clear" />
+
+       <h3>{{ _("List of servers") }}</h3>
+       <table class="mirrors">
+               {% for mirror in mirrors %}
+                       <tr>
+                               <td class="hostname {{ mirror.state.lower() }}">
+                                       <a href="/mirror/{{ mirror.id }}">{{ mirror.hostname }}</a>
+                               </td>
+                               {% if not mirror.state == "UP" %}
+                                       <td>{{ _("Last update") }}: {{ locale.format_date(mirror.last_update) }}.</td>
+                               {% else %}
+                                       <td>&nbsp;</td>
+                               {% end %}
+                       </tr>
+               {% end %}
+       </table>
+{% end block %}
+
+{% block sidebar %}
+       {{ modules.SidebarBanner() }}
+{% end block %}
diff --git a/www/templates/modules/menu-item.html b/www/templates/modules/menu-item.html
deleted file mode 100644 (file)
index f3833a2..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<li>
-       <a href="{{ item.uri }}"{% if item.active %} class="active"{% end %}>{{ item.name }}</a>
-</li>
index 5ca3982468027a65da56acbd8c6718704fa08ea1..165ee4b38be640cdece04e514301dac76cc8a5f2 100644 (file)
@@ -1,7 +1,9 @@
 <div id="menu">
        <ul>
                {% for item in menuitems %}
-                       {{ modules.MenuItem(item) }}
+                       <li>
+                               <a href="{{ item.uri }}"{% if item.active %} class="active"{% end %}>{{ item.description }}</a>
+                       </li>
                {% end %}
        </ul>
 </div>
index 0cd5addf731e1bd90f4543418e226fba817766b4..7078fd738144cc22823abf5c185e66da65df10f0 100644 (file)
@@ -1,6 +1,17 @@
 <div class="post">
-       <a name="{{ item.subject }}"></a>
-       <h3>{{ item.subject }}</h3>
-       <div class="post_info">{{ item.date }} - {{ _("by") }} {{ item.author }}</div>
-       {{ item.content }}
+       <h2 class="title"><a href="/news/{{ item.slug }}">{{ item.title }}</a></h2>
+       <p class="meta">
+               {{ _("Posted by %s on") % item.author }} {{ locale.format_date(item.date, shorter=True) }}
+               <!-- &bull;     <a href="/news/{{ item.slug }}">{{ _("Full article") }}</a> -->
+       </p>
+       <div class="entry">
+               {{ item.text }}
+
+               {% if not uncut %}
+                       <p class="links">
+                                       <a href="/news/{{ item.slug }}">{{ _("Read more") }}</a>
+                       </p>
+               {% end %}
+       </div>
 </div>
+
diff --git a/www/templates/modules/news-line.html b/www/templates/modules/news-line.html
new file mode 100644 (file)
index 0000000..9f6e143
--- /dev/null
@@ -0,0 +1 @@
+<a href="/news/{{ item.slug }}">{{ item.title }}</a> ({{ locale.format_date(item.date, full_format=False, shorter=True) }})
diff --git a/www/templates/modules/news-preview.html b/www/templates/modules/news-preview.html
new file mode 100644 (file)
index 0000000..4f69932
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="post">
+       <a name="{{ item.slug }}"></a>
+       <h3>
+               <a href="/news/{{ item.slug }}">{{ item.title }}</a>
+       </h3>
+       <div class="post_info">{{ item.date }} {{ _("by") }} {{ item.author }}</div>
+       {{ item.text }}
+</div>
+
index c7d6ec8c85ab4c5ccd98187d64cf2015248977de..fd2ffe2e55f3575741329fa9b25e3b1d20f5e733 100644 (file)
@@ -1,14 +1,26 @@
 <div class="post">
-       <a name="{{ entry.slug }}"></a>
-       <h3>
-               <a href="/post/{{ entry.slug }}">{{ entry.title }}</a>
-       </h3>
-       <div class="post_info">
-               {{ entry.published }} {{ _("by") }}
-               <a href="/user/{{ entry.author.name }}">{{ entry.author.realname }}</a>
-       </div>
-       {{ entry.markup }}
-       <div class="footer">
-               Last update: {{ entry.updated }}
+       <h2 class="title"><a href="/post/{{ entry.slug }}">{{ entry.title }}</a></h2>
+       <p class="meta">
+               {{ _("Posted by") }} <a href="/user/{{ entry.author.uid }}">{{ entry.author.cn }}</a>
+               {{ _("on") }} {{ locale.format_date(entry.published, shorter=True) }}
+               <!-- &bull; <a href="/post/{{ entry.slug }}">{{ _("Full article") }}</a> -->
+       </p>
+       <div class="entry">
+               <img class="floatTR" src="{{ entry.author.gravatar_icon(64) }}"
+                       alt="{{ entry.author.cn }}" />
+
+               {% if short %}
+                       {{ entry.abstract }}
+               {% else %}
+                       {{ entry.text }}
+               {% end %}
+
+               <p class="links">
+                       {% if short %}
+                               <a href="/post/{{ entry.slug }}">{{ _("Read more") }}</a>
+                               &bull;
+                       {% end %}
+                       {{ _("Last updated on") }} {{ locale.format_date(entry.updated) }}.
+               </p>
        </div>
 </div>
diff --git a/www/templates/modules/release-item-short.html b/www/templates/modules/release-item-short.html
new file mode 100644 (file)
index 0000000..7dd2036
--- /dev/null
@@ -0,0 +1,18 @@
+<div class="post">
+       <h3>{{ item.name }}</h3>
+       <p>     
+               {{ _("Here you will find the downloads for the version") }} {{ item.name }}:
+       </p>
+
+       <div align="right">
+               {% if item.files %}
+                       {% for file in item.files %}
+                               <a class="{{ file.type }}" href="{{ file.url }}">{{ _(file.desc) }}</a><br />
+                       {% end %}
+               {% else %}
+                       <p>{{ _("There are no downloads available for this release.") }}</p>
+               {% end %}
+       </div>
+
+       <br class="clear" />
+</div>
index 3d7dedfb486bffb660f5b592bcf2de901d814207..e85356ced58e4146851008a9a1f49bb0209e26e3 100644 (file)
@@ -1,14 +1,69 @@
-<div class="post">
-       <h3>{{ item.name }}</h3>
-       <p>     
-               {{ _("Here you will find the downloads for the version") }} {{ item.name }}:
-       </p>
-
-       <div align="right">
-               {% for download in item.downloads %}
-                       <a class="{{ download.type }}" href="{{ download.url }}">{{ _(download.desc) }}</a><br />
+{% if release.files %}
+       <h3>{{ release.name }}</h3>
+
+       <!--
+       {% for file in release.files %}
+               {% if file.type == "iso" %}
+                       <div class="bigdownload">
+                               <a href="{{ file.url }}">
+                                       {{ _("Begin download") }}<br />{{ release.name }}
+                               </a>
+                       </div>
+               {% end %}
+       {% end %}
+       -->
+
+       {% if release.stable == "N" %}
+               <p class="warning">
+                       <strong>{{ _("Beware!") }}</strong>
+                       {{ _("<em>%s</em> was not released yet and so it is not recommended for production use.") % release.name }}
+               </p>
+       {% end %}
+
+       <table class="download">
+               {% for file in release.files %}
+                       <tr class="{{ file.type }}">
+                               <td class="icon">
+                                       <img src="{{ static_url("images/download_type_%s.png" % file.type) }}"
+                                               alt="{{ file.type }}" />
+                               </td>
+                               <td class="link">
+                                       <a href="{{ file.url }}">{{ _(file.desc) }}</a>
+                               </td>
+                               <td>
+                                       {{ _(file.rem) }}
+                               </td>
+                       </tr>
                {% end %}
-       </div>
+
+               <tr>
+                       <td colspan="3">
+                               &nbsp;
+                       </td>
+               </tr>
+<!--           <tr>
+                       <td colspan="3">
+                               <p>
+                                       {{ _("With starting the download you accept the <a href=\"/download/legal\">legal terms</a>.") }}
+                               </p>
+                       </td>
+               </tr>
+               <tr>
+                       <td colspan="3">
+                               &nbsp;
+                       </td>
+               </tr>
+-->
+               <tr>
+                       <td colspan="3">
+                               <p>{{ _("Checksums") }} (SHA1):</p>
+                               <pre>{% for file in release.files %}{{ file.sha1 }} {{ file.filename }}{{ "\n" }}{% end %}</pre>
+                       </td>
+               </tr>
+       </table>
 
        <br class="clear" />
-</div>
+
+{% else %}
+       <p>{{ _("There are no downloads available for this release.") }}</p>
+{% end %}
index fb891fcae25e7d404be4ebfaed73d9690f037ec8..c1958946b2fdef83825fbb5fe9d44b8eb3a6ee8e 100644 (file)
@@ -1,7 +1,6 @@
 {% if item %}
-       <h4>{{ item.title }}</h4>
-
-       <a href="{{ item.link }}" {% if item.link.startswith("http://") %}target="_blank" {% end %}>
-               <img src="{{ static_url(item.uri) }}" border="0" alt="{{ _("Banner") }}" />
+       <h4>{{ item.caption }}</h4>
+       <a href="{{ item.uri }}" {% if item.uri.startswith("http://") %}target="_blank" {% end %}>
+               <img src="{{ static_url("images/banners/" + item.image) }}" border="0" alt="{{ _("Banner") }}" />
        </a>
 {% end %}
index 0483673c6a96892fb77020ee3c127025b7f40b24..ad91de680e55db48d8e1b71bbeaaa1ff1f181b1d 100644 (file)
@@ -7,17 +7,11 @@
                <img src="{{ static_url("images/ipfire_download.png") }}" alt="Tux Logo" />
        </p>
 
-       <p>
-               <strong>{{ _("Current version") }}</strong>:
-               <br />
-               <a href="{{ link("downloads") }}">{{ releases.latest.name }}</a>
-       </p>
-
-       {% if releases.latest_devel %}
-       <p>
-               <strong>{{ _("Current unstable") }}</strong>:
-               <br />
-               <a href="{{ link("downloads/development") }}">{{ releases.latest_devel.name }}</a>
-       </p>
+       {% if latest %}
+               <p>
+                       <strong>{{ _("Current version") }}</strong>:
+                       <br />
+                       <a href="downloads">{{ latest.name }}</a>
+               </p>
        {% end %}
 {% end block %}
diff --git a/www/templates/modules/tracker-peerlist.html b/www/templates/modules/tracker-peerlist.html
new file mode 100644 (file)
index 0000000..8ebf0a1
--- /dev/null
@@ -0,0 +1,15 @@
+<table class="tracker-peerlist">
+       <tr>
+               <th>&nbsp;</th>
+               <th>&nbsp;</th>
+               <th>Hostname</th>
+       </tr>
+       {% for peer in peers %}
+               <tr>
+                       <td class="ip">{{ peer.ip }}</td>
+                       <td><img src="{{ static_url("images/flags/" + peer.country_code + ".png") }}"
+                               alt="{{ peer.country_code }}" /></td>
+                       <td>{{ peer.hostname or "---" }}</td>
+               </tr>
+       {% end %}
+</table>
diff --git a/www/templates/news-author.html b/www/templates/news-author.html
new file mode 100644 (file)
index 0000000..e8379c0
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+
+{% block title %}{{ author.cn }}{% end block %}
+
+{% block content %}
+       <h2>{{ author.cn }}</h2>
+       <p>
+               Fill in some intesting things about the author...
+       </p>
+
+       <h3>{{ _("%s recently announced...") % author.cn }}</h3>
+       <ul class="news_author_latest_news">
+       {% for news in latest_news %}
+               <li>{{ modules.NewsLine(news) }}</li>
+       {% end %}
+       </ul>
+{% end block %}
+
diff --git a/www/templates/news-item.html b/www/templates/news-item.html
new file mode 100644 (file)
index 0000000..6b54f97
--- /dev/null
@@ -0,0 +1,22 @@
+{% extends "base-2.html" %}
+
+{% block title %}{{ item.title }}{% end block %}
+
+{% block content %}
+<!--   <h2>{{ item.title }}</h2>
+       <div class="post_info">
+               {{ locale.format_date(item.date) }} {{ _("by") }}
+               <a href="/author/{{ item.author_id }}">{{ item.author }}</a>
+       </div>
+
+       {{ item.text }} -->
+
+       {{ modules.NewsItem(item, uncut=True) }}
+{% end block %}
+
+{% block sidebar %}
+       <h4>other options</h4>
+       <ul>
+               <li><a href="/news">{{ _("Show all news") }}</a></li>
+       </ul>
+{% end block %}
index e6b98d01f6339a926a4626fe5af9cad13207afbe..17d3f2e608654b389f4f5cef8e01e5da44c264fe 100644 (file)
@@ -1,7 +1,34 @@
-{% extends "base.html" %}
+{% extends "base-1.html" %}
+
+{% block title %}News{% end block %}
 
 {% block content %}
-       {% for item in news.get() %}
+       <h3>What is new on the IPFire project?</h3>
+
+       <!-- XXX fill in some nice illustration -->
+
+       <p>
+               At this place you will know about the most important announces.
+               For development information and the little beans there is the
+               <a href="http://planet.ipfire.org">IPFire Planet</a>.
+       </p>
+       
+       <p>
+               To get latest news, there is the a
+               <a class="feed" href="/news.rss"> RSS feed</a>.
+       </p>
+
+       <br class="clear" />
+
+       {% for item in news %}
                {{ modules.NewsItem(item) }}
        {% end %}
+
+       <p class="links">
+               {% if offset - limit %}
+                       <a href="?offset={{ offset - (2 * limit) }}">&lt;&lt; {{ _("See newer news entries") }}</a>
+                       &bull;
+               {% end %}
+               <a href="?offset={{ offset }}">{{ _("See older news entries") }} &gt;&gt;</a>
+       </p>
 {% end block %}
index 87f3e67abad7afa41ec5a7891b522d5c7f08a766..bd392bdfb21fefd8cc3e6921614fd7d7ab400380 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-2.html" %}
 
 {% block title %}{{ _("IPFire Planet") }}{% end block %}
 
                        </p>
                {% end %}
                <br class="clear" />
-               
+               <!--
                <p>
                        {{ _("Last issues") }}:
                        <ul>
                                {% for entry in entries %}
                                        <li>
-                                               <a href="#{{ entry.id }}">{{ entry.title }}</a>
-                                               {{ _("by") }} {{ entry.author.realname }}
+                                               <a href="/post/{{ entry.slug }}">{{ entry.title }}</a>
+                                               {{ _("by") }}
+                                               <a href="/user/{{ entry.author.uid }}">{{ entry.author.cn }}</a>
                                        </li>
                                {% end %}
                        </ul>
                </p>
+               -->
        </div>
        
        {% for entry in entries %}
-               {{ modules.PlanetEntry(entry) }}
+               {{ modules.PlanetEntry(entry, short=True) }}
        {% end %}
+       
+       <p class="right">
+               {% if offset - limit %}
+                       <a href="?offset={{ offset - (2 * limit) }}">&lt;&lt; {{ _("See newer entries") }}</a>
+                       &bull;
+               {% end %}
+               <a href="?offset={{ offset }}">{{ _("See older entries") }} &gt;&gt;</a>
+       </p>
 {% end block %}
 
 {% block sidebar %}
@@ -49,7 +59,7 @@
                <ul>
                        {% for author in authors %}
                                <li>
-                                       <a href="/user/{{ author.name }}">{{ author.realname }}</a>
+                                       <a href="/user/{{ author.uid }}">{{ author.cn }}</a>
                                </li>
                        {% end %}
                </ul>
index e5a3120fdd92be0d9cb028422f80af5ca8d869bf..c3f0a0c2063d598fccc1f565a5d5e11ebbb484b7 100644 (file)
@@ -1,34 +1,26 @@
-{% extends "base.html" %}
+{% extends "base-2.html" %}
 
-{% block title %}{{ _("IPFire Planet") }} - {{ user.realname }}{% end block %}
+{% block title %}{{ _("IPFire Planet") }} - {{ author.cn }}{% end block %}
 
 {% block content %}
        {% if entries %}
                {% for entry in entries %}
-                       {{ modules.PlanetEntry(entry) }}
+                       {{ modules.PlanetEntry(entry, short=True) }}
                {% end %}
-       {% else %}
-               <div class="post">
-                       {% if lang == "de" %}
-                               <p>
-                                       Der User {{ user.name }} hat bisher keine Einträge verfasst.
-                               </p>
-                       {% else %}
-                               <p>
-                                       The user {{ user.name }} has no entries, yet.
-                               </p>
+               
+               <p class="right">
+                       {% if offset - limit %}
+                               <a href="?offset={{ offset - (2 * limit) }}">&lt;&lt; {{ _("See newer entries") }}</a>
+                               &bull;
                        {% end %}
-               </div>
+                       <a href="?offset={{ offset }}">{{ _("See older entries") }} &gt;&gt;</a>
+               </p>
+       {% else %}
+                       <p>{{ _("%s did no postings, yet.") % author.cn }}</p>
        {% end %}
 {% end block %}
 
 {% block sidebar %}
-       <h4>{{ user.realname }}</h4>
-       <img src="{{ static_url("images/portrait.gif") }}" alt="{{ user.name }}" />
-       <ul>
-               <li>
-                       <a href="http://people.ipfire.org/~{{ user.name }}/" target="_blank">{{ _("Web directory") }}</a>
-               </li>
-               <li>{{ user.mail }}</li>
-       </ul>
+       <h4>{{ author.cn }}</h4>
+       <img src="{{ author.gravatar_icon(170) }}" alt="{{ author.uid }}" />
 {% end %}
diff --git a/www/templates/stasy-base-1.html b/www/templates/stasy-base-1.html
new file mode 100644 (file)
index 0000000..c812e27
--- /dev/null
@@ -0,0 +1 @@
+{% extends "base-1.html" %}
diff --git a/www/templates/stasy-base-2.html b/www/templates/stasy-base-2.html
new file mode 100644 (file)
index 0000000..361a858
--- /dev/null
@@ -0,0 +1 @@
+{% extends "base-2.html" %}
diff --git a/www/templates/stasy-index.html b/www/templates/stasy-index.html
new file mode 100644 (file)
index 0000000..491dbed
--- /dev/null
@@ -0,0 +1,12 @@
+{% extends "stasy-base-2.html" %}
+
+{% block title %}{{ _("Statistical evaluation service") }}{% end block %}
+
+{% block content %}
+       <h3>{{ _("Statistical evaluation service") }}</h3>
+
+       {% for p in profiles %}
+               <a href="/profile/{{ p }}">{{ p }}</a>
+       {% end %}
+
+{% end block %}
diff --git a/www/templates/stasy-profile.html b/www/templates/stasy-profile.html
new file mode 100644 (file)
index 0000000..15ed6c5
--- /dev/null
@@ -0,0 +1,120 @@
+{% extends "stasy-base-1.html" %}
+
+{% block title %}{{ _("Profile") }} {{ profile.public_id }}{% end block %}
+
+{% block content %}
+       <h3>{{ _("Profile") }} {{ profile.public_id }}</h3>
+
+       <table>
+               <tr>
+                       <td colspan="2">
+                               <strong>{{ _("Operating system") }}</strong>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Version") }}
+                       </td>
+                       <td>
+                               {{ profile.release }}
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Architecture") }}
+                       </td>
+                       <td>
+                               {{ profile.arch }}
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Kernel version") }}
+                       </td>
+                       <td>
+                               {{ profile.kernel }}
+                       </td>
+               </tr>
+       </table>
+       
+       <h4>{{ _("Hardware") }}</h4>
+       <table>
+               <tr>
+                       <td colspan="2">
+                               <strong>{{ _("CPU") }}</strong>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Vendor") }}
+                       </td>
+                       <td>
+                               {{ profile.cpu.vendor }}
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Model") }}
+                       </td>
+                       <td>
+                               <!-- XXX hack - becuase module_string does not exist, yet -->
+                                {{ profile.cpu._data.get("model_string", _("Not available")) }}
+                       </td>
+               <tr>
+                       <td>
+                               {{ _("CPU flags") }}
+                       </td>
+                       <td>
+                               {{ locale.list(profile.cpu.flags) }}
+                       </td>
+               </tr>
+               <tr>
+                       <td colspan="2">
+                               <strong>{{ _("Memory") }}<strong>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Size") }}
+                       </td>
+                       <td>
+                               {{ int(profile.memory) / 1024 }} {{ _("MegaBytes") }}
+                       </td>
+               </tr>
+               <tr>
+                       <td colspan="2">
+                               <strong>{{ _("Disk") }}<strong>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               {{ _("Size") }}
+                       </td>
+                       <td>
+                               {{ profile.root_size / 1024 }} {{ _("MegaBytes") }}
+                       </td>
+               </tr>
+               <tr>
+                       <td colspan="2">
+                               <strong>{{ _("Devices") }}</strong>
+                       </td>
+               </tr>
+               {% for device in profile.devices %}
+                       <tr>
+                               <td>
+                                       {{ device.subsystem.upper() }}
+                               </td>
+                               <td>
+                                       <a href="/vendor/{{ device.subsystem.lower() }}/{{ device.vendor }}">{{ device.vendor }}</a>:<a
+                                               href="/model/pci/{{ device.vendor }}/{{device.model }}">{{ device.model }}</a> &bull;
+                                       {{ device.vendor_string }}
+                                       {{ device.model_string }}
+                               </td>
+                       </tr>
+               {% end %}
+       </table>
+
+       <p class="links">
+               {{ _("Last update") }}: {{ locale.format_date(profile.updated) }}
+       </p>
+{% end block %}
diff --git a/www/templates/static/about.html b/www/templates/static/about.html
new file mode 100644 (file)
index 0000000..70d0bf1
--- /dev/null
@@ -0,0 +1,224 @@
+{% extends "../base-1.html" %}
+               
+{% block title %}{{ _("About IPFire") }}{% end block %}
+
+{% block content %}
+       <h3>{{ _("About IPFire") }}</h3>
+       <img src="{{ static_url("images/page_icons/features.png") }}" class="floatTR" alt="{{ _("Features") }}" />
+
+       <h4>What is IPFire?</h4>
+       <p>
+               IPFire is a Linux-based operating system which focuses on its purpose being a firewall system and
+               also flexible to act as a server system. It is completely free as it is licensed under the
+               <a href="#XXX">GNU General Public License</a> in version 3.
+       </p>
+
+       <h4>What makes IPFire special?</h4>
+       <p>
+               We believe in the power of flexibility. People are only as powerful as their environment is.
+               With IPFire, the access to the internet is fast, easy and secure at the same time which in
+               return influences everbody's life.
+       </p>
+
+       <br class="clear" />
+
+       <div id="tabs">
+               <ul>
+                       <li><a href="#concept">{{ _("Concept of the system") }}</a></li>
+                       <li><a href="#security">{{ _("Security") }}</a></li>
+                       <li><a href="#pakfire">{{ _("Packet management") }}</a></li>
+                       <li><a href="#firewall">{{ _("Firewall") }}</a></li>
+                       <li><a href="#vpn">{{ _("VPN") }}</a></li>
+                       <li><a href="#hardware">{{ _("Hardware") }}</a></li>
+                       <li><a href="#virtualization">{{ _("Virtualization") }}</a></li>
+                       <li><a href="#community">{{ _("Community") }}</a></li>
+                       <li><a href="#itsfree">{{ _("It's free") }}</a></li>
+               </ul>
+               <div id="concept">
+                       <p>
+                               The foundation of <strong>IPFire</strong> is the high level of flexibility which lets us
+                               configure different versions of this operating system out of a single base. Beginning with a
+                               few megs big firewall system it is possible to run IPFire as a file server or VPN gateway for
+                               staff, branches and customers. This is manageable with the packet manager that enhances
+                               the system only if you really want to.
+                       </p>
+                       <p>
+                               We believe that this is the best way to provide security to a network. There is no way to give
+                               out a static appliance as is because security is not a single thing to install and never touch
+                               again. It's a kind of process paired with behaviour and restrictions. This plans could look
+                               different from company to company and also differ from the place IPFire is installed at.
+                       </p>
+                       <p>
+                               <em>Please click through the tabs and take a look at what possibilities IPFire offers for
+                               your personal concept of network. And don't be scared. We have built-in our own to
+                               start with...</em>
+                       </p>
+               </div>
+               <div id="security">
+                       <p>
+                               The matter that counts most in the development of IPFire is - of course - security. But
+                               we don't believe that there is only one single way to achvieve security. It is more important
+                               that every administrator knows about what he is configuring and that he is teached about what
+                               is right in his special environment.
+                       </p>
+                       <p>
+                               IPFire 
+                       </p>
+               </div>
+               <div id="pakfire">
+                       <p>
+                               From the technical point of view, IPFire is a very shrinked and hardened firewall system
+                               which comes with an integrated package manager that is called <a href="/features/pakfire">Pakfire</a>.
+                               With only a single click you can extend your system to a server that provides services from different
+                               categories.
+                       </p>
+                       <p>
+                               The most interesting addons:
+                       </p>
+                       <ul>
+                               <!-- XXX make this right -->
+                               <li>File services like: Samba and vsftpd</li>
+                               <li>A collection of command line tools like: tcpdump, nmap and traceroute.</li>
+                               <li>Asterisk</li>
+                               <li><em>{{ _("and many more...") }}</em></li>
+                       </ul>
+                       <p class="links">
+                               <a href="http://wiki.ipfire.org/{{ lang }}/configuration/ipfire/pakfire/start">
+                                       {{ _("How to install a package?") }}
+                               </a>
+                               &bull;
+                               <a href="http://wiki.ipfire.org/{{ lang }}/addons/start">
+                                       {{ _("Full list of installable addons") }}
+                               </a>
+                       </p>
+               </div>
+               <div id="firewall">
+                       <p>
+                               IPFire comes with a SPI (stateful inspection) firewall which is built on top of the
+                               Linux <a href="http://www.netfilter.org/">netfilter</a>.
+                       </p>
+                       <p>
+                               With the installation of IPFire, the network gets seperated into different parts that
+                               represent a special kind of computers with their own level of security:
+                       </p>
+                       <ul>
+                               <li style="color: green;">
+                                       <strong>Green:</strong> The segment of the network which is marked by the colour <em>green</em>,
+                                       which stands for a safe area, is where all client computers get. It is the normal LAN and normally
+                                       wired. Clients can access all other segments without any restriction.
+                               </li>
+                               <li style="color: red;">
+                                       <strong>Red:</strong> The internet as a source of danger gets the colour <em>red</em>.
+                                       No access from the internet is permitted to pass the firewall.
+                               </li>
+                               <li style="color: darkblue;">
+                                       <strong>Blue:</strong> The wireless LAN is an other source of potential harm. So it is seperated
+                                       and got the colour <em>blue</em> for "air". Clients on this part of the network must be
+                                       allowed explicitely to access the internet.
+                               </li>
+                               <li style="color: orange;">
+                                       <strong>Orange:</strong> If there are any servers that are accessable by the internet, it is
+                                       also possible to take them over. For this case, there is the segment coloured <em>orange</em>
+                                       (some compromise between red and green) so that those machines are not able to harm any
+                                       other segment. This is called demilizarized zone (DMZ).
+                               </li>
+                       </ul>
+                       <br class="clear" />
+                       <p>
+                               So there is a best place for every machine in the network. All the segments can be activated seperately
+                               (except green and red are always required).
+                               <br />
+                               On top of all of that, there is an <strong>outgoing firewall</strong> for filtering the egress direction.
+                       </p>
+                       <p class="links">
+                               <a href="http://wiki.ipfire.org/en/configuration/firewall/outgoingfirewall">{{ _("Outgoing firewall configuration") }}</a>
+                       </p>
+               </div>
+               <div id="vpn">
+                       <p>
+                               IPFire can be enhanced to a VPN (virtual private network) gateway that connects places and
+                               persons to the local network. This could either be staff, friends and people you want to share
+                               data with in a secure way but also could be a branch office, important customer or an other
+                               company you are operating with.
+                       </p>
+                       <p>
+                               To be able to dock on diffent technologies IPFire offers these implementations:
+                       </p>
+                       <ul>
+                               <li><strong>IPSec</strong> to connect networks side-by-side (also is called net-to-net).</li>
+                               <li>To connect so called <em>roadwarrior clients</em> there is <strong>OpenVPN</strong>.</li>
+                       </ul>
+                       <!-- XXX there is too less margin on the buttom of this list, so: -->
+                       <br class="clear" />
+                       <p>
+                               Those implementations let IPFire connect to routers or VPN gateways by:
+                               <a href="http://www.cisco.com">Cisco</a>, <a href="http://www.juniper.net">Juniper</a>,
+                               other Linux-based implementations and many more...
+                       </p>
+                       <p class="links">
+                               <!-- XXX a link to the wiki goes here -->
+                               <a href="http://wiki.ipfire.org">{{ _("Learn more about configuring a VPN connection") }}</a>
+                       </p>
+               </div>
+               <div id="hardware">
+                       <p>
+                               Based on a recent version of the Linux kernel 2.6 series, IPFire supports latest hardware
+                               like 10G network cards and wireless hardware out of the box.
+                       </p>
+                       <p>
+                               Developers are concerned about keeping the system running on many variations as
+                               possible what makes IPFire run on cheap hardware as well as running on high
+                               performance servers.
+                       </p>
+                       <p class="links">
+                               <a href="http://wiki.ipfire.org/{{ lang }}/hardware/start">Hardware section on the wiki</a>
+                               &bull;
+                               <a href="http://wiki.ipfire.org/{{ lang }}/hardware/networking">Hardware compatibility list (networking)</a>
+                       </p>
+               </div>
+               <div id="virtualization">
+                       <p>
+                               IPFire can be run as a virtual guest on the following hypervisors:
+                       </p>
+                       <ul>
+                               <li><a href="http://www.linux-kvm.org">KVM</a>/Qemu</li>
+                               <li>Xen (paravirtualized and fully virtualized mode)</li>
+                               <li>VMWare (Workstation, vSphere, ESXi, ...)</li>
+                               <li>Virtualbox</li>
+                       </ul>
+                       <br class="clear" />
+                       <p>
+                               It brings many frontend drivers for high performnce for all the hypervisors.
+                       </p>
+               </div>
+               <div id="community">
+                       <p>
+                               IPFire has got a great community which is a very important thing for the success of the project.
+                       </p>
+                       <p>
+                               There is a nice relationship between all people that are involved into the project
+                               so they are all pull together and make IPFire to the nice project that it is today.
+                       </p>
+                       <p>
+                               Everybody can become part of the community. <a href="/join">Learn more</a>.
+                       </p>
+               </div>
+               <div id="itsfree">
+                       <p>
+                               As IPFire is licensed under the <a href="http://www.gnu.org/licenses/gpl.html">GNU General
+                               Public License</a> in version 3 it is free of charge.
+                       </p>
+                       <p>
+                               There is the opportunity to make a <a href="/donation">donation</a>.
+                       </p>
+               </div>
+       </div>
+{% end block %}
+
+{% block javascript %}
+       <script type="text/javascript">
+               $(function() {
+                       $("#tabs").tabs();
+               });
+       </script>
+{% end block %}
index e807bf57f2d238c083ca992d85e2d986ff8c7345..fb59a579f212690bc056f4585f938657a9c39a6e 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-2.html" %}
                
 {% block title %}{{ _("Artwork") }}{% end block %}
  
@@ -89,7 +89,7 @@
        </div>
 {% end block %}
 
-{{ modules.SidebarBanner(banner) }}
+{{ modules.SidebarBanner() }}
 
 {% block sidebar %}
                <h4><span>Artwork</span> list</h4>
index 3d13424c60d53fa9cbeb17d09cc1d28ddaf40407..837c47055008c3de9f4827509fbaab6796cd540c 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-2.html" %}
                
 {% block title %}CeBIT-Special{% end block %}
 
                </ul>
        {% end %}
 
-{{ modules.SidebarBanner(banner) }}
+{{ modules.SidebarBanner() }}
 
 {% end block %}
 
index 35c3793b1d2a88af95c0f460be2a60fcaef2fc97..b74aa5de9aaca5635d49422e9f824bcd5ab22403 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-2.html" %}
                
 {% block title %}{{ _("Development") }}{% end block %}
 
 
                
        
- {{ modules.SidebarBanner(banner) }}
+ {{ modules.SidebarBanner() }}
 
 {% end block %}
index d41a009333b75e35b413c86158bc7a63265a064f..cb12bee4db830b5b81198ff9cc9067969ca90d6c 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-1.html" %}
                
 {% block title %}{{ _("Donation") }}{% end block %}
  
index 8e7c65f20a4069c125e7d2c7800cad5da37c2d42..649464eafdd842b02b72d416f87d50190125f945 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-2.html" %}
                
 {% block title %}{{ _("Features") }}{% end block %}
 
index 8d0356cfbce279fd52c88ffc53f978d347dea5e2..8d1dd5d55b0393cd1b02cf70d0e1ffea855d5fc5 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "../base.html" %}
+{% extends "../base-1.html" %}
 
 {% block title %}{{ _("Imprint") }}{% end block %}
 
                <p>
                        Because of the fact that the people who started this project are living
                        in Germany and the German law demands it. This side is only available in german language, 
-                       so please have a look at the <a href="{{ lang_link("de") }}">german legal notes</a>.
+                       so please have a look at the german legal notes.
                </p>
        {% end %}
        </div>
index d8901ad785e92d15a67d23089eff04f166d88bc9..1e8de3864c4accda60ea82171fbd0319843a93d5 100644 (file)
@@ -1,10 +1,8 @@
-{% extends "../base.html" %}
+{% extends "../base-1.html" %}
                
 {% block title %}{{ _("Screenshots") }}{% end block %}
 
 {% block content %}
-       
-       
        <div class=post>
                <h3>{{ _("Sceenshots") }}</h3>
                <img src="{{ static_url("images/page_icons/screenshots.png") }}" class="floatTR" border="0" alt="{{ _("Sceenshots") }}" />
        
 {% end block %}
 
-{% block sidebar %}
-               <h4><span>{{ _("Screenshot") }}</span> list</h4>
-               <ul class="links">              
-                       <li class="first"><a href="#{{ _("System") }}">{{ _("System") }}</a></li>
-                       <li><a href="#{{ _("Status") }}">{{ _("Status") }}</a></li>
-                       <li><a href="#{{ _("Network") }}">{{ _("Network") }}</a></li>
-                       <li><a href="#{{ _("Services") }}">{{ _("Services") }}</a></li>
-                       <li><a href="#{{ _("Firewall") }}">{{ _("Firewall") }}</a></li>
-                       <li><a href="#{{ _("IPFire") }}">{{ _("IPFire") }}</a></li>
-                       <li><a href="#{{ _("Logs") }}">{{ _("Logs") }}</a></li>
-               </ul>
- {{ modules.SidebarBanner(banner) }}
-
-{% end block %}
-
 {% block javascript %}
        <script type="text/javascript" src="{{ static_url("js/jquery.lightbox.min.js") }}"></script>
        <script type="text/javascript">
diff --git a/www/templates/static/support.html b/www/templates/static/support.html
new file mode 100644 (file)
index 0000000..36c0fd7
--- /dev/null
@@ -0,0 +1,86 @@
+{% extends "../base-1.html" %}
+               
+{% block title %}{{ _("Getting support") }}{% end block %}
+
+{% block content %}
+       <h3>{{ _("Getting support") }}</h3>
+       <img src="{{ static_url("images/page_icons/features.png") }}" class="floatTR" alt="{{ _("Features") }}" />
+
+       <table>
+               <tr>
+                       <td>
+                               <!-- XXX need an image right here -->
+                               <a href="http://wiki.ipfire.org/{{ lang }}/start" target="_blank">WIKI</a>
+                       </td>
+                       <td>
+                               <p>
+                                       The first place to go when you need help if the <a href="http://wiki.ipfire.org/{{ lang }}/start">wiki</a>.
+                                       It is a collection of pages which will answer most of the question that may come up.
+                                       <br />
+                                       Really important guides are:
+                               </p>
+                               <ul>
+                                       <li><a href="http://wiki.ipfire.org/{{ lang }}/installation/start">{{ _("Installation guide") }}</a>
+                                               - {{ _("Learn how to start.") }}</li>
+                                       <!-- XXX need to find some more -->
+                                       <li>...</li>
+                               </ul>
+                               <p class="note">
+                                       The wiki is maintained by community. Feel free to log on with your account and share your
+                                       knowledge. This is the only way to get the wiki to the best source of information about IPFire.
+                               </p>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               <a href="http://forum.ipfire.org/" target="_blank">FORUM</a>
+                       </td>
+                       <td>
+                               The second place is the <a href="http://forum.ipfire.org">forum</a>. If your question was not answered in
+                               the wiki you may probably want to ask it there.
+                       </td>
+               </tr>
+               <tr>
+                       <td colspan="2">&nbsp; <!-- just some space --></td>
+               </tr>
+               <tr>
+                       <td>
+                               <a href="http://bugtracker.ipfire.org/" target="_blank">BUGTRACKER</a>
+                       </td>
+                       <td>
+                               <p>
+                                       If you are sure (and only if) you found a bug in the IPFire system, you may file it against the
+                                       developers.
+                                       <br />
+                                       This is a way to improve the quality of IPFire.
+                               </p>
+                               <p>
+                                       For historical reasons there are two bug tracking systems available. We use
+                                       <a href="http://www.mantisbt.org/">mantis</a> for IPFire 2.x and earlier versions
+                                       which is replaced in the development of version 3.x by <a href="http://www.redmine.org">redmine</a>.
+                               </p>
+                               <ul>
+                                       <li><a href="http://mantis.ipfire.org/">Development for IPFire 2.x</a></li>
+                                       <li><a href="http://redmine.ipfire.org/">Development for IPFire 3.x</a></li>
+                               </ul>
+                       </td>
+               </tr>
+               <tr>
+                       <td>
+                               IRC
+                       </td>
+                       <td>
+                               <p>
+                                       If you need immediate help there is the IRC channel where you can meet fellow IPFire users
+                                       that may get their hands on your configuration and check it out together.
+                               </p>
+                               <p>
+                                       <strong>{{ _("Server") }}:</strong> irc.freenode.net
+                                       <br />
+                                       <strong>{{ _("Channel") }}:</strong> #ipfire
+                               </p>
+                       </td>
+               </tr>
+       </table>
+
+{% end block %}
diff --git a/www/templates/tracker-torrent-detail.html b/www/templates/tracker-torrent-detail.html
new file mode 100644 (file)
index 0000000..6683f17
--- /dev/null
@@ -0,0 +1,43 @@
+{% extends "base-1.html" %}
+
+{% block title %}{{ _("Torrent Downloads") }}{% end block %}
+
+{% block content %}
+       <div class="post">
+               <a name="latest"></a>
+               <h3>{{ _("IPFire Torrent Tracker") }}</h3>
+
+               <!-- <img src="{{ static_url("images/page_icons/download-torrents.png") }}"
+                       class="floatTR" border="0" alt="{{ _("IPFire Torrent Tracker") }}" /> -->
+
+               <table class="download-torrent-detail">
+                       <tr>
+                               <td>{{ _("Release") }}</td>
+                               <td>
+                                       <a href="http://downloads.ipfire.org/release/{{ release.id }}">{{ release.name }}</a>
+                               </td>
+                       </tr>
+                       <tr>
+                               <td>{{ _("Hash") }}</td>
+                               <td>{{ release.torrent_hash }}</td>
+                       </tr>
+               </table>
+
+               {% if torrent.peers %}
+                       <h4>{{ _("Peers") }}</h4>
+                       {{ modules.TrackerPeerList(torrent.peers, percentages=True) }}
+               {% end %}
+
+               {% if torrent.seeds %}
+                       <h4>{{ _("Seeds") }}</h4>
+                       {{ modules.TrackerPeerList(torrent.seeds) }}
+               {% end %}
+
+               <br class="clear" />
+
+               <p>
+                       <a href="/">{{ _("See list of all torrents on this tracker.") }}</a>
+               </p>
+       </div>
+{% end block %}
+
similarity index 66%
rename from www/templates/downloads-torrents.html
rename to www/templates/tracker-torrents.html
index 4c2f181b09a0471b2b471f89b8d0a251ef6847fe..2bb5eefbb223862ac113da9875865265096ca007 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base-1.html" %}
 
 {% block title %}{{ _("Torrent Downloads") }}{% end block %}
 
@@ -12,7 +12,7 @@
 
                {% if lang == "de" %}
                        <p>
-                               Auf dieser Seite findet man eine Liste fast aller Dateien, die
+                               Auf dieser Seite findet man eine Liste aller Releases, die
                                Ã¼ber den IPFire-Torrent-Tracker verteilt werden.
                        <p>
                        <p>
                        </p>
                {% end %}
                
-                               <br class="clear" />
-
+               <br class="clear" />
                
                <table class="download-torrents">
                        <tr>
                                <th>{{ _("Release") }}</th>
-                               <th>{{ _("File") }}</th>
-                               <th class="seeds">S</th>
-                               <th class="peers">P</th>
-                               <th>&nbsp;</th>
+                               <th class="seeds">{{ _("Seeders") }}</th>
+                               <th class="peers">{{ _("Peers") }}</th>
                        </tr>
-                       {% for release in releases %}
+                       {% for torrent in torrents %}
                                <tr>
-                                       <td>{{ release.name }}</td>
-                                       <td>{{ release.torrent.file[:-8] }}</td>
-                                       <td class="seeds">{{ hashes[release.torrent.hash]["seeds"] }}</td>
-                                       <td class="peers">{{ hashes[release.torrent.hash]["peers"] }}</td>
-                                       <td class="download">
-                                               <a href="{{ release.torrent.url }}">{{ _("Download file") }}</a>
-                                       </td>
+                                       <td><a href="torrent/{{ torrent.hash }}">{{ torrent.name }}</a></td>
+                                       <td class="seeds">{{ torrent.seeds }}</td>
+                                       <td class="peers">{{ torrent.peers }}</td>
                                </tr>
                        {% end %}
                </table>
-               
-               <p class="right">
-                       {{ _("Got that information from <em>%s</em> within %.2f second(s).") % (tracker, request_time) }}
-               </p>
 
                <br class="clear" />
        </div>
index 7bfa12ac668c17ee0c00a1f4787c7b96d5df9db7..f6d0a4c1e179c0e2e1365b309a9cc246fdfcd75a 100644 (file)
 "Torrent Downloads","Torrent-Downloads"
 "Development Downloads","Experimentelle Downloads"
 "Ancient Downloads","Veraltete Downloads"
+"1 second ago","Vor einer Sekunde"
+"%(seconds)d seconds ago","vor %(seconds)d Sekunden"
+"1 minute ago","Vor einer Minute"
+"%(minutes)d minutes ago","Vor %(minutes)d Minuten"
+"%(time)s","%(time)s"
+"yesterday","gestern"
+"yesterday at %(time)s","gestern um %(time)s"
+"%(weekday)s","%(weekday)s"
+"%(weekday)s at %(time)s","%(weekday)s um %(time)s"
+"%(month_name)s %(day)s","%(day)s. %(month_name)s"
+"%(month_name)s %(day)s at %(time)s","%(day)s. %(month_name)s um %(time)s"
+"%(month_name)s %(day)s, %(year)s","%(day)s. %(month_name)s %(year)s"
+"%(month_name)s %(day)s, %(year)s at %(time)s","%(day)s. %(month_name)s %(year)s um %(time)s"
+"Posted by %s on","Gepostet von %s am"
+"January","Januar"
+"February","Februar"
+"March","März"
+"April","April"
+"May","Mai"
+"June","Juni"
+"July","Juli"
+"August","August"
+"September","September"
+"October","Oktober"
+"Nomvember","November"
+"December","Dezember"
+"Monday","Montag"
+"Tuesday","Dienstag"
+"Wednesday","Mittwoch"
+"Thursday","Donnerstag"
+"Friday","Freitag"
+"Saturday","Samstag"
+"Sunday","Sonntag"
+"Read more","Mehr..."
+"Last update","Letzte Aktualisierung"
+"Out of synchronization","Nicht synchronisiert"
+"Up","Online"
+"Down","Offline"
+"Unknown","Unbekannt"
+"List of servers","Liste der Server"
+"List of all mirror servers","Liste aller Mirror-Server"
+"Flexibility","Flexibilität"
+"Security","Sicherheit"
+"Learn more.","Mehr erfahren."
+"Get involved.","Mitmachen."
+"See what makes IPFire so great:","Was macht IPFire so besonders?"
+"Subscribe to the latest news of IPFire","Neuigkeiten von IPFire abbonieren"
+"See newer entries","Aktuellere Beiträge"
+"See older entries","Ältere Beiträge"
+"Posted by","Gepostet von"
+"on","am"
+"Last updated on","Zuletzt aktualisiert am"
+"%s did no postings, yet.","%s hat bisher noch keine Beiträge verfasst."
+"People on the planet","Vom Planeten IPFire..."
+"About","Über IPFire"
+"Recent news","Vorangegangene Neuigkeiten"
+"All posts","Alle Einträge"
+"All rights reserved.","Alle Rechte vorbehalten."
+"release information","Release-Informationen"
+"Quick links","Links"
+"About IPFire","Über IPFire"
+"<< back to home","<< Zurück zur Startseite"
+"State","Status"
+"Open mirror","Mirror Ã¶ffnen"
\ No newline at end of file
index bb5c16730a4bda4e1576fb156365c63eae6ba939..7f25974e59d767e7c973e9930733e90f32cd521d 100755 (executable)
@@ -7,37 +7,28 @@ import os
 import signal
 import sys
 
-import tornado.httpserver
-import tornado.ioloop
+#import tornado.httpserver
+#import tornado.ioloop
 import tornado.options
 
 from webapp import Application
 
 tornado.options.parse_command_line()
 
-def setupLogging():
-       formatter = logging.Formatter("%(asctime)s %(levelname)8s %(message)s")
-
-       #handler = logging.handlers.RotatingFileHandler("webapp.log",
-       #       maxBytes=10*1024**2, backupCount=5)
-       handler = logging.FileHandler("webapp.log")
-
-       handler.setFormatter(formatter)
-       logging.getLogger().addHandler(handler)
-
 if __name__ == "__main__":
-       setupLogging()
        app = Application()
 
        context = daemon.DaemonContext(
                working_directory=os.getcwd(),
-               stdout=sys.stdout, stderr=sys.stderr, # XXX causes errors...
+#              stdout=sys.stdout, stderr=sys.stderr, # XXX causes errors...
        )
 
        context.signal_map = {
                signal.SIGHUP  : app.reload,
-               signal.SIGTERM : app.stop,
+               signal.SIGTERM : app.shutdown,
        }
 
-       with context:
-               app.run()
+#      with context:
+#              app.run()
+
+       app.run()
index 82478dd379cf9044c2447866cca9cb66da4c04e5..550161a8afe7673641c5b35fd3199447ffc616d0 100644 (file)
@@ -3,47 +3,48 @@
 import logging
 import os.path
 import simplejson
-
 import tornado.httpserver
 import tornado.locale
 import tornado.options
 import tornado.web
 
-import datastore
+import backend
 
 from handlers import *
 from ui_modules import *
 
 BASEDIR = os.path.join(os.path.dirname(__file__), "..")
 
+# Enable logging
+tornado.options.enable_pretty_logging()
+tornado.options.parse_command_line()
+
 tornado.locale.load_translations(os.path.join(BASEDIR, "translations"))
 
 class Application(tornado.web.Application):
        def __init__(self):
                settings = dict(
                        cookie_secret = "aXBmaXJlY29va2llc2VjcmV0Cg==",
-                       #debug = True,
+                       debug = True,
                        gzip = True,
                        login_url = "/login",
                        template_path = os.path.join(BASEDIR, "templates"),
                        ui_modules = {
-                               "Build"          : BuildModule,
                                "Menu"           : MenuModule,
-                               "MenuItem"       : MenuItemModule,
+                               "MirrorItem"     : MirrorItemModule,
                                "NewsItem"       : NewsItemModule,
+                               "NewsLine"       : NewsLineModule,
                                "PlanetEntry"    : PlanetEntryModule,
                                "ReleaseItem"    : ReleaseItemModule,
                                "SidebarBanner"  : SidebarBannerModule,
-                               "SidebarItem"    : SidebarItemModule,
                                "SidebarRelease" : SidebarReleaseModule,
+                               "TrackerPeerList": TrackerPeerListModule,
                        },
                        xsrf_cookies = True,
                )
 
                tornado.web.Application.__init__(self, **settings)
 
-               self.ds = datastore.DataStore(self)
-
                self.settings["static_path"] = static_path = os.path.join(BASEDIR, "static")
                static_handlers = [
                        (r"/static/(.*)", tornado.web.StaticFileHandler, dict(path = static_path)),
@@ -52,25 +53,35 @@ class Application(tornado.web.Application):
                ]
 
                self.add_handlers(r"(dev|www)\.ipfire\.(at|org)", [
-                       # Entry sites that lead the user to index
-                       (r"/", MainHandler),
-                       (r"/[A-Za-z]{2}/?", MainHandler),
-                       #
-                       (r"/[A-Za-z]{2}/index", IndexHandler),
-                       (r"/[A-Za-z]{2}/news", NewsHandler),
-                       (r"/[A-Za-z]{2}/builds", BuildHandler),
+                       # Entry site that lead the user to index
+                       (r"/", IndexHandler),
+                       (r"/index\.?(s?html?)?", RootHandler),
+
+                       # Handle news items
+                       #(r"/news/(.*)", NewsRedirectHandler),
+                       (r"/news", NewsIndexHandler),
+                       (r"/news/(.*)", NewsItemHandler),
+                       (r"/author/(.*)", NewsAuthorHandler),
+
                        # Download sites
-                       (r"/[A-Za-z]{2}/downloads?", DownloadHandler),
-                       (r"/[A-Za-z]{2}/downloads?/all", DownloadAllHandler),
-                       (r"/[A-Za-z]{2}/downloads?/development", DownloadDevelopmentHandler),
-                       (r"/[A-Za-z]{2}/downloads?/mirrors", DownloadMirrorHandler),
-                       (r"/[A-Za-z]{2}/downloads?/torrents", DownloadTorrentHandler),
-                       # RSS feed
-                       (r"/([A-Za-z]{2})/news.rss", RSSHandler),
-                       (r"/data/feeds/main-([A-Za-z]{2}).rss", RSSHandler),
+                       (r"/downloads?", DownloadHandler),
+#                      # RSS feed
+#                      (r"/([A-Za-z]{2})/news.rss", RSSHandler),
+#                      (r"/data/feeds/main-([A-Za-z]{2}).rss", RSSHandler),
+
+                       (r"/(de|en)/(.*)", LangCompatHandler)
+
+               ] + static_handlers + [
                        # Always the last rule
-                       (r"/[A-Za-z]{2}/(.*)", StaticHandler),
-               ] + static_handlers)
+                       (r"/(.*)", StaticHandler),
+               ])
+
+               # news.ipfire.org
+               #self.add_handlers(r"news\.ipfire\.org", [
+               #       (r"/", NewsIndexHandler),
+               #       (r"/news/(.*)", NewsItemHandler),
+               #       (r"/author/(.*)", NewsAuthorHandler),
+               #] + static_handlers)
 
                # download.ipfire.org
                self.add_handlers(r"download\.ipfire\.org", [
@@ -79,51 +90,72 @@ class Application(tornado.web.Application):
                        (r"/(.*)", DownloadFileHandler),
                ])
 
+               # downloads.ipfire.org
+               self.add_handlers(r"downloads\.ipfire\.org", [
+                       (r"/", DownloadsIndexHandler),
+                       (r"/latest", DownloadsLatestHandler),
+                       (r"/release/([0-9]+)", DownloadsReleaseHandler),
+                       (r"/older", DownloadsOlderHandler),
+                       (r"/development", DownloadsDevelopmentHandler),
+                       (r"/mirrors", tornado.web.RedirectHandler, { "url" : "http://mirrors.ipfire.org/" }),
+                       (r"/source", tornado.web.RedirectHandler, { "url" : "http://source.ipfire.org/" }),
+               ] + static_handlers)
+
+               # mirrors.ipfire.org
+               self.add_handlers(r"mirrors\.ipfire\.org", [
+                       (r"/", MirrorIndexHandler),
+                       (r"/mirror/([0-9]+)", MirrorItemHandler),
+               ] + static_handlers)
+
                # planet.ipfire.org
                self.add_handlers(r"planet\.ipfire\.org", [
-                       (r"/", MainHandler),
-                       (r"/[A-Za-z]{2}/?", MainHandler),
-                       (r"/[A-Za-z]{2}/index", PlanetMainHandler),
+                       (r"/", PlanetMainHandler),
                        (r"/post/([A-Za-z0-9_-]+)", PlanetPostingHandler),
                        (r"/user/([a-z0-9]+)", PlanetUserHandler),
                ] + static_handlers)
 
-               # source.ipfire.org
-               self.add_handlers(r"source\.ipfire\.org", [
-                       (r"/", MainHandler),
-                       (r"/[A-Za-z]{2}/?", MainHandler),
-                       (r"/[A-Za-z]{2}/index", SourceHandler),
-                       (r"(/source.*|/toolchains/.*)", SourceDownloadHandler),
+               # stasy.ipfire.org
+               self.add_handlers(r"stasy\.ipfire\.org", [
+                       (r"/", StasyIndexHandler),
+                       (r"/profile/([a-z0-9]{40})", StasyProfileHandler),
                ] + static_handlers)
 
-               # torrent.ipfire.org
-               self.add_handlers(r"torrent\.ipfire\.org", [
-                       (r"/", MainHandler),
-                       (r"/[A-Za-z]{2}/?", MainHandler),
-                       (r"/[A-Za-z]{2}/index", DownloadTorrentHandler),
-               ] + static_handlers)
+               # source.ipfire.org
+#              self.add_handlers(r"source\.ipfire\.org", [
+#                      (r"/", MainHandler),
+#                      (r"/[A-Za-z]{2}/?", MainHandler),
+#                      (r"/[A-Za-z]{2}/index", SourceHandler),
+#                      (r"(/source.*|/toolchains/.*)", SourceDownloadHandler),
+#              ] + static_handlers)
 
                # tracker.ipfire.org
-               self.add_handlers(r"tracker\.ipfire\.org", [
-                       (r"/", MainHandler),
-                       (r"/[A-Za-z]{2}/?", MainHandler),
-                       (r"/[A-Za-z]{2}/index", DownloadTorrentHandler),
+               self.add_handlers(r"(torrent|tracker)\.ipfire\.org", [
+                       (r"/", TrackerIndexHandler),
                        (r"/a.*", TrackerAnnounceHandler),
                        (r"/scrape", TrackerScrapeHandler),
+                       (r"/torrent/([0-9a-f]+)", TrackerDetailHandler),
                ] + static_handlers)
 
                # admin.ipfire.org
                self.add_handlers(r"admin\.ipfire\.org", [
                        (r"/", AdminIndexHandler),
-                       (r"/login", AuthLoginHandler),
-                       (r"/logout", AuthLogoutHandler),
+                       (r"/login", AdminLoginHandler),
+                       (r"/logout", AdminLogoutHandler),
                        # Accounts
                        (r"/accounts", AdminAccountsHandler),
-                       (r"/accounts/edit/([0-9]+)", AdminAccountsEditHandler),
+                       #(r"/accounts/delete/([0-9]+)", AdminAccountsDeleteHandler),
+                       #(r"/accounts/edit/([0-9]+)", AdminAccountsEditHandler),
                        # Planet
                        (r"/planet", AdminPlanetHandler),
                        (r"/planet/compose", AdminPlanetComposeHandler),
                        (r"/planet/edit/([0-9]+)", AdminPlanetEditHandler),
+                       # Mirrors
+                       (r"/mirrors", AdminMirrorsHandler),
+                       (r"/mirrors/create", AdminMirrorsCreateHandler),
+                       (r"/mirrors/delete/([0-9]+)", AdminMirrorsDeleteHandler),
+                       (r"/mirrors/edit/([0-9]+)", AdminMirrorsEditHandler),
+                       (r"/mirrors/details/([0-9]+)", AdminMirrorsDetailsHandler),
+                       (r"/mirrors/update", AdminMirrorsUpdateHandler),
                        # API
                        (r"/api/planet/render", AdminApiPlanetRenderMarkupHandler)
                ] + static_handlers)
@@ -140,15 +172,11 @@ class Application(tornado.web.Application):
        def __del__(self):
                logging.info("Shutting down application")
 
-       @property
-       def db(self):
-               return self.ds.db
-
        @property
        def ioloop(self):
                return tornado.ioloop.IOLoop.instance()
 
-       def stop(self, *args):
+       def shutdown(self, *args):
                logging.debug("Caught shutdown signal")
                self.ioloop.stop()
 
@@ -156,18 +184,21 @@ class Application(tornado.web.Application):
 
        def run(self, port=8001):
                logging.debug("Going to background")
+               
+               # All requests should be done after 30 seconds or they will be killed.
+               self.ioloop.set_blocking_log_threshold(30)
 
                http_server = tornado.httpserver.HTTPServer(self, xheaders=True)
-               http_server.listen(port)
-
-               self.ioloop.start()
 
-               while self.__running:
-                       time.sleep(1)
+               # If we are not running in debug mode, we can actually run multiple
+               # frontends to get best performance out of our service.
+               if not self.settings["debug"]:
+                       http_server.bind(port)
+                       http_server.start(num_processes=4)
+               else:
+                       http_server.listen(port)
 
-                       self.ds.trigger()
+               self.ioloop.start()
 
        def reload(self):
                logging.debug("Caught reload signal")
-
-               self.ds.reload()
diff --git a/www/webapp/backend/__init__.py b/www/webapp/backend/__init__.py
new file mode 100644 (file)
index 0000000..c685c19
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+from accounts  import Accounts
+from banners   import Banners
+from geoip             import GeoIP
+from menu              import Menu
+from mirrors   import Mirrors
+from news              import News
+from planet            import Planet
+from releases  import Releases
+from settings  import Settings as Config
+from tracker   import Tracker
diff --git a/www/webapp/backend/accounts.py b/www/webapp/backend/accounts.py
new file mode 100644 (file)
index 0000000..a0c0726
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/python
+
+import hashlib
+import ldap
+import urllib
+
+from misc import Singleton
+from settings import Settings
+
+class Accounts(object):
+       __metaclass__ = Singleton
+
+       def __init__(self):
+               self.__db = None
+
+               self._init()
+
+       @property
+       def search_base(self):
+               return Settings().get("ldap_search_base")
+
+       @property
+       def db(self):
+               if not self.__db:
+                       ldap_uri = Settings().get("ldap_uri")
+
+                       self.__db = ldap.initialize(ldap_uri)
+                       
+                       bind_dn = Settings().get("ldap_bind_dn")
+                       if bind_dn:
+                               bind_pw = Settings().get("ldap_bind_pw")
+
+                               self.__db.simple_bind(bind_dn, bind_pw)
+
+               return self.__db
+
+       def get(self, dn):
+               return self._accounts[dn]
+
+       def _init(self):
+               self._accounts = {}
+               results = self.db.search_s(self.search_base, ldap.SCOPE_SUBTREE,
+                       "(objectClass=posixAccount)", ["loginShell"])
+
+               for dn, attrs in results:
+                       #if attrs["loginShell"] == ["/bin/bash"]:
+                       self._accounts[dn] = Account(dn)
+
+       def list(self):
+               return sorted(self._accounts.values())
+
+       def find(self, uid):
+               for account in self.list():
+                       if account.uid == uid:
+                               return account
+
+       def delete(self, uid):
+               account = self.find(uid)
+               # XXX
+
+       search = find
+
+
+class Account(object):
+       def __init__(self, dn):
+               self.dn = dn
+
+               self.__attributes = {}
+
+       def __repr__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.dn)
+
+       def __cmp__(self, other):
+               return cmp(self.cn, other.cn)
+
+       @property
+       def db(self):
+               return Accounts().db
+
+       @property
+       def attributes(self):
+               if not self.__attributes:
+                       self.fetch_attributes()
+
+               return self.__attributes
+
+       def fetch_attributes(self):
+               result = self.db.search_ext_s(self.dn, ldap.SCOPE_SUBTREE, sizelimit=1)
+               dn, self.__attributes = result[0]
+
+       def get(self, key):
+               try:
+                       attribute = self.attributes[key]
+               except KeyError:
+                       raise AttributeError(key)
+
+               if len(attribute) == 1:
+                       return attribute[0]
+
+               return attribute
+
+       __getattr__ = get
+
+       def set(self, key, value):
+               mod_op = ldap.MOD_ADD
+               if self.attributes.has_key(key):
+                       mod_op = ldap.MOD_REPLACE
+
+               self._modify(mod_op, key, value)
+
+       def _modify(self, op, key, value):
+               modlist = [(op, key, value)]
+
+               self.db.modify_s(self.dn, modlist)
+
+               # Update local cache of attributes
+               self.fetch_attributes()
+
+       def delete(self, key, value=None):
+               self._modify(ldap.MOD_DELETE, key, value)
+
+       def check_password(self, password):
+               """
+                       Bind to the server with given credentials and return
+                       true if password is corrent and false if not.
+
+                       Raises exceptions from the server on any other errors.
+               """
+
+               logging.debug("Checking credentials for %s" % self.dn)
+               try:
+                       self.db.simple_bind_s(self.dn, password)
+               except ldap.INVALID_CREDENTIALS:
+                       return False
+
+               return True
+
+       @property
+       def is_admin(self):
+               return True # XXX todo
+
+       @property
+       def email(self):
+               name = self.cn.lower()
+               name = name.replace(" ", ".")
+
+               for mail in self.mail:
+                       if mail.startswith(name + "@"):
+                               return mail
+
+               raise Exception, "Cannot figure out email address"
+
+       def gravatar_icon(self, size=128):
+               # construct the url
+               gravatar_url = "http://www.gravatar.com/avatar/" + \
+                       hashlib.md5(self.email.lower()).hexdigest() + "?"       
+               gravatar_url += urllib.urlencode({'d': "mm", 's': str(size)})
+
+               return gravatar_url
+
+if __name__ == "__main__":
+       a = Accounts()
+
+       print a.list()
diff --git a/www/webapp/backend/banners.py b/www/webapp/backend/banners.py
new file mode 100644 (file)
index 0000000..08db020
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+
+from databases import Databases
+from misc import Singleton
+
+class Banners(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def list(self):
+               return self.db.query("SELECT * FROM banners")
+
+       def get_random(self):
+               return self.db.get("SELECT * FROM banners ORDER BY RAND() LIMIT 1")
+
+
+if __name__ == "__main__":
+       b = Banners()
+
+       print b.list()
+
+       print "--- RANDOM ---"
+
+       for i in range(5):
+               print i, b.get_random()
diff --git a/www/webapp/backend/databases.py b/www/webapp/backend/databases.py
new file mode 100644 (file)
index 0000000..542c935
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+
+import logging
+import tornado.database
+
+from misc import Singleton
+
+class Row(tornado.database.Row):
+       pass
+
+MYSQL_SERVER = "172.28.1.150"
+
+class Databases(object):
+       __metaclass__ = Singleton
+
+       def __init__(self):
+               self._connections = {}
+
+       @property
+       def webapp(self):
+               if not self._connections.has_key("webapp"):
+                       self._connections["webapp"] = \
+                               Connection(MYSQL_SERVER, "webapp", user="webapp")
+
+               return self._connections["webapp"]
+
+       @property
+       def geoip(self):
+               if not self._connections.has_key("geoip"):
+                       self._connections["geoip"] = \
+                               Connection(MYSQL_SERVER, "geoip", user="webapp")
+
+               return self._connections["geoip"]
+
+       @property
+       def tracker(self):
+               if not self._connections.has_key("tracker"):
+                       self._connections["tracker"] = \
+                               Connection(MYSQL_SERVER, "tracker", user="webapp")
+
+               return self._connections["tracker"]
+
+
+class Connection(tornado.database.Connection):
+       def __init__(self, *args, **kwargs):
+               logging.debug("Creating new database connection: %s" % args[1])
+
+               tornado.database.Connection.__init__(self, *args, **kwargs)
+
+       def update(self, table, item_id, **items):
+               query = "UPDATE %s SET " % table
+
+               keys = []
+               for k, v in items.items():
+                       # Never update id
+                       if k == "id":
+                               continue
+
+                       keys.append("%s='%s'" % (k, v))
+
+               query += ", ".join(keys)
+               query += " WHERE id=%s" % item_id
+
+               return self.execute(query)
+
+       def insert(self, table, **items):
+               query = "INSERT INTO %s" % table
+
+               keys = []
+               vals = []
+
+               for k, v in items.items():
+                       # Never insert id
+                       if k == "id":
+                               continue
+
+                       keys.append(k)
+                       vals.append("'%s'" % v)
+
+               query += "(%s)"% ", ".join(keys)
+               query += " VALUES(%s)" % ", ".join(vals)
+
+               return self.execute(query)
+
+       def _execute(self, cursor, query, parameters):
+               logging.debug("Executing query: %s" % (query % parameters))
+
+               return tornado.database.Connection._execute(self, cursor, query, parameters)
diff --git a/www/webapp/backend/geoip.py b/www/webapp/backend/geoip.py
new file mode 100644 (file)
index 0000000..b2cdc09
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+from databases import Databases
+from misc import Singleton
+
+class GeoIP(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().geoip
+
+       def __encode_ip(self, addr):
+               # ip is calculated as described in http://ipinfodb.com/ip_database.php
+               a1, a2, a3, a4 = addr.split(".")
+
+               return int(((int(a1) * 256 + int(a2)) * 256 + int(a3)) * 256 + int(a4) + 100)
+
+       def get_country(self, addr):
+               return self.db.get("SELECT * FROM ip_group_country WHERE ip_start <= %s \
+                       ORDER BY ip_start DESC LIMIT 1;", self.__encode_ip(addr)).country_code.lower()
+
+       def get_all(self, addr):
+               # XXX should be done with a join
+               location = self.db.get("SELECT location FROM ip_group_city WHERE ip_start <= %s \
+                       ORDER BY ip_start DESC LIMIT 1;", self.__encode_ip(addr)).location
+                       
+               return self.db.get("SELECT * FROM locations WHERE id = %s", int(location))
+
+
+if __name__ == "__main__":
+       g = GeoIP()
+
+       print g.get_country("123.123.123.123")
+       print g.get_all("123.123.123.123")
diff --git a/www/webapp/backend/menu.py b/www/webapp/backend/menu.py
new file mode 100644 (file)
index 0000000..a2ecd2a
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+import re
+
+from databases import Databases
+from misc import Singleton
+
+class Menu(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def get(self, host):
+               menu = []
+               for m in self.db.query("SELECT * FROM menu ORDER BY prio ASC"):
+                       try:
+                               if not re.match(m.sites, host) is None:
+                                       menu.append(m)
+                       except re.error:
+                               # Drop all exceptions that occour when matching the expressions.
+                               pass
+
+               return menu
diff --git a/www/webapp/backend/mirrors.py b/www/webapp/backend/mirrors.py
new file mode 100644 (file)
index 0000000..67db5f8
--- /dev/null
@@ -0,0 +1,171 @@
+#!/usr/bin/python
+
+import logging
+import socket
+import time
+import tornado.httpclient
+
+from databases import Databases
+from geoip import GeoIP
+from misc import Singleton
+
+class Mirrors(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def list(self):
+               return [Mirror(m.id) for m in self.db.query("SELECT id FROM mirrors ORDER BY state")]
+
+       def check_all(self):
+               for mirror in self.list():
+                       mirror.check()
+
+       def get(self, id):
+               return Mirror( id)
+
+       def get_by_hostname(self, hostname):
+               mirror = self.db.get("SELECT id FROM mirrors WHERE hostname=%s", hostname)
+
+               return Mirror(mirror.id)
+
+       def get_with_file(self, filename):
+               return [Mirror(m.mirror) for m in \
+                       self.db.query("SELECT mirror FROM mirror_files WHERE filename=%s", filename)]
+
+
+class Mirror(object):
+       def __init__(self, id):
+               self.id = id
+
+               self.reload()
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def reload(self):
+               self._info = self.db.get("SELECT * FROM mirrors WHERE id=%s", self.id)
+               self._info["url"] = self.generate_url()
+
+       def generate_url(self):
+               url = "http://%s" % self.hostname
+               if not self.path.startswith("/"):
+                       url += "/"
+               url += "%s" % self.path
+               if not self.path.endswith("/"):
+                       url += "/"
+               return url
+
+       def __getattr__(self, key):
+               try:
+                       return self._info[key]
+               except KeyError:
+                       raise AttributeError(key)
+
+       @property
+       def address(self):
+               return socket.gethostbyname(self.hostname)
+
+       @property
+       def country_code(self):
+               return GeoIP().get_country(self.address).lower() or "unknown"
+
+       @property
+       def filelist(self):
+               filelist = self.db.query("SELECT filename FROM mirror_files WHERE mirror=%s ORDER BY filename", self.id)
+               return [f.filename for f in filelist]
+
+       def set_state(self, state):
+               logging.info("Setting state of %s to %s" % (self.hostname, state))
+
+               if self.state == state:
+                       return
+
+               self.db.execute("UPDATE mirrors SET state=%s WHERE id=%s",
+                       state, self.id)
+
+               # Reload changed settings
+               self.reload()
+
+       def check(self):
+               logging.info("Running check for mirror %s" % self.hostname)
+
+               self.check_timestamp()
+               self.check_filelist()
+
+       def check_state(self):
+               logging.debug("Checking state of mirror %s" % self.id)
+
+               if self.disabled == "Y":
+                       self.set_state("DOWN")
+
+               time_diff = time.time() - self.last_update
+               if time_diff > 3*24*60*60: # XXX get this into Settings
+                       self.set_state("DOWN")
+               elif time_diff > 6*60*60:
+                       self.set_state("OUTOFSYNC")
+               else:
+                       self.set_state("UP")
+
+       def check_timestamp(self):
+               if self.releases == "N":
+                       return
+
+               http = tornado.httpclient.AsyncHTTPClient()
+
+               http.fetch(self.url + ".timestamp",
+                       headers={"Pragma" : "no-cache", },
+                       callback=self.__check_timestamp_response)
+
+       def __check_timestamp_response(self, response):
+               if response.error:
+                       logging.debug("Error getting timestamp from %s" % self.hostname)
+                       return
+
+               try:
+                       timestamp = int(response.body.strip())
+               except ValueError:
+                       timestamp = 0
+
+               self.db.execute("UPDATE mirrors SET last_update=%s WHERE id=%s",
+                       timestamp, self.id)
+
+               # Reload changed settings
+               self.reload()
+
+               self.check_state()
+
+               logging.info("Successfully updated timestamp from %s" % self.hostname)
+
+       def check_filelist(self):
+               if self.releases == "N":
+                       return
+
+               http = tornado.httpclient.AsyncHTTPClient()
+
+               http.fetch(self.url + ".filelist",
+                       headers={"Pragma" : "no-cache", },
+                       callback=self.__check_filelist_response)
+
+       def __check_filelist_response(self, response):
+               if response.error:
+                       logging.debug("Error getting timestamp from %s" % self.hostname)
+                       return
+
+               self.db.execute("DELETE FROM mirror_files WHERE mirror=%s", self.id)
+
+               for file in response.body.splitlines():
+                       self.db.execute("INSERT INTO mirror_files(mirror, filename) VALUES(%s, %s)",
+                                       self.id, file)
+
+               logging.info("Successfully updated mirror filelist from %s" % self.hostname)
+
+
+if __name__ == "__main__":
+       m = Mirrors()
+
+       for mirror in m.list():
+               print mirror.hostname, mirror.country_code
diff --git a/www/webapp/backend/misc.py b/www/webapp/backend/misc.py
new file mode 100644 (file)
index 0000000..7279a2e
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+
+class Singleton(type):
+       """
+               A class for using the singleton pattern
+       """
+
+       def __init__(cls, name, bases, dict):
+               super(Singleton, cls).__init__(name, bases, dict)
+               cls.instance = None
+       def __call__(cls, *args, **kw):
+               if cls.instance is None:
+                       cls.instance = super(Singleton, cls).__call__(*args, **kw)
+               return cls.instance
diff --git a/www/webapp/backend/news.py b/www/webapp/backend/news.py
new file mode 100644 (file)
index 0000000..f0c3f8c
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+
+from databases import Databases
+from misc import Singleton
+
+class News(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def list(self):
+               return [i.uuid for i in self.db.query("SELECT DISTINCT uuid FROM news ORDER BY date")]
+
+       def get(self, uuid, lang="en"):
+               return self.db.get("SELECT * FROM news WHERE uuid=%s AND lang=%s",
+                       uuid, lang)
+
+       def get_by_slug(self, slug):
+               return self.db.get("SELECT * FROM news WHERE slug=%s", slug)
+
+       def get_latest(self, author=None, locale=None, limit=1, offset=0):
+               # XXX find a better way to do offset
+
+               if offset:
+                       limit += offset
+
+               query = "SELECT * FROM news WHERE published='Y'"
+
+               if author:
+                       query += " AND author_id='%s'" % author
+
+               if locale:
+                       query += " AND lang='%s'" % locale.code[:2]
+
+               query += " ORDER BY date DESC"
+
+               if limit:
+                       query += " LIMIT %d" % limit
+
+               news = self.db.query(query)
+
+               # XXX can the database do this?
+               if offset:
+                       news = news[offset:]
+
+               return news
+
+if __name__ == "__main__":
+       n = News()
+
+       print n.list()
+       print n.get_latest()
diff --git a/www/webapp/backend/planet.py b/www/webapp/backend/planet.py
new file mode 100644 (file)
index 0000000..8323aae
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+
+import textile
+
+from accounts import Accounts
+from databases import Databases
+
+from misc import Singleton
+
+class PlanetEntry(object):
+       def __init__(self, entry):
+               self.__entry = entry
+
+       @property
+       def id(self):
+               return self.__entry.id
+
+       @property
+       def slug(self):
+               return self.__entry.slug
+
+       @property
+       def title(self):
+               return self.__entry.title
+
+       @property
+       def published(self):
+               return self.__entry.published
+
+       @property
+       def updated(self):
+               return self.__entry.updated
+
+       @property
+       def markdown(self):
+               return self.__entry.markdown
+
+       @property
+       def abstract(self):
+               return self.render(self.markdown, 400)
+
+       def render(self, text, limit=0):
+               if limit and len(text) >= limit:
+                       text = text[:limit] + "..."
+               return textile.textile(text)
+
+       @property
+       def text(self):
+               return self.render(self.markdown)
+
+       @property
+       def author(self):
+               return Accounts().search(self.__entry.author_id)
+
+
+class Planet(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def get_authors(self):
+               authors = []
+               for author in self.db.query("SELECT DISTINCT author_id FROM planet"):
+                       author = Accounts().search(author.author_id)
+                       if author:
+                               authors.append(author)
+
+               return authors
+
+       def get_entry_by_slug(self, slug):
+               entry = self.db.get("SELECT * FROM planet WHERE slug = %s", slug)
+               if entry:
+                       return PlanetEntry(entry)
+
+       def _limit_and_offset_query(self, limit=None, offset=None):
+               query = " "
+
+               if limit:
+                       if offset:
+                               query += "LIMIT %d,%d" % (offset, limit)
+                       else:
+                               query += "LIMIT %d" % limit
+
+               return query
+
+       def get_entries(self, limit=3, offset=None):
+               query = "SELECT * FROM planet ORDER BY published DESC"
+
+               # Respect limit and offset              
+               query += self._limit_and_offset_query(limit=limit, offset=offset)
+
+               entries = []
+               for entry in self.db.query(query):
+                       entries.append(PlanetEntry(entry))
+
+               return entries
+
+       def get_entries_by_author(self, author_id, limit=None, offset=None):
+               query = "SELECT * FROM planet WHERE author_id = '%s'" % author_id
+               query += " ORDER BY published DESC"
+
+               # Respect limit and offset              
+               query += self._limit_and_offset_query(limit=limit, offset=offset)
+
+
+               entries = self.db.query(query)
+
+               return [PlanetEntry(e) for e in entries]
diff --git a/www/webapp/backend/releases.py b/www/webapp/backend/releases.py
new file mode 100644 (file)
index 0000000..8dc7e6c
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/python
+
+import logging
+import urlparse
+
+from databases import Databases
+from misc import Singleton
+from settings import Settings
+
+class File(object):
+       def __init__(self, release, id):
+               self.release = release
+
+               # get all data from database
+               self.__data = self.db.get("SELECT * FROM files WHERE id = %s", id)
+
+       @property
+       def db(self):
+               return self.release.db
+
+       @property
+       def type(self):
+               return self.__data.get("filetype")
+
+       @property
+       def url(self):
+               baseurl = Settings().get("download_url")
+
+               return urlparse.urljoin(baseurl, self.filename)
+
+       @property
+       def desc(self):
+               _ = lambda x: x
+
+               descriptions = {
+                       "iso"           : _("Installable CD image"),
+                       "torrent"       : _("Torrent file"),
+                       "flash"         : _("Flash image"),
+                       "alix"          : _("Alix image"),
+                       "usbfdd"        : _("USB FDD Image"),
+                       "usbhdd"        : _("USB HDD Image"),
+                       "xen"           : _("Pregenerated Xen image"),
+               }
+
+               try:
+                       return descriptions[self.type]
+               except KeyError:
+                       return _("Unknown image type")
+
+       @property
+       def prio(self):
+               priorities = {
+                       "iso"           : 10,
+                       "torrent"       : 20,
+                       "flash"         : 40,
+                       "alix"          : 41,
+                       "usbfdd"        : 31,
+                       "usbhdd"        : 30,
+                       "xen"           : 50,
+               }
+               
+               try:
+                       return priorities[self.type]
+               except KeyError:
+                       return 999
+
+       @property
+       def rem(self):
+               _ = lambda x: x
+       
+               remarks = {
+                       "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."),
+                       "alix"          : _("Flash image where a serial console is enabled by default."),
+                       "usbfdd"        : _("Install IPFire from a floppy-formated USB key."),
+                       "usbhdd"        : _("If the floppy image doesn't work, use this image instead."),
+                       "xen"           : _("A ready-to-run image for Xen."),
+               }
+
+               try:
+                       return remarks[self.type]
+               except KeyError:
+                       return _("Unknown image type")
+
+       @property
+       def sha1(self):
+               return self.__data.get("sha1")
+
+       @property
+       def filename(self):
+               return self.__data.get("filename")
+
+
+class Release(object):
+       @property
+       def db(self):
+               return Releases().db
+
+       def __init__(self, id):
+               self.id = id
+
+               # get all data from database
+               self.__data = \
+                       self.db.get("SELECT * FROM releases WHERE id = %s", self.id)
+               assert self.__data
+
+               self.__files = []
+
+       def __repr__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.name)
+
+       @property
+       def files(self):
+               if not self.__files:
+                       files = self.db.query("SELECT id FROM files WHERE releases = %s \
+                                       AND loadable = 'Y'", self.id)
+
+                       self.__files = [File(self, f.id) for f in files]
+                       self.__files.sort(lambda a, b: cmp(a.prio, b.prio))
+
+               return self.__files
+
+       @property
+       def name(self):
+               return self.__data.get("name")
+
+       @property
+       def stable(self):
+               return self.__data.get("stable")
+
+       @property
+       def published(self):
+               return self.__data.get("published")
+
+       @property
+       def date(self):
+               return self.__data.get("date")
+
+       @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:
+                               return file
+
+
+class Releases(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def list(self):
+               return [Release(r.id) for r in self.db.query("SELECT id FROM releases ORDER BY date DESC")]
+
+       def get_by_id(self, id):
+               id = int(id)
+               if id in [r.id for r in self.db.query("SELECT id FROM releases")]:
+                       return Release(id)
+
+       def get_latest(self, stable=1):
+               query = "SELECT id FROM releases WHERE published='Y' AND"
+               if stable:
+                       query += " stable='Y'"
+               else:
+                       query += " stable='N'"
+
+               query += " ORDER BY date DESC LIMIT 1"
+
+               release = self.db.get(query)
+               if release:
+                       return Release(release.id)
+
+       def get_stable(self):
+               releases = self.db.query("""SELECT id FROM releases
+                       WHERE published='Y' AND stable='Y'
+                       ORDER BY date DESC""")
+
+               return [Release(r.id) for r in releases]
+
+       def get_unstable(self):
+               releases = self.db.query("""SELECT id FROM releases
+                       WHERE published='Y' AND stable='N'
+                       ORDER BY date DESC""")
+
+               return [Release(r.id) for r in releases]
+
+       def get_all(self):
+               releases = self.db.query("""SELECT id FROM releases
+                       WHERE published='Y' ORDER BY date DESC""")
+
+               return [Release(r.id) for r in releases]
+
+
+if __name__ == "__main__":
+       r = Releases()
+
+       for release in r.get_all():
+               print release.name
+
+       print r.get_latest()
diff --git a/www/webapp/backend/settings.py b/www/webapp/backend/settings.py
new file mode 100644 (file)
index 0000000..076ea65
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+
+from databases import Databases
+from misc import Singleton
+
+class Settings(object):
+       __metaclass__ = Singleton
+
+       @property
+       def db(self):
+               return Databases().webapp
+
+       def query(self, key):
+               return self.db.get("SELECT * FROM settings WHERE k=%s", key)
+
+       def get(self, key):
+               return "%s" % self.query(key)["v"]
+
+       def get_id(self, key):
+               return self.query(key)["id"]
+
+       def get_int(self, key):
+               value = self.get(key)
+
+               if value is None:
+                       return None
+
+               return int(value)
+
+       def get_float(self, key):
+               value = self.get(key)
+
+               if value is None:
+                       return None
+
+               return float(value)
+
+       def set(self, key, value):
+               id = self.get(key)
+
+               if not id:
+                       self.db.execute("INSERT INTO settings(k, v) VALUES(%s, %s)", key, value)
+               else:
+                       self.db.execute("UPDATE settings SET v=%s WHERE id=%s" % (value, id))
+
+       def get_all(self):
+               attrs = {}
+
+               for s in self.db.query("SELECT * FROM settings"):
+                       attrs[s.k] = s.v
+
+               return attrs
+
+
+if __name__ == "__main__":
+       s = Settings()
+
+       print s.get_all()
similarity index 93%
rename from www/webapp/datastore/tracker.py
rename to www/webapp/backend/tracker.py
index c1572148d8bfb848e6e142e20541830a00f64cc2..663dd2c6df1518e610ddcce0e8c24c9aa856a476 100644 (file)
@@ -2,6 +2,9 @@
 
 import time
 
+from databases import Databases
+from misc import Singleton
+
 def decode_hex(s):
        ret = []
        for c in s:
@@ -9,25 +12,22 @@ def decode_hex(s):
                        if not c == chr(i):
                                continue
 
-                       ret.append("%0x" % i)
+                       ret.append("%02x" % i)
 
        return "".join(ret)
 
 class Tracker(object):
-       id = "The IPFire Torrent Tracker"
+       id = "TheIPFireTorrentTracker"
 
-       # Intervals
+       # Intervals # XXX needs to be in Settings
        interval = 60*60
        min_interval = 30*60
 
        numwant = 50
 
-       def __init__(self, application):
-               self.application = application
-
        @property
        def db(self):
-               return self.application.db.tracker
+               return Databases().tracker
 
        def _fetch(self, hash, limit=None, random=False, completed=False, no_peer_id=False):
                query = "SELECT * FROM peers WHERE last_update >= %d" % self.since
@@ -37,6 +37,8 @@ class Tracker(object):
 
                if completed:
                        query += " AND left_data = 0"
+               else:
+                       query += " AND left_data != 0"
 
                if random:
                        query += " ORDER BY RAND()"
@@ -79,7 +81,7 @@ class Tracker(object):
                if not self.db.query("SELECT id FROM peers WHERE hash = '%s' AND peer_id = '%s'" % (hash, peer_id)):
                        self.db.execute("INSERT INTO peers(hash, peer_id) VALUES('%s', '%s')" % (hash, peer_id))
 
-               if not hash in [h["hash"] for h in self.hashes]:
+               if not hash in self.hashes:
                        self.db.execute("INSERT INTO hashes(hash) VALUES('%s')" % hash)
 
        def event_stopped(self, hash, peer_id):
@@ -132,7 +134,11 @@ class Tracker(object):
 
        @property
        def hashes(self):
-               return self.db.query("SELECT * FROM hashes");
+               hashes = []
+               for h in self.db.query("SELECT hash FROM hashes"):
+                       hashes.append(h["hash"].lower())
+
+               return hashes
 
        @property
        def now(self):
diff --git a/www/webapp/datastore/__init__.py b/www/webapp/datastore/__init__.py
deleted file mode 100644 (file)
index 224f324..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/python
-
-import banners
-#import builds
-#import info
-import menu
-#import mirrors
-import news
-import releases
-import tracker
-
-from connections import *
-
-class Databases(object):
-       def __init__(self, application):
-               self.application = application
-
-               self.webapp = WebappConnection()
-
-               self.config = self.webapp
-               self.hashes = HashConnection()
-               self.mirrors = self.webapp
-               self.planet = self.webapp
-               self.tracker = TrackerConnection()
-
-
-class DataStore(object):
-       def __init__(self, application):
-               self.application = application
-               self.db = Databases(self.application)
-
-               self.reload()
-
-       def reload(self):
-               self.banners = banners.Banners(self.application, "banners.json")
-               self.config = config.Config(self.application)
-#              self.info = info.Info(self.application, "info.json")
-               self.menu = menu.Menu(self.application, "menu.json")
-#              self.mirrors = mirrors.Mirrors(self.application, "mirrors.json")
-               self.news = news.News(self.application, "news.json")
-               self.releases = releases.Releases(self.application, "releases.json")
-               self.tracker = tracker.Tracker(self.application)
-
-       def trigger(self):
-               pass
diff --git a/www/webapp/datastore/banners.py b/www/webapp/datastore/banners.py
deleted file mode 100644 (file)
index 566ba38..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/python
-
-import random
-import simplejson
-
-from tornado.database import Row
-
-class Banners(object):
-       def __init__(self, application, filename=None):
-               self.application = application
-               self.items = []
-
-               if filename:
-                       self.load(filename)
-
-       def load(self, filename):
-               with open(filename) as f:
-                       self.items = [Row(i) for i in simplejson.load(f)]
-
-       def get(self):
-               if self.items:
-                       return random.choice(self.items)
diff --git a/www/webapp/datastore/builds.py b/www/webapp/datastore/builds.py
deleted file mode 100644 (file)
index e94eb0b..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/python
-
-import os
-import time
-
-from helpers import size
-
-def find(info):
-       ret = []
-       for item in info["nightly_builds"]:
-               path = item.get("path", None)
-               if not path or not os.path.exists(path):
-                       continue
-
-               for host in os.listdir(path):
-                       for build in os.listdir(os.path.join(path, host)):
-                               ret.append(Build(os.path.join(path, host, build)))
-
-       return ret
-
-class Build(object):
-       def __init__(self, path):
-               self.path = path
-
-               self.__buildinfo = None
-       
-       @property
-       def buildinfo(self):
-               if not self.__buildinfo:
-                       f = open(os.path.join(self.path, ".buildinfo"))
-                       self.__buildinfo = f.readlines()
-                       f.close()
-               return self.__buildinfo
-
-       def get(self, key):
-               key = key.upper() + "="
-               for line in self.buildinfo:
-                       if line.startswith(key):
-                               return line[len(key):].strip("\n")
-
-       @property
-       def build_host(self):
-               return self.get("hostname")
-
-       @property
-       def release(self):
-               return self.get("release")
-
-       @property
-       def time(self):
-               return time.localtime(float(self.get("date")))
-
-       @property
-       def date(self):
-               return time.strftime("%a, %Y-%m-%d %H:%M", self.time)
-
-       @property
-       def arch(self):
-               return self.get("arch")
-
-       @property
-       def iso(self):
-               return self.get("iso")
-
-       @property
-       def size(self):
-               return size(os.path.getsize(os.path.join(self.path, self.iso)))
-
-       @property
-       def packages(self):
-               path = "%s/packages_%s" % (self.path, self.arch,)
-               if not os.path.exists(path):
-                       return []
-               return os.listdir(path)
-
-       @property
-       def pxe(self):
-               dir = "/srv/www/ipfire.org/pxe"
-               if not os.path.isdir(dir):
-                       return ""
-
-               for iso in os.listdir(dir):
-                       # Skip non-iso files
-                       if not iso.endswith(".iso"):
-                               continue
-                       if os.readlink(os.path.join(dir, iso)) == os.path.join(self.path, self.iso):
-                               return "[PXE]"
-               return ""
diff --git a/www/webapp/datastore/config.py b/www/webapp/datastore/config.py
deleted file mode 100644 (file)
index 961164f..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#/usr/bin/python
-
-class Config(object):
-       def __init__(self, application):
-               self.application = application
-
-       @property
-       def db(self):
-               return self.application.db.config
-
-       def delete(self, key):
-               self.db.execute("DELETE FROM settings WHERE key = %s", key)
-
-       def get(self, key, default=None):
-               return self.db.get("SELECT key FROM settings WHERE key = %s", key) or default
-
-       def set(self, key, value):
-               self.delete(key)
-               self.db.execute("INSERT INTO settings(key, value) VALUES(%s, %s)", key, value)
diff --git a/www/webapp/datastore/connections.py b/www/webapp/datastore/connections.py
deleted file mode 100644 (file)
index 259dc39..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/python
-
-import tornado.database
-
-MYSQL_SERVER = "mysql.ipfire.org"
-
-class WebappConnection(tornado.database.Connection):
-       def __init__(self):
-               tornado.database.Connection.__init__(self, MYSQL_SERVER, "webapp", user="webapp")
-
-
-class HashConnection(tornado.database.Connection):
-       def __init__(self):
-               tornado.database.Connection.__init__(self, MYSQL_SERVER, "hashes", user="webapp")
-
-
-class TrackerConnection(tornado.database.Connection):
-       def __init__(self):
-               tornado.database.Connection.__init__(self, MYSQL_SERVER, "tracker", user="webapp")
-
-
-class UserConnection(tornado.database.Connection):
-       def __init__(self):
-               tornado.database.Connection.__init__(self, MYSQL_SERVER, "forum", user="webapp")
diff --git a/www/webapp/datastore/info.py b/www/webapp/datastore/info.py
deleted file mode 100644 (file)
index 27f55cc..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/python
-
-from helpers import json_loads
-
-class Info(dict):
-       def __init__(self, application, filename):
-               self.application = application
-               self.load(filename)
-               
-       def load(self, filename):
-               f = open(filename)
-               for key, val in json_loads(f.read()).items():
-                       self[key] = val
-               f.close()
diff --git a/www/webapp/datastore/menu.py b/www/webapp/datastore/menu.py
deleted file mode 100644 (file)
index 3f7d34c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/python
-
-import simplejson
-
-from tornado.database import Row
-
-class Menu(object):
-       def __init__(self, application, filename=None):
-               self.application = application
-               self.items = {}
-
-               if filename:
-                       self.load(filename)
-
-       def load(self, filename):
-               with open(filename) as f:
-                       for url, items in simplejson.load(f).items():
-                               self.items[url] = []
-                               for item in items:
-                                       self.items[url].append(Row(item))
-
-       def get(self, url):
-               return self.items.get(url, [])
diff --git a/www/webapp/datastore/mirrors.py b/www/webapp/datastore/mirrors.py
deleted file mode 100644 (file)
index f801f7e..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-#!/usr/bin/python
-
-import tornado.httpclient
-
-import GeoIP
-import random
-import time
-
-from threading import Thread
-
-from helpers import Item, _stringify, ping, json_loads
-
-import logging
-
-geoip_cache = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
-
-continents = {
-       "america"   : [ "us", ],
-       "asia"      : [ "cn", "jp", ],
-       "australia" : [ "au", ],
-       "europe"    : [ "at", "de", "es", "fr", ],
-}
-
-def continent_by_ip(ip):
-       country = geoip_cache.country_code_by_addr(ip)
-       if not country:
-               return
-
-       country = country.lower()
-
-       for continent, countries in continents.items():
-               if not country in countries:
-                       continue
-               return continent
-
-class Mirrors(object):
-       def __init__(self, application, filename):
-               self.application = application
-               self.items = []
-               self.load(filename)
-
-       def load(self, filename):
-               f = open(filename)
-               data = f.read()
-               f.close()
-
-               for item in json_loads(data):
-                       self.items.append(MirrorItem(**_stringify(item)))
-
-       @property
-       def master(self):
-               return self.items[0]
-
-       @property
-       def all(self):
-               return sorted(self.items)
-
-       @property
-       def random(self):
-               mirrors = self.get()
-               random.shuffle(mirrors)
-               return mirrors
-
-       @property
-       def reachable(self):
-               return self.filter_reachable(self.all)
-
-       @property
-       def unreachable(self):
-               return self.filter_unreachable(self.all)
-
-       def filter_continent(self, mirrors, continent):
-               try:
-                       countries = continents[continent]
-               except KeyError:
-                       return []
-
-               ret = []
-               for mirror in mirrors:
-                       if mirror.location["country_code"] in countries:
-                               ret.append(mirror)
-
-               return ret
-
-       def filter_file(self, mirrors, path):
-               ret = []
-               for mirror in mirrors:
-                       if not mirror["serves"]["isos"]:
-                               continue
-                       if path in mirror.files:
-                               ret.append(mirror)
-               return ret
-
-       def filter_reachable(self, mirrors):
-               return [m for m in mirrors if m.reachable]
-
-       def filter_unreachable(self, mirrors):
-               return [m for m in mirrors if not m.reachable]
-
-       def get(self, continent=None, file=None, unreachable=False, reachable=False):
-               mirrors = self.all
-
-               if continent:
-                       mirrors = self.filter_continent(mirrors, continent)
-               if file:
-                       mirrors = self.filter_file(mirrors, file)
-               if reachable:
-                       mirrors = self.filter_reachable(mirrors)
-               if unreachable:
-                       mirrors = self.filter_unreachable(mirrors)
-
-               return mirrors
-
-       def pickone(self, **kwargs):
-               if kwargs.has_key("ip"):
-                       kwargs["continent"] = continent_by_ip(kwargs.pop("ip"))
-
-               mirrors = self.get(**kwargs)
-
-               # If we did not get any mirrors we try a global one
-               if not mirrors:
-                       del kwargs["continent"]
-                       mirrors = self.get(**kwargs)
-
-               if not mirrors:
-                       return None
-
-               return random.choice(mirrors)
-
-       # To be removed
-       def with_file(self, path):
-               return self.get(file=path)
-
-       trigger_counter = 0
-
-       def trigger(self):
-               if not self.trigger_counter:
-                       self.trigger_counter = 300
-
-                       for mirror in self.all:
-                               mirror.update()
-
-               self.trigger_counter -= 1
-
-
-class MirrorItem(object):
-       def __init__(self, **kwargs):
-               self.items = kwargs
-
-               self.files = []
-               self.timestamp = 0
-
-               logging.debug("Initialized mirror %s" % self.hostname)
-
-       def __cmp__(self, other):
-               return cmp(self.owner, other.owner)
-
-       def __repr__(self):
-               return "<%s %s>" % (self.__class__.__name__, self.hostname)
-
-       def __getattr__(self, key):
-               try:
-                       return self.items[key]
-               except KeyError:
-                       raise AttributeError
-
-       def html_class(self):
-               if time.time() - self.timestamp > 60*60*24:
-                       return "outdated"
-               return "ok"
-
-       def update(self):
-               self.update_filelist()
-               self.update_timestamp()
-
-       def update_filelist(self):
-               http = tornado.httpclient.AsyncHTTPClient()
-
-               http.fetch(self.url + ".filelist",
-                       callback=self.__update_filelist_response)
-
-       def __update_filelist_response(self, response):
-               if response.error:
-                       logging.debug("Error getting filelist from %s" % self.hostname)
-                       return
-
-               if not response.code == 200: 
-                       return
-
-               logging.debug("Got filelist from %s" % self.hostname)
-
-               self.files = []
-
-               for line in response.body.splitlines():
-                       self.files.append(line)
-
-       def update_timestamp(self):
-               http = tornado.httpclient.AsyncHTTPClient()
-
-               http.fetch(self.url + ".timestamp",
-                       callback=self.__update_timestamp_response)
-
-       def __update_timestamp_response(self, response):
-               if response.error:
-                       logging.debug("Error getting timestamp from %s" % self.hostname)
-
-               data = response.body.strip()
-               try:
-                       self.timestamp = int(data)
-               except ValueError:
-                       self.timestamp = 0
-
-       @property
-       def url(self):
-               ret = "http://" + self.hostname
-               if not self.path.startswith("/"):
-                       ret += "/"
-               ret += self.path
-               if not ret.endswith("/"):
-                       ret += "/"
-               return ret
-
-       def filelist_compare(self, other_files):
-               own_files = [f for f in self.files if f in other_files]
-
-               try:
-                       return (len(own_files) * 100) / len(other_files)
-               except ZeroDivisionError:
-                       return 0
-
-       def has_file(self, path):
-               return path in self.files
diff --git a/www/webapp/datastore/news.py b/www/webapp/datastore/news.py
deleted file mode 100644 (file)
index 2ebb256..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/python
-
-import simplejson
-
-from tornado.database import Row
-
-class News(object):
-       def __init__(self, application, filename=None):
-               self.application = application
-               self.items = []
-
-               if filename:
-                       self.load(filename)
-
-       def load(self, filename):
-               f = open(filename)
-               data = f.read()
-               f.close()
-
-               data = data.replace("\n", "").replace("\t", " ")
-
-               json = simplejson.loads(data)
-               for key in sorted(json.keys()):
-                       json[key]["id"] = key
-                       self.items.append(Row(json[key]))
-
-       def get(self, limit=None):
-               ret = self.items[:]
-               ret.reverse()
-               if limit:
-                       ret = ret[:limit]
-               return ret
diff --git a/www/webapp/datastore/releases.py b/www/webapp/datastore/releases.py
deleted file mode 100644 (file)
index 86e005c..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-#!/usr/bin/python
-
-import simplejson
-
-from tornado.database import Row
-
-class ReleaseItem(Row):
-       options = {
-               "iso" : {
-                       "prio" : 10,
-                       "desc" : "Installable CD image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "Use this image to burn a CD and install IPFire from it.",
-               },
-               "torrent" : {
-                       "prio" : 20,
-                       "desc" : "Torrent file",
-                       "url"  : "http://download.ipfire.org/torrent/",
-                       "rem"  : "Download the CD image from the torrent network.",
-               },
-               "flash" : {
-                       "prio" : 40,
-                       "desc" : "Flash image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "An image that is meant to run on embedded devices.",
-               },
-               "alix" : {
-                       "prio" : 41,
-                       "desc" : "Alix image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "Flash image where a serial console is enabled by default.",
-               },
-               "usbfdd" : {
-                       "prio" : 30,
-                       "desc" : "USB FDD image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "Install IPFire from a floppy-formated USB key.",
-               },
-               "usbhdd" : {
-                       "prio" : 30,
-                       "desc" : "USB HDD image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "If the floppy image doesn't work, use this image instead.",
-               },
-               "xen" : {
-                       "prio" : 50,
-                       "desc" : "Pregenerated Xen image",
-                       "url"  : "http://download.ipfire.org/iso/",
-                       "rem"  : "A ready-to-run image for Xen.",
-               },
-       }
-
-       def __init__(self, info):
-               self.info = info
-
-       def __getattr__(self, key):
-               return self.info[key]
-
-       @property
-       def downloads(self):
-               ret = []
-               for fileitem in self.info["files"]:
-                       filetype = fileitem["type"]
-                       ret.append(Row(
-                               desc = self.options[filetype]["desc"],
-                               file = fileitem["name"],
-                               hash = fileitem.get("hash", None),
-                               prio = self.options[filetype]["prio"],
-                               rem  = self.options[filetype]["rem"],
-                               sha1 = fileitem.get("sha1", None),
-                               type = filetype,
-                               url  = self.options[filetype]["url"] + fileitem["name"],
-                       ))
-
-               ret.sort(lambda a, b: cmp(a.prio, b.prio))
-               return ret
-
-       @property
-       def iso(self):
-               for download in self.downloads:
-                       if download.type == "iso":
-                               return download
-
-       @property
-       def torrent(self):
-               for download in self.downloads:
-                       if download.type == "torrent":
-                               return download
-
-       @property
-       def stable(self):
-               return self.status == "stable"
-
-       @property
-       def development(self):
-               return self.status == "development"
-
-
-class Releases(object):
-       def __init__(self, application, filename="releases.json"):
-               self.application = application
-               self.items = []
-
-               if filename:
-                       self.load(filename)
-       
-       def load(self, filename):
-               with open(filename) as f:
-                       self.items = [ReleaseItem(i) for i in simplejson.load(f)]
-
-       @property
-       def all(self):
-               return self.items
-
-       @property
-       def online(self):
-               ret = []
-               for item in self.all:
-                       if item.online:
-                               ret.append(item)
-               return ret
-
-       @property
-       def offline(self):
-               ret = []
-               for item in self.all:
-                       if not item.online:
-                               ret.append(item)
-               return ret
-
-       @property
-       def latest(self):
-               if self.stable:
-                       return self.stable[0]
-
-       @property
-       def latest_devel(self):
-               if self.development:
-                       return self.development[0]
-
-       @property
-       def stable(self):
-               ret = []
-               for item in self.online:
-                       if item.stable:
-                               ret.append(item)
-               return ret
-
-       @property
-       def development(self):
-               ret = []
-               for item in self.online:
-                       if item.development:
-                               ret.append(item)
-               return ret
-
-       @property
-       def torrents(self):
-               ret = []
-               for item in self.online:
-                       if item.torrent:
-                               ret.append(item)
-               return ret
diff --git a/www/webapp/db.py b/www/webapp/db.py
deleted file mode 100644 (file)
index c466196..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/usr/bin/python
-
-import hashlib
-import ldap
-import sqlite3
-import os.path
-
-
-class HashDatabase(object):
-       def __init__(self):
-               self.conn = sqlite3.connect("/srv/www/ipfire.org/source/hashes.db")
-               self.conn.isolation_level = None # autocommit mode
-
-               self.prepare()
-
-       def __del__(self):
-               self.conn.close()
-
-       def prepare(self):
-               c = self.conn.cursor()
-               c.execute("CREATE TABLE IF NOT EXISTS hashes(file, sha1)")
-               c.close()
-
-       def _save_hash(self, path, hash):
-               c = self.conn.cursor()
-               c.execute("INSERT INTO hashes VALUES('%s', '%s')" % (os.path.basename(path), hash))
-               c.close()
-
-       def get_hash(self, path):
-               c = self.conn.cursor()
-               c.execute("SELECT sha1 FROM hashes WHERE file = '%s'" % os.path.basename(path))
-
-               hash = c.fetchone()
-               c.close()
-
-               if not hash:
-                       hash = self._calc_hash(path)
-                       self._save_hash(path, hash)
-
-               if hash:
-                       return "%s" % hash
-
-       def _calc_hash(self, path):
-               if not os.path.exists(path):
-                       return
-
-               m = hashlib.sha1()
-               f = open(path)
-               m.update(f.read())
-               f.close()
-
-               return m.hexdigest()
-
-
-class UserDatabase(object):
-       HOST = "ldap://ldap.ipfire.org"
-       BASEDN = "ou=People,dc=mcfly,dc=local"
-
-       def __init__(self):
-               self.conn = ldap.initialize(self.HOST)
-               self.conn.simple_bind()
-
-       def __del__(self):
-               self.conn.unbind()
-
-       def _find_dn_by_name(self, name):
-               results = self._search(filterstr="(uid=%s)" % name)
-               assert len(results) == 1
-               return results[0][0]
-       
-       def _search(self, filterstr="(objectClass=*)", attrlist=None):
-               return self.conn.search_st(self.BASEDN, ldap.SCOPE_SUBTREE,
-                       filterstr=filterstr, attrlist=attrlist)
-
-       def check_password(self, name, password):
-               dn = self._find_dn_by_name(name)
-               conn = ldap.initialize(self.HOST)
-               try:
-                       conn.simple_bind_s(dn, password)
-                       return True
-               except ldap.INVALID_CREDENTIALS:
-                       return False
-               finally:
-                       conn.unbind_s()
-
-       def get_user_by_id(self, id):
-               results = self._search(filterstr="(uidNumber=%s)" % id)
-               assert len(results) == 1
-               return User(results[0][1])
-
-       def get_user_by_name(self, name):
-               results = self._search(filterstr="(uid=%s)" % name)
-               assert len(results) == 1
-               return User(results[0][1])
-
-       @property
-       def users(self):
-               ret = []
-
-               for dn, attr in self._search():
-                       if dn == self.BASEDN or not attr:
-                               continue
-                       ret.append(User(attr))
-
-               return sorted(ret)
-
-
-class User(object):
-       def __init__(self, obj):
-               self.obj = obj
-
-       def __cmp__(self, other):
-               return cmp(self.realname, other.realname)
-
-       def __repr__(self):
-               return "<%s '%s'>" % (self.__class__.__name__, self.name)
-
-       @property
-       def name(self):
-               return self.obj["uid"][0]
-
-       @property
-       def id(self):
-               return int(self.obj["uidNumber"][0])
-
-       @property
-       def mail(self):
-               #return self.obj["mail"]
-               return "%s@ipfire.org" % self.name
-
-       @property
-       def realname(self):
-               return self.obj["cn"][0]
-
-
-class Databases(object):
-       def __init__(self, application):
-               self.application = application
-
-               self.hashes = HashDatabase()
-               self.planet = PlanetDatabase()
-               self.users = UserDatabase()
index 6b145602920c9139e38b5f739694cfe74ec7233d..fb459f40e0eb4a0e5d3ba6e4ae37bd96c64783ab 100644 (file)
 #!/usr/bin/python
 
-import datetime
-import httplib
-import mimetypes
-import operator
+#import httplib
+#import logging
+#import markdown2
 import os
-import re
-import simplejson
-import stat
-import sqlite3
-import time
-import unicodedata
-import urlparse
-
-import tornado.database
-import tornado.httpclient
-import tornado.locale
+#import random
+#import re
+#import socket
+#import time
+#import tornado.database
+#import tornado.locale
 import tornado.web
+#import unicodedata
 
-from helpers import size
-from datastore.tracker import bencode, bdecode, decode_hex
-
-import markdown
-
-class BaseHandler(tornado.web.RequestHandler):
-       def get_user_locale(self):
-               uri = self.request.uri.split("/")
-               if len(uri) > 1:
-                       for lang in tornado.locale.get_supported_locales(None):
-                               if lang[:2] == uri[1]:
-                                       return tornado.locale.get(lang)
-
-       @property
-       def render_args(self):
-               return {
-                       "banner"    : self.ds.banners.get(),
-                       "hostname"  : self.request.host,
-                       "lang"      : self.locale.code[:2],
-                       "langs"     : [l[:2] for l in tornado.locale.get_supported_locales(None)],
-                       "lang_link" : self.lang_link,
-                       "link"      : self.link,
-                       "title"     : "no title given",
-                       "time_ago"  : self.time_ago,
-                       "server"    : self.request.host.replace("ipfire", "<span>ipfire</span>"),
-                       "uri"       : self.request.uri,
-                       "year"      : time.strftime("%Y"),
-               }
-
-       def render(self, *args, **kwargs):
-               nargs = self.render_args
-               nargs.update(kwargs)
-               tornado.web.RequestHandler.render(self, *args, **nargs)
+import backend
 
-       def link(self, s):
-               return "/%s/%s" % (self.locale.code[:2], s)
-       
-       def lang_link(self, lang):
-               return "/%s/%s" % (lang, self.request.uri[4:])
-       
-       def time_ago(self, stamp):
-               if not stamp:
-                       return "N/A"
+from handlers_admin import *
+from handlers_base import *
+from handlers_download import *
+from handlers_mirrors import *
+from handlers_news import *
+from handlers_planet import *
+from handlers_stasy import *
+from handlers_tracker import *
 
-               ago = time.time() - stamp
-               for unit in ("s", "m", "h", "d", "M"):
-                       if ago < 60:
-                               break
-                       ago /= 60
-               
-               return "<%d%s" % (ago, unit)
-
-       def get_error_html(self, status_code, **kwargs):
-               if status_code in (404, 500):
-                       render_args = self.render_args
-                       render_args.update({
-                               "code"      : status_code,
-                               "exception" : kwargs.get("exception", None),
-                               "message"   : httplib.responses[status_code],
-                       })
-                       return self.render_string("error-%s.html" % status_code, **render_args)
-               else:
-                       return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
-
-       @property
-       def db(self):
-               return self.ds.db
-
-       @property
-       def ds(self):
-               return self.application.ds
 
+class RootHandler(BaseHandler):
+       """
+               This handler redirects any request directly to /.
 
-class MainHandler(BaseHandler):
-       def get(self):
-               lang = self.locale.code[:2]
-               self.redirect("/%s/index" % (lang))
-
-
-class DownloadHandler(BaseHandler):
-       def get(self):
-               self.render("downloads.html", release=self.ds.releases.latest)
-
-
-class DownloadAllHandler(BaseHandler):
-       def get(self):
-               self.render("downloads-all.html", releases=self.ds.releases)
-
-
-class DownloadDevelopmentHandler(BaseHandler):
-       def get(self):
-               self.render("downloads-development.html", releases=self.ds.releases)
-
-
-class DownloadTorrentHandler(BaseHandler):
-       tracker_url = "http://tracker.ipfire.org:6969/stats?format=txt&mode=tpbs"
-
-       @tornado.web.asynchronous
-       def get(self):
-               http = tornado.httpclient.AsyncHTTPClient()
-               http.fetch(self.tracker_url, callback=self.async_callback(self.on_response))
+               It can be used to be compatible with some ancient index urls.
+       """
+       def get(self, *args):
+               self.redirect("/")
 
-       def on_response(self, response):
-               torrents = self.ds.releases.torrents
-               hashes = {}
-               if response.code == 200:
-                       for line in response.body.split("\n"):
-                               if not line: continue
-                               hash, seeds, peers = line.split(":")
-                               hash.lower()
-                               hashes[hash] = {
-                                       "peers" : peers,
-                                       "seeds" : seeds,
-                               }
 
-               self.render("downloads-torrents.html",
-                       hashes=hashes,
-                       releases=torrents,
-                       request_time=response.request_time,
-                       tracker=urlparse.urlparse(response.request.url).netloc)
+class LangCompatHandler(BaseHandler):
+       """
+               Redirect links in the old format to current site:
 
+               E.g. /en/index -> /index
+       """
+       def get(self, lang, page):
+               self.redirect("/%s" % page)
 
-class DownloadTorrentHandler(BaseHandler):
-       @property
-       def tracker(self):
-               return self.ds.tracker
 
+class IndexHandler(BaseHandler):
+       """
+               This handler displays the welcome page.
+       """
        def get(self):
-               releases = self.ds.releases.torrents
-
-               hashes = {}
-               for hash in [release.torrent.hash for release in releases if release.torrent]:
-                       hashes[hash] = {
-                               "peers" : self.tracker.get_peers(hash),
-                               "seeds" : self.tracker.get_seeds(hash),
-                       }
-
-               self.render("downloads-torrents.html",
-                       hashes=hashes,
-                       releases=releases)
-
+               # Get a list of the most recent news items and put them on the page.            
+               latest_news = self.news.get_latest(limit=1, locale=self.locale)
+               recent_news = self.news.get_latest(limit=2, locale=self.locale, offset=1)
 
-class DownloadMirrorHandler(BaseHandler):
-       def get(self):
-               self.render("downloads-mirrors.html", mirrors=self.ds.mirrors)
+               return self.render("index.html",
+                       latest_news=latest_news, recent_news=recent_news)
 
 
 class StaticHandler(BaseHandler):
+       """
+               This handler shows the files that are in plain html format.
+       """
        @property
        def static_path(self):
                return os.path.join(self.application.settings["template_path"], "static")
@@ -184,391 +80,6 @@ class StaticHandler(BaseHandler):
                if not name in self.static_files:
                        raise tornado.web.HTTPError(404)
 
-               self.render("static/%s" % name)
-
-
-class IndexHandler(BaseHandler):
-       def get(self):
-               self.render("index.html", news=self.ds.news)
-
-
-class NewsHandler(BaseHandler):
-       def get(self):
-               self.render("news.html", news=self.ds.news)
-
-
-class BuildHandler(BaseHandler):
-       def prepare(self):
-               self.builds = {
-                       "<12h" : [],
-                       ">12h" : [],
-                       ">24h" : [],
-               }
-
-               for build in self.ds.builds.find(self.ds.info):
-                       if (time.time() - float(build.get("date"))) < 12*60*60:
-                               self.builds["<12h"].append(build)
-                       elif (time.time() - float(build.get("date"))) < 24*60*60:
-                               self.builds[">12h"].append(build)
-                       else:
-                               self.builds[">24h"].append(build)
-
-               for l in self.builds.values():
-                       l.sort()
-
-       def get(self):
-               self.render("builds.html", builds=self.builds)
-
-
-class SourceHandler(BaseHandler):
-       def get(self):
-               source_path = "/srv/sources"
-               fileobjects = []
-
-               for dir, subdirs, files in os.walk(source_path):
-                       if not files:
-                               continue
-                       for file in files:
-                               if file in [f["name"] for f in fileobjects]:
-                                       continue
-
-                               hash = self.db.hash.get_hash(os.path.join(dir, file))
-
-                               if not hash:
-                                       hash = "0000000000000000000000000000000000000000"
-
-                               fileobjects.append({
-                                       "dir"  : dir[len(source_path)+1:],
-                                       "name" : file,
-                                       "hash" : hash,
-                                       "size" : size(os.path.getsize(os.path.join(source_path, dir, file))),
-                               })
-
-               fileobjects.sort(key=operator.itemgetter("name"))
-
-               self.render("sources.html", files=fileobjects)
-
-
-class SourceDownloadHandler(BaseHandler):
-       def head(self, path):
-               self.get(path, include_body=False)
-
-       def get(self, path, include_body=True):
-               source_path = "/srv/sources"
-
-               path = os.path.abspath(os.path.join(source_path, path[1:]))
-
-               if not path.startswith(source_path):
-                       raise tornado.web.HTTPError(403)
-               if not os.path.exists(path):
-                       raise tornado.web.HTTPError(404)
-
-               stat_result = os.stat(path)
-               modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
-
-               self.set_header("Last-Modified", modified)
-               self.set_header("Content-Length", stat_result[stat.ST_SIZE])
-
-               mime_type, encoding = mimetypes.guess_type(path)
-               if mime_type:
-                       self.set_header("Content-Type", mime_type)
-
-               hash = self.db.hash.get_hash(path)
-               if hash:
-                       self.set_header("X-Hash-Sha1", "%s" % hash)
-
-               if not include_body:
-                       return
-               file = open(path, "r")
-               try:
-                       self.write(file.read())
-               finally:
-                       file.close()
-
-
-class DownloadFileHandler(BaseHandler):
-       def get(self, path):
-               for mirror in self.ds.mirrors.with_file(path):
-                       if not mirror.reachable:
-                               continue
-
-                       self.redirect(mirror.url + path)
-                       return
-
-               raise tornado.web.HTTPError(404)
-
-       def get_error_html(self, status_code, **kwargs):
-               return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
-
-
-class RSSHandler(BaseHandler):
-       def get(self, lang):
-               items = []
-               for item in news.get(15):
-                       item = Item(**item.args.copy())
-                       for attr in ("subject", "content"):
-                               if type(item[attr]) == type({}):
-                                       item[attr] = item[attr][lang]
-                       items.append(item)
-
-               self.set_header("Content-Type", "application/rss+xml")
-               self.render("rss.xml", items=items, lang=lang)
-
-
-class TrackerBaseHandler(tornado.web.RequestHandler):
-       def get_hexencoded_argument(self, name, all=False):
-               try:
-                       arguments = self.request.arguments[name]
-               except KeyError:
-                       return None
-
-               arguments_new = []
-               for argument in arguments:
-                       arguments_new.append(decode_hex(argument))
-
-               arguments = arguments_new
-
-               if all:
-                       return arguments
-
-               return arguments[0]
-
-       def send_tracker_error(self, error_message):
-               self.write(bencode({"failure reason" : error_message }))
-               self.finish()
-
-
-class TrackerAnnounceHandler(TrackerBaseHandler):
-       def get(self):
-               self.set_header("Content-Type", "text/plain")
-
-               info_hash = self.get_hexencoded_argument("info_hash")
-               if not info_hash:
-                       self.send_tracker_error("Your client forgot to send your torrent's info_hash.")
-                       return
-
-               peer = {
-                       "id" : self.get_hexencoded_argument("peer_id"),
-                       "ip" : self.get_argument("ip", None),
-                       "port" : self.get_argument("port", None),
-                       "downloaded" : self.get_argument("downloaded", 0),
-                       "uploaded" : self.get_argument("uploaded", 0),
-                       "left" : self.get_argument("left", 0),
-               }
-
-               event = self.get_argument("event", "")
-               if not event in ("started", "stopped", "completed", ""):
-                       self.send_tracker_error("Got unknown event")
-                       return
-
-               if peer["ip"]:
-                       if peer["ip"].startswith("10.") or \
-                               peer["ip"].startswith("172.") or \
-                               peer["ip"].startswith("192.168."):
-                               peer["ip"] = self.request.remote_ip
-
-               if peer["port"]:
-                       peer["port"] = int(peer["port"])
-
-                       if peer["port"] < 0 or peer["port"] > 65535:
-                               self.send_tracker_error("Port number is not in valid range")
-                               return
-
-               eventhandlers = {
-                       "started" : tracker.event_started,
-                       "stopped" : tracker.event_stopped,
-                       "completed" : tracker.event_completed,
-               }
-
-               if event:
-                       eventhandlers[event](info_hash, peer["id"])
-
-               tracker.update(hash=info_hash, **peer)
-
-               no_peer_id = self.get_argument("no_peer_id", False)
-               numwant = self.get_argument("numwant", tracker.numwant)
-
-               self.write(bencode({
-                       "tracker id" : tracker.id,
-                       "interval" : tracker.interval,
-                       "min interval" : tracker.min_interval,
-                       "peers" : tracker.get_peers(info_hash, limit=numwant,
-                               random=True, no_peer_id=no_peer_id),
-                       "complete" : tracker.complete(info_hash),
-                       "incomplete" : tracker.incomplete(info_hash),
-               }))
-               self.finish()
-
-
-class TrackerScrapeHandler(TrackerBaseHandler):
-       def get(self):
-               info_hashes = self.get_hexencoded_argument("info_hash", all=True)
-
-               self.write(bencode(tracker.scrape(hashes=info_hashes)))
-               self.finish()
-
-
-class PlanetBaseHandler(BaseHandler):
-       pass
-
-
-class PlanetMainHandler(PlanetBaseHandler):
-       def get(self):
-               authors = self.db.query("SELECT DISTINCT author_id FROM planet")
-               authors = [a["author_id"] for a in authors]
-
-               users = []
-               for user in self.user_db.users:
-                       if user.id in authors:
-                               users.append(user)
-
-               entries = self.db.query("SELECT * FROM planet "
-                       "ORDER BY published DESC LIMIT 3")
-               
-               for entry in entries:
-                       entry.author = self.user_db.get_user_by_id(entry.author_id)
-
-               self.render("planet-main.html", entries=entries, authors=users)
-
-
-class PlanetUserHandler(PlanetBaseHandler):
-       def get(self, user):
-               if not user in [u.name for u in self.user_db.users]:
-                       raise tornado.web.HTTPError(404, "User is unknown")
-
-               user = self.user_db.get_user_by_name(user)
-
-               entries = self.db.query("SELECT * FROM planet "
-                       "WHERE author_id = '%s' ORDER BY published DESC" % (user.id))
-
-               self.render("planet-user.html", entries=entries, user=user)
-
-
-class PlanetPostingHandler(PlanetBaseHandler):
-       def get(self, slug):
-               entry = self.db.get("SELECT * FROM planet WHERE slug = %s", slug)
-
-               if not entry:
-                       raise tornado.web.HTTPError(404)
-
-               user = self.user_db.get_user_by_id(entry.author_id)
-               entry.author = user
-
-               self.render("planet-posting.html", entry=entry, user=user)
-
-
-class AdminBaseHandler(BaseHandler):
-       def render(self, *args, **kwargs):
-               
-               return BaseHandler.render(self, *args, **kwargs)
-
-
-class AdminIndexHandler(AdminBaseHandler):
-       def get(self):
-               self.render("admin-index.html")
+               self.render("static/%s" % name, lang=self.locale.code[:2])
 
 
-class AdminApiPlanetRenderMarkupHandler(AdminBaseHandler):
-       def get(self):
-               text = self.get_argument("text", "")
-
-               # Render markup
-               self.write(markdown.markdown(text))
-               self.finish()
-
-
-class AdminPlanetHandler(AdminBaseHandler):
-       def get(self):
-               entries = self.planet_db.query("SELECT * FROM entries ORDER BY published DESC")
-
-               for entry in entries:
-                       entry.author = self.user_db.get_user_by_id(entry.author_id)
-
-               self.render("admin-planet.html", entries=entries)
-
-
-class AdminPlanetComposeHandler(AdminBaseHandler):
-       #@tornado.web.authenticated
-       def get(self, id=None):
-               if id:
-                       entry = self.planet_db.get("SELECT * FROM entries WHERE id = '%s'", int(id))
-               else:
-                       entry = tornado.database.Row(id="", title="", text="")
-
-               self.render("admin-planet-compose.html", entry=entry)
-
-       #@tornado.web.authenticated
-       def post(self, id=None):
-               id = self.get_argument("id", id)
-               title = self.get_argument("title")
-               text = self.get_argument("text")
-
-               if id:
-                       entry = self.planet_db.get("SELECT * FROM entries WHERE id = %s", id)
-                       if not entry:
-                               raise tornado.web.HTTPError(404)
-
-                       self.planet_db.execute("UPDATE entries SET title = %s, text = %s "
-                               "WHERE id = %s", title, text, id)
-
-                       slug = entry.slug
-
-               else:
-                       slug = unicodedata.normalize("NFKD", title).encode("ascii", "ignore")
-                       slug = re.sub(r"[^\w]+", " ", slug)
-                       slug = "-".join(slug.lower().strip().split())
-
-                       if not slug:
-                               slug = "entry"
-
-                       while True:
-                               e = self.planet_db.get("SELECT * FROM entries WHERE slug = %s", slug)
-                               if not e:
-                                       break
-                               slug += "-"
-
-                       self.planet_db.execute("INSERT INTO entries(author_id, title, slug, text, published) "
-                               "VALUES(%s, %s, %s, %s, UTC_TIMESTAMP())", 500, title, slug, text)
-
-               self.redirect("/planet")
-
-
-class AdminPlanetEditHandler(AdminPlanetComposeHandler):
-       pass
-
-
-class AdminAccountsHandler(AdminBaseHandler):
-       def get(self):
-               users = self.user_db.users
-
-               self.render("admin-accounts.html", accounts=users)
-
-
-class AdminAccountsEditHandler(AdminBaseHandler):
-       def get(self, id):
-               user = self.user_db.get_user_by_id(id)
-
-               if not user:
-                       raise tornado.web.HTTPError(404)
-
-               self.render("admin-accounts-edit.html", user=user)
-
-
-class AuthLoginHandler(BaseHandler):
-       def get(self):
-               self.render("admin-login.html")
-
-       def post(self):
-               #name = self.get_attribute("name")
-               #password = self.get_attribute("password")
-
-               pass
-
-               #if self.user_db.check_password(name, password):
-               #       self.set_secure_cookie("user", int(user.id))
-
-
-class AuthLogoutHandler(BaseHandler):
-       def get(self):
-               self.clear_cookie("user")
-               self.redirect("/")
diff --git a/www/webapp/handlers_admin.py b/www/webapp/handlers_admin.py
new file mode 100644 (file)
index 0000000..410be01
--- /dev/null
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+
+# XXX most of this is broken
+
+import tornado.web
+
+from handlers_base import *
+
+
+class AdminBaseHandler(BaseHandler):
+       def get_current_user(self):
+               return self.get_secure_cookie("account")
+
+
+class AdminLoginHandler(AdminBaseHandler):
+       def get(self):
+               self.render("admin-login.html")
+
+       def post(self):
+               account = self.accounts.search(self.get_argument("name"))
+               if not account:
+                       raise tornado.web.HTTPError(403)
+
+               if account.check_password(self.get_argument("password")):
+                       self.set_secure_cookie("account", account.uid)
+               else:
+                       raise tornado.web.HTTPError(403)
+
+               self.redirect("/")
+
+
+class AdminLogoutHandler(AdminBaseHandler):
+       def get(self):
+               self.clear_cookie("account")
+               self.redirect("/")
+
+
+class AdminIndexHandler(AdminBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               self.render("admin-index.html")
+
+
+class AdminApiPlanetRenderMarkupHandler(AdminBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               text = self.get_argument("text", "")
+
+               # Render markup
+               self.write(markdown2.markdown(text))
+               self.finish()
+
+
+class AdminPlanetHandler(AdminBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               entries = self.backend.db.planet.query("SELECT * FROM planet ORDER BY published DESC")
+
+               for entry in entries:
+                       entry.author = self.backend.accounts.search(entry.author_id)
+
+               self.render("admin-planet.html", entries=entries)
+
+
+class AdminPlanetComposeHandler(AdminBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id=None):
+               if id:
+                       entry = self.backend.db.planet.get("SELECT * FROM planet WHERE id = '%s'", int(id))
+               else:
+                       entry = tornado.database.Row(id="", title="", markdown="")
+
+               self.render("admin-planet-compose.html", entry=entry)
+
+       @tornado.web.authenticated
+       def post(self, id=None):
+               id = self.get_argument("id", id)
+               title = self.get_argument("title")
+               markdown = self.get_argument("markdown")
+               author = self.backend.accounts.search(self.current_user)
+
+               if id:
+                       entry = self.backend.db.planet.get("SELECT * FROM planet WHERE id = %s", id)
+                       if not entry:
+                               raise tornado.web.HTTPError(404)
+
+                       self.backend.db.planet.execute("UPDATE planet SET title = %s, markdown = %s "
+                               "WHERE id = %s", title, markdown, id)
+
+                       slug = entry.slug
+
+               else:
+                       slug = unicodedata.normalize("NFKD", title).encode("ascii", "ignore")
+                       slug = re.sub(r"[^\w]+", " ", slug)
+                       slug = "-".join(slug.lower().strip().split())
+
+                       if not slug:
+                               slug = "entry"
+
+                       while True:
+                               e = self.backend.db.planet.get("SELECT * FROM planet WHERE slug = %s", slug)
+                               if not e:
+                                       break
+                               slug += "-"
+
+                       self.backend.db.planet.execute("INSERT INTO planet(author_id, title, slug, markdown, published) "
+                               "VALUES(%s, %s, %s, %s, UTC_TIMESTAMP())", author.uid, title, slug, markdown)
+
+               self.redirect("/planet")
+
+
+class AdminPlanetEditHandler(AdminPlanetComposeHandler):
+       pass
+
+
+class AdminAccountsBaseHandler(AdminBaseHandler):
+       @property
+       def accounts(self):
+               return self.backend.accounts
+
+
+class AdminAccountsHandler(AdminAccountsBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               accounts = self.backend.accounts.list()
+               self.render("admin-accounts.html", accounts=accounts)
+
+
+class AdminAccountsEditHandler(AdminAccountsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id):
+               account = self.accounts.search(id)
+               if not account:
+                       raise tornado.web.HTTPError(404)
+
+               self.render("admin-accounts-edit.html", account=account)
+
+
+class AdminAccountsDeleteHandler(AdminAccountsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id):
+               account = self.accounts.search(id)
+               if not account:
+                       raise tornado.web.HTTPError(404)
+
+               self.accounts.delete(id)
+               self.redirect("/accounts")
+
+
+class AdminMirrorsBaseHandler(AdminBaseHandler):
+       @property
+       def db(self):
+               return self.backend.db.mirrors
+
+       @property
+       def mirrors(self):
+               return self.backend.mirrors
+
+
+class AdminMirrorsHandler(AdminMirrorsBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               mirrors = self.mirrors.list()
+
+               self.render("admin-mirrors.html", mirrors=mirrors)
+
+
+class AdminMirrorsUpdateHandler(AdminMirrorsBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               self.mirrors.check_all()
+               self.redirect("/mirrors")
+
+
+class AdminMirrorsCreateHandler(AdminMirrorsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id=None):
+               if id:
+                       mirror = self.db.get("SELECT * FROM mirrors WHERE id = '%s'", int(id))
+               else:
+                       mirror = tornado.database.Row(
+                               id="",
+                               hostname="",
+                               owner="",
+                               location="",
+                               path="/",
+                               releases="Y",
+                               pakfire2="Y",
+                               pakfire3="Y",
+                               disabled="N"
+                       )
+
+               self.render("admin-mirrors-create.html", mirror=mirror)
+
+       @tornado.web.authenticated
+       def post(self, id=None):
+               args = tornado.database.Row()
+               for key in ("id", "hostname", "owner", "location", "path", "releases",
+                               "pakfire2", "pakfire3", "disabled"):
+                       args[key] = self.get_argument(key, "")
+
+               if args.id:
+                       if not self.mirrors.get(args.id):
+                               raise tornado.web.HTTPError(404)
+
+                       #self.db.execute("""UPDATE mirrors SET
+                       #       hostname = %s, owner = %s, location = %s, path = %s, mirror = %s,
+                       #       pakfire2 = %s, pakfire3 = %s, disabled = %s
+                       #       WHERE id = %s""", args.hostname, args.owner, args.location,
+                       #       args.path, args.mirror, args.pakfire2, args.pakfire3, args.disabled,
+                       #       args.id)
+                       self.db.update("mirrors", args.id, **args)
+
+               else:
+                       #self.db.execute("""INSERT INTO
+                       #       mirrors(hostname, owner, location, path, mirror, pakfire2, pakfire3, disabled)
+                       #       VALUES(%s, %s, %s, %s, %s, %s, %s, %s)""", args.hostname, args.owner,
+                       #       args.location, args.path, args.mirror, args.pakfire2, args.pakfire3, args.disabled)
+                       self.db.insert("mirrors", **args)
+
+               # Update database information
+               self.mirrors.check_all()
+
+               self.redirect("/mirrors")
+
+
+class AdminMirrorsEditHandler(AdminMirrorsCreateHandler):
+       pass
+
+
+class AdminMirrorsDeleteHandler(AdminMirrorsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id):
+               self.db.execute("DELETE FROM mirrors WHERE id=%s", id)
+               self.db.execute("DELETE FROM mirror_files WHERE mirror=%s", id)
+               self.redirect("/mirrors")
+
+
+class AdminMirrorsDetailsHandler(AdminMirrorsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id):
+               mirror = self.mirrors.get(id)
+               if not mirror:
+                       raise tornado.web.HTTPError(404)
+
+               self.render("admin-mirrors-details.html", mirror=mirror)
+
+
+class AdminNewsBaseHandler(AdminBaseHandler):
+       @property
+       def news(self):
+               return self.backend.news
+
+
+class AdminNewsHandler(AdminNewsBaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               news = self.news.list()
+
+               self.render("admin-news.html", news=news)
+
+
+class AdminNewsCreateHandler(AdminNewsBaseHandler):
+       @tornado.web.authenticated
+       def get(self, id=None):
+               # if XXX
+               
+               
+               self.render("admin-news-create.html", news=news)
+
+
+class AdminNewsEditHandler(AdminNewsCreateHandler):
+       pass
diff --git a/www/webapp/handlers_base.py b/www/webapp/handlers_base.py
new file mode 100644 (file)
index 0000000..21ded7e
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+import httplib
+import time
+import tornado.locale
+import tornado.web
+
+import backend
+
+class BaseHandler(tornado.web.RequestHandler):
+       def get_account(self, uid):
+               # Find the name of the author
+               return self.accounts.find(uid)
+
+       def get_user_locale(self):
+               DEFAULT_LOCALE = tornado.locale.get("en_US")
+               ALLOWED_LOCALES = \
+                       [tornado.locale.get(l) for l in tornado.locale.get_supported_locales(None)]
+
+               # One can append "?locale=de" to mostly and URI on the site and
+               # another output that guessed.
+               locale = self.get_argument("locale", None)
+               if locale:
+                       locale = tornado.locale.get(locale)
+                       for locale in ALLOWED_LOCALES:
+                               return locale
+
+               # If no locale was provided we guess what the browser sends us
+               locale = self.get_browser_locale()
+               if locale in ALLOWED_LOCALES:
+                       return locale
+
+               # If no one of the cases above worked we use our default locale
+               return DEFAULT_LOCALE
+
+       @property
+       def render_args(self):
+               return {
+                       "hostname" : self.request.host,
+                       "lang" : self.locale.code[:2],
+                       "year" : time.strftime("%Y"),
+               }
+
+       def render(self, *args, **kwargs):
+               kwargs.update(self.render_args)
+               tornado.web.RequestHandler.render(self, *args, **kwargs)
+
+       def render_string(self, *args, **kwargs):
+               kwargs.update(self.render_args)
+               return tornado.web.RequestHandler.render_string(self, *args, **kwargs)
+
+       def get_error_html(self, status_code, **kwargs):
+               if status_code in (404, 500):
+                       render_args = ({
+                               "code"      : status_code,
+                               "exception" : kwargs.get("exception", None),
+                               "message"   : httplib.responses[status_code],
+                       })
+                       return self.render_string("error-%s.html" % status_code, **render_args)
+               else:
+                       return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
+
+       @property
+       def accounts(self):
+               return backend.Accounts()
+
+       @property
+       def banners(self):
+               return backend.Banners()
+
+       @property
+       def mirrors(self):
+               return backend.Mirrors()
+
+       @property
+       def news(self):
+               return backend.News()
+
+       @property
+       def config(self):
+               return backend.Config()
+
+       @property
+       def releases(self):
+               return backend.Releases()
+
+       @property
+       def banners(self):
+               return backend.Banners()
+
+       @property
+       def geoip(self):
+               return backend.GeoIP()
+
+       @property
+       def tracker(self):
+               return backend.Tracker()
diff --git a/www/webapp/handlers_download.py b/www/webapp/handlers_download.py
new file mode 100644 (file)
index 0000000..8033821
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+import random
+
+import tornado.web
+
+from handlers_base import *
+
+class DownloadsIndexHandler(BaseHandler):
+       def get(self):
+               self.render("downloads-index.html", release=self.releases.get_latest())
+
+
+class DownloadsReleaseHandler(BaseHandler):
+       def get(self, release):
+               release = self.releases.get_by_id(release)
+               if not release:
+                       raise tornado.web.HTTPError(404)
+
+               self.render("downloads-item.html", item=release)
+
+
+class DownloadsLatestHandler(BaseHandler):
+       def get(self):
+               release = self.releases.get_latest()
+               if not release:
+                       raise tornado.web.HTTPError(404)
+
+               self.render("downloads-item.html", item=release)
+
+
+class DownloadsOlderHandler(BaseHandler):
+       def get(self):
+               releases = self.releases.get_stable()
+
+               # Drop the latest release
+               if releases:
+                       releases = releases[1:]
+
+               self.render("downloads-older.html", releases=releases)
+
+
+class DownloadsDevelopmentHandler(BaseHandler):
+       def get(self):
+               releases = self.releases.get_unstable()
+
+               self.render("downloads-development.html", releases=releases)
+
+
+class DownloadHandler(BaseHandler):
+       def get(self):
+               self.render("downloads.html", release=self.releases.get_latest())
+
+
+class DownloadAllHandler(BaseHandler):
+       def get(self):
+               self.render("downloads-all.html",
+                       releases=self.releases.get_stable())
+
+
+class DownloadDevelopmentHandler(BaseHandler):
+       def get(self):
+               self.render("downloads-development.html",
+                       releases=self.releases.get_unstable())
+
+
+class DownloadFileHandler(BaseHandler):
+       def get(self, filename):
+               mirrors = self.mirrors.get_with_file(filename)
+
+               # Choose a random one
+               # XXX need better metric here
+               mirror = random.choice(mirrors)
+
+               self.redirect(mirror.url + filename)
diff --git a/www/webapp/handlers_mirrors.py b/www/webapp/handlers_mirrors.py
new file mode 100644 (file)
index 0000000..86d2e8a
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/python
+
+import socket
+import tornado.web
+
+from handlers_base import *
+
+class MirrorIndexHandler(BaseHandler):
+       def get(self):
+               mirrors = self.mirrors.list()
+
+               self.render("mirrors.html", mirrors=mirrors)
+
+
+class MirrorItemHandler(BaseHandler):
+       def get(self, id):
+               mirror = self.mirrors.get(id)
+               if not mirror:
+                       raise tornado.web.HTTPError(404)
+
+               ip = socket.gethostbyname(mirror.hostname)
+               mirror.location = self.geoip.get_all(ip)
+
+               # Shortcut for coordiantes
+               mirror.coordiantes = "%s,%s" % \
+                       (mirror.location.latitude, mirror.location.longitude)
+
+               # Nice string for the user
+               mirror.location_str = mirror.location.country_code
+               if mirror.location.city:
+                       mirror.location_str = "%s, %s" % \
+                               (mirror.location.city, mirror.location_str)
+
+               self.render("mirrors-item.html", item=mirror)
+
+
+class MirrorHandler(BaseHandler):
+       def get(self):
+               self.redirect("mirrors/all")
+
+
+class MirrorAllHandler(BaseHandler):
+       def get(self):
+               self.render("downloads-mirrors.html", mirrors=self.mirrors.list())
+
+
+class MirrorDetailHandler(BaseHandler):
+       def get(self, id):
+               self.render("download-mirror-detail.html", mirror=self.mirrors.get(id))
diff --git a/www/webapp/handlers_news.py b/www/webapp/handlers_news.py
new file mode 100644 (file)
index 0000000..acaf39e
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+
+import tornado.web
+
+from handlers_base import *
+
+class NewsRedirectHandler(BaseHandler):
+       """
+               This handler redirects (with a given slug) to news.ipfire.org.
+
+               We do not show full news items on the main page and so need a simple
+               way to redirect. Here it is.
+       """
+       def get(self, slug):
+               self.redirect("http://news.ipfire.org/news/%s" % slug)
+
+
+class NewsIndexHandler(BaseHandler):
+       """
+               This handler fetches the content that is show on the news portal.
+       """
+       def get(self):
+               offset = int(self.get_argument("offset", 0))
+               limit = int(self.get_argument("limit", 4))
+
+               news = self.news.get_latest(
+                       locale=self.locale,
+                       limit=limit,
+                       offset=offset,
+               )
+
+               return self.render("news.html", news=news,
+                       offset=offset + limit, limit=limit)
+
+
+class NewsItemHandler(BaseHandler):
+       """
+               This handler displays a whole page full of a single news item.
+       """
+       def get(self, slug):
+               news = self.news.get_by_slug(slug)
+               if not news:
+                       raise tornado.web.HTTPError(404)
+
+               # Find the name of the author
+               author = self.get_account(news.author_id)
+               if author:
+                       news.author = author.cn
+               else:
+                       _ = self.locale.translate
+                       news.author = _("Unknown author")
+
+               return self.render("news-item.html", item=news)
+
+
+class NewsAuthorHandler(BaseHandler):
+       """
+               This page displays information about the news author.
+       """
+       def get(self, author):
+               author = self.get_account(author)
+               if not author:
+                       raise tornado.web.HTTPError(404)
+
+               latest_news = self.news.get_latest(author=author.uid,
+                       locale=self.locale, limit=3)
+
+               self.render("news-author.html",
+                       author=author, latest_news=latest_news)
diff --git a/www/webapp/handlers_planet.py b/www/webapp/handlers_planet.py
new file mode 100644 (file)
index 0000000..3bb533b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+
+import tornado.web
+
+from handlers_base import *
+
+import backend
+
+from backend.databases import Databases
+
+class PlanetBaseHandler(BaseHandler):
+       @property
+       def db(self):
+               return Databases().webapp
+
+       @property
+       def planet(self):
+               return backend.Planet()
+
+
+class PlanetMainHandler(PlanetBaseHandler):
+       def get(self):
+               offset = int(self.get_argument("offset", 0))
+               limit = int(self.get_argument("limit", 4))
+
+               entries = self.planet.get_entries(offset=offset, limit=limit)
+
+               self.render("planet-main.html", entries=entries,
+                       authors=self.planet.get_authors(),
+                       offset=offset + limit, limit=limit)
+
+
+class PlanetUserHandler(PlanetBaseHandler):
+       def get(self, author):
+               author = self.accounts.search(author)
+               if not author:
+                       raise tornado.web.HTTPError(404, "User is unknown")
+
+               offset = int(self.get_argument("offset", 0))
+               limit = int(self.get_argument("limit", 4))
+
+               entries = self.planet.get_entries_by_author(author.uid,
+                       offset=offset, limit=limit)
+
+               self.render("planet-user.html", author=author, entries=entries,
+                       offset=offset + limit, limit=limit)
+
+
+class PlanetPostingHandler(PlanetBaseHandler):
+       def get(self, slug):
+               entry = self.planet.get_entry_by_slug(slug)
+
+               if not entry:
+                       raise tornado.web.HTTPError(404)
+
+               self.render("planet-posting.html",
+                       author=entry.author, entry=entry)
diff --git a/www/webapp/handlers_stasy.py b/www/webapp/handlers_stasy.py
new file mode 100644 (file)
index 0000000..5dc0a30
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+import tornado.web
+
+from handlers_base import *
+
+class StasyBaseHandler(BaseHandler):
+       @property
+       def stasy(self):
+               return backend.Stasy()
+
+
+class StasyIndexHandler(StasyBaseHandler):
+       def get(self):
+               profiles = self.stasy.get_profiles()
+
+               self.render("stasy-index.html", profiles=profiles)
+
+
+class StasyProfileHandler(StasyBaseHandler):
+       def get(self, profile_id):
+               profile = self.stasy.get_profile(profile_id)
+               if not profile:
+                       raise tornado.web.HTTPError(404, "Profile not found: %s" % profile_id)
+
+               self.render("stasy-profile.html", profile=profile)
diff --git a/www/webapp/handlers_tracker.py b/www/webapp/handlers_tracker.py
new file mode 100644 (file)
index 0000000..8469298
--- /dev/null
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+
+import tornado.web
+
+from backend.tracker import bencode, bdecode, decode_hex
+from handlers_base import *
+
+
+class TrackerIndexHandler(BaseHandler):
+       def get(self):
+               hashes = self.tracker.hashes
+
+               torrents = []
+               for release in self.releases.list():
+                       if not release.torrent_hash:
+                               continue
+
+                       if release.torrent_hash in hashes:
+                               torrents.append(tornado.database.Row({
+                                       "name"  : release.name,
+                                       "hash"  : release.torrent_hash.lower(),
+                                       "peers" : self.tracker.incomplete(release.torrent_hash),
+                                       "seeds" : self.tracker.complete(release.torrent_hash),
+                               }))
+
+               self.render("tracker-torrents.html", torrents=torrents)         
+
+
+class TrackerDetailHandler(BaseHandler):
+       def get(self, hash):
+               release = None
+               for r in self.releases.list():
+                       if not r.torrent_hash:
+                               continue
+
+                       if r.torrent_hash.lower() == hash.lower():
+                               release = r
+                               break
+
+               if not release:
+                       raise tornado.web.HTTPError(404)
+
+               torrent = tornado.database.Row({
+                       "peers" : self.tracker.get_peers(hash),
+                       "seeds" : self.tracker.get_seeds(hash),
+               })
+
+               self.render("tracker-torrent-detail.html", release=release, torrent=torrent)
+
+
+#class TrackerTorrentsHandler(BaseHandler):
+#      @property
+#      def tracker(self):
+#              return self.tracker
+#
+#      def get(self):          
+#              releases = []
+#
+#              for release in self.releases.get_all():
+#                      if not release.torrent_hash:
+#                              continue
+#
+#                      release.torrent_hash = release.torrent_hash.lower()
+#
+#                      release.torrent_peers = self.tracker.incomplete(release.torrent_hash)
+#                      release.torrent_seeds = self.tracker.complete(release.torrent_hash)
+#
+#                      releases.append(release)
+#
+#              self.render("tracker-torrents.html", releases=releases)
+
+
+class TrackerBaseHandler(tornado.web.RequestHandler):
+       @property
+       def tracker(self):
+               return backend.Tracker()
+
+       def get_hexencoded_argument(self, name, all=False):
+               try:
+                       arguments = self.request.arguments[name]
+               except KeyError:
+                       return None
+
+               arguments_new = []
+               for argument in arguments:
+                       arguments_new.append(decode_hex(argument))
+
+               arguments = arguments_new
+
+               if all:
+                       return arguments
+
+               return arguments[0]
+
+       def send_tracker_error(self, error_message):
+               self.write(bencode({"failure reason" : error_message }))
+               self.finish()
+
+
+class TrackerAnnounceHandler(TrackerBaseHandler):
+       def get(self):
+               self.set_header("Content-Type", "text/plain")
+
+               info_hash = self.get_hexencoded_argument("info_hash")
+               if not info_hash:
+                       self.send_tracker_error("Your client forgot to send your torrent's info_hash.")
+                       return
+
+               peer = {
+                       "id" : self.get_hexencoded_argument("peer_id"),
+                       "ip" : self.request.remote_ip,
+                       "port" : self.get_argument("port", None),
+                       "downloaded" : self.get_argument("downloaded", 0),
+                       "uploaded" : self.get_argument("uploaded", 0),
+                       "left" : self.get_argument("left", 0),
+               }
+
+               event = self.get_argument("event", "")
+               if not event in ("started", "stopped", "completed", ""):
+                       self.send_tracker_error("Got unknown event")
+                       return
+
+               if peer["port"]:
+                       peer["port"] = int(peer["port"])
+
+                       if peer["port"] < 0 or peer["port"] > 65535:
+                               self.send_tracker_error("Port number is not in valid range")
+                               return
+
+               eventhandlers = {
+                       "started" : self.tracker.event_started,
+                       "stopped" : self.tracker.event_stopped,
+                       "completed" : self.tracker.event_completed,
+               }
+
+               if event:
+                       eventhandlers[event](info_hash, peer["id"])
+
+               self.tracker.update(hash=info_hash, **peer)
+
+               no_peer_id = self.get_argument("no_peer_id", False)
+               numwant = self.get_argument("numwant", self.tracker.numwant)
+
+               self.write(bencode({
+                       "tracker id" : self.tracker.id,
+                       "interval" : self.tracker.interval,
+                       "min interval" : self.tracker.min_interval,
+                       "peers" : self.tracker.get_peers(info_hash, limit=numwant,
+                               random=True, no_peer_id=no_peer_id),
+                       "complete" : self.tracker.complete(info_hash),
+                       "incomplete" : self.tracker.incomplete(info_hash),
+               }))
+               self.finish()
+
+
+class TrackerScrapeHandler(TrackerBaseHandler):
+       def get(self):
+               info_hashes = self.get_hexencoded_argument("info_hash", all=True)
+
+               self.write(bencode(self.tracker.scrape(hashes=info_hashes)))
+               self.finish()
diff --git a/www/webapp/helpers.py b/www/webapp/helpers.py
deleted file mode 100644 (file)
index 5e4b25d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/python
-
-import simplejson
-import subprocess
-
-def size(s):
-       suffixes = ["B", "K", "M", "G", "T",]
-       
-       idx = 0
-       while s >= 1024 and idx < len(suffixes):
-               s /= 1024
-               idx += 1
-
-       return "%.0f%s" % (s, suffixes[idx])
-
-def ping(host, count=5, wait=10):
-       cmd = subprocess.Popen(
-               ["ping", "-c%d" % count, "-w%d" % wait, host],
-               stdout = subprocess.PIPE,
-               stderr = subprocess.PIPE,
-       )
-
-       latency = None
-
-       out, error = cmd.communicate()
-
-       for line in out.split("\n"):
-               if not line.startswith("rtt"):
-                       continue
-
-               line = line.split()
-               if len(line) < 4:
-                       break
-
-               rtts = line[3].split("/")
-               if len(rtts) < 4:
-                       break
-
-               latency = "%.1f" % float(rtts[1])
-
-       return latency
diff --git a/www/webapp/markdown.py b/www/webapp/markdown.py
deleted file mode 100644 (file)
index 59ba731..0000000
+++ /dev/null
@@ -1,1877 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2007-2008 ActiveState Corp.
-# License: MIT (http://www.opensource.org/licenses/mit-license.php)
-
-r"""A fast and complete Python implementation of Markdown.
-
-[from http://daringfireball.net/projects/markdown/]
-> Markdown is a text-to-HTML filter; it translates an easy-to-read /
-> easy-to-write structured text format into HTML.  Markdown's text
-> format is most similar to that of plain text email, and supports
-> features such as headers, *emphasis*, code blocks, blockquotes, and
-> links.
->
-> Markdown's syntax is designed not as a generic markup language, but
-> specifically to serve as a front-end to (X)HTML. You can use span-level
-> HTML tags anywhere in a Markdown document, and you can use block level
-> HTML tags (like <div> and <table> as well).
-
-Module usage:
-
-    >>> import markdown2
-    >>> markdown2.markdown("*boo!*")  # or use `html = markdown_path(PATH)`
-    u'<p><em>boo!</em></p>\n'
-
-    >>> markdowner = Markdown()
-    >>> markdowner.convert("*boo!*")
-    u'<p><em>boo!</em></p>\n'
-    >>> markdowner.convert("**boom!**")
-    u'<p><strong>boom!</strong></p>\n'
-
-This implementation of Markdown implements the full "core" syntax plus a
-number of extras (e.g., code syntax coloring, footnotes) as described on
-<http://code.google.com/p/python-markdown2/wiki/Extras>.
-"""
-
-cmdln_desc = """A fast and complete Python implementation of Markdown, a
-text-to-HTML conversion tool for web writers.
-"""
-
-# Dev Notes:
-# - There is already a Python markdown processor
-#   (http://www.freewisdom.org/projects/python-markdown/).
-# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm
-#   not yet sure if there implications with this. Compare 'pydoc sre'
-#   and 'perldoc perlre'.
-
-__version_info__ = (1, 0, 1, 14) # first three nums match Markdown.pl
-__version__ = '1.0.1.14'
-__author__ = "Trent Mick"
-
-import os
-import sys
-from pprint import pprint
-import re
-import logging
-try:
-    from hashlib import md5
-except ImportError:
-    from md5 import md5
-import optparse
-from random import random
-import codecs
-
-
-
-#---- Python version compat
-
-if sys.version_info[:2] < (2,4):
-    from sets import Set as set
-    def reversed(sequence):
-        for i in sequence[::-1]:
-            yield i
-    def _unicode_decode(s, encoding, errors='xmlcharrefreplace'):
-        return unicode(s, encoding, errors)
-else:
-    def _unicode_decode(s, encoding, errors='strict'):
-        return s.decode(encoding, errors)
-
-
-#---- globals
-
-DEBUG = False
-log = logging.getLogger("markdown")
-
-DEFAULT_TAB_WIDTH = 4
-
-# Table of hash values for escaped characters:
-def _escape_hash(s):
-    # Lame attempt to avoid possible collision with someone actually
-    # using the MD5 hexdigest of one of these chars in there text.
-    # Other ideas: random.random(), uuid.uuid()
-    #return md5(s).hexdigest()   # Markdown.pl effectively does this.
-    return 'md5-'+md5(s).hexdigest()
-g_escape_table = dict([(ch, _escape_hash(ch))
-                       for ch in '\\`*_{}[]()>#+-.!'])
-
-
-
-#---- exceptions
-
-class MarkdownError(Exception):
-    pass
-
-
-
-#---- public api
-
-def markdown_path(path, encoding="utf-8",
-                  html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
-                  safe_mode=None, extras=None, link_patterns=None,
-                  use_file_vars=False):
-    text = codecs.open(path, 'r', encoding).read()
-    return Markdown(html4tags=html4tags, tab_width=tab_width,
-                    safe_mode=safe_mode, extras=extras,
-                    link_patterns=link_patterns,
-                    use_file_vars=use_file_vars).convert(text)
-
-def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
-             safe_mode=None, extras=None, link_patterns=None,
-             use_file_vars=False):
-    return Markdown(html4tags=html4tags, tab_width=tab_width,
-                    safe_mode=safe_mode, extras=extras,
-                    link_patterns=link_patterns,
-                    use_file_vars=use_file_vars).convert(text)
-
-class Markdown(object):
-    # The dict of "extras" to enable in processing -- a mapping of
-    # extra name to argument for the extra. Most extras do not have an
-    # argument, in which case the value is None.
-    #
-    # This can be set via (a) subclassing and (b) the constructor
-    # "extras" argument.
-    extras = None
-
-    urls = None
-    titles = None
-    html_blocks = None
-    html_spans = None
-    html_removed_text = "[HTML_REMOVED]"  # for compat with markdown.py
-
-    # Used to track when we're inside an ordered or unordered list
-    # (see _ProcessListItems() for details):
-    list_level = 0
-
-    _ws_only_line_re = re.compile(r"^[ \t]+$", re.M)
-
-    def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
-                 extras=None, link_patterns=None, use_file_vars=False):
-        if html4tags:
-            self.empty_element_suffix = ">"
-        else:
-            self.empty_element_suffix = " />"
-        self.tab_width = tab_width
-
-        # For compatibility with earlier markdown2.py and with
-        # markdown.py's safe_mode being a boolean, 
-        #   safe_mode == True -> "replace"
-        if safe_mode is True:
-            self.safe_mode = "replace"
-        else:
-            self.safe_mode = safe_mode
-
-        if self.extras is None:
-            self.extras = {}
-        elif not isinstance(self.extras, dict):
-            self.extras = dict([(e, None) for e in self.extras])
-        if extras:
-            if not isinstance(extras, dict):
-                extras = dict([(e, None) for e in extras])
-            self.extras.update(extras)
-        assert isinstance(self.extras, dict)
-        self._instance_extras = self.extras.copy()
-        self.link_patterns = link_patterns
-        self.use_file_vars = use_file_vars
-        self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M)
-
-    def reset(self):
-        self.urls = {}
-        self.titles = {}
-        self.html_blocks = {}
-        self.html_spans = {}
-        self.list_level = 0
-        self.extras = self._instance_extras.copy()
-        if "footnotes" in self.extras:
-            self.footnotes = {}
-            self.footnote_ids = []
-
-    def convert(self, text):
-        """Convert the given text."""
-        # Main function. The order in which other subs are called here is
-        # essential. Link and image substitutions need to happen before
-        # _EscapeSpecialChars(), so that any *'s or _'s in the <a>
-        # and <img> tags get encoded.
-
-        # Clear the global hashes. If we don't clear these, you get conflicts
-        # from other articles when generating a page which contains more than
-        # one article (e.g. an index page that shows the N most recent
-        # articles):
-        self.reset()
-
-        if not isinstance(text, unicode):
-            #TODO: perhaps shouldn't presume UTF-8 for string input?
-            text = unicode(text, 'utf-8')
-
-        if self.use_file_vars:
-            # Look for emacs-style file variable hints.
-            emacs_vars = self._get_emacs_vars(text)
-            if "markdown-extras" in emacs_vars:
-                splitter = re.compile("[ ,]+")
-                for e in splitter.split(emacs_vars["markdown-extras"]):
-                    if '=' in e:
-                        ename, earg = e.split('=', 1)
-                        try:
-                            earg = int(earg)
-                        except ValueError:
-                            pass
-                    else:
-                        ename, earg = e, None
-                    self.extras[ename] = earg
-
-        # Standardize line endings:
-        text = re.sub("\r\n|\r", "\n", text)
-
-        # Make sure $text ends with a couple of newlines:
-        text += "\n\n"
-
-        # Convert all tabs to spaces.
-        text = self._detab(text)
-
-        # Strip any lines consisting only of spaces and tabs.
-        # This makes subsequent regexen easier to write, because we can
-        # match consecutive blank lines with /\n+/ instead of something
-        # contorted like /[ \t]*\n+/ .
-        text = self._ws_only_line_re.sub("", text)
-
-        if self.safe_mode:
-            text = self._hash_html_spans(text)
-
-        # Turn block-level HTML blocks into hash entries
-        text = self._hash_html_blocks(text, raw=True)
-
-        # Strip link definitions, store in hashes.
-        if "footnotes" in self.extras:
-            # Must do footnotes first because an unlucky footnote defn
-            # looks like a link defn:
-            #   [^4]: this "looks like a link defn"
-            text = self._strip_footnote_definitions(text)
-        text = self._strip_link_definitions(text)
-
-        text = self._run_block_gamut(text)
-
-        if "footnotes" in self.extras:
-            text = self._add_footnotes(text)
-
-        text = self._unescape_special_chars(text)
-
-        if self.safe_mode:
-            text = self._unhash_html_spans(text)
-
-        text += "\n"
-        return text
-
-    _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE)
-    # This regular expression is intended to match blocks like this:
-    #    PREFIX Local Variables: SUFFIX
-    #    PREFIX mode: Tcl SUFFIX
-    #    PREFIX End: SUFFIX
-    # Some notes:
-    # - "[ \t]" is used instead of "\s" to specifically exclude newlines
-    # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does
-    #   not like anything other than Unix-style line terminators.
-    _emacs_local_vars_pat = re.compile(r"""^
-        (?P<prefix>(?:[^\r\n|\n|\r])*?)
-        [\ \t]*Local\ Variables:[\ \t]*
-        (?P<suffix>.*?)(?:\r\n|\n|\r)
-        (?P<content>.*?\1End:)
-        """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE)
-
-    def _get_emacs_vars(self, text):
-        """Return a dictionary of emacs-style local variables.
-
-        Parsing is done loosely according to this spec (and according to
-        some in-practice deviations from this):
-        http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables
-        """
-        emacs_vars = {}
-        SIZE = pow(2, 13) # 8kB
-
-        # Search near the start for a '-*-'-style one-liner of variables.
-        head = text[:SIZE]
-        if "-*-" in head:
-            match = self._emacs_oneliner_vars_pat.search(head)
-            if match:
-                emacs_vars_str = match.group(1)
-                assert '\n' not in emacs_vars_str
-                emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';')
-                                  if s.strip()]
-                if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]:
-                    # While not in the spec, this form is allowed by emacs:
-                    #   -*- Tcl -*-
-                    # where the implied "variable" is "mode". This form
-                    # is only allowed if there are no other variables.
-                    emacs_vars["mode"] = emacs_var_strs[0].strip()
-                else:
-                    for emacs_var_str in emacs_var_strs:
-                        try:
-                            variable, value = emacs_var_str.strip().split(':', 1)
-                        except ValueError:
-                            log.debug("emacs variables error: malformed -*- "
-                                      "line: %r", emacs_var_str)
-                            continue
-                        # Lowercase the variable name because Emacs allows "Mode"
-                        # or "mode" or "MoDe", etc.
-                        emacs_vars[variable.lower()] = value.strip()
-
-        tail = text[-SIZE:]
-        if "Local Variables" in tail:
-            match = self._emacs_local_vars_pat.search(tail)
-            if match:
-                prefix = match.group("prefix")
-                suffix = match.group("suffix")
-                lines = match.group("content").splitlines(0)
-                #print "prefix=%r, suffix=%r, content=%r, lines: %s"\
-                #      % (prefix, suffix, match.group("content"), lines)
-
-                # Validate the Local Variables block: proper prefix and suffix
-                # usage.
-                for i, line in enumerate(lines):
-                    if not line.startswith(prefix):
-                        log.debug("emacs variables error: line '%s' "
-                                  "does not use proper prefix '%s'"
-                                  % (line, prefix))
-                        return {}
-                    # Don't validate suffix on last line. Emacs doesn't care,
-                    # neither should we.
-                    if i != len(lines)-1 and not line.endswith(suffix):
-                        log.debug("emacs variables error: line '%s' "
-                                  "does not use proper suffix '%s'"
-                                  % (line, suffix))
-                        return {}
-
-                # Parse out one emacs var per line.
-                continued_for = None
-                for line in lines[:-1]: # no var on the last line ("PREFIX End:")
-                    if prefix: line = line[len(prefix):] # strip prefix
-                    if suffix: line = line[:-len(suffix)] # strip suffix
-                    line = line.strip()
-                    if continued_for:
-                        variable = continued_for
-                        if line.endswith('\\'):
-                            line = line[:-1].rstrip()
-                        else:
-                            continued_for = None
-                        emacs_vars[variable] += ' ' + line
-                    else:
-                        try:
-                            variable, value = line.split(':', 1)
-                        except ValueError:
-                            log.debug("local variables error: missing colon "
-                                      "in local variables entry: '%s'" % line)
-                            continue
-                        # Do NOT lowercase the variable name, because Emacs only
-                        # allows "mode" (and not "Mode", "MoDe", etc.) in this block.
-                        value = value.strip()
-                        if value.endswith('\\'):
-                            value = value[:-1].rstrip()
-                            continued_for = variable
-                        else:
-                            continued_for = None
-                        emacs_vars[variable] = value
-
-        # Unquote values.
-        for var, val in emacs_vars.items():
-            if len(val) > 1 and (val.startswith('"') and val.endswith('"')
-               or val.startswith('"') and val.endswith('"')):
-                emacs_vars[var] = val[1:-1]
-
-        return emacs_vars
-
-    # Cribbed from a post by Bart Lateur:
-    # <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
-    _detab_re = re.compile(r'(.*?)\t', re.M)
-    def _detab_sub(self, match):
-        g1 = match.group(1)
-        return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width))
-    def _detab(self, text):
-        r"""Remove (leading?) tabs from a file.
-
-            >>> m = Markdown()
-            >>> m._detab("\tfoo")
-            '    foo'
-            >>> m._detab("  \tfoo")
-            '    foo'
-            >>> m._detab("\t  foo")
-            '      foo'
-            >>> m._detab("  foo")
-            '  foo'
-            >>> m._detab("  foo\n\tbar\tblam")
-            '  foo\n    bar blam'
-        """
-        if '\t' not in text:
-            return text
-        return self._detab_re.subn(self._detab_sub, text)[0]
-
-    _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del'
-    _strict_tag_block_re = re.compile(r"""
-        (                       # save in \1
-            ^                   # start of line  (with re.M)
-            <(%s)               # start tag = \2
-            \b                  # word break
-            (.*\n)*?            # any number of lines, minimally matching
-            </\2>               # the matching end tag
-            [ \t]*              # trailing spaces/tabs
-            (?=\n+|\Z)          # followed by a newline or end of document
-        )
-        """ % _block_tags_a,
-        re.X | re.M)
-
-    _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
-    _liberal_tag_block_re = re.compile(r"""
-        (                       # save in \1
-            ^                   # start of line  (with re.M)
-            <(%s)               # start tag = \2
-            \b                  # word break
-            (.*\n)*?            # any number of lines, minimally matching
-            .*</\2>             # the matching end tag
-            [ \t]*              # trailing spaces/tabs
-            (?=\n+|\Z)          # followed by a newline or end of document
-        )
-        """ % _block_tags_b,
-        re.X | re.M)
-
-    def _hash_html_block_sub(self, match, raw=False):
-        html = match.group(1)
-        if raw and self.safe_mode:
-            html = self._sanitize_html(html)
-        key = _hash_text(html)
-        self.html_blocks[key] = html
-        return "\n\n" + key + "\n\n"
-
-    def _hash_html_blocks(self, text, raw=False):
-        """Hashify HTML blocks
-
-        We only want to do this for block-level HTML tags, such as headers,
-        lists, and tables. That's because we still want to wrap <p>s around
-        "paragraphs" that are wrapped in non-block-level tags, such as anchors,
-        phrase emphasis, and spans. The list of tags we're looking for is
-        hard-coded.
-
-        @param raw {boolean} indicates if these are raw HTML blocks in
-            the original source. It makes a difference in "safe" mode.
-        """
-        if '<' not in text:
-            return text
-
-        # Pass `raw` value into our calls to self._hash_html_block_sub.
-        hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw)
-
-        # First, look for nested blocks, e.g.:
-        #   <div>
-        #       <div>
-        #       tags for inner block must be indented.
-        #       </div>
-        #   </div>
-        #
-        # The outermost tags must start at the left margin for this to match, and
-        # the inner nested divs must be indented.
-        # We need to do this before the next, more liberal match, because the next
-        # match will start at the first `<div>` and stop at the first `</div>`.
-        text = self._strict_tag_block_re.sub(hash_html_block_sub, text)
-
-        # Now match more liberally, simply from `\n<tag>` to `</tag>\n`
-        text = self._liberal_tag_block_re.sub(hash_html_block_sub, text)
-
-        # Special case just for <hr />. It was easier to make a special
-        # case than to make the other regex more complicated.   
-        if "<hr" in text:
-            _hr_tag_re = _hr_tag_re_from_tab_width(self.tab_width)
-            text = _hr_tag_re.sub(hash_html_block_sub, text)
-
-        # Special case for standalone HTML comments:
-        if "<!--" in text:
-            start = 0
-            while True:
-                # Delimiters for next comment block.
-                try:
-                    start_idx = text.index("<!--", start)
-                except ValueError, ex:
-                    break
-                try:
-                    end_idx = text.index("-->", start_idx) + 3
-                except ValueError, ex:
-                    break
-
-                # Start position for next comment block search.
-                start = end_idx
-
-                # Validate whitespace before comment.
-                if start_idx:
-                    # - Up to `tab_width - 1` spaces before start_idx.
-                    for i in range(self.tab_width - 1):
-                        if text[start_idx - 1] != ' ':
-                            break
-                        start_idx -= 1
-                        if start_idx == 0:
-                            break
-                    # - Must be preceded by 2 newlines or hit the start of
-                    #   the document.
-                    if start_idx == 0:
-                        pass
-                    elif start_idx == 1 and text[0] == '\n':
-                        start_idx = 0  # to match minute detail of Markdown.pl regex
-                    elif text[start_idx-2:start_idx] == '\n\n':
-                        pass
-                    else:
-                        break
-
-                # Validate whitespace after comment.
-                # - Any number of spaces and tabs.
-                while end_idx < len(text):
-                    if text[end_idx] not in ' \t':
-                        break
-                    end_idx += 1
-                # - Must be following by 2 newlines or hit end of text.
-                if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'):
-                    continue
-
-                # Escape and hash (must match `_hash_html_block_sub`).
-                html = text[start_idx:end_idx]
-                if raw and self.safe_mode:
-                    html = self._sanitize_html(html)
-                key = _hash_text(html)
-                self.html_blocks[key] = html
-                text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:]
-
-        if "xml" in self.extras:
-            # Treat XML processing instructions and namespaced one-liner
-            # tags as if they were block HTML tags. E.g., if standalone
-            # (i.e. are their own paragraph), the following do not get 
-            # wrapped in a <p> tag:
-            #    <?foo bar?>
-            #
-            #    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapter_1.md"/>
-            _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width)
-            text = _xml_oneliner_re.sub(hash_html_block_sub, text)
-
-        return text
-
-    def _strip_link_definitions(self, text):
-        # Strips link definitions from text, stores the URLs and titles in
-        # hash references.
-        less_than_tab = self.tab_width - 1
-    
-        # Link defs are in the form:
-        #   [id]: url "optional title"
-        _link_def_re = re.compile(r"""
-            ^[ ]{0,%d}\[(.+)\]: # id = \1
-              [ \t]*
-              \n?               # maybe *one* newline
-              [ \t]*
-            <?(.+?)>?           # url = \2
-              [ \t]*
-            (?:
-                \n?             # maybe one newline
-                [ \t]*
-                (?<=\s)         # lookbehind for whitespace
-                ['"(]
-                ([^\n]*)        # title = \3
-                ['")]
-                [ \t]*
-            )?  # title is optional
-            (?:\n+|\Z)
-            """ % less_than_tab, re.X | re.M | re.U)
-        return _link_def_re.sub(self._extract_link_def_sub, text)
-
-    def _extract_link_def_sub(self, match):
-        id, url, title = match.groups()
-        key = id.lower()    # Link IDs are case-insensitive
-        self.urls[key] = self._encode_amps_and_angles(url)
-        if title:
-            self.titles[key] = title.replace('"', '&quot;')
-        return ""
-
-    def _extract_footnote_def_sub(self, match):
-        id, text = match.groups()
-        text = _dedent(text, skip_first_line=not text.startswith('\n')).strip()
-        normed_id = re.sub(r'\W', '-', id)
-        # Ensure footnote text ends with a couple newlines (for some
-        # block gamut matches).
-        self.footnotes[normed_id] = text + "\n\n"
-        return ""
-
-    def _strip_footnote_definitions(self, text):
-        """A footnote definition looks like this:
-
-            [^note-id]: Text of the note.
-
-                May include one or more indented paragraphs.
-
-        Where,
-        - The 'note-id' can be pretty much anything, though typically it
-          is the number of the footnote.
-        - The first paragraph may start on the next line, like so:
-            
-            [^note-id]:
-                Text of the note.
-        """
-        less_than_tab = self.tab_width - 1
-        footnote_def_re = re.compile(r'''
-            ^[ ]{0,%d}\[\^(.+)\]:   # id = \1
-            [ \t]*
-            (                       # footnote text = \2
-              # First line need not start with the spaces.
-              (?:\s*.*\n+)
-              (?:
-                (?:[ ]{%d} | \t)  # Subsequent lines must be indented.
-                .*\n+
-              )*
-            )
-            # Lookahead for non-space at line-start, or end of doc.
-            (?:(?=^[ ]{0,%d}\S)|\Z)
-            ''' % (less_than_tab, self.tab_width, self.tab_width),
-            re.X | re.M)
-        return footnote_def_re.sub(self._extract_footnote_def_sub, text)
-
-
-    _hr_res = [
-        re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M),
-        re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M),
-        re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M),
-    ]
-
-    def _run_block_gamut(self, text):
-        # These are all the transformations that form block-level
-        # tags like paragraphs, headers, and list items.
-
-        text = self._do_headers(text)
-
-        # Do Horizontal Rules:
-        hr = "\n<hr"+self.empty_element_suffix+"\n"
-        for hr_re in self._hr_res:
-            text = hr_re.sub(hr, text)
-
-        text = self._do_lists(text)
-
-        if "pyshell" in self.extras:
-            text = self._prepare_pyshell_blocks(text)
-
-        text = self._do_code_blocks(text)
-
-        text = self._do_block_quotes(text)
-
-        # We already ran _HashHTMLBlocks() before, in Markdown(), but that
-        # was to escape raw HTML in the original Markdown source. This time,
-        # we're escaping the markup we've just created, so that we don't wrap
-        # <p> tags around block-level tags.
-        text = self._hash_html_blocks(text)
-
-        text = self._form_paragraphs(text)
-
-        return text
-
-    def _pyshell_block_sub(self, match):
-        lines = match.group(0).splitlines(0)
-        _dedentlines(lines)
-        indent = ' ' * self.tab_width
-        s = ('\n' # separate from possible cuddled paragraph
-             + indent + ('\n'+indent).join(lines)
-             + '\n\n')
-        return s
-        
-    def _prepare_pyshell_blocks(self, text):
-        """Ensure that Python interactive shell sessions are put in
-        code blocks -- even if not properly indented.
-        """
-        if ">>>" not in text:
-            return text
-
-        less_than_tab = self.tab_width - 1
-        _pyshell_block_re = re.compile(r"""
-            ^([ ]{0,%d})>>>[ ].*\n   # first line
-            ^(\1.*\S+.*\n)*         # any number of subsequent lines
-            ^\n                     # ends with a blank line
-            """ % less_than_tab, re.M | re.X)
-
-        return _pyshell_block_re.sub(self._pyshell_block_sub, text)
-
-    def _run_span_gamut(self, text):
-        # These are all the transformations that occur *within* block-level
-        # tags like paragraphs, headers, and list items.
-    
-        text = self._do_code_spans(text)
-    
-        text = self._escape_special_chars(text)
-    
-        # Process anchor and image tags.
-        text = self._do_links(text)
-    
-        # Make links out of things like `<http://example.com/>`
-        # Must come after _do_links(), because you can use < and >
-        # delimiters in inline links like [this](<url>).
-        text = self._do_auto_links(text)
-
-        if "link-patterns" in self.extras:
-            text = self._do_link_patterns(text)
-    
-        text = self._encode_amps_and_angles(text)
-    
-        text = self._do_italics_and_bold(text)
-    
-        # Do hard breaks:
-        text = re.sub(r" {2,}\n", " <br%s\n" % self.empty_element_suffix, text)
-    
-        return text
-
-    # "Sorta" because auto-links are identified as "tag" tokens.
-    _sorta_html_tokenize_re = re.compile(r"""
-        (
-            # tag
-            </?         
-            (?:\w+)                                     # tag name
-            (?:\s+(?:[\w-]+:)?[\w-]+=(?:".*?"|'.*?'))*  # attributes
-            \s*/?>
-            |
-            # auto-link (e.g., <http://www.activestate.com/>)
-            <\w+[^>]*>
-            |
-            <!--.*?-->      # comment
-            |
-            <\?.*?\?>       # processing instruction
-        )
-        """, re.X)
-    
-    def _escape_special_chars(self, text):
-        # Python markdown note: the HTML tokenization here differs from
-        # that in Markdown.pl, hence the behaviour for subtle cases can
-        # differ (I believe the tokenizer here does a better job because
-        # it isn't susceptible to unmatched '<' and '>' in HTML tags).
-        # Note, however, that '>' is not allowed in an auto-link URL
-        # here.
-        escaped = []
-        is_html_markup = False
-        for token in self._sorta_html_tokenize_re.split(text):
-            if is_html_markup:
-                # Within tags/HTML-comments/auto-links, encode * and _
-                # so they don't conflict with their use in Markdown for
-                # italics and strong.  We're replacing each such
-                # character with its corresponding MD5 checksum value;
-                # this is likely overkill, but it should prevent us from
-                # colliding with the escape values by accident.
-                escaped.append(token.replace('*', g_escape_table['*'])
-                                    .replace('_', g_escape_table['_']))
-            else:
-                escaped.append(self._encode_backslash_escapes(token))
-            is_html_markup = not is_html_markup
-        return ''.join(escaped)
-
-    def _hash_html_spans(self, text):
-        # Used for safe_mode.
-
-        def _is_auto_link(s):
-            if ':' in s and self._auto_link_re.match(s):
-                return True
-            elif '@' in s and self._auto_email_link_re.match(s):
-                return True
-            return False
-
-        tokens = []
-        is_html_markup = False
-        for token in self._sorta_html_tokenize_re.split(text):
-            if is_html_markup and not _is_auto_link(token):
-                sanitized = self._sanitize_html(token)
-                key = _hash_text(sanitized)
-                self.html_spans[key] = sanitized
-                tokens.append(key)
-            else:
-                tokens.append(token)
-            is_html_markup = not is_html_markup
-        return ''.join(tokens)
-
-    def _unhash_html_spans(self, text):
-        for key, sanitized in self.html_spans.items():
-            text = text.replace(key, sanitized)
-        return text
-
-    def _sanitize_html(self, s):
-        if self.safe_mode == "replace":
-            return self.html_removed_text
-        elif self.safe_mode == "escape":
-            replacements = [
-                ('&', '&amp;'),
-                ('<', '&lt;'),
-                ('>', '&gt;'),
-            ]
-            for before, after in replacements:
-                s = s.replace(before, after)
-            return s
-        else:
-            raise MarkdownError("invalid value for 'safe_mode': %r (must be "
-                                "'escape' or 'replace')" % self.safe_mode)
-
-    _tail_of_inline_link_re = re.compile(r'''
-          # Match tail of: [text](/url/) or [text](/url/ "title")
-          \(            # literal paren
-            [ \t]*
-            (?P<url>            # \1
-                <.*?>
-                |
-                .*?
-            )
-            [ \t]*
-            (                   # \2
-              (['"])            # quote char = \3
-              (?P<title>.*?)
-              \3                # matching quote
-            )?                  # title is optional
-          \)
-        ''', re.X | re.S)
-    _tail_of_reference_link_re = re.compile(r'''
-          # Match tail of: [text][id]
-          [ ]?          # one optional space
-          (?:\n[ ]*)?   # one optional newline followed by spaces
-          \[
-            (?P<id>.*?)
-          \]
-        ''', re.X | re.S)
-
-    def _do_links(self, text):
-        """Turn Markdown link shortcuts into XHTML <a> and <img> tags.
-
-        This is a combination of Markdown.pl's _DoAnchors() and
-        _DoImages(). They are done together because that simplified the
-        approach. It was necessary to use a different approach than
-        Markdown.pl because of the lack of atomic matching support in
-        Python's regex engine used in $g_nested_brackets.
-        """
-        MAX_LINK_TEXT_SENTINEL = 3000  # markdown2 issue 24
-
-        # `anchor_allowed_pos` is used to support img links inside
-        # anchors, but not anchors inside anchors. An anchor's start
-        # pos must be `>= anchor_allowed_pos`.
-        anchor_allowed_pos = 0
-
-        curr_pos = 0
-        while True: # Handle the next link.
-            # The next '[' is the start of:
-            # - an inline anchor:   [text](url "title")
-            # - a reference anchor: [text][id]
-            # - an inline img:      ![text](url "title")
-            # - a reference img:    ![text][id]
-            # - a footnote ref:     [^id]
-            #   (Only if 'footnotes' extra enabled)
-            # - a footnote defn:    [^id]: ...
-            #   (Only if 'footnotes' extra enabled) These have already
-            #   been stripped in _strip_footnote_definitions() so no
-            #   need to watch for them.
-            # - a link definition:  [id]: url "title"
-            #   These have already been stripped in
-            #   _strip_link_definitions() so no need to watch for them.
-            # - not markup:         [...anything else...
-            try:
-                start_idx = text.index('[', curr_pos)
-            except ValueError:
-                break
-            text_length = len(text)
-
-            # Find the matching closing ']'.
-            # Markdown.pl allows *matching* brackets in link text so we
-            # will here too. Markdown.pl *doesn't* currently allow
-            # matching brackets in img alt text -- we'll differ in that
-            # regard.
-            bracket_depth = 0
-            for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL, 
-                                            text_length)):
-                ch = text[p]
-                if ch == ']':
-                    bracket_depth -= 1
-                    if bracket_depth < 0:
-                        break
-                elif ch == '[':
-                    bracket_depth += 1
-            else:
-                # Closing bracket not found within sentinel length.
-                # This isn't markup.
-                curr_pos = start_idx + 1
-                continue
-            link_text = text[start_idx+1:p]
-
-            # Possibly a footnote ref?
-            if "footnotes" in self.extras and link_text.startswith("^"):
-                normed_id = re.sub(r'\W', '-', link_text[1:])
-                if normed_id in self.footnotes:
-                    self.footnote_ids.append(normed_id)
-                    result = '<sup class="footnote-ref" id="fnref-%s">' \
-                             '<a href="#fn-%s">%s</a></sup>' \
-                             % (normed_id, normed_id, len(self.footnote_ids))
-                    text = text[:start_idx] + result + text[p+1:]
-                else:
-                    # This id isn't defined, leave the markup alone.
-                    curr_pos = p+1
-                continue
-
-            # Now determine what this is by the remainder.
-            p += 1
-            if p == text_length:
-                return text
-
-            # Inline anchor or img?
-            if text[p] == '(': # attempt at perf improvement
-                match = self._tail_of_inline_link_re.match(text, p)
-                if match:
-                    # Handle an inline anchor or img.
-                    is_img = start_idx > 0 and text[start_idx-1] == "!"
-                    if is_img:
-                        start_idx -= 1
-
-                    url, title = match.group("url"), match.group("title")
-                    if url and url[0] == '<':
-                        url = url[1:-1]  # '<url>' -> 'url'
-                    # We've got to encode these to avoid conflicting
-                    # with italics/bold.
-                    url = url.replace('*', g_escape_table['*']) \
-                             .replace('_', g_escape_table['_'])
-                    if title:
-                        title_str = ' title="%s"' \
-                            % title.replace('*', g_escape_table['*']) \
-                                   .replace('_', g_escape_table['_']) \
-                                   .replace('"', '&quot;')
-                    else:
-                        title_str = ''
-                    if is_img:
-                        result = '<img src="%s" alt="%s"%s%s' \
-                            % (url, link_text.replace('"', '&quot;'),
-                               title_str, self.empty_element_suffix)
-                        curr_pos = start_idx + len(result)
-                        text = text[:start_idx] + result + text[match.end():]
-                    elif start_idx >= anchor_allowed_pos:
-                        result_head = '<a href="%s"%s>' % (url, title_str)
-                        result = '%s%s</a>' % (result_head, link_text)
-                        # <img> allowed from curr_pos on, <a> from
-                        # anchor_allowed_pos on.
-                        curr_pos = start_idx + len(result_head)
-                        anchor_allowed_pos = start_idx + len(result)
-                        text = text[:start_idx] + result + text[match.end():]
-                    else:
-                        # Anchor not allowed here.
-                        curr_pos = start_idx + 1
-                    continue
-
-            # Reference anchor or img?
-            else:
-                match = self._tail_of_reference_link_re.match(text, p)
-                if match:
-                    # Handle a reference-style anchor or img.
-                    is_img = start_idx > 0 and text[start_idx-1] == "!"
-                    if is_img:
-                        start_idx -= 1
-                    link_id = match.group("id").lower()
-                    if not link_id:
-                        link_id = link_text.lower()  # for links like [this][]
-                    if link_id in self.urls:
-                        url = self.urls[link_id]
-                        # We've got to encode these to avoid conflicting
-                        # with italics/bold.
-                        url = url.replace('*', g_escape_table['*']) \
-                                 .replace('_', g_escape_table['_'])
-                        title = self.titles.get(link_id)
-                        if title:
-                            title = title.replace('*', g_escape_table['*']) \
-                                         .replace('_', g_escape_table['_'])
-                            title_str = ' title="%s"' % title
-                        else:
-                            title_str = ''
-                        if is_img:
-                            result = '<img src="%s" alt="%s"%s%s' \
-                                % (url, link_text.replace('"', '&quot;'),
-                                   title_str, self.empty_element_suffix)
-                            curr_pos = start_idx + len(result)
-                            text = text[:start_idx] + result + text[match.end():]
-                        elif start_idx >= anchor_allowed_pos:
-                            result = '<a href="%s"%s>%s</a>' \
-                                % (url, title_str, link_text)
-                            result_head = '<a href="%s"%s>' % (url, title_str)
-                            result = '%s%s</a>' % (result_head, link_text)
-                            # <img> allowed from curr_pos on, <a> from
-                            # anchor_allowed_pos on.
-                            curr_pos = start_idx + len(result_head)
-                            anchor_allowed_pos = start_idx + len(result)
-                            text = text[:start_idx] + result + text[match.end():]
-                        else:
-                            # Anchor not allowed here.
-                            curr_pos = start_idx + 1
-                    else:
-                        # This id isn't defined, leave the markup alone.
-                        curr_pos = match.end()
-                    continue
-
-            # Otherwise, it isn't markup.
-            curr_pos = start_idx + 1
-
-        return text 
-
-
-    _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M)
-    def _setext_h_sub(self, match):
-        n = {"=": 1, "-": 2}[match.group(2)[0]]
-        demote_headers = self.extras.get("demote-headers")
-        if demote_headers:
-            n = min(n + demote_headers, 6)
-        return "<h%d>%s</h%d>\n\n" \
-               % (n, self._run_span_gamut(match.group(1)), n)
-
-    _atx_h_re = re.compile(r'''
-        ^(\#{1,6})  # \1 = string of #'s
-        [ \t]*
-        (.+?)       # \2 = Header text
-        [ \t]*
-        (?<!\\)     # ensure not an escaped trailing '#'
-        \#*         # optional closing #'s (not counted)
-        \n+
-        ''', re.X | re.M)
-    def _atx_h_sub(self, match):
-        n = len(match.group(1))
-        demote_headers = self.extras.get("demote-headers")
-        if demote_headers:
-            n = min(n + demote_headers, 6)
-        return "<h%d>%s</h%d>\n\n" \
-               % (n, self._run_span_gamut(match.group(2)), n)
-
-    def _do_headers(self, text):
-        # Setext-style headers:
-        #     Header 1
-        #     ========
-        #  
-        #     Header 2
-        #     --------
-        text = self._setext_h_re.sub(self._setext_h_sub, text)
-
-        # atx-style headers:
-        #   # Header 1
-        #   ## Header 2
-        #   ## Header 2 with closing hashes ##
-        #   ...
-        #   ###### Header 6
-        text = self._atx_h_re.sub(self._atx_h_sub, text)
-
-        return text
-
-
-    _marker_ul_chars  = '*+-'
-    _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars
-    _marker_ul = '(?:[%s])' % _marker_ul_chars
-    _marker_ol = r'(?:\d+\.)'
-
-    def _list_sub(self, match):
-        lst = match.group(1)
-        lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol"
-        result = self._process_list_items(lst)
-        if self.list_level:
-            return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type)
-        else:
-            return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type)
-
-    def _do_lists(self, text):
-        # Form HTML ordered (numbered) and unordered (bulleted) lists.
-
-        for marker_pat in (self._marker_ul, self._marker_ol):
-            # Re-usable pattern to match any entire ul or ol list:
-            less_than_tab = self.tab_width - 1
-            whole_list = r'''
-                (                   # \1 = whole list
-                  (                 # \2
-                    [ ]{0,%d}
-                    (%s)            # \3 = first list item marker
-                    [ \t]+
-                  )
-                  (?:.+?)
-                  (                 # \4
-                      \Z
-                    |
-                      \n{2,}
-                      (?=\S)
-                      (?!           # Negative lookahead for another list item marker
-                        [ \t]*
-                        %s[ \t]+
-                      )
-                  )
-                )
-            ''' % (less_than_tab, marker_pat, marker_pat)
-        
-            # We use a different prefix before nested lists than top-level lists.
-            # See extended comment in _process_list_items().
-            #
-            # Note: There's a bit of duplication here. My original implementation
-            # created a scalar regex pattern as the conditional result of the test on
-            # $g_list_level, and then only ran the $text =~ s{...}{...}egmx
-            # substitution once, using the scalar as the pattern. This worked,
-            # everywhere except when running under MT on my hosting account at Pair
-            # Networks. There, this caused all rebuilds to be killed by the reaper (or
-            # perhaps they crashed, but that seems incredibly unlikely given that the
-            # same script on the same server ran fine *except* under MT. I've spent
-            # more time trying to figure out why this is happening than I'd like to
-            # admit. My only guess, backed up by the fact that this workaround works,
-            # is that Perl optimizes the substition when it can figure out that the
-            # pattern will never change, and when this optimization isn't on, we run
-            # afoul of the reaper. Thus, the slightly redundant code to that uses two
-            # static s/// patterns rather than one conditional pattern.
-
-            if self.list_level:
-                sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S)
-                text = sub_list_re.sub(self._list_sub, text)
-            else:
-                list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list,
-                                     re.X | re.M | re.S)
-                text = list_re.sub(self._list_sub, text)
-
-        return text
-    
-    _list_item_re = re.compile(r'''
-        (\n)?               # leading line = \1
-        (^[ \t]*)           # leading whitespace = \2
-        (%s) [ \t]+         # list marker = \3
-        ((?:.+?)            # list item text = \4
-         (\n{1,2}))         # eols = \5
-        (?= \n* (\Z | \2 (%s) [ \t]+))
-        ''' % (_marker_any, _marker_any),
-        re.M | re.X | re.S)
-
-    _last_li_endswith_two_eols = False
-    def _list_item_sub(self, match):
-        item = match.group(4)
-        leading_line = match.group(1)
-        leading_space = match.group(2)
-        if leading_line or "\n\n" in item or self._last_li_endswith_two_eols:
-            item = self._run_block_gamut(self._outdent(item))
-        else:
-            # Recursion for sub-lists:
-            item = self._do_lists(self._outdent(item))
-            if item.endswith('\n'):
-                item = item[:-1]
-            item = self._run_span_gamut(item)
-        self._last_li_endswith_two_eols = (len(match.group(5)) == 2)
-        return "<li>%s</li>\n" % item
-
-    def _process_list_items(self, list_str):
-        # Process the contents of a single ordered or unordered list,
-        # splitting it into individual list items.
-    
-        # The $g_list_level global keeps track of when we're inside a list.
-        # Each time we enter a list, we increment it; when we leave a list,
-        # we decrement. If it's zero, we're not in a list anymore.
-        #
-        # We do this because when we're not inside a list, we want to treat
-        # something like this:
-        #
-        #       I recommend upgrading to version
-        #       8. Oops, now this line is treated
-        #       as a sub-list.
-        #
-        # As a single paragraph, despite the fact that the second line starts
-        # with a digit-period-space sequence.
-        #
-        # Whereas when we're inside a list (or sub-list), that line will be
-        # treated as the start of a sub-list. What a kludge, huh? This is
-        # an aspect of Markdown's syntax that's hard to parse perfectly
-        # without resorting to mind-reading. Perhaps the solution is to
-        # change the syntax rules such that sub-lists must start with a
-        # starting cardinal number; e.g. "1." or "a.".
-        self.list_level += 1
-        self._last_li_endswith_two_eols = False
-        list_str = list_str.rstrip('\n') + '\n'
-        list_str = self._list_item_re.sub(self._list_item_sub, list_str)
-        self.list_level -= 1
-        return list_str
-
-    def _get_pygments_lexer(self, lexer_name):
-        try:
-            from pygments import lexers, util
-        except ImportError:
-            return None
-        try:
-            return lexers.get_lexer_by_name(lexer_name)
-        except util.ClassNotFound:
-            return None
-
-    def _color_with_pygments(self, codeblock, lexer, **formatter_opts):
-        import pygments
-        import pygments.formatters
-
-        class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
-            def _wrap_code(self, inner):
-                """A function for use in a Pygments Formatter which
-                wraps in <code> tags.
-                """
-                yield 0, "<code>"
-                for tup in inner:
-                    yield tup 
-                yield 0, "</code>"
-
-            def wrap(self, source, outfile):
-                """Return the source with a code, pre, and div."""
-                return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
-
-        formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts)
-        return pygments.highlight(codeblock, lexer, formatter)
-
-    def _code_block_sub(self, match):
-        codeblock = match.group(1)
-        codeblock = self._outdent(codeblock)
-        codeblock = self._detab(codeblock)
-        codeblock = codeblock.lstrip('\n')  # trim leading newlines
-        codeblock = codeblock.rstrip()      # trim trailing whitespace
-
-        if "code-color" in self.extras and codeblock.startswith(":::"):
-            lexer_name, rest = codeblock.split('\n', 1)
-            lexer_name = lexer_name[3:].strip()
-            lexer = self._get_pygments_lexer(lexer_name)
-            codeblock = rest.lstrip("\n")   # Remove lexer declaration line.
-            if lexer:
-                formatter_opts = self.extras['code-color'] or {}
-                colored = self._color_with_pygments(codeblock, lexer,
-                                                    **formatter_opts)
-                return "\n\n%s\n\n" % colored
-
-        codeblock = self._encode_code(codeblock)
-        return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock
-
-    def _do_code_blocks(self, text):
-        """Process Markdown `<pre><code>` blocks."""
-        code_block_re = re.compile(r'''
-            (?:\n\n|\A)
-            (               # $1 = the code block -- one or more lines, starting with a space/tab
-              (?:
-                (?:[ ]{%d} | \t)  # Lines must start with a tab or a tab-width of spaces
-                .*\n+
-              )+
-            )
-            ((?=^[ ]{0,%d}\S)|\Z)   # Lookahead for non-space at line-start, or end of doc
-            ''' % (self.tab_width, self.tab_width),
-            re.M | re.X)
-
-        return code_block_re.sub(self._code_block_sub, text)
-
-
-    # Rules for a code span:
-    # - backslash escapes are not interpreted in a code span
-    # - to include one or or a run of more backticks the delimiters must
-    #   be a longer run of backticks
-    # - cannot start or end a code span with a backtick; pad with a
-    #   space and that space will be removed in the emitted HTML
-    # See `test/tm-cases/escapes.text` for a number of edge-case
-    # examples.
-    _code_span_re = re.compile(r'''
-            (?<!\\)
-            (`+)        # \1 = Opening run of `
-            (?!`)       # See Note A test/tm-cases/escapes.text
-            (.+?)       # \2 = The code block
-            (?<!`)
-            \1          # Matching closer
-            (?!`)
-        ''', re.X | re.S)
-
-    def _code_span_sub(self, match):
-        c = match.group(2).strip(" \t")
-        c = self._encode_code(c)
-        return "<code>%s</code>" % c
-
-    def _do_code_spans(self, text):
-        #   *   Backtick quotes are used for <code></code> spans.
-        # 
-        #   *   You can use multiple backticks as the delimiters if you want to
-        #       include literal backticks in the code span. So, this input:
-        #     
-        #         Just type ``foo `bar` baz`` at the prompt.
-        #     
-        #       Will translate to:
-        #     
-        #         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
-        #     
-        #       There's no arbitrary limit to the number of backticks you
-        #       can use as delimters. If you need three consecutive backticks
-        #       in your code, use four for delimiters, etc.
-        #
-        #   *   You can use spaces to get literal backticks at the edges:
-        #     
-        #         ... type `` `bar` `` ...
-        #     
-        #       Turns to:
-        #     
-        #         ... type <code>`bar`</code> ...
-        return self._code_span_re.sub(self._code_span_sub, text)
-
-    def _encode_code(self, text):
-        """Encode/escape certain characters inside Markdown code runs.
-        The point is that in code, these characters are literals,
-        and lose their special Markdown meanings.
-        """
-        replacements = [
-            # Encode all ampersands; HTML entities are not
-            # entities within a Markdown code span.
-            ('&', '&amp;'),
-            # Do the angle bracket song and dance:
-            ('<', '&lt;'),
-            ('>', '&gt;'),
-            # Now, escape characters that are magic in Markdown:
-            ('*', g_escape_table['*']),
-            ('_', g_escape_table['_']),
-            ('{', g_escape_table['{']),
-            ('}', g_escape_table['}']),
-            ('[', g_escape_table['[']),
-            (']', g_escape_table[']']),
-            ('\\', g_escape_table['\\']),
-        ]
-        for before, after in replacements:
-            text = text.replace(before, after)
-        return text
-
-    _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
-    _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
-    _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
-    _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S)
-    def _do_italics_and_bold(self, text):
-        # <strong> must go first:
-        if "code-friendly" in self.extras:
-            text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text)
-            text = self._code_friendly_em_re.sub(r"<em>\1</em>", text)
-        else:
-            text = self._strong_re.sub(r"<strong>\2</strong>", text)
-            text = self._em_re.sub(r"<em>\2</em>", text)
-        return text
-    
-
-    _block_quote_re = re.compile(r'''
-        (                           # Wrap whole match in \1
-          (
-            ^[ \t]*>[ \t]?          # '>' at the start of a line
-              .+\n                  # rest of the first line
-            (.+\n)*                 # subsequent consecutive lines
-            \n*                     # blanks
-          )+
-        )
-        ''', re.M | re.X)
-    _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M);
-
-    _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S)
-    def _dedent_two_spaces_sub(self, match):
-        return re.sub(r'(?m)^  ', '', match.group(1))
-
-    def _block_quote_sub(self, match):
-        bq = match.group(1)
-        bq = self._bq_one_level_re.sub('', bq)  # trim one level of quoting
-        bq = self._ws_only_line_re.sub('', bq)  # trim whitespace-only lines
-        bq = self._run_block_gamut(bq)          # recurse
-
-        bq = re.sub('(?m)^', '  ', bq)
-        # These leading spaces screw with <pre> content, so we need to fix that:
-        bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
-
-        return "<blockquote>\n%s\n</blockquote>\n\n" % bq
-
-    def _do_block_quotes(self, text):
-        if '>' not in text:
-            return text
-        return self._block_quote_re.sub(self._block_quote_sub, text)
-
-    def _form_paragraphs(self, text):
-        # Strip leading and trailing lines:
-        text = text.strip('\n')
-
-        # Wrap <p> tags.
-        grafs = re.split(r"\n{2,}", text)
-        for i, graf in enumerate(grafs):
-            if graf in self.html_blocks:
-                # Unhashify HTML blocks
-                grafs[i] = self.html_blocks[graf]
-            else:
-                # Wrap <p> tags.
-                graf = self._run_span_gamut(graf)
-                grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>"
-
-        return "\n\n".join(grafs)
-
-    def _add_footnotes(self, text):
-        if self.footnotes:
-            footer = [
-                '<div class="footnotes">',
-                '<hr' + self.empty_element_suffix,
-                '<ol>',
-            ]
-            for i, id in enumerate(self.footnote_ids):
-                if i != 0:
-                    footer.append('')
-                footer.append('<li id="fn-%s">' % id)
-                footer.append(self._run_block_gamut(self.footnotes[id]))
-                backlink = ('<a href="#fnref-%s" '
-                    'class="footnoteBackLink" '
-                    'title="Jump back to footnote %d in the text.">'
-                    '&#8617;</a>' % (id, i+1))
-                if footer[-1].endswith("</p>"):
-                    footer[-1] = footer[-1][:-len("</p>")] \
-                        + '&nbsp;' + backlink + "</p>"
-                else:
-                    footer.append("\n<p>%s</p>" % backlink)
-                footer.append('</li>')
-            footer.append('</ol>')
-            footer.append('</div>')
-            return text + '\n\n' + '\n'.join(footer)
-        else:
-            return text
-
-    # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
-    #   http://bumppo.net/projects/amputator/
-    _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)')
-    _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I)
-    _naked_gt_re = re.compile(r'''(?<![a-z?!/'"-])>''', re.I)
-
-    def _encode_amps_and_angles(self, text):
-        # Smart processing for ampersands and angle brackets that need
-        # to be encoded.
-        text = self._ampersand_re.sub('&amp;', text)
-    
-        # Encode naked <'s
-        text = self._naked_lt_re.sub('&lt;', text)
-
-        # Encode naked >'s
-        # Note: Other markdown implementations (e.g. Markdown.pl, PHP
-        # Markdown) don't do this.
-        text = self._naked_gt_re.sub('&gt;', text)
-        return text
-
-    def _encode_backslash_escapes(self, text):
-        for ch, escape in g_escape_table.items():
-            text = text.replace("\\"+ch, escape)
-        return text
-
-    _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I)
-    def _auto_link_sub(self, match):
-        g1 = match.group(1)
-        return '<a href="%s">%s</a>' % (g1, g1)
-
-    _auto_email_link_re = re.compile(r"""
-          <
-           (?:mailto:)?
-          (
-              [-.\w]+
-              \@
-              [-\w]+(\.[-\w]+)*\.[a-z]+
-          )
-          >
-        """, re.I | re.X | re.U)
-    def _auto_email_link_sub(self, match):
-        return self._encode_email_address(
-            self._unescape_special_chars(match.group(1)))
-
-    def _do_auto_links(self, text):
-        text = self._auto_link_re.sub(self._auto_link_sub, text)
-        text = self._auto_email_link_re.sub(self._auto_email_link_sub, text)
-        return text
-
-    def _encode_email_address(self, addr):
-        #  Input: an email address, e.g. "foo@example.com"
-        #
-        #  Output: the email address as a mailto link, with each character
-        #      of the address encoded as either a decimal or hex entity, in
-        #      the hopes of foiling most address harvesting spam bots. E.g.:
-        #
-        #    <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
-        #       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
-        #       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
-        #
-        #  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
-        #  mailing list: <http://tinyurl.com/yu7ue>
-        chars = [_xml_encode_email_char_at_random(ch)
-                 for ch in "mailto:" + addr]
-        # Strip the mailto: from the visible part.
-        addr = '<a href="%s">%s</a>' \
-               % (''.join(chars), ''.join(chars[7:]))
-        return addr
-    
-    def _do_link_patterns(self, text):
-        """Caveat emptor: there isn't much guarding against link
-        patterns being formed inside other standard Markdown links, e.g.
-        inside a [link def][like this].
-
-        Dev Notes: *Could* consider prefixing regexes with a negative
-        lookbehind assertion to attempt to guard against this.
-        """
-        link_from_hash = {}
-        for regex, repl in self.link_patterns:
-            replacements = []
-            for match in regex.finditer(text):
-                if hasattr(repl, "__call__"):
-                    href = repl(match)
-                else:
-                    href = match.expand(repl)
-                replacements.append((match.span(), href))
-            for (start, end), href in reversed(replacements):
-                escaped_href = (
-                    href.replace('"', '&quot;')  # b/c of attr quote
-                        # To avoid markdown <em> and <strong>:
-                        .replace('*', g_escape_table['*'])
-                        .replace('_', g_escape_table['_']))
-                link = '<a href="%s">%s</a>' % (escaped_href, text[start:end])
-                hash = md5(link).hexdigest()
-                link_from_hash[hash] = link
-                text = text[:start] + hash + text[end:]
-        for hash, link in link_from_hash.items():
-            text = text.replace(hash, link)
-        return text
-    
-    def _unescape_special_chars(self, text):
-        # Swap back in all the special characters we've hidden.
-        for ch, hash in g_escape_table.items():
-            text = text.replace(hash, ch)
-        return text
-
-    def _outdent(self, text):
-        # Remove one level of line-leading tabs or spaces
-        return self._outdent_re.sub('', text)
-
-
-class MarkdownWithExtras(Markdown):
-    """A markdowner class that enables most extras:
-
-    - footnotes
-    - code-color (only has effect if 'pygments' Python module on path)
-
-    These are not included:
-    - pyshell (specific to Python-related documenting)
-    - code-friendly (because it *disables* part of the syntax)
-    - link-patterns (because you need to specify some actual
-      link-patterns anyway)
-    """
-    extras = ["footnotes", "code-color"]
-
-
-#---- internal support functions
-
-# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
-def _curry(*args, **kwargs):
-    function, args = args[0], args[1:]
-    def result(*rest, **kwrest):
-        combined = kwargs.copy()
-        combined.update(kwrest)
-        return function(*args + rest, **combined)
-    return result
-
-# Recipe: regex_from_encoded_pattern (1.0)
-def _regex_from_encoded_pattern(s):
-    """'foo'    -> re.compile(re.escape('foo'))
-       '/foo/'  -> re.compile('foo')
-       '/foo/i' -> re.compile('foo', re.I)
-    """
-    if s.startswith('/') and s.rfind('/') != 0:
-        # Parse it: /PATTERN/FLAGS
-        idx = s.rfind('/')
-        pattern, flags_str = s[1:idx], s[idx+1:]
-        flag_from_char = {
-            "i": re.IGNORECASE,
-            "l": re.LOCALE,
-            "s": re.DOTALL,
-            "m": re.MULTILINE,
-            "u": re.UNICODE,
-        }
-        flags = 0
-        for char in flags_str:
-            try:
-                flags |= flag_from_char[char]
-            except KeyError:
-                raise ValueError("unsupported regex flag: '%s' in '%s' "
-                                 "(must be one of '%s')"
-                                 % (char, s, ''.join(flag_from_char.keys())))
-        return re.compile(s[1:idx], flags)
-    else: # not an encoded regex
-        return re.compile(re.escape(s))
-
-# Recipe: dedent (0.1.2)
-def _dedentlines(lines, tabsize=8, skip_first_line=False):
-    """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
-    
-        "lines" is a list of lines to dedent.
-        "tabsize" is the tab width to use for indent width calculations.
-        "skip_first_line" is a boolean indicating if the first line should
-            be skipped for calculating the indent width and for dedenting.
-            This is sometimes useful for docstrings and similar.
-    
-    Same as dedent() except operates on a sequence of lines. Note: the
-    lines list is modified **in-place**.
-    """
-    DEBUG = False
-    if DEBUG: 
-        print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
-              % (tabsize, skip_first_line)
-    indents = []
-    margin = None
-    for i, line in enumerate(lines):
-        if i == 0 and skip_first_line: continue
-        indent = 0
-        for ch in line:
-            if ch == ' ':
-                indent += 1
-            elif ch == '\t':
-                indent += tabsize - (indent % tabsize)
-            elif ch in '\r\n':
-                continue # skip all-whitespace lines
-            else:
-                break
-        else:
-            continue # skip all-whitespace lines
-        if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
-        if margin is None:
-            margin = indent
-        else:
-            margin = min(margin, indent)
-    if DEBUG: print "dedent: margin=%r" % margin
-
-    if margin is not None and margin > 0:
-        for i, line in enumerate(lines):
-            if i == 0 and skip_first_line: continue
-            removed = 0
-            for j, ch in enumerate(line):
-                if ch == ' ':
-                    removed += 1
-                elif ch == '\t':
-                    removed += tabsize - (removed % tabsize)
-                elif ch in '\r\n':
-                    if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
-                    lines[i] = lines[i][j:]
-                    break
-                else:
-                    raise ValueError("unexpected non-whitespace char %r in "
-                                     "line %r while removing %d-space margin"
-                                     % (ch, line, margin))
-                if DEBUG:
-                    print "dedent: %r: %r -> removed %d/%d"\
-                          % (line, ch, removed, margin)
-                if removed == margin:
-                    lines[i] = lines[i][j+1:]
-                    break
-                elif removed > margin:
-                    lines[i] = ' '*(removed-margin) + lines[i][j+1:]
-                    break
-            else:
-                if removed:
-                    lines[i] = lines[i][removed:]
-    return lines
-
-def _dedent(text, tabsize=8, skip_first_line=False):
-    """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
-
-        "text" is the text to dedent.
-        "tabsize" is the tab width to use for indent width calculations.
-        "skip_first_line" is a boolean indicating if the first line should
-            be skipped for calculating the indent width and for dedenting.
-            This is sometimes useful for docstrings and similar.
-    
-    textwrap.dedent(s), but don't expand tabs to spaces
-    """
-    lines = text.splitlines(1)
-    _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
-    return ''.join(lines)
-
-
-class _memoized(object):
-   """Decorator that caches a function's return value each time it is called.
-   If called later with the same arguments, the cached value is returned, and
-   not re-evaluated.
-
-   http://wiki.python.org/moin/PythonDecoratorLibrary
-   """
-   def __init__(self, func):
-      self.func = func
-      self.cache = {}
-   def __call__(self, *args):
-      try:
-         return self.cache[args]
-      except KeyError:
-         self.cache[args] = value = self.func(*args)
-         return value
-      except TypeError:
-         # uncachable -- for instance, passing a list as an argument.
-         # Better to not cache than to blow up entirely.
-         return self.func(*args)
-   def __repr__(self):
-      """Return the function's docstring."""
-      return self.func.__doc__
-
-
-def _xml_oneliner_re_from_tab_width(tab_width):
-    """Standalone XML processing instruction regex."""
-    return re.compile(r"""
-        (?:
-            (?<=\n\n)       # Starting after a blank line
-            |               # or
-            \A\n?           # the beginning of the doc
-        )
-        (                           # save in $1
-            [ ]{0,%d}
-            (?:
-                <\?\w+\b\s+.*?\?>   # XML processing instruction
-                |
-                <\w+:\w+\b\s+.*?/>  # namespaced single tag
-            )
-            [ \t]*
-            (?=\n{2,}|\Z)       # followed by a blank line or end of document
-        )
-        """ % (tab_width - 1), re.X)
-_xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width)
-
-def _hr_tag_re_from_tab_width(tab_width):
-     return re.compile(r"""
-        (?:
-            (?<=\n\n)       # Starting after a blank line
-            |               # or
-            \A\n?           # the beginning of the doc
-        )
-        (                       # save in \1
-            [ ]{0,%d}
-            <(hr)               # start tag = \2
-            \b                  # word break
-            ([^<>])*?           # 
-            /?>                 # the matching end tag
-            [ \t]*
-            (?=\n{2,}|\Z)       # followed by a blank line or end of document
-        )
-        """ % (tab_width - 1), re.X)
-_hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width)
-
-
-def _xml_encode_email_char_at_random(ch):
-    r = random()
-    # Roughly 10% raw, 45% hex, 45% dec.
-    # '@' *must* be encoded. I [John Gruber] insist.
-    # Issue 26: '_' must be encoded.
-    if r > 0.9 and ch not in "@_":
-        return ch
-    elif r < 0.45:
-        # The [1:] is to drop leading '0': 0x63 -> x63
-        return '&#%s;' % hex(ord(ch))[1:]
-    else:
-        return '&#%s;' % ord(ch)
-
-def _hash_text(text):
-    return 'md5:'+md5(text.encode("utf-8")).hexdigest()
-
-
-#---- mainline
-
-class _NoReflowFormatter(optparse.IndentedHelpFormatter):
-    """An optparse formatter that does NOT reflow the description."""
-    def format_description(self, description):
-        return description or ""
-
-def _test():
-    import doctest
-    doctest.testmod()
-
-def main(argv=None):
-    if argv is None:
-        argv = sys.argv
-    if not logging.root.handlers:
-        logging.basicConfig()
-
-    usage = "usage: %prog [PATHS...]"
-    version = "%prog "+__version__
-    parser = optparse.OptionParser(prog="markdown2", usage=usage,
-        version=version, description=cmdln_desc,
-        formatter=_NoReflowFormatter())
-    parser.add_option("-v", "--verbose", dest="log_level",
-                      action="store_const", const=logging.DEBUG,
-                      help="more verbose output")
-    parser.add_option("--encoding",
-                      help="specify encoding of text content")
-    parser.add_option("--html4tags", action="store_true", default=False, 
-                      help="use HTML 4 style for empty element tags")
-    parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode",
-                      help="sanitize literal HTML: 'escape' escapes "
-                           "HTML meta chars, 'replace' replaces with an "
-                           "[HTML_REMOVED] note")
-    parser.add_option("-x", "--extras", action="append",
-                      help="Turn on specific extra features (not part of "
-                           "the core Markdown spec). Supported values: "
-                           "'code-friendly' disables _/__ for emphasis; "
-                           "'code-color' adds code-block syntax coloring; "
-                           "'link-patterns' adds auto-linking based on patterns; "
-                           "'footnotes' adds the footnotes syntax;"
-                           "'xml' passes one-liner processing instructions and namespaced XML tags;"
-                           "'pyshell' to put unindented Python interactive shell sessions in a <code> block.")
-    parser.add_option("--use-file-vars",
-                      help="Look for and use Emacs-style 'markdown-extras' "
-                           "file var to turn on extras. See "
-                           "<http://code.google.com/p/python-markdown2/wiki/Extras>.")
-    parser.add_option("--link-patterns-file",
-                      help="path to a link pattern file")
-    parser.add_option("--self-test", action="store_true",
-                      help="run internal self-tests (some doctests)")
-    parser.add_option("--compare", action="store_true",
-                      help="run against Markdown.pl as well (for testing)")
-    parser.set_defaults(log_level=logging.INFO, compare=False,
-                        encoding="utf-8", safe_mode=None, use_file_vars=False)
-    opts, paths = parser.parse_args()
-    log.setLevel(opts.log_level)
-
-    if opts.self_test:
-        return _test()
-
-    if opts.extras:
-        extras = {}
-        for s in opts.extras:
-            splitter = re.compile("[,;: ]+")
-            for e in splitter.split(s):
-                if '=' in e:
-                    ename, earg = e.split('=', 1)
-                    try:
-                        earg = int(earg)
-                    except ValueError:
-                        pass
-                else:
-                    ename, earg = e, None
-                extras[ename] = earg
-    else:
-        extras = None
-
-    if opts.link_patterns_file:
-        link_patterns = []
-        f = open(opts.link_patterns_file)
-        try:
-            for i, line in enumerate(f.readlines()):
-                if not line.strip(): continue
-                if line.lstrip().startswith("#"): continue
-                try:
-                    pat, href = line.rstrip().rsplit(None, 1)
-                except ValueError:
-                    raise MarkdownError("%s:%d: invalid link pattern line: %r"
-                                        % (opts.link_patterns_file, i+1, line))
-                link_patterns.append(
-                    (_regex_from_encoded_pattern(pat), href))
-        finally:
-            f.close()
-    else:
-        link_patterns = None
-
-    from os.path import join, dirname, abspath, exists
-    markdown_pl = join(dirname(dirname(abspath(__file__))), "test",
-                       "Markdown.pl")
-    for path in paths:
-        if opts.compare:
-            print "==== Markdown.pl ===="
-            perl_cmd = 'perl %s "%s"' % (markdown_pl, path)
-            o = os.popen(perl_cmd)
-            perl_html = o.read()
-            o.close()
-            sys.stdout.write(perl_html)
-            print "==== markdown2.py ===="
-        html = markdown_path(path, encoding=opts.encoding,
-                             html4tags=opts.html4tags,
-                             safe_mode=opts.safe_mode,
-                             extras=extras, link_patterns=link_patterns,
-                             use_file_vars=opts.use_file_vars)
-        sys.stdout.write(
-            html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))
-        if opts.compare:
-            test_dir = join(dirname(dirname(abspath(__file__))), "test")
-            if exists(join(test_dir, "test_markdown2.py")):
-                sys.path.insert(0, test_dir)
-                from test_markdown2 import norm_html_from_html
-                norm_html = norm_html_from_html(html)
-                norm_perl_html = norm_html_from_html(perl_html)
-            else:
-                norm_html = html
-                norm_perl_html = perl_html
-            print "==== match? %r ====" % (norm_perl_html == norm_html)
-
-
-if __name__ == "__main__":
-    sys.exit( main(sys.argv) )
-
diff --git a/www/webapp/stasy.py b/www/webapp/stasy.py
new file mode 100644 (file)
index 0000000..a64c1d9
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/python
+
+import logging
+import pymongo
+
+DATABASE_HOST = ["irma.ipfire.org", "madeye.ipfire.org"]
+DATABASE_NAME = "stasy"
+
+class ProfileDict(object):
+       def __init__(self, data):
+               self._data = data
+
+               logging.debug("New: %s" % self._data)
+
+       def __repr__(self):
+               return self.__str__()
+
+       def __str__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.public_id)
+
+       def __getattr__(self, key):
+               try:
+                       return self._data[key]
+               except KeyError:
+                       raise AttributeError, key
+
+       def __setattr(self, key, val):
+               self._data[key] = val
+
+
+class ProfileCPU(ProfileDict):
+       @property
+       def capable_64bit(self):
+               return "lm" in self.flags
+
+       @property
+       def capable_pae(self):
+               return "pae" in self.flags
+
+
+class ProfileHypervisor(ProfileDict):
+       pass
+
+
+class ProfileDevice(ProfileDict):
+       @property
+       def model_string(self):
+               return "XXX"
+
+       @property
+       def vendor_string(self):
+               return "XXX"
+
+
+class Profile(ProfileDict):
+       def __repr__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.public_id)
+
+       @property
+       def cpu(self):
+               return ProfileCPU(self._data["cpu"])
+
+       @property
+       def hypervisor(self):
+               return ProfileHypervisor(self._data["hypervisor"])
+
+       @property
+       def devices(self):
+               return [ProfileDevice(d) for d in self._data["devices"]]
+
+
+class StasyDatabase(object):
+       def __init__(self):
+               # Initialize database connection
+               self._conn = pymongo.Connection(DATABASE_HOST)
+               self._db = self._conn[DATABASE_NAME]
+
+       def get_profile_count(self):
+               # XXX need to implement something to get profiles updated since
+               # a given date
+
+               # All distinct profiles (based on public_id)
+               c = self._db.profiles.find().distinct("public_id")
+
+               return c.count()
+
+       def _get_profile_cursor(self, public_id):
+               c = self._db.profiles.find({ "public_id" : public_id })
+               c.sort("updated", pymongo.ASCENDING)
+
+               return c
+
+       def get_latest_profile(self, public_id):
+               # XXX still finds first one
+               for p in self._get_profile_cursor(public_id).limit(1):
+                       return Profile(p)
+
+       def get_profiles(self):
+               # XXX needs nicer database query
+               profiles = []
+               for p in self._db.profiles.find():
+                       p = Profile(p)
+                       if not p.public_id in profiles:
+                               profiles.append(p.public_id)
+
+               return profiles
+
+       @property
+       def cpus(self):
+               return self._db.profiles.distinct("profile.cpu")
+
+       @property
+       def cpu_vendors(self):
+               return self._db.profiles.distinct("profile.cpu.vendor")
+
+       @property
+       def cpu_map(self):
+               cpus = {}
+
+               for vendor in self.cpu_vendors:
+                       cpus[vendor] = \
+                               self._db.profiles.find({ "profile.cpu.vendor" : vendor }).count()
+
+               return cpus
+
+       @property
+       def hypervisor_vendors(self):
+               return self._db.profiles.distinct("profile.hypervisor.vendor")
+
+       @property
+       def hypervisor_models(self):
+               return self._db.profiles.distinct("profile.hypervisor.model")
+
+       @property
+       def secret_ids(self):
+               return self._db.profiles.distinct("secret_id")
+
+       @property
+       def languages(self):
+               return self._db.profiles.distinct("profile.system.language")
+
+       @property
+       def vendors(self):
+               return self._db.profiles.distinct("profile.system.vendor")
+
+       @property
+       def models(self):
+               return self._db.profiles.distinct("profile.system.model")
+
+
+class Stasy(object):
+       def __init__(self):
+               self.db = StasyDatabase()
+
+       def get_profile(self, public_id):
+               return self.db.get_latest_profile(public_id)
+
+       def get_profiles(self):
+               return self.db.get_profiles()
+
+
+if __name__ == "__main__":
+       s = Stasy()
+
+       print s.get_profile("0" * 40)
+       print s.db.cpu_vendors
+       for id in s.db.secret_ids:
+               print "\t", id
+
+       for p in s.db._db.profiles.find():
+               print p
+
+       print s.db.cpu_map
+       print s.db.hypervisor_vendors
+       print s.db.hypervisor_models
+       print s.db.languages
+       print s.db.vendors
+       print s.db.models
+       print s.db.cpus
index 43992a48f206cac2011a14d22bbfbc00c52587d9..6bff06daf01856e3768a5f8347de9bd2b0e35c3e 100644 (file)
@@ -1,58 +1,81 @@
 #!/usr/bin/python
 
-import markdown
+import logging
+import socket
+import textile
+import tornado.escape
 import tornado.web
 
 from tornado.database import Row
 
+import backend
+
 class UIModule(tornado.web.UIModule):
-       def render_string(self, *args, **kwargs):
-               kwargs.update({
-                       "link" : self.handler.link,
-               })
-               return tornado.web.UIModule.render_string(self, *args, **kwargs)
+       @property
+       def accounts(self):
+               return self.handler.accounts
 
        @property
-       def user_db(self):
-               return self.handler.application.user_db
+       def banners(self):
+               return self.handler.banners
 
+       @property
+       def releases(self):
+               return self.handler.releases
 
-class MenuItemModule(UIModule):
-       def render(self, item):
-               if self.request.uri.endswith(item.uri):
-                       item.active = True
 
-               if not item.uri.startswith("http://"):
-                       item.uri = "/%s%s" % (self.locale.code[:2], item.uri,)
+class MenuModule(UIModule):
+       def render(self):
+               hostname = self.request.host.lower().split(':')[0]
 
-               if type(item.name) == type({}):
-                       item.name = item.name[self.locale.code[:2]]
+               menuitems = []
+               for m in backend.Menu().get(hostname):
+                       m.active = False
 
-               return self.render_string("modules/menu-item.html", item=item)
+                       if m.uri and self.request.uri.endswith(m.uri):
+                               m.active = True
 
+                       # Translate the description of the link
+                       m.description = \
+                               self.locale.translate(m.description)
+                       m.description = tornado.escape.xhtml_escape(m.description)
 
-class MenuModule(UIModule):
-       def render(self):
-               menuclass = self.handler.application.ds.menu
-               host = self.request.host.lower().split(':')[0]
+                       menuitems.append(m)
 
-               return self.render_string("modules/menu.html", menuitems=menuclass.get(host))
+               return self.render_string("modules/menu.html", menuitems=menuitems)
 
 
 class NewsItemModule(UIModule):
-       def render(self, item):
-               item = Row(item.copy())
-               for attr in ("subject", "content"):
-                       if type(item[attr]) != type({}):
-                               continue
-                       item[attr] = item[attr][self.locale.code[:2]]
+       def get_author(self, author):
+               # Get name of author
+               author = self.accounts.find(author)
+               if author:
+                       return author.cn
+               else:
+                       _ = self.locale.translate
+                       return _("Unknown author")
+
+       def render(self, item, uncut=False):
+               # Get author
+               item.author = self.get_author(item.author_id)
 
-               return self.render_string("modules/news-item.html", item=item)
+               if not uncut and len(item.text) >= 400:
+                       item.text = item.text[:400] + "..."
 
+               # Render text
+               item.text = textile.textile(item.text)
 
-#class SidebarModule(UIModule):
-#      def render(self, sidebar):
-#              return self.render_string("modules/sidebar.html", items=sidebar.items)
+               return self.render_string("modules/news-item.html", item=item, uncut=uncut)
+
+
+class NewsLineModule(NewsItemModule):
+       def render(self, item):
+               return self.render_string("modules/news-line.html", item=item)
+
+
+class MirrorItemModule(UIModule):
+       def render(self, item):
+               return self.render_string("modules/mirror-item.html", item=item)
 
 
 class SidebarItemModule(UIModule):
@@ -63,31 +86,39 @@ class SidebarItemModule(UIModule):
 class SidebarReleaseModule(UIModule):
        def render(self):
                return self.render_string("modules/sidebar-release.html",
-                       releases=self.handler.application.ds.releases)
+                       latest=self.releases.get_latest())
 
 
 class ReleaseItemModule(UIModule):
        def render(self, item):
-               return self.render_string("modules/release-item.html", item=item)
+               return self.render_string("modules/release-item.html", release=item)
 
 
 class SidebarBannerModule(UIModule):
-       def render(self, item):
+       def render(self, item=None):
+               if not item:
+                       item = self.banners.get_random()
+
                return self.render_string("modules/sidebar-banner.html", item=item)
 
 
-class BuildModule(UIModule):
-       def render(self, build):
-               return self.render_string("modules/builds.html", build=build)
+class PlanetEntryModule(UIModule):
+       def render(self, entry, short=False):
+               return self.render_string("modules/planet-entry.html",
+                       entry=entry, short=short)
 
 
-class PlanetEntryModule(UIModule):
-       def render(self, entry):
-               if not getattr(entry, "author", None):
-                       entry.author = self.user_db.get_user_by_id(entry.author_id)
+class TrackerPeerListModule(UIModule):
+       def render(self, peers, percentages=False):
+               # Guess country code and hostname of the host
+               for peer in peers:
+                       country_code = backend.GeoIP().get_country(peer["ip"])
+                       peer["country_code"] = country_code or "unknown"
 
-               entry.markup = markdown.markdown(entry.text)
-               entry.published = entry.published.strftime("%Y-%m-%d")
-               entry.updated = entry.updated.strftime("%Y-%m-%d %H:%M")
+                       try:
+                               peer["hostname"] = socket.gethostbyaddr(peer["ip"])[0]
+                       except:
+                               peer["hostname"] = ""
 
-               return self.render_string("modules/planet-entry.html", entry=entry)
+               return self.render_string("modules/tracker-peerlist.html",
+                       peers=[Row(p) for p in peers], percentages=percentages)