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)
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)
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)
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)
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)
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)
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
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)
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)