Python plugin: Improve error reporting during plugin init
[claws.git] / src / plugins / python / messageinfotype.c
1 /* Python plugin for Claws-Mail
2  * Copyright (C) 2009-2012 Holger Berndt
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #include "claws-features.h"
21 #endif
22
23 #include <glib/gi18n.h>
24
25 #include "messageinfotype.h"
26
27 #include "common/tags.h"
28 #include "common/defs.h"
29 #include "mainwindow.h"
30 #include "summaryview.h"
31 #include "procheader.h"
32
33 #include <structmember.h>
34
35 #include <string.h>
36
37 #define HEADER_CONTENT_SIZE BUFFSIZE
38
39 typedef struct {
40     PyObject_HEAD
41     PyObject *from;
42     PyObject *to;
43     PyObject *cc;
44     PyObject *subject;
45     PyObject *msgid;
46     PyObject *filepath;
47     MsgInfo *msginfo;
48 } clawsmail_MessageInfoObject;
49
50
51 static void MessageInfo_dealloc(clawsmail_MessageInfoObject* self)
52 {
53   Py_XDECREF(self->from);
54   Py_XDECREF(self->to);
55   Py_XDECREF(self->cc);
56   Py_XDECREF(self->subject);
57   Py_XDECREF(self->msgid);
58   self->ob_type->tp_free((PyObject*)self);
59 }
60
61 static int MessageInfo_init(clawsmail_MessageInfoObject *self, PyObject *args, PyObject *kwds)
62 {
63   Py_INCREF(Py_None);
64   self->from = Py_None;
65
66   Py_INCREF(Py_None);
67   self->to = Py_None;
68
69   Py_INCREF(Py_None);
70   self->cc = Py_None;
71
72   Py_INCREF(Py_None);
73   self->subject = Py_None;
74
75   Py_INCREF(Py_None);
76   self->msgid = Py_None;
77
78   return 0;
79 }
80
81 static PyObject* MessageInfo_str(PyObject *self)
82 {
83   PyObject *str;
84   str = PyString_FromString("MessageInfo: ");
85   PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "From"));
86   PyString_ConcatAndDel(&str, PyString_FromString(" / "));
87   PyString_ConcatAndDel(&str, PyObject_GetAttrString(self, "Subject"));
88   return str;
89 }
90
91 static PyObject *py_boolean_return_value(gboolean val)
92 {
93   if(val) {
94     Py_INCREF(Py_True);
95     return Py_True;
96   }
97   else {
98     Py_INCREF(Py_False);
99     return Py_False;
100   }
101 }
102
103 static PyObject *is_new(PyObject *self, PyObject *args)
104 {
105   return py_boolean_return_value(MSG_IS_NEW(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
106 }
107
108 static PyObject *is_unread(PyObject *self, PyObject *args)
109 {
110   return py_boolean_return_value(MSG_IS_UNREAD(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
111 }
112
113 static PyObject *is_marked(PyObject *self, PyObject *args)
114 {
115   return py_boolean_return_value(MSG_IS_MARKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
116 }
117
118 static PyObject *is_replied(PyObject *self, PyObject *args)
119 {
120   return py_boolean_return_value(MSG_IS_REPLIED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
121 }
122
123 static PyObject *is_locked(PyObject *self, PyObject *args)
124 {
125   return py_boolean_return_value(MSG_IS_LOCKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
126 }
127
128 static PyObject *is_forwarded(PyObject *self, PyObject *args)
129 {
130   return py_boolean_return_value(MSG_IS_FORWARDED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
131 }
132
133 static PyObject* get_tags(PyObject *self, PyObject *args)
134 {
135   GSList *tags_list;
136   Py_ssize_t num_tags;
137   PyObject *tags_tuple;
138
139   tags_list = ((clawsmail_MessageInfoObject*)self)->msginfo->tags;
140   num_tags = g_slist_length(tags_list);
141
142   tags_tuple = PyTuple_New(num_tags);
143   if(tags_tuple != NULL) {
144     Py_ssize_t iTag;
145     PyObject *tag_object;
146     GSList *walk;
147
148     iTag = 0;
149     for(walk = tags_list; walk; walk = walk->next) {
150       tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
151       if(tag_object == NULL) {
152         Py_DECREF(tags_tuple);
153         return NULL;
154       }
155       PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
156     }
157   }
158
159   return tags_tuple;
160 }
161
162
163 static PyObject* add_or_remove_tag(PyObject *self, PyObject *args, gboolean add)
164 {
165   int retval;
166   const char *tag_str;
167   gint tag_id;
168   MsgInfo *msginfo;
169   MainWindow *mainwin;
170
171   retval = PyArg_ParseTuple(args, "s", &tag_str);
172   if(!retval)
173     return NULL;
174
175   tag_id = tags_get_id_for_str(tag_str);
176   if(tag_id == -1) {
177     PyErr_SetString(PyExc_ValueError, "Tag does not exist");
178     return NULL;
179   }
180
181   msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;
182
183   if(!add) {
184     /* raise KeyError if tag is not set */
185     if(!g_slist_find(msginfo->tags, GINT_TO_POINTER(tag_id))) {
186       PyErr_SetString(PyExc_KeyError, "Tag is not set on this message");
187       return NULL;
188     }
189   }
190
191   procmsg_msginfo_update_tags(msginfo, add, tag_id);
192
193   /* update display */
194   mainwin = mainwindow_get_mainwindow();
195   if(mainwin)
196     summary_redisplay_msg(mainwin->summaryview);
197
198   Py_RETURN_NONE;
199 }
200
201
202
203 static PyObject* add_tag(PyObject *self, PyObject *args)
204 {
205   return add_or_remove_tag(self, args, TRUE);
206 }
207
208
209 static PyObject* remove_tag(PyObject *self, PyObject *args)
210 {
211   return add_or_remove_tag(self, args, FALSE);
212 }
213
214 static PyObject* get_header(PyObject *self, PyObject *args)
215 {
216   int retval;
217   const char *header_str;
218   char *header_str_dup;
219   MsgInfo *msginfo;
220   gchar header_content[HEADER_CONTENT_SIZE];
221
222   retval = PyArg_ParseTuple(args, "s", &header_str);
223   if(!retval)
224     return NULL;
225
226   msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;
227
228   header_str_dup = g_strdup(header_str);
229   retval = procheader_get_header_from_msginfo(msginfo, header_content, HEADER_CONTENT_SIZE, header_str);
230   g_free(header_str_dup);
231   if(retval == 0) {
232     PyObject *header_content_object;
233     gchar *content_start;
234
235     /* the string is now Header: Value. Strip the Header: part */
236     content_start = strstr(header_content, ":");
237     if(content_start == NULL)
238       content_start = header_content;
239     else
240       content_start++;
241     /* strip leading spaces */
242     while(*content_start == ' ')
243       content_start++;
244     header_content_object = Py_BuildValue("s", content_start);
245     return header_content_object;
246   }
247   else {
248     Py_RETURN_NONE;
249   }
250 }
251
252
253 static PyMethodDef MessageInfo_methods[] = {
254   {"is_new",  is_new, METH_NOARGS,
255    "is_new() - checks if the message is new\n"
256    "\n"
257    "Returns True if the new flag of the message is set."},
258
259   {"is_unread",  is_unread, METH_NOARGS,
260    "is_unread() - checks if the message is unread\n"
261    "\n"
262    "Returns True if the unread flag of the message is set."},
263
264   {"is_marked",  is_marked, METH_NOARGS,
265    "is_marked() - checks if the message is marked\n"
266    "\n"
267    "Returns True if the marked flag of the message is set."},
268
269   {"is_replied",  is_replied, METH_NOARGS,
270    "is_replied() - checks if the message has been replied to\n"
271    "\n"
272    "Returns True if the replied flag of the message is set."},
273
274   {"is_locked",  is_locked, METH_NOARGS,
275    "is_locked() - checks if the message has been locked\n"
276    "\n"
277    "Returns True if the locked flag of the message is set."},
278
279   {"is_forwarded",  is_forwarded, METH_NOARGS,
280    "is_forwarded() - checks if the message has been forwarded\n"
281    "\n"
282    "Returns True if the forwarded flag of the message is set."},
283
284   {"get_tags",  get_tags, METH_NOARGS,
285    "get_tags() - get message tags\n"
286    "\n"
287    "Returns a tuple of tags that apply to this message."},
288
289   {"add_tag",  add_tag, METH_VARARGS,
290    "add_tag(tag) - add a tag to this message\n"
291    "\n"
292    "Add a tag to this message. If the tag is already set, nothing is done.\n"
293    "If the tag does not exist, a ValueError exception is raised."},
294
295   {"remove_tag",  remove_tag, METH_VARARGS,
296    "remove_tag(tag) - remove a tag from this message\n"
297    "\n"
298    "Remove a tag from this message. If the tag is not set, a KeyError exception is raised.\n"
299    "If the tag does not exist, a ValueError exception is raised."},
300
301    {"get_header",  get_header, METH_VARARGS,
302     "get_header(name) - get a message header with a given name\n"
303     "\n"
304     "Get a message header content with a given name. If the header does not exist,\n"
305     "the value 'None' is returned. If multiple headers with the same name exist,\n"
306     "the first one is returned."},
307
308   {NULL}
309 };
310
311 static PyMemberDef MessageInfo_members[] = {
312     { "From", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, from), 0,
313         "From - the From header of the message" },
314
315     { "To", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, to), 0,
316         "To - the To header of the message" },
317
318     { "Cc", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, cc), 0,
319         "Cc - the Cc header of the message" },
320
321     {"Subject", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, subject), 0,
322      "Subject - the subject header of the message"},
323
324     {"MessageID", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, msgid), 0,
325      "MessageID - the Message-ID header of the message"},
326
327     {"FilePath", T_OBJECT_EX, offsetof(clawsmail_MessageInfoObject, filepath), 0,
328      "FilePath - path and filename of the message"},
329
330     {NULL}
331 };
332
333 static PyTypeObject clawsmail_MessageInfoType = {
334     PyObject_HEAD_INIT(NULL)
335     0,                         /* ob_size*/
336     "clawsmail.MessageInfo",   /* tp_name*/
337     sizeof(clawsmail_MessageInfoObject), /* tp_basicsize*/
338     0,                         /* tp_itemsize*/
339     (destructor)MessageInfo_dealloc, /* tp_dealloc*/
340     0,                         /* tp_print*/
341     0,                         /* tp_getattr*/
342     0,                         /* tp_setattr*/
343     0,                         /* tp_compare*/
344     0,                         /* tp_repr*/
345     0,                         /* tp_as_number*/
346     0,                         /* tp_as_sequence*/
347     0,                         /* tp_as_mapping*/
348     0,                         /* tp_hash */
349     0,                         /* tp_call*/
350     MessageInfo_str,           /* tp_str*/
351     0,                         /* tp_getattro*/
352     0,                         /* tp_setattro*/
353     0,                         /* tp_as_buffer*/
354     Py_TPFLAGS_DEFAULT,        /* tp_flags*/
355     "A MessageInfo represents" /* tp_doc */
356     "a single message.",
357     0,                         /* tp_traverse */
358     0,                         /* tp_clear */
359     0,                         /* tp_richcompare */
360     0,                         /* tp_weaklistoffset */
361     0,                         /* tp_iter */
362     0,                         /* tp_iternext */
363     MessageInfo_methods,       /* tp_methods */
364     MessageInfo_members,       /* tp_members */
365     0,                         /* tp_getset */
366     0,                         /* tp_base */
367     0,                         /* tp_dict */
368     0,                         /* tp_descr_get */
369     0,                         /* tp_descr_set */
370     0,                         /* tp_dictoffset */
371     (initproc)MessageInfo_init,/* tp_init */
372     0,                         /* tp_alloc */
373     0,                         /* tp_new */
374 };
375
376 gboolean cmpy_add_messageinfo(PyObject *module)
377 {
378   clawsmail_MessageInfoType.tp_new = PyType_GenericNew;
379   if(PyType_Ready(&clawsmail_MessageInfoType) < 0)
380     return FALSE;
381
382   Py_INCREF(&clawsmail_MessageInfoType);
383   return (PyModule_AddObject(module, "MessageInfo", (PyObject*)&clawsmail_MessageInfoType) == 0);
384 }
385
386 #define MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(fis, pms)     \
387   do {                                                            \
388     if(fis) {                                                     \
389       PyObject *str;                                              \
390       str = PyString_FromString(fis);                             \
391       if(str) {                                                   \
392         int retval;                                               \
393         retval = PyObject_SetAttrString((PyObject*)ff, pms, str); \
394         Py_DECREF(str);                                           \
395         if(retval == -1)                                          \
396           goto err;                                               \
397       }                                                           \
398     }                                                             \
399   } while(0)
400
401 static gboolean update_members(clawsmail_MessageInfoObject *ff, MsgInfo *msginfo)
402 {
403   gchar *filepath;
404
405   MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->from, "From");
406   MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->to, "To");
407   MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->cc, "Cc");
408   MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->subject, "Subject");
409   MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(msginfo->msgid, "MessageID");
410
411   filepath = procmsg_get_message_file_path(msginfo);
412   if(filepath) {
413     MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER(filepath, "FilePath");
414     g_free(filepath);
415   }
416   else {
417     MSGINFO_STRING_TO_PYTHON_MESSAGEINFO_MEMBER("", "FilePath");
418   }
419
420   return TRUE;
421 err:
422   return FALSE;
423 }
424
425 PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo)
426 {
427   clawsmail_MessageInfoObject *ff;
428
429   if(!msginfo)
430     return NULL;
431
432   ff = (clawsmail_MessageInfoObject*) PyObject_CallObject((PyObject*) &clawsmail_MessageInfoType, NULL);
433   if(!ff)
434     return NULL;
435
436   ff->msginfo = msginfo;
437
438   if(update_members(ff, msginfo))
439     return (PyObject*)ff;
440   else {
441     Py_XDECREF(ff);
442     return NULL;
443   }
444 }
445
446 PyTypeObject* clawsmail_messageinfo_get_type_object()
447 {
448   return &clawsmail_MessageInfoType;
449 }
450
451 MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self)
452 {
453   return ((clawsmail_MessageInfoObject*)self)->msginfo;
454 }