1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Michael Zucchi <notzed@ximian.com>
18 */
19
20 #include "evolution-data-server-config.h"
21
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30
31 #include <glib/gstdio.h>
32 #include <glib/gi18n-lib.h>
33
34 #include "camel-spool-folder.h"
35 #include "camel-spool-settings.h"
36 #include "camel-spool-store.h"
37
38 #define d(x)
39
40 #define REFRESH_INTERVAL 2
41
42 typedef enum _camel_spool_store_t {
43 CAMEL_SPOOL_STORE_INVALID,
44 CAMEL_SPOOL_STORE_MBOX, /* a single mbox */
45 CAMEL_SPOOL_STORE_ELM /* elm/pine/etc tree of mbox files in folders */
46 } camel_spool_store_t;
47
48 struct _CamelSpoolStorePrivate {
49 camel_spool_store_t store_type;
50 GFileMonitor *monitor;
51 GMutex refresh_lock;
52 guint refresh_id;
53 gint64 last_modified_time;
54 };
55
G_DEFINE_TYPE_WITH_PRIVATE(CamelSpoolStore,camel_spool_store,CAMEL_TYPE_MBOX_STORE)56 G_DEFINE_TYPE_WITH_PRIVATE (
57 CamelSpoolStore,
58 camel_spool_store,
59 CAMEL_TYPE_MBOX_STORE)
60
61 static camel_spool_store_t
62 spool_store_get_type (CamelSpoolStore *spool_store,
63 GError **error)
64 {
65 CamelLocalSettings *local_settings;
66 CamelSettings *settings;
67 CamelService *service;
68 camel_spool_store_t type;
69 struct stat st;
70 gchar *path;
71
72 if (spool_store->priv->store_type != CAMEL_SPOOL_STORE_INVALID)
73 return spool_store->priv->store_type;
74
75 service = CAMEL_SERVICE (spool_store);
76
77 settings = camel_service_ref_settings (service);
78
79 local_settings = CAMEL_LOCAL_SETTINGS (settings);
80 path = camel_local_settings_dup_path (local_settings);
81
82 g_object_unref (settings);
83
84 /* Check the path for validity while we have the opportunity. */
85
86 if (path == NULL || *path != '/') {
87 g_set_error (
88 error, CAMEL_STORE_ERROR,
89 CAMEL_STORE_ERROR_NO_FOLDER,
90 _("Store root %s is not an absolute path"),
91 (path != NULL) ? path : "(null)");
92 type = CAMEL_SPOOL_STORE_INVALID;
93
94 } else if (g_stat (path, &st) == -1) {
95 g_set_error (
96 error, G_IO_ERROR,
97 g_io_error_from_errno (errno),
98 _("Spool “%s” cannot be opened: %s"),
99 path, g_strerror (errno));
100 type = CAMEL_SPOOL_STORE_INVALID;
101
102 } else if (S_ISREG (st.st_mode)) {
103 type = CAMEL_SPOOL_STORE_MBOX;
104
105 } else if (S_ISDIR (st.st_mode)) {
106 type = CAMEL_SPOOL_STORE_ELM;
107
108 } else {
109 g_set_error (
110 error, CAMEL_STORE_ERROR,
111 CAMEL_STORE_ERROR_NO_FOLDER,
112 _("Spool “%s” is not a regular file or directory"),
113 path);
114 type = CAMEL_SPOOL_STORE_INVALID;
115 }
116
117 g_free (path);
118
119 spool_store->priv->store_type = type;
120
121 return type;
122 }
123
124 /* partially copied from mbox */
125 static void
spool_fill_fi(CamelStore * store,CamelFolderInfo * fi,guint32 flags,GCancellable * cancellable)126 spool_fill_fi (CamelStore *store,
127 CamelFolderInfo *fi,
128 guint32 flags,
129 GCancellable *cancellable)
130 {
131 CamelFolder *folder;
132
133 fi->unread = -1;
134 fi->total = -1;
135 folder = camel_object_bag_peek (camel_store_get_folders_bag (store), fi->full_name);
136 if (folder) {
137 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
138 camel_folder_refresh_info_sync (folder, cancellable, NULL);
139 fi->unread = camel_folder_get_unread_message_count (folder);
140 fi->total = camel_folder_get_message_count (folder);
141 g_object_unref (folder);
142 }
143 }
144
145 static CamelFolderInfo *
spool_new_fi(CamelStore * store,CamelFolderInfo * parent,CamelFolderInfo ** fip,const gchar * full,guint32 flags)146 spool_new_fi (CamelStore *store,
147 CamelFolderInfo *parent,
148 CamelFolderInfo **fip,
149 const gchar *full,
150 guint32 flags)
151 {
152 CamelFolderInfo *fi;
153 const gchar *name;
154
155 name = strrchr (full, '/');
156 if (name)
157 name++;
158 else
159 name = full;
160
161 fi = camel_folder_info_new ();
162 fi->full_name = g_strdup (full);
163 fi->display_name = g_strdup (name);
164 fi->unread = -1;
165 fi->total = -1;
166 fi->flags = flags;
167
168 fi->parent = parent;
169 fi->next = *fip;
170 *fip = fi;
171
172 return fi;
173 }
174
175 /* used to find out where we've visited already */
176 struct _inode {
177 dev_t dnode;
178 ino_t inode;
179 };
180
181 /* returns number of records found at or below this level */
182 static gint
scan_dir(CamelStore * store,GHashTable * visited,const gchar * root,const gchar * path,guint32 flags,CamelFolderInfo * parent,CamelFolderInfo ** fip,GCancellable * cancellable,GError ** error)183 scan_dir (CamelStore *store,
184 GHashTable *visited,
185 const gchar *root,
186 const gchar *path,
187 guint32 flags,
188 CamelFolderInfo *parent,
189 CamelFolderInfo **fip,
190 GCancellable *cancellable,
191 GError **error)
192 {
193 DIR *dir;
194 struct dirent *d;
195 gchar *name, *tmp, *fname;
196 gsize name_len;
197 CamelFolderInfo *fi = NULL;
198 struct stat st;
199 CamelFolder *folder;
200 gchar from[80];
201 FILE *fp;
202
203 d (printf ("checking dir '%s' part '%s' for mbox content\n", root, path));
204
205 /* look for folders matching the right structure, recursively */
206 if (path) {
207 name_len = strlen (root) + strlen (path) + 2;
208 name = alloca (name_len);
209 g_snprintf (name, name_len, "%s/%s", root, path);
210 } else
211 name = (gchar *) root; /* XXX casting away const */
212
213 if (g_stat (name, &st) == -1) {
214 g_set_error (
215 error, G_IO_ERROR,
216 g_io_error_from_errno (errno),
217 _("Could not scan folder “%s”: %s"),
218 name, g_strerror (errno));
219 } else if (S_ISREG (st.st_mode)) {
220 /* incase we start scanning from a file. messy duplication :-/ */
221 if (path) {
222 fi = spool_new_fi (
223 store, parent, fip, path,
224 CAMEL_FOLDER_NOINFERIORS |
225 CAMEL_FOLDER_NOCHILDREN);
226 spool_fill_fi (store, fi, flags, cancellable);
227 }
228 return 0;
229 }
230
231 dir = opendir (name);
232 if (dir == NULL) {
233 g_set_error (
234 error, G_IO_ERROR,
235 g_io_error_from_errno (errno),
236 _("Could not scan folder “%s”: %s"),
237 name, g_strerror (errno));
238 return -1;
239 }
240
241 if (path != NULL) {
242 fi = spool_new_fi (
243 store, parent, fip, path,
244 CAMEL_FOLDER_NOSELECT);
245 fip = &fi->child;
246 parent = fi;
247 }
248
249 while ((d = readdir (dir))) {
250 if (strcmp (d->d_name, ".") == 0
251 || strcmp (d->d_name, "..") == 0)
252 continue;
253
254 tmp = g_strdup_printf ("%s/%s", name, d->d_name);
255 if (g_stat (tmp, &st) == 0) {
256 if (path)
257 fname = g_strdup_printf (
258 "%s/%s", path, d->d_name);
259 else
260 fname = g_strdup (d->d_name);
261
262 if (S_ISREG (st.st_mode)) {
263 gint isfolder = FALSE;
264
265 /* first, see if we already have it open */
266 folder = camel_object_bag_peek (camel_store_get_folders_bag (store), fname);
267 if (folder == NULL) {
268 fp = fopen (tmp, "r");
269 if (fp != NULL) {
270 isfolder = (st.st_size == 0
271 || (fgets (from, sizeof (from), fp) != NULL
272 && strncmp (from, "From ", 5) == 0));
273 fclose (fp);
274 }
275 }
276
277 if (folder != NULL || isfolder) {
278 fi = spool_new_fi (
279 store, parent, fip, fname,
280 CAMEL_FOLDER_NOINFERIORS |
281 CAMEL_FOLDER_NOCHILDREN);
282 spool_fill_fi (
283 store, fi, flags, cancellable);
284 }
285 if (folder)
286 g_object_unref (folder);
287
288 } else if (S_ISDIR (st.st_mode)) {
289 struct _inode in = { st.st_dev, st.st_ino };
290
291 /* see if we've visited already */
292 if (g_hash_table_lookup (visited, &in) == NULL) {
293 struct _inode *inew = g_malloc (sizeof (*inew));
294
295 *inew = in;
296 g_hash_table_insert (visited, inew, inew);
297
298 if (scan_dir (store, visited, root, fname, flags, parent, fip, cancellable, error) == -1) {
299 g_free (tmp);
300 g_free (fname);
301 closedir (dir);
302 return -1;
303 }
304 }
305 }
306 g_free (fname);
307
308 }
309 g_free (tmp);
310 }
311 closedir (dir);
312
313 return 0;
314 }
315
316 static guint
inode_hash(gconstpointer d)317 inode_hash (gconstpointer d)
318 {
319 const struct _inode *v = d;
320
321 return v->inode ^ v->dnode;
322 }
323
324 static gboolean
inode_equal(gconstpointer a,gconstpointer b)325 inode_equal (gconstpointer a,
326 gconstpointer b)
327 {
328 const struct _inode *v1 = a, *v2 = b;
329
330 return v1->inode == v2->inode && v1->dnode == v2->dnode;
331 }
332
333 static void
inode_free(gpointer k,gpointer v,gpointer d)334 inode_free (gpointer k,
335 gpointer v,
336 gpointer d)
337 {
338 g_free (k);
339 }
340
341 static CamelFolderInfo *
get_folder_info_elm(CamelStore * store,const gchar * top,guint32 flags,GCancellable * cancellable,GError ** error)342 get_folder_info_elm (CamelStore *store,
343 const gchar *top,
344 guint32 flags,
345 GCancellable *cancellable,
346 GError **error)
347 {
348 CamelLocalSettings *local_settings;
349 CamelSettings *settings;
350 CamelService *service;
351 CamelFolderInfo *fi = NULL;
352 GHashTable *visited;
353 gchar *path;
354
355 service = CAMEL_SERVICE (store);
356
357 settings = camel_service_ref_settings (service);
358
359 local_settings = CAMEL_LOCAL_SETTINGS (settings);
360 path = camel_local_settings_dup_path (local_settings);
361
362 g_object_unref (settings);
363
364 visited = g_hash_table_new (inode_hash, inode_equal);
365
366 if (scan_dir (
367 store, visited, path, top, flags,
368 NULL, &fi, cancellable, error) == -1 && fi != NULL) {
369 camel_folder_info_free (fi);
370 fi = NULL;
371 }
372
373 g_hash_table_foreach (visited, inode_free, NULL);
374 g_hash_table_destroy (visited);
375
376 g_free (path);
377
378 return fi;
379 }
380
381 static CamelFolderInfo *
get_folder_info_mbox(CamelStore * store,const gchar * top,guint32 flags,GCancellable * cancellable,GError ** error)382 get_folder_info_mbox (CamelStore *store,
383 const gchar *top,
384 guint32 flags,
385 GCancellable *cancellable,
386 GError **error)
387 {
388 CamelFolderInfo *fi = NULL, *fip = NULL;
389
390 if (top == NULL || strcmp (top, "INBOX") == 0) {
391 fi = spool_new_fi (
392 store, NULL, &fip, "INBOX",
393 CAMEL_FOLDER_NOINFERIORS |
394 CAMEL_FOLDER_NOCHILDREN |
395 CAMEL_FOLDER_SYSTEM);
396 g_free (fi->display_name);
397 fi->display_name = g_strdup (_("Inbox"));
398 spool_fill_fi (store, fi, flags, cancellable);
399 }
400
401 return fi;
402 }
403
404 static gchar *
spool_store_get_name(CamelService * service,gboolean brief)405 spool_store_get_name (CamelService *service,
406 gboolean brief)
407 {
408 CamelLocalSettings *local_settings;
409 CamelSpoolStore *spool_store;
410 CamelSettings *settings;
411 gchar *name;
412 gchar *path;
413
414 spool_store = CAMEL_SPOOL_STORE (service);
415
416 settings = camel_service_ref_settings (service);
417
418 local_settings = CAMEL_LOCAL_SETTINGS (settings);
419 path = camel_local_settings_dup_path (local_settings);
420
421 g_object_unref (settings);
422
423 if (brief)
424 return path;
425
426 switch (spool_store_get_type (spool_store, NULL)) {
427 case CAMEL_SPOOL_STORE_MBOX:
428 name = g_strdup_printf (
429 _("Spool mail file %s"), path);
430 break;
431 case CAMEL_SPOOL_STORE_ELM:
432 name = g_strdup_printf (
433 _("Spool folder tree %s"), path);
434 break;
435 default:
436 name = g_strdup (_("Invalid spool"));
437 break;
438 }
439
440 g_free (path);
441
442 return name;
443 }
444
445 static CamelFolder *
spool_store_get_folder_sync(CamelStore * store,const gchar * folder_name,CamelStoreGetFolderFlags flags,GCancellable * cancellable,GError ** error)446 spool_store_get_folder_sync (CamelStore *store,
447 const gchar *folder_name,
448 CamelStoreGetFolderFlags flags,
449 GCancellable *cancellable,
450 GError **error)
451 {
452 CamelLocalSettings *local_settings;
453 CamelSpoolStore *spool_store;
454 CamelSettings *settings;
455 CamelService *service;
456 CamelFolder *folder = NULL;
457 camel_spool_store_t type;
458 struct stat st;
459 gchar *name;
460 gchar *path;
461
462 d (printf ("opening folder %s on path %s\n", folder_name, path));
463
464 spool_store = CAMEL_SPOOL_STORE (store);
465 type = spool_store_get_type (spool_store, error);
466
467 if (type == CAMEL_SPOOL_STORE_INVALID)
468 return NULL;
469
470 service = CAMEL_SERVICE (store);
471
472 settings = camel_service_ref_settings (service);
473
474 local_settings = CAMEL_LOCAL_SETTINGS (settings);
475 path = camel_local_settings_dup_path (local_settings);
476
477 g_object_unref (settings);
478
479 /* we only support an 'INBOX' in mbox mode */
480 if (type == CAMEL_SPOOL_STORE_MBOX) {
481 if (strcmp (folder_name, "INBOX") != 0) {
482 g_set_error (
483 error, CAMEL_STORE_ERROR,
484 CAMEL_STORE_ERROR_NO_FOLDER,
485 _("Folder “%s/%s” does not exist."),
486 path, folder_name);
487 } else {
488 folder = camel_spool_folder_new (
489 store, folder_name, flags, cancellable, error);
490 }
491 } else {
492 name = g_build_filename (path, folder_name, NULL);
493 if (g_stat (name, &st) == -1) {
494 if (errno != ENOENT) {
495 g_set_error (
496 error, G_IO_ERROR,
497 g_io_error_from_errno (errno),
498 _("Could not open folder “%s”:\n%s"),
499 folder_name, g_strerror (errno));
500 } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
501 g_set_error (
502 error, CAMEL_STORE_ERROR,
503 CAMEL_STORE_ERROR_NO_FOLDER,
504 _("Folder “%s” does not exist."),
505 folder_name);
506 } else {
507 gint fd = creat (name, 0600);
508 if (fd == -1) {
509 g_set_error (
510 error, G_IO_ERROR,
511 g_io_error_from_errno (errno),
512 _("Could not create folder “%s”:\n%s"),
513 folder_name, g_strerror (errno));
514 } else {
515 close (fd);
516 folder = camel_spool_folder_new (
517 store, folder_name, flags,
518 cancellable, error);
519 }
520 }
521 } else if (!S_ISREG (st.st_mode)) {
522 g_set_error (
523 error, CAMEL_STORE_ERROR,
524 CAMEL_STORE_ERROR_NO_FOLDER,
525 _("“%s” is not a mailbox file."), name);
526 } else {
527 folder = camel_spool_folder_new (
528 store, folder_name, flags, cancellable, error);
529 }
530 g_free (name);
531 }
532
533 g_free (path);
534
535 return folder;
536 }
537
538 static CamelFolderInfo *
spool_store_get_folder_info_sync(CamelStore * store,const gchar * top,CamelStoreGetFolderInfoFlags flags,GCancellable * cancellable,GError ** error)539 spool_store_get_folder_info_sync (CamelStore *store,
540 const gchar *top,
541 CamelStoreGetFolderInfoFlags flags,
542 GCancellable *cancellable,
543 GError **error)
544 {
545 CamelSpoolStore *spool_store;
546 CamelFolderInfo *folder_info = NULL;
547
548 spool_store = CAMEL_SPOOL_STORE (store);
549
550 switch (spool_store_get_type (spool_store, error)) {
551 case CAMEL_SPOOL_STORE_MBOX:
552 folder_info = get_folder_info_mbox (
553 store, top, flags, cancellable, error);
554 break;
555
556 case CAMEL_SPOOL_STORE_ELM:
557 folder_info = get_folder_info_elm (
558 store, top, flags, cancellable, error);
559 break;
560
561 default:
562 break;
563 }
564
565 return folder_info;
566 }
567
568 static CamelFolder *
spool_store_get_inbox_folder_sync(CamelStore * store,GCancellable * cancellable,GError ** error)569 spool_store_get_inbox_folder_sync (CamelStore *store,
570 GCancellable *cancellable,
571 GError **error)
572 {
573 CamelSpoolStore *spool_store;
574 CamelFolder *folder = NULL;
575
576 spool_store = CAMEL_SPOOL_STORE (store);
577
578 switch (spool_store_get_type (spool_store, error)) {
579 case CAMEL_SPOOL_STORE_MBOX:
580 folder = camel_store_get_folder_sync (
581 store, "INBOX", CAMEL_STORE_FOLDER_CREATE,
582 cancellable, error);
583 break;
584
585 case CAMEL_SPOOL_STORE_ELM:
586 g_set_error (
587 error, CAMEL_STORE_ERROR,
588 CAMEL_STORE_ERROR_NO_FOLDER,
589 _("Store does not support an INBOX"));
590 break;
591
592 default:
593 break;
594 }
595
596 return folder;
597 }
598
599 /* default implementation, only delete metadata */
600 static gboolean
spool_store_delete_folder_sync(CamelStore * store,const gchar * folder_name,GCancellable * cancellable,GError ** error)601 spool_store_delete_folder_sync (CamelStore *store,
602 const gchar *folder_name,
603 GCancellable *cancellable,
604 GError **error)
605 {
606 g_set_error (
607 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
608 _("Spool folders cannot be deleted"));
609
610 return FALSE;
611 }
612
613 /* default implementation, rename all */
614 static gboolean
spool_store_rename_folder_sync(CamelStore * store,const gchar * old,const gchar * new,GCancellable * cancellable,GError ** error)615 spool_store_rename_folder_sync (CamelStore *store,
616 const gchar *old,
617 const gchar *new,
618 GCancellable *cancellable,
619 GError **error)
620 {
621 g_set_error (
622 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
623 _("Spool folders cannot be renamed"));
624
625 return FALSE;
626 }
627
628 static gchar *
spool_store_get_full_path(CamelLocalStore * local_store,const gchar * full_name)629 spool_store_get_full_path (CamelLocalStore *local_store,
630 const gchar *full_name)
631 {
632 CamelLocalSettings *local_settings;
633 CamelSpoolStore *spool_store;
634 CamelSettings *settings;
635 CamelService *service;
636 gchar *full_path;
637 gchar *path;
638
639 service = CAMEL_SERVICE (local_store);
640
641 settings = camel_service_ref_settings (service);
642
643 local_settings = CAMEL_LOCAL_SETTINGS (settings);
644 path = camel_local_settings_dup_path (local_settings);
645
646 g_object_unref (settings);
647
648 spool_store = CAMEL_SPOOL_STORE (local_store);
649
650 switch (spool_store_get_type (spool_store, NULL)) {
651 case CAMEL_SPOOL_STORE_MBOX:
652 full_path = g_strdup (path);
653 break;
654
655 case CAMEL_SPOOL_STORE_ELM:
656 full_path = g_build_filename (path, full_name, NULL);
657 break;
658
659 default:
660 full_path = NULL;
661 break;
662 }
663
664 g_free (path);
665
666 return full_path;
667 }
668
669 static gchar *
spool_store_get_meta_path(CamelLocalStore * ls,const gchar * full_name,const gchar * ext)670 spool_store_get_meta_path (CamelLocalStore *ls,
671 const gchar *full_name,
672 const gchar *ext)
673 {
674 CamelService *service;
675 const gchar *user_data_dir;
676 gchar *path, *key;
677
678 service = CAMEL_SERVICE (ls);
679 user_data_dir = camel_service_get_user_data_dir (service);
680
681 key = camel_file_util_safe_filename (full_name);
682 path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
683 g_free (key);
684
685 return path;
686 }
687
688 typedef struct _RefreshData {
689 GWeakRef *spool_weak_ref;
690 gchar *folder_name;
691 } RefreshData;
692
693 static void
refresh_data_free(gpointer ptr)694 refresh_data_free (gpointer ptr)
695 {
696 RefreshData *rd = ptr;
697
698 if (rd) {
699 camel_utils_weak_ref_free (rd->spool_weak_ref);
700 g_free (rd->folder_name);
701 g_slice_free (RefreshData, rd);
702 }
703 }
704
705 static void
spool_store_refresh_folder_cb(CamelSession * session,GCancellable * cancellable,gpointer user_data,GError ** error)706 spool_store_refresh_folder_cb (CamelSession *session,
707 GCancellable *cancellable,
708 gpointer user_data,
709 GError **error)
710 {
711 RefreshData *rd = user_data;
712 CamelFolder *folder;
713 CamelSpoolStore *spool;
714
715 g_return_if_fail (rd != NULL);
716
717 spool = g_weak_ref_get (rd->spool_weak_ref);
718 if (!spool)
719 return;
720
721 if (rd->folder_name)
722 folder = camel_store_get_folder_sync (CAMEL_STORE (spool), rd->folder_name, CAMEL_STORE_FOLDER_NONE, cancellable, NULL);
723 else
724 folder = camel_store_get_inbox_folder_sync (CAMEL_STORE (spool), cancellable, NULL);
725
726 if (folder) {
727 CamelLocalFolder *lf;
728 GStatBuf st;
729
730 lf = CAMEL_LOCAL_FOLDER (folder);
731
732 if (g_stat (lf->folder_path, &st) == 0) {
733 CamelFolderSummary *summary;
734
735 summary = camel_folder_get_folder_summary (folder);
736
737 if (summary && camel_folder_summary_get_timestamp (summary) != st.st_mtime)
738 camel_folder_refresh_info_sync (folder, cancellable, error);
739 }
740
741 g_object_unref (folder);
742 }
743
744 g_object_unref (spool);
745 }
746
747 static gboolean
spool_store_submit_refresh_job_cb(gpointer user_data)748 spool_store_submit_refresh_job_cb (gpointer user_data)
749 {
750 RefreshData *rd = user_data;
751 CamelSpoolStore *spool;
752 gboolean scheduled = FALSE;
753
754 g_return_val_if_fail (rd != NULL, FALSE);
755
756 if (g_source_is_destroyed (g_main_current_source ())) {
757 refresh_data_free (rd);
758 return FALSE;
759 }
760
761 spool = g_weak_ref_get (rd->spool_weak_ref);
762
763 if (spool) {
764 gboolean refresh = FALSE;
765
766 g_mutex_lock (&spool->priv->refresh_lock);
767 if (spool->priv->refresh_id == g_source_get_id (g_main_current_source ())) {
768 spool->priv->refresh_id = 0;
769 refresh = TRUE;
770 }
771 g_mutex_unlock (&spool->priv->refresh_lock);
772
773 if (refresh) {
774 CamelSession *session;
775
776 session = camel_service_ref_session (CAMEL_SERVICE (spool));
777
778 if (session) {
779 camel_session_submit_job (session, _("Refreshing spool folder"),
780 spool_store_refresh_folder_cb, rd, refresh_data_free);
781 scheduled = TRUE;
782 g_object_unref (session);
783 }
784 }
785
786 g_object_unref (spool);
787 }
788
789 if (!scheduled)
790 refresh_data_free (rd);
791
792 return FALSE;
793 }
794
795 static void
spool_store_monitor_changed_cb(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,gpointer user_data)796 spool_store_monitor_changed_cb (GFileMonitor *monitor,
797 GFile *file,
798 GFile *other_file,
799 GFileMonitorEvent event_type,
800 gpointer user_data)
801 {
802 CamelSpoolStore *spool = user_data;
803 GStatBuf st;
804 const gchar *file_path;
805 gchar *full_path = NULL;
806 gchar *basename = NULL;
807 gboolean refresh = FALSE;
808
809 g_return_if_fail (CAMEL_IS_SPOOL_STORE (spool));
810
811 if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
812 return;
813
814 if (!file)
815 return;
816
817 file_path = g_file_peek_path (file);
818
819 switch (spool_store_get_type (spool, NULL)) {
820 case CAMEL_SPOOL_STORE_INVALID:
821 break;
822 case CAMEL_SPOOL_STORE_MBOX:
823 full_path = camel_local_store_get_full_path (CAMEL_LOCAL_STORE (spool), NULL);
824
825 if (g_strcmp0 (full_path, file_path) == 0)
826 refresh = TRUE;
827 break;
828 case CAMEL_SPOOL_STORE_ELM:
829 basename = g_file_get_basename (file);
830 full_path = camel_local_store_get_full_path (CAMEL_LOCAL_STORE (spool), basename);
831 if (g_strcmp0 (full_path, file_path) == 0)
832 refresh = TRUE;
833 break;
834 }
835
836 if (refresh && g_stat (file_path, &st) == 0 &&
837 st.st_mtime != spool->priv->last_modified_time) {
838 spool->priv->last_modified_time = st.st_mtime;
839
840 g_mutex_lock (&spool->priv->refresh_lock);
841
842 if (!spool->priv->refresh_id) {
843 RefreshData *rd;
844
845 rd = g_slice_new0 (RefreshData);
846 rd->spool_weak_ref = camel_utils_weak_ref_new (spool);
847 rd->folder_name = basename;
848 basename = NULL;
849
850 spool->priv->refresh_id = g_timeout_add_seconds (REFRESH_INTERVAL,
851 spool_store_submit_refresh_job_cb, rd);
852 }
853
854 g_mutex_unlock (&spool->priv->refresh_lock);
855 }
856
857 g_free (full_path);
858 g_free (basename);
859 }
860
861 static void
spool_store_update_listen_notifications_cb(GObject * settings,GParamSpec * param,gpointer user_data)862 spool_store_update_listen_notifications_cb (GObject *settings,
863 GParamSpec *param,
864 gpointer user_data)
865 {
866 CamelSpoolStore *spool = user_data;
867 gchar *path = NULL;
868 gboolean listen_notifications = FALSE;
869
870 g_return_if_fail (CAMEL_IS_SPOOL_STORE (spool));
871
872 g_object_get (settings,
873 "path", &path,
874 "listen-notifications", &listen_notifications,
875 NULL);
876
877 g_clear_object (&spool->priv->monitor);
878
879 spool->priv->store_type = CAMEL_SPOOL_STORE_INVALID;
880
881 if (listen_notifications && path &&
882 g_file_test (path, G_FILE_TEST_EXISTS)) {
883 GFile *file;
884
885 file = g_file_new_for_path (path);
886 spool->priv->monitor = g_file_monitor (file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL);
887
888 if (spool->priv->monitor) {
889 g_signal_connect_object (spool->priv->monitor, "changed",
890 G_CALLBACK (spool_store_monitor_changed_cb), spool, 0);
891 }
892
893 g_object_unref (file);
894 }
895
896 g_free (path);
897 }
898
899 static void
spool_store_connect_settings(GObject * object)900 spool_store_connect_settings (GObject *object)
901 {
902 CamelSettings *settings;
903
904 g_return_if_fail (CAMEL_IS_SPOOL_STORE (object));
905
906 settings = camel_service_ref_settings (CAMEL_SERVICE (object));
907 if (!settings)
908 return;
909
910 g_signal_connect_object (settings, "notify::listen-notifications",
911 G_CALLBACK (spool_store_update_listen_notifications_cb), object, 0);
912
913 g_signal_connect_object (settings, "notify::path",
914 G_CALLBACK (spool_store_update_listen_notifications_cb), object, 0);
915
916 spool_store_update_listen_notifications_cb (G_OBJECT (settings), NULL, object);
917
918 g_object_unref (settings);
919 }
920
921 static void
spool_store_settings_changed_cb(GObject * object,GParamSpec * param,gpointer user_data)922 spool_store_settings_changed_cb (GObject *object,
923 GParamSpec *param,
924 gpointer user_data)
925 {
926 g_return_if_fail (CAMEL_IS_SPOOL_STORE (object));
927
928 spool_store_connect_settings (object);
929 }
930
931 static void
spool_store_constructed(GObject * object)932 spool_store_constructed (GObject *object)
933 {
934 /* Chain up to parent's method. */
935 G_OBJECT_CLASS (camel_spool_store_parent_class)->constructed (object);
936
937 g_signal_connect (object, "notify::settings",
938 G_CALLBACK (spool_store_settings_changed_cb), NULL);
939
940 spool_store_connect_settings (object);
941 }
942
943 static void
spool_store_dispose(GObject * object)944 spool_store_dispose (GObject *object)
945 {
946 CamelSpoolStore *spool = CAMEL_SPOOL_STORE (object);
947
948 g_mutex_lock (&spool->priv->refresh_lock);
949 if (spool->priv->refresh_id) {
950 g_source_remove (spool->priv->refresh_id);
951 spool->priv->refresh_id = 0;
952 }
953 g_mutex_unlock (&spool->priv->refresh_lock);
954
955 g_clear_object (&spool->priv->monitor);
956
957 /* Chain up to parent's method. */
958 G_OBJECT_CLASS (camel_spool_store_parent_class)->dispose (object);
959 }
960
961 static void
spool_store_finalize(GObject * object)962 spool_store_finalize (GObject *object)
963 {
964 CamelSpoolStore *spool = CAMEL_SPOOL_STORE (object);
965
966 g_mutex_clear (&spool->priv->refresh_lock);
967
968 /* Chain up to parent's method. */
969 G_OBJECT_CLASS (camel_spool_store_parent_class)->finalize (object);
970 }
971
972 static void
camel_spool_store_class_init(CamelSpoolStoreClass * class)973 camel_spool_store_class_init (CamelSpoolStoreClass *class)
974 {
975 CamelServiceClass *service_class;
976 CamelStoreClass *store_class;
977 CamelLocalStoreClass *local_store_class;
978 GObjectClass *object_class;
979
980 object_class = G_OBJECT_CLASS (class);
981 object_class->constructed = spool_store_constructed;
982 object_class->dispose = spool_store_dispose;
983 object_class->finalize = spool_store_finalize;
984
985 service_class = CAMEL_SERVICE_CLASS (class);
986 service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
987 service_class->get_name = spool_store_get_name;
988
989 store_class = CAMEL_STORE_CLASS (class);
990 store_class->get_folder_sync = spool_store_get_folder_sync;
991 store_class->get_folder_info_sync = spool_store_get_folder_info_sync;
992 store_class->get_inbox_folder_sync = spool_store_get_inbox_folder_sync;
993 store_class->delete_folder_sync = spool_store_delete_folder_sync;
994 store_class->rename_folder_sync = spool_store_rename_folder_sync;
995
996 local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
997 local_store_class->get_full_path = spool_store_get_full_path;
998 local_store_class->get_meta_path = spool_store_get_meta_path;
999 }
1000
1001 static void
camel_spool_store_init(CamelSpoolStore * spool_store)1002 camel_spool_store_init (CamelSpoolStore *spool_store)
1003 {
1004 spool_store->priv = camel_spool_store_get_instance_private (spool_store);
1005 spool_store->priv->store_type = CAMEL_SPOOL_STORE_INVALID;
1006
1007 g_mutex_init (&spool_store->priv->refresh_lock);
1008 }
1009