~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/protocols/dict.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
 
# See LICENSE for details.
3
 
 
4
 
 
5
 
"""
6
 
Dict client protocol implementation.
7
 
 
8
 
@author: U{Pavel Pergamenshchik<mailto:pp64@cornell.edu>}
9
 
"""
10
 
 
11
 
from twisted.protocols import basic
12
 
from twisted.internet import defer, protocol
13
 
from twisted.python import log
14
 
from StringIO import StringIO
15
 
 
16
 
def parseParam(line):
17
 
    """Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
18
 
    if line == '':
19
 
        return (None, '')
20
 
    elif line[0] != '"': # atom
21
 
        mode = 1
22
 
    else: # dqstring
23
 
        mode = 2
24
 
    res = ""
25
 
    io = StringIO(line)
26
 
    if mode == 2: # skip the opening quote
27
 
        io.read(1)
28
 
    while 1:
29
 
        a = io.read(1)
30
 
        if a == '"':
31
 
            if mode == 2:
32
 
                io.read(1) # skip the separating space
33
 
                return (res, io.read())
34
 
        elif a == '\\':
35
 
            a = io.read(1)
36
 
            if a == '':
37
 
                return (None, line) # unexpected end of string
38
 
        elif a == '':
39
 
            if mode == 1:
40
 
                return (res, io.read())
41
 
            else:
42
 
                return (None, line) # unexpected end of string
43
 
        elif a == ' ':
44
 
            if mode == 1:
45
 
                return (res, io.read())
46
 
        res += a
47
 
 
48
 
def makeAtom(line):
49
 
    """Munch a string into an 'atom'"""
50
 
    # FIXME: proper quoting
51
 
    return filter(lambda x: not (x in map(chr, range(33)+[34, 39, 92])), line)
52
 
 
53
 
def makeWord(s):
54
 
    mustquote = range(33)+[34, 39, 92]
55
 
    result = []
56
 
    for c in s:
57
 
        if ord(c) in mustquote:
58
 
            result.append("\\")
59
 
        result.append(c)
60
 
    s = "".join(result)
61
 
    return s
62
 
 
63
 
def parseText(line):
64
 
    if len(line) == 1 and line == '.':
65
 
        return None
66
 
    else:
67
 
        if len(line) > 1 and line[0:2] == '..':
68
 
            line = line[1:]
69
 
        return line
70
 
 
71
 
class Definition:
72
 
    """A word definition"""
73
 
    def __init__(self, name, db, dbdesc, text):
74
 
        self.name = name
75
 
        self.db = db
76
 
        self.dbdesc = dbdesc
77
 
        self.text = text # list of strings not terminated by newline
78
 
 
79
 
class DictClient(basic.LineReceiver):
80
 
    """dict (RFC2229) client"""
81
 
 
82
 
    data = None # multiline data
83
 
    MAX_LENGTH = 1024
84
 
    state = None
85
 
    mode = None
86
 
    result = None
87
 
    factory = None
88
 
 
89
 
    def __init__(self):
90
 
        self.data = None
91
 
        self.result = None
92
 
 
93
 
    def connectionMade(self):
94
 
        self.state = "conn"
95
 
        self.mode = "command"
96
 
 
97
 
    def sendLine(self, line):
98
 
        """Throw up if the line is longer than 1022 characters"""
99
 
        if len(line) > self.MAX_LENGTH - 2:
100
 
            raise ValueError("DictClient tried to send a too long line")
101
 
        basic.LineReceiver.sendLine(self, line)
102
 
 
103
 
    def lineReceived(self, line):
104
 
        try:
105
 
            line = line.decode("UTF-8")
106
 
        except UnicodeError: # garbage received, skip
107
 
            return
108
 
        if self.mode == "text": # we are receiving textual data
109
 
            code = "text"
110
 
        else:
111
 
            if len(line) < 4:
112
 
                log.msg("DictClient got invalid line from server -- %s" % line)
113
 
                self.protocolError("Invalid line from server")
114
 
                self.transport.LoseConnection()
115
 
                return
116
 
            code = int(line[:3])
117
 
            line = line[4:]
118
 
        method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
119
 
        method(line)
120
 
 
121
 
    def dictCode_default(self, line):
122
 
        """Unkown message"""
123
 
        log.msg("DictClient got unexpected message from server -- %s" % line)
124
 
        self.protocolError("Unexpected server message")
125
 
        self.transport.loseConnection()
126
 
 
127
 
    def dictCode_221_ready(self, line):
128
 
        """We are about to get kicked off, do nothing"""
129
 
        pass
130
 
 
131
 
    def dictCode_220_conn(self, line):
132
 
        """Greeting message"""
133
 
        self.state = "ready"
134
 
        self.dictConnected()
135
 
 
136
 
    def dictCode_530_conn(self):
137
 
        self.protocolError("Access denied")
138
 
        self.transport.loseConnection()
139
 
 
140
 
    def dictCode_420_conn(self):
141
 
        self.protocolError("Server temporarily unavailable")
142
 
        self.transport.loseConnection()
143
 
 
144
 
    def dictCode_421_conn(self):
145
 
        self.protocolError("Server shutting down at operator request")
146
 
        self.transport.loseConnection()
147
 
 
148
 
    def sendDefine(self, database, word):
149
 
        """Send a dict DEFINE command"""
150
 
        assert self.state == "ready", "DictClient.sendDefine called when not in ready state"
151
 
        self.result = None  # these two are just in case. In "ready" state, result and data
152
 
        self.data = None    # should be None
153
 
        self.state = "define"
154
 
        command = "DEFINE %s %s" % (makeAtom(database.encode("UTF-8")), makeWord(word.encode("UTF-8")))
155
 
        self.sendLine(command)
156
 
 
157
 
    def sendMatch(self, database, strategy, word):
158
 
        """Send a dict MATCH command"""
159
 
        assert self.state == "ready", "DictClient.sendMatch called when not in ready state"
160
 
        self.result = None
161
 
        self.data = None
162
 
        self.state = "match"
163
 
        command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
164
 
        self.sendLine(command.encode("UTF-8"))
165
 
 
166
 
    def dictCode_550_define(self, line):
167
 
        """Invalid database"""
168
 
        self.mode = "ready"
169
 
        self.defineFailed("Invalid database")
170
 
 
171
 
    def dictCode_550_match(self, line):
172
 
        """Invalid database"""
173
 
        self.mode = "ready"
174
 
        self.matchFailed("Invalid database")
175
 
 
176
 
    def dictCode_551_match(self, line):
177
 
        """Invalid strategy"""
178
 
        self.mode = "ready"
179
 
        self.matchFailed("Invalid strategy")
180
 
 
181
 
    def dictCode_552_define(self, line):
182
 
        """No match"""
183
 
        self.mode = "ready"
184
 
        self.defineFailed("No match")
185
 
 
186
 
    def dictCode_552_match(self, line):
187
 
        """No match"""
188
 
        self.mode = "ready"
189
 
        self.matchFailed("No match")
190
 
 
191
 
    def dictCode_150_define(self, line):
192
 
        """n definitions retrieved"""
193
 
        self.result = []
194
 
 
195
 
    def dictCode_151_define(self, line):
196
 
        """Definition text follows"""
197
 
        self.mode = "text"
198
 
        (word, line) = parseParam(line)
199
 
        (db, line) = parseParam(line)
200
 
        (dbdesc, line) = parseParam(line)
201
 
        if not (word and db and dbdesc):
202
 
            self.protocolError("Invalid server response")
203
 
            self.transport.loseConnection()
204
 
        else:
205
 
            self.result.append(Definition(word, db, dbdesc, []))
206
 
            self.data = []
207
 
 
208
 
    def dictCode_152_match(self, line):
209
 
        """n matches found, text follows"""
210
 
        self.mode = "text"
211
 
        self.result = []
212
 
        self.data = []
213
 
 
214
 
    def dictCode_text_define(self, line):
215
 
        """A line of definition text received"""
216
 
        res = parseText(line)
217
 
        if res == None:
218
 
            self.mode = "command"
219
 
            self.result[-1].text = self.data
220
 
            self.data = None
221
 
        else:
222
 
            self.data.append(line)
223
 
 
224
 
    def dictCode_text_match(self, line):
225
 
        """One line of match text received"""
226
 
        def l(s):
227
 
            p1, t = parseParam(s)
228
 
            p2, t = parseParam(t)
229
 
            return (p1, p2)
230
 
        res = parseText(line)
231
 
        if res == None:
232
 
            self.mode = "command"
233
 
            self.result = map(l, self.data)
234
 
            self.data = None
235
 
        else:
236
 
            self.data.append(line)
237
 
 
238
 
    def dictCode_250_define(self, line):
239
 
        """ok"""
240
 
        t = self.result
241
 
        self.result = None
242
 
        self.state = "ready"
243
 
        self.defineDone(t)
244
 
 
245
 
    def dictCode_250_match(self, line):
246
 
        """ok"""
247
 
        t = self.result
248
 
        self.result = None
249
 
        self.state = "ready"
250
 
        self.matchDone(t)
251
 
    
252
 
    def protocolError(self, reason):
253
 
        """override to catch unexpected dict protocol conditions"""
254
 
        pass
255
 
 
256
 
    def dictConnected(self):
257
 
        """override to be notified when the server is ready to accept commands"""
258
 
        pass
259
 
 
260
 
    def defineFailed(self, reason):
261
 
        """override to catch reasonable failure responses to DEFINE"""
262
 
        pass
263
 
 
264
 
    def defineDone(self, result):
265
 
        """override to catch succesful DEFINE"""
266
 
        pass
267
 
    
268
 
    def matchFailed(self, reason):
269
 
        """override to catch resonable failure responses to MATCH"""
270
 
        pass
271
 
 
272
 
    def matchDone(self, result):
273
 
        """override to catch succesful MATCH"""
274
 
        pass
275
 
 
276
 
 
277
 
class InvalidResponse(Exception):
278
 
    pass
279
 
 
280
 
 
281
 
class DictLookup(DictClient):
282
 
    """Utility class for a single dict transaction. To be used with DictLookupFactory"""
283
 
 
284
 
    def protocolError(self, reason):
285
 
        if not self.factory.done:
286
 
            self.factory.d.errback(InvalidResponse(reason))
287
 
            self.factory.clientDone()
288
 
 
289
 
    def dictConnected(self):
290
 
        if self.factory.queryType == "define":
291
 
            apply(self.sendDefine, self.factory.param)
292
 
        elif self.factory.queryType == "match":
293
 
            apply(self.sendMatch, self.factory.param)
294
 
 
295
 
    def defineFailed(self, reason):
296
 
        self.factory.d.callback([])
297
 
        self.factory.clientDone()
298
 
        self.transport.loseConnection()
299
 
 
300
 
    def defineDone(self, result):
301
 
        self.factory.d.callback(result)
302
 
        self.factory.clientDone()
303
 
        self.transport.loseConnection()
304
 
 
305
 
    def matchFailed(self, reason):
306
 
        self.factory.d.callback([])
307
 
        self.factory.clientDone()
308
 
        self.transport.loseConnection()
309
 
 
310
 
    def matchDone(self, result):
311
 
        self.factory.d.callback(result)
312
 
        self.factory.clientDone()
313
 
        self.transport.loseConnection()
314
 
 
315
 
 
316
 
class DictLookupFactory(protocol.ClientFactory):
317
 
    """Utility factory for a single dict transaction"""
318
 
    protocol = DictLookup
319
 
    done = None
320
 
 
321
 
    def __init__(self, queryType, param, d):
322
 
        self.queryType = queryType
323
 
        self.param = param
324
 
        self.d = d
325
 
        self.done = 0
326
 
 
327
 
    def clientDone(self):
328
 
        """Called by client when done."""
329
 
        self.done = 1
330
 
        del self.d
331
 
    
332
 
    def clientConnectionFailed(self, connector, error):
333
 
        self.d.errback(error)
334
 
 
335
 
    def clientConnectionLost(self, connector, error):
336
 
        if not self.done:
337
 
            self.d.errback(error)
338
 
 
339
 
    def buildProtocol(self, addr):
340
 
        p = self.protocol()
341
 
        p.factory = self
342
 
        return p
343
 
 
344
 
 
345
 
def define(host, port, database, word):
346
 
    """Look up a word using a dict server"""
347
 
    d = defer.Deferred()
348
 
    factory = DictLookupFactory("define", (database, word), d)
349
 
    
350
 
    from twisted.internet import reactor
351
 
    reactor.connectTCP(host, port, factory)
352
 
    return d
353
 
 
354
 
def match(host, port, database, strategy, word):
355
 
    """Match a word using a dict server"""
356
 
    d = defer.Deferred()
357
 
    factory = DictLookupFactory("match", (database, strategy, word), d)
358
 
 
359
 
    from twisted.internet import reactor
360
 
    reactor.connectTCP(host, port, factory)
361
 
    return d
362