Sun, 21 Jul 2019 19:54:15 +0200
MicroPython: started to implement the file manager widget.
--- a/eric6.e4p Sat Jul 20 14:48:09 2019 +0200 +++ b/eric6.e4p Sun Jul 21 19:54:15 2019 +0200 @@ -457,6 +457,7 @@ <Source>eric6/MicroPython/CircuitPythonDevices.py</Source> <Source>eric6/MicroPython/EspDevices.py</Source> <Source>eric6/MicroPython/MicroPythonDevices.py</Source> + <Source>eric6/MicroPython/MicroPythonFileManagerWidget.py</Source> <Source>eric6/MicroPython/MicroPythonFileSystem.py</Source> <Source>eric6/MicroPython/MicroPythonGraphWidget.py</Source> <Source>eric6/MicroPython/MicroPythonReplWidget.py</Source> @@ -1841,6 +1842,7 @@ <Form>eric6/HexEdit/HexEditReplaceWidget.ui</Form> <Form>eric6/HexEdit/HexEditSearchWidget.ui</Form> <Form>eric6/IconEditor/IconSizeDialog.ui</Form> + <Form>eric6/MicroPython/MicroPythonFileManagerWidget.ui</Form> <Form>eric6/MicroPython/MicroPythonReplWidget.ui</Form> <Form>eric6/MultiProject/AddProjectDialog.ui</Form> <Form>eric6/MultiProject/PropertiesDialog.ui</Form> @@ -2300,14 +2302,14 @@ <Other>docs/THANKS</Other> <Other>docs/changelog</Other> <Other>eric6.e4p</Other> + <Other>eric6/APIs/Python/zope-2.10.7.api</Other> + <Other>eric6/APIs/Python/zope-2.11.2.api</Other> + <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/Python3/PyQt4.bas</Other> <Other>eric6/APIs/Python3/PyQt5.bas</Other> <Other>eric6/APIs/Python3/QScintilla2.bas</Other> <Other>eric6/APIs/Python3/eric6.api</Other> <Other>eric6/APIs/Python3/eric6.bas</Other> - <Other>eric6/APIs/Python/zope-2.10.7.api</Other> - <Other>eric6/APIs/Python/zope-2.11.2.api</Other> - <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/QSS/qss.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.bas</Other>
--- a/eric6/MicroPython/EspDevices.py Sat Jul 20 14:48:09 2019 +0200 +++ b/eric6/MicroPython/EspDevices.py Sun Jul 21 19:54:15 2019 +0200 @@ -127,7 +127,6 @@ pythonScript = script.split("\n") self.sendCommands(pythonScript) - # TODO: not yet implemented def canStartFileManager(self): """ Public method to determine, if a File Manager can be started. @@ -136,9 +135,14 @@ File Manager and a reason why it cannot. @rtype tuple of (bool, str) """ - return False, self.tr("File Manager is not supported by this device.") + if self.__replActive or self.__plotterActive: + return False, self.tr("The file manager and the REPL/plotter use" + " the same USB serial connection. Only one" + " can be active at any time. Disconnect the" + " REPL/plotter and try again.") + else: + return True, "" - # TODO: not yet implemented def setFileManager(self, on): """ Public method to set the File Manager status and dependent status. @@ -146,7 +150,9 @@ @param on flag indicating the active status @type bool """ - pass + self.__fileManagerActive = on + self.microPython.setActionButtons( + run=not on, repl=not on, chart=HAS_QTCHART and not on) @pyqtSlot() def handleDataFlood(self):
--- a/eric6/MicroPython/MicroPythonFileSystem.py Sat Jul 20 14:48:09 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileSystem.py Sun Jul 21 19:54:15 2019 +0200 @@ -13,7 +13,9 @@ import time import stat -from PyQt5.QtCore import QObject, QThread +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QThread + +from .MicroPythonSerialPort import MicroPythonSerialPort class MicroPythonFileSystem(QObject): @@ -328,60 +330,132 @@ @exception IOError raised to indicate an issue with the device """ # TODO: not implemented yet + + +class MicroPythonFileManager(QObject): + """ + Class implementing an interface to the device file system commands with + some additional sugar. - ################################################################## - ## Utility methods below - ################################################################## + @signal longListFiles(result) emitted with a tuple of tuples containing the + name, mode, size and time for each directory entry + @signal currentDir(dirname) emitted to report the current directory of the + device - def mtime2string(self, mtime): + @signal longListFilesFailed(exc) emitted with a failure message to indicate + a failed long listing operation + @signal currentDirFailed(exc) emitted with a failure message to indicate + that the current directory is not available + """ + longListFiles = pyqtSignal(tuple) + currentDir = pyqtSignal(str) + + longListFilesFailed = pyqtSignal(str) + currentDirFailed = pyqtSignal(str) + + def __init__(self, port, parent=None): """ - Public method to convert a time value to a string representation. + Constructor - @param mtime time value - @type int - @return string representation of the given time - @rtype str + @param port port name of the device + @type str + @param parent reference to the parent object + @type QObject """ - return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime)) + super(MicroPythonFileManager, self).__init__(parent) + + self.__serialPort = port + self.__serial = MicroPythonSerialPort(parent=self) + self.__fs = MicroPythonFileSystem(parent=self) - def mode2string(self, mode): + @pyqtSlot() + def connect(self): + """ + Public slot to start the manager. """ - Public method to convert a mode value to a string representation. + self.__serial.openSerialLink(self.__serialPort) + self.__fs.setSerial(self.__serial) + + @pyqtSlot() + def disconnect(self): + """ + Public slot to stop the thread. + """ + self.__serial.closeSerialLink() + + @pyqtSlot(str) + def lls(self, dirname): + """ + Public method to get a long listing of the given directory. - @param mode mode value - @type int - @return string representation of the given mode value - @rtype str + @param dirname name of the directory to list + @type str """ - return stat.filemode(mode) -# TODO: remove this -## -##if __name__ == "__main__": -## from PyQt5.QtCore import QCoreApplication, QTimer -## from MicroPythonSerialPort import MicroPythonSerialPort -## -## app = QCoreApplication([]) -## -## serial = MicroPythonSerialPort() -## serial.openSerialLink("/dev/ttyUSB0") -## fs = MicroPythonFileSystem() -## fs.setSerial(serial) -## -## def tf(): -## fs.cd("/flash") -## print(fs.pwd()) -## fs.cd("odroid_go") -## print(fs.pwd()) -## ll = fs.lls() -## print(ll) -## for f, (m, s, t) in ll: -## print(fs.mode2string(m), s, fs.mtime2string(t), f) -## fs.cd("..") -## print(fs.pwd()) -## ll = fs.lls("odroid_go") -## print(ll) -## for f, (m, s, t) in ll: -## print(fs.mode2string(m), s, fs.mtime2string(t), f) -## -## QTimer.singleShot(0, tf) -## app.exec_() + try: + filesList = self.__fs.lls(dirname) + result = [(decoratedName(name, mode), + mode2string(mode), + str(size), + mtime2string(time)) for + name, (mode, size, time) in filesList] + self.longListFiles.emit(tuple(result)) + except Exception as exc: + self.longListFilesFailed.emit(str(exc)) + + @pyqtSlot() + def pwd(self): + """ + Public method to get the current directory of the device. + """ + try: + pwd = self.__fs.pwd() + self.currentDir.emit(pwd) + except Exception as exc: + self.currentDirFailed.emit(str(exc)) + +################################################################## +## Utility methods below +################################################################## + + +def mtime2string(mtime): + """ + Function to convert a time value to a string representation. + + @param mtime time value + @type int + @return string representation of the given time + @rtype str + """ + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime)) + + +def mode2string(mode): + """ + Function to convert a mode value to a string representation. + + @param mode mode value + @type int + @return string representation of the given mode value + @rtype str + """ + return stat.filemode(mode) + + +def decoratedName(name, mode): + """ + Function to decorate the given name according to the given mode. + + @param name file or directory name + @type str + @param mode mode value + @type int + @return decorated file or directory name + @rtype str + """ + if stat.S_ISDIR(mode): + # append a '/' for directories + return name + "/" + else: + # no change + return name
--- a/eric6/MicroPython/MicroPythonReplWidget.py Sat Jul 20 14:48:09 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Sun Jul 21 19:54:15 2019 +0200 @@ -38,6 +38,7 @@ HAS_QTCHART = True except ImportError: HAS_QTCHART = False +from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget import Globals import UI.PixmapCache @@ -878,9 +879,6 @@ self.__showNoDeviceMessage() return - if self.__ui is None: - self.__ui = e5App().getObject("UserInterface") - if self.__plotterRunning: if self.__chartWidget.isDirty(): res = E5MessageBox.okToClearData( @@ -939,3 +937,42 @@ """ self.on_disconnectButton_clicked() self.__device.handleDataFlood() + + @pyqtSlot() + def on_filesButton_clicked(self): + """ + Private slot to open a file manager window to the connected device. + """ + if not self.__device: + self.__showNoDeviceMessage() + return + + if self.__fileManagerRunning: + self.__fileManagerWidget.stop() + self.__ui.removeSideWidget(self.__fileManagerWidget) + + self.__device.setFileManager(False) + self.__fileManagerRunning = False + else: + ok, reason = self.__device.canStartFileManager() + if not ok: + E5MessageBox.warning( + self, + self.tr("Start File Manager"), + self.tr("""<p>The File Manager cannot be started.</p>""" + """<p>Reason: {0}</p>""").format(reason)) + return + + port = self.__getCurrentPort() + self.__fileManagerWidget = MicroPythonFileManagerWidget(port, self) + + self.__ui.addSideWidget(self.__ui.BottomSide, + self.__fileManagerWidget, + UI.PixmapCache.getIcon("filemanager"), + self.tr("μPy Files")) + self.__ui.showSideWidget(self.__fileManagerWidget) + + self.__device.setFileManager(True) + self.__fileManagerRunning = True + + self.__fileManagerWidget.start()
--- a/eric6/MicroPython/MicroPythonSerialPort.py Sat Jul 20 14:48:09 2019 +0200 +++ b/eric6/MicroPython/MicroPythonSerialPort.py Sun Jul 21 19:54:15 2019 +0200 @@ -10,7 +10,7 @@ from __future__ import unicode_literals -from PyQt5.QtCore import QIODevice, QTime +from PyQt5.QtCore import QIODevice, QTime, QCoreApplication from PyQt5.QtSerialPort import QSerialPort @@ -69,7 +69,7 @@ """ Public method to close the open serial connection. """ - if self.__connceted: + if self.__connected: self.close() self.__connected = False @@ -95,15 +95,12 @@ @return bytes read from the device including the expected sequence @rtype bytes """ - from PyQt5.QtCore import QCoreApplication, QEventLoop data = bytearray() t = QTime() t.start() while True: - # TODO: check if this is still needed when used with a QThread - QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) -## if self.waitForReadyRead(self.__timeout): + QCoreApplication.processEvents() c = bytes(self.read(1)) if c: data += c @@ -115,7 +112,5 @@ # break if t.elapsed() > self.__timeout: break -## else: -## break return bytes(data)