Adapted the project 'Add Files' and 'Add Directory' functions to work with remote projects. server

Fri, 23 Feb 2024 16:52:01 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 23 Feb 2024 16:52:01 +0100
branch
server
changeset 10605
b6f5e27daeb5
parent 10604
0f4017309f35
child 10610
bb0149571d94

Adapted the project 'Add Files' and 'Add Directory' functions to work with remote projects.

src/eric7/EricWidgets/EricPathPicker.py file | annotate | diff | comparison | revisions
src/eric7/Project/AddDirectoryDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/AddFileDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/Project.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectBrowserModel.py file | annotate | diff | comparison | revisions
src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py file | annotate | diff | comparison | revisions
src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py file | annotate | diff | comparison | revisions
--- a/src/eric7/EricWidgets/EricPathPicker.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/EricWidgets/EricPathPicker.py	Fri Feb 23 16:52:01 2024 +0100
@@ -22,8 +22,11 @@
 )
 
 from eric7.EricGui import EricPixmapCache
+from eric7.RemoteServerInterface import EricServerFileDialog
+from eric7.SystemUtilities import FileSystemUtilities
 
 from . import EricFileDialog
+from .EricApplication import ericApp
 from .EricCompleters import EricDirCompleter, EricFileCompleter
 
 
@@ -83,6 +86,8 @@
 
         self.__mode = EricPathPicker.DefaultMode
         self.__editorEnabled = True
+        self.__remote = False
+        self.__remotefsInterface = None
 
         self._completer = None
         self.__filters = ""
@@ -187,6 +192,30 @@
         """
         return self.__mode
 
+    def setRemote(self, remote):
+        """
+        Public method to set the remote mode of the path picker.
+
+        @param remote flag indicating the remote mode
+        @type bool
+        """
+        self.__remote = remote
+        if remote:
+            self.__remotefsInterface = (
+                ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            )
+        else:
+            self.__remotefsInterface = None
+
+    def isRemote(self):
+        """
+        Public method to get the path picker remote mode.
+
+        @return flag indicating the remote mode
+        @rtype bool
+        """
+        return self.__remote
+
     def setPickerEnabled(self, enable):
         """
         Public method to set the enabled state of the file dialog button.
@@ -290,10 +319,15 @@
             else:
                 return self._editorText()
         else:
-            if toNative:
-                return os.path.expanduser(QDir.toNativeSeparators(self._editorText()))
+            if self.__remote:
+                return self.__remotefsInterface.expanduser(self._editorText())
             else:
-                return os.path.expanduser(self._editorText())
+                if toNative:
+                    return os.path.expanduser(
+                        QDir.toNativeSeparators(self._editorText())
+                    )
+                else:
+                    return os.path.expanduser(self._editorText())
 
     def setEditText(self, fpath, toNative=True):
         """
@@ -370,6 +404,39 @@
         """
         return self.paths()[-1]
 
+    def strPaths(self):
+        """
+        Public method to get the list of entered paths as strings.
+
+        @return entered paths
+        @rtype list of str
+        """
+        if self.__mode in (
+            EricPathPickerModes.OPEN_FILES_MODE,
+            EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+        ):
+            return self.text().split(";")
+        else:
+            return [self.text()]
+
+    def firstStrPath(self):
+        """
+        Public method to get the first path of a list of entered paths as a string.
+
+        @return first path
+        @rtype pathlib.Path
+        """
+        return self.strPaths()[0]
+
+    def lastStrPath(self):
+        """
+        Public method to get the last path of a list of entered paths as a string.
+
+        @return last path
+        @rtype pathlib.Path
+        """
+        return self.strPaths()[-1]
+
     def setEditorEnabled(self, enable):
         """
         Public method to set the path editor's enabled state.
@@ -534,76 +601,138 @@
         directory = self._editorText()
         if not directory and self.__defaultDirectory:
             directory = self.__defaultDirectory
-        directory = (
-            os.path.expanduser(directory.split(";")[0])
-            if self.__mode
-            in (
-                EricPathPickerModes.OPEN_FILES_MODE,
-                EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+        if self.__remote:
+            directory = (
+                self.__remotefsInterface.expanduser(directory.split(";")[0])
+                if self.__mode == EricPathPickerModes.OPEN_FILES_MODE
+                else self.__remotefsInterface.expanduser(directory)
             )
-            else os.path.expanduser(directory)
-        )
-        if not os.path.isabs(directory) and self.__defaultDirectory:
-            directory = os.path.join(self.__defaultDirectory, directory)
-        directory = QDir.fromNativeSeparators(directory)
+        else:
+            directory = (
+                os.path.expanduser(directory.split(";")[0])
+                if self.__mode
+                in (
+                    EricPathPickerModes.OPEN_FILES_MODE,
+                    EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+                )
+                else os.path.expanduser(directory)
+            )
+            if not os.path.isabs(directory) and self.__defaultDirectory:
+                directory = os.path.join(self.__defaultDirectory, directory)
+            directory = QDir.fromNativeSeparators(directory)
 
         if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
-            fpath = EricFileDialog.getOpenFileName(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getOpenFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+            else:
+                fpath = EricFileDialog.getOpenFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
-            fpaths = EricFileDialog.getOpenFileNames(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
+            if self.__remote:
+                fpaths = EricServerFileDialog.getOpenFileNames(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = ";".join(fpaths)
+            else:
+                fpaths = EricFileDialog.getOpenFileNames(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
         elif self.__mode == EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE:
+            # that is not supported for 'remote' pickers
             fpaths = EricFileDialog.getOpenFileAndDirNames(
                 self, windowTitle, directory, self.__filters
             )
             fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
         elif self.__mode == EricPathPickerModes.SAVE_FILE_MODE:
-            fpath = EricFileDialog.getSaveFileName(
-                self,
-                windowTitle,
-                directory,
-                self.__filters,
-                EricFileDialog.DontConfirmOverwrite,
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+            else:
+                fpath = EricFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                    EricFileDialog.DontConfirmOverwrite,
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE:
-            fpath, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
-                self,
-                windowTitle,
-                directory,
-                self.__filters,
-                None,
-                EricFileDialog.DontConfirmOverwrite,
-            )
-            fpath = pathlib.Path(fpath)
-            if not fpath.suffix:
-                ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
-                if ex:
-                    fpath = fpath.with_suffix(ex)
+            if self.__remote:
+                fpath, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+                fn, ext = self.__remotefsInterface.splitext(fpath)
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
+                    if ex:
+                        fpath = f"{fn}{ex}"
+            else:
+                fpath, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                    None,
+                    EricFileDialog.DontConfirmOverwrite,
+                )
+                fpath = pathlib.Path(fpath)
+                if not fpath.suffix:
+                    ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
+                    if ex:
+                        fpath = fpath.with_suffix(ex)
         elif self.__mode == EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE:
-            fpath = EricFileDialog.getSaveFileName(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+            else:
+                fpath = EricFileDialog.getSaveFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
-            fpath = EricFileDialog.getExistingDirectory(
-                self, windowTitle, directory, EricFileDialog.ShowDirsOnly
-            )
-            fpath = QDir.toNativeSeparators(fpath)
-            while fpath.endswith(os.sep):
-                fpath = fpath[:-1]
+            if self.__remote:
+                fpath = EricServerFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, True
+                )
+                while fpath.endswith(self.__remotefsInterface.separator()):
+                    fpath = fpath[:-1]
+            else:
+                fpath = EricFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, EricFileDialog.ShowDirsOnly
+                )
+                fpath = QDir.toNativeSeparators(fpath)
+                while fpath.endswith(os.sep):
+                    fpath = fpath[:-1]
         elif self.__mode == EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE:
-            fpath = EricFileDialog.getExistingDirectory(
-                self, windowTitle, directory, EricFileDialog.DontUseNativeDialog
-            )
-            fpath = QDir.toNativeSeparators(fpath)
-            while fpath.endswith(os.sep):
-                fpath = fpath[:-1]
+            if self.__remote:
+                fpath = EricServerFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, False
+                )
+                while fpath.endswith(self.__remotefsInterface):
+                    fpath = fpath[:-1]
+            else:
+                fpath = EricFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, EricFileDialog.DontUseNativeDialog
+                )
+                fpath = QDir.toNativeSeparators(fpath)
+                while fpath.endswith(os.sep):
+                    fpath = fpath[:-1]
 
         if fpath:
             self._setEditorText(str(fpath))
@@ -766,3 +895,17 @@
         for index in range(self._editor.count()):
             paths.append(pathlib.Path(self._editor.itemText(index)))
         return paths
+
+    def getRemotePathItems(self):
+        """
+        Public method to get the list of remembered remote paths.
+
+        @return list of remembered paths
+        @rtype list of str
+        """
+        paths = []
+        for index in range(self._editor.count()):
+            paths.append(
+                FileSystemUtilities.remoteFileName(self._editor.itemText(index))
+            )
+        return paths
--- a/src/eric7/Project/AddDirectoryDialog.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/Project/AddDirectoryDialog.py	Fri Feb 23 16:52:01 2024 +0100
@@ -8,9 +8,10 @@
 """
 
 from PyQt6.QtCore import pyqtSlot
-from PyQt6.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
 
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_AddDirectoryDialog import Ui_AddDirectoryDialog
 
@@ -42,13 +43,23 @@
             self.setObjectName(name)
         self.setupUi(self)
 
+        self.__remoteMode = (
+            bool(startdir) and FileSystemUtilities.isRemoteFileName(startdir)
+        ) or FileSystemUtilities.isRemoteFileName(pro.getProjectPath())
+
         self.sourceDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.sourceDirPicker.setDefaultDirectory(startdir)
+        self.sourceDirPicker.setRemote(self.__remoteMode)
+
         self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirPicker.setDefaultDirectory(startdir)
+        self.targetDirPicker.setRemote(self.__remoteMode)
 
         self.__project = pro
-        self.targetDirPicker.setText(self.__project.getProjectPath())
+        if startdir:
+            self.targetDirPicker.setText(startdir)
+        else:
+            self.targetDirPicker.setText(pro.getProjectPath())
 
         if fileTypeFilter and fileTypeFilter != "TRANSLATIONS":
             self.filterComboBox.addItem(
@@ -65,6 +76,8 @@
                 )
         self.filterComboBox.setCurrentIndex(0)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
@@ -103,6 +116,10 @@
         if directory.startswith(self.__project.getProjectPath()):
             self.targetDirPicker.setText(directory)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            bool(directory)
+        )
+
     def getData(self):
         """
         Public slot to retrieve the dialogs data.
--- a/src/eric7/Project/AddFileDialog.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/Project/AddFileDialog.py	Fri Feb 23 16:52:01 2024 +0100
@@ -10,9 +10,11 @@
 import os
 
 from PyQt6.QtCore import pyqtSlot
-from PyQt6.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
 
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_AddFileDialog import Ui_AddFileDialog
 
@@ -42,9 +44,16 @@
             self.setObjectName(name)
         self.setupUi(self)
 
+        self.__remoteMode = (
+            bool(startdir) and FileSystemUtilities.isRemoteFileName(startdir)
+        ) or FileSystemUtilities.isRemoteFileName(pro.getProjectPath())
+
         self.sourceFilesPicker.setMode(EricPathPickerModes.OPEN_FILES_MODE)
+        self.sourceFilesPicker.setRemote(self.__remoteMode)
+
         self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirPicker.setDefaultDirectory(startdir)
+        self.targetDirPicker.setRemote(self.__remoteMode)
 
         if startdir:
             self.targetDirPicker.setText(startdir)
@@ -57,6 +66,8 @@
         if self.fileTypeFilter is not None:
             self.sourcecodeCheckBox.hide()
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
@@ -66,12 +77,16 @@
         Private slot to perform actions before the source files selection
         dialog is shown.
         """
-        path = self.targetDirPicker.text()
-        if not path:
-            path = self.startdir
-        self.sourceFilesPicker.setDefaultDirectory(path)
+        targetPath = self.targetDirPicker.text()
+        if not targetPath:
+            targetPath = self.startdir
+        self.sourceFilesPicker.setDefaultDirectory(targetPath)
 
-        caption = self.tr("Select Files")
+        caption = (
+            self.tr("Select Files")
+            if self.__remoteMode
+            else self.tr("Select Remote Files")
+        )
         if self.fileTypeFilter is None:
             dfilter = self.__project.getFileCategoryFilterString(withAll=True)
         elif (
@@ -103,14 +118,23 @@
         @param sfile the text of the source file picker
         @type str
         """
-        sfile = str(self.sourceFilesPicker.firstPath())
+        sfile = self.sourceFilesPicker.firstStrPath()
         if sfile.startswith(self.__project.getProjectPath()):
-            if os.path.isdir(sfile):
-                directory = sfile
+            if self.__remoteMode:
+                fsInterface = (
+                    ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+                )
+                directory = (
+                    sfile if fsInterface.isdir(sfile) else fsInterface.dirname(sfile)
+                )
             else:
-                directory = os.path.dirname(sfile)
+                directory = (
+                    sfile if os.path.isdir(sfile) else os.path.dirname(sfile)
+                )
             self.targetDirPicker.setText(directory)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(bool(sfile))
+
     def getData(self):
         """
         Public slot to retrieve the dialogs data.
@@ -120,7 +144,7 @@
         @rtype tuple of (list of string, string, boolean)
         """
         return (
-            [str(p) for p in self.sourceFilesPicker.paths()],
+            self.sourceFilesPicker.strPaths(),
             self.targetDirPicker.text(),
             self.sourcecodeCheckBox.isChecked(),
         )
--- a/src/eric7/Project/Project.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/Project/Project.py	Fri Feb 23 16:52:01 2024 +0100
@@ -2035,7 +2035,6 @@
         @param startdir start directory for the selection dialog
         @type str
         """
-        # TODO: adapt to remote server
         from .AddFileDialog import AddFileDialog
 
         if not startdir:
@@ -2045,17 +2044,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>"
@@ -2065,11 +2084,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>"
@@ -2111,15 +2133,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>"
@@ -2127,15 +2154,20 @@
                 )
             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>"
@@ -2148,13 +2180,23 @@
                 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>"
@@ -2165,7 +2207,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)
@@ -2202,16 +2247,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):
--- a/src/eric7/Project/ProjectBrowserModel.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/Project/ProjectBrowserModel.py	Fri Feb 23 16:52:01 2024 +0100
@@ -369,12 +369,14 @@
                         FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
                         parentItem.getProjectTypes()[0],
                         False,
+                        fsInterface=self.__remotefsInterface,
                     )
                     if f.isDir()
                     else ProjectBrowserFileItem(
                         parentItem,
                         FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
                         parentItem.getProjectTypes()[0],
+                        fsInterface=self.__remotefsInterface,
                     )
                 )
                 if self.project.vcs is not None:
@@ -587,6 +589,7 @@
                 self.__projectBrowser.getProjectBrowserFilter(typeString),
                 False,
                 bold,
+                fsInterface=self.__remotefsInterface,
             )
         else:
             if typeString == "SOURCES":
@@ -600,6 +603,7 @@
                 False,
                 bold,
                 sourceLanguage=sourceLanguage,
+                fsInterface=self.__remotefsInterface,
             )
         self.__addVCSStatus(itm, fname)
         if additionalTypeStrings:
@@ -762,12 +766,14 @@
                         FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
                         itm.getProjectTypes()[0],
                         False,
+                        fsInterface=self.__remotefsInterface,
                     )
                     if f.isDir()
                     else ProjectBrowserFileItem(
                         itm,
                         FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
                         itm.getProjectTypes()[0],
+                        fsInterface=self.__remotefsInterface,
                     )
                 )
                 self._addItem(node, itm)
--- a/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py	Fri Feb 23 16:52:01 2024 +0100
@@ -10,6 +10,7 @@
 import base64
 import contextlib
 import os
+import shutil
 import stat
 import time
 
@@ -48,6 +49,8 @@
             "ReadFile": self.__readFile,
             "WriteFile": self.__writeFile,
             "DirEntries": self.__dirEntries,
+            "ExpandUser": self.__expanduser,
+            "ShutilCopy": self.__shutilCopy,
         }
 
     def handleRequest(self, request, params, reqestUuid):
@@ -400,3 +403,37 @@
             "ok": True,
             "result": result,
         }
+
+    def __expanduser(self, params):
+        """
+        Private method to replace an initial component of ~ or ~user replaced.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        return {
+            "ok": True,
+            "name": os.path.expanduser(params["name"])
+        }
+
+    def __shutilCopy(self, params):
+        """
+        Private method to copy a source file to a destination file or directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            return {
+                "ok": True,
+                "dst": shutil.copy(params["src_name"], params["dst_name"]),
+            }
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
--- a/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py	Fri Feb 23 16:50:50 2024 +0100
+++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py	Fri Feb 23 16:52:01 2024 +0100
@@ -246,6 +246,9 @@
             if not ok:
                 raise OSError(error)
 
+            for entry in listing:
+                entry["path"] = FileSystemUtilities.remoteFileName(entry["path"])
+
         return listedDirectory, separator, listing
 
     def direntries(
@@ -353,9 +356,13 @@
                     dirname, pattern=basename, recursive=recursive, filesonly=True
                 )
                 result = (
-                    entries
+                    [FileSystemUtilities.remoteFileName(e) for e in entries]
                     if includeHidden
-                    else [e for e in entries if not e.startswith(".")]
+                    else [
+                        FileSystemUtilities.remoteFileName(e)
+                        for e in entries
+                        if not e.startswith(".")
+                    ]
                 )
 
         return result
@@ -770,6 +777,49 @@
         else:
             return False, "Not connected to an 'eric-ide' server."
 
+    def expanduser(self, name):
+        """
+        Public method to expand an initial '~' or '~user' component.
+
+        @param name path name to be expanded
+        @type str
+        @return expanded path name
+        @rtype str
+        """
+        loop = QEventLoop()
+        ok = False
+        expandedName = name
+
+        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, expandedName
+
+            if reply == "ExpandUser":
+                ok = params["ok"]
+                expandedName = params["name"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ExpandUser",
+                params={"name": FileSystemUtilities.plainFileName(name)},
+                callback=callback,
+            )
+
+            loop.exec()
+        if FileSystemUtilities.isRemoteFileName(name):
+            return FileSystemUtilities.remoteFileName(expandedName)
+        else:
+            return expandedName
+
     #######################################################################
     ## Methods for splitting or joining remote path names.
     ##
@@ -1100,3 +1150,52 @@
         self.writeFile(filename, data, withBackup=withBackup)
 
         return encoding
+
+    #######################################################################
+    ## Methods implementing some 'shutil' like functionality.
+    #######################################################################
+
+    def shutilCopy(self, srcName, dstName):
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        dst = ""
+
+        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, dst
+
+            if reply == "ShutilCopy":
+                ok = params["ok"]
+                if ok:
+                    dst = params["dst"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise OSError("Not connected to an 'eric-ide' server.")
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ShutilCopy",
+                params={
+                    "src_name": FileSystemUtilities.plainFileName(srcName),
+                    "dst_name": FileSystemUtilities.plainFileName(dstName),
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+            return dst

eric ide

mercurial