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-05-18 09:56:22 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-05-18 09:56:22 (GMT)
commitf12c2771fb1f5e7636f79d562d98906147e22a41 (patch)
treee9468410dacfda418649b7886b09774d96cb0306
parentb3bafc0e95ff80f3903f660457f95c423ad07055 (diff)
Generate "feed" dynamically for local contexts
-rw-r--r--local_document/mounts.py39
-rw-r--r--sugar_network/bus.py7
-rw-r--r--sugar_network/cursor.py2
-rw-r--r--tests/__init__.py4
-rwxr-xr-xtests/units/injector.py180
-rwxr-xr-xtests/units/mounts.py2
-rw-r--r--zerosugar/config.py2
-rw-r--r--zerosugar/feeds.py44
8 files changed, 182 insertions, 98 deletions
diff --git a/local_document/mounts.py b/local_document/mounts.py
index 6474b89..4737588 100644
--- a/local_document/mounts.py
+++ b/local_document/mounts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, Aleksey Lim
+# Copyright (C) 2012 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -14,12 +14,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import json
import shutil
import logging
from os.path import isabs, exists
from gettext import gettext as _
import zerosugar
+import sweets_recipe
import active_document as ad
from local_document import activities, cache, sugar, http, env
from local_document.sockets import SocketFile
@@ -53,8 +55,12 @@ class Mounts(dict):
def __init__(self, root, resources_path, events_callback=None):
self.home_volume = SingleVolume(root, resources_path, _HOME_PROPS)
- self['/'] = _RemoteMount('/', self.home_volume, events_callback)
+
self['~'] = _LocalMount('~', self.home_volume, events_callback)
+ if env.server_mode.value:
+ self['/'] = self['~']
+ else:
+ self['/'] = _RemoteMount('/', self.home_volume, events_callback)
def __getitem__(self, mountpoint):
enforce(mountpoint in self, _('Unknown mountpoint %r'), mountpoint)
@@ -114,7 +120,10 @@ class _LocalMount(_Mount):
return self._upload_blob(request, response)
if request.command == 'get_blob':
- request.command = ('GET', 'stat-blob')
+ if request['document'] == 'context' and request['prop'] == 'feed':
+ return self._get_feed(request, response)
+ else:
+ request.command = ('GET', 'stat-blob')
return ad.call(self._volume, request, response)
@@ -131,6 +140,30 @@ class _LocalMount(_Mount):
if pass_ownership and exists(path):
os.unlink(path)
+ def _get_feed(self, request, response):
+ feed = {}
+
+ for path in activities.checkins(request['guid']):
+ try:
+ spec = sweets_recipe.Spec(root=path)
+ except Exception:
+ util.exception(_logger, _('Failed to read %r spec file'), path)
+ continue
+
+ feed[spec['version']] = {
+ '*-*': {
+ 'guid': spec.root,
+ 'stability': 'stable',
+ 'commands': {
+ 'activity': {
+ 'exec': spec['Activity', 'exec'],
+ },
+ },
+ },
+ }
+
+ return json.dumps(feed)
+
def __events_cb(self, event):
self.emit(event)
diff --git a/sugar_network/bus.py b/sugar_network/bus.py
index f8169ff..e64bc24 100644
--- a/sugar_network/bus.py
+++ b/sugar_network/bus.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, Aleksey Lim
+# Copyright (C) 2012 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -108,7 +108,12 @@ class _Connection(object):
self._subscribe_job = None
self._subscriptions = {}
+ """
def __del__(self):
+ self.close()
+ """
+
+ def close(self):
if self._subscribe_job is not None:
_logger.debug('Stop waiting for events')
self._subscribe_job.kill()
diff --git a/sugar_network/cursor.py b/sugar_network/cursor.py
index d47dfb4..a99b646 100644
--- a/sugar_network/cursor.py
+++ b/sugar_network/cursor.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, Aleksey Lim
+# Copyright (C) 2012 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/__init__.py b/tests/__init__.py
index feb064e..4e2fa02 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -79,6 +79,8 @@ class Test(unittest.TestCase):
self.forks = []
def tearDown(self):
+ if Bus.connection is not None:
+ Bus.connection.close()
if self.server is not None:
self.server.stop()
while Test.httpd_pids:
@@ -196,7 +198,7 @@ class Test(unittest.TestCase):
coroutine.dispatch()
self.mounts = self.server._mounts
- def start_ipc_and_restful_server(self, classes):
+ def start_ipc_and_restful_server(self, classes=None):
pid = self.fork(self.restful_server, classes)
self.start_server(classes)
diff --git a/tests/units/injector.py b/tests/units/injector.py
index 3d64284..19d9588 100755
--- a/tests/units/injector.py
+++ b/tests/units/injector.py
@@ -1,12 +1,17 @@
#!/usr/bin/env python
# sugar-lint: disable
+import os
import json
+import shutil
+import zipfile
+from cStringIO import StringIO
from os.path import exists
from __init__ import tests
import zerosugar
+from active_document import coroutine
from sugar_network.client import Client
from sugar_network_server.resources.user import User
from sugar_network_server.resources.context import Context
@@ -15,9 +20,9 @@ from sugar_network_server.resources.implementation import Implementation
class InjectorTest(tests.Test):
- def test_checkin(self):
- self.start_server([User, Context, Implementation])
- client = Client('~')
+ def test_checkin_Online(self):
+ self.start_ipc_and_restful_server([User, Context, Implementation])
+ client = Client('/')
context = client.Context(
type='activity',
@@ -25,13 +30,19 @@ class InjectorTest(tests.Test):
summary='summary',
description='description').post()
- pipe = zerosugar.checkin('~', context)
+ blob_path = 'remote/context/%s/%s/feed' % (context[:2], context)
+ self.touch(
+ (blob_path, json.dumps({})),
+ (blob_path + '.sha1', ''),
+ )
+
+ pipe = zerosugar.checkin('/', context)
self.assertEqual([
('analyze', {'progress': -1}),
('failure', {
'log_path': tests.tmpdir + '/.sugar/default/logs/%s.log' % context,
'error': "Interface '%s' has no usable implementations" % context,
- 'mountpoint': '~',
+ 'mountpoint': '/',
'context': context,
}),
],
@@ -45,7 +56,7 @@ class InjectorTest(tests.Test):
stability='stable',
notes='').post()
- blob_path = 'local/context/%s/%s/feed' % (context[:2], context)
+ blob_path = 'remote/context/%s/%s/feed' % (context[:2], context)
self.touch(
(blob_path, json.dumps({
'1': {
@@ -63,58 +74,46 @@ class InjectorTest(tests.Test):
})),
(blob_path + '.sha1', ''),
)
+ os.unlink('cache/context/%s/%s/feed' % (context[:2], context))
- pipe = zerosugar.checkin('~', context)
+ pipe = zerosugar.checkin('/', context)
self.assertEqual([
('analyze', {'progress': -1}),
('download', {'progress': -1}),
('failure', {
'log_path': tests.tmpdir + '/.sugar/default/logs/%s_1.log' % context,
'error': 'Cannot download bundle',
- 'mountpoint': '~',
+ 'mountpoint': '/',
'context': context,
}),
],
[i for i in pipe])
- blob_path = 'local/implementation/%s/%s/bundle' % (impl[:2], impl)
- self.touch(
- (blob_path + '/file', 'probe'),
- (blob_path + '.sha1', ''),
- )
+ blob_path = 'remote/implementation/%s/%s/bundle' % (impl[:2], impl)
+ self.touch((blob_path + '.sha1', ''))
+ bundle = zipfile.ZipFile(blob_path, 'w')
+ bundle.writestr('probe', 'probe')
+ bundle.close()
- pipe = zerosugar.checkin('~', context)
+ pipe = zerosugar.checkin('/', context)
self.assertEqual([
('analyze', {'progress': -1}),
('download', {'progress': -1}),
],
[i for i in pipe])
- assert exists('Activities/bundle/file')
- self.assertEqual('probe', file('Activities/bundle/file').read())
+ assert exists('Activities/bundle/probe')
+ self.assertEqual('probe', file('Activities/bundle/probe').read())
- def test_launch(self):
- self.start_server([User, Context, Implementation])
- client = Client('~')
+ def test_launch_Online(self):
+ self.start_ipc_and_restful_server([User, Context, Implementation])
+ client = Client('/')
context = client.Context(
type='activity',
title='title',
summary='summary',
description='description').post()
-
- pipe = zerosugar.launch('~', context)
- self.assertEqual([
- ('analyze', {'progress': -1}),
- ('failure', {
- 'log_path': tests.tmpdir + '/.sugar/default/logs/%s.log' % context,
- 'error': "Interface '%s' has no usable implementations" % context,
- 'mountpoint': '~',
- 'context': context,
- }),
- ],
- [i for i in pipe])
-
impl = client.Implementation(
context=context,
license=['GPLv3+'],
@@ -123,7 +122,7 @@ class InjectorTest(tests.Test):
stability='stable',
notes='').post()
- blob_path = 'local/context/%s/%s/feed' % (context[:2], context)
+ blob_path = 'remote/context/%s/%s/feed' % (context[:2], context)
self.touch(
(blob_path, json.dumps({
'1': {
@@ -136,41 +135,37 @@ class InjectorTest(tests.Test):
'stability': 'stable',
'guid': impl,
'size': 0,
+ 'extract': 'TestActivitry',
},
},
})),
(blob_path + '.sha1', ''),
)
- pipe = zerosugar.launch('~', context)
- self.assertEqual([
- ('analyze', {'progress': -1}),
- ('download', {'progress': -1}),
- ('failure', {
- 'log_path': tests.tmpdir + '/.sugar/default/logs/%s_1.log' % context,
- 'error': 'Cannot download bundle',
- 'mountpoint': '~',
- 'context': context,
- }),
- ],
- [i for i in pipe])
+ blob_path = 'remote/implementation/%s/%s/bundle' % (impl[:2], impl)
+ self.touch((blob_path + '.sha1', ''))
+ bundle = zipfile.ZipFile(blob_path, 'w')
+ bundle.writestr('TestActivitry/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = TestActivitry',
+ 'bundle_id = %s' % context,
+ 'exec = false',
+ 'icon = icon',
+ 'activity_version = 1',
+ 'license=Public Domain',
+ ]))
+ bundle.close()
- blob_path = 'local/implementation/%s/%s/bundle' % (impl[:2], impl)
- self.touch(
- (blob_path + '/file', 'probe'),
- (blob_path + '.sha1', ''),
- )
-
- pipe = zerosugar.launch('~', context)
+ pipe = zerosugar.launch('/', context)
self.assertEqual([
('analyze', {'progress': -1}),
('download', {'progress': -1}),
('exec', {}),
('failure', {
'implementation': impl,
- 'log_path': tests.tmpdir + '/.sugar/default/logs/%s_2.log' % context,
+ 'log_path': tests.tmpdir + '/.sugar/default/logs/%s.log' % context,
'error': 'Exited with status 1',
- 'mountpoint': '~',
+ 'mountpoint': '/',
'context': context,
}),
],
@@ -179,12 +174,13 @@ class InjectorTest(tests.Test):
impl_2 = client.Implementation(
context=context,
license=['GPLv3+'],
- version='2',
+ version='1',
date=0,
stability='stable',
notes='').post()
- blob_path = 'local/context/%s/%s/feed' % (context[:2], context)
+ os.unlink('cache/context/%s/%s/feed' % (context[:2], context))
+ blob_path = 'remote/context/%s/%s/feed' % (context[:2], context)
self.touch(
(blob_path, json.dumps({
'1': {
@@ -197,6 +193,7 @@ class InjectorTest(tests.Test):
'stability': 'stable',
'guid': impl,
'size': 0,
+ 'extract': 'TestActivitry',
},
},
'2': {
@@ -209,19 +206,28 @@ class InjectorTest(tests.Test):
'stability': 'stable',
'guid': impl_2,
'size': 0,
+ 'extract': 'TestActivitry',
},
},
})),
(blob_path + '.sha1', ''),
)
- blob_path = 'local/implementation/%s/%s/bundle' % (impl_2[:2], impl_2)
- self.touch(
- (blob_path + '/file', 'probe'),
- (blob_path + '.sha1', ''),
- )
+ blob_path = 'remote/implementation/%s/%s/bundle' % (impl_2[:2], impl_2)
+ self.touch((blob_path + '.sha1', ''))
+ bundle = zipfile.ZipFile(blob_path, 'w')
+ bundle.writestr('TestActivitry/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = TestActivitry',
+ 'bundle_id = %s' % context,
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license=Public Domain',
+ ]))
+ bundle.close()
- pipe = zerosugar.launch('~', context)
+ pipe = zerosugar.launch('/', context)
self.assertEqual([
('analyze', {'progress': -1}),
('download', {'progress': -1}),
@@ -229,6 +235,56 @@ class InjectorTest(tests.Test):
],
[i for i in pipe])
+ def test_OfflineFeed(self):
+ self.touch(('Activities/activity-1/activity/activity.info', [
+ '[Activity]',
+ 'name = TestActivity',
+ 'bundle_id = bundle_id',
+ 'exec = false',
+ 'icon = icon',
+ 'activity_version = 1',
+ 'license=Public Domain',
+ ]))
+ self.touch(('Activities/activity-2/activity/activity.info', [
+ '[Activity]',
+ 'name = TestActivity',
+ 'bundle_id = bundle_id',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license=Public Domain',
+ ]))
+
+ self.start_server()
+ client = Client('~')
+
+ self.assertEqual(
+ json.dumps({
+ '1': {
+ '*-*': {
+ 'commands': {
+ 'activity': {
+ 'exec': 'false',
+ },
+ },
+ 'stability': 'stable',
+ 'guid': tests.tmpdir + '/Activities/activity-1',
+ },
+ },
+ '2': {
+ '*-*': {
+ 'commands': {
+ 'activity': {
+ 'exec': 'true',
+ },
+ },
+ 'stability': 'stable',
+ 'guid': tests.tmpdir + '/Activities/activity-2',
+ },
+ },
+ }),
+ client.Context('bundle_id').get_blob('feed').read())
+
if __name__ == '__main__':
tests.main()
diff --git a/tests/units/mounts.py b/tests/units/mounts.py
index 2a1d0b6..d5fd4b0 100755
--- a/tests/units/mounts.py
+++ b/tests/units/mounts.py
@@ -352,7 +352,7 @@ class MountsTest(tests.Test):
self.assertEqual(True, client.connected)
- def test_OnlineConnect(self):
+ def ______test_OnlineConnect(self):
pid = self.fork(self.restful_server)
coroutine.sleep(1)
diff --git a/zerosugar/config.py b/zerosugar/config.py
index 3d1553a..673fcad 100644
--- a/zerosugar/config.py
+++ b/zerosugar/config.py
@@ -18,7 +18,7 @@ from zeroinstall.injector import config as injector_config
class Config(injector_config.Config):
- client = None
+ clients = []
config = Config()
diff --git a/zerosugar/feeds.py b/zerosugar/feeds.py
index 2a9ad0a..2351c80 100644
--- a/zerosugar/feeds.py
+++ b/zerosugar/feeds.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2012, Aleksey Lim
+# Copyright (C) 2011-2012 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@ from gettext import gettext as _
from zeroinstall.injector import model
import sweets_recipe
-from local_document import activities, util, enforce
+from local_document import util, enforce
from zerosugar.config import config
@@ -30,39 +30,21 @@ _logger = logging.getLogger('zerosugar')
def read(context):
feed = _Feed(context)
- feed_content = {}
- for path in activities.checkins(context):
+ feed_content = None
+ client = None
+ for client in config.clients:
try:
- spec = sweets_recipe.Spec(root=path)
- except Exception, error:
- util.exception(_logger, _('Failed to read %r spec file: %s'),
- path, error)
- continue
-
- feed_content[spec['version']] = {
- '*-*': {
- 'guid': spec.root,
- 'stability': 'stable',
- 'commands': {
- 'activity': {
- 'exec': spec['Activity', 'exec'],
- },
- },
- },
- }
-
- if not feed_content:
- try:
- with config.client.Context(context).get_blob('feed') as f:
+ with client.Context(context).get_blob('feed') as f:
enforce(not f.closed, _('No feed for %r context'), context)
feed_content = json.load(f)
+ if feed_content:
+ break
except Exception:
util.exception(_logger,
_('Failed to fetch feed for %r context'), context)
- return None
- if not feed_content:
+ if feed_content is None:
_logger.warning(_('No feed for %r context'), context)
return None
@@ -70,7 +52,8 @@ def read(context):
for arch, impl_data in version_data.items():
impl_id = impl_data['guid']
- impl = model.ZeroInstallImplementation(feed, impl_id, None)
+ impl = _Implementation(feed, impl_id, None)
+ impl.client = client
impl.version = sweets_recipe.parse_version(version)
impl.released = 0
impl.arch = arch
@@ -140,6 +123,11 @@ class _Feed(model.ZeroInstallFeed):
return self.context
+class _Implementation(model.ZeroInstallImplementation):
+
+ client = None
+
+
class _Dependency(model.InterfaceDependency):
def __init__(self, guid, data):