/* * Copyright (C) 2012 Intel Corporation. * * Authors: Jens Georg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:gupnp-cds-last-change-parser * @short_description: LastChange parser for the format used in * CDS:3 * * #GUPnPCDSLastChangeParser parses XML strings from * CDS's LastChange state variable. * */ #include "xml-util.h" #include "gupnp-cds-last-change-parser.h" /** * GUPnPCDSLastChangeEntry: * * Opaque struct which contains information about the event. **/ struct _GUPnPCDSLastChangeEntry { int ref_count; GUPnPCDSLastChangeEvent event; char *object_id; char *parent_id; char *class; guint32 update_id; gboolean is_subtree_update; }; G_DEFINE_TYPE (GUPnPCDSLastChangeParser, gupnp_cds_last_change_parser, G_TYPE_OBJECT) G_DEFINE_BOXED_TYPE (GUPnPCDSLastChangeEntry, gupnp_cds_last_change_entry, gupnp_cds_last_change_entry_ref, gupnp_cds_last_change_entry_unref); static void gupnp_cds_last_change_parser_init (G_GNUC_UNUSED GUPnPCDSLastChangeParser *parser) { } static void gupnp_cds_last_change_parser_class_init (G_GNUC_UNUSED GUPnPCDSLastChangeParserClass *klass) { } /** * gupnp_cds_last_change_parser_new: * * Create a new #GUPnPCDSLastChangeParser. * * This parser is able to parse LastChange as defined in the * ContentDirectory:3 specification. * * Returns:(transfer full): A new instance of #GUPnPCDSLastChangeParser. **/ GUPnPCDSLastChangeParser * gupnp_cds_last_change_parser_new (void) { return g_object_new (GUPNP_TYPE_CDS_LAST_CHANGE_PARSER, NULL); } /** * gupnp_cds_last_change_parser_parse: * @parser: #GUPnPCDSLastChangeParser * @last_change: XML string to parse * @error: Return value for parser error or %NULL to ingore * * Parse a LastChange XML document in the flavor defined by the * ContentDirectory:3 specification. * * Returns: (element-type GUPnPCDSLastChangeEntry)(transfer full): * List of #GUPnPCDSLastChangeEntrys **/ GList * gupnp_cds_last_change_parser_parse (GUPnPCDSLastChangeParser *parser, const char *last_change, GError **error) { xmlDoc *doc; xmlNode *state_event, *it; GList *result = NULL; GUPnPCDSLastChangeEntry *entry; g_return_val_if_fail (GUPNP_IS_CDS_LAST_CHANGE_PARSER (parser), NULL); doc = xmlParseDoc ((const xmlChar *) last_change); if (doc == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, "Could not parse LastChange XML"); goto out; } state_event = xml_util_get_element ((xmlNode *) doc, "StateEvent", NULL); if (state_event == NULL) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, "Missing StateEvent node"); goto out; } for (it = state_event->children; it != NULL; it = it->next) { if (it->type == XML_TEXT_NODE) continue; else if (g_ascii_strcasecmp ((const char *) it->name, "objAdd") == 0) { const char *tmp; entry = g_slice_new0 (GUPnPCDSLastChangeEntry); entry->ref_count = 1; entry->event = GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_ADDED; tmp = xml_util_get_attribute_content (it, "objID"); entry->object_id = g_strdup (tmp); tmp = xml_util_get_attribute_content (it, "objParentID"); entry->parent_id = g_strdup (tmp); tmp = xml_util_get_attribute_content (it, "objClass"); entry->class = g_strdup (tmp); entry->update_id = (guint32) xml_util_get_uint_attribute (it, "updateID", 0); entry->is_subtree_update = xml_util_get_boolean_attribute (it, "stUpdate"); } else if (g_ascii_strcasecmp ((const char *) it->name, "objMod") == 0) { const char *tmp; entry = g_slice_new0 (GUPnPCDSLastChangeEntry); entry->ref_count = 1; entry->event = GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_MODIFIED; tmp = xml_util_get_attribute_content (it, "objID"); entry->object_id = g_strdup (tmp); entry->update_id = (guint32) xml_util_get_uint_attribute (it, "updateID", 0); entry->is_subtree_update = xml_util_get_boolean_attribute (it, "stUpdate"); } else if (g_ascii_strcasecmp ((const char *) it->name, "objDel") == 0) { const char *tmp; entry = g_slice_new0 (GUPnPCDSLastChangeEntry); entry->ref_count = 1; entry->event = GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_REMOVED; tmp = xml_util_get_attribute_content (it, "objID"); entry->object_id = g_strdup (tmp); entry->update_id = (guint32) xml_util_get_uint_attribute (it, "updateID", 0); entry->is_subtree_update = xml_util_get_boolean_attribute (it, "stUpdate"); } else if (g_ascii_strcasecmp ((const char *) it->name, "stDone") == 0) { const char *tmp; entry = g_slice_new0 (GUPnPCDSLastChangeEntry); entry->ref_count = 1; entry->event = GUPNP_CDS_LAST_CHANGE_EVENT_ST_DONE; tmp = xml_util_get_attribute_content (it, "objID"); entry->object_id = g_strdup (tmp); entry->update_id = (guint32) xml_util_get_uint_attribute (it, "updateID", 0); } else { g_warning ("Skipping invalid LastChange entry: %s", (const char *) it->name); continue; } result = g_list_prepend (result, entry); } result = g_list_reverse (result); out: if (doc != NULL) { xmlFreeDoc (doc); } return result; } /** * gupnp_cds_last_change_entry_ref: * @entry: A #GUPnPCDSLastChangeEntry * * Increase reference count of a #GUPnPCDSLastChangeEntry. * * Returns:(transfer full): The object passed in @entry. **/ GUPnPCDSLastChangeEntry * gupnp_cds_last_change_entry_ref (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, NULL); g_return_val_if_fail (entry->ref_count > 0, NULL); g_atomic_int_inc (&entry->ref_count); return entry; } /** * gupnp_cds_last_change_entry_unref: * @entry: A #GUPnPCDSLastChangeEntry * * Decrease reference count of a #GUPnPCDSLastChangeEntry. If the reference * count drops to 0, @entry is freed. **/ void gupnp_cds_last_change_entry_unref (GUPnPCDSLastChangeEntry *entry) { g_return_if_fail (entry != NULL); g_return_if_fail (entry->ref_count > 0); if (g_atomic_int_dec_and_test (&entry->ref_count)) { g_free (entry->class); g_free (entry->object_id); g_free (entry->parent_id); g_slice_free (GUPnPCDSLastChangeEntry, entry); } } /** * gupnp_cds_last_change_entry_get_event: * @entry: A #GUPnPCDSLastChangeEntry * * Get the type of the last change entry as defined in * #GUPnPCDSLastChangeEvent. * * Returns: An event from the #GUPnPCDSLastChangeEvent or * %GUPNP_CDS_LAST_CHANGE_EVENT_INVALID if the entry is not valid. **/ GUPnPCDSLastChangeEvent gupnp_cds_last_change_entry_get_event (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, GUPNP_CDS_LAST_CHANGE_EVENT_INVALID); return entry->event; } /** * gupnp_cds_last_change_entry_get_object_id: * @entry: A #GUPnPCDSLastChangeEntry * * Get the ID of the object in this change entry. * * Returns: (transfer none): The id of the object of this entry. **/ const char * gupnp_cds_last_change_entry_get_object_id (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, NULL); return entry->object_id; } /** * gupnp_cds_last_change_entry_get_parent_id: * @entry: A #GUPnPCDSLastChangeEntry * * Get the parent object id of the object in this change entry. This is only * valid if gupnp_cds_last_change_entry_get_event() returns * %GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_ADDED. * * Returns: (transfer none): The id of the object's parent of this entry. **/ const char * gupnp_cds_last_change_entry_get_parent_id (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, NULL); return entry->parent_id; } /** * gupnp_cds_last_change_entry_get_class: * @entry: A #GUPnPCDSLastChangeEntry * * Get the class of the object in this change entry. This is only * valid if gupnp_cds_last_change_entry_get_event() returns * %GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_ADDED. * * Returns: (transfer none): The upnp class of the object of this entry. **/ const char * gupnp_cds_last_change_entry_get_class (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, NULL); return entry->class; } /** * gupnp_cds_last_change_entry_is_subtree_update: * @entry: A #GUPnPCDSLastChangeEntry * * Returns whether this entry is part of a subtree update. * * Returns: %TRUE, if the entry is part of a subtree update, %FALSE otherwise. **/ gboolean gupnp_cds_last_change_entry_is_subtree_update (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, FALSE); return entry->is_subtree_update; } /** * gupnp_cds_last_change_entry_get_update_id: * @entry: A #GUPnPCDSLastChangeEntry * * Get the update id of the last change entry. * * Returns: update id of the entry or 0 if the entry is not valid. **/ guint32 gupnp_cds_last_change_entry_get_update_id (GUPnPCDSLastChangeEntry *entry) { g_return_val_if_fail (entry != NULL, 0); return entry->update_id; }