mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

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 }