Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2012-10-22 13:40:14 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-10-22 13:40:14 (GMT)
commita0e8fb3d67f9351dd47a7dd41b27174ba3b0b5e0 (patch)
tree96825c3b671c3f776b4971be84fe833b8e69d790
parent33c4978468f37cf866d8dd520ec78321e20e9abc (diff)
Switch to restful api and launching from service
-rw-r--r--plugin/__init__.py78
-rw-r--r--plugin/browser.py6
-rw-r--r--plugin/bundleregistry.py117
-rw-r--r--plugin/dbus-1/services/org.sugarlabs.Network.service3
-rw-r--r--plugin/launcher.py113
5 files changed, 150 insertions, 167 deletions
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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/licenses/>.
-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 <http://www.gnu.org/licenses/>.
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)