Fri, 08 Mar 2024 15:30:53 +0100
Extended the project support for eric-ide server projects.
--- a/src/eric7/Graphics/PixmapDiagram.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Graphics/PixmapDiagram.py Fri Mar 08 15:30:53 2024 +0100 @@ -12,7 +12,6 @@ QAction, QColor, QFont, - QImage, QPageLayout, QPainter, QPalette, @@ -24,8 +23,10 @@ from eric7 import Preferences from eric7.EricGui import EricPixmapCache from eric7.EricWidgets import EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricMainWindow import EricMainWindow from eric7.EricWidgets.EricZoomWidget import EricZoomWidget +from eric7.SystemUtilities import FileSystemUtilities class PixmapDiagram(EricMainWindow): @@ -186,8 +187,30 @@ @return flag indicating success @rtype bool """ - image = QImage(filename) - if image.isNull(): + pixmap = QPixmap() + if FileSystemUtilities.isRemoteFileName(filename): + try: + data = ( + ericApp() + .getObject("EricServer") + .getServiceInterface("FileSystem") + .readFile(filename) + ) + pixmap.loadFromData(data) + except OSError as err: + EricMessageBox.warning( + self, + self.tr("Pixmap-Viewer"), + self.tr( + """<p>The file <b>{0}</b> cannot be loaded.</p>""" + """<p>Reason: {1}</p>""" + ).format(filename, str(err)), + ) + return False + else: + pixmap.load(filename) + + if pixmap.isNull(): EricMessageBox.warning( self, self.tr("Pixmap-Viewer"), @@ -198,7 +221,7 @@ ) return False - self.pixmapLabel.setPixmap(QPixmap.fromImage(image)) + self.pixmapLabel.setPixmap(pixmap) self.pixmapLabel.adjustSize() return True
--- a/src/eric7/Graphics/SvgDiagram.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Graphics/SvgDiagram.py Fri Mar 08 15:30:53 2024 +0100 @@ -15,8 +15,11 @@ from eric7 import Preferences from eric7.EricGui import EricPixmapCache +from eric7.EricWidgets import EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricMainWindow import EricMainWindow from eric7.EricWidgets.EricZoomWidget import EricZoomWidget +from eric7.SystemUtilities import FileSystemUtilities class SvgDiagram(EricMainWindow): @@ -102,8 +105,7 @@ self.resize(QSize(800, 600).expandedTo(self.minimumSizeHint())) self.zoom = 1.0 - self.svgFile = svgFile - self.svgWidget.load(self.svgFile) + self.__loadSvgFile(svgFile) self.svgWidget.resize(self.svgWidget.renderer().defaultSize()) self.__initActions() @@ -112,6 +114,36 @@ self.grabGesture(Qt.GestureType.PinchGesture) + def __loadSvgFile(self, svgFile): + """ + Private method to load a given SVG file. + + @param svgFile file path of the SVG file + @type str + """ + self.svgFile = svgFile + if FileSystemUtilities.isRemoteFileName(svgFile): + try: + data = ( + ericApp() + .getObject("EricServer") + .getServiceInterface("FileSystem") + .readFile(svgFile) + ) + self.svgWidget.load(data) + except OSError as err: + EricMessageBox.warning( + self, + self.tr("SVG-Viewer"), + self.tr( + "<p>The SVG file <b>{0}</b> could not be loaded.</p>" + "<p>Reason: {1}</p>" + ).format(svgFile, str(err)), + ) + + else: + self.svgWidget.load(self.svgFile) + def __initActions(self): """ Private method to initialize the view actions.
--- a/src/eric7/Graphics/UMLDialog.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Graphics/UMLDialog.py Fri Mar 08 15:30:53 2024 +0100 @@ -17,7 +17,10 @@ from eric7.EricGui import EricPixmapCache from eric7.EricWidgets import EricFileDialog, EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricMainWindow import EricMainWindow +from eric7.RemoteServerInterface import EricServerFileDialog +from eric7.SystemUtilities import FileSystemUtilities from .ApplicationDiagramBuilder import ApplicationDiagramBuilder from .ImportsDiagramBuilder import ImportsDiagramBuilder @@ -86,6 +89,9 @@ self.__project = project self.__diagramType = diagramType + self.__remotefsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) self.umlView = UMLGraphicsView(self.scene, parent=self) @@ -243,34 +249,56 @@ @type str """ if not filename: - fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( - self, - self.tr("Save Diagram"), - "", - self.tr("Eric Graphics File (*.egj);;All Files (*)"), - "", - EricFileDialog.DontConfirmOverwrite, - ) - if not fname: - return + if FileSystemUtilities.isRemoteFileName(self.__project.getProjectPath()): + fname, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter( + self, + self.tr("Save Diagram"), + self.__project.getProjectPath(), + self.tr("Eric Graphics File (*.egj);;All Files (*)"), + "", + ) + if not fname: + return - fpath = pathlib.Path(fname) - if not fpath.suffix: - ex = selectedFilter.split("(*")[1].split(")")[0] - if ex: - fpath = fpath.with_suffix(ex) - if fpath.exists(): + ext = self.__remotefsInterface.splitext(fname)[1] + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + filename = fname + fileExists = self.__remotefsInterface.exists(filename) + else: + fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( + self, + self.tr("Save Diagram"), + self.__project.getProjectPath(), + self.tr("Eric Graphics File (*.egj);;All Files (*)"), + "", + EricFileDialog.DontConfirmOverwrite, + ) + if not fname: + return + + fpath = pathlib.Path(fname) + if not fpath.suffix: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fpath = fpath.with_suffix(ex) + fileExists = fpath.exists() + filename = str(fpath) + + if fileExists: res = EricMessageBox.yesNo( self, self.tr("Save Diagram"), self.tr( - "<p>The file <b>{0}</b> already exists. Overwrite it?</p>" - ).format(fpath), + "<p>The file <b>{0}</b> exists already. Overwrite it?</p>" + ).format(filename), icon=EricMessageBox.Warning, ) if not res: return - filename = str(fpath) + ##filename = str(fpath) res = self.__writeJsonGraphicsFile(filename) @@ -289,12 +317,20 @@ @rtype bool """ if not filename: - filename = EricFileDialog.getOpenFileName( - self, - self.tr("Load Diagram"), - "", - self.tr("Eric Graphics File (*.egj);;All Files (*)"), - ) + if FileSystemUtilities.isRemoteFileName(self.__project.getProjectPath()): + filename = EricServerFileDialog.getOpenFileName( + self, + self.tr("Load Diagram"), + self.__project.getProjectPath(), + self.tr("Eric Graphics File (*.egj);;All Files (*)"), + ) + else: + filename = EricFileDialog.getOpenFileName( + self, + self.tr("Load Diagram"), + self.__project.getProjectPath(), + self.tr("Eric Graphics File (*.egj);;All Files (*)"), + ) if not filename: # Canceled by user return False @@ -343,8 +379,11 @@ try: jsonString = json.dumps(data, indent=2) - with open(filename, "w") as f: - f.write(jsonString) + if FileSystemUtilities.isRemoteFileName(filename): + self.__remotefsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + with open(filename, "w") as f: + f.write(jsonString) return True except (OSError, TypeError) as err: EricMessageBox.critical( @@ -368,8 +407,12 @@ @rtype bool """ try: - with open(filename, "r") as f: - jsonString = f.read() + if FileSystemUtilities.isRemoteFileName(filename): + bdata = self.__remotefsInterface.readFile(filename) + jsonString = bdata.decode("utf-8") + else: + with open(filename, "r") as f: + jsonString = f.read() data = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical(
--- a/src/eric7/HexEdit/HexEditMainWindow.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/HexEdit/HexEditMainWindow.py Fri Mar 08 15:30:53 2024 +0100 @@ -27,9 +27,11 @@ from eric7.EricGui import EricPixmapCache from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets import EricFileDialog, EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricClickableLabel import EricClickableLabel from eric7.EricWidgets.EricMainWindow import EricMainWindow from eric7.Globals import recentNameHexFiles, strGroup +from eric7.RemoteServerInterface import EricServerFileDialog from eric7.SystemUtilities import FileSystemUtilities from .HexEditGotoWidget import HexEditGotoWidget @@ -82,6 +84,12 @@ if not self.__fromEric: self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) + self.__remotefsInterface =( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + if self.__fromEric + else None + ) + self.__editor = HexEditWidget() self.__searchWidget = HexEditSearchReplaceWidget(self.__editor, self, False) self.__replaceWidget = HexEditSearchReplaceWidget(self.__editor, self, True) @@ -1056,11 +1064,20 @@ ): self.__lastOpenPath = self.__project.getProjectPath() - fileName = EricFileDialog.getOpenFileName( - self, - self.tr("Open binary file in new window"), - self.__lastOpenPath, - self.tr("All Files (*)"), + fileName = ( + EricServerFileDialog.getOpenFileName( + self, + self.tr("Open binary file"), + self.__lastOpenPath, + self.tr("All Files (*)"), + ) + if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath) + else EricFileDialog.getOpenFileName( + self, + self.tr("Open binary file in new window"), + self.__lastOpenPath, + self.tr("All Files (*)"), + ) ) if fileName: he = HexEditMainWindow( @@ -1097,17 +1114,12 @@ @param fileName name of the binary file to load @type str """ - if not os.path.exists(fileName): - EricMessageBox.warning( - self, - self.tr("eric Hex Editor"), - self.tr("The file '{0}' does not exist.").format(fileName), - ) - return - try: - with open(fileName, "rb") as f: - data = f.read() + if FileSystemUtilities.isRemoteFileName(fileName): + data = self.__remotefsInterface.readFile(fileName) + else: + with open(fileName, "rb") as f: + data = f.read() except OSError as err: EricMessageBox.warning( self, @@ -1118,7 +1130,11 @@ ) return - self.__lastOpenPath = os.path.dirname(fileName) + self.__lastOpenPath = ( + self.__remotefsInterface.dirname(fileName) + if FileSystemUtilities.isRemoteFileName(fileName) + else os.path.dirname(fileName) + ) self.__editor.setData(data) self.__setCurrentFile(fileName) @@ -1138,11 +1154,20 @@ ): self.__lastOpenPath = self.__project.getProjectPath() - fileName = EricFileDialog.getOpenFileName( - self, - self.tr("Open binary file"), - self.__lastOpenPath, - self.tr("All Files (*)"), + fileName = ( + EricServerFileDialog.getOpenFileName( + self, + self.tr("Open binary file"), + self.__lastOpenPath, + self.tr("All Files (*)"), + ) + if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath) + else EricFileDialog.getOpenFileName( + self, + self.tr("Open binary file"), + self.__lastOpenPath, + self.tr("All Files (*)"), + ) ) if fileName: self.__loadHexFile(fileName) @@ -1190,17 +1215,35 @@ if not self.__lastSavePath and self.__lastOpenPath: self.__lastSavePath = self.__lastOpenPath - fileName = EricFileDialog.getSaveFileName( - self, - self.tr("Save binary file"), - self.__lastSavePath, - self.tr("All Files (*)"), - options=EricFileDialog.DontConfirmOverwrite, + fileName = ( + EricServerFileDialog.getSaveFileName( + self, + self.tr("Save binary file"), + self.__lastSavePath, + self.tr("All Files (*)"), + ) + if FileSystemUtilities.isRemoteFileName(self.__lastSavePath) + else EricFileDialog.getSaveFileName( + self, + self.tr("Save binary file"), + self.__lastSavePath, + self.tr("All Files (*)"), + options=EricFileDialog.DontConfirmOverwrite, + ) ) if not fileName: return False - if pathlib.Path(fileName).exists(): + if ( + ( + FileSystemUtilities.isRemoteFileName(fileName) + and self.__remotefsInterface.exists(fileName) + ) + or ( + FileSystemUtilities.isPlainFileName(fileName) + and pathlib.Path(fileName).exists() + ) + ): res = EricMessageBox.yesNo( self, self.tr("Save binary file"), @@ -1226,8 +1269,12 @@ @rtype bool """ try: - with open(fileName, "wb") as f: - f.write(self.__editor.data()) + data = self.__editor.data() + if FileSystemUtilities.isRemoteFileName(fileName): + self.__remotefsInterface.writeFile(fileName, data) + else: + with open(fileName, "wb") as f: + f.write(data) except OSError as err: EricMessageBox.warning( self, @@ -1261,29 +1308,50 @@ if not savePath and self.__lastOpenPath: savePath = self.__lastOpenPath - fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( - self, - self.tr("Save to readable file"), - savePath, - self.tr("Text Files (*.txt);;All Files (*)"), - self.tr("Text Files (*.txt)"), - EricFileDialog.DontConfirmOverwrite, - ) - if not fileName: - return + if FileSystemUtilities.isRemoteFileName(savePath): + fileName, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter( + self, + self.tr("Save to readable file"), + savePath, + self.tr("Text Files (*.txt);;All Files (*)"), + self.tr("Text Files (*.txt)"), + ) + if not fileName: + return - fpath = pathlib.Path(fileName) - if not fpath.suffix: - ex = selectedFilter.split("(*")[1].split(")")[0] - if ex: - fpath = fpath.with_suffix(ex) - if fpath.exists(): + ext = self.__remotefsInterface.splitext(fileName)[1] + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fileName += ex + fileExists = self.__remotefsInterface.exists(fileName) + else: + fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( + self, + self.tr("Save to readable file"), + savePath, + self.tr("Text Files (*.txt);;All Files (*)"), + self.tr("Text Files (*.txt)"), + EricFileDialog.DontConfirmOverwrite, + ) + if not fileName: + return + + fpath = pathlib.Path(fileName) + if not fpath.suffix: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fpath = fpath.with_suffix(ex) + fileExists = fpath.exists() + fileName = str(fpath) + + if fileExists: res = EricMessageBox.yesNo( self, self.tr("Save to readable file"), self.tr( "<p>The file <b>{0}</b> already exists. Overwrite it?</p>" - ).format(fpath), + ).format(fileName), icon=EricMessageBox.Warning, ) if not res: @@ -1295,8 +1363,13 @@ else self.__editor.toReadableString() ) try: - with fpath.open("w", encoding="latin1") as f: - f.write(readableData) + if FileSystemUtilities.isRemoteFileName(fileName): + self.__remotefsInterface.writeFile( + fileName, readableData.encode("latin1") + ) + else: + with open(fileName, "w", encoding="latin1") as f: + f.write(readableData) except OSError as err: EricMessageBox.warning( self, @@ -1535,6 +1608,10 @@ self.__recentMenu.clear() + connected = ( + self.__fromEric and ericApp().getObject("EricServer").isServerConnected() + ) + for idx, rs in enumerate(self.__recent, start=1): formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}" act = self.__recentMenu.addAction( @@ -1546,7 +1623,10 @@ ) ) act.setData(rs) - act.setEnabled(pathlib.Path(rs).exists()) + act.setEnabled( + (FileSystemUtilities.isRemoteFileName(rs) and connected) + or pathlib.Path(rs).exists() + ) self.__recentMenu.addSeparator() self.__recentMenu.addAction(self.tr("&Clear"), self.__clearRecent) @@ -1581,7 +1661,7 @@ rs = Preferences.Prefs.rsettings.value(recentNameHexFiles) if rs is not None: for f in Preferences.toList(rs): - if pathlib.Path(f).exists(): + if FileSystemUtilities.isRemoteFileName(f) or pathlib.Path(f).exists(): self.__recent.append(f) def __saveRecent(self):
--- a/src/eric7/IconEditor/IconEditorWindow.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/IconEditor/IconEditorWindow.py Fri Mar 08 15:30:53 2024 +0100 @@ -11,7 +11,7 @@ import os import pathlib -from PyQt6.QtCore import QEvent, QSignalMapper, QSize, Qt, pyqtSignal +from PyQt6.QtCore import QBuffer, QByteArray, QEvent, QSignalMapper, QSize, Qt, pyqtSignal from PyQt6.QtGui import QImage, QImageReader, QImageWriter, QKeySequence, QPalette from PyQt6.QtWidgets import QDockWidget, QLabel, QScrollArea, QWhatsThis @@ -19,8 +19,10 @@ from eric7.EricGui import EricPixmapCache from eric7.EricGui.EricAction import EricAction, createActionGroup from eric7.EricWidgets import EricFileDialog, EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricMainWindow import EricMainWindow from eric7.EricWidgets.EricZoomWidget import EricZoomWidget +from eric7.SystemUtilities import FileSystemUtilities from .IconEditorGrid import IconEditorGrid, IconEditorTool @@ -1282,7 +1284,28 @@ @param fileName name of the icon file to load @type str """ - img = QImage(fileName) + img = QImage() + if self.fromEric and FileSystemUtilities.isRemoteFileName(fileName): + try: + data = ( + ericApp() + .getObject("EricServer") + .getServiceInterface("FileSystem") + .readFile(fileName) + ) + img.loadFromData(data) + except OSError as err: + EricMessageBox.warning( + self, + self.tr("eric Icon Editor"), + self.tr( + """<p>The file <b>{0}</b> cannot be loaded.</p>""" + """<p>Reason: {1}</p>""" + ).format(fileName, str(err)), + ) + return + else: + img.load(fileName) if img.isNull(): EricMessageBox.warning( self, @@ -1303,7 +1326,30 @@ @rtype bool """ img = self.__editor.iconImage() - res = img.save(fileName) + if self.fromEric and FileSystemUtilities.isRemoteFileName(fileName): + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + imageFormat = fsInterface.splitext(fileName)[1][1:].upper() + data = QByteArray() + buffer = QBuffer(data) + res = img.save(buffer, imageFormat) + if res: + try: + fsInterface.writeFile(fileName, data) + except OSError as err: + EricMessageBox.warning( + self, + self.tr("eric Icon Editor"), + self.tr( + """<p>The file <b>{0}</b> cannot be written.</p>""" + """<p>Reason: {1}</p>""" + ).format(fileName, str(err)), + ) + self.__checkActions() + return False + else: + res = img.save(fileName) if not res: EricMessageBox.warning( @@ -1311,9 +1357,7 @@ self.tr("eric Icon Editor"), self.tr("Cannot write file '{0}'.").format(fileName), ) - self.__checkActions() - return False self.__editor.setDirty(False, setCleanState=True)
--- a/src/eric7/PdfViewer/PdfViewerWindow.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/PdfViewer/PdfViewerWindow.py Fri Mar 08 15:30:53 2024 +0100 @@ -11,7 +11,7 @@ import os import pathlib -from PyQt6.QtCore import QPointF, QSize, Qt, pyqtSignal, pyqtSlot +from PyQt6.QtCore import QBuffer, QByteArray, QIODevice, QPointF, QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtGui import QAction, QActionGroup, QClipboard, QGuiApplication, QKeySequence from PyQt6.QtPdf import QPdfDocument, QPdfLink from PyQt6.QtPdfWidgets import QPdfView @@ -30,9 +30,11 @@ from eric7.EricGui import EricPixmapCache from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets import EricFileDialog, EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricMainWindow import EricMainWindow from eric7.EricWidgets.EricStretchableSpacer import EricStretchableSpacer from eric7.Globals import recentNamePdfFiles +from eric7.RemoteServerInterface import EricServerFileDialog from eric7.SystemUtilities import FileSystemUtilities, OSUtilities from .PdfInfoWidget import PdfInfoWidget @@ -79,6 +81,12 @@ if not self.__fromEric: self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) + self.__remotefsInterface =( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + if self.__fromEric + else None + ) + self.__pdfDocument = QPdfDocument(self) self.__cw = QSplitter(Qt.Orientation.Horizontal, self) @@ -961,8 +969,31 @@ """ canceled = False err = QPdfDocument.Error.IncorrectPassword + + if FileSystemUtilities.isRemoteFileName(fileName): + try: + data = QByteArray(self.__remotefsInterface.readFile(fileName)) + buffer = QBuffer(data) + except OSError as err: + EricMessageBox.warning( + self, + self.tr("OpenPDF File"), + self.tr( + "<p>The PDF file <b>{0}</b> could not be read.</p>" + "<p>Reason: {1}</p>" + ).format(fileName, str(err)), + ) + return + else: + buffer = None + while not canceled and err == QPdfDocument.Error.IncorrectPassword: - err = self.__pdfDocument.load(fileName) + if FileSystemUtilities.isRemoteFileName(fileName): + buffer.open(QIODevice.OpenModeFlag.ReadOnly) + self.__pdfDocument.load(buffer) + err = QPdfDocument.Error.None_ + else: + err = self.__pdfDocument.load(fileName) if err == QPdfDocument.Error.IncorrectPassword: password, ok = QInputDialog.getText( self, @@ -986,7 +1017,11 @@ self.__documentInfoWidget.setFileName("") return - self.__lastOpenPath = os.path.dirname(fileName) + self.__lastOpenPath = ( + self.__remotefsInterface.dirname(fileName) + if FileSystemUtilities.isRemoteFileName(fileName) + else os.path.dirname(fileName) + ) self.__setCurrentFile(fileName) documentTitle = self.__pdfDocument.metaData(QPdfDocument.MetaDataField.Title) @@ -1019,11 +1054,20 @@ ): self.__lastOpenPath = self.__project.getProjectPath() - fileName = EricFileDialog.getOpenFileName( - self, - self.tr("Open PDF File"), - self.__lastOpenPath, - self.tr("PDF Files (*.pdf);;All Files (*)"), + fileName = ( + EricServerFileDialog.getOpenFileName( + self, + self.tr("Open PDF File"), + self.__lastOpenPath, + self.tr("PDF Files (*.pdf);;All Files (*)"), + ) + if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath) + else EricFileDialog.getOpenFileName( + self, + self.tr("Open PDF File"), + self.__lastOpenPath, + self.tr("PDF Files (*.pdf);;All Files (*)"), + ) ) if fileName: self.__loadPdfFile(fileName) @@ -1044,11 +1088,20 @@ self.__lastOpenPath = self.__project.getProjectPath() if not fileName: - fileName = EricFileDialog.getOpenFileName( - self, - self.tr("Open PDF File"), - self.__lastOpenPath, - self.tr("PDF Files (*.pdf);;All Files (*)"), + fileName = ( + EricServerFileDialog.getOpenFileName( + self, + self.tr("Open PDF File"), + self.__lastOpenPath, + self.tr("PDF Files (*.pdf);;All Files (*)"), + ) + if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath) + else EricFileDialog.getOpenFileName( + self, + self.tr("Open PDF File"), + self.__lastOpenPath, + self.tr("PDF Files (*.pdf);;All Files (*)"), + ) ) if fileName: viewer = PdfViewerWindow(
--- a/src/eric7/Project/DebuggerPropertiesDialog.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/DebuggerPropertiesDialog.py Fri Mar 08 15:30:53 2024 +0100 @@ -28,16 +28,18 @@ settings. """ - def __init__(self, project, parent=None, name=None): + def __init__(self, project, isRemote=False, parent=None, name=None): """ Constructor @param project reference to the project object @type Project - @param parent parent widget of this dialog - @type QWidget - @param name name of this dialog - @type str + @param isRemote flag indicating a remote project (defaults to False) + @type bool (optional) + @param parent parent widget of this dialog (defaults to None) + @type QWidget (optional) + @param name name of this dialog (defaults to None) + @type str (optional) """ super().__init__(parent) if name: @@ -45,6 +47,7 @@ self.setupUi(self) self.project = project + self.__isRemote = isRemote debugClientsHistory = Preferences.getProject("DebugClientsHistory") self.debugClientPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) @@ -61,8 +64,11 @@ venvManager = ericApp().getObject("VirtualEnvManager") + # Virtual Environment self.venvGroupBox.setVisible( not self.project.getProjectData(dataKey="EMBEDDED_VENV") + and not isRemote + # TODO: change once remote environments have been implemented ) self.venvComboBox.addItem("") if self.project.getProjectData(dataKey="EMBEDDED_VENV"): @@ -89,7 +95,12 @@ else: venvIndex = 0 self.venvComboBox.setCurrentIndex(venvIndex) - if self.project.debugProperties["DEBUGCLIENT"]: + + # Debug Client + self.debugClientGroup.setVisible(not isRemote) + if isRemote: + self.debugClientPicker.clear() + elif self.project.debugProperties["DEBUGCLIENT"]: self.debugClientPicker.setText( self.project.debugProperties["DEBUGCLIENT"], toNative=False ) @@ -101,12 +112,17 @@ else: debugClient = "" self.debugClientPicker.setText(debugClient, toNative=False) + + # Debug Environment self.debugEnvironmentOverrideCheckBox.setChecked( self.project.debugProperties["ENVIRONMENTOVERRIDE"] ) self.debugEnvironmentEdit.setText( self.project.debugProperties["ENVIRONMENTSTRING"] ) + + # Remote (ssh) Debugger + self.remoteDebuggerGroup.setVisible(not isRemote) self.remoteDebuggerGroup.setChecked( self.project.debugProperties["REMOTEDEBUGGER"] ) @@ -120,11 +136,20 @@ ) self.translationRemoteEdit.setText(self.project.debugProperties["REMOTEPATH"]) self.translationLocalEdit.setText(self.project.debugProperties["LOCALPATH"]) + + # Console Debugger + self.consoleDebuggerGroup.setVisible(not isRemote) self.consoleDebuggerGroup.setChecked( self.project.debugProperties["CONSOLEDEBUGGER"] ) self.consoleCommandEdit.setText(self.project.debugProperties["CONSOLECOMMAND"]) - self.redirectCheckBox.setChecked(self.project.debugProperties["REDIRECT"]) + + # Redirect stdin/stdout/stderr + self.redirectCheckBox.setChecked( + self.project.debugProperties["REDIRECT"] or isRemote + ) + + # No encoding self.noEncodingCheckBox.setChecked(self.project.debugProperties["NOENCODING"]) msh = self.minimumSizeHint() @@ -148,17 +173,20 @@ """ self.project.debugProperties["VIRTUALENV"] = self.venvComboBox.currentText() - self.project.debugProperties["DEBUGCLIENT"] = self.debugClientPicker.text( - toNative=False - ) - if not self.project.debugProperties["DEBUGCLIENT"]: - if self.project.getProjectData(dataKey="PROGLANGUAGE") == "Python3": - debugClient = os.path.join( - getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py" - ) - else: - debugClient = "" - self.project.debugProperties["DEBUGCLIENT"] = debugClient + if self.__isRemote: + self.project.debugProperties["DEBUGCLIENT"] = "" + else: + self.project.debugProperties["DEBUGCLIENT"] = self.debugClientPicker.text( + toNative=False + ) + if not self.project.debugProperties["DEBUGCLIENT"]: + if self.project.getProjectData(dataKey="PROGLANGUAGE") == "Python3": + debugClient = os.path.join( + getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py" + ) + else: + debugClient = "" + self.project.debugProperties["DEBUGCLIENT"] = debugClient self.project.debugProperties[ "ENVIRONMENTOVERRIDE" @@ -166,25 +194,43 @@ self.project.debugProperties[ "ENVIRONMENTSTRING" ] = self.debugEnvironmentEdit.text() - self.project.debugProperties[ - "REMOTEDEBUGGER" - ] = self.remoteDebuggerGroup.isChecked() - self.project.debugProperties["REMOTEHOST"] = self.remoteHostEdit.text() - self.project.debugProperties["REMOTECOMMAND"] = self.remoteCommandEdit.text() - self.project.debugProperties[ - "REMOTEDEBUGCLIENT" - ] = self.remoteDebugClientEdit.text() - self.project.debugProperties[ - "PATHTRANSLATION" - ] = self.pathTranslationGroup.isChecked() - self.project.debugProperties["REMOTEPATH"] = self.translationRemoteEdit.text() - self.project.debugProperties["LOCALPATH"] = self.translationLocalEdit.text() - self.project.debugProperties[ - "CONSOLEDEBUGGER" - ] = self.consoleDebuggerGroup.isChecked() - self.project.debugProperties["CONSOLECOMMAND"] = self.consoleCommandEdit.text() - self.project.debugProperties["REDIRECT"] = self.redirectCheckBox.isChecked() + + if self.__isRemote: + self.project.debugProperties["REMOTEDEBUGGER"] = False + else: + self.project.debugProperties[ + "REMOTEDEBUGGER" + ] = self.remoteDebuggerGroup.isChecked() + self.project.debugProperties["REMOTEHOST"] = self.remoteHostEdit.text() + self.project.debugProperties[ + "REMOTECOMMAND" + ] = self.remoteCommandEdit.text() + self.project.debugProperties[ + "REMOTEDEBUGCLIENT" + ] = self.remoteDebugClientEdit.text() + self.project.debugProperties[ + "PATHTRANSLATION" + ] = self.pathTranslationGroup.isChecked() + self.project.debugProperties[ + "REMOTEPATH" + ] = self.translationRemoteEdit.text() + self.project.debugProperties["LOCALPATH"] = self.translationLocalEdit.text() + + if self.__isRemote: + self.project.debugProperties["CONSOLEDEBUGGER"] = False + else: + self.project.debugProperties[ + "CONSOLEDEBUGGER" + ] = self.consoleDebuggerGroup.isChecked() + self.project.debugProperties[ + "CONSOLECOMMAND" + ] = self.consoleCommandEdit.text() + + self.project.debugProperties["REDIRECT"] = ( + self.redirectCheckBox.isChecked() or self.__isRemote + ) self.project.debugProperties["NOENCODING"] = self.noEncodingCheckBox.isChecked() + self.project.debugPropertiesLoaded = True self.project.debugPropertiesChanged = True
--- a/src/eric7/Project/DebuggerPropertiesDialog.ui Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/DebuggerPropertiesDialog.ui Fri Mar 08 15:30:53 2024 +0100 @@ -18,7 +18,7 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="debugClientGroup"> <property name="title"> <string>Debug Client</string> </property>
--- a/src/eric7/Project/Project.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/Project.py Fri Mar 08 15:30:53 2024 +0100 @@ -1145,6 +1145,8 @@ self.name = self.__remotefsInterface.splitext( self.__remotefsInterface.basename(fn) )[0] + # TODO: read in a file system cache of ppath + self.__remotefsInterface.populateFsCache(self.ppath) else: self.pfile = os.path.abspath(fn) self.ppath = os.path.abspath(os.path.dirname(fn)) @@ -1185,7 +1187,7 @@ for fn in self.__pdata[fileCategory]: dn = ( self.__remotefsInterface.dirname(fn) - if FileSystemUtilities.isRemoteFileName(fn) + if FileSystemUtilities.isRemoteFileName(self.ppath) else os.path.dirname(fn) ) if dn and dn not in self.subdirs: @@ -1651,7 +1653,9 @@ """ from .DebuggerPropertiesDialog import DebuggerPropertiesDialog - dlg = DebuggerPropertiesDialog(self) + dlg = DebuggerPropertiesDialog( + self, isRemote=FileSystemUtilities.isRemoteFileName(self.ppath) + ) if dlg.exec() == QDialog.DialogCode.Accepted: dlg.storeData() @@ -1664,7 +1668,10 @@ @return value of the property @rtype Any """ - if key == "INTERPRETER": + if ( + key == "INTERPRETER" + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ): return ( ericApp() .getObject("VirtualEnvManager") @@ -2368,16 +2375,26 @@ isSourceFile = fn in self.__pdata["SOURCES"] if newfn is None: - newfn = EricFileDialog.getSaveFileName( - None, - self.tr("Rename file"), - oldfn, - "", - EricFileDialog.DontConfirmOverwrite, - ) + if isRemote: + newfn = EricServerFileDialog.getSaveFileName( + None, + self.tr("Rename File"), + oldfn, + "", + ) + else: + newfn = EricFileDialog.getSaveFileName( + None, + self.tr("Rename File"), + oldfn, + "", + options=EricFileDialog.DontConfirmOverwrite, + ) + if newfn: + newfn = FileSystemUtilities.toNativeSeparators(newfn) + if not newfn: return False - newfn = FileSystemUtilities.toNativeSeparators(newfn) if (not isRemote and os.path.exists(newfn)) or ( isRemote and self.__remotefsInterface.exists(newfn) @@ -2396,7 +2413,7 @@ try: if isRemote: - self.__remotefsInterface.rename(oldfn, newfn) + self.__remotefsInterface.replace(oldfn, newfn) else: os.rename(oldfn, newfn) except OSError as msg: @@ -4011,6 +4028,10 @@ self.ui.taskViewer.clearProjectTasks() self.ui.taskViewer.setProjectOpen(False) + # TODO: clear the file system cache for ppath if remote project + if FileSystemUtilities.isRemoteFileName(self.ppath): + self.__remotefsInterface.removeFromFsCache(self.ppath) + # now shutdown the vcs interface if not FileSystemUtilities.isRemoteFileName(self.ppath) and self.vcs: self.vcs.vcsShutdown() @@ -6439,6 +6460,9 @@ forProject = True override = False + if FileSystemUtilities.isRemoteFileName(self.ppath): + return None + if vcsSystem is None: if self.__pdata["VCS"] and self.__pdata["VCS"] != "None": vcsSystem = self.__pdata["VCS"] @@ -7902,6 +7926,8 @@ and self.opened and FileSystemUtilities.isRemoteFileName(self.pfile) ) + if not connected and FileSystemUtilities.isRemoteFileName(self.ppath): + self.closeProject(noSave=True) @pyqtSlot() def __openRemoteProject(self): @@ -7930,7 +7956,6 @@ defaultPath, self.tr("Project Files (*.epj)"), defaultFilter, - EricFileDialog.DontConfirmOverwrite, ) if fn:
--- a/src/eric7/Project/ProjectBaseBrowser.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/ProjectBaseBrowser.py Fri Mar 08 15:30:53 2024 +0100 @@ -269,7 +269,10 @@ if self.backMenu is not None: self.backMenu.setEnabled(True) - if self.project.vcs is not None: + if ( + self.project.vcs is not None + and not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ): self.vcsHelper = self.project.vcs.vcsGetProjectBrowserHelper( self, self.project, self.isTranslationsBrowser ) @@ -280,6 +283,8 @@ self.dirMenu, self.dirMultiMenu, ) + else: + self.vcsHelper = None def _newProject(self): """ @@ -502,7 +507,7 @@ if self.project.vcs is None: for act in self.menuActions: act.setEnabled(True) - else: + elif self.vcsHelper is not None: self.vcsHelper.showContextMenu(menu, self.menuActions) def _showContextMenuMulti(self, menu): @@ -519,7 +524,7 @@ if self.project.vcs is None: for act in self.multiMenuActions: act.setEnabled(True) - else: + elif self.vcsHelper is not None: self.vcsHelper.showContextMenuMulti(menu, self.multiMenuActions) def _showContextMenuDir(self, menu): @@ -535,7 +540,7 @@ if self.project.vcs is None: for act in self.dirMenuActions: act.setEnabled(True) - else: + elif self.vcsHelper is not None: self.vcsHelper.showContextMenuDir(menu, self.dirMenuActions) def _showContextMenuDirMulti(self, menu): @@ -551,7 +556,7 @@ if self.project.vcs is None: for act in self.dirMultiMenuActions: act.setEnabled(True) - else: + elif self.vcsHelper is not None: self.vcsHelper.showContextMenuDirMulti(menu, self.dirMultiMenuActions) def _showContextMenuBack(self, menu): # noqa: U100
--- a/src/eric7/Project/ProjectBrowserModel.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/ProjectBrowserModel.py Fri Mar 08 15:30:53 2024 +0100 @@ -342,63 +342,106 @@ """ self._addWatchedItem(parentItem) - qdir = QDir(parentItem.dirName()) + dirName = parentItem.dirName() + if FileSystemUtilities.isPlainFileName(dirName): + qdir = QDir(parentItem.dirName()) - fileFilter = ( - (QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot) - if Preferences.getProject("BrowsersListHiddenFiles") - else QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot - ) - entryInfoList = qdir.entryInfoList(fileFilter) + fileFilter = ( + ( + QDir.Filter.AllEntries + | QDir.Filter.Hidden + | QDir.Filter.NoDotAndDotDot + ) + if Preferences.getProject("BrowsersListHiddenFiles") + else QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot + ) + entryInfoList = qdir.entryInfoList(fileFilter) - if len(entryInfoList) > 0: - if repopulate: - self.beginInsertRows( - self.createIndex(parentItem.row(), 0, parentItem), - 0, - len(entryInfoList) - 1, - ) - states = {} - if self.project.vcs is not None: - for f in entryInfoList: - fname = f.absoluteFilePath() - states[os.path.normcase(fname)] = 0 - dname = parentItem.dirName() - self.project.vcs.clearStatusCache() - states = self.project.vcs.vcsAllRegisteredStates(states, dname) + if len(entryInfoList) > 0: + if repopulate: + self.beginInsertRows( + self.createIndex(parentItem.row(), 0, parentItem), + 0, + len(entryInfoList) - 1, + ) + states = {} + if self.project.vcs is not None: + for f in entryInfoList: + fname = f.absoluteFilePath() + states[os.path.normcase(fname)] = 0 + dname = parentItem.dirName() + self.project.vcs.clearStatusCache() + states = self.project.vcs.vcsAllRegisteredStates(states, dname) - for f in entryInfoList: - node = ( - ProjectBrowserDirectoryItem( - parentItem, - FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()), - parentItem.getProjectTypes()[0], - False, - fsInterface=self.__remotefsInterface, - ) - if f.isDir() - else ProjectBrowserFileItem( - parentItem, - FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()), - parentItem.getProjectTypes()[0], - fsInterface=self.__remotefsInterface, + for f in entryInfoList: + node = ( + ProjectBrowserDirectoryItem( + parentItem, + FileSystemUtilities.toNativeSeparators( + f.absoluteFilePath() + ), + parentItem.getProjectTypes()[0], + False, + fsInterface=self.__remotefsInterface, + ) + if f.isDir() + else ProjectBrowserFileItem( + parentItem, + FileSystemUtilities.toNativeSeparators( + f.absoluteFilePath() + ), + parentItem.getProjectTypes()[0], + fsInterface=self.__remotefsInterface, + ) ) - ) - if self.project.vcs is not None: - fname = f.absoluteFilePath() - if ( - states[os.path.normcase(fname)] - == VersionControlState.Controlled - ): - node.addVcsStatus(self.project.vcs.vcsName()) - self.project.clearStatusMonitorCachedState(f.absoluteFilePath()) + if self.project.vcs is not None: + fname = f.absoluteFilePath() + if ( + states[os.path.normcase(fname)] + == VersionControlState.Controlled + ): + node.addVcsStatus(self.project.vcs.vcsName()) + self.project.clearStatusMonitorCachedState( + f.absoluteFilePath() + ) + else: + node.addVcsStatus(self.tr("local")) else: - node.addVcsStatus(self.tr("local")) - else: + node.addVcsStatus("") + self._addItem(node, parentItem) + if repopulate: + self.endInsertRows() + + elif FileSystemUtilities.isRemoteFileName(dirName): + entriesList = self.__remotefsInterface.listdir(dirName)[2] + if len(entriesList) > 0: + if repopulate: + self.beginInsertRows( + self.createIndex(parentItem.row(), 0, parentItem), + 0, + len(entryInfoList) - 1, + ) + for entry in entriesList: + node = ( + ProjectBrowserDirectoryItem( + parentItem, + entry["path"], + parentItem.getProjectTypes()[0], + False, + fsInterface=self.__remotefsInterface, + ) + if entry["is_dir"] + else ProjectBrowserFileItem( + parentItem, + entry["path"], + parentItem.getProjectTypes()[0], + fsInterface=self.__remotefsInterface, + ) + ) node.addVcsStatus("") - self._addItem(node, parentItem) - if repopulate: - self.endInsertRows() + self._addItem(node, parentItem) + if repopulate: + self.endInsertRows() def projectClosed(self): """ @@ -427,7 +470,9 @@ states = {} fileCategories = self.project.getFileCategories() - if self.project.vcs is not None: + if self.project.vcs is not None and not FileSystemUtilities.isRemoteFileName( + self.project.ppath + ): for fileCategory in fileCategories: for fn in self.project.getProjectData(dataKey=fileCategory): states[os.path.normcase(os.path.join(self.project.ppath, fn))] = 0 @@ -479,7 +524,10 @@ ) ) self._addItem(itm, parentItem) - if self.project.vcs is not None: + if ( + self.project.vcs is not None + and not FileSystemUtilities.isRemoteFileName(self.project.ppath) + ): if ( states[os.path.normcase(fname)] == VersionControlState.Controlled
--- a/src/eric7/Project/ProjectOthersBrowser.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/ProjectOthersBrowser.py Fri Mar 08 15:30:53 2024 +0100 @@ -17,6 +17,7 @@ from eric7 import Preferences from eric7.EricGui import EricPixmapCache from eric7.EricWidgets import EricMessageBox +from eric7.SystemUtilities import FileSystemUtilities from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog from eric7.Utilities import MimeTypes @@ -159,7 +160,9 @@ self.menu.addSeparator() self.menu.addAction(self.tr("Refresh"), self.__refreshItem) self.menu.addSeparator() - self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.menuFileManagerAct = self.menu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.menu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard) self.menu.addSeparator() self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs) @@ -180,7 +183,9 @@ self.dirMenu.addAction(self.tr("Add files..."), self.__addOthersFiles) self.dirMenu.addAction(self.tr("Add directory..."), self.__addOthersDirectory) self.dirMenu.addSeparator() - self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.dirMenuFileManagerAct = self.dirMenu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard) self.dirMenu.addSeparator() self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs) @@ -198,7 +203,7 @@ self.tr("Add directory..."), lambda: self.project.addDirectory("OTHERS") ) self.backMenu.addSeparator() - self.backMenu.addAction( + self.backMenuFileManagerAct = self.backMenu.addAction( self.tr("Show in File Manager"), self._showProjectInFileManager ) self.backMenu.addSeparator() @@ -241,6 +246,8 @@ if not self.project.isOpen(): return + isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + with contextlib.suppress(Exception): # secok cnt = self.getSelectedItemsCount( [ @@ -272,18 +279,24 @@ self.openInEditorAct.setVisible(itm.isSvgFile()) self.openInPdfViewerAct.setVisible(itm.isPdfFile()) self.mimeTypeAct.setVisible(True) + self.menuFileManagerAct.setVisible(not isRemote) self.menu.popup(self.mapToGlobal(coord)) - elif isinstance(itm, ProjectBrowserDirectoryItem): + elif isinstance( + itm, + ( + ProjectBrowserDirectoryItem, + ProjectBrowserSimpleDirectoryItem, + ), + ): self.removeDirAct.setVisible(True) self.deleteDirAct.setVisible(True) - self.dirMenu.popup(self.mapToGlobal(coord)) - elif isinstance(itm, ProjectBrowserSimpleDirectoryItem): - self.removeDirAct.setVisible(False) - self.deleteDirAct.setVisible(False) + self.dirMenuFileManagerAct.setVisible(not isRemote) self.dirMenu.popup(self.mapToGlobal(coord)) else: + self.backMenuFileManagerAct.setVisible(not isRemote) self.backMenu.popup(self.mapToGlobal(coord)) else: + self.backMenuFileManagerAct.setVisible(not isRemote) self.backMenu.popup(self.mapToGlobal(coord)) def __showContextMenu(self): @@ -336,7 +349,7 @@ itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) ): self.renameFileAct.setEnabled(False) - else: + elif self.vcsHelper is not None: self.vcsHelper.showContextMenu(menu, self.menuActions) def _editPixmap(self):
--- a/src/eric7/Project/ProjectSourcesBrowser.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/Project/ProjectSourcesBrowser.py Fri Mar 08 15:30:53 2024 +0100 @@ -23,6 +23,7 @@ from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricPathPickerDialog import EricPathPickerModes from eric7.Graphics.UMLDialog import UMLDialog, UMLDialogType +from eric7.SystemUtilities import FileSystemUtilities from eric7.UI.BrowserModel import ( BrowserClassAttributeItem, BrowserClassItem, @@ -317,12 +318,14 @@ self.sourceMenu.addSeparator() self.sourceMenu.addMenu(self.graphicsMenu) self.sourceMenu.addMenu(self.checksMenu) - self.sourceMenu.addMenu(self.formattingMenu) + self.sourceMenuActions["Formatting"] = self.sourceMenu.addMenu( + self.formattingMenu + ) self.sourceMenuActions["Show"] = self.sourceMenu.addMenu(self.menuShow) self.sourceMenu.addSeparator() self.__startAct = self.sourceMenu.addMenu(self.__startMenu) self.sourceMenu.addSeparator() - self.sourceMenu.addAction( + self.__sourceMenuFileManagerAct = self.sourceMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.sourceMenu.addAction( @@ -348,7 +351,9 @@ self.tr("Add source directory..."), self.__addSourceDirectory ) self.menu.addSeparator() - self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__menuFileManagerAct = self.menu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.menu.addSeparator() self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs) self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs) @@ -378,7 +383,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.attributeMenu.addSeparator() - self.attributeMenu.addAction( + self.__attributeMenuFileManagerAct = self.attributeMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.attributeMenu.addSeparator() @@ -407,7 +412,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.backMenu.addSeparator() - self.backMenu.addAction( + self.__backMenuFileManagerAct = self.backMenu.addAction( self.tr("Show in File Manager"), self._showProjectInFileManager ) self.backMenu.addSeparator() @@ -427,7 +432,7 @@ self.multiMenuActions.append(act) self.multiMenu.addSeparator() self.multiMenu.addMenu(self.checksMenu) - self.multiMenu.addMenu(self.formattingMenu) + self.__multiMenuFormattingAct = self.multiMenu.addMenu(self.formattingMenu) self.multiMenu.addSeparator() self.multiMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs) self.multiMenu.addAction( @@ -453,9 +458,11 @@ self.dirMenu.addSeparator() act = self.dirMenu.addMenu(self.graphicsMenu) self.dirMenu.addMenu(self.checksMenu) - self.dirMenu.addMenu(self.formattingMenu) + self.__dirMenuFormattingAct = self.dirMenu.addMenu(self.formattingMenu) self.dirMenu.addSeparator() - self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__dirMenuFileManagerAct = self.dirMenu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard) self.dirMenu.addSeparator() self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs) @@ -480,6 +487,8 @@ self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure) self.sourceMenu.aboutToShow.connect(self.__showContextMenu) + self.menu.aboutToShow.connect(self.__showContextMenuGeneral) + self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute) self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) @@ -524,7 +533,7 @@ self.sourceMenu.addSeparator() act = self.sourceMenu.addMenu(self.graphicsMenu) self.sourceMenu.addSeparator() - self.sourceMenu.addAction( + self.__sourceMenuFileManagerAct = self.sourceMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.sourceMenu.addSeparator() @@ -544,7 +553,9 @@ self.tr("Add source directory..."), self.__addSourceDirectory ) self.menu.addSeparator() - self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__menuFileManagerAct = self.menu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.menu.addSeparator() self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs) self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs) @@ -569,7 +580,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.attributeMenu.addSeparator() - self.attributeMenu.addAction( + self.__attributeMenuFileManagerAct = self.attributeMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.attributeMenu.addSeparator() @@ -595,7 +606,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.backMenu.addSeparator() - self.backMenu.addAction( + self.__backMenuFileManagerAct = self.backMenu.addAction( self.tr("Show in File Manager"), self._showProjectInFileManager ) self.backMenu.addSeparator() @@ -633,7 +644,9 @@ self.dirMenu.addSeparator() act = self.dirMenu.addMenu(self.graphicsMenu) self.dirMenu.addSeparator() - self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__dirMenuFileManagerAct = self.dirMenu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.dirMenu.addSeparator() self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs) self.dirMenu.addAction( @@ -657,6 +670,8 @@ self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure) self.sourceMenu.aboutToShow.connect(self.__showContextMenu) + self.menu.aboutToShow.connect(self.__showContextMenuGeneral) + self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute) self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) @@ -687,7 +702,7 @@ self.sourceMenu.addSeparator() self.sourceMenu.addMenu(self.checksMenu) self.sourceMenu.addSeparator() - self.sourceMenu.addAction( + self.__sourceMenuFileManagerAct = self.sourceMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.sourceMenu.addAction( @@ -710,7 +725,9 @@ self.tr("Add source directory..."), self.__addSourceDirectory ) self.menu.addSeparator() - self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__menuFileManagerAct = self.menu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.menu.addSeparator() self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs) self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs) @@ -735,7 +752,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.attributeMenu.addSeparator() - self.attributeMenu.addAction( + self.__attrMenuFileManagerAct = self.attributeMenu.addAction( self.tr("Show in File Manager"), self._showInFileManager ) self.attributeMenu.addSeparator() @@ -761,7 +778,7 @@ lambda: self.project.addDirectory("SOURCES"), ) self.backMenu.addSeparator() - self.backMenu.addAction( + self.__backMenuFileManagerAct = self.backMenu.addAction( self.tr("Show in File Manager"), self._showProjectInFileManager ) self.backMenu.addSeparator() @@ -803,7 +820,9 @@ self.dirMenu.addSeparator() self.dirMenu.addMenu(self.checksMenu) self.dirMenu.addSeparator() - self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager) + self.__dirMenuFileManagerAct = self.dirMenu.addAction( + self.tr("Show in File Manager"), self._showInFileManager + ) self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard) self.dirMenu.addSeparator() self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs) @@ -828,6 +847,8 @@ self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure) self.sourceMenu.aboutToShow.connect(self.__showContextMenu) + self.menu.aboutToShow.connect(self.__showContextMenuGeneral) + self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute) self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) @@ -955,14 +976,46 @@ else: self.__startAct.setEnabled(False) + self.sourceMenuActions["Formatting"].setEnabled( + self.sourceMenuActions["Formatting"].isEnabled() + and not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.__sourceMenuFileManagerAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.showMenu.emit("Main", self.sourceMenu) + def __showContextMenuGeneral(self): + """ + Private slot called by the menu aboutToShow signal. + """ + ProjectBaseBrowser._showContextMenu(self, self.menu) + + self.__menuFileManagerAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + + def __showContextMenuAttribute(self): + """ + Private slot called by the attributeMenu aboutToShow signal. + """ + ProjectBaseBrowser._showContextMenu(self, self.menu) + + self.__attributeMenuFileManagerAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + def __showContextMenuMulti(self): """ Private slot called by the multiMenu aboutToShow signal. """ ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu) + self.__multiMenuFormattingAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.showMenu.emit("MainMulti", self.multiMenu) def __showContextMenuDir(self): @@ -971,6 +1024,13 @@ """ ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu) + self.__dirMenuFormattingAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.__dirMenuFileManagerAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.showMenu.emit("MainDir", self.dirMenu) def __showContextMenuDirMulti(self): @@ -987,6 +1047,10 @@ """ ProjectBaseBrowser._showContextMenuBack(self, self.backMenu) + self.__backMenuFileManagerAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + ) + self.showMenu.emit("MainBack", self.backMenu) def __showContextMenuShow(self): @@ -1062,8 +1126,14 @@ """ from .NewPythonPackageDialog import NewPythonPackageDialog + isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + remotefsInterface = ericApp().getObject("EricServer").getServiceInterface( + "FileSystem" + ) + separator = remotefsInterface.separator() if isRemote else os.sep + dn = self.currentDirectory(relative=True) - if dn.startswith(os.sep): + if dn.startswith(separator): dn = dn[1:] dlg = NewPythonPackageDialog(dn, self) if dlg.exec() == QDialog.DialogCode.Accepted: @@ -1072,10 +1142,22 @@ packagePath = self.project.ppath packageFile = "" for name in nameParts: - packagePath = os.path.join(packagePath, name) - if not os.path.exists(packagePath): + packagePath = ( + remotefsInterface.join(packagePath, name) + if isRemote + else os.path.join(packagePath, name) + ) + exists = ( + remotefsInterface.exists(packagePath) + if isRemote + else os.path.exists(packagePath) + ) + if not exists: try: - os.mkdir(packagePath) + if isRemote: + remotefsInterface.mkdir(packagePath) + else: + os.mkdir(packagePath) except OSError as err: EricMessageBox.critical( self, @@ -1087,11 +1169,23 @@ ).format(packagePath, str(err)), ) return - packageFile = os.path.join(packagePath, "__init__.py") - if not os.path.exists(packageFile): + packageFile = ( + remotefsInterface.join(packagePath, "__init__.py") + if isRemote + else os.path.join(packagePath, "__init__.py") + ) + exists = ( + remotefsInterface.exists(packageFile) + if isRemote + else os.path.exists(packageFile) + ) + if not exists: try: - with open(packageFile, "w", encoding="utf-8"): - pass + if isRemote: + remotefsInterface.writeFile(packageFile, b"") + else: + with open(packageFile, "w", encoding="utf-8"): + pass except OSError as err: EricMessageBox.critical( self, @@ -1111,6 +1205,11 @@ """ Private method to add a new source file to the project. """ + isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + remotefsInterface = ericApp().getObject("EricServer").getServiceInterface( + "FileSystem" + ) + dn = self.currentDirectory() filename, ok = EricPathPickerDialog.getStrPath( self, @@ -1122,9 +1221,15 @@ filters=self.project.getFileCategoryFilters( categories=["SOURCES"], withAll=False ), + remote=isRemote, ) if ok: - if os.path.exists(filename): + exists = ( + remotefsInterface.exists(filename) + if isRemote + else os.path.exists(filename) + ) + if exists: EricMessageBox.critical( self, self.tr("New source file"), @@ -1139,9 +1244,16 @@ newline = ( None if self.project.useSystemEol() else self.project.getEolString() ) - with open(filename, "w", newline=newline) as f: - f.write("# -*- coding: utf-8 -*-\n") - f.write("# {0}\n".format(self.project.getRelativePath(filename))) + header = "# -*- coding: utf-8 -*-\n# {0}\n".format( + self.project.getRelativePath(filename) + ) + if isRemote: + remotefsInterface.writeFile( + filename, header.encode("utf-8"), newline=newline + ) + else: + with open(filename, "w", newline=newline) as f: + f.write(header) except OSError as err: EricMessageBox.critical( self, @@ -1342,12 +1454,22 @@ """ Private method to handle the imports diagram context menu action. """ + isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + remotefsInterface = ericApp().getObject("EricServer").getServiceInterface( + "FileSystem" + ) + itm = self.model().item(self.currentIndex()) try: fn = itm.fileName() except AttributeError: fn = itm.dirName() - package = fn if os.path.isdir(fn) else os.path.dirname(fn) + if isRemote: + package = ( + fn if remotefsInterface.isdir(fn) else remotefsInterface.dirname(fn) + ) + else: + package = fn if os.path.isdir(fn) else os.path.dirname(fn) res = EricMessageBox.yesNo( self, self.tr("Imports Diagram"), @@ -1367,12 +1489,22 @@ """ Private method to handle the package diagram context menu action. """ + isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()) + remotefsInterface = ericApp().getObject("EricServer").getServiceInterface( + "FileSystem" + ) + itm = self.model().item(self.currentIndex()) try: fn = itm.fileName() except AttributeError: fn = itm.dirName() - package = fn if os.path.isdir(fn) else os.path.dirname(fn) + if isRemote: + package = ( + fn if remotefsInterface.isdir(fn) else remotefsInterface.dirname(fn) + ) + else: + package = fn if os.path.isdir(fn) else os.path.dirname(fn) res = EricMessageBox.yesNo( self, self.tr("Package Diagram"),
--- a/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Fri Mar 08 15:30:23 2024 +0100 +++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Fri Mar 08 15:30:53 2024 +0100 @@ -13,13 +13,17 @@ import re import stat -from PyQt6.QtCore import QEventLoop, QObject, pyqtSlot +from PyQt6.QtCore import QByteArray, QEventLoop, QObject, pyqtSlot from eric7 import Utilities from eric7.RemoteServer.EricRequestCategory import EricRequestCategory from eric7.SystemUtilities import FileSystemUtilities +_RemoteFsCache = {} +# dictionary containing cached remote file system data keyed by remote path + + class EricServerNotConnectedError(OSError): """ Class defining a special OSError indicating a missing server connection. @@ -29,7 +33,7 @@ """ Constructor """ - super().__init("Not connected to an 'eric-ide' server.") + super().__init__("Not connected to an 'eric-ide' server.") class EricServerFileSystemInterface(QObject): @@ -82,8 +86,6 @@ if connected: if not bool(self.__serverPathSep): self.__serverPathSep = self.__getPathSep() - else: - self.__serverPathSep = "" def __getPathSep(self): """ @@ -203,13 +205,15 @@ else: return False, EricServerFileSystemInterface.NotConnectedMessage - def listdir(self, directory=""): + def listdir(self, directory="", recursive=False): """ Public method to get a directory listing. @param directory directory to be listed. An empty directory means to list the eric-ide server current directory. (defaults to "") @type str (optional) + @param recursive flag indicating a recursive listing (defaults to False) + @type bool (optional) @return tuple containing the listed directory, the path separator and the directory listing. Each directory listing entry contains a dictionary with the relevant data. @@ -252,7 +256,10 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Listdir", - params={"directory": FileSystemUtilities.plainFileName(directory)}, + params={ + "directory": FileSystemUtilities.plainFileName(directory), + "recursive": recursive, + }, callback=callback, ) @@ -293,7 +300,7 @@ @param recursive flag indicating a recursive search (defaults to True) @type bool (optional) @param dirsonly flag indicating to return only directories. When True it has - precedence over the 'filesonly' parameter ((defaults to False) + precedence over the 'filesonly' parameter (defaults to False) @type bool @return list of all files and directories in the tree rooted at path. The names are expanded to start with the given directory name. @@ -443,9 +450,12 @@ @return flag indicating a directory @rtype bool """ - with contextlib.suppress(KeyError, OSError): - result = self.stat(name, ["st_mode"]) - return stat.S_ISDIR(result["st_mode"]) + try: + return stat.S_ISDIR(_RemoteFsCache[name]["mode"]) + except KeyError: + with contextlib.suppress(KeyError, OSError): + result = self.stat(name, ["st_mode"]) + return stat.S_ISDIR(result["st_mode"]) return False @@ -458,9 +468,12 @@ @return flag indicating a regular file @rtype bool """ - with contextlib.suppress(KeyError, OSError): - result = self.stat(name, ["st_mode"]) - return stat.S_ISREG(result["st_mode"]) + try: + return stat.S_ISREG(_RemoteFsCache[name]["mode"]) + except KeyError: + with contextlib.suppress(KeyError, OSError): + result = self.stat(name, ["st_mode"]) + return stat.S_ISREG(result["st_mode"]) return False @@ -491,6 +504,9 @@ nameExists = params["exists"] loop.quit() + if name in _RemoteFsCache: + return True + if self.__serverInterface.isServerConnected(): self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, @@ -598,6 +614,8 @@ ) loop.exec() + if ok: + self.populateFsCache(directory) return ok, error else: @@ -649,6 +667,8 @@ ) loop.exec() + if ok: + self.populateFsCache(directory) return ok, error else: @@ -693,6 +713,8 @@ ) loop.exec() + if ok: + self.removeFromFsCache(directory) return ok, error else: @@ -742,6 +764,12 @@ ) loop.exec() + if ok: + with contextlib.suppress(KeyError): + entry = _RemoteFsCache.pop(oldName) + entry["path"] = newName + entry["name"] = self.basename(newName) + _RemoteFsCache[newName] = entry return ok, error else: @@ -786,6 +814,9 @@ ) loop.exec() + if ok: + with contextlib.suppress(KeyError): + del _RemoteFsCache[filename] return ok, error else: @@ -859,14 +890,11 @@ @return flag indicating an absolute path @rtype bool """ - if self.__serverInterface.isServerConnected(): - if self.__serverPathSep == "\\": - s = FileSystemUtilities.plainFileName(p)[:3].replace("/", "\\") - return s.startswith("\\)") or s.startswith(":\\", 1) - else: - return FileSystemUtilities.plainFileName(p).startswith("/") + if self.__serverPathSep == "\\": + s = FileSystemUtilities.plainFileName(p)[:3].replace("/", "\\") + return s.startswith("\\)") or s.startswith(":\\", 1) else: - return os.path.isabs(p) + return FileSystemUtilities.plainFileName(p).startswith("/") def abspath(self, p): """ @@ -877,13 +905,10 @@ @return absolute path @rtype str """ - if self.__serverInterface.isServerConnected(): - p = FileSystemUtilities.plainFileName(p) - if not self.isabs(p): - p = self.join(self.getcwd(), p) - return FileSystemUtilities.remoteFileName(p) - else: - return os.path.abspath(p) + p = FileSystemUtilities.plainFileName(p) + if not self.isabs(p): + p = self.join(self.getcwd(), p) + return FileSystemUtilities.remoteFileName(p) def join(self, a, *p): """ @@ -897,19 +922,15 @@ @return joined path name @rtype str """ - if self.__serverInterface.isServerConnected(): - path = a - for b in p: - if b.startswith(self.__serverPathSep): - path = b - elif not path or path.endswith(self.__serverPathSep): - path += b - else: - path += self.__serverPathSep + b - return path - - else: - return os.path.join(a, *p) + path = a + for b in p: + if b.startswith(self.__serverPathSep): + path = b + elif not path or path.endswith(self.__serverPathSep): + path += b + else: + path += self.__serverPathSep + b + return path def split(self, p): """ @@ -921,22 +942,18 @@ path separator. @rtype tuple of (str, str) """ - if self.__serverInterface.isServerConnected(): - if self.__serverPathSep == "\\": - # remote is a Windows system - normp = p.replace("/", "\\") - else: - # remote is a Posix system - normp = p.replace("\\", "/") + if self.__serverPathSep == "\\": + # remote is a Windows system + normp = p.replace("/", "\\") + else: + # remote is a Posix system + normp = p.replace("\\", "/") - i = normp.rfind(self.__serverPathSep) + 1 - head, tail = normp[:i], normp[i:] - if head and head != self.__serverPathSep * len(head): - head = head.rstrip(self.__serverPathSep) - return head, tail - - else: - return os.path.split(p) + i = normp.rfind(self.__serverPathSep) + 1 + head, tail = normp[:i], normp[i:] + if head and head != self.__serverPathSep * len(head): + head = head.rstrip(self.__serverPathSep) + return head, tail def splitext(self, p): """ @@ -958,23 +975,19 @@ @return tuple containing the drive letter (incl. colon) and the path @rtype tuple of (str, str) """ - if self.__serverInterface.isServerConnected(): - plainp = FileSystemUtilities.plainFileName(p) + plainp = FileSystemUtilities.plainFileName(p) - if self.__serverPathSep == "\\": - # remote is a Windows system - normp = plainp.replace("/", "\\") - if normp[1:2] == ":": - return normp[:2], normp[2:] - else: - return "", normp + if self.__serverPathSep == "\\": + # remote is a Windows system + normp = plainp.replace("/", "\\") + if normp[1:2] == ":": + return normp[:2], normp[2:] else: - # remote is a Posix system - normp = plainp.replace("\\", "/") return "", normp - else: - return os.path.splitdrive(p) + # remote is a Posix system + normp = plainp.replace("\\", "/") + return "", normp def dirname(self, p): """ @@ -1028,7 +1041,7 @@ ## Methods for reading and writing files ####################################################################### - def readFile(self, filename, create=False): + def readFile(self, filename, create=False, newline=None): """ Public method to read a file from the eric-ide server. @@ -1037,6 +1050,9 @@ @param create flag indicating to create an empty file, if it does not exist (defaults to False) @type bool (optional) + @param newline determines how to parse newline characters from the stream + (defaults to None) + @type str (optional) @return bytes data read from the eric-ide server @rtype bytes @exception EricServerNotConnectedError raised to indicate a missing server @@ -1079,6 +1095,7 @@ params={ "filename": FileSystemUtilities.plainFileName(filename), "create": create, + "newline": "<<none>>" if newline is None else newline, }, callback=callback, ) @@ -1089,17 +1106,20 @@ return bText - def writeFile(self, filename, data, withBackup=False): + def writeFile(self, filename, data, withBackup=False, newline=None): """ Public method to write the data to a file on the eric-ide server. @param filename name of the file to write @type str @param data data to be written - @type bytes + @type bytes or QByteArray @param withBackup flag indicating to create a backup file first (defaults to False) @type bool (optional) + @param newline determines how to parse newline characters from the stream + (defaults to None) + @type str (optional) @exception EricServerNotConnectedError raised to indicate a missing server connection @exception OSError raised in case the server reported an issue @@ -1129,6 +1149,8 @@ raise EricServerNotConnectedError else: + if isinstance(data, QByteArray): + data = bytes(data) self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="WriteFile", @@ -1136,6 +1158,7 @@ "filename": FileSystemUtilities.plainFileName(filename), "filedata": str(base64.b85encode(data), encoding="ascii"), "with_backup": withBackup, + "newline": "<<none>>" if newline is None else newline, }, callback=callback, ) @@ -1315,6 +1338,9 @@ ) loop.exec() + if ok: + self.removeFromFsCache(pathname) + if not ok: raise OSError(error) @@ -1364,3 +1390,41 @@ return cpath tail = tail[1:] return "" + + ####################################################################### + ## Remote file system cache methods. + ####################################################################### + + # TODO: change cache when file renamed/moved/deleted/... + def populateFsCache(self, directory): + """ + Public method to populate the remote file system cache for a given directory. + + @param directory remote directory to be cached + @type str + @exception ValueError raised to indicate an empty directory + """ + if not directory: + raise ValueError("The directory to be cached must not be empty.") + + try: + listing = self.listdir(directory=directory, recursive=True)[2] + for entry in listing: + _RemoteFsCache[ + FileSystemUtilities.remoteFileName(entry["path"]) + ] = entry + print(f"Remote Cache Size: {len(_RemoteFsCache)} entries") + except OSError as err: + print("error in 'populateFsCache()':", str(err)) + + def removeFromFsCache(self, directory): + """ + Public method to remove a given directory from the remote file system cache. + + @param directory remote directory to be removed + @type str + """ + for entryPath in list(_RemoteFsCache.keys()): + if entryPath.startswith(directory): + del _RemoteFsCache[entryPath] + print(f"Remote Cache Size: {len(_RemoteFsCache)} entries")