diff --git a/shell/Makefile.am b/shell/Makefile.am index 0424f9e5..6d6f6336 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -7,6 +7,7 @@ bin_SCRIPTS = \ sugardir = $(pkgdatadir)/shell sugar_PYTHON = \ - __init__.py + __init__.py \ + shellservice.py EXTRA_DIST = $(bin_SCRIPTS) diff --git a/shell/model/ShellModel.py b/shell/model/ShellModel.py index b1c21bcb..11254d92 100644 --- a/shell/model/ShellModel.py +++ b/shell/model/ShellModel.py @@ -55,8 +55,7 @@ class ShellModel(gobject.GObject): self._mesh = MeshModel(self._bundle_registry) self._home = HomeModel(self._bundle_registry) - path = os.path.expanduser('~/Activities') - self._bundle_registry.add_search_path(path) + self._bundle_registry.add_search_path(env.get_user_activities_dir()) for path in env.get_data_dirs(): bundles_path = os.path.join(path, 'activities') diff --git a/shell/shellservice.py b/shell/shellservice.py new file mode 100644 index 00000000..dcbfc0e6 --- /dev/null +++ b/shell/shellservice.py @@ -0,0 +1,19 @@ +import dbus + +_DBUS_SERVICE = "org.laptop.Shell" +_DBUS_INTERFACE = "org.laptop.Shell" +_DBUS_PATH = "/org/laptop/Shell" + +class ShellService(dbus.service.Object): + + def __init__(self, shellModel): + self._shellModel = shellModel + + bus = dbus.SessionBus() + bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus) + dbus.service.Object.__init__(self, bus_name, _DBUS_PATH) + + @dbus.service.method(_DBUS_INTERFACE, in_signature="s", out_signature="b") + def add_bundle(self, bundle_path): + registry = self._shellModel.get_bundle_registry() + return registry.add_bundle(bundle_path) diff --git a/shell/sugar-shell b/shell/sugar-shell index 4eaccf51..08a3a6d7 100755 --- a/shell/sugar-shell +++ b/shell/sugar-shell @@ -37,6 +37,7 @@ sys.path.insert(0, os.path.join(env.get_data_dir(), 'shell')) from view.FirstTimeDialog import FirstTimeDialog from view.Shell import Shell from model.ShellModel import ShellModel +from shellservice import ShellService name = profile.get_nick_name() if not name or not len(name): @@ -54,6 +55,7 @@ f.write(os.environ["DBUS_SESSION_BUS_ADDRESS"]) f.close() model = ShellModel() +service = ShellService(model) shell = Shell(model) # Start the NetworkManager applet diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index 2ca0cfda..c265b6c1 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -69,10 +69,13 @@ class ActivitiesBox(hippo.CanvasBox): self._invite_to_item = {} self._invites = self._shell_model.get_invites() - for bundle in self._shell_model.get_bundle_registry(): + bundle_registry = self._shell_model.get_bundle_registry() + for bundle in bundle_registry: if bundle.get_show_launcher(): self.add_activity(bundle) + bundle_registry.connect('bundle-added', self._bundle_added_cb) + for invite in self._invites: self.add_invite(invite) self._invites.connect('invite-added', self._invite_added_cb) @@ -92,6 +95,9 @@ class ActivitiesBox(hippo.CanvasBox): def _invite_removed_cb(self, invites, invite): self.remove_invite(invite) + def _bundle_added_cb(self, bundle_registry, bundle): + self.add_activity(bundle) + def add_activity(self, activity): item = ActivityItem(activity) item.connect('activated', self._activity_clicked_cb) diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py index a2e338fb..9b3a81d6 100644 --- a/sugar/activity/bundlebuilder.py +++ b/sugar/activity/bundlebuilder.py @@ -68,12 +68,6 @@ def _extract_bundle(source_file, dest_dir): def _get_source_path(): return os.getcwd() -def _get_activities_path(): - path = os.path.expanduser('~/Activities') - if not os.path.isdir(path): - os.mkdir(path) - return path - def _get_bundle_dir(): bundle_name = os.path.basename(_get_source_path()) return bundle_name + '.activity' @@ -82,7 +76,7 @@ def _get_install_dir(prefix): return os.path.join(prefix, 'share/activities') def _get_bundle_path(): - return os.path.join(_get_activities_path(), _get_bundle_dir()) + return os.path.join(env.get_user_activities_dir(), _get_bundle_dir()) def _get_package_name(): bundle = Bundle(_get_source_path()) diff --git a/sugar/activity/bundleregistry.py b/sugar/activity/bundleregistry.py index 8b7c44a4..22a84e1c 100644 --- a/sugar/activity/bundleregistry.py +++ b/sugar/activity/bundleregistry.py @@ -1,5 +1,6 @@ import os from ConfigParser import ConfigParser +import gobject from sugar.activity.bundle import Bundle from sugar import env @@ -18,10 +19,17 @@ class _ServiceManager(object): util.write_service(name, full_exec, self._path) -class BundleRegistry: +class BundleRegistry(gobject.GObject): """Service that tracks the available activity bundles""" + __gsignals__ = { + 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + def __init__(self): + gobject.GObject.__init__(self) + self._bundles = {} self._search_path = [] self._service_manager = _ServiceManager() @@ -54,10 +62,14 @@ class BundleRegistry: bundle_dir = os.path.join(path, f) if os.path.isdir(bundle_dir) and \ bundle_dir.endswith('.activity'): - self._add_bundle(bundle_dir) + self.add_bundle(bundle_dir) - def _add_bundle(self, bundle_path): + def add_bundle(self, bundle_path): bundle = Bundle(bundle_path) if bundle.is_valid(): self._bundles[bundle.get_service_name()] = bundle self._service_manager.add(bundle) + self.emit('bundle-added', bundle) + return True + else: + return False diff --git a/sugar/env.py b/sugar/env.py index 14d8d70c..c0df585b 100644 --- a/sugar/env.py +++ b/sugar/env.py @@ -84,3 +84,10 @@ def get_user_service_dir(): if not os.path.isdir(service_dir): os.makedirs(service_dir) return service_dir + +def get_user_activities_dir(): + path = os.path.expanduser('~/Activities') + if not os.path.isdir(path): + os.mkdir(path) + return path + diff --git a/tools/Makefile.am b/tools/Makefile.am index c4cd3bbd..fb3f598f 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1 +1,2 @@ -bin_SCRIPTS = sugar-setup-activity +bin_SCRIPTS = sugar-install-bundle \ + sugar-setup-activity diff --git a/tools/sugar-install-bundle b/tools/sugar-install-bundle new file mode 100755 index 00000000..0945c29a --- /dev/null +++ b/tools/sugar-install-bundle @@ -0,0 +1,56 @@ +#!/usr/bin/env python +import sys +import os +import zipfile +import dbus + +from sugar import env + +DBUS_SERVICE = "org.laptop.Shell" +DBUS_PATH = "/org/laptop/Shell" + +# We check here that all the files in the .xo are inside one only dir (bundle_root_dir). +def get_bundle_root_dir(file_names): + bundle_root_dir = None + for file_name in file_names: + if not bundle_root_dir: + bundle_root_dir = file_name.partition('/')[0] + if not bundle_root_dir.endswith('.activity'): + raise 'Incorrect bundle.' + else: + if not file_name.startswith(bundle_root_dir): + raise 'Incorrect bundle.' + + return bundle_root_dir + +bus = dbus.SessionBus() +proxy_obj = bus.get_object(DBUS_SERVICE, DBUS_PATH) +dbus_service = dbus.Interface(proxy_obj, DBUS_SERVICE) + +bundle_dir = env.get_user_activities_dir() + +zip_file = zipfile.ZipFile(sys.argv[1]) +file_names = zip_file.namelist() +bundle_root_dir = get_bundle_root_dir(file_names) +bundle_path = os.path.join(bundle_dir, bundle_root_dir) + +# FIXME: we need to support installing different versions of the same bundle. +if os.path.exists(bundle_path): + raise IOError, 'This bundle is already installed as ' + bundle_path + +if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', sys.argv[1], '-d', bundle_dir): + raise RuntimeError, 'An error occurred while extracting the .xo contents.' + +# notify shell of new bundle +if not dbus_service.add_bundle(bundle_path): + # error, let's delete the just expanded bundle. + for root, dirs, files in os.walk(bundle_path, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(bundle_path) + + raise RuntimeError, 'Bundle is not well-formed.' + +print "%s: '%s' installed." % (sys.argv[0], sys.argv[1])