The activity registry is now private to the shell.

Changed the activityfactory to take a bundle instead of a bundle_id
so that it doesn't depend on the registry.
This commit is contained in:
Marco Pesenti Gritti 2008-10-06 15:54:46 +02:00
parent 0f33a634c0
commit 5c6c7ab1d1
5 changed files with 70 additions and 330 deletions

View File

@ -6,5 +6,4 @@ sugar_PYTHON = \
activityhandle.py \ activityhandle.py \
activityservice.py \ activityservice.py \
bundlebuilder.py \ bundlebuilder.py \
main.py \ main.py
registry.py

View File

@ -53,6 +53,3 @@ class. This class allows for querying the ID of the root
window, requesting sharing across the network, and basic window, requesting sharing across the network, and basic
"what type of application are you" queries. "what type of application are you" queries.
""" """
from sugar.activity.registry import ActivityRegistry
from sugar.activity.registry import get_registry
from sugar.activity.registry import ActivityInfo

View File

@ -23,7 +23,6 @@ import gobject
from sugar.presence import presenceservice from sugar.presence import presenceservice
from sugar.activity.activityhandle import ActivityHandle from sugar.activity.activityhandle import ActivityHandle
from sugar.activity import registry
from sugar import util from sugar import util
from sugar import env from sugar import env
@ -84,9 +83,9 @@ def create_activity_id():
def get_environment(activity): def get_environment(activity):
environ = os.environ.copy() environ = os.environ.copy()
bin_path = os.path.join(activity.path, 'bin') bin_path = os.path.join(activity.get_path(), 'bin')
activity_root = env.get_profile_path(activity.bundle_id) activity_root = env.get_profile_path(activity.get_bundle_id())
if not os.path.exists(activity_root): if not os.path.exists(activity_root):
os.mkdir(activity_root) os.mkdir(activity_root)
@ -102,19 +101,19 @@ def get_environment(activity):
if not os.path.exists(tmp_dir): if not os.path.exists(tmp_dir):
os.mkdir(tmp_dir) os.mkdir(tmp_dir)
environ['SUGAR_BUNDLE_PATH'] = activity.path environ['SUGAR_BUNDLE_PATH'] = activity.get_path()
environ['SUGAR_BUNDLE_ID'] = activity.bundle_id environ['SUGAR_BUNDLE_ID'] = activity.get_bundle_id()
environ['SUGAR_ACTIVITY_ROOT'] = activity_root environ['SUGAR_ACTIVITY_ROOT'] = activity_root
environ['PATH'] = bin_path + ':' + environ['PATH'] environ['PATH'] = bin_path + ':' + environ['PATH']
#environ['RAINBOW_STRACE_LOG'] = '1' #environ['RAINBOW_STRACE_LOG'] = '1'
if activity.path.startswith(env.get_user_activities_path()): if activity.get_path().startswith(env.get_user_activities_path()):
environ['SUGAR_LOCALEDIR'] = os.path.join(activity.path, 'locale') environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(), 'locale')
if activity.bundle_id in [ 'org.laptop.WebActivity', if activity.get_bundle_id() in [ 'org.laptop.WebActivity',
'org.laptop.GmailActivity', 'org.laptop.GmailActivity',
'org.laptop.WikiBrowseActivity' 'org.laptop.WikiBrowseActivity'
]: ]:
environ['RAINBOW_CONSTANT_UID'] = 'yes' environ['RAINBOW_CONSTANT_UID'] = 'yes'
return environ return environ
@ -123,8 +122,8 @@ def get_command(activity, activity_id=None, object_id=None, uri=None):
if not activity_id: if not activity_id:
activity_id = create_activity_id() activity_id = create_activity_id()
command = activity.command.split(' ') command = activity.get_command().split(' ')
command.extend(['-b', activity.bundle_id]) command.extend(['-b', activity.get_bundle_id()])
command.extend(['-a', activity_id]) command.extend(['-a', activity_id])
if object_id is not None: if object_id is not None:
@ -139,7 +138,7 @@ def get_command(activity, activity_id=None, object_id=None, uri=None):
def open_log_file(activity): def open_log_file(activity):
i = 1 i = 1
while True: while True:
path = env.get_logs_path('%s-%s.log' % (activity.bundle_id, i)) path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i))
try: try:
fd = os.open(path, os.O_EXCL | os.O_CREAT \ fd = os.open(path, os.O_EXCL | os.O_CREAT \
| os.O_SYNC | os.O_WRONLY, 0644) | os.O_SYNC | os.O_WRONLY, 0644)
@ -164,10 +163,10 @@ class ActivityCreationHandler(gobject.GObject):
create call. create call.
""" """
def __init__(self, service_name, handle): def __init__(self, bundle, handle):
"""Initialise the handler """Initialise the handler
service_name -- the service name of the bundle factory bundle -- the ActivityBundle to launch
activity_handle -- stores the values which are to activity_handle -- stores the values which are to
be passed to the service to uniquely identify be passed to the service to uniquely identify
the activity to be created and the sharing the activity to be created and the sharing
@ -190,15 +189,16 @@ class ActivityCreationHandler(gobject.GObject):
""" """
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._service_name = service_name self._bundle = bundle
self._service_name = bundle.get_bundle_id()
self._handle = handle self._handle = handle
self._use_rainbow = os.path.exists('/etc/olpc-security') self._use_rainbow = os.path.exists('/etc/olpc-security')
if service_name in [ 'org.laptop.JournalActivity', if self._service_name in [ 'org.laptop.JournalActivity',
'org.laptop.Terminal', 'org.laptop.Terminal',
'org.laptop.Log', 'org.laptop.Log',
'org.laptop.Analyze' 'org.laptop.Analyze'
]: ]:
self._use_rainbow = False self._use_rainbow = False
bus = dbus.SessionBus() bus = dbus.SessionBus()
@ -233,49 +233,46 @@ class ActivityCreationHandler(gobject.GObject):
reply_handler=self._no_reply_handler, reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_error_handler) error_handler=self._notify_launch_error_handler)
activity_registry = registry.get_registry() environ = get_environment(self._bundle)
activity = activity_registry.get_activity(self._service_name) (log_path, log_file) = open_log_file(self._bundle)
if activity: command = get_command(self._bundle, self._handle.activity_id,
environ = get_environment(activity) self._handle.object_id,
(log_path, log_file) = open_log_file(activity) self._handle.uri)
command = get_command(activity, self._handle.activity_id,
self._handle.object_id,
self._handle.uri)
if not self._use_rainbow: if not self._use_rainbow:
# use gobject spawn functionality, so that zombies are # use gobject spawn functionality, so that zombies are
# automatically reaped by the gobject event loop. # automatically reaped by the gobject event loop.
def child_setup(): def child_setup():
# clone logfile.fileno() onto stdout/stderr # clone logfile.fileno() onto stdout/stderr
os.dup2(log_file.fileno(), 1) os.dup2(log_file.fileno(), 1)
os.dup2(log_file.fileno(), 2) os.dup2(log_file.fileno(), 2)
# close all other fds # close all other fds
_close_fds() _close_fds()
# we need to sanitize and str-ize the various bits which # we need to sanitize and str-ize the various bits which
# dbus gives us. # dbus gives us.
gobject.spawn_async([str(s) for s in command], gobject.spawn_async([str(s) for s in command],
envp=['%s=%s' % (k, str(v)) envp=['%s=%s' % (k, str(v))
for k, v in environ.items()], for k, v in environ.items()],
working_directory=str(activity.path), working_directory=str(self._bundle.get_path()),
child_setup=child_setup, child_setup=child_setup,
flags=(gobject.SPAWN_SEARCH_PATH | flags=(gobject.SPAWN_SEARCH_PATH |
gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN)) gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN))
log_file.close() log_file.close()
else: else:
log_file.close() log_file.close()
system_bus = dbus.SystemBus() system_bus = dbus.SystemBus()
factory = system_bus.get_object(_RAINBOW_SERVICE_NAME, factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
_RAINBOW_ACTIVITY_FACTORY_PATH) _RAINBOW_ACTIVITY_FACTORY_PATH)
factory.CreateActivity( factory.CreateActivity(
log_path, log_path,
environ, environ,
command, command,
environ['SUGAR_BUNDLE_PATH'], environ['SUGAR_BUNDLE_PATH'],
environ['SUGAR_BUNDLE_ID'], environ['SUGAR_BUNDLE_ID'],
timeout=30, timeout=30,
reply_handler=self._create_reply_handler, reply_handler=self._create_reply_handler,
error_handler=self._create_error_handler, error_handler=self._create_error_handler,
dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE) dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE)
def _no_reply_handler(self, *args): def _no_reply_handler(self, *args):
pass pass
@ -315,18 +312,18 @@ class ActivityCreationHandler(gobject.GObject):
logging.error("Datastore find failed %s" % err) logging.error("Datastore find failed %s" % err)
self._create_activity() self._create_activity()
def create(service_name, activity_handle=None): def create(bundle, activity_handle=None):
"""Create a new activity from its name.""" """Create a new activity from its name."""
if not activity_handle: if not activity_handle:
activity_handle = ActivityHandle() activity_handle = ActivityHandle()
return ActivityCreationHandler(service_name, activity_handle) return ActivityCreationHandler(bundle, activity_handle)
def create_with_uri(service_name, uri): def create_with_uri(bundle, uri):
"""Create a new activity and pass the uri as handle.""" """Create a new activity and pass the uri as handle."""
activity_handle = ActivityHandle(uri=uri) activity_handle = ActivityHandle(uri=uri)
return ActivityCreationHandler(service_name, activity_handle) return ActivityCreationHandler(bundle, activity_handle)
def create_with_object_id(service_name, object_id): def create_with_object_id(bundle, object_id):
"""Create a new activity and pass the object id as handle.""" """Create a new activity and pass the object id as handle."""
activity_handle = ActivityHandle(object_id=object_id) activity_handle = ActivityHandle(object_id=object_id)
return ActivityCreationHandler(service_name, activity_handle) return ActivityCreationHandler(bundle, activity_handle)

View File

@ -1,189 +0,0 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2007 One Laptop Per Child
#
# 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.
import logging
import dbus
import gobject
_ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
_ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
_ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
def _activity_info_from_dict(info_dict):
if not info_dict:
return None
return ActivityInfo(info_dict['name'], info_dict['icon'],
info_dict['bundle_id'], info_dict['version'],
info_dict['path'], info_dict['show_launcher'],
info_dict['command'], info_dict['favorite'],
info_dict['installation_time'],
info_dict['position_x'], info_dict['position_y'])
class ActivityInfo(object):
def __init__(self, name, icon, bundle_id, version, path, show_launcher,
command, favorite, installation_time, position_x, position_y):
self.name = name
self.icon = icon
self.bundle_id = bundle_id
self.version = version
self.path = path
self.command = command
self.show_launcher = show_launcher
self.favorite = favorite
self.installation_time = installation_time
self.position = (position_x, position_y)
class ActivityRegistry(gobject.GObject):
__gsignals__ = {
'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self):
gobject.GObject.__init__(self)
bus = dbus.SessionBus()
# NOTE: We need to follow_name_owner_changes here
# because we can not connect to a signal unless
# we follow the changes or we start the service
# before we connect. Starting the service here
# causes a major bottleneck during startup
bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME,
_ACTIVITY_REGISTRY_PATH,
follow_name_owner_changes = True)
self._registry = dbus.Interface(bus_object, _ACTIVITY_REGISTRY_IFACE)
self._registry.connect_to_signal('ActivityAdded',
self._activity_added_cb)
self._registry.connect_to_signal('ActivityRemoved',
self._activity_removed_cb)
self._registry.connect_to_signal('ActivityChanged',
self._activity_changed_cb)
# Two caches fo saving some travel across dbus.
self._service_name_to_activity_info = {}
self._mime_type_to_activities = {}
def _convert_info_list(self, info_list):
result = []
for info_dict in info_list:
result.append(_activity_info_from_dict(info_dict))
return result
def get_activities(self):
info_list = self._registry.GetActivities()
return self._convert_info_list(info_list)
def _get_activities_cb(self, reply_handler, info_list):
result = []
for info_dict in info_list:
result.append(_activity_info_from_dict(info_dict))
reply_handler(result)
def _get_activities_error_cb(self, error_handler, e):
if error_handler:
error_handler(e)
else:
logging.error('Error getting activities async: %s' % str(e))
def get_activities_async(self, reply_handler=None, error_handler=None):
if not reply_handler:
logging.error('Function get_activities_async called' \
'without a reply handler. Can not run.')
return
self._registry.GetActivities(
reply_handler=lambda info_list: \
self._get_activities_cb(reply_handler, info_list),
error_handler=lambda e: \
self._get_activities_error_cb(error_handler, e))
def get_activity(self, service_name):
if self._service_name_to_activity_info.has_key(service_name):
return self._service_name_to_activity_info[service_name]
info_dict = self._registry.GetActivity(service_name)
activity_info = _activity_info_from_dict(info_dict)
self._service_name_to_activity_info[service_name] = activity_info
return activity_info
def find_activity(self, name):
info_list = self._registry.FindActivity(name)
return self._convert_info_list(info_list)
def get_activities_for_type(self, mime_type):
if self._mime_type_to_activities.has_key(mime_type):
return self._mime_type_to_activities[mime_type]
info_list = self._registry.GetActivitiesForType(mime_type)
activities = self._convert_info_list(info_list)
self._mime_type_to_activities[mime_type] = activities
return activities
def add_bundle(self, bundle_path):
result = self._registry.AddBundle(bundle_path)
# Need to invalidate here because get_activity could be called after
# add_bundle and before we receive activity-added, causing a race.
self._invalidate_cache()
return result
def _activity_added_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_added_cb: invalidating cache')
self._invalidate_cache()
self.emit('activity-added', _activity_info_from_dict(info_dict))
def _invalidate_cache(self):
self._service_name_to_activity_info.clear()
self._mime_type_to_activities.clear()
def remove_bundle(self, bundle_path):
self._invalidate_cache()
return self._registry.RemoveBundle(bundle_path)
def _activity_removed_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_removed_cb: flushing caches')
self._invalidate_cache()
self.emit('activity-removed', _activity_info_from_dict(info_dict))
def _activity_changed_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_changed_cb: flushing caches')
self._invalidate_cache()
self.emit('activity-changed', _activity_info_from_dict(info_dict))
def set_activity_favorite(self, bundle_id, version, favorite):
self._registry.SetActivityFavorite(bundle_id, version, favorite)
def set_activity_position(self, bundle_id, version, x, y):
self._registry.SetActivityPosition(bundle_id, version, x, y)
_registry = None
def get_registry():
global _registry
if not _registry:
_registry = ActivityRegistry()
return _registry

View File

@ -259,7 +259,6 @@ class ActivityBundle(Bundle):
return command return command
def get_mime_types(self): def get_mime_types(self):
"""Get the MIME types supported by the activity""" """Get the MIME types supported by the activity"""
return self._mime_types return self._mime_types
@ -268,22 +267,7 @@ class ActivityBundle(Bundle):
"""Get whether there should be a visible launcher for the activity""" """Get whether there should be a visible launcher for the activity"""
return self._show_launcher return self._show_launcher
def is_installed(self): def install(self, install_dir, strict_manifest=False):
if activity.get_registry().get_activity(self._bundle_id):
return True
else:
return False
def need_upgrade(self):
"""Returns True if installing this activity bundle is meaningful -
that is, if an identical version of this activity is not
already installed.
Until we have cryptographic hashes to check identity, returns
True always. See http://dev.laptop.org/ticket/7534."""
return True
def unpack(self, install_dir, strict_manifest=False):
self._unzip(install_dir) self._unzip(install_dir)
install_path = os.path.join(install_dir, self._zip_root_dir) install_path = os.path.join(install_dir, self._zip_root_dir)
@ -352,35 +336,7 @@ class ActivityBundle(Bundle):
os.path.basename(info_file))) os.path.basename(info_file)))
return install_path return install_path
def install(self): def uninstall(self, install_path, force=False):
activities_path = env.get_user_activities_path()
act = activity.get_registry().get_activity(self._bundle_id)
if act is not None and act.path.startswith(activities_path):
raise AlreadyInstalledException
install_dir = env.get_user_activities_path()
install_path = self.unpack(install_dir)
if not activity.get_registry().add_bundle(install_path):
raise RegistrationException
def uninstall(self, force=False):
if self._zip_file is None:
install_path = self._path
else:
if not self.is_installed():
raise NotInstalledException
act = activity.get_registry().get_activity(self._bundle_id)
if not force and act.version != self._activity_version:
logging.warning('Not uninstalling, different bundle present')
return
elif not act.path.startswith(env.get_user_activities_path()):
logging.warning('Not uninstalling system activity')
return
install_path = act.path
xdg_data_home = os.getenv('XDG_DATA_HOME', xdg_data_home = os.getenv('XDG_DATA_HOME',
os.path.expanduser('~/.local/share')) os.path.expanduser('~/.local/share'))
@ -404,23 +360,3 @@ class ActivityBundle(Bundle):
os.remove(path) os.remove(path)
self._uninstall(install_path) self._uninstall(install_path)
if not activity.get_registry().remove_bundle(install_path):
raise RegistrationException
def upgrade(self):
act = activity.get_registry().get_activity(self._bundle_id)
if act is None:
logging.warning('Activity not installed')
elif act.path.startswith(env.get_user_activities_path()):
try:
self.uninstall(force=True)
except Exception, e:
logging.warning('Uninstall failed (%s), still trying ' \
'to install newer bundle', e)
else:
logging.warning('Unable to uninstall system activity, ' \
'installing upgraded version in user activities')
self.install()