src/eric7/MicroPython/MicroPythonFileManagerWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a file manager for MicroPython devices.
8 """
9
10 import os
11 import shutil
12
13 from PyQt6.QtCore import pyqtSlot, Qt, QPoint
14 from PyQt6.QtWidgets import (
15 QWidget, QTreeWidgetItem, QHeaderView, QMenu, QInputDialog, QLineEdit,
16 QDialog
17 )
18
19 from EricWidgets import EricMessageBox, EricPathPickerDialog
20 from EricWidgets.EricPathPicker import EricPathPickerModes
21 from EricWidgets.EricFileSaveConfirmDialog import confirmOverwrite
22 from EricWidgets.EricApplication import ericApp
23
24 from .Ui_MicroPythonFileManagerWidget import Ui_MicroPythonFileManagerWidget
25
26 from .MicroPythonFileManager import MicroPythonFileManager
27 from .MicroPythonFileSystemUtilities import (
28 mtime2string, mode2string, decoratedName, listdirStat
29 )
30
31 from UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
32
33 import UI.PixmapCache
34 import Preferences
35 import Utilities
36 import Globals
37
38
39 class MicroPythonFileManagerWidget(QWidget, Ui_MicroPythonFileManagerWidget):
40 """
41 Class implementing a file manager for MicroPython devices.
42 """
43 def __init__(self, commandsInterface, deviceWithLocalAccess, parent=None):
44 """
45 Constructor
46
47 @param commandsInterface reference to the commands interface object
48 @type MicroPythonCommandsInterface
49 @param deviceWithLocalAccess flag indicating the device supports file
50 access via a local directory
51 @type bool
52 @param parent reference to the parent widget
53 @type QWidget
54 """
55 super().__init__(parent)
56 self.setupUi(self)
57
58 self.__repl = parent
59 self.__deviceWithLocalAccess = deviceWithLocalAccess
60
61 self.syncButton.setIcon(UI.PixmapCache.getIcon("2rightarrow"))
62 self.putButton.setIcon(UI.PixmapCache.getIcon("1rightarrow"))
63 self.putAsButton.setIcon(UI.PixmapCache.getIcon("putAs"))
64 self.getButton.setIcon(UI.PixmapCache.getIcon("1leftarrow"))
65 self.getAsButton.setIcon(UI.PixmapCache.getIcon("getAs"))
66 self.localUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
67 self.localHomeButton.setIcon(UI.PixmapCache.getIcon("home"))
68 self.localReloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
69 self.deviceUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
70 self.deviceHomeButton.setIcon(UI.PixmapCache.getIcon("home"))
71 self.deviceReloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
72
73 self.deviceUpButton.setEnabled(not self.__repl.isMicrobit())
74 self.deviceHomeButton.setEnabled(not self.__repl.isMicrobit())
75
76 self.putButton.setEnabled(False)
77 self.putAsButton.setEnabled(False)
78 self.getButton.setEnabled(False)
79 self.getAsButton.setEnabled(False)
80
81 self.localFileTreeWidget.header().setSortIndicator(
82 0, Qt.SortOrder.AscendingOrder)
83 self.deviceFileTreeWidget.header().setSortIndicator(
84 0, Qt.SortOrder.AscendingOrder)
85
86 self.__progressInfoDialog = None
87 self.__fileManager = MicroPythonFileManager(commandsInterface, self)
88
89 self.__fileManager.longListFiles.connect(self.__handleLongListFiles)
90 self.__fileManager.currentDir.connect(self.__handleCurrentDir)
91 self.__fileManager.currentDirChanged.connect(self.__handleCurrentDir)
92 self.__fileManager.putFileDone.connect(self.__newDeviceList)
93 self.__fileManager.getFileDone.connect(self.__handleGetDone)
94 self.__fileManager.rsyncDone.connect(self.__handleRsyncDone)
95 self.__fileManager.rsyncProgressMessage.connect(
96 self.__handleRsyncProgressMessage)
97 self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList)
98 self.__fileManager.createDirectoryDone.connect(self.__newDeviceList)
99 self.__fileManager.deleteFileDone.connect(self.__newDeviceList)
100 self.__fileManager.fsinfoDone.connect(self.__fsInfoResultReceived)
101
102 self.__fileManager.error.connect(self.__handleError)
103
104 self.localFileTreeWidget.customContextMenuRequested.connect(
105 self.__showLocalContextMenu)
106 self.deviceFileTreeWidget.customContextMenuRequested.connect(
107 self.__showDeviceContextMenu)
108
109 self.__localMenu = QMenu(self)
110 self.__localMenu.addAction(self.tr("Change Directory"),
111 self.__changeLocalDirectory)
112 self.__localMenu.addAction(
113 self.tr("Create Directory"), self.__createLocalDirectory)
114 self.__localDelDirTreeAct = self.__localMenu.addAction(
115 self.tr("Delete Directory Tree"), self.__deleteLocalDirectoryTree)
116 self.__localMenu.addSeparator()
117 self.__localDelFileAct = self.__localMenu.addAction(
118 self.tr("Delete File"), self.__deleteLocalFile)
119 self.__localMenu.addSeparator()
120 act = self.__localMenu.addAction(self.tr("Show Hidden Files"))
121 act.setCheckable(True)
122 act.setChecked(Preferences.getMicroPython("ShowHiddenLocal"))
123 act.triggered[bool].connect(self.__localHiddenChanged)
124
125 self.__deviceMenu = QMenu(self)
126 if not self.__repl.isMicrobit():
127 self.__deviceMenu.addAction(
128 self.tr("Change Directory"), self.__changeDeviceDirectory)
129 self.__deviceMenu.addAction(
130 self.tr("Create Directory"), self.__createDeviceDirectory)
131 if not self.__deviceWithLocalAccess:
132 self.__devDelDirAct = self.__deviceMenu.addAction(
133 self.tr("Delete Directory"), self.__deleteDeviceDirectory)
134 self.__devDelDirTreeAct = self.__deviceMenu.addAction(
135 self.tr("Delete Directory Tree"),
136 self.__deleteDeviceDirectoryTree)
137 self.__deviceMenu.addSeparator()
138 self.__devDelFileAct = self.__deviceMenu.addAction(
139 self.tr("Delete File"), self.__deleteDeviceFile)
140 self.__deviceMenu.addSeparator()
141 act = self.__deviceMenu.addAction(self.tr("Show Hidden Files"))
142 act.setCheckable(True)
143 act.setChecked(Preferences.getMicroPython("ShowHiddenDevice"))
144 act.triggered[bool].connect(self.__deviceHiddenChanged)
145 if not parent.isMicrobit():
146 self.__deviceMenu.addSeparator()
147 self.__deviceMenu.addAction(
148 self.tr("Show Filesystem Info"), self.__showFileSystemInfo)
149
150 def start(self):
151 """
152 Public method to start the widget.
153 """
154 dirname = ""
155 vm = ericApp().getObject("ViewManager")
156 aw = vm.activeWindow()
157 if aw:
158 dirname = os.path.dirname(aw.getFileName())
159 if not dirname:
160 dirname = (
161 Preferences.getMicroPython("MpyWorkspace") or
162 Preferences.getMultiProject("Workspace") or
163 os.path.expanduser("~")
164 )
165 self.__listLocalFiles(dirname)
166
167 if self.__deviceWithLocalAccess:
168 dirname = self.__repl.getDeviceWorkspace()
169 if dirname:
170 self.__listLocalFiles(dirname, True)
171 return
172
173 # list files via device script
174 self.__fileManager.pwd()
175
176 def stop(self):
177 """
178 Public method to stop the widget.
179 """
180 pass
181
182 @pyqtSlot(str, str)
183 def __handleError(self, method, error):
184 """
185 Private slot to handle errors.
186
187 @param method name of the method the error occured in
188 @type str
189 @param error error message
190 @type str
191 """
192 EricMessageBox.warning(
193 self,
194 self.tr("Error handling device"),
195 self.tr("<p>There was an error communicating with the connected"
196 " device.</p><p>Method: {0}</p><p>Message: {1}</p>")
197 .format(method, error))
198
199 @pyqtSlot(str)
200 def __handleCurrentDir(self, dirname):
201 """
202 Private slot to handle a change of the current directory of the device.
203
204 @param dirname name of the current directory
205 @type str
206 """
207 self.deviceCwd.setText(dirname)
208 self.__newDeviceList()
209
210 @pyqtSlot(tuple)
211 def __handleLongListFiles(self, filesList):
212 """
213 Private slot to receive a long directory listing.
214
215 @param filesList tuple containing tuples with name, mode, size and time
216 for each directory entry
217 @type tuple of (str, str, str, str)
218 """
219 self.deviceFileTreeWidget.clear()
220 for name, mode, size, dateTime in filesList:
221 itm = QTreeWidgetItem(self.deviceFileTreeWidget,
222 [name, mode, size, dateTime])
223 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter)
224 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight)
225 self.deviceFileTreeWidget.header().resizeSections(
226 QHeaderView.ResizeMode.ResizeToContents)
227
228 def __listLocalFiles(self, dirname="", localDevice=False):
229 """
230 Private method to populate the local files list.
231
232 @param dirname name of the local directory to be listed
233 @type str
234 @param localDevice flag indicating device access via local file system
235 @type bool
236 """
237 if not dirname:
238 dirname = os.getcwd()
239 if dirname.endswith(os.sep):
240 dirname = dirname[:-1]
241 if localDevice:
242 self.deviceCwd.setText(dirname)
243 showHidden = Preferences.getMicroPython("ShowHiddenDevice")
244 else:
245 self.localCwd.setText(dirname)
246 showHidden = Preferences.getMicroPython("ShowHiddenLocal")
247
248 filesStatList = listdirStat(dirname, showHidden=showHidden)
249 filesList = [(
250 decoratedName(f, s[0], os.path.isdir(os.path.join(dirname, f))),
251 mode2string(s[0]),
252 str(s[6]),
253 mtime2string(s[8])) for f, s in filesStatList]
254 fileTreeWidget = (
255 self.deviceFileTreeWidget
256 if localDevice else
257 self.localFileTreeWidget
258 )
259 fileTreeWidget.clear()
260 for item in filesList:
261 itm = QTreeWidgetItem(fileTreeWidget, item)
262 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter)
263 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight)
264 fileTreeWidget.header().resizeSections(
265 QHeaderView.ResizeMode.ResizeToContents)
266
267 @pyqtSlot(QTreeWidgetItem, int)
268 def on_localFileTreeWidget_itemActivated(self, item, column):
269 """
270 Private slot to handle the activation of a local item.
271
272 If the item is a directory, the list will be re-populated for this
273 directory.
274
275 @param item reference to the activated item
276 @type QTreeWidgetItem
277 @param column column of the activation
278 @type int
279 """
280 name = os.path.join(self.localCwd.text(), item.text(0))
281 if name.endswith("/"):
282 # directory names end with a '/'
283 self.__listLocalFiles(name[:-1])
284 elif Utilities.MimeTypes.isTextFile(name):
285 ericApp().getObject("ViewManager").getEditor(name)
286
287 @pyqtSlot()
288 def on_localFileTreeWidget_itemSelectionChanged(self):
289 """
290 Private slot handling a change of selection in the local pane.
291 """
292 enable = bool(len(self.localFileTreeWidget.selectedItems()))
293 if enable:
294 enable &= not (
295 self.localFileTreeWidget.selectedItems()[0].text(0)
296 .endswith("/"))
297 self.putButton.setEnabled(enable)
298 self.putAsButton.setEnabled(enable)
299
300 @pyqtSlot()
301 def on_localUpButton_clicked(self):
302 """
303 Private slot to go up one directory level.
304 """
305 cwd = self.localCwd.text()
306 dirname = os.path.dirname(cwd)
307 self.__listLocalFiles(dirname)
308
309 @pyqtSlot()
310 def on_localHomeButton_clicked(self):
311 """
312 Private slot to change directory to the configured workspace.
313 """
314 dirname = (
315 Preferences.getMicroPython("MpyWorkspace") or
316 Preferences.getMultiProject("Workspace") or
317 os.path.expanduser("~")
318 )
319 self.__listLocalFiles(dirname)
320
321 @pyqtSlot()
322 def on_localReloadButton_clicked(self):
323 """
324 Private slot to reload the local list.
325 """
326 dirname = self.localCwd.text()
327 self.__listLocalFiles(dirname)
328
329 @pyqtSlot(QTreeWidgetItem, int)
330 def on_deviceFileTreeWidget_itemActivated(self, item, column):
331 """
332 Private slot to handle the activation of a device item.
333
334 If the item is a directory, the current working directory is changed
335 and the list will be re-populated for this directory.
336
337 @param item reference to the activated item
338 @type QTreeWidgetItem
339 @param column column of the activation
340 @type int
341 """
342 if self.__deviceWithLocalAccess:
343 name = os.path.join(self.deviceCwd.text(), item.text(0))
344 if name.endswith("/"):
345 # directory names end with a '/'
346 self.__listLocalFiles(name[:-1], True)
347 elif Utilities.MimeTypes.isTextFile(name):
348 ericApp().getObject("ViewManager").getEditor(name)
349 else:
350 cwd = self.deviceCwd.text()
351 if cwd.endswith("/"):
352 name = cwd + item.text(0)
353 else:
354 name = "{0}/{1}".format(cwd, item.text(0))
355 if name.endswith("/"):
356 # directory names end with a '/'
357 self.__fileManager.cd(name[:-1])
358
359 @pyqtSlot()
360 def on_deviceFileTreeWidget_itemSelectionChanged(self):
361 """
362 Private slot handling a change of selection in the local pane.
363 """
364 enable = bool(len(self.deviceFileTreeWidget.selectedItems()))
365 if enable:
366 enable &= not (
367 self.deviceFileTreeWidget.selectedItems()[0].text(0)
368 .endswith("/"))
369 self.getButton.setEnabled(enable)
370 self.getAsButton.setEnabled(enable)
371
372 @pyqtSlot()
373 def on_deviceUpButton_clicked(self):
374 """
375 Private slot to go up one directory level on the device.
376 """
377 cwd = self.deviceCwd.text()
378 dirname = os.path.dirname(cwd)
379 if self.__deviceWithLocalAccess:
380 self.__listLocalFiles(dirname, True)
381 else:
382 self.__fileManager.cd(dirname)
383
384 @pyqtSlot()
385 def on_deviceHomeButton_clicked(self):
386 """
387 Private slot to move to the device home directory.
388 """
389 if self.__deviceWithLocalAccess:
390 dirname = self.__repl.getDeviceWorkspace()
391 if dirname:
392 self.__listLocalFiles(dirname, True)
393 return
394
395 # list files via device script
396 self.__fileManager.cd("/")
397
398 @pyqtSlot()
399 def on_deviceReloadButton_clicked(self):
400 """
401 Private slot to reload the device list.
402 """
403 dirname = self.deviceCwd.text()
404 if self.__deviceWithLocalAccess:
405 self.__listLocalFiles(dirname, True)
406 else:
407 if dirname:
408 self.__newDeviceList()
409 else:
410 self.__fileManager.pwd()
411
412 def __isFileInList(self, filename, treeWidget):
413 """
414 Private method to check, if a file name is contained in a tree widget.
415
416 @param filename name of the file to check
417 @type str
418 @param treeWidget reference to the tree widget to be checked against
419 @return flag indicating that the file name is present
420 @rtype bool
421 """
422 itemCount = treeWidget.topLevelItemCount()
423 return (
424 itemCount > 0 and
425 any(treeWidget.topLevelItem(row).text(0) == filename
426 for row in range(itemCount))
427 )
428
429 @pyqtSlot()
430 def on_putButton_clicked(self, putAs=False):
431 """
432 Private slot to copy the selected file to the connected device.
433
434 @param putAs flag indicating to give it a new name
435 @type bool
436 """
437 selectedItems = self.localFileTreeWidget.selectedItems()
438 if selectedItems:
439 filename = selectedItems[0].text(0).strip()
440 if not filename.endswith("/"):
441 # it is really a file
442 if putAs:
443 deviceFilename, ok = QInputDialog.getText(
444 self,
445 self.tr("Put File As"),
446 self.tr("Enter a new name for the file"),
447 QLineEdit.EchoMode.Normal,
448 filename)
449 if not ok or not filename:
450 return
451 else:
452 deviceFilename = filename
453
454 if self.__isFileInList(deviceFilename,
455 self.deviceFileTreeWidget):
456 # ask for overwrite permission
457 action, resultFilename = confirmOverwrite(
458 deviceFilename, self.tr("Copy File to Device"),
459 self.tr("The given file exists already"
460 " (Enter file name only)."),
461 False, self)
462 if action == "cancel":
463 return
464 elif action == "rename":
465 deviceFilename = os.path.basename(resultFilename)
466
467 if self.__deviceWithLocalAccess:
468 shutil.copy2(
469 os.path.join(self.localCwd.text(), filename),
470 os.path.join(self.deviceCwd.text(), deviceFilename)
471 )
472 self.__listLocalFiles(self.deviceCwd.text(),
473 localDevice=True)
474 else:
475 deviceCwd = self.deviceCwd.text()
476 if deviceCwd:
477 if deviceCwd != "/":
478 deviceFilename = deviceCwd + "/" + deviceFilename
479 else:
480 deviceFilename = "/" + deviceFilename
481 self.__fileManager.put(
482 os.path.join(self.localCwd.text(), filename),
483 deviceFilename
484 )
485
486 @pyqtSlot()
487 def on_putAsButton_clicked(self):
488 """
489 Private slot to copy the selected file to the connected device
490 with a different name.
491 """
492 self.on_putButton_clicked(putAs=True)
493
494 @pyqtSlot()
495 def on_getButton_clicked(self, getAs=False):
496 """
497 Private slot to copy the selected file from the connected device.
498
499 @param getAs flag indicating to give it a new name
500 @type bool
501 """
502 selectedItems = self.deviceFileTreeWidget.selectedItems()
503 if selectedItems:
504 filename = selectedItems[0].text(0).strip()
505 if not filename.endswith("/"):
506 # it is really a file
507 if getAs:
508 localFilename, ok = QInputDialog.getText(
509 self,
510 self.tr("Get File As"),
511 self.tr("Enter a new name for the file"),
512 QLineEdit.EchoMode.Normal,
513 filename)
514 if not ok or not filename:
515 return
516 else:
517 localFilename = filename
518
519 if self.__isFileInList(localFilename,
520 self.localFileTreeWidget):
521 # ask for overwrite permission
522 action, resultFilename = confirmOverwrite(
523 localFilename, self.tr("Copy File from Device"),
524 self.tr("The given file exists already."),
525 True, self)
526 if action == "cancel":
527 return
528 elif action == "rename":
529 localFilename = resultFilename
530
531 if self.__deviceWithLocalAccess:
532 shutil.copy2(
533 os.path.join(self.deviceCwd.text(), filename),
534 os.path.join(self.localCwd.text(), localFilename)
535 )
536 self.__listLocalFiles(self.localCwd.text())
537 else:
538 deviceCwd = self.deviceCwd.text()
539 if deviceCwd:
540 filename = deviceCwd + "/" + filename
541 self.__fileManager.get(
542 filename,
543 os.path.join(self.localCwd.text(), localFilename)
544 )
545
546 @pyqtSlot()
547 def on_getAsButton_clicked(self):
548 """
549 Private slot to copy the selected file from the connected device
550 with a different name.
551 """
552 self.on_getButton_clicked(getAs=True)
553
554 @pyqtSlot(str, str)
555 def __handleGetDone(self, deviceFile, localFile):
556 """
557 Private slot handling a successful copy of a file from the device.
558
559 @param deviceFile name of the file on the device
560 @type str
561 @param localFile name of the local file
562 @type str
563 """
564 self.__listLocalFiles(self.localCwd.text())
565
566 @pyqtSlot()
567 def on_syncButton_clicked(self):
568 """
569 Private slot to synchronize the local directory to the device.
570 """
571 self.__fileManager.rsync(
572 self.localCwd.text(),
573 self.deviceCwd.text(),
574 mirror=True,
575 localDevice=self.__deviceWithLocalAccess,
576 )
577
578 @pyqtSlot(str, str)
579 def __handleRsyncDone(self, localDir, deviceDir):
580 """
581 Private method to handle the completion of the rsync operation.
582
583 @param localDir name of the local directory
584 @type str
585 @param deviceDir name of the device directory
586 @type str
587 """
588 # simulate button presses to reload the two lists
589 self.on_localReloadButton_clicked()
590 self.on_deviceReloadButton_clicked()
591
592 @pyqtSlot(str)
593 def __handleRsyncProgressMessage(self, message):
594 """
595 Private slot handling progress messages sent by the file manager.
596
597 @param message message to be shown
598 @type str
599 """
600 if self.__progressInfoDialog is None:
601 from .MicroPythonProgressInfoDialog import (
602 MicroPythonProgressInfoDialog
603 )
604 self.__progressInfoDialog = MicroPythonProgressInfoDialog(self)
605 self.__progressInfoDialog.finished.connect(
606 self.__progressInfoDialogFinished)
607 self.__progressInfoDialog.show()
608 self.__progressInfoDialog.addMessage(message)
609
610 @pyqtSlot()
611 def __progressInfoDialogFinished(self):
612 """
613 Private slot handling the closing of the progress info dialog.
614 """
615 self.__progressInfoDialog.deleteLater()
616 self.__progressInfoDialog = None
617
618 @pyqtSlot()
619 def __newDeviceList(self):
620 """
621 Private slot to initiate a new long list of the device directory.
622 """
623 self.__fileManager.lls(
624 self.deviceCwd.text(),
625 showHidden=Preferences.getMicroPython("ShowHiddenDevice")
626 )
627
628 ##################################################################
629 ## Context menu methods for the local files below
630 ##################################################################
631
632 @pyqtSlot(QPoint)
633 def __showLocalContextMenu(self, pos):
634 """
635 Private slot to show the REPL context menu.
636
637 @param pos position to show the menu at
638 @type QPoint
639 """
640 hasSelection = bool(len(self.localFileTreeWidget.selectedItems()))
641 if hasSelection:
642 name = self.localFileTreeWidget.selectedItems()[0].text(0)
643 isDir = name.endswith("/")
644 isFile = not isDir
645 else:
646 isDir = False
647 isFile = False
648 self.__localDelDirTreeAct.setEnabled(isDir)
649 self.__localDelFileAct.setEnabled(isFile)
650
651 self.__localMenu.exec(self.localFileTreeWidget.mapToGlobal(pos))
652
653 @pyqtSlot()
654 def __changeLocalDirectory(self, localDevice=False):
655 """
656 Private slot to change the local directory.
657
658 @param localDevice flag indicating device access via local file system
659 @type bool
660 """
661 cwdWidget = self.deviceCwd if localDevice else self.localCwd
662
663 dirPath, ok = EricPathPickerDialog.getPath(
664 self,
665 self.tr("Change Directory"),
666 self.tr("Select Directory"),
667 EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE,
668 path=cwdWidget.text(),
669 defaultDirectory=cwdWidget.text(),
670 )
671 if ok and dirPath:
672 if not os.path.isabs(dirPath):
673 dirPath = os.path.join(cwdWidget.text(), dirPath)
674 cwdWidget.setText(dirPath)
675 self.__listLocalFiles(dirPath, localDevice=localDevice)
676
677 @pyqtSlot()
678 def __createLocalDirectory(self, localDevice=False):
679 """
680 Private slot to create a local directory.
681
682 @param localDevice flag indicating device access via local file system
683 @type bool
684 """
685 cwdWidget = self.deviceCwd if localDevice else self.localCwd
686
687 dirPath, ok = QInputDialog.getText(
688 self,
689 self.tr("Create Directory"),
690 self.tr("Enter directory name:"),
691 QLineEdit.EchoMode.Normal)
692 if ok and dirPath:
693 dirPath = os.path.join(cwdWidget.text(), dirPath)
694 try:
695 os.mkdir(dirPath)
696 self.__listLocalFiles(cwdWidget.text(),
697 localDevice=localDevice)
698 except OSError as exc:
699 EricMessageBox.critical(
700 self,
701 self.tr("Create Directory"),
702 self.tr("""<p>The directory <b>{0}</b> could not be"""
703 """ created.</p><p>Reason: {1}</p>""").format(
704 dirPath, str(exc))
705 )
706
707 @pyqtSlot()
708 def __deleteLocalDirectoryTree(self, localDevice=False):
709 """
710 Private slot to delete a local directory tree.
711
712 @param localDevice flag indicating device access via local file system
713 @type bool
714 """
715 if localDevice:
716 cwdWidget = self.deviceCwd
717 fileTreeWidget = self.deviceFileTreeWidget
718 else:
719 cwdWidget = self.localCwd
720 fileTreeWidget = self.localFileTreeWidget
721
722 if bool(len(fileTreeWidget.selectedItems())):
723 name = fileTreeWidget.selectedItems()[0].text(0)
724 dirname = os.path.join(cwdWidget.text(), name[:-1])
725 dlg = DeleteFilesConfirmationDialog(
726 self,
727 self.tr("Delete Directory Tree"),
728 self.tr(
729 "Do you really want to delete this directory tree?"),
730 [dirname])
731 if dlg.exec() == QDialog.DialogCode.Accepted:
732 try:
733 shutil.rmtree(dirname)
734 self.__listLocalFiles(cwdWidget.text(),
735 localDevice=localDevice)
736 except Exception as exc:
737 EricMessageBox.critical(
738 self,
739 self.tr("Delete Directory Tree"),
740 self.tr("""<p>The directory <b>{0}</b> could not be"""
741 """ deleted.</p><p>Reason: {1}</p>""").format(
742 dirname, str(exc))
743 )
744
745 @pyqtSlot()
746 def __deleteLocalFile(self, localDevice=False):
747 """
748 Private slot to delete a local file.
749
750 @param localDevice flag indicating device access via local file system
751 @type bool
752 """
753 if localDevice:
754 cwdWidget = self.deviceCwd
755 fileTreeWidget = self.deviceFileTreeWidget
756 else:
757 cwdWidget = self.localCwd
758 fileTreeWidget = self.localFileTreeWidget
759
760 if bool(len(fileTreeWidget.selectedItems())):
761 name = fileTreeWidget.selectedItems()[0].text(0)
762 filename = os.path.join(cwdWidget.text(), name)
763 dlg = DeleteFilesConfirmationDialog(
764 self,
765 self.tr("Delete File"),
766 self.tr(
767 "Do you really want to delete this file?"),
768 [filename])
769 if dlg.exec() == QDialog.DialogCode.Accepted:
770 try:
771 os.remove(filename)
772 self.__listLocalFiles(cwdWidget.text(),
773 localDevice=localDevice)
774 except OSError as exc:
775 EricMessageBox.critical(
776 self,
777 self.tr("Delete File"),
778 self.tr("""<p>The file <b>{0}</b> could not be"""
779 """ deleted.</p><p>Reason: {1}</p>""").format(
780 filename, str(exc))
781 )
782
783 @pyqtSlot(bool)
784 def __localHiddenChanged(self, checked):
785 """
786 Private slot handling a change of the local show hidden menu entry.
787
788 @param checked new check state of the action
789 @type bool
790 """
791 Preferences.setMicroPython("ShowHiddenLocal", checked)
792 self.on_localReloadButton_clicked()
793
794 ##################################################################
795 ## Context menu methods for the device files below
796 ##################################################################
797
798 @pyqtSlot(QPoint)
799 def __showDeviceContextMenu(self, pos):
800 """
801 Private slot to show the REPL context menu.
802
803 @param pos position to show the menu at
804 @type QPoint
805 """
806 hasSelection = bool(len(self.deviceFileTreeWidget.selectedItems()))
807 if hasSelection:
808 name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
809 isDir = name.endswith("/")
810 isFile = not isDir
811 else:
812 isDir = False
813 isFile = False
814 if not self.__repl.isMicrobit():
815 if not self.__deviceWithLocalAccess:
816 self.__devDelDirAct.setEnabled(isDir)
817 self.__devDelDirTreeAct.setEnabled(isDir)
818 self.__devDelFileAct.setEnabled(isFile)
819
820 self.__deviceMenu.exec(self.deviceFileTreeWidget.mapToGlobal(pos))
821
822 @pyqtSlot()
823 def __changeDeviceDirectory(self):
824 """
825 Private slot to change the current directory of the device.
826
827 Note: This triggers a re-population of the device list for the new
828 current directory.
829 """
830 if self.__deviceWithLocalAccess:
831 self.__changeLocalDirectory(True)
832 else:
833 dirPath, ok = QInputDialog.getText(
834 self,
835 self.tr("Change Directory"),
836 self.tr("Enter the directory path on the device:"),
837 QLineEdit.EchoMode.Normal,
838 self.deviceCwd.text())
839 if ok and dirPath:
840 if not dirPath.startswith("/"):
841 dirPath = self.deviceCwd.text() + "/" + dirPath
842 self.__fileManager.cd(dirPath)
843
844 @pyqtSlot()
845 def __createDeviceDirectory(self):
846 """
847 Private slot to create a directory on the device.
848 """
849 if self.__deviceWithLocalAccess:
850 self.__createLocalDirectory(True)
851 else:
852 dirPath, ok = QInputDialog.getText(
853 self,
854 self.tr("Create Directory"),
855 self.tr("Enter directory name:"),
856 QLineEdit.EchoMode.Normal)
857 if ok and dirPath:
858 self.__fileManager.mkdir(dirPath)
859
860 @pyqtSlot()
861 def __deleteDeviceDirectory(self):
862 """
863 Private slot to delete an empty directory on the device.
864 """
865 if self.__deviceWithLocalAccess:
866 self.__deleteLocalDirectoryTree(True)
867 else:
868 if bool(len(self.deviceFileTreeWidget.selectedItems())):
869 name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
870 cwd = self.deviceCwd.text()
871 if cwd:
872 if cwd != "/":
873 dirname = cwd + "/" + name[:-1]
874 else:
875 dirname = "/" + name[:-1]
876 else:
877 dirname = name[:-1]
878 dlg = DeleteFilesConfirmationDialog(
879 self,
880 self.tr("Delete Directory"),
881 self.tr(
882 "Do you really want to delete this directory?"),
883 [dirname])
884 if dlg.exec() == QDialog.DialogCode.Accepted:
885 self.__fileManager.rmdir(dirname)
886
887 @pyqtSlot()
888 def __deleteDeviceDirectoryTree(self):
889 """
890 Private slot to delete a directory and all its subdirectories
891 recursively.
892 """
893 if self.__deviceWithLocalAccess:
894 self.__deleteLocalDirectoryTree(True)
895 else:
896 if bool(len(self.deviceFileTreeWidget.selectedItems())):
897 name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
898 cwd = self.deviceCwd.text()
899 if cwd:
900 if cwd != "/":
901 dirname = cwd + "/" + name[:-1]
902 else:
903 dirname = "/" + name[:-1]
904 else:
905 dirname = name[:-1]
906 dlg = DeleteFilesConfirmationDialog(
907 self,
908 self.tr("Delete Directory Tree"),
909 self.tr(
910 "Do you really want to delete this directory tree?"),
911 [dirname])
912 if dlg.exec() == QDialog.DialogCode.Accepted:
913 self.__fileManager.rmdir(dirname, recursive=True)
914
915 @pyqtSlot()
916 def __deleteDeviceFile(self):
917 """
918 Private slot to delete a file.
919 """
920 if self.__deviceWithLocalAccess:
921 self.__deleteLocalFile(True)
922 else:
923 if bool(len(self.deviceFileTreeWidget.selectedItems())):
924 name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
925 dirname = self.deviceCwd.text()
926 if dirname:
927 if dirname != "/":
928 filename = dirname + "/" + name
929 else:
930 filename = "/" + name
931 else:
932 filename = name
933 dlg = DeleteFilesConfirmationDialog(
934 self,
935 self.tr("Delete File"),
936 self.tr(
937 "Do you really want to delete this file?"),
938 [filename])
939 if dlg.exec() == QDialog.DialogCode.Accepted:
940 self.__fileManager.delete(filename)
941
942 @pyqtSlot(bool)
943 def __deviceHiddenChanged(self, checked):
944 """
945 Private slot handling a change of the device show hidden menu entry.
946
947 @param checked new check state of the action
948 @type bool
949 """
950 Preferences.setMicroPython("ShowHiddenDevice", checked)
951 self.on_deviceReloadButton_clicked()
952
953 @pyqtSlot()
954 def __showFileSystemInfo(self):
955 """
956 Private slot to show some file system information.
957 """
958 self.__fileManager.fileSystemInfo()
959
960 @pyqtSlot(tuple)
961 def __fsInfoResultReceived(self, fsinfo):
962 """
963 Private slot to show the file system information of the device.
964
965 @param fsinfo tuple of tuples containing the file system name, the
966 total size, the used size and the free size
967 @type tuple of tuples of (str, int, int, int)
968 """
969 msg = self.tr("<h3>Filesystem Information</h3>")
970 for name, totalSize, usedSize, freeSize in fsinfo:
971 msg += self.tr(
972 "<h4>{0}</h4"
973 "<table>"
974 "<tr><td>Total Size: </td><td align='right'>{1}</td></tr>"
975 "<tr><td>Used Size: </td><td align='right'>{2}</td></tr>"
976 "<tr><td>Free Size: </td><td align='right'>{3}</td></tr>"
977 "</table>"
978 ).format(name,
979 Globals.dataString(totalSize),
980 Globals.dataString(usedSize),
981 Globals.dataString(freeSize),
982 )
983 EricMessageBox.information(
984 self,
985 self.tr("Filesystem Information"),
986 msg)

eric ide

mercurial