/* * Copyright (C) 2002 Jorn Baayen * Copyright (C) 2003 Marco Pesenti Gritti * Copyright (C) 2003 Christian Persch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ #include "config.h" #include "ephy-node-db.h" #include "ephy-file-helpers.h" #include "ephy-debug.h" #include #include #include #include #include /* FIXME I want to find a better way to deal with "root" nodes */ #define RESERVED_IDS 30 enum { PROP_0, PROP_NAME, PROP_IMMUTABLE, }; #define EPHY_NODE_DB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_NODE_DB, EphyNodeDbPrivate)) struct _EphyNodeDbPrivate { char *name; gboolean immutable; guint id_factory; GPtrArray *id_to_node; }; static GObjectClass *parent_class = NULL; static void ephy_node_db_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EphyNodeDb *db = EPHY_NODE_DB (object); switch (prop_id) { case PROP_NAME: g_value_set_string (value, db->priv->name); break; case PROP_IMMUTABLE: g_value_set_boolean (value, ephy_node_db_is_immutable (db)); break; } } static void ephy_node_db_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EphyNodeDb *db = EPHY_NODE_DB (object); switch (prop_id) { case PROP_NAME: db->priv->name = g_value_dup_string (value); break; case PROP_IMMUTABLE: ephy_node_db_set_immutable (db, g_value_get_boolean (value)); break; } } static void ephy_node_db_init (EphyNodeDb *db) { db->priv = EPHY_NODE_DB_GET_PRIVATE (db); /* id to node */ db->priv->id_to_node = g_ptr_array_new (); /* id factory */ db->priv->id_factory = RESERVED_IDS; } static void ephy_node_db_finalize (GObject *object) { EphyNodeDb *db = EPHY_NODE_DB (object); g_ptr_array_free (db->priv->id_to_node, TRUE); g_free (db->priv->name); G_OBJECT_CLASS (parent_class)->finalize (object); } /** * ephy_node_db_new: * @name: the name of the new #EphyNodeDb * * Creates and returns a new #EphyNodeDb, named @name. * * Return value: the new #EphyNodeDb **/ EphyNodeDb * ephy_node_db_new (const char *name) { EphyNodeDb *db; db = EPHY_NODE_DB (g_object_new (EPHY_TYPE_NODE_DB, "name", name, NULL)); g_return_val_if_fail (db->priv != NULL, NULL); return db; } static inline EphyNode * node_from_id_real (EphyNodeDb *db, guint id) { EphyNode *ret = NULL; if (id < db->priv->id_to_node->len) ret = g_ptr_array_index (db->priv->id_to_node, id); return ret; } /** * ephy_node_db_get_name: * @db: an #EphyNodeDb * * Return value: the name of @db **/ const char * ephy_node_db_get_name (EphyNodeDb *db) { return db->priv->name; } /** * ephy_node_db_is_immutable: * @db: an #EphyNodeDb * * Return value: %TRUE if @db is immutable **/ gboolean ephy_node_db_is_immutable (EphyNodeDb *db) { return db->priv->immutable; } /** * ephy_node_db_set_immutable: * @db: an #EphyNodeDb * @immutable: %TRUE to make @db immutable * * If @immutable is %TRUE, sets @db immutable (read-only). Otherwise, sets @db * to be read-write. **/ void ephy_node_db_set_immutable (EphyNodeDb *db, gboolean immutable) { db->priv->immutable = immutable; g_object_notify (G_OBJECT (db), "immutable"); } /** * ephy_node_db_get_node_from_id: * @db: an #EphyNodeDb * @id: an id specifying an #EphyNode in @db * * Returns the #EphyNode with id @id from @db, or %NULL if no such id exists. * * Return value: an #EphyNode **/ EphyNode * ephy_node_db_get_node_from_id (EphyNodeDb *db, guint id) { EphyNode *ret = NULL; ret = node_from_id_real (db, id); return ret; } guint _ephy_node_db_new_id (EphyNodeDb *db) { guint ret; while (node_from_id_real (db, db->priv->id_factory) != NULL) { db->priv->id_factory++; } ret = db->priv->id_factory; return ret; } void _ephy_node_db_add_id (EphyNodeDb *db, guint id, EphyNode *node) { /* resize array if needed */ if (id >= db->priv->id_to_node->len) g_ptr_array_set_size (db->priv->id_to_node, id + 1); g_ptr_array_index (db->priv->id_to_node, id) = node; } void _ephy_node_db_remove_id (EphyNodeDb *db, guint id) { g_ptr_array_index (db->priv->id_to_node, id) = NULL; /* reset id factory so we use the freed node id */ db->priv->id_factory = RESERVED_IDS; } /** * ephy_node_db_load_from_file: * @db: a new #EphyNodeDb * @xml_file: the filename from which @db will be populated * @xml_root: the root element in @xml_file * @xml_version: the required version attribute in the @xml_root * * Populates @db with data from @xml_file. The node database will be populated * with everything inside of the @xml_root tag from @xml_file. If @xml_version * is different from the version attribute of the @xml_root element, this * function will fail. * * The data will most probably have been stored using * ephy_node_db_write_to_xml_safe(). * * Return value: %TRUE if successful **/ gboolean ephy_node_db_load_from_file (EphyNodeDb *db, const char *xml_file, const xmlChar *xml_root, const xmlChar *xml_version) { xmlTextReaderPtr reader; gboolean success = TRUE; gboolean was_immutable; int ret; LOG ("ephy_node_db_load_from_file %s", xml_file); START_PROFILER ("loading node db") if (g_file_test (xml_file, G_FILE_TEST_EXISTS) == FALSE) { return FALSE; } reader = xmlNewTextReaderFilename (xml_file); if (reader == NULL) { return FALSE; } was_immutable = db->priv->immutable; db->priv->immutable = FALSE; ret = xmlTextReaderRead (reader); while (ret == 1) { const xmlChar *name; xmlReaderTypes type; gboolean skip = FALSE; name = xmlTextReaderConstName (reader); type = xmlTextReaderNodeType (reader); if (xmlStrEqual (name, (const xmlChar *)"node") && type == XML_READER_TYPE_ELEMENT) { xmlNodePtr subtree; /* grow the subtree and load the node from it */ subtree = xmlTextReaderExpand (reader); if (subtree != NULL) { ephy_node_new_from_xml (db, subtree); } skip = TRUE; } else if (xmlStrEqual (name, xml_root) && type == XML_READER_TYPE_ELEMENT) { xmlChar *version; /* check version info */ version = xmlTextReaderGetAttribute (reader, (const xmlChar *)"version"); if (xmlStrEqual (version, xml_version) == FALSE) { success = FALSE; xmlFree (version); break; } xmlFree (version); } /* next one, please */ ret = skip ? xmlTextReaderNext (reader) : xmlTextReaderRead (reader); } xmlFreeTextReader (reader); db->priv->immutable = was_immutable; STOP_PROFILER ("loading node db") return (success && ret == 0); } static int ephy_node_db_write_to_xml_valist (EphyNodeDb *db, const xmlChar *filename, const xmlChar *root, const xmlChar *version, const xmlChar *comment, EphyNode *first_node, va_list argptr) { xmlTextWriterPtr writer; EphyNode *node; int ret; LOG ("Saving node db to %s", filename); START_PROFILER ("Saving node db") /* FIXME: do we want to turn compression on ? */ writer = xmlNewTextWriterFilename ((const char *)filename, 0); if (writer == NULL) { return -1; } ret = xmlTextWriterSetIndent (writer, 1); if (ret < 0) goto out; ret = xmlTextWriterSetIndentString (writer, (const xmlChar *)" "); if (ret < 0) goto out; ret = xmlTextWriterStartDocument (writer, "1.0", NULL, NULL); if (ret < 0) goto out; ret = xmlTextWriterStartElement (writer, root); if (ret < 0) goto out; ret = xmlTextWriterWriteAttribute (writer, (const xmlChar *)"version", version); if (ret < 0) goto out; if (comment != NULL) { ret = xmlTextWriterWriteComment (writer, comment); if (ret < 0) goto out; } node = first_node; while (node != NULL) { GPtrArray *children; EphyNodeFilterFunc filter; gpointer user_data; int i; filter = va_arg (argptr, EphyNodeFilterFunc); user_data = va_arg (argptr, gpointer); children = ephy_node_get_children (node); for (i = 0; i < children->len; i++) { EphyNode *kid; kid = g_ptr_array_index (children, i); if (!filter || filter (kid, user_data)) { ret = ephy_node_write_to_xml (kid, writer); if (ret < 0) break; } } if (ret < 0) break; node = va_arg (argptr, EphyNode *); } if (ret < 0) goto out; ret = xmlTextWriterEndElement (writer); /* root */ if (ret < 0) goto out; ret = xmlTextWriterEndDocument (writer); if (ret < 0) goto out; out: xmlFreeTextWriter (writer); STOP_PROFILER ("Saving node db") return ret >= 0 ? 0 : -1; } /** * ephy_node_db_write_to_xml_safe: * @db: an #EphyNodeDb * @filename: the XML file in which @db's data will be stored * @root: the desired root element in @filename * @version: the version attribute to the @root element * @comment: a comment to place directly inside the @root element of @filename * @node: The first node of data to write * @Varargs: number of exceptions, list of their #EphyNodes, and more such * sequences, followed by %NULL * * Writes @db's data to an XML file for storage. The data can be retrieved in * the future using ephy_node_db_load_from_file(). * * The function arguments are straightforward until @node, at which point some * explanation is necessary. * * The variable argument list starts at @node, which is an #EphyNode containing * data to write to @filename. The next argument is an integer specifying the * number of exception nodes. After this integer, that number of * #EphyNode arguments should be given. Each of these exception * nodes determines which data out of @node will not be * written to @filename. * * To insert all of an #EphyNode's contents without exception, simply give the * integer %0 after @node. * * The remainder of this function's arguments will be groups of such #EphyNode - * integer - (list of #EphyNodes). Finally, the last argument must be * %NULL. * * Return value: %0 on success or a negative number on failure **/ int ephy_node_db_write_to_xml_safe (EphyNodeDb *db, const xmlChar *filename, const xmlChar *root, const xmlChar *version, const xmlChar *comment, EphyNode *node, ...) { va_list argptr; int ret = 0; char *tmp_file; tmp_file = g_strconcat ((const gchar *)filename, ".tmp", NULL); va_start (argptr, node); ret = ephy_node_db_write_to_xml_valist (db, (const xmlChar *)tmp_file, root, version, comment, node, argptr); va_end (argptr); if (ret < 0) { g_warning ("Failed to write XML data to %s", tmp_file); goto failed; } if (ephy_file_switch_temp_file ((const char *)filename, tmp_file) == FALSE) { ret = -1; } failed: g_free (tmp_file); return ret; } static void ephy_node_db_class_init (EphyNodeDbClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = ephy_node_db_finalize; object_class->set_property = ephy_node_db_set_property; object_class->get_property = ephy_node_db_get_property; g_object_class_install_property (object_class, PROP_NAME, g_param_spec_string ("name", "Name", "Name", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_IMMUTABLE, g_param_spec_boolean ("immutable", "Immutable", "Immutable", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_type_class_add_private (object_class, sizeof (EphyNodeDbPrivate)); } GType ephy_node_db_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { const GTypeInfo our_info = { sizeof (EphyNodeDbClass), NULL, NULL, (GClassInitFunc) ephy_node_db_class_init, NULL, NULL, sizeof (EphyNodeDb), 0, (GInstanceInitFunc) ephy_node_db_init }; type = g_type_register_static (G_TYPE_OBJECT, "EphyNodeDb", &our_info, 0); } return type; }