1
# Twisted, the Framework of Your Internet
2
# Copyright (C) 2001 Matthew W. Lefkowitz
4
# This library is free software; you can redistribute it and/or
5
# modify it under the terms of version 2.1 of the GNU Lesser General Public
6
# License as published by the Free Software Foundation.
8
# This library is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
# Lesser General Public License for more details.
13
# You should have received a copy of the GNU Lesser General Public
14
# License along with this library; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
Dict client protocol implementation.
20
@author: U{Pavel Pergamenshchik<mailto:pp64@cornell.edu>}
23
from twisted.protocols import basic
24
from twisted.internet import defer, protocol
25
from twisted.python import log
26
from StringIO import StringIO
29
"""Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
32
elif line[0] != '"': # atom
38
if mode == 2: # skip the opening quote
44
io.read(1) # skip the separating space
45
return (res, io.read())
49
return (None, line) # unexpected end of string
52
return (res, io.read())
54
return (None, line) # unexpected end of string
57
return (res, io.read())
61
"""Munch a string into an 'atom'"""
62
# FIXME: proper quoting
63
return filter(lambda x: not (x in map(chr, range(33)+[34, 39, 92])), line)
66
mustquote = range(33)+[34, 39, 92]
69
if ord(c) in mustquote:
76
if len(line) == 1 and line == '.':
79
if len(line) > 1 and line[0:2] == '..':
84
"""A word definition"""
85
def __init__(self, name, db, dbdesc, text):
89
self.text = text # list of strings not terminated by newline
91
class DictClient(basic.LineReceiver):
92
"""dict (RFC2229) client"""
94
data = None # multiline data
105
def connectionMade(self):
107
self.mode = "command"
109
def sendLine(self, line):
110
"""Throw up if the line is longer than 1022 characters"""
111
if len(line) > self.MAX_LENGTH - 2:
112
raise ValueError("DictClient tried to send a too long line")
113
basic.LineReceiver.sendLine(self, line)
115
def lineReceived(self, line):
117
line = line.decode("UTF-8")
118
except UnicodeError: # garbage received, skip
120
if self.mode == "text": # we are receiving textual data
124
log.msg("DictClient got invalid line from server -- %s" % line)
125
self.protocolError("Invalid line from server")
126
self.transport.LoseConnection()
130
method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
133
def dictCode_default(self, line):
135
log.msg("DictClient got unexpected message from server -- %s" % line)
136
self.protocolError("Unexpected server message")
137
self.transport.loseConnection()
139
def dictCode_221_ready(self, line):
140
"""We are about to get kicked off, do nothing"""
143
def dictCode_220_conn(self, line):
144
"""Greeting message"""
148
def dictCode_530_conn(self):
149
self.protocolError("Access denied")
150
self.transport.loseConnection()
152
def dictCode_420_conn(self):
153
self.protocolError("Server temporarily unavailable")
154
self.transport.loseConnection()
156
def dictCode_421_conn(self):
157
self.protocolError("Server shutting down at operator request")
158
self.transport.loseConnection()
160
def sendDefine(self, database, word):
161
"""Send a dict DEFINE command"""
162
assert self.state == "ready", "DictClient.sendDefine called when not in ready state"
163
self.result = None # these two are just in case. In "ready" state, result and data
164
self.data = None # should be None
165
self.state = "define"
166
command = "DEFINE %s %s" % (makeAtom(database.encode("UTF-8")), makeWord(word.encode("UTF-8")))
167
self.sendLine(command)
169
def sendMatch(self, database, strategy, word):
170
"""Send a dict MATCH command"""
171
assert self.state == "ready", "DictClient.sendMatch called when not in ready state"
175
command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
176
self.sendLine(command.encode("UTF-8"))
178
def dictCode_550_define(self, line):
179
"""Invalid database"""
181
self.defineFailed("Invalid database")
183
def dictCode_550_match(self, line):
184
"""Invalid database"""
186
self.matchFailed("Invalid database")
188
def dictCode_551_match(self, line):
189
"""Invalid strategy"""
191
self.matchFailed("Invalid strategy")
193
def dictCode_552_define(self, line):
196
self.defineFailed("No match")
198
def dictCode_552_match(self, line):
201
self.matchFailed("No match")
203
def dictCode_150_define(self, line):
204
"""n definitions retrieved"""
207
def dictCode_151_define(self, line):
208
"""Definition text follows"""
210
(word, line) = parseParam(line)
211
(db, line) = parseParam(line)
212
(dbdesc, line) = parseParam(line)
213
if not (word and db and dbdesc):
214
self.protocolError("Invalid server response")
215
self.transport.loseConnection()
217
self.result.append(Definition(word, db, dbdesc, []))
220
def dictCode_152_match(self, line):
221
"""n matches found, text follows"""
226
def dictCode_text_define(self, line):
227
"""A line of definition text received"""
228
res = parseText(line)
230
self.mode = "command"
231
self.result[-1].text = self.data
234
self.data.append(line)
236
def dictCode_text_match(self, line):
237
"""One line of match text received"""
239
p1, t = parseParam(s)
240
p2, t = parseParam(t)
242
res = parseText(line)
244
self.mode = "command"
245
self.result = map(l, self.data)
248
self.data.append(line)
250
def dictCode_250_define(self, line):
257
def dictCode_250_match(self, line):
264
def protocolError(self, reason):
265
"""override to catch unexpected dict protocol conditions"""
268
def dictConnected(self):
269
"""override to be notified when the server is ready to accept commands"""
272
def defineFailed(self, reason):
273
"""override to catch reasonable failure responses to DEFINE"""
276
def defineDone(self, result):
277
"""override to catch succesful DEFINE"""
280
def matchFailed(self, reason):
281
"""override to catch resonable failure responses to MATCH"""
284
def matchDone(self, result):
285
"""override to catch succesful MATCH"""
289
class InvalidResponse(Exception):
293
class DictLookup(DictClient):
294
"""Utility class for a single dict transaction. To be used with DictLookupFactory"""
296
def protocolError(self, reason):
297
if not self.factory.done:
298
self.factory.d.errback(InvalidResponse(reason))
299
self.factory.clientDone()
301
def dictConnected(self):
302
if self.factory.queryType == "define":
303
apply(self.sendDefine, self.factory.param)
304
elif self.factory.queryType == "match":
305
apply(self.sendMatch, self.factory.param)
307
def defineFailed(self, reason):
308
self.factory.d.callback([])
309
self.factory.clientDone()
310
self.transport.loseConnection()
312
def defineDone(self, result):
313
self.factory.d.callback(result)
314
self.factory.clientDone()
315
self.transport.loseConnection()
317
def matchFailed(self, reason):
318
self.factory.d.callback([])
319
self.factory.clientDone()
320
self.transport.loseConnection()
322
def matchDone(self, result):
323
self.factory.d.callback(result)
324
self.factory.clientDone()
325
self.transport.loseConnection()
328
class DictLookupFactory(protocol.ClientFactory):
329
"""Utility factory for a single dict transaction"""
330
protocol = DictLookup
333
def __init__(self, queryType, param, d):
334
self.queryType = queryType
339
def clientDone(self):
340
"""Called by client when done."""
344
def clientConnectionFailed(self, connector, error):
345
self.d.errback(error)
347
def clientConnectionLost(self, connector, error):
349
self.d.errback(error)
351
def buildProtocol(self, addr):
357
def define(host, port, database, word):
358
"""Look up a word using a dict server"""
360
factory = DictLookupFactory("define", (database, word), d)
362
from twisted.internet import reactor
363
reactor.connectTCP(host, port, factory)
366
def match(host, port, database, strategy, word):
367
"""Match a word using a dict server"""
369
factory = DictLookupFactory("match", (database, strategy, word), d)
371
from twisted.internet import reactor
372
reactor.connectTCP(host, port, factory)