/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Jeffrey Stedfast */ #ifdef HAVE_CONFIG_H #include #endif #include #include "camel-debug.h" #include "camel-offline-folder.h" #include "camel-offline-settings.h" #include "camel-offline-store.h" #include "camel-operation.h" #include "camel-session.h" #define CAMEL_OFFLINE_FOLDER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), CAMEL_TYPE_OFFLINE_FOLDER, CamelOfflineFolderPrivate)) typedef struct _AsyncContext AsyncContext; typedef struct _OfflineDownsyncData OfflineDownsyncData; struct _CamelOfflineFolderPrivate { gboolean offline_sync; GMutex store_changes_lock; guint store_changes_id; gboolean store_changes_after_frozen; }; struct _AsyncContext { gchar *expression; }; struct _OfflineDownsyncData { CamelFolder *folder; CamelFolderChangeInfo *changes; }; /* The custom property ID is a CamelArg artifact. * It still identifies the property in state files. */ enum { PROP_0, PROP_OFFLINE_SYNC = 0x2400 }; G_DEFINE_TYPE (CamelOfflineFolder, camel_offline_folder, CAMEL_TYPE_FOLDER) static void async_context_free (AsyncContext *async_context) { g_free (async_context->expression); g_slice_free (AsyncContext, async_context); } static void offline_downsync_data_free (OfflineDownsyncData *data) { if (data->changes != NULL) camel_folder_change_info_free (data->changes); g_object_unref (data->folder); g_slice_free (OfflineDownsyncData, data); } static void offline_folder_downsync_background (CamelSession *session, GCancellable *cancellable, OfflineDownsyncData *data, GError **error) { camel_operation_push_message ( cancellable, _("Downloading new messages for offline mode in '%s'"), camel_folder_get_full_name (data->folder)); if (data->changes) { GPtrArray *uid_added; gboolean success = TRUE; gint ii; uid_added = data->changes->uid_added; for (ii = 0; success && ii < uid_added->len; ii++) { const gchar *uid; gint percent; percent = ii * 100 / uid_added->len; uid = g_ptr_array_index (uid_added, ii); camel_operation_progress (cancellable, percent); success = camel_folder_synchronize_message_sync ( data->folder, uid, cancellable, error); } } else { camel_offline_folder_downsync_sync ( CAMEL_OFFLINE_FOLDER (data->folder), "(match-all)", cancellable, error); } camel_operation_pop_message (cancellable); } static void offline_folder_store_changes_job_cb (CamelSession *session, GCancellable *cancellable, gpointer user_data, GError **error) { CamelFolder *folder = user_data; g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder)); camel_folder_synchronize_sync (folder, FALSE, cancellable, error); } static gboolean offline_folder_schedule_store_changes_job (gpointer user_data) { CamelOfflineFolder *offline_folder = user_data; GSource *source; source = g_main_current_source (); if (g_source_is_destroyed (source)) return FALSE; g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (offline_folder), FALSE); g_mutex_lock (&offline_folder->priv->store_changes_lock); if (offline_folder->priv->store_changes_id == g_source_get_id (source)) { CamelSession *session; offline_folder->priv->store_changes_id = 0; session = camel_service_ref_session (CAMEL_SERVICE (camel_folder_get_parent_store (CAMEL_FOLDER (offline_folder)))); if (session) { gchar *description; description = g_strdup_printf (_("Storing changes in folder '%s'"), camel_folder_get_full_name (CAMEL_FOLDER (offline_folder))); camel_session_submit_job (session, description, offline_folder_store_changes_job_cb, g_object_ref (offline_folder), g_object_unref); g_free (description); } g_clear_object (&session); } g_mutex_unlock (&offline_folder->priv->store_changes_lock); return FALSE; } static void offline_folder_maybe_schedule_folder_change_store (CamelOfflineFolder *offline_folder) { CamelSession *session; CamelStore *store; g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (offline_folder)); g_mutex_lock (&offline_folder->priv->store_changes_lock); if (offline_folder->priv->store_changes_id) g_source_remove (offline_folder->priv->store_changes_id); offline_folder->priv->store_changes_id = 0; offline_folder->priv->store_changes_after_frozen = FALSE; if (camel_folder_is_frozen (CAMEL_FOLDER (offline_folder))) { offline_folder->priv->store_changes_after_frozen = TRUE; g_mutex_unlock (&offline_folder->priv->store_changes_lock); return; } store = camel_folder_get_parent_store (CAMEL_FOLDER (offline_folder)); session = camel_service_ref_session (CAMEL_SERVICE (store)); if (session && camel_session_get_online (session) && CAMEL_IS_OFFLINE_STORE (store) && camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) { CamelSettings *settings; gint interval = -1; settings = camel_service_ref_settings (CAMEL_SERVICE (store)); if (settings && CAMEL_IS_OFFLINE_SETTINGS (settings)) interval = camel_offline_settings_get_store_changes_interval (CAMEL_OFFLINE_SETTINGS (settings)); g_clear_object (&settings); if (interval == 0) offline_folder_schedule_store_changes_job (offline_folder); else if (interval > 0) offline_folder->priv->store_changes_id = g_timeout_add_seconds (interval, offline_folder_schedule_store_changes_job, offline_folder); } g_clear_object (&session); g_mutex_unlock (&offline_folder->priv->store_changes_lock); } static void offline_folder_changed (CamelFolder *folder, CamelFolderChangeInfo *changes) { CamelStore *parent_store; CamelService *service; CamelSession *session; CamelSettings *settings; gboolean sync_store; gboolean sync_folder; parent_store = camel_folder_get_parent_store (folder); service = CAMEL_SERVICE (parent_store); session = camel_service_ref_session (service); settings = camel_service_ref_settings (service); sync_store = camel_offline_settings_get_stay_synchronized ( CAMEL_OFFLINE_SETTINGS (settings)); g_object_unref (settings); sync_folder = camel_offline_folder_get_offline_sync ( CAMEL_OFFLINE_FOLDER (folder)); if (changes->uid_added->len > 0 && (sync_store || sync_folder)) { OfflineDownsyncData *data; gchar *description; data = g_slice_new0 (OfflineDownsyncData); data->changes = camel_folder_change_info_new (); camel_folder_change_info_cat (data->changes, changes); data->folder = g_object_ref (folder); description = g_strdup_printf (_("Checking download of new messages for offline in '%s'"), camel_folder_get_full_name (folder)); camel_session_submit_job ( session, description, (CamelSessionCallback) offline_folder_downsync_background, data, (GDestroyNotify) offline_downsync_data_free); g_free (description); } g_object_unref (session); if (changes && changes->uid_changed && changes->uid_changed->len > 0) offline_folder_maybe_schedule_folder_change_store (CAMEL_OFFLINE_FOLDER (folder)); } static void offline_folder_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_OFFLINE_SYNC: camel_offline_folder_set_offline_sync ( CAMEL_OFFLINE_FOLDER (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void offline_folder_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_OFFLINE_SYNC: g_value_set_boolean ( value, camel_offline_folder_get_offline_sync ( CAMEL_OFFLINE_FOLDER (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void offline_folder_dispose (GObject *object) { CamelOfflineFolder *offline_folder = CAMEL_OFFLINE_FOLDER (object); g_mutex_lock (&offline_folder->priv->store_changes_lock); if (offline_folder->priv->store_changes_id) g_source_remove (offline_folder->priv->store_changes_id); offline_folder->priv->store_changes_id = 0; g_mutex_unlock (&offline_folder->priv->store_changes_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (camel_offline_folder_parent_class)->dispose (object); } static void offline_folder_finalize (GObject *object) { CamelOfflineFolder *offline_folder = CAMEL_OFFLINE_FOLDER (object); g_mutex_clear (&offline_folder->priv->store_changes_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (camel_offline_folder_parent_class)->finalize (object); } static void offline_folder_thaw (CamelFolder *folder) { /* Chain up to parent's method. */ CAMEL_FOLDER_CLASS (camel_offline_folder_parent_class)->thaw (folder); if (!camel_folder_is_frozen (folder)) { CamelOfflineFolder *offline_folder; g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder)); offline_folder = CAMEL_OFFLINE_FOLDER (folder); g_mutex_lock (&offline_folder->priv->store_changes_lock); if (offline_folder->priv->store_changes_after_frozen) { offline_folder->priv->store_changes_after_frozen = FALSE; g_mutex_unlock (&offline_folder->priv->store_changes_lock); offline_folder_maybe_schedule_folder_change_store (offline_folder); } else { g_mutex_unlock (&offline_folder->priv->store_changes_lock); } } } static gboolean offline_folder_downsync_sync (CamelOfflineFolder *offline, const gchar *expression, GCancellable *cancellable, GError **error) { CamelFolder *folder = (CamelFolder *) offline; GPtrArray *uids, *uncached_uids = NULL; const gchar *display_name; const gchar *message; gint i; message = _("Syncing messages in folder '%s' to disk"); display_name = camel_folder_get_display_name (folder); camel_operation_push_message (cancellable, message, display_name); if (expression) uids = camel_folder_search_by_expression (folder, expression, cancellable, NULL); else uids = camel_folder_get_uids (folder); if (!uids) goto done; uncached_uids = camel_folder_get_uncached_uids (folder, uids, NULL); if (uids) { if (expression) camel_folder_search_free (folder, uids); else camel_folder_free_uids (folder, uids); } if (!uncached_uids) goto done; for (i = 0; i < uncached_uids->len; i++) { camel_folder_synchronize_message_sync ( folder, uncached_uids->pdata[i], cancellable, NULL); camel_operation_progress ( cancellable, i * 100 / uncached_uids->len); } done: if (uncached_uids) camel_folder_free_uids (folder, uncached_uids); camel_operation_pop_message (cancellable); return TRUE; } static void camel_offline_folder_class_init (CamelOfflineFolderClass *class) { GObjectClass *object_class; CamelFolderClass *folder_class; g_type_class_add_private (class, sizeof (CamelOfflineFolderPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = offline_folder_set_property; object_class->get_property = offline_folder_get_property; object_class->dispose = offline_folder_dispose; object_class->finalize = offline_folder_finalize; folder_class = CAMEL_FOLDER_CLASS (class); folder_class->thaw = offline_folder_thaw; class->downsync_sync = offline_folder_downsync_sync; g_object_class_install_property ( object_class, PROP_OFFLINE_SYNC, g_param_spec_boolean ( "offline-sync", "Offline Sync", _("Copy folder content locally for _offline operation"), FALSE, G_PARAM_READWRITE | CAMEL_PARAM_PERSISTENT)); } static void camel_offline_folder_init (CamelOfflineFolder *folder) { folder->priv = CAMEL_OFFLINE_FOLDER_GET_PRIVATE (folder); g_mutex_init (&folder->priv->store_changes_lock); folder->priv->store_changes_after_frozen = FALSE; g_signal_connect ( folder, "changed", G_CALLBACK (offline_folder_changed), NULL); } /** * camel_offline_folder_get_offline_sync: * @folder: a #CamelOfflineFolder * * Since: 2.32 **/ gboolean camel_offline_folder_get_offline_sync (CamelOfflineFolder *folder) { g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE); return folder->priv->offline_sync; } /** * camel_offline_folder_set_offline_sync: * @folder: a #CamelOfflineFolder * @offline_sync: whether to synchronize for offline use * * Since: 2.32 **/ void camel_offline_folder_set_offline_sync (CamelOfflineFolder *folder, gboolean offline_sync) { g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder)); if (folder->priv->offline_sync == offline_sync) return; folder->priv->offline_sync = offline_sync; g_object_notify (G_OBJECT (folder), "offline-sync"); } /** * camel_offline_folder_downsync_sync: * @folder: a #CamelOfflineFolder * @expression: search expression describing which set of messages * to downsync (%NULL for all) * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Synchronizes messages in @folder described by the search @expression to * the local machine for offline availability. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_offline_folder_downsync_sync (CamelOfflineFolder *folder, const gchar *expression, GCancellable *cancellable, GError **error) { CamelOfflineFolderClass *class; gboolean success; g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE); class = CAMEL_OFFLINE_FOLDER_GET_CLASS (folder); g_return_val_if_fail (class->downsync_sync != NULL, FALSE); success = class->downsync_sync ( folder, expression, cancellable, error); CAMEL_CHECK_GERROR (folder, downsync_sync, success, error); return success; } /* Helper for camel_offline_folder_downsync() */ static void offline_folder_downsync_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { gboolean success; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) task_data; success = camel_offline_folder_downsync_sync ( CAMEL_OFFLINE_FOLDER (source_object), async_context->expression, cancellable, &local_error); if (local_error != NULL) { g_task_return_error (task, local_error); } else { g_task_return_boolean (task, success); } } /** * camel_offline_folder_downsync: * @folder: a #CamelOfflineFolder * @expression: search expression describing which set of messages * to downsync (%NULL for all) * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULl * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Synchronizes messages in @folder described by the search @expression to * the local machine asynchronously for offline availability. * * When the operation is finished, @callback will be called. You can then * call camel_offline_folder_downsync_finish() to get the result of the * operation. * * Since: 3.0 **/ void camel_offline_folder_downsync (CamelOfflineFolder *folder, const gchar *expression, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; g_return_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder)); async_context = g_slice_new0 (AsyncContext); async_context->expression = g_strdup (expression); task = g_task_new (folder, cancellable, callback, user_data); g_task_set_source_tag (task, camel_offline_folder_downsync); g_task_set_priority (task, io_priority); g_task_set_task_data ( task, async_context, (GDestroyNotify) async_context_free); g_task_run_in_thread (task, offline_folder_downsync_thread); g_object_unref (task); } /** * camel_offline_folder_downsync_finish: * @folder: a #CamelOfflineFolder * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with camel_offline_folder_downsync(). * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_offline_folder_downsync_finish (CamelOfflineFolder *folder, GAsyncResult *result, GError **error) { g_return_val_if_fail (CAMEL_IS_OFFLINE_FOLDER (folder), FALSE); g_return_val_if_fail (g_task_is_valid (result, folder), FALSE); g_return_val_if_fail ( g_async_result_is_tagged ( result, camel_offline_folder_downsync), FALSE); return g_task_propagate_boolean (G_TASK (result), error); }