Index: third_party/twisted_8_1/twisted/news/nntp.py |
diff --git a/third_party/twisted_8_1/twisted/news/nntp.py b/third_party/twisted_8_1/twisted/news/nntp.py |
deleted file mode 100644 |
index 2e9861e5c7ca6c38786e7ba9336f8e1aff26a4c2..0000000000000000000000000000000000000000 |
--- a/third_party/twisted_8_1/twisted/news/nntp.py |
+++ /dev/null |
@@ -1,1069 +0,0 @@ |
-# -*- test-case-name: twisted.news.test.test_nntp -*- |
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories. |
-# See LICENSE for details. |
- |
- |
-""" |
-NNTP protocol support. |
- |
-Maintainer: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} |
- |
-The following protocol commands are currently understood:: |
- |
- LIST LISTGROUP XOVER XHDR |
- POST GROUP ARTICLE STAT HEAD |
- BODY NEXT MODE STREAM MODE READER SLAVE |
- LAST QUIT HELP IHAVE XPATH |
- XINDEX XROVER TAKETHIS CHECK |
- |
-The following protocol commands require implementation:: |
- |
- NEWNEWS |
- XGTITLE XPAT |
- XTHREAD AUTHINFO NEWGROUPS |
- |
- |
-Other desired features: |
- |
- - A real backend |
- - More robust client input handling |
- - A control protocol |
-""" |
- |
-import time |
-import types |
- |
-try: |
- import cStringIO as StringIO |
-except: |
- import StringIO |
- |
-from twisted.protocols import basic |
-from twisted.python import log |
- |
-def parseRange(text): |
- articles = text.split('-') |
- if len(articles) == 1: |
- try: |
- a = int(articles[0]) |
- return a, a |
- except ValueError, e: |
- return None, None |
- elif len(articles) == 2: |
- try: |
- if len(articles[0]): |
- l = int(articles[0]) |
- else: |
- l = None |
- if len(articles[1]): |
- h = int(articles[1]) |
- else: |
- h = None |
- except ValueError, e: |
- return None, None |
- return l, h |
- |
- |
-def extractCode(line): |
- line = line.split(' ', 1) |
- if len(line) != 2: |
- return None |
- try: |
- return int(line[0]), line[1] |
- except ValueError: |
- return None |
- |
- |
-class NNTPError(Exception): |
- def __init__(self, string): |
- self.string = string |
- |
- def __str__(self): |
- return 'NNTPError: %s' % self.string |
- |
- |
-class NNTPClient(basic.LineReceiver): |
- MAX_COMMAND_LENGTH = 510 |
- |
- def __init__(self): |
- self.currentGroup = None |
- |
- self._state = [] |
- self._error = [] |
- self._inputBuffers = [] |
- self._responseCodes = [] |
- self._responseHandlers = [] |
- |
- self._postText = [] |
- |
- self._newState(self._statePassive, None, self._headerInitial) |
- |
- |
- def gotAllGroups(self, groups): |
- "Override for notification when fetchGroups() action is completed" |
- |
- |
- def getAllGroupsFailed(self, error): |
- "Override for notification when fetchGroups() action fails" |
- |
- |
- def gotOverview(self, overview): |
- "Override for notification when fetchOverview() action is completed" |
- |
- |
- def getOverviewFailed(self, error): |
- "Override for notification when fetchOverview() action fails" |
- |
- |
- def gotSubscriptions(self, subscriptions): |
- "Override for notification when fetchSubscriptions() action is completed" |
- |
- |
- def getSubscriptionsFailed(self, error): |
- "Override for notification when fetchSubscriptions() action fails" |
- |
- |
- def gotGroup(self, group): |
- "Override for notification when fetchGroup() action is completed" |
- |
- |
- def getGroupFailed(self, error): |
- "Override for notification when fetchGroup() action fails" |
- |
- |
- def gotArticle(self, article): |
- "Override for notification when fetchArticle() action is completed" |
- |
- |
- def getArticleFailed(self, error): |
- "Override for notification when fetchArticle() action fails" |
- |
- |
- def gotHead(self, head): |
- "Override for notification when fetchHead() action is completed" |
- |
- |
- def getHeadFailed(self, error): |
- "Override for notification when fetchHead() action fails" |
- |
- |
- def gotBody(self, info): |
- "Override for notification when fetchBody() action is completed" |
- |
- |
- def getBodyFailed(self, body): |
- "Override for notification when fetchBody() action fails" |
- |
- |
- def postedOk(self): |
- "Override for notification when postArticle() action is successful" |
- |
- |
- def postFailed(self, error): |
- "Override for notification when postArticle() action fails" |
- |
- |
- def gotXHeader(self, headers): |
- "Override for notification when getXHeader() action is successful" |
- |
- |
- def getXHeaderFailed(self, error): |
- "Override for notification when getXHeader() action fails" |
- |
- |
- def gotNewNews(self, news): |
- "Override for notification when getNewNews() action is successful" |
- |
- |
- def getNewNewsFailed(self, error): |
- "Override for notification when getNewNews() action fails" |
- |
- |
- def gotNewGroups(self, groups): |
- "Override for notification when getNewGroups() action is successful" |
- |
- |
- def getNewGroupsFailed(self, error): |
- "Override for notification when getNewGroups() action fails" |
- |
- |
- def setStreamSuccess(self): |
- "Override for notification when setStream() action is successful" |
- |
- |
- def setStreamFailed(self, error): |
- "Override for notification when setStream() action fails" |
- |
- |
- def fetchGroups(self): |
- """ |
- Request a list of all news groups from the server. gotAllGroups() |
- is called on success, getGroupsFailed() on failure |
- """ |
- self.sendLine('LIST') |
- self._newState(self._stateList, self.getAllGroupsFailed) |
- |
- |
- def fetchOverview(self): |
- """ |
- Request the overview format from the server. gotOverview() is called |
- on success, getOverviewFailed() on failure |
- """ |
- self.sendLine('LIST OVERVIEW.FMT') |
- self._newState(self._stateOverview, self.getOverviewFailed) |
- |
- |
- def fetchSubscriptions(self): |
- """ |
- Request a list of the groups it is recommended a new user subscribe to. |
- gotSubscriptions() is called on success, getSubscriptionsFailed() on |
- failure |
- """ |
- self.sendLine('LIST SUBSCRIPTIONS') |
- self._newState(self._stateSubscriptions, self.getSubscriptionsFailed) |
- |
- |
- def fetchGroup(self, group): |
- """ |
- Get group information for the specified group from the server. gotGroup() |
- is called on success, getGroupFailed() on failure. |
- """ |
- self.sendLine('GROUP %s' % (group,)) |
- self._newState(None, self.getGroupFailed, self._headerGroup) |
- |
- |
- def fetchHead(self, index = ''): |
- """ |
- Get the header for the specified article (or the currently selected |
- article if index is '') from the server. gotHead() is called on |
- success, getHeadFailed() on failure |
- """ |
- self.sendLine('HEAD %s' % (index,)) |
- self._newState(self._stateHead, self.getHeadFailed) |
- |
- |
- def fetchBody(self, index = ''): |
- """ |
- Get the body for the specified article (or the currently selected |
- article if index is '') from the server. gotBody() is called on |
- success, getBodyFailed() on failure |
- """ |
- self.sendLine('BODY %s' % (index,)) |
- self._newState(self._stateBody, self.getBodyFailed) |
- |
- |
- def fetchArticle(self, index = ''): |
- """ |
- Get the complete article with the specified index (or the currently |
- selected article if index is '') or Message-ID from the server. |
- gotArticle() is called on success, getArticleFailed() on failure. |
- """ |
- self.sendLine('ARTICLE %s' % (index,)) |
- self._newState(self._stateArticle, self.getArticleFailed) |
- |
- |
- def postArticle(self, text): |
- """ |
- Attempt to post an article with the specified text to the server. 'text' |
- must consist of both head and body data, as specified by RFC 850. If the |
- article is posted successfully, postedOk() is called, otherwise postFailed() |
- is called. |
- """ |
- self.sendLine('POST') |
- self._newState(None, self.postFailed, self._headerPost) |
- self._postText.append(text) |
- |
- |
- def fetchNewNews(self, groups, date, distributions = ''): |
- """ |
- Get the Message-IDs for all new news posted to any of the given |
- groups since the specified date - in seconds since the epoch, GMT - |
- optionally restricted to the given distributions. gotNewNews() is |
- called on success, getNewNewsFailed() on failure. |
- |
- One invocation of this function may result in multiple invocations |
- of gotNewNews()/getNewNewsFailed(). |
- """ |
- date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split() |
- line = 'NEWNEWS %%s %s %s %s' % (date, timeStr, distributions) |
- groupPart = '' |
- while len(groups) and len(line) + len(groupPart) + len(groups[-1]) + 1 < NNTPClient.MAX_COMMAND_LENGTH: |
- group = groups.pop() |
- groupPart = groupPart + ',' + group |
- |
- self.sendLine(line % (groupPart,)) |
- self._newState(self._stateNewNews, self.getNewNewsFailed) |
- |
- if len(groups): |
- self.fetchNewNews(groups, date, distributions) |
- |
- |
- def fetchNewGroups(self, date, distributions): |
- """ |
- Get the names of all new groups created/added to the server since |
- the specified date - in seconds since the ecpoh, GMT - optionally |
- restricted to the given distributions. gotNewGroups() is called |
- on success, getNewGroupsFailed() on failure. |
- """ |
- date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split() |
- self.sendLine('NEWGROUPS %s %s %s' % (date, timeStr, distributions)) |
- self._newState(self._stateNewGroups, self.getNewGroupsFailed) |
- |
- |
- def fetchXHeader(self, header, low = None, high = None, id = None): |
- """ |
- Request a specific header from the server for an article or range |
- of articles. If 'id' is not None, a header for only the article |
- with that Message-ID will be requested. If both low and high are |
- None, a header for the currently selected article will be selected; |
- If both low and high are zero-length strings, headers for all articles |
- in the currently selected group will be requested; Otherwise, high |
- and low will be used as bounds - if one is None the first or last |
- article index will be substituted, as appropriate. |
- """ |
- if id is not None: |
- r = header + ' <%s>' % (id,) |
- elif low is high is None: |
- r = header |
- elif high is None: |
- r = header + ' %d-' % (low,) |
- elif low is None: |
- r = header + ' -%d' % (high,) |
- else: |
- r = header + ' %d-%d' % (low, high) |
- self.sendLine('XHDR ' + r) |
- self._newState(self._stateXHDR, self.getXHeaderFailed) |
- |
- |
- def setStream(self): |
- """ |
- Set the mode to STREAM, suspending the normal "lock-step" mode of |
- communications. setStreamSuccess() is called on success, |
- setStreamFailed() on failure. |
- """ |
- self.sendLine('MODE STREAM') |
- self._newState(None, self.setStreamFailed, self._headerMode) |
- |
- |
- def quit(self): |
- self.sendLine('QUIT') |
- self.transport.loseConnection() |
- |
- |
- def _newState(self, method, error, responseHandler = None): |
- self._inputBuffers.append([]) |
- self._responseCodes.append(None) |
- self._state.append(method) |
- self._error.append(error) |
- self._responseHandlers.append(responseHandler) |
- |
- |
- def _endState(self): |
- buf = self._inputBuffers[0] |
- del self._responseCodes[0] |
- del self._inputBuffers[0] |
- del self._state[0] |
- del self._error[0] |
- del self._responseHandlers[0] |
- return buf |
- |
- |
- def _newLine(self, line, check = 1): |
- if check and line and line[0] == '.': |
- line = line[1:] |
- self._inputBuffers[0].append(line) |
- |
- |
- def _setResponseCode(self, code): |
- self._responseCodes[0] = code |
- |
- |
- def _getResponseCode(self): |
- return self._responseCodes[0] |
- |
- |
- def lineReceived(self, line): |
- if not len(self._state): |
- self._statePassive(line) |
- elif self._getResponseCode() is None: |
- code = extractCode(line) |
- if code is None or not (200 <= code[0] < 400): # An error! |
- self._error[0](line) |
- self._endState() |
- else: |
- self._setResponseCode(code) |
- if self._responseHandlers[0]: |
- self._responseHandlers[0](code) |
- else: |
- self._state[0](line) |
- |
- |
- def _statePassive(self, line): |
- log.msg('Server said: %s' % line) |
- |
- |
- def _passiveError(self, error): |
- log.err('Passive Error: %s' % (error,)) |
- |
- |
- def _headerInitial(self, (code, message)): |
- if code == 200: |
- self.canPost = 1 |
- else: |
- self.canPost = 0 |
- self._endState() |
- |
- |
- def _stateList(self, line): |
- if line != '.': |
- data = filter(None, line.strip().split()) |
- self._newLine((data[0], int(data[1]), int(data[2]), data[3]), 0) |
- else: |
- self.gotAllGroups(self._endState()) |
- |
- |
- def _stateOverview(self, line): |
- if line != '.': |
- self._newLine(filter(None, line.strip().split()), 0) |
- else: |
- self.gotOverview(self._endState()) |
- |
- |
- def _stateSubscriptions(self, line): |
- if line != '.': |
- self._newLine(line.strip(), 0) |
- else: |
- self.gotSubscriptions(self._endState()) |
- |
- |
- def _headerGroup(self, (code, line)): |
- self.gotGroup(tuple(line.split())) |
- self._endState() |
- |
- |
- def _stateArticle(self, line): |
- if line != '.': |
- if line.startswith('.'): |
- line = line[1:] |
- self._newLine(line, 0) |
- else: |
- self.gotArticle('\n'.join(self._endState())+'\n') |
- |
- |
- def _stateHead(self, line): |
- if line != '.': |
- self._newLine(line, 0) |
- else: |
- self.gotHead('\n'.join(self._endState())) |
- |
- |
- def _stateBody(self, line): |
- if line != '.': |
- if line.startswith('.'): |
- line = line[1:] |
- self._newLine(line, 0) |
- else: |
- self.gotBody('\n'.join(self._endState())+'\n') |
- |
- |
- def _headerPost(self, (code, message)): |
- if code == 340: |
- self.transport.write(self._postText[0].replace('\n', '\r\n').replace('\r\n.', '\r\n..')) |
- if self._postText[0][-1:] != '\n': |
- self.sendLine('') |
- self.sendLine('.') |
- del self._postText[0] |
- self._newState(None, self.postFailed, self._headerPosted) |
- else: |
- self.postFailed('%d %s' % (code, message)) |
- self._endState() |
- |
- |
- def _headerPosted(self, (code, message)): |
- if code == 240: |
- self.postedOk() |
- else: |
- self.postFailed('%d %s' % (code, message)) |
- self._endState() |
- |
- |
- def _stateXHDR(self, line): |
- if line != '.': |
- self._newLine(line.split(), 0) |
- else: |
- self._gotXHeader(self._endState()) |
- |
- |
- def _stateNewNews(self, line): |
- if line != '.': |
- self._newLine(line, 0) |
- else: |
- self.gotNewNews(self._endState()) |
- |
- |
- def _stateNewGroups(self, line): |
- if line != '.': |
- self._newLine(line, 0) |
- else: |
- self.gotNewGroups(self._endState()) |
- |
- |
- def _headerMode(self, (code, message)): |
- if code == 203: |
- self.setStreamSuccess() |
- else: |
- self.setStreamFailed((code, message)) |
- self._endState() |
- |
- |
-class NNTPServer(basic.LineReceiver): |
- COMMANDS = [ |
- 'LIST', 'GROUP', 'ARTICLE', 'STAT', 'MODE', 'LISTGROUP', 'XOVER', |
- 'XHDR', 'HEAD', 'BODY', 'NEXT', 'LAST', 'POST', 'QUIT', 'IHAVE', |
- 'HELP', 'SLAVE', 'XPATH', 'XINDEX', 'XROVER', 'TAKETHIS', 'CHECK' |
- ] |
- |
- def __init__(self): |
- self.servingSlave = 0 |
- |
- |
- def connectionMade(self): |
- self.inputHandler = None |
- self.currentGroup = None |
- self.currentIndex = None |
- self.sendLine('200 server ready - posting allowed') |
- |
- def lineReceived(self, line): |
- if self.inputHandler is not None: |
- self.inputHandler(line) |
- else: |
- parts = line.strip().split() |
- if len(parts): |
- cmd, parts = parts[0].upper(), parts[1:] |
- if cmd in NNTPServer.COMMANDS: |
- func = getattr(self, 'do_%s' % cmd) |
- try: |
- func(*parts) |
- except TypeError: |
- self.sendLine('501 command syntax error') |
- log.msg("501 command syntax error") |
- log.msg("command was", line) |
- log.deferr() |
- except: |
- self.sendLine('503 program fault - command not performed') |
- log.msg("503 program fault") |
- log.msg("command was", line) |
- log.deferr() |
- else: |
- self.sendLine('500 command not recognized') |
- |
- |
- def do_LIST(self, subcmd = '', *dummy): |
- subcmd = subcmd.strip().lower() |
- if subcmd == 'newsgroups': |
- # XXX - this could use a real implementation, eh? |
- self.sendLine('215 Descriptions in form "group description"') |
- self.sendLine('.') |
- elif subcmd == 'overview.fmt': |
- defer = self.factory.backend.overviewRequest() |
- defer.addCallbacks(self._gotOverview, self._errOverview) |
- log.msg('overview') |
- elif subcmd == 'subscriptions': |
- defer = self.factory.backend.subscriptionRequest() |
- defer.addCallbacks(self._gotSubscription, self._errSubscription) |
- log.msg('subscriptions') |
- elif subcmd == '': |
- defer = self.factory.backend.listRequest() |
- defer.addCallbacks(self._gotList, self._errList) |
- else: |
- self.sendLine('500 command not recognized') |
- |
- |
- def _gotList(self, list): |
- self.sendLine('215 newsgroups in form "group high low flags"') |
- for i in list: |
- self.sendLine('%s %d %d %s' % tuple(i)) |
- self.sendLine('.') |
- |
- |
- def _errList(self, failure): |
- print 'LIST failed: ', failure |
- self.sendLine('503 program fault - command not performed') |
- |
- |
- def _gotSubscription(self, parts): |
- self.sendLine('215 information follows') |
- for i in parts: |
- self.sendLine(i) |
- self.sendLine('.') |
- |
- |
- def _errSubscription(self, failure): |
- print 'SUBSCRIPTIONS failed: ', failure |
- self.sendLine('503 program fault - comand not performed') |
- |
- |
- def _gotOverview(self, parts): |
- self.sendLine('215 Order of fields in overview database.') |
- for i in parts: |
- self.sendLine(i + ':') |
- self.sendLine('.') |
- |
- |
- def _errOverview(self, failure): |
- print 'LIST OVERVIEW.FMT failed: ', failure |
- self.sendLine('503 program fault - command not performed') |
- |
- |
- def do_LISTGROUP(self, group = None): |
- group = group or self.currentGroup |
- if group is None: |
- self.sendLine('412 Not currently in newsgroup') |
- else: |
- defer = self.factory.backend.listGroupRequest(group) |
- defer.addCallbacks(self._gotListGroup, self._errListGroup) |
- |
- |
- def _gotListGroup(self, (group, articles)): |
- self.currentGroup = group |
- if len(articles): |
- self.currentIndex = int(articles[0]) |
- else: |
- self.currentIndex = None |
- |
- self.sendLine('211 list of article numbers follow') |
- for i in articles: |
- self.sendLine(str(i)) |
- self.sendLine('.') |
- |
- |
- def _errListGroup(self, failure): |
- print 'LISTGROUP failed: ', failure |
- self.sendLine('502 no permission') |
- |
- |
- def do_XOVER(self, range): |
- if self.currentGroup is None: |
- self.sendLine('412 No news group currently selected') |
- else: |
- l, h = parseRange(range) |
- defer = self.factory.backend.xoverRequest(self.currentGroup, l, h) |
- defer.addCallbacks(self._gotXOver, self._errXOver) |
- |
- |
- def _gotXOver(self, parts): |
- self.sendLine('224 Overview information follows') |
- for i in parts: |
- self.sendLine('\t'.join(map(str, i))) |
- self.sendLine('.') |
- |
- |
- def _errXOver(self, failure): |
- print 'XOVER failed: ', failure |
- self.sendLine('420 No article(s) selected') |
- |
- |
- def xhdrWork(self, header, range): |
- if self.currentGroup is None: |
- self.sendLine('412 No news group currently selected') |
- else: |
- if range is None: |
- if self.currentIndex is None: |
- self.sendLine('420 No current article selected') |
- return |
- else: |
- l = h = self.currentIndex |
- else: |
- # FIXME: articles may be a message-id |
- l, h = parseRange(range) |
- |
- if l is h is None: |
- self.sendLine('430 no such article') |
- else: |
- return self.factory.backend.xhdrRequest(self.currentGroup, l, h, header) |
- |
- |
- def do_XHDR(self, header, range = None): |
- d = self.xhdrWork(header, range) |
- if d: |
- d.addCallbacks(self._gotXHDR, self._errXHDR) |
- |
- |
- def _gotXHDR(self, parts): |
- self.sendLine('221 Header follows') |
- for i in parts: |
- self.sendLine('%d %s' % i) |
- self.sendLine('.') |
- |
- def _errXHDR(self, failure): |
- print 'XHDR failed: ', failure |
- self.sendLine('502 no permission') |
- |
- |
- def do_XROVER(self, header, range = None): |
- d = self.xhdrWork(header, range) |
- if d: |
- d.addCallbacks(self._gotXROVER, self._errXROVER) |
- |
- |
- def _gotXROVER(self, parts): |
- self.sendLine('224 Overview information follows') |
- for i in parts: |
- self.sendLine('%d %s' % i) |
- self.sendLine('.') |
- |
- |
- def _errXROVER(self, failure): |
- print 'XROVER failed: ', |
- self._errXHDR(failure) |
- |
- |
- def do_POST(self): |
- self.inputHandler = self._doingPost |
- self.message = '' |
- self.sendLine('340 send article to be posted. End with <CR-LF>.<CR-LF>') |
- |
- |
- def _doingPost(self, line): |
- if line == '.': |
- self.inputHandler = None |
- group, article = self.currentGroup, self.message |
- self.message = '' |
- |
- defer = self.factory.backend.postRequest(article) |
- defer.addCallbacks(self._gotPost, self._errPost) |
- else: |
- self.message = self.message + line + '\r\n' |
- |
- |
- def _gotPost(self, parts): |
- self.sendLine('240 article posted ok') |
- |
- |
- def _errPost(self, failure): |
- print 'POST failed: ', failure |
- self.sendLine('441 posting failed') |
- |
- |
- def do_CHECK(self, id): |
- d = self.factory.backend.articleExistsRequest(id) |
- d.addCallbacks(self._gotCheck, self._errCheck) |
- |
- |
- def _gotCheck(self, result): |
- if result: |
- self.sendLine("438 already have it, please don't send it to me") |
- else: |
- self.sendLine('238 no such article found, please send it to me') |
- |
- |
- def _errCheck(self, failure): |
- print 'CHECK failed: ', failure |
- self.sendLine('431 try sending it again later') |
- |
- |
- def do_TAKETHIS(self, id): |
- self.inputHandler = self._doingTakeThis |
- self.message = '' |
- |
- |
- def _doingTakeThis(self, line): |
- if line == '.': |
- self.inputHandler = None |
- article = self.message |
- self.message = '' |
- d = self.factory.backend.postRequest(article) |
- d.addCallbacks(self._didTakeThis, self._errTakeThis) |
- else: |
- self.message = self.message + line + '\r\n' |
- |
- |
- def _didTakeThis(self, result): |
- self.sendLine('239 article transferred ok') |
- |
- |
- def _errTakeThis(self, failure): |
- print 'TAKETHIS failed: ', failure |
- self.sendLine('439 article transfer failed') |
- |
- |
- def do_GROUP(self, group): |
- defer = self.factory.backend.groupRequest(group) |
- defer.addCallbacks(self._gotGroup, self._errGroup) |
- |
- |
- def _gotGroup(self, (name, num, high, low, flags)): |
- self.currentGroup = name |
- self.currentIndex = low |
- self.sendLine('211 %d %d %d %s group selected' % (num, low, high, name)) |
- |
- |
- def _errGroup(self, failure): |
- print 'GROUP failed: ', failure |
- self.sendLine('411 no such group') |
- |
- |
- def articleWork(self, article, cmd, func): |
- if self.currentGroup is None: |
- self.sendLine('412 no newsgroup has been selected') |
- else: |
- if not article: |
- if self.currentIndex is None: |
- self.sendLine('420 no current article has been selected') |
- else: |
- article = self.currentIndex |
- else: |
- if article[0] == '<': |
- return func(self.currentGroup, index = None, id = article) |
- else: |
- try: |
- article = int(article) |
- return func(self.currentGroup, article) |
- except ValueError, e: |
- self.sendLine('501 command syntax error') |
- |
- |
- def do_ARTICLE(self, article = None): |
- defer = self.articleWork(article, 'ARTICLE', self.factory.backend.articleRequest) |
- if defer: |
- defer.addCallbacks(self._gotArticle, self._errArticle) |
- |
- |
- def _gotArticle(self, (index, id, article)): |
- if isinstance(article, types.StringType): |
- import warnings |
- warnings.warn( |
- "Returning the article as a string from `articleRequest' " |
- "is deprecated. Return a file-like object instead." |
- ) |
- article = StringIO.StringIO(article) |
- self.currentIndex = index |
- self.sendLine('220 %d %s article' % (index, id)) |
- s = basic.FileSender() |
- d = s.beginFileTransfer(article, self.transport) |
- d.addCallback(self.finishedFileTransfer) |
- |
- ## |
- ## Helper for FileSender |
- ## |
- def finishedFileTransfer(self, lastsent): |
- if lastsent != '\n': |
- line = '\r\n.' |
- else: |
- line = '.' |
- self.sendLine(line) |
- ## |
- |
- def _errArticle(self, failure): |
- print 'ARTICLE failed: ', failure |
- self.sendLine('423 bad article number') |
- |
- |
- def do_STAT(self, article = None): |
- defer = self.articleWork(article, 'STAT', self.factory.backend.articleRequest) |
- if defer: |
- defer.addCallbacks(self._gotStat, self._errStat) |
- |
- |
- def _gotStat(self, (index, id, article)): |
- self.currentIndex = index |
- self.sendLine('223 %d %s article retreived - request text separately' % (index, id)) |
- |
- |
- def _errStat(self, failure): |
- print 'STAT failed: ', failure |
- self.sendLine('423 bad article number') |
- |
- |
- def do_HEAD(self, article = None): |
- defer = self.articleWork(article, 'HEAD', self.factory.backend.headRequest) |
- if defer: |
- defer.addCallbacks(self._gotHead, self._errHead) |
- |
- |
- def _gotHead(self, (index, id, head)): |
- self.currentIndex = index |
- self.sendLine('221 %d %s article retrieved' % (index, id)) |
- self.transport.write(head + '\r\n') |
- self.sendLine('.') |
- |
- |
- def _errHead(self, failure): |
- print 'HEAD failed: ', failure |
- self.sendLine('423 no such article number in this group') |
- |
- |
- def do_BODY(self, article): |
- defer = self.articleWork(article, 'BODY', self.factory.backend.bodyRequest) |
- if defer: |
- defer.addCallbacks(self._gotBody, self._errBody) |
- |
- |
- def _gotBody(self, (index, id, body)): |
- if isinstance(body, types.StringType): |
- import warnings |
- warnings.warn( |
- "Returning the article as a string from `articleRequest' " |
- "is deprecated. Return a file-like object instead." |
- ) |
- body = StringIO.StringIO(body) |
- self.currentIndex = index |
- self.sendLine('221 %d %s article retrieved' % (index, id)) |
- self.lastsent = '' |
- s = basic.FileSender() |
- d = s.beginFileTransfer(body, self.transport) |
- d.addCallback(self.finishedFileTransfer) |
- |
- def _errBody(self, failure): |
- print 'BODY failed: ', failure |
- self.sendLine('423 no such article number in this group') |
- |
- |
- # NEXT and LAST are just STATs that increment currentIndex first. |
- # Accordingly, use the STAT callbacks. |
- def do_NEXT(self): |
- i = self.currentIndex + 1 |
- defer = self.factory.backend.articleRequest(self.currentGroup, i) |
- defer.addCallbacks(self._gotStat, self._errStat) |
- |
- |
- def do_LAST(self): |
- i = self.currentIndex - 1 |
- defer = self.factory.backend.articleRequest(self.currentGroup, i) |
- defer.addCallbacks(self._gotStat, self._errStat) |
- |
- |
- def do_MODE(self, cmd): |
- cmd = cmd.strip().upper() |
- if cmd == 'READER': |
- self.servingSlave = 0 |
- self.sendLine('200 Hello, you can post') |
- elif cmd == 'STREAM': |
- self.sendLine('500 Command not understood') |
- else: |
- # This is not a mistake |
- self.sendLine('500 Command not understood') |
- |
- |
- def do_QUIT(self): |
- self.sendLine('205 goodbye') |
- self.transport.loseConnection() |
- |
- |
- def do_HELP(self): |
- self.sendLine('100 help text follows') |
- self.sendLine('Read the RFC.') |
- self.sendLine('.') |
- |
- |
- def do_SLAVE(self): |
- self.sendLine('202 slave status noted') |
- self.servingeSlave = 1 |
- |
- |
- def do_XPATH(self, article): |
- # XPATH is a silly thing to have. No client has the right to ask |
- # for this piece of information from me, and so that is what I'll |
- # tell them. |
- self.sendLine('502 access restriction or permission denied') |
- |
- |
- def do_XINDEX(self, article): |
- # XINDEX is another silly command. The RFC suggests it be relegated |
- # to the history books, and who am I to disagree? |
- self.sendLine('502 access restriction or permission denied') |
- |
- |
- def do_XROVER(self, range = None): |
- self.do_XHDR(self, 'References', range) |
- |
- |
- def do_IHAVE(self, id): |
- self.factory.backend.articleExistsRequest(id).addCallback(self._foundArticle) |
- |
- |
- def _foundArticle(self, result): |
- if result: |
- self.sendLine('437 article rejected - do not try again') |
- else: |
- self.sendLine('335 send article to be transferred. End with <CR-LF>.<CR-LF>') |
- self.inputHandler = self._handleIHAVE |
- self.message = '' |
- |
- |
- def _handleIHAVE(self, line): |
- if line == '.': |
- self.inputHandler = None |
- self.factory.backend.postRequest( |
- self.message |
- ).addCallbacks(self._gotIHAVE, self._errIHAVE) |
- |
- self.message = '' |
- else: |
- self.message = self.message + line + '\r\n' |
- |
- |
- def _gotIHAVE(self, result): |
- self.sendLine('235 article transferred ok') |
- |
- |
- def _errIHAVE(self, failure): |
- print 'IHAVE failed: ', failure |
- self.sendLine('436 transfer failed - try again later') |
- |
- |
-class UsenetClientProtocol(NNTPClient): |
- """ |
- A client that connects to an NNTP server and asks for articles new |
- since a certain time. |
- """ |
- |
- def __init__(self, groups, date, storage): |
- """ |
- Fetch all new articles from the given groups since the |
- given date and dump them into the given storage. groups |
- is a list of group names. date is an integer or floating |
- point representing seconds since the epoch (GMT). storage is |
- any object that implements the NewsStorage interface. |
- """ |
- NNTPClient.__init__(self) |
- self.groups, self.date, self.storage = groups, date, storage |
- |
- |
- def connectionMade(self): |
- NNTPClient.connectionMade(self) |
- log.msg("Initiating update with remote host: " + str(self.transport.getPeer())) |
- self.setStream() |
- self.fetchNewNews(self.groups, self.date, '') |
- |
- |
- def articleExists(self, exists, article): |
- if exists: |
- self.fetchArticle(article) |
- else: |
- self.count = self.count - 1 |
- self.disregard = self.disregard + 1 |
- |
- |
- def gotNewNews(self, news): |
- self.disregard = 0 |
- self.count = len(news) |
- log.msg("Transfering " + str(self.count) + " articles from remote host: " + str(self.transport.getPeer())) |
- for i in news: |
- self.storage.articleExistsRequest(i).addCallback(self.articleExists, i) |
- |
- |
- def getNewNewsFailed(self, reason): |
- log.msg("Updated failed (" + reason + ") with remote host: " + str(self.transport.getPeer())) |
- self.quit() |
- |
- |
- def gotArticle(self, article): |
- self.storage.postRequest(article) |
- self.count = self.count - 1 |
- if not self.count: |
- log.msg("Completed update with remote host: " + str(self.transport.getPeer())) |
- if self.disregard: |
- log.msg("Disregarded %d articles." % (self.disregard,)) |
- self.factory.updateChecks(self.transport.getPeer()) |
- self.quit() |