Imported Upstream version 1.1.4 upstream/1.1.4
authorAnas Nashif <anas.nashif@intel.com>
Tue, 19 Feb 2013 17:21:13 +0000 (09:21 -0800)
committerAnas Nashif <anas.nashif@intel.com>
Tue, 19 Feb 2013 17:21:13 +0000 (09:21 -0800)
16 files changed:
AUTHORS [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README [new file with mode: 0644]
db.c [new file with mode: 0644]
db.h [new file with mode: 0644]
package.c [new file with mode: 0644]
package.h [new file with mode: 0644]
setup.py [new file with mode: 0644]
sqlitecache.c [new file with mode: 0644]
sqlitecachec.py [new file with mode: 0644]
xml-parser.c [new file with mode: 0644]
xml-parser.h [new file with mode: 0644]
yum-metadata-parser.spec [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..3ecc168
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+James Bowes 
+Florian Festi
+Tambet Ingo  <tambet@ximian.com>
+Jeremy Katz 
+Paul Nasrat
+Seth Vidal
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..cd7a62c
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,98 @@
+2010-01-07  Seth Vidal <skvidal@fedoraproject.org>
+
+       * setup.py, yum-metadata-parser.spec: mark as 1.1.3
+
+2009-11-02  James Antill <james@and.org>
+
+       * db.c: Add an index on files.pkgKey ... needed for .simpleFiles()
+       to not suck
+
+2009-08-18  Mike Bonnet <mikeb@redhat.com>
+
+       * sqlitecachec.py: configure sqlite to return utf-8-encoded strs
+       instead of unicode objects  sqlite by default returns all text as
+       unicode objects, and this causes a number of problems when merging
+       repos which contain utf-8 characters in Provides or Requires (as the
+       current F11/F12 repos do).  For a testcase, try merging 2 F12 repos,
+       and you should see it fail with a UnicodeDecodeError in
+       packages.py:_dump_pco().  This patch instructs sqlite to return all
+       text as utf-8-encoded strs, which avoids these encoding issues.
+
+2008-10-14  James Antill <james@and.org>
+
+       * db.c: Turn off .sqlite updating from new .xml data, bug 465898
+
+2008-09-10  Seth Vidal <skvidal@fedoraproject.org>
+
+       * db.c, db.h, sqlitecache.c: commit patches from Ville Skyttä to
+       make indexes after the data has been inserted. Closed rh bug 461403
+
+
+2008-09-10  Seth Vidal <skvidal@fedoraproject.org>
+
+       * sqlitecache.c: apply patch to improve error messages from Ville
+       Skyttä from rh bug 461405
+
+2008-01-25  Seth Vidal <skvidal@fedoraproject.org>
+
+       * sqlitecachec.py: apply patch from Panu Matilainen to setup the db
+       for exclusive lock write access.  closes rh bug 353171
+
+2007-11-27  Paul Nasrat <pauln@truemesh.com>
+
+       * sqlitecache.c: Fix segmentation fault experienced with a malformed
+       primary.xml
+
+2007-08-29  Seth Vidal <skvidal@fedoraproject.org>
+
+       * yum-metadata-parser.spec: remove %dist which doesn't really belong
+       anyway
+
+2007-08-24  Seth Vidal <skvidal@fedoraproject.org>
+
+       * ChangeLog: changelog merge
+
+2007-08-24  Seth Vidal <skvidal@fedoraproject.org>
+
+       * setup.py, yum-metadata-parser.spec: bump version number
+
+2007-08-24  Seth Vidal <skvidal@fedoraproject.org>
+
+       * db.c: commit Florian's patches to create more indexes in the
+       sqlite files made by yum-metadata-parser
+
+2007-07-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Fix segfault in the xml parser
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Use a common sax_error function
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Use a common sax_characters function
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Use SAXContext for other
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Use SAXContext for filelists
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Extract out a common set of SAXContext members for
+       the three file types, and  use them with primary.
+
+2007-06-03  James Bowes <jbowes@redhat.com>
+
+       * xml-parser.c: Use a common sax warning callback for the three file
+       types.
+
+2007-05-30  James Bowes <jbowes@redhat.com>
+
+       * Get SAX error callbacks for filelists and other to use the right
+       context type.
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..ee18a53
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,15 @@
+AUTHORS
+ChangeLog
+MANIFEST
+MANIFEST.in
+README
+db.c
+db.h
+package.c
+package.h
+setup.py
+sqlitecache.c
+sqlitecachec.py
+xml-parser.c
+xml-parser.h
+yum-metadata-parser.spec
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..0c54a80
--- /dev/null
@@ -0,0 +1,5 @@
+include README ChangeLog AUTHORS
+include MANIFEST.in MANIFEST
+include *.c *.h
+include *.py
+include *.spec
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..3c8a8d6
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: yum-metadata-parser
+Version: 1.1.4
+Summary: A fast YUM meta-data parser
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..a610bc2
--- /dev/null
+++ b/README
@@ -0,0 +1,19 @@
+YUM metadata parser written in C.
+
+* Why? 
+The biggest complaint people have with YUM is often the performance of parsing
+the metadata. This implementation should be ~10 times faster, parsing the
+primary.xml file under 1 second usually, filelists.xml under 3 seconds and
+other.xml under 4 seconds. It uses a lot less memory as well, some testings
+I have done show it uses ~4mb instead of 40mb standard YUM uses.
+
+* How?
+Should be really easy:
+python setup.py build
+sudo python setup.py install --prefix=/usr
+
+(Assuming you python prefix is /usr).
+
+The next time you use yum, it regenerates the sqlitecache because the database
+schema is slightly different.
+
diff --git a/db.c b/db.c
new file mode 100644 (file)
index 0000000..4d49595
--- /dev/null
+++ b/db.c
@@ -0,0 +1,978 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include "db.h"
+
+/*  We have a lot of code so we can "quickly" update the .sqlite file using
+ * the old .sqlite data and the new .xml data. However it seems to have weird
+ * edge cases where it doesn't work, rhbz 465898 etc. ... so we turn it off. */
+#define YMP_CONFIG_UPDATE_DB 0
+
+GQuark
+yum_db_error_quark (void)
+{
+    static GQuark quark;
+
+    if (!quark)
+        quark = g_quark_from_static_string ("yum_db_error");
+
+    return quark;
+}
+
+#define ENCODED_PACKAGE_FILE_FILES 2048
+#define ENCODED_PACKAGE_FILE_TYPES 60
+
+typedef struct {
+    GString *files;
+    GString *types;
+} EncodedPackageFile;
+
+static EncodedPackageFile *
+encoded_package_file_new (void)
+{
+    EncodedPackageFile *enc;
+
+    enc = g_new0 (EncodedPackageFile, 1);
+    enc->files = g_string_sized_new (ENCODED_PACKAGE_FILE_FILES);
+    enc->types = g_string_sized_new (ENCODED_PACKAGE_FILE_TYPES);
+
+    return enc;
+}
+
+static void
+encoded_package_file_free (EncodedPackageFile *file)
+{
+    g_string_free (file->files, TRUE);
+    g_string_free (file->types, TRUE);
+    g_free (file);
+}
+
+static GHashTable *
+package_files_to_hash (GSList *files)
+{
+    GHashTable *hash;
+    GSList *iter;
+    PackageFile *file;
+    EncodedPackageFile *enc;
+    char *dir;
+    char *name;
+
+    hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                  (GDestroyNotify) g_free,
+                                  (GDestroyNotify) encoded_package_file_free);
+
+    for (iter = files; iter; iter = iter->next) {
+        file = (PackageFile *) iter->data;
+
+        dir = g_path_get_dirname (file->name);
+        name = g_path_get_basename (file->name);
+
+        enc = (EncodedPackageFile *) g_hash_table_lookup (hash, dir);
+        if (!enc) {
+            enc = encoded_package_file_new ();
+            g_hash_table_insert (hash, dir, enc);
+        } else
+            g_free (dir);
+
+        if (enc->files->len)
+            g_string_append_c (enc->files, '/');
+        g_string_append (enc->files, name);
+        g_free (name);
+
+        if (!strcmp (file->type, "dir"))
+            g_string_append_c (enc->types, 'd');
+        else if (!strcmp (file->type, "file"))
+            g_string_append_c (enc->types, 'f');
+        else if (!strcmp (file->type, "ghost"))
+            g_string_append_c (enc->types, 'g');
+    }
+
+    return hash;
+}
+
+char *
+yum_db_filename (const char *prefix)
+{
+    char *filename;
+
+    filename = g_strconcat (prefix, ".sqlite", NULL);
+    return filename;
+}
+
+typedef enum {
+    DB_STATUS_OK,
+    DB_STATUS_VERSION_MISMATCH,
+    DB_STATUS_CHECKSUM_MISMATCH,
+    DB_STATUS_ERROR
+} DBStatus;
+
+static DBStatus
+dbinfo_status (sqlite3 *db, const char *checksum)
+{
+    const char *query;
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    DBStatus status = DB_STATUS_ERROR;
+
+    query = "SELECT dbversion, checksum FROM db_info";
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK)
+        goto cleanup;
+
+    while ((rc = sqlite3_step (handle)) == SQLITE_ROW) {
+        int dbversion;
+        const char *dbchecksum;
+
+        dbversion  = sqlite3_column_int  (handle, 0);
+        dbchecksum = (const char *) sqlite3_column_text (handle, 1);
+
+        if (dbversion != YUM_SQLITE_CACHE_DBVERSION) {
+            g_message ("Warning: cache file is version %d, we need %d, will regenerate",
+                       dbversion, YUM_SQLITE_CACHE_DBVERSION);
+            status = DB_STATUS_VERSION_MISMATCH;
+        } else if (strcmp (checksum, dbchecksum)) {
+            g_message ("sqlite cache needs updating, reading in metadata");
+            status = DB_STATUS_CHECKSUM_MISMATCH;
+        } else
+            status = DB_STATUS_OK;
+
+        break;
+    }
+
+ cleanup:
+    if (handle)
+        sqlite3_finalize (handle);
+
+    return status;
+}
+
+static void
+yum_db_create_dbinfo_table (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql = "CREATE TABLE db_info (dbversion INTEGER, checksum TEXT)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create db_info table: %s",
+                     sqlite3_errmsg (db));
+    }
+}
+
+sqlite3 *
+yum_db_open (const char *path,
+             const char *checksum,
+             CreateTablesFn create_tables,
+             GError **err)
+{
+    int rc;
+    sqlite3 *db = NULL;
+    gboolean db_existed;
+
+    db_existed = g_file_test (path, G_FILE_TEST_EXISTS);
+
+    rc = sqlite3_open (path, &db);
+    if (rc == SQLITE_OK) {
+        if (db_existed) {
+            DBStatus status = dbinfo_status (db, checksum);
+
+            switch (status) {
+            case DB_STATUS_OK:
+                /* Everything is up-to-date */
+                sqlite3_close (db);
+                return NULL;
+                break;
+            case DB_STATUS_CHECKSUM_MISMATCH:
+                if (YMP_CONFIG_UPDATE_DB) {
+                    sqlite3_exec (db, "PRAGMA synchronous = 0", NULL,NULL,NULL);
+                    sqlite3_exec (db, "DELETE FROM db_info", NULL, NULL, NULL);
+                    return db;
+                    break;
+                }
+                /* FALL THROUGH */
+            case DB_STATUS_VERSION_MISMATCH:
+            case DB_STATUS_ERROR:
+                sqlite3_close (db);
+                db = NULL;
+                unlink (path);
+                break;
+            }
+        }
+    } else {
+        /* Let's try to delete it and try again,
+           maybe it's a sqlite3 version mismatch. */
+        sqlite3_close (db);
+        db = NULL;
+        unlink (path);
+    }
+
+    if (!db) {
+        rc = sqlite3_open (path, &db);
+        if (rc != SQLITE_OK) {
+            g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                         "Can not open SQL database: %s",
+                         sqlite3_errmsg (db));
+            goto cleanup;
+        }
+    }
+
+    yum_db_create_dbinfo_table (db, err);
+    if (*err)
+        goto cleanup;
+
+    create_tables (db, err);
+    if (*err)
+        goto cleanup;
+
+    sqlite3_exec (db, "PRAGMA synchronous = 0", NULL, NULL, NULL);
+
+ cleanup:
+    if (*err && db) {
+        sqlite3_close (db);
+        db = NULL;
+    }
+
+    return db;
+}
+
+void
+yum_db_dbinfo_update (sqlite3 *db, const char *checksum, GError **err)
+{
+    int rc;
+    char *sql;
+
+    sql = g_strdup_printf
+        ("INSERT INTO db_info (dbversion, checksum) VALUES (%d, '%s')",
+         YUM_SQLITE_CACHE_DBVERSION, checksum);
+
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK)
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not update dbinfo table: %s",
+                     sqlite3_errmsg (db));
+
+    g_free (sql);
+}
+
+GHashTable *
+yum_db_read_package_ids (sqlite3 *db, GError **err)
+{
+    const char *query;
+    int rc;
+    GHashTable *hash = NULL;
+    sqlite3_stmt *handle = NULL;
+
+    query = "SELECT pkgId, pkgKey FROM packages";
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare SQL clause: %s",
+                     sqlite3_errmsg (db));
+        goto cleanup;
+    }
+
+    hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                  (GDestroyNotify) g_free, NULL);
+
+    while ((rc = sqlite3_step (handle)) == SQLITE_ROW) {
+        char *pkgId;
+        gint pkgKey;
+
+        pkgId  = g_strdup ((char *) sqlite3_column_text  (handle, 0));
+        pkgKey = sqlite3_column_int (handle, 1);
+
+        g_hash_table_insert (hash, pkgId, GINT_TO_POINTER (pkgKey));
+    }
+
+    if (rc != SQLITE_DONE)
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Error reading from SQL: %s",
+                     sqlite3_errmsg (db));
+
+ cleanup:
+    if (handle)
+        sqlite3_finalize (handle);
+
+    return hash;
+}
+
+void
+yum_db_create_primary_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql =
+        "CREATE TABLE packages ("
+        "  pkgKey INTEGER PRIMARY KEY,"
+        "  pkgId TEXT,"
+        "  name TEXT,"
+        "  arch TEXT,"
+        "  version TEXT,"
+        "  epoch TEXT,"
+        "  release TEXT,"
+        "  summary TEXT,"
+        "  description TEXT,"
+        "  url TEXT,"
+        "  time_file INTEGER,"
+        "  time_build INTEGER,"
+        "  rpm_license TEXT,"
+        "  rpm_vendor TEXT,"
+        "  rpm_group TEXT,"
+        "  rpm_buildhost TEXT,"
+        "  rpm_sourcerpm TEXT,"
+        "  rpm_header_start INTEGER,"
+        "  rpm_header_end INTEGER,"
+        "  rpm_packager TEXT,"
+        "  size_package INTEGER,"
+        "  size_installed INTEGER,"
+        "  size_archive INTEGER,"
+        "  location_href TEXT,"
+        "  location_base TEXT,"
+        "  checksum_type TEXT)";
+
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create packages table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TABLE files ("
+        "  name TEXT,"
+        "  type TEXT,"
+        "  pkgKey INTEGER)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create files table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TABLE %s ("
+        "  name TEXT,"
+        "  flags TEXT,"
+        "  epoch TEXT,"
+        "  version TEXT,"
+        "  release TEXT,"
+        "  pkgKey INTEGER %s)";
+
+    const char *deps[] = { "requires", "provides", "conflicts", "obsoletes", NULL };
+    int i;
+
+    for (i = 0; deps[i]; i++) {
+        const char *prereq;
+        char *query;
+
+        if (!strcmp(deps[i], "requires")) {
+            prereq = ", pre BOOLEAN DEFAULT FALSE";
+        } else
+            prereq = "";
+
+        query = g_strdup_printf (sql, deps[i], prereq);
+        rc = sqlite3_exec (db, query, NULL, NULL, NULL);
+        g_free (query);
+
+        if (rc != SQLITE_OK) {
+            g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                         "Can not create %s table: %s",
+                         deps[i], sqlite3_errmsg (db));
+            return;
+        }
+    }
+
+    sql =
+        "CREATE TRIGGER removals AFTER DELETE ON packages"
+        "  BEGIN"
+        "    DELETE FROM files WHERE pkgKey = old.pkgKey;"
+        "    DELETE FROM requires WHERE pkgKey = old.pkgKey;"
+        "    DELETE FROM provides WHERE pkgKey = old.pkgKey;"
+        "    DELETE FROM conflicts WHERE pkgKey = old.pkgKey;"
+        "    DELETE FROM obsoletes WHERE pkgKey = old.pkgKey;"
+        "  END;";
+
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create removals trigger: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+}
+
+void
+yum_db_index_primary_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql = "CREATE INDEX IF NOT EXISTS packagename ON packages (name)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create packagename index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+    
+    sql = "CREATE INDEX IF NOT EXISTS packageId ON packages (pkgId)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create packageId index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql = "CREATE INDEX IF NOT EXISTS filenames ON files (name)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create filenames index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql = "CREATE INDEX IF NOT EXISTS pkgfiles ON files (pkgKey)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create index on files table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    const char *deps[] = { "requires", "provides", "conflicts", "obsoletes", NULL };
+    int i;
+
+    const char *pkgindexsql = "CREATE INDEX IF NOT EXISTS pkg%s on %s (pkgKey)";
+    const char *nameindexsql = "CREATE INDEX IF NOT EXISTS %sname ON %s (name)";
+
+    for (i = 0; deps[i]; i++) {
+        char *query;
+
+        query = g_strdup_printf(pkgindexsql, deps[i], deps[i]);
+        rc = sqlite3_exec (db, query, NULL, NULL, NULL);
+        g_free (query);
+
+        if (rc != SQLITE_OK) {
+            g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                         "Can not create index on %s table: %s",
+                         deps[i], sqlite3_errmsg (db));
+            return;
+        }
+
+        if (i < 2) {
+            query = g_strdup_printf(nameindexsql, deps[i], deps[i]);
+            rc = sqlite3_exec (db, query, NULL, NULL, NULL);
+            if (rc != SQLITE_OK) {
+                g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                             "Can not create %sname index: %s",
+                             deps[i], sqlite3_errmsg (db));
+                return;
+            }
+        }
+    }
+}
+
+sqlite3_stmt *
+yum_db_package_prepare (sqlite3 *db, GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    const char *query;
+
+    query =
+        "INSERT INTO packages ("
+        "  pkgId, name, arch, version, epoch, release, summary, description,"
+        "  url, time_file, time_build, rpm_license, rpm_vendor, rpm_group,"
+        "  rpm_buildhost, rpm_sourcerpm, rpm_header_start, rpm_header_end,"
+        "  rpm_packager, size_package, size_installed, size_archive,"
+        "  location_href, location_base, checksum_type) "
+        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,"
+        "  ?, ?, ?, ?, ?, ?, ?)";
+
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare packages insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+}
+
+void
+yum_db_package_write (sqlite3 *db, sqlite3_stmt *handle, Package *p)
+{
+    int rc;
+
+    sqlite3_bind_text (handle, 1,  p->pkgId, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 2,  p->name, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 3,  p->arch, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 4,  p->version, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 5,  p->epoch, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 6,  p->release, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 7,  p->summary, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 8,  p->description, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 9,  p->url, -1, SQLITE_STATIC);
+    sqlite3_bind_int  (handle, 10, p->time_file);
+    sqlite3_bind_int  (handle, 11, p->time_build);
+    sqlite3_bind_text (handle, 12, p->rpm_license, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 13, p->rpm_vendor, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 14, p->rpm_group, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 15, p->rpm_buildhost, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 16, p->rpm_sourcerpm, -1, SQLITE_STATIC);
+    sqlite3_bind_int  (handle, 17, p->rpm_header_start);
+    sqlite3_bind_int  (handle, 18, p->rpm_header_end);
+    sqlite3_bind_text (handle, 19, p->rpm_packager, -1, SQLITE_STATIC);
+    sqlite3_bind_int  (handle, 20, p->size_package);
+    sqlite3_bind_int  (handle, 21, p->size_installed);
+    sqlite3_bind_int  (handle, 22, p->size_archive);
+    sqlite3_bind_text (handle, 23, p->location_href, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 24, p->location_base, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 25, p->checksum_type, -1, SQLITE_STATIC);
+
+    rc = sqlite3_step (handle);
+    sqlite3_reset (handle);
+
+    if (rc != SQLITE_DONE) {
+        g_critical ("Error adding package to SQL: %s",
+                    sqlite3_errmsg (db));
+    } else
+        p->pkgKey = sqlite3_last_insert_rowid (db);
+}
+
+sqlite3_stmt *
+yum_db_dependency_prepare (sqlite3 *db,
+                           const char *table,
+                           GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    char *query;
+
+    const char *pre_name = "";
+    const char *pre_value = "";
+
+    if (!strcmp (table, "requires")) {
+        pre_name = ", pre";
+        pre_value = ", ?";
+    }
+
+    query = g_strdup_printf
+        ("INSERT INTO %s (name, flags, epoch, version, release, pkgKey%s) "
+         "VALUES (?, ?, ?, ?, ?, ?%s)", table, pre_name, pre_value);
+
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    g_free (query);
+
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare dependency insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+}
+
+void
+yum_db_dependency_write (sqlite3 *db,
+                         sqlite3_stmt *handle,
+                         gint64 pkgKey,
+                         Dependency *dep,
+                         gboolean isRequirement)
+{
+    int rc;
+
+    sqlite3_bind_text (handle, 1, dep->name,    -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 2, dep->flags,   -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 3, dep->epoch,   -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 4, dep->version, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 5, dep->release, -1, SQLITE_STATIC);
+    sqlite3_bind_int  (handle, 6, pkgKey);
+
+    if (isRequirement) {
+        if (dep->pre)
+            sqlite3_bind_text (handle, 7, "TRUE", -1, SQLITE_TRANSIENT);
+        else
+            sqlite3_bind_text (handle, 7, "FALSE", -1, SQLITE_TRANSIENT);
+    }
+
+    rc = sqlite3_step (handle);
+    sqlite3_reset (handle);
+
+    if (rc != SQLITE_DONE)
+        g_critical ("Error adding dependency to SQL: %s",
+                    sqlite3_errmsg (db));
+}
+
+sqlite3_stmt *
+yum_db_file_prepare (sqlite3 *db, GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    const char *query;
+
+    query = "INSERT INTO files (name, type, pkgKey) VALUES (?, ?, ?)";
+
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare file insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+
+}
+
+void
+yum_db_file_write (sqlite3 *db,
+                   sqlite3_stmt *handle,
+                   gint64 pkgKey,
+                   PackageFile *file)
+{
+    int rc;
+
+    sqlite3_bind_text (handle, 1, file->name, -1, SQLITE_STATIC);
+    sqlite3_bind_text (handle, 2, file->type, -1, SQLITE_STATIC);
+    sqlite3_bind_int  (handle, 3, pkgKey);
+
+    rc = sqlite3_step (handle);
+    sqlite3_reset (handle);
+
+    if (rc != SQLITE_DONE)
+        g_critical ("Error adding package file to SQL: %s",
+                    sqlite3_errmsg (db));
+}
+
+void
+yum_db_create_filelist_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql =
+        "CREATE TABLE packages ("
+        "  pkgKey INTEGER PRIMARY KEY,"
+        "  pkgId TEXT)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create packages table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TABLE filelist ("
+        "  pkgKey INTEGER,"
+        "  dirname TEXT,"
+        "  filenames TEXT,"
+        "  filetypes TEXT)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create filelist table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TRIGGER remove_filelist AFTER DELETE ON packages"
+        "  BEGIN"
+        "    DELETE FROM filelist WHERE pkgKey = old.pkgKey;"
+        "  END;";
+
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create remove_filelist trigger: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+}
+
+void
+yum_db_index_filelist_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql = "CREATE INDEX IF NOT EXISTS keyfile ON filelist (pkgKey)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create keyfile index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql = "CREATE INDEX IF NOT EXISTS pkgId ON packages (pkgId)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create pkgId index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql = "CREATE INDEX IF NOT EXISTS dirnames ON filelist (dirname)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create dirnames index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+}
+
+sqlite3_stmt *
+yum_db_package_ids_prepare (sqlite3 *db, GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    const char *query;
+
+    query = "INSERT INTO packages (pkgId) VALUES (?)";
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare package ids insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+}
+
+void
+yum_db_package_ids_write (sqlite3 *db, sqlite3_stmt *handle, Package *p)
+{
+    int rc;
+
+    sqlite3_bind_text (handle, 1,  p->pkgId, -1, SQLITE_STATIC);
+    rc = sqlite3_step (handle);
+    sqlite3_reset (handle);
+
+    if (rc != SQLITE_DONE) {
+        g_critical ("Error adding package to SQL: %s",
+                    sqlite3_errmsg (db));
+    } else
+        p->pkgKey = sqlite3_last_insert_rowid (db);
+}
+
+sqlite3_stmt *
+yum_db_filelists_prepare (sqlite3 *db, GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    const char *query;
+
+    query =
+        "INSERT INTO filelist (pkgKey, dirname, filenames, filetypes) "
+        " VALUES (?, ?, ?, ?)";
+
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare filelist insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+}
+
+typedef struct {
+    sqlite3 *db;
+    sqlite3_stmt *handle;
+    gint64 pkgKey;
+} FileWriteInfo;
+
+static void
+write_file (gpointer key, gpointer value, gpointer user_data)
+{
+    EncodedPackageFile *file = (EncodedPackageFile *) value;
+    FileWriteInfo *info = (FileWriteInfo *) user_data;
+    int rc;
+
+    sqlite3_bind_int  (info->handle, 1, info->pkgKey);
+    sqlite3_bind_text (info->handle, 2, (const char *) key, -1, SQLITE_STATIC);
+    sqlite3_bind_text (info->handle, 3, file->files->str, -1, SQLITE_STATIC);
+    sqlite3_bind_text (info->handle, 4, file->types->str, -1, SQLITE_STATIC);
+
+    rc = sqlite3_step (info->handle);
+    sqlite3_reset (info->handle);
+
+    if (rc != SQLITE_DONE) {
+        g_critical ("Error adding file to SQL: %s",
+                    sqlite3_errmsg (info->db));
+    }
+}
+
+void
+yum_db_filelists_write (sqlite3 *db, sqlite3_stmt *handle, Package *p)
+{
+    GHashTable *hash;
+    FileWriteInfo info;
+
+    info.db = db;
+    info.handle = handle;
+    info.pkgKey = p->pkgKey;
+
+    hash = package_files_to_hash (p->files);
+    g_hash_table_foreach (hash, write_file, &info);
+    g_hash_table_destroy (hash);
+}
+
+void
+yum_db_create_other_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql =
+        "CREATE TABLE packages ("
+        "  pkgKey INTEGER PRIMARY KEY,"
+        "  pkgId TEXT)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create packages table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TABLE changelog ("
+        "  pkgKey INTEGER,"
+        "  author TEXT,"
+        "  date INTEGER,"
+        "  changelog TEXT)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create changelog table: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql =
+        "CREATE TRIGGER remove_changelogs AFTER DELETE ON packages"
+        "  BEGIN"
+        "    DELETE FROM changelog WHERE pkgKey = old.pkgKey;"
+        "  END;";
+
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create remove_changelogs trigger: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+}
+
+void
+yum_db_index_other_tables (sqlite3 *db, GError **err)
+{
+    int rc;
+    const char *sql;
+
+    sql = "CREATE INDEX IF NOT EXISTS keychange ON changelog (pkgKey)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create keychange index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+
+    sql = "CREATE INDEX IF NOT EXISTS pkgId ON packages (pkgId)";
+    rc = sqlite3_exec (db, sql, NULL, NULL, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not create pkgId index: %s",
+                     sqlite3_errmsg (db));
+        return;
+    }
+}
+
+sqlite3_stmt *
+yum_db_changelog_prepare (sqlite3 *db, GError **err)
+{
+    int rc;
+    sqlite3_stmt *handle = NULL;
+    const char *query;
+
+    query =
+        "INSERT INTO changelog (pkgKey, author, date, changelog) "
+        " VALUES (?, ?, ?, ?)";
+
+    rc = sqlite3_prepare (db, query, -1, &handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare changelog insertion: %s",
+                     sqlite3_errmsg (db));
+        sqlite3_finalize (handle);
+        handle = NULL;
+    }
+
+    return handle;
+}
+
+void
+yum_db_changelog_write (sqlite3 *db, sqlite3_stmt *handle, Package *p)
+{
+    GSList *iter;
+    ChangelogEntry *entry;
+    int rc;
+
+    for (iter = p->changelogs; iter; iter = iter->next) {
+        entry = (ChangelogEntry *) iter->data;
+
+        sqlite3_bind_int  (handle, 1, p->pkgKey);
+        sqlite3_bind_text (handle, 2, entry->author, -1, SQLITE_STATIC);
+        sqlite3_bind_int  (handle, 3, entry->date);
+        sqlite3_bind_text (handle, 4, entry->changelog, -1, SQLITE_STATIC);
+
+        rc = sqlite3_step (handle);
+        sqlite3_reset (handle);
+
+        if (rc != SQLITE_DONE) {
+            g_critical ("Error adding changelog to SQL: %s",
+                        sqlite3_errmsg (db));
+        }
+    }
+}
diff --git a/db.h b/db.h
new file mode 100644 (file)
index 0000000..fce455d
--- /dev/null
+++ b/db.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef __YUM_DB_H__
+#define __YUM_DB_H__
+
+#include <glib.h>
+#include <sqlite3.h>
+#include "package.h"
+
+#define YUM_SQLITE_CACHE_DBVERSION 10
+
+#define YUM_DB_ERROR yum_db_error_quark()
+GQuark yum_db_error_quark (void);
+
+typedef void (*CreateTablesFn) (sqlite3 *db, GError **err);
+
+char         *yum_db_filename               (const char *prefix);
+sqlite3      *yum_db_open                   (const char *path,
+                                             const char *checksum,
+                                             CreateTablesFn create_tables,
+                                             GError **err);
+
+void          yum_db_dbinfo_update          (sqlite3 *db,
+                                             const char *checksum,
+                                             GError **err);
+
+GHashTable   *yum_db_read_package_ids       (sqlite3 *db, GError **err);
+
+/* Primary */
+
+void          yum_db_create_primary_tables  (sqlite3 *db, GError **err);
+void          yum_db_index_primary_tables   (sqlite3 *db, GError **err);
+sqlite3_stmt *yum_db_package_prepare        (sqlite3 *db, GError **err);
+void          yum_db_package_write          (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             Package *p);
+
+sqlite3_stmt *yum_db_dependency_prepare     (sqlite3 *db,
+                                             const char *table,
+                                             GError **err);
+void          yum_db_dependency_write       (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             gint64 pkgKey,
+                                             Dependency *dep,
+                                             gboolean isRequirement);
+
+sqlite3_stmt *yum_db_file_prepare           (sqlite3 *db, GError **err);
+void          yum_db_file_write             (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             gint64 pkgKey,
+                                             PackageFile *file);
+
+/* Filelists */
+
+void          yum_db_create_filelist_tables (sqlite3 *db, GError **err);
+void          yum_db_index_filelist_tables  (sqlite3 *db, GError **err);
+sqlite3_stmt *yum_db_package_ids_prepare    (sqlite3 *db, GError **err);
+void          yum_db_package_ids_write      (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             Package *p);
+
+sqlite3_stmt *yum_db_filelists_prepare      (sqlite3 *db, GError **err);
+void          yum_db_filelists_write        (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             Package *p);
+
+/* Other */
+void          yum_db_create_other_tables    (sqlite3 *db, GError **err);
+void          yum_db_index_other_tables     (sqlite3 *db, GError **err);
+sqlite3_stmt *yum_db_changelog_prepare      (sqlite3 *db, GError **err);
+void          yum_db_changelog_write        (sqlite3 *db,
+                                             sqlite3_stmt *handle,
+                                             Package *p);
+
+
+#endif /* __YUM_DB_H__ */
diff --git a/package.c b/package.c
new file mode 100644 (file)
index 0000000..dfd9ed9
--- /dev/null
+++ b/package.c
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include "package.h"
+
+#define PACKAGE_CHUNK_SIZE 2048
+
+Dependency *
+dependency_new (void)
+{
+    Dependency *dep;
+
+    dep = g_new0 (Dependency, 1);
+
+    return dep;
+}
+
+PackageFile *
+package_file_new (void)
+{
+    PackageFile *file;
+
+    file = g_new0 (PackageFile, 1);
+
+    return file;
+}
+
+ChangelogEntry *
+changelog_entry_new (void)
+{
+    ChangelogEntry *entry;
+
+    entry = g_new0 (ChangelogEntry, 1);
+
+    return entry;
+}
+
+Package *
+package_new (void)
+{
+    Package *package;
+
+    package = g_new0 (Package, 1);
+    package->chunk = g_string_chunk_new (PACKAGE_CHUNK_SIZE);
+
+    return package;
+}
+
+void
+package_free (Package *package)
+{
+    g_string_chunk_free (package->chunk);
+
+    if (package->requires) {
+        g_slist_foreach (package->requires, (GFunc) g_free, NULL);
+        g_slist_free (package->requires);
+    }
+
+    if (package->provides) {
+        g_slist_foreach (package->provides, (GFunc) g_free, NULL);
+        g_slist_free (package->provides);
+    }
+
+    if (package->conflicts) {
+        g_slist_foreach (package->conflicts, (GFunc) g_free, NULL);
+        g_slist_free (package->conflicts);
+    }
+
+    if (package->obsoletes) {
+        g_slist_foreach (package->obsoletes, (GFunc) g_free, NULL);
+        g_slist_free (package->obsoletes);
+    }
+
+    if (package->files) {
+        g_slist_foreach (package->files, (GFunc) g_free, NULL);
+        g_slist_free (package->files);
+    }
+
+    if (package->changelogs) {
+        g_slist_foreach (package->changelogs, (GFunc) g_free, NULL);
+        g_slist_free (package->changelogs);
+    }
+
+    g_free (package);
+}
diff --git a/package.h b/package.h
new file mode 100644 (file)
index 0000000..abce671
--- /dev/null
+++ b/package.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef __YUM_PACKAGE_H__
+#define __YUM_PACKAGE_H__
+
+#include <glib.h>
+
+typedef struct {
+    char *name;
+    char *flags;
+    char *epoch;
+    char *version;
+    char *release;
+    gboolean pre;
+} Dependency;
+
+typedef struct {
+    char *type;
+    char *name;
+} PackageFile;
+
+typedef struct {
+    char *author;
+    gint64 date;
+    char *changelog;
+} ChangelogEntry;
+
+typedef struct {
+    gint64 pkgKey;
+    char *pkgId;
+    char *name;
+    char *arch;
+    char *version;
+    char *epoch;
+    char *release;
+    char *summary;
+    char *description;
+    char *url;
+    gint64 time_file;
+    gint64 time_build;
+    char *rpm_license;
+    char *rpm_vendor;
+    char *rpm_group;
+    char *rpm_buildhost;
+    char *rpm_sourcerpm;
+    gint64 rpm_header_start;
+    gint64 rpm_header_end;
+    char *rpm_packager;
+    gint64 size_package;
+    gint64 size_installed;
+    gint64 size_archive;
+    char *location_href;
+    char *location_base;
+    char *checksum_type;
+
+    GSList *requires;
+    GSList *provides;
+    GSList *conflicts;
+    GSList *obsoletes;
+
+    GSList *files;
+    GSList *changelogs;
+
+    GStringChunk *chunk;
+} Package;
+
+typedef void (*PackageFn) (Package *pkg, gpointer data);
+
+Dependency     *dependency_new      (void);
+PackageFile    *package_file_new    (void);
+ChangelogEntry *changelog_entry_new (void);
+Package        *package_new         (void);
+void            package_free        (Package *package);
+
+#endif /* __YUM_PACKAGE_H__ */
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..d0d0429
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+import os, string
+from distutils.core import setup, Extension
+
+pc = os.popen("pkg-config --cflags-only-I glib-2.0 libxml-2.0 sqlite3", "r")
+includes = map(lambda x:x[2:], string.split(pc.readline()))
+pc.close()
+
+pc = os.popen("pkg-config --libs-only-l glib-2.0 libxml-2.0 sqlite3", "r")
+libs = map(lambda x:x[2:], string.split(pc.readline()))
+pc.close()
+
+pc = os.popen("pkg-config --libs-only-L glib-2.0 libxml-2.0 sqlite3", "r")
+libdirs = map(lambda x:x[2:], string.split(pc.readline()))
+pc.close()
+
+module = Extension('_sqlitecache',
+                   include_dirs = includes,
+                   libraries = libs,
+                   library_dirs = libdirs,
+                   sources = ['package.c',
+                              'xml-parser.c',
+                              'db.c',
+                              'sqlitecache.c'])
+
+setup (name = 'yum-metadata-parser',
+       version = '1.1.4',
+       description = 'A fast YUM meta-data parser',
+          py_modules = ['sqlitecachec'],
+       ext_modules = [module])
diff --git a/sqlitecache.c b/sqlitecache.c
new file mode 100644 (file)
index 0000000..3857be7
--- /dev/null
@@ -0,0 +1,633 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <Python.h>
+
+#include "xml-parser.h"
+#include "db.h"
+#include "package.h"
+
+/* Make room for 2500 package ids, 40 bytes + '\0' each */
+#define PACKAGE_IDS_CHUNK 41 * 2500
+
+typedef struct _UpdateInfo UpdateInfo;
+
+typedef void (*InfoInitFn) (UpdateInfo *update_info, sqlite3 *db, GError **err);
+typedef void (*InfoCleanFn) (UpdateInfo *update_info);
+
+typedef void (*XmlParseFn)  (const char *filename,
+                             CountFn count_callback,
+                             PackageFn package_callback,
+                             gpointer user_data,
+                             GError **err);
+
+typedef void (*WriteDbPackageFn) (UpdateInfo *update_info, Package *package);
+
+typedef void (*IndexTablesFn) (sqlite3 *db, GError **err);
+
+struct _UpdateInfo {
+    sqlite3 *db;
+    sqlite3_stmt *remove_handle;
+    guint32 count_from_md;
+    guint32 packages_seen;
+    guint32 add_count;
+    guint32 del_count;
+    GHashTable *current_packages;
+    GHashTable *all_packages;
+    GStringChunk *package_ids_chunk;
+    GTimer *timer;
+    gpointer python_callback;
+    
+    InfoInitFn info_init;
+    InfoCleanFn info_clean;
+    CreateTablesFn create_tables;
+    WriteDbPackageFn write_package;
+    XmlParseFn xml_parse;
+    IndexTablesFn index_tables;
+
+    gpointer user_data;
+};
+
+static void
+update_info_init (UpdateInfo *info, GError **err)
+{
+    const char *sql;
+    int rc;
+
+    sql = "DELETE FROM packages WHERE pkgKey = ?";
+    rc = sqlite3_prepare (info->db, sql, -1, &info->remove_handle, NULL);
+    if (rc != SQLITE_OK) {
+        g_set_error (err, YUM_DB_ERROR, YUM_DB_ERROR,
+                     "Can not prepare package removal: %s",
+                     sqlite3_errmsg (info->db));
+        sqlite3_finalize (info->remove_handle);
+        return;
+    }
+
+    info->count_from_md = 0;
+    info->packages_seen = 0;
+    info->add_count = 0;
+    info->del_count = 0;
+    info->all_packages = g_hash_table_new (g_str_hash, g_str_equal);
+    info->package_ids_chunk = g_string_chunk_new (PACKAGE_IDS_CHUNK);
+    info->timer = g_timer_new ();
+    g_timer_start (info->timer);
+    info->current_packages = yum_db_read_package_ids (info->db, err);
+}
+
+static void
+remove_entry (gpointer key, gpointer value, gpointer user_data)
+{
+    UpdateInfo *info = (UpdateInfo *) user_data;
+
+    if (g_hash_table_lookup (info->all_packages, key) == NULL) {
+        int rc;
+
+        sqlite3_bind_int (info->remove_handle, 1, GPOINTER_TO_INT (value));
+        rc = sqlite3_step (info->remove_handle);
+        sqlite3_reset (info->remove_handle);
+
+        if (rc != SQLITE_DONE)
+            g_warning ("Error removing package from SQL: %s",
+                       sqlite3_errmsg (info->db));
+
+        info->del_count++;
+    }
+}
+
+static void
+update_info_remove_old_entries (UpdateInfo *info)
+{
+    g_hash_table_foreach (info->current_packages, remove_entry, info);
+}
+
+static void
+count_cb (guint32 count, gpointer user_data)
+{
+    UpdateInfo *info = (UpdateInfo *) user_data;
+
+    info->count_from_md = count;
+}
+
+static void
+update_info_done (UpdateInfo *info, GError **err)
+{
+    if (info->remove_handle)
+        sqlite3_finalize (info->remove_handle);
+    if (info->current_packages)
+        g_hash_table_destroy (info->current_packages);
+    if (info->all_packages)
+        g_hash_table_destroy (info->all_packages);
+    if (info->package_ids_chunk)
+        g_string_chunk_free (info->package_ids_chunk);
+
+    g_timer_stop (info->timer);
+    if (!*err) {
+        g_message ("Added %d new packages, deleted %d old in %.2f seconds",
+                   info->add_count, info->del_count,
+                   g_timer_elapsed (info->timer, NULL));
+    }
+
+    g_timer_destroy (info->timer);
+}
+
+
+/* Primary */
+
+typedef struct {
+    UpdateInfo update_info;
+    sqlite3_stmt *pkg_handle;
+    sqlite3_stmt *requires_handle;
+    sqlite3_stmt *provides_handle;
+    sqlite3_stmt *conflicts_handle;
+    sqlite3_stmt *obsoletes_handle;
+    sqlite3_stmt *files_handle;
+} PackageWriterInfo;
+
+static void
+package_writer_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
+{
+    PackageWriterInfo *info = (PackageWriterInfo *) update_info;
+
+    info->pkg_handle = yum_db_package_prepare (db, err);
+    if (*err)
+        return;
+    info->requires_handle = yum_db_dependency_prepare (db, "requires", err);
+    if (*err)
+        return;
+    info->provides_handle = yum_db_dependency_prepare (db, "provides", err);
+    if (*err)
+        return;
+    info->conflicts_handle = yum_db_dependency_prepare (db, "conflicts", err);
+    if (*err)
+        return;
+    info->obsoletes_handle = yum_db_dependency_prepare (db, "obsoletes", err);
+    if (*err)
+        return;
+    info->files_handle = yum_db_file_prepare (db, err);
+}
+
+static void
+write_deps (sqlite3 *db, sqlite3_stmt *handle, gint64 pkgKey, 
+            GSList *deps)
+{
+    GSList *iter;
+
+    for (iter = deps; iter; iter = iter->next)
+        yum_db_dependency_write (db, handle, pkgKey, (Dependency *) iter->data,
+                                 FALSE);
+}
+
+static void
+write_requirements (sqlite3 *db, sqlite3_stmt *handle, gint64 pkgKey,
+            GSList *deps)
+{
+    GSList *iter;
+
+    for (iter = deps; iter; iter = iter->next)
+        yum_db_dependency_write (db, handle, pkgKey, (Dependency *) iter->data,
+                                 TRUE);
+}
+
+
+static void
+write_files (sqlite3 *db, sqlite3_stmt *handle, Package *pkg)
+{
+    GSList *iter;
+
+    for (iter = pkg->files; iter; iter = iter->next)
+        yum_db_file_write (db, handle, pkg->pkgKey,
+                           (PackageFile *) iter->data);
+}
+
+static void
+write_package_to_db (UpdateInfo *update_info, Package *package)
+{
+    PackageWriterInfo *info = (PackageWriterInfo *) update_info;
+
+    yum_db_package_write (update_info->db, info->pkg_handle, package);
+
+    write_requirements (update_info->db, info->requires_handle,
+                    package->pkgKey, package->requires);
+    write_deps (update_info->db, info->provides_handle,
+                package->pkgKey, package->provides);
+    write_deps (update_info->db, info->conflicts_handle,
+                package->pkgKey, package->conflicts);
+    write_deps (update_info->db, info->obsoletes_handle,
+                package->pkgKey, package->obsoletes);
+
+    write_files (update_info->db, info->files_handle, package);
+}
+
+static void
+package_writer_info_clean (UpdateInfo *update_info)
+{
+    PackageWriterInfo *info = (PackageWriterInfo *) update_info;
+    
+    if (info->pkg_handle)
+        sqlite3_finalize (info->pkg_handle);
+    if (info->requires_handle)
+        sqlite3_finalize (info->requires_handle);
+    if (info->provides_handle)
+        sqlite3_finalize (info->provides_handle);
+    if (info->conflicts_handle)
+        sqlite3_finalize (info->conflicts_handle);
+    if (info->obsoletes_handle)
+        sqlite3_finalize (info->obsoletes_handle);
+    if (info->files_handle)
+        sqlite3_finalize (info->files_handle);
+}
+
+
+/* Filelists */
+
+typedef struct {
+    UpdateInfo update_info;
+    sqlite3_stmt *pkg_handle;
+    sqlite3_stmt *file_handle;
+} FileListInfo;
+
+static void
+update_filelist_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
+{
+    FileListInfo *info = (FileListInfo *) update_info;
+
+    info->pkg_handle = yum_db_package_ids_prepare (db, err);
+    if (*err)
+        return;
+
+    info->file_handle = yum_db_filelists_prepare (db, err);
+}
+
+static void
+update_filelist_info_clean (UpdateInfo *update_info)
+{
+    FileListInfo *info = (FileListInfo *) update_info;
+
+    if (info->pkg_handle)
+        sqlite3_finalize (info->pkg_handle);
+    if (info->file_handle)
+        sqlite3_finalize (info->file_handle);
+}
+
+static void
+write_filelist_package_to_db (UpdateInfo *update_info, Package *package)
+{
+    FileListInfo *info = (FileListInfo *) update_info;
+
+    yum_db_package_ids_write (update_info->db, info->pkg_handle, package);
+    yum_db_filelists_write (update_info->db, info->file_handle, package);
+}
+
+
+/* Other */
+
+typedef struct {
+    UpdateInfo update_info;
+    sqlite3_stmt *pkg_handle;
+    sqlite3_stmt *changelog_handle;
+} UpdateOtherInfo;
+
+static void
+update_other_info_init (UpdateInfo *update_info, sqlite3 *db, GError **err)
+{
+    UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
+    info->pkg_handle = yum_db_package_ids_prepare (db, err);
+    if (*err)
+        return;
+
+    info->changelog_handle = yum_db_changelog_prepare (db, err);
+}
+
+static void
+update_other_info_clean (UpdateInfo *update_info)
+{
+    UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
+
+    if (info->pkg_handle)
+        sqlite3_finalize (info->pkg_handle);
+    if (info->changelog_handle)
+        sqlite3_finalize (info->changelog_handle);
+}
+
+static void
+write_other_package_to_db (UpdateInfo *update_info, Package *package)
+{
+    UpdateOtherInfo *info = (UpdateOtherInfo *) update_info;
+
+    yum_db_package_ids_write (update_info->db, info->pkg_handle, package);
+    yum_db_changelog_write (update_info->db, info->changelog_handle, package);
+}
+
+
+/*****************************************************************************/
+
+static void
+progress_cb (UpdateInfo *update_info)
+{
+    PyObject *progress = (PyObject *) update_info->python_callback;
+    PyObject *repoid = (PyObject *) update_info->user_data;
+    PyObject *args;
+    PyObject *result;
+
+    Py_INCREF(repoid);
+   
+    args = PyTuple_New (3);
+    PyTuple_SET_ITEM (args, 0, PyInt_FromLong (update_info->packages_seen));
+    PyTuple_SET_ITEM (args, 1, PyInt_FromLong (update_info->count_from_md));
+    PyTuple_SET_ITEM (args, 2, repoid);
+
+    result = PyEval_CallObject (progress, args);
+    Py_DECREF (args);
+    Py_XDECREF (result);
+}
+
+static void
+update_package_cb (Package *p, gpointer user_data)
+{
+    UpdateInfo *update_info = (UpdateInfo *) user_data;
+
+    /* TODO: Wire in logging of skipped packages */
+    if (p->pkgId == NULL) {
+        return;
+    }
+
+    g_hash_table_insert (update_info->all_packages,
+                         g_string_chunk_insert (update_info->package_ids_chunk,
+                                                p->pkgId),
+                         GINT_TO_POINTER (1));
+
+    if (g_hash_table_lookup (update_info->current_packages,
+                             p->pkgId) == NULL) {
+        
+        update_info->write_package (update_info, p);
+        update_info->add_count++;
+    }
+
+    if (update_info->count_from_md > 0 && update_info->python_callback) {
+        update_info->packages_seen++;
+        progress_cb (update_info);
+    }
+}
+
+static char *
+update_packages (UpdateInfo *update_info,
+                 const char *md_filename,
+                 const char *checksum,
+                 gpointer python_callback,
+                 gpointer user_data,
+                 GError **err)
+{
+    char *db_filename;
+
+    db_filename = yum_db_filename (md_filename);
+    update_info->db = yum_db_open (db_filename, checksum,
+                                   update_info->create_tables,
+                                   err);
+
+    if (*err)
+        goto cleanup;
+
+    if (!update_info->db)
+        return db_filename;
+
+    update_info_init (update_info, err);
+    if (*err)
+        goto cleanup;
+    
+    update_info->python_callback = python_callback;
+    update_info->user_data = user_data;
+
+    update_info->info_init (update_info, update_info->db, err);
+    if (*err)
+        goto cleanup;
+
+    sqlite3_exec (update_info->db, "BEGIN", NULL, NULL, NULL);
+    update_info->xml_parse (md_filename,
+                            count_cb,
+                            update_package_cb,
+                            update_info,
+                            err);
+    if (*err)
+        goto cleanup;
+    sqlite3_exec (update_info->db, "COMMIT", NULL, NULL, NULL);
+
+    update_info->index_tables (update_info->db, err);
+    if (*err)
+        goto cleanup;
+
+    update_info_remove_old_entries (update_info);
+    yum_db_dbinfo_update (update_info->db, checksum, err);
+
+ cleanup:
+    update_info->info_clean (update_info);
+    update_info_done (update_info, err);
+
+    if (update_info->db)
+        sqlite3_close (update_info->db);
+
+    if (*err) {
+        g_free (db_filename);
+        db_filename = NULL;
+    }
+
+    return db_filename;
+}
+
+/*********************************************************************/
+
+static gboolean
+py_parse_args (PyObject *args,
+               const char **md_filename,
+               const char **checksum,
+               PyObject **log,
+               PyObject **progress,
+               PyObject **repoid)
+{
+    PyObject *callback;
+
+    if (!PyArg_ParseTuple (args, "ssOO", md_filename, checksum, &callback,
+                           repoid))
+        return FALSE;
+
+    if (PyObject_HasAttrString (callback, "log")) {
+        *log = PyObject_GetAttrString (callback, "log");
+
+        if (!PyCallable_Check (*log)) {
+            PyErr_SetString (PyExc_TypeError, "parameter must be callable");
+            return FALSE;
+        }
+    }
+
+    if (PyObject_HasAttrString (callback, "progressbar")) {
+        *progress = PyObject_GetAttrString (callback, "progressbar");
+
+        if (!PyCallable_Check (*progress)) {
+            PyErr_SetString (PyExc_TypeError, "parameter must be callable");
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+log_cb (const gchar *log_domain,
+        GLogLevelFlags log_level,
+        const gchar *message,
+        gpointer user_data)
+{
+    PyObject *callback = (PyObject *) user_data;
+    int level;
+    PyObject *args;
+    PyObject *result;
+
+    if (!callback)
+        return;
+
+    args = PyTuple_New (2);
+
+    switch (log_level) {
+    case G_LOG_LEVEL_DEBUG:
+        level = 2;
+        break;
+    case G_LOG_LEVEL_MESSAGE:
+        level = 1;
+        break;
+    case G_LOG_LEVEL_WARNING:
+        level = 0;
+        break;
+    case G_LOG_LEVEL_CRITICAL:
+    default:
+        level = -1;
+        break;
+    }
+
+    PyTuple_SET_ITEM (args, 0, PyInt_FromLong (level));
+    PyTuple_SET_ITEM (args, 1, PyString_FromString (message));
+
+    result = PyEval_CallObject (callback, args);
+    Py_DECREF (args);
+    Py_XDECREF (result);
+}
+
+static PyObject *
+py_update (PyObject *self, PyObject *args, UpdateInfo *update_info)
+{
+    const char *md_filename = NULL;
+    const char *checksum = NULL;
+    PyObject *log = NULL;
+    PyObject *progress = NULL;
+    PyObject *repoid = NULL;
+    guint log_id = 0;
+    char *db_filename;
+    PyObject *ret = NULL;
+    GError *err = NULL;
+
+    if (!py_parse_args (args, &md_filename, &checksum, &log, &progress,
+                        &repoid))
+        return NULL;
+
+    GLogLevelFlags level = G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING |
+        G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_DEBUG;
+    log_id = g_log_set_handler (NULL, level, log_cb, log);
+
+    db_filename = update_packages (update_info, md_filename, checksum,
+                                   progress, repoid, &err);
+
+    g_log_remove_handler (NULL, log_id);
+
+    if (db_filename) {
+        ret = PyString_FromString (db_filename);
+        g_free (db_filename);
+    } else {
+        PyErr_SetString (PyExc_TypeError, err->message);
+        g_error_free (err);
+    }
+
+    return ret;
+}
+
+static PyObject *
+py_update_primary (PyObject *self, PyObject *args)
+{
+    PackageWriterInfo info;
+    memset (&info, 0, sizeof (PackageWriterInfo));
+
+    info.update_info.info_init = package_writer_info_init;
+    info.update_info.info_clean = package_writer_info_clean;
+    info.update_info.create_tables = yum_db_create_primary_tables;
+    info.update_info.write_package = write_package_to_db;
+    info.update_info.xml_parse = yum_xml_parse_primary;
+    info.update_info.index_tables = yum_db_index_primary_tables;
+
+    return py_update (self, args, (UpdateInfo *) &info);
+}
+
+static PyObject *
+py_update_filelist (PyObject *self, PyObject *args)
+{
+    FileListInfo info;
+    memset (&info, 0, sizeof (FileListInfo));
+
+    info.update_info.info_init = update_filelist_info_init;
+    info.update_info.info_clean = update_filelist_info_clean;
+    info.update_info.create_tables = yum_db_create_filelist_tables;
+    info.update_info.write_package = write_filelist_package_to_db;
+    info.update_info.xml_parse = yum_xml_parse_filelists;
+    info.update_info.index_tables = yum_db_index_filelist_tables;
+
+    return py_update (self, args, (UpdateInfo *) &info);
+}
+
+static PyObject *
+py_update_other (PyObject *self, PyObject *args)
+{
+    UpdateOtherInfo info;
+    memset (&info, 0, sizeof (UpdateOtherInfo));
+
+    info.update_info.info_init = update_other_info_init;
+    info.update_info.info_clean = update_other_info_clean;
+    info.update_info.create_tables = yum_db_create_other_tables;
+    info.update_info.write_package = write_other_package_to_db;
+    info.update_info.xml_parse = yum_xml_parse_other;
+    info.update_info.index_tables = yum_db_index_other_tables;
+
+    return py_update (self, args, (UpdateInfo *) &info);
+}
+
+static PyMethodDef SqliteMethods[] = {
+    {"update_primary", py_update_primary, METH_VARARGS,
+     "Parse YUM primary.xml metadata."},
+    {"update_filelist", py_update_filelist, METH_VARARGS,
+     "Parse YUM filelists.xml metadata."},
+    {"update_other", py_update_other, METH_VARARGS,
+     "Parse YUM other.xml metadata."},
+
+    {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+init_sqlitecache (void)
+{
+    PyObject * m, * d;
+
+    m = Py_InitModule ("_sqlitecache", SqliteMethods);
+
+    d = PyModule_GetDict(m);
+    PyDict_SetItemString(d, "DBVERSION", PyInt_FromLong(YUM_SQLITE_CACHE_DBVERSION));
+}
diff --git a/sqlitecachec.py b/sqlitecachec.py
new file mode 100644 (file)
index 0000000..8b0ca08
--- /dev/null
@@ -0,0 +1,62 @@
+# 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 Library 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.
+
+try:
+    import sqlite3 as sqlite
+except ImportError:
+    import sqlite
+import _sqlitecache
+
+DBVERSION = _sqlitecache.DBVERSION
+
+class RepodataParserSqlite:
+    def __init__(self, storedir, repoid, callback=None):
+        self.callback = callback
+        self.repoid = repoid
+
+    def open_database(self, filename):
+        if not filename:
+            return None
+        con = sqlite.connect(filename)
+        con.text_factory = str
+        if sqlite.version_info[0] > 1:
+            con.row_factory = sqlite.Row
+        cur = con.cursor()
+        cur.execute("pragma locking_mode = EXCLUSIVE")
+        del cur
+        return con
+
+    def getPrimary(self, location, checksum):
+        """Load primary.xml.gz from an sqlite cache and update it 
+           if required"""
+        return self.open_database(_sqlitecache.update_primary(location,
+                                                                                                                         checksum,
+                                                              self.callback,
+                                                              self.repoid))
+
+    def getFilelists(self, location, checksum):
+        """Load filelist.xml.gz from an sqlite cache and update it if 
+           required"""
+        return self.open_database(_sqlitecache.update_filelist(location,
+                                                                                                                          checksum,
+                                                               self.callback,
+                                                               self.repoid))
+
+    def getOtherdata(self, location, checksum):
+        """Load other.xml.gz from an sqlite cache and update it if required"""
+        return self.open_database(_sqlitecache.update_other(location,
+                                                                                                                       checksum,
+                                                            self.callback,
+                                                            self.repoid))
+    
diff --git a/xml-parser.c b/xml-parser.c
new file mode 100644 (file)
index 0000000..9617d17
--- /dev/null
@@ -0,0 +1,1064 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include <sqlite3.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "xml-parser.h"
+
+#define PACKAGE_FIELD_SIZE 1024
+
+GQuark
+yum_parser_error_quark (void)
+{
+    static GQuark quark;
+
+    if (!quark)
+        quark = g_quark_from_static_string ("yum_parser_error");
+
+    return quark;
+}
+
+static guint32
+string_to_guint32_with_default (const char *n, guint32 def)
+{
+    char *ret;
+    guint32 z;
+
+    z = strtoul (n, &ret, 10);
+    if (*ret != '\0')
+        return def;
+    else
+        return z;
+}
+
+typedef struct {
+    const char *md_type;
+    xmlParserCtxt *xml_context;
+    GError **error;
+    CountFn count_fn;
+    PackageFn package_fn;
+    gpointer user_data;
+
+    Package *current_package;
+
+    gboolean want_text;
+    GString *text_buffer;
+} SAXContext;
+
+typedef enum {
+    PRIMARY_PARSER_TOPLEVEL = 0,
+    PRIMARY_PARSER_PACKAGE,
+    PRIMARY_PARSER_FORMAT,
+    PRIMARY_PARSER_DEP,
+} PrimarySAXContextState;
+
+typedef struct {
+    SAXContext sctx;
+
+    PrimarySAXContextState state;
+
+    GSList **current_dep_list;
+    PackageFile *current_file;
+} PrimarySAXContext;
+
+static void
+primary_parser_toplevel_start (PrimarySAXContext *ctx,
+                               const char *name,
+                               const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    if (!strcmp (name, "package")) {
+        g_assert (sctx->current_package == NULL);
+
+        ctx->state = PRIMARY_PARSER_PACKAGE;
+
+        sctx->current_package = package_new ();
+    }
+
+    else if (sctx->count_fn && !strcmp (name, "metadata")) {
+        int i;
+        const char *attr;
+        const char *value;
+
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "packages")) {
+                sctx->count_fn (string_to_guint32_with_default (value, 0),
+                               sctx->user_data);
+                break;
+            }
+        }
+    }
+}
+
+static void
+parse_version_info(const char **attrs, Package *p)
+{
+    int i;
+    const char *attr;
+    const char *value;
+
+    for (i = 0; attrs && attrs[i]; i++) {
+        attr = attrs[i];
+        value = attrs[++i];
+
+        if (!strcmp (attr, "epoch"))
+            p->epoch = g_string_chunk_insert (p->chunk, value);
+        else if (!strcmp (attr, "ver"))
+            p->version = g_string_chunk_insert (p->chunk, value);
+        else if (!strcmp (attr, "rel"))
+            p->release = g_string_chunk_insert (p->chunk, value);
+    }
+}
+
+static void
+primary_parser_package_start (PrimarySAXContext *ctx,
+                              const char *name,
+                              const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+    int i;
+    const char *attr;
+    const char *value;
+
+    g_assert (p != NULL);
+
+    sctx->want_text = TRUE;
+
+    if (!strcmp (name, "format")) {
+        ctx->state = PRIMARY_PARSER_FORMAT;
+    }
+
+    else if (!strcmp (name, "version")) {
+        parse_version_info(attrs, p);
+    }
+
+    else if (!strcmp (name, "checksum")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "type"))
+                p->checksum_type = g_string_chunk_insert (p->chunk, value);
+        }
+    }
+
+    else if (!strcmp (name, "time")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "file"))
+                p->time_file = strtol(value, NULL, 10);
+            else if (!strcmp (attr, "build"))
+                p->time_build = strtol(value, NULL, 10);
+        }
+    }
+
+    else if (!strcmp (name, "size")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "package"))
+                p->size_package = strtol(value, NULL, 10);
+            else if (!strcmp (attr, "installed"))
+                p->size_installed = strtol(value, NULL, 10);
+            else if (!strcmp (attr, "archive"))
+                p->size_archive = strtol(value, NULL, 10);
+        }
+    }
+
+    else if (!strcmp (name, "location")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "href"))
+                p->location_href = g_string_chunk_insert (p->chunk, value);
+            else if (!strcmp (attr, "xml:base"))
+                p->location_base = g_string_chunk_insert (p->chunk, value);
+        }
+    }
+}
+
+static void
+primary_parser_format_start (PrimarySAXContext *ctx,
+                             const char *name,
+                             const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+    int i;
+    const char *attr;
+    const char *value;
+
+    g_assert (p != NULL);
+
+    if (!strcmp (name, "rpm:header-range")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "start"))
+                p->rpm_header_start = strtol(value, NULL, 10);
+            else if (!strcmp (attr, "end"))
+                p->rpm_header_end = strtol(value, NULL, 10);
+        }
+    }
+
+    else if (!strcmp (name, "rpm:provides")) {
+        ctx->state = PRIMARY_PARSER_DEP;
+        ctx->current_dep_list = &sctx->current_package->provides;
+    } else if (!strcmp (name, "rpm:requires")) {
+        ctx->state = PRIMARY_PARSER_DEP;
+        ctx->current_dep_list = &sctx->current_package->requires;
+    } else if (!strcmp (name, "rpm:obsoletes")) {
+        ctx->state = PRIMARY_PARSER_DEP;
+        ctx->current_dep_list = &sctx->current_package->obsoletes;
+    } else if (!strcmp (name, "rpm:conflicts")) {
+        ctx->state = PRIMARY_PARSER_DEP;
+        ctx->current_dep_list = &sctx->current_package->conflicts;
+    }
+
+    else if (!strcmp (name, "file")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "type")) {
+                ctx->current_file = package_file_new ();
+                ctx->current_file->type =
+                    g_string_chunk_insert_const (p->chunk, value);
+            }
+        }
+    }
+}
+
+static void
+primary_parser_dep_start (PrimarySAXContext *ctx,
+                          const char *name,
+                          const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    const char *tmp_name = NULL;
+    const char *tmp_version = NULL;
+    const char *tmp_release = NULL;
+    const char *tmp_epoch = NULL;
+    const char *tmp_flags = NULL;
+    gboolean tmp_pre = FALSE;
+    Dependency *dep;
+    int i;
+    gboolean ignore = FALSE;
+    const char *attr;
+    const char *value;
+
+    if (!strcmp (name, "rpm:entry")) {
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "name")) {
+                if (!strncmp (value, "rpmlib(", strlen ("rpmlib("))) {
+                    ignore = TRUE;
+                    break;
+                }
+                tmp_name = value;
+            } else if (!strcmp (attr, "flags"))
+                tmp_flags = value;
+            else if (!strcmp (attr, "epoch"))
+                tmp_epoch = value;
+            else if (!strcmp (attr, "ver"))
+                tmp_version = value;
+            else if (!strcmp (attr, "rel"))
+                tmp_release = value;
+            else if (!strcmp (attr, "pre"))
+                tmp_pre = TRUE;
+        }
+
+        if (!ignore) {
+            GStringChunk *chunk = sctx->current_package->chunk;
+
+            dep = dependency_new ();
+            dep->name = g_string_chunk_insert (chunk, tmp_name);
+            if (tmp_flags)
+                dep->flags = g_string_chunk_insert (chunk, tmp_flags);
+            if (tmp_epoch)
+                dep->epoch = g_string_chunk_insert (chunk, tmp_epoch);
+            if (tmp_version)
+                dep->version = g_string_chunk_insert (chunk, tmp_version);
+            if (tmp_release)
+                dep->release = g_string_chunk_insert (chunk, tmp_release);
+            dep->pre = tmp_pre;
+
+            *ctx->current_dep_list = g_slist_prepend (*ctx->current_dep_list,
+                                                      dep);
+        }
+    }
+}
+
+static void
+primary_sax_start_element (void *data, const char *name, const char **attrs)
+{
+    PrimarySAXContext *ctx = (PrimarySAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    if (sctx->text_buffer->len)
+        g_string_truncate (sctx->text_buffer, 0);
+
+    switch (ctx->state) {
+    case PRIMARY_PARSER_TOPLEVEL:
+        primary_parser_toplevel_start (ctx, name, attrs);
+        break;
+    case PRIMARY_PARSER_PACKAGE:
+        primary_parser_package_start (ctx, name, attrs);
+        break;
+    case PRIMARY_PARSER_FORMAT:
+        primary_parser_format_start (ctx, name, attrs);
+        break;
+    case PRIMARY_PARSER_DEP:
+        primary_parser_dep_start (ctx, name, attrs);
+        break;
+
+    default:
+        break;
+    }
+}
+
+static void
+primary_parser_package_end (PrimarySAXContext *ctx, const char *name)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+
+    g_assert (p != NULL);
+
+    if (!strcmp (name, "package")) {
+        if (sctx->package_fn && !*sctx->error)
+            sctx->package_fn (p, sctx->user_data);
+
+        package_free (p);
+        sctx->current_package = NULL;
+
+        sctx->want_text = FALSE;
+        ctx->state = PRIMARY_PARSER_TOPLEVEL;
+    }
+
+    else if (sctx->text_buffer->len == 0)
+        /* Nothing interesting to do here */
+        return;
+
+    else if (!strcmp (name, "name"))
+        p->name = g_string_chunk_insert_len (p->chunk,
+                                             sctx->text_buffer->str,
+                                             sctx->text_buffer->len);
+    else if (!strcmp (name, "arch"))
+        p->arch = g_string_chunk_insert_len (p->chunk,
+                                             sctx->text_buffer->str,
+                                             sctx->text_buffer->len);
+    else if (!strcmp (name, "checksum"))
+        p->pkgId = g_string_chunk_insert_len (p->chunk,
+                                              sctx->text_buffer->str,
+                                              sctx->text_buffer->len);
+    else if (!strcmp (name, "summary"))
+        p->summary = g_string_chunk_insert_len (p->chunk,
+                                                sctx->text_buffer->str,
+                                                sctx->text_buffer->len);
+    else if (!strcmp (name, "description"))
+        p->description = g_string_chunk_insert_len (p->chunk,
+                                                    sctx->text_buffer->str,
+                                                    sctx->text_buffer->len);
+    else if (!strcmp (name, "packager"))
+        p->rpm_packager = g_string_chunk_insert_len (p->chunk,
+                                                     sctx->text_buffer->str,
+                                                     sctx->text_buffer->len);
+    else if (!strcmp (name, "url"))
+        p->url = g_string_chunk_insert_len (p->chunk,
+                                            sctx->text_buffer->str,
+                                            sctx->text_buffer->len);
+}
+
+static void
+primary_parser_format_end (PrimarySAXContext *ctx, const char *name)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+
+    g_assert (p != NULL);
+
+    if (!strcmp (name, "rpm:license"))
+        p->rpm_license = g_string_chunk_insert_len (p->chunk,
+                                                    sctx->text_buffer->str,
+                                                    sctx->text_buffer->len);
+    if (!strcmp (name, "rpm:vendor"))
+        p->rpm_vendor = g_string_chunk_insert_len (p->chunk,
+                                                   sctx->text_buffer->str,
+                                                   sctx->text_buffer->len);
+    if (!strcmp (name, "rpm:group"))
+        p->rpm_group = g_string_chunk_insert_len (p->chunk,
+                                                  sctx->text_buffer->str,
+                                                  sctx->text_buffer->len);
+    if (!strcmp (name, "rpm:buildhost"))
+        p->rpm_buildhost = g_string_chunk_insert_len (p->chunk,
+                                                      sctx->text_buffer->str,
+                                                      sctx->text_buffer->len);
+    if (!strcmp (name, "rpm:sourcerpm"))
+        p->rpm_sourcerpm = g_string_chunk_insert_len (p->chunk,
+                                                      sctx->text_buffer->str,
+                                                      sctx->text_buffer->len);
+    else if (!strcmp (name, "file")) {
+        PackageFile *file = ctx->current_file != NULL ?
+            ctx->current_file : package_file_new ();
+
+        file->name = g_string_chunk_insert_len (p->chunk,
+                                                sctx->text_buffer->str,
+                                                sctx->text_buffer->len);
+
+        if (!file->type)
+            file->type = g_string_chunk_insert_const (p->chunk, "file");
+
+        p->files = g_slist_prepend (p->files, file);
+        ctx->current_file = NULL;
+    } else if (!strcmp (name, "format"))
+        ctx->state = PRIMARY_PARSER_PACKAGE;
+}
+
+static void
+primary_parser_dep_end (PrimarySAXContext *ctx, const char *name)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    g_assert (sctx->current_package != NULL);
+
+    if (strcmp (name, "rpm:entry"))
+        ctx->state = PRIMARY_PARSER_FORMAT;
+}
+
+static void
+primary_sax_end_element (void *data, const char *name)
+{
+    PrimarySAXContext *ctx = (PrimarySAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    switch (ctx->state) {
+    case PRIMARY_PARSER_PACKAGE:
+        primary_parser_package_end (ctx, name);
+        break;
+    case PRIMARY_PARSER_FORMAT:
+        primary_parser_format_end (ctx, name);
+        break;
+    case PRIMARY_PARSER_DEP:
+        primary_parser_dep_end (ctx, name);
+        break;
+    default:
+        break;
+    }
+
+    g_string_truncate (sctx->text_buffer, 0);
+}
+
+static void
+sax_characters (void *data, const char *ch, int len)
+{
+    SAXContext *sctx = (SAXContext *) data;
+
+    if (sctx->want_text)
+        g_string_append_len (sctx->text_buffer, ch, len);
+}
+
+static void
+sax_warning (void *data, const char *msg, ...)
+{
+    va_list args;
+    char *tmp;
+
+    va_start (args, msg);
+
+    tmp = g_strdup_vprintf (msg, args);
+    g_warning ("* SAX Warning: %s", tmp);
+    g_free (tmp);
+
+    va_end (args);
+}
+
+static void
+sax_error (void *data, const char *msg, ...)
+{
+    SAXContext *sctx = (SAXContext *) data;
+    va_list args;
+    char *tmp;
+
+    va_start (args, msg);
+
+    tmp = g_strdup_vprintf (msg, args);
+    g_set_error (sctx->error, YUM_PARSER_ERROR, YUM_PARSER_ERROR,
+                 "Parsing %s error: %s", sctx->md_type, tmp);
+    g_free (tmp);
+
+    va_end (args);
+}
+
+static xmlSAXHandler primary_sax_handler = {
+    NULL,      /* internalSubset */
+    NULL,      /* isStandalone */
+    NULL,      /* hasInternalSubset */
+    NULL,      /* hasExternalSubset */
+    NULL,      /* resolveEntity */
+    NULL,      /* getEntity */
+    NULL,      /* entityDecl */
+    NULL,      /* notationDecl */
+    NULL,      /* attributeDecl */
+    NULL,      /* elementDecl */
+    NULL,      /* unparsedEntityDecl */
+    NULL,      /* setDocumentLocator */
+    NULL,      /* startDocument */
+    NULL,      /* endDocument */
+    (startElementSAXFunc) primary_sax_start_element, /* startElement */
+    (endElementSAXFunc) primary_sax_end_element,     /* endElement */
+    NULL,      /* reference */
+    (charactersSAXFunc) sax_characters,      /* characters */
+    NULL,      /* ignorableWhitespace */
+    NULL,      /* processingInstruction */
+    NULL,      /* comment */
+    sax_warning,      /* warning */
+    sax_error,      /* error */
+    sax_error,      /* fatalError */
+};
+
+void
+sax_context_init (SAXContext *sctx,
+                  const char *md_type,
+                  CountFn count_callback,
+                  PackageFn package_callback,
+                  gpointer user_data,
+                  GError **err)
+{
+    sctx->md_type = md_type;
+    sctx->error = err;
+    sctx->count_fn = count_callback;
+    sctx->package_fn = package_callback;
+    sctx->user_data = user_data;
+    sctx->current_package = NULL;
+    sctx->want_text = FALSE;
+    sctx->text_buffer = g_string_sized_new (PACKAGE_FIELD_SIZE);
+}
+
+void
+yum_xml_parse_primary (const char *filename,
+                       CountFn count_callback,
+                       PackageFn package_callback,
+                       gpointer user_data,
+                       GError **err)
+{
+    PrimarySAXContext ctx;
+    SAXContext *sctx = &ctx.sctx;
+    int rc;
+
+    ctx.state = PRIMARY_PARSER_TOPLEVEL;
+    ctx.current_dep_list = NULL;
+    ctx.current_file = NULL;
+
+    sax_context_init(sctx, "primary.xml", count_callback, package_callback,
+                     user_data, err);
+
+    xmlSubstituteEntitiesDefault (1);
+    rc = xmlSAXUserParseFile (&primary_sax_handler, &ctx, filename);
+
+    if (sctx->current_package) {
+        g_warning ("Incomplete package lost");
+        package_free (sctx->current_package);
+    }
+
+    g_string_free (sctx->text_buffer, TRUE);
+}
+
+/*****************************************************************************/
+
+
+static void
+parse_package (const char **attrs, Package *p)
+{
+    int i;
+    const char *attr;
+    const char *value;
+
+    for (i = 0; attrs && attrs[i]; i++) {
+        attr = attrs[i];
+        value = attrs[++i];
+
+        if (!strcmp (attr, "pkgid"))
+            p->pkgId = g_string_chunk_insert (p->chunk, value);
+        if (!strcmp (attr, "name"))
+            p->name = g_string_chunk_insert (p->chunk, value);
+        else if (!strcmp (attr, "arch"))
+            p->arch = g_string_chunk_insert (p->chunk, value);
+    }
+}
+
+typedef enum {
+    FILELIST_PARSER_TOPLEVEL = 0,
+    FILELIST_PARSER_PACKAGE,
+} FilelistSAXContextState;
+
+typedef struct {
+    SAXContext sctx;
+
+    FilelistSAXContextState state;
+
+    PackageFile *current_file;
+} FilelistSAXContext;
+
+static void
+filelist_parser_toplevel_start (FilelistSAXContext *ctx,
+                                const char *name,
+                                const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    if (!strcmp (name, "package")) {
+        g_assert (sctx->current_package == NULL);
+
+        ctx->state = FILELIST_PARSER_PACKAGE;
+
+        sctx->current_package = package_new ();
+        parse_package (attrs, sctx->current_package);
+    }
+
+    else if (sctx->count_fn && !strcmp (name, "filelists")) {
+        int i;
+        const char *attr;
+        const char *value;
+
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "packages")) {
+                sctx->count_fn (string_to_guint32_with_default (value, 0),
+                                sctx->user_data);
+                break;
+            }
+        }
+    }
+}
+
+static void
+filelist_parser_package_start (FilelistSAXContext *ctx,
+                               const char *name,
+                               const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+    int i;
+    const char *attr;
+    const char *value;
+
+    g_assert (p != NULL);
+
+    sctx->want_text = TRUE;
+
+    if (!strcmp (name, "version")) {
+        parse_version_info(attrs, p);
+    }
+
+    else if (!strcmp (name, "file")) {
+        ctx->current_file = package_file_new ();
+
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "type"))
+                ctx->current_file->type =
+                    g_string_chunk_insert_const (p->chunk, value);
+        }
+    }
+}
+
+static void
+filelist_sax_start_element (void *data, const char *name, const char **attrs)
+{
+    FilelistSAXContext *ctx = (FilelistSAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    if (sctx->text_buffer->len)
+        g_string_truncate (sctx->text_buffer, 0);
+
+    switch (ctx->state) {
+    case FILELIST_PARSER_TOPLEVEL:
+        filelist_parser_toplevel_start (ctx, name, attrs);
+        break;
+    case FILELIST_PARSER_PACKAGE:
+        filelist_parser_package_start (ctx, name, attrs);
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+filelist_parser_package_end (FilelistSAXContext *ctx, const char *name)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+
+    g_assert (p != NULL);
+
+    sctx->want_text = FALSE;
+
+    if (!strcmp (name, "package")) {
+        if (sctx->package_fn && !*sctx->error)
+            sctx->package_fn (p, sctx->user_data);
+
+        package_free (p);
+        sctx->current_package = NULL;
+
+        if (ctx->current_file) {
+            g_free (ctx->current_file);
+            ctx->current_file = NULL;
+        }
+
+        ctx->state = FILELIST_PARSER_TOPLEVEL;
+    }
+
+    else if (!strcmp (name, "file")) {
+        PackageFile *file = ctx->current_file;
+        file->name = g_string_chunk_insert_len (p->chunk,
+                                                sctx->text_buffer->str,
+                                                sctx->text_buffer->len);
+        if (!file->type)
+            file->type = g_string_chunk_insert_const (p->chunk, "file");
+
+        p->files = g_slist_prepend (p->files, file);
+        ctx->current_file = NULL;
+    }
+}
+
+static void
+filelist_sax_end_element (void *data, const char *name)
+{
+    FilelistSAXContext *ctx = (FilelistSAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    switch (ctx->state) {
+    case FILELIST_PARSER_PACKAGE:
+        filelist_parser_package_end (ctx, name);
+        break;
+    default:
+        break;
+    }
+
+    g_string_truncate (sctx->text_buffer, 0);
+}
+
+static xmlSAXHandler filelist_sax_handler = {
+    NULL,      /* internalSubset */
+    NULL,      /* isStandalone */
+    NULL,      /* hasInternalSubset */
+    NULL,      /* hasExternalSubset */
+    NULL,      /* resolveEntity */
+    NULL,      /* getEntity */
+    NULL,      /* entityDecl */
+    NULL,      /* notationDecl */
+    NULL,      /* attributeDecl */
+    NULL,      /* elementDecl */
+    NULL,      /* unparsedEntityDecl */
+    NULL,      /* setDocumentLocator */
+    NULL,      /* startDocument */
+    NULL,      /* endDocument */
+    (startElementSAXFunc) filelist_sax_start_element, /* startElement */
+    (endElementSAXFunc) filelist_sax_end_element,     /* endElement */
+    NULL,      /* reference */
+    (charactersSAXFunc) sax_characters,      /* characters */
+    NULL,      /* ignorableWhitespace */
+    NULL,      /* processingInstruction */
+    NULL,      /* comment */
+    sax_warning,      /* warning */
+    sax_error,      /* error */
+    sax_error,      /* fatalError */
+};
+
+void
+yum_xml_parse_filelists (const char *filename,
+                         CountFn count_callback,
+                         PackageFn package_callback,
+                         gpointer user_data,
+                         GError **err)
+{
+    FilelistSAXContext ctx;
+    SAXContext *sctx = &ctx.sctx;
+
+    int rc;
+
+    ctx.state = FILELIST_PARSER_TOPLEVEL;
+    ctx.current_file = NULL;
+    
+    sax_context_init(sctx, "filelists.xml", count_callback, package_callback,
+                     user_data, err);
+
+    xmlSubstituteEntitiesDefault (1);
+    rc = xmlSAXUserParseFile (&filelist_sax_handler, &ctx, filename);
+
+    if (sctx->current_package) {
+        g_warning ("Incomplete package lost");
+        package_free (sctx->current_package);
+    }
+
+    if (ctx.current_file)
+        g_free (ctx.current_file);
+
+    g_string_free (sctx->text_buffer, TRUE);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    OTHER_PARSER_TOPLEVEL = 0,
+    OTHER_PARSER_PACKAGE,
+} OtherSAXContextState;
+
+typedef struct {
+    SAXContext sctx;
+
+    OtherSAXContextState state;
+
+    ChangelogEntry *current_entry;
+} OtherSAXContext;
+
+static void
+other_parser_toplevel_start (OtherSAXContext *ctx,
+                             const char *name,
+                             const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    if (!strcmp (name, "package")) {
+        g_assert (sctx->current_package == NULL);
+
+        ctx->state = OTHER_PARSER_PACKAGE;
+
+        sctx->current_package = package_new ();
+        parse_package (attrs, sctx->current_package);
+    }
+
+    else if (sctx->count_fn && !strcmp (name, "otherdata")) {
+        int i;
+        const char *attr;
+        const char *value;
+
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "packages")) {
+                sctx->count_fn (string_to_guint32_with_default (value, 0),
+                                sctx->user_data);
+                break;
+            }
+        }
+    }
+}
+
+static void
+other_parser_package_start (OtherSAXContext *ctx,
+                            const char *name,
+                            const char **attrs)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+    int i;
+    const char *attr;
+    const char *value;
+
+    g_assert (p != NULL);
+
+    sctx->want_text = TRUE;
+
+    if (!strcmp (name, "version")) {
+        parse_version_info(attrs, p);
+    }
+
+    else if (!strcmp (name, "changelog")) {
+        ctx->current_entry = changelog_entry_new ();
+
+        for (i = 0; attrs && attrs[i]; i++) {
+            attr = attrs[i];
+            value = attrs[++i];
+
+            if (!strcmp (attr, "author"))
+                ctx->current_entry->author =
+                    g_string_chunk_insert_const (p->chunk, value);
+            else if (!strcmp (attr, "date"))
+                ctx->current_entry->date = strtol(value, NULL, 10);
+        }
+    }
+}
+
+static void
+other_sax_start_element (void *data, const char *name, const char **attrs)
+{
+    OtherSAXContext *ctx = (OtherSAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    if (sctx->text_buffer->len)
+        g_string_truncate (sctx->text_buffer, 0);
+
+    switch (ctx->state) {
+    case OTHER_PARSER_TOPLEVEL:
+        other_parser_toplevel_start (ctx, name, attrs);
+        break;
+    case OTHER_PARSER_PACKAGE:
+        other_parser_package_start (ctx, name, attrs);
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+other_parser_package_end (OtherSAXContext *ctx, const char *name)
+{
+    SAXContext *sctx = &ctx->sctx;
+
+    Package *p = sctx->current_package;
+
+    g_assert (p != NULL);
+
+    sctx->want_text = FALSE;
+
+    if (!strcmp (name, "package")) {
+
+        if (p->changelogs)
+            p->changelogs = g_slist_reverse (p->changelogs);
+
+        if (sctx->package_fn && !*sctx->error)
+            sctx->package_fn (p, sctx->user_data);
+
+        package_free (p);
+        sctx->current_package = NULL;
+
+        if (ctx->current_entry) {
+            g_free (ctx->current_entry);
+            ctx->current_entry = NULL;
+        }
+
+        ctx->state = OTHER_PARSER_TOPLEVEL;
+    }
+
+    else if (!strcmp (name, "changelog")) {
+        ctx->current_entry->changelog =
+            g_string_chunk_insert_len (p->chunk,
+                                       sctx->text_buffer->str,
+                                       sctx->text_buffer->len);
+
+        p->changelogs = g_slist_prepend (p->changelogs, ctx->current_entry);
+        ctx->current_entry = NULL;
+    }
+}
+
+static void
+other_sax_end_element (void *data, const char *name)
+{
+    OtherSAXContext *ctx = (OtherSAXContext *) data;
+    SAXContext *sctx = &ctx->sctx;
+
+    switch (ctx->state) {
+    case OTHER_PARSER_PACKAGE:
+        other_parser_package_end (ctx, name);
+        break;
+    default:
+        break;
+    }
+
+    g_string_truncate (sctx->text_buffer, 0);
+}
+
+static xmlSAXHandler other_sax_handler = {
+    NULL,      /* internalSubset */
+    NULL,      /* isStandalone */
+    NULL,      /* hasInternalSubset */
+    NULL,      /* hasExternalSubset */
+    NULL,      /* resolveEntity */
+    NULL,      /* getEntity */
+    NULL,      /* entityDecl */
+    NULL,      /* notationDecl */
+    NULL,      /* attributeDecl */
+    NULL,      /* elementDecl */
+    NULL,      /* unparsedEntityDecl */
+    NULL,      /* setDocumentLocator */
+    NULL,      /* startDocument */
+    NULL,      /* endDocument */
+    (startElementSAXFunc) other_sax_start_element, /* startElement */
+    (endElementSAXFunc) other_sax_end_element,     /* endElement */
+    NULL,      /* reference */
+    (charactersSAXFunc) sax_characters,      /* characters */
+    NULL,      /* ignorableWhitespace */
+    NULL,      /* processingInstruction */
+    NULL,      /* comment */
+    sax_warning,      /* warning */
+    sax_error,      /* error */
+    sax_error,      /* fatalError */
+};
+
+void
+yum_xml_parse_other (const char *filename,
+                     CountFn count_callback,
+                     PackageFn package_callback,
+                     gpointer user_data,
+                     GError **err)
+{
+    OtherSAXContext ctx;
+    SAXContext *sctx = &ctx.sctx;
+
+    int rc;
+
+    ctx.state = OTHER_PARSER_TOPLEVEL;
+    ctx.current_entry = NULL;
+    
+    sax_context_init(sctx, "other.xml", count_callback, package_callback,
+                     user_data, err);
+
+    xmlSubstituteEntitiesDefault (1);
+    rc = xmlSAXUserParseFile (&other_sax_handler, &ctx, filename);
+
+    if (sctx->current_package) {
+        g_warning ("Incomplete package lost");
+        package_free (sctx->current_package);
+    }
+
+    if (ctx.current_entry)
+        g_free (ctx.current_entry);
+
+    g_string_free (sctx->text_buffer, TRUE);
+}
diff --git a/xml-parser.h b/xml-parser.h
new file mode 100644 (file)
index 0000000..fa2c07d
--- /dev/null
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef __YUM_XML_PARSER_H__
+#define __YUM_XML_PARSER_H__
+
+#include "package.h"
+
+typedef void (*CountFn) (guint32 count, gpointer data);
+
+#define YUM_PARSER_ERROR yum_parser_error_quark()
+GQuark yum_parser_error_quark (void);
+
+void
+yum_xml_parse_primary (const char *filename,
+                       CountFn count_callback,
+                       PackageFn package_callback,
+                       gpointer user_data,
+                       GError **err);
+
+void
+yum_xml_parse_filelists (const char *filename,
+                         CountFn count_callback,
+                         PackageFn package_callback,
+                         gpointer user_data,
+                         GError **err);
+
+void yum_xml_parse_other (const char *filename,
+                          CountFn count_callback,
+                          PackageFn package_callback,
+                          gpointer user_data,
+                          GError **err);
+
+#endif /* __YUM_XML_PARSER_H__ */
diff --git a/yum-metadata-parser.spec b/yum-metadata-parser.spec
new file mode 100644 (file)
index 0000000..2d40714
--- /dev/null
@@ -0,0 +1,88 @@
+%{!?python_sitelib_platform: %define python_sitelib_platform %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
+
+Summary: A fast metadata parser for yum
+Name: yum-metadata-parser
+Version: 1.1.4
+Release: 1
+Source0: %{name}-%{version}.tar.gz
+License: GPL
+Group: Development/Libraries
+URL: http://devel.linux.duke.edu/cgi-bin/viewcvs.cgi/yum-metadata-parser/
+Requires: yum >= 2.6.2
+BuildRequires: python-devel
+BuildRequires: glib2-devel
+BuildRequires: libxml2-devel
+BuildRequires: sqlite-devel
+BuildRequires: pkgconfig
+BuildRoot:  %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+%description
+Fast metadata parser for yum implemented in C.
+
+%prep
+%setup
+
+%build
+%{__python} setup.py build
+
+%install
+%{__python} setup.py install -O1 --root=%{buildroot}
+
+%clean
+%{__rm} -rf %{buildroot}
+
+%files
+%defattr(-,root,root)
+%doc README AUTHORS ChangeLog
+%{python_sitelib_platform}/_sqlitecache.so
+%{python_sitelib_platform}/sqlitecachec.py
+%{python_sitelib_platform}/sqlitecachec.pyc
+%{python_sitelib_platform}/sqlitecachec.pyo
+%{python_sitelib_platform}/*egg-info
+
+
+%changelog
+* Thu Jan  7 2010 Seth Vidal <skvidal at fedoraproject.org>
+- 1.1.4 b/c I made a mistake
+
+* Thu Jan  7 2010 Seth Vidal <skvidal at fedoraproject.org>
+- add the egginfo support for python 2.5 and above
+
+* Thu Jan  7 2010 Seth Vidal <skvidal at fedoraproject.org> 
+- bump the version number for an official release
+
+* Wed Aug 29 2007 Seth Vidal <skvidal at fedoraproject.org>
+- remove the %{dist} which doesn't really belong in the upstream pkg
+
+* Fri Aug 24 2007 Seth Vidal <skvidal at fedoraproject.org>
+- 1.1.2
+
+* Wed May 16 2007 Paul Nasrat <pnasrat at redhat.com>
+- Expose DBVERSION
+
+* Fri Apr 27 2007 Seth Vidal <skvidal at linux.duke.edu>
+- split out 1.1.0 for dbversion 10
+
+* Wed Apr  4 2007 Seth Vidal <skvidal at linux.duke.edu>
+- 1.0.4
+
+* Sun Jan  7 2007 Seth Vidal <skvidal at linux.duke.edu>
+- 1.0.3
+
+* Wed Jul 12 2006 Seth Vidal <skvidal at linux.duke.edu>
+- 1.0.2
+
+* Mon Jun 19 2006 Seth Vidal <skvidal at linux.duke.edu>
+- 1.0.1
+
+* Mon Jun 05 2006 Tambet Ingo <tambet@ximian.com> - 1.0-3
+- Require yum >= 2.6.2
+
+* Sat Jun 04 2006 Terje Rosten <terje.rosten@pvv.org> - 1.0-2
+- add buildrequires
+- doc files
+- url
+
+* Fri Jun 02 2006 Terje Rosten <terje.rosten@pvv.org> - 1.0-0.1
+- initial package
+