]> git.ipfire.org Git - pbs.git/commitdiff
packages: Make files downloadable again
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 14 Jul 2022 14:35:36 +0000 (14:35 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 14 Jul 2022 14:35:36 +0000 (14:35 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/buildservice/packages.py
src/templates/modules/packages-files-table.html
src/web/__init__.py
src/web/packages.py

index 632cfa78b8ae17eded778485efc6dee4463a7544..cfd0fd4c5a0e50e3e78356c4cad0e8cea7bdad63 100644 (file)
@@ -1,7 +1,9 @@
 #!/usr/bin/python
 
+import asyncio
 import datetime
 import logging
+import mimetypes
 import os
 import shutil
 import urllib.parse
@@ -417,7 +419,7 @@ class Package(base.DataObject):
 
                ret = []
                for row in res:
-                       f = File(self.backend, row)
+                       f = File(self.backend, self, row)
                        ret.append(f)
 
                return ret
@@ -427,7 +429,7 @@ class Package(base.DataObject):
                        WHERE pkg_id = %s AND name = %s", self.id, filename)
 
                if res:
-                       return File(self.backend, res)
+                       return File(self.backend, self, res)
 
        def add_file(self, name, size, hash_sha512, type, config, mode, user, group, mtime, capabilities):
                # Convert mtime from seconds since epoch to datetime
@@ -437,11 +439,11 @@ class Package(base.DataObject):
                        \"user\", \"group\", mtime, capabilities) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
                        self.id, name, size, hash_sha512, type, config, mode, user, group, mtime, capabilities)
 
-       def open(self):
-               path = os.path.join(PACKAGES_DIR, self.path)
-
-               if os.path.exists(path):
-                       return pakfire.packages.open(None, None, path)
+       async def open(self):
+               """
+                       Opens the package archive and returns a handle
+               """
+               return await self.backend.open(self.path)
 
        ## properties
 
@@ -476,22 +478,58 @@ class Package(base.DataObject):
 
 
 class File(base.Object):
-       def init(self, data):
+       def init(self, package, data):
+               self.package = package
                self.data = data
 
-       def __getattr__(self, attr):
-               try:
-                       return self.data[attr]
-               except KeyError:
-                       raise AttributeError(attr)
+       @property
+       def name(self):
+               return self.data.name
 
        @property
-       def downloadable(self):
-               # All regular files are downloadable.
-               return self.type == 0
+       def size(self):
+               return self.data.size
+
+       @property
+       def hash_sha512(self):
+               return self.data.hash_sha512
+
+       @property
+       def type(self):
+               return self.data.type
+
+       @property
+       def config(self):
+               return self.data.config
+
+       @property
+       def mode(self):
+               return self.data.mode
+
+       @property
+       def user(self):
+               return self.data.user
 
        @property
-       def viewable(self):
+       def group(self):
+               return self.data.group
+
+       @property
+       def mtime(self):
+               return self.data.mtime
+
+       @property
+       def capabilities(self):
+               return self.data.capabilities
+
+       def is_downloadable(self):
+               """
+                       Returns True if this file is downloadable
+               """
+               # All regular files are downloadable
+               return self.type == 0
+
+       def is_viewable(self):
                # Empty files cannot be viewed.
                if self.size == 0:
                        return False
@@ -501,3 +539,28 @@ class File(base.Object):
                                return True
 
                return False
+
+       @property
+       def mimetype(self):
+               """
+                       The (guessed) MIME type of this file
+               """
+               # Guess the MIME type of the file.
+               type, encoding = mimetypes.guess_type(self.name)
+
+               return type or "application/octet-stream"
+
+       # Payload
+
+       async def get_payload(self):
+               """
+                       Fetches and returns the payload
+               """
+               # Open the package
+               p = await self.package.open()
+
+               # Create a helper function to read the entire payload
+               func = lambda: p.read(self.name)
+
+               # Read the payload in a separate thread
+               return await asyncio.to_thread(func)
index 5d25ed6751543a0064036f7f85c0d2480ac742a5..73a8d42fe95373d6b6cc748fa81f6f046e44881c 100644 (file)
                                                {{ file.name }}
                                        </td>
                                        <td>
-                                               {% if file.viewable %}
+                                               {% if file.is_viewable() %}
                                                        <a class="btn btn-light" href="/package/{{ pkg.uuid }}/view{{ file.name }}">
                                                                <i class="icon-file"></i>{{ _("View file") }}
                                                        </a>
                                                {% end %}
                                        </td>
                                        <td>
-                                       {% if file.downloadable %}
+                                       {% if file.is_downloadable() %}
                                                <a class="btn btn-light" href="/package/{{ pkg.uuid }}/download{{ file.name }}">
                                                        <i class="icon-download"></i>{{ _("Download") }}
                                                </a>
index 3f928a7210eceffd7e5f42eb81394ad04f1f219e..e02c8b8d80a8b726d262152389e0ead3d196a094 100644 (file)
@@ -134,7 +134,8 @@ class Application(tornado.web.Application):
                        (r"/packages", packages.IndexHandler),
                        (r"/packages/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})", packages.ShowHandler),
                        (r"/packages/([\w\-\+]+)", packages.NameHandler),
-                       (r"/package/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})/download(.*)", packages.PackageFileDownloadHandler),
+                       (r"/package/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})/download(.*)",
+                               packages.FileDownloadHandler),
                        (r"/package/([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})/view(.*)", packages.PackageFileViewHandler),
                        (r"/package/([\w\-\+]+)/properties", packages.PackagePropertiesHandler),
 
index f1fdd5613c1daffbcd11a15efc8a5704f9b83e06..1dd88aaeec23d17af3f200d86f040964ebe11518 100644 (file)
@@ -1,6 +1,5 @@
 #!/usr/bin/python
 
-import mimetypes
 import os.path
 import tornado.web
 
@@ -42,7 +41,7 @@ class ShowHandler(base.BaseHandler):
        def get(self, uuid):
                package = self.backend.packages.get_by_uuid(uuid)
                if not package:
-                       return tornado.web.HTTPError(404, "Could not find package: %s" % uuid)
+                       raise tornado.web.HTTPError(404, "Could not find package: %s" % uuid)
 
                self.render("packages/show.html", package=package)
 
@@ -83,60 +82,45 @@ class PackagePropertiesHandler(base.BaseHandler):
                build.pkg.update_property("critical_path", critical_path)
 
 
-class PackageFileDownloadHandler(base.BaseHandler):
-       def get_file(self, pkg_uuid, filename):
-               # Fetch package.
-               pkg = self.backend.packages.get_by_uuid(pkg_uuid)
-               if not pkg:
-                       raise tornado.web.HTTPError(404, "Package not found: %s" % pkg_uuid)
+class FileDownloadHandler(base.BaseHandler):
+       async def get(self, uuid, path):
+               package = self.backend.packages.get_by_uuid(uuid)
+               if not package:
+                       raise tornado.web.HTTPError(404, "Could not find package: %s" % uuid)
 
-               # Check if the package has got a file with the given name.
-               file = pkg.get_file(filename)
+               # Fetch the file
+               file = package.get_file(path)
                if not file:
-                       raise tornado.web.HTTPError(404, "Package %s does not contain file %s" % (pkg, filename))
-
-               # Open the package in the filesystem.
-               pkg_file = pkg.open()
-               if not pkg_file:
-                       raise tornado.web.HTTPError(404, "Could not open package %s" % pkg.path)
-
-               # Open the file to transfer it to the client.
-               f = pkg_file.open_file(filename)
-               if not f:
-                       raise tornado.web.HTTPError(404, "Package %s does not contain file %s" % (pkg_file, filename))
+                       raise tornado.web.HTTPError(404, "Could not find file %s in %s" % (path, package))
 
-               # Guess the MIME type of the file.
-               (type, encoding) = mimetypes.guess_type(filename)
-               if not type:
-                       type = "text/plain"
+               # Is this file downloadable?
+               if not file.is_downloadable():
+                       raise tornado.web.HTTPError(400, "%s cannot be downloaded" % file)
 
-               return (pkg, f, type)
+               # Send Content-Length
+               self.set_header("Content-Length", file.size)
 
-       def get(self, pkg_uuid, filename):
-               pkg, f, mimetype = self.get_file(pkg_uuid, filename)
+               # Send MIME type
+               self.set_header("Content-Type", file.mimetype)
 
-               # Send the filename and mimetype in header.
-               self.set_header("Content-Disposition", "attachment; filename=%s" % os.path.basename(filename))
-               self.set_header("Content-Type", mimetype)
+               # Send the filename
+               self.set_header("Content-Disposition",
+                       "attachment; filename=%s" % os.path.basename(file.name))
 
                # These pages should not be indexed
                self.add_header("X-Robots-Tag", "noindex")
 
-               # Transfer the content chunk by chunk.
-               while True:
-                       buf = f.read(BUFFER_SIZE)
-                       if not buf:
-                               break
+               # XXX this should probably not be done in one large operation.
+               # Instead we should send the file chunk by chunk.
 
-                       self.write(buf)
-
-               f.close()
+               # Fetch the payload
+               payload = await file.get_payload()
 
-               # Done.
-               self.finish()
+               # Send the payload to the client
+               self.finish(payload)
 
 
-class PackageFileViewHandler(PackageFileDownloadHandler):
+class PackageFileViewHandler(FileDownloadHandler):
        def get(self, pkg_uuid, filename):
                pkg, f, mimetype = self.get_file(pkg_uuid, filename)