diff options
author | btimby <btimby@67cdc799-7952-0410-af00-57a81ceafa0f> | 2012-04-20 20:07:40 +0000 |
---|---|---|
committer | btimby <btimby@67cdc799-7952-0410-af00-57a81ceafa0f> | 2012-04-20 20:07:40 +0000 |
commit | e0a4a12d4268ad0d32b38218228f49d587fa2f88 (patch) | |
tree | 6f812f0f8f6cfbb9ebbbc9bbeebc17322b040cc1 /fs/expose | |
parent | 03936e1d3e3dd66579045614ab389a533bada1b2 (diff) | |
download | pyfilesystem-git-e0a4a12d4268ad0d32b38218228f49d587fa2f88.tar.gz |
Added decorator to ensure pyftpdlib receives OS errors. Added configurable encoding. Added more robust stat() method (no longer relies on os.stat)
Diffstat (limited to 'fs/expose')
-rw-r--r-- | fs/expose/ftp.py | 121 |
1 files changed, 104 insertions, 17 deletions
diff --git a/fs/expose/ftp.py b/fs/expose/ftp.py index 5e3914a..0a09581 100644 --- a/fs/expose/ftp.py +++ b/fs/expose/ftp.py @@ -17,10 +17,32 @@ loopback address. import os import stat +import time from pyftpdlib import ftpserver +from fs.path import * from fs.osfs import OSFS +from fs.errors import convert_fs_errors + +# Get these once so we can reuse them: +UID = os.getuid() +GID = os.getgid() + + +class FakeStat(object): + """ + Pyftpdlib uses stat inside the library. This class emulates the standard + os.stat_result class to make pyftpdlib happy. Think of it as a stat-like + object ;-). + """ + def __init__(self, **kwargs): + for attr in dir(stat): + if not attr.startswith('ST_'): + continue + attr = attr.lower() + value = kwargs.get(attr, 0) + setattr(self, attr, value) class FTPFS(ftpserver.AbstractedFS): @@ -28,57 +50,120 @@ class FTPFS(ftpserver.AbstractedFS): The basic FTP Filesystem. This is a bridge between a pyfs filesystem and pyftpdlib's AbstractedFS. This class will cause the FTP server to service the given fs instance. """ - def __init__(self, fs, root, cmd_channel): + encoding = 'utf8' + "Sets the encoding to use for paths." + + def __init__(self, fs, root, cmd_channel, encoding=None): self.fs = fs + if encoding is not None: + self.encoding = encoding super(FTPFS, self).__init__(root, cmd_channel) def validpath(self, path): - # All paths are valid because we offload chrooting to pyfs. - return True + try: + normpath(path) + return True + except: + return False + @convert_fs_errors def open(self, path, mode): + if not isinstance(path, unicode): + path = path.decode(self.encoding) return self.fs.open(path, mode) def chdir(self, path): - # Put the user into the requested directory, again, all paths - # are valid. self._cwd = self.ftp2fs(path) + @convert_fs_errors def mkdir(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) self.fs.makedir(path) + @convert_fs_errors def listdir(self, path): - return map(lambda x: x.encode('utf8'), self.fs.listdir(path)) + if not isinstance(path, unicode): + path = path.decode(self.encoding) + return map(lambda x: x.encode(self.encoding), self.fs.listdir(path)) + @convert_fs_errors def rmdir(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) self.fs.removedir(path) + @convert_fs_errors def remove(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) self.fs.remove(path) + @convert_fs_errors def rename(self, src, dst): self.fs.rename(src, dst) def chmod(self, path, mode): raise NotImplementedError() + @convert_fs_errors def stat(self, path): - # TODO: stat needs to be handled using fs.getinfo() method. - return super(FTPFS, self).stat(self.fs.getsyspath(path)) - - def lstat(self, path): - return self.stat(path) - + if not isinstance(path, unicode): + path = path.decode(self.encoding) + info = self.fs.getinfo(path) + kwargs = { + 'st_size': info.get('size'), + # Echo current user instead of 0/0. + 'st_uid': UID, + 'st_gid': GID, + } + if 'st_atime' in info: + kwargs['st_atime'] = info.get('st_atime') + elif 'accessed_time' in info: + kwargs['st_atime'] = time.mktime(info.get("accessed_time").timetuple()) + if 'st_mtime' in info: + kwargs['st_mtime'] = info.get('st_mtime') + elif 'modified_time' in info: + kwargs['st_mtime'] = time.mktime(info.get("modified_time").timetuple()) + # Pyftpdlib uses st_ctime on Windows platform, try to provide it. + if 'st_ctime' in info: + kwargs['st_ctime'] = info.get('st_ctime') + elif 'created_time' in info: + kwargs['st_ctime'] = time.mktime(info.get("created_time").timetuple()) + elif 'st_mtime' in kwargs: + # As a last resort, just copy the modified time. + kwargs['st_ctime'] = kwargs['st_mtime'] + if self.fs.isdir(path): + kwargs['st_mode'] = 0777 | stat.S_IFDIR + else: + kwargs['st_mode'] = 0777 | stat.S_IFREG + return FakeStat(**kwargs) + + # No link support... + lstat = stat + + @convert_fs_errors def isfile(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) return self.fs.isfile(path) + @convert_fs_errors def isdir(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) return self.fs.isdir(path) + @convert_fs_errors def getsize(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) return self.fs.getsize(path) + @convert_fs_errors def getmtime(self, path): + if not isinstance(path, unicode): + path = path.decode(self.encoding) return self.fs.getinfo(path).time def realpath(self, path): @@ -91,21 +176,23 @@ class FTPFS(ftpserver.AbstractedFS): class FTPFSFactory(object): """ A factory class which can hold a reference to a file system object and - later pass it along to an FTPFS instance. An instance of this object allows - multiple FTPFS instances to be created by pyftpdlib and share the same fs. + encoding, then later pass it along to an FTPFS instance. An instance of + this object allows multiple FTPFS instances to be created by pyftpdlib + while sharing the same fs. """ - def __init__(self, fs): + def __init__(self, fs, encoding=None): """ Initializes the factory with an fs instance. """ self.fs = fs + self.encoding = encoding def __call__(self, root, cmd_channel): """ This is the entry point of pyftpdlib. We will pass along the two parameters - as well as the previously provided fs instance. + as well as the previously provided fs instance and encoding. """ - return FTPFS(self.fs, root, cmd_channel) + return FTPFS(self.fs, root, cmd_channel, encoding=encoding) class HomeFTPFS(FTPFS): |