src/mn-mailbox.gob (31247B) - 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 <stdarg.h> 22 #include <time.h> 23 #include <libxml/tree.h> 24 #include "mn-decls.h" 25 %} 26 27 %h{ 28 #define MN_MAILBOX_MAX_TYPES 8 29 extern GType mn_mailbox_types[MN_MAILBOX_MAX_TYPES + 1]; 30 31 typedef struct _MNMailboxConfiguration MNMailboxConfiguration; 32 %} 33 34 %privateheader{ 35 #include "mn-xml.h" 36 37 typedef enum 38 { 39 /* load from mailboxes.xml */ 40 MN_MAILBOX_PARAM_LOAD = MN_XML_PARAM_IMPORT, 41 42 /* save to mailboxes.xml */ 43 MN_MAILBOX_PARAM_SAVE = MN_XML_PARAM_EXPORT, 44 45 /* load from and save to mailboxes.xml */ 46 MN_MAILBOX_PARAM_LOAD_SAVE = MN_MAILBOX_PARAM_LOAD | MN_MAILBOX_PARAM_SAVE, 47 48 /* ignore the case of a string property when checking if it has the default value */ 49 MN_MAILBOX_PARAM_IGNORE_CASE = MN_XML_PARAM_IGNORE_CASE, 50 51 /* require a string property to be non-empty */ 52 MN_MAILBOX_PARAM_REQUIRED = 1 << (MN_XML_PARAM_USER_SHIFT + 0), 53 } MNMailboxParamFlags; 54 %} 55 56 %{ 57 #include <glib/gi18n.h> 58 #include <libgnomevfs/gnome-vfs.h> 59 #if WITH_MBOX || WITH_MOZILLA || WITH_MH || WITH_MAILDIR || WITH_SYLPHEED 60 #include "mn-system-vfs-mailbox.h" 61 #include "mn-custom-vfs-mailbox.h" 62 #endif 63 #if WITH_POP3 64 #include "mn-pop3-mailbox.h" 65 #endif 66 #if WITH_IMAP 67 #include "mn-imap-mailbox.h" 68 #endif 69 #if WITH_GMAIL 70 #include "mn-gmail-mailbox.h" 71 #endif 72 #if WITH_YAHOO 73 #include "mn-yahoo-mailbox.h" 74 #endif 75 #if WITH_HOTMAIL 76 #include "mn-hotmail-mailbox.h" 77 #endif 78 #if WITH_EVOLUTION 79 #include "mn-evolution-mailbox.h" 80 #endif 81 #include "mn-util.h" 82 #include "mn-message.h" 83 #include "mn-conf.h" 84 #include "mn-locked-callback.h" 85 #include "mn-shell.h" 86 87 struct _MNMailboxConfiguration 88 { 89 GType type; 90 unsigned int n_parameters; 91 GParameter *parameters; 92 }; 93 94 GType mn_mailbox_types[MN_MAILBOX_MAX_TYPES + 1]; 95 96 typedef struct 97 { 98 MNMailbox *self; 99 GHashTable *messages; 100 GHashTable *messages_considered_as_read; 101 gboolean display_seen_mail; 102 } FilterMessagesInfo; 103 104 typedef struct 105 { 106 GHashTable *other; 107 gboolean changed; 108 } CompareMessagesInfo; 109 110 static unsigned int cleanup_messages_considered_as_read_idle_id = 0; 111 %} 112 113 class MN:Mailbox from G:Object (abstract) 114 { 115 classwide const char *type; 116 classwide int default_check_delay = -1; 117 118 /* 119 * If enabled, the base MNMailbox implementation of added() will 120 * call mn_mailbox_enable_checking(). 121 */ 122 classwide gboolean enable_checking_when_added = TRUE; 123 124 /* 125 * Whether the mailbox is in the mailboxes list (a mailbox can be 126 * removed from the mailboxes list without being finalized 127 * immediately if a threaded check is in progress). 128 * 129 * Used for performance (testing it is faster than searching the 130 * list) and for thread safety. Do not access directly, use the 131 * accessors. 132 */ 133 private gboolean _active; 134 135 public gboolean 136 get_active (self) 137 { 138 return g_atomic_int_get(&selfp->_active); 139 } 140 141 private void 142 set_active (self, gboolean value) 143 { 144 g_atomic_int_set(&selfp->_active, value); 145 } 146 147 /** 148 * added: 149 * @self: the object which received the signal 150 * 151 * This signal gets emitted after the mailbox is added to the 152 * mailboxes list. 153 **/ 154 signal NONE (NONE) 155 void added (self) 156 { 157 self_set_active(self, TRUE); 158 159 if (SELF_GET_CLASS(self)->enable_checking_when_added) 160 self_enable_checking(self); 161 } 162 163 /** 164 * removed: 165 * @self: the object which received the signal 166 * 167 * This signal gets emitted after the mailbox is removed from the 168 * mailboxes list. 169 **/ 170 signal NONE (NONE) 171 void removed (self) 172 { 173 self_set_active(self, FALSE); 174 175 mn_source_clear(&selfp->check_timeout_id); 176 177 /* 178 * Do not queue a cleanup of the messages-considered-as-read GConf 179 * setting: it should not be done if the mailbox is only being 180 * replaced (eg. from mn_mailbox_properties_dialog_apply()). 181 * 182 * If however the mailbox is being permanently removed, its 183 * messages considered as read will be cleaned up the next time 184 * another mailbox is checked, which is good enough. 185 * 186 * Note that we could queue a cleanup from here by adding and 187 * testing a "gboolean replacing" signal parameter, but it is not 188 * worth the effort. 189 */ 190 } 191 192 public char *runtime_name destroywith g_free; 193 194 public char *name destroywith g_free; 195 property STRING name (link, flags = MN_MAILBOX_PARAM_LOAD_SAVE); 196 197 public char *open_command destroywith g_free; 198 property STRING open_command (link, flags = MN_MAILBOX_PARAM_LOAD_SAVE); 199 200 public char *mark_as_read_command destroywith g_free; 201 property STRING mark_as_read_command (link, flags = MN_MAILBOX_PARAM_LOAD_SAVE); 202 203 public char *mark_as_spam_command destroywith g_free; 204 property STRING mark_as_spam_command (link, flags = MN_MAILBOX_PARAM_LOAD_SAVE); 205 206 public char *delete_command destroywith g_free; 207 property STRING delete_command (link, flags = MN_MAILBOX_PARAM_LOAD_SAVE); 208 209 public char *stock_id destroywith g_free; 210 property STRING stock_id (link, export); 211 212 public char *format destroywith g_free; 213 property STRING format (link, export); 214 215 private unsigned int check_timeout_id; 216 217 public int runtime_check_delay; 218 219 public int check_delay; 220 property INT check_delay (link, 221 flags = CONSTRUCT | MN_MAILBOX_PARAM_LOAD_SAVE, 222 default_value = -1); 223 224 private gboolean poll = TRUE; 225 property BOOLEAN poll (export) 226 set 227 { 228 gboolean new_poll = g_value_get_boolean(VAL); 229 230 /* 231 * We do nothing unless the property has changed, because we do 232 * not want to reset an already existing check timeout. 233 */ 234 if (new_poll != selfp->poll) 235 { 236 selfp->poll = new_poll; 237 if (self_get_active(self) && selfp->checking_enabled) 238 self_update_check_timeout(self); 239 240 g_object_notify(G_OBJECT(self), "manually-checkable"); 241 } 242 } 243 get 244 { 245 g_value_set_boolean(VAL, selfp->poll); 246 }; 247 248 /* 249 * Whether the user can initiate a check manually (for instance by 250 * clicking on an Update menu item or by running mail-notification 251 * --update). 252 */ 253 property BOOLEAN manually_checkable (export) 254 get 255 { 256 g_value_set_boolean(VAL, selfp->checking_enabled && selfp->poll); 257 }; 258 259 private void 260 update_check_timeout (self) 261 { 262 g_assert(self_get_active(self) == TRUE); 263 g_assert(selfp->checking_enabled == TRUE); 264 265 mn_source_clear(&selfp->check_timeout_id); 266 if (selfp->poll && self->runtime_check_delay > 0) 267 selfp->check_timeout_id = gdk_threads_add_timeout(self->runtime_check_delay * 1000, self_check_timeout_cb, self); 268 } 269 270 /* whether set_messages() has ever been called */ 271 private gboolean all_messages_set; 272 273 /* all unread (unseen and seen) messages, indexed by id */ 274 private GHashTable *all_messages = {g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_object_unref)} destroywith g_hash_table_destroy; 275 276 /* all_messages, indexed by mid (message references held by all_message) */ 277 private GHashTable *all_messages_by_mid = {g_hash_table_new(g_str_hash, g_str_equal)} destroywith g_hash_table_destroy; 278 279 /* all_messages after applying the "seen messages" filter, indexed by id */ 280 public GHashTable *messages = {g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_object_unref)} destroywith g_hash_table_destroy; 281 282 /* for performance */ 283 public time_t timestamp; /* timestamp of most recent message in self->messages */ 284 285 property POINTER messages (type = GHashTable *) 286 get 287 { 288 g_value_set_pointer(VAL, self->messages); 289 }; 290 291 protected void 292 set_messages (self, GSList *messages) 293 { 294 GSList *l; 295 296 selfp->all_messages_set = TRUE; 297 298 g_hash_table_remove_all(selfp->all_messages); 299 g_hash_table_remove_all(selfp->all_messages_by_mid); 300 301 MN_LIST_FOREACH(l, messages) 302 { 303 MNMessage *message = l->data; 304 305 g_hash_table_replace(selfp->all_messages, message->id, g_object_ref(message)); 306 307 if (message->mid) 308 /* do not ref message, it is owned by all_messages */ 309 g_hash_table_replace(selfp->all_messages_by_mid, message->mid, message); 310 } 311 312 /* 313 * Some messages might be gone, queue a cleanup of the 314 * considered-as-read GConf setting. 315 */ 316 self_queue_cleanup_messages_considered_as_read(); 317 318 self_filter_messages(self); 319 } 320 321 /** 322 * filter_messages: 323 * @self: the mailbox to act upon 324 * 325 * Filters @self->all_messages with the "seen mail" filter and the 326 * considered-as-read GConf list, and stores the resulting set in 327 * @self->messages. Additionally, if @self->messages has changed, 328 * emits the "messages-changed" signal. 329 **/ 330 private void 331 filter_messages (self) 332 { 333 FilterMessagesInfo info; 334 gboolean changed = FALSE; 335 gboolean has_new = FALSE; 336 337 /* filter messages */ 338 339 self->timestamp = 0; 340 341 info.self = self; 342 info.messages = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_object_unref); 343 info.messages_considered_as_read = mn_conf_get_string_hash_set(MN_CONF_MESSAGES_CONSIDERED_AS_READ); 344 info.display_seen_mail = mn_conf_get_bool(MN_CONF_DISPLAY_SEEN_MAIL); 345 346 g_hash_table_foreach(selfp->all_messages, (GHFunc) self_filter_messages_cb, &info); 347 348 g_hash_table_destroy(info.messages_considered_as_read); 349 350 /* detect changes */ 351 352 if (self_compare_messages(self->messages, info.messages)) 353 changed = TRUE; 354 if (self_compare_messages(info.messages, self->messages)) 355 changed = has_new = TRUE; 356 357 g_hash_table_destroy(self->messages); 358 self->messages = info.messages; 359 360 g_object_notify(G_OBJECT(self), "messages"); 361 362 if (changed) 363 self_messages_changed(self, has_new); 364 } 365 366 private void 367 filter_messages_cb (const char *id, 368 MNMessage *message, 369 FilterMessagesInfo *info) 370 { 371 Self *self = info->self; 372 373 if (! info->display_seen_mail && (message->flags & MN_MESSAGE_NEW) == 0) 374 return; 375 376 if (g_hash_table_lookup(info->messages_considered_as_read, message->id)) 377 return; 378 379 if (message->sent_time > self->timestamp) 380 self->timestamp = message->sent_time; 381 382 g_hash_table_replace(info->messages, message->id, g_object_ref(message)); 383 } 384 385 /** 386 * compare_messages: 387 * @messages1: a %GHashTable containing %MNMessage objects 388 * @messages2: another %GHashTable containing %MNMessage objects 389 * 390 * Compares @messages1 and @messages2. 391 * 392 * Return value: %TRUE if @messages1 contains messages not present 393 * in @messages2, %FALSE otherwise. 394 **/ 395 private gboolean 396 compare_messages (GHashTable *messages1, GHashTable *messages2) 397 { 398 CompareMessagesInfo info; 399 400 info.other = messages2; 401 info.changed = FALSE; 402 g_hash_table_foreach(messages1, (GHFunc) self_compare_messages_cb, &info); 403 404 return info.changed; 405 } 406 407 private void 408 compare_messages_cb (const char *id, 409 MNMessage *message, 410 CompareMessagesInfo *info) 411 { 412 if (! info->changed && ! g_hash_table_lookup(info->other, id)) 413 info->changed = TRUE; 414 } 415 416 private void 417 queue_cleanup_messages_considered_as_read (void) 418 { 419 if (! cleanup_messages_considered_as_read_idle_id) 420 cleanup_messages_considered_as_read_idle_id = gdk_threads_add_idle(self_cleanup_messages_considered_as_read_cb, NULL); 421 } 422 423 private gboolean 424 cleanup_messages_considered_as_read_cb (gpointer data) 425 { 426 self_cleanup_messages_considered_as_read(); 427 428 cleanup_messages_considered_as_read_idle_id = 0; 429 return FALSE; /* remove source */ 430 } 431 432 /* 433 * Remove messages which no longer exist from the considered-as-read 434 * GConf list. 435 * 436 * The primary goal is to ensure that a message which has been 437 * opened or marked as read (these functions implicitly add the 438 * message to the considered-as-read list) and then marked back as 439 * unread in the mail reader will reappear in MN. 440 * 441 * The secondary goal is to ensure that the considered-as-list will 442 * not grow forever. 443 */ 444 private void 445 cleanup_messages_considered_as_read (void) 446 { 447 GList *l; 448 GHashTable *set; 449 450 /* 451 * If there is a mailbox which has not been successfully checked 452 * yet, abort. Otherwise, the messages of that mailbox could be 453 * mistakenly cleaned up. 454 */ 455 MN_LIST_FOREACH(l, mn_shell->mailboxes->list) 456 { 457 MNMailbox *mailbox = l->data; 458 459 if (! mailbox->_priv->all_messages_set) 460 return; 461 } 462 463 set = mn_conf_get_string_hash_set(MN_CONF_MESSAGES_CONSIDERED_AS_READ); 464 465 if (g_hash_table_foreach_remove(set, (GHRFunc) self_cleanup_messages_considered_as_read_remove_cb, NULL)) 466 /* one or more messages were removed, reflect the changes */ 467 mn_conf_set_string_hash_set(MN_CONF_MESSAGES_CONSIDERED_AS_READ, set); 468 469 g_hash_table_destroy(set); 470 } 471 472 private gboolean 473 cleanup_messages_considered_as_read_remove_cb (const char *id, 474 gpointer value, 475 gpointer user_data) 476 { 477 GList *l; 478 479 MN_LIST_FOREACH(l, mn_shell->mailboxes->list) 480 { 481 MNMailbox *mailbox = l->data; 482 483 if (g_hash_table_lookup(mailbox->_priv->all_messages, id)) 484 return FALSE; /* message still exists, do not remove it */ 485 } 486 487 return TRUE; /* message no longer exists, remove it */ 488 } 489 490 /** 491 * messages-changed: 492 * @self: the object which received the signal 493 * @has_new: whether a new message has been received or not 494 * 495 * This signal gets emitted whenever the messages property changes. 496 * 497 * It is considered that the property changes if a new message 498 * appears or if an old message disappears. If the contents of an 499 * existing message change but the message keeps the same id, this 500 * signal is not emitted (use the "notify::messages" signal if you 501 * need notification of such events). 502 **/ 503 signal private NONE (BOOLEAN) 504 void messages_changed (self, gboolean has_new); 505 506 public char *error destroywith g_free; 507 property STRING error (link); 508 509 protected void 510 set_error (self, const char *format, ...) 511 attr {G_GNUC_PRINTF(2, 3)} 512 { 513 char *error = NULL; 514 515 if (format) 516 MN_STRDUP_VPRINTF(error, format); 517 518 g_object_set(G_OBJECT(self), MN_MAILBOX_PROP_ERROR(error), NULL); 519 g_free(error); 520 } 521 522 public void 523 init_types (void) 524 { 525 int i = 0; 526 527 #if WITH_MBOX || WITH_MOZILLA || WITH_MH || WITH_MAILDIR || WITH_SYLPHEED 528 /* 529 * MNSystemVFSMailbox must be registered before 530 * MNCustomVFSMailbox, because the latter's parse_uri() method 531 * will accept any URI. 532 */ 533 mn_mailbox_types[i++] = MN_TYPE_SYSTEM_VFS_MAILBOX; 534 mn_mailbox_types[i++] = MN_TYPE_CUSTOM_VFS_MAILBOX; 535 #endif 536 #if WITH_POP3 537 mn_mailbox_types[i++] = MN_TYPE_POP3_MAILBOX; 538 #endif 539 #if WITH_IMAP 540 mn_mailbox_types[i++] = MN_TYPE_IMAP_MAILBOX; 541 #endif 542 #if WITH_GMAIL 543 mn_mailbox_types[i++] = MN_TYPE_GMAIL_MAILBOX; 544 #endif 545 #if WITH_YAHOO 546 mn_mailbox_types[i++] = MN_TYPE_YAHOO_MAILBOX; 547 #endif 548 #if WITH_HOTMAIL 549 mn_mailbox_types[i++] = MN_TYPE_HOTMAIL_MAILBOX; 550 #endif 551 #if WITH_EVOLUTION 552 mn_mailbox_types[i++] = MN_TYPE_EVOLUTION_MAILBOX; 553 #endif 554 mn_mailbox_types[i] = 0; 555 } 556 557 /* references the returned class */ 558 public MNMailboxClass * 559 get_class_from_name (const char *type (check null)) 560 { 561 int i; 562 563 for (i = 0; mn_mailbox_types[i]; i++) 564 { 565 SelfClass *class; 566 567 class = g_type_class_ref(mn_mailbox_types[i]); 568 if (! strcmp(class->type, type)) 569 return class; 570 571 g_type_class_unref(class); 572 } 573 574 return NULL; 575 } 576 577 public GType 578 get_type_from_name (const char *type (check null)) 579 { 580 SelfClass *class; 581 582 class = self_get_class_from_name(type); 583 if (class) 584 { 585 GType gtype; 586 587 gtype = G_OBJECT_CLASS_TYPE(class); 588 g_type_class_unref(class); 589 590 return gtype; 591 } 592 593 return 0; 594 } 595 596 init (self) 597 { 598 mn_g_object_gconf_notifications_add_gdk_locked(self, 599 MN_CONF_DISPLAY_SEEN_MAIL, self_notify_display_seen_messages_cb, self, 600 MN_CONF_MESSAGES_CONSIDERED_AS_READ, self_notify_messages_considered_as_read_cb, self, 601 NULL); 602 } 603 604 finalize (self) 605 { 606 /* 607 * Even though we clear the source in removed(), it might have 608 * been reinstalled afterwards (by a mn_mailbox_set_poll() call 609 * from a check thread, etc). 610 */ 611 if (selfp->check_timeout_id) 612 g_source_remove(selfp->check_timeout_id); 613 } 614 615 private void 616 notify_display_seen_messages_cb (GConfClient *client, 617 unsigned int cnxn_id, 618 GConfEntry *entry, 619 gpointer user_data) 620 { 621 Self *self = user_data; 622 623 self_filter_messages(self); 624 } 625 626 private void 627 notify_messages_considered_as_read_cb (GConfClient *client, 628 unsigned int cnxn_id, 629 GConfEntry *entry, 630 gpointer user_data) 631 { 632 Self *self = user_data; 633 634 self_filter_messages(self); 635 } 636 637 public MNMailbox * 638 new (const char *type (check null), ...) 639 attr {G_GNUC_NULL_TERMINATED} 640 { 641 va_list args; 642 GType type_id; 643 const char *first_property_name; 644 GObject *object; 645 646 type_id = self_get_type_from_name(type); 647 if (! type_id) 648 return NULL; 649 650 va_start(args, type); 651 first_property_name = va_arg(args, const char *); 652 object = g_object_new_valist(type_id, first_property_name, args); 653 va_end(args); 654 655 return SELF(object); 656 } 657 658 public MNMailbox * 659 new_from_xml_node (xmlNode *node (check null), GError **err) 660 { 661 char *type; 662 Self *self = NULL; 663 664 type = xmlGetProp(node, "type"); 665 if (! type) 666 { 667 g_set_error(err, 0, 0, _("\"type\" attribute missing")); 668 return NULL; 669 } 670 671 self = self_new(type, NULL); 672 if (! self) 673 { 674 g_set_error(err, 0, 0, _("unknown mailbox type \"%s\""), type); 675 goto end; 676 } 677 678 mn_xml_import_properties(G_OBJECT(self), node); 679 680 if (! self_validate(self, err)) 681 { 682 g_object_unref(self); 683 self = NULL; 684 } 685 686 end: 687 g_free(type); 688 return self; 689 } 690 691 public xmlNode * 692 xml_node_new (self) 693 { 694 xmlNode *node; 695 696 node = xmlNewNode(NULL, "mailbox"); 697 xmlSetProp(node, "type", SELF_GET_CLASS(self)->type); 698 mn_xml_export_properties(G_OBJECT(self), node); 699 700 return node; 701 } 702 703 public MNMailbox * 704 new_from_uri (const char *uri (check null)) 705 { 706 int i; 707 708 for (i = 0; mn_mailbox_types[i]; i++) 709 { 710 MNMailboxClass *class; 711 MNMailbox *mailbox; 712 713 class = g_type_class_ref(mn_mailbox_types[i]); 714 mailbox = class->parse_uri ? class->parse_uri(NULL, uri) : NULL; 715 g_type_class_unref(class); 716 717 if (mailbox) 718 return mailbox; 719 } 720 721 return NULL; 722 } 723 724 public MNMailbox * 725 new_from_configuration (MNMailboxConfiguration *config (check null)) 726 { 727 return g_object_newv(config->type, config->n_parameters, config->parameters); 728 } 729 730 public MNMailboxConfiguration * 731 get_configuration (self) 732 { 733 GObject *object = G_OBJECT(self); 734 GArray *parameters; 735 GParamSpec **properties; 736 unsigned int n_properties; 737 int i; 738 MNMailboxConfiguration *config; 739 740 parameters = g_array_new(FALSE, FALSE, sizeof(GParameter)); 741 742 properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(self), &n_properties); 743 for (i = 0; i < n_properties; i++) 744 if ((properties[i]->flags & MN_MAILBOX_PARAM_SAVE) != 0) 745 { 746 GParameter parameter = { NULL, { 0, } }; 747 748 parameter.name = g_param_spec_get_name(properties[i]); 749 750 g_value_init(¶meter.value, G_PARAM_SPEC_VALUE_TYPE(properties[i])); 751 g_object_get_property(object, parameter.name, ¶meter.value); 752 753 g_array_append_val(parameters, parameter); 754 } 755 g_free(properties); 756 757 config = g_new0(MNMailboxConfiguration, 1); 758 config->type = G_OBJECT_TYPE(self); 759 config->n_parameters = parameters->len; 760 config->parameters = (GParameter *) g_array_free(parameters, FALSE); 761 762 return config; 763 } 764 765 public void 766 configuration_free (MNMailboxConfiguration *config (check null)) 767 { 768 int i; 769 770 for (i = 0; i < config->n_parameters; i++) 771 g_value_unset(&config->parameters[i].value); 772 773 g_free(config->parameters); 774 g_free(config); 775 } 776 777 public MNMailbox * 778 new_from_obsolete_uri (const char *uri (check null)) 779 { 780 char *real_uri; 781 char *scheme; 782 gboolean obsolete = FALSE; 783 Self *self = NULL; 784 785 real_uri = g_str_has_prefix(uri, "pop3:") /* also handle very old pop3 locators */ 786 ? g_strconcat("pop://", uri + 5, NULL) 787 : g_strdup(uri); 788 789 scheme = gnome_vfs_get_uri_scheme(real_uri); 790 if (scheme) 791 { 792 if (! strcmp(scheme, "pop") || ! strcmp(scheme, "pops") 793 || ! strcmp(scheme, "imap") || ! strcmp(scheme, "imaps") 794 || ! strcmp(scheme, "gmail")) 795 obsolete = TRUE; 796 g_free(scheme); 797 } 798 799 self = obsolete ? self_parse_obsolete_uri(real_uri) : self_new_from_uri(real_uri); 800 g_free(real_uri); 801 802 if (self && ! self_validate(self, NULL)) 803 { 804 g_object_unref(self); 805 self = NULL; 806 } 807 808 return self; 809 } 810 811 private MNMailbox * 812 parse_obsolete_uri (const char *uri (check null)) 813 { 814 int len; 815 int buflen; 816 char *scheme; 817 char *username; 818 char *password; 819 char *authmech; 820 char *hostname; 821 int port; 822 char *path; 823 char **queries; 824 Self *self = NULL; 825 826 len = strlen(uri); 827 buflen = len + 1; 828 829 { 830 char *pat; 831 char scheme_buf[buflen]; 832 char auth_buf[buflen]; 833 char location_buf[buflen]; 834 char username_buf[buflen]; 835 char password_buf[buflen]; 836 char authmech_buf[buflen]; 837 char hostname_buf[buflen]; 838 int _port; 839 char path_buf[buflen]; 840 char queries_buf[buflen]; 841 gboolean has_location = FALSE; 842 gboolean has_password = FALSE; 843 gboolean has_authmech = FALSE; 844 gboolean has_port = FALSE; 845 gboolean has_path = FALSE; 846 gboolean has_queries = FALSE; 847 int n; 848 849 /* split URI in 3 parts: scheme, auth and location */ 850 851 pat = g_strdup_printf("%%%i[^:]://%%%i[^@]@%%%is", len, len, len); 852 n = sscanf(uri, pat, scheme_buf, auth_buf, location_buf); 853 g_free(pat); 854 855 if (n >= 2) 856 { 857 if (n == 3) 858 has_location = TRUE; 859 } 860 else 861 return NULL; /* unparsable */ 862 863 /* split auth part in 3 subparts: username, password and authmech */ 864 865 /* 866 * For backward compatibility with previous versions of Mail 867 * Notification, we also support ;auth= (in lowercase). 868 */ 869 870 pat = g_strdup_printf("%%%i[^:]:%%%i[^;];%%*1[aA]%%*1[uU]%%*1[tT]%%*1[hH]=%%%is", len, len, len); 871 n = sscanf(auth_buf, pat, username_buf, password_buf, authmech_buf); 872 g_free(pat); 873 874 if (n >= 2) 875 { 876 has_password = TRUE; 877 if (n == 3) 878 has_authmech = TRUE; 879 } 880 else 881 { 882 pat = g_strdup_printf("%%%i[^;];%%*1[aA]%%*1[uU]%%*1[tT]%%*1[hH]=%%%is", len, len); 883 n = sscanf(auth_buf, pat, username_buf, authmech_buf); 884 g_free(pat); 885 886 if (n >= 1) 887 { 888 if (n == 2) 889 has_authmech = TRUE; 890 } 891 else 892 return NULL; /* unparsable */ 893 } 894 895 if (has_location) 896 { 897 char hostport_buf[buflen]; 898 899 /* split location part in 3 subparts: hostport, path and queries */ 900 901 pat = g_strdup_printf("%%%i[^/]/%%%i[^?]?%%%is", len, len, len); 902 n = sscanf(location_buf, pat, hostport_buf, path_buf, queries_buf); 903 g_free(pat); 904 905 if (n >= 2) 906 { 907 has_path = TRUE; 908 if (n == 3) 909 has_queries = TRUE; 910 } 911 else 912 { 913 pat = g_strdup_printf("%%%i[^?]?%%%is", len, len); 914 n = sscanf(location_buf, pat, hostport_buf, queries_buf); 915 g_free(pat); 916 917 if (n == 2) 918 has_queries = TRUE; 919 } 920 921 /* split hostport in 2 subparts: host and port */ 922 923 pat = g_strdup_printf("[%%%i[^]]]:%%u", len); 924 n = sscanf(hostport_buf, pat, hostname_buf, &_port); 925 g_free(pat); 926 927 if (n < 1) 928 { 929 pat = g_strdup_printf("%%%i[^:]:%%u", len); 930 n = sscanf(hostport_buf, pat, hostname_buf, &_port); 931 g_free(pat); 932 } 933 934 if (n == 2) 935 has_port = TRUE; 936 } 937 938 scheme = gnome_vfs_unescape_string(scheme_buf, NULL); 939 username = gnome_vfs_unescape_string(username_buf, NULL); 940 password = has_password ? gnome_vfs_unescape_string(password_buf, NULL) : NULL; 941 authmech = has_authmech ? gnome_vfs_unescape_string(authmech_buf, NULL) : NULL; 942 hostname = has_location ? gnome_vfs_unescape_string(hostname_buf, NULL) : NULL; 943 port = has_port ? _port : 0; 944 path = has_path ? gnome_vfs_unescape_string(path_buf, NULL) : NULL; 945 if (has_queries) 946 { 947 int i; 948 949 queries = g_strsplit(queries_buf, "&", 0); 950 for (i = 0; queries[i]; i++) 951 { 952 char *unescaped; 953 954 unescaped = gnome_vfs_unescape_string(queries[i], NULL); 955 956 g_free(queries[i]); 957 queries[i] = unescaped; 958 } 959 } 960 else 961 queries = NULL; 962 } 963 964 if (! strcmp(scheme, "pop") || ! strcmp(scheme, "pops")) 965 { 966 #if WITH_POP3 967 MNPIMailboxConnectionType connection_type; 968 969 if (queries && mn_strv_find(queries, "STLS") != -1) 970 connection_type = MN_PI_MAILBOX_CONNECTION_TYPE_INBAND_SSL; 971 else 972 connection_type = ! strcmp(scheme, "pops") 973 ? MN_PI_MAILBOX_CONNECTION_TYPE_SSL 974 : MN_PI_MAILBOX_CONNECTION_TYPE_NORMAL; 975 976 self = self_new("pop3", 977 "connection-type", connection_type, 978 "username", username, 979 "password", password, 980 "authmech", authmech, 981 "hostname", hostname, 982 "port", port, 983 NULL); 984 #endif 985 } 986 else if (! strcmp(scheme, "imap") || ! strcmp(scheme, "imaps")) 987 { 988 #if WITH_IMAP 989 MNPIMailboxConnectionType connection_type; 990 991 if (queries && mn_strv_find(queries, "STARTTLS") != -1) 992 connection_type = MN_PI_MAILBOX_CONNECTION_TYPE_INBAND_SSL; 993 else 994 connection_type = ! strcmp(scheme, "imaps") 995 ? MN_PI_MAILBOX_CONNECTION_TYPE_SSL 996 : MN_PI_MAILBOX_CONNECTION_TYPE_NORMAL; 997 998 self = self_new("imap", 999 "connection-type", connection_type, 1000 "username", username, 1001 "password", password, 1002 "authmech", authmech, 1003 "hostname", hostname, 1004 "port", port, 1005 NULL); 1006 1007 if (path) 1008 g_object_set(self, MN_IMAP_MAILBOX_PROP_MAILBOX(path), NULL); 1009 1010 if (queries && mn_strv_find(queries, "noidle") != -1) 1011 g_object_set(G_OBJECT(self), MN_IMAP_MAILBOX_PROP_USE_IDLE_EXTENSION(MN_IMAP_MAILBOX_USE_IDLE_NEVER), NULL); 1012 #endif 1013 } 1014 else if (! strcmp(scheme, "gmail")) 1015 { 1016 #if WITH_GMAIL 1017 self = self_new("gmail", 1018 "username", username, 1019 "password", password, 1020 NULL); 1021 #endif 1022 } 1023 1024 g_free(scheme); 1025 g_free(username); 1026 g_free(password); 1027 g_free(authmech); 1028 g_free(hostname); 1029 g_free(path); 1030 g_strfreev(queries); 1031 1032 return self; 1033 } 1034 1035 private gboolean 1036 check_timeout_cb (gpointer data) 1037 { 1038 Self *self = data; 1039 1040 self_check(self); 1041 1042 return TRUE; /* continue */ 1043 } 1044 1045 private gboolean 1046 validate (self, GError **err) 1047 { 1048 GParamSpec **properties; 1049 unsigned int n_properties; 1050 int i; 1051 gboolean status = TRUE; 1052 1053 properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(self), &n_properties); 1054 for (i = 0; i < n_properties; i++) 1055 if ((properties[i]->flags & MN_MAILBOX_PARAM_REQUIRED) != 0) 1056 { 1057 GValue value = { 0, }; 1058 const char *str; 1059 gboolean is_empty; 1060 1061 g_assert(G_IS_PARAM_SPEC_STRING(properties[i])); 1062 1063 g_value_init(&value, G_TYPE_STRING); 1064 g_object_get_property(G_OBJECT(self), g_param_spec_get_name(properties[i]), &value); 1065 1066 str = g_value_get_string(&value); 1067 is_empty = ! str || ! *str; 1068 1069 g_value_unset(&value); 1070 1071 if (is_empty) 1072 { 1073 g_set_error(err, 0, 0, _("property \"%s\" has no value"), g_param_spec_get_name(properties[i])); 1074 status = FALSE; 1075 break; 1076 } 1077 } 1078 g_free(properties); 1079 1080 return status; 1081 } 1082 1083 /** 1084 * seal: 1085 * @self: a mailbox 1086 * 1087 * Seals the mailbox before it is made operational by being added to 1088 * the mailboxes list. The point of this function is to allow 1089 * subclasses to perform initialization which needs to consult the 1090 * value of properties loaded from mailboxes.xml or set by the 1091 * properties dialog. That would not be possible from init(), since 1092 * these properties are only set after the mailbox is constructed. 1093 **/ 1094 virtual public void 1095 seal (self) 1096 { 1097 if (self->name) 1098 { 1099 g_free(self->runtime_name); 1100 self->runtime_name = g_strdup(self->name); 1101 } 1102 1103 self->runtime_check_delay = self->check_delay != -1 1104 ? self->check_delay 1105 : SELF_GET_CLASS(self)->default_check_delay; 1106 } 1107 1108 //[G_GNUC_UNUSED] /* invoked via the class pointer */ 1109 virtual private MNMailbox * 1110 parse_uri (self, const char *uri (check null)); 1111 1112 virtual public void 1113 check (self) 1114 { 1115 g_assert(self_get_active(self) == TRUE); 1116 g_assert(selfp->checking_enabled == TRUE); 1117 } 1118 1119 /* 1120 * Mailboxes start with this property disabled. As long as this 1121 * property is disabled, the Update menu items are insensitive, the 1122 * periodic poll timeout is not installed, and mn_mailbox_check() is 1123 * not called. This allows subclasses to prevent checks while they 1124 * are saving the password, etc. 1125 */ 1126 private gboolean checking_enabled; 1127 1128 protected void 1129 enable_checking (self) 1130 { 1131 selfp->checking_enabled = TRUE; 1132 g_object_notify(G_OBJECT(self), "manually-checkable"); 1133 1134 self_update_check_timeout(self); 1135 self_check(self); 1136 } 1137 1138 protected void 1139 notice (self, const char *format (check null), ...) 1140 attr {G_GNUC_PRINTF(2, 3)} 1141 { 1142 char *message; 1143 1144 /* 1145 * Disregard messages sent by a threaded check still in progress 1146 * after the mailbox has been removed. 1147 */ 1148 if (! self_get_active(self)) 1149 return; 1150 1151 MN_STRDUP_VPRINTF(message, format); 1152 mn_info(_("%s: %s"), self->runtime_name, message); 1153 g_free(message); 1154 } 1155 1156 protected void 1157 warning (self, const char *format (check null), ...) 1158 attr {G_GNUC_PRINTF(2, 3)} 1159 { 1160 char *message; 1161 1162 /* 1163 * Disregard messages sent by a threaded check still in progress 1164 * after the mailbox has been removed. 1165 */ 1166 if (! self_get_active(self)) 1167 return; 1168 1169 MN_STRDUP_VPRINTF(message, format); 1170 g_warning(_("%s: %s"), self->runtime_name, message); 1171 g_free(message); 1172 } 1173 1174 protected MNMessage * 1175 get_message_from_mid (self, const char *mid (check null)) 1176 { 1177 return g_hash_table_lookup(selfp->all_messages_by_mid, mid); 1178 } 1179 1180 public char * 1181 get_command (self, const char *id (check null)) 1182 { 1183 char *prop; 1184 char *command; 1185 1186 prop = g_strconcat(id, "-command", NULL); 1187 g_object_get(self, prop, &command, NULL); 1188 g_free(prop); 1189 1190 if (command && ! *command) 1191 { 1192 g_free(command); 1193 return NULL; 1194 } 1195 1196 return command; 1197 } 1198 1199 public gboolean 1200 has_command (self, const char *id (check null)) 1201 { 1202 char *command; 1203 gboolean has; 1204 1205 command = self_get_command(self, id); 1206 has = command != NULL; 1207 g_free(command); 1208 1209 return has; 1210 } 1211 }