예제 #1
0
class BlogRPC(xmlrpc.XMLRPC):
    """Publishes stuff"""

    def __init__(self, store):
        xmlrpc.XMLRPC.__init__(self)
        self.store = store
    
    def xmlrpc_publish(self, author, title, category, content):
        newid = IBlog(self.store).getNextId()
        newPost = Post(store=self.store,
                       id=newid,
                       author=unicode(author),
                       title=unicode(title),
                       category=unicode(category),
                       content=unicode(content))
        IBlog(self.store).addNewPost(newPost)
        return 'Successfully added post number %s' % newid
    xmlrpc_publish = transacted(xmlrpc_publish)

    def xmlrpc_edit(self, id, author, title, category, content):
        post = IBlog(self.store).getOne(id)
        post.author = author
        post.title = title
        post.category = category
        post.content = content
        post.setModified()
        return 'Successfully modified post number %s' % id
    xmlrpc_edit = transacted(xmlrpc_edit)
        
    def xmlrpc_entries(self, count):
        return [(entry.id, entry.author, entry.category, entry.title, entry.content) \
                for entry in IBlog(self.store).getPosts(count)]
    
    xmlrpc_entries = transacted(xmlrpc_entries)
예제 #2
0
class TransactedMethodItem(item.Item):
    """
    Helper class for testing the L{axiom.item.transacted} decorator.
    """
    value = text()
    calledWith = inmemory()

    def method(self, a, b, c):
        self.value = u"changed"
        self.calledWith = [a, b, c]
        raise Exception("TransactedMethodItem.method test exception")
    method.attribute = 'value'
    method = item.transacted(method)
예제 #3
0
class DraftMIMEMessageStorer(_MIMEMessageStorerBase):
    """
    Persistent MIME parser/storage class for draft messages.
    """
    def messageDone(self):
        """
        Create a draft Message and associate the Part with it.
        """
        result = super(DraftMIMEMessageStorer, self).messageDone()
        self.message = exmess.Message.createDraft(self.store, self.part,
                                                  self.source)
        self.setMessageAttributes()
        return result

    messageDone = item.transacted(messageDone)
예제 #4
0
class IncomingMIMEMessageStorer(_MIMEMessageStorerBase):
    """
    Persistent MIME parser/storage class for messages received by the
    system from external entities.
    """
    def messageDone(self):
        """
        Create an Incoming message and associate the Part with it.
        """
        result = super(IncomingMIMEMessageStorer, self).messageDone()
        self.message = exmess.Message.createIncoming(self.store, self.part,
                                                     self.source)
        self.setMessageAttributes()
        return result

    messageDone = item.transacted(messageDone)
예제 #5
0
class Ticket(Item):
    schemaVersion = 2
    typeName = 'ticket'

    issuer = reference(allowNone=False)
    booth = reference(allowNone=False)
    avatar = reference()
    claimed = integer(default=0)
    product = reference(allowNone=False)

    email = text()
    nonce = text()

    def __init__(self, **kw):
        super(Ticket, self).__init__(**kw)
        self.booth.createdTicketCount += 1
        self.nonce = _generateNonce()

    def claim(self):
        if not self.claimed:
            log.msg("Claiming a ticket for the first time for %r" %
                    (self.email, ))
            username, domain = self.email.split('@', 1)
            realm = IRealm(self.store)
            acct = realm.accountByAddress(username, domain)
            if acct is None:
                acct = realm.addAccount(username, domain, None)
            self.avatar = acct
            self.claimed += 1
            self.booth.ticketClaimed(self)
            self.product.installProductOn(IBeneficiary(self.avatar))
        else:
            log.msg("Ignoring re-claim of ticket for: %r" % (self.email, ))
        return self.avatar

    claim = transacted(claim)
예제 #6
0
class TicketBooth(Item, PrefixURLMixin):
    implements(ISiteRootPlugin)

    typeName = 'ticket_powerup'
    schemaVersion = 1

    sessioned = True

    claimedTicketCount = integer(default=0)
    createdTicketCount = integer(default=0)

    defaultTicketEmail = text(default=None)

    prefixURL = 'ticket'

    def createResource(self):
        return TicketClaimer(self)

    def createTicket(self, issuer, email, product):
        t = self.store.findOrCreate(Ticket,
                                    product=product,
                                    booth=self,
                                    avatar=None,
                                    issuer=issuer,
                                    email=email)
        return t

    createTicket = transacted(createTicket)

    def ticketClaimed(self, ticket):
        self.claimedTicketCount += 1

    def ticketLink(self, domainName, httpPortNumber, nonce):
        httpPort = ''
        httpScheme = 'http'

        if httpPortNumber == 443:
            httpScheme = 'https'
        elif httpPortNumber != 80:
            httpPort = ':' + str(httpPortNumber)

        return '%s://%s%s/%s/%s' % (httpScheme, domainName, httpPort,
                                    self.prefixURL, nonce)

    def issueViaEmail(self,
                      issuer,
                      email,
                      product,
                      templateData,
                      domainName,
                      httpPort=80):
        """
        Send a ticket via email to the supplied address, which, when claimed, will
        create an avatar and allow the given product to endow it with
        things.

        @param issuer: An object, preferably a user, to track who issued this
        ticket.

        @param email: a str, formatted as an rfc2821 email address
        (user@domain) -- source routes not allowed.

        @param product: an instance of L{Product}

        @param domainName: a domain name, used as the domain part of the
        sender's address, and as the web server to generate a link to within
        the email.

        @param httpPort: a port number for the web server running on domainName

        @param templateData: A string containing an rfc2822-format email
        message, which will have several python values interpolated into it
        dictwise:

            %(from)s: To be used for the From: header; will contain an
             rfc2822-format address.

            %(to)s: the address that we are going to send to.

            %(date)s: an rfc2822-format date.

            %(message-id)s: an rfc2822 message-id

            %(link)s: an HTTP URL that we are generating a link to.

        """

        ticket = self.createTicket(issuer, unicode(email, 'ascii'), product)
        nonce = ticket.nonce

        signupInfo = {
            'from': 'signup@' + domainName,
            'to': email,
            'date': rfc822.formatdate(),
            'message-id': smtp.messageid(),
            'link': self.ticketLink(domainName, httpPort, nonce)
        }

        msg = templateData % signupInfo

        return ticket, _sendEmail(signupInfo['from'], email, msg)
예제 #7
0
class BlogMessage:
    implements(smtp.IMessage)

    def __init__(self, store):
        self.lines = []
        self.store = store

    def lineReceived(self, line):
        self.lines.append(line)

    def eomReceived(self):
        post = {}
        isContent = False
        ctnt_buff = []
        recipients = self.lines[0]
        addrs = []

        for recipient in recipients:
            if '@' not in recipient.orig.addrstr:
                # Avoid answering to bounches
                if not recipient.orig.addrstr == '<>':
                    addrs.append(recipient.orig.addrstr[:-1] + '@' +
                                 recipient.orig.domain + '>')
            else:
                # Avoid answering to bounches
                if not recipient.orig.addrstr == '<#@[]>':
                    addrs.append(recipient.orig.addrstr)

        for line in self.lines[1:]:
            if not isContent:
                try:
                    field, value = line.split(':', 1)
                except ValueError:
                    continue
                if field.lower() != 'content':
                    post[field.lower()] = value.strip()
                else:
                    isContent = True
                    ctnt_buff.append(value.strip())
            else:
                ctnt_buff.append(line.strip())
        post['content'] = '\n'.join(ctnt_buff)

        for header in 'content author category title'.split():
            if not post.has_key(header):
                self.lines = []
                return defer.fail(None)
        if post.has_key('id'):
            oldpost = IBlog(self.store).getOne(int(post['id']))
            oldpost.author = unicode(post['author'])
            oldpost.title = unicode(post['title'])
            oldpost.category = unicode(post['category'])
            oldpost.content = unicode(post['content'])
            oldpost.setModified()
            action = 'modified'
            id = post['id']
        else:
            newid = IBlog(self.store).getNextId()
            newPost = Post(store=self.store,
                           id=newid,
                           author=unicode(post['author']),
                           title=unicode(post['title']),
                           category=unicode(post['category']),
                           content=unicode(post['content']))
            IBlog(self.store).addNewPost(newPost)
            action = 'added'
            id = newid
        self.lines = []
        msg = """From: <%s>
Subject: Successfull Post

Post number %s successfully %s
""" % (FROM, id, action)
        return self.sendNotify(addrs, msg)

    eomReceived = transacted(eomReceived)

    def toLog(self, what):
        print what

    def sendNotify(self, to_addr, msg):
        d = smtp.sendmail(SMTP_HOST, FROM, to_addr, msg)
        d.addCallback(self.toLog)
        d.addErrback(self.toLog)
        return d

    def connectionLost(self):
        # There was an error, throw away the stored lines
        self.lines = None
예제 #8
0
class Composer(item.Item):
    implements(ixmantissa.INavigableElement, iquotient.IMessageSender)

    typeName = 'quotient_composer'
    schemaVersion = 6

    powerupInterfaces = (ixmantissa.INavigableElement, iquotient.IMessageSender)

    privateApplication = dependsOn(PrivateApplication)
    mda = dependsOn(MailDeliveryAgent)
    deliveryAgent = dependsOn(DeliveryAgent)
    prefs = dependsOn(ComposePreferenceCollection)

    def installed(self):
        defaultFrom = self.store.findOrCreate(FromAddress, _address=None)
        defaultFrom.setAsDefault()

    def getTabs(self):
        return [webnav.Tab('Mail', self.storeID, 0.6, children=
                    [webnav.Tab('Compose', self.storeID, 0.1)],
                authoritative=False)]


    def sendMessage(self, fromAddress, toAddresses, msg):
        """
        Send a message from this composer.

        @param toAddresses: List of email addresses (Which can be
            coerced to L{smtp.Address}es).
        @param msg: The L{exmess.Message} to send.
        """
        m = MessageDelivery(composer=self, message=msg,
                                    store=self.store)
        m.send(fromAddress, toAddresses)


    def _createBounceMessage(self, log, toAddress, msg):
        """
        Create a multipart MIME message that will be sent to the user to indicate
        that their message has bounced.

        @param log: ???
        @param toAddress: The email address that bounced
        @param msg: The message that bounced

        @return: L{MP.MIMEMultipart}
        """
        bounceText = (
            'Your message to %(recipient)s, subject "%(subject)s", '
            'could not be delivered.')
        bounceText %= {
            'recipient': toAddress,
            'subject': msg.impl.getHeader(u'subject')}

        original = P.Parser().parse(msg.impl.source.open())

        m = MMP.MIMEMultipart(
            'mixed',
            None,
            [MT.MIMEText(bounceText, 'plain'),
             MT.MIMEText(log, 'plain'),
             MM.MIMEMessage(original)])

        m['Subject'] = 'Unable to deliver message to ' + toAddress
        m['From'] = '<>'
        m['To'] = ''
        return m


    def _sendBounceMessage(self, m):
        """
        Insert the given MIME message into the inbox for this user.

        @param m: L{MMP.MIMEMultipart}
        """
        s = S.StringIO()
        G.Generator(s).flatten(m)
        s.seek(0)
        self.createMessageAndQueueIt(
            FromAddress.findDefault(self.store).address, s, False)


    def messageBounced(self, log, toAddress, msg):
        m = self._createBounceMessage(log, toAddress, msg)
        self._sendBounceMessage(m)


    def createMessageAndQueueIt(self, fromAddress, s, draft):
        """
        Create a message out of C{s}, from C{fromAddress}

        @param fromAddress: address from which to send the email
        @type fromAddress: C{unicode}
        @param s: message to send
        @type s: line iterable
        @type draft: C{bool}
        @param draft: Flag indicating whether this is a draft message or not
        (eg, a bounce message).

        @rtype: L{xquotient.exmess.Message}
        """
        def deliverMIMEMessage():
            md = iquotient.IMIMEDelivery(self.store)
            if draft:
                mr = md._createMIMEDraftReceiver('sent://' + fromAddress)
            else:
                mr = md.createMIMEReceiver('sent://' + fromAddress)
            for L in s:
                mr.lineReceived(L.rstrip('\n'))
            mr.messageDone()
            return mr.message
        return self.store.transact(deliverMIMEMessage)


    def updateDraftMessage(self, fromAddress, messageFile, message):
        """
        Change the IMessageData associated with the given message to a
        L{mimestorage.Part} created by parsing C{messageFile}.

        @type fromAddress: C{unicode}
        @param fromAddress: RFC 2821 address from which to send the email

        @param messageFile: an iterable of lines from the new MIME message

        @param message: The existing L{exmess.Message} with which to
        associate the new IMessageData.
        """
        md = iquotient.IMIMEDelivery(self.store)
        mr = md._createDraftUpdateReceiver(message, 'sent://' + fromAddress)
        for L in messageFile:
            mr.lineReceived(L.strip('\n'))
        mr.messageDone()
        return None
    updateDraftMessage = item.transacted(updateDraftMessage)


    def createRedirectedMessage(self, fromAddress, toAddresses, message):
        """
        Create a L{Message} item based on C{message}, with the C{Resent-From}
        and C{Resent-To} headers set

        @type fromAddress: L{smtpout.FromAddress}

        @type toAddresses: sequence of L{mimeutil.EmailAddress}

        @type message: L{Message}

        @rtype: L{Message}
        """
        m = P.Parser().parse(message.impl.source.open())
        def insertResentHeaders(i):
            m._headers.insert(i, ('resent-from', MH.Header(
                fromAddress.address).encode()))
            m._headers.insert(i, ('resent-to', MH.Header(
                mimeutil.flattenEmailAddresses(toAddresses)).encode()))
            m._headers.insert(i, ('resent-date', EU.formatdate()))
            m._headers.insert(i, ('resent-message-id',
                                  smtp.messageid('divmod.xquotient')))
        for (i, (name, _)) in enumerate(m._headers):
            #insert Resent-* headers after all Received headers but
            #before the rest
            if name.lower() != "received":
                insertResentHeaders(i)
                break
        else:
            insertResentHeaders(0)
        s = S.StringIO()
        G.Generator(s).flatten(m)
        s.seek(0)

        return self.createMessageAndQueueIt(fromAddress.address, s, True)


    def redirect(self, fromAddress, toAddresses, message):
        """
        Redirect C{message} from C{fromAddress} to C{toAddresses}.
        Parameters the same as for L{createRedirectedMessage}

        @rtype: C{None}
        """
        msg = self.createRedirectedMessage(fromAddress, toAddresses, message)
        addresses = [addr.email for addr in toAddresses]
        self.sendMessage(fromAddress, addresses, msg)
        message.addStatus(REDIRECTED_STATUS)
예제 #9
0
class InboxScreen(webtheme.ThemedElement, renderers.ButtonRenderingMixin):
    """
    Renderer for boxes for of email.

    @ivar store: The L{axiom.store.Store} containing the state this instance
    renders.

    @ivar inbox: The L{Inbox} which serves as the model for this view.

    @ivar messageDetailFragmentFactory: the class which should be used to
    render L{xquotient.exmess.Message} objects.  Defaults to
    L{ActionlessMessageDetail}
    """
    implements(ixmantissa.INavigableFragment)

    fragmentName = 'inbox'
    live = 'athena'
    title = ''
    jsClass = u'Quotient.Mailbox.Controller'

    translator = None


    # A dictionary mapping view parameters to their current state.  Valid keys
    # in this dictionary are:
    #
    #   view - mapped to one of "all", "trash", "sent", "spam", "deferred", or "inbox"
    #   tag - mapped to a tag name or None
    #   person - mapped to a person name or None
    #   account - mapped to an account name or None
    viewSelection = None


    def __init__(self, inbox):
        athena.LiveElement.__init__(self)
        self.translator = ixmantissa.IWebTranslator(inbox.store)
        self.store = inbox.store
        self.inbox = inbox

        self.viewSelection = {
            "view": "inbox",
            "tag": None,
            "person": None,
            "account": None}

        self.scrollingFragment = self._createScrollingFragment()
        self.scrollingFragment.setFragmentParent(self)

    def _createScrollingFragment(self):
        """
        Create a Fragment which will display a mailbox.
        """
        f = MailboxScrollingFragment(self.store)
        f.docFactory = getLoader(f.fragmentName)
        return f


    def getInitialArguments(self):
        """
        Return the initial view complexity for the mailbox.
        """
        return (self.inbox.uiComplexity,)



    messageDetailFragmentFactory = ActionlessMessageDetail



    def _messageFragment(self, message):
        """
        Return a fragment which will render C{message}

        @param message: the message to render
        @type message: L{xquotient.exmess.Message}

        @rtype: L{messageDetailFragmentFactory}
        """
        f = self.messageDetailFragmentFactory(message)
        f.setFragmentParent(self)
        return f


    def _currentAsFragment(self, currentMessage):
        if currentMessage is None:
            return ''
        return self._messageFragment(currentMessage)


    def messageActions(self, request, tag):
        """
        Renderer which returns a fragment which renders actions for the inbox

        @rtype: L{MessageActions}
        """
        f = MessageActions()
        f.setFragmentParent(self)
        return f
    renderer(messageActions)

    def scroller(self, request, tag):
        return self.scrollingFragment
    renderer(scroller)


    def getUserTagNames(self):
        """
        Return an alphabetically sorted list of unique tag names as unicode
        strings.
        """
        names = list(self.inbox.store.findOrCreate(tags.Catalog).tagNames())
        names.sort()
        return names


    def viewPane(self, request, tag):
        attrs = tag.attributes

        iq = inevow.IQ(self.docFactory)
        if 'open' in attrs:
            paneBodyPattern = 'open-pane-body'
        else:
            paneBodyPattern = 'pane-body'
        paneBodyPattern = iq.onePattern(paneBodyPattern)

        return dictFillSlots(iq.onePattern('view-pane'),
                             {'name': attrs['name'],
                              'pane-body': paneBodyPattern.fillSlots(
                                             'renderer', T.directive(attrs['renderer']))})
    renderer(viewPane)


    def personChooser(self, request, tag):
        select = inevow.IQ(self.docFactory).onePattern('personChooser')
        option = inevow.IQ(select).patternGenerator('personChoice')
        selectedOption = inevow.IQ(select).patternGenerator('selectedPersonChoice')

        for person in [None] + list(self.inbox.getPeople()):
            if person == self.viewSelection["person"]:
                p = selectedOption
            else:
                p = option
            if person:
                name = person.getDisplayName()
                key = self.translator.toWebID(person)
            else:
                name = key = 'all'

            opt = p().fillSlots(
                    'personName', name).fillSlots(
                    'personKey', key)

            select[opt]
        return select
    renderer(personChooser)


    # This is the largest unread count allowed.  Counts larger than this will
    # not be reported, to save on database work.  This is, I hope, a temporary
    # feature which will be replaced once counts can be done truly efficiently,
    # by saving the intended results in the DB.
    countLimit = 1000

    def getUnreadMessageCount(self, viewSelection):
        """
        @return: number of unread messages in current view
        """
        sq = _viewSelectionToMailboxSelector(self.inbox.store, viewSelection)
        sq.refineByStatus(UNREAD_STATUS)
        sq.setLimit(self.countLimit)
        lsq = sq.count()
        return lsq

    def mailViewChooser(self, request, tag):
        select = inevow.IQ(self.docFactory).onePattern('mailViewChooser')
        option = inevow.IQ(select).patternGenerator('mailViewChoice')
        selectedOption = inevow.IQ(select).patternGenerator('selectedMailViewChoice')

        counts = self.mailViewCounts()
        counts = sorted(counts.iteritems(), key=lambda (v, c): VIEWS.index(v))

        curview = self.viewSelection["view"]
        for (view, count) in counts:
            if view == curview:
                p = selectedOption
            else:
                p = option

            select[p().fillSlots(
                        'mailViewName', view.title()).fillSlots(
                        'count', count)]
        return select
    renderer(mailViewChooser)


    def tagChooser(self, request, tag):
        select = inevow.IQ(self.docFactory).onePattern('tagChooser')
        option = inevow.IQ(select).patternGenerator('tagChoice')
        selectedOption = inevow.IQ(select).patternGenerator('selectedTagChoice')
        for tag in [None] + self.getUserTagNames():
            if tag == self.viewSelection["tag"]:
                p = selectedOption
            else:
                p = option
            opt = p().fillSlots('tagName', tag or 'all')
            select[opt]
        return select
    renderer(tagChooser)


    def _accountNames(self):
        return getMessageSources(self.inbox.store)

    def accountChooser(self, request, tag):
        select = inevow.IQ(self.docFactory).onePattern('accountChooser')
        option = inevow.IQ(select).patternGenerator('accountChoice')
        selectedOption = inevow.IQ(select).patternGenerator('selectedAccountChoice')
        for acc in [None] + list(self._accountNames()):
            if acc == self.viewSelection["account"]:
                p = selectedOption
            else:
                p = option
            opt = p().fillSlots('accountName', acc or 'all')
            select[opt]
        return select
    renderer(accountChooser)

    def head(self):
        return None

    # remote methods

    def setComplexity(self, n):
        self.inbox.uiComplexity = n
    expose(setComplexity)

    setComplexity = transacted(setComplexity)

    def fastForward(self, viewSelection, webID):
        """
        Retrieve message detail information for the specified message as well
        as look-ahead information for the next message.  Mark the specified
        message as read.
        """
        currentMessage = self.translator.fromWebID(webID)
        currentMessage.markRead()
        return self._messageFragment(currentMessage)
    expose(fastForward)

    fastForward = transacted(fastForward)

    def mailViewCounts(self):
        counts = {}
        viewSelection = dict(self.viewSelection)
        for v in VIEWS:
            viewSelection["view"] = v
            counts[v] = self.getUnreadMessageCount(viewSelection)
        return counts


    def _messagePreview(self, msg):
        if msg is not None:
            return {
                u'subject': msg.subject}
        return None


    def actOnMessageIdentifierList(self, action, messageIdentifiers, extraArguments=None):
        """
        Perform an action on list of messages specified by their web
        identifier.

        @type action: C{unicode}
        @param action: The name of the action to perform.  This may be any
        string which can be prepended with C{'action_'} to name a method
        defined on this class.

        @type currentMessageIdentifier: C{unicode}
        @param currentMessageIdentifier: The web ID for the message which is
        currently being displayed on the client.

        @type messageIdentifiers: C{list} of C{unicode}
        @param messageIdentifiers: A list of web IDs for messages on which to act.

        @type extraArguments: C{None} or C{dict}
        @param extraArguments: Additional keyword arguments to pass on to the
        action handler.
        """
        msgs = map(self.translator.fromWebID, messageIdentifiers)

        if extraArguments is not None:
            extraArguments = dict((k.encode('ascii'), v)
                                    for (k, v) in extraArguments.iteritems())
        return self.inbox.performMany(action, msgs, args=extraArguments)
    expose(actOnMessageIdentifierList)


    def actOnMessageBatch(self, action, viewSelection, batchType, include,
                          exclude, extraArguments=None):
        """
        Perform an action on a set of messages defined by a common
        characteristic or which are specifically included but not specifically
        excluded.
        """
        msgs = self.inbox.messagesForBatchType(
            batchType, viewSelection,
            exclude=[self.translator.fromWebID(webID)
                        for webID in exclude])

        msgs = itertools.chain(
            msgs,
            (self.translator.fromWebID(webID) for webID in include))

        if extraArguments is not None:
            extraArguments = dict((k.encode('ascii'), v)
                                    for (k, v) in extraArguments.iteritems())

        return self.inbox.performMany(action, msgs, args=extraArguments)
    expose(actOnMessageBatch)


    def getComposer(self):
        """
        Return an inline L{xquotient.compose.ComposeFragment} instance with
        empty to address, subject, message body and attacments
        """
        f = ComposeFragment(
            self.inbox.store.findUnique(Composer),
            recipients=None,
            subject=u'',
            messageBody=u'',
            attachments=(),
            inline=True)
        f.setFragmentParent(self)
        f.docFactory = getLoader(f.fragmentName)
        return f
    expose(getComposer)