Sun, 12 Sep 2010 15:02:07 +0200
Migrated the shortcuts handler to shortcuts reader.
# -*- coding: utf-8 -*- # Copyright (c) 2007 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog showing the available plugins. """ import sys import os import zipfile from PyQt4.QtGui import * from PyQt4.QtCore import * from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from .Ui_PluginRepositoryDialog import Ui_PluginRepositoryDialog from E5Gui import E5MessageBox from E5XML.PluginRepositoryReader import PluginRepositoryReader from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired import Utilities import Preferences import UI.PixmapCache from eric5config import getConfig descrRole = Qt.UserRole urlRole = Qt.UserRole + 1 filenameRole = Qt.UserRole + 2 authorRole = Qt.UserRole + 3 class PluginRepositoryWidget(QWidget, Ui_PluginRepositoryDialog): """ Class implementing a dialog showing the available plugins. @signal closeAndInstall() emitted when the Close & Install button is pressed """ closeAndInstall = pyqtSignal() def __init__(self, parent = None): """ Constructor @param parent parent of this dialog (QWidget) """ QWidget.__init__(self, parent) self.setupUi(self) self.__updateButton = \ self.buttonBox.addButton(self.trUtf8("Update"), QDialogButtonBox.ActionRole) self.__downloadButton = \ self.buttonBox.addButton(self.trUtf8("Download"), QDialogButtonBox.ActionRole) self.__downloadButton.setEnabled(False) self.__downloadCancelButton = \ self.buttonBox.addButton(self.trUtf8("Cancel"), QDialogButtonBox.ActionRole) self.__installButton = \ self.buttonBox.addButton(self.trUtf8("Close && Install"), QDialogButtonBox.ActionRole) self.__downloadCancelButton.setEnabled(False) self.__installButton.setEnabled(False) self.repositoryList.headerItem().setText(self.repositoryList.columnCount(), "") self.repositoryList.header().setSortIndicator(0, Qt.AscendingOrder) self.pluginRepositoryFile = \ os.path.join(Utilities.getConfigDir(), "PluginRepository") # attributes for the network objects self.__networkManager = QNetworkAccessManager(self) self.__networkManager.proxyAuthenticationRequired.connect( proxyAuthenticationRequired) self.__networkManager.sslErrors.connect(self.__sslErrors) self.__replies = [] self.__doneMethod = None self.__inDownload = False self.__pluginsToDownload = [] self.__pluginsDownloaded = [] self.__populateList() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot to handle the click of a button of the button box. """ if button == self.__updateButton: self.__updateList() elif button == self.__downloadButton: self.__downloadPlugins() elif button == self.__downloadCancelButton: self.__downloadCancel() elif button == self.__installButton: self.closeAndInstall.emit() def __formatDescription(self, lines): """ Private method to format the description. @param lines lines of the description (list of strings) @return formatted description (string) """ # remove empty line at start and end newlines = lines[:] if len(newlines) and newlines[0] == '': del newlines[0] if len(newlines) and newlines[-1] == '': del newlines[-1] # replace empty lines by newline character index = 0 while index < len(newlines): if newlines[index] == '': newlines[index] = '\n' index += 1 # join lines by a blank return ' '.join(newlines) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_repositoryList_currentItemChanged(self, current, previous): """ Private slot to handle the change of the current item. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the old current item (QTreeWidgetItem) """ if self.__repositoryMissing or current is None: return self.urlEdit.setText(current.data(0, urlRole) or "") self.descriptionEdit.setPlainText(current.data(0, descrRole) and \ self.__formatDescription(current.data(0, descrRole)) or "") self.authorEdit.setText(current.data(0, authorRole) or "") def __selectedItems(self): """ Private method to get all selected items without the toplevel ones. @return list of selected items (list) """ ql = self.repositoryList.selectedItems() for index in range(self.repositoryList.topLevelItemCount()): ti = self.repositoryList.topLevelItem(index) if ti in ql: ql.remove(ti) return ql @pyqtSlot() def on_repositoryList_itemSelectionChanged(self): """ Private slot to handle a change of the selection. """ self.__downloadButton.setEnabled(len(self.__selectedItems())) def __updateList(self): """ Private slot to download a new list and display the contents. """ url = Preferences.getUI("PluginRepositoryUrl5") self.__downloadFile(url, self.pluginRepositoryFile, self.__downloadRepositoryFileDone) def __downloadRepositoryFileDone(self, status, filename): """ Private method called after the repository file was downloaded. @param status flaging indicating a successful download (boolean) @param filename full path of the downloaded file (string) """ self.__populateList() def __downloadPluginDone(self, status, filename): """ Private method called, when the download of a plugin is finished. @param status flaging indicating a successful download (boolean) @param filename full path of the downloaded file (string) """ if status: self.__pluginsDownloaded.append(filename) del self.__pluginsToDownload[0] if len(self.__pluginsToDownload): self.__downloadPlugin() else: self.__downloadPluginsDone() def __downloadPlugin(self): """ Private method to download the next plugin. """ self.__downloadFile(self.__pluginsToDownload[0][0], self.__pluginsToDownload[0][1], self.__downloadPluginDone) def __downloadPlugins(self): """ Private slot to download the selected plugins. """ self.__pluginsDownloaded = [] self.__pluginsToDownload = [] self.__downloadButton.setEnabled(False) self.__installButton.setEnabled(False) for itm in self.repositoryList.selectedItems(): if itm not in [self.__stableItem, self.__unstableItem, self.__unknownItem]: url = itm.data(0, urlRole) filename = os.path.join( Preferences.getPluginManager("DownloadPath"), itm.data(0, filenameRole)) self.__pluginsToDownload.append((url, filename)) self.__downloadPlugin() def __downloadPluginsDone(self): """ Private method called, when the download of the plugins is finished. """ self.__downloadButton.setEnabled(len(self.__selectedItems())) self.__installButton.setEnabled(True) self.__doneMethod = None E5MessageBox.information(self, self.trUtf8("Download Plugin Files"), self.trUtf8("""The requested plugins were downloaded.""")) self.downloadProgress.setValue(0) # repopulate the list to update the refresh icons self.__populateList() def __resortRepositoryList(self): """ Private method to resort the tree. """ self.repositoryList.sortItems(self.repositoryList.sortColumn(), self.repositoryList.header().sortIndicatorOrder()) def __populateList(self): """ Private method to populate the list of available plugins. """ self.repositoryList.clear() self.__stableItem = None self.__unstableItem = None self.__unknownItem = None self.downloadProgress.setValue(0) self.__doneMethod = None if os.path.exists(self.pluginRepositoryFile): self.__repositoryMissing = False f = QFile(self.pluginRepositoryFile) if f.open(QIODevice.ReadOnly): reader = PluginRepositoryReader(f, self) reader.readXML() self.repositoryList.resizeColumnToContents(0) self.repositoryList.resizeColumnToContents(1) self.repositoryList.resizeColumnToContents(2) self.__resortRepositoryList() else: E5MessageBox.critical(self, self.trUtf8("Read plugins repository file"), self.trUtf8("<p>The plugins repository file <b>{0}</b> " "could not be read. Select Update</p>")\ .format(self.pluginRepositoryFile)) else: self.__repositoryMissing = True QTreeWidgetItem(self.repositoryList, ["", self.trUtf8("No plugin repository file available.\nSelect Update.") ]) self.repositoryList.resizeColumnToContents(1) def __downloadFile(self, url, filename, doneMethod = None): """ Private slot to download the given file. @param url URL for the download (string) @param filename local name of the file (string) @param doneMethod method to be called when done """ self.__updateButton.setEnabled(False) self.__downloadButton.setEnabled(False) self.__downloadCancelButton.setEnabled(True) self.statusLabel.setText(url) self.__doneMethod = doneMethod self.__downloadURL = url self.__downloadFileName = filename self.__downloadIODevice = QFile(self.__downloadFileName + ".tmp") self.__downloadCancelled = False reply = self.__networkManager.get(QNetworkRequest(QUrl(url))) reply.finished[()].connect(self.__downloadFileDone) reply.downloadProgress.connect(self.__downloadProgress) self.__replies.append(reply) def __downloadFileDone(self): """ Private method called, after the file has been downloaded from the internet. """ self.__updateButton.setEnabled(True) self.__downloadCancelButton.setEnabled(False) self.statusLabel.setText(" ") ok = True reply = self.sender() if reply in self.__replies: self.__replies.remove(reply) if reply.error() != QNetworkReply.NoError: ok = False if not self.__downloadCancelled: E5MessageBox.warning(self, self.trUtf8("Error downloading file"), self.trUtf8( """<p>Could not download the requested file from {0}.</p>""" """<p>Error: {1}</p>""" ).format(self.__downloadURL, reply.errorString()) ) self.downloadProgress.setValue(0) self.__downloadURL = None self.__downloadIODevice.remove() self.__downloadIODevice = None if self.repositoryList.topLevelItemCount(): if self.repositoryList.currentItem() is None: self.repositoryList.setCurrentItem( self.repositoryList.topLevelItem(0)) else: self.__downloadButton.setEnabled(len(self.__selectedItems())) return self.__downloadIODevice.open(QIODevice.WriteOnly) self.__downloadIODevice.write(reply.readAll()) self.__downloadIODevice.close() if QFile.exists(self.__downloadFileName): QFile.remove(self.__downloadFileName) self.__downloadIODevice.rename(self.__downloadFileName) self.__downloadIODevice = None self.__downloadURL = None if self.__doneMethod is not None: self.__doneMethod(ok, self.__downloadFileName) def __downloadCancel(self): """ Private slot to cancel the current download. """ if self.__replies: reply = self.__replies[0] self.__downloadCancelled = True self.__pluginsToDownload = [] reply.abort() def __downloadProgress(self, done, total): """ Private slot to show the download progress. @param done number of bytes downloaded so far (integer) @param total total bytes to be downloaded (integer) """ if total: self.downloadProgress.setMaximum(total) self.downloadProgress.setValue(done) def addEntry(self, name, short, description, url, author, version, filename, status): """ Public method to add an entry to the list. @param name data for the name field (string) @param short data for the short field (string) @param description data for the description field (list of strings) @param url data for the url field (string) @param author data for the author field (string) @param version data for the version field (string) @param filename data for the filename field (string) @param status status of the plugin (string [stable, unstable, unknown]) """ if status == "stable": if self.__stableItem is None: self.__stableItem = \ QTreeWidgetItem(self.repositoryList, [self.trUtf8("Stable")]) self.__stableItem.setExpanded(True) parent = self.__stableItem elif status == "unstable": if self.__unstableItem is None: self.__unstableItem = \ QTreeWidgetItem(self.repositoryList, [self.trUtf8("Unstable")]) self.__unstableItem.setExpanded(True) parent = self.__unstableItem else: if self.__unknownItem is None: self.__unknownItem = \ QTreeWidgetItem(self.repositoryList, [self.trUtf8("Unknown")]) self.__unknownItem.setExpanded(True) parent = self.__unknownItem itm = QTreeWidgetItem(parent, [name, version, short]) itm.setData(0, urlRole, url) itm.setData(0, filenameRole, filename) itm.setData(0, authorRole, author) itm.setData(0, descrRole, description) if self.__isUpToDate(filename, version): itm.setIcon(1, UI.PixmapCache.getIcon("empty.png")) else: itm.setIcon(1, UI.PixmapCache.getIcon("download.png")) def __isUpToDate(self, filename, version): """ Private method to check, if the given archive is up-to-date. @param filename data for the filename field (string) @param version data for the version field (string) @return flag indicating up-to-date (boolean) """ archive = os.path.join(Preferences.getPluginManager("DownloadPath"), filename) # check, if the archive exists if not os.path.exists(archive): return False # check, if the archive is a valid zip file if not zipfile.is_zipfile(archive): return False zip = zipfile.ZipFile(archive, "r") try: aversion = zip.read("VERSION").decode("utf-8") except KeyError: aversion = "" zip.close() return aversion == version def __sslErrors(self, reply, errors): """ Private slot to handle SSL errors. @param reply reference to the reply object (QNetworkReply) @param errors list of SSL errors (list of QSslError) """ errorStrings = [] for err in errors: errorStrings.append(err.errorString()) errorString = '.<br />'.join(errorStrings) ret = E5MessageBox.yesNo(self, self.trUtf8("SSL Errors"), self.trUtf8("""<p>SSL Errors:</p>""" """<p>{0}</p>""" """<p>Do you want to ignore these errors?</p>""")\ .format(errorString), icon = E5MessageBox.Warning) if ret: reply.ignoreSslErrors() else: self.__downloadCancelled = True reply.abort() def getDownloadedPlugins(self): """ Public method to get the list of recently downloaded plugin files. @return list of plugin filenames (list of strings) """ return self.__pluginsDownloaded class PluginRepositoryDialog(QDialog): """ Class for the dialog variant. """ def __init__(self, parent = None): """ Constructor @param parent reference to the parent widget (QWidget) """ QDialog.__init__(self, parent) self.setSizeGripEnabled(True) self.__layout = QVBoxLayout(self) self.__layout.setMargin(0) self.setLayout(self.__layout) self.cw = PluginRepositoryWidget(self) size = self.cw.size() self.__layout.addWidget(self.cw) self.resize(size) self.cw.buttonBox.accepted[()].connect(self.accept) self.cw.buttonBox.rejected[()].connect(self.reject) self.cw.closeAndInstall.connect(self.__closeAndInstall) def __closeAndInstall(self): """ Private slot to handle the closeAndInstall signal. """ self.done(QDialog.Accepted + 1) def getDownloadedPlugins(self): """ Public method to get the list of recently downloaded plugin files. @return list of plugin filenames (list of strings) """ return self.cw.getDownloadedPlugins() class PluginRepositoryWindow(QMainWindow): """ Main window class for the standalone dialog. """ def __init__(self, parent = None): """ Constructor @param parent reference to the parent widget (QWidget) """ QMainWindow.__init__(self, parent) self.cw = PluginRepositoryWidget(self) size = self.cw.size() self.setCentralWidget(self.cw) self.resize(size) self.cw.buttonBox.accepted[()].connect(self.close) self.cw.buttonBox.rejected[()].connect(self.close) self.cw.closeAndInstall.connect(self.__startPluginInstall) def __startPluginInstall(self): """ Private slot to start the eric5 plugin installation dialog. """ proc = QProcess() applPath = os.path.join(getConfig("ericDir"), "eric5-plugininstall.py") args = [] args.append(applPath) args += self.cw.getDownloadedPlugins() if not os.path.isfile(applPath) or not proc.startDetached(sys.executable, args): E5MessageBox.critical(self, self.trUtf8('Process Generation Error'), self.trUtf8( '<p>Could not start the process.<br>' 'Ensure that it is available as <b>{0}</b>.</p>' ).format(applPath), self.trUtf8('OK')) self.close()