예제 #1
0
파일: fs.py 프로젝트: Scrik/ftp-cloudfs
    def remove(self, path):
        """
        Remove a file.

        Raises OSError on error.
        """
        path = self.abspath(path)
        logging.debug("remove %r" % path)
        logging.info("remove %r" % path)
        container, name = parse_fspath(path)

        if not name:
            raise IOSError(EACCES, "Can't remove a container")

        if self.isdir(path):
            raise IOSError(EACCES,
                           "Can't remove a directory (use rmdir instead)")

        meta = self.conn.head_object(container, name)
        if 'x-object-manifest' in meta:
            self._remove_path_folder_files(
                u'/' + unicode(unquote(meta['x-object-manifest']), "utf-8"))
        self.conn.delete_object(container, name)
        self._listdir_cache.flush(posixpath.dirname(path))
        return not name
예제 #2
0
파일: fs.py 프로젝트: jakop345/sftp
    def rmdir(self, path):
        """
        Remove a directory.

        Eaise OSError on error.
        """
        path = self.abspath(path)
        logging.debug("rmdir %r" % path)
        container, obj = parse_fspath(path)

        if not self.isdir(path):
            if self.isfile(path):
                raise IOSError(ENOTDIR, "Not a directory")
            raise IOSError(ENOENT, 'No such file or directory')

        if self.listdir(path):
            raise IOSError(ENOTEMPTY, "Directory not empty: %s" % path)

        if obj:
            self._listdir_cache.flush(posixpath.dirname(path))
            logging.debug("Removing directory %r in %r" % (obj, container))
            self.conn.delete_object(container, obj)
        else:
            self._listdir_cache.flush("/")
            logging.debug("Removing container %r" % (container,))
            self.conn.delete_container(container)
예제 #3
0
파일: fs.py 프로젝트: jakop345/sftp
    def rename(self, src, dst):
        """
        Rename a file/directory from src to dst.

        Raises OSError on error.
        """
        src = self.abspath(src)
        dst = self.abspath(dst)
        logging.debug("rename %r -> %r" % (src, dst))
        self._listdir_cache.flush()
        # Check not renaming to itself
        if src == dst:
            logging.debug("Renaming %r to itself - doing nothing" % src)
            return
        # If dst is an existing directory, copy src inside it
        if self.isdir(dst):
            if dst:
                dst += "/"
            dst += posixpath.basename(src)
        # Check constraints for renaming a directory
        if self.isdir(src):
            if self.listdir(src):
                raise IOSError(ENOTEMPTY, "Can't rename non-empty directory: %s" % src)
            if self.isfile(dst):
                raise IOSError(ENOTDIR, "Can't rename directory to file")
        # Check not renaming to itself
        if src == dst:
            logging.debug("Renaming %r to itself - doing nothing" % src)
            return
        # Parse the paths now
        src_container_name, src_path = parse_fspath(src)
        dst_container_name, dst_path = parse_fspath(dst)
        logging.debug("`.. %r/%r -> %r/%r" % (src_container_name, src_path, dst_container_name, dst_path))
        # Check if we are renaming containers
        if not src_path and not dst_path and src_container_name and dst_container_name:
            return self._rename_container(src_container_name, dst_container_name)
        # ...otherwise can't deal with root stuff
        if not src_container_name or not src_path or not dst_container_name or not dst_path:
            raise IOSError(EACCES, "Can't rename to / from root")
        # Check destination directory exists
        if not self.isdir(posixpath.split(dst)[0]):
            raise IOSError(ENOENT, "Can't copy %r to %r, destination directory doesn't exist" % (src, dst))

        # check dst container
        self._container_exists(dst_container_name)

        # Do the rename of the file/dir
        meta = self.conn.head_object(src_container_name, src_path)
        if 'x-object-manifest' in meta:
            # a manifest file
            headers = { 'x-object-manifest': meta['x-object-manifest'] }
        else:
            # regular file
            headers = { 'x-copy-from': "/%s/%s" % (src_container_name, src_path) }
        self.conn.put_object(dst_container_name, dst_path, headers=headers, contents=None)
        # Delete src
        self.conn.delete_object(src_container_name, src_path)
        self._listdir_cache.flush(posixpath.dirname(src))
        self._listdir_cache.flush(posixpath.dirname(dst))
예제 #4
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
 def rename(self, src, dst):
     '''Rename a file/directory from src to dst, raise OSError on error'''
     src = self.abspath(src)
     dst = self.abspath(dst)
     logging.debug("rename %r -> %r" % (src, dst))
     self._listdir_cache.flush()
     # Check not renaming to itself
     if src == dst:
         logging.debug("Renaming %r to itself - doing nothing" % src)
         return
     # If dst is an existing directory, copy src inside it
     if self.isdir(dst):
         if dst:
             dst += "/"
         dst += posixpath.basename(src)
     # Check constraints for renaming a directory
     if self.isdir(src):
         if self.listdir(src):
             raise IOSError(ENOTEMPTY,
                            "Can't rename non-empty directory: %s" % src)
         if self.isfile(dst):
             raise IOSError(ENOTDIR, "Can't rename directory to file")
     # Check not renaming to itself
     if src == dst:
         logging.debug("Renaming %r to itself - doing nothing" % src)
         return
     # Parse the paths now
     src_container_name, src_path = parse_fspath(src)
     dst_container_name, dst_path = parse_fspath(dst)
     logging.debug(
         "`.. %r/%r -> %r/%r" %
         (src_container_name, src_path, dst_container_name, dst_path))
     # Check if we are renaming containers
     if not src_path and not dst_path and src_container_name and dst_container_name:
         return self._rename_container(src_container_name,
                                       dst_container_name)
     # ...otherwise can't deal with root stuff
     if not src_container_name or not src_path or not dst_container_name or not dst_path:
         raise IOSError(EACCES, "Can't rename to / from root")
     # Check destination directory exists
     if not self.isdir(posixpath.split(dst)[0]):
         raise IOSError(
             ENOENT,
             "Can't copy %r to %r, destination directory doesn't exist" %
             (src, dst))
     # Do the rename of the file/dir
     src_container = self._get_container(src_container_name)
     dst_container = self._get_container(dst_container_name)
     src_obj = src_container.get_object(src_path)
     # Copy src -> dst
     src_obj.copy_to(dst_container_name, dst_path)
     # Delete src
     src_container.delete_object(src_path)
     self._listdir_cache.flush(posixpath.dirname(src))
     self._listdir_cache.flush(posixpath.dirname(dst))
예제 #5
0
파일: fs.py 프로젝트: jakop345/sftp
 def chdir(self, path):
     """Change current directory, raise OSError on error"""
     path = self.abspath(path)
     logging.debug("chdir %r" % path)
     if not path.startswith("/"):
         raise IOSError(ENOENT, 'Failed to change directory.')
     container, obj = parse_fspath(path)
     if not container:
         logging.debug("cd to /")
     else:
         logging.debug("cd to container %r directory %r" % (container, obj))
         if not self.isdir(path):
             raise IOSError(ENOTDIR, "Can't cd to a file")
     self._cwd = path
예제 #6
0
    def stat(self, path):
        """
        Returns an os.stat_result for path or raises IOSError.

        Returns the information from the cache if possible.
        """
        path = path.rstrip("/") or "/"
        logging.debug("stat path %r" % (path))
        directory, leaf = posixpath.split(path)
        # Refresh the cache it if is old, or wrong
        if not self.valid(directory):
            logging.debug("invalid cache for %r (path: %r)" %
                          (directory, self.path))
            self.listdir(directory)
        if path != "/":
            try:
                stat_info = self.cache[smart_str(leaf)]
            except KeyError:
                logging.debug("Didn't find %r in directory listing" % leaf)
                # it can be a container and the user doesn't have
                # permissions to list the root
                if directory == '/' and leaf:
                    try:
                        container = self.conn.head_container(leaf)
                        if (self.cffs.storage_policy is not None
                                and container['x-storage-policy'] !=
                                self.cffs.storage_policy):
                            raise IOSError(
                                ENOENT, 'No such file or directory %s' % leaf)
                    except ClientException:
                        raise IOSError(ENOENT,
                                       'No such file or directory %s' % leaf)

                    logging.debug(
                        "Accessing %r container without root listing" % leaf)
                    stat_info = self._make_stat(
                        count=int(container["x-container-object-count"]),
                        bytes=int(container["x-container-bytes-used"]),
                    )
                else:
                    raise IOSError(ENOENT,
                                   'No such file or directory %s' % leaf)
        else:
            # Root directory size is sum of containers, count is containers
            bytes = sum(stat_info.st_size for stat_info in self.cache.values())
            count = len(self.cache)
            stat_info = self._make_stat(count=count, bytes=bytes)
        logging.debug("stat path: %r" % stat_info)
        return stat_info
예제 #7
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
    def md5(self, path):
        '''Return the object MD5 for path, raise OSError on error'''
        path = self.abspath(path)
        logging.debug("md5 %r" % path)
        container, name = parse_fspath(path)

        if not name:
            raise IOSError(EACCES, "Can't return the MD5 of a container")

        if self.isdir(path):
            # this is only 100% accurate for virtual directories
            raise IOSError(EACCES, "Can't return the MD5 of a directory")

        container = self._get_container(container)
        obj = container.get_object(name)
        return obj.etag
예제 #8
0
파일: fs.py 프로젝트: jakop345/sftp
    def __init__(self, cffs, container, obj, mode):
        self.cffs = cffs
        self.container = container
        self.name = obj
        self.mode = mode
        self.closed = False
        self.total_size = 0
        self.part_size = 0
        self.part = 0
        self.headers = dict()
        self.content_type = mimetypes.guess_type(self.name)[0]
        self.pending_copy_task = None

        self.obj = None

        # this is only used by `seek`, so we delay the HEAD request until is required
        self.size = None

        if not all([container, obj]):
            self.closed = True
            raise IOSError(EPERM, 'Container and object required')

        logging.debug("ObjectStorageFD object: %r (mode: %r)" % (obj, mode))

        if 'r' in self.mode:
            logging.debug("read fd %r" % self.name)
        else: # write
            logging.debug("write fd %r" % self.name)
            self.obj = ChunkObject(self.conn, self.container, self.name, content_type=self.content_type)
예제 #9
0
파일: fs.py 프로젝트: jakop345/sftp
    def write(self, data):
        """Write data to the object."""
        if 'r' in self.mode:
            raise IOSError(EPERM, "File is opened for read")

        # large file support
        if self.split_size:
            # data can be of any size, so we need to split it in split_size chunks
            offs = 0
            while offs < len(data):
                if self.part_size + len(data) - offs > self.split_size:
                    current_size = self.split_size-self.part_size
                    logging.debug("data is to large (%r), using %s" % (len(data), current_size))
                else:
                    current_size = len(data)-offs
                self.part_size += current_size
                if not self.obj:
                    self.obj = ChunkObject(self.conn, self.container, self.part_name, content_type=self.content_type)
                self.obj.send_chunk(data[offs:offs+current_size])
                offs += current_size
                if self.part_size == self.split_size:
                    logging.debug("current size is %r, split_file is %r" % (self.part_size, self.split_size))
                    self.obj.finish_chunk()
                    # this obj is not valid anymore, will create a new one if a new part is required
                    self.obj = None
                    # make it the first part
                    if self.part == 0:
                        self._start_copy_task()
                    self.part_size = 0
                    self.part += 1
        else:
            self.obj.send_chunk(data)
예제 #10
0
파일: fs.py 프로젝트: jakop345/sftp
 def _container_exists(self, container):
     # verify the container exsists
     try:
         self.conn.head_container(container)
     except ClientException, e:
         if e.http_status == 404:
             raise IOSError(ENOTDIR, "Container not found")
         raise
예제 #11
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
 def wrapper(*args, **kwargs):
     name = getattr(fn, "func_name", "unknown")
     log = lambda msg: logging.warning("At %s: %s" % (name, msg))
     try:
         return fn(*args, **kwargs)
     except (cloudfiles.errors.NoSuchContainer,
             cloudfiles.errors.NoSuchObject), e:
         raise IOSError(ENOENT, 'Not found: %s' % e)
예제 #12
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
    def remove(self, path):
        '''Remove a file, raise OSError on error'''
        path = self.abspath(path)
        logging.debug("remove %r" % path)
        container, name = parse_fspath(path)

        if not name:
            raise IOSError(EACCES, "Can't remove a container")

        if self.isdir(path):
            raise IOSError(EACCES,
                           "Can't remove a directory (use rmdir instead)")

        container = self._get_container(container)
        obj = container.get_object(name)
        container.delete_object(obj)
        self._listdir_cache.flush(posixpath.dirname(path))
        return not name
예제 #13
0
파일: fs.py 프로젝트: jakop345/sftp
    def mkstemp(self, suffix='', prefix='', dir=None, mode='wb'):
        """
        A wrapper around tempfile.mkstemp creating a file with a unique name.

        Unlike mkstemp it returns an object with a file-like interface.
        """
        e = "mkstemp suffix=%r prefix=%r, dir=%r mode=%r - not implemented" % (suffix, prefix, dir, mode)
        logging.debug(e)
        raise IOSError(EPERM, 'Operation not permitted: %s' % e)
예제 #14
0
파일: fs.py 프로젝트: jakop345/sftp
    def readlink(self, path):
        """
        Return a string representing the path to which a symbolic link points.

        We never return that we have a symlink in stat, so this should
        never be called.
        """
        e = "readlink %r - not implemented" % path
        logging.debug(e)
        raise IOSError(EPERM, 'Operation not permitted: %s' % e)
예제 #15
0
파일: fs.py 프로젝트: jakop345/sftp
    def md5(self, path):
        """
        Return the object MD5 for path.

        Raise OSError on error.
        """
        path = self.abspath(path)
        logging.debug("md5 %r" % path)
        container, name = parse_fspath(path)

        if not name:
            raise IOSError(EACCES, "Can't return the MD5 of a container")

        if self.isdir(path):
            # this is only 100% accurate for virtual directories
            raise IOSError(EACCES, "Can't return the MD5 of a directory")

        meta = self.conn.head_object(container, name)
        return meta["etag"]
예제 #16
0
파일: fs.py 프로젝트: jakop345/sftp
 def close(self):
     """Close the object and finish the data transfer."""
     if 'r' in self.mode:
         return
     if self.pending_copy_task:
         logging.debug("waiting for a pending copy task...")
         self.pending_copy_task.join()
         logging.debug("wait is over")
         if self.pending_copy_task.exitcode != 0:
             raise IOSError(EIO, 'Failed to store the file')
     if self.obj is not None:
         self.obj.finish_chunk()
예제 #17
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
    def stat(self, path):
        '''Returns an os.stat_result for path or raises IOSError

        Returns the information from the cache if possible
        '''
        path = path.rstrip("/") or "/"
        logging.debug("stat path %r" % (path))
        directory, leaf = posixpath.split(path)
        # Refresh the cache it if is old, or wrong
        if not self.valid(directory):
            logging.debug("invalid cache for %r (path: %r)" %
                          (directory, self.path))
            self.listdir(directory)
        if path != "/":
            try:
                stat_info = self.cache[leaf]
            except KeyError:
                logging.debug("Didn't find %r in directory listing" % leaf)
                # it can be a container and the user doesn't have
                # permissions to list the root
                if directory == '/' and leaf:
                    try:
                        container = self.cffs.connection.get_container(leaf)
                    except cloudfiles.errors.ResponseError:
                        raise IOSError(ENOENT,
                                       'No such file or directory %s' % leaf)

                    logging.debug(
                        "Accessing %r container without root listing" % leaf)
                    stat_info = self._make_stat(count=container.object_count,
                                                bytes=container.size_used)
                else:
                    raise IOSError(ENOENT,
                                   'No such file or directory %s' % leaf)
        else:
            # Root directory size is sum of containers, count is containers
            bytes = sum(stat_info.st_size for stat_info in self.cache.values())
            count = len(self.cache)
            stat_info = self._make_stat(count=count, bytes=bytes)
        return stat_info
예제 #18
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
def parse_fspath(path):
    '''Returns a (container, path) tuple. For shorter paths
    replaces not provided values with empty strings.
    May raise IOSError for invalid paths
    '''
    if not path.startswith('/'):
        logging.warning(
            'parse_fspath: You have to provide an absolute path: %r' % path)
        raise IOSError(ENOENT, 'Absolute path needed')
    parts = path.split('/', 2)[1:]
    while len(parts) < 2:
        parts.append('')
    return tuple(parts)
예제 #19
0
파일: fs.py 프로젝트: jakop345/sftp
    def seek(self, offset, whence=None):
        """
        Seek in the object.

        It's supported only for read operations because of object storage limitations.
        """
        logging.debug("seek offset=%s, whence=%s" % (str(offset), str(whence)))

        if 'r' in self.mode:

            if self.size is None:
                meta = self.conn.head_object(self.container, self.name)
                try:
                    self.size = int(meta["content-length"])
                except ValueError:
                    raise IOSError(EPERM, "Invalid file size")

            if not whence:
                offs = offset
            elif whence == 1:
                offs = self.total_size + offset
            elif whence == 2:
                offs = self.size - offset
            else:
                raise IOSError(EPERM, "Invalid file offset")

            if offs < 0 or offs > self.size:
                raise IOSError(EPERM, "Invalid file offset")

            # we need to start over after a seek call
            if self.obj is not None:
                del self.obj # GC the generator
                self.obj = None
            self.total_size = offs
        else:
            raise IOSError(EPERM, "Seek not available for write operations")
예제 #20
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
def translate_cloudfiles_error(fn):
    """
    Decorator to catch cloudfiles errors and translating them into IOSError.

    Other exceptions are not caught.
    """
    @wraps(fn)
    def wrapper(*args, **kwargs):
        name = getattr(fn, "func_name", "unknown")
        log = lambda msg: logging.warning("At %s: %s" % (name, msg))
        try:
            return fn(*args, **kwargs)
        except (cloudfiles.errors.NoSuchContainer,
                cloudfiles.errors.NoSuchObject), e:
            raise IOSError(ENOENT, 'Not found: %s' % e)
        except cloudfiles.errors.ContainerNotEmpty, e:
            raise IOSError(ENOTEMPTY, 'Directory not empty: %s' % e)
예제 #21
0
파일: fs.py 프로젝트: jakop345/sftp
    def wrapper(*args,**kwargs):
        name = getattr(fn, "func_name", "unknown")
        log = lambda msg: logging.debug("At %s: %s" % (name, msg))
        try:
            return fn(*args, **kwargs)
        except ClientException, e:
            # some errno mapping
            if e.http_status == 404:
                err = ENOENT
            elif e.http_status == 400:
                err = EPERM
            elif e.http_status == 403:
                err = EACCES
            else:
                err = EIO

            msg = "%s: %s" % (smart_str(e.msg), smart_str(e.http_reason))
            log(msg)
            raise IOSError(err, msg)
예제 #22
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
    def __init__(self, cffs, container, obj, mode):
        self.cffs = cffs
        self.container = container
        self.name = obj
        self.mode = mode
        self.closed = False
        self.total_size = 0
        self.stream = None

        if not all([container, obj]):
            self.closed = True
            raise IOSError(EPERM, 'Container and object required')

        self.container = self.cffs._get_container(self.container)

        if 'r' in self.mode:
            self.obj = self.container.get_object(self.name)
            logging.debug("read fd obj.name=%r obj.size=%r" %
                          (self.obj.name, self.obj.size))
        else:  #write
            self.obj = ChunkObject(self.container, obj)
            self.obj.content_type = mimetypes.guess_type(obj)[0]
            self.obj.prepare_chunk()
예제 #23
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
 def write(self, data):
     '''Write data to the object'''
     if 'r' in self.mode:
         raise IOSError(EPERM, "Can't write to stream opened for read")
     self.obj.send_chunk(data)
예제 #24
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
 def seek(self, *kargs, **kwargs):
     '''Seek in the object: FIXME doesn't work and raises an error'''
     logging.debug("seek args=%s, kargs=%s" % (str(kargs), str(kwargs)))
     raise IOSError(EPERM, "Seek not implemented")
예제 #25
0
파일: fs.py 프로젝트: jakop345/sftp
 def chmod(self, path, mode):
     """Change file/directory mode"""
     e = "chmod %03o %r - not implemented" % (mode, path)
     logging.debug(e)
     raise IOSError(EPERM, 'Operation not permitted: %s' % e)
예제 #26
0
파일: fs.py 프로젝트: benroeder/ftp-cloudfs
 """
 @wraps(fn)
 def wrapper(*args, **kwargs):
     name = getattr(fn, "func_name", "unknown")
     log = lambda msg: logging.warning("At %s: %s" % (name, msg))
     try:
         return fn(*args, **kwargs)
     except (cloudfiles.errors.NoSuchContainer,
             cloudfiles.errors.NoSuchObject), e:
         raise IOSError(ENOENT, 'Not found: %s' % e)
     except cloudfiles.errors.ContainerNotEmpty, e:
         raise IOSError(ENOTEMPTY, 'Directory not empty: %s' % e)
     except cloudfiles.errors.ResponseError, e:
         log("Response error: %s" % e)
         # FIXME make some attempt to raise different errors on e.status
         raise IOSError(EPERM, 'Operation not permitted: %s' % e)
     except (cloudfiles.errors.AuthenticationError,
             cloudfiles.errors.AuthenticationFailed,
             cloudfiles.errors.ContainerNotPublic), e:
         log("Authentication error: %s" % e)
         raise IOSError(EPERM, 'Operation not permitted: %s' % e)
     except (cloudfiles.errors.CDNNotEnabled,
             cloudfiles.errors.IncompleteSend,
             cloudfiles.errors.InvalidContainerName,
             cloudfiles.errors.InvalidMetaName,
             cloudfiles.errors.InvalidMetaValue,
             cloudfiles.errors.InvalidObjectName,
             cloudfiles.errors.InvalidObjectSize,
             cloudfiles.errors.InvalidUrl), e:
         log("Unexpected cloudfiles error: %s" % e)
         raise IOSError(EIO, 'Unexpected cloudfiles error')