Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar_network/model/routes.py
blob: ff0377f9c3f3fc029c10fab8a7bda3a603e23cd3 (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
# Copyright (C) 2013-2014 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 logging

from sugar_network.db import files
from sugar_network.toolkit.router import route
from sugar_network.toolkit.coroutine import this
from sugar_network.toolkit import coroutine


_logger = logging.getLogger('model.routes')


class FrontRoutes(object):

    def __init__(self):
        self._spooler = coroutine.Spooler()
        this.broadcast = self._broadcast

    @route('GET', mime_type='text/html')
    def hello(self):
        return _HELLO_HTML

    @route('OPTIONS')
    def options(self, request, response):
        if request.environ['HTTP_ORIGIN']:
            response['Access-Control-Allow-Methods'] = \
                    request.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
            response['Access-Control-Allow-Headers'] = \
                    request.environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
        else:
            response['Allow'] = 'GET, HEAD, POST, PUT, DELETE'
        response.content_length = 0

    @route('GET', cmd='subscribe', mime_type='text/event-stream')
    def subscribe(self, request=None, response=None, **condition):
        """Subscribe to Server-Sent Events."""
        if request is not None and not condition:
            condition = request
        if response is not None:
            response.content_type = 'text/event-stream'
            response['Cache-Control'] = 'no-cache'
        return self._pull_events(request, condition)

    @route('GET', ['robots.txt'], mime_type='text/plain')
    def robots(self, request, response):
        return 'User-agent: *\nDisallow: /\n'

    @route('GET', ['favicon.ico'])
    def favicon(self, request, response):
        return files.get('favicon.ico')

    def _broadcast(self, event):
        _logger.debug('Broadcast event: %r', event)
        self._spooler.notify_all(event)

    def _pull_events(self, request, condition):
        _logger.debug('Start %s-nth subscription', self._spooler.waiters + 1)

        # Unblock `GET /?cmd=subscribe` call to let non-greenlet application
        # initiate a subscription and do not stuck in waiting for the 1st event
        yield {'event': 'pong'}

        subscription = None
        if request is not None:
            subscription = request.content_stream
            if subscription is not None:
                coroutine.spawn(self._wait_for_closing, subscription)

        while True:
            event = self._spooler.wait()
            if not isinstance(event, dict):
                if event is subscription:
                    break
                else:
                    continue
            for key, value in condition.items():
                if value.startswith('!'):
                    if event.get(key) == value[1:]:
                        break
                elif event.get(key) != value:
                    break
            else:
                yield event

        _logger.debug('Stop %s-nth subscription', self._spooler.waiters)

    def _wait_for_closing(self, rfile):
        try:
            coroutine.select([rfile.fileno()], [], [])
        finally:
            self._spooler.notify_all(rfile)


_HELLO_HTML = """\
<h2>Welcome to Sugar Network API!</h2>
Visit the <a href="http://wiki.sugarlabs.org/go/Sugar_Network/API">
Sugar Labs Wiki</a> to learn how it can be used.
"""