Fri, 26 Jul 2019 20:05:49 +0200
MicroPython: continued implementing the file manager widget.
--- a/eric6/MicroPython/CircuitPythonDevices.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/CircuitPythonDevices.py Fri Jul 26 20:05:49 2019 +0200 @@ -135,7 +135,7 @@ path = '{0}:\\'.format(disk) if (os.path.exists(path) and getVolumeName(path) == 'CIRCUITPY'): - deviceDirectory = path + deviceDirectory = path break finally: ctypes.windll.kernel32.SetErrorMode(oldMode)
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py Fri Jul 26 20:05:49 2019 +0200 @@ -10,10 +10,13 @@ from __future__ import unicode_literals import os +import shutil +import time from PyQt5.QtCore import pyqtSlot, Qt, QPoint from PyQt5.QtWidgets import ( - QWidget, QTreeWidgetItem, QHeaderView, QMenu, QInputDialog, QLineEdit + QWidget, QTreeWidgetItem, QHeaderView, QMenu, QInputDialog, QLineEdit, + QDialog ) from E5Gui import E5MessageBox, E5PathPickerDialog @@ -28,6 +31,8 @@ mtime2string, mode2string, decoratedName, listdirStat ) +from UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog + import UI.PixmapCache import Preferences import Utilities @@ -53,7 +58,9 @@ self.putButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) self.getButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) self.localUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) + self.localReloadButton.setIcon(UI.PixmapCache.getIcon("reload")) self.deviceUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) + self.deviceReloadButton.setIcon(UI.PixmapCache.getIcon("reload")) self.putButton.setEnabled(False) self.getButton.setEnabled(False) @@ -72,7 +79,6 @@ self.__fileManager.putFileDone.connect(self.__newDeviceList) self.__fileManager.getFileDone.connect(self.__handleGetDone) self.__fileManager.rsyncDone.connect(self.__handleRsyncDone) - self.__fileManager.rsyncMessages.connect(self.__handleRsyncMessages) self.__fileManager.rsyncProgressMessage.connect( self.__handleRsyncProgressMessage) self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList) @@ -93,8 +99,16 @@ self.__localMenu = QMenu(self) self.__localMenu.addAction(self.tr("Change Directory"), self.__changeLocalDirectory) - # TODO: add some more local entries - # TODO: add entry to reload + self.__localMenu.addAction( + self.tr("Create Directory"), self.__createLocalDirectory) + self.__localDelDirTreeAct = self.__localMenu.addAction( + self.tr("Delete Directory Tree"), self.__deleteLocalDirectoryTree) + self.__localMenu.addSeparator() + self.__localDelFileAct = self.__localMenu.addAction( + self.tr("Delete File"), self.__deleteLocalFile) + self.__localMenu.addSeparator() + self.__localMenu.addAction( + self.tr("Show Time"), self.__showLocalTime) self.__deviceMenu = QMenu(self) self.__deviceMenu.addAction( @@ -109,8 +123,6 @@ self.__devDelFileAct = self.__deviceMenu.addAction( self.tr("Delete File"), self.__deleteDeviceFile) self.__deviceMenu.addSeparator() - # TODO: add entry to reload - self.__deviceMenu.addSeparator() self.__deviceMenu.addAction( self.tr("Synchronize Time"), self.__synchronizeTime) self.__deviceMenu.addAction( @@ -181,9 +193,9 @@ @type tuple of (str, str, str, str) """ self.deviceFileTreeWidget.clear() - for name, mode, size, time in filesList: + for name, mode, size, dateTime in filesList: itm = QTreeWidgetItem(self.deviceFileTreeWidget, - [name, mode, size, time]) + [name, mode, size, dateTime]) itm.setTextAlignment(1, Qt.AlignHCenter) itm.setTextAlignment(2, Qt.AlignRight) self.deviceFileTreeWidget.header().resizeSections( @@ -258,6 +270,14 @@ dirname = os.path.dirname(cwd) self.__listLocalFiles(dirname) + @pyqtSlot() + def on_localReloadButton_clicked(self): + """ + Private slot to reload the local list. + """ + dirname = self.localCwd.text() + self.__listLocalFiles(dirname) + @pyqtSlot(QTreeWidgetItem, int) def on_deviceFileTreeWidget_itemActivated(self, item, column): """ @@ -297,6 +317,17 @@ dirname = os.path.dirname(cwd) self.__fileManager.cd(dirname) + @pyqtSlot() + def on_deviceReloadButton_clicked(self): + """ + Private slot to reload the device list. + """ + dirname = self.deviceCwd.text() + if dirname: + self.__fileManager.lls(dirname) + else: + self.__fileManager.pwd() + def __isFileInList(self, filename, treeWidget): """ Private method to check, if a file name is contained in a tree widget. @@ -327,7 +358,6 @@ # it is really a file if self.__isFileInList(filename, self.deviceFileTreeWidget): # ask for overwrite permission - # TODO: test this action, resultFilename = confirmOverwrite( filename, self.tr("Copy File to Device"), self.tr("The given file exists already" @@ -359,7 +389,6 @@ # it is really a file if self.__isFileInList(filename, self.localFileTreeWidget): # ask for overwrite permission - # TODO: test this action, resultFilename = confirmOverwrite( filename, self.tr("Copy File from Device"), self.tr("The given file exists already."), @@ -393,7 +422,6 @@ """ self.__listLocalFiles(self.localCwd.text()) - # TODO: test this @pyqtSlot() def on_syncButton_clicked(self): """ @@ -418,32 +446,31 @@ self.__listLocalFiles(self.localCwd.text()) self.__fileManager.lls(self.deviceCwd.text()) - @pyqtSlot(list) - def __handleRsyncMessages(self, messages): - """ - Private slot to handle messages from the rsync operation. - - @param messages list of message generated by the rsync operation - @type list - """ - E5MessageBox.information( - self, - self.tr("rsync Messages"), - self.tr("""<p>rsync gave the following messages</p>""" - """<ul><li>{0}</li></ul>""").format( - "</li><li>".join(messages) - ) - ) - @pyqtSlot(str) def __handleRsyncProgressMessage(self, message): """ Private slot handling progress messages sent by the file manager. + + @param message message to be shown + @type str """ - # TODO: not implemented yet - # create and open the info dialog, if it is None - # connect to the dialog finished(int) signal to set the memeber variable to None - # add messages to dialog + if self.__progressInfoDialog is None: + from .MicroPythonProgressInfoDialog import ( + MicroPythonProgressInfoDialog + ) + self.__progressInfoDialog = MicroPythonProgressInfoDialog(self) + self.__progressInfoDialog.finished.connect( + self.__progressInfoDialogFinished) + self.__progressInfoDialog.show() + self.__progressInfoDialog.addMessage(message) + + @pyqtSlot() + def __progressInfoDialogFinished(self): + """ + Private slot handling the closing of the progress info dialog. + """ + self.__progressInfoDialog.deleteLater() + self.__progressInfoDialog = None @pyqtSlot() def __newDeviceList(self): @@ -464,6 +491,17 @@ @param pos position to show the menu at @type QPoint """ + hasSelection = bool(len(self.localFileTreeWidget.selectedItems())) + if hasSelection: + name = self.localFileTreeWidget.selectedItems()[0].text(0) + isDir = name.endswith("/") + isFile = not isDir + else: + isDir = False + isFile = False + self.__localDelDirTreeAct.setEnabled(isDir) + self.__localDelFileAct.setEnabled(isFile) + self.__localMenu.exec_(self.localFileTreeWidget.mapToGlobal(pos)) @pyqtSlot() @@ -484,6 +522,104 @@ self.localCwd.setText(dirPath) self.__listLocalFiles(dirPath) + # TODO: test this + @pyqtSlot() + def __createLocalDirectory(self): + """ + Private slot to create a local directory. + """ + dirPath, ok = QInputDialog.getText( + self, + self.tr("Create Directory"), + self.tr("Enter directory name:"), + QLineEdit.Normal) + if ok and dirPath: + dirPath = os.path.join(self.localCwd.text(), dirPath) + try: + os.mkdir(dirPath) + except (OSError, IOError) as exc: + E5MessageBox.critical( + self, + self.tr("Create Directory"), + self.tr("""<p>The directory <b>{0}</b> could not be""" + """ created.</p><p>Reason: {1}</p>""").format( + dirPath, str(exc)) + ) + + # TODO: test this + @pyqtSlot() + def __deleteLocalDirectoryTree(self): + """ + Private slot to delete a local directory tree. + """ + if bool(len(self.localFileTreeWidget.selectedItems())): + name = self.localFileTreeWidget.selectedItems()[0].text(0) + dirname = os.path.join(self.localCwd.text(), name[:-1]) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete Directory Tree"), + self.tr( + "Do you really want to delete this directory tree?"), + [dirname]) + if dlg.exec_() == QDialog.Accepted: + try: + shutil.rmtree(dirname) + except Exception as exc: + E5MessageBox.critical( + self, + self.tr("Delete Directory Tree"), + self.tr("""<p>The directory <b>{0}</b> could not be""" + """ deleted.</p><p>Reason: {1}</p>""").format( + dirname, str(exc)) + ) + + # TODO: test this + @pyqtSlot() + def __deleteLocalFile(self): + """ + Private slot to delete a local file. + """ + if bool(len(self.localFileTreeWidget.selectedItems())): + name = self.localFileTreeWidget.selectedItems()[0].text(0) + filename = os.path.join(self.localCwd.text(), name) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete File"), + self.tr( + "Do you really want to delete this file?"), + [filename]) + if dlg.exec_() == QDialog.Accepted: + try: + os.remove(filename) + except (OSError, IOError) as exc: + E5MessageBox.critical( + self, + self.tr("Delete File"), + self.tr("""<p>The file <b>{0}</b> could not be""" + """ deleted.</p><p>Reason: {1}</p>""").format( + filename, str(exc)) + ) + + # TODO: test this + @pyqtSlot() + def __showLocalTime(self): + """ + Private slot to show the local date and time. + """ + localdatetime = time.localtime() + loacldate = time.strftime('%Y-%m-%d', localdatetime) + localtime = time.strftime('%H:%M:%S', localdatetime) + E5MessageBox.information( + self, + self.tr("Local Date and Time"), + self.tr("<h3>Local Date and Time</h3>" + "<table>" + "<tr><td><b>Date</b></td><td>{0}</td></tr>" + "<tr><td><b>Time</b></td><td>{1}</td></tr>" + "</table>" + ).format(loacldate, localtime) + ) + ################################################################## ## Context menu methods for the device files below ################################################################## @@ -552,8 +688,14 @@ if bool(len(self.deviceFileTreeWidget.selectedItems())): name = self.deviceFileTreeWidget.selectedItems()[0].text(0) dirname = self.deviceCwd.text() + "/" + name[:-1] - # TODO: add confirmation - self.__fileManager.rmdir(dirname) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete Directory"), + self.tr( + "Do you really want to delete this directory?"), + [dirname]) + if dlg.exec_() == QDialog.Accepted: + self.__fileManager.rmdir(dirname) @pyqtSlot() def __deleteDeviceDirectoryTree(self): @@ -564,19 +706,31 @@ if bool(len(self.deviceFileTreeWidget.selectedItems())): name = self.deviceFileTreeWidget.selectedItems()[0].text(0) dirname = self.deviceCwd.text() + "/" + name[:-1] - # TODO: add confirmation - self.__fileManager.rmdir(dirname, recursive=True) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete Directory Tree"), + self.tr( + "Do you really want to delete this directory tree?"), + [dirname]) + if dlg.exec_() == QDialog.Accepted: + self.__fileManager.rmdir(dirname, recursive=True) @pyqtSlot() def __deleteDeviceFile(self): """ - Private slot to delete a file + Private slot to delete a file. """ if bool(len(self.deviceFileTreeWidget.selectedItems())): name = self.deviceFileTreeWidget.selectedItems()[0].text(0) filename = self.deviceCwd.text() + "/" + name - # TODO: add confirmation - self.__fileManager.delete(filename) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete File"), + self.tr( + "Do you really want to delete this file?"), + [filename]) + if dlg.exec_() == QDialog.Accepted: + self.__fileManager.delete(filename) @pyqtSlot() def __synchronizeTime(self):
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.ui Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.ui Fri Jul 26 20:05:49 2019 +0200 @@ -174,6 +174,9 @@ </item> <item row="2" column="0"> <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>1</number> + </property> <item> <widget class="QLabel" name="localCwd"> <property name="sizePolicy"> @@ -191,10 +194,20 @@ </property> </widget> </item> + <item> + <widget class="QToolButton" name="localReloadButton"> + <property name="toolTip"> + <string>Press to reload the list</string> + </property> + </widget> + </item> </layout> </item> <item row="2" column="2"> <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>1</number> + </property> <item> <widget class="QLabel" name="deviceCwd"> <property name="sizePolicy"> @@ -212,6 +225,13 @@ </property> </widget> </item> + <item> + <widget class="QToolButton" name="deviceReloadButton"> + <property name="toolTip"> + <string>Press to reload the list</string> + </property> + </widget> + </item> </layout> </item> </layout> @@ -223,7 +243,9 @@ <tabstop>putButton</tabstop> <tabstop>getButton</tabstop> <tabstop>localUpButton</tabstop> + <tabstop>localReloadButton</tabstop> <tabstop>deviceUpButton</tabstop> + <tabstop>deviceReloadButton</tabstop> </tabstops> <resources/> <connections/>
--- a/eric6/MicroPython/MicroPythonFileSystem.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileSystem.py Fri Jul 26 20:05:49 2019 +0200 @@ -607,7 +607,6 @@ on the connected device @signal rsyncDone(localName, deviceName) emitted after the rsync operation has been completed - @signal rsyncMessages(list) emitted with a list of messages @signal rsyncProgressMessage(msg) emitted to send a message about what rsync is doing @signal removeDirectoryDone() emitted after a directory has been deleted @@ -629,7 +628,6 @@ putFileDone = pyqtSignal(str, str) deleteFileDone = pyqtSignal(str) rsyncDone = pyqtSignal(str, str) - rsyncMessages = pyqtSignal(list) rsyncProgressMessage = pyqtSignal(str) removeDirectoryDone = pyqtSignal() createDirectoryDone = pyqtSignal() @@ -776,18 +774,20 @@ @param mirror flag indicating to mirror the local directory to the device directory @type bool - @return tuple containing a list of messages and list of errors - @rtype tuple of (list of str, list of str) + @return list of errors + @rtype list of str """ - # TODO: get rid of messages and replace by rsyncProgressMessage signal - messages = [] errors = [] if not os.path.isdir(hostDirectory): - return ([], [self.tr( + return [self.tr( "The given name '{0}' is not a directory or does not exist.") .format(hostDirectory) - ]) + ] + + self.rsyncProgressMessage.emit( + self.tr("Synchronizing <b>{0}</b>.").format(deviceDirectory) + ) sourceDict = {} sourceFiles = listdirStat(hostDirectory) @@ -798,13 +798,13 @@ try: destinationFiles = self.__fs.lls(deviceDirectory, fullstat=True) except Exception as exc: - return ([], [str(exc)]) + return [str(exc)] if destinationFiles is None: # the destination directory does not exist try: self.__fs.mkdir(deviceDirectory) except Exception as exc: - return ([], [str(exc)]) + return [str(exc)] else: for name, nstat in destinationFiles: destinationDict[name] = nstat @@ -819,27 +819,32 @@ # name exists in source but not in device sourceFilename = os.path.join(hostDirectory, sourceBasename) destFilename = deviceDirectory + "/" + sourceBasename + self.rsyncProgressMessage.emit( + self.tr("Adding <b>{0}</b>...").format(destFilename)) if os.path.isfile(sourceFilename): try: self.__fs.put(sourceFilename, destFilename) except Exception as exc: - messages.append(str(exc)) + # just note issues but ignore them otherwise + errors.append(str(exc)) if os.path.isdir(sourceFilename): # recurse - msg, err = self.__rsync(sourceFilename, destFilename, - mirror=mirror) - messages.extend(msg) - errors.extend(err) + errs = self.__rsync(sourceFilename, destFilename, + mirror=mirror) + # just note issues but ignore them otherwise + errors.extend(errs) if mirror: for destBasename in toDelete: # name exists in device but not local, delete destFilename = deviceDirectory + "/" + destBasename + self.rsyncProgressMessage.emit( + self.tr("Removing <b>{0}</b>...").format(destFilename)) try: self.__fs.rmrf(destFilename, recursive=True, force=True) except Exception as exc: - # ignore errors here - messages.append(str(exc)) + # just note issues but ignore them otherwise + errors.append(str(exc)) for sourceBasename in toUpdate: # names exist in both; do an update @@ -850,33 +855,41 @@ destMode = destStat[0] if os.path.isdir(sourceFilename): if stat.S_ISDIR(destMode): - # both are directories => recurse - msg, err = self.__rsync(sourceFilename, destFilename, - mirror=mirror) - messages.extend(msg) - errors.extend(err) + # both are directories => recurs + errs = self.__rsync(sourceFilename, destFilename, + mirror=mirror) + # just note issues but ignore them otherwise + errors.extend(errs) else: - messages.append(self.tr( - "Source '{0}' is a directory and destination '{1}'" - " is a file. Ignoring it." - ).format(sourceFilename, destFilename)) + self.rsyncProgressMessage.emit( + self.tr("Source <b>{0}</b> is a directory and" + " destination <b>{1}</b> is a file. Ignoring" + " it.") + .format(sourceFilename, destFilename) + ) else: if stat.S_ISDIR(destMode): - messages.append(self.tr( - "Source '{0}' is a file and destination '{1}' is" - " a directory. Ignoring it." - ).format(sourceFilename, destFilename)) + self.rsyncProgressMessage.emit( + self.tr("Source <b>{0}</b> is a file and destination" + " <b>{1}</b> is a directory. Ignoring it.") + .format(sourceFilename, destFilename) + ) else: if sourceStat[8] > destStat[8]: # mtime - messages.append(self.tr( - "'{0}' is newer than '{1}' - copying" - ).format(sourceFilename, destFilename)) + self.rsyncProgressMessage.emit( + self.tr("Updating <b>{0}</b>...") + .format(destFilename) + ) try: self.__fs.put(sourceFilename, destFilename) except Exception as exc: - messages.append(str(exc)) + errors.append(str(exc)) - return messages, errors + self.rsyncProgressMessage.emit( + self.tr("Done synchronizing <b>{0}</b>.").format(deviceDirectory) + ) + + return errors @pyqtSlot(str, str) @pyqtSlot(str, str, bool) @@ -892,14 +905,10 @@ the device directory @type bool """ - messages, errors = self.__rsync(hostDirectory, deviceDirectory, - mirror=mirror) + errors = self.__rsync(hostDirectory, deviceDirectory, mirror=mirror) if errors: self.error.emit("rsync", "\n".join(errors)) - if messages: - self.rsyncMessages.emit(messages) - self.rsyncDone.emit(hostDirectory, deviceDirectory) @pyqtSlot(str)
--- a/eric6/MicroPython/MicroPythonFileSystemUtilities.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileSystemUtilities.py Fri Jul 26 20:05:49 2019 +0200 @@ -54,6 +54,9 @@ if stat.S_ISDIR(mode) or isDir: # append a '/' for directories return name + "/" + elif stat.S_ISLNK(mode): + # append a '@' for links + return name + "@" else: # no change return name
--- a/eric6/MicroPython/MicroPythonProgressInfoDialog.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.py Fri Jul 26 20:05:49 2019 +0200 @@ -7,7 +7,10 @@ Module implementing a dialog to show progress messages. """ +from __future__ import unicode_literals + from PyQt5.QtCore import pyqtSlot +from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import QDialog from .Ui_MicroPythonProgressInfoDialog import Ui_MicroPythonProgressInfoDialog @@ -35,4 +38,8 @@ @param message progress information to be shown @type str """ - # TODO: not implemented yet + tc = self.progressEdit.textCursor() + tc.movePosition(QTextCursor.End) + self.progressEdit.setTextCursor(tc) + self.progressEdit.appendHtml(message) + self.progressEdit.ensureCursorVisible()
--- a/eric6/MicroPython/MicroPythonProgressInfoDialog.ui Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.ui Fri Jul 26 20:05:49 2019 +0200 @@ -22,6 +22,9 @@ <property name="tabChangesFocus"> <bool>true</bool> </property> + <property name="lineWrapMode"> + <enum>QPlainTextEdit::NoWrap</enum> + </property> <property name="readOnly"> <bool>true</bool> </property>
--- a/eric6/MicroPython/MicroPythonSerialPort.py Thu Jul 25 19:55:40 2019 +0200 +++ b/eric6/MicroPython/MicroPythonSerialPort.py Fri Jul 26 20:05:49 2019 +0200 @@ -119,8 +119,6 @@ break if size is not None and len(data) >= size: break -# else: -# break if t.elapsed() > self.__timeout: self.__timedOut = True break