Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: third_party/twisted_8_1/twisted/news/nntp.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.news.test.test_nntp -*-
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 NNTP protocol support.
8
9 Maintainer: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
10
11 The following protocol commands are currently understood::
12
13 LIST LISTGROUP XOVER XHDR
14 POST GROUP ARTICLE STAT HEAD
15 BODY NEXT MODE STREAM MODE READER SLAVE
16 LAST QUIT HELP IHAVE XPATH
17 XINDEX XROVER TAKETHIS CHECK
18
19 The following protocol commands require implementation::
20
21 NEWNEWS
22 XGTITLE XPAT
23 XTHREAD AUTHINFO NEWGROUPS
24
25
26 Other desired features:
27
28 - A real backend
29 - More robust client input handling
30 - A control protocol
31 """
32
33 import time
34 import types
35
36 try:
37 import cStringIO as StringIO
38 except:
39 import StringIO
40
41 from twisted.protocols import basic
42 from twisted.python import log
43
44 def parseRange(text):
45 articles = text.split('-')
46 if len(articles) == 1:
47 try:
48 a = int(articles[0])
49 return a, a
50 except ValueError, e:
51 return None, None
52 elif len(articles) == 2:
53 try:
54 if len(articles[0]):
55 l = int(articles[0])
56 else:
57 l = None
58 if len(articles[1]):
59 h = int(articles[1])
60 else:
61 h = None
62 except ValueError, e:
63 return None, None
64 return l, h
65
66
67 def extractCode(line):
68 line = line.split(' ', 1)
69 if len(line) != 2:
70 return None
71 try:
72 return int(line[0]), line[1]
73 except ValueError:
74 return None
75
76
77 class NNTPError(Exception):
78 def __init__(self, string):
79 self.string = string
80
81 def __str__(self):
82 return 'NNTPError: %s' % self.string
83
84
85 class NNTPClient(basic.LineReceiver):
86 MAX_COMMAND_LENGTH = 510
87
88 def __init__(self):
89 self.currentGroup = None
90
91 self._state = []
92 self._error = []
93 self._inputBuffers = []
94 self._responseCodes = []
95 self._responseHandlers = []
96
97 self._postText = []
98
99 self._newState(self._statePassive, None, self._headerInitial)
100
101
102 def gotAllGroups(self, groups):
103 "Override for notification when fetchGroups() action is completed"
104
105
106 def getAllGroupsFailed(self, error):
107 "Override for notification when fetchGroups() action fails"
108
109
110 def gotOverview(self, overview):
111 "Override for notification when fetchOverview() action is completed"
112
113
114 def getOverviewFailed(self, error):
115 "Override for notification when fetchOverview() action fails"
116
117
118 def gotSubscriptions(self, subscriptions):
119 "Override for notification when fetchSubscriptions() action is completed "
120
121
122 def getSubscriptionsFailed(self, error):
123 "Override for notification when fetchSubscriptions() action fails"
124
125
126 def gotGroup(self, group):
127 "Override for notification when fetchGroup() action is completed"
128
129
130 def getGroupFailed(self, error):
131 "Override for notification when fetchGroup() action fails"
132
133
134 def gotArticle(self, article):
135 "Override for notification when fetchArticle() action is completed"
136
137
138 def getArticleFailed(self, error):
139 "Override for notification when fetchArticle() action fails"
140
141
142 def gotHead(self, head):
143 "Override for notification when fetchHead() action is completed"
144
145
146 def getHeadFailed(self, error):
147 "Override for notification when fetchHead() action fails"
148
149
150 def gotBody(self, info):
151 "Override for notification when fetchBody() action is completed"
152
153
154 def getBodyFailed(self, body):
155 "Override for notification when fetchBody() action fails"
156
157
158 def postedOk(self):
159 "Override for notification when postArticle() action is successful"
160
161
162 def postFailed(self, error):
163 "Override for notification when postArticle() action fails"
164
165
166 def gotXHeader(self, headers):
167 "Override for notification when getXHeader() action is successful"
168
169
170 def getXHeaderFailed(self, error):
171 "Override for notification when getXHeader() action fails"
172
173
174 def gotNewNews(self, news):
175 "Override for notification when getNewNews() action is successful"
176
177
178 def getNewNewsFailed(self, error):
179 "Override for notification when getNewNews() action fails"
180
181
182 def gotNewGroups(self, groups):
183 "Override for notification when getNewGroups() action is successful"
184
185
186 def getNewGroupsFailed(self, error):
187 "Override for notification when getNewGroups() action fails"
188
189
190 def setStreamSuccess(self):
191 "Override for notification when setStream() action is successful"
192
193
194 def setStreamFailed(self, error):
195 "Override for notification when setStream() action fails"
196
197
198 def fetchGroups(self):
199 """
200 Request a list of all news groups from the server. gotAllGroups()
201 is called on success, getGroupsFailed() on failure
202 """
203 self.sendLine('LIST')
204 self._newState(self._stateList, self.getAllGroupsFailed)
205
206
207 def fetchOverview(self):
208 """
209 Request the overview format from the server. gotOverview() is called
210 on success, getOverviewFailed() on failure
211 """
212 self.sendLine('LIST OVERVIEW.FMT')
213 self._newState(self._stateOverview, self.getOverviewFailed)
214
215
216 def fetchSubscriptions(self):
217 """
218 Request a list of the groups it is recommended a new user subscribe to.
219 gotSubscriptions() is called on success, getSubscriptionsFailed() on
220 failure
221 """
222 self.sendLine('LIST SUBSCRIPTIONS')
223 self._newState(self._stateSubscriptions, self.getSubscriptionsFailed)
224
225
226 def fetchGroup(self, group):
227 """
228 Get group information for the specified group from the server. gotGroup ()
229 is called on success, getGroupFailed() on failure.
230 """
231 self.sendLine('GROUP %s' % (group,))
232 self._newState(None, self.getGroupFailed, self._headerGroup)
233
234
235 def fetchHead(self, index = ''):
236 """
237 Get the header for the specified article (or the currently selected
238 article if index is '') from the server. gotHead() is called on
239 success, getHeadFailed() on failure
240 """
241 self.sendLine('HEAD %s' % (index,))
242 self._newState(self._stateHead, self.getHeadFailed)
243
244
245 def fetchBody(self, index = ''):
246 """
247 Get the body for the specified article (or the currently selected
248 article if index is '') from the server. gotBody() is called on
249 success, getBodyFailed() on failure
250 """
251 self.sendLine('BODY %s' % (index,))
252 self._newState(self._stateBody, self.getBodyFailed)
253
254
255 def fetchArticle(self, index = ''):
256 """
257 Get the complete article with the specified index (or the currently
258 selected article if index is '') or Message-ID from the server.
259 gotArticle() is called on success, getArticleFailed() on failure.
260 """
261 self.sendLine('ARTICLE %s' % (index,))
262 self._newState(self._stateArticle, self.getArticleFailed)
263
264
265 def postArticle(self, text):
266 """
267 Attempt to post an article with the specified text to the server. 'text '
268 must consist of both head and body data, as specified by RFC 850. If th e
269 article is posted successfully, postedOk() is called, otherwise postFail ed()
270 is called.
271 """
272 self.sendLine('POST')
273 self._newState(None, self.postFailed, self._headerPost)
274 self._postText.append(text)
275
276
277 def fetchNewNews(self, groups, date, distributions = ''):
278 """
279 Get the Message-IDs for all new news posted to any of the given
280 groups since the specified date - in seconds since the epoch, GMT -
281 optionally restricted to the given distributions. gotNewNews() is
282 called on success, getNewNewsFailed() on failure.
283
284 One invocation of this function may result in multiple invocations
285 of gotNewNews()/getNewNewsFailed().
286 """
287 date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split( )
288 line = 'NEWNEWS %%s %s %s %s' % (date, timeStr, distributions)
289 groupPart = ''
290 while len(groups) and len(line) + len(groupPart) + len(groups[-1]) + 1 < NNTPClient.MAX_COMMAND_LENGTH:
291 group = groups.pop()
292 groupPart = groupPart + ',' + group
293
294 self.sendLine(line % (groupPart,))
295 self._newState(self._stateNewNews, self.getNewNewsFailed)
296
297 if len(groups):
298 self.fetchNewNews(groups, date, distributions)
299
300
301 def fetchNewGroups(self, date, distributions):
302 """
303 Get the names of all new groups created/added to the server since
304 the specified date - in seconds since the ecpoh, GMT - optionally
305 restricted to the given distributions. gotNewGroups() is called
306 on success, getNewGroupsFailed() on failure.
307 """
308 date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split( )
309 self.sendLine('NEWGROUPS %s %s %s' % (date, timeStr, distributions))
310 self._newState(self._stateNewGroups, self.getNewGroupsFailed)
311
312
313 def fetchXHeader(self, header, low = None, high = None, id = None):
314 """
315 Request a specific header from the server for an article or range
316 of articles. If 'id' is not None, a header for only the article
317 with that Message-ID will be requested. If both low and high are
318 None, a header for the currently selected article will be selected;
319 If both low and high are zero-length strings, headers for all articles
320 in the currently selected group will be requested; Otherwise, high
321 and low will be used as bounds - if one is None the first or last
322 article index will be substituted, as appropriate.
323 """
324 if id is not None:
325 r = header + ' <%s>' % (id,)
326 elif low is high is None:
327 r = header
328 elif high is None:
329 r = header + ' %d-' % (low,)
330 elif low is None:
331 r = header + ' -%d' % (high,)
332 else:
333 r = header + ' %d-%d' % (low, high)
334 self.sendLine('XHDR ' + r)
335 self._newState(self._stateXHDR, self.getXHeaderFailed)
336
337
338 def setStream(self):
339 """
340 Set the mode to STREAM, suspending the normal "lock-step" mode of
341 communications. setStreamSuccess() is called on success,
342 setStreamFailed() on failure.
343 """
344 self.sendLine('MODE STREAM')
345 self._newState(None, self.setStreamFailed, self._headerMode)
346
347
348 def quit(self):
349 self.sendLine('QUIT')
350 self.transport.loseConnection()
351
352
353 def _newState(self, method, error, responseHandler = None):
354 self._inputBuffers.append([])
355 self._responseCodes.append(None)
356 self._state.append(method)
357 self._error.append(error)
358 self._responseHandlers.append(responseHandler)
359
360
361 def _endState(self):
362 buf = self._inputBuffers[0]
363 del self._responseCodes[0]
364 del self._inputBuffers[0]
365 del self._state[0]
366 del self._error[0]
367 del self._responseHandlers[0]
368 return buf
369
370
371 def _newLine(self, line, check = 1):
372 if check and line and line[0] == '.':
373 line = line[1:]
374 self._inputBuffers[0].append(line)
375
376
377 def _setResponseCode(self, code):
378 self._responseCodes[0] = code
379
380
381 def _getResponseCode(self):
382 return self._responseCodes[0]
383
384
385 def lineReceived(self, line):
386 if not len(self._state):
387 self._statePassive(line)
388 elif self._getResponseCode() is None:
389 code = extractCode(line)
390 if code is None or not (200 <= code[0] < 400): # An error!
391 self._error[0](line)
392 self._endState()
393 else:
394 self._setResponseCode(code)
395 if self._responseHandlers[0]:
396 self._responseHandlers[0](code)
397 else:
398 self._state[0](line)
399
400
401 def _statePassive(self, line):
402 log.msg('Server said: %s' % line)
403
404
405 def _passiveError(self, error):
406 log.err('Passive Error: %s' % (error,))
407
408
409 def _headerInitial(self, (code, message)):
410 if code == 200:
411 self.canPost = 1
412 else:
413 self.canPost = 0
414 self._endState()
415
416
417 def _stateList(self, line):
418 if line != '.':
419 data = filter(None, line.strip().split())
420 self._newLine((data[0], int(data[1]), int(data[2]), data[3]), 0)
421 else:
422 self.gotAllGroups(self._endState())
423
424
425 def _stateOverview(self, line):
426 if line != '.':
427 self._newLine(filter(None, line.strip().split()), 0)
428 else:
429 self.gotOverview(self._endState())
430
431
432 def _stateSubscriptions(self, line):
433 if line != '.':
434 self._newLine(line.strip(), 0)
435 else:
436 self.gotSubscriptions(self._endState())
437
438
439 def _headerGroup(self, (code, line)):
440 self.gotGroup(tuple(line.split()))
441 self._endState()
442
443
444 def _stateArticle(self, line):
445 if line != '.':
446 if line.startswith('.'):
447 line = line[1:]
448 self._newLine(line, 0)
449 else:
450 self.gotArticle('\n'.join(self._endState())+'\n')
451
452
453 def _stateHead(self, line):
454 if line != '.':
455 self._newLine(line, 0)
456 else:
457 self.gotHead('\n'.join(self._endState()))
458
459
460 def _stateBody(self, line):
461 if line != '.':
462 if line.startswith('.'):
463 line = line[1:]
464 self._newLine(line, 0)
465 else:
466 self.gotBody('\n'.join(self._endState())+'\n')
467
468
469 def _headerPost(self, (code, message)):
470 if code == 340:
471 self.transport.write(self._postText[0].replace('\n', '\r\n').replace ('\r\n.', '\r\n..'))
472 if self._postText[0][-1:] != '\n':
473 self.sendLine('')
474 self.sendLine('.')
475 del self._postText[0]
476 self._newState(None, self.postFailed, self._headerPosted)
477 else:
478 self.postFailed('%d %s' % (code, message))
479 self._endState()
480
481
482 def _headerPosted(self, (code, message)):
483 if code == 240:
484 self.postedOk()
485 else:
486 self.postFailed('%d %s' % (code, message))
487 self._endState()
488
489
490 def _stateXHDR(self, line):
491 if line != '.':
492 self._newLine(line.split(), 0)
493 else:
494 self._gotXHeader(self._endState())
495
496
497 def _stateNewNews(self, line):
498 if line != '.':
499 self._newLine(line, 0)
500 else:
501 self.gotNewNews(self._endState())
502
503
504 def _stateNewGroups(self, line):
505 if line != '.':
506 self._newLine(line, 0)
507 else:
508 self.gotNewGroups(self._endState())
509
510
511 def _headerMode(self, (code, message)):
512 if code == 203:
513 self.setStreamSuccess()
514 else:
515 self.setStreamFailed((code, message))
516 self._endState()
517
518
519 class NNTPServer(basic.LineReceiver):
520 COMMANDS = [
521 'LIST', 'GROUP', 'ARTICLE', 'STAT', 'MODE', 'LISTGROUP', 'XOVER',
522 'XHDR', 'HEAD', 'BODY', 'NEXT', 'LAST', 'POST', 'QUIT', 'IHAVE',
523 'HELP', 'SLAVE', 'XPATH', 'XINDEX', 'XROVER', 'TAKETHIS', 'CHECK'
524 ]
525
526 def __init__(self):
527 self.servingSlave = 0
528
529
530 def connectionMade(self):
531 self.inputHandler = None
532 self.currentGroup = None
533 self.currentIndex = None
534 self.sendLine('200 server ready - posting allowed')
535
536 def lineReceived(self, line):
537 if self.inputHandler is not None:
538 self.inputHandler(line)
539 else:
540 parts = line.strip().split()
541 if len(parts):
542 cmd, parts = parts[0].upper(), parts[1:]
543 if cmd in NNTPServer.COMMANDS:
544 func = getattr(self, 'do_%s' % cmd)
545 try:
546 func(*parts)
547 except TypeError:
548 self.sendLine('501 command syntax error')
549 log.msg("501 command syntax error")
550 log.msg("command was", line)
551 log.deferr()
552 except:
553 self.sendLine('503 program fault - command not performed ')
554 log.msg("503 program fault")
555 log.msg("command was", line)
556 log.deferr()
557 else:
558 self.sendLine('500 command not recognized')
559
560
561 def do_LIST(self, subcmd = '', *dummy):
562 subcmd = subcmd.strip().lower()
563 if subcmd == 'newsgroups':
564 # XXX - this could use a real implementation, eh?
565 self.sendLine('215 Descriptions in form "group description"')
566 self.sendLine('.')
567 elif subcmd == 'overview.fmt':
568 defer = self.factory.backend.overviewRequest()
569 defer.addCallbacks(self._gotOverview, self._errOverview)
570 log.msg('overview')
571 elif subcmd == 'subscriptions':
572 defer = self.factory.backend.subscriptionRequest()
573 defer.addCallbacks(self._gotSubscription, self._errSubscription)
574 log.msg('subscriptions')
575 elif subcmd == '':
576 defer = self.factory.backend.listRequest()
577 defer.addCallbacks(self._gotList, self._errList)
578 else:
579 self.sendLine('500 command not recognized')
580
581
582 def _gotList(self, list):
583 self.sendLine('215 newsgroups in form "group high low flags"')
584 for i in list:
585 self.sendLine('%s %d %d %s' % tuple(i))
586 self.sendLine('.')
587
588
589 def _errList(self, failure):
590 print 'LIST failed: ', failure
591 self.sendLine('503 program fault - command not performed')
592
593
594 def _gotSubscription(self, parts):
595 self.sendLine('215 information follows')
596 for i in parts:
597 self.sendLine(i)
598 self.sendLine('.')
599
600
601 def _errSubscription(self, failure):
602 print 'SUBSCRIPTIONS failed: ', failure
603 self.sendLine('503 program fault - comand not performed')
604
605
606 def _gotOverview(self, parts):
607 self.sendLine('215 Order of fields in overview database.')
608 for i in parts:
609 self.sendLine(i + ':')
610 self.sendLine('.')
611
612
613 def _errOverview(self, failure):
614 print 'LIST OVERVIEW.FMT failed: ', failure
615 self.sendLine('503 program fault - command not performed')
616
617
618 def do_LISTGROUP(self, group = None):
619 group = group or self.currentGroup
620 if group is None:
621 self.sendLine('412 Not currently in newsgroup')
622 else:
623 defer = self.factory.backend.listGroupRequest(group)
624 defer.addCallbacks(self._gotListGroup, self._errListGroup)
625
626
627 def _gotListGroup(self, (group, articles)):
628 self.currentGroup = group
629 if len(articles):
630 self.currentIndex = int(articles[0])
631 else:
632 self.currentIndex = None
633
634 self.sendLine('211 list of article numbers follow')
635 for i in articles:
636 self.sendLine(str(i))
637 self.sendLine('.')
638
639
640 def _errListGroup(self, failure):
641 print 'LISTGROUP failed: ', failure
642 self.sendLine('502 no permission')
643
644
645 def do_XOVER(self, range):
646 if self.currentGroup is None:
647 self.sendLine('412 No news group currently selected')
648 else:
649 l, h = parseRange(range)
650 defer = self.factory.backend.xoverRequest(self.currentGroup, l, h)
651 defer.addCallbacks(self._gotXOver, self._errXOver)
652
653
654 def _gotXOver(self, parts):
655 self.sendLine('224 Overview information follows')
656 for i in parts:
657 self.sendLine('\t'.join(map(str, i)))
658 self.sendLine('.')
659
660
661 def _errXOver(self, failure):
662 print 'XOVER failed: ', failure
663 self.sendLine('420 No article(s) selected')
664
665
666 def xhdrWork(self, header, range):
667 if self.currentGroup is None:
668 self.sendLine('412 No news group currently selected')
669 else:
670 if range is None:
671 if self.currentIndex is None:
672 self.sendLine('420 No current article selected')
673 return
674 else:
675 l = h = self.currentIndex
676 else:
677 # FIXME: articles may be a message-id
678 l, h = parseRange(range)
679
680 if l is h is None:
681 self.sendLine('430 no such article')
682 else:
683 return self.factory.backend.xhdrRequest(self.currentGroup, l, h, header)
684
685
686 def do_XHDR(self, header, range = None):
687 d = self.xhdrWork(header, range)
688 if d:
689 d.addCallbacks(self._gotXHDR, self._errXHDR)
690
691
692 def _gotXHDR(self, parts):
693 self.sendLine('221 Header follows')
694 for i in parts:
695 self.sendLine('%d %s' % i)
696 self.sendLine('.')
697
698 def _errXHDR(self, failure):
699 print 'XHDR failed: ', failure
700 self.sendLine('502 no permission')
701
702
703 def do_XROVER(self, header, range = None):
704 d = self.xhdrWork(header, range)
705 if d:
706 d.addCallbacks(self._gotXROVER, self._errXROVER)
707
708
709 def _gotXROVER(self, parts):
710 self.sendLine('224 Overview information follows')
711 for i in parts:
712 self.sendLine('%d %s' % i)
713 self.sendLine('.')
714
715
716 def _errXROVER(self, failure):
717 print 'XROVER failed: ',
718 self._errXHDR(failure)
719
720
721 def do_POST(self):
722 self.inputHandler = self._doingPost
723 self.message = ''
724 self.sendLine('340 send article to be posted. End with <CR-LF>.<CR-LF>' )
725
726
727 def _doingPost(self, line):
728 if line == '.':
729 self.inputHandler = None
730 group, article = self.currentGroup, self.message
731 self.message = ''
732
733 defer = self.factory.backend.postRequest(article)
734 defer.addCallbacks(self._gotPost, self._errPost)
735 else:
736 self.message = self.message + line + '\r\n'
737
738
739 def _gotPost(self, parts):
740 self.sendLine('240 article posted ok')
741
742
743 def _errPost(self, failure):
744 print 'POST failed: ', failure
745 self.sendLine('441 posting failed')
746
747
748 def do_CHECK(self, id):
749 d = self.factory.backend.articleExistsRequest(id)
750 d.addCallbacks(self._gotCheck, self._errCheck)
751
752
753 def _gotCheck(self, result):
754 if result:
755 self.sendLine("438 already have it, please don't send it to me")
756 else:
757 self.sendLine('238 no such article found, please send it to me')
758
759
760 def _errCheck(self, failure):
761 print 'CHECK failed: ', failure
762 self.sendLine('431 try sending it again later')
763
764
765 def do_TAKETHIS(self, id):
766 self.inputHandler = self._doingTakeThis
767 self.message = ''
768
769
770 def _doingTakeThis(self, line):
771 if line == '.':
772 self.inputHandler = None
773 article = self.message
774 self.message = ''
775 d = self.factory.backend.postRequest(article)
776 d.addCallbacks(self._didTakeThis, self._errTakeThis)
777 else:
778 self.message = self.message + line + '\r\n'
779
780
781 def _didTakeThis(self, result):
782 self.sendLine('239 article transferred ok')
783
784
785 def _errTakeThis(self, failure):
786 print 'TAKETHIS failed: ', failure
787 self.sendLine('439 article transfer failed')
788
789
790 def do_GROUP(self, group):
791 defer = self.factory.backend.groupRequest(group)
792 defer.addCallbacks(self._gotGroup, self._errGroup)
793
794
795 def _gotGroup(self, (name, num, high, low, flags)):
796 self.currentGroup = name
797 self.currentIndex = low
798 self.sendLine('211 %d %d %d %s group selected' % (num, low, high, name))
799
800
801 def _errGroup(self, failure):
802 print 'GROUP failed: ', failure
803 self.sendLine('411 no such group')
804
805
806 def articleWork(self, article, cmd, func):
807 if self.currentGroup is None:
808 self.sendLine('412 no newsgroup has been selected')
809 else:
810 if not article:
811 if self.currentIndex is None:
812 self.sendLine('420 no current article has been selected')
813 else:
814 article = self.currentIndex
815 else:
816 if article[0] == '<':
817 return func(self.currentGroup, index = None, id = article)
818 else:
819 try:
820 article = int(article)
821 return func(self.currentGroup, article)
822 except ValueError, e:
823 self.sendLine('501 command syntax error')
824
825
826 def do_ARTICLE(self, article = None):
827 defer = self.articleWork(article, 'ARTICLE', self.factory.backend.articl eRequest)
828 if defer:
829 defer.addCallbacks(self._gotArticle, self._errArticle)
830
831
832 def _gotArticle(self, (index, id, article)):
833 if isinstance(article, types.StringType):
834 import warnings
835 warnings.warn(
836 "Returning the article as a string from `articleRequest' "
837 "is deprecated. Return a file-like object instead."
838 )
839 article = StringIO.StringIO(article)
840 self.currentIndex = index
841 self.sendLine('220 %d %s article' % (index, id))
842 s = basic.FileSender()
843 d = s.beginFileTransfer(article, self.transport)
844 d.addCallback(self.finishedFileTransfer)
845
846 ##
847 ## Helper for FileSender
848 ##
849 def finishedFileTransfer(self, lastsent):
850 if lastsent != '\n':
851 line = '\r\n.'
852 else:
853 line = '.'
854 self.sendLine(line)
855 ##
856
857 def _errArticle(self, failure):
858 print 'ARTICLE failed: ', failure
859 self.sendLine('423 bad article number')
860
861
862 def do_STAT(self, article = None):
863 defer = self.articleWork(article, 'STAT', self.factory.backend.articleRe quest)
864 if defer:
865 defer.addCallbacks(self._gotStat, self._errStat)
866
867
868 def _gotStat(self, (index, id, article)):
869 self.currentIndex = index
870 self.sendLine('223 %d %s article retreived - request text separately' % (index, id))
871
872
873 def _errStat(self, failure):
874 print 'STAT failed: ', failure
875 self.sendLine('423 bad article number')
876
877
878 def do_HEAD(self, article = None):
879 defer = self.articleWork(article, 'HEAD', self.factory.backend.headReque st)
880 if defer:
881 defer.addCallbacks(self._gotHead, self._errHead)
882
883
884 def _gotHead(self, (index, id, head)):
885 self.currentIndex = index
886 self.sendLine('221 %d %s article retrieved' % (index, id))
887 self.transport.write(head + '\r\n')
888 self.sendLine('.')
889
890
891 def _errHead(self, failure):
892 print 'HEAD failed: ', failure
893 self.sendLine('423 no such article number in this group')
894
895
896 def do_BODY(self, article):
897 defer = self.articleWork(article, 'BODY', self.factory.backend.bodyReque st)
898 if defer:
899 defer.addCallbacks(self._gotBody, self._errBody)
900
901
902 def _gotBody(self, (index, id, body)):
903 if isinstance(body, types.StringType):
904 import warnings
905 warnings.warn(
906 "Returning the article as a string from `articleRequest' "
907 "is deprecated. Return a file-like object instead."
908 )
909 body = StringIO.StringIO(body)
910 self.currentIndex = index
911 self.sendLine('221 %d %s article retrieved' % (index, id))
912 self.lastsent = ''
913 s = basic.FileSender()
914 d = s.beginFileTransfer(body, self.transport)
915 d.addCallback(self.finishedFileTransfer)
916
917 def _errBody(self, failure):
918 print 'BODY failed: ', failure
919 self.sendLine('423 no such article number in this group')
920
921
922 # NEXT and LAST are just STATs that increment currentIndex first.
923 # Accordingly, use the STAT callbacks.
924 def do_NEXT(self):
925 i = self.currentIndex + 1
926 defer = self.factory.backend.articleRequest(self.currentGroup, i)
927 defer.addCallbacks(self._gotStat, self._errStat)
928
929
930 def do_LAST(self):
931 i = self.currentIndex - 1
932 defer = self.factory.backend.articleRequest(self.currentGroup, i)
933 defer.addCallbacks(self._gotStat, self._errStat)
934
935
936 def do_MODE(self, cmd):
937 cmd = cmd.strip().upper()
938 if cmd == 'READER':
939 self.servingSlave = 0
940 self.sendLine('200 Hello, you can post')
941 elif cmd == 'STREAM':
942 self.sendLine('500 Command not understood')
943 else:
944 # This is not a mistake
945 self.sendLine('500 Command not understood')
946
947
948 def do_QUIT(self):
949 self.sendLine('205 goodbye')
950 self.transport.loseConnection()
951
952
953 def do_HELP(self):
954 self.sendLine('100 help text follows')
955 self.sendLine('Read the RFC.')
956 self.sendLine('.')
957
958
959 def do_SLAVE(self):
960 self.sendLine('202 slave status noted')
961 self.servingeSlave = 1
962
963
964 def do_XPATH(self, article):
965 # XPATH is a silly thing to have. No client has the right to ask
966 # for this piece of information from me, and so that is what I'll
967 # tell them.
968 self.sendLine('502 access restriction or permission denied')
969
970
971 def do_XINDEX(self, article):
972 # XINDEX is another silly command. The RFC suggests it be relegated
973 # to the history books, and who am I to disagree?
974 self.sendLine('502 access restriction or permission denied')
975
976
977 def do_XROVER(self, range = None):
978 self.do_XHDR(self, 'References', range)
979
980
981 def do_IHAVE(self, id):
982 self.factory.backend.articleExistsRequest(id).addCallback(self._foundArt icle)
983
984
985 def _foundArticle(self, result):
986 if result:
987 self.sendLine('437 article rejected - do not try again')
988 else:
989 self.sendLine('335 send article to be transferred. End with <CR-LF> .<CR-LF>')
990 self.inputHandler = self._handleIHAVE
991 self.message = ''
992
993
994 def _handleIHAVE(self, line):
995 if line == '.':
996 self.inputHandler = None
997 self.factory.backend.postRequest(
998 self.message
999 ).addCallbacks(self._gotIHAVE, self._errIHAVE)
1000
1001 self.message = ''
1002 else:
1003 self.message = self.message + line + '\r\n'
1004
1005
1006 def _gotIHAVE(self, result):
1007 self.sendLine('235 article transferred ok')
1008
1009
1010 def _errIHAVE(self, failure):
1011 print 'IHAVE failed: ', failure
1012 self.sendLine('436 transfer failed - try again later')
1013
1014
1015 class UsenetClientProtocol(NNTPClient):
1016 """
1017 A client that connects to an NNTP server and asks for articles new
1018 since a certain time.
1019 """
1020
1021 def __init__(self, groups, date, storage):
1022 """
1023 Fetch all new articles from the given groups since the
1024 given date and dump them into the given storage. groups
1025 is a list of group names. date is an integer or floating
1026 point representing seconds since the epoch (GMT). storage is
1027 any object that implements the NewsStorage interface.
1028 """
1029 NNTPClient.__init__(self)
1030 self.groups, self.date, self.storage = groups, date, storage
1031
1032
1033 def connectionMade(self):
1034 NNTPClient.connectionMade(self)
1035 log.msg("Initiating update with remote host: " + str(self.transport.getP eer()))
1036 self.setStream()
1037 self.fetchNewNews(self.groups, self.date, '')
1038
1039
1040 def articleExists(self, exists, article):
1041 if exists:
1042 self.fetchArticle(article)
1043 else:
1044 self.count = self.count - 1
1045 self.disregard = self.disregard + 1
1046
1047
1048 def gotNewNews(self, news):
1049 self.disregard = 0
1050 self.count = len(news)
1051 log.msg("Transfering " + str(self.count) + " articles from remote host: " + str(self.transport.getPeer()))
1052 for i in news:
1053 self.storage.articleExistsRequest(i).addCallback(self.articleExists, i)
1054
1055
1056 def getNewNewsFailed(self, reason):
1057 log.msg("Updated failed (" + reason + ") with remote host: " + str(self. transport.getPeer()))
1058 self.quit()
1059
1060
1061 def gotArticle(self, article):
1062 self.storage.postRequest(article)
1063 self.count = self.count - 1
1064 if not self.count:
1065 log.msg("Completed update with remote host: " + str(self.transport.g etPeer()))
1066 if self.disregard:
1067 log.msg("Disregarded %d articles." % (self.disregard,))
1068 self.factory.updateChecks(self.transport.getPeer())
1069 self.quit()
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/news/news.py ('k') | third_party/twisted_8_1/twisted/news/tap.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698