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