web_PYTHON = \
src/web/__init__.py \
src/web/blog.py \
+ src/web/download.py \
src/web/handlers.py \
src/web/handlers_accounts.py \
src/web/handlers_admin.py \
templates_blog_modulesdir = $(templates_blogdir)/modules
+templates_download_DATA = \
+ src/templates/download/base.html \
+ src/templates/download/index.html \
+ src/templates/download/release.html
+
+templates_downloaddir = $(templatesdir)/download
+
+templates_download_modules_DATA = \
+ src/templates/download/modules/button.html
+
+templates_download_modulesdir = $(templates_downloaddir)/modules
+
templates_location_DATA = \
src/templates/location/lookup.html
_ = lambda x: x
descriptions = {
- "armv5tel" : _("Image for the armv5tel architecture"),
- "armv5tel-scon" : _("armv5tel image for boards with serial console"),
- "iso" : _("Installable CD image"),
- "torrent" : _("Torrent file"),
- "flash" : _("Flash image"),
- "alix" : _("Flash image for devices with serial console"),
+ "armv5tel" : _("Flash Image"),
+ "armv5tel-scon" : _("Flash Image with serial console"),
+ "iso" : _("CD Image"),
+ "torrent" : _("Torrent File"),
+ "flash" : _("Flash Image"),
+ "alix" : _("Flash Image with serial console"),
"usbfdd" : _("USB FDD Image"),
"usbhdd" : _("USB HDD Image"),
- "xen" : _("Pregenerated Xen image"),
+ "xen" : _("Pre-generated Xen Image"),
"xen-downloader": _("Xen-Image Generator"),
}
self.__files = []
+ def __str__(self):
+ return self.name
+
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.name)
def __cmp__(self, other):
return cmp(self.id, other.id)
+ @property
+ def arches(self):
+ for arch in ("x86_64", "aarch64", "i586", "arm"):
+ if arch in (f.arch for f in self.files):
+ yield arch
+
@property
def files(self):
if not self.__files:
return self.__files
+ def get_files_by_arch(self, arch):
+ for f in self.files:
+ if f.arch == arch:
+ yield f
+
@property
def torrents(self):
torrents = []
return self.__data.name
@property
- def sname(self):
+ def slug(self):
return self.__data.sname
+ # XXX compat
+ sname = slug
+
+ # XXX cache this
+ @property
+ def blog(self):
+ if self.__data.blog_id:
+ return self.backend.blog.get_by_id(self.__data.blog_id)
+
@property
def fireinfo_id(self):
name = self.sname.replace("ipfire-", "IPFire ").replace("-", " - ")
class Releases(Object):
+ def _get_releases(self, query, *args):
+ res = self.db.query(query, *args)
+
+ for row in res:
+ yield Release(self.backend, row.id, data=row)
+
def get_by_id(self, id):
ret = self.db.get("SELECT * FROM releases WHERE id = %s", id)
if ret:
return Release(self.backend, ret.id, data=ret)
+ def get_releases_older_than(self, release, limit=None):
+ return self._get_releases("SELECT * FROM releases \
+ WHERE published IS NOT NULL AND published < %s \
+ ORDER BY published DESC LIMIT %s", release.published, limit)
+
def get_latest_unstable(self):
ret = self.db.get("SELECT * FROM releases r1 \
WHERE r1.published IS NOT NULL AND r1.published <= NOW() \
--- /dev/null
+{% extends "../base.html" %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Download") }}{% end block %}
+
+{% block content %}
+ <div class="row justify-content-center my-5">
+ <div class="col col-sm-8 text-center">
+ <h1>{{ _("Download IPFire") }}</h1>
+
+ <p class="lead mb-5">
+ As you download and use IPFire, the IPFire Project invites you to become
+ an active contributor to our community.
+ </p>
+
+ {% module DownloadButton(release) %}
+
+ <ul class="nav justify-content-center">
+ {% if release.blog %}
+ <li class="nav-item">
+ <a class="nav-link" href="https://blog.ipfire.org/post/{{ release.blog.slug }}">
+ {{ _("Release Notes") }}
+ </a>
+ </li>
+ {% end %}
+
+ <li class="nav-item">
+ <a class="nav-link" href="/download/{{ release.slug }}">
+ {{ _("More Download Options") }}
+ </a>
+ </li>
+ </nav>
+ </div>
+ </div>
+{% end block %}
--- /dev/null
+<a class="btn btn-primary btn-lg download-splash my-2" href="{{ file.url }}">
+ {{ _("%s for %s") % (release, file.arch) }}
+</a>
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ _("Download %s") % release }}{% end block %}
+
+{% block content %}
+ <div class="text-center my-5">
+ <h1 class="mb-0">{{ release }}</h1>
+ <h6 class="text-muted">
+ {{ _("Released %s") % locale.format_date(release.published, relative=False, shorter=True) }}
+
+ {% if release.blog %}
+ •
+ <a href="https://blog.ipfire.org/post/{{ release.blog.slug }}">{{ _("Release Notes") }}</a>
+ {% end %}
+ </h6>
+ </div>
+
+ <div class="card-deck">
+ {% for arch in release.arches %}
+ <div class="card">
+ <div class="card-header text-center">
+ <h6 class="my-0">{{ arch }}</h6>
+ </div>
+
+ <ul class="list-group list-group-flush">
+ {% for file in release.get_files_by_arch(arch) %}
+ <li class="list-group-item d-flex flex-column align-items-start">
+ <div class="d-flex w-100 justify-content-between">
+ <a class="download-splash" href="{{ file.url }}">{{ _(file.desc) }}</a>
+
+ {% if file.size >= 1024 * 1024 %}
+ <span class="text-muted">{{ format_size(file.size) }}</span>
+ {% end %}
+ </div>
+ </li>
+ {% end %}
+ </ul>
+ </div>
+ {% end %}
+ </div>
+
+ <ul class="nav justify-content-center my-3">
+ <li class="nav-item">
+ <a class="nav-link" data-toggle="collapse" href="#checksums">
+ {{ _("Checksums") }}
+ </a>
+ </li>
+ </ul>
+
+ <div class="collapse" id="checksums">
+ <div class="card card-body">
+ <h5 class="card-title text-center">{{ _("Checksums") }}</h5>
+
+ <table class="table table-sm">
+ <thead>
+ <tr>
+ <th scope="col"></th>
+ <th scope="col">{{ _("SHA 1") }}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {% for f in release.files %}
+ <tr>
+ <td>{{ f.basename }}</td>
+ <td>
+ <pre>{{ f.sha1 }}</pre>
+ </td>
+ </tr>
+ {% end %}
+ </tbody>
+ </table>
+ </div>
+ </div>
+{% end block %}
+++ /dev/null
-<a class="btn btn-primary btn-xlg m-5 d-flex justify-content-center" href="{{ escape(image.url) }}">
- <div class="mr-3">
- <svg class="icon i_lg i_white i_download"><use xlink:href="#download"/></svg>
- </div>
- <div>
- {{ _("Download %s") % escape(release.name) }}<br>
- <small>({{ _("ISO-Image") }} - {{ escape(image.arch) }} - {{ format_size(image.size) }})</small>
- </div>
-</a>
\ No newline at end of file
from handlers import *
from . import blog
+from . import download
from . import location
from . import ui_modules
"BlogPost" : blog.PostModule,
"BlogPosts" : blog.PostsModule,
+ # Download
+ "DownloadButton" : download.ButtonModule,
+
# Location
"Map" : ui_modules.MapModule,
# Old modules
- "DownloadButton" : ui_modules.DownloadButtonModule,
"LanguageName" : ui_modules.LanguageNameModule,
"MirrorItem" : ui_modules.MirrorItemModule,
(r"/", IndexHandler),
# Download sites
- (r"/download", DownloadHandler),
(r"/downloads", tornado.web.RedirectHandler, { "url" : "/download" }),
+ (r"/download", download.IndexHandler),
+ (r"/download/([0-9a-z\-\.]+)", download.ReleaseHandler),
# Donate
(r"/donate", DonateHandler),
--- /dev/null
+#!/usr/bin/python
+
+import tornado.web
+
+from . import handlers_base as base
+
+from . import ui_modules
+
+class IndexHandler(base.BaseHandler):
+ def get(self):
+ release = self.backend.releases.get_latest()
+ if not release:
+ raise tornado.web.HTTPError(404)
+
+ self.render("download/index.html", release=release)
+
+
+class ReleaseHandler(base.BaseHandler):
+ def get(self, slug):
+ release = self.backend.releases.get_by_sname(slug)
+ if not release:
+ raise tornado.web.HTTPError(404)
+
+ self.render("download/release.html", release=release)
+
+
+class ButtonModule(ui_modules.UIModule):
+ def render(self, release):
+ for arch in release.arches:
+ for file in release.get_files_by_arch(arch):
+ return self.render_string("download/modules/button.html", release=release, file=file)
+
+ # return nothing
+ return ""