/*
Copyright (C) 2015 ABRT team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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 General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "abrt_problems2_task.h"
enum {
SN_STATUS_CHANGED,
SN_LAST_SIGNAL
} SignalNumber;
static guint s_signals[SN_LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE(AbrtP2Task, abrt_p2_task, G_TYPE_OBJECT)
static void abrt_p2_task_finalize(GObject *gobject)
{
AbrtP2TaskPrivate *pv = abrt_p2_task_get_instance_private(ABRT_P2_TASK(gobject));
g_variant_unref(pv->p2t_details);
if (pv->p2t_results != NULL)
g_variant_unref(pv->p2t_results);
}
static void abrt_p2_task_class_init(AbrtP2TaskClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = abrt_p2_task_finalize;
s_signals[SN_STATUS_CHANGED] = g_signal_new ("status-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(AbrtP2TaskClass, status_changed),
/*accumulator*/NULL, /*accu_data*/NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE,
/*n_params*/1,
G_TYPE_INT);
}
#define ABRT_P2_TASK_ABSTRACT_FUNCTION_CALL(ret, method, task, ...) \
do { if (ABRT_P2_TASK_GET_CLASS(task)->method == NULL) { \
error_msg("Undefined Task abstract function "#method); \
abort(); \
}\
ret = ABRT_P2_TASK_GET_CLASS(task)->method(task, __VA_ARGS__); } while(0)
#define ABRT_P2_TASK_VIRTUAL_FUNCTION_CALL(method, task, ...) \
do { if (ABRT_P2_TASK_GET_CLASS(task)->method != NULL) \
ABRT_P2_TASK_GET_CLASS(task)->method(task, __VA_ARGS__); } while(0)
#define ABRT_P2_TASK_VIRTUAL_CANCEL(task, error) \
ABRT_P2_TASK_VIRTUAL_FUNCTION_CALL(cancel, task, error)
#define ABRT_P2_TASK_VIRTUAL_FINISH(task, error) \
ABRT_P2_TASK_VIRTUAL_FUNCTION_CALL(finish, task, error)
#define ABRT_P2_TASK_VIRTUAL_START(task, options, error) \
ABRT_P2_TASK_VIRTUAL_FUNCTION_CALL(start, task, options, error)
static void abrt_p2_task_init(AbrtP2Task *self)
{
self->pv = abrt_p2_task_get_instance_private(self);
self->pv->p2t_details = g_variant_new("a{sv}", NULL);
}
static void abrt_p2_task_change_status(AbrtP2Task *task,
AbrtP2TaskStatus status)
{
if (task->pv->p2t_status == status)
return;
task->pv->p2t_status = status;
g_signal_emit(task,
s_signals[SN_STATUS_CHANGED],
0,
status);
}
AbrtP2TaskStatus abrt_p2_task_status(AbrtP2Task *task)
{
return task->pv->p2t_status;
}
GVariant *abrt_p2_task_details(AbrtP2Task *task)
{
return g_variant_ref(task->pv->p2t_details);
}
void abrt_p2_task_add_detail(AbrtP2Task *task,
const char *key,
GVariant *value)
{
GVariantDict dict;
g_variant_dict_init(&dict, task->pv->p2t_details);
g_variant_dict_insert(&dict, key, "v", value);
if (task->pv->p2t_details)
g_variant_unref(task->pv->p2t_details);
task->pv->p2t_details = g_variant_dict_end(&dict);
}
void abrt_p2_task_set_response(AbrtP2Task *task,
GVariant *response)
{
if (task->pv->p2t_results != NULL)
log_warning("Task already has response assigned");
task->pv->p2t_results = response;
}
bool abrt_p2_task_is_cancelled(AbrtP2Task *task)
{
return (task->pv->p2t_cancellable
&& g_cancellable_is_cancelled(task->pv->p2t_cancellable))
|| task->pv->p2t_status == ABRT_P2_TASK_STATUS_CANCELED;
}
void abrt_p2_task_cancel(AbrtP2Task *task,
GError **error)
{
if (abrt_p2_task_is_cancelled(task))
return;
if (task->pv->p2t_status == ABRT_P2_TASK_STATUS_RUNNING)
g_cancellable_cancel(task->pv->p2t_cancellable);
else if (task->pv->p2t_status == ABRT_P2_TASK_STATUS_STOPPED)
{
ABRT_P2_TASK_VIRTUAL_CANCEL(task, error);
if (*error == NULL)
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_CANCELED);
}
else
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"Task is not in the state that allows cancelling");
}
void abrt_p2_task_finish(AbrtP2Task *task,
GVariant **result,
gint32 *code,
GError **error)
{
if ( task->pv->p2t_status != ABRT_P2_TASK_STATUS_DONE
&& task->pv->p2t_status != ABRT_P2_TASK_STATUS_FAILED)
{
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"Cannot finalize undone task");
return;
}
ABRT_P2_TASK_VIRTUAL_FINISH(task, error);
if (*error != NULL)
return;
if (task->pv->p2t_results)
*result = g_variant_ref(task->pv->p2t_results);
else
*result = g_variant_new("a{sv}", NULL);
*code = task->pv->p2t_code;
}
static void abrt_p2_task_finish_gtask(GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
AbrtP2Task *task = ABRT_P2_TASK(source_object);
if (!g_task_is_valid(result, task))
{
error_msg("BUG:%s:%s: invalid GTask", __FILE__, __func__);
return;
}
GError *error = NULL;
const gint32 code = g_task_propagate_int(G_TASK(result), &error);
if (code == ABRT_P2_TASK_CODE_STOP)
{
log_debug("Task stopped");
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_STOPPED);
}
else if (code >= ABRT_P2_TASK_CODE_DONE)
{
log_debug("Task done");
task->pv->p2t_code = code - ABRT_P2_TASK_CODE_DONE;
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_DONE);
}
else if (abrt_p2_task_is_cancelled(task))
{
if (error != NULL)
{
log_debug("Task canceled with error: %s", error->message);
g_error_free(error);
error = NULL;
}
else
log_debug("Task canceled");
ABRT_P2_TASK_VIRTUAL_CANCEL(task, &error);
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_CANCELED);
}
else
{
GVariantDict response;
g_variant_dict_init(&response, NULL);
if (error != NULL)
{
log_debug("Task failed with error: %s", error->message);
g_variant_dict_insert(&response,
"Error.Message",
"s",
error->message);
g_error_free(error);
}
else if (code == ABRT_P2_TASK_CODE_ERROR)
{
log_debug("Task failed without error message");
g_variant_dict_insert(&response,
"Error.Message",
"s",
"Task failed");
}
else
{
error_msg("BUG:%s:%s: unknown Task return code: %d",
__FILE__,
__func__,
code);
g_variant_dict_insert(&response,
"Error.Message",
"s",
"Internal error: Invalid Task return code");
}
abrt_p2_task_set_response(task, g_variant_dict_end(&response));
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_FAILED);
}
g_object_unref(task->pv->p2t_cancellable);
task->pv->p2t_cancellable = NULL;
}
static void abrt_p2_task_thread(GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
AbrtP2Task *stask = source_object;
GError *error = NULL;
gint32 code;
ABRT_P2_TASK_ABSTRACT_FUNCTION_CALL(code, run, stask, &error);
if (error == NULL)
g_task_return_int(task, code);
else
g_task_return_error(task, error);
}
void abrt_p2_task_start(AbrtP2Task *task,
GVariant *options,
GError **error)
{
if ( task->pv->p2t_status != ABRT_P2_TASK_STATUS_NEW
&& task->pv->p2t_status != ABRT_P2_TASK_STATUS_STOPPED)
{
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"Cannot start task that is not new or stopped");
return;
}
ABRT_P2_TASK_VIRTUAL_START(task, options, error);
if (*error != NULL)
return;
task->pv->p2t_cancellable = g_cancellable_new();
GTask *gtask = g_task_new(task,
task->pv->p2t_cancellable,
abrt_p2_task_finish_gtask,
NULL);
g_task_run_in_thread(gtask, abrt_p2_task_thread);
abrt_p2_task_change_status(task, ABRT_P2_TASK_STATUS_RUNNING);
g_object_unref(gtask);
}
static void abrt_p2_task_autonomous_cb(AbrtP2Task *task,
gint32 status,
gpointer user_data)
{
switch(status)
{
case ABRT_P2_TASK_STATUS_NEW:
error_msg("Autonomous task has changed status to NEW");
break;
case ABRT_P2_TASK_STATUS_RUNNING:
log_debug("Autonomous task has successfully started");
break;
case ABRT_P2_TASK_STATUS_STOPPED:
{
error_msg("Autonomous task has been stopped and will be canceled");
GError *error = NULL;
abrt_p2_task_cancel(task, &error);
if (error != NULL)
{
error_msg("Failed to cancel stopped autonomous task: %s",
error->message);
g_error_free(error);
g_object_unref(task);
}
}
break;
case ABRT_P2_TASK_STATUS_CANCELED:
log_notice("Autonomous task has been canceled");
g_object_unref(task);
break;
case ABRT_P2_TASK_STATUS_FAILED:
{
GVariant *response;
gint32 code;
GError *error = NULL;
abrt_p2_task_finish(task, &response, &code, &error);
if (error != NULL)
{
error_msg("Failed to finish canceled task: %s", error->message);
}
else
{
const char *error_message;
g_variant_lookup(response, "Error.Message", "&s", &error_message);
error_msg("Autonomous task has failed: %d: %s", code, error_message);
g_variant_unref(response);
}
g_object_unref(task);
}
break;
case ABRT_P2_TASK_STATUS_DONE:
log_notice("Autonomous task has been successfully finished");
g_object_unref(task);
break;
default:
error_msg("BUG: %s: forgotten task state", __func__);
break;
}
}
void abrt_p2_task_autonomous_run(AbrtP2Task *task,
GError **error)
{
g_signal_connect(task,
"status-changed",
G_CALLBACK(abrt_p2_task_autonomous_cb),
NULL);
abrt_p2_task_start(task, NULL, error);
}