1from notmuch2 import _capi as capi
2
3
4class NotmuchError(Exception):
5    """Base exception for errors originating from the notmuch library.
6
7    Usually this will have two attributes:
8
9    :status: This is a numeric status code corresponding to the error
10       code in the notmuch library.  This is normally fairly
11       meaningless, it can also often be ``None``.  This exists mostly
12       to easily create new errors from notmuch status codes and
13       should not normally be used by users.
14
15    :message: A user-facing message for the error.  This can
16       occasionally also be ``None``.  Usually you'll want to call
17       ``str()`` on the error object instead to get a sensible
18       message.
19    """
20
21    @classmethod
22    def exc_type(cls, status):
23        """Return correct exception type for notmuch status."""
24        types = {
25            capi.lib.NOTMUCH_STATUS_OUT_OF_MEMORY:
26                OutOfMemoryError,
27            capi.lib.NOTMUCH_STATUS_READ_ONLY_DATABASE:
28                ReadOnlyDatabaseError,
29            capi.lib.NOTMUCH_STATUS_XAPIAN_EXCEPTION:
30                XapianError,
31            capi.lib.NOTMUCH_STATUS_FILE_ERROR:
32                FileError,
33            capi.lib.NOTMUCH_STATUS_FILE_NOT_EMAIL:
34                FileNotEmailError,
35            capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
36                DuplicateMessageIdError,
37            capi.lib.NOTMUCH_STATUS_NULL_POINTER:
38                NullPointerError,
39            capi.lib.NOTMUCH_STATUS_TAG_TOO_LONG:
40                TagTooLongError,
41            capi.lib.NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
42                UnbalancedFreezeThawError,
43            capi.lib.NOTMUCH_STATUS_UNBALANCED_ATOMIC:
44                UnbalancedAtomicError,
45            capi.lib.NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
46                UnsupportedOperationError,
47            capi.lib.NOTMUCH_STATUS_UPGRADE_REQUIRED:
48                UpgradeRequiredError,
49            capi.lib.NOTMUCH_STATUS_PATH_ERROR:
50                PathError,
51            capi.lib.NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
52                IllegalArgumentError,
53            capi.lib.NOTMUCH_STATUS_NO_CONFIG:
54                NoConfigError,
55            capi.lib.NOTMUCH_STATUS_NO_DATABASE:
56                NoDatabaseError,
57            capi.lib.NOTMUCH_STATUS_DATABASE_EXISTS:
58                DatabaseExistsError,
59            capi.lib.NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
60                QuerySyntaxError,
61        }
62        return types[status]
63
64    def __new__(cls, *args, **kwargs):
65        """Return the correct subclass based on status."""
66        # This is simplistic, but the actual __init__ will fail if the
67        # signature is wrong anyway.
68        if args:
69            status = args[0]
70        else:
71            status = kwargs.get('status', None)
72        if status and cls == NotmuchError:
73            exc = cls.exc_type(status)
74            return exc.__new__(exc, *args, **kwargs)
75        else:
76            return super().__new__(cls)
77
78    def __init__(self, status=None, message=None):
79        self.status = status
80        self.message = message
81
82    def __str__(self):
83        if self.message:
84            return self.message
85        elif self.status:
86            char_str = capi.lib.notmuch_status_to_string(self.status)
87            return capi.ffi.string(char_str).decode(errors='replace')
88        else:
89            return 'Unknown error'
90
91
92class OutOfMemoryError(NotmuchError): pass
93class ReadOnlyDatabaseError(NotmuchError): pass
94class XapianError(NotmuchError): pass
95class FileError(NotmuchError): pass
96class FileNotEmailError(NotmuchError): pass
97class DuplicateMessageIdError(NotmuchError): pass
98class NullPointerError(NotmuchError): pass
99class TagTooLongError(NotmuchError): pass
100class UnbalancedFreezeThawError(NotmuchError): pass
101class UnbalancedAtomicError(NotmuchError): pass
102class UnsupportedOperationError(NotmuchError): pass
103class UpgradeRequiredError(NotmuchError): pass
104class PathError(NotmuchError): pass
105class IllegalArgumentError(NotmuchError): pass
106class NoConfigError(NotmuchError): pass
107class NoDatabaseError(NotmuchError): pass
108class DatabaseExistsError(NotmuchError): pass
109class QuerySyntaxError(NotmuchError): pass
110
111class ObjectDestroyedError(NotmuchError):
112    """The object has already been destroyed and it's memory freed.
113
114    This occurs when :meth:`destroy` has been called on the object but
115    you still happen to have access to the object.  This should not
116    normally occur since you should never call :meth:`destroy` by
117    hand.
118    """
119
120    def __str__(self):
121        if self.message:
122            return self.message
123        else:
124            return 'Memory already freed'
125