/*
* properties-mixin.c - Source for GabblePropertiesMixin
* Copyright (C) 2006 Collabora Ltd.
*
* @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
* @author Robert McQueen <robert.mcqueen@collabora.co.uk>
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <dbus/dbus-glib.h>
#include <stdio.h>
#include <string.h>
#define DEBUG_FLAG GABBLE_DEBUG_PROPERTIES
#include "ansi.h"
#include "debug.h"
#include "properties-mixin.h"
#include "properties-mixin-signals-marshal.h"
#include "telepathy-errors.h"
#ifdef EMULATOR
#include "libgabble_wsd_solution.h"
GET_STATIC_VAR_FROM_TLS(offset_quark1,gabble_mixin,GQuark)
#define offset_quark1 (*GET_WSD_VAR_NAME(offset_quark1,gabble_mixin, s)())
GET_STATIC_VAR_FROM_TLS(offset_quark,gabble_mixin,GQuark)
#define offset_quark (*GET_WSD_VAR_NAME(offset_quark,gabble_mixin, s)())
#endif
struct _GabblePropertiesContext {
GabblePropertiesMixinClass *mixin_cls;
GabblePropertiesMixin *mixin;
DBusGMethodInvocation *dbus_ctx;
guint32 remaining;
GValue **values;
};
struct _GabblePropertiesMixinPrivate {
GObject *object;
GabblePropertiesContext context;
};
/**
* gabble_properties_mixin_class_get_offset_quark:
*
* Returns: the quark used for storing mixin offset on a GObjectClass
*/
GQuark
gabble_properties_mixin_class_get_offset_quark ()
{
#ifndef EMULATOR
static GQuark offset_quark1= 0;
#endif
if (!offset_quark1)
offset_quark1 = g_quark_from_static_string("PropertiesMixinClassOffsetQuark");
return offset_quark1;
}
/**
* gabble_properties_mixin_get_offset_quark:
*
* Returns: the quark used for storing mixin offset on a GObject
*/
GQuark
gabble_properties_mixin_get_offset_quark ()
{
#ifndef EMULATOR
static GQuark offset_quark = 0;
#endif
if (!offset_quark)
offset_quark = g_quark_from_static_string("PropertiesMixinOffsetQuark");
return offset_quark;
}
void gabble_properties_mixin_class_init (GObjectClass *obj_cls,
glong offset,
const GabblePropertySignature *signatures,
guint num_properties,
GabblePropertiesSetFunc set_func)
{
GabblePropertiesMixinClass *mixin_cls;
g_assert (G_IS_OBJECT_CLASS (obj_cls));
g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
GABBLE_PROPERTIES_MIXIN_CLASS_OFFSET_QUARK,
GINT_TO_POINTER (offset));
mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (obj_cls);
mixin_cls->signatures = signatures;
mixin_cls->num_props = num_properties;
mixin_cls->set_properties = set_func;
mixin_cls->properties_changed_signal_id =
g_signal_new ("properties-changed",
G_OBJECT_CLASS_TYPE (obj_cls),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_VALUE, G_TYPE_INVALID)))));
mixin_cls->property_flags_changed_signal_id =
g_signal_new ("property-flags-changed",
G_OBJECT_CLASS_TYPE (obj_cls),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)))));
}
void gabble_properties_mixin_init (GObject *obj, glong offset)
{
GabblePropertiesMixinClass *mixin_cls;
GabblePropertiesMixin *mixin;
GabblePropertiesContext *ctx;
g_assert (G_IS_OBJECT (obj));
g_type_set_qdata (G_OBJECT_TYPE (obj),
GABBLE_PROPERTIES_MIXIN_OFFSET_QUARK,
GINT_TO_POINTER (offset));
mixin = GABBLE_PROPERTIES_MIXIN (obj);
mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
mixin->properties = g_new0 (GabbleProperty, mixin_cls->num_props);
mixin->priv = g_new0 (GabblePropertiesMixinPrivate, 1);
mixin->priv->object = obj;
ctx = &mixin->priv->context;
ctx->mixin_cls = mixin_cls;
ctx->mixin = mixin;
ctx->values = g_new0 (GValue *, mixin_cls->num_props);
}
void gabble_properties_mixin_finalize (GObject *obj)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GabblePropertiesContext *ctx = &mixin->priv->context;
guint i;
for (i = 0; i < mixin_cls->num_props; i++)
{
GabbleProperty *prop = &mixin->properties[i];
if (prop->value)
{
g_value_unset (prop->value);
g_free (prop->value);
}
if (ctx->values[i])
{
g_value_unset (ctx->values[i]);
}
}
g_free (ctx->values);
g_free (mixin->priv);
g_free (mixin->properties);
}
gboolean
gabble_properties_mixin_list_properties (GObject *obj, GPtrArray **ret, GError **error)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
guint i;
*ret = g_ptr_array_sized_new (mixin_cls->num_props);
for (i = 0; i < mixin_cls->num_props; i++)
{
const GabblePropertySignature *sig = &mixin_cls->signatures[i];
GabbleProperty *prop = &mixin->properties[i];
const gchar *dbus_sig;
GValue val = { 0, };
switch (sig->type) {
case G_TYPE_BOOLEAN:
dbus_sig = "b";
break;
case G_TYPE_INT:
dbus_sig = "i";
break;
case G_TYPE_UINT:
dbus_sig = "u";
break;
case G_TYPE_STRING:
dbus_sig = "s";
break;
default:
g_assert_not_reached ();
continue;
};
g_value_init (&val, TP_TYPE_PROPERTY_INFO_STRUCT);
g_value_take_boxed (&val,
dbus_g_type_specialized_construct (TP_TYPE_PROPERTY_INFO_STRUCT));
dbus_g_type_struct_set (&val,
0, i,
1, sig->name,
2, dbus_sig,
3, prop->flags,
G_MAXUINT);
g_ptr_array_add (*ret, g_value_get_boxed (&val));
}
return TRUE;
}
gboolean
gabble_properties_mixin_get_properties (GObject *obj, const GArray *properties, GPtrArray **ret, GError **error)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
guint i;
/* Check input property identifiers */
for (i = 0; i < properties->len; i++)
{
guint prop_id = g_array_index (properties, guint, i);
/* Valid? */
if (prop_id >= mixin_cls->num_props)
{
g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
"invalid property identifier %d", prop_id);
return FALSE;
}
/* Permitted? */
if (!gabble_properties_mixin_is_readable (obj, prop_id))
{
g_set_error (error, TELEPATHY_ERRORS, PermissionDenied,
"permission denied for property identifier %d", prop_id);
return FALSE;
}
}
/* If we got this far, return the actual values */
*ret = g_ptr_array_sized_new (properties->len);
for (i = 0; i < properties->len; i++)
{
guint prop_id = g_array_index (properties, guint, i);
GValue val_struct = { 0, };
/* id/value struct */
g_value_init (&val_struct, TP_TYPE_PROPERTY_VALUE_STRUCT);
g_value_take_boxed (&val_struct,
dbus_g_type_specialized_construct (TP_TYPE_PROPERTY_VALUE_STRUCT));
dbus_g_type_struct_set (&val_struct,
0, prop_id,
1, mixin->properties[prop_id].value,
G_MAXUINT);
g_ptr_array_add (*ret, g_value_get_boxed (&val_struct));
}
return TRUE;
}
void
gabble_properties_mixin_set_properties (GObject *obj,
const GPtrArray *properties,
DBusGMethodInvocation *context)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GabblePropertiesContext *ctx = &mixin->priv->context;
GError *error = NULL;
guint i;
/* Is another SetProperties request already in progress? */
if (ctx->dbus_ctx)
{
error = g_error_new (TELEPATHY_ERRORS, NotAvailable,
"A SetProperties request is already in progress");
goto ERROR;
}
ctx->dbus_ctx = context;
error = NULL;
/* Check input property identifiers */
for (i = 0; i < properties->len; i++)
{
GValue val_struct = { 0, };
guint prop_id;
GValue *prop_val;
g_value_init (&val_struct, TP_TYPE_PROPERTY_VALUE_STRUCT);
g_value_set_static_boxed (&val_struct, g_ptr_array_index (properties, i));
dbus_g_type_struct_get (&val_struct,
0, &prop_id,
1, &prop_val,
G_MAXUINT);
/* Valid? */
if (prop_id >= mixin_cls->num_props)
{
g_value_unset (prop_val);
error = g_error_new (TELEPATHY_ERRORS, InvalidArgument,
"invalid property identifier %d", prop_id);
goto ERROR;
}
/* Store the value in the context */
ctx->remaining |= 1 << prop_id;
ctx->values[prop_id] = prop_val;
/* Permitted? */
if (!gabble_properties_mixin_is_writable (obj, prop_id))
{
error = g_error_new (TELEPATHY_ERRORS, PermissionDenied,
"permission denied for property identifier %d", prop_id);
goto ERROR;
}
/* Compatible type? */
if (!g_value_type_compatible (G_VALUE_TYPE (prop_val),
mixin_cls->signatures[prop_id].type))
{
error = g_error_new (TELEPATHY_ERRORS, NotAvailable,
"incompatible value type for property identifier %d",
prop_id);
goto ERROR;
}
}
if (mixin_cls->set_properties)
{
if (mixin_cls->set_properties (obj, ctx, &error))
return;
}
else
{
gabble_properties_context_return (ctx, NULL);
return;
}
ERROR:
gabble_properties_context_return (ctx, error);
}
gboolean
gabble_properties_mixin_has_property (GObject *obj, const gchar *name,
guint *property)
{
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
guint i;
for (i = 0; i < mixin_cls->num_props; i++)
{
if (strcmp (mixin_cls->signatures[i].name, name) == 0)
{
if (property)
*property = i;
return TRUE;
}
}
return FALSE;
}
gboolean
gabble_properties_context_has (GabblePropertiesContext *ctx, guint property)
{
g_assert (property < ctx->mixin_cls->num_props);
return (ctx->values[property] != NULL);
}
gboolean
gabble_properties_context_has_other_than (GabblePropertiesContext *ctx, guint property)
{
g_assert (property < ctx->mixin_cls->num_props);
return ((ctx->remaining & ~(1 << property)) != 0);
}
const GValue *
gabble_properties_context_get (GabblePropertiesContext *ctx, guint property)
{
g_assert (property < ctx->mixin_cls->num_props);
return ctx->values[property];
}
guint
gabble_properties_context_get_value_count (GabblePropertiesContext *ctx)
{
guint i, n;
n = 0;
for (i = 0; i < ctx->mixin_cls->num_props; i++)
{
if (ctx->values[i])
n++;
}
return n;
}
void
gabble_properties_context_remove (GabblePropertiesContext *ctx, guint property)
{
g_assert (property < ctx->mixin_cls->num_props);
ctx->remaining &= ~(1 << property);
}
void
gabble_properties_context_return (GabblePropertiesContext *ctx, GError *error)
{
GObject *obj = ctx->mixin->priv->object;
GArray *changed_props_val, *changed_props_flags;
guint i;
gabble_debug (DEBUG_FLAG, "%s", (error) ? "failure" : "success");
changed_props_val = changed_props_flags = NULL;
for (i = 0; i < ctx->mixin_cls->num_props; i++)
{
if (ctx->values[i])
{
if (!error)
{
gabble_properties_mixin_change_value (obj, i, ctx->values[i],
&changed_props_val);
gabble_properties_mixin_change_flags (obj, i,
TP_PROPERTY_FLAG_READ, 0, &changed_props_flags);
}
g_value_unset (ctx->values[i]);
ctx->values[i] = NULL;
}
}
if (!error)
{
gabble_properties_mixin_emit_changed (obj, &changed_props_val);
gabble_properties_mixin_emit_flags (obj, &changed_props_flags);
dbus_g_method_return (ctx->dbus_ctx);
}
else
{
dbus_g_method_return_error (ctx->dbus_ctx, error);
g_error_free (error);
}
ctx->dbus_ctx = NULL;
ctx->remaining = 0;
}
gboolean
gabble_properties_context_return_if_done (GabblePropertiesContext *ctx)
{
if (ctx->remaining == 0)
{
gabble_properties_context_return (ctx, NULL);
return TRUE;
}
return FALSE;
}
#define RPTS_APPEND_FLAG_IF_SET(flag) \
if (flags & flag) \
{ \
if (i++ > 0) \
g_string_append (str, "|"); \
g_string_append (str, #flag + 17); \
}
static gchar *
property_flags_to_string (TpPropertyFlags flags)
{
gint i = 0;
GString *str;
str = g_string_new ("[" ANSI_BOLD_OFF);
RPTS_APPEND_FLAG_IF_SET (TP_PROPERTY_FLAG_READ);
RPTS_APPEND_FLAG_IF_SET (TP_PROPERTY_FLAG_WRITE);
g_string_append (str, ANSI_BOLD_ON "]");
return g_string_free (str, FALSE);
}
static gboolean
values_are_equal (const GValue *v1, const GValue *v2)
{
GType type = G_VALUE_TYPE (v1);
const gchar *s1, *s2;
switch (type) {
case G_TYPE_BOOLEAN:
return (g_value_get_boolean (v1) == g_value_get_boolean (v2));
case G_TYPE_STRING:
s1 = g_value_get_string (v1);
s2 = g_value_get_string (v2);
/* are they both NULL? */
if (s1 == s2)
return TRUE;
/* is one of them NULL? */
if (s1 == NULL || s2 == NULL)
return FALSE;
return (strcmp (s1, s2) == 0);
case G_TYPE_UINT:
return (g_value_get_uint (v1) == g_value_get_uint (v2));
case G_TYPE_INT:
return (g_value_get_int (v1) == g_value_get_int (v2));
}
return FALSE;
}
void
gabble_properties_mixin_change_value (GObject *obj, guint prop_id,
const GValue *new_value,
GArray **props)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GabbleProperty *prop;
g_assert (prop_id < mixin_cls->num_props);
prop = &mixin->properties[prop_id];
if (prop->value)
{
if (values_are_equal (prop->value, new_value))
return;
}
else
{
prop->value = g_new0 (GValue, 1);
g_value_init (prop->value, mixin_cls->signatures[prop_id].type);
}
g_value_copy (new_value, prop->value);
if (props)
{
guint i;
if (*props == NULL)
{
*props = g_array_sized_new (FALSE, FALSE, sizeof (guint),
mixin_cls->num_props);
}
for (i = 0; i < (*props)->len; i++)
{
if (g_array_index (*props, guint, i) == prop_id)
return;
}
g_array_append_val (*props, prop_id);
}
else
{
GArray *changed_props = g_array_sized_new (FALSE, FALSE,
sizeof (guint), 1);
g_array_append_val (changed_props, prop_id);
gabble_properties_mixin_emit_changed (obj, &changed_props);
}
}
void
gabble_properties_mixin_change_flags (GObject *obj, guint prop_id,
TpPropertyFlags add,
TpPropertyFlags remove,
GArray **props)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GabbleProperty *prop;
guint prev_flags;
g_assert (prop_id < mixin_cls->num_props);
prop = &mixin->properties[prop_id];
prev_flags = prop->flags;
prop->flags |= add;
prop->flags &= ~remove;
if (prop->flags == prev_flags)
return;
if (props)
{
guint i;
if (*props == NULL)
{
*props = g_array_sized_new (FALSE, FALSE, sizeof (guint),
mixin_cls->num_props);
}
for (i = 0; i < (*props)->len; i++)
{
if (g_array_index (*props, guint, i) == prop_id)
return;
}
g_array_append_val (*props, prop_id);
}
else
{
GArray *changed_props = g_array_sized_new (FALSE, FALSE,
sizeof (guint), 1);
g_array_append_val (changed_props, prop_id);
gabble_properties_mixin_emit_flags (obj, &changed_props);
}
}
void
gabble_properties_mixin_emit_changed (GObject *obj, GArray **props)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GPtrArray *prop_arr;
GValue prop_list = { 0, };
guint i;
if (*props == NULL)
return;
if ((*props)->len == 0)
return;
prop_arr = g_ptr_array_sized_new ((*props)->len);
if (DEBUGGING)
g_message (ANSI_BOLD_ON ANSI_FG_CYAN
"%s: emitting properties changed for propert%s:\n",
G_STRFUNC, ((*props)->len > 1) ? "ies" : "y");
for (i = 0; i < (*props)->len; i++)
{
GValue prop_val = { 0, };
guint prop_id = g_array_index (*props, guint, i);
g_value_init (&prop_val, TP_TYPE_PROPERTY_VALUE_STRUCT);
g_value_take_boxed (&prop_val,
dbus_g_type_specialized_construct (TP_TYPE_PROPERTY_VALUE_STRUCT));
dbus_g_type_struct_set (&prop_val,
0, prop_id,
1, mixin->properties[prop_id].value,
G_MAXUINT);
g_ptr_array_add (prop_arr, g_value_get_boxed (&prop_val));
if (DEBUGGING)
g_message (" %s\n", mixin_cls->signatures[prop_id].name);
}
if (DEBUGGING)
{
g_message (ANSI_RESET);
fflush (stdout);
}
g_signal_emit (obj, mixin_cls->properties_changed_signal_id, 0, prop_arr);
g_value_init (&prop_list, TP_TYPE_PROPERTY_VALUE_LIST);
g_value_take_boxed (&prop_list, prop_arr);
g_value_unset (&prop_list);
g_array_free (*props, TRUE);
*props = NULL;
}
void
gabble_properties_mixin_emit_flags (GObject *obj, GArray **props)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
GPtrArray *prop_arr;
GValue prop_list = { 0, };
guint i;
if (*props == NULL)
return;
if ((*props)->len == 0)
return;
prop_arr = g_ptr_array_sized_new ((*props)->len);
if (DEBUGGING)
g_message (ANSI_BOLD_ON ANSI_FG_WHITE
"%s: emitting properties flags changed for propert%s:\n",
G_STRFUNC, ((*props)->len > 1) ? "ies" : "y");
for (i = 0; i < (*props)->len; i++)
{
GValue prop_val = { 0, };
guint prop_id = g_array_index (*props, guint, i);
guint prop_flags;
prop_flags = mixin->properties[prop_id].flags;
g_value_init (&prop_val, TP_TYPE_PROPERTY_FLAGS_STRUCT);
g_value_take_boxed (&prop_val,
dbus_g_type_specialized_construct (TP_TYPE_PROPERTY_FLAGS_STRUCT));
dbus_g_type_struct_set (&prop_val,
0, prop_id,
1, prop_flags,
G_MAXUINT);
g_ptr_array_add (prop_arr, g_value_get_boxed (&prop_val));
if (DEBUGGING)
{
gchar *str_flags = property_flags_to_string (prop_flags);
g_message (" %s's flags now: %s\n",
mixin_cls->signatures[prop_id].name, str_flags);
g_free (str_flags);
}
}
if (DEBUGGING)
{
g_message (ANSI_RESET);
fflush (stdout);
}
g_signal_emit (obj, mixin_cls->property_flags_changed_signal_id, 0, prop_arr);
g_value_init (&prop_list, TP_TYPE_PROPERTY_FLAGS_LIST);
g_value_take_boxed (&prop_list, prop_arr);
g_value_unset (&prop_list);
g_array_free (*props, TRUE);
*props = NULL;
}
gboolean
gabble_properties_mixin_is_readable (GObject *obj, guint prop_id)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
if (prop_id >= mixin_cls->num_props)
return FALSE;
return ((mixin->properties[prop_id].flags & TP_PROPERTY_FLAG_READ) != 0);
}
gboolean
gabble_properties_mixin_is_writable (GObject *obj, guint prop_id)
{
GabblePropertiesMixin *mixin = GABBLE_PROPERTIES_MIXIN (obj);
GabblePropertiesMixinClass *mixin_cls = GABBLE_PROPERTIES_MIXIN_CLASS (
G_OBJECT_GET_CLASS (obj));
if (prop_id >= mixin_cls->num_props)
return FALSE;
return ((mixin->properties[prop_id].flags & TP_PROPERTY_FLAG_WRITE) != 0);
}