def __downloadRepositoryFileDone(self): """ Private method called after the repository file was downloaded. """ reply = self.sender() if reply in self.__replies: self.__replies.remove(reply) if reply.error() != QNetworkReply.NoError: E5MessageBox.warning( None, self.tr("Error downloading file"), self.tr( """<p>Could not download the requested file""" """ from {0}.</p><p>Error: {1}</p>""" ).format(Preferences.getUI("PluginRepositoryUrl6"), reply.errorString()) ) return ioDevice = QFile(self.pluginRepositoryFile + ".tmp") ioDevice.open(QIODevice.WriteOnly) ioDevice.write(reply.readAll()) ioDevice.close() if QFile.exists(self.pluginRepositoryFile): QFile.remove(self.pluginRepositoryFile) ioDevice.rename(self.pluginRepositoryFile) if os.path.exists(self.pluginRepositoryFile): f = QFile(self.pluginRepositoryFile) if f.open(QIODevice.ReadOnly): # save current URL url = Preferences.getUI("PluginRepositoryUrl6") # read the repository file from E5XML.PluginRepositoryReader import PluginRepositoryReader reader = PluginRepositoryReader(f, self.checkPluginEntry) reader.readXML() if url != Preferences.getUI("PluginRepositoryUrl6"): # redo if it is a redirect self.checkPluginUpdatesAvailable() return if self.__updateAvailable: res = E5MessageBox.information( None, self.tr("New plugin versions available"), self.tr("<p>There are new plug-ins or plug-in" " updates available. Use the plug-in" " repository dialog to get them.</p>"), E5MessageBox.StandardButtons( E5MessageBox.Ignore | E5MessageBox.Open), E5MessageBox.Open) if res == E5MessageBox.Open: self.__ui.showPluginsAvailable()
def cutVideo(self) -> bool: self.setCursor(Qt.BusyCursor) clips = len(self.clipTimes) filename, filelist = '', [] source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile() _, sourceext = os.path.splitext(source) if clips > 0: self.finalFilename, _ = QFileDialog.getSaveFileName( self.parent, 'Save video', source, 'Video files (*%s)' % sourceext) if self.finalFilename != '': self.saveAction.setDisabled(True) self.showProgress(clips) file, ext = os.path.splitext(self.finalFilename) index = 1 self.progress.setLabelText('Cutting video clips...') qApp.processEvents() for clip in self.clipTimes: duration = self.deltaToQTime(clip[0].msecsTo( clip[1])).toString(self.timeformat) filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext) filelist.append(filename) self.videoService.cut(source, filename, clip[0].toString(self.timeformat), duration) index += 1 if len(filelist) > 1: self.joinVideos(filelist, self.finalFilename) else: QFile.remove(self.finalFilename) QFile.rename(filename, self.finalFilename) self.unsetCursor() self.progress.setLabelText('Complete...') qApp.processEvents() self.saveAction.setEnabled(True) self.progress.close() self.progress.deleteLater() self.complete() self.saveAction.setEnabled(True) self.unsetCursor() self.saveAction.setDisabled(True) return True self.unsetCursor() self.saveAction.setDisabled(True) return False
def listSaves(self, folder: QDir) -> List[mobase.ISaveGame]: profiles = list() for path in Path(folder.absolutePath()).glob("*/Saved Games/*"): if (path.name == "Autosave" or path.name == "Pictures" or "_invalid" in path.name): continue if path.is_dir(): saveFolder = QDir(str(path)) if not saveFolder.exists("SaveGame.inf"): savePath = saveFolder.absolutePath() QFile.rename(savePath, savePath + "_invalid") continue else: continue profiles.append(path) return [BlackAndWhite2SaveGame(path) for path in profiles]
def stampRenamed(self, stamp): existingName = self.mStampsByName.key(stamp) self.mStampsByName.remove(existingName) self.mStampsByName.insert(stamp.name(), stamp) existingFileName = stamp.fileName() newFileName = findStampFileName(stamp.name(), existingFileName) if (existingFileName != newFileName): if (QFile.rename(stampFilePath(existingFileName), stampFilePath(newFileName))): stamp.setFileName(newFileName)
def saveDataAndVideo(self, path): # Get valid file names dataFullPath, videoFullPath = self.getFilenames(path) # Get the EEG data D = self.lsl.getData() # Get the EEG channels channels = self.connectionForm.getValues()['channels'].strip() channels = channels.split(',') if channels != '' else range( 1, len(D[0]) + 1) # Create a new DataFrame file = pd.DataFrame(D, columns=channels) # Export DataFrame to CSV file.to_csv(dataFullPath, index=False) self.statusBar.showMessage('EEG file saved as {}'.format(dataFullPath)) # Rename video file file = QFile('video.hdf5') file.rename(videoFullPath) self.statusBar.showMessage( 'Video file saved as {}'.format(videoFullPath))
def DeleteRawEvent(self): if self.currentSelectPic is None: return baseName = self.currentSelectPic.baseName() foundSuffix = [] for suffix in self.rawSuffix: fname = baseName + '.' + suffix rawPath = path.join(self.rawDir, fname) targetPath = path.join(self.moveToDirWidget.text(), fname) print(rawPath) d = QFile(rawPath) if d.exists(): foundSuffix.append(suffix) d.rename(targetPath) if len(foundSuffix) == 0: self.messageBox.setText("Can't find raw file") else: files = [baseName + '.' + x for x in foundSuffix] print(files) self.messageBox.setText("Delete raw: " + ', '.join(files))
def on_btnFile_rename_clicked(self): self.__showBtnInfo(self.sender()) sous=self.ui.editFile.text().strip() #源文件 if sous=="": self.ui.textEdit.appendPlainText("请先选择一个文件") return fileInfo=QFileInfo(sous) newFile=fileInfo.path()+"/"+fileInfo.baseName()+".XZY" #更改文件后缀为".XYZ" if QFile.rename(sous,newFile): self.ui.textEdit.appendPlainText("源文件:"+sous) self.ui.textEdit.appendPlainText("重命名为:"+newFile+"\n") else: self.ui.textEdit.appendPlainText("重命名文件失败\n")
def rename(self): file_name = self.model().fileName(self.currentIndex()) new_file_name, ok = QInputDialog.getText( self, self.tr("Rename"), self.tr(f"Rename file \"{file_name}\" and its usages to:"), text=file_name) if ok: file = QFile.rename( self.model().filePath(self.currentIndex()), posixpath.join( posixpath.dirname(self.model().filePath( self.currentIndex())), new_file_name)) if file: self.model().fileRenamed.emit( posixpath.dirname(self.model().filePath( self.currentIndex())), self.model().fileName(self.currentIndex()), new_file_name) else: QMessageBox.warning(self, self.tr("Warning"), self.tr("Cannot rename"))
class DownloadItem(QWidget, Ui_DownloadItem): """ Class implementing a widget controlling a download. @signal statusChanged() emitted upon a status change of a download @signal downloadFinished() emitted when a download finished @signal progress(int, int) emitted to signal the download progress """ statusChanged = pyqtSignal() downloadFinished = pyqtSignal() progress = pyqtSignal(int, int) Downloading = 0 DownloadSuccessful = 1 DownloadCancelled = 2 def __init__(self, reply=None, requestFilename=False, webPage=None, download=False, parent=None, mainWindow=None): """ Constructor @keyparam reply reference to the network reply object (QNetworkReply) @keyparam requestFilename flag indicating to ask the user for a filename (boolean) @keyparam webPage reference to the web page object the download originated from (QWebPage) @keyparam download flag indicating a download operation (boolean) @keyparam parent reference to the parent widget (QWidget) @keyparam mainWindow reference to the main window (HelpWindow) """ super(DownloadItem, self).__init__(parent) self.setupUi(self) p = self.infoLabel.palette() p.setColor(QPalette.Text, Qt.darkGray) self.infoLabel.setPalette(p) self.progressBar.setMaximum(0) self.__isFtpDownload = reply is not None and \ reply.url().scheme() == "ftp" self.tryAgainButton.setIcon(UI.PixmapCache.getIcon("restart.png")) self.tryAgainButton.setEnabled(False) self.tryAgainButton.setVisible(False) self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png")) self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png")) self.openButton.setIcon(UI.PixmapCache.getIcon("open.png")) self.openButton.setEnabled(False) self.openButton.setVisible(False) if self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.__state = DownloadItem.Downloading icon = self.style().standardIcon(QStyle.SP_FileIcon) self.fileIcon.setPixmap(icon.pixmap(48, 48)) self.__mainWindow = mainWindow self.__reply = reply self.__requestFilename = requestFilename self.__page = webPage self.__pageUrl = webPage and webPage.mainFrame().url() or QUrl() self.__toDownload = download self.__bytesReceived = 0 self.__bytesTotal = -1 self.__downloadTime = QTime() self.__output = QFile() self.__fileName = "" self.__originalFileName = "" self.__startedSaving = False self.__finishedDownloading = False self.__gettingFileName = False self.__canceledFileSelect = False self.__autoOpen = False self.__sha1Hash = QCryptographicHash(QCryptographicHash.Sha1) self.__md5Hash = QCryptographicHash(QCryptographicHash.Md5) if not requestFilename: self.__requestFilename = \ Preferences.getUI("RequestDownloadFilename") self.__initialize() def __initialize(self, tryAgain=False): """ Private method to (re)initialize the widget. @param tryAgain flag indicating a retry (boolean) """ if self.__reply is None: return self.__startedSaving = False self.__finishedDownloading = False self.__bytesReceived = 0 self.__bytesTotal = -1 self.__sha1Hash.reset() self.__md5Hash.reset() # start timer for the download estimation self.__downloadTime.start() # attach to the reply object self.__url = self.__reply.url() self.__reply.setParent(self) self.__reply.setReadBufferSize(16 * 1024 * 1024) self.__reply.readyRead.connect(self.__readyRead) self.__reply.error.connect(self.__networkError) self.__reply.downloadProgress.connect(self.__downloadProgress) self.__reply.metaDataChanged.connect(self.__metaDataChanged) self.__reply.finished.connect(self.__finished) # reset info self.infoLabel.clear() self.progressBar.setValue(0) self.__getFileName() if self.__reply.error() != QNetworkReply.NoError: self.__networkError() self.__finished() def __getFileName(self): """ Private method to get the file name to save to from the user. """ if self.__gettingFileName: return import Helpviewer.HelpWindow downloadDirectory = Helpviewer.HelpWindow.HelpWindow\ .downloadManager().downloadDirectory() if self.__fileName: fileName = self.__fileName originalFileName = self.__originalFileName self.__toDownload = True ask = False else: defaultFileName, originalFileName = \ self.__saveFileName(downloadDirectory) fileName = defaultFileName self.__originalFileName = originalFileName ask = True self.__autoOpen = False if not self.__toDownload: from .DownloadAskActionDialog import DownloadAskActionDialog url = self.__reply.url() dlg = DownloadAskActionDialog( QFileInfo(originalFileName).fileName(), self.__reply.header(QNetworkRequest.ContentTypeHeader), "{0}://{1}".format(url.scheme(), url.authority()), self) if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel": self.progressBar.setVisible(False) self.__reply.close() self.on_stopButton_clicked() self.filenameLabel.setText( self.tr("Download canceled: {0}").format( QFileInfo(defaultFileName).fileName())) self.__canceledFileSelect = True return if dlg.getAction() == "scan": self.__mainWindow.requestVirusTotalScan(url) self.progressBar.setVisible(False) self.__reply.close() self.on_stopButton_clicked() self.filenameLabel.setText( self.tr("VirusTotal scan scheduled: {0}").format( QFileInfo(defaultFileName).fileName())) self.__canceledFileSelect = True return self.__autoOpen = dlg.getAction() == "open" if PYQT_VERSION_STR >= "5.0.0": from PyQt5.QtCore import QStandardPaths tempLocation = QStandardPaths.standardLocations( QStandardPaths.TempLocation)[0] else: from PyQt5.QtGui import QDesktopServices tempLocation = QDesktopServices.storageLocation( QDesktopServices.TempLocation) fileName = tempLocation + '/' + \ QFileInfo(fileName).completeBaseName() if ask and not self.__autoOpen and self.__requestFilename: self.__gettingFileName = True fileName = E5FileDialog.getSaveFileName( None, self.tr("Save File"), defaultFileName, "") self.__gettingFileName = False if not fileName: self.progressBar.setVisible(False) self.__reply.close() self.on_stopButton_clicked() self.filenameLabel.setText( self.tr("Download canceled: {0}") .format(QFileInfo(defaultFileName).fileName())) self.__canceledFileSelect = True return fileInfo = QFileInfo(fileName) Helpviewer.HelpWindow.HelpWindow.downloadManager()\ .setDownloadDirectory(fileInfo.absoluteDir().absolutePath()) self.filenameLabel.setText(fileInfo.fileName()) self.__output.setFileName(fileName + ".part") self.__fileName = fileName # check file path for saving saveDirPath = QFileInfo(self.__fileName).dir() if not saveDirPath.exists(): if not saveDirPath.mkpath(saveDirPath.absolutePath()): self.progressBar.setVisible(False) self.on_stopButton_clicked() self.infoLabel.setText(self.tr( "Download directory ({0}) couldn't be created.") .format(saveDirPath.absolutePath())) return self.filenameLabel.setText(QFileInfo(self.__fileName).fileName()) if self.__requestFilename: self.__readyRead() def __saveFileName(self, directory): """ Private method to calculate a name for the file to download. @param directory name of the directory to store the file into (string) @return proposed filename and original filename (string, string) """ path = parseContentDisposition(self.__reply) info = QFileInfo(path) baseName = info.completeBaseName() endName = info.suffix() origName = baseName if endName: origName += '.' + endName name = directory + baseName if endName: name += '.' + endName if not self.__requestFilename: # do not overwrite, if the user is not being asked i = 1 while QFile.exists(name): # file exists already, don't overwrite name = directory + baseName + ('-{0:d}'.format(i)) if endName: name += '.' + endName i += 1 return name, origName def __open(self): """ Private slot to open the downloaded file. """ info = QFileInfo(self.__output) url = QUrl.fromLocalFile(info.absoluteFilePath()) QDesktopServices.openUrl(url) @pyqtSlot() def on_tryAgainButton_clicked(self): """ Private slot to retry the download. """ self.retry() def retry(self): """ Public slot to retry the download. """ if not self.tryAgainButton.isEnabled(): return self.tryAgainButton.setEnabled(False) self.tryAgainButton.setVisible(False) self.openButton.setEnabled(False) self.openButton.setVisible(False) if not self.__isFtpDownload: self.stopButton.setEnabled(True) self.stopButton.setVisible(True) self.pauseButton.setEnabled(True) self.pauseButton.setVisible(True) self.progressBar.setVisible(True) if self.__page: nam = self.__page.networkAccessManager() else: import Helpviewer.HelpWindow nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() reply = nam.get(QNetworkRequest(self.__url)) if self.__output.exists(): self.__output.remove() self.__output = QFile() self.__reply = reply self.__initialize(tryAgain=True) self.__state = DownloadItem.Downloading self.statusChanged.emit() @pyqtSlot(bool) def on_pauseButton_clicked(self, checked): """ Private slot to pause the download. @param checked flag indicating the state of the button (boolean) """ if checked: self.__reply.readyRead.disconnect(self.__readyRead) self.__reply.setReadBufferSize(16 * 1024) else: self.__reply.readyRead.connect(self.__readyRead) self.__reply.setReadBufferSize(16 * 1024 * 1024) self.__readyRead() @pyqtSlot() def on_stopButton_clicked(self): """ Private slot to stop the download. """ self.cancelDownload() def cancelDownload(self): """ Public slot to stop the download. """ self.setUpdatesEnabled(False) if not self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.tryAgainButton.setEnabled(True) self.tryAgainButton.setVisible(True) self.openButton.setEnabled(False) self.openButton.setVisible(False) self.setUpdatesEnabled(True) self.__state = DownloadItem.DownloadCancelled self.__reply.abort() self.downloadFinished.emit() @pyqtSlot() def on_openButton_clicked(self): """ Private slot to open the downloaded file. """ self.openFile() def openFile(self): """ Public slot to open the downloaded file. """ info = QFileInfo(self.__fileName) url = QUrl.fromLocalFile(info.absoluteFilePath()) QDesktopServices.openUrl(url) def openFolder(self): """ Public slot to open the folder containing the downloaded file. """ info = QFileInfo(self.__fileName) url = QUrl.fromLocalFile(info.absolutePath()) QDesktopServices.openUrl(url) def __readyRead(self): """ Private slot to read the available data. """ if self.__requestFilename and not self.__output.fileName(): return if not self.__output.isOpen(): # in case someone else has already put a file there if not self.__requestFilename: self.__getFileName() if not self.__output.open(QIODevice.WriteOnly): self.infoLabel.setText( self.tr("Error opening save file: {0}") .format(self.__output.errorString())) self.on_stopButton_clicked() self.statusChanged.emit() return self.statusChanged.emit() buffer = self.__reply.readAll() self.__sha1Hash.addData(buffer) self.__md5Hash.addData(buffer) bytesWritten = self.__output.write(buffer) if bytesWritten == -1: self.infoLabel.setText( self.tr("Error saving: {0}") .format(self.__output.errorString())) self.on_stopButton_clicked() else: self.__startedSaving = True if self.__finishedDownloading: self.__finished() def __networkError(self): """ Private slot to handle a network error. """ self.infoLabel.setText( self.tr("Network Error: {0}") .format(self.__reply.errorString())) self.tryAgainButton.setEnabled(True) self.tryAgainButton.setVisible(True) self.downloadFinished.emit() def __metaDataChanged(self): """ Private slot to handle a change of the meta data. """ locationHeader = self.__reply.header(QNetworkRequest.LocationHeader) if locationHeader and locationHeader.isValid(): self.__url = QUrl(locationHeader) import Helpviewer.HelpWindow self.__reply = Helpviewer.HelpWindow.HelpWindow\ .networkAccessManager().get(QNetworkRequest(self.__url)) self.__initialize() def __downloadProgress(self, bytesReceived, bytesTotal): """ Private method to show the download progress. @param bytesReceived number of bytes received (integer) @param bytesTotal number of total bytes (integer) """ self.__bytesReceived = bytesReceived self.__bytesTotal = bytesTotal currentValue = 0 totalValue = 0 if bytesTotal > 0: currentValue = bytesReceived * 100 / bytesTotal totalValue = 100 self.progressBar.setValue(currentValue) self.progressBar.setMaximum(totalValue) self.progress.emit(currentValue, totalValue) self.__updateInfoLabel() def bytesTotal(self): """ Public method to get the total number of bytes of the download. @return total number of bytes (integer) """ if self.__bytesTotal == -1: self.__bytesTotal = self.__reply.header( QNetworkRequest.ContentLengthHeader) if self.__bytesTotal is None: self.__bytesTotal = -1 return self.__bytesTotal def bytesReceived(self): """ Public method to get the number of bytes received. @return number of bytes received (integer) """ return self.__bytesReceived def remainingTime(self): """ Public method to get an estimation for the remaining time. @return estimation for the remaining time (float) """ if not self.downloading(): return -1.0 if self.bytesTotal() == -1: return -1.0 cSpeed = self.currentSpeed() if cSpeed != 0: timeRemaining = (self.bytesTotal() - self.bytesReceived()) / cSpeed else: timeRemaining = 1 # ETA should never be 0 if timeRemaining == 0: timeRemaining = 1 return timeRemaining def currentSpeed(self): """ Public method to get an estimation for the download speed. @return estimation for the download speed (float) """ if not self.downloading(): return -1.0 return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed() def __updateInfoLabel(self): """ Private method to update the info label. """ if self.__reply.error() != QNetworkReply.NoError: return bytesTotal = self.bytesTotal() running = not self.downloadedSuccessfully() speed = self.currentSpeed() timeRemaining = self.remainingTime() info = "" if running: remaining = "" if bytesTotal > 0: remaining = timeString(timeRemaining) info = self.tr("{0} of {1} ({2}/sec)\n{3}")\ .format( dataString(self.__bytesReceived), bytesTotal == -1 and self.tr("?") or dataString(bytesTotal), dataString(int(speed)), remaining) else: if self.__bytesReceived == bytesTotal or bytesTotal == -1: info = self.tr("{0} downloaded\nSHA1: {1}\nMD5: {2}")\ .format(dataString(self.__output.size()), str(self.__sha1Hash.result().toHex(), encoding="ascii"), str(self.__md5Hash.result().toHex(), encoding="ascii") ) else: info = self.tr("{0} of {1} - Stopped")\ .format(dataString(self.__bytesReceived), dataString(bytesTotal)) self.infoLabel.setText(info) def downloading(self): """ Public method to determine, if a download is in progress. @return flag indicating a download is in progress (boolean) """ return self.__state == DownloadItem.Downloading def downloadedSuccessfully(self): """ Public method to check for a successful download. @return flag indicating a successful download (boolean) """ return self.__state == DownloadItem.DownloadSuccessful def downloadCanceled(self): """ Public method to check, if the download was cancelled. @return flag indicating a canceled download (boolean) """ return self.__state == DownloadItem.DownloadCancelled def __finished(self): """ Private slot to handle the download finished. """ self.__finishedDownloading = True if not self.__startedSaving: return noError = self.__reply.error() == QNetworkReply.NoError self.progressBar.setVisible(False) if not self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.openButton.setEnabled(noError) self.openButton.setVisible(noError) self.__output.close() if QFile.exists(self.__fileName): QFile.remove(self.__fileName) self.__output.rename(self.__fileName) self.__updateInfoLabel() self.__state = DownloadItem.DownloadSuccessful self.statusChanged.emit() self.downloadFinished.emit() if self.__autoOpen: self.__open() def canceledFileSelect(self): """ Public method to check, if the user canceled the file selection. @return flag indicating cancellation (boolean) """ return self.__canceledFileSelect def setIcon(self, icon): """ Public method to set the download icon. @param icon reference to the icon to be set (QIcon) """ self.fileIcon.setPixmap(icon.pixmap(48, 48)) def fileName(self): """ Public method to get the name of the output file. @return name of the output file (string) """ return self.__fileName def absoluteFilePath(self): """ Public method to get the absolute path of the output file. @return absolute path of the output file (string) """ return QFileInfo(self.__fileName).absoluteFilePath() def getData(self): """ Public method to get the relevant download data. @return tuple of URL, save location, flag and the URL of the related web page (QUrl, string, boolean,QUrl) """ return (self.__url, QFileInfo(self.__fileName).filePath(), self.downloadedSuccessfully(), self.__pageUrl) def setData(self, data): """ Public method to set the relevant download data. @param data tuple of URL, save location, flag and the URL of the related web page (QUrl, string, boolean, QUrl) """ self.__url = data[0] self.__fileName = data[1] self.__pageUrl = data[3] self.__isFtpDownload = self.__url.scheme() == "ftp" self.filenameLabel.setText(QFileInfo(self.__fileName).fileName()) self.infoLabel.setText(self.__fileName) self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.openButton.setEnabled(data[2]) self.openButton.setVisible(data[2]) self.tryAgainButton.setEnabled(not data[2]) self.tryAgainButton.setVisible(not data[2]) if data[2]: self.__state = DownloadItem.DownloadSuccessful else: self.__state = DownloadItem.DownloadCancelled self.progressBar.setVisible(False) def getInfoData(self): """ Public method to get the text of the info label. @return text of the info label (string) """ return self.infoLabel.text() def getPageUrl(self): """ Public method to get the URL of the download page. @return URL of the download page (QUrl) """ return self.__pageUrl
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() DescrRole = Qt.UserRole UrlRole = Qt.UserRole + 1 FilenameRole = Qt.UserRole + 2 AuthorRole = Qt.UserRole + 3 PluginStatusUpToDate = 0 PluginStatusNew = 1 PluginStatusLocalUpdate = 2 PluginStatusRemoteUpdate = 3 def __init__(self, parent=None, external=False): """ Constructor @param parent parent of this dialog (QWidget) @param external flag indicating an instatiation as a main window (boolean) """ super(PluginRepositoryWidget, self).__init__(parent) self.setupUi(self) self.__updateButton = self.buttonBox.addButton( self.tr("Update"), QDialogButtonBox.ActionRole) self.__downloadButton = self.buttonBox.addButton( self.tr("Download"), QDialogButtonBox.ActionRole) self.__downloadButton.setEnabled(False) self.__downloadInstallButton = self.buttonBox.addButton( self.tr("Download && Install"), QDialogButtonBox.ActionRole) self.__downloadInstallButton.setEnabled(False) self.__downloadCancelButton = self.buttonBox.addButton( self.tr("Cancel"), QDialogButtonBox.ActionRole) self.__installButton = \ self.buttonBox.addButton(self.tr("Close && Install"), QDialogButtonBox.ActionRole) self.__downloadCancelButton.setEnabled(False) self.__installButton.setEnabled(False) self.repositoryUrlEdit.setText( Preferences.getUI("PluginRepositoryUrl6")) self.repositoryList.headerItem().setText( self.repositoryList.columnCount(), "") self.repositoryList.header().setSortIndicator(0, Qt.AscendingOrder) self.__pluginContextMenu = QMenu(self) self.__hideAct = self.__pluginContextMenu.addAction( self.tr("Hide"), self.__hidePlugin) self.__hideSelectedAct = self.__pluginContextMenu.addAction( self.tr("Hide Selected"), self.__hideSelectedPlugins) self.__pluginContextMenu.addSeparator() self.__showAllAct = self.__pluginContextMenu.addAction( self.tr("Show All"), self.__showAllPlugins) self.__pluginContextMenu.addSeparator() self.__pluginContextMenu.addAction( self.tr("Cleanup Downloads"), self.__cleanupDownloads) self.pluginRepositoryFile = \ os.path.join(Utilities.getConfigDir(), "PluginRepository") self.__external = external # attributes for the network objects self.__networkManager = QNetworkAccessManager(self) self.__networkManager.proxyAuthenticationRequired.connect( proxyAuthenticationRequired) if SSL_AVAILABLE: self.__sslErrorHandler = E5SslErrorHandler(self) self.__networkManager.sslErrors.connect(self.__sslErrors) self.__replies = [] self.__doneMethod = None self.__inDownload = False self.__pluginsToDownload = [] self.__pluginsDownloaded = [] self.__isDownloadInstall = False self.__allDownloadedOk = False self.__hiddenPlugins = Preferences.getPluginManager("HiddenPlugins") self.__populateList() @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot to handle the click of a button of the button box. @param button reference to the button pressed (QAbstractButton) """ if button == self.__updateButton: self.__updateList() elif button == self.__downloadButton: self.__isDownloadInstall = False self.__downloadPlugins() elif button == self.__downloadInstallButton: self.__isDownloadInstall = True self.__allDownloadedOk = True self.__downloadPlugins() elif button == self.__downloadCancelButton: self.__downloadCancel() elif button == self.__installButton: self.__closeAndInstall() 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(QPoint) def on_repositoryList_customContextMenuRequested(self, pos): """ Private slot to show the context menu. @param pos position to show the menu (QPoint) """ self.__hideAct.setEnabled( self.repositoryList.currentItem() is not None and len(self.__selectedItems()) == 1) self.__hideSelectedAct.setEnabled( len(self.__selectedItems()) > 1) self.__showAllAct.setEnabled(bool(self.__hasHiddenPlugins())) self.__pluginContextMenu.popup(self.repositoryList.mapToGlobal(pos)) @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, PluginRepositoryWidget.UrlRole) or "") self.descriptionEdit.setPlainText( current.data(0, PluginRepositoryWidget.DescrRole) and self.__formatDescription( current.data(0, PluginRepositoryWidget.DescrRole)) or "") self.authorEdit.setText( current.data(0, PluginRepositoryWidget.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())) self.__downloadInstallButton.setEnabled(len(self.__selectedItems())) self.__installButton.setEnabled(len(self.__selectedItems())) def __updateList(self): """ Private slot to download a new list and display the contents. """ url = self.repositoryUrlEdit.text() 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 flag indicating a successful download (boolean) @param filename full path of the downloaded file (string) """ if status: self.__pluginsDownloaded.append(filename) if self.__isDownloadInstall: self.__allDownloadedOk &= status 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.__downloadInstallButton.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, PluginRepositoryWidget.UrlRole) filename = os.path.join( Preferences.getPluginManager("DownloadPath"), itm.data(0, PluginRepositoryWidget.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.__downloadInstallButton.setEnabled(len(self.__selectedItems())) self.__installButton.setEnabled(True) self.__doneMethod = None if not self.__external: ui = e5App().getObject("UserInterface") else: ui = None if ui and ui.notificationsEnabled(): ui.showNotification( UI.PixmapCache.getPixmap("plugin48.png"), self.tr("Download Plugin Files"), self.tr("""The requested plugins were downloaded.""")) if self.__isDownloadInstall: self.closeAndInstall.emit() else: if ui is None or not ui.notificationsEnabled(): E5MessageBox.information( self, self.tr("Download Plugin Files"), self.tr("""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): from E5XML.PluginRepositoryReader import PluginRepositoryReader reader = PluginRepositoryReader(f, self.addEntry) reader.readXML() self.repositoryList.resizeColumnToContents(0) self.repositoryList.resizeColumnToContents(1) self.repositoryList.resizeColumnToContents(2) self.__resortRepositoryList() url = Preferences.getUI("PluginRepositoryUrl6") if url != self.repositoryUrlEdit.text(): self.repositoryUrlEdit.setText(url) E5MessageBox.warning( self, self.tr("Plugins Repository URL Changed"), self.tr( """The URL of the Plugins Repository has""" """ changed. Select the "Update" button to get""" """ the new repository file.""")) else: E5MessageBox.critical( self, self.tr("Read plugins repository file"), self.tr("<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.tr( "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.__downloadInstallButton.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 request = QNetworkRequest(QUrl(url)) request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.AlwaysNetwork) reply = self.__networkManager.get(request) 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.tr("Error downloading file"), self.tr( """<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())) self.__downloadInstallButton.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]) """ pluginName = filename.rsplit("-", 1)[0] if pluginName in self.__hiddenPlugins: return if status == "stable": if self.__stableItem is None: self.__stableItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Stable")]) self.__stableItem.setExpanded(True) parent = self.__stableItem elif status == "unstable": if self.__unstableItem is None: self.__unstableItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Unstable")]) self.__unstableItem.setExpanded(True) parent = self.__unstableItem else: if self.__unknownItem is None: self.__unknownItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Unknown")]) self.__unknownItem.setExpanded(True) parent = self.__unknownItem itm = QTreeWidgetItem(parent, [name, version, short]) itm.setData(0, PluginRepositoryWidget.UrlRole, url) itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) itm.setData(0, PluginRepositoryWidget.AuthorRole, author) itm.setData(0, PluginRepositoryWidget.DescrRole, description) updateStatus = self.__updateStatus(filename, version) if updateStatus == PluginRepositoryWidget.PluginStatusUpToDate: itm.setIcon(1, UI.PixmapCache.getIcon("empty.png")) itm.setToolTip(1, self.tr("up-to-date")) elif updateStatus == PluginRepositoryWidget.PluginStatusNew: itm.setIcon(1, UI.PixmapCache.getIcon("download.png")) itm.setToolTip(1, self.tr("new download available")) elif updateStatus == PluginRepositoryWidget.PluginStatusLocalUpdate: itm.setIcon(1, UI.PixmapCache.getIcon("updateLocal.png")) itm.setToolTip(1, self.tr("update installable")) elif updateStatus == PluginRepositoryWidget.PluginStatusRemoteUpdate: itm.setIcon(1, UI.PixmapCache.getIcon("updateRemote.png")) itm.setToolTip(1, self.tr("updated download available")) def __updateStatus(self, filename, version): """ Private method to check, if the given archive update status. @param filename data for the filename field (string) @param version data for the version field (string) @return plug-in update status (integer, one of PluginStatusNew, PluginStatusUpToDate, PluginStatusLocalUpdate, PluginStatusRemoteUpdate) """ archive = os.path.join(Preferences.getPluginManager("DownloadPath"), filename) # check, if it is an update (i.e. we already have archives # with the same pattern) archivesPattern = archive.rsplit('-', 1)[0] + "-*.zip" if len(glob.glob(archivesPattern)) == 0: return PluginRepositoryWidget.PluginStatusNew # check, if the archive exists if not os.path.exists(archive): return PluginRepositoryWidget.PluginStatusRemoteUpdate # check, if the archive is a valid zip file if not zipfile.is_zipfile(archive): return PluginRepositoryWidget.PluginStatusRemoteUpdate zip = zipfile.ZipFile(archive, "r") try: aversion = zip.read("VERSION").decode("utf-8") except KeyError: aversion = "" zip.close() if aversion == version: if not self.__external: # Check against installed/loaded plug-ins pluginManager = e5App().getObject("PluginManager") pluginName = filename.rsplit('-', 1)[0] pluginDetails = pluginManager.getPluginDetails(pluginName) if pluginDetails is None or pluginDetails["version"] < version: return PluginRepositoryWidget.PluginStatusLocalUpdate return PluginRepositoryWidget.PluginStatusUpToDate else: return PluginRepositoryWidget.PluginStatusRemoteUpdate 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) """ ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] if ignored == E5SslErrorHandler.NotIgnored: self.__downloadCancelled = True 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 @pyqtSlot(bool) def on_repositoryUrlEditButton_toggled(self, checked): """ Private slot to set the read only status of the repository URL line edit. @param checked state of the push button (boolean) """ self.repositoryUrlEdit.setReadOnly(not checked) def __closeAndInstall(self): """ Private method to close the dialog and invoke the install dialog. """ if not self.__pluginsDownloaded and self.__selectedItems(): for itm in self.__selectedItems(): filename = os.path.join( Preferences.getPluginManager("DownloadPath"), itm.data(0, PluginRepositoryWidget.FilenameRole)) self.__pluginsDownloaded.append(filename) self.closeAndInstall.emit() def __hidePlugin(self): """ Private slot to hide the current plug-in. """ itm = self.__selectedItems()[0] pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) .rsplit("-", 1)[0]) self.__updateHiddenPluginsList([pluginName]) def __hideSelectedPlugins(self): """ Private slot to hide all selected plug-ins. """ hideList = [] for itm in self.__selectedItems(): pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) .rsplit("-", 1)[0]) hideList.append(pluginName) self.__updateHiddenPluginsList(hideList) def __showAllPlugins(self): """ Private slot to show all plug-ins. """ self.__hiddenPlugins = [] self.__updateHiddenPluginsList([]) def __hasHiddenPlugins(self): """ Private method to check, if there are any hidden plug-ins. @return flag indicating the presence of hidden plug-ins (boolean) """ return bool(self.__hiddenPlugins) def __updateHiddenPluginsList(self, hideList): """ Private method to store the list of hidden plug-ins to the settings. @param hideList list of plug-ins to add to the list of hidden ones (list of string) """ if hideList: self.__hiddenPlugins.extend( [p for p in hideList if p not in self.__hiddenPlugins]) Preferences.setPluginManager("HiddenPlugins", self.__hiddenPlugins) self.__populateList() def __cleanupDownloads(self): """ Private slot to cleanup the plug-in downloads area. """ downloadPath = Preferences.getPluginManager("DownloadPath") downloads = {} # plug-in name as key, file name as value # step 1: extract plug-ins and downloaded files for pluginFile in os.listdir(downloadPath): if not os.path.isfile(os.path.join(downloadPath, pluginFile)): continue pluginName = pluginFile.rsplit("-", 1)[0] if pluginName not in downloads: downloads[pluginName] = [] downloads[pluginName].append(pluginFile) # step 2: delete old entries for pluginName in downloads: downloads[pluginName].sort() if pluginName in self.__hiddenPlugins and \ not Preferences.getPluginManager("KeepHidden"): removeFiles = downloads[pluginName] else: removeFiles = downloads[pluginName][ :-Preferences.getPluginManager("KeepGenerations")] for removeFile in removeFiles: try: os.remove(os.path.join(downloadPath, removeFile)) except (IOError, OSError) as err: E5MessageBox.critical( self, self.tr("Cleanup of Plugin Downloads"), self.tr("""<p>The plugin download <b>{0}</b> could""" """ not be deleted.</p><p>Reason: {1}</p>""") .format(removeFile, str(err)))
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() DescrRole = Qt.UserRole UrlRole = Qt.UserRole + 1 FilenameRole = Qt.UserRole + 2 AuthorRole = Qt.UserRole + 3 PluginStatusUpToDate = 0 PluginStatusNew = 1 PluginStatusLocalUpdate = 2 PluginStatusRemoteUpdate = 3 def __init__(self, parent=None, external=False): """ Constructor @param parent parent of this dialog (QWidget) @param external flag indicating an instatiation as a main window (boolean) """ super(PluginRepositoryWidget, self).__init__(parent) self.setupUi(self) self.__updateButton = self.buttonBox.addButton( self.tr("Update"), QDialogButtonBox.ActionRole) self.__downloadButton = self.buttonBox.addButton( self.tr("Download"), QDialogButtonBox.ActionRole) self.__downloadButton.setEnabled(False) self.__downloadInstallButton = self.buttonBox.addButton( self.tr("Download && Install"), QDialogButtonBox.ActionRole) self.__downloadInstallButton.setEnabled(False) self.__downloadCancelButton = self.buttonBox.addButton( self.tr("Cancel"), QDialogButtonBox.ActionRole) self.__installButton = \ self.buttonBox.addButton(self.tr("Close && Install"), QDialogButtonBox.ActionRole) self.__downloadCancelButton.setEnabled(False) self.__installButton.setEnabled(False) self.repositoryUrlEdit.setText( Preferences.getUI("PluginRepositoryUrl6")) self.repositoryList.headerItem().setText( self.repositoryList.columnCount(), "") self.repositoryList.header().setSortIndicator(0, Qt.AscendingOrder) self.__pluginContextMenu = QMenu(self) self.__hideAct = self.__pluginContextMenu.addAction( self.tr("Hide"), self.__hidePlugin) self.__hideSelectedAct = self.__pluginContextMenu.addAction( self.tr("Hide Selected"), self.__hideSelectedPlugins) self.__pluginContextMenu.addSeparator() self.__showAllAct = self.__pluginContextMenu.addAction( self.tr("Show All"), self.__showAllPlugins) self.__pluginContextMenu.addSeparator() self.__pluginContextMenu.addAction( self.tr("Cleanup Downloads"), self.__cleanupDownloads) self.pluginRepositoryFile = \ os.path.join(Utilities.getConfigDir(), "PluginRepository") self.__external = external # attributes for the network objects self.__networkManager = QNetworkAccessManager(self) self.__networkManager.proxyAuthenticationRequired.connect( proxyAuthenticationRequired) if SSL_AVAILABLE: self.__sslErrorHandler = E5SslErrorHandler(self) self.__networkManager.sslErrors.connect(self.__sslErrors) self.__replies = [] self.__networkConfigurationManager = QNetworkConfigurationManager(self) self.__onlineStateChanged( self.__networkConfigurationManager.isOnline()) self.__networkConfigurationManager.onlineStateChanged.connect( self.__onlineStateChanged) self.__doneMethod = None self.__inDownload = False self.__pluginsToDownload = [] self.__pluginsDownloaded = [] self.__isDownloadInstall = False self.__allDownloadedOk = False self.__hiddenPlugins = Preferences.getPluginManager("HiddenPlugins") self.__populateList() @pyqtSlot(bool) def __onlineStateChanged(self, online): """ Private slot handling online state changes. @param online flag indicating the online status @type bool """ self.__updateButton.setEnabled(online) self.on_repositoryList_itemSelectionChanged() if online: msg = self.tr("Network Status: online") else: msg = self.tr("Network Status: offline") self.statusLabel.setText(msg) @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot to handle the click of a button of the button box. @param button reference to the button pressed (QAbstractButton) """ if button == self.__updateButton: self.__updateList() elif button == self.__downloadButton: self.__isDownloadInstall = False self.__downloadPlugins() elif button == self.__downloadInstallButton: self.__isDownloadInstall = True self.__allDownloadedOk = True self.__downloadPlugins() elif button == self.__downloadCancelButton: self.__downloadCancel() elif button == self.__installButton: self.__closeAndInstall() 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(QPoint) def on_repositoryList_customContextMenuRequested(self, pos): """ Private slot to show the context menu. @param pos position to show the menu (QPoint) """ self.__hideAct.setEnabled( self.repositoryList.currentItem() is not None and len(self.__selectedItems()) == 1) self.__hideSelectedAct.setEnabled( len(self.__selectedItems()) > 1) self.__showAllAct.setEnabled(bool(self.__hasHiddenPlugins())) self.__pluginContextMenu.popup(self.repositoryList.mapToGlobal(pos)) @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, PluginRepositoryWidget.UrlRole) or "") self.descriptionEdit.setPlainText( current.data(0, PluginRepositoryWidget.DescrRole) and self.__formatDescription( current.data(0, PluginRepositoryWidget.DescrRole)) or "") self.authorEdit.setText( current.data(0, PluginRepositoryWidget.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()) and self.__networkConfigurationManager.isOnline()) self.__downloadInstallButton.setEnabled( len(self.__selectedItems()) and self.__networkConfigurationManager.isOnline()) self.__installButton.setEnabled(len(self.__selectedItems())) def __updateList(self): """ Private slot to download a new list and display the contents. """ url = self.repositoryUrlEdit.text() 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 flag indicating a successful download (boolean) @param filename full path of the downloaded file (string) """ if status: self.__pluginsDownloaded.append(filename) if self.__isDownloadInstall: self.__allDownloadedOk &= status 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.__downloadInstallButton.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, PluginRepositoryWidget.UrlRole) filename = os.path.join( Preferences.getPluginManager("DownloadPath"), itm.data(0, PluginRepositoryWidget.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.__downloadInstallButton.setEnabled(len(self.__selectedItems())) self.__installButton.setEnabled(True) self.__doneMethod = None if not self.__external: ui = e5App().getObject("UserInterface") else: ui = None if ui and ui.notificationsEnabled(): ui.showNotification( UI.PixmapCache.getPixmap("plugin48.png"), self.tr("Download Plugin Files"), self.tr("""The requested plugins were downloaded.""")) if self.__isDownloadInstall: self.closeAndInstall.emit() else: if ui is None or not ui.notificationsEnabled(): E5MessageBox.information( self, self.tr("Download Plugin Files"), self.tr("""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): from E5XML.PluginRepositoryReader import PluginRepositoryReader reader = PluginRepositoryReader(f, self.addEntry) reader.readXML() self.repositoryList.resizeColumnToContents(0) self.repositoryList.resizeColumnToContents(1) self.repositoryList.resizeColumnToContents(2) self.__resortRepositoryList() url = Preferences.getUI("PluginRepositoryUrl6") if url != self.repositoryUrlEdit.text(): self.repositoryUrlEdit.setText(url) E5MessageBox.warning( self, self.tr("Plugins Repository URL Changed"), self.tr( """The URL of the Plugins Repository has""" """ changed. Select the "Update" button to get""" """ the new repository file.""")) else: E5MessageBox.critical( self, self.tr("Read plugins repository file"), self.tr("<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.tr( "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 """ if self.__networkConfigurationManager.isOnline(): self.__updateButton.setEnabled(False) self.__downloadButton.setEnabled(False) self.__downloadInstallButton.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 request = QNetworkRequest(QUrl(url)) request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.AlwaysNetwork) reply = self.__networkManager.get(request) reply.finished.connect(self.__downloadFileDone) reply.downloadProgress.connect(self.__downloadProgress) self.__replies.append(reply) else: E5MessageBox.warning( self, self.tr("Error downloading file"), self.tr( """<p>Could not download the requested file""" """ from {0}.</p><p>Error: {1}</p>""" ).format(url, self.tr("Computer is offline."))) def __downloadFileDone(self): """ Private method called, after the file has been downloaded from the internet. """ self.__updateButton.setEnabled(True) self.__downloadCancelButton.setEnabled(False) self.__onlineStateChanged( self.__networkConfigurationManager.isOnline()) 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.tr("Error downloading file"), self.tr( """<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())) self.__downloadInstallButton.setEnabled( len(self.__selectedItems())) reply.deleteLater() 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 reply.deleteLater() 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]) """ pluginName = filename.rsplit("-", 1)[0] if pluginName in self.__hiddenPlugins: return if status == "stable": if self.__stableItem is None: self.__stableItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Stable")]) self.__stableItem.setExpanded(True) parent = self.__stableItem elif status == "unstable": if self.__unstableItem is None: self.__unstableItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Unstable")]) self.__unstableItem.setExpanded(True) parent = self.__unstableItem else: if self.__unknownItem is None: self.__unknownItem = \ QTreeWidgetItem(self.repositoryList, [self.tr("Unknown")]) self.__unknownItem.setExpanded(True) parent = self.__unknownItem itm = QTreeWidgetItem(parent, [name, version, short]) itm.setData(0, PluginRepositoryWidget.UrlRole, url) itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) itm.setData(0, PluginRepositoryWidget.AuthorRole, author) itm.setData(0, PluginRepositoryWidget.DescrRole, description) updateStatus = self.__updateStatus(filename, version) if updateStatus == PluginRepositoryWidget.PluginStatusUpToDate: itm.setIcon(1, UI.PixmapCache.getIcon("empty.png")) itm.setToolTip(1, self.tr("up-to-date")) elif updateStatus == PluginRepositoryWidget.PluginStatusNew: itm.setIcon(1, UI.PixmapCache.getIcon("download.png")) itm.setToolTip(1, self.tr("new download available")) elif updateStatus == PluginRepositoryWidget.PluginStatusLocalUpdate: itm.setIcon(1, UI.PixmapCache.getIcon("updateLocal.png")) itm.setToolTip(1, self.tr("update installable")) elif updateStatus == PluginRepositoryWidget.PluginStatusRemoteUpdate: itm.setIcon(1, UI.PixmapCache.getIcon("updateRemote.png")) itm.setToolTip(1, self.tr("updated download available")) def __updateStatus(self, filename, version): """ Private method to check, if the given archive update status. @param filename data for the filename field (string) @param version data for the version field (string) @return plug-in update status (integer, one of PluginStatusNew, PluginStatusUpToDate, PluginStatusLocalUpdate, PluginStatusRemoteUpdate) """ archive = os.path.join(Preferences.getPluginManager("DownloadPath"), filename) # check, if it is an update (i.e. we already have archives # with the same pattern) archivesPattern = archive.rsplit('-', 1)[0] + "-*.zip" if len(glob.glob(archivesPattern)) == 0: return PluginRepositoryWidget.PluginStatusNew # check, if the archive exists if not os.path.exists(archive): return PluginRepositoryWidget.PluginStatusRemoteUpdate # check, if the archive is a valid zip file if not zipfile.is_zipfile(archive): return PluginRepositoryWidget.PluginStatusRemoteUpdate zip = zipfile.ZipFile(archive, "r") try: aversion = zip.read("VERSION").decode("utf-8") except KeyError: aversion = "" zip.close() if aversion == version: if not self.__external: # Check against installed/loaded plug-ins pluginManager = e5App().getObject("PluginManager") pluginName = filename.rsplit('-', 1)[0] pluginDetails = pluginManager.getPluginDetails(pluginName) if pluginDetails is None or pluginDetails["version"] < version: return PluginRepositoryWidget.PluginStatusLocalUpdate return PluginRepositoryWidget.PluginStatusUpToDate else: return PluginRepositoryWidget.PluginStatusRemoteUpdate 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) """ ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] if ignored == E5SslErrorHandler.NotIgnored: self.__downloadCancelled = True 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 @pyqtSlot(bool) def on_repositoryUrlEditButton_toggled(self, checked): """ Private slot to set the read only status of the repository URL line edit. @param checked state of the push button (boolean) """ self.repositoryUrlEdit.setReadOnly(not checked) def __closeAndInstall(self): """ Private method to close the dialog and invoke the install dialog. """ if not self.__pluginsDownloaded and self.__selectedItems(): for itm in self.__selectedItems(): filename = os.path.join( Preferences.getPluginManager("DownloadPath"), itm.data(0, PluginRepositoryWidget.FilenameRole)) self.__pluginsDownloaded.append(filename) self.closeAndInstall.emit() def __hidePlugin(self): """ Private slot to hide the current plug-in. """ itm = self.__selectedItems()[0] pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) .rsplit("-", 1)[0]) self.__updateHiddenPluginsList([pluginName]) def __hideSelectedPlugins(self): """ Private slot to hide all selected plug-ins. """ hideList = [] for itm in self.__selectedItems(): pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) .rsplit("-", 1)[0]) hideList.append(pluginName) self.__updateHiddenPluginsList(hideList) def __showAllPlugins(self): """ Private slot to show all plug-ins. """ self.__hiddenPlugins = [] self.__updateHiddenPluginsList([]) def __hasHiddenPlugins(self): """ Private method to check, if there are any hidden plug-ins. @return flag indicating the presence of hidden plug-ins (boolean) """ return bool(self.__hiddenPlugins) def __updateHiddenPluginsList(self, hideList): """ Private method to store the list of hidden plug-ins to the settings. @param hideList list of plug-ins to add to the list of hidden ones (list of string) """ if hideList: self.__hiddenPlugins.extend( [p for p in hideList if p not in self.__hiddenPlugins]) Preferences.setPluginManager("HiddenPlugins", self.__hiddenPlugins) self.__populateList() def __cleanupDownloads(self): """ Private slot to cleanup the plug-in downloads area. """ downloadPath = Preferences.getPluginManager("DownloadPath") downloads = {} # plug-in name as key, file name as value # step 1: extract plug-ins and downloaded files for pluginFile in os.listdir(downloadPath): if not os.path.isfile(os.path.join(downloadPath, pluginFile)): continue pluginName = pluginFile.rsplit("-", 1)[0] if pluginName not in downloads: downloads[pluginName] = [] downloads[pluginName].append(pluginFile) # step 2: delete old entries for pluginName in downloads: downloads[pluginName].sort() if pluginName in self.__hiddenPlugins and \ not Preferences.getPluginManager("KeepHidden"): removeFiles = downloads[pluginName] else: removeFiles = downloads[pluginName][ :-Preferences.getPluginManager("KeepGenerations")] for removeFile in removeFiles: try: os.remove(os.path.join(downloadPath, removeFile)) except (IOError, OSError) as err: E5MessageBox.critical( self, self.tr("Cleanup of Plugin Downloads"), self.tr("""<p>The plugin download <b>{0}</b> could""" """ not be deleted.</p><p>Reason: {1}</p>""") .format(removeFile, str(err)))
def __downloadFileDone(self, reply, fileName, doneMethod): """ Private method called, after the file has been downloaded from the Internet. @param reply reference to the reply object of the download @type QNetworkReply @param fileName local name of the file @type str @param doneMethod method to be called when done @type func """ self.__updateButton.setEnabled(True) self.__closeButton.setEnabled(True) self.__downloadCancelButton.setEnabled(False) self.__onlineStateChanged(self.__isOnline()) ok = True if reply in self.__replies: self.__replies.remove(reply) if reply.error() != QNetworkReply.NoError: ok = False if reply.error() != QNetworkReply.OperationCanceledError: E5MessageBox.warning( self, self.tr("Error downloading file"), self.tr( """<p>Could not download the requested file""" """ from {0}.</p><p>Error: {1}</p>""" ).format(reply.url().toString(), reply.errorString()) ) self.downloadProgress.setValue(0) if self.repositoryList.topLevelItemCount(): if self.repositoryList.currentItem() is None: self.repositoryList.setCurrentItem( self.repositoryList.topLevelItem(0)) else: self.__downloadButton.setEnabled( len(self.__selectedItems())) self.__downloadInstallButton.setEnabled( len(self.__selectedItems())) reply.deleteLater() return downloadIODevice = QFile(fileName + ".tmp") downloadIODevice.open(QIODevice.WriteOnly) # read data in chunks chunkSize = 64 * 1024 * 1024 while True: data = reply.read(chunkSize) if data is None or len(data) == 0: break downloadIODevice.write(data) downloadIODevice.close() if QFile.exists(fileName): QFile.remove(fileName) downloadIODevice.rename(fileName) reply.deleteLater() if doneMethod is not None: doneMethod(ok, fileName)
class DownloadItem(QWidget, Ui_DownloadItem): """ Class implementing a widget controlling a download. @signal statusChanged() emitted upon a status change of a download @signal downloadFinished() emitted when a download finished @signal progress(int, int) emitted to signal the download progress """ statusChanged = pyqtSignal() downloadFinished = pyqtSignal() progress = pyqtSignal(int, int) Downloading = 0 DownloadSuccessful = 1 DownloadCancelled = 2 def __init__(self, reply=None, requestFilename=False, webPage=None, download=False, parent=None, mainWindow=None): """ Constructor @keyparam reply reference to the network reply object (QNetworkReply) @keyparam requestFilename flag indicating to ask the user for a filename (boolean) @keyparam webPage reference to the web page object the download originated from (QWebPage) @keyparam download flag indicating a download operation (boolean) @keyparam parent reference to the parent widget (QWidget) @keyparam mainWindow reference to the main window (HelpWindow) """ super(DownloadItem, self).__init__(parent) self.setupUi(self) p = self.infoLabel.palette() p.setColor(QPalette.Text, Qt.darkGray) self.infoLabel.setPalette(p) self.progressBar.setMaximum(0) self.__isFtpDownload = reply is not None and \ reply.url().scheme() == "ftp" self.tryAgainButton.setIcon(UI.PixmapCache.getIcon("restart.png")) self.tryAgainButton.setEnabled(False) self.tryAgainButton.setVisible(False) self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png")) self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png")) self.openButton.setIcon(UI.PixmapCache.getIcon("open.png")) self.openButton.setEnabled(False) self.openButton.setVisible(False) if self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.__state = DownloadItem.Downloading icon = self.style().standardIcon(QStyle.SP_FileIcon) self.fileIcon.setPixmap(icon.pixmap(48, 48)) self.__mainWindow = mainWindow self.__reply = reply self.__requestFilename = requestFilename self.__page = webPage self.__pageUrl = webPage and webPage.mainFrame().url() or QUrl() self.__toDownload = download self.__bytesReceived = 0 self.__bytesTotal = -1 self.__downloadTime = QTime() self.__output = QFile() self.__fileName = "" self.__originalFileName = "" self.__startedSaving = False self.__finishedDownloading = False self.__gettingFileName = False self.__canceledFileSelect = False self.__autoOpen = False self.__sha1Hash = QCryptographicHash(QCryptographicHash.Sha1) self.__md5Hash = QCryptographicHash(QCryptographicHash.Md5) if not requestFilename: self.__requestFilename = \ Preferences.getUI("RequestDownloadFilename") self.__initialize() def __initialize(self, tryAgain=False): """ Private method to (re)initialize the widget. @param tryAgain flag indicating a retry (boolean) """ if self.__reply is None: return self.__startedSaving = False self.__finishedDownloading = False self.__bytesReceived = 0 self.__bytesTotal = -1 self.__sha1Hash.reset() self.__md5Hash.reset() # start timer for the download estimation self.__downloadTime.start() # attach to the reply object self.__url = self.__reply.url() self.__reply.setParent(self) self.__reply.setReadBufferSize(16 * 1024 * 1024) self.__reply.readyRead.connect(self.__readyRead) self.__reply.error.connect(self.__networkError) self.__reply.downloadProgress.connect(self.__downloadProgress) self.__reply.metaDataChanged.connect(self.__metaDataChanged) self.__reply.finished.connect(self.__finished) # reset info self.infoLabel.clear() self.progressBar.setValue(0) self.__getFileName() if self.__reply.error() != QNetworkReply.NoError: self.__networkError() self.__finished() def __getFileName(self): """ Private method to get the file name to save to from the user. """ if self.__gettingFileName: return import Helpviewer.HelpWindow downloadDirectory = Helpviewer.HelpWindow.HelpWindow\ .downloadManager().downloadDirectory() if self.__fileName: fileName = self.__fileName originalFileName = self.__originalFileName self.__toDownload = True ask = False else: defaultFileName, originalFileName = \ self.__saveFileName(downloadDirectory) fileName = defaultFileName self.__originalFileName = originalFileName ask = True self.__autoOpen = False if not self.__toDownload: from .DownloadAskActionDialog import DownloadAskActionDialog url = self.__reply.url() dlg = DownloadAskActionDialog( QFileInfo(originalFileName).fileName(), self.__reply.header(QNetworkRequest.ContentTypeHeader), "{0}://{1}".format(url.scheme(), url.authority()), self) if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel": self.progressBar.setVisible(False) self.__reply.close() self.on_stopButton_clicked() self.filenameLabel.setText( self.tr("Download canceled: {0}").format( QFileInfo(defaultFileName).fileName())) self.__canceledFileSelect = True return ## if dlg.getAction() == "scan": ## self.__mainWindow.requestVirusTotalScan(url) ## ## self.progressBar.setVisible(False) ## self.__reply.close() ## self.on_stopButton_clicked() ## self.filenameLabel.setText( ## self.tr("VirusTotal scan scheduled: {0}").format( ## QFileInfo(defaultFileName).fileName())) ## self.__canceledFileSelect = True ## return ## self.__autoOpen = dlg.getAction() == "open" if PYQT_VERSION_STR >= "5.0.0": from PyQt5.QtCore import QStandardPaths tempLocation = QStandardPaths.storageLocation( QStandardPaths.TempLocation) else: from PyQt5.QtGui import QDesktopServices tempLocation = QDesktopServices.storageLocation( QDesktopServices.TempLocation) fileName = tempLocation + '/' + \ QFileInfo(fileName).completeBaseName() if ask and not self.__autoOpen and self.__requestFilename: self.__gettingFileName = True fileName = E5FileDialog.getSaveFileName(None, self.tr("Save File"), defaultFileName, "") self.__gettingFileName = False if not fileName: self.progressBar.setVisible(False) self.__reply.close() self.on_stopButton_clicked() self.filenameLabel.setText( self.tr("Download canceled: {0}").format( QFileInfo(defaultFileName).fileName())) self.__canceledFileSelect = True return fileInfo = QFileInfo(fileName) Helpviewer.HelpWindow.HelpWindow.downloadManager()\ .setDownloadDirectory(fileInfo.absoluteDir().absolutePath()) self.filenameLabel.setText(fileInfo.fileName()) self.__output.setFileName(fileName + ".part") self.__fileName = fileName # check file path for saving saveDirPath = QFileInfo(self.__fileName).dir() if not saveDirPath.exists(): if not saveDirPath.mkpath(saveDirPath.absolutePath()): self.progressBar.setVisible(False) self.on_stopButton_clicked() self.infoLabel.setText( self.tr("Download directory ({0}) couldn't be created."). format(saveDirPath.absolutePath())) return self.filenameLabel.setText(QFileInfo(self.__fileName).fileName()) if self.__requestFilename: self.__readyRead() def __saveFileName(self, directory): """ Private method to calculate a name for the file to download. @param directory name of the directory to store the file into (string) @return proposed filename and original filename (string, string) """ path = "" if self.__reply.hasRawHeader("Content-Disposition"): header = bytes(self.__reply.rawHeader("Content-Disposition"))\ .decode() if header: pos = header.find("filename=") if pos != -1: path = header[pos + 9:] if path.startswith('"') and path.endswith('"'): path = path[1:-1] if not path: path = self.__url.path() info = QFileInfo(path) baseName = info.completeBaseName() endName = info.suffix() if not baseName: baseName = "unnamed_download" origName = baseName if endName: origName += '.' + endName name = directory + baseName if endName: name += '.' + endName if not self.__requestFilename: # do not overwrite, if the user is not being asked i = 1 while QFile.exists(name): # file exists already, don't overwrite name = directory + baseName + ('-{0:d}'.format(i)) if endName: name += '.' + endName i += 1 return name, origName def __open(self): """ Private slot to open the downloaded file. """ info = QFileInfo(self.__output) url = QUrl.fromLocalFile(info.absoluteFilePath()) QDesktopServices.openUrl(url) @pyqtSlot() def on_tryAgainButton_clicked(self): """ Private slot to retry the download. """ self.retry() def retry(self): """ Public slot to retry the download. """ if not self.tryAgainButton.isEnabled(): return self.tryAgainButton.setEnabled(False) self.tryAgainButton.setVisible(False) self.openButton.setEnabled(False) self.openButton.setVisible(False) if not self.__isFtpDownload: self.stopButton.setEnabled(True) self.stopButton.setVisible(True) self.pauseButton.setEnabled(True) self.pauseButton.setVisible(True) self.progressBar.setVisible(True) if self.__page: nam = self.__page.networkAccessManager() else: import Helpviewer.HelpWindow nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() reply = nam.get(QNetworkRequest(self.__url)) if self.__output.exists(): self.__output.remove() self.__output = QFile() self.__reply = reply self.__initialize(tryAgain=True) self.__state = DownloadItem.Downloading self.statusChanged.emit() @pyqtSlot(bool) def on_pauseButton_clicked(self, checked): """ Private slot to pause the download. @param checked flag indicating the state of the button (boolean) """ if checked: self.__reply.readyRead.disconnect(self.__readyRead) self.__reply.setReadBufferSize(16 * 1024) else: self.__reply.readyRead.connect(self.__readyRead) self.__reply.setReadBufferSize(16 * 1024 * 1024) self.__readyRead() @pyqtSlot() def on_stopButton_clicked(self): """ Private slot to stop the download. """ self.cancelDownload() def cancelDownload(self): """ Public slot to stop the download. """ self.setUpdatesEnabled(False) if not self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.tryAgainButton.setEnabled(True) self.tryAgainButton.setVisible(True) self.openButton.setEnabled(False) self.openButton.setVisible(False) self.setUpdatesEnabled(True) self.__state = DownloadItem.DownloadCancelled self.__reply.abort() self.downloadFinished.emit() @pyqtSlot() def on_openButton_clicked(self): """ Private slot to open the downloaded file. """ self.openFile() def openFile(self): """ Public slot to open the downloaded file. """ info = QFileInfo(self.__fileName) url = QUrl.fromLocalFile(info.absoluteFilePath()) QDesktopServices.openUrl(url) def openFolder(self): """ Public slot to open the folder containing the downloaded file. """ info = QFileInfo(self.__fileName) url = QUrl.fromLocalFile(info.absolutePath()) QDesktopServices.openUrl(url) def __readyRead(self): """ Private slot to read the available data. """ if self.__requestFilename and not self.__output.fileName(): return if not self.__output.isOpen(): # in case someone else has already put a file there if not self.__requestFilename: self.__getFileName() if not self.__output.open(QIODevice.WriteOnly): self.infoLabel.setText( self.tr("Error opening save file: {0}").format( self.__output.errorString())) self.on_stopButton_clicked() self.statusChanged.emit() return self.statusChanged.emit() buffer = self.__reply.readAll() self.__sha1Hash.addData(buffer) self.__md5Hash.addData(buffer) bytesWritten = self.__output.write(buffer) if bytesWritten == -1: self.infoLabel.setText( self.tr("Error saving: {0}").format( self.__output.errorString())) self.on_stopButton_clicked() else: self.__startedSaving = True if self.__finishedDownloading: self.__finished() def __networkError(self): """ Private slot to handle a network error. """ self.infoLabel.setText( self.tr("Network Error: {0}").format(self.__reply.errorString())) self.tryAgainButton.setEnabled(True) self.tryAgainButton.setVisible(True) self.downloadFinished.emit() def __metaDataChanged(self): """ Private slot to handle a change of the meta data. """ locationHeader = self.__reply.header(QNetworkRequest.LocationHeader) if locationHeader and locationHeader.isValid(): self.__url = QUrl(locationHeader) import Helpviewer.HelpWindow self.__reply = Helpviewer.HelpWindow.HelpWindow\ .networkAccessManager().get(QNetworkRequest(self.__url)) self.__initialize() def __downloadProgress(self, bytesReceived, bytesTotal): """ Private method to show the download progress. @param bytesReceived number of bytes received (integer) @param bytesTotal number of total bytes (integer) """ self.__bytesReceived = bytesReceived self.__bytesTotal = bytesTotal currentValue = 0 totalValue = 0 if bytesTotal > 0: currentValue = bytesReceived * 100 / bytesTotal totalValue = 100 self.progressBar.setValue(currentValue) self.progressBar.setMaximum(totalValue) self.progress.emit(currentValue, totalValue) self.__updateInfoLabel() def bytesTotal(self): """ Public method to get the total number of bytes of the download. @return total number of bytes (integer) """ if self.__bytesTotal == -1: self.__bytesTotal = self.__reply.header( QNetworkRequest.ContentLengthHeader) if self.__bytesTotal is None: self.__bytesTotal = -1 return self.__bytesTotal def bytesReceived(self): """ Public method to get the number of bytes received. @return number of bytes received (integer) """ return self.__bytesReceived def remainingTime(self): """ Public method to get an estimation for the remaining time. @return estimation for the remaining time (float) """ if not self.downloading(): return -1.0 if self.bytesTotal() == -1: return -1.0 timeRemaining = (self.bytesTotal() - self.bytesReceived()) / self.currentSpeed() # ETA should never be 0 if timeRemaining == 0: timeRemaining = 1 return timeRemaining def currentSpeed(self): """ Public method to get an estimation for the download speed. @return estimation for the download speed (float) """ if not self.downloading(): return -1.0 return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed() def __updateInfoLabel(self): """ Private method to update the info label. """ if self.__reply.error() != QNetworkReply.NoError: return bytesTotal = self.bytesTotal() running = not self.downloadedSuccessfully() speed = self.currentSpeed() timeRemaining = self.remainingTime() info = "" if running: remaining = "" if bytesTotal > 0: remaining = timeString(timeRemaining) info = self.tr("{0} of {1} ({2}/sec)\n{3}")\ .format( dataString(self.__bytesReceived), bytesTotal == -1 and self.tr("?") or dataString(bytesTotal), dataString(int(speed)), remaining) else: if self.__bytesReceived == bytesTotal or bytesTotal == -1: info = self.tr("{0} downloaded\nSHA1: {1}\nMD5: {2}")\ .format(dataString(self.__output.size()), str(self.__sha1Hash.result().toHex(), encoding="ascii"), str(self.__md5Hash.result().toHex(), encoding="ascii") ) else: info = self.tr("{0} of {1} - Stopped")\ .format(dataString(self.__bytesReceived), dataString(bytesTotal)) self.infoLabel.setText(info) def downloading(self): """ Public method to determine, if a download is in progress. @return flag indicating a download is in progress (boolean) """ return self.__state == DownloadItem.Downloading def downloadedSuccessfully(self): """ Public method to check for a successful download. @return flag indicating a successful download (boolean) """ return self.__state == DownloadItem.DownloadSuccessful def downloadCanceled(self): """ Public method to check, if the download was cancelled. @return flag indicating a canceled download (boolean) """ return self.__state == DownloadItem.DownloadCancelled def __finished(self): """ Private slot to handle the download finished. """ self.__finishedDownloading = True if not self.__startedSaving: return noError = self.__reply.error() == QNetworkReply.NoError self.progressBar.setVisible(False) if not self.__isFtpDownload: self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.openButton.setEnabled(noError) self.openButton.setVisible(noError) self.__output.close() if QFile.exists(self.__fileName): QFile.remove(self.__fileName) self.__output.rename(self.__fileName) self.__updateInfoLabel() self.__state = DownloadItem.DownloadSuccessful self.statusChanged.emit() self.downloadFinished.emit() if self.__autoOpen: self.__open() def canceledFileSelect(self): """ Public method to check, if the user canceled the file selection. @return flag indicating cancellation (boolean) """ return self.__canceledFileSelect def setIcon(self, icon): """ Public method to set the download icon. @param icon reference to the icon to be set (QIcon) """ self.fileIcon.setPixmap(icon.pixmap(48, 48)) def fileName(self): """ Public method to get the name of the output file. @return name of the output file (string) """ return self.__fileName def absoluteFilePath(self): """ Public method to get the absolute path of the output file. @return absolute path of the output file (string) """ return QFileInfo(self.__fileName).absoluteFilePath() def getData(self): """ Public method to get the relevant download data. @return tuple of URL, save location, flag and the URL of the related web page (QUrl, string, boolean,QUrl) """ return (self.__url, QFileInfo(self.__fileName).filePath(), self.downloadedSuccessfully(), self.__pageUrl) def setData(self, data): """ Public method to set the relevant download data. @param data tuple of URL, save location, flag and the URL of the related web page (QUrl, string, boolean, QUrl) """ self.__url = data[0] self.__fileName = data[1] self.__pageUrl = data[3] self.__isFtpDownload = self.__url.scheme() == "ftp" self.filenameLabel.setText(QFileInfo(self.__fileName).fileName()) self.infoLabel.setText(self.__fileName) self.stopButton.setEnabled(False) self.stopButton.setVisible(False) self.pauseButton.setEnabled(False) self.pauseButton.setVisible(False) self.openButton.setEnabled(data[2]) self.openButton.setVisible(data[2]) self.tryAgainButton.setEnabled(not data[2]) self.tryAgainButton.setVisible(not data[2]) if data[2]: self.__state = DownloadItem.DownloadSuccessful else: self.__state = DownloadItem.DownloadCancelled self.progressBar.setVisible(False) def getInfoData(self): """ Public method to get the text of the info label. @return text of the info label (string) """ return self.infoLabel.text() def getPageUrl(self): """ Public method to get the URL of the download page. @return URL of the download page (QUrl) """ return self.__pageUrl
def save(self, parent, filename=None): """Saves the current file and sets is_modified to False. If the file has never been saved (i.e., doesn't have a filename) or is not writable, this method calls save_as. :type parent: QWidget - The parent window for dialogs, alerts, etc. :type filename: str - Full path of file or None (the default) :rtype: bool - True = Successfully saved file, False = error (user was notified) or user cancelled """ if filename is None: filename = self.filename # If the current file has never been saved... if filename is None: # ...call save_as instead self.on_file_saveas() wavfile = QFile(filename) # If the original file exists, but can't be opened for writing... if wavfile.exists() and not wavfile.open(QIODevice.ReadWrite): # ...then treat this as a Save-As command return self.save_as(parent) tmpfile = QTemporaryFile(filename) # If we can't open our temporary file for writing... if not QTemporaryFile.open(QIODevice.WriteOnly): # ...something's wrong (disk full?), so report it to the user and exit QMessageBox.critical( parent, make_caption('Warning'), "Unable to create temporary file:\n\n\t<b>{}</b>\n\n\n{}". format(tmpfile.fileName(), tmpfile.errorString())) return False # If the original file exists... if wavfile.exists(): # ...set the temporary file permissions to match the original tmpfile.setPermissions(wavfile.permissions()) # Ignore errors elif sys.platform == 'win32': # ...otherwise (on Windows) use standard permissions tmpfile.setPermissions(QFile.WriteGroup | QFile.WriteOther) elif sys.platform.startswith('linux') or sys.platform.startswith( 'darwin'): # ...otherwise (on Linux) use the file mode creation mask for the current process tmpfile.setPermissions(self.umask_as_permission() & Document.PERMISSION_MASK) else: raise RuntimeError( 'Unsupported platform: ' + sys.platform) # TODO - check this at startup, not here tmpfile.close() # TODO - is this necessary? # Write out the WAV file try: with wave.open(tmpfile.fileName(), mode='wb') as wave_write: # Set the audio file properties wave_write.setnchannels(self.channels) wave_write.setsampwidth(self.bytes_per_sample) wave_write.setframerate(self.sampling_rate) wave_write.setnframes(self.frames) wave_write.setcomptype(None) # Write the contents of memory out to the file wave_write.writeframes(self.frames) except wave.Error as e: # An error occurred, notify user and return False QMessageBox.critical( parent, make_caption('Warning'), "Unable to write file:\n\n\t<b>{}</b>\n\n\n{}".format( tmpfile.fileName(), e.args[0])) return False # Data was written successfully to temp file, so inform QTemporaryFile not to remove it automatically; we're # going to rename it to the original file tmpfile.setAutoRemove(False) backup_filename = filename + '~' # Remove any previous backup QFile.remove(backup_filename) # Rename the original file to the backup name QFile.rename(filename, backup_filename) # Rename the temporary file to the original name if not tmpfile.rename(filename): # If anything goes wrong, rename the backup file back to the original name QFile.rename(backup_filename, filename) QMessageBox.critical( parent, make_caption('Warning'), "Unable to rename file:\n\n\tFrom: <b>{}</b>\n\tTo: <b>{}</b>\n\n\n{}" .format(backup_filename, filename, tmpfile.errorString())) return False settings = QSettings() if not settings.value( Settings.CREATE_BACKUP_ON_SAVE, defaultValue=Settings.CREATE_BACKUP_ON_SAVE_DFLT): QFile.remove(backup_filename) self.is_modified = False self._set_filename(filename) return True