| Index: third_party/twisted_8_1/twisted/news/database.py
|
| diff --git a/third_party/twisted_8_1/twisted/news/database.py b/third_party/twisted_8_1/twisted/news/database.py
|
| deleted file mode 100644
|
| index b39192f936dfa24909733b483ad658948d5b3155..0000000000000000000000000000000000000000
|
| --- a/third_party/twisted_8_1/twisted/news/database.py
|
| +++ /dev/null
|
| @@ -1,995 +0,0 @@
|
| -# -*- test-case-name: twisted.news.test.test_news -*-
|
| -# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
|
| -# See LICENSE for details.
|
| -
|
| -
|
| -"""
|
| -News server backend implementations
|
| -
|
| -Maintainer: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
|
| -
|
| -Future Plans: A PyFramer-based backend and a new backend interface that is
|
| -less NNTP specific
|
| -"""
|
| -
|
| -
|
| -from __future__ import nested_scopes
|
| -
|
| -from twisted.news.nntp import NNTPError
|
| -from twisted.mail import smtp
|
| -from twisted.internet import defer
|
| -from twisted.enterprise import adbapi
|
| -from twisted.persisted import dirdbm
|
| -
|
| -import getpass, pickle, time, socket, md5
|
| -import os
|
| -import StringIO
|
| -from zope.interface import implements, Interface
|
| -
|
| -
|
| -ERR_NOGROUP, ERR_NOARTICLE = range(2, 4) # XXX - put NNTP values here (I guess?)
|
| -
|
| -OVERVIEW_FMT = [
|
| - 'Subject', 'From', 'Date', 'Message-ID', 'References',
|
| - 'Bytes', 'Lines', 'Xref'
|
| -]
|
| -
|
| -def hexdigest(md5): #XXX: argh. 1.5.2 doesn't have this.
|
| - return ''.join(map(lambda x: hex(ord(x))[2:], md5.digest()))
|
| -
|
| -class Article:
|
| - def __init__(self, head, body):
|
| - self.body = body
|
| - self.headers = {}
|
| - header = None
|
| - for line in head.split('\r\n'):
|
| - if line[0] in ' \t':
|
| - i = list(self.headers[header])
|
| - i[1] += '\r\n' + line
|
| - else:
|
| - i = line.split(': ', 1)
|
| - header = i[0].lower()
|
| - self.headers[header] = tuple(i)
|
| -
|
| - if not self.getHeader('Message-ID'):
|
| - s = str(time.time()) + self.body
|
| - id = hexdigest(md5.md5(s)) + '@' + socket.gethostname()
|
| - self.putHeader('Message-ID', '<%s>' % id)
|
| -
|
| - if not self.getHeader('Bytes'):
|
| - self.putHeader('Bytes', str(len(self.body)))
|
| -
|
| - if not self.getHeader('Lines'):
|
| - self.putHeader('Lines', str(self.body.count('\n')))
|
| -
|
| - if not self.getHeader('Date'):
|
| - self.putHeader('Date', time.ctime(time.time()))
|
| -
|
| -
|
| - def getHeader(self, header):
|
| - h = header.lower()
|
| - if self.headers.has_key(h):
|
| - return self.headers[h][1]
|
| - else:
|
| - return ''
|
| -
|
| -
|
| - def putHeader(self, header, value):
|
| - self.headers[header.lower()] = (header, value)
|
| -
|
| -
|
| - def textHeaders(self):
|
| - headers = []
|
| - for i in self.headers.values():
|
| - headers.append('%s: %s' % i)
|
| - return '\r\n'.join(headers) + '\r\n'
|
| -
|
| - def overview(self):
|
| - xover = []
|
| - for i in OVERVIEW_FMT:
|
| - xover.append(self.getHeader(i))
|
| - return xover
|
| -
|
| -
|
| -class NewsServerError(Exception):
|
| - pass
|
| -
|
| -
|
| -class INewsStorage(Interface):
|
| - """
|
| - An interface for storing and requesting news articles
|
| - """
|
| -
|
| - def listRequest():
|
| - """
|
| - Returns a deferred whose callback will be passed a list of 4-tuples
|
| - containing (name, max index, min index, flags) for each news group
|
| - """
|
| -
|
| -
|
| - def subscriptionRequest():
|
| - """
|
| - Returns a deferred whose callback will be passed the list of
|
| - recommended subscription groups for new server users
|
| - """
|
| -
|
| -
|
| - def postRequest(message):
|
| - """
|
| - Returns a deferred whose callback will be invoked if 'message'
|
| - is successfully posted to one or more specified groups and
|
| - whose errback will be invoked otherwise.
|
| - """
|
| -
|
| -
|
| - def overviewRequest():
|
| - """
|
| - Returns a deferred whose callback will be passed the a list of
|
| - headers describing this server's overview format.
|
| - """
|
| -
|
| -
|
| - def xoverRequest(group, low, high):
|
| - """
|
| - Returns a deferred whose callback will be passed a list of xover
|
| - headers for the given group over the given range. If low is None,
|
| - the range starts at the first article. If high is None, the range
|
| - ends at the last article.
|
| - """
|
| -
|
| -
|
| - def xhdrRequest(group, low, high, header):
|
| - """
|
| - Returns a deferred whose callback will be passed a list of XHDR data
|
| - for the given group over the given range. If low is None,
|
| - the range starts at the first article. If high is None, the range
|
| - ends at the last article.
|
| - """
|
| -
|
| -
|
| - def listGroupRequest(group):
|
| - """
|
| - Returns a deferred whose callback will be passed a two-tuple of
|
| - (group name, [article indices])
|
| - """
|
| -
|
| -
|
| - def groupRequest(group):
|
| - """
|
| - Returns a deferred whose callback will be passed a five-tuple of
|
| - (group name, article count, highest index, lowest index, group flags)
|
| - """
|
| -
|
| -
|
| - def articleExistsRequest(id):
|
| - """
|
| - Returns a deferred whose callback will be passed with a true value
|
| - if a message with the specified Message-ID exists in the database
|
| - and with a false value otherwise.
|
| - """
|
| -
|
| -
|
| - def articleRequest(group, index, id = None):
|
| - """
|
| - Returns a deferred whose callback will be passed a file-like object
|
| - containing the full article text (headers and body) for the article
|
| - of the specified index in the specified group, and whose errback
|
| - will be invoked if the article or group does not exist. If id is
|
| - not None, index is ignored and the article with the given Message-ID
|
| - will be returned instead, along with its index in the specified
|
| - group.
|
| - """
|
| -
|
| -
|
| - def headRequest(group, index):
|
| - """
|
| - Returns a deferred whose callback will be passed the header for
|
| - the article of the specified index in the specified group, and
|
| - whose errback will be invoked if the article or group does not
|
| - exist.
|
| - """
|
| -
|
| -
|
| - def bodyRequest(group, index):
|
| - """
|
| - Returns a deferred whose callback will be passed the body for
|
| - the article of the specified index in the specified group, and
|
| - whose errback will be invoked if the article or group does not
|
| - exist.
|
| - """
|
| -
|
| -class NewsStorage:
|
| - """
|
| - Backwards compatibility class -- There is no reason to inherit from this,
|
| - just implement INewsStorage instead.
|
| - """
|
| - def listRequest(self):
|
| - raise NotImplementedError()
|
| - def subscriptionRequest(self):
|
| - raise NotImplementedError()
|
| - def postRequest(self, message):
|
| - raise NotImplementedError()
|
| - def overviewRequest(self):
|
| - return defer.succeed(OVERVIEW_FMT)
|
| - def xoverRequest(self, group, low, high):
|
| - raise NotImplementedError()
|
| - def xhdrRequest(self, group, low, high, header):
|
| - raise NotImplementedError()
|
| - def listGroupRequest(self, group):
|
| - raise NotImplementedError()
|
| - def groupRequest(self, group):
|
| - raise NotImplementedError()
|
| - def articleExistsRequest(self, id):
|
| - raise NotImplementedError()
|
| - def articleRequest(self, group, index, id = None):
|
| - raise NotImplementedError()
|
| - def headRequest(self, group, index):
|
| - raise NotImplementedError()
|
| - def bodyRequest(self, group, index):
|
| - raise NotImplementedError()
|
| -
|
| -
|
| -class PickleStorage:
|
| - """A trivial NewsStorage implementation using pickles
|
| -
|
| - Contains numerous flaws and is generally unsuitable for any
|
| - real applications. Consider yourself warned!
|
| - """
|
| -
|
| - implements(INewsStorage)
|
| -
|
| - sharedDBs = {}
|
| -
|
| - def __init__(self, filename, groups = None, moderators = ()):
|
| - self.datafile = filename
|
| - self.load(filename, groups, moderators)
|
| -
|
| -
|
| - def getModerators(self, groups):
|
| - # first see if any groups are moderated. if so, nothing gets posted,
|
| - # but the whole messages gets forwarded to the moderator address
|
| - moderators = []
|
| - for group in groups:
|
| - moderators.append(self.db['moderators'].get(group, None))
|
| - return filter(None, moderators)
|
| -
|
| -
|
| - def notifyModerators(self, moderators, article):
|
| - # Moderated postings go through as long as they have an Approved
|
| - # header, regardless of what the value is
|
| - article.putHeader('To', ', '.join(moderators))
|
| - return smtp.sendEmail(
|
| - 'twisted@' + socket.gethostname(),
|
| - moderators,
|
| - article.body,
|
| - dict(article.headers.values())
|
| - )
|
| -
|
| -
|
| - def listRequest(self):
|
| - "Returns a list of 4-tuples: (name, max index, min index, flags)"
|
| - l = self.db['groups']
|
| - r = []
|
| - for i in l:
|
| - if len(self.db[i].keys()):
|
| - low = min(self.db[i].keys())
|
| - high = max(self.db[i].keys()) + 1
|
| - else:
|
| - low = high = 0
|
| - if self.db['moderators'].has_key(i):
|
| - flags = 'm'
|
| - else:
|
| - flags = 'y'
|
| - r.append((i, high, low, flags))
|
| - return defer.succeed(r)
|
| -
|
| - def subscriptionRequest(self):
|
| - return defer.succeed(['alt.test'])
|
| -
|
| - def postRequest(self, message):
|
| - cleave = message.find('\r\n\r\n')
|
| - headers, article = message[:cleave], message[cleave + 4:]
|
| -
|
| - a = Article(headers, article)
|
| - groups = a.getHeader('Newsgroups').split()
|
| - xref = []
|
| -
|
| - # Check moderated status
|
| - moderators = self.getModerators(groups)
|
| - if moderators and not a.getHeader('Approved'):
|
| - return self.notifyModerators(moderators, a)
|
| -
|
| - for group in groups:
|
| - if self.db.has_key(group):
|
| - if len(self.db[group].keys()):
|
| - index = max(self.db[group].keys()) + 1
|
| - else:
|
| - index = 1
|
| - xref.append((group, str(index)))
|
| - self.db[group][index] = a
|
| -
|
| - if len(xref) == 0:
|
| - return defer.fail(None)
|
| -
|
| - a.putHeader('Xref', '%s %s' % (
|
| - socket.gethostname().split()[0],
|
| - ''.join(map(lambda x: ':'.join(x), xref))
|
| - ))
|
| -
|
| - self.flush()
|
| - return defer.succeed(None)
|
| -
|
| -
|
| - def overviewRequest(self):
|
| - return defer.succeed(OVERVIEW_FMT)
|
| -
|
| -
|
| - def xoverRequest(self, group, low, high):
|
| - if not self.db.has_key(group):
|
| - return defer.succeed([])
|
| - r = []
|
| - for i in self.db[group].keys():
|
| - if (low is None or i >= low) and (high is None or i <= high):
|
| - r.append([str(i)] + self.db[group][i].overview())
|
| - return defer.succeed(r)
|
| -
|
| -
|
| - def xhdrRequest(self, group, low, high, header):
|
| - if not self.db.has_key(group):
|
| - return defer.succeed([])
|
| - r = []
|
| - for i in self.db[group].keys():
|
| - if low is None or i >= low and high is None or i <= high:
|
| - r.append((i, self.db[group][i].getHeader(header)))
|
| - return defer.succeed(r)
|
| -
|
| -
|
| - def listGroupRequest(self, group):
|
| - if self.db.has_key(group):
|
| - return defer.succeed((group, self.db[group].keys()))
|
| - else:
|
| - return defer.fail(None)
|
| -
|
| - def groupRequest(self, group):
|
| - if self.db.has_key(group):
|
| - if len(self.db[group].keys()):
|
| - num = len(self.db[group].keys())
|
| - low = min(self.db[group].keys())
|
| - high = max(self.db[group].keys())
|
| - else:
|
| - num = low = high = 0
|
| - flags = 'y'
|
| - return defer.succeed((group, num, high, low, flags))
|
| - else:
|
| - return defer.fail(ERR_NOGROUP)
|
| -
|
| -
|
| - def articleExistsRequest(self, id):
|
| - for g in self.db.values():
|
| - for a in g.values():
|
| - if a.getHeader('Message-ID') == id:
|
| - return defer.succeed(1)
|
| - return defer.succeed(0)
|
| -
|
| -
|
| - def articleRequest(self, group, index, id = None):
|
| - if id is not None:
|
| - raise NotImplementedError
|
| -
|
| - if self.db.has_key(group):
|
| - if self.db[group].has_key(index):
|
| - a = self.db[group][index]
|
| - return defer.succeed((
|
| - index,
|
| - a.getHeader('Message-ID'),
|
| - StringIO.StringIO(a.textHeaders() + '\r\n' + a.body)
|
| - ))
|
| - else:
|
| - return defer.fail(ERR_NOARTICLE)
|
| - else:
|
| - return defer.fail(ERR_NOGROUP)
|
| -
|
| -
|
| - def headRequest(self, group, index):
|
| - if self.db.has_key(group):
|
| - if self.db[group].has_key(index):
|
| - a = self.db[group][index]
|
| - return defer.succeed((index, a.getHeader('Message-ID'), a.textHeaders()))
|
| - else:
|
| - return defer.fail(ERR_NOARTICLE)
|
| - else:
|
| - return defer.fail(ERR_NOGROUP)
|
| -
|
| -
|
| - def bodyRequest(self, group, index):
|
| - if self.db.has_key(group):
|
| - if self.db[group].has_key(index):
|
| - a = self.db[group][index]
|
| - return defer.succeed((index, a.getHeader('Message-ID'), StringIO.StringIO(a.body)))
|
| - else:
|
| - return defer.fail(ERR_NOARTICLE)
|
| - else:
|
| - return defer.fail(ERR_NOGROUP)
|
| -
|
| -
|
| - def flush(self):
|
| - pickle.dump(self.db, open(self.datafile, 'w'))
|
| -
|
| -
|
| - def load(self, filename, groups = None, moderators = ()):
|
| - if PickleStorage.sharedDBs.has_key(filename):
|
| - self.db = PickleStorage.sharedDBs[filename]
|
| - else:
|
| - try:
|
| - self.db = pickle.load(open(filename))
|
| - PickleStorage.sharedDBs[filename] = self.db
|
| - except IOError, e:
|
| - self.db = PickleStorage.sharedDBs[filename] = {}
|
| - self.db['groups'] = groups
|
| - if groups is not None:
|
| - for i in groups:
|
| - self.db[i] = {}
|
| - self.db['moderators'] = dict(moderators)
|
| - self.flush()
|
| -
|
| -
|
| -class Group:
|
| - name = None
|
| - flags = ''
|
| - minArticle = 1
|
| - maxArticle = 0
|
| - articles = None
|
| -
|
| - def __init__(self, name, flags = 'y'):
|
| - self.name = name
|
| - self.flags = flags
|
| - self.articles = {}
|
| -
|
| -
|
| -class NewsShelf:
|
| - """
|
| - A NewStorage implementation using Twisted's dirdbm persistence module.
|
| - """
|
| -
|
| - implements(INewsStorage)
|
| -
|
| - def __init__(self, mailhost, path):
|
| - self.path = path
|
| - self.mailhost = mailhost
|
| -
|
| - if not os.path.exists(path):
|
| - os.mkdir(path)
|
| -
|
| - self.dbm = dirdbm.Shelf(os.path.join(path, "newsshelf"))
|
| - if not len(self.dbm.keys()):
|
| - self.initialize()
|
| -
|
| -
|
| - def initialize(self):
|
| - # A dictionary of group name/Group instance items
|
| - self.dbm['groups'] = dirdbm.Shelf(os.path.join(self.path, 'groups'))
|
| -
|
| - # A dictionary of group name/email address
|
| - self.dbm['moderators'] = dirdbm.Shelf(os.path.join(self.path, 'moderators'))
|
| -
|
| - # A list of group names
|
| - self.dbm['subscriptions'] = []
|
| -
|
| - # A dictionary of MessageID strings/xref lists
|
| - self.dbm['Message-IDs'] = dirdbm.Shelf(os.path.join(self.path, 'Message-IDs'))
|
| -
|
| -
|
| - def addGroup(self, name, flags):
|
| - self.dbm['groups'][name] = Group(name, flags)
|
| -
|
| -
|
| - def addSubscription(self, name):
|
| - self.dbm['subscriptions'] = self.dbm['subscriptions'] + [name]
|
| -
|
| -
|
| - def addModerator(self, group, email):
|
| - self.dbm['moderators'][group] = email
|
| -
|
| -
|
| - def listRequest(self):
|
| - result = []
|
| - for g in self.dbm['groups'].values():
|
| - result.append((g.name, g.maxArticle, g.minArticle, g.flags))
|
| - return defer.succeed(result)
|
| -
|
| -
|
| - def subscriptionRequest(self):
|
| - return defer.succeed(self.dbm['subscriptions'])
|
| -
|
| -
|
| - def getModerator(self, groups):
|
| - # first see if any groups are moderated. if so, nothing gets posted,
|
| - # but the whole messages gets forwarded to the moderator address
|
| - for group in groups:
|
| - try:
|
| - return self.dbm['moderators'][group]
|
| - except KeyError:
|
| - pass
|
| - return None
|
| -
|
| -
|
| - def notifyModerator(self, moderator, article):
|
| - # Moderated postings go through as long as they have an Approved
|
| - # header, regardless of what the value is
|
| - print 'To is ', moderator
|
| - article.putHeader('To', moderator)
|
| - return smtp.sendEmail(
|
| - self.mailhost,
|
| - 'twisted-news@' + socket.gethostname(),
|
| - moderator,
|
| - article.body,
|
| - dict(article.headers.values())
|
| - )
|
| -
|
| -
|
| - def postRequest(self, message):
|
| - cleave = message.find('\r\n\r\n')
|
| - headers, article = message[:cleave], message[cleave + 4:]
|
| -
|
| - article = Article(headers, article)
|
| - groups = article.getHeader('Newsgroups').split()
|
| - xref = []
|
| -
|
| - # Check for moderated status
|
| - moderator = self.getModerator(groups)
|
| - if moderator and not article.getHeader('Approved'):
|
| - return self.notifyModerator(moderator, article)
|
| -
|
| -
|
| - for group in groups:
|
| - try:
|
| - g = self.dbm['groups'][group]
|
| - except KeyError:
|
| - pass
|
| - else:
|
| - index = g.maxArticle + 1
|
| - g.maxArticle += 1
|
| - g.articles[index] = article
|
| - xref.append((group, str(index)))
|
| - self.dbm['groups'][group] = g
|
| -
|
| - if not xref:
|
| - return defer.fail(NewsServerError("No groups carried: " + ' '.join(groups)))
|
| -
|
| - article.putHeader('Xref', '%s %s' % (socket.gethostname().split()[0], ' '.join(map(lambda x: ':'.join(x), xref))))
|
| - self.dbm['Message-IDs'][article.getHeader('Message-ID')] = xref
|
| - return defer.succeed(None)
|
| -
|
| -
|
| - def overviewRequest(self):
|
| - return defer.succeed(OVERVIEW_FMT)
|
| -
|
| -
|
| - def xoverRequest(self, group, low, high):
|
| - if not self.dbm['groups'].has_key(group):
|
| - return defer.succeed([])
|
| -
|
| - if low is None:
|
| - low = 0
|
| - if high is None:
|
| - high = self.dbm['groups'][group].maxArticle
|
| - r = []
|
| - for i in range(low, high + 1):
|
| - if self.dbm['groups'][group].articles.has_key(i):
|
| - r.append([str(i)] + self.dbm['groups'][group].articles[i].overview())
|
| - return defer.succeed(r)
|
| -
|
| -
|
| - def xhdrRequest(self, group, low, high, header):
|
| - if group not in self.dbm['groups']:
|
| - return defer.succeed([])
|
| -
|
| - if low is None:
|
| - low = 0
|
| - if high is None:
|
| - high = self.dbm['groups'][group].maxArticle
|
| - r = []
|
| - for i in range(low, high + 1):
|
| - if self.dbm['groups'][group].articles.has_key(i):
|
| - r.append((i, self.dbm['groups'][group].articles[i].getHeader(header)))
|
| - return defer.succeed(r)
|
| -
|
| -
|
| - def listGroupRequest(self, group):
|
| - if self.dbm['groups'].has_key(group):
|
| - return defer.succeed((group, self.dbm['groups'][group].articles.keys()))
|
| - return defer.fail(NewsServerError("No such group: " + group))
|
| -
|
| -
|
| - def groupRequest(self, group):
|
| - try:
|
| - g = self.dbm['groups'][group]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such group: " + group))
|
| - else:
|
| - flags = g.flags
|
| - low = g.minArticle
|
| - high = g.maxArticle
|
| - num = high - low + 1
|
| - return defer.succeed((group, num, high, low, flags))
|
| -
|
| -
|
| - def articleExistsRequest(self, id):
|
| - return defer.succeed(id in self.dbm['Message-IDs'])
|
| -
|
| -
|
| - def articleRequest(self, group, index, id = None):
|
| - if id is not None:
|
| - try:
|
| - xref = self.dbm['Message-IDs'][id]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such article: " + id))
|
| - else:
|
| - group, index = xref[0]
|
| - index = int(index)
|
| -
|
| - try:
|
| - a = self.dbm['groups'][group].articles[index]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such group: " + group))
|
| - else:
|
| - return defer.succeed((
|
| - index,
|
| - a.getHeader('Message-ID'),
|
| - StringIO.StringIO(a.textHeaders() + '\r\n' + a.body)
|
| - ))
|
| -
|
| -
|
| - def headRequest(self, group, index, id = None):
|
| - if id is not None:
|
| - try:
|
| - xref = self.dbm['Message-IDs'][id]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such article: " + id))
|
| - else:
|
| - group, index = xref[0]
|
| - index = int(index)
|
| -
|
| - try:
|
| - a = self.dbm['groups'][group].articles[index]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such group: " + group))
|
| - else:
|
| - return defer.succeed((index, a.getHeader('Message-ID'), a.textHeaders()))
|
| -
|
| -
|
| - def bodyRequest(self, group, index, id = None):
|
| - if id is not None:
|
| - try:
|
| - xref = self.dbm['Message-IDs'][id]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such article: " + id))
|
| - else:
|
| - group, index = xref[0]
|
| - index = int(index)
|
| -
|
| - try:
|
| - a = self.dbm['groups'][group].articles[index]
|
| - except KeyError:
|
| - return defer.fail(NewsServerError("No such group: " + group))
|
| - else:
|
| - return defer.succeed((index, a.getHeader('Message-ID'), StringIO.StringIO(a.body)))
|
| -
|
| -
|
| -class NewsStorageAugmentation:
|
| - """
|
| - A NewsStorage implementation using Twisted's asynchronous DB-API
|
| - """
|
| -
|
| - implements(INewsStorage)
|
| -
|
| - schema = """
|
| -
|
| - CREATE TABLE groups (
|
| - group_id SERIAL,
|
| - name VARCHAR(80) NOT NULL,
|
| -
|
| - flags INTEGER DEFAULT 0 NOT NULL
|
| - );
|
| -
|
| - CREATE UNIQUE INDEX group_id_index ON groups (group_id);
|
| - CREATE UNIQUE INDEX name_id_index ON groups (name);
|
| -
|
| - CREATE TABLE articles (
|
| - article_id SERIAL,
|
| - message_id TEXT,
|
| -
|
| - header TEXT,
|
| - body TEXT
|
| - );
|
| -
|
| - CREATE UNIQUE INDEX article_id_index ON articles (article_id);
|
| - CREATE UNIQUE INDEX article_message_index ON articles (message_id);
|
| -
|
| - CREATE TABLE postings (
|
| - group_id INTEGER,
|
| - article_id INTEGER,
|
| - article_index INTEGER NOT NULL
|
| - );
|
| -
|
| - CREATE UNIQUE INDEX posting_article_index ON postings (article_id);
|
| -
|
| - CREATE TABLE subscriptions (
|
| - group_id INTEGER
|
| - );
|
| -
|
| - CREATE TABLE overview (
|
| - header TEXT
|
| - );
|
| - """
|
| -
|
| - def __init__(self, info):
|
| - self.info = info
|
| - self.dbpool = adbapi.ConnectionPool(**self.info)
|
| -
|
| -
|
| - def __setstate__(self, state):
|
| - self.__dict__ = state
|
| - self.info['password'] = getpass.getpass('Database password for %s: ' % (self.info['user'],))
|
| - self.dbpool = adbapi.ConnectionPool(**self.info)
|
| - del self.info['password']
|
| -
|
| -
|
| - def listRequest(self):
|
| - # COALESCE may not be totally portable
|
| - # it is shorthand for
|
| - # CASE WHEN (first parameter) IS NOT NULL then (first parameter) ELSE (second parameter) END
|
| - sql = """
|
| - SELECT groups.name,
|
| - COALESCE(MAX(postings.article_index), 0),
|
| - COALESCE(MIN(postings.article_index), 0),
|
| - groups.flags
|
| - FROM groups LEFT OUTER JOIN postings
|
| - ON postings.group_id = groups.group_id
|
| - GROUP BY groups.name, groups.flags
|
| - ORDER BY groups.name
|
| - """
|
| - return self.dbpool.runQuery(sql)
|
| -
|
| -
|
| - def subscriptionRequest(self):
|
| - sql = """
|
| - SELECT groups.name FROM groups,subscriptions WHERE groups.group_id = subscriptions.group_id
|
| - """
|
| - return self.dbpool.runQuery(sql)
|
| -
|
| -
|
| - def postRequest(self, message):
|
| - cleave = message.find('\r\n\r\n')
|
| - headers, article = message[:cleave], message[cleave + 4:]
|
| - article = Article(headers, article)
|
| - return self.dbpool.runInteraction(self._doPost, article)
|
| -
|
| -
|
| - def _doPost(self, transaction, article):
|
| - # Get the group ids
|
| - groups = article.getHeader('Newsgroups').split()
|
| - if not len(groups):
|
| - raise NNTPError('Missing Newsgroups header')
|
| -
|
| - sql = """
|
| - SELECT name, group_id FROM groups
|
| - WHERE name IN (%s)
|
| - """ % (', '.join([("'%s'" % (adbapi.safe(group),)) for group in groups]),)
|
| -
|
| - transaction.execute(sql)
|
| - result = transaction.fetchall()
|
| -
|
| - # No relevant groups, bye bye!
|
| - if not len(result):
|
| - raise NNTPError('None of groups in Newsgroup header carried')
|
| -
|
| - # Got some groups, now find the indices this article will have in each
|
| - sql = """
|
| - SELECT groups.group_id, COALESCE(MAX(postings.article_index), 0) + 1
|
| - FROM groups LEFT OUTER JOIN postings
|
| - ON postings.group_id = groups.group_id
|
| - WHERE groups.group_id IN (%s)
|
| - GROUP BY groups.group_id
|
| - """ % (', '.join([("%d" % (id,)) for (group, id) in result]),)
|
| -
|
| - transaction.execute(sql)
|
| - indices = transaction.fetchall()
|
| -
|
| - if not len(indices):
|
| - raise NNTPError('Internal server error - no indices found')
|
| -
|
| - # Associate indices with group names
|
| - gidToName = dict([(b, a) for (a, b) in result])
|
| - gidToIndex = dict(indices)
|
| -
|
| - nameIndex = []
|
| - for i in gidToName:
|
| - nameIndex.append((gidToName[i], gidToIndex[i]))
|
| -
|
| - # Build xrefs
|
| - xrefs = socket.gethostname().split()[0]
|
| - xrefs = xrefs + ' ' + ' '.join([('%s:%d' % (group, id)) for (group, id) in nameIndex])
|
| - article.putHeader('Xref', xrefs)
|
| -
|
| - # Hey! The article is ready to be posted! God damn f'in finally.
|
| - sql = """
|
| - INSERT INTO articles (message_id, header, body)
|
| - VALUES ('%s', '%s', '%s')
|
| - """ % (
|
| - adbapi.safe(article.getHeader('Message-ID')),
|
| - adbapi.safe(article.textHeaders()),
|
| - adbapi.safe(article.body)
|
| - )
|
| -
|
| - transaction.execute(sql)
|
| -
|
| - # Now update the posting to reflect the groups to which this belongs
|
| - for gid in gidToName:
|
| - sql = """
|
| - INSERT INTO postings (group_id, article_id, article_index)
|
| - VALUES (%d, (SELECT last_value FROM articles_article_id_seq), %d)
|
| - """ % (gid, gidToIndex[gid])
|
| - transaction.execute(sql)
|
| -
|
| - return len(nameIndex)
|
| -
|
| -
|
| - def overviewRequest(self):
|
| - sql = """
|
| - SELECT header FROM overview
|
| - """
|
| - return self.dbpool.runQuery(sql).addCallback(lambda result: [header[0] for header in result])
|
| -
|
| -
|
| - def xoverRequest(self, group, low, high):
|
| - sql = """
|
| - SELECT postings.article_index, articles.header
|
| - FROM articles,postings,groups
|
| - WHERE postings.group_id = groups.group_id
|
| - AND groups.name = '%s'
|
| - AND postings.article_id = articles.article_id
|
| - %s
|
| - %s
|
| - """ % (
|
| - adbapi.safe(group),
|
| - low is not None and "AND postings.article_index >= %d" % (low,) or "",
|
| - high is not None and "AND postings.article_index <= %d" % (high,) or ""
|
| - )
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda results: [
|
| - [id] + Article(header, None).overview() for (id, header) in results
|
| - ]
|
| - )
|
| -
|
| -
|
| - def xhdrRequest(self, group, low, high, header):
|
| - sql = """
|
| - SELECT articles.header
|
| - FROM groups,postings,articles
|
| - WHERE groups.name = '%s' AND postings.group_id = groups.group_id
|
| - AND postings.article_index >= %d
|
| - AND postings.article_index <= %d
|
| - """ % (adbapi.safe(group), low, high)
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda results: [
|
| - (i, Article(h, None).getHeader(h)) for (i, h) in results
|
| - ]
|
| - )
|
| -
|
| -
|
| - def listGroupRequest(self, group):
|
| - sql = """
|
| - SELECT postings.article_index FROM postings,groups
|
| - WHERE postings.group_id = groups.group_id
|
| - AND groups.name = '%s'
|
| - """ % (adbapi.safe(group),)
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda results, group = group: (group, [res[0] for res in results])
|
| - )
|
| -
|
| -
|
| - def groupRequest(self, group):
|
| - sql = """
|
| - SELECT groups.name,
|
| - COUNT(postings.article_index),
|
| - COALESCE(MAX(postings.article_index), 0),
|
| - COALESCE(MIN(postings.article_index), 0),
|
| - groups.flags
|
| - FROM groups LEFT OUTER JOIN postings
|
| - ON postings.group_id = groups.group_id
|
| - WHERE groups.name = '%s'
|
| - GROUP BY groups.name, groups.flags
|
| - """ % (adbapi.safe(group),)
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda results: tuple(results[0])
|
| - )
|
| -
|
| -
|
| - def articleExistsRequest(self, id):
|
| - sql = """
|
| - SELECT COUNT(message_id) FROM articles
|
| - WHERE message_id = '%s'
|
| - """ % (adbapi.safe(id),)
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda result: bool(result[0][0])
|
| - )
|
| -
|
| -
|
| - def articleRequest(self, group, index, id = None):
|
| - if id is not None:
|
| - sql = """
|
| - SELECT postings.article_index, articles.message_id, articles.header, articles.body
|
| - FROM groups,postings LEFT OUTER JOIN articles
|
| - ON articles.message_id = '%s'
|
| - WHERE groups.name = '%s'
|
| - AND groups.group_id = postings.group_id
|
| - """ % (adbapi.safe(id), adbapi.safe(group))
|
| - else:
|
| - sql = """
|
| - SELECT postings.article_index, articles.message_id, articles.header, articles.body
|
| - FROM groups,articles LEFT OUTER JOIN postings
|
| - ON postings.article_id = articles.article_id
|
| - WHERE postings.article_index = %d
|
| - AND postings.group_id = groups.group_id
|
| - AND groups.name = '%s'
|
| - """ % (index, adbapi.safe(group))
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda result: (
|
| - result[0][0],
|
| - result[0][1],
|
| - StringIO.StringIO(result[0][2] + '\r\n' + result[0][3])
|
| - )
|
| - )
|
| -
|
| -
|
| - def headRequest(self, group, index):
|
| - sql = """
|
| - SELECT postings.article_index, articles.message_id, articles.header
|
| - FROM groups,articles LEFT OUTER JOIN postings
|
| - ON postings.article_id = articles.article_id
|
| - WHERE postings.article_index = %d
|
| - AND postings.group_id = groups.group_id
|
| - AND groups.name = '%s'
|
| - """ % (index, adbapi.safe(group))
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(lambda result: result[0])
|
| -
|
| -
|
| - def bodyRequest(self, group, index):
|
| - sql = """
|
| - SELECT postings.article_index, articles.message_id, articles.body
|
| - FROM groups,articles LEFT OUTER JOIN postings
|
| - ON postings.article_id = articles.article_id
|
| - WHERE postings.article_index = %d
|
| - AND postings.group_id = groups.group_id
|
| - AND groups.name = '%s'
|
| - """ % (index, adbapi.safe(group))
|
| -
|
| - return self.dbpool.runQuery(sql).addCallback(
|
| - lambda result: result[0]
|
| - ).addCallback(
|
| - lambda (index, id, body): (index, id, StringIO.StringIO(body))
|
| - )
|
| -
|
| -####
|
| -#### XXX - make these static methods some day
|
| -####
|
| -def makeGroupSQL(groups):
|
| - res = ''
|
| - for g in groups:
|
| - res = res + """\n INSERT INTO groups (name) VALUES ('%s');\n""" % (adbapi.safe(g),)
|
| - return res
|
| -
|
| -
|
| -def makeOverviewSQL():
|
| - res = ''
|
| - for o in OVERVIEW_FMT:
|
| - res = res + """\n INSERT INTO overview (header) VALUES ('%s');\n""" % (adbapi.safe(o),)
|
| - return res
|
|
|