1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * GData Client
4  * Copyright (C) Philip Withnall 2008–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-entry
22  * @short_description: GData entry object
23  * @stability: Stable
24  * @include: gdata/gdata-entry.h
25  *
26  * #GDataEntry represents a single object on the online service, such as a playlist, video or calendar event. It is a snapshot of the
27  * state of that object at the time of querying the service, so modifications made to a #GDataEntry will not be automatically or
28  * magically propagated to the server.
29  */
30 
31 #include <config.h>
32 #include <glib.h>
33 #include <glib/gi18n-lib.h>
34 #include <libxml/parser.h>
35 #include <string.h>
36 #include <json-glib/json-glib.h>
37 
38 #include "gdata-entry.h"
39 #include "gdata-types.h"
40 #include "gdata-service.h"
41 #include "gdata-private.h"
42 #include "gdata-comparable.h"
43 #include "atom/gdata-category.h"
44 #include "atom/gdata-link.h"
45 #include "atom/gdata-author.h"
46 
47 static void gdata_entry_constructed (GObject *object);
48 static void gdata_entry_dispose (GObject *object);
49 static void gdata_entry_finalize (GObject *object);
50 static void gdata_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
51 static void gdata_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
52 static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
53 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
54 static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error);
55 static void pre_get_xml (GDataParsable *parsable, GString *xml_string);
56 static void get_xml (GDataParsable *parsable, GString *xml_string);
57 static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
58 static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
59 static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
60 static void get_json (GDataParsable *parsable, JsonBuilder *builder);
61 
62 struct _GDataEntryPrivate {
63 	gchar *title;
64 	gchar *summary;
65 	gchar *id;
66 	gchar *etag;
67 	gint64 updated;
68 	gint64 published;
69 	GList *categories; /* GDataCategory */
70 	gchar *content;
71 	gboolean content_is_uri;
72 	GList *links; /* GDataLink */
73 	GList *authors; /* GDataAuthor */
74 	gchar *rights;
75 
76 	/* Batch processing data */
77 	GDataBatchOperationType batch_operation_type;
78 	guint batch_id;
79 };
80 
81 enum {
82 	PROP_TITLE = 1,
83 	PROP_SUMMARY,
84 	PROP_ETAG,
85 	PROP_ID,
86 	PROP_UPDATED,
87 	PROP_PUBLISHED,
88 	PROP_CONTENT,
89 	PROP_IS_INSERTED,
90 	PROP_RIGHTS,
91 	PROP_CONTENT_URI
92 };
93 
G_DEFINE_TYPE(GDataEntry,gdata_entry,GDATA_TYPE_PARSABLE)94 G_DEFINE_TYPE (GDataEntry, gdata_entry, GDATA_TYPE_PARSABLE)
95 
96 static void
97 gdata_entry_class_init (GDataEntryClass *klass)
98 {
99 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
100 	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
101 
102 	g_type_class_add_private (klass, sizeof (GDataEntryPrivate));
103 
104 	gobject_class->constructed = gdata_entry_constructed;
105 	gobject_class->get_property = gdata_entry_get_property;
106 	gobject_class->set_property = gdata_entry_set_property;
107 	gobject_class->dispose = gdata_entry_dispose;
108 	gobject_class->finalize = gdata_entry_finalize;
109 
110 	parsable_class->pre_parse_xml = pre_parse_xml;
111 	parsable_class->parse_xml = parse_xml;
112 	parsable_class->post_parse_xml = post_parse_xml;
113 	parsable_class->pre_get_xml = pre_get_xml;
114 	parsable_class->get_xml = get_xml;
115 	parsable_class->get_namespaces = get_namespaces;
116 	parsable_class->element_name = "entry";
117 
118 	parsable_class->parse_json = parse_json;
119 	parsable_class->get_json = get_json;
120 
121 	klass->get_entry_uri = get_entry_uri;
122 
123 	/**
124 	 * GDataEntry:title:
125 	 *
126 	 * A human-readable title for the entry.
127 	 *
128 	 * For more information, see the <ulink type="http"
129 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.title">Atom specification</ulink>.
130 	 */
131 	g_object_class_install_property (gobject_class, PROP_TITLE,
132 	                                 g_param_spec_string ("title",
133 	                                                      "Title", "A human-readable title for the entry.",
134 	                                                      NULL,
135 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
136 
137 	/**
138 	 * GDataEntry:summary:
139 	 *
140 	 * A short summary, abstract, or excerpt of the entry.
141 	 *
142 	 * For more information, see the <ulink type="http"
143 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.summary">Atom specification</ulink>.
144 	 *
145 	 * Since: 0.4.0
146 	 */
147 	g_object_class_install_property (gobject_class, PROP_SUMMARY,
148 	                                 g_param_spec_string ("summary",
149 	                                                      "Summary", "A short summary, abstract, or excerpt of the entry.",
150 	                                                      NULL,
151 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
152 
153 	/**
154 	 * GDataEntry:id:
155 	 *
156 	 * A permanent, universally unique identifier for the entry, in IRI form. This is %NULL for new entries (i.e. ones which haven't yet been
157 	 * inserted on the server, created with gdata_entry_new()), and a non-empty IRI string for all other entries.
158 	 *
159 	 * For more information, see the <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.id">
160 	 * Atom specification</ulink>.
161 	 */
162 	g_object_class_install_property (gobject_class, PROP_ID,
163 	                                 g_param_spec_string ("id",
164 	                                                      "ID", "A permanent, universally unique identifier for the entry, in IRI form.",
165 	                                                      NULL,
166 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
167 
168 	/**
169 	 * GDataEntry:etag:
170 	 *
171 	 * An identifier for a particular version of the entry. This changes every time the entry on the server changes, and can be used
172 	 * for conditional retrieval and locking.
173 	 *
174 	 * For more information, see the <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/reference.html#ResourceVersioning">
175 	 * GData specification</ulink>.
176 	 *
177 	 * Since: 0.2.0
178 	 */
179 	g_object_class_install_property (gobject_class, PROP_ETAG,
180 	                                 g_param_spec_string ("etag",
181 	                                                      "ETag", "An identifier for a particular version of the entry.",
182 	                                                      NULL,
183 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
184 
185 	/**
186 	 * GDataEntry:updated:
187 	 *
188 	 * The date and time when the entry was most recently updated significantly.
189 	 *
190 	 * For more information, see the <ulink type="http"
191 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.updated">Atom specification</ulink>.
192 	 */
193 	g_object_class_install_property (gobject_class, PROP_UPDATED,
194 	                                 g_param_spec_int64 ("updated",
195 	                                                     "Updated", "The date and time when the entry was most recently updated significantly.",
196 	                                                     -1, G_MAXINT64, -1,
197 	                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
198 
199 	/**
200 	 * GDataEntry:published:
201 	 *
202 	 * The date and time the entry was first published or made available.
203 	 *
204 	 * For more information, see the <ulink type="http"
205 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.published">Atom specification</ulink>.
206 	 */
207 	g_object_class_install_property (gobject_class, PROP_PUBLISHED,
208 	                                 g_param_spec_int64 ("published",
209 	                                                     "Published", "The date and time the entry was first published or made available.",
210 	                                                     -1, G_MAXINT64, -1,
211 	                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
212 
213 	/**
214 	 * GDataEntry:content:
215 	 *
216 	 * The content of the entry. This is mutually exclusive with #GDataEntry:content.
217 	 *
218 	 * For more information, see the <ulink type="http"
219 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.content">Atom specification</ulink>.
220 	 */
221 	g_object_class_install_property (gobject_class, PROP_CONTENT,
222 	                                 g_param_spec_string ("content",
223 	                                                      "Content", "The content of the entry.",
224 	                                                      NULL,
225 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
226 
227 	/**
228 	 * GDataEntry:content-uri:
229 	 *
230 	 * A URI pointing to the location of the content of the entry. This is mutually exclusive with #GDataEntry:content.
231 	 *
232 	 * For more information, see the
233 	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.content">Atom specification</ulink>.
234 	 *
235 	 * Since: 0.7.0
236 	 */
237 	g_object_class_install_property (gobject_class, PROP_CONTENT_URI,
238 	                                 g_param_spec_string ("content-uri",
239 	                                                      "Content URI", "A URI pointing to the location of the content of the entry.",
240 	                                                      NULL,
241 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
242 
243 	/**
244 	 * GDataEntry:is-inserted:
245 	 *
246 	 * Whether the entry has been inserted on the server. This is %FALSE for entries which have just been created using gdata_entry_new() and
247 	 * %TRUE for entries returned from the server by queries. It is set to %TRUE when an entry is inserted using gdata_service_insert_entry().
248 	 */
249 	g_object_class_install_property (gobject_class, PROP_IS_INSERTED,
250 	                                 g_param_spec_boolean ("is-inserted",
251 	                                                       "Inserted?", "Whether the entry has been inserted on the server.",
252 	                                                       FALSE,
253 	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
254 
255 	/**
256 	 * GDataEntry:rights:
257 	 *
258 	 * The ownership rights pertaining to the entry.
259 	 *
260 	 * For more information, see the <ulink type="http"
261 	 * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.rights">Atom specification</ulink>.
262 	 *
263 	 * Since: 0.5.0
264 	 */
265 	g_object_class_install_property (gobject_class, PROP_RIGHTS,
266 	                                 g_param_spec_string ("rights",
267 	                                                      "Rights", "The ownership rights pertaining to the entry.",
268 	                                                      NULL,
269 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
270 }
271 
272 static void
gdata_entry_init(GDataEntry * self)273 gdata_entry_init (GDataEntry *self)
274 {
275 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_ENTRY, GDataEntryPrivate);
276 	self->priv->updated = -1;
277 	self->priv->published = -1;
278 }
279 
280 static void
gdata_entry_constructed(GObject * object)281 gdata_entry_constructed (GObject *object)
282 {
283 	GDataEntryClass *klass = GDATA_ENTRY_GET_CLASS (object);
284 	GObjectClass *parent_class = G_OBJECT_CLASS (gdata_entry_parent_class);
285 
286 	/* This can't be done in *_init() because the class properties haven't been properly set then */
287 	if (klass->kind_term != NULL) {
288 		/* Ensure we have the correct category/kind */
289 		GDataCategory *category = gdata_category_new (klass->kind_term, "http://schemas.google.com/g/2005#kind", NULL);
290 		gdata_entry_add_category (GDATA_ENTRY (object), category);
291 		g_object_unref (category);
292 	}
293 
294 	/* Chain up to the parent class */
295 	if (parent_class->constructed != NULL)
296 		parent_class->constructed (object);
297 }
298 
299 static void
gdata_entry_dispose(GObject * object)300 gdata_entry_dispose (GObject *object)
301 {
302 	GDataEntryPrivate *priv = GDATA_ENTRY (object)->priv;
303 
304 	g_list_free_full (priv->categories, g_object_unref);
305 	priv->categories = NULL;
306 
307 	g_list_free_full (priv->links, g_object_unref);
308 	priv->links = NULL;
309 
310 	g_list_free_full (priv->authors, g_object_unref);
311 	priv->authors = NULL;
312 
313 	/* Chain up to the parent class */
314 	G_OBJECT_CLASS (gdata_entry_parent_class)->dispose (object);
315 }
316 
317 static void
gdata_entry_finalize(GObject * object)318 gdata_entry_finalize (GObject *object)
319 {
320 	GDataEntryPrivate *priv = GDATA_ENTRY (object)->priv;
321 
322 	g_free (priv->title);
323 	g_free (priv->summary);
324 	g_free (priv->id);
325 	g_free (priv->etag);
326 	g_free (priv->rights);
327 	g_free (priv->content);
328 
329 	/* Chain up to the parent class */
330 	G_OBJECT_CLASS (gdata_entry_parent_class)->finalize (object);
331 }
332 
333 static void
gdata_entry_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)334 gdata_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
335 {
336 	GDataEntryPrivate *priv = GDATA_ENTRY (object)->priv;
337 
338 	switch (property_id) {
339 		case PROP_TITLE:
340 			g_value_set_string (value, priv->title);
341 			break;
342 		case PROP_SUMMARY:
343 			g_value_set_string (value, priv->summary);
344 			break;
345 		case PROP_ID:
346 			g_value_set_string (value, priv->id);
347 			break;
348 		case PROP_ETAG:
349 			g_value_set_string (value, priv->etag);
350 			break;
351 		case PROP_UPDATED:
352 			g_value_set_int64 (value, priv->updated);
353 			break;
354 		case PROP_PUBLISHED:
355 			g_value_set_int64 (value, priv->published);
356 			break;
357 		case PROP_CONTENT:
358 			g_value_set_string (value, (priv->content_is_uri == FALSE) ? priv->content : NULL);
359 			break;
360 		case PROP_CONTENT_URI:
361 			g_value_set_string (value, (priv->content_is_uri == TRUE) ? priv->content : NULL);
362 			break;
363 		case PROP_IS_INSERTED:
364 			g_value_set_boolean (value, gdata_entry_is_inserted (GDATA_ENTRY (object)));
365 			break;
366 		case PROP_RIGHTS:
367 			g_value_set_string (value, priv->rights);
368 			break;
369 		default:
370 			/* We don't have any other property... */
371 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
372 			break;
373 	}
374 }
375 
376 static void
gdata_entry_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)377 gdata_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
378 {
379 	GDataEntry *self = GDATA_ENTRY (object);
380 
381 	switch (property_id) {
382 		case PROP_ID:
383 			/* Construct only */
384 			self->priv->id = g_value_dup_string (value);
385 			break;
386 		case PROP_ETAG:
387 			/* Construct only */
388 			self->priv->etag = g_value_dup_string (value);
389 			break;
390 		case PROP_TITLE:
391 			gdata_entry_set_title (self, g_value_get_string (value));
392 			break;
393 		case PROP_SUMMARY:
394 			gdata_entry_set_summary (self, g_value_get_string (value));
395 			break;
396 		case PROP_CONTENT:
397 			gdata_entry_set_content (self, g_value_get_string (value));
398 			break;
399 		case PROP_CONTENT_URI:
400 			gdata_entry_set_content_uri (self, g_value_get_string (value));
401 			break;
402 		case PROP_RIGHTS:
403 			gdata_entry_set_rights (self, g_value_get_string (value));
404 			break;
405 		default:
406 			/* We don't have any other property... */
407 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
408 			break;
409 	}
410 }
411 
412 static gboolean
pre_parse_xml(GDataParsable * parsable,xmlDoc * doc,xmlNode * root_node,gpointer user_data,GError ** error)413 pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
414 {
415 	/* Extract the ETag */
416 	GDATA_ENTRY (parsable)->priv->etag = (gchar*) xmlGetProp (root_node, (xmlChar*) "etag");
417 
418 	return TRUE;
419 }
420 
421 static gboolean
parse_xml(GDataParsable * parsable,xmlDoc * doc,xmlNode * node,gpointer user_data,GError ** error)422 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
423 {
424 	gboolean success;
425 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
426 
427 	if (gdata_parser_is_namespace (node, "http://www.w3.org/2005/Atom") == TRUE) {
428 		if (gdata_parser_string_from_element (node, "title", P_DEFAULT | P_NO_DUPES, &(priv->title), &success, error) == TRUE ||
429 		    gdata_parser_string_from_element (node, "id", P_REQUIRED | P_NON_EMPTY | P_NO_DUPES, &(priv->id), &success, error) == TRUE ||
430 		    gdata_parser_string_from_element (node, "summary", P_NONE, &(priv->summary), &success, error) == TRUE ||
431 		    gdata_parser_string_from_element (node, "rights", P_NONE, &(priv->rights), &success, error) == TRUE ||
432 		    gdata_parser_int64_time_from_element (node, "updated", P_REQUIRED | P_NO_DUPES, &(priv->updated), &success, error) == TRUE ||
433 		    gdata_parser_int64_time_from_element (node, "published", P_REQUIRED | P_NO_DUPES, &(priv->published), &success, error) == TRUE ||
434 		    gdata_parser_object_from_element_setter (node, "category", P_REQUIRED, GDATA_TYPE_CATEGORY,
435 		                                             gdata_entry_add_category, parsable, &success, error) == TRUE ||
436 		    gdata_parser_object_from_element_setter (node, "link", P_REQUIRED, GDATA_TYPE_LINK,
437 		                                             gdata_entry_add_link, parsable, &success, error) == TRUE ||
438 		    gdata_parser_object_from_element_setter (node, "author", P_REQUIRED, GDATA_TYPE_AUTHOR,
439 		                                             gdata_entry_add_author, parsable, &success, error) == TRUE) {
440 			return success;
441 		} else if (xmlStrcmp (node->name, (xmlChar*) "content") == 0) {
442 			/* atom:content */
443 			priv->content = (gchar*) xmlGetProp (node, (xmlChar*) "src");
444 			priv->content_is_uri = TRUE;
445 
446 			if (priv->content == NULL) {
447 				priv->content = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
448 				priv->content_is_uri = FALSE;
449 			}
450 
451 			return TRUE;
452 		}
453 	} else if (gdata_parser_is_namespace (node, "http://schemas.google.com/gdata/batch") == TRUE) {
454 		if (xmlStrcmp (node->name, (xmlChar*) "id") == 0 ||
455 		    xmlStrcmp (node->name, (xmlChar*) "status") == 0 ||
456 		    xmlStrcmp (node->name, (xmlChar*) "operation") == 0) {
457 			/* Ignore batch operation elements; they're handled in GDataBatchFeed */
458 			return TRUE;
459 		}
460 	}
461 
462 	return GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_xml (parsable, doc, node, user_data, error);
463 }
464 
465 static gboolean
post_parse_xml(GDataParsable * parsable,gpointer user_data,GError ** error)466 post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error)
467 {
468 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
469 
470 	/* Check for missing required elements */
471 	/* Can't uncomment it, as things like access rules break the Atom standard */
472 	/*if (priv->title == NULL)
473 		return gdata_parser_error_required_element_missing ("title", "entry", error);
474 	if (priv->id == NULL)
475 		return gdata_parser_error_required_element_missing ("id", "entry", error);
476 	if (priv->updated.tv_sec == 0 && priv->updated.tv_usec == 0)
477 		return gdata_parser_error_required_element_missing ("updated", "entry", error);*/
478 
479 	/* Reverse our lists of stuff */
480 	priv->categories = g_list_reverse (priv->categories);
481 	priv->links = g_list_reverse (priv->links);
482 	priv->authors = g_list_reverse (priv->authors);
483 
484 	return TRUE;
485 }
486 
487 static void
pre_get_xml(GDataParsable * parsable,GString * xml_string)488 pre_get_xml (GDataParsable *parsable, GString *xml_string)
489 {
490 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
491 
492 	/* Add the entry's ETag, if available */
493 	if (gdata_entry_get_etag (GDATA_ENTRY (parsable)) != NULL)
494 		gdata_parser_string_append_escaped (xml_string, " gd:etag='", priv->etag, "'");
495 }
496 
497 static void
get_xml(GDataParsable * parsable,GString * xml_string)498 get_xml (GDataParsable *parsable, GString *xml_string)
499 {
500 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
501 	GList *categories, *links, *authors;
502 
503 	gdata_parser_string_append_escaped (xml_string, "<title type='text'>", priv->title, "</title>");
504 
505 	if (priv->id != NULL)
506 		gdata_parser_string_append_escaped (xml_string, "<id>", priv->id, "</id>");
507 
508 	if (priv->updated != -1) {
509 		gchar *updated = gdata_parser_int64_to_iso8601 (priv->updated);
510 		g_string_append_printf (xml_string, "<updated>%s</updated>", updated);
511 		g_free (updated);
512 	}
513 
514 	if (priv->published != -1) {
515 		gchar *published = gdata_parser_int64_to_iso8601 (priv->published);
516 		g_string_append_printf (xml_string, "<published>%s</published>", published);
517 		g_free (published);
518 	}
519 
520 	if (priv->summary != NULL)
521 		gdata_parser_string_append_escaped (xml_string, "<summary type='text'>", priv->summary, "</summary>");
522 
523 	if (priv->rights != NULL)
524 		gdata_parser_string_append_escaped (xml_string, "<rights>", priv->rights, "</rights>");
525 
526 	if (priv->content != NULL) {
527 		if (priv->content_is_uri == TRUE)
528 			gdata_parser_string_append_escaped (xml_string, "<content type='text/plain' src='", priv->content, "'/>");
529 		else
530 			gdata_parser_string_append_escaped (xml_string, "<content type='text'>", priv->content, "</content>");
531 	}
532 
533 	for (categories = priv->categories; categories != NULL; categories = categories->next)
534 		_gdata_parsable_get_xml (GDATA_PARSABLE (categories->data), xml_string, FALSE);
535 
536 	for (links = priv->links; links != NULL; links = links->next)
537 		_gdata_parsable_get_xml (GDATA_PARSABLE (links->data), xml_string, FALSE);
538 
539 	for (authors = priv->authors; authors != NULL; authors = authors->next)
540 		_gdata_parsable_get_xml (GDATA_PARSABLE (authors->data), xml_string, FALSE);
541 
542 	/* Batch operation data */
543 	if (priv->batch_id != 0) {
544 		const gchar *batch_op;
545 
546 		switch (priv->batch_operation_type) {
547 			case GDATA_BATCH_OPERATION_QUERY:
548 				batch_op = "query";
549 				break;
550 			case GDATA_BATCH_OPERATION_INSERTION:
551 				batch_op = "insert";
552 				break;
553 			case GDATA_BATCH_OPERATION_UPDATE:
554 				batch_op = "update";
555 				break;
556 			case GDATA_BATCH_OPERATION_DELETION:
557 				batch_op = "delete";
558 				break;
559 			default:
560 				g_assert_not_reached ();
561 				break;
562 		}
563 
564 		g_string_append_printf (xml_string, "<batch:id>%u</batch:id><batch:operation type='%s'/>", priv->batch_id, batch_op);
565 	}
566 }
567 
568 static void
get_namespaces(GDataParsable * parsable,GHashTable * namespaces)569 get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
570 {
571 	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005");
572 
573 	if (GDATA_ENTRY (parsable)->priv->batch_id != 0)
574 		g_hash_table_insert (namespaces, (gchar*) "batch", (gchar*) "http://schemas.google.com/gdata/batch");
575 }
576 
577 static gchar *
get_entry_uri(const gchar * id)578 get_entry_uri (const gchar *id)
579 {
580 	/* We assume the entry ID is also its entry URI; subclasses can override this
581 	 * if the service they implement has a convoluted API */
582 	return g_strdup (id);
583 }
584 
585 static gboolean
parse_json(GDataParsable * parsable,JsonReader * reader,gpointer user_data,GError ** error)586 parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
587 {
588 	gboolean success;
589 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
590 
591 	if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT | P_NO_DUPES, &(priv->title), &success, error) == TRUE ||
592 	    gdata_parser_string_from_json_member (reader, "id", P_NON_EMPTY | P_NO_DUPES, &(priv->id), &success, error) == TRUE ||
593 	    gdata_parser_string_from_json_member (reader, "description", P_NONE, &(priv->summary), &success, error) == TRUE ||
594 	    gdata_parser_int64_time_from_json_member (reader, "updated", P_REQUIRED | P_NO_DUPES, &(priv->updated), &success, error) == TRUE ||
595 	    gdata_parser_string_from_json_member (reader, "etag", P_NON_EMPTY | P_NO_DUPES, &(priv->etag), &success, error) == TRUE) {
596 		return success;
597 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "selfLink") == 0) {
598 		GDataLink *_link;
599 		const gchar *uri;
600 
601 		/* Empty URI? */
602 		uri = json_reader_get_string_value (reader);
603 		if (uri == NULL || *uri == '\0') {
604 			return gdata_parser_error_required_json_content_missing (reader, error);
605 		}
606 
607 		_link = gdata_link_new (uri, GDATA_LINK_SELF);
608 		gdata_entry_add_link (GDATA_ENTRY (parsable), _link);
609 		g_object_unref (_link);
610 
611 		return TRUE;
612 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "kind") == 0) {
613 		GDataCategory *category;
614 		const gchar *kind;
615 
616 		/* Empty kind? */
617 		kind = json_reader_get_string_value (reader);
618 		if (kind == NULL || *kind == '\0') {
619 			return gdata_parser_error_required_json_content_missing (reader, error);
620 		}
621 
622 		category = gdata_category_new (kind, "http://schemas.google.com/g/2005#kind", NULL);
623 		gdata_entry_add_category (GDATA_ENTRY (parsable), category);
624 		g_object_unref (category);
625 
626 		return TRUE;
627 	}
628 
629 	return GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_json (parsable, reader, user_data, error);
630 }
631 
632 static void
get_json(GDataParsable * parsable,JsonBuilder * builder)633 get_json (GDataParsable *parsable, JsonBuilder *builder)
634 {
635 	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
636 	GList *i;
637 	GDataLink *_link;
638 
639 	json_builder_set_member_name (builder, "title");
640 	json_builder_add_string_value (builder, priv->title);
641 
642 	if (priv->id != NULL) {
643 		json_builder_set_member_name (builder, "id");
644 		json_builder_add_string_value (builder, priv->id);
645 	}
646 
647 	if (priv->summary != NULL) {
648 		json_builder_set_member_name (builder, "description");
649 		json_builder_add_string_value (builder, priv->summary);
650 	}
651 
652 	if (priv->updated != -1) {
653 		gchar *updated = gdata_parser_int64_to_iso8601 (priv->updated);
654 		json_builder_set_member_name (builder, "updated");
655 		json_builder_add_string_value (builder, updated);
656 		g_free (updated);
657 	}
658 
659 	/* If we have a "kind" category, add that. */
660 	for (i = priv->categories; i != NULL; i = i->next) {
661 		GDataCategory *category = GDATA_CATEGORY (i->data);
662 
663 		if (g_strcmp0 (gdata_category_get_scheme (category), "http://schemas.google.com/g/2005#kind") == 0) {
664 			json_builder_set_member_name (builder, "kind");
665 			json_builder_add_string_value (builder, gdata_category_get_term (category));
666 		}
667 	}
668 
669 	/* Add the ETag, if available. */
670 	if (gdata_entry_get_etag (GDATA_ENTRY (parsable)) != NULL) {
671 		json_builder_set_member_name (builder, "etag");
672 		json_builder_add_string_value (builder, priv->etag);
673 	}
674 
675 	/* Add the self-link. */
676 	_link = gdata_entry_look_up_link (GDATA_ENTRY (parsable), GDATA_LINK_SELF);
677 	if (_link != NULL) {
678 		json_builder_set_member_name (builder, "selfLink");
679 		json_builder_add_string_value (builder, gdata_link_get_uri (_link));
680 	}
681 }
682 
683 /**
684  * gdata_entry_new:
685  * @id: (allow-none): the entry's ID, or %NULL
686  *
687  * Creates a new #GDataEntry with the given ID and default properties.
688  *
689  * Return value: a new #GDataEntry; unref with g_object_unref()
690  */
691 GDataEntry *
gdata_entry_new(const gchar * id)692 gdata_entry_new (const gchar *id)
693 {
694 	GDataEntry *entry = GDATA_ENTRY (g_object_new (GDATA_TYPE_ENTRY, "id", id, NULL));
695 
696 	/* Set this here, as it interferes with P_NO_DUPES when parsing */
697 	entry->priv->title = g_strdup (""); /* title can't be NULL */
698 
699 	return entry;
700 }
701 
702 /**
703  * gdata_entry_get_title:
704  * @self: a #GDataEntry
705  *
706  * Returns the title of the entry. This will never be %NULL, but may be an empty string.
707  *
708  * Return value: the entry's title
709  */
710 const gchar *
gdata_entry_get_title(GDataEntry * self)711 gdata_entry_get_title (GDataEntry *self)
712 {
713 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
714 	return self->priv->title;
715 }
716 
717 /**
718  * gdata_entry_set_title:
719  * @self: a #GDataEntry
720  * @title: (allow-none): the new entry title, or %NULL
721  *
722  * Sets the title of the entry.
723  */
724 void
gdata_entry_set_title(GDataEntry * self,const gchar * title)725 gdata_entry_set_title (GDataEntry *self, const gchar *title)
726 {
727 	g_return_if_fail (GDATA_IS_ENTRY (self));
728 
729 	g_free (self->priv->title);
730 	self->priv->title = g_strdup (title);
731 	g_object_notify (G_OBJECT (self), "title");
732 }
733 
734 /**
735  * gdata_entry_get_summary:
736  * @self: a #GDataEntry
737  *
738  * Returns the summary of the entry.
739  *
740  * Return value: the entry's summary, or %NULL
741  *
742  * Since: 0.4.0
743  */
744 const gchar *
gdata_entry_get_summary(GDataEntry * self)745 gdata_entry_get_summary (GDataEntry *self)
746 {
747 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
748 	return self->priv->summary;
749 }
750 
751 /**
752  * gdata_entry_set_summary:
753  * @self: a #GDataEntry
754  * @summary: (allow-none): the new entry summary, or %NULL
755  *
756  * Sets the summary of the entry.
757  *
758  * Since: 0.4.0
759  */
760 void
gdata_entry_set_summary(GDataEntry * self,const gchar * summary)761 gdata_entry_set_summary (GDataEntry *self, const gchar *summary)
762 {
763 	g_return_if_fail (GDATA_IS_ENTRY (self));
764 
765 	g_free (self->priv->summary);
766 	self->priv->summary = g_strdup (summary);
767 	g_object_notify (G_OBJECT (self), "summary");
768 }
769 
770 /**
771  * gdata_entry_get_id:
772  * @self: a #GDataEntry
773  *
774  * Returns the URN ID of the entry; a unique and permanent identifier for the object the entry represents.
775  *
776  * The ID may be %NULL if and only if the #GDataEntry has been newly created, and hasn't yet been inserted on the server.
777  *
778  * Return value: (nullable): the entry's ID, or %NULL
779  */
780 const gchar *
gdata_entry_get_id(GDataEntry * self)781 gdata_entry_get_id (GDataEntry *self)
782 {
783 	gchar *id;
784 
785 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
786 
787 	/* We have to get the actual property since GDataDocumentsEntry overrides it. We then store it in our own ID field so that we can
788 	 * free it later on. */
789 	g_object_get (G_OBJECT (self), "id", &id, NULL);
790 	if (g_strcmp0 (id, self->priv->id) != 0) {
791 		g_free (self->priv->id);
792 		self->priv->id = id;
793 	} else {
794 		g_free (id);
795 	}
796 
797 	return self->priv->id;
798 }
799 
800 /**
801  * gdata_entry_get_etag:
802  * @self: a #GDataEntry
803  *
804  * Returns the ETag of the entry; a unique identifier for each version of the entry. For more information, see the
805  * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/reference.html#ResourceVersioning">online documentation</ulink>.
806  *
807  * The ETag will never be empty; it's either %NULL or a valid ETag.
808  *
809  * Return value: (nullable): the entry's ETag, or %NULL
810  *
811  * Since: 0.2.0
812  */
813 const gchar *
gdata_entry_get_etag(GDataEntry * self)814 gdata_entry_get_etag (GDataEntry *self)
815 {
816 	gchar *etag;
817 
818 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
819 
820 	/* We have to check if the property's set since GDataAccessRule overrides it and sets it to always be NULL (since ACL entries don't support
821 	 * ETags, for some reason). */
822 	g_object_get (G_OBJECT (self), "etag", &etag, NULL);
823 	if (etag != NULL) {
824 		g_free (etag);
825 		return self->priv->etag;
826 	}
827 
828 	return NULL;
829 }
830 
831 /*
832  * _gdata_entry_set_etag:
833  * @self: a #GDataEntry
834  * @etag: the new ETag value
835  *
836  * Sets the value of the #GDataEntry:etag property to @etag.
837  *
838  * Since: 0.17.2
839  */
840 void
_gdata_entry_set_etag(GDataEntry * self,const gchar * etag)841 _gdata_entry_set_etag (GDataEntry *self, const gchar *etag)
842 {
843 	g_return_if_fail (GDATA_IS_ENTRY (self));
844 
845 	g_free (self->priv->etag);
846 	self->priv->etag = g_strdup (etag);
847 }
848 
849 /**
850  * gdata_entry_get_updated:
851  * @self: a #GDataEntry
852  *
853  * Gets the time the entry was last updated.
854  *
855  * Return value: the UNIX timestamp for the last update of the entry
856  */
857 gint64
gdata_entry_get_updated(GDataEntry * self)858 gdata_entry_get_updated (GDataEntry *self)
859 {
860 	g_return_val_if_fail (GDATA_IS_ENTRY (self), -1);
861 	return self->priv->updated;
862 }
863 
864 /*
865  * _gdata_entry_set_updated:
866  * @self: a #GDataEntry
867  * @updated: the new updated value
868  *
869  * Sets the value of the #GDataEntry:updated property to @updated.
870  *
871  * Since: 0.6.0
872  */
873 void
_gdata_entry_set_updated(GDataEntry * self,gint64 updated)874 _gdata_entry_set_updated (GDataEntry *self, gint64 updated)
875 {
876 	g_return_if_fail (GDATA_IS_ENTRY (self));
877 	self->priv->updated = updated;
878 }
879 
880 /*
881  * _gdata_entry_set_published:
882  * @self: a #GDataEntry
883  * @updated: the new published value
884  *
885  * Sets the value of the #GDataEntry:published property to @published.
886  *
887  * Since: 0.17.0
888  */
889 void
_gdata_entry_set_published(GDataEntry * self,gint64 published)890 _gdata_entry_set_published (GDataEntry *self, gint64 published)
891 {
892 	g_return_if_fail (GDATA_IS_ENTRY (self));
893 	self->priv->published = published;
894 }
895 
896 /*
897  * _gdata_entry_set_id:
898  * @self: a #GDataEntry
899  * @id: (nullable): the new ID
900  *
901  * Sets the value of the #GDataEntry:id property to @id.
902  *
903  * Since: 0.17.0
904  */
905 void
_gdata_entry_set_id(GDataEntry * self,const gchar * id)906 _gdata_entry_set_id (GDataEntry *self, const gchar *id)
907 {
908 	g_return_if_fail (GDATA_IS_ENTRY (self));
909 
910 	g_free (self->priv->id);
911 	self->priv->id = g_strdup (id);
912 }
913 
914 /**
915  * gdata_entry_get_published:
916  * @self: a #GDataEntry
917  *
918  * Gets the time the entry was originally published.
919  *
920  * Return value: the UNIX timestamp for the original publish time of the entry
921  */
922 gint64
gdata_entry_get_published(GDataEntry * self)923 gdata_entry_get_published (GDataEntry *self)
924 {
925 	g_return_val_if_fail (GDATA_IS_ENTRY (self), -1);
926 	return self->priv->published;
927 }
928 
929 /**
930  * gdata_entry_add_category:
931  * @self: a #GDataEntry
932  * @category: a #GDataCategory to add
933  *
934  * Adds @category to the list of categories in the given #GDataEntry, and increments its reference count.
935  *
936  * Duplicate categories will not be added to the list.
937  */
938 void
gdata_entry_add_category(GDataEntry * self,GDataCategory * category)939 gdata_entry_add_category (GDataEntry *self, GDataCategory *category)
940 {
941 	g_return_if_fail (GDATA_IS_ENTRY (self));
942 	g_return_if_fail (GDATA_IS_CATEGORY (category));
943 
944 	/* Check to see if it's a kind category and if it matches the entry's predetermined kind */
945 	if (g_strcmp0 (gdata_category_get_scheme (category), "http://schemas.google.com/g/2005#kind") == 0) {
946 		GDataEntryClass *klass = GDATA_ENTRY_GET_CLASS (self);
947 		GList *element;
948 
949 		if (klass->kind_term != NULL && g_strcmp0 (gdata_category_get_term (category), klass->kind_term) != 0) {
950 			/* This used to make sense as a warning, but the new
951 			 * JSON APIs use a lot of different kinds for very
952 			 * highly related JSON schemas, which libgdata uses a
953 			 * single class for…so it makes less sense now. */
954 			g_debug ("Adding a kind category term, '%s', to an entry of kind '%s'.",
955 			         gdata_category_get_term (category), klass->kind_term);
956 		}
957 
958 		/* If it is a kind category, remove the entry’s existing kind category to allow the new one
959 		 * to be added. This is necessary because the existing category was set in
960 		 * gdata_entry_constructed() and might not contain all the attributes of the actual XML
961 		 * category.
962 		 *
963 		 * See: https://bugzilla.gnome.org/show_bug.cgi?id=707477 */
964 		element = g_list_find_custom (self->priv->categories, category, (GCompareFunc) gdata_comparable_compare);
965 		if (element != NULL) {
966 			g_assert (GDATA_IS_CATEGORY (element->data));
967 			g_object_unref (element->data);
968 			self->priv->categories = g_list_delete_link (self->priv->categories, element);
969 		}
970 	}
971 
972 	/* Add the category if we don't already have it */
973 	if (g_list_find_custom (self->priv->categories, category, (GCompareFunc) gdata_comparable_compare) == NULL)
974 		self->priv->categories = g_list_prepend (self->priv->categories, g_object_ref (category));
975 }
976 
977 /**
978  * gdata_entry_get_categories:
979  * @self: a #GDataEntry
980  *
981  * Gets a list of the #GDataCategorys containing this entry.
982  *
983  * Return value: (element-type GData.Category) (transfer none): a #GList of #GDataCategorys
984  *
985  * Since: 0.2.0
986  */
987 GList *
gdata_entry_get_categories(GDataEntry * self)988 gdata_entry_get_categories (GDataEntry *self)
989 {
990 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
991 	return self->priv->categories;
992 }
993 
994 /**
995  * gdata_entry_get_authors:
996  * @self: a #GDataEntry
997  *
998  * Gets a list of the #GDataAuthors for this entry.
999  *
1000  * Return value: (element-type GData.Author) (transfer none): a #GList of #GDataAuthors
1001  *
1002  * Since: 0.7.0
1003  */
1004 GList *
gdata_entry_get_authors(GDataEntry * self)1005 gdata_entry_get_authors (GDataEntry *self)
1006 {
1007 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1008 	return self->priv->authors;
1009 }
1010 
1011 /**
1012  * gdata_entry_get_content:
1013  * @self: a #GDataEntry
1014  *
1015  * Returns the textual content in this entry. If the content in this entry is pointed to by a URI, %NULL will be returned; the content URI will be
1016  * returned by gdata_entry_get_content_uri().
1017  *
1018  * Return value: the entry's content, or %NULL
1019  */
1020 const gchar *
gdata_entry_get_content(GDataEntry * self)1021 gdata_entry_get_content (GDataEntry *self)
1022 {
1023 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1024 	return (self->priv->content_is_uri == FALSE) ? self->priv->content : NULL;
1025 }
1026 
1027 /**
1028  * gdata_entry_set_content:
1029  * @self: a #GDataEntry
1030  * @content: (allow-none): the new content for the entry, or %NULL
1031  *
1032  * Sets the entry's content to @content. This unsets #GDataEntry:content-uri.
1033  */
1034 void
gdata_entry_set_content(GDataEntry * self,const gchar * content)1035 gdata_entry_set_content (GDataEntry *self, const gchar *content)
1036 {
1037 	g_return_if_fail (GDATA_IS_ENTRY (self));
1038 
1039 	g_free (self->priv->content);
1040 	self->priv->content = g_strdup (content);
1041 	self->priv->content_is_uri = FALSE;
1042 
1043 	g_object_freeze_notify (G_OBJECT (self));
1044 	g_object_notify (G_OBJECT (self), "content");
1045 	g_object_notify (G_OBJECT (self), "content-uri");
1046 	g_object_thaw_notify (G_OBJECT (self));
1047 }
1048 
1049 /**
1050  * gdata_entry_get_content_uri:
1051  * @self: a #GDataEntry
1052  *
1053  * Returns a URI pointing to the content of this entry. If the content in this entry is stored directly, %NULL will be returned; the content will be
1054  * returned by gdata_entry_get_content().
1055  *
1056  * Return value: a URI pointing to the entry's content, or %NULL
1057  *
1058  * Since: 0.7.0
1059  */
1060 const gchar *
gdata_entry_get_content_uri(GDataEntry * self)1061 gdata_entry_get_content_uri (GDataEntry *self)
1062 {
1063 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1064 	return (self->priv->content_is_uri == TRUE) ? self->priv->content : NULL;
1065 }
1066 
1067 /**
1068  * gdata_entry_set_content_uri:
1069  * @self: a #GDataEntry
1070  * @content_uri: (allow-none): the new URI pointing to the content for the entry, or %NULL
1071  *
1072  * Sets the URI pointing to the entry's content to @content. This unsets #GDataEntry:content.
1073  *
1074  * Since: 0.7.0
1075  */
1076 void
gdata_entry_set_content_uri(GDataEntry * self,const gchar * content_uri)1077 gdata_entry_set_content_uri (GDataEntry *self, const gchar *content_uri)
1078 {
1079 	g_return_if_fail (GDATA_IS_ENTRY (self));
1080 
1081 	g_free (self->priv->content);
1082 	self->priv->content = g_strdup (content_uri);
1083 	self->priv->content_is_uri = TRUE;
1084 
1085 	g_object_freeze_notify (G_OBJECT (self));
1086 	g_object_notify (G_OBJECT (self), "content");
1087 	g_object_notify (G_OBJECT (self), "content-uri");
1088 	g_object_thaw_notify (G_OBJECT (self));
1089 }
1090 
1091 /**
1092  * gdata_entry_add_link:
1093  * @self: a #GDataEntry
1094  * @_link: a #GDataLink to add
1095  *
1096  * Adds @_link to the list of links in the given #GDataEntry and increments its reference count.
1097  *
1098  * Duplicate links will not be added to the list.
1099  */
1100 void
gdata_entry_add_link(GDataEntry * self,GDataLink * _link)1101 gdata_entry_add_link (GDataEntry *self, GDataLink *_link)
1102 {
1103 	/* TODO: More link API */
1104 	g_return_if_fail (GDATA_IS_ENTRY (self));
1105 	g_return_if_fail (GDATA_IS_LINK (_link));
1106 
1107 	if (g_list_find_custom (self->priv->links, _link, (GCompareFunc) gdata_comparable_compare) == NULL)
1108 		self->priv->links = g_list_prepend (self->priv->links, g_object_ref (_link));
1109 }
1110 
1111 /**
1112  * gdata_entry_remove_link:
1113  * @self: a #GDataEntry
1114  * @_link: a #GDataLink to remove
1115  *
1116  * Removes @_link from the list of links in the given #GDataEntry and decrements its reference count (since the #GDataEntry held a reference to it
1117  * while it was in the list).
1118  *
1119  * Return value: %TRUE if @_link was found in the #GDataEntry and removed, %FALSE if it was not found
1120  *
1121  * Since: 0.10.0
1122  */
1123 gboolean
gdata_entry_remove_link(GDataEntry * self,GDataLink * _link)1124 gdata_entry_remove_link (GDataEntry *self, GDataLink *_link)
1125 {
1126 	GList *i;
1127 
1128 	g_return_val_if_fail (GDATA_IS_ENTRY (self), FALSE);
1129 	g_return_val_if_fail (GDATA_IS_LINK (_link), FALSE);
1130 
1131 	i = g_list_find_custom (self->priv->links, _link, (GCompareFunc) gdata_comparable_compare);
1132 
1133 	if (i == NULL) {
1134 		return FALSE;
1135 	}
1136 
1137 	self->priv->links = g_list_delete_link (self->priv->links, i);
1138 	g_object_unref (_link);
1139 
1140 	return TRUE;
1141 }
1142 
1143 static gint
link_compare_cb(const GDataLink * _link,const gchar * rel)1144 link_compare_cb (const GDataLink *_link, const gchar *rel)
1145 {
1146 	return strcmp (gdata_link_get_relation_type ((GDataLink*) _link), rel);
1147 }
1148 
1149 /**
1150  * gdata_entry_look_up_link:
1151  * @self: a #GDataEntry
1152  * @rel: the value of the <structfield>rel</structfield> attribute of the desired link
1153  *
1154  * Looks up a link by relation type from the list of links in the entry. If the link has one of the standard Atom relation types,
1155  * use one of the defined @rel values, instead of a static string. e.g. %GDATA_LINK_EDIT or %GDATA_LINK_SELF.
1156  *
1157  * In the rare event of requiring a list of links with the same @rel value, use gdata_entry_look_up_links().
1158  *
1159  * Return value: (transfer none): a #GDataLink, or %NULL if one was not found
1160  *
1161  * Since: 0.1.1
1162  */
1163 GDataLink *
gdata_entry_look_up_link(GDataEntry * self,const gchar * rel)1164 gdata_entry_look_up_link (GDataEntry *self, const gchar *rel)
1165 {
1166 	GList *element;
1167 
1168 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1169 	g_return_val_if_fail (rel != NULL, NULL);
1170 
1171 	element = g_list_find_custom (self->priv->links, rel, (GCompareFunc) link_compare_cb);
1172 	if (element == NULL)
1173 		return NULL;
1174 	return GDATA_LINK (element->data);
1175 }
1176 
1177 /**
1178  * gdata_entry_look_up_links:
1179  * @self: a #GDataEntry
1180  * @rel: the value of the <structfield>rel</structfield> attribute of the desired links
1181  *
1182  * Looks up a list of links by relation type from the list of links in the entry. If the links have one of the standard Atom
1183  * relation types, use one of the defined @rel values, instead of a static string. e.g. %GDATA_LINK_EDIT or %GDATA_LINK_SELF.
1184  *
1185  * If you will only use the first link found, consider calling gdata_entry_look_up_link() instead.
1186  *
1187  * Return value: (element-type GData.Link) (transfer container): a #GList of #GDataLinks, or %NULL if none were found; free the list with
1188  * g_list_free()
1189  *
1190  * Since: 0.4.0
1191  */
1192 GList *
gdata_entry_look_up_links(GDataEntry * self,const gchar * rel)1193 gdata_entry_look_up_links (GDataEntry *self, const gchar *rel)
1194 {
1195 	GList *i, *results = NULL;
1196 
1197 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1198 	g_return_val_if_fail (rel != NULL, NULL);
1199 
1200 	for (i = self->priv->links; i != NULL; i = i->next) {
1201 		const gchar *relation_type = gdata_link_get_relation_type (((GDataLink*) i->data));
1202 		if (strcmp (relation_type, rel) == 0)
1203 			results = g_list_prepend (results, i->data);
1204 	}
1205 
1206 	return g_list_reverse (results);
1207 }
1208 
1209 /**
1210  * gdata_entry_add_author:
1211  * @self: a #GDataEntry
1212  * @author: a #GDataAuthor to add
1213  *
1214  * Adds @author to the list of authors in the given #GDataEntry and increments its reference count.
1215  *
1216  * Duplicate authors will not be added to the list.
1217  */
1218 void
gdata_entry_add_author(GDataEntry * self,GDataAuthor * author)1219 gdata_entry_add_author (GDataEntry *self, GDataAuthor *author)
1220 {
1221 	/* TODO: More author API */
1222 	g_return_if_fail (GDATA_IS_ENTRY (self));
1223 	g_return_if_fail (GDATA_IS_AUTHOR (author));
1224 
1225 	if (g_list_find_custom (self->priv->authors, author, (GCompareFunc) gdata_comparable_compare) == NULL)
1226 		self->priv->authors = g_list_prepend (self->priv->authors, g_object_ref (author));
1227 }
1228 
1229 /**
1230  * gdata_entry_is_inserted:
1231  * @self: a #GDataEntry
1232  *
1233  * Returns whether the entry is marked as having been inserted on (uploaded to) the server already.
1234  *
1235  * Return value: %TRUE if the entry has been inserted already, %FALSE otherwise
1236  */
1237 gboolean
gdata_entry_is_inserted(GDataEntry * self)1238 gdata_entry_is_inserted (GDataEntry *self)
1239 {
1240 	g_return_val_if_fail (GDATA_IS_ENTRY (self), FALSE);
1241 
1242 	if (self->priv->id != NULL || self->priv->updated != -1)
1243 		return TRUE;
1244 	return FALSE;
1245 }
1246 
1247 /**
1248  * gdata_entry_get_rights:
1249  * @self: a #GDataEntry
1250  *
1251  * Returns the rights pertaining to the entry, or %NULL if not set.
1252  *
1253  * Return value: the entry's rights information
1254  *
1255  * Since: 0.5.0
1256  */
1257 const gchar *
gdata_entry_get_rights(GDataEntry * self)1258 gdata_entry_get_rights (GDataEntry *self)
1259 {
1260 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
1261 	return self->priv->rights;
1262 }
1263 
1264 /**
1265  * gdata_entry_set_rights:
1266  * @self: a #GDataEntry
1267  * @rights: (allow-none): the new rights, or %NULL
1268  *
1269  * Sets the rights for this entry.
1270  *
1271  * Since: 0.5.0
1272  */
1273 void
gdata_entry_set_rights(GDataEntry * self,const gchar * rights)1274 gdata_entry_set_rights (GDataEntry *self, const gchar *rights)
1275 {
1276 	g_return_if_fail (GDATA_IS_ENTRY (self));
1277 
1278 	g_free (self->priv->rights);
1279 	self->priv->rights = g_strdup (rights);
1280 	g_object_notify (G_OBJECT (self), "rights");
1281 }
1282 
1283 /*
1284  * _gdata_entry_set_batch_data:
1285  * @self: a #GDataEntry
1286  * @id: the batch operation ID
1287  * @type: the type of batch operation being performed on the #GDataEntry
1288  *
1289  * Sets the batch operation data needed when outputting the XML for a #GDataEntry to be put into a batch operation feed.
1290  *
1291  * Since: 0.6.0
1292  */
1293 void
_gdata_entry_set_batch_data(GDataEntry * self,guint id,GDataBatchOperationType type)1294 _gdata_entry_set_batch_data (GDataEntry *self, guint id, GDataBatchOperationType type)
1295 {
1296 	g_return_if_fail (GDATA_IS_ENTRY (self));
1297 
1298 	self->priv->batch_id = id;
1299 	self->priv->batch_operation_type = type;
1300 }
1301