Thu, 22 Feb 2024 16:26:46 +0100
Implemented first stage of remote project support.
--- a/src/eric7/Graphics/ApplicationDiagramBuilder.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Graphics/ApplicationDiagramBuilder.py Thu Feb 22 16:26:46 2024 +0100 @@ -218,12 +218,13 @@ # no root path detected return - if FileSystemUtilities.isRemoteFileName(rpath): - root = self.__remotefsInterface.splitdrive(rpath)[1][1:].replace( + root = ( + self.__remotefsInterface.splitdrive(rpath)[1][1:].replace( self.__remotefsInterface.separator(), "." ) - else: - root = os.path.splitdrive(rpath)[1][1:].replace(os.sep, ".") + if FileSystemUtilities.isRemoteFileName(rpath) + else os.path.splitdrive(rpath)[1][1:].replace(os.sep, ".") + ) packages = {} self.__shapes = {}
--- a/src/eric7/Graphics/PackageDiagramBuilder.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Graphics/PackageDiagramBuilder.py Thu Feb 22 16:26:46 2024 +0100 @@ -183,7 +183,8 @@ subpackage["path"], "__init__.*" ) ) - ) != 0 + ) + != 0 ): subpackagesList.append( FileSystemUtilities.remoteFileName(subpackage["path"]) @@ -194,7 +195,8 @@ if ( subpackage.is_dir() and subpackage.name != "__pycache__" - and len(glob.glob(os.path.join(subpackage.path, "__init__.*"))) != 0 + and len(glob.glob(os.path.join(subpackage.path, "__init__.*"))) + != 0 ): subpackagesList.append(subpackage.path) @@ -280,10 +282,11 @@ self.allClasses = {} globPattern = os.path.join(self.package, "__init__.*") - if FileSystemUtilities.isRemoteFileName(self.package): - initlist = self.__remotefsInterface.glob(globPattern) - else: - initlist = glob.glob(globPattern) + initlist = ( + self.__remotefsInterface.glob(globPattern) + if FileSystemUtilities.isRemoteFileName(self.package) + else glob.glob(globPattern) + ) if len(initlist) == 0: ct = QGraphicsTextItem(None) self.scene.addItem(ct)
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Thu Feb 22 16:26:46 2024 +0100 @@ -1148,9 +1148,9 @@ self.__timenow = time.monotonic() try: - if FileSystemUtilities.isRemoteFileName(self.filename): + if FileSystemUtilities.isRemoteFileName(filename): source, encoding = self.__remotefsInterface.readEncodedFile( - self.filename + filename ) else: source, encoding = Utilities.readEncodedFile(filename)
--- a/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Thu Feb 22 16:26:46 2024 +0100 @@ -467,12 +467,11 @@ self.__timenow = time.monotonic() try: - if FileSystemUtilities.isRemoteFileName(self.filename): - source = self.__remotefsInterface.readEncodedFile( - self.filename - )[0] - else: - source = Utilities.readEncodedFile(filename)[0] + source = ( + self.__remotefsInterface.readEncodedFile(self.filename)[0] + if FileSystemUtilities.isRemoteFileName(self.filename) + else Utilities.readEncodedFile(filename)[0] + ) source = Utilities.normalizeCode(source) except (OSError, UnicodeError) as msg: self.noResults = False
--- a/src/eric7/Project/DebuggerPropertiesFile.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Project/DebuggerPropertiesFile.py Thu Feb 22 16:26:46 2024 +0100 @@ -17,6 +17,8 @@ from eric7 import Preferences from eric7.EricGui.EricOverrideCursor import EricOverridenCursor from eric7.EricWidgets import EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp +from eric7.SystemUtilities import FileSystemUtilities Project = typing.TypeVar("Project") @@ -48,6 +50,10 @@ @return flag indicating a successful write @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + debuggerPropertiesDict = { "header": { "comment": "eric debugger properties file for project {0}".format( @@ -66,13 +72,18 @@ try: jsonString = json.dumps(debuggerPropertiesDict, indent=2) + "\n" - with open(filename, "w") as f: - f.write(jsonString) + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Save Remote Debugger Properties") + fsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + title = self.tr("Save Debugger Properties") + with open(filename, "w") as f: + f.write(jsonString) except (OSError, TypeError) as err: with EricOverridenCursor(): EricMessageBox.critical( None, - self.tr("Save Debugger Properties"), + title, self.tr( "<p>The project debugger properties file" " <b>{0}</b> could not be written.</p>" @@ -93,14 +104,23 @@ @return flag indicating a successful read @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + try: - with open(filename, "r") as f: - jsonString = f.read() + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Read Remote Debugger Properties") + jsonString = fsInterface.readFile(filename).decode("utf-8") + else: + title = self.tr("Read Debugger Properties") + with open(filename, "r") as f: + jsonString = f.read() debuggerPropertiesDict = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical( None, - self.tr("Read Debugger Properties"), + title, self.tr( "<p>The project debugger properties file <b>{0}</b>" " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/Project/Project.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Project/Project.py Thu Feb 22 16:26:46 2024 +0100 @@ -23,8 +23,6 @@ from PyQt6.QtCore import ( QByteArray, QCryptographicHash, - QFile, - QIODevice, QObject, QProcess, pyqtSignal, @@ -45,12 +43,8 @@ from eric7.EricWidgets.EricApplication import ericApp from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog from eric7.EricWidgets.EricProgressDialog import EricProgressDialog -from eric7.EricXML.DebuggerPropertiesReader import DebuggerPropertiesReader -from eric7.EricXML.ProjectReader import ProjectReader -from eric7.EricXML.SessionReader import SessionReader -from eric7.EricXML.TasksReader import TasksReader -from eric7.EricXML.UserProjectReader import UserProjectReader from eric7.Globals import recentNameProject +from eric7.RemoteServerInterface import EricServerFileDialog from eric7.Sessions.SessionFile import SessionFile from eric7.SystemUtilities import ( FileSystemUtilities, @@ -165,18 +159,21 @@ DefaultMake = "make" DefaultMakefile = "makefile" - def __init__(self, parent=None, filename=None): + def __init__(self, parent=None, filename=None, remoteServer=None): """ Constructor @param parent parent widget (usually the ui object) @type QWidget - @param filename optional filename of a project file to open - @type str + @param filename optional filename of a project file to open (defaults to None) + @type str (optional) + @param remoteServer reference to the 'eric-ide' server interface object + @type EricServerInterface """ super().__init__(parent) self.ui = parent + self.__remotefsInterface = remoteServer.getServiceInterface("FileSystem") self.__progLanguages = [ "Python3", @@ -217,7 +214,7 @@ else: self.vcs = self.initVCS() - self.__model = ProjectBrowserModel(self) + self.__model = ProjectBrowserModel(self, fsInterface=self.__remotefsInterface) self.codemetrics = None self.codecoverage = None @@ -1106,10 +1103,18 @@ """ removed = False removelist = [] - for file in self.__pdata[index]: - if not os.path.exists(os.path.join(self.ppath, file)): - removelist.append(file) - removed = True + if FileSystemUtilities.isRemoteFileName(self.ppath): + for file in self.__pdata[index]: + if not self.__remotefsInterface.exists( + self.__remotefsInterface.join(self.ppath, file) + ): + removelist.append(file) + removed = True + else: + for file in self.__pdata[index]: + if not os.path.exists(os.path.join(self.ppath, file)): + removelist.append(file) + removed = True if removed: for file in removelist: @@ -1118,50 +1123,27 @@ def __readProject(self, fn): """ - Private method to read in a project (.epj or .e4p) file. + Private method to read in a project file (.epj). @param fn filename of the project file to be read @type str @return flag indicating success @rtype bool """ - if os.path.splitext(fn)[1] == ".epj": - # new JSON based format - with EricOverrideCursor(): - res = self.__projectFile.readFile(fn) - else: - # old XML based format - f = QFile(fn) - if f.open(QIODevice.OpenModeFlag.ReadOnly): - reader = ProjectReader(f, self) - reader.readXML() - res = not reader.hasError() - f.close() - - # create hash value, if it doesn't have one - if reader.version.startswith("5.") and not self.__pdata["HASH"]: - hashStr = str( - QCryptographicHash.hash( - QByteArray(self.ppath.encode("utf-8")), - QCryptographicHash.Algorithm.Sha1, - ).toHex(), - encoding="utf-8", - ) - self.__pdata["HASH"] = hashStr - self.setDirty(True) - else: - EricMessageBox.critical( - self.ui, - self.tr("Read Project File"), - self.tr( - "<p>The project file <b>{0}</b> could not be read.</p>" - ).format(fn), - ) - res = False + with EricOverrideCursor(): + res = self.__projectFile.readFile(fn) if res: - self.pfile = os.path.abspath(fn) - self.ppath = os.path.abspath(os.path.dirname(fn)) + if FileSystemUtilities.isRemoteFileName(fn): + self.pfile = fn + self.ppath = self.__remotefsInterface.dirname(fn) + self.name = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(fn) + )[0] + else: + self.pfile = os.path.abspath(fn) + self.ppath = os.path.abspath(os.path.dirname(fn)) + self.name = os.path.splitext(os.path.basename(fn))[0] # insert filename into list of recently opened projects self.__syncRecent() @@ -1172,15 +1154,22 @@ )[0] elif self.__pdata["MAINSCRIPT"]: self.translationsRoot = os.path.splitext(self.__pdata["MAINSCRIPT"])[0] - if os.path.isdir(os.path.join(self.ppath, self.translationsRoot)): - dn = self.translationsRoot + + if FileSystemUtilities.isRemoteFileName(self.ppath): + if self.__remotefsInterface.isdir( + self.__remotefsInterface.join(self.ppath, self.translationsRoot) + ): + dn = self.translationsRoot + else: + dn = self.__remotefsInterface.dirname(self.translationsRoot) else: - dn = os.path.dirname(self.translationsRoot) - if dn not in self.subdirs: + if os.path.isdir(os.path.join(self.ppath, self.translationsRoot)): + dn = self.translationsRoot + else: + dn = os.path.dirname(self.translationsRoot) + if dn and dn not in self.subdirs: self.subdirs.append(dn) - self.name = os.path.splitext(os.path.basename(fn))[0] - # check, if the files of the project still exist in the # project directory for fileCategory in self.getFileCategories(): @@ -1189,14 +1178,22 @@ # get the names of subdirectories the files are stored in for fileCategory in [c for c in self.getFileCategories() if c != "OTHERS"]: for fn in self.__pdata[fileCategory]: - dn = os.path.dirname(fn) - if dn not in self.subdirs: + dn = ( + self.__remotefsInterface.dirname(fn) + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.dirname(fn) + ) + if dn and dn not in self.subdirs: self.subdirs.append(dn) # get the names of other subdirectories for fn in self.__pdata["OTHERS"]: - dn = os.path.dirname(fn) - if dn not in self.otherssubdirs: + dn = ( + self.__remotefsInterface.dirname(fn) + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.dirname(fn) + ) + if dn and dn not in self.otherssubdirs: self.otherssubdirs.append(dn) return res @@ -1235,9 +1232,16 @@ res = self.__projectFile.writeFile(fn) if res: - self.pfile = os.path.abspath(fn) - self.ppath = os.path.abspath(os.path.dirname(fn)) - self.name = os.path.splitext(os.path.basename(fn))[0] + if FileSystemUtilities.isRemoteFileName(fn): + self.pfile = fn + self.ppath = self.__remotefsInterface.dirname(fn) + self.name = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(fn) + )[0] + else: + self.pfile = os.path.abspath(fn) + self.ppath = os.path.abspath(os.path.dirname(fn)) + self.name = os.path.splitext(os.path.basename(fn))[0] self.setDirty(False) # insert filename into list of recently opened projects @@ -1247,35 +1251,27 @@ def __readUserProperties(self): """ - Private method to read in the user specific project file (.eqj or - .e4q). + Private method to read in the user specific project file (.eqj). """ if self.pfile is None: return - fn1, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.eqj".format(fn1)) - if os.path.exists(fn): - # try the new JSON based format first - self.__userProjectFile.readFile(fn) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}.eqj" + ) + if not self.__remotefsInterface.exists(fn): + return else: - # try the old XML based format second - fn = os.path.join(self.getProjectManagementDir(), "{0}.e4q".format(fn1)) - if os.path.exists(fn): - f = QFile(fn) - if f.open(QIODevice.OpenModeFlag.ReadOnly): - reader = UserProjectReader(f, self) - reader.readXML() - f.close() - else: - EricMessageBox.critical( - self.ui, - self.tr("Read User Project Properties"), - self.tr( - "<p>The user specific project properties file" - " <b>{0}</b> could not be read.</p>" - ).format(fn), - ) + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.eqj") + if not os.path.exists(fn): + return + + self.__userProjectFile.readFile(fn) def __writeUserProperties(self): """ @@ -1284,8 +1280,16 @@ if self.pfile is None: return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.eqj".format(fn)) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}.eqj" + ) + else: + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.eqj") with EricOverrideCursor(): self.__userProjectFile.writeFile(fn) @@ -1298,17 +1302,25 @@ if self.pfile is None: enable = False else: - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn_new = os.path.join(self.getProjectManagementDir(), "{0}.esj".format(fn)) - fn_old = os.path.join(self.getProjectManagementDir(), "{0}.e5s".format(fn)) - enable = os.path.exists(fn_new) or os.path.exists(fn_old) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn_sess = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn}.esj" + ) + enable = self.__remotefsInterface.exists(fn) + else: + fn, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn_sess = os.path.join(self.getProjectManagementDir(), f"{fn}.esj") + enable = os.path.exists(fn_sess) self.sessActGrp.findChild(QAction, "project_load_session").setEnabled(enable) self.sessActGrp.findChild(QAction, "project_delete_session").setEnabled(enable) @pyqtSlot() def __readSession(self, quiet=False, indicator=""): """ - Private method to read in the project session file (.esj or .e5s). + Private method to read in the project session file (.esj). @param quiet flag indicating quiet operations. If this flag is true, no errors are reported. @@ -1325,34 +1337,24 @@ ) return - fn1, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join( - self.getProjectManagementDir(), "{0}{1}.esj".format(fn1, indicator) - ) - if os.path.exists(fn): - # try the new JSON based format first - self.__sessionFile.readFile(fn) - else: - # try the old XML based format second - fn = os.path.join( - self.getProjectManagementDir(), "{0}{1}.e5s".format(fn1, indicator) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}{indicator}.esj" ) - if os.path.exists(fn): - f = QFile(fn) - if f.open(QIODevice.OpenModeFlag.ReadOnly): - reader = SessionReader(f, False) - reader.readXML(quiet=quiet) - f.close() - else: - if not quiet: - EricMessageBox.critical( - self.ui, - self.tr("Read project session"), - self.tr( - "<p>The project session file <b>{0}</b> could" - " not be read.</p>" - ).format(fn), - ) + if not self.__remotefsInterface.exists(fn): + return + else: + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join( + self.getProjectManagementDir(), f"{fn1}{indicator}.esj" + ) + if not os.path.exists(fn): + return + + self.__sessionFile.readFile(fn) @pyqtSlot() def __writeSession(self, quiet=False, indicator=""): @@ -1374,10 +1376,18 @@ ) return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join( - self.getProjectManagementDir(), "{0}{1}.esj".format(fn, indicator) - ) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}{indicator}.esj" + ) + else: + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join( + self.getProjectManagementDir(), f"{fn1}{indicator}.esj" + ) self.__sessionFile.writeFile(fn) @@ -1393,28 +1403,36 @@ ) return - fname, ext = os.path.splitext(os.path.basename(self.pfile)) - - for ext in (".esj", ".e5s", ".e4s"): - fn = os.path.join( - self.getProjectManagementDir(), "{0}{1}".format(fname, ext) - ) - if os.path.exists(fn): - try: + try: + if FileSystemUtilities.isRemoteFileName(self.pfile): + title = self.tr("Delete Remote Project Session") + fname, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fname}.esj" + ) + if self.__remotefsInterface.exists(fn): + self.__remotefsInterface.remove(fn) + else: + title = self.tr("Delete Project Session") + fname, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fname}.esj") + if os.path.exists(fn): os.remove(fn) - except OSError: - EricMessageBox.critical( - self.ui, - self.tr("Delete Project Session"), - self.tr( - "<p>The project session file <b>{0}</b> could" - " not be deleted.</p>" - ).format(fn), - ) + except OSError: + EricMessageBox.critical( + self.ui, + title, + self.tr( + "<p>The project session file <b>{0}</b> could" + " not be deleted.</p>" + ).format(fn), + ) def __readTasks(self): """ - Private method to read in the project tasks file (.etj or .e6t). + Private method to read in the project tasks file (.etj). """ if self.pfile is None: EricMessageBox.critical( @@ -1424,28 +1442,22 @@ ) return - base, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(base)) - if os.path.exists(fn): - # try new style JSON file first - self.__tasksFile.readFile(fn) + if FileSystemUtilities.isRemoteFileName(self.pfile): + base, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{base}.etj" + ) + if not self.__remotefsInterface.exists(fn): + return else: - # try old style XML file second - fn = os.path.join(self.getProjectManagementDir(), "{0}.e6t".format(base)) - if os.path.exists(fn): - f = QFile(fn) - if f.open(QIODevice.OpenModeFlag.ReadOnly): - reader = TasksReader(f, True) - reader.readXML() - f.close() - else: - EricMessageBox.critical( - self.ui, - self.tr("Read Tasks"), - self.tr( - "<p>The tasks file <b>{0}</b> could not be read.</p>" - ).format(fn), - ) + base, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{base}.etj") + if not os.path.exists(fn): + return + + self.__tasksFile.readFile(fn) def writeTasks(self): """ @@ -1454,9 +1466,17 @@ if self.pfile is None: return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - - fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(fn)) + if FileSystemUtilities.isRemoteFileName(self.pfile): + base, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{base}.etj" + ) + else: + base, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{base}.etj") + self.__tasksFile.writeFile(fn) def __showContextMenuDebugger(self): @@ -1467,13 +1487,18 @@ if self.pfile is None: enable = False else: - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - # try new style file first - fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn)) - if not os.path.exists(fn): - # try old style file second - fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn)) - enable = os.path.exists(fn) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}.edj" + ) + enable = self.__remotefsInterface.exists(fn) + else: + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj") + enable = os.path.exists(fn) self.dbgActGrp.findChild( QAction, "project_debugger_properties_load" ).setEnabled(enable) @@ -1500,34 +1525,24 @@ ) return - fn1, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn1)) - if os.path.exists(fn): - # try the new JSON based format first - if self.__debuggerPropertiesFile.readFile(fn): - self.debugPropertiesLoaded = True - self.debugPropertiesChanged = False + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}.edj" + ) + if not self.__remotefsInterface.exists(fn): + return else: - # try the old XML based format second - fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn1)) - - f = QFile(fn) - if f.open(QIODevice.OpenModeFlag.ReadOnly): - reader = DebuggerPropertiesReader(f, self) - reader.readXML(quiet=quiet) - f.close() - self.debugPropertiesLoaded = True - self.debugPropertiesChanged = False - else: - if not quiet: - EricMessageBox.critical( - self.ui, - self.tr("Read Debugger Properties"), - self.tr( - "<p>The project debugger properties file" - " <b>{0}</b> could not be read.</p>" - ).format(fn), - ) + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj") + if not os.path.exists(fn): + return + + if self.__debuggerPropertiesFile.readFile(fn): + self.debugPropertiesLoaded = True + self.debugPropertiesChanged = False @pyqtSlot() def __writeDebugProperties(self, quiet=False): @@ -1547,8 +1562,16 @@ ) return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn)) + if FileSystemUtilities.isRemoteFileName(self.pfile): + fn1, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fn1}.edj" + ) + else: + fn1, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj") with EricOverrideCursor(): self.__debuggerPropertiesFile.writeFile(fn) @@ -1566,24 +1589,32 @@ ) return - fname, ext = os.path.splitext(os.path.basename(self.pfile)) - - for ext in (".edj", ".e4d"): - fn = os.path.join( - self.getProjectManagementDir(), "{0}{1}".format(fname, ext) - ) - if os.path.exists(fn): - try: + try: + if FileSystemUtilities.isRemoteFileName(self.pfile): + title = self.tr("Delete Remote Debugger Properties") + fname, _ext = self.__remotefsInterface.splitext( + self.__remotefsInterface.basename(self.pfile) + ) + fn = self.__remotefsInterface.join( + self.getProjectManagementDir(), f"{fname}.edj" + ) + if self.__remotefsInterface.exists(fn): + self.__remotefsInterface.remove(fn) + else: + title = self.tr("Delete Debugger Properties") + fname, _ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), f"{fname}.edj") + if os.path.exists(fn): os.remove(fn) - except OSError: - EricMessageBox.critical( - self.ui, - self.tr("Delete Debugger Properties"), - self.tr( - "<p>The project debugger properties file" - " <b>{0}</b> could not be deleted.</p>" - ).format(fn), - ) + except OSError: + EricMessageBox.critical( + self.ui, + title, + self.tr( + "<p>The project debugger properties file" + " <b>{0}</b> could not be deleted.</p>" + ).format(fn), + ) def __initDebugProperties(self): """ @@ -1804,16 +1835,37 @@ for langFile in self.__pdata["TRANSLATIONS"][:]: qmFile = self.__binaryTranslationFile(langFile) if qmFile: - if qmFile not in self.__pdata["TRANSLATIONS"] and os.path.exists( - os.path.join(self.ppath, qmFile) - ): - self.appendFile(qmFile) - if tbPath: - qmFile = os.path.join(tbPath, os.path.basename(qmFile)) + if FileSystemUtilities.isRemoteFileName(self.ppath): + if ( + qmFile not in self.__pdata["TRANSLATIONS"] + and self.__remotefsInterface.exists( + self.__remotefsInterface.join(self.ppath, qmFile) + ) + ): + self.appendFile(qmFile) + if tbPath: + qmFile = self.__remotefsInterface.join( + tbPath, self.__remotefsInterface.basename(qmFile) + ) + if ( + qmFile not in self.__pdata["TRANSLATIONS"] + and self.__remotefsInterface.exists( + self.__remotefsInterface.join(self.ppath, qmFile) + ) + ): + self.appendFile(qmFile) + else: if qmFile not in self.__pdata["TRANSLATIONS"] and os.path.exists( os.path.join(self.ppath, qmFile) ): self.appendFile(qmFile) + if tbPath: + qmFile = os.path.join(tbPath, os.path.basename(qmFile)) + if ( + qmFile not in self.__pdata["TRANSLATIONS"] + and os.path.exists(os.path.join(self.ppath, qmFile)) + ): + self.appendFile(qmFile) def removeLanguageFile(self, langFile): """ @@ -1832,12 +1884,18 @@ if qmFile: with contextlib.suppress(ValueError): if self.__pdata["TRANSLATIONSBINPATH"]: - qmFile = self.getRelativePath( - os.path.join( + if FileSystemUtilities.isRemoteFileName(self.ppath): + qmFile = self.__remotefsInterface.join( self.__pdata["TRANSLATIONSBINPATH"], - os.path.basename(qmFile), + self.__remotefsInterface.basename(qmFile), ) - ) + else: + qmFile = self.getRelativePath( + os.path.join( + self.__pdata["TRANSLATIONSBINPATH"], + os.path.basename(qmFile), + ) + ) self.__model.removeItem(qmFile) self.__pdata["TRANSLATIONS"].remove(qmFile) self.setDirty(True) @@ -1853,13 +1911,18 @@ qmFile = self.__binaryTranslationFile(langFile) try: - fn = os.path.join(self.ppath, langFile) - if os.path.exists(fn): - os.remove(fn) + if FileSystemUtilities.isRemoteFileName(self.ppath): + fn = self.__remotefsInterface.join(self.ppath, langFile) + if self.__remotefsInterface.exists(fn): + self.__remotefsInterface.remove(fn) + else: + fn = os.path.join(self.ppath, langFile) + if os.path.exists(fn): + os.remove(fn) except OSError as err: EricMessageBox.critical( self.ui, - self.tr("Delete translation"), + self.tr("Delete Translation"), self.tr( "<p>The selected translation file <b>{0}</b> could not be" " deleted.</p><p>Reason: {1}</p>" @@ -1910,14 +1973,22 @@ # make it relative to the project root, if it starts with that path # assume relative paths are relative to the project root - newfn = self.getRelativePath(fn) if os.path.isabs(fn) else fn - newdir = os.path.dirname(newfn) + if FileSystemUtilities.isRemoteFileName(self.ppath): + newfn = self.getRelativePath(fn) if fn.startswith(self.ppath) else fn + newdir = self.__remotefsInterface.dirname(newfn) + else: + newfn = self.getRelativePath(fn) if os.path.isabs(fn) else fn + newdir = os.path.dirname(newfn) if isSourceFile: filetype = "SOURCES" else: filetype = "OTHERS" - bfn = os.path.basename(newfn) + bfn = ( + self.__remotefsInterface.basename(newfn) + if FileSystemUtilities.isRemoteFileName(self.ppath) + else os.path.basename(newfn) + ) if fnmatch.fnmatch(bfn, "*.ts") or fnmatch.fnmatch(bfn, "*.qm"): filetype = "TRANSLATIONS" else: @@ -1974,6 +2045,7 @@ @param startdir start directory for the selection dialog @type str """ + # TODO: adapt to remote server from .AddFileDialog import AddFileDialog if not startdir: @@ -2038,6 +2110,7 @@ @param quiet flag indicating quiet operations @type bool """ + # TODO: adapt to remote server # get all relevant filename patterns patterns = [] ignorePatterns = [] @@ -2121,6 +2194,7 @@ @param target target directory @type str """ + # TODO: adapt to remote server # first perform the addition of source self.__addSingleDirectory(filetype, source, target, True) @@ -2159,6 +2233,7 @@ @param startdir start directory for the selection dialog @type str """ + # TODO: adapt to remote server from .AddDirectoryDialog import AddDirectoryDialog if not startdir: @@ -2199,6 +2274,7 @@ @param fn file name or directory name to add @type str """ + # TODO: adapt to remote server if fn: # if it is below the project directory, make it relative to that fn = self.getRelativePath(fn) @@ -2244,6 +2320,7 @@ @return flag indicating success @rtype bool """ + # TODO: adapt to remote server fn = self.getRelativePath(oldfn) isSourceFile = fn in self.__pdata["SOURCES"] @@ -2302,6 +2379,7 @@ even if it doesn't have the source extension @type bool """ + # TODO: adapt to remote server fn = self.getRelativePath(oldname) if os.path.dirname(oldname) == os.path.dirname(newname): if self.__isInPdata(oldname): @@ -2324,6 +2402,7 @@ @return list of files starting with a common prefix @rtype list of str """ + # TODO: adapt to remote server filelist = [] start = self.getRelativePath(start) for fileCategory in [ @@ -3423,9 +3502,6 @@ """ if self.isDirty(): if len(self.pfile) > 0: - if self.pfile.endswith(".e4p"): - self.pfile = self.pfile.replace(".e4p", ".epj") - self.__syncRecent() ok = self.__writeProject() else: ok = self.saveProjectAs() @@ -3927,8 +4003,7 @@ @rtype str """ if self.pfile: - name = os.path.splitext(self.pfile)[0] - return os.path.basename(name) + return self.name else: return "" @@ -3939,7 +4014,10 @@ @return path of the management directory @rtype str """ - return os.path.join(self.ppath, ".eric7project") + if FileSystemUtilities.isRemoteFileName(self.ppath): + return self.__remotefsInterface.join(self.ppath, ".eric7project") + else: + return os.path.join(self.ppath, ".eric7project") def createProjectManagementDir(self): """ @@ -3949,8 +4027,10 @@ """ # create management directory if not present mgmtDir = self.getProjectManagementDir() - if not os.path.exists(mgmtDir): - os.makedirs(mgmtDir) + if FileSystemUtilities.isRemoteFileName(mgmtDir): + self.__remotefsInterface.makedirs(mgmtDir, exist_ok=True) + else: + os.makedirs(mgmtDir, exist_ok=True) def getHash(self): """ @@ -3976,7 +4056,13 @@ return "" try: - return str(pathlib.Path(path).relative_to(self.ppath)) + if FileSystemUtilities.isRemoteFileName(self.ppath): + if self.__remotefsInterface.separator() == "\\": + return str(pathlib.PureWindowsPath(path).relative_to(self.ppath)) + else: + return str(pathlib.PurePosixPath(path).relative_to(self.ppath)) + else: + return str(pathlib.PurePath(path).relative_to(self.ppath)) except ValueError: return path @@ -3991,7 +4077,12 @@ belong to the project @rtype str """ - return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path)) + if FileSystemUtilities.isRemoteFileName(self.ppath): + return self.__remotefsInterface.fromNativeSeparators( + self.getRelativePath(path) + ) + else: + return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path)) def getAbsolutePath(self, fn): """ @@ -4003,8 +4094,11 @@ @return absolute path @rtype str """ - if not os.path.isabs(fn): - fn = os.path.join(self.ppath, fn) + if not fn.startswith(self.ppath): + if FileSystemUtilities.isRemoteFileName(self.ppath): + fn = self.__remotefsInterface.join(self.ppath, fn) + else: + fn = os.path.join(self.ppath, fn) return fn def getAbsoluteUniversalPath(self, fn): @@ -4017,8 +4111,15 @@ @return absolute path @rtype str """ - if not os.path.isabs(fn): - fn = os.path.join(self.ppath, FileSystemUtilities.toNativeSeparators(fn)) + if not fn.startswith(self.ppath): + if FileSystemUtilities.isRemoteFileName(self.ppath): + fn = self.__remotefsInterface.join( + self.ppath, self.__remotefsInterface.fromNativeSeparators(fn) + ) + else: + fn = os.path.join( + self.ppath, FileSystemUtilities.fromNativeSeparators(fn) + ) return fn def getEolString(self): @@ -4241,7 +4342,11 @@ @return flag indicating membership @rtype bool """ - newfn = os.path.abspath(fn) + newfn = ( + fn + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.abspath(fn) + ) newfn = self.getRelativePath(newfn) if newfn in self.__pdata[group] or ( group == "OTHERS" @@ -4327,6 +4432,25 @@ act.triggered.connect(self.openProject) self.actions.append(act) + self.openRemoteAct = EricAction( + self.tr("Open remote project"), + EricPixmapCache.getIcon("projectOpen-remote"), + self.tr("Open (Remote)..."), + 0, + 0, + self.actGrp1, + "project_open_remote", + ) + self.openRemoteAct.setStatusTip(self.tr("Open an existing remote project")) + self.openRemoteAct.setWhatsThis( + self.tr( + "<b>Open (Remote)...</b><p>This opens an existing remote project.</p>" + ) + ) + self.openRemoteAct.triggered.connect(self.__openRemoteProject) + self.actions.append(self.openRemoteAct) + self.openRemoteAct.setEnabled(False) # server is not connected initially + self.reloadAct = EricAction( self.tr("Reload project"), EricPixmapCache.getIcon("projectReload"), @@ -7294,6 +7418,34 @@ # the configuration file does not exist or is invalid JSON self.__initVenvConfiguration() + ############################################################################# + ## Below are methods implementing the support for 'eric-ide server projects + ############################################################################# + + @pyqtSlot(bool) + def remoteConnectionChanged(self, connected): + """ + Public slot to handle a change of the 'eric-ide' server connection state. + + @param connected flag indicating the connection state + @type bool + """ + self.openRemoteAct.setEnabled(connected) + + @pyqtSlot() + def __openRemoteProject(self): + """ + Private slot to open a project of an 'eric-ide' server. + """ + # TODO: not implemented yet + fn = EricServerFileDialog.getOpenFileName( + self.parent(), + self.tr("Open project"), + "", + self.tr("Project Files (*.epj)"), + ) + if fn: + self.openProject(fn=fn) # # eflag: noqa = M601
--- a/src/eric7/Project/ProjectBrowserModel.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Project/ProjectBrowserModel.py Thu Feb 22 16:26:46 2024 +0100 @@ -138,7 +138,7 @@ Class implementing the data structure for project browser directory items. """ - def __init__(self, parent, dinfo, projectType, full=True, bold=False): + def __init__(self, parent, dinfo, projectType, full=True, bold=False, fsInterface=None): """ Constructor @@ -152,8 +152,11 @@ @type bool @param bold flag indicating a highlighted font @type bool + @param fsInterface reference to the 'eric-ide' server file system interface + (defaults to None) + @type EricServerFileSystemInterface (optional) """ - BrowserDirectoryItem.__init__(self, parent, dinfo, full) + BrowserDirectoryItem.__init__(self, parent, dinfo, full, fsInterface) ProjectBrowserItemMixin.__init__(self, projectType, bold) self.type_ = BrowserItemType.PbDirectory @@ -165,7 +168,7 @@ """ def __init__( - self, parent, finfo, projectType, full=True, bold=False, sourceLanguage="" + self, parent, finfo, projectType, full=True, bold=False, sourceLanguage="", fsInterface=None ): """ Constructor @@ -182,8 +185,11 @@ @type bool @param sourceLanguage source code language of the project @type str + @param fsInterface reference to the 'eric-ide' server file system interface + (defaults to None) + @type EricServerFileSystemInterface (optional) """ - BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage) + BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage, fsInterface) ProjectBrowserItemMixin.__init__(self, projectType, bold) self.type_ = BrowserItemType.PbFile @@ -198,12 +204,15 @@ vcsStateChanged = pyqtSignal(str) - def __init__(self, parent): + def __init__(self, parent, fsInterface=None): """ Constructor @param parent reference to parent object @type Project.Project + @param fsInterface reference to the 'eric-ide' server interface object + (defaults to None) + @type EricServerFileSystemInterface (optional) """ super().__init__(parent, nopopulate=True) @@ -215,6 +224,8 @@ self.project = parent self.__projectBrowser = None + self.__remotefsInterface = fsInterface + self.watchedItems = {} self.__watcherActive = True self.watcher = QFileSystemWatcher(self) @@ -418,7 +429,16 @@ ) for fn in self.project.getProjectData(dataKey=fileCategory): - fname = os.path.join(self.project.ppath, fn) + fname = ( + self.__remotefsInterface.join(self.project.ppath, fn) + if FileSystemUtilities.isRemoteFileName(self.project.ppath) + else os.path.join(self.project.ppath, fn) + ) + isdir = ( + self.__remotefsInterface.isdir(fname) + if FileSystemUtilities.isRemoteFileName(fname) + else os.path.isdir(fname) + ) parentItem, dt = self.findParentItemByName( self.__projectBrowser.getProjectBrowserFilter(fileCategory), fn ) @@ -429,8 +449,9 @@ self.__projectBrowser.getProjectBrowserFilter(fileCategory), False, bold, + fsInterface=self.__remotefsInterface, ) - if os.path.isdir(fname) + if isdir else ProjectBrowserFileItem( parentItem, fname, @@ -438,6 +459,7 @@ False, bold, sourceLanguage=sourceLanguage, + fsInterface=self.__remotefsInterface, ) ) self._addItem(itm, parentItem)
--- a/src/eric7/Project/ProjectFile.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Project/ProjectFile.py Thu Feb 22 16:26:46 2024 +0100 @@ -17,6 +17,7 @@ from eric7 import Preferences from eric7.EricGui.EricOverrideCursor import EricOverridenCursor from eric7.EricWidgets import EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp from eric7.SystemUtilities import FileSystemUtilities Project = typing.TypeVar("Project") @@ -48,6 +49,11 @@ @return flag indicating a successful write @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + isRemote = FileSystemUtilities.isRemoteFileName(filename) + projectDict = { "header": { "comment": "eric project file for project {0}".format( @@ -85,19 +91,28 @@ "SOURCESDIR", ): with contextlib.suppress(KeyError): - projectDict["project"][key] = FileSystemUtilities.fromNativeSeparators( - projectDict["project"][key] + projectDict["project"][key] = ( + fsInterface.fromNativeSeparators(projectDict["project"][key]) + if isRemote + else FileSystemUtilities.fromNativeSeparators( + projectDict["project"][key] + ) ) try: jsonString = json.dumps(projectDict, indent=2, sort_keys=True) + "\n" - with open(filename, "w", newline="") as f: - f.write(jsonString) + if isRemote: + title = self.tr("Save Remote Project File") + fsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + title = self.tr("Save Project File") + with open(filename, "w", newline="") as f: + f.write(jsonString) except (OSError, TypeError) as err: with EricOverridenCursor(): EricMessageBox.critical( None, - self.tr("Save Project File"), + title, self.tr( "<p>The project file <b>{0}</b> could not be " "written.</p><p>Reason: {1}</p>" @@ -116,14 +131,24 @@ @return flag indicating a successful read @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + + isRemote = FileSystemUtilities.isRemoteFileName(filename) try: - with open(filename, "r") as f: - jsonString = f.read() + if isRemote: + title = self.tr("Read Remote Project File") + jsonString = fsInterface.readFile(filename).decode("utf-8") + else: + title = self.tr("Read Project File") + with open(filename, "r") as f: + jsonString = f.read() projectDict = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical( None, - self.tr("Read Project File"), + title, self.tr( "<p>The project file <b>{0}</b> could not be " "read.</p><p>Reason: {1}</p>" @@ -134,10 +159,17 @@ # modify paths to contain native separators for key in self.__project.getFileCategories() + ["TRANSLATIONEXCEPTIONS"]: with contextlib.suppress(KeyError): - projectDict["project"][key] = [ - FileSystemUtilities.toNativeSeparators(f) - for f in projectDict["project"][key] - ] + projectDict["project"][key] = ( + [ + fsInterface.toNativeSeparators(f) + for f in projectDict["project"][key] + ] + if isRemote + else [ + FileSystemUtilities.toNativeSeparators(f) + for f in projectDict["project"][key] + ] + ) for key in ( "SPELLWORDS", "SPELLEXCLUDES", @@ -148,8 +180,12 @@ "SOURCESDIR", ): with contextlib.suppress(KeyError): - projectDict["project"][key] = FileSystemUtilities.toNativeSeparators( - projectDict["project"][key] + projectDict["project"][key] = ( + fsInterface.toNativeSeparators(projectDict["project"][key]) + if isRemote + else FileSystemUtilities.toNativeSeparators( + projectDict["project"][key] + ) ) self.__project.setProjectData(projectDict["project"])
--- a/src/eric7/Project/UserProjectFile.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Project/UserProjectFile.py Thu Feb 22 16:26:46 2024 +0100 @@ -16,6 +16,8 @@ from eric7 import Preferences from eric7.EricGui.EricOverrideCursor import EricOverridenCursor from eric7.EricWidgets import EricMessageBox +from eric7.EricWidgets.EricApplication import ericApp +from eric7.SystemUtilities import FileSystemUtilities Project = typing.TypeVar("Project") @@ -47,6 +49,10 @@ @return flag indicating a successful write @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + userProjectDict = { "header": { "comment": "eric user project file for project {0}".format( @@ -62,13 +68,18 @@ try: jsonString = json.dumps(userProjectDict, indent=2) + "\n" - with open(filename, "w") as f: - f.write(jsonString) + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Save Remote User Project Properties") + fsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + title = self.tr("Save User Project Properties") + with open(filename, "w") as f: + f.write(jsonString) except (OSError, TypeError) as err: with EricOverridenCursor(): EricMessageBox.critical( None, - self.tr("Save User Project Properties"), + title, self.tr( "<p>The user specific project properties file" " <b>{0}</b> could not be written.</p>" @@ -89,14 +100,23 @@ @return flag indicating a successful read @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + try: - with open(filename, "r") as f: - jsonString = f.read() + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Read Remote User Project Properties") + jsonString = fsInterface.readFile(filename).decode("utf-8") + else: + title = self.tr("Read User Project Properties") + with open(filename, "r") as f: + jsonString = f.read() userProjectDict = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical( None, - self.tr("Read User Project Properties"), + title, self.tr( "<p>The user specific project properties file <b>{0}</b>" " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/QScintilla/Editor.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/QScintilla/Editor.py Thu Feb 22 16:26:46 2024 +0100 @@ -8739,7 +8739,7 @@ if not self.checkDirty(): return - if FileSystemUtilities.isRemoteFileName(self.fileName): + if FileSystemUtilities.isRemoteFileName(self.fileName): # noqa: Y108 package = ( self.fileName if self.__remotefsInterface.isdir(self.fileName)
--- a/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py Thu Feb 22 16:26:46 2024 +0100 @@ -38,6 +38,7 @@ "Getcwd": self.__getcwd, "Listdir": self.__listdir, "Mkdir": self.__mkdir, + "MakeDirs": self.__makedirs, "Rmdir": self.__rmdir, "Replace": self.__replace, "Remove": self.__remove, @@ -73,7 +74,12 @@ self.__server.sendJson( category=EricRequestCategory.FileSystem, reply=request, - params={"Error": f"Request type '{request}' is not supported."}, + params={ + "ok": False, + "error": f"Request type '{request}' is not supported.", + "info": list(self.__requestMethodMapping.keys()), + }, + reqestUuid=reqestUuid, ) def __getPathSeparator(self, params): @@ -96,9 +102,8 @@ @return dictionary containing the reply data @rtype dict """ - directory = params["directory"] try: - os.chdir(directory) + os.chdir(params["directory"]) return {"ok": True} except OSError as err: return { @@ -170,12 +175,9 @@ @return dictionary containing the reply data @rtype dict """ - filename = params["filename"] - statItems = params["st_names"] - try: - result = os.stat(filename) - resultDict = {st: getattr(result, st) for st in statItems} + result = os.stat(params["filename"]) + resultDict = {st: getattr(result, st) for st in params["st_names"]} return {"ok": True, "result": resultDict} except OSError as err: return { @@ -224,10 +226,26 @@ @return dictionary containing the reply data @rtype dict """ - directory = params["directory"] + try: + os.mkdir(params["directory"]) + return {"ok": True} + except OSError as err: + return { + "ok": False, + "error": str(err), + } + def __makedirs(self, params): + """ + Private method to create a new directory. + + @param params dictionary containing the request data + @type dict + @return dictionary containing the reply data + @rtype dict + """ try: - os.makedirs(directory) + os.makedirs(params["directory"], exist_ok=params["exist_ok"]) return {"ok": True} except OSError as err: return { @@ -244,10 +262,8 @@ @return dictionary containing the reply data @rtype dict """ - directory = params["directory"] - try: - os.rmdir(directory) + os.rmdir(params["directory"]) return {"ok": True} except OSError as err: return { @@ -265,11 +281,8 @@ @return dictionary containing the reply data @rtype dict """ - oldName = params["old_name"] - newName = params["new_name"] - try: - os.replace(oldName, newName) + os.replace(params["old_name"], params["new_name"]) return {"ok": True} except OSError as err: return { @@ -286,10 +299,8 @@ @return dictionary containing the reply data @rtype dict """ - filename = params["filename"] - try: - os.remove(filename) + os.remove(params["filename"]) return {"ok": True} except OSError as err: return {
--- a/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Thu Feb 22 16:26:46 2024 +0100 @@ -74,6 +74,9 @@ def __getPathSep(self): """ Private method to get the path separator of the connected server. + + @return path separator character of the server + @rtype str """ loop = QEventLoop() sep = "" @@ -349,10 +352,11 @@ entries = self.direntries( dirname, pattern=basename, recursive=recursive, filesonly=True ) - if includeHidden: - result = entries - else: - result = [e for e in entries if not e.startswith(".")] + result = ( + entries + if includeHidden + else [e for e in entries if not e.startswith(".")] + ) return result @@ -578,6 +582,57 @@ else: return False, "Not connected to an 'eric-ide' server." + def makedirs(self, directory, exist_ok=False): + """ + Public method to create a new directory on the eric-ide serverincluding all + intermediate-level directories. + + @param directory absolute path of the new directory + @type str + @param exist_ok flag indicating that the existence of the directory is + acceptable (defaults to False) + @type bool (optional) + @return tuple containing an OK flag and an error string in case of an issue + @rtype tuple of (bool, str) + """ + loop = QEventLoop() + ok = False + error = "" + + def callback(reply, params): + """ + Function to handle the server reply + + @param reply name of the server reply + @type str + @param params dictionary containing the reply data + @type dict + """ + nonlocal ok, error + + if reply == "MakeDirs": + ok = params["ok"] + with contextlib.suppress(KeyError): + error = params["error"] + loop.quit() + + if self.__serverInterface.isServerConnected(): + self.__serverInterface.sendJson( + category=EricRequestCategory.FileSystem, + request="MakeDirs", + params={ + "directory": FileSystemUtilities.plainFileName(directory), + "exist_ok": exist_ok, + }, + callback=callback, + ) + + loop.exec() + return ok, error + + else: + return False, "Not connected to an 'eric-ide' server." + def rmdir(self, directory): """ Public method to delete a directory on the eric-ide server. @@ -844,6 +899,32 @@ """ return self.split(p)[1] + def toNativeSeparators(self, p): + """ + Public method to convert a path to use server native separator characters. + + @param p path name to be converted + @type str + @return path name with converted separator characters + @rtype str + """ + if self.__serverPathSep == "/": + return p.replace("\\", "/") + else: + return p.replace("/", "\\") + + def fromNativeSeparators(self, p): + """ + Public method to convert a path using server native separator characters to + use "/" separator characters. + + @param p path name to be converted + @type str + @return path name with converted separator characters + @rtype str + """ + return p.replace(self.__serverPathSep, "/") + ####################################################################### ## Methods for reading and writing files ####################################################################### @@ -962,7 +1043,7 @@ def readEncodedFile(self, filename, create=False): """ - Function to read a file and decode its contents into proper text. + Public method to read a file and decode its contents into proper text. @param filename name of the file to read @type str @@ -977,7 +1058,7 @@ def readEncodedFileWithEncoding(self, filename, encoding, create=False): """ - Function to read a file and decode its contents into proper text. + Public method to read a file and decode its contents into proper text. @param filename name of the file to read @type str @@ -992,9 +1073,11 @@ data = self.readFile(filename, create=create) return Utilities.decodeWithEncoding(data, encoding) - def writeEncodedFile(self, filename, text, origEncoding, forcedEncoding="", withBackup=False): + def writeEncodedFile( + self, filename, text, origEncoding, forcedEncoding="", withBackup=False + ): """ - Function to write a file with properly encoded text. + Public method to write a file with properly encoded text. @param filename name of the file to read @type str
--- a/src/eric7/Sessions/SessionFile.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Sessions/SessionFile.py Thu Feb 22 16:26:46 2024 +0100 @@ -16,6 +16,7 @@ from eric7.EricGui.EricOverrideCursor import EricOverridenCursor from eric7.EricWidgets import EricMessageBox from eric7.EricWidgets.EricApplication import ericApp +from eric7.SystemUtilities import FileSystemUtilities class SessionFile(QObject): @@ -46,6 +47,10 @@ @rtype bool """ # get references to objects we need + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + project = ericApp().getObject("Project") projectBrowser = ericApp().getObject("ProjectBrowser") multiProject = ericApp().getObject("MultiProject") @@ -223,13 +228,18 @@ try: jsonString = json.dumps(sessionDict, indent=2) + "\n" - with open(filename, "w") as f: - f.write(jsonString) + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Save Remote Session") + fsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + title = self.tr("Save Session") + with open(filename, "w") as f: + f.write(jsonString) except (OSError, TypeError) as err: with EricOverridenCursor(): EricMessageBox.critical( None, - self.tr("Save Session"), + title, self.tr( "<p>The session file <b>{0}</b> could not be" " written.</p><p>Reason: {1}</p>" @@ -248,14 +258,23 @@ @return flag indicating a successful read @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + try: - with open(filename, "r") as f: - jsonString = f.read() + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Read Remote Session") + jsonString = fsInterface.readFile(filename).decode("utf-8") + else: + title = self.tr("Read Session") + with open(filename, "r") as f: + jsonString = f.read() sessionDict = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical( None, - self.tr("Read Session"), + title, self.tr( "<p>The session file <b>{0}</b> could not be read.</p>" "<p>Reason: {1}</p>"
--- a/src/eric7/Tasks/TasksFile.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Tasks/TasksFile.py Thu Feb 22 16:26:46 2024 +0100 @@ -16,6 +16,7 @@ from eric7.EricGui.EricOverrideCursor import EricOverridenCursor from eric7.EricWidgets import EricMessageBox from eric7.EricWidgets.EricApplication import ericApp +from eric7.SystemUtilities import FileSystemUtilities from .Task import TaskPriority, TaskType @@ -46,6 +47,10 @@ @return flag indicating a successful write @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + # prepare the tasks data dictionary # step 0: header tasksDict = {} @@ -85,13 +90,18 @@ try: jsonString = json.dumps(tasksDict, indent=2) + "\n" - with open(filename, "w") as f: - f.write(jsonString) + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Save Remote Tasks") + fsInterface.writeFile(filename, jsonString.encode("utf-8")) + else: + title = self.tr("Save Tasks") + with open(filename, "w") as f: + f.write(jsonString) except (OSError, TypeError) as err: with EricOverridenCursor(): EricMessageBox.critical( None, - self.tr("Save Tasks"), + title, self.tr( "<p>The tasks file <b>{0}</b> could not be" " written.</p><p>Reason: {1}</p>" @@ -110,14 +120,23 @@ @return flag indicating a successful read @rtype bool """ + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) + try: - with open(filename, "r") as f: - jsonString = f.read() + if FileSystemUtilities.isRemoteFileName(filename): + title = self.tr("Read Remote Tasks") + jsonString = fsInterface.readFile(filename).decode("utf-8") + else: + title = self.tr("Read Tasks") + with open(filename, "r") as f: + jsonString = f.read() tasksDict = json.loads(jsonString) except (OSError, json.JSONDecodeError) as err: EricMessageBox.critical( None, - self.tr("Read Tasks"), + title, self.tr( "<p>The tasks file <b>{0}</b> could not be read.</p>" "<p>Reason: {1}</p>"
--- a/src/eric7/UI/BrowserModel.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/UI/BrowserModel.py Thu Feb 22 16:26:46 2024 +0100 @@ -618,7 +618,6 @@ """ self._addWatchedItem(parentItem) - # TODO: add support for 'remote:' directories dirName = parentItem.dirName() if FileSystemUtilities.isPlainFileName(dirName): qdir = QDir(dirName) @@ -638,18 +637,26 @@ if f.isDir(): node = BrowserDirectoryItem( parentItem, - FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()), + FileSystemUtilities.toNativeSeparators( + f.absoluteFilePath() + ), False, ) else: - fileFilters = Preferences.getUI("BrowsersFileFilters").split(";") + fileFilters = Preferences.getUI("BrowsersFileFilters").split( + ";" + ) if fileFilters: fn = f.fileName() - if any(fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters): + if any( + fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters + ): continue node = BrowserFileItem( parentItem, - FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()), + FileSystemUtilities.toNativeSeparators( + f.absoluteFilePath() + ), ) self._addItem(node, parentItem) if repopulate: @@ -673,10 +680,14 @@ fsInterface=self.__remotefsInterface, ) else: - fileFilters = Preferences.getUI("BrowsersFileFilters").split(";") + fileFilters = Preferences.getUI("BrowsersFileFilters").split( + ";" + ) if fileFilters: fn = entry["name"] - if any(fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters): + if any( + fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters + ): continue node = BrowserFileItem( parentItem, @@ -1300,11 +1311,7 @@ if FileSystemUtilities.isRemoteFileName(dinfo): self._dirName = dinfo - dn = ( - self._dirName - if full - else self.__fsInterface.basename(self._dirName) - ) + dn = self._dirName if full else self.__fsInterface.basename(self._dirName) else: self._dirName = os.path.abspath(dinfo) dn = self._dirName if full else os.path.basename(self._dirName) @@ -1337,11 +1344,7 @@ """ if FileSystemUtilities.isRemoteFileName(dinfo): self._dirName = dinfo - dn = ( - self._dirName - if full - else self.__fsInterface.basename(self._dirName) - ) + dn = self._dirName if full else self.__fsInterface.basename(self._dirName) else: self._dirName = os.path.abspath(dinfo) dn = self._dirName if full else os.path.basename(self._dirName)
--- a/src/eric7/UI/UserInterface.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/UI/UserInterface.py Thu Feb 22 16:26:46 2024 +0100 @@ -311,6 +311,10 @@ splash.showMessage(self.tr("Initializing Basic Services...")) + # create the remote server interface + logging.debug("Creating 'eric-ide' Server Interface...") + self.__ericServerInterface = EricServerInterface(self) + # Generate the conda interface logging.debug("Creating Conda Interface...") self.condaInterface = Conda(self) @@ -329,7 +333,7 @@ # Generate an empty project object and multi project object logging.debug("Creating Project Manager...") - self.project = Project(self) + self.project = Project(self, remoteServer=self.__ericServerInterface) ericApp().registerObject("Project", self.project) logging.debug("Creating Multi-Project Manager...") @@ -597,6 +601,9 @@ self.__ericServerInterface.connectionStateChanged.connect( self.shell.remoteConnectionChanged ) + self.__ericServerInterface.connectionStateChanged.connect( + self.project.remoteConnectionChanged + ) self.__ericServerInterface.aboutToDisconnect.connect( self.viewmanager.closeRemoteEditors ) @@ -804,15 +811,10 @@ from .PythonAstViewer import PythonAstViewer from .PythonDisViewer import PythonDisViewer - - # create the remote server interface - logging.debug("Creating 'eric-ide' Server Interface...") - self.__ericServerInterface = EricServerInterface(self) - # Create the view manager depending on the configuration setting logging.debug("Creating Viewmanager...") self.viewmanager = ViewManager.factory( - self, self.__debugServer, self.pluginManager + self, self.__debugServer, self.__ericServerInterface, self.pluginManager ) # Create previewer
--- a/src/eric7/Utilities/ClassBrowsers/pyclbr.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Utilities/ClassBrowsers/pyclbr.py Thu Feb 22 16:26:46 2024 +0100 @@ -417,27 +417,28 @@ fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem") if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]): - type = ClassBrowsers.determineSourceType(module, isTypeFile) + sourceType = ClassBrowsers.determineSourceType(module, isTypeFile) file = fsInterface.join(searchPath[0], module) else: # search the path for the module searchPath = [] if searchPath is None else searchPath[:] fullpath = searchPath[:] + sys.path[:] - f, file, (suff, mode, type) = ClassBrowsers.find_module( + f, file, (suff, mode, sourceType) = ClassBrowsers.find_module( module, fullpath, isTypeFile ) if f: f.close() - if type not in SUPPORTED_TYPES: + if sourceType not in SUPPORTED_TYPES: # not Python source, can't do anything with this module return {} try: - if FileSystemUtilities.isRemoteFileName(file): - src = fsInterface.readEncodedFile(file)[0] - else: - src = Utilities.readEncodedFile(file)[0] + src = ( + fsInterface.readEncodedFile(file)[0] + if FileSystemUtilities.isRemoteFileName(file) + else Utilities.readEncodedFile(file)[0] + ) except (OSError, UnicodeError): # can't do anything with this module return {}
--- a/src/eric7/Utilities/ClassBrowsers/rbclbr.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Utilities/ClassBrowsers/rbclbr.py Thu Feb 22 16:26:46 2024 +0100 @@ -295,24 +295,25 @@ fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem") if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]): - type = ClassBrowsers.determineSourceType(module) + sourceType = ClassBrowsers.determineSourceType(module) file = fsInterface.join(searchPath[0], module) else: # search the path for the module fullpath = [] if searchPath is None else searchPath[:] - f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath) + f, file, (suff, mode, sourceType) = ClassBrowsers.find_module(module, fullpath) if f: f.close() - if type not in SUPPORTED_TYPES: + if sourceType not in SUPPORTED_TYPES: # not Ruby source, can't do anything with this module return {} try: - if FileSystemUtilities.isRemoteFileName(file): - src = fsInterface.readEncodedFile(file)[0] - else: - src = Utilities.readEncodedFile(file)[0] + src = ( + fsInterface.readEncodedFile(file)[0] + if FileSystemUtilities.isRemoteFileName(file) + else Utilities.readEncodedFile(file)[0] + ) except (OSError, UnicodeError): # can't do anything with this module return {}
--- a/src/eric7/Utilities/ModuleParser.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/Utilities/ModuleParser.py Thu Feb 22 16:26:46 2024 +0100 @@ -1679,6 +1679,7 @@ @return reference to a Module object containing the parsed module information @rtype Module + @exception ImportError raised to indicate an unsupported source code type """ global _modules @@ -1689,7 +1690,9 @@ isRemoteFileName = FileSystemUtilities.isRemoteFileName(module) if isRemoteFileName: - fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem") + fsInterface = ( + ericApp().getObject("EricServer").getServiceInterface("FileSystem") + ) module, extension = fsInterface.splitext(fsInterface.basename(module)) else: with contextlib.suppress(ValueError): @@ -1740,7 +1743,7 @@ moduleType = PTL_SOURCE elif extension == ".rb": moduleType = RB_SOURCE - elif extension in _extensions: + elif extension in _extensions: # noqa: Y106 moduleType = PY_SOURCE else: raise ImportError @@ -1777,15 +1780,14 @@ mod = Module(modname, file, moduleType) with contextlib.suppress(UnicodeError, OSError): - if isRemoteFileName: - src = ( - ericApp() - .getObject("EricServer") - .getServiceInterface("FileSystem") - .readEncodedFile(file)[0] - ) - else: - src = Utilities.readEncodedFile(file)[0] + src = ( + ericApp() + .getObject("EricServer") + .getServiceInterface("FileSystem") + .readEncodedFile(file)[0] + if isRemoteFileName + else Utilities.readEncodedFile(file)[0] + ) mod.scan(src) if caching: _modules[modname] = mod
--- a/src/eric7/ViewManager/ViewManager.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/ViewManager/ViewManager.py Thu Feb 22 16:26:46 2024 +0100 @@ -149,7 +149,7 @@ self.__watcher = QFileSystemWatcher(self) self.__watcher.fileChanged.connect(self.__watchedFileChanged) - def setReferences(self, ui, dbs): + def setReferences(self, ui, dbs, remoteServerInterface): """ Public method to set some references needed later on. @@ -157,11 +157,16 @@ @type UserInterface @param dbs reference to the debug server object @type DebugServer + @param remoteServerInterface reference to the 'eric-ide' server interface + @type EricServerInterface """ from eric7.QScintilla.SearchReplaceWidget import SearchReplaceSlidingWidget self.ui = ui self.dbs = dbs + self.__remotefsInterface = remoteServerInterface.getServiceInterface( + "FileSystem" + ) self.__searchReplaceWidget = SearchReplaceSlidingWidget(self, ui) @@ -6114,9 +6119,15 @@ filenames = [] for editor in self.editors: fn = editor.getFileName() - if fn is not None and fn not in filenames and os.path.exists(fn): + if fn is not None and fn not in filenames: # only return names of existing files - filenames.append(fn) + exists = ( + self.__remotefsInterface.exists(fn) + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.exists(fn) + ) + if exists: + filenames.append(fn) return filenames
--- a/src/eric7/ViewManager/__init__.py Mon Feb 19 19:37:00 2024 +0100 +++ b/src/eric7/ViewManager/__init__.py Thu Feb 22 16:26:46 2024 +0100 @@ -23,7 +23,7 @@ ###################################################################### -def factory(ui, dbs, pluginManager): +def factory(ui, dbs, remoteServerInterface, pluginManager): """ Modul factory function to generate the right viewmanager type. @@ -34,6 +34,8 @@ @type UserInterface @param dbs reference to the debug server object @type DebugServer + @param remoteServerInterface reference to the 'eric-ide' server interface + @type EricServerInterface @param pluginManager reference to the plugin manager object @type PluginManager @return the instantiated viewmanager @@ -48,5 +50,5 @@ if vm is None: raise RuntimeError(f"Could not create a viemanager object.\nError: {err}") Preferences.setViewManager("tabview") - vm.setReferences(ui, dbs) + vm.setReferences(ui, dbs, remoteServerInterface) return vm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-dark/projectOpen-remote.svg Thu Feb 22 16:26:46 2024 +0100 @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + viewBox="0 0 22 22" + id="svg10" + sodipodi:docname="projectOpen-remote.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview12" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="46.681818" + inkscape:cx="12.028238" + inkscape:cy="11.546251" + inkscape:window-width="2580" + inkscape:window-height="1285" + inkscape:window-x="861" + inkscape:window-y="96" + inkscape:window-maximized="0" + inkscape:current-layer="svg10" /> + <defs + id="defs4"> + <style + type="text/css" + id="style2">.ColorScheme-Text { + color:#eff0f1; + }</style> + </defs> + <g + id="g4755" + transform="matrix(0.75,0,0,0.75,2.75,0.25)"> + <path + d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z" + color="#eff0f1" + fill="currentColor" + id="path6" /> + <path + class="ColorScheme-Text" + d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z" + color="#eff0f1" + fill="currentColor" + id="path8" /> + </g> + <path + style="color:#eff0f1;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099" + d="m 8.4999998,17 v 1.333333 H 1 v 1.333334 H 8.4999998 V 21 H 13.499999 V 19.666667 H 21 V 18.333333 H 13.499999 V 17 H 8.4999998 M 11,17.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2500002,-0.594666 -1.2500002,-1.333333 0,-0.738667 0.5575002,-1.333333 1.2500002,-1.333333" + class="ColorScheme-Text" + id="path4" + sodipodi:nodetypes="ccccccccccccccsssc" /> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-light/projectOpen-remote.svg Thu Feb 22 16:26:46 2024 +0100 @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + viewBox="0 0 22 22" + id="svg1062" + sodipodi:docname="projectOpen-remote.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1064" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="46.681818" + inkscape:cx="11.289192" + inkscape:cy="11.803311" + inkscape:window-width="1857" + inkscape:window-height="1337" + inkscape:window-x="62" + inkscape:window-y="17" + inkscape:window-maximized="0" + inkscape:current-layer="svg1062" /> + <defs + id="defs1056"> + <style + type="text/css" + id="style1054">.ColorScheme-Text { + color:#eff0f1; + }</style> + </defs> + <g + id="g9300" + transform="matrix(0.75,0,0,0.75,2.75,0.25)"> + <path + d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z" + color="#eff0f1" + fill="#232629" + id="path1058" /> + <path + class="ColorScheme-Text" + d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z" + color="#eff0f1" + fill="#232629" + id="path1060" /> + </g> + <path + style="color:#232629;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099" + d="m 8.7679983,17 v 1.333333 H 1.268 v 1.333334 H 8.7679983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.767997 V 17 H 8.7679983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.25,-0.594666 -1.25,-1.333333 0,-0.738667 0.5575,-1.333333 1.25,-1.333333" + class="ColorScheme-Text" + id="path4" + sodipodi:nodetypes="ccccccccccccccsssc" /> +</svg>