From 64d33027a89eee8ebc4b07dcba2f46d8e751c2d5 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Thu, 04 Oct 2012 14:13:10 +0000 Subject: Reimplement requests from CLI in sugar-network utility --- diff --git a/sugar-network b/sugar-network index 2dd1194..bcc7015 100755 --- a/sugar-network +++ b/sugar-network @@ -15,67 +15,137 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from sugar_network.toolkit import application, sugar +import os +import re +import json +import shlex +import locale +from os.path import join + +import active_document as ad +from sugar_network import local, sugar, toolkit, IPCClient +from sugar_network.resources.volume import Volume, Request +from sugar_network.local.mountset import Mountset +from sugar_network.local.mounts import HomeMount, RemoteMount from active_toolkit.options import Option -from active_toolkit import enforce +from active_toolkit import printf, application, coroutine, enforce + +anonymous = Option( + 'use anonymous user to access to Sugar Network server; ' + 'only read-only operations are available in this mode', + default=False, type_cast=Option.bool_cast, action='store_true', + name='anonymous') -command = Option( - 'if context implementation supports several commands, ' \ - 'specify one of them to launch', - default='activity', short_option='-C') +_ESCAPE_VALUE_RE = re.compile('([^\[\]\{\}0-9][^\]\[\{\}]+)') class Application(application.Application): + def __init__(self, **kwargs): + application.Application.__init__(self, **kwargs) + application.rundir.value = join(local.local_root.value, 'run') + @application.command( - 'CONTEXT [RESTRICTION] [ARGS]\n' - 'if context is associatad with software, launch one of its ' - 'implementations') - def launch(self): - enforce(self.args, 'Context is not specified') - #context = self.args.pop(0) - raise NotImplementedError() + 'send POST API request') + def POST(self): + self._call('POST', sys.stdin.read()) @application.command( - 'CONTEXT\n' - 'if context is associatad with software, download one of its ' - 'implementations and place it to ~/Activities') - def checkin(self): - enforce(self.args, 'Context is not specified') - #context = self.args.pop(0) - raise NotImplementedError() + 'send PUT API request') + def PUT(self): + self._call('PUT', sys.stdin.read()) @application.command( - 'CONTEXT\n' - 'delete all implementations downloaded by "checkin" command') - def checkout(self): - enforce(self.args, 'Context is not specified') - #context = self.args.pop(0) - raise NotImplementedError() + 'send GET API request') + def GET(self): + self._call('GET', None) + + def _call(self, method, content=None): + request = Request(method=method) + request.content = content + response = ad.Response() + reply = [] + + if self.args and self.args[0].startswith('/'): + path = self.args.pop(0).strip('/').split('/') + request['document'] = path.pop(0) + if path: + request['guid'] = path.pop(0) + if path: + request['prop'] = path.pop(0) + + for arg in self.args: + arg = shlex.split(arg) + if not arg: + continue + arg = arg[0] + if '=' not in arg: + reply.append(arg) + continue + arg, value = arg.split('=', 1) + arg = arg.strip() + enforce(arg, 'No argument name in %r expression', arg) + if arg in request: + if isinstance(request[arg], basestring): + request[arg] = [request[arg]] + request[arg].append(value) + else: + request[arg] = value + + pid_path = None + cp = None + try: + if not self.check_for_instance(): + pid_path = self.new_instance() + if anonymous.value: + sugar.uid = lambda: 'anonymous' + sugar.nickname = lambda: 'anonymous' + sugar.color = lambda: '#000000,#000000' + else: + toolkit.ensure_dsa_pubkey(sugar.profile_path('owner.key')) + volume = Volume(local.db_path()) + cp = Mountset(volume) + cp['~'] = HomeMount(volume) + cp['/'] = RemoteMount(volume) + cp['/'].mounted.wait() + else: + cp = IPCClient() + + def events_cb(events): + for event in events: + pass + + coroutine.spawn(events_cb, cp.subscribe()) + coroutine.dispatch() + result = cp.call(request, response) + + finally: + cp.close() + if pid_path: + os.unlink(pid_path) + + if result is not None: + if reply: + for key in reply: + key = _ESCAPE_VALUE_RE.sub("'\\1'", key) + print eval('result%s' % key) + else: + print json.dumps(result, indent=2) # New defaults application.debug.value = sugar.logger_level() +Option.seek('main', [application.debug, anonymous]) +Option.seek('local', [local.api_url, local.layers]) -Option.seek('sugar-network') -Option.seek('sugar-network', [application.debug]) +locale.setlocale(locale.LC_ALL, '') app = Application( name='sugar-network', description='Sugar Network client utility', epilog='See http://wiki.sugarlabs.org/go/Sugar_Network ' \ 'for details.', - where={ - 'CONTEXT': - 'context GUID or name context should implement', - 'RESTRICTION': - 'particular context implementation in form of:\n' \ - '=|>=|< VERSION', - 'ARGS': - 'arbitrary command-line arguments to pass as-is to ' \ - 'launching context implementation', - }, config_files=[ '/etc/sweets.conf', '~/.config/sweets/config', diff --git a/sugar-network-service b/sugar-network-service index f0267a6..3553c60 100755 --- a/sugar-network-service +++ b/sugar-network-service @@ -17,13 +17,10 @@ import os import sys -import json -import shlex import errno import signal import locale import logging -from contextlib import contextmanager from os.path import join, abspath, exists import active_document as ad @@ -56,7 +53,6 @@ class NullHandler(logging.Handler): class Application(application.Application): _ipc_server = None - _events = {} def __init__(self, **kwargs): application.Application.__init__(self, **kwargs) @@ -105,7 +101,7 @@ class Application(application.Application): printf.info('Index database in %r', local.local_root.value) - volume = Volume(self._db_path) + volume = Volume(local.db_path()) try: volume.populate() activities.populate(volume['context'], local.activity_dirs.value) @@ -113,54 +109,25 @@ class Application(application.Application): volume.close() @application.command( - 'start sneakernet synchronization; if PATH is specified, ' - 'use it as a synchronization directory; otherwise, ' - 'look for mounts (in --mounts-root) that contain ' - 'sugar-network-sync/ subdirectory', - args='[PATH]') - def offline_sync(self): - with self._rendezvous(): - path = None - if self.args: - path = self.args.pop(0) - Client.call('POST', cmd='start_sync', rewind=True, path=path) - self._events['sync_complete'].wait() - - @application.command(hidden=True) - def POST(self): - self._call('POST', sys.stdin.read()) - - @application.command(hidden=True) - def PUT(self): - self._call('PUT', sys.stdin.read()) - - @application.command(hidden=True) - def GET(self): - result = self._call('GET', None) - - if type(result) in (list, set, tuple): - for i in result: - print i - elif type(result) is dict: - print json.dumps(result, indent=2) - else: - print result - - @application.command( 'start service and log to standard output') def debug(self): - self._start() + self.start_service() @application.command( 'start service and log to files', name='start', keep_stdout=True) - def _start(self): + def start_service(self): if self.check_for_instance(): printf.info('%s is already run', self.name) exit(1) jobs = coroutine.Pool() - mountset = self._mountset() + + toolkit.ensure_dsa_pubkey(sugar.profile_path('owner.key')) + volume = Volume(local.db_path(), lazy_open=local.lazy_open.value) + mountset = Mountset(volume) + mountset['~'] = HomeMount(volume) + mountset['/'] = RemoteMount(volume) def delayed_start(event=None): logging.info('Proceed delayed start') @@ -209,86 +176,6 @@ class Application(application.Application): mountset.close() os.unlink(pid_path) - def _mountset(self): - if local.anonymous.value: - sugar.uid = lambda: 'anonymous' - sugar.nickname = lambda: 'anonymous' - sugar.color = lambda: '#000000,#000000' - else: - # In case if it is new Sugar Shell profile - toolkit.ensure_dsa_pubkey(sugar.profile_path('owner.key')) - - volume = Volume(self._db_path, lazy_open=local.lazy_open.value) - mountset = Mountset(volume) - mountset['~'] = HomeMount(volume) - mountset['/'] = RemoteMount(volume) - - return mountset - - @contextmanager - def _rendezvous(self): - - def events_cb(event): - if event['event'] == 'sync_start': - printf.info('Synchronize with %(path)s directory' % event) - elif event['event'] == 'sync_progress': - printf.progress(event['progress']) - elif event['event'] == 'sync_continue': - printf.info('Mounted synchronization disk(s) is full, ' \ - 'mount new one to %s', local.mounts_root.value) - elif event['event'] == 'sync_error': - printf.info('Failed to sync, %(error)s' % event) - elif event['event'] == 'sync_complete': - self._events['sync_complete'].set() - - self._events['sync_complete'] = coroutine.Event() - - pid_path = None - mountset = None - try: - if not self.check_for_instance(): - printf.info('%s is not started, ' \ - 'launch it for this command only', self.name) - pid_path = self.new_instance() - mountset = self._mountset() - dbus_thread.spawn_service(Network) - coroutine.spawn(dbus_thread.start, mountset) - coroutine.dispatch() - mountset.opened.wait() - - Client.connect(events_cb) - yield - - finally: - if mountset is not None: - mountset.close() - os.unlink(pid_path) - - def _call(self, method, content=None): - kwargs = {} - for arg in self.args: - pair = shlex.split(arg) - if not pair: - continue - pair = pair[0] - enforce('=' in pair, 'No "=" assign symbol in %r expression', arg) - arg, value = pair.split('=', 1) - arg = arg.strip() - enforce(arg, 'No argument name in %r expression', arg) - if arg in kwargs: - if isinstance(kwargs[arg], basestring): - kwargs[arg] = [kwargs[arg]] - kwargs[arg].append(value) - else: - kwargs[arg] = value - - with self._rendezvous(): - return Client.call(method, content=content, **kwargs) - - @property - def _db_path(self): - return join(local.local_root.value, 'local') - def __SIGCHLD_cb(self): while True: try: @@ -326,8 +213,8 @@ Option.seek('node', [node.port, node.sync_dirs]) Option.seek('stats', stats) Option.seek('active-document', ad) -application = Application( - name='sugar-network-service', +app = Application( + name='sugar-network', description='Sugar Network service.', epilog='See http://wiki.sugarlabs.org/go/Sugar_Network ' \ 'for details.', @@ -337,4 +224,4 @@ application = Application( '~/.config/sweets/config', sugar.profile_path('sweets.conf'), ]) -application.start() +app.start() diff --git a/sugar_network/local/__init__.py b/sugar_network/local/__init__.py index 5fed679..6a6d736 100644 --- a/sugar_network/local/__init__.py +++ b/sugar_network/local/__init__.py @@ -69,11 +69,6 @@ tmpdir = Option( 'if specified, use this directory for temporary files, such files ' 'might take hunder of megabytes while node synchronizing') -anonymous = Option( - 'use anonymous user to access to Sugar Network server; ' - 'only read-only operations are available in this mode', - default=False, type_cast=Option.bool_cast, action='store_true') - ipc_port = Option( 'port number to listen for incomming connections from IPC clients', default=5001, type_cast=int) @@ -86,7 +81,8 @@ hub_root = Option( layers = Option( 'space separated list of layers to restrict Sugar Network content by', - default=[], type_cast=Option.list_cast, type_repr=Option.list_repr) + default=[], type_cast=Option.list_cast, type_repr=Option.list_repr, + name='layers') def path(*args): @@ -142,3 +138,7 @@ def ensure_path(*args): raise return abspath(result) + + +def db_path(): + return join(local_root.value, 'local') diff --git a/sugar_network/resources/volume.py b/sugar_network/resources/volume.py index 03b1816..376bb2a 100644 --- a/sugar_network/resources/volume.py +++ b/sugar_network/resources/volume.py @@ -188,7 +188,7 @@ class Commands(object): @ad.volume_command(method='GET', cmd='subscribe', mime_type='application/json') - def subscribe(self, request, response, only_commits=False): + def subscribe(self, request=None, response=None, only_commits=False): """Subscribe to Server-Sent Events. :param only_commits: @@ -196,8 +196,9 @@ class Commands(object): that is useful to minimize interactions between server and clients """ - response.content_type = 'text/event-stream' - response['Cache-Control'] = 'no-cache' + if response is not None: + response.content_type = 'text/event-stream' + response['Cache-Control'] = 'no-cache' peer = 'anonymous' if hasattr(request, 'environ'): peer = request.environ.get('HTTP_SUGAR_USER') or peer -- cgit v0.9.1