Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar_network_webui/client.py
blob: 88992dd0b638acebd747099d1463f2996d7eadbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# 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 json
import logging

from sugar_network.toolkit import coroutine


_call = None
_logger = logging.getLogger('sugar_network')


class ServerError(RuntimeError):
    pass


class Client(object):

    anonymous = False
    sugar_uid = None
    api_url = None

    _subscriptions = {}
    _pull = None

    @classmethod
    def call(cls, *args, **kwargs):
        return _call(*args, **kwargs)

    @classmethod
    def connect(cls, callback, **condition):
        cls._subscriptions[callback] = condition
        if cls._pull is None:
            cls._pull = coroutine.spawn(cls._pull_events)

    @classmethod
    def disconnect(cls, callback):
        if callback in cls._subscriptions:
            del cls._subscriptions[callback]

    @classmethod
    def _pull_events(cls):
        for event in Client.call('GET', [], 'subscribe'):
            event = json.loads(event[6:])
            for callback, condition in cls._subscriptions.items():
                for key, value in condition.items():
                    if event.get(key) != value:
                        break
                else:
                    try:
                        callback(event)
                    except Exception:
                        logging.exception('Failed to dispatch %r', event)

    def __init__(self):
        self._resources = {}

    @property
    def inline(self):
        return self.call('GET', [], 'inline') or Client.anonymous

    def launch(self, context, command='activity', object_id=None, uri=None,
            args=None):
        """Launch context implementation.

        Function will call fork at the beginning. In forked process,
        it will try to choose proper implementation to execute and launch it.

        Execution log will be stored in `~/.sugar/PROFILE/logs` directory.

        :param context:
            context GUID to look for implementations
        :param command:
            command that selected implementation should support
        :param object_id:
            optional id to restore Journal object
        :param uri:
            optional uri to open; if implementation supports it
        :param args:
            optional list of arguments to pass to launching implementation

        """
        return self.call('GET', ['context', context], 'launch',
                object_id=object_id, uri=uri, args=args)

    def __getattr__(self, name):
        """Class-like object to access to a resource or call a method.

        :param name:
            resource name started with capital char
        :returns:
            a class-like resource object

        """
        resource = self._resources.get(name)
        if resource is None:
            resource = _Resource(name.lower())
            self._resources[name] = resource
        return resource

    def __enter__(self):
        return self


class _Resource(object):

    def __init__(self, name):
        self.document = name

    def url(self, guid, prop):
        return Client.api_url + '/%s/%s/%s' % (self.document, guid, prop)

    def cursor(self, query=None, order_by=None, reply=None, page_size=18,
            **filters):
        """Query resource objects.

        :param query:
            full text search query string in Xapian format
        :param order_by:
            name of property to sort by; might be prefixed by either `+` or `-`
            to change order's direction
        :param reply:
            list of property names to return for found objects;
            by default, only GUIDs will be returned; for missed properties,
            will be sent additional requests to a server on getting access
            to particular object.
        :param page_size:
            number of items in one cached page, there are might be several
            (at least two) pages
        :param filters:
            a dictionary of properties to filter resulting list

        """
        from cursor import Cursor
        return Cursor(self.document, query, order_by, reply,
                page_size, **filters)

    def delete(self, guid):
        """Delete resource object.

        :param guid:
            resource object's GUID

        """
        return Client.call('DELETE', [self.document, guid])

    def __call__(self, guid=None, reply=None, **kwargs):
        from .objects import Object
        return Object(self.document, reply or [], guid, **kwargs)