From a0e8fb3d67f9351dd47a7dd41b27174ba3b0b5e0 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Mon, 22 Oct 2012 13:40:14 +0000 Subject: Switch to restful api and launching from service --- diff --git a/plugin/__init__.py b/plugin/__init__.py index 05e1097..5f48f95 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import json +import time import logging import subprocess from os.path import abspath, dirname @@ -24,7 +25,8 @@ import dbus import gobject from sugar.graphics.alert import NotifyAlert, ErrorAlert -from sugar_network import sugar, api_url, server_mode + +from sugar_network import api_url, server_mode, IPCClient from active_toolkit.options import Option from jarabe import plugins @@ -33,7 +35,8 @@ ORDER = 5 TITLE = _('Sugar Network integration') SN_BROWSER_NAME = 'sugar-network-browser' -SN_MASER_URL = 'http://api-devel.network.sugarlabs.org' +#SN_MASER_URL = 'http://api-testing.network.sugarlabs.org' +SN_MASER_URL = 'http://localhost:8800' _ALERT_SEVERITIES = { # severity: (alert_class, alert_message) @@ -88,7 +91,7 @@ def start(): shell = get_model() def delayed_start(): - get_client().Publish({'event': 'delayed-start'}) + get_client().post([], {'event': 'delayed-start'}, cmd='publish') get_browser() def activity_added_cb(model, activity): @@ -99,11 +102,6 @@ def start(): shell.connect('activity-added', activity_added_cb) -def binding(): - srcroot = dirname(abspath(__file__)) - return ['export XDG_DATA_DIRS=%s:$XDG_DATA_DIRS' % srcroot] - - def control_panel_section(): section = gtk.VBox() @@ -152,6 +150,16 @@ def get_client(): global _client if _client is None: + ts = time.time() + subprocess.check_call([ + 'sugar-network-service', 'start', + '--webui', + '--delayed-start', + '--lazy-open', + '--replace', + ]) + _logger.debug('Starting sugar-network-service took %s seconds', + time.time() - ts) _client = _Client() return _client @@ -198,38 +206,34 @@ def _get_bundle_path(self): return env.split('=', 1)[-1] -class _Client(object): +class _Client(gobject.GObject): + + __gsignals__ = { + 'event': ( + gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + [str, gobject.TYPE_PYOBJECT]), + } def __init__(self): - self._event_callbacks = [] - self._client = dbus.Interface( - dbus.SessionBus().get_object( - 'org.sugarlabs.Network', - '/org/sugarlabs/Network'), - 'org.sugarlabs.Network') - self._client.connect_to_signal('Event', self.__Event_cb) - - def connect_to_signal(self, signal, callback): - if signal == 'Event': - self._event_callbacks.append(callback) - else: - return self._client.connect_to_signal(signal, callback) + gobject.GObject.__init__(self) + self._client = IPCClient() + self._subscription = self._client.subscribe() + gobject.io_add_watch(self._subscription.fileno(), + gobject.IO_IN | gobject.IO_HUP, self.__subscription_cb) def __getattr__(self, name): return getattr(self._client, name) - def __Event_cb(self, event): - event = json.loads(event) - - for callback in self._event_callbacks: - try: - callback(event) - except Exception: - _logger.exception('%r callback failed on %r SN event', - callback, event) - - event_type = event.get('event') - if event.get('event') == 'alert': - add_alert(event['severity'], msg=event['message']) - elif event_type == 'sync_complete': - add_alert('info', msg=_('Synchronization completed')) + def __subscription_cb(self, source, cb_condition, pipe, activity_id): + try: + event = self._subscription.pull() + if event is not None: + event_type = event['event'] + if event_type == 'alert': + add_alert(event['severity'], msg=event['message']) + elif event_type == 'sync_complete': + add_alert('info', msg=_('Synchronization completed')) + self.emit('event', event_type, event) + except Exception: + _logger.exception('Cannot dispatch %r event', event) + return True diff --git a/plugin/browser.py b/plugin/browser.py index fc7733a..f9b377d 100644 --- a/plugin/browser.py +++ b/plugin/browser.py @@ -65,7 +65,7 @@ class Browser(Window): self.show_all() self._webkit.connect('notify::load-status', self.__load_status_cb) - get_client().connect_to_signal('Event', self.__Event_cb) + get_client().connect('event', self.__Event_cb) gobject.timeout_add_seconds(_RETRY_TIMEOUT, self._open) @@ -105,8 +105,8 @@ class Browser(Window): wm.set_activity_id(window.window, create_activity_id()) self.disconnect_by_func(self.__realize_cb) - def __Event_cb(self, event): - if event.get('event') in ('mount', 'umount'): + def __Event_cb(self, event, data): + if event in ('mount', 'umount'): self._open() diff --git a/plugin/bundleregistry.py b/plugin/bundleregistry.py index 8707d5f..20860da 100644 --- a/plugin/bundleregistry.py +++ b/plugin/bundleregistry.py @@ -13,17 +13,17 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os import shutil import logging -from os.path import lexists from gettext import gettext as _ import gtk import gobject from pylru import lrucache -from sugar_network import checkins, sugar +from sugar import util + +from sugar_network import clones from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.contentbundle import ContentBundle from sugar.bundle.bundleversion import NormalizedVersion @@ -38,6 +38,14 @@ _stub_icon_path = None _online_cache = lrucache(32) +def stub_icon(): + global _stub_icon_path + if not _stub_icon_path: + theme = gtk.icon_theme_get_default() + _stub_icon_path = theme.lookup_icon('empty', 0, 0).get_filename() + return _stub_icon_path + + class BundleRegistry(gobject.GObject): """Tracks the available activity bundles""" @@ -54,7 +62,7 @@ class BundleRegistry(gobject.GObject): gobject.GObject.__init__(self) self._bundles = {} - get_client().connect_to_signal('Event', self.__Event_cb) + get_client().connect('event', self.__Event_cb) self._populate() def __iter__(self): @@ -78,8 +86,9 @@ class BundleRegistry(gobject.GObject): return None try: - props = get_client().Get(mountpoint, 'context', context_guid, - ['guid', 'keep', 'keep_impl', 'title']) + props = get_client().get(['context', context_guid], + mountpoint=mountpoint, + reply=['guid', 'keep', 'keep_impl', 'title']) bundle = _ContextInfo(mountpoint, props) except Exception: _logger.warning('Cannot fetch activity metadata for %r on %r', @@ -120,7 +129,8 @@ class BundleRegistry(gobject.GObject): bundle = self._bundles.get(bundle_id) if bundle is None: return - get_client().Update('~', 'context', bundle_id, {'keep': keep}) + get_client().put(['context', bundle_id], {'keep': keep}, + mountpoint='~') bundle.props['keep'] = keep self.emit('bundle-changed', bundle) @@ -135,7 +145,8 @@ class BundleRegistry(gobject.GObject): if bundle is None: return position = bundle.props['position'] = [int(x), int(y)] - get_client().Update('~', 'context', bundle_id, {'position': position}) + get_client().put(['context', bundle_id], {'position': position}, + mountpoint='~') self.emit('bundle-changed', bundle) def is_activity_protected(self, bundle_id): @@ -184,25 +195,18 @@ class BundleRegistry(gobject.GObject): self.install(bundle) def _populate(self): - - def reply_handler(entries, total): - for props in entries: - if props['guid'] in self._bundles: - continue - self._add_bundle(props['guid'], props) - - def error_handler(error): - _logger.warning('Cannot call Find(): %s', error) - - get_client().Find('~', 'context', - ['guid', 'keep', 'keep_impl', 'position'], + response = get_client().get(['context'], mountpoint='~', + reply=['guid', 'keep', 'keep_impl', 'position'], # TODO process result by portions less than 1024 - {'keep_impl': 2, 'limit': 1024}, - reply_handler=reply_handler, error_handler=error_handler) + limit=1024, keep_impl=2) + for props in response['result']: + if props['guid'] in self._bundles: + continue + self._add_bundle(props['guid'], props) def _add_bundle(self, bundle_id, props): bundle = None - for path in checkins(bundle_id): + for path in clones(bundle_id): try: bundle = _BundleInfo(ActivityBundle(path)) break @@ -235,8 +239,8 @@ class BundleRegistry(gobject.GObject): def _set_keep_impl(self, bundle_id, keep_impl): if keep_impl: - props = get_client().Get('~', 'context', bundle_id, - ['guid', 'keep', 'keep_impl', 'position']) + props = get_client().get(['context', bundle_id], mountpoint='~', + reply=['guid', 'keep', 'keep_impl', 'position']) bundle = self._bundles.get(bundle_id) if bundle is None: bundle = self._add_bundle(bundle_id, props) @@ -246,22 +250,21 @@ class BundleRegistry(gobject.GObject): else: self._remove_bundle(bundle_id) - def __Event_cb(self, event): - if 'mountpoint' in event and event['mountpoint'] != '~' or \ - event.get('document') != 'context': + def __Event_cb(self, event, data): + if 'mountpoint' in data and data['mountpoint'] != '~' or \ + data.get('document') != 'context': return - event_type = event.get('event') - if event_type in ('create', 'update'): - bundle_id = event['guid'] - props = event['props'] + if event in ('create', 'update'): + bundle_id = data['guid'] + props = data['props'] if props.get('keep_impl') in (0, 2): self._set_keep_impl(bundle_id, props['keep_impl']) if 'keep' in props: self._set_keep(bundle_id, props['keep']) - elif event_type == 'delete': - bundle_id = event['guid'] + elif event == 'delete': + bundle_id = data['guid'] self._set_keep_impl(bundle_id, 0) - elif event_type == 'populate': + elif event == 'populate': self._populate() @@ -270,6 +273,7 @@ class _ContextInfo(object): def __init__(self, mountpoint, props): self.mountpoint = mountpoint self.props = props + self._tmp_icon = None def get_name(self): return self.props['title'] @@ -278,22 +282,24 @@ class _ContextInfo(object): return self.props['guid'] def get_icon(self): - blob = None - try: - blob = get_client().GetBlob(self.mountpoint, 'context', - self.get_bundle_id(), 'artifact_icon') - except Exception, error: - _logger.debug('Fail to get icon for %r: %s', - self.get_bundle_id(), error) - - if not blob or os.stat(blob['path']).st_size == 0: - return _stub_icon() - else: - path = sugar.profile_path('data', self.get_bundle_id() + '.svg') - if lexists(path): - os.unlink(path) - os.symlink(blob['path'], path) - return path + if self._tmp_icon is None: + blob = None + try: + blob = get_client().get( + ['context', self.get_bundle_id(), 'artifact_icon'], + mountpoint=self.mountpoint) + except Exception, error: + _logger.debug('Fail to get icon for %r: %s', + self.get_bundle_id(), error) + if not blob: + self._tmp_icon = stub_icon() + else: + self._tmp_icon = util.TempFilePath() + with file(self._tmp_icon, 'w') as f: + f.write(blob) + + # Cast to `str to avoid spreading `util.TempFilePath` + return str(self._tmp_icon) def get_tags(self): # Doesn't matter with Sweets' features enabled @@ -370,12 +376,3 @@ class _BrowserInfo(object): def get_path(self): return '/' - - -def _stub_icon(): - global _stub_icon_path - - if not _stub_icon_path: - theme = gtk.icon_theme_get_default() - _stub_icon_path = theme.lookup_icon('empty', 0, 0).get_filename() - return _stub_icon_path diff --git a/plugin/dbus-1/services/org.sugarlabs.Network.service b/plugin/dbus-1/services/org.sugarlabs.Network.service deleted file mode 100644 index 2c3f36e..0000000 --- a/plugin/dbus-1/services/org.sugarlabs.Network.service +++ /dev/null @@ -1,3 +0,0 @@ -[D-BUS Service] -Name = org.sugarlabs.Network -Exec = /usr/bin/env sugar-network-service --webui --delayed-start --lazy-open start diff --git a/plugin/launcher.py b/plugin/launcher.py index d55117e..ec403ed 100644 --- a/plugin/launcher.py +++ b/plugin/launcher.py @@ -14,14 +14,10 @@ # along with this program. If not, see . import logging -from gettext import gettext as _ import gtk import wnck import gconf -import gobject - -from sugar_network import launch from sugar import wm from sugar.graphics.xocolor import XoColor @@ -30,7 +26,8 @@ from sugar.activity.activityfactory import create_activity_id from jarabe.view.launcher import LaunchWindow from jarabe.journal import model from jarabe.model import shell -from jarabe.plugins.sn import get_client, get_registry, add_alert, get_browser +from jarabe.plugins.sn import get_client, get_registry, get_browser +from jarabe.plugins.sn.bundleregistry import stub_icon _logger = logging.getLogger('plugins.sn.launcher') @@ -42,7 +39,7 @@ class Launcher(object): self._launches = {} self._screen = wnck.screen_get_default() self._screen.connect('window-opened', self.__window_opened_cb) - get_client().connect_to_signal('Event', self.__Event_cb) + get_client().connect('event', self.__Event_cb) def launch(self, bundle, activity_id=None, object_id=None, uri=None, color=None, invited=None, args=None): @@ -53,62 +50,53 @@ class Launcher(object): activity.get_window().activate(gtk.get_current_event_time()) return - def found_jobject(props): + def found_jobjects(jobjects, total): + if not jobjects: + props = {} + else: + props = jobjects[0] self._launch(bundle, props.get('activity_id') or activity_id, props.get('object_id') or object_id, + props.get('icon-color') or color, uri, - XoColor(props['icon-color']) if 'icon-color' in props - else color, args) - def not_found_jobject(error): + def not_found_jobjects(error): _logger.exception('Failed to launch %r: %s', bundle.get_bundle_id(), error) - # pylint: disable-msg=W0212 if activity_id and not object_id: _logger.debug('Look for jobject for %r activity_id', activity_id) + # pylint: disable-msg=W0212 model._get_datastore().find({'activity_id': activity_id}, ['uid'], - reply_handler=lambda jobjects, total: - found_jobject(jobjects[0] if total else {}), - error_handler=not_found_jobject, byte_arrays=True) - elif object_id and not activity_id: - _logger.debug('Look for %r jobject', object_id) - model._get_datastore().get_properties(object_id, - reply_handler=found_jobject, - error_handler=not_found_jobject, byte_arrays=True) + reply_handler=found_jobjects, + error_handler=not_found_jobjects, + byte_arrays=True) else: - self._launch(bundle, activity_id, object_id, uri, color, args) + self._launch(bundle, activity_id, object_id, color, uri, args) - def _launch(self, bundle, activity_id, object_id, uri, color, extra_args): + def _launch(self, bundle, activity_id, object_id, color, uri, extra_args): if not activity_id: activity_id = create_activity_id() - if color is None: - gc = gconf.client_get_default() - color = XoColor(gc.get_string('/desktop/sugar/user/color')) - - args = ['-b', bundle.get_bundle_id()] - if activity_id: - args.extend(['-a', activity_id]) - if object_id: - args.extend(['-o', object_id]) - if uri: - args.extend(['-u', uri]) - if extra_args: - args.extend(extra_args) _logger.info('Starting %r: activity_id=%r object_id=%r uri=%r', bundle.get_bundle_id(), activity_id, object_id, uri) - pipe = launch(bundle.mountpoint, bundle.get_bundle_id(), 'activity', - args) - gobject.io_add_watch(pipe.fileno(), gobject.IO_IN | gobject.IO_HUP, - self.__progress_cb, pipe, activity_id) + self._start_launcher(bundle.get_bundle_id(), bundle.get_icon(), + activity_id, color) + get_client().get(['context', bundle.get_bundle_id()], cmd='launch', + mountpoint=bundle.mountpoint, args=extra_args, + activity_id=activity_id, object_id=object_id, uri=uri, + color=None if color is None else color.to_string()) - window = LaunchWindow(activity_id, bundle.get_icon(), color) + def _start_launcher(self, bundle_id, icon, activity_id, color): + if color is None: + gc = gconf.client_get_default() + color = XoColor(gc.get_string('/desktop/sugar/user/color')) + window = LaunchWindow(activity_id, icon, color) window.connect('realize', self.__window_realize_cb, - bundle.get_bundle_id(), activity_id) + bundle_id, activity_id) window.show() self._launches[activity_id] = window @@ -141,28 +129,25 @@ class Launcher(object): gtk.gdk.PROP_MODE_REPLACE, 'launcher') wm.set_bundle_id(widget.window, str(bundle_id)) - def __Event_cb(self, event): - if event.get('event') != 'launch': + def __Event_cb(self, event, data): + if event != 'launch': return - bundle = get_registry().get_bundle( - event['context'], event['mountpoint']) - if bundle is None: - add_alert('error', msg=_('Cannot find %s activity to launch') % - event['context']) - else: - self.launch(bundle, object_id=event['object_id'], uri=event['uri'], - args=event['args']) - - def __progress_cb(self, source, cb_condition, pipe, activity_id): - event = pipe.read() - if event is None: - return False - _logger.debug('Execution progress for %r: %r', activity_id, event) - try: - if event['state'] == 'failure': - _logger.warning('Activity %r failed', activity_id) - self._stop_launcher(activity_id) - self._failure_report(event) - except Exception: - _logger.exception('Failed to process event') - return True + + if data['state'] == 'fork': + if data['activity_id'] not in self._launches: + bundle = get_registry().get_bundle( + data['context'], data.get('mountpoint') or '/') + if bundle is None: + icon = stub_icon() + else: + icon = bundle.get_icon() + color = data.get('color') + if color: + color = XoColor(color) + self._start_launcher(data['context'], icon, + data['activity_id'], color) + + elif event['state'] == 'failure': + _logger.warning('Activity %r failed', data['activity_id']) + self._stop_launcher(data['activity_id']) + self._failure_report(event) -- cgit v0.9.1