diff -r 000000000000 -r d0f3a028347a telepathygabble/src/properties-mixin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telepathygabble/src/properties-mixin.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,830 @@ +/* + * properties-mixin.c - Source for GabblePropertiesMixin + * Copyright (C) 2006 Collabora Ltd. + * + * @author Ole Andre Vadla Ravnaas + * @author Robert McQueen + * + * 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 +#include +#include + +#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); +} +