The Calendar, Memos and Tasks views use to do D-Bus calls to the backends on the main (UI) thread, which could result in UI freezes, until the operation was done on the backend (and server) side. This commit fixes that by invoking the operations in a dedicated thread. It has few additional advantages too: - operations can be cancelled - proper error reporting to a user - less code duplication between the views for common operations There had been fixed some performance issues when selecting/unselecting sources in the source selector as well.
306 lines
8.8 KiB
C
306 lines
8.8 KiB
C
/*
|
|
* e-alert-sink.c
|
|
*
|
|
* This program 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 program 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 General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* SECTION: e-alert-sink
|
|
* @short_description: an interface to handle alerts
|
|
* @include: e-util/e-util.h
|
|
*
|
|
* A widget that implements #EAlertSink means it can handle #EAlerts,
|
|
* usually by displaying them to the user.
|
|
**/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <glib/gi18n-lib.h>
|
|
#include <camel/camel.h>
|
|
|
|
#include "e-alert-sink.h"
|
|
#include "e-activity.h"
|
|
|
|
#include "e-alert-dialog.h"
|
|
|
|
G_DEFINE_INTERFACE (
|
|
EAlertSink,
|
|
e_alert_sink,
|
|
GTK_TYPE_WIDGET)
|
|
|
|
static void
|
|
alert_sink_fallback (GtkWidget *widget,
|
|
EAlert *alert)
|
|
{
|
|
GtkWidget *dialog;
|
|
gpointer parent;
|
|
|
|
parent = gtk_widget_get_toplevel (widget);
|
|
parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
|
|
|
|
dialog = e_alert_dialog_new (parent, alert);
|
|
gtk_dialog_run (GTK_DIALOG (dialog));
|
|
gtk_widget_destroy (dialog);
|
|
}
|
|
|
|
static void
|
|
alert_sink_submit_alert (EAlertSink *alert_sink,
|
|
EAlert *alert)
|
|
{
|
|
/* This is just a lame fallback handler. Implementors
|
|
* are strongly encouraged to override this method. */
|
|
alert_sink_fallback (GTK_WIDGET (alert_sink), alert);
|
|
}
|
|
|
|
static void
|
|
e_alert_sink_default_init (EAlertSinkInterface *iface)
|
|
{
|
|
iface->submit_alert = alert_sink_submit_alert;
|
|
}
|
|
|
|
/**
|
|
* e_alert_sink_submit_alert:
|
|
* @alert_sink: an #EAlertSink
|
|
* @alert: an #EAlert
|
|
*
|
|
* This function is a place to pass #EAlert objects. Beyond that it has no
|
|
* well-defined behavior. It's up to the widget implementing the #EAlertSink
|
|
* interface to decide what to do with them.
|
|
**/
|
|
void
|
|
e_alert_sink_submit_alert (EAlertSink *alert_sink,
|
|
EAlert *alert)
|
|
{
|
|
EAlertSinkInterface *iface;
|
|
|
|
g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
|
|
g_return_if_fail (E_IS_ALERT (alert));
|
|
|
|
iface = E_ALERT_SINK_GET_INTERFACE (alert_sink);
|
|
g_return_if_fail (iface->submit_alert != NULL);
|
|
|
|
iface->submit_alert (alert_sink, alert);
|
|
}
|
|
|
|
struct _EAlertSinkThreadJobData {
|
|
EActivity *activity;
|
|
gchar *alert_ident;
|
|
gchar *alert_arg_0;
|
|
GError *error;
|
|
|
|
EAlertSinkThreadJobFunc func;
|
|
gpointer user_data;
|
|
GDestroyNotify free_user_data;
|
|
};
|
|
|
|
static gboolean
|
|
e_alert_sink_thread_job_done_cb (gpointer user_data)
|
|
{
|
|
EAlertSinkThreadJobData *job_data = user_data;
|
|
EAlertSink *alert_sink;
|
|
GCancellable *cancellable;
|
|
|
|
g_return_val_if_fail (job_data != NULL, FALSE);
|
|
g_return_val_if_fail (job_data->func != NULL, FALSE);
|
|
|
|
alert_sink = e_activity_get_alert_sink (job_data->activity);
|
|
cancellable = e_activity_get_cancellable (job_data->activity);
|
|
|
|
camel_operation_pop_message (cancellable);
|
|
|
|
if (e_activity_handle_cancellation (job_data->activity, job_data->error)) {
|
|
/* do nothing */
|
|
} else if (job_data->error != NULL) {
|
|
if (job_data->alert_arg_0) {
|
|
e_alert_submit (
|
|
alert_sink,
|
|
job_data->alert_ident,
|
|
job_data->alert_arg_0, job_data->error->message, NULL);
|
|
} else {
|
|
e_alert_submit (
|
|
alert_sink,
|
|
job_data->alert_ident,
|
|
job_data->error->message, NULL);
|
|
}
|
|
} else {
|
|
e_activity_set_state (job_data->activity, E_ACTIVITY_COMPLETED);
|
|
}
|
|
|
|
/* clean-up */
|
|
g_clear_object (&job_data->activity);
|
|
g_clear_error (&job_data->error);
|
|
g_free (job_data->alert_ident);
|
|
g_free (job_data->alert_arg_0);
|
|
|
|
if (job_data->free_user_data)
|
|
job_data->free_user_data (job_data->user_data);
|
|
|
|
g_free (job_data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gpointer
|
|
e_alert_sink_thread_job (gpointer user_data)
|
|
{
|
|
EAlertSinkThreadJobData *job_data = user_data;
|
|
GCancellable *cancellable;
|
|
|
|
g_return_val_if_fail (job_data != NULL, NULL);
|
|
g_return_val_if_fail (job_data->func != NULL, NULL);
|
|
g_return_val_if_fail (job_data->error == NULL, NULL);
|
|
|
|
cancellable = e_activity_get_cancellable (job_data->activity);
|
|
|
|
job_data->func (job_data, job_data->user_data, cancellable, &job_data->error);
|
|
|
|
g_timeout_add (1, e_alert_sink_thread_job_done_cb, job_data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* e_alert_sink_submit_thread_job:
|
|
* @alert_sink: an #EAlertSink instance
|
|
* @description: user-friendly description of the job, to be shown in UI
|
|
* @alert_ident: in case of an error, this alert identificator is used
|
|
* for EAlert construction
|
|
* @alert_arg_0: (allow-none): in case of an error, use this string as
|
|
* the first argument to the EAlert construction; the second argument
|
|
* is the actual error message; can be #NULL, in which case only
|
|
* the error message is passed to the EAlert construction
|
|
* @func: function to be run in a dedicated thread
|
|
* @user_data: (allow-none): custom data passed into @func; can be #NULL
|
|
* @free_user_data: (allow-none): function to be called on @user_data,
|
|
* when the job is over; can be #NULL
|
|
*
|
|
* Runs the @func in a dedicated thread. Any error is propagated to UI.
|
|
* The cancellable passed into the @func is a #CamelOperation, thus
|
|
* the caller can overwrite progress and description message on it.
|
|
*
|
|
* Returns: (transfer full): Newly created #EActivity on success.
|
|
* The caller is responsible to g_object_unref() it when done with it.
|
|
*
|
|
* Note: The @free_user_data, if set, is called in the main thread.
|
|
*
|
|
* Note: This function should be called only from the main thread.
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
EActivity *
|
|
e_alert_sink_submit_thread_job (EAlertSink *alert_sink,
|
|
const gchar *description,
|
|
const gchar *alert_ident,
|
|
const gchar *alert_arg_0,
|
|
EAlertSinkThreadJobFunc func,
|
|
gpointer user_data,
|
|
GDestroyNotify free_user_data)
|
|
{
|
|
EActivity *activity;
|
|
GCancellable *cancellable;
|
|
EAlertSinkThreadJobData *job_data;
|
|
GThread *thread;
|
|
|
|
g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
|
|
g_return_val_if_fail (description != NULL, NULL);
|
|
g_return_val_if_fail (func != NULL, NULL);
|
|
|
|
activity = e_activity_new ();
|
|
cancellable = camel_operation_new ();
|
|
|
|
e_activity_set_alert_sink (activity, alert_sink);
|
|
e_activity_set_cancellable (activity, cancellable);
|
|
e_activity_set_text (activity, description);
|
|
|
|
camel_operation_push_message (cancellable, "%s", description);
|
|
|
|
job_data = g_new0 (EAlertSinkThreadJobData, 1);
|
|
job_data->activity = g_object_ref (activity);
|
|
job_data->alert_ident = g_strdup (alert_ident);
|
|
job_data->alert_arg_0 = g_strdup (alert_arg_0);
|
|
job_data->error = NULL;
|
|
job_data->func = func;
|
|
job_data->user_data = user_data;
|
|
job_data->free_user_data = free_user_data;
|
|
|
|
thread = g_thread_try_new (G_STRFUNC, e_alert_sink_thread_job, job_data, &job_data->error);
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
if (thread) {
|
|
g_thread_unref (thread);
|
|
} else {
|
|
g_prefix_error (&job_data->error, _("Failed to create a thread: "));
|
|
g_timeout_add (1, e_alert_sink_thread_job_done_cb, job_data);
|
|
}
|
|
|
|
return activity;
|
|
}
|
|
|
|
/**
|
|
* e_alert_sink_thread_job_set_alert_ident:
|
|
* @job_data: Thread job data, as passed to a thread
|
|
* function specified at e_alert_sink_submit_thread_job()
|
|
* @alert_ident: A new alert identificator to use; cannot be #NULL
|
|
*
|
|
* Change an alert identificator to be used for error reporting.
|
|
* This can be used within a thread function at e_alert_sink_submit_thread_job(),
|
|
* to overwrite the default error message, in case of a need to more fine-tuned
|
|
* infomation to a user being available.
|
|
*
|
|
* See: e_alert_sink_thread_job_set_alert_arg_0
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_alert_sink_thread_job_set_alert_ident (EAlertSinkThreadJobData *job_data,
|
|
const gchar *alert_ident)
|
|
{
|
|
g_return_if_fail (job_data != NULL);
|
|
g_return_if_fail (alert_ident != NULL);
|
|
|
|
if (job_data->alert_ident != alert_ident) {
|
|
g_free (job_data->alert_ident);
|
|
job_data->alert_ident = g_strdup (alert_ident);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* e_alert_sink_thread_job_set_alert_arg_0:
|
|
* @job_data: Thread job data, as passed to a thread
|
|
* function specified at e_alert_sink_submit_thread_job()
|
|
* @alert_arg_0: (allow-none): A new argument 0 of the alert;
|
|
* can be #NULL, to unset the previously set value
|
|
*
|
|
* Change an argument 0 for an alert to be used for error reporting.
|
|
* This can be used within a thread function at e_alert_sink_submit_thread_job(),
|
|
* to overwrite the default argument 0 of the erorr message. It might be
|
|
* usually used with combination of e_alert_sink_thread_job_set_alert_ident().
|
|
*
|
|
* Since: 3.14
|
|
**/
|
|
void
|
|
e_alert_sink_thread_job_set_alert_arg_0 (EAlertSinkThreadJobData *job_data,
|
|
const gchar *alert_arg_0)
|
|
{
|
|
g_return_if_fail (job_data != NULL);
|
|
|
|
if (job_data->alert_arg_0 != alert_arg_0) {
|
|
g_free (job_data->alert_arg_0);
|
|
job_data->alert_arg_0 = g_strdup (alert_arg_0);
|
|
}
|
|
}
|