goa: Add missing linker flag (for real).
[platform/upstream/evolution-data-server.git] / libedataserver / e-source-refresh.c
1 /*
2  * e-source-refresh.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 /**
20  * SECTION: e-source-refresh
21  * @include: libedataserver/libedataserver.h
22  * @short_description: #ESource extension for refresh settings
23  *
24  * The #ESourceRefresh extension tracks the interval for fetching
25  * updates from a remote server.
26  *
27  * Access the extension as follows:
28  *
29  * |[
30  *   #include <libedataserver/libedataserver.h>
31  *
32  *   ESourceRefresh *extension;
33  *
34  *   extension = e_source_get_extension (source, E_SOURCE_EXTENSION_REFRESH);
35  * ]|
36  **/
37
38 #include "e-source-refresh.h"
39
40 #define E_SOURCE_REFRESH_GET_PRIVATE(obj) \
41         (G_TYPE_INSTANCE_GET_PRIVATE \
42         ((obj), E_TYPE_SOURCE_REFRESH, ESourceRefreshPrivate))
43
44 typedef struct _TimeoutNode TimeoutNode;
45
46 struct _ESourceRefreshPrivate {
47         gboolean enabled;
48         guint interval_minutes;
49
50         GMutex timeout_lock;
51         GHashTable *timeout_table;
52         guint next_timeout_id;
53 };
54
55 struct _TimeoutNode {
56         GSource *source;
57         GMainContext *context;
58         ESourceRefresh *extension;  /* not referenced */
59
60         ESourceRefreshFunc callback;
61         gpointer user_data;
62         GDestroyNotify notify;
63 };
64
65 enum {
66         PROP_0,
67         PROP_ENABLED,
68         PROP_INTERVAL_MINUTES
69 };
70
71 G_DEFINE_TYPE (
72         ESourceRefresh,
73         e_source_refresh,
74         E_TYPE_SOURCE_EXTENSION)
75
76 static TimeoutNode *
77 timeout_node_new (ESourceRefresh *extension,
78                   GMainContext *context,
79                   ESourceRefreshFunc callback,
80                   gpointer user_data,
81                   GDestroyNotify notify)
82 {
83         TimeoutNode *node;
84
85         if (context != NULL)
86                 g_main_context_ref (context);
87
88         node = g_slice_new0 (TimeoutNode);
89         node->context = context;
90         node->callback = callback;
91         node->user_data = user_data;
92         node->notify = notify;
93
94         /* Do not reference.  The timeout node will
95          * not outlive the ESourceRefresh extension. */
96         node->extension = extension;
97
98         return node;
99 }
100
101 static gboolean
102 timeout_node_invoke (gpointer data)
103 {
104         TimeoutNode *node = data;
105         ESourceExtension *extension;
106         ESource *source;
107
108         extension = E_SOURCE_EXTENSION (node->extension);
109         source = e_source_extension_ref_source (extension);
110         g_return_val_if_fail (source != NULL, FALSE);
111
112         /* We allow timeouts to be scheduled for disabled data sources
113          * but we don't invoke the callback.  Keeps the logic simple. */
114         if (e_source_get_enabled (source))
115                 node->callback (source, node->user_data);
116
117         g_object_unref (source);
118
119         return TRUE;
120 }
121
122 static void
123 timeout_node_attach (TimeoutNode *node)
124 {
125         guint interval_minutes;
126
127         if (node->source != NULL)
128                 return;
129
130         interval_minutes =
131                 e_source_refresh_get_interval_minutes (node->extension);
132         node->source = g_timeout_source_new_seconds (interval_minutes * 60);
133
134         g_source_set_callback (
135                 node->source,
136                 timeout_node_invoke,
137                 node,
138                 (GDestroyNotify) NULL);
139
140         g_source_attach (node->source, node->context);
141 }
142
143 static void
144 timeout_node_detach (TimeoutNode *node)
145 {
146         if (node->source == NULL)
147                 return;
148
149         g_source_destroy (node->source);
150         g_source_unref (node->source);
151         node->source = NULL;
152 }
153
154 static void
155 timeout_node_free (TimeoutNode *node)
156 {
157         if (node->source != NULL)
158                 timeout_node_detach (node);
159
160         if (node->context != NULL)
161                 g_main_context_unref (node->context);
162
163         if (node->notify != NULL)
164                 node->notify (node->user_data);
165
166         g_slice_free (TimeoutNode, node);
167 }
168
169 static void
170 source_refresh_update_timeouts (ESourceRefresh *extension,
171                                 gboolean invoke_callbacks)
172 {
173         GList *list, *link;
174
175         g_mutex_lock (&extension->priv->timeout_lock);
176
177         list = g_hash_table_get_values (extension->priv->timeout_table);
178
179         for (link = list; link != NULL; link = g_list_next (link)) {
180                 TimeoutNode *node = link->data;
181
182                 timeout_node_detach (node);
183
184                 if (invoke_callbacks)
185                         timeout_node_invoke (node);
186
187                 if (e_source_refresh_get_enabled (extension))
188                         timeout_node_attach (node);
189         }
190
191         g_list_free (list);
192
193         g_mutex_unlock (&extension->priv->timeout_lock);
194 }
195
196 static gboolean
197 source_refresh_idle_cb (gpointer user_data)
198 {
199         ESource *source = E_SOURCE (user_data);
200
201         if (e_source_get_enabled (source))
202                 e_source_refresh_force_timeout (source);
203
204         return FALSE;
205 }
206
207 static void
208 source_refresh_notify_enabled_cb (ESource *source,
209                                   GParamSpec *pspec,
210                                   ESourceRefresh *extension)
211 {
212         GSource *idle_source;
213         GMainContext *main_context;
214
215         main_context = e_source_ref_main_context (source);
216
217         idle_source = g_idle_source_new ();
218         g_source_set_callback (
219                 idle_source,
220                 source_refresh_idle_cb,
221                 g_object_ref (source),
222                 (GDestroyNotify) g_object_unref);
223         g_source_attach (idle_source, main_context);
224         g_source_unref (idle_source);
225
226         g_main_context_unref (main_context);
227 }
228
229 static void
230 source_refresh_set_property (GObject *object,
231                              guint property_id,
232                              const GValue *value,
233                              GParamSpec *pspec)
234 {
235         switch (property_id) {
236                 case PROP_ENABLED:
237                         e_source_refresh_set_enabled (
238                                 E_SOURCE_REFRESH (object),
239                                 g_value_get_boolean (value));
240                         return;
241
242                 case PROP_INTERVAL_MINUTES:
243                         e_source_refresh_set_interval_minutes (
244                                 E_SOURCE_REFRESH (object),
245                                 g_value_get_uint (value));
246                         return;
247         }
248
249         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
250 }
251
252 static void
253 source_refresh_get_property (GObject *object,
254                              guint property_id,
255                              GValue *value,
256                              GParamSpec *pspec)
257 {
258         switch (property_id) {
259                 case PROP_ENABLED:
260                         g_value_set_boolean (
261                                 value,
262                                 e_source_refresh_get_enabled (
263                                 E_SOURCE_REFRESH (object)));
264                         return;
265
266                 case PROP_INTERVAL_MINUTES:
267                         g_value_set_uint (
268                                 value,
269                                 e_source_refresh_get_interval_minutes (
270                                 E_SOURCE_REFRESH (object)));
271                         return;
272         }
273
274         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
275 }
276
277 static void
278 source_refresh_dispose (GObject *object)
279 {
280         ESourceRefreshPrivate *priv;
281
282         priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
283
284         g_hash_table_remove_all (priv->timeout_table);
285
286         /* Chain up to parent's dispose() method. */
287         G_OBJECT_CLASS (e_source_refresh_parent_class)->dispose (object);
288 }
289
290 static void
291 source_refresh_finalize (GObject *object)
292 {
293         ESourceRefreshPrivate *priv;
294
295         priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
296
297         g_mutex_clear (&priv->timeout_lock);
298         g_hash_table_destroy (priv->timeout_table);
299
300         /* Chain up to parent's finalize() method. */
301         G_OBJECT_CLASS (e_source_refresh_parent_class)->finalize (object);
302 }
303
304 static void
305 source_refresh_constructed (GObject *object)
306 {
307         ESourceExtension *extension;
308         ESource *source;
309
310         /* Chain up to parent's constructed() method. */
311         G_OBJECT_CLASS (e_source_refresh_parent_class)->constructed (object);
312
313         extension = E_SOURCE_EXTENSION (object);
314         source = e_source_extension_ref_source (extension);
315
316         /* There should be no lifecycle issues here
317          * since we get finalized with our ESource. */
318         g_signal_connect (
319                 source, "notify::enabled",
320                 G_CALLBACK (source_refresh_notify_enabled_cb),
321                 extension);
322
323         g_object_unref (source);
324 }
325
326 static void
327 e_source_refresh_class_init (ESourceRefreshClass *class)
328 {
329         GObjectClass *object_class;
330         ESourceExtensionClass *extension_class;
331
332         g_type_class_add_private (class, sizeof (ESourceRefreshPrivate));
333
334         object_class = G_OBJECT_CLASS (class);
335         object_class->set_property = source_refresh_set_property;
336         object_class->get_property = source_refresh_get_property;
337         object_class->dispose = source_refresh_dispose;
338         object_class->finalize = source_refresh_finalize;
339         object_class->constructed = source_refresh_constructed;
340
341         extension_class = E_SOURCE_EXTENSION_CLASS (class);
342         extension_class->name = E_SOURCE_EXTENSION_REFRESH;
343
344         g_object_class_install_property (
345                 object_class,
346                 PROP_ENABLED,
347                 g_param_spec_boolean (
348                         "enabled",
349                         "Enabled",
350                         "Whether to periodically refresh",
351                         TRUE,
352                         G_PARAM_READWRITE |
353                         G_PARAM_CONSTRUCT |
354                         G_PARAM_STATIC_STRINGS |
355                         E_SOURCE_PARAM_SETTING));
356
357         g_object_class_install_property (
358                 object_class,
359                 PROP_INTERVAL_MINUTES,
360                 g_param_spec_uint (
361                         "interval-minutes",
362                         "Interval in Minutes",
363                         "Refresh interval in minutes",
364                         0, G_MAXUINT, 60,
365                         G_PARAM_READWRITE |
366                         G_PARAM_CONSTRUCT |
367                         G_PARAM_STATIC_STRINGS |
368                         E_SOURCE_PARAM_SETTING));
369 }
370
371 static void
372 e_source_refresh_init (ESourceRefresh *extension)
373 {
374         GHashTable *timeout_table;
375
376         timeout_table = g_hash_table_new_full (
377                 (GHashFunc) g_direct_hash,
378                 (GEqualFunc) g_direct_equal,
379                 (GDestroyNotify) NULL,
380                 (GDestroyNotify) timeout_node_free);
381
382         extension->priv = E_SOURCE_REFRESH_GET_PRIVATE (extension);
383         g_mutex_init (&extension->priv->timeout_lock);
384         extension->priv->timeout_table = timeout_table;
385         extension->priv->next_timeout_id = 1;
386 }
387
388 /**
389  * e_source_refresh_get_enabled:
390  * @extension: an #ESourceRefresh
391  *
392  * Returns whether to periodically fetch updates from a remote server.
393  *
394  * The refresh interval is determined by the #ESourceRefresh:interval-minutes
395  * property.
396  *
397  * Returns: whether periodic refresh is enabled
398  *
399  * Since: 3.6
400  **/
401 gboolean
402 e_source_refresh_get_enabled (ESourceRefresh *extension)
403 {
404         g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
405
406         return extension->priv->enabled;
407 }
408
409 /**
410  * e_source_refresh_set_enabled:
411  * @extension: an #ESourceRefresh
412  * @enabled: whether to enable periodic refresh
413  *
414  * Sets whether to periodically fetch updates from a remote server.
415  *
416  * The refresh interval is determined by the #ESourceRefresh:interval-minutes
417  * property.
418  *
419  * Since: 3.6
420  **/
421 void
422 e_source_refresh_set_enabled (ESourceRefresh *extension,
423                               gboolean enabled)
424 {
425         g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
426
427         extension->priv->enabled = enabled;
428
429         g_object_notify (G_OBJECT (extension), "enabled");
430
431         source_refresh_update_timeouts (extension, FALSE);
432 }
433
434 /**
435  * e_source_refresh_get_interval_minutes:
436  * @extension: an #ESourceRefresh
437  *
438  * Returns the interval for fetching updates from a remote server.
439  *
440  * Note this value is only effective when the #ESourceRefresh:enabled
441  * property is %TRUE.
442  *
443  * Returns: the interval in minutes
444  *
445  * Since: 3.6
446  **/
447 guint
448 e_source_refresh_get_interval_minutes (ESourceRefresh *extension)
449 {
450         g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
451
452         return extension->priv->interval_minutes;
453 }
454
455 /**
456  * e_source_refresh_set_interval_minutes:
457  * @extension: an #ESourceRefresh
458  * @interval_minutes: the interval in minutes
459  *
460  * Sets the interval for fetching updates from a remote server.
461  *
462  * Note this value is only effective when the #ESourceRefresh:enabled
463  * property is %TRUE.
464  *
465  * Since: 3.6
466  **/
467 void
468 e_source_refresh_set_interval_minutes (ESourceRefresh *extension,
469                                        guint interval_minutes)
470 {
471         g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
472
473         if (interval_minutes == extension->priv->interval_minutes)
474                 return;
475
476         extension->priv->interval_minutes = interval_minutes;
477
478         g_object_notify (G_OBJECT (extension), "interval-minutes");
479
480         source_refresh_update_timeouts (extension, FALSE);
481 }
482
483 /**
484  * e_source_refresh_add_timeout:
485  * @source: an #ESource
486  * @context: (allow-none): a #GMainContext, or %NULL (if %NULL, the default
487  *           context will be used)
488  * @callback: function to call on each timeout
489  * @user_data: data to pass to @callback
490  * @notify: (allow-none): function to call when the timeout is removed,
491  *          or %NULL
492  *
493  * This is a simple way to schedule a periodic data source refresh.
494  *
495  * Adds a timeout #GSource to @context and handles all the bookkeeping
496  * if @source's refresh #ESourceRefresh:enabled state or its refresh
497  * #ESourceRefresh:interval-minutes value changes.  The @callback is
498  * expected to dispatch an asynchronous job to connect to and fetch
499  * updates from a remote server.
500  *
501  * The returned ID can be passed to e_source_refresh_remove_timeout() to
502  * remove the timeout from @context.  Note the ID is a private handle and
503  * cannot be passed to g_source_remove().
504  *
505  * Returns: a refresh timeout ID
506  *
507  * Since: 3.6
508  **/
509 guint
510 e_source_refresh_add_timeout (ESource *source,
511                               GMainContext *context,
512                               ESourceRefreshFunc callback,
513                               gpointer user_data,
514                               GDestroyNotify notify)
515 {
516         ESourceRefresh *extension;
517         const gchar *extension_name;
518         TimeoutNode *node;
519         guint timeout_id;
520         gpointer key;
521
522         g_return_val_if_fail (E_IS_SOURCE (source), 0);
523         g_return_val_if_fail (callback != NULL, 0);
524
525         extension_name = E_SOURCE_EXTENSION_REFRESH;
526         extension = e_source_get_extension (source, extension_name);
527
528         g_mutex_lock (&extension->priv->timeout_lock);
529
530         timeout_id = extension->priv->next_timeout_id++;
531
532         key = GUINT_TO_POINTER (timeout_id);
533         node = timeout_node_new (
534                 extension, context, callback, user_data, notify);
535         g_hash_table_insert (extension->priv->timeout_table, key, node);
536
537         if (e_source_refresh_get_enabled (extension))
538                 timeout_node_attach (node);
539
540         g_mutex_unlock (&extension->priv->timeout_lock);
541
542         return timeout_id;
543 }
544
545 /**
546  * e_source_refresh_force_timeout:
547  * @source: an #ESource
548  *
549  * For all timeouts added with e_source_refresh_add_timeout(), invokes
550  * the #ESourceRefreshFunc callback immediately and then, if the refresh
551  * #ESourceRefresh:enabled state is TRUE, reschedules the timeout.
552  *
553  * This function is called automatically when the #ESource switches from
554  * disabled to enabled, but can also be useful when a network connection
555  * becomes available or when waking up from hibernation or suspend.
556  *
557  * Since: 3.6
558  **/
559 void
560 e_source_refresh_force_timeout (ESource *source)
561 {
562         ESourceRefresh *extension;
563         const gchar *extension_name;
564
565         g_return_if_fail (E_IS_SOURCE (source));
566
567         extension_name = E_SOURCE_EXTENSION_REFRESH;
568         extension = e_source_get_extension (source, extension_name);
569
570         source_refresh_update_timeouts (extension, TRUE);
571 }
572
573 /**
574  * e_source_refresh_remove_timeout:
575  * @source: an #ESource
576  * @refresh_timeout_id: a refresh timeout ID
577  *
578  * Removes a timeout #GSource added by e_source_refresh_add_timeout().
579  *
580  * Returns: %TRUE if the timeout was found and removed
581  *
582  * Since: 3.6
583  **/
584 gboolean
585 e_source_refresh_remove_timeout (ESource *source,
586                                  guint refresh_timeout_id)
587 {
588         ESourceRefresh *extension;
589         const gchar *extension_name;
590         gboolean removed;
591         gpointer key;
592
593         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
594         g_return_val_if_fail (refresh_timeout_id > 0, FALSE);
595
596         extension_name = E_SOURCE_EXTENSION_REFRESH;
597         extension = e_source_get_extension (source, extension_name);
598
599         g_mutex_lock (&extension->priv->timeout_lock);
600
601         key = GUINT_TO_POINTER (refresh_timeout_id);
602         removed = g_hash_table_remove (extension->priv->timeout_table, key);
603
604         g_mutex_unlock (&extension->priv->timeout_lock);
605
606         return removed;
607 }
608
609 /**
610  * e_source_refresh_remove_timeouts_by_data:
611  * @source: an #ESource
612  * @user_data: user data to match against timeout callbacks
613  *
614  * Removes all timeout #GSource's added by e_source_refresh_add_timeout()
615  * whose callback data pointer matches @user_data.
616  *
617  * Returns: the number of timeouts found and removed
618  *
619  * Since: 3.6
620  **/
621 guint
622 e_source_refresh_remove_timeouts_by_data (ESource *source,
623                                           gpointer user_data)
624 {
625         ESourceRefresh *extension;
626         const gchar *extension_name;
627         GQueue trash = G_QUEUE_INIT;
628         GHashTableIter iter;
629         gpointer key, value;
630         guint n_removed = 0;
631
632         g_return_val_if_fail (E_IS_SOURCE (source), 0);
633
634         extension_name = E_SOURCE_EXTENSION_REFRESH;
635         extension = e_source_get_extension (source, extension_name);
636
637         g_mutex_lock (&extension->priv->timeout_lock);
638
639         g_hash_table_iter_init (&iter, extension->priv->timeout_table);
640
641         while (g_hash_table_iter_next (&iter, &key, &value)) {
642                 TimeoutNode *node = value;
643
644                 if (node->user_data == user_data)
645                         g_queue_push_tail (&trash, key);
646         }
647
648         while ((key = g_queue_pop_head (&trash)) != NULL)
649                 if (g_hash_table_remove (extension->priv->timeout_table, key))
650                         n_removed++;
651
652         g_mutex_unlock (&extension->priv->timeout_lock);
653
654         return n_removed;
655 }
656