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-sylpheed-mailbox-backend.gob (14474B) - 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-vfs-mailbox-backend.h"
     22 %}
     23 
     24 %privateheader{
     25 #include "mn-locked-callback.h"
     26 
     27 /* taken from procmsg.h in the Sylpheed sources */
     28 #define SYLPHEED_MSG_NEW	(1U << 0)
     29 #define SYLPHEED_MSG_UNREAD	(1U << 1)
     30 
     31 /* taken from defs.h in the Sylpheed sources */
     32 #define SYLPHEED_MARK_FILE	".sylpheed_mark"
     33 
     34 typedef struct
     35 {
     36   char	*position;
     37   gsize	bytes_left;
     38 } ByteStream;
     39 %}
     40 
     41 %{
     42 #include <stdlib.h>
     43 #include <string.h>
     44 #include <sys/stat.h>
     45 #include <fcntl.h>
     46 #include <errno.h>
     47 #include <glib/gi18n.h>
     48 #include "mn-mailbox-private.h"
     49 #include "mn-reentrant-mailbox-private.h"
     50 #include "mn-vfs-mailbox-private.h"
     51 #include "mn-vfs-mailbox-backend-private.h"
     52 #include "mn-vfs.h"
     53 #include "mn-util.h"
     54 #include "mn-message-mime.h"
     55 #include "mn-sylpheed-message.h"
     56 
     57 /* taken from defs.h in the Sylpheed sources */
     58 #define SYLPHEED_MARK_VERSION		2
     59 
     60 /* taken from defs.h in the Claws Mail sources */
     61 #define CLAWS_MAIL_CACHE_FILE		".sylpheed_claws_cache"
     62 %}
     63 
     64 class MN:Sylpheed:Mailbox:Backend from MN:VFS:Mailbox:Backend
     65 {
     66   class_init (class)
     67   {
     68     MN_VFS_MAILBOX_BACKEND_CLASS(class)->format = "Sylpheed";
     69   }
     70 
     71   override (MN:VFS:Mailbox:Backend) void
     72     monitor_cb (MNVFSMailboxBackend *backend,
     73 		const char *info_uri,
     74 		GnomeVFSMonitorEventType event_type)
     75   {
     76     if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED
     77 	|| event_type == GNOME_VFS_MONITOR_EVENT_DELETED
     78 	|| event_type == GNOME_VFS_MONITOR_EVENT_CREATED)
     79       {
     80 	char *filename;
     81 
     82 	filename = mn_vfs_uri_extract_short_name(info_uri);
     83 	if (filename)
     84 	  {
     85 	    /*
     86 	     * The status of the mailbox can only have changed if the
     87 	     * subject of the event is the mark file or a message file
     88 	     * (having a numbered filename).
     89 	     */
     90 	    if (! strcmp(filename, SYLPHEED_MARK_FILE) || mn_str_isnumeric(filename))
     91 	      mn_vfs_mailbox_backend_queue_check(backend);
     92 
     93 	    g_free(filename);
     94 	  }
     95       }
     96   }
     97 
     98   override (MN:VFS:Mailbox:Backend) gboolean
     99     is (MNVFSMailboxBackend *dummy,
    100 	MNVFSMailboxBackendClass *class,
    101 	MNVFSMailbox *mailbox)
    102   {
    103     gboolean is;
    104     GnomeVFSURI *uri;
    105 
    106     uri = gnome_vfs_uri_append_file_name(mailbox->vfs_uri, SYLPHEED_MARK_FILE);
    107     is = mn_vfs_test(uri, G_FILE_TEST_IS_REGULAR);
    108     gnome_vfs_uri_unref(uri);
    109 
    110     return is;
    111   }
    112 
    113   private gboolean
    114     is_claws_mail_mailbox (self)
    115   {
    116     gboolean is;
    117     GnomeVFSURI *uri;
    118 
    119     uri = gnome_vfs_uri_append_file_name(MN_VFS_MAILBOX_BACKEND(self)->mailbox->vfs_uri, CLAWS_MAIL_CACHE_FILE);
    120     is = mn_vfs_test(uri, G_FILE_TEST_IS_REGULAR);
    121     gnome_vfs_uri_unref(uri);
    122 
    123     return is;
    124   }
    125 
    126   /* non-reentrant, must be called with a lock held */
    127   private gboolean
    128     has_sylpheed_locking (void)
    129   {
    130     static gboolean checked = FALSE;
    131     static gboolean has = FALSE;
    132 
    133     if (! checked)
    134       {
    135 	char *output;
    136 
    137 	if (g_spawn_command_line_sync("sylpheed --version", &output, NULL, NULL, NULL))
    138 	  {
    139 	    if (strstr(output, "+locking"))
    140 	      has = TRUE;
    141 	    g_free(output);
    142 	  }
    143 
    144 	checked = TRUE;
    145       }
    146 
    147     return has;
    148   }
    149 
    150   private void
    151     update_check_latency (self)
    152   {
    153     MNVFSMailboxBackend *backend = MN_VFS_MAILBOX_BACKEND(self);
    154 
    155     mn_vfs_mailbox_lock(backend->mailbox);
    156 
    157     /*
    158      * If it is a Claws Mail mailbox, the check_latency can be set to
    159      * 0, since Claws Mail does not write the mark file in place but
    160      * uses an atomic rename() to move the new mark file over the
    161      * previous one.
    162      */
    163     if (self_is_claws_mail_mailbox(self))
    164       backend->check_latency = 0;
    165     else
    166       {
    167 	/*
    168 	 * If Sylpheed was compiled with the locking patch and the
    169 	 * mailbox is local, check_latency can be set to 0, since we
    170 	 * lock the mark file while reading it.
    171 	 */
    172 	if (self_has_sylpheed_locking() && gnome_vfs_uri_is_local(backend->mailbox->vfs_uri))
    173 	  backend->check_latency = 0;
    174 	else
    175 	  /*
    176 	   * Otherwise, set check_latency to 3 seconds to avoid race
    177 	   * conditions that can occur when Sylpheed writes the mark
    178 	   * file while we read it.
    179 	   */
    180 	  backend->check_latency = 3000;
    181       }
    182 
    183     mn_vfs_mailbox_unlock(backend->mailbox);
    184   }
    185 
    186   override (MN:VFS:Mailbox:Backend) void
    187     check (MNVFSMailboxBackend *backend, int check_id)
    188   {
    189     GError *err = NULL;
    190     GnomeVFSResult result;
    191     GnomeVFSResult close_result;
    192     GnomeVFSDirectoryHandle *handle;
    193     GnomeVFSFileInfo *file_info;
    194     GHashTable *marks;
    195     GSList *messages = NULL;
    196     int num_errors = 0;
    197 
    198     self_update_check_latency(SELF(backend));
    199 
    200     mn_vfs_mailbox_backend_monitor(backend, check_id, backend->mailbox->uri, GNOME_VFS_MONITOR_DIRECTORY);
    201 
    202     marks = self_read_marks(backend->mailbox->vfs_uri, &err);
    203     if (! marks)
    204       {
    205 	if (! mn_reentrant_mailbox_check_aborted(MN_REENTRANT_MAILBOX(backend->mailbox), check_id))
    206 	  {
    207 	    GDK_THREADS_ENTER();
    208 
    209 	    mn_mailbox_set_error(MN_MAILBOX(backend->mailbox), _("unable to read %s: %s"), SYLPHEED_MARK_FILE, err->message);
    210 
    211 	    gdk_flush();
    212 	    GDK_THREADS_LEAVE();
    213 	  }
    214 
    215 	g_error_free(err);
    216 	return;
    217       }
    218 
    219     if (mn_reentrant_mailbox_check_aborted(MN_REENTRANT_MAILBOX(backend->mailbox), check_id))
    220       goto finish;
    221 
    222     result = gnome_vfs_directory_open_from_uri(&handle, backend->mailbox->vfs_uri, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
    223     if (result != GNOME_VFS_OK)
    224       {
    225 	if (! mn_reentrant_mailbox_check_aborted(MN_REENTRANT_MAILBOX(backend->mailbox), check_id))
    226 	  {
    227 	    GDK_THREADS_ENTER();
    228 
    229 	    mn_mailbox_set_error(MN_MAILBOX(backend->mailbox), _("unable to open folder: %s"), gnome_vfs_result_to_string(result));
    230 
    231 	    gdk_flush();
    232 	    GDK_THREADS_LEAVE();
    233 	  }
    234 
    235 	goto end;
    236       }
    237 
    238     file_info = gnome_vfs_file_info_new();
    239     while ((result = gnome_vfs_directory_read_next(handle, file_info)) == GNOME_VFS_OK)
    240       if (mn_str_isnumeric(file_info->name))
    241 	{
    242 	  guint32 num = atoi(file_info->name);
    243 	  guint32 sflags;
    244 	  gboolean has_mark;
    245 	  gpointer value;
    246 
    247 	  has_mark = g_hash_table_lookup_extended(marks, GUINT_TO_POINTER(num), NULL, &value);
    248 	  if (has_mark)
    249 	    sflags = GPOINTER_TO_UINT(value);
    250 
    251 	  if (! has_mark || (sflags & (SYLPHEED_MSG_NEW | SYLPHEED_MSG_UNREAD)) != 0)
    252 	    {
    253 	      MNMessageFlags flags = 0;
    254 	      MNVFSMessage *message;
    255 
    256 	      if (mn_reentrant_mailbox_check_aborted(MN_REENTRANT_MAILBOX(backend->mailbox), check_id))
    257 		break;
    258 
    259 	      if (! has_mark || (sflags & SYLPHEED_MSG_NEW) != 0)
    260 		flags |= MN_MESSAGE_NEW;
    261 
    262 	      /*
    263 	       * We set handle_status to FALSE, since Sylpheed has its
    264 	       * own way (mark file) of differencing seen/unseen
    265 	       * messages.
    266 	       */
    267 	      message = mn_vfs_message_new(MN_TYPE_SYLPHEED_MESSAGE,
    268 					   backend,
    269 					   NULL,
    270 					   backend->mailbox->vfs_uri,
    271 					   file_info->name,
    272 					   flags,
    273 					   FALSE,
    274 					   &err);
    275 	      if (message)
    276 		messages = g_slist_prepend(messages, message);
    277 	      else if (err)
    278 		{
    279 		  GnomeVFSURI *message_uri;
    280 		  char *message_text_uri;
    281 
    282 		  message_uri = gnome_vfs_uri_append_file_name(backend->mailbox->vfs_uri, file_info->name);
    283 		  message_text_uri = gnome_vfs_uri_to_string(message_uri, GNOME_VFS_URI_HIDE_PASSWORD);
    284 		  gnome_vfs_uri_unref(message_uri);
    285 
    286 		  mn_mailbox_warning(MN_MAILBOX(backend->mailbox), "cannot read message \"%s\": %s",
    287 				     message_text_uri, err->message);
    288 		  g_free(message_text_uri);
    289 		  g_clear_error(&err);
    290 
    291 		  num_errors++;
    292 		}
    293 	    }
    294 	}
    295     gnome_vfs_file_info_unref(file_info);
    296     close_result = gnome_vfs_directory_close(handle);
    297 
    298   finish:
    299     GDK_THREADS_ENTER();
    300 
    301     if (! mn_reentrant_mailbox_check_aborted(MN_REENTRANT_MAILBOX(backend->mailbox), check_id))
    302       {
    303 	if (result == GNOME_VFS_ERROR_EOF || result == GNOME_VFS_OK)
    304 	  {
    305 	    if (close_result == GNOME_VFS_OK)
    306 	      {
    307 		mn_mailbox_set_messages(MN_MAILBOX(backend->mailbox), messages);
    308 
    309 		if (num_errors != 0)
    310 		  mn_mailbox_set_error(MN_MAILBOX(backend->mailbox),
    311 				       ngettext("cannot read %i message",
    312 						"cannot read %i messages",
    313 						num_errors),
    314 				       num_errors);
    315 	      }
    316 	    else
    317 	      mn_mailbox_set_error(MN_MAILBOX(backend->mailbox), _("unable to close folder: %s"), gnome_vfs_result_to_string(close_result));
    318 	  }
    319 	else
    320 	  mn_mailbox_set_error(MN_MAILBOX(backend->mailbox), _("error while reading folder: %s"), gnome_vfs_result_to_string(result));
    321       }
    322 
    323     mn_g_object_slist_free(messages);
    324 
    325     gdk_flush();
    326     GDK_THREADS_LEAVE();
    327 
    328   end:
    329     g_hash_table_destroy(marks);
    330   }
    331 
    332   private gboolean
    333     read_local_mark_file (const char *filename (check null),
    334 			  gsize *size (check null),
    335 			  char **contents (check null),
    336 			  GError **err)
    337   {
    338     int fd;
    339     struct flock lock;
    340     GIOChannel *channel;
    341     GError *tmp_err = NULL;
    342     gboolean status = FALSE;
    343 
    344     fd = open(filename, O_RDONLY);
    345     if (fd < 0)
    346       {
    347 	g_set_error(err, 0, 0, "%s", g_strerror(errno));
    348 	return FALSE;
    349       }
    350 
    351     memset(&lock, 0, sizeof(lock));
    352     lock.l_start = 0;		/* from l_whence */
    353     lock.l_len = 0;		/* to end of file */
    354     lock.l_type = F_RDLCK;	/* read lock */
    355     lock.l_whence = SEEK_SET;	/* from start of file */
    356 
    357     /* ignore lock failures */
    358     fcntl(fd, F_SETLKW, &lock);
    359 
    360     channel = g_io_channel_unix_new(fd);
    361     if (g_io_channel_set_encoding(channel, NULL, &tmp_err) == G_IO_STATUS_NORMAL)
    362       {
    363 	if (g_io_channel_read_to_end(channel, contents, size, err) == G_IO_STATUS_NORMAL)
    364 	  status = TRUE;
    365       }
    366     else
    367       {
    368 	g_set_error(err, 0, 0, _("unable to set encoding: %s"), tmp_err->message);
    369 	g_error_free(tmp_err);
    370       }
    371 
    372     g_io_channel_shutdown(channel, FALSE, NULL);
    373     g_io_channel_unref(channel);
    374 
    375     return status;
    376   }
    377 
    378   private gboolean
    379     read_remote_mark_file (GnomeVFSURI *uri (check null),
    380 			   gsize *size (check null),
    381 			   char **contents (check null),
    382 			   GError **err)
    383   {
    384     GnomeVFSResult result;
    385     int _size;
    386 
    387     result = mn_vfs_read_entire_file_uri(uri, &_size, contents);
    388     if (result == GNOME_VFS_OK)
    389       {
    390 	*size = _size;
    391 	return TRUE;
    392       }
    393     else
    394       {
    395 	g_set_error(err, 0, 0, "%s", gnome_vfs_result_to_string(result));
    396 	return FALSE;
    397       }
    398   }
    399 
    400   private gboolean
    401     read_mark_file (GnomeVFSURI *mailbox_uri (check null),
    402 		    gsize *size (check null),
    403 		    char **contents (check null),
    404 		    GError **err)
    405   {
    406     GnomeVFSURI *markfile_uri;
    407     char *filename;
    408     gboolean status;
    409 
    410     markfile_uri = gnome_vfs_uri_append_file_name(mailbox_uri, SYLPHEED_MARK_FILE);
    411 
    412     filename = mn_vfs_get_local_path(markfile_uri);
    413     if (filename)
    414       {
    415 	status = self_read_local_mark_file(filename, size, contents, err);
    416 	g_free(filename);
    417       }
    418     else
    419       status = self_read_remote_mark_file(markfile_uri, size, contents, err);
    420 
    421     gnome_vfs_uri_unref(markfile_uri);
    422 
    423     return status;
    424   }
    425 
    426   private gboolean
    427     byte_stream_read (ByteStream *stream (check null),
    428 		      gpointer buf (check null),
    429 		      int size,
    430 		      GError **err)
    431   {
    432     if (stream->bytes_left >= size)
    433       {
    434 	memcpy(buf, stream->position, size);
    435 
    436 	stream->position += size;
    437 	stream->bytes_left -= size;
    438 
    439 	return TRUE;
    440       }
    441     else
    442       {
    443 	g_set_error(err, 0, 0, _("unexpected end of file"));
    444 	return FALSE;
    445       }
    446   }
    447 
    448   protected GHashTable *
    449     read_marks (GnomeVFSURI *mailbox_uri (check null), GError **err)
    450   {
    451     GHashTable *marks = NULL;
    452     gsize bytes_left;
    453     char *buf;
    454 
    455     if (self_read_mark_file(mailbox_uri, &bytes_left, &buf, err))
    456       {
    457 	ByteStream stream = { buf, bytes_left };
    458 	guint32 version;
    459 
    460 	if (self_byte_stream_read(&stream, &version, sizeof(version), err))
    461 	  {
    462 	    if (version == SYLPHEED_MARK_VERSION)
    463 	      {
    464 		guint32 num;
    465 
    466 		marks = g_hash_table_new(g_direct_hash, g_direct_equal);
    467 
    468 		while (self_byte_stream_read(&stream, &num, sizeof(num), NULL))
    469 		  {
    470 		    guint32 flags;
    471 
    472 		    if (! self_byte_stream_read(&stream, &flags, sizeof(flags), err))
    473 		      {
    474 			g_hash_table_destroy(marks);
    475 			marks = NULL;
    476 			break;
    477 		      }
    478 
    479 		    g_hash_table_insert(marks, GUINT_TO_POINTER(num), GUINT_TO_POINTER(flags));
    480 		  }
    481 	      }
    482 	    else
    483 	      g_set_error(err, 0, 0, _("incompatible file version \"%i\""), version);
    484 	  }
    485 
    486 	g_free(buf);
    487       }
    488 
    489     return marks;
    490   }
    491 
    492   private void
    493     write_marks_foreach_cb (gpointer key, gpointer value, gpointer data)
    494   {
    495     GByteArray *array = data;
    496     guint32 num;
    497     guint32 flags;
    498 
    499     num = GPOINTER_TO_UINT(key);
    500     flags = GPOINTER_TO_UINT(value);
    501 
    502     g_byte_array_append(array, (const guint8 *) &num, sizeof(num));
    503     g_byte_array_append(array, (const guint8 *) &flags, sizeof(flags));
    504   }
    505 
    506   /* writes to the same mark file must be serialized (see below) */
    507   protected gboolean
    508     write_marks (GnomeVFSURI *mailbox_uri (check null),
    509 		 GHashTable *marks (check null),
    510 		 GError **err)
    511   {
    512     GByteArray *data;
    513     guint32 version;
    514     GnomeVFSURI *markfile_uri;
    515     gboolean status;
    516 
    517     /*
    518      * We do not need to lock the mark file: races with Sylpheed are
    519      * avoided since mn_vfs_write_entire_file_uri_safe() writes the
    520      * file atomically (with a move).
    521      *
    522      * However, only one thread at once can write a given mark file,
    523      * since mn_vfs_write_entire_file_uri_safe() is not thread-safe
    524      * (it uses fixed temporary file names, foo.tmp and foo.old).
    525      */
    526 
    527     data = g_byte_array_new();
    528 
    529     version = SYLPHEED_MARK_VERSION;
    530     g_byte_array_append(data, (const guint8 *) &version, sizeof(version));
    531 
    532     g_hash_table_foreach(marks, self_write_marks_foreach_cb, data);
    533 
    534     markfile_uri = gnome_vfs_uri_append_file_name(mailbox_uri, SYLPHEED_MARK_FILE);
    535     /* Sylpheed uses S_IRUSR | S_IWUSR for data files */
    536     status = mn_vfs_write_entire_file_uri_safe(markfile_uri, data->len, data->data, S_IRUSR | S_IWUSR, err);
    537     gnome_vfs_uri_unref(markfile_uri);
    538 
    539     g_byte_array_free(data, TRUE);
    540 
    541     return status;
    542   }
    543 }