Add new ESource classes.
[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/e-source-refresh.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/e-source-refresh.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_get_source (extension);
110         g_return_val_if_fail (E_IS_SOURCE (source), 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         return TRUE;
118 }
119
120 static void
121 timeout_node_attach (TimeoutNode *node)
122 {
123         guint interval_minutes;
124
125         if (node->source != NULL)
126                 return;
127
128         interval_minutes =
129                 e_source_refresh_get_interval_minutes (node->extension);
130         node->source = g_timeout_source_new_seconds (interval_minutes * 60);
131
132         g_source_set_callback (
133                 node->source,
134                 timeout_node_invoke,
135                 node,
136                 (GDestroyNotify) NULL);
137
138         g_source_attach (node->source, node->context);
139 }
140
141 static void
142 timeout_node_detach (TimeoutNode *node)
143 {
144         if (node->source == NULL)
145                 return;
146
147         g_source_destroy (node->source);
148         g_source_unref (node->source);
149         node->source = NULL;
150 }
151
152 static void
153 timeout_node_free (TimeoutNode *node)
154 {
155         if (node->source != NULL)
156                 timeout_node_detach (node);
157
158         if (node->context != NULL)
159                 g_main_context_unref (node->context);
160
161         if (node->notify != NULL)
162                 node->notify (node->user_data);
163
164         g_slice_free (TimeoutNode, node);
165 }
166
167 static void
168 source_refresh_update_timeouts (ESourceRefresh *extension,
169                                 gboolean invoke_callbacks)
170 {
171         GList *list, *link;
172
173         g_mutex_lock (extension->priv->timeout_lock);
174
175         list = g_hash_table_get_values (extension->priv->timeout_table);
176
177         for (link = list; link != NULL; link = g_list_next (link)) {
178                 TimeoutNode *node = link->data;
179
180                 timeout_node_detach (node);
181
182                 if (invoke_callbacks)
183                         timeout_node_invoke (node);
184
185                 if (e_source_refresh_get_enabled (extension))
186                         timeout_node_attach (node);
187         }
188
189         g_list_free (list);
190
191         g_mutex_unlock (extension->priv->timeout_lock);
192 }
193
194 static void
195 source_refresh_notify_enabled_cb (ESource *source,
196                                   GParamSpec *pspec,
197                                   ESourceRefresh *extension)
198 {
199         if (e_source_get_enabled (source))
200                 e_source_refresh_force_timeout (source);
201 }
202
203 static void
204 source_refresh_set_property (GObject *object,
205                              guint property_id,
206                              const GValue *value,
207                              GParamSpec *pspec)
208 {
209         switch (property_id) {
210                 case PROP_ENABLED:
211                         e_source_refresh_set_enabled (
212                                 E_SOURCE_REFRESH (object),
213                                 g_value_get_boolean (value));
214                         return;
215
216                 case PROP_INTERVAL_MINUTES:
217                         e_source_refresh_set_interval_minutes (
218                                 E_SOURCE_REFRESH (object),
219                                 g_value_get_uint (value));
220                         return;
221         }
222
223         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
224 }
225
226 static void
227 source_refresh_get_property (GObject *object,
228                              guint property_id,
229                              GValue *value,
230                              GParamSpec *pspec)
231 {
232         switch (property_id) {
233                 case PROP_ENABLED:
234                         g_value_set_boolean (
235                                 value,
236                                 e_source_refresh_get_enabled (
237                                 E_SOURCE_REFRESH (object)));
238                         return;
239
240                 case PROP_INTERVAL_MINUTES:
241                         g_value_set_uint (
242                                 value,
243                                 e_source_refresh_get_interval_minutes (
244                                 E_SOURCE_REFRESH (object)));
245                         return;
246         }
247
248         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
249 }
250
251 static void
252 source_refresh_dispose (GObject *object)
253 {
254         ESourceRefreshPrivate *priv;
255
256         priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
257
258         g_hash_table_remove_all (priv->timeout_table);
259
260         /* Chain up to parent's dispose() method. */
261         G_OBJECT_CLASS (e_source_refresh_parent_class)->dispose (object);
262 }
263
264 static void
265 source_refresh_finalize (GObject *object)
266 {
267         ESourceRefreshPrivate *priv;
268
269         priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
270
271         g_mutex_free (priv->timeout_lock);
272         g_hash_table_destroy (priv->timeout_table);
273
274         /* Chain up to parent's finalize() method. */
275         G_OBJECT_CLASS (e_source_refresh_parent_class)->finalize (object);
276 }
277
278 static void
279 source_refresh_constructed (GObject *object)
280 {
281         ESourceExtension *extension;
282         ESource *source;
283
284         /* Chain up to parent's constructed() method. */
285         G_OBJECT_CLASS (e_source_refresh_parent_class)->constructed (object);
286
287         extension = E_SOURCE_EXTENSION (object);
288         source = e_source_extension_get_source (extension);
289
290         /* There should be no lifecycle issues here
291          * since we get finalized with our ESource. */
292         g_signal_connect (
293                 source, "notify::enabled",
294                 G_CALLBACK (source_refresh_notify_enabled_cb),
295                 extension);
296 }
297
298 static void
299 e_source_refresh_class_init (ESourceRefreshClass *class)
300 {
301         GObjectClass *object_class;
302         ESourceExtensionClass *extension_class;
303
304         g_type_class_add_private (class, sizeof (ESourceRefreshPrivate));
305
306         object_class = G_OBJECT_CLASS (class);
307         object_class->set_property = source_refresh_set_property;
308         object_class->get_property = source_refresh_get_property;
309         object_class->dispose = source_refresh_dispose;
310         object_class->finalize = source_refresh_finalize;
311         object_class->constructed = source_refresh_constructed;
312
313         extension_class = E_SOURCE_EXTENSION_CLASS (class);
314         extension_class->name = E_SOURCE_EXTENSION_REFRESH;
315
316         g_object_class_install_property (
317                 object_class,
318                 PROP_ENABLED,
319                 g_param_spec_boolean (
320                         "enabled",
321                         "Enabled",
322                         "Whether to periodically refresh",
323                         TRUE,
324                         G_PARAM_READWRITE |
325                         G_PARAM_CONSTRUCT |
326                         G_PARAM_STATIC_STRINGS |
327                         E_SOURCE_PARAM_SETTING));
328
329         g_object_class_install_property (
330                 object_class,
331                 PROP_INTERVAL_MINUTES,
332                 g_param_spec_uint (
333                         "interval-minutes",
334                         "Interval in Minutes",
335                         "Refresh interval in minutes",
336                         0, G_MAXUINT, 60,
337                         G_PARAM_READWRITE |
338                         G_PARAM_CONSTRUCT |
339                         G_PARAM_STATIC_STRINGS |
340                         E_SOURCE_PARAM_SETTING));
341 }
342
343 static void
344 e_source_refresh_init (ESourceRefresh *extension)
345 {
346         GHashTable *timeout_table;
347
348         timeout_table = g_hash_table_new_full (
349                 (GHashFunc) g_direct_hash,
350                 (GEqualFunc) g_direct_equal,
351                 (GDestroyNotify) NULL,
352                 (GDestroyNotify) timeout_node_free);
353
354         extension->priv = E_SOURCE_REFRESH_GET_PRIVATE (extension);
355         extension->priv->timeout_lock = g_mutex_new ();
356         extension->priv->timeout_table = timeout_table;
357         extension->priv->next_timeout_id = 1;
358 }
359
360 /**
361  * e_source_refresh_get_enabled:
362  * @extension: an #ESourceRefresh
363  *
364  * Returns whether to periodically fetch updates from a remote server.
365  *
366  * The refresh interval is determined by the #ESourceRefresh:interval-minutes
367  * property.
368  *
369  * Returns: whether periodic refresh is enabled
370  *
371  * Since: 3.6
372  **/
373 gboolean
374 e_source_refresh_get_enabled (ESourceRefresh *extension)
375 {
376         g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
377
378         return extension->priv->enabled;
379 }
380
381 /**
382  * e_source_refresh_set_enabled:
383  * @extension: an #ESourceRefresh
384  * @enabled: whether to enable periodic refresh
385  *
386  * Sets whether to periodically fetch updates from a remote server.
387  *
388  * The refresh interval is determined by the #ESourceRefresh:interval-minutes
389  * property.
390  *
391  * Since: 3.6
392  **/
393 void
394 e_source_refresh_set_enabled (ESourceRefresh *extension,
395                               gboolean enabled)
396 {
397         g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
398
399         if (enabled == extension->priv->enabled)
400                 return;
401
402         extension->priv->enabled = enabled;
403
404         g_object_notify (G_OBJECT (extension), "enabled");
405
406         source_refresh_update_timeouts (extension, FALSE);
407 }
408
409 /**
410  * e_source_refresh_get_interval_minutes:
411  * @extension: an #ESourceRefresh
412  *
413  * Returns the interval for fetching updates from a remote server.
414  *
415  * Note this value is only effective when the #ESourceRefresh:enabled
416  * property is %TRUE.
417  *
418  * Returns: the interval in minutes
419  *
420  * Since: 3.6
421  **/
422 guint
423 e_source_refresh_get_interval_minutes (ESourceRefresh *extension)
424 {
425         g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
426
427         return extension->priv->interval_minutes;
428 }
429
430 /**
431  * e_source_refresh_set_interval_minutes:
432  * @extension: an #ESourceRefresh
433  * @interval_minutes: the interval in minutes
434  *
435  * Sets the interval for fetching updates from a remote server.
436  *
437  * Note this value is only effective when the #ESourceRefresh:enabled
438  * property is %TRUE.
439  *
440  * Since: 3.6
441  **/
442 void
443 e_source_refresh_set_interval_minutes (ESourceRefresh *extension,
444                                        guint interval_minutes)
445 {
446         g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
447
448         if (interval_minutes == extension->priv->interval_minutes)
449                 return;
450
451         extension->priv->interval_minutes = interval_minutes;
452
453         g_object_notify (G_OBJECT (extension), "interval-minutes");
454
455         source_refresh_update_timeouts (extension, FALSE);
456 }
457
458 /**
459  * e_source_refresh_add_timeout:
460  * @source: an #ESource
461  * @context: (allow-none): a #GMainContext, or %NULL (if %NULL, the default
462  *           context will be used)
463  * @callback: function to call on each timeout
464  * @user_data: data to pass to @callback
465  * @notify: (allow-none): function to call when the timeout is removed,
466  *          or %NULL
467  *
468  * This is a simple way to schedule a periodic data source refresh.
469  *
470  * Adds a timeout #GSource to @context and handles all the bookkeeping
471  * if @source's refresh #ESourceRefresh:enabled state or its refresh
472  * #ESourceRefresh:interval-minutes value changes.  The @callback is
473  * expected to dispatch an asynchronous job to connect to and fetch
474  * updates from a remote server.
475  *
476  * The returned ID can be passed to e_source_refresh_remove_timeout() to
477  * remove the timeout from @context.  Note the ID is a private handle and
478  * cannot be passed to g_source_remove().
479  *
480  * Returns: a refresh timeout ID
481  *
482  * Since: 3.6
483  **/
484 guint
485 e_source_refresh_add_timeout (ESource *source,
486                               GMainContext *context,
487                               ESourceRefreshFunc callback,
488                               gpointer user_data,
489                               GDestroyNotify notify)
490 {
491         ESourceRefresh *extension;
492         const gchar *extension_name;
493         TimeoutNode *node;
494         guint timeout_id;
495         gpointer key;
496
497         g_return_val_if_fail (E_IS_SOURCE (source), 0);
498         g_return_val_if_fail (callback != NULL, 0);
499
500         extension_name = E_SOURCE_EXTENSION_REFRESH;
501         extension = e_source_get_extension (source, extension_name);
502
503         g_mutex_lock (extension->priv->timeout_lock);
504
505         timeout_id = extension->priv->next_timeout_id++;
506
507         key = GUINT_TO_POINTER (timeout_id);
508         node = timeout_node_new (
509                 extension, context, callback, user_data, notify);
510         g_hash_table_insert (extension->priv->timeout_table, key, node);
511
512         if (e_source_refresh_get_enabled (extension))
513                 timeout_node_attach (node);
514
515         g_mutex_unlock (extension->priv->timeout_lock);
516
517         return timeout_id;
518 }
519
520 /**
521  * e_source_refresh_force_timeout:
522  * @source: an #ESource
523  *
524  * For all timeouts added with e_source_refresh_add_timeout(), invokes
525  * the #ESourceRefreshFunc callback immediately and then, if the refresh
526  * #ESourceRefresh:enabled state is TRUE, reschedules the timeout.
527  *
528  * This function is called automatically when the #ESource switches from
529  * disabled to enabled, but can also be useful when a network connection
530  * becomes available or when waking up from hibernation or suspend.
531  *
532  * Since: 3.6
533  **/
534 void
535 e_source_refresh_force_timeout (ESource *source)
536 {
537         ESourceRefresh *extension;
538         const gchar *extension_name;
539
540         g_return_if_fail (E_IS_SOURCE (source));
541
542         extension_name = E_SOURCE_EXTENSION_REFRESH;
543         extension = e_source_get_extension (source, extension_name);
544
545         source_refresh_update_timeouts (extension, TRUE);
546 }
547
548 /**
549  * e_source_refresh_remove_timeout:
550  * @source: an #ESource
551  * @refresh_timeout_id: a refresh timeout ID
552  *
553  * Removes a timeout #GSource added by e_source_refresh_add_timeout().
554  *
555  * Returns: %TRUE if the timeout was found and removed
556  *
557  * Since: 3.6
558  **/
559 gboolean
560 e_source_refresh_remove_timeout (ESource *source,
561                                  guint refresh_timeout_id)
562 {
563         ESourceRefresh *extension;
564         const gchar *extension_name;
565         gboolean removed;
566         gpointer key;
567
568         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
569         g_return_val_if_fail (refresh_timeout_id > 0, FALSE);
570
571         extension_name = E_SOURCE_EXTENSION_REFRESH;
572         extension = e_source_get_extension (source, extension_name);
573
574         g_mutex_lock (extension->priv->timeout_lock);
575
576         key = GUINT_TO_POINTER (refresh_timeout_id);
577         removed = g_hash_table_remove (extension->priv->timeout_table, key);
578
579         g_mutex_unlock (extension->priv->timeout_lock);
580
581         return removed;
582 }
583
584 /**
585  * e_source_refresh_remove_timeouts_by_data:
586  * @source: an #ESource
587  * @user_data: user data to match against timeout callbacks
588  *
589  * Removes all timeout #GSource's added by e_source_refresh_add_timeout()
590  * whose callback data pointer matches @user_data.
591  *
592  * Returns: the number of timeouts found and removed
593  *
594  * Since: 3.6
595  **/
596 guint
597 e_source_refresh_remove_timeouts_by_data (ESource *source,
598                                           gpointer user_data)
599 {
600         ESourceRefresh *extension;
601         const gchar *extension_name;
602         GQueue trash = G_QUEUE_INIT;
603         GHashTableIter iter;
604         gpointer key, value;
605         guint n_removed = 0;
606
607         g_return_val_if_fail (E_IS_SOURCE (source), 0);
608
609         extension_name = E_SOURCE_EXTENSION_REFRESH;
610         extension = e_source_get_extension (source, extension_name);
611
612         g_mutex_lock (extension->priv->timeout_lock);
613
614         g_hash_table_iter_init (&iter, extension->priv->timeout_table);
615
616         while (g_hash_table_iter_next (&iter, &key, &value)) {
617                 TimeoutNode *node = value;
618
619                 if (node->user_data == user_data)
620                         g_queue_push_tail (&trash, key);
621         }
622
623         while ((key = g_queue_pop_head (&trash)) != NULL)
624                 if (g_hash_table_remove (extension->priv->timeout_table, key))
625                         n_removed++;
626
627         g_mutex_unlock (extension->priv->timeout_lock);
628
629         return n_removed;
630 }
631