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/libedataserver.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/libedataserver.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_ref_source (extension);
110 g_return_val_if_fail (source != NULL, 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);
117 g_object_unref (source);
123 timeout_node_attach (TimeoutNode *node)
125 guint interval_minutes;
127 if (node->source != NULL)
131 e_source_refresh_get_interval_minutes (node->extension);
132 node->source = g_timeout_source_new_seconds (interval_minutes * 60);
134 g_source_set_callback (
138 (GDestroyNotify) NULL);
140 g_source_attach (node->source, node->context);
144 timeout_node_detach (TimeoutNode *node)
146 if (node->source == NULL)
149 g_source_destroy (node->source);
150 g_source_unref (node->source);
155 timeout_node_free (TimeoutNode *node)
157 if (node->source != NULL)
158 timeout_node_detach (node);
160 if (node->context != NULL)
161 g_main_context_unref (node->context);
163 if (node->notify != NULL)
164 node->notify (node->user_data);
166 g_slice_free (TimeoutNode, node);
170 source_refresh_update_timeouts (ESourceRefresh *extension,
171 gboolean invoke_callbacks)
175 g_mutex_lock (&extension->priv->timeout_lock);
177 list = g_hash_table_get_values (extension->priv->timeout_table);
179 for (link = list; link != NULL; link = g_list_next (link)) {
180 TimeoutNode *node = link->data;
182 timeout_node_detach (node);
184 if (invoke_callbacks)
185 timeout_node_invoke (node);
187 if (e_source_refresh_get_enabled (extension))
188 timeout_node_attach (node);
193 g_mutex_unlock (&extension->priv->timeout_lock);
197 source_refresh_idle_cb (gpointer user_data)
199 ESource *source = E_SOURCE (user_data);
201 if (e_source_get_enabled (source))
202 e_source_refresh_force_timeout (source);
208 source_refresh_notify_enabled_cb (ESource *source,
210 ESourceRefresh *extension)
212 GSource *idle_source;
213 GMainContext *main_context;
215 main_context = e_source_ref_main_context (source);
217 idle_source = g_idle_source_new ();
218 g_source_set_callback (
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);
226 g_main_context_unref (main_context);
230 source_refresh_set_property (GObject *object,
235 switch (property_id) {
237 e_source_refresh_set_enabled (
238 E_SOURCE_REFRESH (object),
239 g_value_get_boolean (value));
242 case PROP_INTERVAL_MINUTES:
243 e_source_refresh_set_interval_minutes (
244 E_SOURCE_REFRESH (object),
245 g_value_get_uint (value));
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
253 source_refresh_get_property (GObject *object,
258 switch (property_id) {
260 g_value_set_boolean (
262 e_source_refresh_get_enabled (
263 E_SOURCE_REFRESH (object)));
266 case PROP_INTERVAL_MINUTES:
269 e_source_refresh_get_interval_minutes (
270 E_SOURCE_REFRESH (object)));
274 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
278 source_refresh_dispose (GObject *object)
280 ESourceRefreshPrivate *priv;
282 priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
284 g_hash_table_remove_all (priv->timeout_table);
286 /* Chain up to parent's dispose() method. */
287 G_OBJECT_CLASS (e_source_refresh_parent_class)->dispose (object);
291 source_refresh_finalize (GObject *object)
293 ESourceRefreshPrivate *priv;
295 priv = E_SOURCE_REFRESH_GET_PRIVATE (object);
297 g_mutex_clear (&priv->timeout_lock);
298 g_hash_table_destroy (priv->timeout_table);
300 /* Chain up to parent's finalize() method. */
301 G_OBJECT_CLASS (e_source_refresh_parent_class)->finalize (object);
305 source_refresh_constructed (GObject *object)
307 ESourceExtension *extension;
310 /* Chain up to parent's constructed() method. */
311 G_OBJECT_CLASS (e_source_refresh_parent_class)->constructed (object);
313 extension = E_SOURCE_EXTENSION (object);
314 source = e_source_extension_ref_source (extension);
316 /* There should be no lifecycle issues here
317 * since we get finalized with our ESource. */
319 source, "notify::enabled",
320 G_CALLBACK (source_refresh_notify_enabled_cb),
323 g_object_unref (source);
327 e_source_refresh_class_init (ESourceRefreshClass *class)
329 GObjectClass *object_class;
330 ESourceExtensionClass *extension_class;
332 g_type_class_add_private (class, sizeof (ESourceRefreshPrivate));
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;
341 extension_class = E_SOURCE_EXTENSION_CLASS (class);
342 extension_class->name = E_SOURCE_EXTENSION_REFRESH;
344 g_object_class_install_property (
347 g_param_spec_boolean (
350 "Whether to periodically refresh",
354 G_PARAM_STATIC_STRINGS |
355 E_SOURCE_PARAM_SETTING));
357 g_object_class_install_property (
359 PROP_INTERVAL_MINUTES,
362 "Interval in Minutes",
363 "Refresh interval in minutes",
367 G_PARAM_STATIC_STRINGS |
368 E_SOURCE_PARAM_SETTING));
372 e_source_refresh_init (ESourceRefresh *extension)
374 GHashTable *timeout_table;
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);
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;
389 * e_source_refresh_get_enabled:
390 * @extension: an #ESourceRefresh
392 * Returns whether to periodically fetch updates from a remote server.
394 * The refresh interval is determined by the #ESourceRefresh:interval-minutes
397 * Returns: whether periodic refresh is enabled
402 e_source_refresh_get_enabled (ESourceRefresh *extension)
404 g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
406 return extension->priv->enabled;
410 * e_source_refresh_set_enabled:
411 * @extension: an #ESourceRefresh
412 * @enabled: whether to enable periodic refresh
414 * Sets whether to periodically fetch updates from a remote server.
416 * The refresh interval is determined by the #ESourceRefresh:interval-minutes
422 e_source_refresh_set_enabled (ESourceRefresh *extension,
425 g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
427 extension->priv->enabled = enabled;
429 g_object_notify (G_OBJECT (extension), "enabled");
431 source_refresh_update_timeouts (extension, FALSE);
435 * e_source_refresh_get_interval_minutes:
436 * @extension: an #ESourceRefresh
438 * Returns the interval for fetching updates from a remote server.
440 * Note this value is only effective when the #ESourceRefresh:enabled
443 * Returns: the interval in minutes
448 e_source_refresh_get_interval_minutes (ESourceRefresh *extension)
450 g_return_val_if_fail (E_IS_SOURCE_REFRESH (extension), FALSE);
452 return extension->priv->interval_minutes;
456 * e_source_refresh_set_interval_minutes:
457 * @extension: an #ESourceRefresh
458 * @interval_minutes: the interval in minutes
460 * Sets the interval for fetching updates from a remote server.
462 * Note this value is only effective when the #ESourceRefresh:enabled
468 e_source_refresh_set_interval_minutes (ESourceRefresh *extension,
469 guint interval_minutes)
471 g_return_if_fail (E_IS_SOURCE_REFRESH (extension));
473 if (interval_minutes == extension->priv->interval_minutes)
476 extension->priv->interval_minutes = interval_minutes;
478 g_object_notify (G_OBJECT (extension), "interval-minutes");
480 source_refresh_update_timeouts (extension, FALSE);
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,
493 * This is a simple way to schedule a periodic data source refresh.
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.
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().
505 * Returns: a refresh timeout ID
510 e_source_refresh_add_timeout (ESource *source,
511 GMainContext *context,
512 ESourceRefreshFunc callback,
514 GDestroyNotify notify)
516 ESourceRefresh *extension;
517 const gchar *extension_name;
522 g_return_val_if_fail (E_IS_SOURCE (source), 0);
523 g_return_val_if_fail (callback != NULL, 0);
525 extension_name = E_SOURCE_EXTENSION_REFRESH;
526 extension = e_source_get_extension (source, extension_name);
528 g_mutex_lock (&extension->priv->timeout_lock);
530 timeout_id = extension->priv->next_timeout_id++;
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);
537 if (e_source_refresh_get_enabled (extension))
538 timeout_node_attach (node);
540 g_mutex_unlock (&extension->priv->timeout_lock);
546 * e_source_refresh_force_timeout:
547 * @source: an #ESource
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.
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.
560 e_source_refresh_force_timeout (ESource *source)
562 ESourceRefresh *extension;
563 const gchar *extension_name;
565 g_return_if_fail (E_IS_SOURCE (source));
567 extension_name = E_SOURCE_EXTENSION_REFRESH;
568 extension = e_source_get_extension (source, extension_name);
570 source_refresh_update_timeouts (extension, TRUE);
574 * e_source_refresh_remove_timeout:
575 * @source: an #ESource
576 * @refresh_timeout_id: a refresh timeout ID
578 * Removes a timeout #GSource added by e_source_refresh_add_timeout().
580 * Returns: %TRUE if the timeout was found and removed
585 e_source_refresh_remove_timeout (ESource *source,
586 guint refresh_timeout_id)
588 ESourceRefresh *extension;
589 const gchar *extension_name;
593 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
594 g_return_val_if_fail (refresh_timeout_id > 0, FALSE);
596 extension_name = E_SOURCE_EXTENSION_REFRESH;
597 extension = e_source_get_extension (source, extension_name);
599 g_mutex_lock (&extension->priv->timeout_lock);
601 key = GUINT_TO_POINTER (refresh_timeout_id);
602 removed = g_hash_table_remove (extension->priv->timeout_table, key);
604 g_mutex_unlock (&extension->priv->timeout_lock);
610 * e_source_refresh_remove_timeouts_by_data:
611 * @source: an #ESource
612 * @user_data: user data to match against timeout callbacks
614 * Removes all timeout #GSource's added by e_source_refresh_add_timeout()
615 * whose callback data pointer matches @user_data.
617 * Returns: the number of timeouts found and removed
622 e_source_refresh_remove_timeouts_by_data (ESource *source,
625 ESourceRefresh *extension;
626 const gchar *extension_name;
627 GQueue trash = G_QUEUE_INIT;
632 g_return_val_if_fail (E_IS_SOURCE (source), 0);
634 extension_name = E_SOURCE_EXTENSION_REFRESH;
635 extension = e_source_get_extension (source, extension_name);
637 g_mutex_lock (&extension->priv->timeout_lock);
639 g_hash_table_iter_init (&iter, extension->priv->timeout_table);
641 while (g_hash_table_iter_next (&iter, &key, &value)) {
642 TimeoutNode *node = value;
644 if (node->user_data == user_data)
645 g_queue_push_tail (&trash, key);
648 while ((key = g_queue_pop_head (&trash)) != NULL)
649 if (g_hash_table_remove (extension->priv->timeout_table, key))
652 g_mutex_unlock (&extension->priv->timeout_lock);