Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / protocols / dict.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4
5 """
6 Dict client protocol implementation.
7
8 @author: Pavel Pergamenshchik
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