From eaef567cabc5c0dda9706faa0e17e20909131ade Mon Sep 17 00:00:00 2001 From: simpoir Date: Fri, 30 Jan 2009 04:14:48 +0000 Subject: ajout initial du tree jhbuild [jhbuild base] --- (limited to 'src/sugar/bundle/activitybundle.py') 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) -- cgit v0.9.1