1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * GData Client
4 * Copyright (C) Philip Withnall 2010 <philip@tecnocode.co.uk>
5 *
6 * GData Client is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * GData Client is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /**
21 * SECTION:gdata-contacts-group
22 * @short_description: GData Contacts group object
23 * @stability: Stable
24 * @include: gdata/services/contacts/gdata-contacts-group.h
25 *
26 * #GDataContactsGroup is a subclass of #GDataEntry to represent a group from a Google address book.
27 *
28 * For more details of Google Contacts' GData API, see the
29 * <ulink type="http" url="http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html#Groups">online documentation</ulink>.
30 *
31 * The user-set name of the group is stored in the #GDataEntry:title property, retrievable using gdata_entry_get_title(). Note that for system groups
32 * (see #GDataContactsGroup:system-group-id), this group name is provided by Google, and is not localised. Clients should provide their own localised
33 * group names for the system groups.
34 *
35 * In addition to all the standard properties available for a group, #GDataContactsGroup supports an additional kind of property: extended
36 * properties. Extended properties, set with gdata_contacts_group_set_extended_property() and retrieved with
37 * gdata_contacts_group_get_extended_property(), are provided as a method of storing client-specific data which shouldn't be seen or be editable
38 * by the user, such as IDs and cache times.
39 *
40 * <example>
41 * <title>Adding a New Group</title>
42 * <programlisting>
43 * GDataContactsService *service;
44 * GDataContactsGroup *group, *updated_group;
45 * GDataContactsContact *contact, *updated_contact;
46 * GError *error = NULL;
47 *
48 * /<!-- -->* Create a service and return a contact to add to the new group. *<!-- -->/
49 * service = create_contacts_service ();
50 * contact = query_user_for_contact (service);
51 *
52 * /<!-- -->* Create the new group *<!-- -->/
53 * group = gdata_contacts_group_new (NULL);
54 * gdata_entry_set_title (GDATA_ENTRY (group), "Group Name");
55 *
56 * /<!-- -->* Insert the group on the server *<!-- -->/
57 * updated_group = gdata_contacts_service_insert_group (service, group, NULL, &error);
58 *
59 * g_object_unref (group);
60 *
61 * if (error != NULL) {
62 * g_error ("Error adding a group: %s", error->message);
63 * g_error_free (error);
64 * g_object_unref (contact);
65 * g_object_unref (service);
66 * return;
67 * }
68 *
69 * /<!-- -->* Add the contact to the new group. *<!-- -->/
70 * gdata_contacts_contact_add_group (contact, gdata_entry_get_id (GDATA_ENTRY (updated_group)));
71 *
72 * g_object_unref (updated_group);
73 *
74 * /<!-- -->* Update the contact on the server *<!-- -->/
75 * updated_contact = GDATA_CONTACTS_CONTACT (gdata_service_update_entry (GDATA_SERVICE (service), GDATA_ENTRY (contact), NULL, &error));
76 *
77 * g_object_unref (contact);
78 * g_object_unref (service);
79 *
80 * if (error != NULL) {
81 * g_error ("Error updating contact: %s", error->message);
82 * g_error_free (error);
83 * return;
84 * }
85 *
86 * /<!-- -->* Do something with the updated contact, such as update them in the UI, or store their ID for future use. *<!-- -->/
87 *
88 * g_object_unref (updated_contact);
89 * </programlisting>
90 * </example>
91 *
92 * Since: 0.7.0
93 */
94
95 #include <config.h>
96 #include <glib.h>
97 #include <glib/gi18n-lib.h>
98 #include <libxml/parser.h>
99 #include <string.h>
100
101 #include "gdata-contacts-group.h"
102 #include "gdata-parser.h"
103 #include "gdata-types.h"
104 #include "gdata-private.h"
105
106 /* The maximum number of extended properties the server allows us. See
107 * http://code.google.com/apis/contacts/docs/3.0/reference.html#ProjectionsAndExtended.
108 * When updating this, make sure to update the API documentation for gdata_contacts_group_get_extended_property() and
109 * gdata_contacts_group_set_extended_property(). */
110 #define MAX_N_EXTENDED_PROPERTIES 10
111
112 static GObject *gdata_contacts_group_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
113 static void gdata_contacts_group_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
114 static void gdata_contacts_group_finalize (GObject *object);
115 static void get_xml (GDataParsable *parsable, GString *xml_string);
116 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
117 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
118 static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
119
120 struct _GDataContactsGroupPrivate {
121 gint64 edited;
122 GHashTable *extended_properties;
123 gboolean deleted;
124 gchar *system_group_id;
125 };
126
127 enum {
128 PROP_EDITED = 1,
129 PROP_DELETED,
130 PROP_SYSTEM_GROUP_ID
131 };
132
G_DEFINE_TYPE(GDataContactsGroup,gdata_contacts_group,GDATA_TYPE_ENTRY)133 G_DEFINE_TYPE (GDataContactsGroup, gdata_contacts_group, GDATA_TYPE_ENTRY)
134
135 static void
136 gdata_contacts_group_class_init (GDataContactsGroupClass *klass)
137 {
138 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
139 GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
140 GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
141
142 g_type_class_add_private (klass, sizeof (GDataContactsGroupPrivate));
143
144 gobject_class->constructor = gdata_contacts_group_constructor;
145 gobject_class->get_property = gdata_contacts_group_get_property;
146 gobject_class->finalize = gdata_contacts_group_finalize;
147
148 parsable_class->parse_xml = parse_xml;
149 parsable_class->get_xml = get_xml;
150 parsable_class->get_namespaces = get_namespaces;
151
152 entry_class->get_entry_uri = get_entry_uri;
153 entry_class->kind_term = "http://schemas.google.com/contact/2008#group";
154
155 /**
156 * GDataContactsGroup:edited:
157 *
158 * The last time the group was edited. If the group has not been edited yet, the content indicates the time it was created.
159 *
160 * For more information, see the <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appEdited">
161 * Atom Publishing Protocol specification</ulink>.
162 *
163 * Since: 0.7.0
164 */
165 g_object_class_install_property (gobject_class, PROP_EDITED,
166 g_param_spec_int64 ("edited",
167 "Edited", "The last time the group was edited.",
168 -1, G_MAXINT64, -1,
169 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
170
171 /**
172 * GDataContactsGroup:deleted:
173 *
174 * Whether the entry has been deleted.
175 *
176 * Since: 0.7.0
177 */
178 g_object_class_install_property (gobject_class, PROP_DELETED,
179 g_param_spec_boolean ("deleted",
180 "Deleted", "Whether the entry has been deleted.",
181 FALSE,
182 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
183
184 /**
185 * GDataContactsGroup:system-group-id:
186 *
187 * The system group ID for this group, if it's a system group. If the group is not a system group, this is %NULL. Otherwise, it is one of the
188 * four system group IDs: %GDATA_CONTACTS_GROUP_CONTACTS, %GDATA_CONTACTS_GROUP_FRIENDS, %GDATA_CONTACTS_GROUP_FAMILY and
189 * %GDATA_CONTACTS_GROUP_COWORKERS.
190 *
191 * If this is non-%NULL, the group name stored in #GDataEntry:title will not be localised, so clients should provide localised group names of
192 * their own for each of the system groups. Whether a group is a system group should be detected solely on the basis of the value of this
193 * property, not by comparing the group name (#GDataEntry:title) or entry ID (#GDataEntry:id). The entry ID is not the same as the system
194 * group ID.
195 *
196 * Since: 0.7.0
197 */
198 g_object_class_install_property (gobject_class, PROP_SYSTEM_GROUP_ID,
199 g_param_spec_string ("system-group-id",
200 "System group ID", "The system group ID for this group, if it's a system group.",
201 NULL,
202 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
203 }
204
205 static void notify_content_cb (GObject *gobject, GParamSpec *pspec, GDataContactsGroup *self);
206
207 static void
notify_title_cb(GObject * gobject,GParamSpec * pspec,GDataContactsGroup * self)208 notify_title_cb (GObject *gobject, GParamSpec *pspec, GDataContactsGroup *self)
209 {
210 /* Update GDataEntry:content */
211 g_signal_handlers_block_by_func (self, notify_content_cb, self);
212 gdata_entry_set_content (GDATA_ENTRY (self), gdata_entry_get_title (GDATA_ENTRY (self)));
213 g_signal_handlers_unblock_by_func (self, notify_content_cb, self);
214 }
215
216 static void
notify_content_cb(GObject * gobject,GParamSpec * pspec,GDataContactsGroup * self)217 notify_content_cb (GObject *gobject, GParamSpec *pspec, GDataContactsGroup *self)
218 {
219 /* Update GDataEntry:title */
220 g_signal_handlers_block_by_func (self, notify_title_cb, self);
221 gdata_entry_set_title (GDATA_ENTRY (self), gdata_entry_get_content (GDATA_ENTRY (self)));
222 g_signal_handlers_unblock_by_func (self, notify_title_cb, self);
223 }
224
225 static void
gdata_contacts_group_init(GDataContactsGroup * self)226 gdata_contacts_group_init (GDataContactsGroup *self)
227 {
228 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CONTACTS_GROUP, GDataContactsGroupPrivate);
229 self->priv->extended_properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
230 self->priv->edited = -1;
231
232 /* Listen to change notifications for the entry's title and content, since they're linked */
233 g_signal_connect (self, "notify::title", (GCallback) notify_title_cb, self);
234 g_signal_connect (self, "notify::content", (GCallback) notify_content_cb, self);
235 }
236
237 static GObject *
gdata_contacts_group_constructor(GType type,guint n_construct_params,GObjectConstructParam * construct_params)238 gdata_contacts_group_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
239 {
240 GObject *object;
241 guint i;
242
243 /* Find the "id" property and ensure it's sane */
244 for (i = 0; i < n_construct_params; i++) {
245 GParamSpec *pspec = construct_params[i].pspec;
246 GValue *value = construct_params[i].value;
247
248 if (strcmp (g_param_spec_get_name (pspec), "id") == 0) {
249 gchar *base, *id;
250
251 id = g_value_dup_string (value);
252
253 /* Fix the ID to refer to the full projection, rather than the base projection. */
254 if (id != NULL) {
255 base = strstr (id, "/base/");
256 if (base != NULL)
257 memcpy (base, "/full/", 6);
258 }
259
260 g_value_take_string (value, id);
261
262 break;
263 }
264 }
265
266 /* Chain up to the parent class */
267 object = G_OBJECT_CLASS (gdata_contacts_group_parent_class)->constructor (type, n_construct_params, construct_params);
268
269 if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) {
270 GDataContactsGroupPrivate *priv = GDATA_CONTACTS_GROUP (object)->priv;
271 GTimeVal time_val;
272
273 /* Set the edited property to the current time (creation time). We don't do this in *_init() since that would cause setting it from
274 * parse_xml() to fail (duplicate element). */
275 g_get_current_time (&time_val);
276 priv->edited = time_val.tv_sec;
277 }
278
279 return object;
280 }
281
282 static void
gdata_contacts_group_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)283 gdata_contacts_group_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
284 {
285 GDataContactsGroupPrivate *priv = GDATA_CONTACTS_GROUP (object)->priv;
286
287 switch (property_id) {
288 case PROP_EDITED:
289 g_value_set_int64 (value, priv->edited);
290 break;
291 case PROP_DELETED:
292 g_value_set_boolean (value, priv->deleted);
293 break;
294 case PROP_SYSTEM_GROUP_ID:
295 g_value_set_string (value, priv->system_group_id);
296 break;
297 default:
298 /* We don't have any other property... */
299 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
300 break;
301 }
302 }
303
304 static void
gdata_contacts_group_finalize(GObject * object)305 gdata_contacts_group_finalize (GObject *object)
306 {
307 GDataContactsGroupPrivate *priv = GDATA_CONTACTS_GROUP (object)->priv;
308
309 g_hash_table_destroy (priv->extended_properties);
310 g_free (priv->system_group_id);
311
312 /* Chain up to the parent class */
313 G_OBJECT_CLASS (gdata_contacts_group_parent_class)->finalize (object);
314 }
315
316 static gboolean
parse_xml(GDataParsable * parsable,xmlDoc * doc,xmlNode * node,gpointer user_data,GError ** error)317 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
318 {
319 gboolean success;
320 GDataContactsGroup *self = GDATA_CONTACTS_GROUP (parsable);
321
322 if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE &&
323 gdata_parser_int64_time_from_element (node, "edited", P_REQUIRED | P_NO_DUPES, &(self->priv->edited), &success, error) == TRUE) {
324 return success;
325 } else if (gdata_parser_is_namespace (node, "http://www.w3.org/2005/Atom") == TRUE && xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
326 /* We have to override <id> parsing to fix the projection. Modify it in-place so that the parser in GDataEntry will pick up the
327 * changes. This fixes bugs caused by referring to contacts by the base projection, rather than the full projection; such as
328 * http://code.google.com/p/gdata-issues/issues/detail?id=2129. */
329 gchar *base;
330 gchar *id = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
331
332 if (id != NULL) {
333 base = strstr (id, "/base/");
334 if (base != NULL) {
335 memcpy (base, "/full/", 6);
336 xmlNodeSetContent (node, (xmlChar*) id);
337 }
338 }
339
340 xmlFree (id);
341
342 return GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
343 } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/g/2005") == TRUE) {
344 if (xmlStrcmp (node->name, (xmlChar*) "extendedProperty") == 0) {
345 /* gd:extendedProperty */
346 xmlChar *name, *value;
347 xmlBuffer *buffer = NULL;
348
349 name = xmlGetProp (node, (xmlChar*) "name");
350 if (name == NULL)
351 return gdata_parser_error_required_property_missing (node, "name", error);
352
353 /* Get either the value property, or the element's content */
354 value = xmlGetProp (node, (xmlChar*) "value");
355 if (value == NULL) {
356 xmlNode *child_node;
357
358 /* Use the element's content instead (arbitrary XML) */
359 buffer = xmlBufferCreate ();
360 for (child_node = node->children; child_node != NULL; child_node = child_node->next)
361 xmlNodeDump (buffer, doc, child_node, 0, 0);
362 value = (xmlChar*) xmlBufferContent (buffer);
363 }
364
365 gdata_contacts_group_set_extended_property (self, (gchar*) name, (gchar*) value);
366
367 xmlFree (name);
368 if (buffer != NULL)
369 xmlBufferFree (buffer);
370 else
371 xmlFree (value);
372 } else if (xmlStrcmp (node->name, (xmlChar*) "deleted") == 0) {
373 /* gd:deleted */
374 if (self->priv->deleted == TRUE)
375 return gdata_parser_error_duplicate_element (node, error);
376
377 self->priv->deleted = TRUE;
378 } else {
379 return GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
380 }
381 } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/contact/2008") == TRUE) {
382 if (xmlStrcmp (node->name, (xmlChar*) "systemGroup") == 0) {
383 /* gContact:systemGroup */
384 xmlChar *value;
385
386 if (self->priv->system_group_id != NULL)
387 return gdata_parser_error_duplicate_element (node, error);
388
389 value = xmlGetProp (node, (xmlChar*) "id");
390 if (value == NULL || *value == '\0') {
391 xmlFree (value);
392 return gdata_parser_error_required_property_missing (node, "id", error);
393 }
394
395 self->priv->system_group_id = (gchar*) value;
396 } else {
397 return GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
398 }
399 } else {
400 return GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->parse_xml (parsable, doc, node, user_data, error);
401 }
402
403 return TRUE;
404 }
405
406 static void
get_extended_property_xml_cb(const gchar * name,const gchar * value,GString * xml_string)407 get_extended_property_xml_cb (const gchar *name, const gchar *value, GString *xml_string)
408 {
409 /* Note that the value *isn't* escaped (see http://code.google.com/apis/gdata/docs/2.0/elements.html#gdExtendedProperty) */
410 gdata_parser_string_append_escaped (xml_string, "<gd:extendedProperty name='", name, "'>");
411 g_string_append_printf (xml_string, "%s</gd:extendedProperty>", value);
412 }
413
414 static void
get_xml(GDataParsable * parsable,GString * xml_string)415 get_xml (GDataParsable *parsable, GString *xml_string)
416 {
417 GDataContactsGroupPrivate *priv = GDATA_CONTACTS_GROUP (parsable)->priv;
418
419 /* Chain up to the parent class */
420 GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->get_xml (parsable, xml_string);
421
422 /* Extended properties */
423 g_hash_table_foreach (priv->extended_properties, (GHFunc) get_extended_property_xml_cb, xml_string);
424 }
425
426 static void
get_namespaces(GDataParsable * parsable,GHashTable * namespaces)427 get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
428 {
429 /* Chain up to the parent class */
430 GDATA_PARSABLE_CLASS (gdata_contacts_group_parent_class)->get_namespaces (parsable, namespaces);
431
432 g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005");
433 g_hash_table_insert (namespaces, (gchar*) "gContact", (gchar*) "http://schemas.google.com/contact/2008");
434 g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app");
435 }
436
437 static gchar *
get_entry_uri(const gchar * id)438 get_entry_uri (const gchar *id)
439 {
440 const gchar *base_pos;
441 gchar *uri = g_strdup (id);
442
443 /* The service API sometimes stubbornly insists on using the "base" view instead of the "full" view, which we have to fix, or our extended
444 * attributes are never visible */
445 base_pos = strstr (uri, "/base/");
446 if (base_pos != NULL)
447 memcpy ((char*) base_pos, "/full/", 6);
448
449 return uri;
450 }
451
452 /**
453 * gdata_contacts_group_new:
454 * @id: (allow-none): the group's ID, or %NULL
455 *
456 * Creates a new #GDataContactsGroup with the given ID and default properties.
457 *
458 * Return value: a new #GDataContactsGroup; unref with g_object_unref()
459 *
460 * Since: 0.7.0
461 */
462 GDataContactsGroup *
gdata_contacts_group_new(const gchar * id)463 gdata_contacts_group_new (const gchar *id)
464 {
465 return GDATA_CONTACTS_GROUP (g_object_new (GDATA_TYPE_CONTACTS_GROUP, "id", id, NULL));
466 }
467
468 /**
469 * gdata_contacts_group_get_edited:
470 * @self: a #GDataContactsGroup
471 *
472 * Gets the #GDataContactsGroup:edited property. If the property is unset, <code class="literal">-1</code> will be returned.
473 *
474 * Return value: the UNIX timestamp for the time the file was last edited, or <code class="literal">-1</code>
475 *
476 * Since: 0.7.0
477 */
478 gint64
gdata_contacts_group_get_edited(GDataContactsGroup * self)479 gdata_contacts_group_get_edited (GDataContactsGroup *self)
480 {
481 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), -1);
482 return self->priv->edited;
483 }
484
485 /**
486 * gdata_contacts_group_get_system_group_id:
487 * @self: a #GDataContactsGroup
488 *
489 * Gets the #GDataContactsGroup:system-group-id property. If the group is not a system group, %NULL will be returned.
490 *
491 * Return value: the group's system group ID, or %NULL
492 *
493 * Since: 0.7.0
494 */
495 const gchar *
gdata_contacts_group_get_system_group_id(GDataContactsGroup * self)496 gdata_contacts_group_get_system_group_id (GDataContactsGroup *self)
497 {
498 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), NULL);
499 return self->priv->system_group_id;
500 }
501
502 /**
503 * gdata_contacts_group_get_extended_property:
504 * @self: a #GDataContactsGroup
505 * @name: the property name; an arbitrary, unique string
506 *
507 * Gets the value of an extended property of the group. Each group can have up to 10 client-set extended properties to store data of the client's
508 * choosing.
509 *
510 * Return value: the property's value, or %NULL
511 *
512 * Since: 0.7.0
513 */
514 const gchar *
gdata_contacts_group_get_extended_property(GDataContactsGroup * self,const gchar * name)515 gdata_contacts_group_get_extended_property (GDataContactsGroup *self, const gchar *name)
516 {
517 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), NULL);
518 g_return_val_if_fail (name != NULL && *name != '\0', NULL);
519 return g_hash_table_lookup (self->priv->extended_properties, name);
520 }
521
522 /**
523 * gdata_contacts_group_get_extended_properties:
524 * @self: a #GDataContactsGroup
525 *
526 * Gets the full list of extended properties of the group; a hash table mapping property name to value.
527 *
528 * Return value: (transfer none): a #GHashTable of extended properties
529 *
530 * Since: 0.7.0
531 */
532 GHashTable *
gdata_contacts_group_get_extended_properties(GDataContactsGroup * self)533 gdata_contacts_group_get_extended_properties (GDataContactsGroup *self)
534 {
535 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), NULL);
536 return self->priv->extended_properties;
537 }
538
539 /**
540 * gdata_contacts_group_set_extended_property:
541 * @self: a #GDataContactsGroup
542 * @name: the property name; an arbitrary, unique string
543 * @value: (allow-none): the property value, or %NULL
544 *
545 * Sets the value of a group's extended property. Extended property names are unique (but of the client's choosing), and reusing the same property
546 * name will result in the old value of that property being overwritten.
547 *
548 * To unset a property, set @value to %NULL or an empty string.
549 *
550 * A group may have up to 10 extended properties, and each should be reasonably small (i.e. not a photo or ringtone). For more information, see the
551 * <ulink type="http" url="http://code.google.com/apis/contacts/docs/2.0/reference.html#ProjectionsAndExtended">online documentation</ulink>.
552 * %FALSE will be returned if you attempt to add more than 10 extended properties.
553 *
554 * Return value: %TRUE if the property was updated or deleted successfully, %FALSE otherwise
555 *
556 * Since: 0.7.0
557 */
558 gboolean
gdata_contacts_group_set_extended_property(GDataContactsGroup * self,const gchar * name,const gchar * value)559 gdata_contacts_group_set_extended_property (GDataContactsGroup *self, const gchar *name, const gchar *value)
560 {
561 GHashTable *extended_properties = self->priv->extended_properties;
562
563 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), FALSE);
564 g_return_val_if_fail (name != NULL && *name != '\0', FALSE);
565
566 if (value == NULL || *value == '\0') {
567 /* Removing a property */
568 g_hash_table_remove (extended_properties, name);
569 return TRUE;
570 }
571
572 /* We can't add more than MAX_N_EXTENDED_PROPERTIES */
573 if (g_hash_table_lookup (extended_properties, name) == NULL && g_hash_table_size (extended_properties) >= MAX_N_EXTENDED_PROPERTIES)
574 return FALSE;
575
576 /* Updating an existing property or adding a new one */
577 g_hash_table_insert (extended_properties, g_strdup (name), g_strdup (value));
578
579 return TRUE;
580 }
581
582 /**
583 * gdata_contacts_group_is_deleted:
584 * @self: a #GDataContactsGroup
585 *
586 * Returns whether the group has recently been deleted. This will always return %FALSE unless #GDataContactsQuery:show-deleted has been set to %TRUE
587 * for the query which returned the group; then this function will return %TRUE only if the group has been deleted.
588 *
589 * If a group has been deleted, no other information is available about it. This is designed to allow groups to be deleted from local address
590 * books using incremental updates from the server (e.g. with #GDataQuery:updated-min and #GDataContactsQuery:show-deleted).
591 *
592 * Return value: %TRUE if the group has been deleted, %FALSE otherwise
593 *
594 * Since: 0.7.0
595 */
596 gboolean
gdata_contacts_group_is_deleted(GDataContactsGroup * self)597 gdata_contacts_group_is_deleted (GDataContactsGroup *self)
598 {
599 g_return_val_if_fail (GDATA_IS_CONTACTS_GROUP (self), FALSE);
600 return self->priv->deleted;
601 }
602