Sat, 09 Jan 2010 19:43:36 +0000
Fixed a bunch of issues including a workaround for a bug in the Python email package (s. EmailDialog.py).
# -*- coding: utf-8 -*- # Copyright (c) 2003 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to send bug reports. """ import sys import os import mimetypes import smtplib import socket from PyQt4.QtCore import * from PyQt4.QtGui import * from .Ui_EmailDialog import Ui_EmailDialog from .Info import Program, Version, BugAddress, FeatureAddress import Preferences import Utilities ############################################################ ## This code is to work around a bug in the Python email ## ## package for Image and Audio mime messages. ## ############################################################ from base64 import b64encode as _bencode def _encode_base64(msg): """Encode the message's payload in Base64. Also, add an appropriate Content-Transfer-Encoding header. """ orig = msg.get_payload() encdata = str(_bencode(orig), "ASCII") msg.set_payload(encdata) msg['Content-Transfer-Encoding'] = 'base64' from email import encoders encoders.encode_base64 = _encode_base64 # WORK AROUND: implement our corrected encoder from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.audio import MIMEAudio from email.mime.multipart import MIMEMultipart class EmailDialog(QDialog, Ui_EmailDialog): """ Class implementing a dialog to send bug reports. """ def __init__(self, mode = "bug", parent = None): """ Constructor @param mode mode of this dialog (string, "bug" or "feature") @param parent parent widget of this dialog (QWidget) """ QDialog.__init__(self, parent) self.setupUi(self) self.__mode = mode if self.__mode == "feature": self.setWindowTitle(self.trUtf8("Send feature request")) self.msgLabel.setText(self.trUtf8( "Enter your &feature request below." " Version information is added automatically.")) self.__toAddress = FeatureAddress else: # default is bug self.msgLabel.setText(self.trUtf8( "Enter your &bug description below." " Version information is added automatically.")) self.__toAddress = BugAddress self.sendButton = \ self.buttonBox.addButton(self.trUtf8("Send"), QDialogButtonBox.ActionRole) self.sendButton.setEnabled(False) self.sendButton.setDefault(True) height = self.height() self.mainSplitter.setSizes([int(0.7 * height), int(0.3 * height)]) self.attachments.headerItem().setText(self.attachments.columnCount(), "") self.attachments.header().setResizeMode(QHeaderView.Interactive) sig = Preferences.getUser("Signature") if sig: self.message.setPlainText(sig) cursor = self.message.textCursor() cursor.setPosition(0) self.message.setTextCursor(cursor) self.message.ensureCursorVisible() self.__deleteFiles = [] def keyPressEvent(self, ev): """ Re-implemented to handle the user pressing the escape key. @param ev key event (QKeyEvent) """ if ev.key() == Qt.Key_Escape: res = QMessageBox.question(self, self.trUtf8("Close dialog"), self.trUtf8("""Do you really want to close the dialog?"""), QMessageBox.StandardButtons(\ QMessageBox.No | \ QMessageBox.Yes), QMessageBox.No) if res == QMessageBox.Yes: self.reject() def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. @param button button that was clicked (QAbstractButton) """ if button == self.sendButton: self.on_sendButton_clicked() def on_buttonBox_rejected(self): """ Private slot to handle the rejected signal of the button box. """ res = QMessageBox.question(self, self.trUtf8("Close dialog"), self.trUtf8("""Do you really want to close the dialog?"""), QMessageBox.StandardButtons(\ QMessageBox.No | \ QMessageBox.Yes), QMessageBox.No) if res == QMessageBox.Yes: self.reject() @pyqtSlot() def on_sendButton_clicked(self): """ Private slot to send the email message. """ if self.attachments.topLevelItemCount(): msg = self.__createMultipartMail() else: msg = self.__createSimpleMail() ok = self.__sendmail(msg) if ok: for f in self.__deleteFiles: try: os.remove(f) except OSError: pass self.accept() def __createSimpleMail(self): """ Private method to create a simple mail message. @return string containing the mail message """ try: import sipconfig sip_version_str = sipconfig.Configuration().sip_version_str except ImportError: sip_version_str = "sip version not available" msgtext = "%s\r\n----\r\n%s----\r\n%s----\r\n%s" % \ (self.message.toPlainText(), Utilities.generateVersionInfo("\r\n"), Utilities.generatePluginsVersionInfo("\r\n"), Utilities.generateDistroInfo("\r\n")) msg = MIMEText(msgtext, _charset = Preferences.getSystem("StringEncoding")) msg['From'] = Preferences.getUser("Email") msg['To'] = self.__toAddress msg['Subject'] = '[eric5] %s' % self.subject.text() return msg.as_string() def __createMultipartMail(self): """ Private method to create a multipart mail message. @return string containing the mail message """ try: import sipconfig sip_version_str = sipconfig.Configuration().sip_version_str except ImportError: sip_version_str = "sip version not available" mpPreamble = ("This is a MIME-encoded message with attachments. " "If you see this message, your mail client is not " "capable of displaying the attachments.") msgtext = "%s\r\n----\r\n%s----\r\n%s----\r\n%s" % \ (self.message.toPlainText(), Utilities.generateVersionInfo("\r\n"), Utilities.generatePluginsVersionInfo("\r\n"), Utilities.generateDistroInfo("\r\n")) # first part of multipart mail explains format msg = MIMEMultipart() msg['From'] = Preferences.getUser("Email") msg['To'] = self.__toAddress msg['Subject'] = '[eric5] %s' % self.subject.text() msg.preamble = mpPreamble msg.epilogue = '' # second part is intended to be read att = MIMEText(msgtext.encode(), _charset = Preferences.getSystem("StringEncoding")) msg.attach(att) # next parts contain the attachments for index in range(self.attachments.topLevelItemCount()): itm = self.attachments.topLevelItem(index) maintype, subtype = itm.text(1).split('/', 1) fname = itm.text(0) name = os.path.basename(fname) if maintype == 'text': att = MIMEText(open(fname, 'r').read(), _subtype = subtype) elif maintype == 'image': att = MIMEImage(open(fname, 'rb').read(), _subtype = subtype) elif maintype == 'audio': att = MIMEAudio(open(fname, 'rb').read(), _subtype = subtype) else: att = MIMEBase(maintype, subtype) att.set_payload(open(fname, 'rb').read()) encoders.encode_base64(att) att.add_header('Content-Disposition', 'attachment', filename = fname) msg.attach(att) return msg.as_string() def __sendmail(self, msg): """ Private method to actually send the message. @param msg the message to be sent (string) @return flag indicating success (boolean) """ try: server = smtplib.SMTP(Preferences.getUser("MailServer"), Preferences.getUser("MailServerPort")) if Preferences.getUser("MailServerUseTLS"): server.starttls() if Preferences.getUser("MailServerAuthentication"): # mail server needs authentication password = Preferences.getUser("MailServerPassword") if not password: password, ok = QInputDialog.getText(\ self, self.trUtf8("Mail Server Password"), self.trUtf8("Enter your mail server password"), QLineEdit.Password) if not ok: # abort return False try: server.login(Preferences.getUser("MailServerUser"), password) except (smtplib.SMTPException, socket.error) as e: res = QMessageBox.critical(self, self.trUtf8("Send bug report"), self.trUtf8("""<p>Authentication failed.<br>Reason: {0}</p>""") .format(str(e)), QMessageBox.StandardButtons(\ QMessageBox.Abort | \ QMessageBox.Retry), QMessageBox.Retry) if res == QMessageBox.Retry: return self.__sendmail(msg) else: return False QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() result = server.sendmail(Preferences.getUser("Email"), self.__toAddress, msg) server.quit() QApplication.restoreOverrideCursor() except (smtplib.SMTPException, socket.error) as e: QApplication.restoreOverrideCursor() res = QMessageBox.critical(self, self.trUtf8("Send bug report"), self.trUtf8("""<p>Message could not be sent.<br>Reason: {0}</p>""") .format(str(e)), QMessageBox.StandardButtons(\ QMessageBox.Abort | \ QMessageBox.Retry), QMessageBox.Retry) if res == QMessageBox.Retry: return self.__sendmail(msg) else: return False return True @pyqtSlot() def on_addButton_clicked(self): """ Private slot to handle the Add... button. """ fname = QFileDialog.getOpenFileName(\ self, self.trUtf8("Attach file")) if fname: self.attachFile(fname, False) def attachFile(self, fname, deleteFile): """ Public method to add an attachment. @param fname name of the file to be attached (string) @param deleteFile flag indicating to delete the file after it has been sent (boolean) """ type = mimetypes.guess_type(fname)[0] if not type: type = "application/octet-stream" itm = QTreeWidgetItem(self.attachments, [fname, type]) self.attachments.header().resizeSections(QHeaderView.ResizeToContents) self.attachments.header().setStretchLastSection(True) if deleteFile: self.__deleteFiles.append(fname) @pyqtSlot() def on_deleteButton_clicked(self): """ Private slot to handle the Delete button. """ itm = self.attachments.currentItem() if itm is not None: itm = self.attachments.takeTopLevelItem(\ self.attachments.indexOfTopLevelItem(itm)) del itm def on_subject_textChanged(self, txt): """ Private slot to handle the textChanged signal of the subject edit. @param txt changed text (string) """ self.sendButton.setEnabled(\ self.subject.text() != "" and \ self.message.toPlainText() != "") def on_message_textChanged(self): """ Private slot to handle the textChanged signal of the message edit. @param txt changed text (string) """ self.sendButton.setEnabled(\ self.subject.text() != "" and \ self.message.toPlainText() != "")