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 }