From: Andrew V. <av...@us...> - 2005-04-26 21:49:52
|
Update of /cvsroot/mailmanager/mailmanager In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv1873 Modified Files: Tag: db-backend MailManager.py Added Files: Tag: db-backend MMImportHandler.py Log Message: Still not completely there but it's late. Tickets are now exported correctly and the import is pretty much there. The parser still needs a little work. I think this should scale fine, even with large import/export sets. --- NEW FILE: MMImportHandler.py --- # logicalware mail manager # (c) Copyright Logical Progression Ltd 2002-2005 # logicalware mail manager comes with ABSOLUTELY NO WARRANTY. Further details # including conditions of redistribution are contained in LICENSE.txt # http://www.logicalprogression.net/ # $Id: MMImportHandler.py,v 1.1.2.1 2005/04/26 21:49:36 aveitch Exp $ __version__ = '$Revision: 1.1.2.1 $' # Psycopg from psycopg import Binary # Zope modules. from Acquisition import Implicit import zLOG # Python library modules. import base64 from xml.sax.handler import ContentHandler class MMImportHandler(ContentHandler, Implicit): """Import a MailManager XML archive file into the database.""" def startElement(self, name, attrs): self.data_list = [] self.base64 = False if attrs.has_key('encoding'): if attrs['encoding'] == 'base64': self.base64 = True if name in ('ticket', 'attachment', 'message', 'history'): self.row = {} self.table = name def endElement(self, name): # If encoding was set to base64, then decode if self.base64: self.row[name] = base64.decodestring(''.join(self.data_list)) else: self.row[name] = ''.join(self.data_list) # The body column of attachments needs to be encoded as a BLOB if self.table == 'attachment' and name == 'body': self.row[name] = Binary(self.row[name]) if name == 'ticket': self.sql.addTicket(self.row) zLOG.LOG(subsystem='MailManager', severity=zLOG.INFO, summary='Import Ticket %s' % self.row['id']) elif name == 'attachment': self.sql.addAttachment(self.row) elif name == 'message': print self.row self.sql.addMessage(self.row) elif name == 'history': self.sql.addHistory(self.row) def characters(self, data): self.data_list.append(data) Index: MailManager.py =================================================================== RCS file: /cvsroot/mailmanager/mailmanager/MailManager.py,v retrieving revision 1.147.2.62 retrieving revision 1.147.2.63 diff -u -d -r1.147.2.62 -r1.147.2.63 --- MailManager.py 25 Apr 2005 14:52:17 -0000 1.147.2.62 +++ MailManager.py 26 Apr 2005 21:49:36 -0000 1.147.2.63 @@ -19,6 +19,7 @@ # Modules from this package. from MailMixin import MailMixin +from MMImportHandler import MMImportHandler # Add methods. from Products.CookieCrumbler.CookieCrumbler import manage_addCC @@ -29,11 +30,12 @@ from MMUserFolder import manage_addMMUserFolder # Python library modules. -import re, os, stat, cgi -import time -import email.Utils +import base64, cgi, datetime, os, re, stat, time from calendar import monthrange -import datetime +import email.Utils +from tempfile import TemporaryFile +from xml.sax.saxutils import XMLGenerator +from xml.sax import make_parser # Psycopg from psycopg import Binary @@ -1264,7 +1266,6 @@ security.declareProtected('MailManager Settings', 'delCustomer') def delCustomer(self, username, REQUEST): """Delete a customer.""" - print username self.sql.deleteCustomer(username=username) REQUEST.set('section', 'remove') return self.CustomerSettings(self, REQUEST) @@ -1380,9 +1381,9 @@ # System Settings security.declareProtected('MailManager Settings', 'deleteTickets') - def deleteTickets(self, age, account_id, section, REQUEST, RESPONSE, + def deleteTickets(self, to_date, account_id, section, REQUEST, RESPONSE, status=None, category0=None, category1=None, - category2=None, archive=0): + category2=None, archive=False): """Delete selected tickets.""" if section == 'delete': if status is None: @@ -1391,38 +1392,89 @@ else: REQUEST.set('section', 'confirm') return self.SystemSettings(self, REQUEST) - results = self.Catalog(status=status, + if archive: + f = TemporaryFile(suffix='.xml') + exp = XMLGenerator(f, 'utf-8') + exp.startDocument() + exp.startElement('mailmanager', {}) + tickets=self.sql.listTickets(to_date=to_date, + account_id=account_id, + status=status, + category0=category0, + category1=category1, + category2=category2) + for ticket in tickets: + exp.startElement('ticket', {}) + self.row2xml(exp, tickets.names(), ticket) + messages = self.sql.listMessages(ticket_id=ticket.id) + for message in messages: + exp.startElement('message', {}) + self.row2xml(exp, messages.names(), message) + attach = self.sql.listAttachments(message_id=message.id, + include_body=True) + for att in attach: + exp.startElement('attachment', {}) + self.row2xml(exp, attach.names(), att, binary=True) + exp.endElement('attachment') + exp.endElement('message') + history = self.sql.listHistory(ticket_id=ticket.id) + for hist in history: + exp.startElement('history', {}) + self.row2xml(exp, history.names(), hist) + exp.endElement('history') + exp.endElement('ticket') + exp.endElement('mailmanager') + exp.endDocument() + f.seek(0) + + # In all cases we need to delete the tickets + self.sql.deleteTickets(to_date=to_date, account_id=account_id, + status=status, category0=category0, category1=category1, - category2=category2, - date_opened={'query': age, 'range': 'max'}) - ids = [tick['id'] for tick in results] + category2=category2) + if archive: - try: - return self.accounts[account_id].archiveTickets(ids, RESPONSE) - except ImportError: - REQUEST.set('error', 'Python 2.3 required to use this feature') + # Return the archive file + RESPONSE.setHeader('Content-Type', 'text/xml') + RESPONSE.setHeader('Content-Disposition', + 'attachment;filename="mailmanager.xml"') + # This should be replaced with a filestream iterator - unfortunately + # the stock iterator requires a filename, not a file object. + RESPONSE.write(f.read()) else: - self.accounts[account_id].manage_delObjects(ids) - REQUEST.set('section', 'delete') - return self.SystemSettings(self, REQUEST) + REQUEST.set('section', 'delete') + return self.SystemSettings(self, REQUEST) + + security.declarePrivate('row2xml') + def row2xml(self, exp, columns, row, binary=False): + """Convert a database row into XML.""" + for column in columns: + # Ignore nulls, text index and calculated status + if (row[column] is not None and column != 'idxfti' and + column != 'full_status'): + # Unfortunately there's no easy way to work out what fields + # are binary so it is hardcoded here. + if column == 'body' and binary: + exp.startElement('body', {'encoding': 'base64'}) + exp.characters(base64.encodestring(row[column])) + else: + exp.startElement(column, {}) + exp.characters(str(row[column])) + exp.endElement(column) security.declareProtected('MailManager Settings', 'setTicketsScreen') - def restoreTickets(self, account_id, archive_file, REQUEST): + def restoreTickets(self, archive_file, REQUEST=None): """Restore tickets from an export file.""" - try: - self.accounts[account_id].restoreTickets(archive_file) - except BadRequest: - REQUEST.set('section', 'restore') - REQUEST.set('error', 'Error: Duplicate ticket on restore') - except ValueError: - REQUEST.set('section', 'restore') - REQUEST.set('error', 'Error: Attempt to import to wrong account') - except ImportError: - REQUEST.set('section', 'restore') - REQUEST.set('error', 'Python 2.3 required to use this feature') - return self.SystemSettings(self, REQUEST) + parser = make_parser() + # Manually wrap an the parser with the present acquisition context to + # allow the parser to access the ZSQL methods in the 'sql' folder. + parser.setContentHandler(MMImportHandler().__of__(self)) + parser.parse(archive_file) + print 'Parsed' + if REQUEST is not None: + return self.SystemSettings(self, REQUEST) security.declareProtected('MailManager Settings', 'setTicketsScreen') def setTicketsScreen(self, refresh, ticket_id=False, status=False, @@ -1713,6 +1765,7 @@ SESSION.set('subsection', subsection) return subsection + ############################################################################### ############################################################################### |