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 }