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 }