1"""
2Defines the Exception classes thrown by PyFilesystem objects. Exceptions relating
3to the underlying filesystem are translated in to one of the following Exceptions.
4Exceptions that relate to a path store that path in `self.path`.
5
6All Exception classes are derived from `FSError` which can be used as a
7catch-all exception.
8
9"""
10
11__all__ = ['FSError',
12           'CreateFailedError',
13           'PathError',
14           'InvalidPathError',
15           'InvalidCharsInPathError',
16           'OperationFailedError',
17           'UnsupportedError',
18           'RemoteConnectionError',
19           'StorageSpaceError',
20           'PermissionDeniedError',
21           'FSClosedError',
22           'OperationTimeoutError',
23           'RemoveRootError',
24           'ResourceError',
25           'NoSysPathError',
26           'NoMetaError',
27           'NoPathURLError',
28           'ResourceNotFoundError',
29           'ResourceInvalidError',
30           'DestinationExistsError',
31           'DirectoryNotEmptyError',
32           'ParentDirectoryMissingError',
33           'ResourceLockedError',
34           'NoMMapError',
35           'BackReferenceError',
36           'convert_fs_errors',
37           'convert_os_errors',
38           ]
39
40import sys
41import errno
42import six
43
44from fs.path import *
45from fs.local_functools import wraps
46
47
48class FSError(Exception):
49    """Base exception class for the FS module."""
50    default_message = "Unspecified error"
51
52    def __init__(self,msg=None,details=None):
53        if msg is None:
54            msg = self.default_message
55        self.msg = msg
56        self.details = details
57
58    def __str__(self):
59        keys = {}
60        for k,v in self.__dict__.iteritems():
61            if isinstance(v,unicode):
62                v = v.encode(sys.getfilesystemencoding())
63            keys[k] = v
64        return str(self.msg % keys)
65
66    def __unicode__(self):
67        keys = {}
68        for k,v in self.__dict__.iteritems():
69            if isinstance(v, six.binary_type):
70                v = v.decode(sys.getfilesystemencoding(), 'replace')
71            keys[k] = v
72        return unicode(self.msg, encoding=sys.getfilesystemencoding(), errors='replace') % keys
73
74    def __reduce__(self):
75        return (self.__class__,(),self.__dict__.copy(),)
76
77
78class CreateFailedError(FSError):
79    """An exception thrown when a FS could not be created"""
80    default_message = "Unable to create filesystem"
81
82
83class PathError(FSError):
84    """Exception for errors to do with a path string.
85    """
86    default_message = "Path is invalid: %(path)s"
87
88    def __init__(self,path="",**kwds):
89        self.path = path
90        super(PathError,self).__init__(**kwds)
91
92
93class InvalidPathError(PathError):
94    """Base exception for fs paths that can't be mapped on to the underlaying filesystem."""
95    default_message = "Path is invalid on this filesystem %(path)s"
96
97
98class InvalidCharsInPathError(InvalidPathError):
99    """The path contains characters that are invalid on this filesystem"""
100    default_message = "Path contains invalid characters: %(path)s"
101
102
103class OperationFailedError(FSError):
104    """Base exception class for errors associated with a specific operation."""
105    default_message = "Unable to %(opname)s: unspecified error [%(errno)s - %(details)s]"
106
107    def __init__(self,opname="",path=None,**kwds):
108        self.opname = opname
109        self.path = path
110        self.errno = getattr(kwds.get("details",None),"errno",None)
111        super(OperationFailedError,self).__init__(**kwds)
112
113
114class UnsupportedError(OperationFailedError):
115    """Exception raised for operations that are not supported by the FS."""
116    default_message = "Unable to %(opname)s: not supported by this filesystem"
117
118
119class RemoteConnectionError(OperationFailedError):
120    """Exception raised when operations encounter remote connection trouble."""
121    default_message = "%(opname)s: remote connection errror"
122
123
124class StorageSpaceError(OperationFailedError):
125    """Exception raised when operations encounter storage space trouble."""
126    default_message = "Unable to %(opname)s: insufficient storage space"
127
128
129class PermissionDeniedError(OperationFailedError):
130    default_message = "Unable to %(opname)s: permission denied"
131
132
133class FSClosedError(OperationFailedError):
134    default_message = "Unable to %(opname)s: the FS has been closed"
135
136
137class OperationTimeoutError(OperationFailedError):
138    default_message = "Unable to %(opname)s: operation timed out"
139
140
141class RemoveRootError(OperationFailedError):
142    default_message = "Can't remove root dir"
143
144
145class ResourceError(FSError):
146    """Base exception class for error associated with a specific resource."""
147    default_message = "Unspecified resource error: %(path)s"
148
149    def __init__(self,path="",**kwds):
150        self.path = path
151        self.opname = kwds.pop("opname",None)
152        super(ResourceError,self).__init__(**kwds)
153
154
155class NoSysPathError(ResourceError):
156    """Exception raised when there is no syspath for a given path."""
157    default_message = "No mapping to OS filesystem: %(path)s"
158
159
160class NoMetaError(FSError):
161    """Exception raised when there is no meta value available."""
162    default_message = "No meta value named '%(meta_name)s' could be retrieved"
163    def __init__(self, meta_name, msg=None):
164        self.meta_name = meta_name
165        super(NoMetaError, self).__init__(msg)
166    def __reduce__(self):
167        return (self.__class__,(self.meta_name,),self.__dict__.copy(),)
168
169
170class NoPathURLError(ResourceError):
171    """Exception raised when there is no URL form for a given path."""
172    default_message = "No URL form: %(path)s"
173
174
175class ResourceNotFoundError(ResourceError):
176    """Exception raised when a required resource is not found."""
177    default_message = "Resource not found: %(path)s"
178
179
180class ResourceInvalidError(ResourceError):
181    """Exception raised when a resource is the wrong type."""
182    default_message = "Resource is invalid: %(path)s"
183
184
185class DestinationExistsError(ResourceError):
186    """Exception raised when a target destination already exists."""
187    default_message = "Destination exists: %(path)s"
188
189
190class DirectoryNotEmptyError(ResourceError):
191    """Exception raised when a directory to be removed is not empty."""
192    default_message = "Directory is not empty: %(path)s"
193
194
195class ParentDirectoryMissingError(ResourceError):
196    """Exception raised when a parent directory is missing."""
197    default_message = "Parent directory is missing: %(path)s"
198
199
200class ResourceLockedError(ResourceError):
201    """Exception raised when a resource can't be used because it is locked."""
202    default_message = "Resource is locked: %(path)s"
203
204
205class NoMMapError(ResourceError):
206    """Exception raise when getmmap fails to create a mmap"""
207    default_message = "Can't get mmap for %(path)s"
208
209
210class BackReferenceError(ValueError):
211    """Exception raised when too many backrefs exist in a path (ex: '/..', '/docs/../..')."""
212
213
214def convert_fs_errors(func):
215    """Function wrapper to convert FSError instances into OSError."""
216    @wraps(func)
217    def wrapper(*args,**kwds):
218        try:
219            return func(*args,**kwds)
220        except ResourceNotFoundError, e:
221            raise OSError(errno.ENOENT,str(e))
222        except ParentDirectoryMissingError, e:
223            if sys.platform == "win32":
224                raise OSError(errno.ESRCH,str(e))
225            else:
226                raise OSError(errno.ENOENT,str(e))
227        except ResourceInvalidError, e:
228            raise OSError(errno.EINVAL,str(e))
229        except PermissionDeniedError, e:
230            raise OSError(errno.EACCES,str(e))
231        except ResourceLockedError, e:
232            if sys.platform == "win32":
233                raise WindowsError(32,str(e))
234            else:
235                raise OSError(errno.EACCES,str(e))
236        except DirectoryNotEmptyError, e:
237            raise OSError(errno.ENOTEMPTY,str(e))
238        except DestinationExistsError, e:
239            raise OSError(errno.EEXIST,str(e))
240        except StorageSpaceError, e:
241            raise OSError(errno.ENOSPC,str(e))
242        except RemoteConnectionError, e:
243            raise OSError(errno.ENETDOWN,str(e))
244        except UnsupportedError, e:
245            raise OSError(errno.ENOSYS,str(e))
246        except FSError, e:
247            raise OSError(errno.EFAULT,str(e))
248    return wrapper
249
250
251def convert_os_errors(func):
252    """Function wrapper to convert OSError/IOError instances into FSError."""
253    opname = func.__name__
254    @wraps(func)
255    def wrapper(self,*args,**kwds):
256        try:
257            return func(self,*args,**kwds)
258        except (OSError,IOError), e:
259            (exc_type,exc_inst,tb) = sys.exc_info()
260            path = getattr(e,"filename",None)
261            if path and path[0] == "/" and hasattr(self,"root_path"):
262                path = normpath(path)
263                if isprefix(self.root_path,path):
264                    path = path[len(self.root_path):]
265            if not hasattr(e,"errno") or not e.errno:
266                raise OperationFailedError(opname,details=e),None,tb
267            if e.errno == errno.ENOENT:
268                raise ResourceNotFoundError(path,opname=opname,details=e),None,tb
269            if e.errno == errno.EFAULT:
270                # This can happen when listdir a directory that is deleted by another thread
271                # Best to interpret it as a resource not found
272                raise ResourceNotFoundError(path,opname=opname,details=e),None,tb
273            if e.errno == errno.ESRCH:
274                raise ResourceNotFoundError(path,opname=opname,details=e),None,tb
275            if e.errno == errno.ENOTEMPTY:
276                raise DirectoryNotEmptyError(path,opname=opname,details=e),None,tb
277            if e.errno == errno.EEXIST:
278                raise DestinationExistsError(path,opname=opname,details=e),None,tb
279            if e.errno == 183: # some sort of win32 equivalent to EEXIST
280                raise DestinationExistsError(path,opname=opname,details=e),None,tb
281            if e.errno == errno.ENOTDIR:
282                raise ResourceInvalidError(path,opname=opname,details=e),None,tb
283            if e.errno == errno.EISDIR:
284                raise ResourceInvalidError(path,opname=opname,details=e),None,tb
285            if e.errno == errno.EINVAL:
286                raise ResourceInvalidError(path,opname=opname,details=e),None,tb
287            if e.errno == errno.ENOSPC:
288                raise StorageSpaceError(opname,path=path,details=e),None,tb
289            if e.errno == errno.EPERM:
290                raise PermissionDeniedError(opname,path=path,details=e),None,tb
291            if hasattr(errno,"ENONET") and e.errno == errno.ENONET:
292                raise RemoteConnectionError(opname,path=path,details=e),None,tb
293            if e.errno == errno.ENETDOWN:
294                raise RemoteConnectionError(opname,path=path,details=e),None,tb
295            if e.errno == errno.ECONNRESET:
296                raise RemoteConnectionError(opname,path=path,details=e),None,tb
297            if e.errno == errno.EACCES:
298                if sys.platform == "win32":
299                    if e.args[0] and e.args[0] == 32:
300                        raise ResourceLockedError(path,opname=opname,details=e),None,tb
301                raise PermissionDeniedError(opname,details=e),None,tb
302            # Sometimes windows gives some random errors...
303            if sys.platform == "win32":
304                if e.errno in (13,):
305                    raise ResourceInvalidError(path,opname=opname,details=e),None,tb
306            if e.errno == errno.ENAMETOOLONG:
307                raise PathError(path,details=e),None,tb
308            if e.errno == errno.EOPNOTSUPP:
309                raise UnsupportedError(opname,details=e),None,tb
310            if e.errno == errno.ENOSYS:
311                raise UnsupportedError(opname,details=e),None,tb
312            raise OperationFailedError(opname,details=e),None,tb
313    return wrapper
314
315
316