#!/usr/bin/python
+import asyncio
import datetime
import logging
+import mimetypes
import os
import shutil
import urllib.parse
ret = []
for row in res:
- f = File(self.backend, row)
+ f = File(self.backend, self, row)
ret.append(f)
return ret
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
\"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
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
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)
{{ 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>
(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),
#!/usr/bin/python
-import mimetypes
import os.path
import tornado.web
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)
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)