diff options
Diffstat (limited to 'fs/ftpfs.py')
-rw-r--r-- | fs/ftpfs.py | 214 |
1 files changed, 109 insertions, 105 deletions
diff --git a/fs/ftpfs.py b/fs/ftpfs.py index 50891af..2527285 100644 --- a/fs/ftpfs.py +++ b/fs/ftpfs.py @@ -8,19 +8,20 @@ FTPFS is a filesystem for accessing an FTP server (uses ftplib in standard libra __all__ = ['FTPFS'] -import sys +import sys import fs from fs.base import * from fs.errors import * from fs.path import pathsplit, abspath, dirname, recursepath, normpath, pathjoin, isbase +from fs import iotools from ftplib import FTP, error_perm, error_temp, error_proto, error_reply try: - from ftplib import _GLOBAL_DEFAULT_TIMEOUT + from ftplib import _GLOBAL_DEFAULT_TIMEOUT except ImportError: - _GLOBAL_DEFAULT_TIMEOUT = object() + _GLOBAL_DEFAULT_TIMEOUT = object() import threading import datetime @@ -596,15 +597,15 @@ def _skip(s, i, c): return i -def fileftperrors(f): +def fileftperrors(f): @wraps(f) def deco(self, *args, **kwargs): self._lock.acquire() - try: + try: try: ret = f(self, *args, **kwargs) - except Exception, e: - self.ftpfs._translate_exception(args[0] if args else '', e) + except Exception, e: + self.ftpfs._translate_exception(args[0] if args else '', e) finally: self._lock.release() return ret @@ -620,7 +621,7 @@ class _FTPFile(object): def __init__(self, ftpfs, ftp, path, mode): if not hasattr(self, '_lock'): - self._lock = threading.RLock() + self._lock = threading.RLock() self.ftpfs = ftpfs self.ftp = ftp self.path = normpath(path) @@ -760,29 +761,29 @@ class _FTPFile(object): def truncate(self, size=None): self.ftpfs._on_file_written(self.path) # Inefficient, but I don't know how else to implement this - if size is None: + if size is None: size = self.tell() - - if self.conn is not None: + + if self.conn is not None: self.conn.close() self.close() - - read_f = None + + read_f = None try: read_f = self.ftpfs.open(self.path, 'rb') data = read_f.read(size) finally: if read_f is not None: - read_f.close() - - self.ftp = self.ftpfs._open_ftp() + read_f.close() + + self.ftp = self.ftpfs._open_ftp() self.mode = 'w' self.__init__(self.ftpfs, self.ftp, _encode(self.path), self.mode) - #self._start_file(self.mode, self.path) + #self._start_file(self.mode, self.path) self.write(data) if len(data) < size: self.write('\0' * (size - len(data))) - + @fileftperrors def close(self): @@ -800,7 +801,7 @@ class _FTPFile(object): self.ftp.close() except error_temp, error_perm: pass - self.closed = True + self.closed = True def __iter__(self): return self.next() @@ -837,7 +838,7 @@ class _FTPFile(object): yield line append(c) -def ftperrors(f): +def ftperrors(f): @wraps(f) def deco(self, *args, **kwargs): self._lock.acquire() @@ -849,33 +850,33 @@ def ftperrors(f): except Exception, e: self._translate_exception(args[0] if args else '', e) finally: - self._leave_dircache() + self._leave_dircache() finally: self._lock.release() return ret return deco -def _encode(s): +def _encode(s): if isinstance(s, unicode): return s.encode('utf-8') return s class _DirCache(dict): - def __init__(self): - super(_DirCache, self).__init__() + def __init__(self): + super(_DirCache, self).__init__() self.count = 0 - + def addref(self): self.count += 1 return self.count - + def decref(self): self.count -= 1 return self.count -class FTPFS(FS): - +class FTPFS(FS): + _meta = { 'thread_safe' : True, 'network' : True, 'virtual': False, @@ -883,7 +884,7 @@ class FTPFS(FS): 'unicode_paths' : True, 'case_insensitive_paths' : False, 'atomic.move' : True, - 'atomic.copy' : True, + 'atomic.copy' : True, 'atomic.makedir' : True, 'atomic.rename' : True, 'atomic.setcontents' : False, @@ -892,7 +893,7 @@ class FTPFS(FS): def __init__(self, host='', user='', passwd='', acct='', timeout=_GLOBAL_DEFAULT_TIMEOUT, port=21, dircache=True): """Connect to a FTP server. - + :param host: Host to connect to :param user: Username, or a blank string for anonymous :param passwd: Password, if required @@ -902,7 +903,7 @@ class FTPFS(FS): :param dircache: If True then directory information will be cached, speeding up operations such as `getinfo`, `isdir`, `isfile`, but changes to the ftp file structure will not be visible until - :meth:`~fs.ftpfs.FTPFS.clear_dircache` is called + :meth:`~fs.ftpfs.FTPFS.clear_dircache` is called """ @@ -915,28 +916,28 @@ class FTPFS(FS): self.acct = acct self.timeout = timeout self.default_timeout = timeout is _GLOBAL_DEFAULT_TIMEOUT - self.use_dircache = dircache + self.use_dircache = dircache self.use_mlst = False self._lock = threading.RLock() self._init_dircache() - + self._cache_hint = False - try: + try: self.ftp except FSError: self.closed = True raise - def _init_dircache(self): + def _init_dircache(self): self.dircache = _DirCache() @synchronize - def cache_hint(self, enabled): + def cache_hint(self, enabled): self._cache_hint = bool(enabled) def _enter_dircache(self): - self.dircache.addref() + self.dircache.addref() def _leave_dircache(self): self.dircache.decref() @@ -945,8 +946,8 @@ class FTPFS(FS): self.clear_dircache() else: self.clear_dircache() - assert self.dircache.count >= 0, "dircache count should never be negative" - + assert self.dircache.count >= 0, "dircache count should never be negative" + @synchronize def _on_file_written(self, path): self.refresh_dircache(dirname(path)) @@ -979,7 +980,7 @@ class FTPFS(FS): def on_line(line): if not isinstance(line, unicode): - line = line.decode('utf-8') + line = line.decode('utf-8') info = parse_ftp_list_line(line, self.use_mlst) if info: info = info.__dict__ @@ -1027,24 +1028,24 @@ class FTPFS(FS): :param path: Path of directory to clear cache for, or all directories if None (the default) - """ + """ if not paths: self.dircache.clear() - else: + else: dircache = self.dircache paths = [normpath(abspath(path)) for path in paths] - for cached_path in dircache.keys(): + for cached_path in dircache.keys(): for path in paths: if isbase(cached_path, path): dircache.pop(cached_path, None) break - + @synchronize - def refresh_dircache(self, *paths): + def refresh_dircache(self, *paths): for path in paths: path = abspath(normpath(path)) - self.dircache.pop(path, None) + self.dircache.pop(path, None) @synchronize def _check_path(self, path): @@ -1065,20 +1066,20 @@ class FTPFS(FS): @ftperrors def get_ftp(self): if self.closed: - return None + return None if not getattr(self, '_ftp', None): self._ftp = self._open_ftp() - return self._ftp - + return self._ftp + ftp = property(get_ftp) @ftperrors def _open_ftp(self): try: - ftp = FTP() + ftp = FTP() if self.default_timeout or sys.version_info < (2,6,): ftp.connect(self.host, self.port) - else: + else: ftp.connect(self.host, self.port, self.timeout) ftp.login(self.user, self.passwd, self.acct) except socket_error, e: @@ -1086,13 +1087,13 @@ class FTPFS(FS): return ftp def __getstate__(self): - state = super(FTPFS, self).__getstate__() + state = super(FTPFS, self).__getstate__() del state['_lock'] state.pop('_ftp', None) return state def __setstate__(self,state): - super(FTPFS, self).__setstate__(state) + super(FTPFS, self).__setstate__(state) self._init_dircache() self._lock = threading.RLock() #self._ftp = None @@ -1127,7 +1128,7 @@ class FTPFS(FS): code, message = str(exception).split(' ', 1) code = int(code) if code == 550: - pass + pass if code == 552: raise StorageSpaceError raise PermissionDeniedError(str(exception), path=path, msg="FTP error: %s" % str(exception), details=exception) @@ -1140,53 +1141,56 @@ class FTPFS(FS): try: self.ftp.close() except FSError: - pass + pass self.closed = True def getpathurl(self, path, allow_none=False): path = normpath(path) - credentials = '%s:%s' % (self.user, self.passwd) - if credentials == ':': + credentials = '%s:%s' % (self.user, self.passwd) + if credentials == ':': url = 'ftp://%s%s' % (self.host.rstrip('/'), abspath(path)) else: - url = 'ftp://%s@%s%s' % (credentials, self.host.rstrip('/'), abspath(path)) - return url + url = 'ftp://%s@%s%s' % (credentials, self.host.rstrip('/'), abspath(path)) + return url + @iotools.filelike_to_stream @ftperrors - def open(self, path, mode='r'): + def open(self, path, mode, buffering=-1, encoding=None, errors=None, newline=None, line_buffering=False, **kwargs): path = normpath(path) - mode = mode.lower() + mode = mode.lower() if self.isdir(path): - raise ResourceInvalidError(path) + raise ResourceInvalidError(path) if 'r' in mode or 'a' in mode: if not self.isfile(path): raise ResourceNotFoundError(path) if 'w' in mode or 'a' in mode or '+' in mode: self.refresh_dircache(dirname(path)) ftp = self._open_ftp() - f = _FTPFile(self, ftp, normpath(path), mode) - return f - + f = _FTPFile(self, ftp, normpath(path), mode) + return f + @ftperrors - def setcontents(self, path, data, chunk_size=1024*64): - path = normpath(path) - if isinstance(data, basestring): - data = StringIO(data) - self.refresh_dircache(dirname(path)) - self.ftp.storbinary('STOR %s' % _encode(path), data, blocksize=chunk_size) - + def setcontents(self, path, data=b'', encoding=None, errors=None, chunk_size=1024*64): + path = normpath(path) + data = iotools.make_bytes_io(data, encoding=encoding, errors=errors) + self.refresh_dircache(dirname(path)) + self.ftp.storbinary('STOR %s' % _encode(path), data, blocksize=chunk_size) + @ftperrors - def getcontents(self, path, mode="rb"): - path = normpath(path) - contents = StringIO() - self.ftp.retrbinary('RETR %s' % _encode(path), contents.write, blocksize=1024*64) - return contents.getvalue() + def getcontents(self, path, mode="rb", encoding=None, errors=None, newline=None): + path = normpath(path) + contents = StringIO() + self.ftp.retrbinary('RETR %s' % _encode(path), contents.write, blocksize=1024*64) + data = contents.getvalue() + if 'b' in data: + return data + return iotools.decode_binary(data, encoding=encoding, errors=errors) @ftperrors def exists(self, path): path = normpath(path) if path in ('', '/'): - return True + return True dirlist, fname = self._get_dirlist(path) return fname in dirlist @@ -1213,7 +1217,7 @@ class FTPFS(FS): return not info['try_cwd'] @ftperrors - def listdir(self, path="./", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): + def listdir(self, path="./", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): path = normpath(path) #self.clear_dircache(path) if not self.exists(path): @@ -1242,7 +1246,7 @@ class FTPFS(FS): return {} return [(p, getinfo(p)) - for p in self.listdir(path, + for p in self.listdir(path, wildcard=wildcard, full=full, absolute=absolute, @@ -1281,20 +1285,20 @@ class FTPFS(FS): if self.isfile(path): raise ResourceInvalidError(path) raise DestinationExistsError(path) - checkdir(path) + checkdir(path) @ftperrors - def remove(self, path): + def remove(self, path): if not self.exists(path): raise ResourceNotFoundError(path) if not self.isfile(path): raise ResourceInvalidError(path) - self.refresh_dircache(dirname(path)) - self.ftp.delete(_encode(path)) + self.refresh_dircache(dirname(path)) + self.ftp.delete(_encode(path)) @ftperrors def removedir(self, path, recursive=False, force=False): - path = abspath(normpath(path)) + path = abspath(normpath(path)) if not self.exists(path): raise ResourceNotFoundError(path) if self.isfile(path): @@ -1328,10 +1332,10 @@ class FTPFS(FS): self.clear_dircache(dirname(path), path) @ftperrors - def rename(self, src, dst): + def rename(self, src, dst): try: self.refresh_dircache(dirname(src), dirname(dst)) - self.ftp.rename(_encode(src), _encode(dst)) + self.ftp.rename(_encode(src), _encode(dst)) except error_perm, exception: code, message = str(exception).split(' ', 1) if code == "550": @@ -1339,7 +1343,7 @@ class FTPFS(FS): raise ParentDirectoryMissingError(dst) raise except error_reply: - pass + pass @ftperrors def getinfo(self, path): @@ -1386,46 +1390,46 @@ class FTPFS(FS): def move(self, src, dst, overwrite=False, chunk_size=16384): if not overwrite and self.exists(dst): raise DestinationExistsError(dst) - #self.refresh_dircache(dirname(src), dirname(dst)) + #self.refresh_dircache(dirname(src), dirname(dst)) try: self.rename(src, dst) except: self.copy(src, dst, overwrite=overwrite) self.remove(src) finally: - self.refresh_dircache(src, dirname(src), dst, dirname(dst)) - - @ftperrors - def copy(self, src, dst, overwrite=False, chunk_size=1024*64): + self.refresh_dircache(src, dirname(src), dst, dirname(dst)) + + @ftperrors + def copy(self, src, dst, overwrite=False, chunk_size=1024*64): if not self.isfile(src): if self.isdir(src): raise ResourceInvalidError(src, msg="Source is not a file: %(path)s") raise ResourceNotFoundError(src) if not overwrite and self.exists(dst): raise DestinationExistsError(dst) - + dst = normpath(dst) - src_file = None + src_file = None try: src_file = self.open(src, "rb") - ftp = self._open_ftp() - ftp.voidcmd('TYPE I') - ftp.storbinary('STOR %s' % _encode(normpath(dst)), src_file, blocksize=chunk_size) + ftp = self._open_ftp() + ftp.voidcmd('TYPE I') + ftp.storbinary('STOR %s' % _encode(normpath(dst)), src_file, blocksize=chunk_size) finally: - self.refresh_dircache(dirname(dst)) + self.refresh_dircache(dirname(dst)) if src_file is not None: - src_file.close() - + src_file.close() + @ftperrors def movedir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): - self.clear_dircache(dirname(src), dirname(dst)) - super(FTPFS, self).movedir(src, dst, overwrite, ignore_errors, chunk_size) + self.clear_dircache(dirname(src), dirname(dst)) + super(FTPFS, self).movedir(src, dst, overwrite, ignore_errors, chunk_size) @ftperrors def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): - self.clear_dircache(dirname(dst)) - super(FTPFS, self).copydir(src, dst, overwrite, ignore_errors, chunk_size) + self.clear_dircache(dirname(dst)) + super(FTPFS, self).copydir(src, dst, overwrite, ignore_errors, chunk_size) if __name__ == "__main__": |