From 2230cfbae1d8723330845abf11d40505d904bbec Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Wed, 7 Oct 2015 21:22:56 -0500 Subject: Add initial code for reading in APK files. --- apkkit/__init__.py | 0 apkkit/base/__init__.py | 0 apkkit/base/package.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ apkkit/io/__init__.py | 0 apkkit/io/apkfile.py | 10 +++ 5 files changed, 168 insertions(+) create mode 100644 apkkit/__init__.py create mode 100644 apkkit/base/__init__.py create mode 100644 apkkit/base/package.py create mode 100644 apkkit/io/__init__.py create mode 100644 apkkit/io/apkfile.py diff --git a/apkkit/__init__.py b/apkkit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apkkit/base/__init__.py b/apkkit/base/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apkkit/base/package.py b/apkkit/base/package.py new file mode 100644 index 0000000..87164e3 --- /dev/null +++ b/apkkit/base/package.py @@ -0,0 +1,158 @@ +"""Contains the Package class and related helper classes and functions.""" + +pkginfo_template = """ +# Generated by APK Kit for Adélie Linux +# {builduser}@{buildhost} {builddate} +pkgname = {name} +pkgver = {ver} +pkgdesc = {desc} +arch = {arch} +""" + +class Package: + """The base package class.""" + + def __init__(self, name, version, arch, description=None, url=None, size=0, + provides=None, depends=None): + """Initialise a package object. + + :param str name: + The name of the package. + + :param str version: + The version of the package. + + :param str arch: + The architecture of the package. + + :param str description: + (Recommended) The description of the package. Defaults to the name + if not set. + + :param int size: + (Recommended) The installed size of the package. You almost always + want to set this to something other than 0 if you don't want unhappy + users. :) + + :param str url: + (Optional) The URL of the homepage for the package. + + :param list provides: + (Optional) One or more virtuals that this package provides. + + :param list depends: + (Optional) One or more packages that are required to be installed + to use this package. + """ + + self._pkgname = name + self._pkgver = version + self._pkgdesc = description or name + self._url = url + self._size = int(size) + self._arch = arch + self._provides = provides or list() + self._depends = depends or list() + + @property + def name(self): + """The name of the package.""" + return self._pkgname + + @property + def version(self): + """The version of the package.""" + return self._pkgver + + @property + def description(self): + """The description of the package.""" + return self._pkgdesc + + @property + def url(self): + """The URL of the homepage of the package.""" + return self._url + + @property + def size(self): + """The installed size of the package in bytes.""" + return self._size + + @property + def arch(self): + """The architecture of the package.""" + return self._arch + + @property + def depends(self): + """The dependencies of the package.""" + return self._depends + + def __repr__(self): + return 'Package(name="{name}", version="{ver}", arch="{arch}", '\ + 'description="{desc}", url="{url}", size={size}, '\ + 'provides={prov}, depends={dep})'.format( + name=self._pkgname, ver=self._pkgver, arch=self._arch, + desc=self._pkgdesc, prov=self._provides, dep=self._depends, + url=self._url, size=self._size) + + def to_pkginfo(self): + """Serialises the package's information into the PKGINFO format. + + :returns str: The PKGINFO for this package. Unicode str, ready to be + written to a file. + + .. note:: To write a file, see the :py:meth:`.write_pkginfo` helper + method. + """ + + @classmethod + def from_pkginfo(cls, buf): + """Create a new :py:class:`Package` object from an existing PKGINFO. + + :param buf: + The buffer to read from (whether file, StringIO, etc). + + :returns: + A :py:class:`Package` object with the details from the PKGINFO. + + :throws ValueError: + If a required field is missing from the PKGINFO. + """ + + params = {} + param_map = {'pkgname': 'name', 'pkgver': 'version', 'arch': 'arch', + 'pkgdesc': 'description', 'provides': 'provides', + 'depend': 'depends', 'url': 'url', 'size': 'size'} + list_keys = {'provides', 'depend'} + + params['provides'] = list() + params['depends'] = list() + + for line in buf.readlines(): + # XXX TODO make this better + if type(line) != str: + line = line.decode('utf-8') + + # Skip comments. + if len(line) == 0 or line[0] == '#': + continue + + if line.index('=') == -1: + print('!!! malformed line? {} !!!'.format(line)) + continue + + (key, value) = line.split('=', 1) + key = key.strip() + value = value.strip() + + if key in param_map: + if key in list_keys: + params[param_map[key]].append(value) + else: + params[param_map[key]] = value + else: + print('!!! unrecognised PKGINFO key {} !!!'.format(key)) + + return cls(**params) diff --git a/apkkit/io/__init__.py b/apkkit/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apkkit/io/apkfile.py b/apkkit/io/apkfile.py new file mode 100644 index 0000000..327ba08 --- /dev/null +++ b/apkkit/io/apkfile.py @@ -0,0 +1,10 @@ +from apkkit.base.package import Package +import gzip # Not used, but we need to raise ImportError if gzip isn't built. +import tarfile + +class APKFile: + """Represents an APK file on disk (or in memory).""" + + def __init__(self, filename, mode='r'): + self.tar = tarfile.open(filename, mode) + self.package = Package.from_pkginfo(self.tar.extractfile('.PKGINFO')) -- cgit v1.2.3-70-g09d2