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.
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.
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/>
20 * SECTION: e-source-refresh
21 * @include: libedataserver/e-source-refresh.h
22 * @short_description: #ESource extension for refresh settings
24 * The #ESourceRefresh extension tracks the interval for fetching
25 * updates from a remote server.
27 * Access the extension as follows:
30 * #include <libedataserver/e-source-refresh.h>
32 * ESourceRefresh *extension;
34 * extension = e_source_get_extension (source, E_SOURCE_EXTENSION_REFRESH);
38 #include "e-source-refresh.h"
40 #define E_SOURCE_REFRESH_GET_PRIVATE(obj) \
41 (G_TYPE_INSTANCE_GET_PRIVATE \
42 ((obj), E_TYPE_SOURCE_REFRESH, ESourceRefreshPrivate))
44 typedef struct _TimeoutNode TimeoutNode;
46 struct _ESourceRefreshPrivate {
48 guint interval_minutes;
51 GHashTable *timeout_table;
52 guint next_timeout_id;
57 GMainContext *context;
58 ESourceRefresh *extension; /* not referenced */
60 ESourceRefreshFunc callback;
62 GDestroyNotify notify;
74 E_TYPE_SOURCE_EXTENSION)
77 timeout_node_new (ESourceRefresh *extension,
78 GMainContext *context,
79 ESourceRefreshFunc callback,
81 GDestroyNotify notify)
86 g_main_context_ref (context);
88 node = g_slice_new0 (TimeoutNode);
89 node->context = context;
90 node->callback = callback;
91 node->user_data = user_data;
92 node->notify = notify;
94 /* Do not reference. The timeout node will
95 * not outlive the ESourceRefresh extension. */
96 node->extension = extension;
102 timeout_node_invoke (gpointer data)
104 TimeoutNode *node = data;
105 ESourceExtension *extension;
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);
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);
121 timeout_node_attach (TimeoutNode *node)
123 guint interval_minutes;
125 if (node->source != NULL)
129 e_source_refresh_get_interval_minutes (node->extension);
130 node->source = g_timeout_source_new_seconds (interval_minutes * 60);
132 g_source_set_callback (
136 (GDestroyNotify) NULL);
138 g_source_attach (node->source, node->context);
142 timeout_node_detach (TimeoutNode *node)
144 if (node->source == NULL)
147 g_source_destroy (node->source);
148 g_source_unref (node->source);
153 timeout_node_free (TimeoutNode *node)
155 if (node->source != NULL)
156 timeout_node_detach (node);
158 if (node->context != NULL)
159 g_main_context_unref (node->context);
161 if (node->notify != NULL)
162 node->notify (node->user_data);
164 g_slice_free (TimeoutNode, node);
168 source_refresh_update_timeouts (ESourceRefresh *extension,
169 gboolean invoke_callbacks)
173 g_mutex_lock (extension->priv->timeout_lock);
175 list = g_hash_table_get_values (extension->priv->timeout_table);
177 for (link = list; link != NULL; link = g_list_next (link)) {
178 TimeoutNode *node = link->data;
180 timeout_node_detach (node);
182 if (invoke_callbacks)
183 timeout_node_invoke (node);
185 if (e_source_refresh_get_enabled (extension))
186 timeout_node_attach (node);
191 g_mutex_unlock (extension->priv->timeout_lock);
195 source_refresh_notify_enabled_cb (ESource *source,
197 ESourceRefresh *extension)
199 if (e_source_get_enabled (source))
200 e_source_refresh_force_timeout (source);
204 source_refresh_set_property (GObject *object,
209 switch (property_id) {
211 e_source_refresh_set_enabled (
212 E_SOURCE_REFRESH (object),
213 g_value_get_boolean (value));
216 case PROP_INTERVAL_MINUTES:
217 e_source_refresh_set_interval_minutes (
218 E_SOURCE_REFRESH (object),
219 g_value_get_uint (value));
223 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
227 source_refresh_get_property (GObject *object,
232 switch (property_id) {
234 g_value_set_boolean (
236 e_source_refresh_get_enabled (
237 E_SOURCE_REFRESH (object)));
240 case PROP_INTERVAL_MINUTES:
243 e_source_refresh_get_interval_minutes (
244 E_SOURCE_REFRESH (object)));
248 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
252 source_refresh_dispose (GObject *object)
254 ESourceRefreshPrivate *priv;
256 priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
258 g_hash_table_remove_all (priv->timeout_table);
260 /* Chain up to parent's dispose() method. */
261 G_OBJECT_CLASS (e_source_refresh_parent_class)->dispose (object);
265 source_refresh_finalize (GObject *object)
267 ESourceRefreshPrivate *priv;
269 priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
271 g_mutex_free (priv->timeout_lock);
272 g_hash_table_destroy (priv->timeout_table);
274 /* Chain up to parent's finalize() method. */
275 G_OBJECT_CLASS (e_source_refresh_parent_class)->finalize (object);
279 source_refresh_constructed (GObject *object)
281 ESourceExtension *extension;
284 /* Chain up to parent's constructed() method. */
285 G_OBJECT_CLASS (e_source_refresh_parent_class)->constructed (object);
287 extension = E_SOURCE_EXTENSION (object);
288 source = e_source_extension_get_source (extension);
290 /* There should be no lifecycle issues here
291 * since we get finalized with our ESource. */
293 source, "notify::enabled",
294 G_CALLBACK (source_refresh_notify_enabled_cb),
299 e_source_refresh_class_init (ESourceRefreshClass *class)
301 GObjectClass *object_class;
302 ESourceExtensionClass *extension_class;
304 g_type_class_add_private (class, sizeof (ESourceRefreshPrivate));
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;
313 extension_class = E_SOURCE_EXTENSION_CLASS (class);
314 extension_class->name = E_SOURCE_EXTENSION_REFRESH;
316 g_object_class_install_property (
319 g_param_spec_boolean (
322 "Whether to periodically refresh",
326 G_PARAM_STATIC_STRINGS |
327 E_SOURCE_PARAM_SETTING));
329 g_object_class_install_property (
331 PROP_INTERVAL_MINUTES,
334 "Interval in Minutes",
335 "Refresh interval in minutes",
339 G_PARAM_STATIC_STRINGS |
340 E_SOURCE_PARAM_SETTING));
344 e_source_refresh_init (ESourceRefresh *extension)
346 GHashTable *timeout_table;
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);
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;
361 * e_source_refresh_get_enabled:
362 * @extension: an #ESourceRefresh
364 * Returns whether to periodically fetch updates from a remote server.
366 * The refresh interval is determined by the #ESourceRefresh:interval-minutes
369 * Returns: whether periodic refresh is enabled
374 e_source_refresh_get_enabled (ESourceRefresh *extension)
376 g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
378 return extension->priv->enabled;
382 * e_source_refresh_set_enabled:
383 * @extension: an #ESourceRefresh
384 * @enabled: whether to enable periodic refresh
386 * Sets whether to periodically fetch updates from a remote server.
388 * The refresh interval is determined by the #ESourceRefresh:interval-minutes
394 e_source_refresh_set_enabled (ESourceRefresh *extension,
397 g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
399 if (enabled == extension->priv->enabled)
402 extension->priv->enabled = enabled;
404 g_object_notify (G_OBJECT (extension), "enabled");
406 source_refresh_update_timeouts (extension, FALSE);
410 * e_source_refresh_get_interval_minutes:
411 * @extension: an #ESourceRefresh
413 * Returns the interval for fetching updates from a remote server.
415 * Note this value is only effective when the #ESourceRefresh:enabled
418 * Returns: the interval in minutes
423 e_source_refresh_get_interval_minutes (ESourceRefresh *extension)
425 g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
427 return extension->priv->interval_minutes;
431 * e_source_refresh_set_interval_minutes:
432 * @extension: an #ESourceRefresh
433 * @interval_minutes: the interval in minutes
435 * Sets the interval for fetching updates from a remote server.
437 * Note this value is only effective when the #ESourceRefresh:enabled
443 e_source_refresh_set_interval_minutes (ESourceRefresh *extension,
444 guint interval_minutes)
446 g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
448 if (interval_minutes == extension->priv->interval_minutes)
451 extension->priv->interval_minutes = interval_minutes;
453 g_object_notify (G_OBJECT (extension), "interval-minutes");
455 source_refresh_update_timeouts (extension, FALSE);
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,
468 * This is a simple way to schedule a periodic data source refresh.
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.
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().
480 * Returns: a refresh timeout ID
485 e_source_refresh_add_timeout (ESource *source,
486 GMainContext *context,
487 ESourceRefreshFunc callback,
489 GDestroyNotify notify)
491 ESourceRefresh *extension;
492 const gchar *extension_name;
497 g_return_val_if_fail (E_IS_SOURCE (source), 0);
498 g_return_val_if_fail (callback != NULL, 0);
500 extension_name = E_SOURCE_EXTENSION_REFRESH;
501 extension = e_source_get_extension (source, extension_name);
503 g_mutex_lock (extension->priv->timeout_lock);
505 timeout_id = extension->priv->next_timeout_id++;
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);
512 if (e_source_refresh_get_enabled (extension))
513 timeout_node_attach (node);
515 g_mutex_unlock (extension->priv->timeout_lock);
521 * e_source_refresh_force_timeout:
522 * @source: an #ESource
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.
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.
535 e_source_refresh_force_timeout (ESource *source)
537 ESourceRefresh *extension;
538 const gchar *extension_name;
540 g_return_if_fail (E_IS_SOURCE (source));
542 extension_name = E_SOURCE_EXTENSION_REFRESH;
543 extension = e_source_get_extension (source, extension_name);
545 source_refresh_update_timeouts (extension, TRUE);
549 * e_source_refresh_remove_timeout:
550 * @source: an #ESource
551 * @refresh_timeout_id: a refresh timeout ID
553 * Removes a timeout #GSource added by e_source_refresh_add_timeout().
555 * Returns: %TRUE if the timeout was found and removed
560 e_source_refresh_remove_timeout (ESource *source,
561 guint refresh_timeout_id)
563 ESourceRefresh *extension;
564 const gchar *extension_name;
568 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
569 g_return_val_if_fail (refresh_timeout_id > 0, FALSE);
571 extension_name = E_SOURCE_EXTENSION_REFRESH;
572 extension = e_source_get_extension (source, extension_name);
574 g_mutex_lock (extension->priv->timeout_lock);
576 key = GUINT_TO_POINTER (refresh_timeout_id);
577 removed = g_hash_table_remove (extension->priv->timeout_table, key);
579 g_mutex_unlock (extension->priv->timeout_lock);
585 * e_source_refresh_remove_timeouts_by_data:
586 * @source: an #ESource
587 * @user_data: user data to match against timeout callbacks
589 * Removes all timeout #GSource's added by e_source_refresh_add_timeout()
590 * whose callback data pointer matches @user_data.
592 * Returns: the number of timeouts found and removed
597 e_source_refresh_remove_timeouts_by_data (ESource *source,
600 ESourceRefresh *extension;
601 const gchar *extension_name;
602 GQueue trash = G_QUEUE_INIT;
607 g_return_val_if_fail (E_IS_SOURCE (source), 0);
609 extension_name = E_SOURCE_EXTENSION_REFRESH;
610 extension = e_source_get_extension (source, extension_name);
612 g_mutex_lock (extension->priv->timeout_lock);
614 g_hash_table_iter_init (&iter, extension->priv->timeout_table);
616 while (g_hash_table_iter_next (&iter, &key, &value)) {
617 TimeoutNode *node = value;
619 if (node->user_data == user_data)
620 g_queue_push_tail (&trash, key);
623 while ((key = g_queue_pop_head (&trash)) != NULL)
624 if (g_hash_table_remove (extension->priv->timeout_table, key))
627 g_mutex_unlock (extension->priv->timeout_lock);