Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar/bundle
diff options
context:
space:
mode:
Diffstat (limited to 'src/sugar/bundle')
-rw-r--r--src/sugar/bundle/Makefile.am6
-rw-r--r--src/sugar/bundle/__init__.py16
-rw-r--r--src/sugar/bundle/activitybundle.py375
-rw-r--r--src/sugar/bundle/bundle.py199
-rw-r--r--src/sugar/bundle/contentbundle.py220
5 files changed, 816 insertions, 0 deletions
diff --git a/src/sugar/bundle/Makefile.am b/src/sugar/bundle/Makefile.am
new file mode 100644
index 0000000..f1af791
--- /dev/null
+++ b/src/sugar/bundle/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pythondir)/sugar/bundle
+sugar_PYTHON = \
+ __init__.py \
+ bundle.py \
+ activitybundle.py \
+ contentbundle.py
diff --git a/src/sugar/bundle/__init__.py b/src/sugar/bundle/__init__.py
new file mode 100644
index 0000000..85ebced
--- /dev/null
+++ b/src/sugar/bundle/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
new file mode 100644
index 0000000..eb35307
--- /dev/null
+++ b/src/sugar/bundle/activitybundle.py
@@ -0,0 +1,375 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Sugar activity bundles
+
+UNSTABLE.
+"""
+
+from ConfigParser import ConfigParser
+import locale
+import os
+import tempfile
+import logging
+
+from sugar import env
+from sugar import util
+from sugar.bundle.bundle import Bundle, \
+ MalformedBundleException, NotInstalledException
+
+class ActivityBundle(Bundle):
+ """A Sugar activity bundle
+
+ See http://wiki.laptop.org/go/Activity_bundles for details
+ """
+
+ MIME_TYPE = 'application/vnd.olpc-sugar'
+ DEPRECATED_MIME_TYPE = 'application/vnd.olpc-x-sugar'
+
+ _zipped_extension = '.xo'
+ _unzipped_extension = '.activity'
+ _infodir = 'activity'
+
+ def __init__(self, path):
+ Bundle.__init__(self, path)
+ self.activity_class = None
+ self.bundle_exec = None
+
+ self._name = None
+ self._icon = None
+ self._bundle_id = None
+ self._mime_types = None
+ self._show_launcher = True
+ self._activity_version = 0
+ self._installation_time = os.stat(path).st_mtime
+ self._manifest = None
+
+ info_file = self.get_file('activity/activity.info')
+ if info_file is None:
+ raise MalformedBundleException('No activity.info file')
+ self._parse_info(info_file)
+
+ linfo_file = self._get_linfo_file()
+ if linfo_file:
+ self._parse_linfo(linfo_file)
+
+ def _get_manifest(self):
+ if self._manifest is None:
+ self._manifest = self._read_manifest()
+ return self._manifest
+
+ manifest = property(_get_manifest, None, None,
+ "NOTICE: this property is potentially quite slow, so better make sure "
+ "that it's not called at performance-critical points like shell or "
+ "activity startup.")
+
+ def _raw_manifest(self):
+ f = self.get_file("MANIFEST")
+ if not f:
+ logging.warning("Activity directory lacks a MANIFEST file.")
+ return []
+
+ ret = [line.strip() for line in f.readlines()]
+ f.close()
+ return ret
+
+ def _read_manifest(self):
+ """return a list with the lines in MANIFEST, with invalid lines replaced
+ by empty lines.
+
+ Since absolute order carries information on file history, it should
+ be preserved. For instance, when renaming a file, you should leave
+ the new name on the same line as the old one.
+ """
+ logging.debug('STARTUP: Reading manifest')
+ lines = self._raw_manifest()
+
+ # Remove trailing newlines, they do not help keep absolute position.
+ while lines and lines[-1] == "":
+ lines = lines[:-1]
+
+ for num, line in enumerate(lines):
+ if not line:
+ continue
+
+ # Remove duplicates
+ if line in lines[0:num]:
+ lines[num] = ""
+ logging.warning("Bundle %s: duplicate entry in MANIFEST: %s"
+ % (self._name,line))
+ continue
+
+ # Remove MANIFEST
+ if line == "MANIFEST":
+ lines[num] = ""
+ logging.warning("Bundle %s: MANIFEST includes itself: %s"
+ % (self._name,line))
+
+ # Remove invalid files
+ if not self.is_file(line):
+ lines[num] = ""
+ logging.warning("Bundle %s: invalid entry in MANIFEST: %s"
+ % (self._name,line))
+
+ return lines
+
+ def get_files(self, manifest = None):
+ files = [line for line in (manifest or self.manifest) if line]
+
+ if self.is_file('MANIFEST'):
+ files.append('MANIFEST')
+
+ return files
+
+ def _parse_info(self, info_file):
+ cp = ConfigParser()
+ cp.readfp(info_file)
+
+ section = 'Activity'
+
+ if cp.has_option(section, 'bundle_id'):
+ self._bundle_id = cp.get(section, 'bundle_id')
+ # FIXME deprecated
+ elif cp.has_option(section, 'service_name'):
+ self._bundle_id = cp.get(section, 'service_name')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s does not specify a bundle id' %
+ self._path)
+
+ if cp.has_option(section, 'name'):
+ self._name = cp.get(section, 'name')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s does not specify a name' % self._path)
+
+ # FIXME class is deprecated
+ if cp.has_option(section, 'class'):
+ self.activity_class = cp.get(section, 'class')
+ elif cp.has_option(section, 'exec'):
+ self.bundle_exec = cp.get(section, 'exec')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s must specify either class or exec' %
+ self._path)
+
+ if cp.has_option(section, 'mime_types'):
+ mime_list = cp.get(section, 'mime_types').strip(';')
+ self._mime_types = [ mime.strip() for mime in mime_list.split(';') ]
+
+ if cp.has_option(section, 'show_launcher'):
+ if cp.get(section, 'show_launcher') == 'no':
+ self._show_launcher = False
+
+ if cp.has_option(section, 'icon'):
+ self._icon = cp.get(section, 'icon')
+
+ if cp.has_option(section, 'activity_version'):
+ version = cp.get(section, 'activity_version')
+ try:
+ self._activity_version = int(version)
+ except ValueError:
+ raise MalformedBundleException(
+ 'Activity bundle %s has invalid version number %s' %
+ (self._path, version))
+
+ def _get_linfo_file(self):
+ lang = locale.getdefaultlocale()[0]
+ if not lang:
+ return None
+
+ linfo_path = os.path.join('locale', lang, 'activity.linfo')
+ linfo_file = self.get_file(linfo_path)
+ if linfo_file is not None:
+ return linfo_file
+
+ linfo_path = os.path.join('locale', lang[:2], 'activity.linfo')
+ linfo_file = self.get_file(linfo_path)
+ if linfo_file is not None:
+ return linfo_file
+
+ return None
+
+ def _parse_linfo(self, linfo_file):
+ cp = ConfigParser()
+ cp.readfp(linfo_file)
+
+ section = 'Activity'
+
+ if cp.has_option(section, 'name'):
+ self._name = cp.get(section, 'name')
+
+ def get_locale_path(self):
+ """Get the locale path inside the (installed) activity bundle."""
+ if self._zip_file is not None:
+ raise NotInstalledException
+ return os.path.join(self._path, 'locale')
+
+ def get_icons_path(self):
+ """Get the icons path inside the (installed) activity bundle."""
+ if self._zip_file is not None:
+ raise NotInstalledException
+ return os.path.join(self._path, 'icons')
+
+ def get_path(self):
+ """Get the activity bundle path."""
+ return self._path
+
+ def get_name(self):
+ """Get the activity user visible name."""
+ return self._name
+
+ def get_installation_time(self):
+ """Get a timestamp representing the time at which this activity was
+ installed."""
+ return self._installation_time
+
+ def get_bundle_id(self):
+ """Get the activity bundle id"""
+ return self._bundle_id
+
+ # FIXME: this should return the icon data, not a filename, so that
+ # we don't need to create a temp file in the zip case
+ def get_icon(self):
+ """Get the activity icon name"""
+ icon_path = os.path.join('activity', self._icon + '.svg')
+ if self._zip_file is None:
+ return os.path.join(self._path, icon_path)
+ else:
+ icon_data = self.get_file(icon_path).read()
+ temp_file, temp_file_path = tempfile.mkstemp(self._icon)
+ os.write(temp_file, icon_data)
+ os.close(temp_file)
+ return util.TempFilePath(temp_file_path)
+
+ def get_activity_version(self):
+ """Get the activity version"""
+ return self._activity_version
+
+ def get_command(self):
+ """Get the command to execute to launch the activity factory"""
+ if self.bundle_exec:
+ command = os.path.expandvars(self.bundle_exec)
+ else:
+ command = 'sugar-activity ' + self.activity_class
+
+ return command
+
+ def get_mime_types(self):
+ """Get the MIME types supported by the activity"""
+ return self._mime_types
+
+ def get_show_launcher(self):
+ """Get whether there should be a visible launcher for the activity"""
+ return self._show_launcher
+
+ def install(self, install_dir=None, strict_manifest=False):
+ if install_dir is None:
+ install_dir = env.get_user_activities_path()
+
+ self._unzip(install_dir)
+
+ install_path = os.path.join(install_dir, self._zip_root_dir)
+
+ # List installed files
+ manifestfiles = self.get_files(self._raw_manifest())
+ paths = []
+ for root, dirs_, files in os.walk(install_path):
+ rel_path = root[len(install_path) + 1:]
+ for f in files:
+ paths.append(os.path.join(rel_path, f))
+
+ # Check the list against the MANIFEST
+ for path in paths:
+ if path in manifestfiles:
+ manifestfiles.remove(path)
+ elif path != "MANIFEST":
+ logging.warning("Bundle %s: %s not in MANIFEST"%
+ (self._name,path))
+ if strict_manifest:
+ os.remove(os.path.join(install_path, path))
+
+ # Is anything in MANIFEST left over after accounting for all files?
+ if manifestfiles:
+ err = ("Bundle %s: files in MANIFEST not included: %s"%
+ (self._name,str(manifestfiles)))
+ if strict_manifest:
+ raise MalformedBundleException(err)
+ else:
+ logging.warning(err)
+
+ xdg_data_home = os.getenv('XDG_DATA_HOME',
+ os.path.expanduser('~/.local/share'))
+
+ mime_path = os.path.join(install_path, 'activity', 'mimetypes.xml')
+ if os.path.isfile(mime_path):
+ mime_dir = os.path.join(xdg_data_home, 'mime')
+ mime_pkg_dir = os.path.join(mime_dir, 'packages')
+ if not os.path.isdir(mime_pkg_dir):
+ os.makedirs(mime_pkg_dir)
+ installed_mime_path = os.path.join(mime_pkg_dir,
+ '%s.xml' % self._bundle_id)
+ os.symlink(mime_path, installed_mime_path)
+ os.spawnlp(os.P_WAIT, 'update-mime-database',
+ 'update-mime-database', mime_dir)
+
+ mime_types = self.get_mime_types()
+ if mime_types is not None:
+ installed_icons_dir = os.path.join(xdg_data_home,
+ 'icons/sugar/scalable/mimetypes')
+ if not os.path.isdir(installed_icons_dir):
+ os.makedirs(installed_icons_dir)
+
+ for mime_type in mime_types:
+ mime_icon_base = os.path.join(install_path, 'activity',
+ mime_type.replace('/', '-'))
+ svg_file = mime_icon_base + '.svg'
+ info_file = mime_icon_base + '.icon'
+ if os.path.isfile(svg_file):
+ os.symlink(svg_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(svg_file)))
+ if os.path.isfile(info_file):
+ os.symlink(info_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(info_file)))
+ return install_path
+
+ def uninstall(self, install_path, force=False):
+ xdg_data_home = os.getenv('XDG_DATA_HOME',
+ os.path.expanduser('~/.local/share'))
+
+ mime_dir = os.path.join(xdg_data_home, 'mime')
+ installed_mime_path = os.path.join(mime_dir, 'packages',
+ '%s.xml' % self._bundle_id)
+ if os.path.exists(installed_mime_path):
+ os.remove(installed_mime_path)
+ os.spawnlp(os.P_WAIT, 'update-mime-database',
+ 'update-mime-database', mime_dir)
+
+ mime_types = self.get_mime_types()
+ if mime_types is not None:
+ installed_icons_dir = os.path.join(xdg_data_home,
+ 'icons/sugar/scalable/mimetypes')
+ if os.path.isdir(installed_icons_dir):
+ for f in os.listdir(installed_icons_dir):
+ path = os.path.join(installed_icons_dir, f)
+ if os.path.islink(path) and \
+ os.readlink(path).startswith(install_path):
+ os.remove(path)
+
+ self._uninstall(install_path)
diff --git a/src/sugar/bundle/bundle.py b/src/sugar/bundle/bundle.py
new file mode 100644
index 0000000..a1b2686
--- /dev/null
+++ b/src/sugar/bundle/bundle.py
@@ -0,0 +1,199 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Sugar bundle file handler
+
+UNSTABLE.
+"""
+
+import os
+import logging
+import shutil
+import StringIO
+import zipfile
+
+class AlreadyInstalledException(Exception):
+ pass
+
+class NotInstalledException(Exception):
+ pass
+
+class InvalidPathException(Exception):
+ pass
+
+class ZipExtractException(Exception):
+ pass
+
+class RegistrationException(Exception):
+ pass
+
+class MalformedBundleException(Exception):
+ pass
+
+class Bundle(object):
+ """A Sugar activity, content module, etc.
+
+ The bundle itself may be either a zip file or a directory
+ hierarchy, with metadata about the bundle stored various files
+ inside it.
+
+ This is an abstract base class. See ActivityBundle and
+ ContentBundle for more details on those bundle types.
+ """
+
+ _zipped_extension = None
+ _unzipped_extension = None
+
+ def __init__(self, path):
+ self._path = path
+ self._zip_root_dir = None
+
+ if os.path.isdir(self._path):
+ self._zip_file = None
+ else:
+ self._zip_file = zipfile.ZipFile(self._path)
+ self._check_zip_bundle()
+
+ # manifest = self._get_file(self._infodir + '/contents')
+ # if manifest is None:
+ # raise MalformedBundleException('No manifest file')
+ #
+ # signature = self._get_file(self._infodir + '/contents.sig')
+ # if signature is None:
+ # raise MalformedBundleException('No signature file')
+
+ def __del__(self):
+ if self._zip_file is not None:
+ self._zip_file.close()
+
+ def _check_zip_bundle(self):
+ file_names = self._zip_file.namelist()
+ if len(file_names) == 0:
+ raise MalformedBundleException('Empty zip file')
+
+ if file_names[0] == 'mimetype':
+ del file_names[0]
+
+ self._zip_root_dir = file_names[0].split('/')[0]
+ if self._zip_root_dir.startswith('.'):
+ raise MalformedBundleException(
+ 'root directory starts with .')
+ if self._unzipped_extension is not None:
+ (name_, ext) = os.path.splitext(self._zip_root_dir)
+ if ext != self._unzipped_extension:
+ raise MalformedBundleException(
+ 'All files in the bundle must be inside a single ' +
+ 'directory whose name ends with "%s"' %
+ self._unzipped_extension)
+
+ for file_name in file_names:
+ if not file_name.startswith(self._zip_root_dir):
+ raise MalformedBundleException(
+ 'All files in the bundle must be inside a single ' +
+ 'top-level directory')
+
+ def get_file(self, filename):
+ f = None
+
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ try:
+ f = open(path,"rb")
+ except IOError:
+ return None
+ else:
+ path = os.path.join(self._zip_root_dir, filename)
+ try:
+ data = self._zip_file.read(path)
+ f = StringIO.StringIO(data)
+ except KeyError:
+ logging.debug('%s not found.' % filename)
+
+ return f
+
+ def is_file(self, filename):
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ return os.path.isfile(path)
+ else:
+ path = os.path.join(self._zip_root_dir, filename)
+ try:
+ self._zip_file.getinfo(path)
+ except KeyError:
+ return False
+
+ return True
+
+ def is_dir(self, filename):
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ return os.path.isdir(path)
+ else:
+ path = os.path.join(self._zip_root_dir, filename, "")
+ for f in self._zip_file.namelist():
+ if f.startswith(path):
+ return True
+ return False
+
+ def get_path(self):
+ """Get the bundle path."""
+ return self._path
+
+ def _unzip(self, install_dir):
+ if self._zip_file is None:
+ raise AlreadyInstalledException
+
+ if not os.path.isdir(install_dir):
+ os.mkdir(install_dir, 0775)
+
+ # zipfile provides API that in theory would let us do this
+ # correctly by hand, but handling all the oddities of
+ # Windows/UNIX mappings, extension attributes, deprecated
+ # features, etc makes it impractical.
+ # FIXME: use manifest
+ if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', '-o', self._path,
+ '-x', 'mimetype', '-d', install_dir):
+ # clean up install dir after failure
+ shutil.rmtree(os.path.join(install_dir, self._zip_root_dir),
+ ignore_errors=True)
+ # indicate failure.
+ raise ZipExtractException
+
+ def _zip(self, bundle_path):
+ if self._zip_file is not None:
+ raise NotInstalledException
+
+ raise NotImplementedError
+
+ def _uninstall(self, install_path):
+ if not os.path.isdir(install_path):
+ raise InvalidPathException
+ if self._unzipped_extension is not None:
+ (name_, ext) = os.path.splitext(install_path)
+ if ext != self._unzipped_extension:
+ raise InvalidPathException
+
+ for root, dirs, files in os.walk(install_path, topdown=False):
+ for name in files:
+ os.remove(os.path.join(root, name))
+ for name in dirs:
+ path = os.path.join(root, name)
+ if os.path.islink(path):
+ os.remove(path)
+ else:
+ os.rmdir(path)
+ os.rmdir(install_path)
diff --git a/src/sugar/bundle/contentbundle.py b/src/sugar/bundle/contentbundle.py
new file mode 100644
index 0000000..a95ed61
--- /dev/null
+++ b/src/sugar/bundle/contentbundle.py
@@ -0,0 +1,220 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Sugar content bundles
+
+UNSTABLE.
+"""
+
+from ConfigParser import ConfigParser
+import os
+import urllib
+
+from sugar import env
+from sugar.bundle.bundle import Bundle, NotInstalledException, \
+ MalformedBundleException
+
+class ContentBundle(Bundle):
+ """A Sugar content bundle
+
+ See http://wiki.laptop.org/go/Content_bundles for details
+ """
+
+ MIME_TYPE = 'application/vnd.olpc-content'
+
+ _zipped_extension = '.xol'
+ _unzipped_extension = None
+ _infodir = 'library'
+
+ def __init__(self, path):
+ Bundle.__init__(self, path)
+
+ self._locale = None
+ self._l10n = None
+ self._category = None
+ self._name = None
+ self._subcategory = None
+ self._category_class = None
+ self._category_icon = None
+ self._library_version = None
+ self._bundle_class = None
+ self._activity_start = None
+
+ info_file = self.get_file('library/library.info')
+ if info_file is None:
+ raise MalformedBundleException('No library.info file')
+ self._parse_info(info_file)
+
+ if (self.get_file('index.html') is None and
+ self.get_file('library/library.xml') is None):
+ raise MalformedBundleException(
+ 'Content bundle %s has neither index.html nor library.xml' %
+ self._path)
+
+ def _parse_info(self, info_file):
+ cp = ConfigParser()
+ cp.readfp(info_file)
+
+ section = 'Library'
+
+ if cp.has_option(section, 'host_version'):
+ version = cp.get(section, 'host_version')
+ try:
+ if int(version) != 1:
+ raise MalformedBundleException(
+ 'Content bundle %s has unknown host_version number %s' %
+ (self._path, version))
+ except ValueError:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid host_version number %s' %
+ (self._path, version))
+
+ if cp.has_option(section, 'name'):
+ self._name = cp.get(section, 'name')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a name' % self._path)
+
+ if cp.has_option(section, 'library_version'):
+ version = cp.get(section, 'library_version')
+ try:
+ self._library_version = int(version)
+ except ValueError:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid version number %s' %
+ (self._path, version))
+
+ if cp.has_option(section, 'l10n'):
+ l10n = cp.get(section, 'l10n')
+ if l10n == 'true':
+ self._l10n = True
+ elif l10n == 'false':
+ self._l10n = False
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid l10n key "%s"' %
+ (self._path, l10n))
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify if it is localized' %
+ self._path)
+
+ if cp.has_option(section, 'locale'):
+ self._locale = cp.get(section, 'locale')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a locale' % self._path)
+
+ if cp.has_option(section, 'category'):
+ self._category = cp.get(section, 'category')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a category' % self._path)
+
+ if cp.has_option(section, 'category_icon'):
+ self._category_icon = cp.get(section, 'category_icon')
+ else:
+ self._category_icon = None
+
+ if cp.has_option(section, 'category_class'):
+ self._category_class = cp.get(section, 'category_class')
+ else:
+ self._category_class = None
+
+ if cp.has_option(section, 'subcategory'):
+ self._subcategory = cp.get(section, 'subcategory')
+ else:
+ self._subcategory = None
+
+ if cp.has_option(section, 'bundle_class'):
+ self._bundle_class = cp.get(section, 'bundle_class')
+ else:
+ self._bundle_class = None
+
+ if cp.has_option(section, 'activity_start'):
+ self._activity_start = cp.get(section, 'activity_start')
+ else:
+ self._activity_start = 'index.html'
+
+ def get_name(self):
+ return self._name
+
+ def get_library_version(self):
+ return self._library_version
+
+ def get_l10n(self):
+ return self._l10n
+
+ def get_locale(self):
+ return self._locale
+
+ def get_category(self):
+ return self._category
+
+ def get_category_icon(self):
+ return self._category_icon
+
+ def get_category_class(self):
+ return self._category_class
+
+ def get_subcategory(self):
+ return self._subcategory
+
+ def get_bundle_class(self):
+ return self._bundle_class
+
+ def get_activity_start(self):
+ return self._activity_start
+
+ def _run_indexer(self):
+ xdg_data_dirs = os.getenv('XDG_DATA_DIRS',
+ '/usr/local/share/:/usr/share/')
+ for path in xdg_data_dirs.split(':'):
+ indexer = os.path.join(path, 'library-common', 'make_index.py')
+ if os.path.exists(indexer):
+ os.spawnlp(os.P_WAIT, 'python', 'python', indexer)
+
+ def get_root_dir(self):
+ return os.path.join(env.get_user_library_path(), self._zip_root_dir)
+
+ def get_start_path(self):
+ return os.path.join(self.get_root_dir(), self._activity_start)
+
+ def get_start_uri(self):
+ return "file://" + urllib.pathname2url(self.get_start_path())
+
+ def is_installed(self):
+ if self._zip_file is None:
+ return True
+ elif os.path.isdir(self.get_root_dir()):
+ return True
+ else:
+ return False
+
+ def install(self):
+ self._unzip(env.get_user_library_path())
+ self._run_indexer()
+
+ def uninstall(self):
+ if self._zip_file is None:
+ if not self.is_installed():
+ raise NotInstalledException
+ install_dir = self._path
+ else:
+ install_dir = os.path.join(self.get_root_dir())
+ self._uninstall(install_dir)
+ self._run_indexer()