src/mn-mailboxes.gob (17837B) - raw
1 /*
2 * Mail Notification
3 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 %headertop{
21 #include "mn-mailbox.h"
22 %}
23
24 %{
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <sys/stat.h>
29 #include <stdarg.h>
30 #include <errno.h>
31 #include <glib/gi18n.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include "mn-conf.h"
35 #include "mn-message.h"
36 #include "mn-mailbox-private.h"
37 #include "mn-test-mailbox.h"
38 #include "mn-shell.h"
39 #include "mn-util.h"
40 %}
41
42 class MN:Mailboxes from G:Object
43 {
44 public GList *list; /* freed in finalize */
45
46 /* sorted by sent time, most recent first */
47 public GPtrArray *messages = {g_ptr_array_new()} destroywith mn_g_object_ptr_array_free;
48
49 /* the references to the messages are held by the GPtrArray */
50 public GHashTable *messages_hash_table = {g_hash_table_new(g_str_hash, g_str_equal)} destroywith g_hash_table_destroy;
51
52 private GSList *add_queue destroywith mn_g_object_slist_free;
53 private GSList *remove_queue destroywith mn_g_object_slist_free;
54 private unsigned int queue_idle_id;
55
56 /*
57 * Can be set by mailboxes to indicate that mailboxes.xml must be
58 * saved after having been loaded. Used by MNAuthenticatedMailbox to
59 * remove the plain text passwords saved by older versions of MN.
60 */
61 public gboolean must_save_after_load;
62
63 /**
64 * mailbox-added:
65 * @self: the object which received the signal
66 * @mailbox: the mailbox which was added
67 *
68 * This signal gets emitted after a mailbox is added to the list.
69 **/
70 signal first private NONE (OBJECT)
71 void mailbox_added (self, MN:Mailbox *mailbox (check null type))
72 {
73 self_connect_mailbox_signals(self, mailbox);
74
75 /* emit the "added" signal on the mailbox */
76 mn_mailbox_added(mailbox);
77 }
78
79 /**
80 * mailbox-removed:
81 * @self: the object which received the signal
82 * @mailbox: the mailbox which was removed
83 *
84 * This signal gets emitted after a mailbox is removed from the
85 * list.
86 **/
87 signal first private NONE (OBJECT)
88 void mailbox_removed (self, MN:Mailbox *mailbox (check null type))
89 {
90 self_disconnect_mailbox_signals(self, mailbox);
91
92 /* emit the "removed" signal on the mailbox */
93 mn_mailbox_removed(mailbox);
94
95 /* messages and error have possibly changed */
96 self_messages_changed(self, FALSE);
97 self_error_changed(self);
98 }
99
100 //[G_GNUC_UNUSED] /* we use g_signal_emit_by_name(), for passing a detail */
101 signal (DETAILED) private NONE (OBJECT, POINTER)
102 void mailbox_notify (self,
103 MN:Mailbox *mailbox (check null type),
104 GParamSpec *pspec (check null));
105
106 /**
107 * list-changed:
108 * @self: the object which received the signal
109 *
110 * This signal gets emitted after the mailbox list changes (but more
111 * than one mailbox may have been added, removed or have changed
112 * between two emissions of this signal).
113 **/
114 signal first private NONE (NONE)
115 void list_changed (self)
116 {
117 /* manually-checkable has possibly changed */
118 g_object_notify(G_OBJECT(self), "manually-checkable");
119 }
120
121 /**
122 * messages-changed:
123 * @self: the object which received the signal
124 * @has_new: whether a new message has been received or not
125 *
126 * This signal gets emitted whenever the messages member has
127 * potentially changed, either because one of the mailboxes messages
128 * property has changed, or because a mailbox has been removed from
129 * the list.
130 *
131 * Note: messages are only compared by id (in
132 * mn_mailbox_filter_messages()), therefore two messages having the
133 * same id and different data are not considered different.
134 **/
135 signal first private NONE (BOOLEAN)
136 void messages_changed (self, gboolean has_new)
137 {
138 GList *l;
139
140 mn_g_object_ptr_array_free(self->messages);
141 self->messages = g_ptr_array_new();
142
143 g_hash_table_remove_all(self->messages_hash_table);
144
145 MN_LIST_FOREACH(l, self->list)
146 {
147 MNMailbox *mailbox = l->data;
148
149 g_hash_table_foreach(mailbox->messages, (GHFunc) self_messages_changed_cb, self);
150 }
151
152 g_ptr_array_sort(self->messages, (GCompareFunc) self_messages_sort_cb);
153 }
154
155 private void
156 messages_changed_cb (const char *id,
157 MNMessage *message,
158 Self *self)
159 {
160 g_ptr_array_add(self->messages, g_object_ref(message));
161 g_hash_table_insert(self->messages_hash_table, message->id, message);
162 }
163
164 private int
165 messages_sort_cb (MNMessage **a, MNMessage **b)
166 {
167 /* sort by sent time in descending order */
168 return (*b)->sent_time - (*a)->sent_time;
169 }
170
171 /**
172 * error-changed:
173 * @self: the object which received the signal
174 *
175 * This signal gets emitted whenever the global error state has
176 * possibly changed, either because one of the mailboxes error
177 * property has changed, or because a mailbox has been removed from
178 * the list.
179 **/
180 signal private NONE (NONE)
181 void error_changed (self);
182
183 property BOOLEAN manually_checkable (export)
184 get
185 {
186 GList *l;
187 gboolean value = FALSE;
188
189 MN_LIST_FOREACH(l, self->list)
190 {
191 MNMailbox *mailbox = l->data;
192
193 if (mn_mailbox_get_manually_checkable(mailbox))
194 {
195 value = TRUE;
196 break;
197 }
198 }
199
200 g_value_set_boolean(VAL, value);
201 };
202
203 init (self)
204 {
205 char *filename;
206 gboolean exists;
207
208 mn_shell->mailboxes = self;
209
210 filename = g_build_filename(mn_conf_dot_dir, "mailboxes.xml", NULL);
211 exists = g_file_test(filename, G_FILE_TEST_EXISTS);
212 g_free(filename);
213
214 if (exists)
215 self_load(self);
216 else if (mn_conf_is_set(MN_CONF_OBSOLETE_MAILBOXES))
217 {
218 GSList *gconf_mailboxes;
219 GSList *l;
220 GSList *invalid_uri_list = NULL;
221 gboolean list_changed = FALSE;
222
223 gconf_mailboxes = mn_conf_get_string_list(MN_CONF_OBSOLETE_MAILBOXES);
224 MN_LIST_FOREACH(l, gconf_mailboxes)
225 {
226 const char *uri = l->data;
227 MNMailbox *mailbox;
228
229 mailbox = mn_mailbox_new_from_obsolete_uri(uri);
230 if (mailbox)
231 {
232 mn_mailbox_seal(mailbox);
233 self_add_real(self, mailbox);
234 g_object_unref(mailbox);
235
236 list_changed = TRUE;
237 }
238 else
239 invalid_uri_list = g_slist_append(invalid_uri_list, (gpointer) uri);
240 }
241
242 if (list_changed)
243 {
244 self_list_changed(self);
245 self_save(self); /* save the imported mailboxes */
246 }
247
248 if (invalid_uri_list)
249 {
250 mn_show_invalid_uri_list_dialog(NULL, _("An error has occurred while importing old mailboxes"), invalid_uri_list);
251 g_slist_free(invalid_uri_list);
252 }
253
254 mn_g_slist_free_deep(gconf_mailboxes);
255 }
256 }
257
258 finalize (self)
259 {
260 GList *l;
261
262 /*
263 * We need to disconnect the mailbox signals because on exit, a
264 * mailbox can survive the MNMailboxes object (if a check thread
265 * is running).
266 */
267 MN_LIST_FOREACH(l, self->list)
268 self_disconnect_mailbox_signals(self, l->data);
269
270 mn_g_object_list_free(self->list);
271
272 if (selfp->queue_idle_id)
273 g_source_remove(selfp->queue_idle_id);
274 }
275
276 private void
277 connect_mailbox_signals (self, MN:Mailbox *mailbox (check null type))
278 {
279 g_object_connect(mailbox,
280 "signal::messages-changed", self_mailbox_messages_changed_h, self,
281 "signal::notify", self_mailbox_notify_h, self,
282 "signal::notify::error", self_mailbox_notify_error_h, self,
283 "signal::notify::manually-checkable", self_mailbox_notify_manually_checkable_h, self,
284 NULL);
285 }
286
287 private void
288 disconnect_mailbox_signals (self, MN:Mailbox *mailbox (check null type))
289 {
290 g_object_disconnect(mailbox,
291 "any-signal", self_mailbox_messages_changed_h, self,
292 "any-signal", self_mailbox_notify_h, self,
293 "any-signal", self_mailbox_notify_error_h, self,
294 "any-signal", self_mailbox_notify_manually_checkable_h, self,
295 NULL);
296 }
297
298 private void
299 load (self)
300 {
301 GError *err = NULL;
302
303 if (! self_load_real(self, &err))
304 {
305 mn_show_error_dialog(NULL, _("Unable to load the mailboxes configuration"), "%s", err->message);
306 g_error_free(err);
307 }
308
309 if (self->must_save_after_load)
310 self_save(self);
311 }
312
313 private void
314 add_error (GString **errors (check null),
315 int *n_errors (check null),
316 const char *format,
317 ...)
318 attr {G_GNUC_PRINTF(3, 4)}
319 {
320 char *message;
321
322 if (*errors)
323 g_string_append_c(*errors, '\n');
324 else
325 *errors = g_string_new(NULL);
326
327 MN_STRDUP_VPRINTF(message, format);
328 g_string_append(*errors, message);
329 g_free(message);
330
331 (*n_errors)++;
332 }
333
334 private gboolean
335 load_real (self, GError **err)
336 {
337 char *filename;
338 xmlDoc *doc;
339 xmlNode *root;
340 xmlNode *node;
341 gboolean list_changed = FALSE;
342 gboolean status = TRUE;
343 GString *errors = NULL;
344 int n_errors = 0;
345
346 filename = g_build_filename(mn_conf_dot_dir, "mailboxes.xml", NULL);
347 doc = xmlParseFile(filename);
348 g_free(filename);
349
350 if (! doc)
351 {
352 g_set_error(err, 0, 0, _("Unable to parse the XML document."));
353 return FALSE;
354 }
355
356 root = xmlDocGetRootElement(doc);
357 if (! root)
358 {
359 g_set_error(err, 0, 0, _("The root element is missing."));
360 goto error;
361 }
362
363 if (strcmp(root->name, "mailboxes"))
364 {
365 g_set_error(err, 0, 0, _("The root element \"%s\" is invalid."), root->name);
366 goto error;
367 }
368
369 for (node = root->children; node; node = node->next)
370 if (node->type == XML_ELEMENT_NODE)
371 {
372 if (! strcmp(node->name, "mailbox"))
373 {
374 MNMailbox *mailbox;
375 GError *tmp_err = NULL;
376
377 mailbox = mn_mailbox_new_from_xml_node(node, &tmp_err);
378 if (mailbox)
379 {
380 mn_mailbox_seal(mailbox);
381 self_add_real(self, mailbox);
382 g_object_unref(mailbox);
383 list_changed = TRUE;
384 }
385 else
386 {
387 self_add_error(&errors, &n_errors, _("On line %i: %s."), node->line, tmp_err->message);
388 g_error_free(tmp_err);
389 }
390 }
391 else
392 self_add_error(&errors, &n_errors, _("On line %i: unknown element \"%s\"."), node->line, node->name);
393 }
394
395 if (list_changed)
396 self_list_changed(self);
397
398 if (errors)
399 {
400 mn_show_error_dialog(NULL,
401 ngettext("An error has occurred while loading the mailboxes configuration",
402 "Errors have occurred while loading the mailboxes configuration",
403 n_errors),
404 "%s", errors->str);
405 g_string_free(errors, TRUE);
406 }
407
408 goto end;
409
410 error:
411 status = FALSE;
412
413 end:
414 xmlFreeDoc(doc);
415
416 return status;
417 }
418
419 private void
420 save (self)
421 {
422 GError *err = NULL;
423
424 if (! self_save_real(self, &err))
425 {
426 mn_show_error_dialog(NULL, _("Unable to save the mailboxes configuration"), "%s", err->message);
427 g_error_free(err);
428 }
429 }
430
431 private gboolean
432 save_real (self, GError **err)
433 {
434 int indent;
435 xmlDoc *doc;
436 xmlNode *root;
437 GList *l;
438 char *filename;
439 char *tmp_filename;
440 char *old_filename;
441 int fd = -1;
442 FILE *f = NULL;
443 gboolean old_exists;
444 gboolean status = TRUE;
445
446 indent = xmlIndentTreeOutput;
447 xmlIndentTreeOutput = 1;
448
449 doc = xmlNewDoc("1.0");
450 root = xmlNewNode(NULL, "mailboxes");
451 xmlDocSetRootElement(doc, root);
452
453 MN_LIST_FOREACH(l, self->list)
454 {
455 MNMailbox *mailbox = l->data;
456 xmlNode *node;
457
458 if (! MN_IS_TEST_MAILBOX(mailbox))
459 {
460 node = mn_mailbox_xml_node_new(mailbox);
461 xmlAddChild(root, node); /* owns node */
462 }
463 }
464
465 filename = g_build_filename(mn_conf_dot_dir, "mailboxes.xml", NULL);
466 tmp_filename = g_strconcat(filename, ".tmp", NULL);
467 old_filename = g_strconcat(filename, ".old", NULL);
468
469 if (g_file_test(tmp_filename, G_FILE_TEST_EXISTS) && unlink(tmp_filename) < 0)
470 {
471 g_set_error(err, 0, 0, _("Unable to remove %s: %s."), tmp_filename, g_strerror(errno));
472 goto error;
473 }
474
475 /* the file may contain passwords; restrict permissions (600) */
476 fd = open(tmp_filename, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
477 if (fd < 0)
478 {
479 g_set_error(err, 0, 0, _("Unable to create %s: %s."), tmp_filename, g_strerror(errno));
480 goto error;
481 }
482
483 f = fdopen(fd, "w");
484 if (! f)
485 {
486 g_set_error(err, 0, 0, _("Unable to open %s for writing: %s."), tmp_filename, g_strerror(errno));
487 goto error;
488 }
489 fd = -1; /* now owned by f */
490
491 if (xmlDocFormatDump(f, doc, 1) < 0)
492 {
493 g_set_error(err, 0, 0, _("Unable to write the XML document."));
494 goto error;
495 }
496
497 if (fclose(f) != 0)
498 {
499 g_set_error(err, 0, 0, _("Unable to close %s: %s."), tmp_filename, g_strerror(errno));
500 goto error;
501 }
502 f = NULL;
503
504 old_exists = g_file_test(filename, G_FILE_TEST_EXISTS);
505 if (old_exists)
506 {
507 if (rename(filename, old_filename) < 0)
508 {
509 g_set_error(err, 0, 0, _("Unable to rename %s to %s: %s."), filename, old_filename, g_strerror(errno));
510 goto error;
511 }
512 }
513
514 if (rename(tmp_filename, filename) < 0)
515 {
516 g_set_error(err, 0, 0, _("Unable to rename %s to %s: %s."), tmp_filename, filename, g_strerror(errno));
517 goto error;
518 }
519
520 if (old_exists)
521 if (unlink(old_filename) < 0) /* non fatal */
522 g_warning(_("unable to delete %s: %s"), old_filename, g_strerror(errno));
523
524 goto end; /* success */
525
526 error:
527 status = FALSE;
528
529 end:
530 xmlFreeDoc(doc);
531 xmlIndentTreeOutput = indent;
532
533 g_free(filename);
534 g_free(tmp_filename);
535 g_free(old_filename);
536
537 if (fd >= 0)
538 close(fd);
539 if (f)
540 fclose(f);
541
542 return status;
543 }
544
545 private void
546 mailbox_messages_changed_h (MNMailbox *mailbox,
547 gboolean has_new,
548 gpointer user_data)
549 {
550 Self *self = user_data;
551 int num_messages;
552
553 num_messages = g_hash_table_size(mailbox->messages);
554
555 mn_info(ngettext("%s has %i new message", "%s has %i new messages", num_messages),
556 mailbox->runtime_name, num_messages);
557
558 self_messages_changed(self, has_new);
559 }
560
561 private void
562 mailbox_notify_h (GObject *object, GParamSpec *pspec, gpointer user_data)
563 {
564 Self *self = user_data;
565 char *detailed_signal;
566
567 detailed_signal = g_strconcat("mailbox-notify::", g_param_spec_get_name(pspec), NULL);
568 g_signal_emit_by_name(self, detailed_signal, object, pspec);
569 g_free(detailed_signal);
570 }
571
572 private void
573 mailbox_notify_error_h (GObject *object,
574 GParamSpec *pspec,
575 gpointer user_data)
576 {
577 Self *self = user_data;
578 MNMailbox *mailbox = MN_MAILBOX(object);
579
580 if (mailbox->error)
581 mn_info(_("%s reported an error: %s"), mailbox->runtime_name, mailbox->error);
582
583 self_error_changed(self);
584 }
585
586 private void
587 mailbox_notify_manually_checkable_h (GObject *object,
588 GParamSpec *pspec,
589 gpointer user_data)
590 {
591 Self *self = user_data;
592
593 /* manually-checkable has possibly changed */
594 g_object_notify(G_OBJECT(self), "manually-checkable");
595 }
596
597 public void
598 check (self)
599 {
600 GList *l;
601
602 MN_LIST_FOREACH(l, self->list)
603 {
604 MNMailbox *mailbox = l->data;
605
606 if (mn_mailbox_get_manually_checkable(mailbox))
607 mn_mailbox_check(mailbox);
608 }
609 }
610
611 private void
612 add_real (self, MN:Mailbox *mailbox (check null type))
613 {
614 g_object_ref(mailbox);
615 self->list = g_list_insert_sorted(self->list, mailbox, self_compare_by_name_func);
616 self_mailbox_added(self, mailbox);
617 }
618
619 public void
620 add (self, MN:Mailbox *mailbox (check null type))
621 {
622 self_add_real(self, mailbox);
623 self_list_changed(self);
624
625 if (! MN_IS_TEST_MAILBOX(mailbox))
626 self_save(self);
627 }
628
629 public void
630 queue_add (self, MN:Mailbox *mailbox (check null type))
631 {
632 g_object_ref(mailbox);
633 selfp->add_queue = g_slist_append(selfp->add_queue, mailbox);
634
635 if (! selfp->queue_idle_id)
636 selfp->queue_idle_id = gdk_threads_add_idle(self_queue_idle_cb, self);
637 }
638
639 private void
640 remove_real (self, MN:Mailbox *mailbox (check null type))
641 {
642 self->list = g_list_remove(self->list, mailbox);
643 self_mailbox_removed(self, mailbox);
644 g_object_unref(mailbox);
645 }
646
647 public void
648 remove (self, MN:Mailbox *mailbox (check null type))
649 {
650 self_remove_real(self, mailbox);
651 self_list_changed(self);
652 if (! MN_IS_TEST_MAILBOX(mailbox))
653 self_save(self);
654 }
655
656 public void
657 queue_remove (self, MN:Mailbox *mailbox (check null type))
658 {
659 g_object_ref(mailbox);
660 selfp->remove_queue = g_slist_append(selfp->remove_queue, mailbox);
661
662 if (! selfp->queue_idle_id)
663 selfp->queue_idle_id = gdk_threads_add_idle(self_queue_idle_cb, self);
664 }
665
666 private gboolean
667 queue_idle_cb (gpointer data)
668 {
669 Self *self = data;
670 GSList *l;
671
672 MN_LIST_FOREACH(l, selfp->add_queue)
673 self_add_real(self, l->data);
674
675 mn_g_object_slist_clear(&selfp->add_queue);
676
677 MN_LIST_FOREACH(l, selfp->remove_queue)
678 self_remove_real(self, l->data);
679
680 mn_g_object_slist_clear(&selfp->remove_queue);
681
682 self_list_changed(self);
683 self_save(self);
684
685 selfp->queue_idle_id = 0;
686 return FALSE; /* remove */
687 }
688
689 public int
690 compare_by_name_func (gconstpointer a, gconstpointer b)
691 {
692 MNMailbox *mailbox_a = (MNMailbox *) a;
693 MNMailbox *mailbox_b = (MNMailbox *) b;
694
695 return g_utf8_collate(mailbox_a->runtime_name, mailbox_b->runtime_name);
696 }
697
698 public MNMailboxes *
699 new (void)
700 {
701 return GET_NEW;
702 }
703 }