summaryrefslogtreecommitdiff
path: root/fs/ftpfs.py
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ftpfs.py')
-rw-r--r--fs/ftpfs.py214
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__":