1
# -*- test-case-name: twisted.news.test.test_nntp -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
9
Maintainer: Jp Calderone
11
The following protocol commands are currently understood::
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
19
The following protocol commands require implementation::
23
XTHREAD AUTHINFO NEWGROUPS
26
Other desired features:
29
- More robust client input handling
37
import cStringIO as StringIO
41
from twisted.protocols import basic
42
from twisted.python import log
45
articles = text.split('-')
46
if len(articles) == 1:
52
elif len(articles) == 2:
67
def extractCode(line):
68
line = line.split(' ', 1)
72
return int(line[0]), line[1]
77
class NNTPError(Exception):
78
def __init__(self, string):
82
return 'NNTPError: %s' % self.string
85
class NNTPClient(basic.LineReceiver):
86
MAX_COMMAND_LENGTH = 510
89
self.currentGroup = None
93
self._inputBuffers = []
94
self._responseCodes = []
95
self._responseHandlers = []
99
self._newState(self._statePassive, None, self._headerInitial)
102
def gotAllGroups(self, groups):
103
"Override for notification when fetchGroups() action is completed"
106
def getAllGroupsFailed(self, error):
107
"Override for notification when fetchGroups() action fails"
110
def gotOverview(self, overview):
111
"Override for notification when fetchOverview() action is completed"
114
def getOverviewFailed(self, error):
115
"Override for notification when fetchOverview() action fails"
118
def gotSubscriptions(self, subscriptions):
119
"Override for notification when fetchSubscriptions() action is completed"
122
def getSubscriptionsFailed(self, error):
123
"Override for notification when fetchSubscriptions() action fails"
126
def gotGroup(self, group):
127
"Override for notification when fetchGroup() action is completed"
130
def getGroupFailed(self, error):
131
"Override for notification when fetchGroup() action fails"
134
def gotArticle(self, article):
135
"Override for notification when fetchArticle() action is completed"
138
def getArticleFailed(self, error):
139
"Override for notification when fetchArticle() action fails"
142
def gotHead(self, head):
143
"Override for notification when fetchHead() action is completed"
146
def getHeadFailed(self, error):
147
"Override for notification when fetchHead() action fails"
150
def gotBody(self, info):
151
"Override for notification when fetchBody() action is completed"
154
def getBodyFailed(self, body):
155
"Override for notification when fetchBody() action fails"
159
"Override for notification when postArticle() action is successful"
162
def postFailed(self, error):
163
"Override for notification when postArticle() action fails"
166
def gotXHeader(self, headers):
167
"Override for notification when getXHeader() action is successful"
170
def getXHeaderFailed(self, error):
171
"Override for notification when getXHeader() action fails"
174
def gotNewNews(self, news):
175
"Override for notification when getNewNews() action is successful"
178
def getNewNewsFailed(self, error):
179
"Override for notification when getNewNews() action fails"
182
def gotNewGroups(self, groups):
183
"Override for notification when getNewGroups() action is successful"
186
def getNewGroupsFailed(self, error):
187
"Override for notification when getNewGroups() action fails"
190
def setStreamSuccess(self):
191
"Override for notification when setStream() action is successful"
194
def setStreamFailed(self, error):
195
"Override for notification when setStream() action fails"
198
def fetchGroups(self):
200
Request a list of all news groups from the server. gotAllGroups()
201
is called on success, getGroupsFailed() on failure
203
self.sendLine('LIST')
204
self._newState(self._stateList, self.getAllGroupsFailed)
207
def fetchOverview(self):
209
Request the overview format from the server. gotOverview() is called
210
on success, getOverviewFailed() on failure
212
self.sendLine('LIST OVERVIEW.FMT')
213
self._newState(self._stateOverview, self.getOverviewFailed)
216
def fetchSubscriptions(self):
218
Request a list of the groups it is recommended a new user subscribe to.
219
gotSubscriptions() is called on success, getSubscriptionsFailed() on
222
self.sendLine('LIST SUBSCRIPTIONS')
223
self._newState(self._stateSubscriptions, self.getSubscriptionsFailed)
226
def fetchGroup(self, group):
228
Get group information for the specified group from the server. gotGroup()
229
is called on success, getGroupFailed() on failure.
231
self.sendLine('GROUP %s' % (group,))
232
self._newState(None, self.getGroupFailed, self._headerGroup)
235
def fetchHead(self, index = ''):
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
241
self.sendLine('HEAD %s' % (index,))
242
self._newState(self._stateHead, self.getHeadFailed)
245
def fetchBody(self, index = ''):
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
251
self.sendLine('BODY %s' % (index,))
252
self._newState(self._stateBody, self.getBodyFailed)
255
def fetchArticle(self, index = ''):
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.
261
self.sendLine('ARTICLE %s' % (index,))
262
self._newState(self._stateArticle, self.getArticleFailed)
265
def postArticle(self, text):
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 the
269
article is posted successfully, postedOk() is called, otherwise postFailed()
272
self.sendLine('POST')
273
self._newState(None, self.postFailed, self._headerPost)
274
self._postText.append(text)
277
def fetchNewNews(self, groups, date, distributions = ''):
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.
284
One invocation of this function may result in multiple invocations
285
of gotNewNews()/getNewNewsFailed().
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)
290
while len(groups) and len(line) + len(groupPart) + len(groups[-1]) + 1 < NNTPClient.MAX_COMMAND_LENGTH:
292
groupPart = groupPart + ',' + group
294
self.sendLine(line % (groupPart,))
295
self._newState(self._stateNewNews, self.getNewNewsFailed)
298
self.fetchNewNews(groups, date, distributions)
301
def fetchNewGroups(self, date, distributions):
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.
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)
313
def fetchXHeader(self, header, low = None, high = None, id = None):
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.
325
r = header + ' <%s>' % (id,)
326
elif low is high is None:
329
r = header + ' %d-' % (low,)
331
r = header + ' -%d' % (high,)
333
r = header + ' %d-%d' % (low, high)
334
self.sendLine('XHDR ' + r)
335
self._newState(self._stateXHDR, self.getXHeaderFailed)
340
Set the mode to STREAM, suspending the normal "lock-step" mode of
341
communications. setStreamSuccess() is called on success,
342
setStreamFailed() on failure.
344
self.sendLine('MODE STREAM')
345
self._newState(None, self.setStreamFailed, self._headerMode)
349
self.sendLine('QUIT')
350
self.transport.loseConnection()
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)
362
buf = self._inputBuffers[0]
363
del self._responseCodes[0]
364
del self._inputBuffers[0]
367
del self._responseHandlers[0]
371
def _newLine(self, line, check = 1):
372
if check and line and line[0] == '.':
374
self._inputBuffers[0].append(line)
377
def _setResponseCode(self, code):
378
self._responseCodes[0] = code
381
def _getResponseCode(self):
382
return self._responseCodes[0]
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!
394
self._setResponseCode(code)
395
if self._responseHandlers[0]:
396
self._responseHandlers[0](code)
401
def _statePassive(self, line):
402
log.msg('Server said: %s' % line)
405
def _passiveError(self, error):
406
log.err('Passive Error: %s' % (error,))
409
def _headerInitial(self, (code, message)):
417
def _stateList(self, line):
419
data = filter(None, line.strip().split())
420
self._newLine((data[0], int(data[1]), int(data[2]), data[3]), 0)
422
self.gotAllGroups(self._endState())
425
def _stateOverview(self, line):
427
self._newLine(filter(None, line.strip().split()), 0)
429
self.gotOverview(self._endState())
432
def _stateSubscriptions(self, line):
434
self._newLine(line.strip(), 0)
436
self.gotSubscriptions(self._endState())
439
def _headerGroup(self, (code, line)):
440
self.gotGroup(tuple(line.split()))
444
def _stateArticle(self, line):
446
if line.startswith('.'):
448
self._newLine(line, 0)
450
self.gotArticle('\n'.join(self._endState())+'\n')
453
def _stateHead(self, line):
455
self._newLine(line, 0)
457
self.gotHead('\n'.join(self._endState()))
460
def _stateBody(self, line):
462
if line.startswith('.'):
464
self._newLine(line, 0)
466
self.gotBody('\n'.join(self._endState())+'\n')
469
def _headerPost(self, (code, message)):
471
self.transport.write(self._postText[0].replace('\n', '\r\n').replace('\r\n.', '\r\n..'))
472
if self._postText[0][-1:] != '\n':
475
del self._postText[0]
476
self._newState(None, self.postFailed, self._headerPosted)
478
self.postFailed('%d %s' % (code, message))
482
def _headerPosted(self, (code, message)):
486
self.postFailed('%d %s' % (code, message))
490
def _stateXHDR(self, line):
492
self._newLine(line.split(), 0)
494
self._gotXHeader(self._endState())
497
def _stateNewNews(self, line):
499
self._newLine(line, 0)
501
self.gotNewNews(self._endState())
504
def _stateNewGroups(self, line):
506
self._newLine(line, 0)
508
self.gotNewGroups(self._endState())
511
def _headerMode(self, (code, message)):
513
self.setStreamSuccess()
515
self.setStreamFailed((code, message))
519
class NNTPServer(basic.LineReceiver):
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'
527
self.servingSlave = 0
530
def connectionMade(self):
531
self.inputHandler = None
532
self.currentGroup = None
533
self.currentIndex = None
534
self.sendLine('200 server ready - posting allowed')
536
def lineReceived(self, line):
537
if self.inputHandler is not None:
538
self.inputHandler(line)
540
parts = line.strip().split()
542
cmd, parts = parts[0].upper(), parts[1:]
543
if cmd in NNTPServer.COMMANDS:
544
func = getattr(self, 'do_%s' % cmd)
548
self.sendLine('501 command syntax error')
549
log.msg("501 command syntax error")
550
log.msg("command was", line)
553
self.sendLine('503 program fault - command not performed')
554
log.msg("503 program fault")
555
log.msg("command was", line)
558
self.sendLine('500 command not recognized')
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"')
567
elif subcmd == 'overview.fmt':
568
defer = self.factory.backend.overviewRequest()
569
defer.addCallbacks(self._gotOverview, self._errOverview)
571
elif subcmd == 'subscriptions':
572
defer = self.factory.backend.subscriptionRequest()
573
defer.addCallbacks(self._gotSubscription, self._errSubscription)
574
log.msg('subscriptions')
576
defer = self.factory.backend.listRequest()
577
defer.addCallbacks(self._gotList, self._errList)
579
self.sendLine('500 command not recognized')
582
def _gotList(self, list):
583
self.sendLine('215 newsgroups in form "group high low flags"')
585
self.sendLine('%s %d %d %s' % tuple(i))
589
def _errList(self, failure):
590
print 'LIST failed: ', failure
591
self.sendLine('503 program fault - command not performed')
594
def _gotSubscription(self, parts):
595
self.sendLine('215 information follows')
601
def _errSubscription(self, failure):
602
print 'SUBSCRIPTIONS failed: ', failure
603
self.sendLine('503 program fault - comand not performed')
606
def _gotOverview(self, parts):
607
self.sendLine('215 Order of fields in overview database.')
609
self.sendLine(i + ':')
613
def _errOverview(self, failure):
614
print 'LIST OVERVIEW.FMT failed: ', failure
615
self.sendLine('503 program fault - command not performed')
618
def do_LISTGROUP(self, group = None):
619
group = group or self.currentGroup
621
self.sendLine('412 Not currently in newsgroup')
623
defer = self.factory.backend.listGroupRequest(group)
624
defer.addCallbacks(self._gotListGroup, self._errListGroup)
627
def _gotListGroup(self, (group, articles)):
628
self.currentGroup = group
630
self.currentIndex = int(articles[0])
632
self.currentIndex = None
634
self.sendLine('211 list of article numbers follow')
636
self.sendLine(str(i))
640
def _errListGroup(self, failure):
641
print 'LISTGROUP failed: ', failure
642
self.sendLine('502 no permission')
645
def do_XOVER(self, range):
646
if self.currentGroup is None:
647
self.sendLine('412 No news group currently selected')
649
l, h = parseRange(range)
650
defer = self.factory.backend.xoverRequest(self.currentGroup, l, h)
651
defer.addCallbacks(self._gotXOver, self._errXOver)
654
def _gotXOver(self, parts):
655
self.sendLine('224 Overview information follows')
657
self.sendLine('\t'.join(map(str, i)))
661
def _errXOver(self, failure):
662
print 'XOVER failed: ', failure
663
self.sendLine('420 No article(s) selected')
666
def xhdrWork(self, header, range):
667
if self.currentGroup is None:
668
self.sendLine('412 No news group currently selected')
671
if self.currentIndex is None:
672
self.sendLine('420 No current article selected')
675
l = h = self.currentIndex
677
# FIXME: articles may be a message-id
678
l, h = parseRange(range)
681
self.sendLine('430 no such article')
683
return self.factory.backend.xhdrRequest(self.currentGroup, l, h, header)
686
def do_XHDR(self, header, range = None):
687
d = self.xhdrWork(header, range)
689
d.addCallbacks(self._gotXHDR, self._errXHDR)
692
def _gotXHDR(self, parts):
693
self.sendLine('221 Header follows')
695
self.sendLine('%d %s' % i)
698
def _errXHDR(self, failure):
699
print 'XHDR failed: ', failure
700
self.sendLine('502 no permission')
703
def do_XROVER(self, header, range = None):
704
d = self.xhdrWork(header, range)
706
d.addCallbacks(self._gotXROVER, self._errXROVER)
709
def _gotXROVER(self, parts):
710
self.sendLine('224 Overview information follows')
712
self.sendLine('%d %s' % i)
716
def _errXROVER(self, failure):
717
print 'XROVER failed: ',
718
self._errXHDR(failure)
722
self.inputHandler = self._doingPost
724
self.sendLine('340 send article to be posted. End with <CR-LF>.<CR-LF>')
727
def _doingPost(self, line):
729
self.inputHandler = None
730
group, article = self.currentGroup, self.message
733
defer = self.factory.backend.postRequest(article)
734
defer.addCallbacks(self._gotPost, self._errPost)
736
self.message = self.message + line + '\r\n'
739
def _gotPost(self, parts):
740
self.sendLine('240 article posted ok')
743
def _errPost(self, failure):
744
print 'POST failed: ', failure
745
self.sendLine('441 posting failed')
748
def do_CHECK(self, id):
749
d = self.factory.backend.articleExistsRequest(id)
750
d.addCallbacks(self._gotCheck, self._errCheck)
753
def _gotCheck(self, result):
755
self.sendLine("438 already have it, please don't send it to me")
757
self.sendLine('238 no such article found, please send it to me')
760
def _errCheck(self, failure):
761
print 'CHECK failed: ', failure
762
self.sendLine('431 try sending it again later')
765
def do_TAKETHIS(self, id):
766
self.inputHandler = self._doingTakeThis
770
def _doingTakeThis(self, line):
772
self.inputHandler = None
773
article = self.message
775
d = self.factory.backend.postRequest(article)
776
d.addCallbacks(self._didTakeThis, self._errTakeThis)
778
self.message = self.message + line + '\r\n'
781
def _didTakeThis(self, result):
782
self.sendLine('239 article transferred ok')
785
def _errTakeThis(self, failure):
786
print 'TAKETHIS failed: ', failure
787
self.sendLine('439 article transfer failed')
790
def do_GROUP(self, group):
791
defer = self.factory.backend.groupRequest(group)
792
defer.addCallbacks(self._gotGroup, self._errGroup)
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))
801
def _errGroup(self, failure):
802
print 'GROUP failed: ', failure
803
self.sendLine('411 no such group')
806
def articleWork(self, article, cmd, func):
807
if self.currentGroup is None:
808
self.sendLine('412 no newsgroup has been selected')
811
if self.currentIndex is None:
812
self.sendLine('420 no current article has been selected')
814
article = self.currentIndex
816
if article[0] == '<':
817
return func(self.currentGroup, index = None, id = article)
820
article = int(article)
821
return func(self.currentGroup, article)
822
except ValueError, e:
823
self.sendLine('501 command syntax error')
826
def do_ARTICLE(self, article = None):
827
defer = self.articleWork(article, 'ARTICLE', self.factory.backend.articleRequest)
829
defer.addCallbacks(self._gotArticle, self._errArticle)
832
def _gotArticle(self, (index, id, article)):
833
if isinstance(article, types.StringType):
836
"Returning the article as a string from `articleRequest' "
837
"is deprecated. Return a file-like object instead."
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)
847
## Helper for FileSender
849
def finishedFileTransfer(self, lastsent):
857
def _errArticle(self, failure):
858
print 'ARTICLE failed: ', failure
859
self.sendLine('423 bad article number')
862
def do_STAT(self, article = None):
863
defer = self.articleWork(article, 'STAT', self.factory.backend.articleRequest)
865
defer.addCallbacks(self._gotStat, self._errStat)
868
def _gotStat(self, (index, id, article)):
869
self.currentIndex = index
870
self.sendLine('223 %d %s article retreived - request text separately' % (index, id))
873
def _errStat(self, failure):
874
print 'STAT failed: ', failure
875
self.sendLine('423 bad article number')
878
def do_HEAD(self, article = None):
879
defer = self.articleWork(article, 'HEAD', self.factory.backend.headRequest)
881
defer.addCallbacks(self._gotHead, self._errHead)
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')
891
def _errHead(self, failure):
892
print 'HEAD failed: ', failure
893
self.sendLine('423 no such article number in this group')
896
def do_BODY(self, article):
897
defer = self.articleWork(article, 'BODY', self.factory.backend.bodyRequest)
899
defer.addCallbacks(self._gotBody, self._errBody)
902
def _gotBody(self, (index, id, body)):
903
if isinstance(body, types.StringType):
906
"Returning the article as a string from `articleRequest' "
907
"is deprecated. Return a file-like object instead."
909
body = StringIO.StringIO(body)
910
self.currentIndex = index
911
self.sendLine('221 %d %s article retrieved' % (index, id))
913
s = basic.FileSender()
914
d = s.beginFileTransfer(body, self.transport)
915
d.addCallback(self.finishedFileTransfer)
917
def _errBody(self, failure):
918
print 'BODY failed: ', failure
919
self.sendLine('423 no such article number in this group')
922
# NEXT and LAST are just STATs that increment currentIndex first.
923
# Accordingly, use the STAT callbacks.
925
i = self.currentIndex + 1
926
defer = self.factory.backend.articleRequest(self.currentGroup, i)
927
defer.addCallbacks(self._gotStat, self._errStat)
931
i = self.currentIndex - 1
932
defer = self.factory.backend.articleRequest(self.currentGroup, i)
933
defer.addCallbacks(self._gotStat, self._errStat)
936
def do_MODE(self, cmd):
937
cmd = cmd.strip().upper()
939
self.servingSlave = 0
940
self.sendLine('200 Hello, you can post')
941
elif cmd == 'STREAM':
942
self.sendLine('500 Command not understood')
944
# This is not a mistake
945
self.sendLine('500 Command not understood')
949
self.sendLine('205 goodbye')
950
self.transport.loseConnection()
954
self.sendLine('100 help text follows')
955
self.sendLine('Read the RFC.')
960
self.sendLine('202 slave status noted')
961
self.servingeSlave = 1
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
968
self.sendLine('502 access restriction or permission denied')
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')
977
def do_XROVER(self, range = None):
978
self.do_XHDR(self, 'References', range)
981
def do_IHAVE(self, id):
982
self.factory.backend.articleExistsRequest(id).addCallback(self._foundArticle)
985
def _foundArticle(self, result):
987
self.sendLine('437 article rejected - do not try again')
989
self.sendLine('335 send article to be transferred. End with <CR-LF>.<CR-LF>')
990
self.inputHandler = self._handleIHAVE
994
def _handleIHAVE(self, line):
996
self.inputHandler = None
997
self.factory.backend.postRequest(
999
).addCallbacks(self._gotIHAVE, self._errIHAVE)
1003
self.message = self.message + line + '\r\n'
1006
def _gotIHAVE(self, result):
1007
self.sendLine('235 article transferred ok')
1010
def _errIHAVE(self, failure):
1011
print 'IHAVE failed: ', failure
1012
self.sendLine('436 transfer failed - try again later')
1015
class UsenetClientProtocol(NNTPClient):
1017
A client that connects to an NNTP server and asks for articles new
1018
since a certain time.
1021
def __init__(self, groups, date, storage):
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.
1029
NNTPClient.__init__(self)
1030
self.groups, self.date, self.storage = groups, date, storage
1033
def connectionMade(self):
1034
NNTPClient.connectionMade(self)
1035
log.msg("Initiating update with remote host: " + str(self.transport.getPeer()))
1037
self.fetchNewNews(self.groups, self.date, '')
1040
def articleExists(self, exists, article):
1042
self.fetchArticle(article)
1044
self.count = self.count - 1
1045
self.disregard = self.disregard + 1
1048
def gotNewNews(self, news):
1050
self.count = len(news)
1051
log.msg("Transfering " + str(self.count) + " articles from remote host: " + str(self.transport.getPeer()))
1053
self.storage.articleExistsRequest(i).addCallback(self.articleExists, i)
1056
def getNewNewsFailed(self, reason):
1057
log.msg("Updated failed (" + reason + ") with remote host: " + str(self.transport.getPeer()))
1061
def gotArticle(self, article):
1062
self.storage.postRequest(article)
1063
self.count = self.count - 1
1065
log.msg("Completed update with remote host: " + str(self.transport.getPeer()))
1067
log.msg("Disregarded %d articles." % (self.disregard,))
1068
self.factory.updateChecks(self.transport.getPeer())