--- a/src/eric7/Project/Project.py Sun Jun 02 09:51:47 2024 +0200 +++ b/src/eric7/Project/Project.py Wed Jul 03 09:20:41 2024 +0200 @@ -31,7 +31,7 @@ from PyQt6.QtGui import QAction, QKeySequence from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu, QToolBar -from eric7 import Globals, Preferences, Utilities +from eric7 import EricUtilities, Preferences, Utilities from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction from eric7.CodeFormatting.BlackUtilities import aboutBlack from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction @@ -44,6 +44,7 @@ from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog from eric7.EricWidgets.EricProgressDialog import EricProgressDialog from eric7.Globals import recentNameProject +from eric7.RemoteServerInterface import EricServerFileDialog from eric7.Sessions.SessionFile import SessionFile from eric7.SystemUtilities import ( FileSystemUtilities, @@ -158,18 +159,22 @@ 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.__remoteServer = remoteServer + self.__remotefsInterface = remoteServer.getServiceInterface("FileSystem") self.__progLanguages = [ "Python3", @@ -210,7 +215,7 @@ else: self.vcs = self.initVCS() - self.__model = ProjectBrowserModel(self) + self.__model = ProjectBrowserModel(self, fsInterface=self.__remotefsInterface) self.codemetrics = None self.codecoverage = None @@ -508,7 +513,6 @@ self.opened = False self.subdirs = [] # record the project dir as a relative path (i.e. empty path) - self.otherssubdirs = [] self.vcs = None self.vcsRequested = False self.dbgVirtualEnv = "" @@ -976,7 +980,13 @@ rp = Preferences.Prefs.rsettings.value(recentNameProject) if rp is not None: for f in rp: - if pathlib.Path(f).exists(): + if ( + FileSystemUtilities.isRemoteFileName(f) + and ( + not self.__remoteServer.isServerConnected() + or self.__remotefsInterface.exists(f) + ) + ) or pathlib.Path(f).exists(): self.recent.append(f) def __saveRecent(self): @@ -1098,10 +1108,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: @@ -1110,7 +1128,7 @@ def __readProject(self, fn): """ - Private method to read in a project (.epj) file. + Private method to read in a project file (.epj). @param fn filename of the project file to be read @type str @@ -1121,8 +1139,17 @@ 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] + self.__remotefsInterface.populateFsCache(self.ppath) + 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() @@ -1133,15 +1160,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(): @@ -1150,15 +1184,21 @@ # 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(self.ppath) + 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: - self.otherssubdirs.append(dn) + dn = ( + self.__remotefsInterface.dirname(fn) + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.dirname(fn) + ) return res @@ -1196,9 +1236,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 @@ -1213,10 +1260,22 @@ 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): - 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: + 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): """ @@ -1225,8 +1284,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) @@ -1239,9 +1306,18 @@ if self.pfile is None: enable = False else: - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn_sess = os.path.join(self.getProjectManagementDir(), "{0}.esj".format(fn)) - enable = os.path.exists(fn_sess) + 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) @@ -1265,12 +1341,22 @@ ) 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): - self.__sessionFile.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}{indicator}.esj" + ) + 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=""): @@ -1292,10 +1378,16 @@ ) 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) @@ -1311,21 +1403,32 @@ ) return - fname, ext = os.path.splitext(os.path.basename(self.pfile)) - - fn = os.path.join(self.getProjectManagementDir(), f"{fname}.esj") - if os.path.exists(fn): - try: - 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), + 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, + title, + self.tr( + "<p>The project session file <b>{0}</b> could" + " not be deleted.</p>" + ).format(fn), + ) def __readTasks(self): """ @@ -1339,10 +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): - 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: + 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): """ @@ -1351,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): @@ -1364,9 +1487,18 @@ if self.pfile is None: enable = False else: - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".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) @@ -1392,9 +1524,21 @@ ) 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) and self.__debuggerPropertiesFile.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}.edj" + ) + 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}.edj") + if not os.path.exists(fn): + return + if self.__debuggerPropertiesFile.readFile(fn): self.debugPropertiesLoaded = True self.debugPropertiesChanged = False @@ -1416,8 +1560,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) @@ -1434,21 +1586,32 @@ ) return - fname, ext = os.path.splitext(os.path.basename(self.pfile)) - - fn = os.path.join(self.getProjectManagementDir(), f"{fname}.edj") - if os.path.exists(fn): - try: - 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), + 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, + title, + self.tr( + "<p>The project debugger properties file" + " <b>{0}</b> could not be deleted.</p>" + ).format(fn), + ) def __initDebugProperties(self): """ @@ -1489,7 +1652,9 @@ """ from .DebuggerPropertiesDialog import DebuggerPropertiesDialog - dlg = DebuggerPropertiesDialog(self) + dlg = DebuggerPropertiesDialog( + self, isRemote=FileSystemUtilities.isRemoteFileName(self.ppath) + ) if dlg.exec() == QDialog.DialogCode.Accepted: dlg.storeData() @@ -1502,7 +1667,9 @@ @return value of the property @rtype Any """ - if key == "INTERPRETER": + if key == "INTERPRETER" and not FileSystemUtilities.isRemoteFileName( + self.ppath + ): return ( ericApp() .getObject("VirtualEnvManager") @@ -1669,16 +1836,34 @@ 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): """ @@ -1697,12 +1882,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) @@ -1718,13 +1909,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>" @@ -1775,14 +1971,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: @@ -1823,8 +2027,6 @@ dirty = True else: updateModel and self.repopulateItem(newfn) - if newdir not in self.otherssubdirs: - self.otherssubdirs.append(newdir) if dirty: self.setDirty(True) @@ -1848,17 +2050,37 @@ if dlg.exec() == QDialog.DialogCode.Accepted: fnames, target, isSource = dlg.getData() if target != "": + isRemote = FileSystemUtilities.isRemoteFileName(target) for fn in fnames: - targetfile = os.path.join(target, os.path.basename(fn)) - if not FileSystemUtilities.samepath(os.path.dirname(fn), target): + targetfile = ( + self.__remotefsInterface.join( + target, self.__remotefsInterface.basename(fn) + ) + if isRemote + else os.path.join(target, os.path.basename(fn)) + ) + if not FileSystemUtilities.samepath( + ( + self.__remotefsInterface.dirname(fn) + if isRemote + else os.path.dirname(fn) + ), + target, + ): try: - if not os.path.isdir(target): - os.makedirs(target) - - if os.path.exists(targetfile): + if isRemote: + if not self.__remotefsInterface.isdir(target): + self.__remotefsInterface.makedirs(target) + else: + if not os.path.isdir(target): + os.makedirs(target) + + if (not isRemote and os.path.exists(targetfile)) or ( + isRemote and self.__remotefsInterface.exists(targetfile) + ): res = EricMessageBox.yesNo( self.ui, - self.tr("Add file"), + self.tr("Add File"), self.tr( "<p>The file <b>{0}</b> already" " exists.</p><p>Overwrite it?</p>" @@ -1868,11 +2090,14 @@ if not res: return # don't overwrite - shutil.copy(fn, target) + if isRemote: + self.__remotefsInterface.shutilCopy(fn, target) + else: + shutil.copy(fn, target) except OSError as why: EricMessageBox.critical( self.ui, - self.tr("Add file"), + self.tr("Add File"), self.tr( "<p>The selected file <b>{0}</b> could" " not be added to <b>{1}</b>.</p>" @@ -1913,15 +2138,20 @@ ignorePatterns.append(pattern) files = [] + isRemote = FileSystemUtilities.isRemoteFileName(target) for pattern in patterns: - sstring = "{0}{1}{2}".format(source, os.sep, pattern) - files.extend(glob.glob(sstring)) + if isRemote: + sstring = self.__remotefsInterface.join(source, pattern) + files.extend(self.__remotefsInterface.glob(sstring)) + else: + sstring = os.path.join(source, pattern) + files.extend(glob.glob(sstring)) if len(files) == 0: if not quiet: EricMessageBox.information( self.ui, - self.tr("Add directory"), + self.tr("Add Directory"), self.tr( "<p>The source directory doesn't contain" " any files belonging to the selected category.</p>" @@ -1929,15 +2159,19 @@ ) return - if not FileSystemUtilities.samepath(target, source) and not os.path.isdir( - target + if not FileSystemUtilities.samepath(target, source) and not ( + (not isRemote and os.path.isdir(target)) + or (isRemote and self.__remotefsInterface.isdir(target)) ): try: - os.makedirs(target) + if isRemote: + self.__remotefsInterface.makedirs(target) + else: + os.makedirs(target) except OSError as why: EricMessageBox.critical( self.ui, - self.tr("Add directory"), + self.tr("Add Directory"), self.tr( "<p>The target directory <b>{0}</b> could not be" " created.</p><p>Reason: {1}</p>" @@ -1950,13 +2184,21 @@ if fnmatch.fnmatch(file, pattern): continue - targetfile = os.path.join(target, os.path.basename(file)) + targetfile = ( + self.__remotefsInterface.join( + target, self.__remotefsInterface.basename(file) + ) + if isRemote + else os.path.join(target, os.path.basename(file)) + ) if not FileSystemUtilities.samepath(target, source): try: - if os.path.exists(targetfile): + if (not isRemote and os.path.exists(targetfile)) or ( + isRemote and self.__remotefsInterface.exists(targetfile) + ): res = EricMessageBox.yesNo( self.ui, - self.tr("Add directory"), + self.tr("Add Directory"), self.tr( "<p>The file <b>{0}</b> already exists.</p>" "<p>Overwrite it?</p>" @@ -1967,7 +2209,10 @@ continue # don't overwrite, carry on with next file - shutil.copy(file, target) + if isRemote: + self.__remotefsInterface.shutilCopy(file, target) + else: + shutil.copy(file, target) except OSError: continue self.appendFile(targetfile) @@ -2003,16 +2248,28 @@ if filetype == "__IGNORE__" ] - # now recurse into subdirectories - with os.scandir(source) as dirEntriesIterator: - for dirEntry in dirEntriesIterator: - if dirEntry.is_dir() and not any( - fnmatch.fnmatch(dirEntry.name, ignore_pattern) + # now recurs into subdirectories + if FileSystemUtilities.isRemoteFileName(target): + for entry in self.__remotefsInterface.listdir(source)[2]: + if entry["is_dir"] and not any( + fnmatch.fnmatch(entry["name"], ignore_pattern) for ignore_pattern in ignore_patterns ): self.__addRecursiveDirectory( - filetype, dirEntry.path, os.path.join(target, dirEntry.name) + filetype, + entry["path"], + self.__remotefsInterface.join(target, entry["name"]), ) + else: + with os.scandir(source) as dirEntriesIterator: + for dirEntry in dirEntriesIterator: + if dirEntry.is_dir() and not any( + fnmatch.fnmatch(dirEntry.name, ignore_pattern) + for ignore_pattern in ignore_patterns + ): + self.__addRecursiveDirectory( + filetype, dirEntry.path, os.path.join(target, dirEntry.name) + ) @pyqtSlot() def addDirectory(self, fileTypeFilter=None, startdir=None): @@ -2065,11 +2322,17 @@ @type str """ if fn: + separator = ( + self.__remotefsInterface.separator() + if FileSystemUtilities.isRemoteFileName(fn) + else os.sep + ) + # if it is below the project directory, make it relative to that fn = self.getRelativePath(fn) # if it ends with the directory separator character, remove it - if fn.endswith(os.sep): + if fn.endswith(separator): fn = fn[:-1] if fn not in self.__pdata["OTHERS"]: @@ -2077,9 +2340,6 @@ self.othersAdded(fn) self.setDirty(True) - if os.path.isdir(fn) and fn not in self.otherssubdirs: - self.otherssubdirs.append(fn) - def renameMainScript(self, oldfn, newfn): """ Public method to rename the main script. @@ -2109,22 +2369,36 @@ @return flag indicating success @rtype bool """ + isRemote = FileSystemUtilities.isRemoteFileName(oldfn) + fn = self.getRelativePath(oldfn) isSourceFile = fn in self.__pdata["SOURCES"] if newfn is None: - newfn = EricFileDialog.getSaveFileName( - None, - self.tr("Rename file"), - oldfn, - "", - options=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 os.path.exists(newfn): + + if (not isRemote and os.path.exists(newfn)) or ( + isRemote and self.__remotefsInterface.exists(newfn) + ): res = EricMessageBox.yesNo( self.ui, self.tr("Rename File"), @@ -2138,7 +2412,10 @@ return False try: - os.rename(oldfn, newfn) + if isRemote: + self.__remotefsInterface.replace(oldfn, newfn) + else: + os.rename(oldfn, newfn) except OSError as msg: EricMessageBox.critical( self.ui, @@ -2167,8 +2444,15 @@ even if it doesn't have the source extension @type bool """ + if FileSystemUtilities.isRemoteFileName(oldname): + oldDirName = self.__remotefsInterface.dirname(oldname) + newDirName = self.__remotefsInterface.dirname(newname) + else: + oldDirName = os.path.dirname(oldname) + newDirName = os.path.dirname(newname) + fn = self.getRelativePath(oldname) - if os.path.dirname(oldname) == os.path.dirname(newname): + if oldDirName == newDirName: if self.__isInPdata(oldname): self.removeFile(oldname, False) self.appendFile(newname, isSourceFile, False) @@ -2189,6 +2473,8 @@ @return list of files starting with a common prefix @rtype list of str """ + isRemote = FileSystemUtilities.isRemoteFileName(self.ppath) + filelist = [] start = self.getRelativePath(start) for fileCategory in [ @@ -2196,7 +2482,11 @@ ]: for entry in self.__pdata[fileCategory][:]: if entry.startswith(start): - filelist.append(os.path.join(self.ppath, entry)) + filelist.append( + self.__remotefsInterface.join(self.ppath, entry) + if isRemote + else os.path.join(self.ppath, entry) + ) return filelist def __reorganizeFiles(self): @@ -2204,8 +2494,9 @@ Private method to reorganize files stored in the project. """ reorganized = False - - # init data store for the reorganization + isRemote = FileSystemUtilities.isRemoteFileName(self.ppath) + + # initialize data store for the reorganization newPdata = {} for fileCategory in self.getFileCategories(): newPdata[fileCategory] = [] @@ -2214,7 +2505,11 @@ for fileCategory in self.getFileCategories(): for fn in self.__pdata[fileCategory][:]: filetype = fileCategory - bfn = os.path.basename(fn) + bfn = ( + self.__remotefsInterface.basename(fn) + if isRemote + else os.path.basename(fn) + ) for pattern in sorted(self.__pdata["FILETYPES"], reverse=True): if fnmatch.fnmatch(bfn, pattern): filetype = self.__pdata["FILETYPES"][pattern] @@ -2243,6 +2538,8 @@ @param newdn new directory name @type str """ + isRemote = FileSystemUtilities.isRemoteFileName(self.ppath) + olddn = self.getRelativePath(olddn) newdn = self.getRelativePath(newdn) for fileCategory in [ @@ -2252,7 +2549,12 @@ if entry.startswith(olddn): entry = entry.replace(olddn, newdn) self.appendFile( - os.path.join(self.ppath, entry), fileCategory == "SOURCES" + ( + self.__remotefsInterface.join(self.ppath, entry) + if isRemote + else os.path.join(self.ppath, entry) + ), + fileCategory == "SOURCES", ) self.setDirty(True) @@ -2278,12 +2580,8 @@ self.__pdata[fileCategory].remove(entry) entry = entry.replace(olddn, newdn) self.__pdata[fileCategory].append(entry) - if fileCategory == "OTHERS": - if newdn not in self.otherssubdirs: - self.otherssubdirs.append(newdn) - else: - if newdn not in self.subdirs: - self.subdirs.append(newdn) + if fileCategory != "OTHERS" and newdn not in self.subdirs: + self.subdirs.append(newdn) if typeStrings: # the directory is controlled by the project self.setDirty(True) @@ -2325,13 +2623,19 @@ @param dn directory name to be removed from the project @type str """ + separator = ( + self.__remotefsInterface.separator() + if FileSystemUtilities.isRemoteFileName(self.ppath) + else os.sep + ) + dirty = False dn = self.getRelativePath(dn) for entry in self.__pdata["OTHERS"][:]: if entry.startswith(dn): self.__pdata["OTHERS"].remove(entry) dirty = True - dn2 = dn if dn.endswith(os.sep) else dn + os.sep + dn2 = dn if dn.endswith(separator) else dn + separator for fileCategory in [c for c in self.getFileCategories() if c != "OTHERS"]: for entry in self.__pdata[fileCategory][:]: if entry.startswith(dn2): @@ -2352,26 +2656,38 @@ @rtype bool """ try: - os.remove(os.path.join(self.ppath, fn)) - path, ext = os.path.splitext(fn) - if ext == ".ui": - fn2 = os.path.join(self.ppath, "{0}.h".format(fn)) - if os.path.isfile(fn2): - os.remove(fn2) - head, tail = os.path.split(path) - for ext in [".pyc", ".pyo"]: - fn2 = os.path.join(self.ppath, path + ext) - if os.path.isfile(fn2): - os.remove(fn2) - pat = os.path.join( - self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext) + if FileSystemUtilities.isRemoteFileName(self.ppath): + self.__remotefsInterface.remove( + self.__remotefsInterface.join(self.ppath, fn) ) - for f in glob.glob(pat): - os.remove(f) + filepath = self.__remotefsInterface.splitext(fn)[0] + head, tail = self.__remotefsInterface.split(filepath) + for ext in [".pyc", ".pyo"]: + fn2 = self.__remotefsInterface.join(self.ppath, filepath + ext) + if self.__remotefsInterface.isfile(fn2): + self.__remotefsInterface.remove(fn2) + pat = self.__remotefsInterface.join( + self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext) + ) + for f in self.__remotefsInterface.glob(pat): + self.__remotefsInterface.remove(f) + else: + os.remove(os.path.join(self.ppath, fn)) + filepath = os.path.splitext(fn)[0] + head, tail = os.path.split(filepath) + for ext in [".pyc", ".pyo"]: + fn2 = os.path.join(self.ppath, filepath + ext) + if os.path.isfile(fn2): + os.remove(fn2) + pat = os.path.join( + self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext) + ) + for f in glob.glob(pat): + os.remove(f) except OSError as err: EricMessageBox.critical( self.ui, - self.tr("Delete file"), + self.tr("Delete File"), self.tr( "<p>The selected file <b>{0}</b> could not be" " deleted.</p><p>Reason: {1}</p>" @@ -2380,8 +2696,6 @@ return False self.removeFile(fn) - if ext == ".ui": - self.removeFile(fn + ".h") return True def deleteDirectory(self, dn): @@ -2393,14 +2707,16 @@ @return flag indicating success @rtype bool """ - if not os.path.isabs(dn): - dn = os.path.join(self.ppath, dn) + dn = self.getAbsolutePath(dn) try: - shutil.rmtree(dn, ignore_errors=True) + if FileSystemUtilities.isRemoteFileName(dn): + self.__remotefsInterface.shutilRmtree(dn, ignore_errors=True) + else: + shutil.rmtree(dn, ignore_errors=True) except OSError as err: EricMessageBox.critical( self.ui, - self.tr("Delete directory"), + self.tr("Delete Directory"), self.tr( "<p>The selected directory <b>{0}</b> could not be" " deleted.</p><p>Reason: {1}</p>" @@ -2434,6 +2750,7 @@ This method displays the new project dialog and initializes the project object with the data entered. """ + # assume remote project without VCS if connected to server from eric7.VCS.CommandOptionsDialog import VcsCommandOptionsDialog from .PropertiesDialog import PropertiesDialog @@ -2441,7 +2758,9 @@ if not self.checkDirty(): return - dlg = PropertiesDialog(self, new=True) + isRemote = self.__remoteServer.isServerConnected() + + dlg = PropertiesDialog(self, new=True, isRemote=isRemote) if dlg.exec() == QDialog.DialogCode.Accepted: self.closeProject() @@ -2458,9 +2777,13 @@ self.reloadAct.setEnabled(True) self.closeAct.setEnabled(True) self.saveasAct.setEnabled(True) + self.saveasRemoteAct.setEnabled( + self.__remoteServer.isServerConnected() + and FileSystemUtilities.isRemoteFileName(self.pfile) + ) self.actGrp2.setEnabled(True) self.propsAct.setEnabled(True) - self.userPropsAct.setEnabled(True) + self.userPropsAct.setEnabled(not isRemote) self.filetypesAct.setEnabled(True) self.lexersAct.setEnabled(True) self.sessActGrp.setEnabled(False) @@ -2470,14 +2793,30 @@ self.menuCheckAct.setEnabled(True) self.menuShowAct.setEnabled(True) self.menuDiagramAct.setEnabled(True) - self.menuApidocAct.setEnabled(True) + self.menuApidocAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.menuPackagersAct.setEnabled(True) - self.pluginGrp.setEnabled(self.__pdata["PROJECTTYPE"] in ["E7Plugin"]) + self.pluginGrp.setEnabled( + self.__pdata["PROJECTTYPE"] in ["E7Plugin"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.addLanguageAct.setEnabled(bool(self.__pdata["TRANSLATIONPATTERN"])) - self.makeGrp.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"]) - self.menuMakeAct.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"]) + self.makeGrp.setEnabled( + self.__pdata["MAKEPARAMS"]["MakeEnabled"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) + self.menuMakeAct.setEnabled( + self.__pdata["MAKEPARAMS"]["MakeEnabled"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.menuOtherToolsAct.setEnabled(True) - self.menuFormattingAct.setEnabled(True) + self.menuFormattingAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) + self.menuVcsAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.projectAboutToBeCreated.emit() @@ -2497,9 +2836,17 @@ } # create the project directory if it doesn't exist already - if not os.path.isdir(self.ppath): + ppathExists = ( + self.__remotefsInterface.isdir(self.ppath) + if isRemote + else os.path.isdir(self.ppath) + ) + if not ppathExists: try: - os.makedirs(self.ppath) + if isRemote: + self.__remotefsInterface.makedirs(self.ppath) + else: + os.makedirs(self.ppath) except OSError: EricMessageBox.critical( self.ui, @@ -2517,23 +2864,41 @@ # create an empty __init__.py file to make it a Python package # (only for Python and Python3) if self.__pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]: - fn = os.path.join(self.ppath, "__init__.py") - with open(fn, "w", encoding="utf-8"): - pass + if isRemote: + fn = self.__remotefsInterface.join(self.ppath, "__init__.py") + self.__remotefsInterface.writeFile(fn, b"") + else: + fn = os.path.join(self.ppath, "__init__.py") + with open(fn, "w", encoding="utf-8"): + pass self.appendFile(fn, True) # create an empty main script file, if a name was given if self.__pdata["MAINSCRIPT"]: - if not os.path.isabs(self.__pdata["MAINSCRIPT"]): - ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + if isRemote: + if not self.__remotefsInterface.isabs( + self.__pdata["MAINSCRIPT"] + ): + ms = self.__remotefsInterface.join( + self.ppath, self.__pdata["MAINSCRIPT"] + ) + else: + ms = self.__pdata["MAINSCRIPT"] + self.__remotefsInterface.makedirs( + self.__remotefsInterface.dirname(ms), exist_ok=True + ) + self.__remotefsInterface.writeFile(ms, b"") else: - ms = self.__pdata["MAINSCRIPT"] - os.makedirs(os.path.dirname(ms), exist_ok=True) - with open(ms, "w"): - pass + if not os.path.isabs(self.__pdata["MAINSCRIPT"]): + ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + else: + ms = self.__pdata["MAINSCRIPT"] + os.makedirs(os.path.dirname(ms), exist_ok=True) + with open(ms, "w"): + pass self.appendFile(ms, True) - if self.__pdata["MAKEPARAMS"]["MakeEnabled"]: + if self.__pdata["MAKEPARAMS"]["MakeEnabled"] and not isRemote: mf = self.__pdata["MAKEPARAMS"]["MakeFile"] if mf: if not os.path.isabs(mf): @@ -2545,15 +2910,34 @@ pass self.appendFile(mf) - tpd = os.path.join(self.ppath, self.translationsRoot) - if not self.translationsRoot.endswith(os.sep): - tpd = os.path.dirname(tpd) - if not os.path.isdir(tpd): - os.makedirs(tpd, exist_ok=True) - if self.__pdata["TRANSLATIONSBINPATH"]: - tpd = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"]) + if isRemote: + tpd = self.__remotefsInterface.join( + self.ppath, self.translationsRoot + ) + if not self.translationsRoot.endswith( + self.__remotefsInterface.separator() + ): + tpd = self.__remotefsInterface.dirname(tpd) + if not self.__remotefsInterface.isdir(tpd): + self.__remotefsInterface.makedirs(tpd, exist_ok=True) + if self.__pdata["TRANSLATIONSBINPATH"]: + tpd = self.__remotefsInterface.join( + self.ppath, self.__pdata["TRANSLATIONSBINPATH"] + ) + if not self.__remotefsInterface.isdir(tpd): + self.__remotefsInterface.makedirs(tpd, exist_ok=True) + else: + tpd = os.path.join(self.ppath, self.translationsRoot) + if not self.translationsRoot.endswith(os.sep): + tpd = os.path.dirname(tpd) if not os.path.isdir(tpd): os.makedirs(tpd, exist_ok=True) + if self.__pdata["TRANSLATIONSBINPATH"]: + tpd = os.path.join( + self.ppath, self.__pdata["TRANSLATIONSBINPATH"] + ) + if not os.path.isdir(tpd): + os.makedirs(tpd, exist_ok=True) # create management directory if not present self.createProjectManagementDir() @@ -2578,21 +2962,39 @@ return if self.__pdata["MAINSCRIPT"]: - if not os.path.isabs(self.__pdata["MAINSCRIPT"]): - ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + if isRemote: + if not self.__remotefsInterface.isabs( + self.__pdata["MAINSCRIPT"] + ): + ms = self.__remotefsInterface.join( + self.ppath, self.__pdata["MAINSCRIPT"] + ) + else: + ms = self.__pdata["MAINSCRIPT"] + msExists = self.__remotefsInterface.exists(ms) else: - ms = self.__pdata["MAINSCRIPT"] - if not os.path.exists(ms): + if not os.path.isabs(self.__pdata["MAINSCRIPT"]): + ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + else: + ms = self.__pdata["MAINSCRIPT"] + msExists = os.path.exists(ms) + if not msExists: try: - os.makedirs(os.path.dirname(ms), exist_ok=True) - with open(ms, "w"): - pass + if isRemote: + self.__remotefsInterface.makedirs( + self.__remotefsInterface.dirname(ms), exist_ok=True + ) + self.__remotefsInterface.writeFile(ms, b"") + else: + os.makedirs(os.path.dirname(ms), exist_ok=True) + with open(ms, "w"): + pass except OSError as err: EricMessageBox.critical( self.ui, self.tr("Create main script"), self.tr( - "<p>The mainscript <b>{0}</b> could not" + "<p>The main script <b>{0}</b> could not" " be created.<br/>Reason: {1}</p>" ).format(ms, str(err)), ) @@ -2600,7 +3002,7 @@ else: ms = "" - if self.__pdata["MAKEPARAMS"]["MakeEnabled"]: + if self.__pdata["MAKEPARAMS"]["MakeEnabled"] and not isRemote: mf = self.__pdata["MAKEPARAMS"]["MakeFile"] if mf: if not os.path.isabs(mf): @@ -2631,89 +3033,102 @@ yesDefault=True, ) if res: - self.newProjectAddFiles(ms) - addAllToVcs = res + self.newProjectAddFiles(ms, isRemote=isRemote) + addAllToVcs = res and not isRemote + # create an empty __init__.py file to make it a Python package # if none exists (only for Python and Python3) if self.__pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]: - fn = os.path.join(self.ppath, "__init__.py") - if not os.path.exists(fn): - with open(fn, "w", encoding="utf-8"): - pass - self.appendFile(fn, True) + if isRemote: + fn = self.__remotefsInterface.join(self.ppath, "__init__.py") + if not self.__remotefsInterface.exists(fn): + self.__remotefsInterface.writeFile(fn, b"") + self.appendFile(fn, True) + else: + fn = os.path.join(self.ppath, "__init__.py") + if not os.path.exists(fn): + with open(fn, "w", encoding="utf-8"): + pass + self.appendFile(fn, True) self.saveProject() # check, if the existing project directory is already under # VCS control - pluginManager = ericApp().getObject("PluginManager") - for indicator, vcsData in list( - pluginManager.getVcsSystemIndicators().items() - ): - if os.path.exists(os.path.join(self.ppath, indicator)): - if len(vcsData) > 1: - vcsList = [] - for _vcsSystemStr, vcsSystemDisplay in vcsData: - vcsList.append(vcsSystemDisplay) - res, vcs_ok = QInputDialog.getItem( - None, - self.tr("New Project"), - self.tr("Select Version Control System"), - vcsList, - 0, - False, - ) - if vcs_ok: - for vcsSystemStr, vcsSystemDisplay in vcsData: - if res == vcsSystemDisplay: - vcsSystem = vcsSystemStr - break + if not isRemote: + pluginManager = ericApp().getObject("PluginManager") + for indicator, vcsData in list( + pluginManager.getVcsSystemIndicators().items() + ): + if os.path.exists(os.path.join(self.ppath, indicator)): + if len(vcsData) > 1: + vcsList = [] + for _vcsSystemStr, vcsSystemDisplay in vcsData: + vcsList.append(vcsSystemDisplay) + res, vcs_ok = QInputDialog.getItem( + None, + self.tr("New Project"), + self.tr("Select Version Control System"), + vcsList, + 0, + False, + ) + if vcs_ok: + for vcsSystemStr, vcsSystemDisplay in vcsData: + if res == vcsSystemDisplay: + vcsSystem = vcsSystemStr + break + else: + vcsSystem = "None" else: vcsSystem = "None" else: - vcsSystem = "None" - else: - vcsSystem = vcsData[0][1] - self.__pdata["VCS"] = vcsSystem - self.vcs = self.initVCS() - self.setDirty(True) - if self.vcs is not None: - # edit VCS command options - if self.vcs.vcsSupportCommandOptions(): - vcores = EricMessageBox.yesNo( - self.ui, - self.tr("New Project"), - self.tr( - """Would you like to edit the VCS""" - """ command options?""" - ), - ) + vcsSystem = vcsData[0][1] + self.__pdata["VCS"] = vcsSystem + self.vcs = self.initVCS() + self.setDirty(True) + if self.vcs is not None: + # edit VCS command options + if self.vcs.vcsSupportCommandOptions(): + vcores = EricMessageBox.yesNo( + self.ui, + self.tr("New Project"), + self.tr( + """Would you like to edit the VCS""" + """ command options?""" + ), + ) + else: + vcores = False + if vcores: + codlg = VcsCommandOptionsDialog(self.vcs) + if codlg.exec() == QDialog.DialogCode.Accepted: + self.vcs.vcsSetOptions(codlg.getOptions()) + # add project file to repository + if res == 0: + apres = EricMessageBox.yesNo( + self.ui, + self.tr("New Project"), + self.tr( + "Shall the project file be added" + " to the repository?" + ), + yesDefault=True, + ) + if apres: + self.saveProject() + self.vcs.vcsAdd(self.pfile) else: - vcores = False - if vcores: - codlg = VcsCommandOptionsDialog(self.vcs) - if codlg.exec() == QDialog.DialogCode.Accepted: - self.vcs.vcsSetOptions(codlg.getOptions()) - # add project file to repository - if res == 0: - apres = EricMessageBox.yesNo( - self.ui, - self.tr("New project"), - self.tr( - "Shall the project file be added" - " to the repository?" - ), - yesDefault=True, - ) - if apres: - self.saveProject() - self.vcs.vcsAdd(self.pfile) - else: - self.__pdata["VCS"] = "None" - self.saveProject() - break + self.__pdata["VCS"] = "None" + self.saveProject() + break # put the project under VCS control - if self.vcs is None and self.vcsSoftwareAvailable() and self.vcsRequested: + if ( + not isRemote + and self.vcs is None + and self.vcsSoftwareAvailable() + and self.vcsRequested + ): vcsSystemsDict = ( ericApp() .getObject("PluginManager") @@ -2781,27 +3196,42 @@ # set the auto save flag to its supposed value Preferences.setProject("AutoSaveProject", autoSaveProject) - if self.__pdata["EMBEDDED_VENV"]: + if self.__pdata[ + "EMBEDDED_VENV" + ] and not FileSystemUtilities.isRemoteFileName(self.ppath): self.__createEmbeddedEnvironment() - self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"]) + self.menuEnvironmentAct.setEnabled( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.projectOpenedHooks.emit() self.projectOpened.emit() # open the main script if self.__pdata["MAINSCRIPT"]: - if not os.path.isabs(self.__pdata["MAINSCRIPT"]): - ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + if isRemote: + if not self.__remotefsInterface.isabs(self.__pdata["MAINSCRIPT"]): + ms = self.__remotefsInterface.join( + self.ppath, self.__pdata["MAINSCRIPT"] + ) + else: + ms = self.__pdata["MAINSCRIPT"] else: - ms = self.__pdata["MAINSCRIPT"] + if not os.path.isabs(self.__pdata["MAINSCRIPT"]): + ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + else: + ms = self.__pdata["MAINSCRIPT"] self.sourceFile.emit(ms) - def newProjectAddFiles(self, mainscript): + def newProjectAddFiles(self, mainscript, isRemote=False): """ Public method to add files to a new project. @param mainscript name of the mainscript @type str + @param isRemote flag indicating a remote project (defaults to False) + @type bool (optional) """ # Show the file type associations for the user to change self.__showFiletypeAssociations() @@ -2810,54 +3240,113 @@ if self.__pdata["EMBEDDED_VENV"]: environmentPath = self.__findEmbeddedEnvironment() if environmentPath: - # there is already an embeded venv; ignore thie whenn adding files - ignoreList = [os.path.split(environmentPath)[-1]] + # there is already an embedded venv; ignore this when adding files + ignoreList = ( + [self.__remotefsInterface.split(environmentPath)[-1]] + if isRemote + else [os.path.split(environmentPath)[-1]] + ) with EricOverrideCursor(): # search the project directory for files with known extensions for filespec in self.__pdata["FILETYPES"]: - files = FileSystemUtilities.direntries( - self.ppath, - filesonly=True, - pattern=filespec, - ignore=ignoreList, + files = ( + self.__remotefsInterface.direntries( + self.ppath, + filesonly=True, + pattern=filespec, + ignore=ignoreList, + ) + if isRemote + else FileSystemUtilities.direntries( + self.ppath, + filesonly=True, + pattern=filespec, + ignore=ignoreList, + ) ) for file in files: self.appendFile(file) # special handling for translation files if self.translationsRoot: - tpd = os.path.join(self.ppath, self.translationsRoot) - if not self.translationsRoot.endswith(os.sep): - tpd = os.path.dirname(tpd) + if isRemote: + tpd = self.__remotefsInterface.join( + self.ppath, self.translationsRoot + ) + if not self.translationsRoot.endswith(os.sep): + tpd = self.__remotefsInterface.dirname(tpd) + else: + tpd = os.path.join(self.ppath, self.translationsRoot) + if not self.translationsRoot.endswith(os.sep): + tpd = os.path.dirname(tpd) else: tpd = self.ppath tslist = [] if self.__pdata["TRANSLATIONPATTERN"]: - pattern = os.path.basename(self.__pdata["TRANSLATIONPATTERN"]) + pattern = ( + self.__remotefsInterface.basename( + self.__pdata["TRANSLATIONPATTERN"] + ) + if isRemote + else os.path.basename(self.__pdata["TRANSLATIONPATTERN"]) + ) if "%language%" in pattern: pattern = pattern.replace("%language%", "*") else: tpd = self.__pdata["TRANSLATIONPATTERN"].split("%language%")[0] else: pattern = "*.ts" - tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern)) + tslist.extend( + self.__remotefsInterface.direntries(tpd, True, pattern) + if isRemote + else FileSystemUtilities.direntries(tpd, True, pattern) + ) + pattern = self.__binaryTranslationFile(pattern) if pattern: - tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern)) + tslist.extend( + self.__remotefsInterface.direntries(tpd, True, pattern) + if isRemote + else FileSystemUtilities.direntries(tpd, True, pattern) + ) if tslist: - if "_" in os.path.basename(tslist[0]): - # the first entry determines the mainscript name - mainscriptname = ( - os.path.splitext(mainscript)[0] - or os.path.basename(tslist[0]).split("_")[0] - ) - self.__pdata["TRANSLATIONPATTERN"] = os.path.join( - os.path.dirname(tslist[0]), - "{0}_%language%{1}".format( - os.path.basename(tslist[0]).split("_")[0], - os.path.splitext(tslist[0])[1], - ), - ) + hasUnderscore = ( + "_" in self.__remotefsInterface.basename(tslist[0]) + if isRemote + else "_" in os.path.basename(tslist[0]) + ) + if hasUnderscore: + # the first entry determines the main script name + if isRemote: + mainscriptname = ( + self.__remotefsInterface.splitext(mainscript)[0] + or self.__remotefsInterface.basename(tslist[0]).split("_")[ + 0 + ] + ) + self.__pdata["TRANSLATIONPATTERN"] = ( + self.__remotefsInterface.join( + self.__remotefsInterface.dirname(tslist[0]), + "{0}_%language%{1}".format( + self.__remotefsInterface.basename(tslist[0]).split( + "_" + )[0], + self.__remotefsInterface.splitext(tslist[0])[1], + ), + ) + ) + else: + mainscriptname = ( + os.path.splitext(mainscript)[0] + or os.path.basename(tslist[0]).split("_")[0] + ) + self.__pdata["TRANSLATIONPATTERN"] = os.path.join( + os.path.dirname(tslist[0]), + "{0}_%language%{1}".format( + os.path.basename(tslist[0]).split("_")[0], + os.path.splitext(tslist[0])[1], + ), + ) else: mainscriptname = "" pattern, ok = QInputDialog.getText( @@ -2885,12 +3374,20 @@ self.__pdata["TRANSLATIONS"].append(ts) self.projectFileAdded.emit(ts, "TRANSLATIONS") if self.__pdata["TRANSLATIONSBINPATH"]: - tpd = os.path.join( - self.ppath, self.__pdata["TRANSLATIONSBINPATH"] - ) - pattern = os.path.basename( - self.__pdata["TRANSLATIONPATTERN"] - ).replace("%language%", "*") + if isRemote: + tpd = self.__remotefsInterface.join( + self.ppath, self.__pdata["TRANSLATIONSBINPATH"] + ) + pattern = self.__remotefsInterface.basename( + self.__pdata["TRANSLATIONPATTERN"] + ).replace("%language%", "*") + else: + tpd = os.path.join( + self.ppath, self.__pdata["TRANSLATIONSBINPATH"] + ) + pattern = os.path.basename( + self.__pdata["TRANSLATIONPATTERN"] + ).replace("%language%", "*") pattern = self.__binaryTranslationFile(pattern) qmlist = FileSystemUtilities.direntries(tpd, True, pattern) for qm in qmlist: @@ -2909,30 +3406,55 @@ """ from .PropertiesDialog import PropertiesDialog - dlg = PropertiesDialog(self, new=False) + isRemote = FileSystemUtilities.isRemoteFileName(self.ppath) + dlg = PropertiesDialog(self, new=False, isRemote=isRemote) if dlg.exec() == QDialog.DialogCode.Accepted: fileTypesDict = copy.copy(self.__pdata["FILETYPES"]) dlg.storeData() self.setDirty(True) if self.__pdata["MAINSCRIPT"]: - if not os.path.isabs(self.__pdata["MAINSCRIPT"]): - ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + if isRemote: + if not self.__remotefsInterface.isabs(self.__pdata["MAINSCRIPT"]): + ms = self.__remotefsInterface.join( + self.ppath, self.__pdata["MAINSCRIPT"] + ) + else: + ms = self.__pdata["MAINSCRIPT"] + if self.__remotefsInterface.exists(ms): + self.appendFile(ms) else: - ms = self.__pdata["MAINSCRIPT"] - if os.path.exists(ms): - self.appendFile(ms) + if not os.path.isabs(self.__pdata["MAINSCRIPT"]): + ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + else: + ms = self.__pdata["MAINSCRIPT"] + if os.path.exists(ms): + self.appendFile(ms) if self.__pdata["MAKEPARAMS"]["MakeEnabled"]: mf = self.__pdata["MAKEPARAMS"]["MakeFile"] - if mf: - if not os.path.isabs(mf): - mf = os.path.join(self.ppath, mf) + if isRemote: + if mf: + if not self.__remotefsInterface.isabs(mf): + mf = self.__remotefsInterface.join(self.ppath, mf) + else: + mf = self.__remotefsInterface.join( + self.ppath, Project.DefaultMakefile + ) + exists = self.__remotefsInterface.exists(mf) else: - mf = os.path.join(self.ppath, Project.DefaultMakefile) - if not os.path.exists(mf): + if mf: + if not os.path.isabs(mf): + mf = os.path.join(self.ppath, mf) + else: + mf = os.path.join(self.ppath, Project.DefaultMakefile) + exists = os.path.exists(mf) + if not exists: try: - with open(mf, "w"): - pass + if isRemote: + self.__remotefsInterface.writeFile(mf, b"") + else: + with open(mf, "w"): + pass except OSError as err: EricMessageBox.critical( self.ui, @@ -2945,20 +3467,38 @@ self.appendFile(mf) if self.translationsRoot: - tp = os.path.join(self.ppath, self.translationsRoot) - if not self.translationsRoot.endswith(os.sep): - tp = os.path.dirname(tp) + if isRemote: + tp = self.__remotefsInterface.join( + self.ppath, self.translationsRoot + ) + if not self.translationsRoot.endswith( + self.__remotefsInterface.separator() + ): + tp = self.__remotefsInterface.dirname(tp) + if not self.__remotefsInterface.isdir(tp): + self.__remotefsInterface.makedirs(tp) + else: + tp = os.path.join(self.ppath, self.translationsRoot) + if not self.translationsRoot.endswith(os.sep): + tp = os.path.dirname(tp) + if not os.path.isdir(tp): + os.makedirs(tp) else: tp = self.ppath - if not os.path.isdir(tp): - os.makedirs(tp) if tp != self.ppath and tp not in self.subdirs: self.subdirs.append(tp) if self.__pdata["TRANSLATIONSBINPATH"]: - tp = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"]) - if not os.path.isdir(tp): - os.makedirs(tp) + if isRemote: + tp = self.__remotefsInterface.join( + self.ppath, self.__pdata["TRANSLATIONSBINPATH"] + ) + if not self.__remotefsInterface.isdir(tp): + self.__remotefsInterface.makedirs(tp) + else: + tp = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"]) + if not os.path.isdir(tp): + os.makedirs(tp) if tp != self.ppath and tp not in self.subdirs: self.subdirs.append(tp) @@ -2970,7 +3510,11 @@ if self.__pdata["FILETYPES"] != fileTypesDict: self.__reorganizeFiles() - if self.__pdata["EMBEDDED_VENV"] and not self.__findEmbeddedEnvironment(): + if ( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + and not self.__findEmbeddedEnvironment() + ): self.__createEmbeddedEnvironment() def __showUserProperties(self): @@ -3110,7 +3654,7 @@ if fn is None: fn = EricFileDialog.getOpenFileName( self.parent(), - self.tr("Open project"), + self.tr("Open Project"), Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir(), self.tr("Project Files (*.epj)"), ) @@ -3145,53 +3689,65 @@ with EricOverrideCursor(): oldState = self.isDirty() self.vcs = self.initVCS() - if self.vcs is None and self.isDirty() == oldState: - # check, if project is version controlled - pluginManager = ericApp().getObject("PluginManager") - for ( - indicator, - vcsData, - ) in pluginManager.getVcsSystemIndicators().items(): - if os.path.exists(os.path.join(self.ppath, indicator)): - if len(vcsData) > 1: - vcsList = [] - for _vcsSystemStr, vcsSystemDisplay in vcsData: - vcsList.append(vcsSystemDisplay) - with EricOverridenCursor(): - res, vcs_ok = QInputDialog.getItem( - None, - self.tr("New Project"), - self.tr("Select Version Control System"), - vcsList, - 0, - False, - ) - if vcs_ok: - for vcsSystemStr, vcsSystemDisplay in vcsData: - if res == vcsSystemDisplay: - vcsSystem = vcsSystemStr - break + if not FileSystemUtilities.isRemoteFileName(self.ppath): + if self.vcs is None and self.isDirty() == oldState: + # check, if project is version controlled + pluginManager = ericApp().getObject("PluginManager") + for ( + indicator, + vcsData, + ) in pluginManager.getVcsSystemIndicators().items(): + if os.path.exists(os.path.join(self.ppath, indicator)): + if len(vcsData) > 1: + vcsList = [] + for _vcsSystemStr, vcsSystemDisplay in vcsData: + vcsList.append(vcsSystemDisplay) + with EricOverridenCursor(): + res, vcs_ok = QInputDialog.getItem( + None, + self.tr("New Project"), + self.tr( + "Select Version Control System" + ), + vcsList, + 0, + False, + ) + if vcs_ok: + for ( + vcsSystemStr, + vcsSystemDisplay, + ) in vcsData: + if res == vcsSystemDisplay: + vcsSystem = vcsSystemStr + break + else: + vcsSystem = "None" else: vcsSystem = "None" else: - vcsSystem = "None" - else: - vcsSystem = vcsData[0][0] - self.__pdata["VCS"] = vcsSystem - self.vcs = self.initVCS() - self.setDirty(True) - if self.vcs is not None and ( - self.vcs.vcsRegisteredState(self.ppath) - != VersionControlState.Controlled - ): - self.__pdata["VCS"] = "None" - self.vcs = self.initVCS() + vcsSystem = vcsData[0][0] + self.__pdata["VCS"] = vcsSystem + self.vcs = self.initVCS() + self.setDirty(True) + if self.vcs is not None and ( + self.vcs.vcsRegisteredState(self.ppath) + != VersionControlState.Controlled + ): + self.__pdata["VCS"] = "None" + self.vcs = self.initVCS() self.reloadAct.setEnabled(True) self.closeAct.setEnabled(True) self.saveasAct.setEnabled(True) + self.saveasRemoteAct.setEnabled( + self.__remoteServer.isServerConnected() + and FileSystemUtilities.isRemoteFileName(self.pfile) + ) self.actGrp2.setEnabled(True) self.propsAct.setEnabled(True) - self.userPropsAct.setEnabled(True) + self.userPropsAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.pfile) + ) self.filetypesAct.setEnabled(True) self.lexersAct.setEnabled(True) self.sessActGrp.setEnabled(True) @@ -3201,20 +3757,34 @@ self.menuCheckAct.setEnabled(True) self.menuShowAct.setEnabled(True) self.menuDiagramAct.setEnabled(True) - self.menuApidocAct.setEnabled(True) - self.menuPackagersAct.setEnabled(True) + self.menuApidocAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) + self.menuPackagersAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.pluginGrp.setEnabled( self.__pdata["PROJECTTYPE"] in ["E7Plugin"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) ) self.addLanguageAct.setEnabled( bool(self.__pdata["TRANSLATIONPATTERN"]) ) - self.makeGrp.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"]) + self.makeGrp.setEnabled( + self.__pdata["MAKEPARAMS"]["MakeEnabled"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.menuMakeAct.setEnabled( self.__pdata["MAKEPARAMS"]["MakeEnabled"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) ) self.menuOtherToolsAct.setEnabled(True) - self.menuFormattingAct.setEnabled(True) + self.menuFormattingAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) + self.menuVcsAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) # open a project debugger properties file being quiet # about errors @@ -3223,7 +3793,9 @@ self.__model.projectOpened() - if self.__pdata["EMBEDDED_VENV"]: + if self.__pdata[ + "EMBEDDED_VENV" + ] and not FileSystemUtilities.isRemoteFileName(self.ppath): envPath = self.__findEmbeddedEnvironment() if bool(envPath): self.__loadEnvironmentConfiguration() @@ -3235,7 +3807,10 @@ self.__configureEnvironment(envPath) else: self.__createEmbeddedEnvironment() - self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"]) + self.menuEnvironmentAct.setEnabled( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.projectOpenedHooks.emit() self.projectOpened.emit() @@ -3266,7 +3841,8 @@ self.__readSession(quiet=True) # start the VCS monitor thread - self.__vcsConnectStatusMonitor() + if not FileSystemUtilities.isRemoteFileName(self.ppath): + self.__vcsConnectStatusMonitor() else: self.__initData() # delete all invalid data read @@ -3328,7 +3904,7 @@ if fpath.exists(): res = EricMessageBox.yesNo( self.ui, - self.tr("Save File"), + self.tr("Save Project"), self.tr( """<p>The file <b>{0}</b> already exists.""" """ Overwrite it?</p>""" @@ -3338,7 +3914,6 @@ if not res: return False - self.name = fpath.stem ok = self.__writeProject(str(fpath)) if ok: @@ -3451,7 +4026,10 @@ return False # stop the VCS monitor thread - if self.vcs is not None: + if ( + not FileSystemUtilities.isRemoteFileName(self.ppath) + and self.vcs is not None + ): self.vcs.stopStatusMonitor() # now save the tasks @@ -3460,8 +4038,11 @@ self.ui.taskViewer.clearProjectTasks() self.ui.taskViewer.setProjectOpen(False) + if FileSystemUtilities.isRemoteFileName(self.ppath): + self.__remotefsInterface.removeFromFsCache(self.ppath) + # now shutdown the vcs interface - if self.vcs: + if not FileSystemUtilities.isRemoteFileName(self.ppath) and self.vcs: self.vcs.vcsShutdown() self.vcs.deleteLater() self.vcs = None @@ -3473,6 +4054,7 @@ self.__initData() self.reloadAct.setEnabled(False) self.closeAct.setEnabled(False) + self.saveasRemoteAct.setEnabled(False) self.saveasAct.setEnabled(False) self.saveAct.setEnabled(False) self.actGrp2.setEnabled(False) @@ -3526,7 +4108,7 @@ if reportSyntaxErrors and filesWithSyntaxErrors > 0: EricMessageBox.critical( self.ui, - self.tr("Syntax errors detected"), + self.tr("Syntax Errors Detected"), self.tr( """The project contains %n file(s) with syntax errors.""", "", @@ -3562,7 +4144,7 @@ if reportSyntaxErrors and filesWithSyntaxErrors > 0: EricMessageBox.critical( self.ui, - self.tr("Syntax errors detected"), + self.tr("Syntax Errors Detected"), self.tr( """The project contains %n file(s) with syntax errors.""", "", @@ -3587,7 +4169,12 @@ """ if self.__pdata["MAINSCRIPT"]: if normalized: - return os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + if FileSystemUtilities.isRemoteFileName(self.ppath): + return self.__remotefsInterface.join( + self.ppath, self.__pdata["MAINSCRIPT"] + ) + else: + return os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) else: return self.__pdata["MAINSCRIPT"] else: @@ -3620,7 +4207,13 @@ raise ValueError("Given file type has incorrect value.") if normalized: - return [os.path.join(self.ppath, fn) for fn in self.__pdata[fileType]] + if FileSystemUtilities.isRemoteFileName(self.ppath): + return [ + self.__remotefsInterface.join(self.ppath, fn) + for fn in self.__pdata[fileType] + ] + else: + return [os.path.join(self.ppath, fn) for fn in self.__pdata[fileType]] else: return self.__pdata[fileType] @@ -3715,16 +4308,18 @@ @rtype tuple of (str, str) """ pwl = "" - if self.__pdata["SPELLWORDS"]: - pwl = os.path.join(self.ppath, self.__pdata["SPELLWORDS"]) - if not os.path.isfile(pwl): - pwl = "" - pel = "" - if self.__pdata["SPELLEXCLUDES"]: - pel = os.path.join(self.ppath, self.__pdata["SPELLEXCLUDES"]) - if not os.path.isfile(pel): - pel = "" + + if not FileSystemUtilities.isRemoteFileName(self.ppath): + if self.__pdata["SPELLWORDS"]: + pwl = os.path.join(self.ppath, self.__pdata["SPELLWORDS"]) + if not os.path.isfile(pwl): + pwl = "" + + if self.__pdata["SPELLEXCLUDES"]: + pel = os.path.join(self.ppath, self.__pdata["SPELLEXCLUDES"]) + if not os.path.isfile(pel): + pel = "" return (pwl, pel) @@ -3750,25 +4345,30 @@ """ return self.ppath - def startswithProjectPath(self, path): + def startswithProjectPath(self, checkpath): """ Public method to check, if a path starts with the project path. - @param path path to be checked + @param checkpath path to be checked @type str @return flag indicating that the path starts with the project path @rtype bool """ - return bool(self.ppath) and ( - path == self.ppath - or FileSystemUtilities.normcasepath( - FileSystemUtilities.toNativeSeparators(path) - ).startswith( - FileSystemUtilities.normcasepath( - FileSystemUtilities.toNativeSeparators(self.ppath + "/") + if FileSystemUtilities.isRemoteFileName(self.ppath): + return checkpath == self.ppath or checkpath.startswith( + self.ppath + self.__remotefsInterface.separator() + ) + else: + return bool(self.ppath) and ( + checkpath == self.ppath + or FileSystemUtilities.normcasepath( + FileSystemUtilities.toNativeSeparators(checkpath) + ).startswith( + FileSystemUtilities.normcasepath( + FileSystemUtilities.toNativeSeparators(self.ppath + "/") + ) ) ) - ) def getProjectFile(self): """ @@ -3789,8 +4389,7 @@ @rtype str """ if self.pfile: - name = os.path.splitext(self.pfile)[0] - return os.path.basename(name) + return self.name else: return "" @@ -3801,7 +4400,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): """ @@ -3811,8 +4413,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): """ @@ -3823,37 +4427,52 @@ """ return self.__pdata["HASH"] - def getRelativePath(self, path): + def getRelativePath(self, fullpath): """ Public method to convert a file path to a project relative file path. - @param path file or directory name to convert + @param fullpath file or directory name to convert @type str @return project relative path or unchanged path, if path doesn't belong to the project @rtype str """ - if path is None: + if fullpath is None: return "" try: - return str(pathlib.Path(path).relative_to(self.ppath)) + if FileSystemUtilities.isRemoteFileName(self.ppath): + if self.__remotefsInterface.separator() == "\\": + return str( + pathlib.PureWindowsPath(fullpath).relative_to(self.ppath) + ) + else: + return str(pathlib.PurePosixPath(fullpath).relative_to(self.ppath)) + else: + return str(pathlib.PurePath(fullpath).relative_to(self.ppath)) except ValueError: - return path - - def getRelativeUniversalPath(self, path): + return fullpath + + def getRelativeUniversalPath(self, fullpath): """ Public method to convert a file path to a project relative file path with universal separators. - @param path file or directory name to convert + @param fullpath file or directory name to convert @type str @return project relative path or unchanged path, if path doesn't belong to the project @rtype str """ - return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path)) + if FileSystemUtilities.isRemoteFileName(self.ppath): + return self.__remotefsInterface.fromNativeSeparators( + self.getRelativePath(fullpath) + ) + else: + return FileSystemUtilities.fromNativeSeparators( + self.getRelativePath(fullpath) + ) def getAbsolutePath(self, fn): """ @@ -3865,8 +4484,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): @@ -3879,8 +4501,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): @@ -3962,7 +4591,11 @@ """ venvName = ( self.__venvConfiguration["name"] - if self.__pdata["EMBEDDED_VENV"] and bool(self.__venvConfiguration["name"]) + if ( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + and bool(self.__venvConfiguration["name"]) + ) else self.getDebugProperty("VIRTUALENV") ) if ( @@ -3981,7 +4614,9 @@ @return path name of the embedded virtual environment @rtype str """ - if self.__pdata["EMBEDDED_VENV"]: + if self.__pdata["EMBEDDED_VENV"] and not FileSystemUtilities.isRemoteFileName( + self.ppath + ): return self.__findEmbeddedEnvironment() else: return "" @@ -3999,7 +4634,10 @@ """ interpreter = ( self.__venvConfiguration["interpreter"] - if self.__pdata["EMBEDDED_VENV"] + if ( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) else "" ) if not interpreter: @@ -4070,7 +4708,11 @@ @return flag indicating membership @rtype bool """ - newfn = os.path.abspath(fn) + newfn = ( + self.__remotefsInterface.abspath(fn) + if FileSystemUtilities.isRemoteFileName(self.ppath) + else os.path.abspath(fn) + ) newfn = self.getRelativePath(newfn) return any( newfn in self.__pdata[category] for category in self.getFileCategories() @@ -4103,7 +4745,11 @@ @return flag indicating membership @rtype bool """ - newfn = os.path.abspath(fn) + newfn = ( + self.__remotefsInterface.abspath(fn) + if FileSystemUtilities.isRemoteFileName(fn) + else os.path.abspath(fn) + ) newfn = self.getRelativePath(newfn) if newfn in self.__pdata[group] or ( group == "OTHERS" @@ -4111,7 +4757,10 @@ ): return True - if OSUtilities.isWindowsPlatform(): + if ( + OSUtilities.isWindowsPlatform() + or self.__remotefsInterface.separator() == "\\" + ): # try the above case-insensitive newfn = newfn.lower() if any(entry.lower() == newfn for entry in self.__pdata[group]): @@ -4189,6 +4838,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"), @@ -4256,6 +4924,28 @@ self.saveasAct.triggered.connect(self.saveProjectAs) self.actions.append(self.saveasAct) + self.saveasRemoteAct = EricAction( + self.tr("Save project as (Remote)"), + EricPixmapCache.getIcon("projectSaveAs-remote"), + self.tr("Save as (Remote)..."), + 0, + 0, + self, + "project_save_as_remote", + ) + self.saveasRemoteAct.setStatusTip( + self.tr("Save the current project to a new remote file") + ) + self.saveasRemoteAct.setWhatsThis( + self.tr( + """<b>Save as (Remote)</b>""" + """<p>This saves the current project to a new remote file.</p>""" + ) + ) + self.saveasRemoteAct.triggered.connect(self.__saveRemoteProjectAs) + self.actions.append(self.saveasRemoteAct) + self.saveasRemoteAct.setEnabled(False) # server is not connected initially + ################################################################### ## Project management actions ################################################################### @@ -5248,6 +5938,7 @@ """ menu = QMenu(self.tr("&Project"), self.parent()) self.recentMenu = QMenu(self.tr("Open &Recent Projects"), menu) + self.recentMenu.setIcon(EricPixmapCache.getIcon("projectOpenRecent")) self.sessionMenu = QMenu(self.tr("Session"), menu) self.debuggerMenu = QMenu(self.tr("Debugger"), menu) self.environmentMenu = QMenu(self.tr("Embedded Environment"), menu) @@ -5363,6 +6054,7 @@ menu.addSeparator() menu.addAction(self.saveAct) menu.addAction(self.saveasAct) + menu.addAction(self.saveasRemoteAct) menu.addSeparator() menu.addActions(self.actGrp2.actions()) menu.addSeparator() @@ -5379,7 +6071,7 @@ # build the project tools menu toolsMenu.setTearOffEnabled(True) toolsMenu.addSeparator() - toolsMenu.addMenu(self.vcsMenu) + self.menuVcsAct = toolsMenu.addMenu(self.vcsMenu) toolsMenu.addSeparator() self.menuCheckAct = toolsMenu.addMenu(self.checksMenu) toolsMenu.addSeparator() @@ -5435,6 +6127,7 @@ tb.addSeparator() tb.addAction(self.saveAct) tb.addAction(self.saveasAct) + tb.addAction(self.saveasRemoteAct) toolbarManager.addToolBar(tb, tb.windowTitle()) toolbarManager.addAction(self.addFilesAct, tb.windowTitle()) @@ -5452,7 +6145,10 @@ Private method to set up the project menu. """ self.menuRecentAct.setEnabled(len(self.recent) > 0) - self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"]) + self.menuEnvironmentAct.setEnabled( + self.__pdata["EMBEDDED_VENV"] + and not FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.showMenu.emit("Main", self.__menus["Main"]) @@ -5462,7 +6158,9 @@ with the central store. """ for recent in self.recent[:]: - if FileSystemUtilities.samepath(self.pfile, recent): + if ( + FileSystemUtilities.isRemoteFileName(recent) and recent == self.pfile + ) or FileSystemUtilities.samepath(self.pfile, recent): self.recent.remove(recent) self.recent.insert(0, self.pfile) maxRecent = Preferences.getProject("RecentNumber") @@ -5482,11 +6180,26 @@ formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}" act = self.recentMenu.addAction( formatStr.format( - idx, FileSystemUtilities.compactPath(rp, self.ui.maxMenuFilePathLen) + idx, + ( + self.__remotefsInterface.compactPath( + rp, self.ui.maxMenuFilePathLen + ) + if FileSystemUtilities.isRemoteFileName(rp) + else FileSystemUtilities.compactPath( + rp, self.ui.maxMenuFilePathLen + ) + ), ) ) act.setData(rp) - act.setEnabled(pathlib.Path(rp).exists()) + if FileSystemUtilities.isRemoteFileName(rp): + act.setEnabled( + self.__remoteServer.isServerConnected + and self.__remotefsInterface.exists(rp) + ) + else: + act.setEnabled(pathlib.Path(rp).exists()) self.recentMenu.addSeparator() self.recentMenu.addAction(self.tr("&Clear"), self.clearRecent) @@ -5537,7 +6250,9 @@ self.__findProjectFileDialog.sourceFile.connect(self.sourceFile) self.__findProjectFileDialog.designerFile.connect(self.designerFile) self.__findProjectFileDialog.linguistFile.connect(self.linguistFile) - self.__findProjectFileDialog.show() + self.__findProjectFileDialog.show( + FileSystemUtilities.isRemoteFileName(self.ppath) + ) self.__findProjectFileDialog.raise_() self.__findProjectFileDialog.activateWindow() @@ -5563,6 +6278,8 @@ recursiveSearch = Preferences.getProject("SearchNewFilesRecursively") newFiles = [] + isRemote = FileSystemUtilities.isRemoteFileName(self.ppath) + ignore_patterns = [ pattern for pattern, filetype in self.__pdata["FILETYPES"].items() @@ -5577,9 +6294,17 @@ ): continue - curpath = os.path.join(self.ppath, directory) + curpath = ( + self.__remotefsInterface.join(self.ppath, directory) + if isRemote + else os.path.join(self.ppath, directory) + ) try: - newSources = os.listdir(curpath) + newSources = ( + [e["name"] for e in self.__remotefsInterface.listdir(curpath)[2]] + if isRemote + else os.listdir(curpath) + ) except OSError: newSources = [] pattern = ( @@ -5596,11 +6321,24 @@ # set fn to project relative name # then reset ns to fully qualified name for insertion, # possibly. - fn = os.path.join(directory, ns) if directory else ns - ns = os.path.abspath(os.path.join(curpath, ns)) + if isRemote: + fn = ( + self.__remotefsInterface.join(directory, ns) + if directory + else ns + ) + ns = self.__remotefsInterface.abspath( + self.__remotefsInterface.join(curpath, ns) + ) + + isdir_ns = self.__remotefsInterface.isdir(ns) + else: + fn = os.path.join(directory, ns) if directory else ns + ns = os.path.abspath(os.path.join(curpath, ns)) + isdir_ns = os.path.isdir(ns) # do not bother with dirs here... - if os.path.isdir(ns): + if isdir_ns: if recursiveSearch: d = self.getRelativePath(ns) if d not in dirs: @@ -5608,7 +6346,11 @@ continue filetype = "" - bfn = os.path.basename(fn) + bfn = ( + self.__remotefsInterface.basename(fn) + if isRemote + else os.path.basename(fn) + ) # check against ignore patterns first (see issue 553) if any( @@ -5626,8 +6368,9 @@ filetype in self.getFileCategories() and ( fn not in self.__pdata[filetype] - and ( + or ( filetype == "OTHERS" + and fn not in self.__pdata[filetype] and os.path.dirname(fn) not in self.__pdata["OTHERS"] ) ) @@ -5769,6 +6512,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"] @@ -5945,11 +6691,19 @@ """ from eric7.DataViews.CodeMetricsDialog import CodeMetricsDialog - files = [ - os.path.join(self.ppath, file) - for file in self.__pdata["SOURCES"] - if file.endswith(".py") - ] + files = ( + [ + self.__remotefsInterface.join(self.ppath, file) + for file in self.__pdata["SOURCES"] + if file.endswith(".py") + ] + if FileSystemUtilities.isRemoteFileName(self.ppath) + else [ + os.path.join(self.ppath, file) + for file in self.__pdata["SOURCES"] + if file.endswith(".py") + ] + ) self.codemetrics = CodeMetricsDialog() self.codemetrics.show() self.codemetrics.prepare(files) @@ -5991,11 +6745,19 @@ else: return - files = [ - os.path.join(self.ppath, file) - for file in self.__pdata["SOURCES"] - if os.path.splitext(file)[1].startswith(".py") - ] + files = ( + [ + self.__remotefsInterface.join(self.ppath, file) + for file in self.__pdata["SOURCES"] + if self.__remotefsInterface.splitext(file)[1].startswith(".py") + ] + if FileSystemUtilities.isRemoteFileName(self.ppath) + else [ + os.path.join(self.ppath, file) + for file in self.__pdata["SOURCES"] + if os.path.splitext(file)[1].startswith(".py") + ] + ) self.codecoverage = PyCoverageDialog() self.codecoverage.show() self.codecoverage.start(fn, files) @@ -6410,8 +7172,8 @@ os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) ) if archiveVersion and ( - Globals.versionToTuple(version) - < Globals.versionToTuple(archiveVersion) + EricUtilities.versionToTuple(version) + < EricUtilities.versionToTuple(archiveVersion) ): version = archiveVersion except OSError as why: @@ -6606,7 +7368,9 @@ @return flag indicating enabled make support @rtype bool """ - return self.__pdata["MAKEPARAMS"]["MakeEnabled"] + return self.__pdata["MAKEPARAMS"][ + "MakeEnabled" + ] and not FileSystemUtilities.isRemoteFileName(self.ppath) @pyqtSlot() def __autoExecuteMake(self): @@ -6614,7 +7378,9 @@ Private slot to execute a project specific make run (auto-run) (execute or question). """ - if Preferences.getProject("AutoExecuteMake"): + if Preferences.getProject( + "AutoExecuteMake" + ) and not FileSystemUtilities.isRemoteFileName(self.ppath): self.__executeMake(questionOnly=self.__pdata["MAKEPARAMS"]["MakeTestOnly"]) @pyqtSlot() @@ -6625,6 +7391,14 @@ @param questionOnly flag indicating to ask make for changes only @type bool """ + if FileSystemUtilities.isRemoteFileName(self.ppath): + EricMessageBox.critical( + self.ui, + self.tr("Execute Make"), + self.tr("'Make' is not supported for remote projects. Aborting..."), + ) + return + if ( not self.__pdata["MAKEPARAMS"]["MakeEnabled"] or self.__makeProcess is not None @@ -6850,6 +7624,10 @@ """ Private slot called before the 'Other Tools' menu is shown. """ + self.createSBOMAct.setEnabled( + not FileSystemUtilities.isRemoteFileName(self.ppath) + ) + self.showMenu.emit("OtherTools", self.othersMenu) @pyqtSlot() @@ -6990,20 +7768,27 @@ @rtype str """ ppath = self.getProjectPath() - if ppath and os.path.exists(ppath): - with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator: - for dirEntry in ppathDirEntriesIterator: - # potential venv directory; check for 'pyvenv.cfg' - if dirEntry.is_dir() and os.path.exists( - os.path.join(dirEntry.path, "pyvenv.cfg") - ): - return dirEntry.path - - # check for some common names in case 'pyvenv.cfg' is missing + if FileSystemUtilities.isRemoteFileName(ppath): + # check for some common names for venvPathName in (".venv", "venv", ".env", "env"): - venvPath = os.path.join(self.getProjectPath(), venvPathName) - if os.path.isdir(venvPath): + venvPath = self.__remotefsInterface.join(ppath, venvPathName) + if self.__remotefsInterface.isdir(venvPath): return venvPath + else: + if ppath and os.path.exists(ppath): + with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator: + for dirEntry in ppathDirEntriesIterator: + # potential venv directory; check for 'pyvenv.cfg' + if dirEntry.is_dir() and os.path.exists( + os.path.join(dirEntry.path, "pyvenv.cfg") + ): + return dirEntry.path + + # check for some common names in case 'pyvenv.cfg' is missing + for venvPathName in (".venv", "venv", ".env", "env"): + venvPath = os.path.join(ppath, venvPathName) + if os.path.isdir(venvPath): + return venvPath return "" @@ -7209,6 +7994,91 @@ if dirEntry.is_dir(): self.__clearByteCodeCaches(dirEntry.path) + ############################################################################# + ## 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) + self.saveasRemoteAct.setEnabled( + connected + and self.opened + and FileSystemUtilities.isRemoteFileName(self.pfile) + ) + if not connected and FileSystemUtilities.isRemoteFileName(self.ppath): + self.closeProject(noSave=True) + + @pyqtSlot() + def __openRemoteProject(self): + """ + Private slot to open a project of an 'eric-ide' server. + """ + fn = EricServerFileDialog.getOpenFileName( + self.parent(), + self.tr("Open Remote Project"), + "", + self.tr("Project Files (*.epj)"), + ) + if fn: + self.openProject(fn=fn) + + @pyqtSlot() + def __saveRemoteProjectAs(self): + """ + Private slot to save the current remote project to different remote file. + """ + defaultFilter = self.tr("Project Files (*.epj)") + defaultPath = self.ppath if self.ppath else "" + fn, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter( + self.parent(), + self.tr("Save Remote Project"), + defaultPath, + self.tr("Project Files (*.epj)"), + defaultFilter, + ) + + if fn: + fname, ext = self.__remotefsInterface.splitext(fn) + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fn = f"{fname}{ex}" + if self.__remotefsInterface.exists(fn): + res = EricMessageBox.yesNo( + self.ui, + self.tr("Save Remote Project"), + self.tr( + """<p>The file <b>{0}</b> already exists.""" + """ Overwrite it?</p>""" + ).format(fn), + icon=EricMessageBox.Warning, + ) + if not res: + return + + ok = self.__writeProject(fn) + + if ok: + # create management directory if not present + self.createProjectManagementDir() + + # now save the tasks + self.writeTasks() + + self.sessActGrp.setEnabled(ok) + self.menuSessionAct.setEnabled(ok) + self.projectClosedHooks.emit() + self.projectClosed.emit(False) + self.projectOpenedHooks.emit() + self.projectOpened.emit() + # # eflag: noqa = M601