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 }