1 /*
2  * camel-subscribable.c
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-data-server-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 
22 #include "camel-async-closure.h"
23 #include "camel-debug.h"
24 #include "camel-session.h"
25 #include "camel-vtrash-folder.h"
26 
27 #include "camel-subscribable.h"
28 
29 typedef struct _AsyncContext AsyncContext;
30 typedef struct _SignalClosure SignalClosure;
31 
32 struct _AsyncContext {
33 	gchar *folder_name;
34 };
35 
36 struct _SignalClosure {
37 	GWeakRef subscribable;
38 	CamelFolderInfo *folder_info;
39 };
40 
41 enum {
42 	FOLDER_SUBSCRIBED,
43 	FOLDER_UNSUBSCRIBED,
44 	LAST_SIGNAL
45 };
46 
47 static guint signals[LAST_SIGNAL];
48 
G_DEFINE_INTERFACE(CamelSubscribable,camel_subscribable,CAMEL_TYPE_STORE)49 G_DEFINE_INTERFACE (CamelSubscribable, camel_subscribable, CAMEL_TYPE_STORE)
50 
51 static void
52 async_context_free (AsyncContext *async_context)
53 {
54 	g_free (async_context->folder_name);
55 
56 	g_slice_free (AsyncContext, async_context);
57 }
58 
59 static void
signal_closure_free(SignalClosure * signal_closure)60 signal_closure_free (SignalClosure *signal_closure)
61 {
62 	g_weak_ref_clear (&signal_closure->subscribable);
63 
64 	if (signal_closure->folder_info != NULL)
65 		camel_folder_info_free (signal_closure->folder_info);
66 
67 	g_slice_free (SignalClosure, signal_closure);
68 }
69 
70 static gboolean
subscribable_emit_folder_subscribed_cb(gpointer user_data)71 subscribable_emit_folder_subscribed_cb (gpointer user_data)
72 {
73 	SignalClosure *signal_closure = user_data;
74 	CamelSubscribable *subscribable;
75 
76 	subscribable = g_weak_ref_get (&signal_closure->subscribable);
77 
78 	if (subscribable != NULL) {
79 		g_signal_emit (
80 			subscribable,
81 			signals[FOLDER_SUBSCRIBED], 0,
82 			signal_closure->folder_info);
83 		g_object_unref (subscribable);
84 	}
85 
86 	return FALSE;
87 }
88 
89 static gboolean
subscribable_emit_folder_unsubscribed_cb(gpointer user_data)90 subscribable_emit_folder_unsubscribed_cb (gpointer user_data)
91 {
92 	SignalClosure *signal_closure = user_data;
93 	CamelSubscribable *subscribable;
94 
95 	subscribable = g_weak_ref_get (&signal_closure->subscribable);
96 
97 	if (subscribable != NULL) {
98 		g_signal_emit (
99 			subscribable,
100 			signals[FOLDER_UNSUBSCRIBED], 0,
101 			signal_closure->folder_info);
102 		g_object_unref (subscribable);
103 	}
104 
105 	return FALSE;
106 }
107 
108 static void
camel_subscribable_default_init(CamelSubscribableInterface * iface)109 camel_subscribable_default_init (CamelSubscribableInterface *iface)
110 {
111 	signals[FOLDER_SUBSCRIBED] = g_signal_new (
112 		"folder-subscribed",
113 		G_OBJECT_CLASS_TYPE (iface),
114 		G_SIGNAL_RUN_FIRST,
115 		G_STRUCT_OFFSET (
116 			CamelSubscribableInterface,
117 			folder_subscribed),
118 		NULL, NULL, NULL,
119 		G_TYPE_NONE, 1,
120 		CAMEL_TYPE_FOLDER_INFO);
121 
122 	signals[FOLDER_UNSUBSCRIBED] = g_signal_new (
123 		"folder-unsubscribed",
124 		G_OBJECT_CLASS_TYPE (iface),
125 		G_SIGNAL_RUN_FIRST,
126 		G_STRUCT_OFFSET (
127 			CamelSubscribableInterface,
128 			folder_unsubscribed),
129 		NULL, NULL, NULL,
130 		G_TYPE_NONE, 1,
131 		CAMEL_TYPE_FOLDER_INFO);
132 }
133 
134 /**
135  * camel_subscribable_folder_is_subscribed:
136  * @subscribable: a #CamelSubscribable
137  * @folder_name: full path of the folder
138  *
139  * Find out if a folder has been subscribed to.
140  *
141  * Returns: %TRUE if the folder has been subscribed to or %FALSE otherwise
142  *
143  * Since: 3.2
144  **/
145 gboolean
camel_subscribable_folder_is_subscribed(CamelSubscribable * subscribable,const gchar * folder_name)146 camel_subscribable_folder_is_subscribed (CamelSubscribable *subscribable,
147                                          const gchar *folder_name)
148 {
149 	CamelSubscribableInterface *iface;
150 
151 	g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
152 	g_return_val_if_fail (folder_name != NULL, FALSE);
153 
154 	iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
155 	g_return_val_if_fail (iface->folder_is_subscribed != NULL, FALSE);
156 
157 	return iface->folder_is_subscribed (subscribable, folder_name);
158 }
159 
160 /**
161  * camel_subscribable_subscribe_folder_sync:
162  * @subscribable: a #CamelSubscribable
163  * @folder_name: full path of the folder
164  * @cancellable: optional #GCancellable object, or %NULL
165  * @error: return location for a #GError, or %NULL
166  *
167  * Subscribes to the folder described by @folder_name.
168  *
169  * Returns: %TRUE on success, %FALSE on error
170  *
171  * Since: 3.2
172  **/
173 gboolean
camel_subscribable_subscribe_folder_sync(CamelSubscribable * subscribable,const gchar * folder_name,GCancellable * cancellable,GError ** error)174 camel_subscribable_subscribe_folder_sync (CamelSubscribable *subscribable,
175                                           const gchar *folder_name,
176                                           GCancellable *cancellable,
177                                           GError **error)
178 {
179 	CamelAsyncClosure *closure;
180 	GAsyncResult *result;
181 	gboolean success;
182 
183 	g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
184 	g_return_val_if_fail (folder_name != NULL, FALSE);
185 
186 	closure = camel_async_closure_new ();
187 
188 	camel_subscribable_subscribe_folder (
189 		subscribable, folder_name,
190 		G_PRIORITY_DEFAULT, cancellable,
191 		camel_async_closure_callback, closure);
192 
193 	result = camel_async_closure_wait (closure);
194 
195 	success = camel_subscribable_subscribe_folder_finish (
196 		subscribable, result, error);
197 
198 	camel_async_closure_free (closure);
199 
200 	return success;
201 }
202 
203 /* Helper for camel_subscribable_subscribe_folder() */
204 static void
subscribable_subscribe_folder_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)205 subscribable_subscribe_folder_thread (GTask *task,
206                                       gpointer source_object,
207                                       gpointer task_data,
208                                       GCancellable *cancellable)
209 {
210 	CamelSubscribable *subscribable;
211 	CamelSubscribableInterface *iface;
212 	const gchar *folder_name;
213 	const gchar *message;
214 	gboolean success;
215 	AsyncContext *async_context;
216 	GError *local_error = NULL;
217 
218 	subscribable = CAMEL_SUBSCRIBABLE (source_object);
219 	async_context = (AsyncContext *) task_data;
220 
221 	folder_name = async_context->folder_name;
222 
223 	iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
224 	g_return_if_fail (iface->subscribe_folder_sync != NULL);
225 
226 	/* Need to establish a connection before subscribing. */
227 	camel_service_connect_sync (
228 		CAMEL_SERVICE (subscribable), cancellable, &local_error);
229 	if (local_error != NULL) {
230 		g_task_return_error (task, local_error);
231 		return;
232 	}
233 
234 	message = _("Subscribing to folder “%s”");
235 	camel_operation_push_message (cancellable, message, folder_name);
236 
237 	success = iface->subscribe_folder_sync (
238 		subscribable, folder_name, cancellable, &local_error);
239 	CAMEL_CHECK_LOCAL_GERROR (
240 		subscribable, subscribe_folder_sync, success, local_error);
241 
242 	camel_operation_pop_message (cancellable);
243 
244 	if (local_error != NULL) {
245 		g_task_return_error (task, local_error);
246 	} else {
247 		g_task_return_boolean (task, success);
248 	}
249 }
250 
251 /**
252  * camel_subscribable_subscribe_folder:
253  * @subscribable: a #CamelSubscribable
254  * @folder_name: full path of the folder
255  * @io_priority: the I/O priority of the request
256  * @cancellable: optional #GCancellable object, or %NULL
257  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
258  * @user_data: data to pass to the callback function
259  *
260  * Asynchronously subscribes to the folder described by @folder_name.
261  *
262  * When the operation is finished, @callback will be called.  You can then
263  * call camel_subscribable_subscribe_folder_finish() to get the result of
264  * the operation.
265  *
266  * Since: 3.2
267  **/
268 void
camel_subscribable_subscribe_folder(CamelSubscribable * subscribable,const gchar * folder_name,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)269 camel_subscribable_subscribe_folder (CamelSubscribable *subscribable,
270                                      const gchar *folder_name,
271                                      gint io_priority,
272                                      GCancellable *cancellable,
273                                      GAsyncReadyCallback callback,
274                                      gpointer user_data)
275 {
276 	GTask *task;
277 	CamelService *service;
278 	AsyncContext *async_context;
279 
280 	g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
281 	g_return_if_fail (folder_name != NULL);
282 
283 	service = CAMEL_SERVICE (subscribable);
284 
285 	async_context = g_slice_new0 (AsyncContext);
286 	async_context->folder_name = g_strdup (folder_name);
287 
288 	task = g_task_new (subscribable, cancellable, callback, user_data);
289 	g_task_set_source_tag (task, camel_subscribable_subscribe_folder);
290 	g_task_set_priority (task, io_priority);
291 
292 	g_task_set_task_data (
293 		task, async_context,
294 		(GDestroyNotify) async_context_free);
295 
296 	camel_service_queue_task (
297 		service, task, subscribable_subscribe_folder_thread);
298 
299 	g_object_unref (task);
300 }
301 
302 /**
303  * camel_subscribable_subscribe_folder_finish:
304  * @subscribable: a #CamelSubscribable
305  * @result: a #GAsyncResult
306  * @error: return location for a #GError, or %NULL
307  *
308  * Finishes the operation started with camel_subscribable_subscribe_folder().
309  *
310  * Returns: %TRUE on success, %FALSE on error
311  *
312  * Since: 3.2
313  **/
314 gboolean
camel_subscribable_subscribe_folder_finish(CamelSubscribable * subscribable,GAsyncResult * result,GError ** error)315 camel_subscribable_subscribe_folder_finish (CamelSubscribable *subscribable,
316                                             GAsyncResult *result,
317                                             GError **error)
318 {
319 	g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
320 	g_return_val_if_fail (g_task_is_valid (result, subscribable), FALSE);
321 
322 	g_return_val_if_fail (
323 		g_async_result_is_tagged (
324 		result, camel_subscribable_subscribe_folder), FALSE);
325 
326 	return g_task_propagate_boolean (G_TASK (result), error);
327 }
328 
329 /**
330  * camel_subscribable_unsubscribe_folder_sync:
331  * @subscribable: a #CamelSubscribable
332  * @folder_name: full path of the folder
333  * @cancellable: optional #GCancellable object, or %NULL
334  * @error: return location for a #GError, or %NULL
335  *
336  * Unsubscribes from the folder described by @folder_name.
337  *
338  * Returns: %TRUE on success, %FALSE on error
339  *
340  * Since: 3.2
341  **/
342 gboolean
camel_subscribable_unsubscribe_folder_sync(CamelSubscribable * subscribable,const gchar * folder_name,GCancellable * cancellable,GError ** error)343 camel_subscribable_unsubscribe_folder_sync (CamelSubscribable *subscribable,
344                                             const gchar *folder_name,
345                                             GCancellable *cancellable,
346                                             GError **error)
347 {
348 	CamelAsyncClosure *closure;
349 	GAsyncResult *result;
350 	gboolean success;
351 
352 	g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
353 	g_return_val_if_fail (folder_name != NULL, FALSE);
354 
355 	closure = camel_async_closure_new ();
356 
357 	camel_subscribable_unsubscribe_folder (
358 		subscribable, folder_name,
359 		G_PRIORITY_DEFAULT, cancellable,
360 		camel_async_closure_callback, closure);
361 
362 	result = camel_async_closure_wait (closure);
363 
364 	success = camel_subscribable_unsubscribe_folder_finish (
365 		subscribable, result, error);
366 
367 	camel_async_closure_free (closure);
368 
369 	return success;
370 }
371 
372 /* Helper for camel_subscribable_unsubscribe_folder() */
373 static void
subscribable_unsubscribe_folder_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)374 subscribable_unsubscribe_folder_thread (GTask *task,
375                                         gpointer source_object,
376                                         gpointer task_data,
377                                         GCancellable *cancellable)
378 {
379 	CamelSubscribable *subscribable;
380 	CamelSubscribableInterface *iface;
381 	const gchar *folder_name;
382 	const gchar *message;
383 	gboolean success;
384 	AsyncContext *async_context;
385 	GError *local_error = NULL;
386 
387 	subscribable = CAMEL_SUBSCRIBABLE (source_object);
388 	async_context = (AsyncContext *) task_data;
389 
390 	folder_name = async_context->folder_name;
391 
392 	iface = CAMEL_SUBSCRIBABLE_GET_INTERFACE (subscribable);
393 	g_return_if_fail (iface->unsubscribe_folder_sync != NULL);
394 
395 	/* Need to establish a connection before unsubscribing. */
396 	camel_service_connect_sync (
397 		CAMEL_SERVICE (subscribable), cancellable, &local_error);
398 	if (local_error != NULL) {
399 		g_task_return_error (task, local_error);
400 		return;
401 	}
402 
403 	message = _("Unsubscribing from folder “%s”");
404 	camel_operation_push_message (cancellable, message, folder_name);
405 
406 	success = iface->unsubscribe_folder_sync (
407 		subscribable, folder_name, cancellable, &local_error);
408 	CAMEL_CHECK_LOCAL_GERROR (
409 		subscribable, unsubscribe_folder_sync, success, local_error);
410 
411 	if (success)
412 		camel_store_delete_cached_folder (CAMEL_STORE (subscribable), folder_name);
413 
414 	camel_operation_pop_message (cancellable);
415 
416 	if (local_error != NULL) {
417 		g_task_return_error (task, local_error);
418 	} else {
419 		g_task_return_boolean (task, success);
420 	}
421 }
422 
423 /**
424  * camel_subscribable_unsubscribe_folder:
425  * @subscribable: a #CamelSubscribable
426  * @folder_name: full path of the folder
427  * @io_priority: the I/O priority of the request
428  * @cancellable: optional #GCancellable object, or %NULL
429  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
430  * @user_data: data to pass to the callback function
431  *
432  * Asynchronously unsubscribes from the folder described by @folder_name.
433  *
434  * When the operation is finished, @callback will be called.  You can then
435  * call camel_subscribable_unsubscribe_folder_finish() to get the result of
436  * the operation.
437  *
438  * Since: 3.2
439  **/
440 void
camel_subscribable_unsubscribe_folder(CamelSubscribable * subscribable,const gchar * folder_name,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)441 camel_subscribable_unsubscribe_folder (CamelSubscribable *subscribable,
442                                        const gchar *folder_name,
443                                        gint io_priority,
444                                        GCancellable *cancellable,
445                                        GAsyncReadyCallback callback,
446                                        gpointer user_data)
447 {
448 	GTask *task;
449 	CamelService *service;
450 	AsyncContext *async_context;
451 
452 	g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
453 	g_return_if_fail (folder_name != NULL);
454 
455 	service = CAMEL_SERVICE (subscribable);
456 
457 	async_context = g_slice_new0 (AsyncContext);
458 	async_context->folder_name = g_strdup (folder_name);
459 
460 	task = g_task_new (subscribable, cancellable, callback, user_data);
461 	g_task_set_source_tag (task, camel_subscribable_unsubscribe_folder);
462 	g_task_set_priority (task, io_priority);
463 
464 	g_task_set_task_data (
465 		task, async_context,
466 		(GDestroyNotify) async_context_free);
467 
468 	camel_service_queue_task (
469 		service, task, subscribable_unsubscribe_folder_thread);
470 
471 	g_object_unref (task);
472 }
473 
474 /**
475  * camel_subscribable_unsubscribe_folder_finish:
476  * @subscribable: a #CamelSubscribable
477  * @result: a #GAsyncResult
478  * @error: return location for a #GError, or %NULL
479  *
480  * Finishes the operation started with camel_subscribable_unsubscribe_folder().
481  *
482  * Returns: %TRUE on success, %FALSE on error
483  *
484  * Since: 3.2
485  **/
486 gboolean
camel_subscribable_unsubscribe_folder_finish(CamelSubscribable * subscribable,GAsyncResult * result,GError ** error)487 camel_subscribable_unsubscribe_folder_finish (CamelSubscribable *subscribable,
488                                               GAsyncResult *result,
489                                               GError **error)
490 {
491 	g_return_val_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable), FALSE);
492 	g_return_val_if_fail (g_task_is_valid (result, subscribable), FALSE);
493 
494 	g_return_val_if_fail (
495 		g_async_result_is_tagged (
496 		result, camel_subscribable_unsubscribe_folder), FALSE);
497 
498 	return g_task_propagate_boolean (G_TASK (result), error);
499 }
500 
501 /**
502  * camel_subscribable_folder_subscribed:
503  * @subscribable: a #CamelSubscribable
504  * @folder_info: information about the subscribed folder
505  *
506  * Emits the #CamelSubscribable::folder-subscribed signal from an idle source
507  * on the main loop.  The idle source's priority is #G_PRIORITY_HIGH_IDLE.
508  *
509  * This function is only intended for Camel providers.
510  *
511  * Since: 3.2
512  **/
513 void
camel_subscribable_folder_subscribed(CamelSubscribable * subscribable,CamelFolderInfo * folder_info)514 camel_subscribable_folder_subscribed (CamelSubscribable *subscribable,
515                                       CamelFolderInfo *folder_info)
516 {
517 	CamelService *service;
518 	CamelSession *session;
519 	SignalClosure *signal_closure;
520 
521 	g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
522 	g_return_if_fail (folder_info != NULL);
523 
524 	service = CAMEL_SERVICE (subscribable);
525 	session = camel_service_ref_session (service);
526 
527 	if (!session)
528 		return;
529 
530 	signal_closure = g_slice_new0 (SignalClosure);
531 	g_weak_ref_init (&signal_closure->subscribable, subscribable);
532 	signal_closure->folder_info = camel_folder_info_clone (folder_info);
533 
534 	/* Prioritize ahead of GTK+ redraws. */
535 	camel_session_idle_add (
536 		session, G_PRIORITY_HIGH_IDLE,
537 		subscribable_emit_folder_subscribed_cb,
538 		signal_closure,
539 		(GDestroyNotify) signal_closure_free);
540 
541 	g_object_unref (session);
542 }
543 
544 /**
545  * camel_subscribable_folder_unsubscribed:
546  * @subscribable: a #CamelSubscribable
547  * @folder_info: information about the unsubscribed folder
548  *
549  * Emits the #CamelSubscribable::folder-unsubscribed signal from an idle source
550  * on the main loop.  The idle source's priority is #G_PRIORITY_HIGH_IDLE.
551  *
552  * This function is only intended for Camel providers.
553  *
554  * Since: 3.2
555  **/
556 void
camel_subscribable_folder_unsubscribed(CamelSubscribable * subscribable,CamelFolderInfo * folder_info)557 camel_subscribable_folder_unsubscribed (CamelSubscribable *subscribable,
558                                         CamelFolderInfo *folder_info)
559 {
560 	CamelService *service;
561 	CamelSession *session;
562 	SignalClosure *signal_closure;
563 
564 	g_return_if_fail (CAMEL_IS_SUBSCRIBABLE (subscribable));
565 	g_return_if_fail (folder_info != NULL);
566 
567 	service = CAMEL_SERVICE (subscribable);
568 	session = camel_service_ref_session (service);
569 
570 	if (!session)
571 		return;
572 
573 	signal_closure = g_slice_new0 (SignalClosure);
574 	g_weak_ref_init (&signal_closure->subscribable, subscribable);
575 	signal_closure->folder_info = camel_folder_info_clone (folder_info);
576 
577 	/* Prioritize ahead of GTK+ redraws. */
578 	camel_session_idle_add (
579 		session, G_PRIORITY_HIGH_IDLE,
580 		subscribable_emit_folder_unsubscribed_cb,
581 		signal_closure,
582 		(GDestroyNotify) signal_closure_free);
583 
584 	g_object_unref (session);
585 }
586 
587