diff options
author | Aleksey Lim <alsroot@sugarlabs.org> | 2012-05-18 09:56:22 (GMT) |
---|---|---|
committer | Aleksey Lim <alsroot@sugarlabs.org> | 2012-05-18 09:56:22 (GMT) |
commit | f12c2771fb1f5e7636f79d562d98906147e22a41 (patch) | |
tree | e9468410dacfda418649b7886b09774d96cb0306 | |
parent | b3bafc0e95ff80f3903f660457f95c423ad07055 (diff) |
Generate "feed" dynamically for local contexts
-rw-r--r-- | local_document/mounts.py | 39 | ||||
-rw-r--r-- | sugar_network/bus.py | 7 | ||||
-rw-r--r-- | sugar_network/cursor.py | 2 | ||||
-rw-r--r-- | tests/__init__.py | 4 | ||||
-rwxr-xr-x | tests/units/injector.py | 180 | ||||
-rwxr-xr-x | tests/units/mounts.py | 2 | ||||
-rw-r--r-- | zerosugar/config.py | 2 | ||||
-rw-r--r-- | zerosugar/feeds.py | 44 |
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): |