Ejemplo n.º 1
0
    def _real_listdir(self, path):
        """
        Return a list of directories, files etc. in the directory
        named `path`.

        If the directory listing from the server can't be parsed
        raise a `ParserError`.
        """
        # We _can't_ put this check into `FTPHost._dir`; see its docstring.
        path = self._path.abspath(path)
        # `listdir` should only be allowed for directories and links to them.
        if not self._path.isdir(path):
            raise ftp_error.PermanentError(
                "550 %s: no such directory or wrong directory parser used" %
                path)
        # Set up for `for` loop
        lines = self._host_dir(path)
        # Exit the method now if there aren't any files
        if lines == ['']:
            return []
        names = []
        for line in lines:
            if self._parser.ignores_line(line):
                continue
            # For `listdir`, we are interested in just the names,
            #  but we use the `time_shift` parameter to have the
            #  correct timestamp values in the cache.
            stat_result = self._parser.parse_line(line,
                                                  self._host.time_shift())
            loop_path = self._path.join(path, stat_result._st_name)
            self._lstat_cache[loop_path] = stat_result
            st_name = stat_result._st_name
            if st_name not in (self._host.curdir, self._host.pardir):
                names.append(st_name)
        return names
Ejemplo n.º 2
0
    def _real_lstat(self, path, _exception_for_missing_path=True):
        """
        Return an object similar to that returned by `os.lstat`.

        If the directory listing from the server can't be parsed,
        raise a `ParserError`. If the directory can be parsed and the
        `path` is not found, raise a `PermanentError`. That means that
        if the directory containing `path` can't be parsed we get a
        `ParserError`, independent on the presence of `path` on the
        server.

        (`_exception_for_missing_path` is an implementation aid and
        _not_ intended for use by ftputil clients.)
        """
        path = self._path.abspath(path)
        # If the path is in the cache, return the lstat result.
        if path in self._lstat_cache:
            return self._lstat_cache[path]
        # Note: (l)stat works by going one directory up and parsing
        #  the output of an FTP `DIR` command. Unfortunately, it is
        #  not possible to do this for the root directory `/`.
        if path == '/':
            raise ftp_error.RootDirError("can't stat remote root directory")
        dirname, basename = self._path.split(path)
        # If even the directory doesn't exist and we don't want the
        #  exception, treat it the same as if the path wasn't found in
        #  the directory's contents (compare below). The use of `isdir`
        #  here causes a recursion but that should be ok because that
        #  will at the latest stop when we've got to the root directory.
        #         if not self._path.isdir(dirname) and not _exception_for_missing_path:
        #             return None
        # Loop through all lines of the directory listing. We
        #  probably won't need all lines for the particular path but
        #  we want to collect as many stat results in the cache as
        #  possible.
        lstat_result_for_path = None
        for stat_result in self._stat_results_from_dir(dirname):
            # Needed to work without cache or with disabled cache.
            if stat_result._st_name == basename:
                lstat_result_for_path = stat_result
        if lstat_result_for_path is not None:
            return lstat_result_for_path
        # Path was not found during the loop.
        if _exception_for_missing_path:
            #TODO Use FTP DIR command on the file to implicitly use
            #  the usual status code of the server for missing files
            #  (450 vs. 550).
            raise ftp_error.PermanentError(
                "550 %s: no such file or directory" % path)
        else:
            # Be explicit. Returning `None` is a signal for
            #  `_Path.exists/isfile/isdir/islink` that the path was
            #  not found. If we would raise an exception, there would
            #  be no distinction between a missing path or a more
            #  severe error in the code above.
            return None
Ejemplo n.º 3
0
    def _real_lstat(self, path, _exception_for_missing_path=True):
        """
        Return an object similar to that returned by `os.lstat`.

        If the directory listing from the server can't be parsed,
        raise a `ParserError`. If the directory can be parsed and the
        `path` is not found, raise a `PermanentError`. That means that
        if the directory containing `path` can't be parsed we get a
        `ParserError`, independent on the presence of `path` on the
        server.

        (`_exception_for_missing_path` is an implementation aid and
        _not_ intended for use by ftputil clients.)
        """
        path = self._path.abspath(path)
        # if the path is in the cache, return the lstat result
        if path in self._lstat_cache:
            return self._lstat_cache[path]
        # get output from FTP's `DIR` command
        lines = []
        # Note: (l)stat works by going one directory up and parsing
        #  the output of an FTP `DIR` command. Unfortunately, it is
        #  not possible to do this for the root directory `/`.
        if path == '/':
            raise ftp_error.RootDirError(
                  "can't stat remote root directory")
        dirname, basename = self._path.split(path)
        lstat_result_for_path = None
        # loop through all lines of the directory listing; we
        #  probably won't need all lines for the particular path but
        #  we want to collect as many stat results in the cache as
        #  possible
        lines = self._host_dir(dirname)
        for line in lines:
            if self._parser.ignores_line(line):
                continue
            stat_result = self._parser.parse_line(line,
                          self._host.time_shift())
            loop_path = self._path.join(dirname, stat_result._st_name)
            self._lstat_cache[loop_path] = stat_result
            # needed to work without cache or with disabled cache
            if stat_result._st_name == basename:
                lstat_result_for_path = stat_result
        if lstat_result_for_path:
            return lstat_result_for_path
        # path was not found
        if _exception_for_missing_path:
            raise ftp_error.PermanentError(
                  "550 %s: no such file or directory" % path)
        else:
            # be explicit; returning `None` is a signal for
            #  `_Path.exists/isfile/isdir/islink` that the path was
            #  not found; if we would raise an exception, there would
            #  be no distinction between a missing path or a more
            #  severe error in the code above
            return None
Ejemplo n.º 4
0
 def remove(self, path):
     """Remove the given file or link."""
     path = self.path.abspath(path)
     # though `isfile` includes also links to files, `islink`
     #  is needed to include links to directories
     if self.path.isfile(path) or self.path.islink(path):
         def command(self, path):
             ftp_error._try_with_oserror(self._session.delete, path)
         self._robust_ftp_command(command, path)
     else:
         raise ftp_error.PermanentError("remove/unlink can only delete "
                                        "files and links, not directories")
     self.stat_cache.invalidate(path)
Ejemplo n.º 5
0
 def remove(self, path):
     """Remove the given file or link."""
     path = self.path.abspath(path)
     # Though `isfile` includes also links to files, `islink`
     #  is needed to include links to directories.
     if self.path.isfile(path) or self.path.islink(path) or \
        not self.path.exists(path):
         # If the path doesn't exist, let the removal command trigger
         #  an exception with a more appropriate error message.
         def command(self, path):
             """Callback function."""
             ftp_error._try_with_oserror(self._session.delete, path)
         self._robust_ftp_command(command, path)
     else:
         raise ftp_error.PermanentError("remove/unlink can only delete "
                                        "files and links, not directories")
     self.stat_cache.invalidate(path)
Ejemplo n.º 6
0
    def rmdir(self, path):
        """
        Remove the _empty_ directory `path` on the remote host.

        Compatibility note:

        Previous versions of ftputil could possibly delete non-
        empty directories as well, - if the server allowed it. This
        is no longer supported.
        """
        path = self.path.abspath(path)
        if self.listdir(path):
            raise ftp_error.PermanentError("directory '%s' not empty" % path)
        #XXX how will `rmd` work with links?
        def command(self, path):
            ftp_error._try_with_oserror(self._session.rmd, path)
        self._robust_ftp_command(command, path)
        self.stat_cache.invalidate(path)
Ejemplo n.º 7
0
    def _real_stat(self, path, _exception_for_missing_path=True):
        """
        Return info from a "stat" call on `path`.

        If the directory containing `path` can't be parsed, raise
        a `ParserError`. If the listing can be parsed but the
        `path` can't be found, raise a `PermanentError`. Also raise
        a `PermanentError` if there's an endless (cyclic) chain of
        symbolic links "behind" the `path`.

        (`_exception_for_missing_path` is an implementation aid and
        _not_ intended for use by ftputil clients.)
        """
        # Save for error message.
        original_path = path
        # Most code in this method is used to detect recursive
        #  link structures.
        visited_paths = set()
        while True:
            # Stat the link if it is one, else the file/directory.
            lstat_result = self._real_lstat(path, _exception_for_missing_path)
            if lstat_result is None:
                return None
            # If the file is not a link, the `stat` result is the
            #  same as the `lstat` result.
            if not stat.S_ISLNK(lstat_result.st_mode):
                return lstat_result
            # If we stat'ed a link, calculate a normalized path for
            #  the file the link points to.
            # We don't use `basename`.
            # pylint: disable=W0612
            dirname, basename = self._path.split(path)
            path = self._path.join(dirname, lstat_result._st_target)
            path = self._path.abspath(self._path.normpath(path))
            # Check for cyclic structure.
            if path in visited_paths:
                # We had seen this path already.
                raise ftp_error.PermanentError(
                    "recursive link structure detected for remote path '%s'" %
                    original_path)
            # Remember the path we have encountered.
            visited_paths.add(path)
Ejemplo n.º 8
0
    def _real_listdir(self, path):
        """
        Return a list of directories, files etc. in the directory
        named `path`.

        If the directory listing from the server can't be parsed
        raise a `ParserError`.
        """
        # We _can't_ put this check into `FTPHost._dir`; see its docstring.
        path = self._path.abspath(path)
        # `listdir` should only be allowed for directories and links to them.
        if not self._path.isdir(path):
            raise ftp_error.PermanentError(
                "550 %s: no such directory or wrong directory parser used" %
                path)
        # Set up for `for` loop.
        names = []
        for stat_result in self._stat_results_from_dir(path):
            st_name = stat_result._st_name
            names.append(st_name)
        return names
Ejemplo n.º 9
0
    def rmdir(self, path):
        """
        Remove the _empty_ directory `path` on the remote host.

        Compatibility note:

        Previous versions of ftputil could possibly delete non-
        empty directories as well, - if the server allowed it. This
        is no longer supported.
        """
        # Fail early if we get a unicode path which can't be encoded.
        path = str(path)
        path = self.path.abspath(path)
        if self.listdir(path):
            raise ftp_error.PermanentError("directory '%s' not empty" % path)
        #XXX How does `rmd` work with links?
        def command(self, path):
            """Callback function."""
            ftp_error._try_with_oserror(self._session.rmd, path)

        self._robust_ftp_command(command, path)
        self.stat_cache.invalidate(path)