eric6/MicroPython/MicroPythonFileManager.py

branch
micropython
changeset 7095
8e10acb1cd85
parent 7089
9f9816b19aa4
child 7101
40506ba8680c
equal deleted inserted replaced
7094:d5f340dfb986 7095:8e10acb1cd85
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing some file system commands for MicroPython.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13 import stat
14
15 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
16
17 from .MicroPythonFileSystemUtilities import (
18 mtime2string, mode2string, decoratedName, listdirStat
19 )
20
21
22 # TODO: modify to use MicroPythonCommandsInterface
23 class MicroPythonFileManager(QObject):
24 """
25 Class implementing an interface to the device file system commands with
26 some additional sugar.
27
28 @signal longListFiles(result) emitted with a tuple of tuples containing the
29 name, mode, size and time for each directory entry
30 @signal currentDir(dirname) emitted to report the current directory of the
31 device
32 @signal currentDirChanged(dirname) emitted to report back a change of the
33 current directory
34 @signal getFileDone(deviceFile, localFile) emitted after the file was
35 fetched from the connected device and written to the local file system
36 @signal putFileDone(localFile, deviceFile) emitted after the file was
37 copied to the connected device
38 @signal deleteFileDone(deviceFile) emitted after the file has been deleted
39 on the connected device
40 @signal rsyncDone(localName, deviceName) emitted after the rsync operation
41 has been completed
42 @signal rsyncProgressMessage(msg) emitted to send a message about what
43 rsync is doing
44 @signal removeDirectoryDone() emitted after a directory has been deleted
45 @signal createDirectoryDone() emitted after a directory was created
46 @signal fsinfoDone(fsinfo) emitted after the file system information was
47 obtained
48
49 @signal synchTimeDone() emitted after the time was synchronizde to the
50 device
51 @signal showTimeDone(dateTime) emitted after the date and time was fetched
52 from the connected device
53 @signal showVersionDone(versionInfo) emitted after the version information
54 was fetched from the connected device
55 @signal showImplementationDone(name,version) emitted after the
56 implementation information has been obtained
57
58 @signal error(exc) emitted with a failure message to indicate a failure
59 during the most recent operation
60 """
61 longListFiles = pyqtSignal(tuple)
62 currentDir = pyqtSignal(str)
63 currentDirChanged = pyqtSignal(str)
64 getFileDone = pyqtSignal(str, str)
65 putFileDone = pyqtSignal(str, str)
66 deleteFileDone = pyqtSignal(str)
67 rsyncDone = pyqtSignal(str, str)
68 rsyncProgressMessage = pyqtSignal(str)
69 removeDirectoryDone = pyqtSignal()
70 createDirectoryDone = pyqtSignal()
71 fsinfoDone = pyqtSignal(tuple)
72
73 synchTimeDone = pyqtSignal()
74 showTimeDone = pyqtSignal(str)
75 showVersionDone = pyqtSignal(dict)
76 showImplementationDone = pyqtSignal(str, str)
77
78 error = pyqtSignal(str, str)
79
80 def __init__(self, commandsInterface, parent=None):
81 """
82 Constructor
83
84 @param commandsInterface reference to the commands interface object
85 @type MicroPythonCommandsInterface
86 @param parent reference to the parent object
87 @type QObject
88 """
89 super(MicroPythonFileManager, self).__init__(parent)
90
91 self.__commandsInterface = commandsInterface
92
93 @pyqtSlot(str)
94 def lls(self, dirname):
95 """
96 Public slot to get a long listing of the given directory.
97
98 @param dirname name of the directory to list
99 @type str
100 """
101 try:
102 filesList = self.__commandsInterface.lls(dirname)
103 result = [(decoratedName(name, mode),
104 mode2string(mode),
105 str(size),
106 mtime2string(mtime)) for
107 name, (mode, size, mtime) in filesList]
108 self.longListFiles.emit(tuple(result))
109 except Exception as exc:
110 self.error.emit("lls", str(exc))
111
112 @pyqtSlot()
113 def pwd(self):
114 """
115 Public slot to get the current directory of the device.
116 """
117 try:
118 pwd = self.__commandsInterface.pwd()
119 self.currentDir.emit(pwd)
120 except Exception as exc:
121 self.error.emit("pwd", str(exc))
122
123 @pyqtSlot(str)
124 def cd(self, dirname):
125 """
126 Public slot to change the current directory of the device.
127
128 @param dirname name of the desired current directory
129 @type str
130 """
131 try:
132 self.__commandsInterface.cd(dirname)
133 self.currentDirChanged.emit(dirname)
134 except Exception as exc:
135 self.error.emit("cd", str(exc))
136
137 @pyqtSlot(str)
138 @pyqtSlot(str, str)
139 def get(self, deviceFileName, hostFileName=""):
140 """
141 Public slot to get a file from the connected device.
142
143 @param deviceFileName name of the file on the device
144 @type str
145 @param hostFileName name of the local file
146 @type str
147 """
148 if hostFileName and os.path.isdir(hostFileName):
149 # only a local directory was given
150 hostFileName = os.path.join(hostFileName,
151 os.path.basename(deviceFileName))
152 try:
153 self.__commandsInterface.get(deviceFileName, hostFileName)
154 self.getFileDone.emit(deviceFileName, hostFileName)
155 except Exception as exc:
156 self.error.emit("get", str(exc))
157
158 @pyqtSlot(str)
159 @pyqtSlot(str, str)
160 def put(self, hostFileName, deviceFileName=""):
161 """
162 Public slot to put a file onto the device.
163
164 @param hostFileName name of the local file
165 @type str
166 @param deviceFileName name of the file on the connected device
167 @type str
168 """
169 try:
170 self.__commandsInterface.put(hostFileName, deviceFileName)
171 self.putFileDone.emit(hostFileName, deviceFileName)
172 except Exception as exc:
173 self.error.emit("put", str(exc))
174
175 @pyqtSlot(str)
176 def delete(self, deviceFileName):
177 """
178 Public slot to delete a file on the device.
179
180 @param deviceFileName name of the file on the connected device
181 @type str
182 """
183 try:
184 self.__commandsInterface.rm(deviceFileName)
185 self.deleteFileDone.emit(deviceFileName)
186 except Exception as exc:
187 self.error.emit("delete", str(exc))
188
189 def __rsync(self, hostDirectory, deviceDirectory, mirror=True):
190 """
191 Private method to synchronize a local directory to the device.
192
193 @param hostDirectory name of the local directory
194 @type str
195 @param deviceDirectory name of the directory on the device
196 @type str
197 @param mirror flag indicating to mirror the local directory to
198 the device directory
199 @type bool
200 @return list of errors
201 @rtype list of str
202 """
203 errors = []
204
205 if not os.path.isdir(hostDirectory):
206 return [self.tr(
207 "The given name '{0}' is not a directory or does not exist.")
208 .format(hostDirectory)
209 ]
210
211 self.rsyncProgressMessage.emit(
212 self.tr("Synchronizing <b>{0}</b>.").format(deviceDirectory)
213 )
214
215 sourceDict = {}
216 sourceFiles = listdirStat(hostDirectory)
217 for name, nstat in sourceFiles:
218 sourceDict[name] = nstat
219
220 destinationDict = {}
221 try:
222 destinationFiles = self.__commandsInterface.lls(deviceDirectory,
223 fullstat=True)
224 except Exception as exc:
225 return [str(exc)]
226 if destinationFiles is None:
227 # the destination directory does not exist
228 try:
229 self.__commandsInterface.mkdir(deviceDirectory)
230 except Exception as exc:
231 return [str(exc)]
232 else:
233 for name, nstat in destinationFiles:
234 destinationDict[name] = nstat
235
236 destinationSet = set(destinationDict.keys())
237 sourceSet = set(sourceDict.keys())
238 toAdd = sourceSet - destinationSet # add to dev
239 toDelete = destinationSet - sourceSet # delete from dev
240 toUpdate = destinationSet.intersection(sourceSet) # update files
241
242 for sourceBasename in toAdd:
243 # name exists in source but not in device
244 sourceFilename = os.path.join(hostDirectory, sourceBasename)
245 destFilename = deviceDirectory + "/" + sourceBasename
246 self.rsyncProgressMessage.emit(
247 self.tr("Adding <b>{0}</b>...").format(destFilename))
248 if os.path.isfile(sourceFilename):
249 try:
250 self.__commandsInterface.put(sourceFilename, destFilename)
251 except Exception as exc:
252 # just note issues but ignore them otherwise
253 errors.append(str(exc))
254 if os.path.isdir(sourceFilename):
255 # recurse
256 errs = self.__rsync(sourceFilename, destFilename,
257 mirror=mirror)
258 # just note issues but ignore them otherwise
259 errors.extend(errs)
260
261 if mirror:
262 for destBasename in toDelete:
263 # name exists in device but not local, delete
264 destFilename = deviceDirectory + "/" + destBasename
265 self.rsyncProgressMessage.emit(
266 self.tr("Removing <b>{0}</b>...").format(destFilename))
267 try:
268 self.__commandsInterface.rmrf(destFilename, recursive=True,
269 force=True)
270 except Exception as exc:
271 # just note issues but ignore them otherwise
272 errors.append(str(exc))
273
274 for sourceBasename in toUpdate:
275 # names exist in both; do an update
276 sourceStat = sourceDict[sourceBasename]
277 destStat = destinationDict[sourceBasename]
278 sourceFilename = os.path.join(hostDirectory, sourceBasename)
279 destFilename = deviceDirectory + "/" + sourceBasename
280 destMode = destStat[0]
281 if os.path.isdir(sourceFilename):
282 if stat.S_ISDIR(destMode):
283 # both are directories => recurs
284 errs = self.__rsync(sourceFilename, destFilename,
285 mirror=mirror)
286 # just note issues but ignore them otherwise
287 errors.extend(errs)
288 else:
289 self.rsyncProgressMessage.emit(
290 self.tr("Source <b>{0}</b> is a directory and"
291 " destination <b>{1}</b> is a file. Ignoring"
292 " it.")
293 .format(sourceFilename, destFilename)
294 )
295 else:
296 if stat.S_ISDIR(destMode):
297 self.rsyncProgressMessage.emit(
298 self.tr("Source <b>{0}</b> is a file and destination"
299 " <b>{1}</b> is a directory. Ignoring it.")
300 .format(sourceFilename, destFilename)
301 )
302 else:
303 if sourceStat[8] > destStat[8]: # mtime
304 self.rsyncProgressMessage.emit(
305 self.tr("Updating <b>{0}</b>...")
306 .format(destFilename)
307 )
308 try:
309 self.__commandsInterface.put(sourceFilename,
310 destFilename)
311 except Exception as exc:
312 errors.append(str(exc))
313
314 self.rsyncProgressMessage.emit(
315 self.tr("Done synchronizing <b>{0}</b>.").format(deviceDirectory)
316 )
317
318 return errors
319
320 @pyqtSlot(str, str)
321 @pyqtSlot(str, str, bool)
322 def rsync(self, hostDirectory, deviceDirectory, mirror=True):
323 """
324 Public slot to synchronize a local directory to the device.
325
326 @param hostDirectory name of the local directory
327 @type str
328 @param deviceDirectory name of the directory on the device
329 @type str
330 @param mirror flag indicating to mirror the local directory to
331 the device directory
332 @type bool
333 """
334 errors = self.__rsync(hostDirectory, deviceDirectory, mirror=mirror)
335 if errors:
336 self.error.emit("rsync", "\n".join(errors))
337
338 self.rsyncDone.emit(hostDirectory, deviceDirectory)
339
340 @pyqtSlot(str)
341 def mkdir(self, dirname):
342 """
343 Public slot to create a new directory.
344
345 @param dirname name of the directory to create
346 @type str
347 """
348 try:
349 self.__commandsInterface.mkdir(dirname)
350 self.createDirectoryDone.emit()
351 except Exception as exc:
352 self.error.emit("mkdir", str(exc))
353
354 @pyqtSlot(str)
355 @pyqtSlot(str, bool)
356 def rmdir(self, dirname, recursive=False):
357 """
358 Public slot to (recursively) remove a directory.
359
360 @param dirname name of the directory to be removed
361 @type str
362 @param recursive flag indicating a recursive removal
363 @type bool
364 """
365 try:
366 if recursive:
367 self.__commandsInterface.rmrf(dirname, recursive=True,
368 force=True)
369 else:
370 self.__commandsInterface.rmdir(dirname)
371 self.removeDirectoryDone.emit()
372 except Exception as exc:
373 self.error.emit("rmdir", str(exc))
374
375 def fileSystemInfo(self):
376 """
377 Public method to obtain information about the currently mounted file
378 systems.
379 """
380 try:
381 fsinfo = self.__commandsInterface.fileSystemInfo()
382 self.fsinfoDone.emit(fsinfo)
383 except Exception as exc:
384 self.error.emit("fileSystemInfo", str(exc))
385
386 ##################################################################
387 ## some non-filesystem related methods below
388 ##################################################################
389
390 @pyqtSlot()
391 def synchronizeTime(self):
392 """
393 Public slot to set the time of the connected device to the local
394 computer's time.
395 """
396 try:
397 self.__commandsInterface.syncTime()
398 self.synchTimeDone.emit()
399 except Exception as exc:
400 self.error.emit("rmdir", str(exc))
401
402 @pyqtSlot()
403 def showTime(self):
404 """
405 Public slot to get the current date and time of the device.
406 """
407 try:
408 dt = self.__commandsInterface.showTime()
409 self.showTimeDone.emit(dt)
410 except Exception as exc:
411 self.error.emit("showTime", str(exc))
412
413 @pyqtSlot()
414 def showVersion(self):
415 """
416 Public slot to get the version info for the MicroPython run by the
417 connected device.
418 """
419 try:
420 versionInfo = self.__commandsInterface.version()
421 self.showVersionDone.emit(versionInfo)
422 except Exception as exc:
423 self.error.emit("showVersion", str(exc))
424
425 @pyqtSlot()
426 def showImplementation(self):
427 """
428 Public slot to obtain some implementation related information.
429 """
430 try:
431 impInfo = self.__commandsInterface.getImplementation()
432 if impInfo["name"] == "micropython":
433 name = "MicroPython"
434 elif impInfo["name"] == "circuitpython":
435 name = "CircuitPython"
436 elif impInfo["name"] == "unknown":
437 name = self.tr("unknown")
438 else:
439 name = impInfo["name"]
440 if impInfo["version"] == "unknown":
441 version = self.tr("unknown")
442 else:
443 version = impInfo["version"]
444 self.showImplementationDone.emit(name, version)
445 except Exception as exc:
446 self.error.emit("showVersion", str(exc))

eric ide

mercurial