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-04 14:13:10 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-10-04 14:13:10 (GMT)
commit64d33027a89eee8ebc4b07dcba2f46d8e751c2d5 (patch)
tree327de81dd453d03cc4596b0f737e2759803993f2
parent6fdaeb595cff999d4ef06ab23567940b601fb7ac (diff)
Reimplement requests from CLI in sugar-network utility
-rwxr-xr-xsugar-network146
-rwxr-xr-xsugar-network-service137
-rw-r--r--sugar_network/local/__init__.py12
-rw-r--r--sugar_network/resources/volume.py7
4 files changed, 130 insertions, 172 deletions
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 <http://www.gnu.org/licenses/>.
-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