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