1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
6
Dict client protocol implementation.
8
@author: Pavel Pergamenshchik
11
from twisted.protocols import basic
12
from twisted.internet import defer, protocol
13
from twisted.python import log
14
from StringIO import StringIO
17
"""Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
20
elif line[0] != '"': # atom
26
if mode == 2: # skip the opening quote
32
io.read(1) # skip the separating space
33
return (res, io.read())
37
return (None, line) # unexpected end of string
40
return (res, io.read())
42
return (None, line) # unexpected end of string
45
return (res, io.read())
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)
54
mustquote = range(33)+[34, 39, 92]
57
if ord(c) in mustquote:
64
if len(line) == 1 and line == '.':
67
if len(line) > 1 and line[0:2] == '..':
72
"""A word definition"""
73
def __init__(self, name, db, dbdesc, text):
77
self.text = text # list of strings not terminated by newline
79
class DictClient(basic.LineReceiver):
80
"""dict (RFC2229) client"""
82
data = None # multiline data
93
def connectionMade(self):
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)
103
def lineReceived(self, line):
105
line = line.decode("UTF-8")
106
except UnicodeError: # garbage received, skip
108
if self.mode == "text": # we are receiving textual data
112
log.msg("DictClient got invalid line from server -- %s" % line)
113
self.protocolError("Invalid line from server")
114
self.transport.LoseConnection()
118
method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
121
def dictCode_default(self, line):
123
log.msg("DictClient got unexpected message from server -- %s" % line)
124
self.protocolError("Unexpected server message")
125
self.transport.loseConnection()
127
def dictCode_221_ready(self, line):
128
"""We are about to get kicked off, do nothing"""
131
def dictCode_220_conn(self, line):
132
"""Greeting message"""
136
def dictCode_530_conn(self):
137
self.protocolError("Access denied")
138
self.transport.loseConnection()
140
def dictCode_420_conn(self):
141
self.protocolError("Server temporarily unavailable")
142
self.transport.loseConnection()
144
def dictCode_421_conn(self):
145
self.protocolError("Server shutting down at operator request")
146
self.transport.loseConnection()
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)
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"
163
command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
164
self.sendLine(command.encode("UTF-8"))
166
def dictCode_550_define(self, line):
167
"""Invalid database"""
169
self.defineFailed("Invalid database")
171
def dictCode_550_match(self, line):
172
"""Invalid database"""
174
self.matchFailed("Invalid database")
176
def dictCode_551_match(self, line):
177
"""Invalid strategy"""
179
self.matchFailed("Invalid strategy")
181
def dictCode_552_define(self, line):
184
self.defineFailed("No match")
186
def dictCode_552_match(self, line):
189
self.matchFailed("No match")
191
def dictCode_150_define(self, line):
192
"""n definitions retrieved"""
195
def dictCode_151_define(self, line):
196
"""Definition text follows"""
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()
205
self.result.append(Definition(word, db, dbdesc, []))
208
def dictCode_152_match(self, line):
209
"""n matches found, text follows"""
214
def dictCode_text_define(self, line):
215
"""A line of definition text received"""
216
res = parseText(line)
218
self.mode = "command"
219
self.result[-1].text = self.data
222
self.data.append(line)
224
def dictCode_text_match(self, line):
225
"""One line of match text received"""
227
p1, t = parseParam(s)
228
p2, t = parseParam(t)
230
res = parseText(line)
232
self.mode = "command"
233
self.result = map(l, self.data)
236
self.data.append(line)
238
def dictCode_250_define(self, line):
245
def dictCode_250_match(self, line):
252
def protocolError(self, reason):
253
"""override to catch unexpected dict protocol conditions"""
256
def dictConnected(self):
257
"""override to be notified when the server is ready to accept commands"""
260
def defineFailed(self, reason):
261
"""override to catch reasonable failure responses to DEFINE"""
264
def defineDone(self, result):
265
"""override to catch succesful DEFINE"""
268
def matchFailed(self, reason):
269
"""override to catch resonable failure responses to MATCH"""
272
def matchDone(self, result):
273
"""override to catch succesful MATCH"""
277
class InvalidResponse(Exception):
281
class DictLookup(DictClient):
282
"""Utility class for a single dict transaction. To be used with DictLookupFactory"""
284
def protocolError(self, reason):
285
if not self.factory.done:
286
self.factory.d.errback(InvalidResponse(reason))
287
self.factory.clientDone()
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)
295
def defineFailed(self, reason):
296
self.factory.d.callback([])
297
self.factory.clientDone()
298
self.transport.loseConnection()
300
def defineDone(self, result):
301
self.factory.d.callback(result)
302
self.factory.clientDone()
303
self.transport.loseConnection()
305
def matchFailed(self, reason):
306
self.factory.d.callback([])
307
self.factory.clientDone()
308
self.transport.loseConnection()
310
def matchDone(self, result):
311
self.factory.d.callback(result)
312
self.factory.clientDone()
313
self.transport.loseConnection()
316
class DictLookupFactory(protocol.ClientFactory):
317
"""Utility factory for a single dict transaction"""
318
protocol = DictLookup
321
def __init__(self, queryType, param, d):
322
self.queryType = queryType
327
def clientDone(self):
328
"""Called by client when done."""
332
def clientConnectionFailed(self, connector, error):
333
self.d.errback(error)
335
def clientConnectionLost(self, connector, error):
337
self.d.errback(error)
339
def buildProtocol(self, addr):
345
def define(host, port, database, word):
346
"""Look up a word using a dict server"""
348
factory = DictLookupFactory("define", (database, word), d)
350
from twisted.internet import reactor
351
reactor.connectTCP(host, port, factory)
354
def match(host, port, database, strategy, word):
355
"""Match a word using a dict server"""
357
factory = DictLookupFactory("match", (database, strategy, word), d)
359
from twisted.internet import reactor
360
reactor.connectTCP(host, port, factory)