File: fanchat.py

package info (click to toggle)
python-tubes 0.2.1-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 740 kB
  • sloc: python: 3,215; makefile: 149
file content (167 lines) | stat: -rw-r--r-- 4,831 bytes parent folder | download | duplicates (2)
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
163
164
165
166
167

from collections import defaultdict
from json import loads, dumps

from zope.interface.common.mapping import IMapping

from twisted.internet.endpoints import serverFromString
from twisted.internet.defer import Deferred, inlineCallbacks

from tubes.routing import Router, Routed, to
from tubes.itube import IFrame
from tubes.tube import series, tube, receiver
from tubes.framing import bytesToLines, linesToBytes
from tubes.fan import Out, In
from tubes.listening import Listener
from tubes.protocol import flowFountFromEndpoint




@tube
class Participant(object):
    outputType = Routed(IMapping)

    def __init__(self, hub, requestsFount, responsesDrain):
        self._hub = hub
        self._participation = {}
        self._in = In()
        self._router = Router()
        self._participating = {}

        # self._in is both commands from our own client and also messages from
        # other clients.
        requestsFount.flowTo(self._in.newDrain())
        self._in.fount.flowTo(series(self, self._router.drain))

        self.client = self._router.newRoute()
        self.client.flowTo(responsesDrain)

    def received(self, item):
        kwargs = item.copy()
        return getattr(self, "do_" + kwargs.pop("type"))(**kwargs)

    def do_name(self, name):
        self.name = name
        yield to(self.client, dict(named=name))

    def do_joined(self, sender, channel):
        """
        Someone joined a channel I'm participating in.
        """
        yield to(self.client, dict(type="joined"))

    def do_join(self, channel):
        fountFromChannel, drainToChannel = (
            self._hub.channelNamed(channel).participate(self)
        )
        fountFromChannel.flowTo(self._in.newDrain())
        fountToChannel = self._router.newRoute()
        fountToChannel.flowTo(drainToChannel)

        self._participating[channel] = fountToChannel
        yield to(self._participating[channel],
                 dict(type="joined"))

    def do_speak(self, channel, message, id):
        yield to(self._participating[channel],
                 dict(type="spoke", message=message, id=id))

    def do_shout(self, message, id):
        for channel in self._participating.values():
            yield to(channel, dict(type="spoke", message=message, id=id))
        yield to(self.client, dict(type="shouted", id=id))

    def do_tell(self, receiver, message):
        # TODO: implement _establishRapportWith; should be more or less like
        # joining a channel.
        rapport = self._establishRapportWith(receiver)
        yield to(rapport, dict(type="told", message=message))
        # TODO: when does a rapport end?  timeout as soon as the write buffer
        # is empty?

    def do_told(self, sender, message):
        yield to(self.client, message)

    def do_spoke(self, channel, sender, message, id):
        yield to(self.client,
                 dict(type="spoke", channel=channel,
                      sender=sender.name, message=message,
                      id=id))



@receiver(IFrame, IMapping)
def linesToCommands(line):
    yield loads(line)



@receiver(IMapping, IFrame)
def commandsToLines(message):
    yield dumps(message)



class Channel(object):
    def __init__(self, name):
        self._name = name
        self._out = Out()
        self._in = In()
        self._in.fount.flowTo(self._out.drain)

    def participate(self, participant):
        @receiver(IMapping, IMapping)
        def addSender(item):
            yield dict(item, sender=participant, channel=self._name)

        return (self._out.newFount(),
                series(addSender, self._in.newDrain()))



@tube
class OnStop(object):
    def __init__(self, callback):
        self.callback = callback
    def received(self, item):
        yield item
    def stopped(self, reason):
        self.callback()
        return ()



class Hub(object):
    def __init__(self):
        self.participants = []
        self.channels = {}

    def newParticipantFlow(self, flow):
        commandFount = flow.fount.flowTo(
            series(OnStop(lambda: self.participants.remove(participant)),
                   bytesToLines(), linesToCommands)
        )
        commandDrain = series(commandsToLines, linesToBytes(), flow.drain)
        participant = Participant(self, commandFount, commandDrain)
        self.participants.append(participant)

    def channelNamed(self, name):
        if name not in self.channels:
            self.channels[name] = Channel(name)
        return self.channels[name]



@inlineCallbacks
def main(reactor, port="stdio:"):
    endpoint = serverFromString(reactor, port)
    flowFount = yield flowFountFromEndpoint(endpoint)
    flowFount.flowTo(Listener(Hub().newParticipantFlow))
    yield Deferred()



from twisted.internet.task import react
from sys import argv
react(main, argv[1:])