1"""
2This file is part of notmuch.
3
4Notmuch is free software: you can redistribute it and/or modify it
5under the terms of the GNU General Public License as published by the
6Free Software Foundation, either version 3 of the License, or (at your
7option) any later version.
8
9Notmuch is distributed in the hope that it will be useful, but WITHOUT
10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12for more details.
13
14You should have received a copy of the GNU General Public License
15along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
16
17Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
18"""
19
20from ctypes import c_char_p, c_int
21
22from .globals import (
23    nmlib,
24    Enum,
25    Python3StringMixIn,
26)
27
28class Status(Enum):
29    """Enum with a string representation of a notmuch_status_t value."""
30    _status2str = nmlib.notmuch_status_to_string
31    _status2str.restype = c_char_p
32    _status2str.argtypes = [c_int]
33
34    def __init__(self, statuslist):
35        """It is initialized with a list of strings that are available as
36        Status().string1 - Status().stringn attributes.
37        """
38        super(Status, self).__init__(statuslist)
39
40    @classmethod
41    def status2str(self, status):
42        """Get a (unicode) string representation of a notmuch_status_t value."""
43        # define strings for custom error messages
44        if status == STATUS.NOT_INITIALIZED:
45            return "Operation on uninitialized object impossible."
46        return unicode(Status._status2str(status))
47
48STATUS = Status(['SUCCESS',
49  'OUT_OF_MEMORY',
50  'READ_ONLY_DATABASE',
51  'XAPIAN_EXCEPTION',
52  'FILE_ERROR',
53  'FILE_NOT_EMAIL',
54  'DUPLICATE_MESSAGE_ID',
55  'NULL_POINTER',
56  'TAG_TOO_LONG',
57  'UNBALANCED_FREEZE_THAW',
58  'UNBALANCED_ATOMIC',
59  'UNSUPPORTED_OPERATION',
60  'UPGRADE_REQUIRED',
61  'PATH_ERROR',
62  'NOT_INITIALIZED'])
63"""STATUS is a class, whose attributes provide constants that serve as return
64indicators for notmuch functions. Currently the following ones are defined. For
65possible return values and specific meaning for each method, see the method
66description.
67
68  * SUCCESS
69  * OUT_OF_MEMORY
70  * READ_ONLY_DATABASE
71  * XAPIAN_EXCEPTION
72  * FILE_ERROR
73  * FILE_NOT_EMAIL
74  * DUPLICATE_MESSAGE_ID
75  * NULL_POINTER
76  * TAG_TOO_LONG
77  * UNBALANCED_FREEZE_THAW
78  * UNBALANCED_ATOMIC
79  * UNSUPPORTED_OPERATION
80  * UPGRADE_REQUIRED
81  * PATH_ERROR
82  * NOT_INITIALIZED
83
84Invoke the class method `notmuch.STATUS.status2str` with a status value as
85argument to receive a human readable string"""
86STATUS.__name__ = 'STATUS'
87
88
89class NotmuchError(Exception, Python3StringMixIn):
90    """Is initiated with a (notmuch.STATUS[, message=None]). It will not
91    return an instance of the class NotmuchError, but a derived instance
92    of a more specific Error Message, e.g. OutOfMemoryError. Each status
93    but SUCCESS has a corresponding subclassed Exception."""
94
95    @classmethod
96    def get_exc_subclass(cls, status):
97        """Returns a fine grained Exception() type,
98        detailing the error status"""
99        subclasses = {
100            STATUS.OUT_OF_MEMORY: OutOfMemoryError,
101            STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
102            STATUS.XAPIAN_EXCEPTION: XapianError,
103            STATUS.FILE_ERROR: FileError,
104            STATUS.FILE_NOT_EMAIL: FileNotEmailError,
105            STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
106            STATUS.NULL_POINTER: NullPointerError,
107            STATUS.TAG_TOO_LONG: TagTooLongError,
108            STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
109            STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
110            STATUS.UNSUPPORTED_OPERATION: UnsupportedOperationError,
111            STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
112            STATUS.PATH_ERROR: PathError,
113            STATUS.NOT_INITIALIZED: NotInitializedError,
114        }
115        assert 0 < status <= len(subclasses)
116        return subclasses[status]
117
118    def __new__(cls, *args, **kwargs):
119        """Return a correct subclass of NotmuchError if needed
120
121        We return a NotmuchError instance if status is None (or 0) and a
122        subclass that inherits from NotmuchError depending on the
123        'status' parameter otherwise."""
124        # get 'status'. Passed in as arg or kwarg?
125        status = args[0] if len(args) else kwargs.get('status', None)
126        # no 'status' or cls is subclass already, return 'cls' instance
127        if not status or cls != NotmuchError:
128            return super(NotmuchError, cls).__new__(cls)
129        subclass = cls.get_exc_subclass(status)  # which class to use?
130        return subclass.__new__(subclass, *args, **kwargs)
131
132    def __init__(self, status=None, message=None):
133        self.status = status
134        self.message = message
135
136    def __unicode__(self):
137        if self.message is not None:
138            return self.message
139        elif self.status is not None:
140            return STATUS.status2str(self.status)
141        else:
142            return 'Unknown error'
143
144
145# List of Subclassed exceptions that correspond to STATUS values and are
146# subclasses of NotmuchError.
147class OutOfMemoryError(NotmuchError):
148    status = STATUS.OUT_OF_MEMORY
149
150
151class ReadOnlyDatabaseError(NotmuchError):
152    status = STATUS.READ_ONLY_DATABASE
153
154
155class XapianError(NotmuchError):
156    status = STATUS.XAPIAN_EXCEPTION
157
158
159class FileError(NotmuchError):
160    status = STATUS.FILE_ERROR
161
162
163class FileNotEmailError(NotmuchError):
164    status = STATUS.FILE_NOT_EMAIL
165
166
167class DuplicateMessageIdError(NotmuchError):
168    status = STATUS.DUPLICATE_MESSAGE_ID
169
170
171class NullPointerError(NotmuchError):
172    status = STATUS.NULL_POINTER
173
174
175class TagTooLongError(NotmuchError):
176    status = STATUS.TAG_TOO_LONG
177
178
179class UnbalancedFreezeThawError(NotmuchError):
180    status = STATUS.UNBALANCED_FREEZE_THAW
181
182
183class UnbalancedAtomicError(NotmuchError):
184    status = STATUS.UNBALANCED_ATOMIC
185
186
187class UnsupportedOperationError(NotmuchError):
188    status = STATUS.UNSUPPORTED_OPERATION
189
190
191class UpgradeRequiredError(NotmuchError):
192    status = STATUS.UPGRADE_REQUIRED
193
194
195class PathError(NotmuchError):
196    status = STATUS.PATH_ERROR
197
198
199class NotInitializedError(NotmuchError):
200    """Derived from NotmuchError, this occurs if the underlying data
201    structure (e.g. database is not initialized (yet) or an iterator has
202    been exhausted. You can test for NotmuchError with .status =
203    STATUS.NOT_INITIALIZED"""
204    status = STATUS.NOT_INITIALIZED
205