Python: Experimantal implementation of Python bindings + tests.
authorTomas Mlcoch <tmlcoch@redhat.com>
Mon, 6 May 2013 12:04:22 +0000 (14:04 +0200)
committerTomas Mlcoch <tmlcoch@redhat.com>
Mon, 6 May 2013 12:04:22 +0000 (14:04 +0200)
43 files changed:
CMakeLists.txt
README.md
src/CMakeLists.txt
src/python/CMakeLists.txt [new file with mode: 0644]
src/python/__init__.py [new file with mode: 0644]
src/python/createrepo_cmodule.c [new file with mode: 0644]
src/python/exception-py.c [new file with mode: 0644]
src/python/exception-py.h [new file with mode: 0644]
src/python/load_metadata-py.c [new file with mode: 0644]
src/python/load_metadata-py.h [new file with mode: 0644]
src/python/locate_metadata-py.c [new file with mode: 0644]
src/python/locate_metadata-py.h [new file with mode: 0644]
src/python/metadata_hashtable.c [new file with mode: 0644]
src/python/metadata_hashtable.h [new file with mode: 0644]
src/python/package-py.c [new file with mode: 0644]
src/python/package-py.h [new file with mode: 0644]
src/python/parsepkg-py.c [new file with mode: 0644]
src/python/parsepkg-py.h [new file with mode: 0644]
src/python/repomd-py.c [new file with mode: 0644]
src/python/repomd-py.h [new file with mode: 0644]
src/python/repomdrecord-py.c [new file with mode: 0644]
src/python/repomdrecord-py.h [new file with mode: 0644]
src/python/sqlite-py.c [new file with mode: 0644]
src/python/sqlite-py.h [new file with mode: 0644]
src/python/typeconversion.c [new file with mode: 0644]
src/python/typeconversion.h [new file with mode: 0644]
src/python/xml_dump-py.c [new file with mode: 0644]
src/python/xml_dump-py.h [new file with mode: 0644]
tests/CMakeLists.txt
tests/python/CMakeLists.txt [new file with mode: 0644]
tests/python/tests/CMakeLists.txt [new file with mode: 0644]
tests/python/tests/__init__.py [new file with mode: 0644]
tests/python/tests/fixtures.py [new file with mode: 0644]
tests/python/tests/run_nosetests.sh.in [new file with mode: 0755]
tests/python/tests/test_load_metadata.py [new file with mode: 0644]
tests/python/tests/test_locate_metadata.py [new file with mode: 0644]
tests/python/tests/test_package.py [new file with mode: 0644]
tests/python/tests/test_parsepkg.py [new file with mode: 0644]
tests/python/tests/test_repomd.py [new file with mode: 0644]
tests/python/tests/test_repomdrecord.py [new file with mode: 0644]
tests/python/tests/test_sqlite.py [new file with mode: 0644]
tests/python/tests/test_version.py [new file with mode: 0644]
tests/run_gtester.sh.in [new file with mode: 0755]

index 53b6f89..1ceea1c 100644 (file)
@@ -115,4 +115,5 @@ ADD_CUSTOM_TARGET(tests)
 
 ADD_SUBDIRECTORY (src)
 ADD_SUBDIRECTORY (doc)
+ENABLE_TESTING()
 ADD_SUBDIRECTORY (tests EXCLUDE_FROM_ALL)
index 80e400e..6a352bd 100644 (file)
--- a/README.md
+++ b/README.md
@@ -51,6 +51,22 @@ Modify createrepo_c.spec and run:
 
     utils/make_rpm.sh .
 
+## Testing
+
+All unit tests run from librepo checkout dir
+
+### Build C tests && run c and python tests
+
+    make tests && make test
+
+### Run (from your checkout dir) - C unittests:
+
+    build/tests/run_gtester.sh
+
+### Run (from your checkout dir) - Python unittests:
+
+    PYTHONPATH=`readlink -f ./build/src/python/` nosetests -s tests/python/tests/
+
 ---------------------------------------------------
 
 # Differences in behavior between createrepo_c and createrepo
index c68e6d8..8c6e526 100644 (file)
@@ -71,3 +71,5 @@ INSTALL(FILES "createrepo_c.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig")
 INSTALL(TARGETS libcreaterepo_c LIBRARY DESTINATION ${LIB_INSTALL_DIR})
 INSTALL(TARGETS createrepo_c DESTINATION bin/)
 INSTALL(TARGETS mergerepo_c DESTINATION bin/)
+
+ADD_SUBDIRECTORY(python)
diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ed69687
--- /dev/null
@@ -0,0 +1,41 @@
+FIND_PACKAGE (PythonLibs)
+FIND_PACKAGE (PythonInterp REQUIRED)
+EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c "from sys import stdout; from distutils import sysconfig; stdout.write(sysconfig.get_python_lib(True))" OUTPUT_VARIABLE PYTHON_INSTALL_DIR)
+INCLUDE_DIRECTORIES (${PYTHON_INCLUDE_PATH})
+
+MESSAGE(STATUS "Python install dir is ${PYTHON_INSTALL_DIR}")
+
+set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-strict-aliasing")
+set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-strict-aliasing")
+set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-strict-aliasing")
+
+SET (createrepo_c_COPIES __init__.py)
+FILE(COPY ${createrepo_c_COPIES} DESTINATION ./createrepo_c/)
+
+SET (craeterepo_cmodule_SRCS
+     createrepo_cmodule.c
+     exception-py.c
+     load_metadata-py.c
+     locate_metadata-py.c
+     #metadata_hashtable.c
+     package-py.c
+     parsepkg-py.c
+     repomd-py.c
+     repomdrecord-py.c
+     sqlite-py.c
+     typeconversion.c
+     xml_dump-py.c
+    )
+
+ADD_LIBRARY(_createrepo_cmodule SHARED ${craeterepo_cmodule_SRCS})
+SET_TARGET_PROPERTIES(_createrepo_cmodule PROPERTIES PREFIX "")
+SET_TARGET_PROPERTIES(_createrepo_cmodule PROPERTIES LIBRARY_OUTPUT_DIRECTORY "./createrepo_c")
+TARGET_LINK_LIBRARIES(_createrepo_cmodule libcreaterepo_c)
+TARGET_LINK_LIBRARIES(_createrepo_cmodule
+                        ${EXPAT_LIBRARIES}
+                        ${CURL_LIBRARY}
+                        ${PYTHON_LIBRARY}
+                     )
+
+INSTALL(FILES __init__.py DESTINATION ${PYTHON_INSTALL_DIR}/createrepo_c)
+INSTALL(TARGETS _createrepo_cmodule LIBRARY DESTINATION ${PYTHON_INSTALL_DIR}/createrepo_c)
diff --git a/src/python/__init__.py b/src/python/__init__.py
new file mode 100644 (file)
index 0000000..f1cae00
--- /dev/null
@@ -0,0 +1,93 @@
+"""
+"""
+
+import _createrepo_c
+
+VERSION_MAJOR = _createrepo_c.VERSION_MAJOR
+VERSION_MINOR = _createrepo_c.VERSION_MINOR
+VERSION_PATCH = _createrepo_c.VERSION_PATCH
+VERSION = u"%d.%d.%d" % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
+
+MD5     = _createrepo_c.MD5
+SHA1    = _createrepo_c.SHA1
+SHA256  = _createrepo_c.SHA256
+
+AUTO_DETECT_COMPRESSION = _createrepo_c.AUTO_DETECT_COMPRESSION
+UNKNOWN_COMPRESSION     = _createrepo_c.UNKNOWN_COMPRESSION
+NO_COMPRESSION          = _createrepo_c.NO_COMPRESSION
+GZ_COMPRESSION          = _createrepo_c.GZ_COMPRESSION
+BZ2_COMPRESSION         = _createrepo_c.BZ2_COMPRESSION
+XZ_COMPRESSION          = _createrepo_c.XZ_COMPRESSION
+
+HT_KEY_DEFAULT  = _createrepo_c.HT_KEY_DEFAULT
+HT_KEY_HASH     = _createrepo_c.HT_KEY_HASH
+HT_KEY_NAME     = _createrepo_c.HT_KEY_NAME
+HT_KEY_FILENAME = _createrepo_c.HT_KEY_FILENAME
+
+DB_PRIMARY      = _createrepo_c.DB_PRIMARY
+DB_FILELISTS    = _createrepo_c.DB_FILELISTS
+DB_OTHER        = _createrepo_c.DB_OTHER
+
+CreaterepoCException = _createrepo_c.CreaterepoCException
+
+# Metadata class
+
+Metadata = _createrepo_c.Metadata
+
+# MetadataLocation class
+
+MetadataLocation = _createrepo_c.MetadataLocation
+
+# Package class
+
+class Package(_createrepo_c.Package):
+    def __copy__(self):
+        return self.copy()
+    def __deepcopy__(self, _):
+        return self.copy()
+
+# Repomd class
+
+Repomd = _createrepo_c.Repomd
+
+# RepomdRecord class
+
+class RepomdRecord(_createrepo_c.RepomdRecord):
+    def compress_and_fill(self, hashtype, compresstype):
+        rec = RepomdRecord("")
+        _createrepo_c.RepomdRecord.compress_and_fill(self, rec, hashtype, compresstype)
+        return rec
+
+
+# Sqlite class
+
+Sqlite = _createrepo_c.Sqlite
+
+class PrimarySqlite(Sqlite):
+    def __init__(self, filename):
+        Sqlite.__init__(self, filename, DB_PRIMARY)
+
+class FilelistsSqlite(Sqlite):
+    def __init__(self, filename):
+        Sqlite.__init__(self, filename, DB_FILELISTS)
+
+class OtherSqlite(Sqlite):
+    def __init__(self, filename):
+        Sqlite.__init__(self, filename, DB_OTHER)
+
+# Methods
+
+xml_dump_primary = _createrepo_c.xml_dump_primary
+xml_dump_filelists = _createrepo_c.xml_dump_filelists
+xml_dump_other = _createrepo_c.xml_dump_other
+xml_dump = _createrepo_c.xml_dump
+
+def package_from_rpm(filename, checksum_type=SHA256, location_href=None,
+                     location_base=None, changelog_limit=10):
+    return _createrepo_c.package_from_rpm(filename, checksum_type,
+                      location_href, location_base, changelog_limit)
+
+def xml_from_rpm(filename, checksum_type=SHA256, location_href=None,
+                     location_base=None, changelog_limit=10):
+    return _createrepo_c.xml_from_rpm(filename, checksum_type,
+                      location_href, location_base, changelog_limit)
diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c
new file mode 100644 (file)
index 0000000..500b042
--- /dev/null
@@ -0,0 +1,135 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2012-2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+
+#include "src/createrepo_c.h"
+
+#include "exception-py.h"
+#include "load_metadata-py.h"
+#include "locate_metadata-py.h"
+#include "package-py.h"
+#include "parsepkg-py.h"
+#include "repomd-py.h"
+#include "repomdrecord-py.h"
+#include "sqlite-py.h"
+#include "xml_dump-py.h"
+
+static struct PyMethodDef createrepo_c_methods[] = {
+    {"package_from_rpm",        (PyCFunction)py_package_from_rpm,
+     METH_VARARGS | METH_KEYWORDS, NULL},
+    {"xml_from_rpm",            (PyCFunction)py_xml_from_rpm,
+     METH_VARARGS | METH_KEYWORDS, NULL},
+    {"xml_dump_primary",        (PyCFunction)py_xml_dump_primary,
+     METH_VARARGS, NULL},
+    {"xml_dump_filelists",      (PyCFunction)py_xml_dump_filelists,
+     METH_VARARGS, NULL},
+    {"xml_dump_other",          (PyCFunction)py_xml_dump_other,
+     METH_VARARGS, NULL},
+    {"xml_dump",                (PyCFunction)py_xml_dump,
+     METH_VARARGS, NULL},
+    { NULL }
+};
+
+PyMODINIT_FUNC
+init_createrepo_c(void)
+{
+    PyObject *m = Py_InitModule("_createrepo_c", createrepo_c_methods);
+    if (!m)
+        return;
+
+    /* Exceptions */
+    if (!init_exceptions())
+        return;
+    PyModule_AddObject(m, "CreaterepoCException", CrErr_Exception);
+
+    /* Objects */
+
+    /* _createrepo_c.Package */
+    if (PyType_Ready(&Package_Type) < 0)
+        return;
+    Py_INCREF(&Package_Type);
+    PyModule_AddObject(m, "Package", (PyObject *)&Package_Type);
+
+    /* _createrepo_c.Metadata */
+    if (PyType_Ready(&Metadata_Type) < 0)
+        return;
+    Py_INCREF(&Metadata_Type);
+    PyModule_AddObject(m, "Metadata", (PyObject *)&Metadata_Type);
+
+    /* _createrepo_c.MetadataLocation */
+    if (PyType_Ready(&MetadataLocation_Type) < 0)
+        return;
+    Py_INCREF(&MetadataLocation_Type);
+    PyModule_AddObject(m, "MetadataLocation", (PyObject *)&MetadataLocation_Type);
+
+    /* _createrepo_c.Repomd */
+    if (PyType_Ready(&Repomd_Type) < 0)
+        return;
+    Py_INCREF(&Repomd_Type);
+    PyModule_AddObject(m, "Repomd", (PyObject *)&Repomd_Type);
+
+    /* _createrepo_c.RepomdRecord */
+    if (PyType_Ready(&RepomdRecord_Type) < 0)
+        return;
+    Py_INCREF(&RepomdRecord_Type);
+    PyModule_AddObject(m, "RepomdRecord", (PyObject *)&RepomdRecord_Type);
+
+    /* _createrepo_c.Sqlite */
+    if (PyType_Ready(&Sqlite_Type) < 0)
+        return;
+    Py_INCREF(&Sqlite_Type);
+    PyModule_AddObject(m, "Sqlite", (PyObject *)&Sqlite_Type);
+
+    /* Createrepo init */
+
+    cr_xml_dump_init();
+    cr_package_parser_init();
+
+    /* Module constants */
+
+    /* Version */
+    PyModule_AddIntConstant(m, "VERSION_MAJOR", CR_VERSION_MAJOR);
+    PyModule_AddIntConstant(m, "VERSION_MINOR", CR_VERSION_MINOR);
+    PyModule_AddIntConstant(m, "VERSION_PATCH", CR_VERSION_PATCH);
+
+    /* Checksum types */
+    PyModule_AddIntConstant(m, "MD5", CR_CHECKSUM_MD5);
+    PyModule_AddIntConstant(m, "SHA1", CR_CHECKSUM_SHA1);
+    PyModule_AddIntConstant(m, "SHA256", CR_CHECKSUM_SHA256);
+
+    /* Compression types */
+    PyModule_AddIntConstant(m, "AUTO_DETECT_COMPRESSION", CR_CW_AUTO_DETECT_COMPRESSION);
+    PyModule_AddIntConstant(m, "UNKNOWN_COMPRESSION", CR_CW_UNKNOWN_COMPRESSION);
+    PyModule_AddIntConstant(m, "NO_COMPRESSION", CR_CW_NO_COMPRESSION);
+    PyModule_AddIntConstant(m, "GZ_COMPRESSION", CR_CW_GZ_COMPRESSION);
+    PyModule_AddIntConstant(m, "BZ2_COMPRESSION", CR_CW_BZ2_COMPRESSION);
+    PyModule_AddIntConstant(m, "XZ_COMPRESSION", CR_CW_XZ_COMPRESSION);
+
+    /* Load Metadata key values */
+    PyModule_AddIntConstant(m, "HT_KEY_DEFAULT", CR_HT_KEY_DEFAULT);
+    PyModule_AddIntConstant(m, "HT_KEY_HASH", CR_HT_KEY_HASH);
+    PyModule_AddIntConstant(m, "HT_KEY_NAME", CR_HT_KEY_NAME);
+    PyModule_AddIntConstant(m, "HT_KEY_FILENAME", CR_HT_KEY_FILENAME);
+
+    /* Sqlite DB types */
+    PyModule_AddIntConstant(m, "DB_PRIMARY", CR_DB_PRIMARY);
+    PyModule_AddIntConstant(m, "DB_FILELISTS", CR_DB_FILELISTS);
+    PyModule_AddIntConstant(m, "DB_OTHER", CR_DB_OTHER);
+}
diff --git a/src/python/exception-py.c b/src/python/exception-py.c
new file mode 100644 (file)
index 0000000..c1d1f63
--- /dev/null
@@ -0,0 +1,34 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2012-2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include "exception-py.h"
+
+PyObject *CrErr_Exception = NULL;
+
+int
+init_exceptions()
+{
+    CrErr_Exception = PyErr_NewException("_createrepo_c.Exception", NULL, NULL);
+    if (!CrErr_Exception)
+        return 0;
+    Py_INCREF(CrErr_Exception);
+
+    return 1;
+}
diff --git a/src/python/exception-py.h b/src/python/exception-py.h
new file mode 100644 (file)
index 0000000..dfeb02c
--- /dev/null
@@ -0,0 +1,29 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2012-2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_EXCEPTION_PY_H
+#define CR_EXCEPTION_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyObject *CrErr_Exception;
+
+int init_exceptions();
+
+#endif
diff --git a/src/python/load_metadata-py.c b/src/python/load_metadata-py.c
new file mode 100644 (file)
index 0000000..74da3b6
--- /dev/null
@@ -0,0 +1,339 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "load_metadata-py.h"
+#include "locate_metadata-py.h"
+#include "package-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+#include "metadata_hashtable.h"
+
+/* TODO:
+ * keys() and records() method (same method - alias only)
+ **/
+
+typedef struct {
+    PyObject_HEAD
+    cr_Metadata md;
+} _MetadataObject;
+
+static int
+check_MetadataStatus(const _MetadataObject *self)
+{
+    assert(self != NULL);
+    assert(MetadataObject_Check(self));
+    if (self->md == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c Metadata object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+metadata_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+
+    _MetadataObject *self = (_MetadataObject *)type->tp_alloc(type, 0);
+    if (self)
+        self->md = NULL;
+    return (PyObject *)self;
+}
+
+static int
+metadata_init(_MetadataObject *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = { "key", "use_single_chunk", "pkglist", NULL };
+    int key = CR_HT_KEY_DEFAULT;
+    int use_single_chunk = 0;
+    PyObject *py_pkglist = NULL;
+    GSList *pkglist = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiO!:metadata_init", kwlist,
+                          &key, &use_single_chunk, &PyList_Type, &py_pkglist))
+        return -1;
+
+    /* Free all previous resources when reinitialization */
+    if (self->md) {
+        cr_metadata_free(self->md);
+    }
+
+    /* Init */
+    pkglist = GSList_FromPyList_Str(py_pkglist);
+    self->md = cr_metadata_new(key, use_single_chunk, pkglist);
+    g_slist_free(pkglist);
+    if (self->md == NULL) {
+        PyErr_SetString(CrErr_Exception, "Metadata initialization failed");
+        return -1;
+    }
+    return 0;
+}
+
+static void
+metadata_dealloc(_MetadataObject *self)
+{
+    if (self->md)
+        cr_metadata_free(self->md);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+metadata_repr(_MetadataObject *self)
+{
+    CR_UNUSED(self);
+    return PyString_FromFormat("<createrepo_c.Metadata object>");
+}
+
+/* Getters */
+
+static PyObject *
+get_key(_MetadataObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    if (check_MetadataStatus(self))
+        return NULL;
+    cr_HashTableKey val = self->md->key;
+    return PyLong_FromLong((long) val);
+}
+
+/*
+static PyObject *
+get_ht(_MetadataObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    if (check_MetadataStatus(self))
+        return NULL;
+    return Object_FromGHashtable((PyObject *) self, self->md->ht);
+}
+*/
+
+static PyGetSetDef metadata_getsetters[] = {
+    {"key", (getter)get_key, NULL, NULL, NULL},
+//    {"ht",  (getter)get_ht, NULL, NULL, NULL},
+    {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+/* Metadata methods */
+
+static PyObject *
+load_xml(_MetadataObject *self, PyObject *args)
+{
+    int rc;
+    PyObject *ml;
+
+    if (!PyArg_ParseTuple(args, "O!:load_xml", &MetadataLocation_Type, &ml))
+        return NULL;
+    if (check_MetadataStatus(self))
+        return NULL;
+
+    rc = cr_metadata_load_xml(self->md, MetadataLocation_FromPyObject(ml));
+    if (rc != CR_LOAD_METADATA_OK) {
+        PyErr_SetString(CrErr_Exception, "Cannot load metadata");
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+locate_and_load_xml(_MetadataObject *self, PyObject *args)
+{
+    int rc;
+    char *path;
+
+    if (!PyArg_ParseTuple(args, "s:locate_and_load_xml", &path))
+        return NULL;
+    if (check_MetadataStatus(self))
+        return NULL;
+
+    rc = cr_metadata_locate_and_load_xml(self->md, path);
+    if (rc != CR_LOAD_METADATA_OK) {
+        PyErr_SetString(CrErr_Exception, "Cannot load metadata");
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+/* Hashtable methods */
+
+static PyObject *
+ht_len(_MetadataObject *self, PyObject *noarg)
+{
+    CR_UNUSED(noarg);
+    unsigned long len = 0;
+    if (check_MetadataStatus(self))
+        return NULL;
+    if (self->md->ht)
+        len = (unsigned long) g_hash_table_size(self->md->ht);
+    return PyLong_FromUnsignedLong(len);
+}
+
+/*
+static PyObject *
+ht_add(_MetadataObject *self, PyObject *args)
+{
+    char *key;
+    PyObject *py_pkg;
+    cr_Package *pkg;
+
+    if (!PyArg_ParseTuple(args, "sO!:add", &key, &Package_Type, &pkg))
+        return NULL;
+    if (check_MetadataHashtableStatus(self))
+        return NULL;
+
+    pkg = Package_FromPyObject(pkg);
+    if (!pkg)
+        Py_RETURN_NONE;
+
+    Py_XINCREF(py_pkg);
+    // XXX: Store referenced object for Py_XDECREF!!!!!
+    g_hash_table_replace(self->md->ht, key, pkg);
+    Py_RETURN_NONE;
+}
+*/
+
+static PyObject *
+ht_has_key(_MetadataObject *self, PyObject *args)
+{
+    char *key;
+
+    if (!PyArg_ParseTuple(args, "s:has_key", &key))
+        return NULL;
+    if (check_MetadataStatus(self))
+        return NULL;
+
+    if (g_hash_table_lookup(self->md->ht, key))
+        Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+ht_keys(_MetadataObject *self, PyObject *args)
+{
+    CR_UNUSED(args);
+
+    if (check_MetadataStatus(self))
+        return NULL;
+    GList *keys = g_hash_table_get_keys(self->md->ht);
+    PyObject *list = PyList_New(0);
+    for (GList *elem = keys; elem; elem = g_list_next(elem)) {
+        PyObject *py_str = PyString_FromString(elem->data);
+        assert(py_str);
+        if (PyList_Append(list, py_str) == -1) {
+            Py_XDECREF(list);
+            return NULL;
+        }
+    }
+    return list;
+}
+
+static PyObject *
+ht_remove(_MetadataObject *self, PyObject *args)
+{
+    char *key;
+
+    if (!PyArg_ParseTuple(args, "s:del", &key))
+        return NULL;
+    if (check_MetadataStatus(self))
+        return NULL;
+
+    if (g_hash_table_remove(self->md->ht, key))
+        Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+ht_get(_MetadataObject *self, PyObject *args)
+{
+    char *key;
+
+    if (!PyArg_ParseTuple(args, "s:get", &key))
+        return NULL;
+    if (check_MetadataStatus(self))
+        return NULL;
+
+    cr_Package *pkg = g_hash_table_lookup(self->md->ht, key);
+    if (!pkg)
+        Py_RETURN_NONE;
+    return (Object_FromPackage_WithParent(pkg, 0, (PyObject *) self));
+}
+
+static struct PyMethodDef metadata_methods[] = {
+    {"load_xml", (PyCFunction)load_xml, METH_VARARGS, NULL},
+    {"locate_and_load_xml", (PyCFunction)locate_and_load_xml, METH_VARARGS, NULL},
+    {"len",     (PyCFunction)ht_len, METH_NOARGS, NULL},
+//    {"add",     (PyCFunction)ht_add, METH_VARARGS, NULL},
+    {"has_key", (PyCFunction)ht_has_key, METH_VARARGS, NULL},
+    {"keys",    (PyCFunction)ht_keys, METH_NOARGS, NULL},
+    {"remove",  (PyCFunction)ht_remove, METH_VARARGS, NULL},
+    {"get",     (PyCFunction)ht_get, METH_VARARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+/* Object */
+
+PyTypeObject Metadata_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.Metadata",            /* tp_name */
+    sizeof(_MetadataObject),        /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor)metadata_dealloc,   /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc)metadata_repr,        /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "Metadata object",              /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    metadata_methods,               /* tp_methods */
+    0,                              /* tp_members */
+    metadata_getsetters,            /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc)metadata_init,        /* tp_init */
+    0,                              /* tp_alloc */
+    metadata_new,                   /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/load_metadata-py.h b/src/python/load_metadata-py.h
new file mode 100644 (file)
index 0000000..36cb51d
--- /dev/null
@@ -0,0 +1,29 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_LOAD_METADATA_PY_H
+#define CR_LOAD_METADATA_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject Metadata_Type;
+
+#define MetadataObject_Check(o)   PyObject_TypeCheck(o, &Metadata_Type)
+
+#endif
diff --git a/src/python/locate_metadata-py.c b/src/python/locate_metadata-py.c
new file mode 100644 (file)
index 0000000..90463aa
--- /dev/null
@@ -0,0 +1,216 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "locate_metadata-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+typedef struct {
+    PyObject_HEAD
+    struct cr_MetadataLocation *ml;
+} _MetadataLocationObject;
+
+struct cr_MetadataLocation *
+MetadataLocation_FromPyObject(PyObject *o)
+{
+    if (!MetadataLocationObject_Check(o)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a createrepo_c.MetadataLocation object.");
+        return NULL;
+    }
+    return ((_MetadataLocationObject *) o)->ml;
+}
+
+static int
+check_MetadataLocationStatus(const _MetadataLocationObject *self)
+{
+    assert(self != NULL);
+    assert(MetadataLocationObject_Check(self));
+    if (self->ml == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c MetadataLocation object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+metadatalocation_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+
+    _MetadataLocationObject *self = (_MetadataLocationObject *)type->tp_alloc(type, 0);
+    if (self)
+        self->ml = NULL;
+    return (PyObject *)self;
+}
+
+static int
+metadatalocation_init(_MetadataLocationObject *self, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(kwds);
+    char *repopath;
+    int ignore_db;
+
+    if (!PyArg_ParseTuple(args, "si|:metadatalocation_init", &repopath, &ignore_db))
+        return -1;
+
+    /* Free all previous resources when reinitialization */
+    if (self->ml) {
+        cr_metadatalocation_free(self->ml);
+    }
+
+    /* Init */
+    self->ml = cr_locate_metadata(repopath, ignore_db);
+    if (self->ml == NULL) {
+        PyErr_SetString(CrErr_Exception, "MetadataLocation initialization failed");
+        return -1;
+    }
+    return 0;
+}
+
+static void
+metadatalocation_dealloc(_MetadataLocationObject *self)
+{
+    if (self->ml)
+        cr_metadatalocation_free(self->ml);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+metadatalocation_repr(_MetadataLocationObject *self)
+{
+    CR_UNUSED(self);
+    return PyString_FromFormat("<createrepo_c.MetadataLocation object>");
+}
+
+/* MetadataLocation methods */
+
+static struct PyMethodDef metadatalocation_methods[] = {
+    {NULL} /* sentinel */
+};
+
+/* Mapping interface */
+
+Py_ssize_t
+length(_MetadataLocationObject *self)
+{
+    if (self->ml)
+        return 9;
+    return 0;
+}
+
+PyObject *
+getitem(_MetadataLocationObject *self, PyObject *pykey)
+{
+    char *key, *value;
+
+    if (check_MetadataLocationStatus(self))
+        return NULL;
+
+    if (!PyString_Check(pykey)) {
+        PyErr_SetString(PyExc_ValueError, "String expected!");
+        return NULL;
+    }
+
+    key = PyString_AsString(pykey);
+    value = NULL;
+
+    if (!strcmp(key, "primary")) {
+        value = self->ml->pri_xml_href;
+    } else if (!strcmp(key, "filelists")) {
+        value = self->ml->fil_xml_href;
+    } else if (!strcmp(key, "other")) {
+        value = self->ml->oth_xml_href;
+    } else if (!strcmp(key, "primary_db")) {
+        value = self->ml->pri_sqlite_href;
+    } else if (!strcmp(key, "filelists_db")) {
+        value = self->ml->fil_sqlite_href;
+    } else if (!strcmp(key, "other_db")) {
+        value = self->ml->oth_sqlite_href;
+    } else if (!strcmp(key, "group")) {
+        value = self->ml->groupfile_href;
+    } else if (!strcmp(key, "group_gz")) {
+        value = self->ml->cgroupfile_href;
+    } else if (!strcmp(key, "updateinfo")) {
+        value = self->ml->updateinfo_href;
+    }
+
+    if (value)
+        return PyString_FromString(value);
+    else
+        Py_RETURN_NONE;
+}
+
+static PyMappingMethods mapping_methods = {
+    .mp_length = (lenfunc) length,
+    .mp_subscript = (binaryfunc) getitem,
+    .mp_ass_subscript = NULL,
+};
+
+/* Object */
+
+PyTypeObject MetadataLocation_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.MetadataLocation",    /* tp_name */
+    sizeof(_MetadataLocationObject),/* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor)metadatalocation_dealloc,/* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc)metadatalocation_repr,/* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    &mapping_methods,               /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "MetadataLocation object",      /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    metadatalocation_methods,       /* tp_methods */
+    0,                              /* tp_members */
+    0,                              /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc)metadatalocation_init,/* tp_init */
+    0,                              /* tp_alloc */
+    metadatalocation_new,           /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/locate_metadata-py.h b/src/python/locate_metadata-py.h
new file mode 100644 (file)
index 0000000..384b662
--- /dev/null
@@ -0,0 +1,30 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_LOCATE_METADATA_PY_H
+#define CR_LOCATE_METADATA_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject MetadataLocation_Type;
+
+#define MetadataLocationObject_Check(o)   PyObject_TypeCheck(o, &MetadataLocation_Type)
+struct cr_MetadataLocation *MetadataLocation_FromPyObject(PyObject *o);
+
+#endif
diff --git a/src/python/metadata_hashtable.c b/src/python/metadata_hashtable.c
new file mode 100644 (file)
index 0000000..421e094
--- /dev/null
@@ -0,0 +1,254 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <glib.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "metadata_hashtable.h"
+#include "package-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+/* TODO:
+ * Add support for iteration
+ **/
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *owner;
+    GHashTable *ht;
+} _MetadataHashtableObject;
+
+PyObject *
+Object_FromGHashtable(PyObject *owner, GHashTable *ht)
+{
+    PyObject *pyht;
+
+    if (!ht) {
+        PyErr_SetString(PyExc_TypeError, "Expected a hash table pointer not NULL.");
+        return NULL;
+    }
+
+    Py_XINCREF(owner);
+    ((_MetadataHashtableObject *) pyht)->owner = owner;
+    pyht = PyObject_CallObject((PyObject*)&Package_Type, NULL);
+    ((_MetadataHashtableObject *) pyht)->ht = ht;
+    return pyht;
+}
+
+static int
+check_MetadataHashtableStatus(const _MetadataHashtableObject *self)
+{
+    assert(self != NULL);
+    assert(MetadataHashtableObject_Check(self));
+    if (self->ht == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c MetadataHashtable object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+metadata_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+
+    _MetadataHashtableObject *self = (_MetadataHashtableObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->ht = NULL;
+    }
+    return (PyObject *)self;
+}
+
+static int
+metadata_init(_MetadataHashtableObject *self, PyObject *args, PyObject *kwds)
+{
+    char *kwlist[] = {NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|:metadata_init", kwlist))
+        return -1;
+
+    /* Free all previous resources when reinitialization */
+    if (self->ht)
+        g_hash_table_destroy(self->ht);
+    Py_XDECREF(self->owner);
+    self->owner = NULL;
+    self->ht = NULL;
+    return 0;
+}
+
+static void
+metadata_dealloc(_MetadataHashtableObject *self)
+{
+    if (self->ht)
+        g_hash_table_destroy(self->ht);
+    Py_XDECREF(self->owner);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+metadata_repr(_MetadataHashtableObject *self)
+{
+    CR_UNUSED(self);
+    return PyString_FromFormat("<createrepo_c.MetadataHashtable object>");
+}
+
+/* MetadataHashtable methods */
+
+static PyObject *
+len(_MetadataHashtableObject *self, PyObject *noarg)
+{
+    CR_UNUSED(noarg);
+    unsigned long len = 0;
+    if (self->ht)
+        len = (unsigned long) g_hash_table_size(self->ht);
+    return PyLong_FromUnsignedLong(len);
+}
+
+/*
+static PyObject *
+add(_MetadataHashtableObject *self, PyObject *args)
+{
+    char *key;
+    PyObject *py_pkg;
+    cr_Package *pkg;
+
+    if (!PyArg_ParseTuple(args, "sO!:add", &key, &Package_Type, &pkg))
+        return NULL;
+    if (check_MetadataHashtableStatus(self))
+        return NULL;
+
+    pkg = Package_FromPyObject(pkg);
+    if (!pkg)
+        Py_RETURN_NONE;
+
+    Py_XINCREF(py_pkg);
+    // XXX: Store referenced object for Py_XDECREF!!!!!
+    g_hash_table_replace(self->ht, key, pkg);
+    Py_RETURN_NONE;
+}
+*/
+
+static PyObject *
+has_key(_MetadataHashtableObject *self, PyObject *args)
+{
+    char *key;
+
+    if (!PyArg_ParseTuple(args, "s:has_key", &key))
+        return NULL;
+    if (check_MetadataHashtableStatus(self))
+        return NULL;
+
+    if (g_hash_table_lookup(self->ht, key))
+        Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+}
+
+static PyObject *
+keys(_MetadataHashtableObject *self, PyObject *args)
+{
+    CR_UNUSED(args);
+
+    GList *keys = g_hash_table_get_values(self->ht);
+    PyObject *list = PyList_New(0);
+    for (GList *elem = keys; elem; elem = g_list_next(elem)) {
+        PyObject *py_str = PyString_FromString(elem->data);
+        assert(py_str);
+        if (PyList_Append(list, py_str) == -1) {
+            Py_XDECREF(list);
+            return NULL;
+        }
+    }
+    return list;
+}
+
+static PyObject *
+del(_MetadataHashtableObject *self, PyObject *args)
+{
+    char *key;
+
+    if (!PyArg_ParseTuple(args, "s:del", &key))
+        return NULL;
+    if (check_MetadataHashtableStatus(self))
+        return NULL;
+
+    if (g_hash_table_remove(self->ht, key))
+        Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+}
+
+static struct PyMethodDef metadata_methods[] = {
+    {"len", (PyCFunction)len, METH_NOARGS, NULL},
+//    {"add", (PyCFunction)add, METH_VARARGS, NULL},
+    {"has_key", (PyCFunction)has_key, METH_VARARGS, NULL},
+    {"keys", (PyCFunction)keys, METH_NOARGS, NULL},
+    {"del", (PyCFunction)del, METH_VARARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+/* Object */
+
+PyTypeObject MetadataHashtable_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.MetadataHashtable",   /* tp_name */
+    sizeof(_MetadataHashtableObject),/* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor)metadata_dealloc,   /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc)metadata_repr,        /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "MetadataHashtable object",     /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    metadata_methods,               /* tp_methods */
+    0,                              /* tp_members */
+    0,                              /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc)metadata_init,        /* tp_init */
+    0,                              /* tp_alloc */
+    metadata_new,                   /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/metadata_hashtable.h b/src/python/metadata_hashtable.h
new file mode 100644 (file)
index 0000000..05ec8af
--- /dev/null
@@ -0,0 +1,31 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_METADATA_HASHTABLE_PY_H
+#define CR_METADATA_HASHTABLE_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject MetadataHashtable_Type;
+
+#define MetadataHashtableObject_Check(o)   PyObject_TypeCheck(o, &MetadataHashtable_Type)
+
+PyObject *Object_FromGHashtable(PyObject self, GHashTable *ht);
+
+#endif
diff --git a/src/python/package-py.c b/src/python/package-py.c
new file mode 100644 (file)
index 0000000..9db2324
--- /dev/null
@@ -0,0 +1,503 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "package-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+typedef struct {
+    PyObject_HEAD
+    cr_Package *package;
+    int free_on_destroy;
+    PyObject *parent;
+} _PackageObject;
+
+cr_Package *
+Package_FromPyObject(PyObject *o)
+{
+    if (!PackageObject_Check(o)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a createrepo_c.Package object.");
+        return NULL;
+    }
+    return ((_PackageObject *)o)->package;
+}
+
+PyObject *
+Object_FromPackage(cr_Package *pkg, int free_on_destroy)
+{
+    PyObject *pypkg;
+
+    if (!pkg) {
+        PyErr_SetString(PyExc_TypeError, "Expected a cr_Package pointer not NULL.");
+        return NULL;
+    }
+
+    pypkg = PyObject_CallObject((PyObject*)&Package_Type, NULL);
+    // XXX: Remove empty package in pypkg and replace it with pkg
+    cr_package_free(((_PackageObject *)pypkg)->package);
+    ((_PackageObject *)pypkg)->package = pkg;
+    ((_PackageObject *)pypkg)->free_on_destroy = free_on_destroy;
+    ((_PackageObject *)pypkg)->parent = NULL;
+
+    return pypkg;
+}
+
+PyObject *
+Object_FromPackage_WithParent(cr_Package *pkg, int free_on_destroy, PyObject *parent)
+{
+    PyObject *pypkg;
+    pypkg = Object_FromPackage(pkg, free_on_destroy);
+    if (pypkg) {
+        ((_PackageObject *)pypkg)->parent = parent;
+        Py_XINCREF(parent);
+    }
+    return pypkg;
+}
+
+static int
+check_PackageStatus(const _PackageObject *self)
+{
+    assert(self != NULL);
+    assert(PackageObject_Check(self));
+    if (self->package == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c Package object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+package_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+    _PackageObject *self = (_PackageObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->package = NULL;
+        self->free_on_destroy = 1;
+        self->parent = NULL;
+    }
+    return (PyObject *)self;
+}
+
+static int
+package_init(_PackageObject *self, PyObject *args, PyObject *kwds)
+{
+    char *kwlist[] = {NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|:package_init", kwlist))
+        return -1;
+
+    if (self->package && self->free_on_destroy)  // reinitialization by __init__()
+        cr_package_free(self->package);
+    if (self->parent) {
+        Py_DECREF(self->parent);
+        self->parent = NULL;
+    }
+
+    self->package = cr_package_new();
+    if (self->package == NULL) {
+        PyErr_SetString(CrErr_Exception, "Package initialization failed");
+        return -1;
+    }
+    return 0;
+}
+
+static void
+package_dealloc(_PackageObject *self)
+{
+    if (self->package && self->free_on_destroy)
+        cr_package_free(self->package);
+    if (self->parent) {
+        Py_DECREF(self->parent);
+        self->parent = NULL;
+    }
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+package_repr(_PackageObject *self)
+{
+    cr_Package *pkg = self->package;
+    PyObject *repr;
+    if (pkg) {
+        repr = PyString_FromFormat("<createrepo_c.Package object id %s, %s>",
+                                   (pkg->pkgId ? pkg->pkgId : "-"),
+                                   (pkg->name  ? pkg->name  : "-"));
+    } else {
+       repr = PyString_FromFormat("<createrepo_c.Package object id -, ->");
+    }
+    return repr;
+}
+
+static PyObject *
+package_str(_PackageObject *self)
+{
+    PyObject *ret;
+    if (check_PackageStatus(self))
+        return NULL;
+    if (self->package) {
+        char *nevra = cr_package_nvra(self->package);
+        ret = PyString_FromString(nevra);
+        free(nevra);
+    } else {
+        ret = PyString_FromString("-");
+    }
+    return ret;
+}
+
+/* Package methods */
+
+static PyObject *
+nvra(_PackageObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    PyObject *pystr;
+    if (check_PackageStatus(self))
+        return NULL;
+    char *nvra = cr_package_nvra(self->package);
+    pystr = PyStringOrNone_FromString(nvra);
+    free(nvra);
+    return pystr;
+}
+
+static PyObject *
+nevra(_PackageObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    PyObject *pystr;
+    if (check_PackageStatus(self))
+        return NULL;
+    char *nevra = cr_package_nevra(self->package);
+    pystr = PyStringOrNone_FromString(nevra);
+    free(nevra);
+    return pystr;
+}
+
+static PyObject *
+copy_pkg(_PackageObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    if (check_PackageStatus(self))
+        return NULL;
+    return Object_FromPackage(cr_package_copy(self->package), 1);
+}
+
+static struct PyMethodDef package_methods[] = {
+    {"nvra", (PyCFunction)nvra, METH_NOARGS, NULL},
+    {"nevra", (PyCFunction)nevra, METH_NOARGS, NULL},
+    {"copy", (PyCFunction)copy_pkg, METH_NOARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+/* Getters */
+
+static PyObject *
+get_num(_PackageObject *self, void *member_offset)
+{
+    if (check_PackageStatus(self))
+        return NULL;
+    cr_Package *pkg = self->package;
+    gint64 val = *((gint64 *) ((size_t)pkg + (size_t) member_offset));
+    return PyLong_FromLongLong((long long) val);
+}
+
+static PyObject *
+get_str(_PackageObject *self, void *member_offset)
+{
+    if (check_PackageStatus(self))
+        return NULL;
+    cr_Package *pkg = self->package;
+    char *str = *((char **) ((size_t) pkg + (size_t) member_offset));
+    if (str == NULL)
+        Py_RETURN_NONE;
+    return PyString_FromString(str);
+}
+
+/** Return offset of a selected member of cr_Package structure. */
+#define OFFSET(member) (void *) offsetof(cr_Package, member)
+
+/** Convert C object to PyObject.
+ * @param       C object
+ * @return      PyObject representation
+ */
+typedef PyObject *(*ConversionFromFunc)(void *);
+
+/** Check an element from a list if has a valid format.
+ * @param       a single list element
+ * @return      0 if ok, 1 otherwise
+ */
+typedef int *(*ConversionToCheckFunc)(void *);
+
+/** Convert PyObject to C representation.
+ * @param       PyObject
+ * @return      C representation
+ */
+typedef void *(*ConversionToFunc)(void *, void *);
+
+/* Pre-Declaration for check functions */
+static int CheckPyDependency(PyObject *dep);
+static int CheckPyPackageFile(PyObject *dep);
+static int CheckPyChangelogEntry(PyObject *dep);
+
+typedef struct {
+    size_t offset;          /*!< Ofset of the list in cr_Package */
+    ConversionFromFunc f;   /*!< Conversion func to PyObject from a C object */
+    ConversionToCheckFunc t_check; /*!< Check func for a single element of list */
+    ConversionToFunc t;     /*!< Conversion func to C object from PyObject */
+} ListConvertor;
+
+/** List of convertors for converting a lists in cr_Package. */
+static ListConvertor list_convertors[] = {
+    { offsetof(cr_Package, requires),   PyObject_FromDependency,
+      CheckPyDependency, PyObject_ToDependency },
+    { offsetof(cr_Package, provides),   PyObject_FromDependency,
+      CheckPyDependency, PyObject_ToDependency },
+    { offsetof(cr_Package, conflicts),  PyObject_FromDependency,
+      CheckPyDependency, PyObject_ToDependency },
+    { offsetof(cr_Package, obsoletes),  PyObject_FromDependency,
+      CheckPyDependency, PyObject_ToDependency },
+    { offsetof(cr_Package, files),      PyObject_FromPackageFile,
+      CheckPyPackageFile, PyObject_ToPackageFile },
+    { offsetof(cr_Package, changelogs), PyObject_FromChangelogEntry,
+      CheckPyChangelogEntry, PyObject_ToChangelogEntry },
+};
+
+static PyObject *
+get_list(_PackageObject *self, void *conv)
+{
+    ListConvertor *convertor = conv;
+    PyObject *list;
+    cr_Package *pkg = self->package;
+    GSList *glist = *((GSList **) ((size_t) pkg + (size_t) convertor->offset));
+
+    if (check_PackageStatus(self))
+        return NULL;
+
+    if ((list = PyList_New(0)) == NULL)
+        return NULL;
+
+    for (GSList *elem = glist; elem; elem = g_slist_next(elem))
+        PyList_Append(list, convertor->f(elem->data));
+
+    return list;
+}
+
+/* Setters */
+
+static int
+set_num(_PackageObject *self, PyObject *value, void *member_offset)
+{
+    long val;
+    if (check_PackageStatus(self))
+        return -1;
+    if (PyLong_Check(value)) {
+        val = PyLong_AsLong(value);
+    } else if (PyInt_Check(value)) {
+        val = PyInt_AS_LONG(value);
+    } else {
+        PyErr_SetString(PyExc_ValueError, "Number expected!");
+        return -1;
+    }
+    cr_Package *pkg = self->package;
+    *((long *) ((size_t) pkg + (size_t) member_offset)) = val;
+    return 0;
+}
+
+static int
+set_str(_PackageObject *self, PyObject *value, void *member_offset)
+{
+    if (check_PackageStatus(self))
+        return -1;
+    if (!PyString_Check(value)) {
+        PyErr_SetString(PyExc_ValueError, "String expected!");
+        return -1;
+    }
+    cr_Package *pkg = self->package;
+
+    // Check if chunk exits
+    // If it doesn't - this is package from loaded metadata and all its
+    // strings are in a metadata common chunk (cr_Metadata->chunk).
+    // In this case, we have to create a chunk for this package before
+    // inserting a new string.
+    if (!pkg->chunk)
+        pkg->chunk = g_string_chunk_new(0);
+
+    char *str = g_string_chunk_insert(pkg->chunk, PyString_AsString(value));
+    *((char **) ((size_t) pkg + (size_t) member_offset)) = str;
+    return 0;
+}
+
+
+static int
+CheckPyDependency(PyObject *dep)
+{
+    if (!PyTuple_Check(dep) || PyTuple_Size(dep) != 6) {
+        PyErr_SetString(PyExc_ValueError, "Element of list has to be a tuple with 6 items.");
+        return 1;
+    }
+    return 0;
+}
+
+static int
+CheckPyPackageFile(PyObject *dep)
+{
+    if (!PyTuple_Check(dep) || PyTuple_Size(dep) != 3) {
+        PyErr_SetString(PyExc_ValueError, "Element of list has to be a tuple with 3 items.");
+        return 1;
+    }
+    return 0;
+}
+
+static int
+CheckPyChangelogEntry(PyObject *dep)
+{
+    if (!PyTuple_Check(dep) || PyTuple_Size(dep) != 3) {
+        PyErr_SetString(PyExc_ValueError, "Element of list has to be a tuple with 3 items.");
+        return 1;
+    }
+    return 0;
+}
+
+static int
+set_list(_PackageObject *self, PyObject *list, void *conv)
+{
+    ListConvertor *convertor = conv;
+    cr_Package *pkg = self->package;
+    GSList *glist = NULL;
+
+    if (check_PackageStatus(self))
+        return -1;
+
+    if (!PyList_Check(list)) {
+        PyErr_SetString(PyExc_ValueError, "List expected!");
+        return -1;
+    }
+
+    // Check if chunk exits
+    // If it doesn't - this is package from loaded metadata and all its
+    // strings are in a metadata common chunk (cr_Metadata->chunk).
+    // In this case, we have to create a chunk for this package before
+    // inserting a new string.
+    if (!pkg->chunk)
+        pkg->chunk = g_string_chunk_new(0);
+
+    Py_ssize_t len = PyList_Size(list);
+
+    // Check all elements
+    for (Py_ssize_t x = 0; x < len; x++) {
+        PyObject *elem = PyList_GetItem(list, x);
+        if (convertor->t_check && convertor->t_check(elem)) // XXX
+            return -1;
+    }
+
+    for (Py_ssize_t x = 0; x < len; x++) {
+        glist = g_slist_prepend(glist, convertor->t(PyList_GetItem(list, x), pkg->chunk));
+    }
+
+    *((GSList **) ((size_t) pkg + (size_t) convertor->offset)) = glist;
+    return 0;
+}
+
+static PyGetSetDef package_getsetters[] = {
+    {"pkgId",            (getter)get_str, (setter)set_str, NULL, OFFSET(pkgId)},
+    {"name",             (getter)get_str, (setter)set_str, NULL, OFFSET(name)},
+    {"arch",             (getter)get_str, (setter)set_str, NULL, OFFSET(arch)},
+    {"version",          (getter)get_str, (setter)set_str, NULL, OFFSET(version)},
+    {"epoch",            (getter)get_str, (setter)set_str, NULL, OFFSET(epoch)},
+    {"release",          (getter)get_str, (setter)set_str, NULL, OFFSET(release)},
+    {"summary",          (getter)get_str, (setter)set_str, NULL, OFFSET(summary)},
+    {"description",      (getter)get_str, (setter)set_str, NULL, OFFSET(description)},
+    {"url",              (getter)get_str, (setter)set_str, NULL, OFFSET(url)},
+    {"time_file",        (getter)get_num, (setter)set_num, NULL, OFFSET(time_file)},
+    {"time_build",       (getter)get_num, (setter)set_num, NULL, OFFSET(time_build)},
+    {"rpm_license",      (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_license)},
+    {"rpm_vendor",       (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_vendor)},
+    {"rpm_group",        (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_group)},
+    {"rpm_buildhost",    (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_buildhost)},
+    {"rpm_sourcerpm",    (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_sourcerpm)},
+    {"rpm_header_start", (getter)get_num, (setter)set_num, NULL, OFFSET(rpm_header_start)},
+    {"rpm_header_end",   (getter)get_num, (setter)set_num, NULL, OFFSET(rpm_header_end)},
+    {"rpm_packager",     (getter)get_str, (setter)set_str, NULL, OFFSET(rpm_packager)},
+    {"size_package",     (getter)get_num, (setter)set_num, NULL, OFFSET(size_package)},
+    {"size_installed",   (getter)get_num, (setter)set_num, NULL, OFFSET(size_installed)},
+    {"size_archive",     (getter)get_num, (setter)set_num, NULL, OFFSET(size_archive)},
+    {"location_href",    (getter)get_str, (setter)set_str, NULL, OFFSET(location_href)},
+    {"location_base",    (getter)get_str, (setter)set_str, NULL, OFFSET(location_base)},
+    {"checksum_type",    (getter)get_str, (setter)set_str, NULL, OFFSET(checksum_type)},
+    {"requires",         (getter)get_list, (setter)set_list, NULL, &(list_convertors[0])},
+    {"provides",         (getter)get_list, (setter)set_list, NULL, &(list_convertors[1])},
+    {"conflicts",        (getter)get_list, (setter)set_list, NULL, &(list_convertors[2])},
+    {"obsoletes",        (getter)get_list, (setter)set_list, NULL, &(list_convertors[3])},
+    {"files",            (getter)get_list, (setter)set_list, NULL, &(list_convertors[4])},
+    {"changelogs",       (getter)get_list, (setter)set_list, NULL, &(list_convertors[5])},
+    {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+/* Object */
+
+PyTypeObject Package_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.Package",             /* tp_name */
+    sizeof(_PackageObject),         /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor) package_dealloc,   /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc) package_repr,        /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    (reprfunc)package_str,          /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "Package object",               /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    package_methods,                /* tp_methods */
+    0,                              /* tp_members */
+    package_getsetters,             /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc) package_init,        /* tp_init */
+    0,                              /* tp_alloc */
+    package_new,                    /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/package-py.h b/src/python/package-py.h
new file mode 100644 (file)
index 0000000..335d57c
--- /dev/null
@@ -0,0 +1,33 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_PACKAGE_PY_H
+#define CR_PACKAGE_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject Package_Type;
+
+#define PackageObject_Check(o)   PyObject_TypeCheck(o, &Package_Type)
+
+PyObject *Object_FromPackage(cr_Package *pkg, int free_on_destroy);
+cr_Package *Package_FromPyObject(PyObject *o);
+PyObject * Object_FromPackage_WithParent(cr_Package *pkg, int free_on_destroy, PyObject *parent);
+
+#endif
diff --git a/src/python/parsepkg-py.c b/src/python/parsepkg-py.c
new file mode 100644 (file)
index 0000000..bac5231
--- /dev/null
@@ -0,0 +1,113 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "src/createrepo_c.h"
+
+#include "typeconversion.h"
+#include "parsepkg-py.h"
+#include "package-py.h"
+#include "exception-py.h"
+
+PyObject *
+py_package_from_rpm(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+
+    PyObject *ret;
+    cr_Package *pkg;
+    int checksum_type, changelog_limit;
+    char *filename, *location_href, *location_base;
+
+    if (!PyArg_ParseTuple(args, "sizzi:py_package_from_rpm",
+                                         &filename,
+                                         &checksum_type,
+                                         &location_href,
+                                         &location_base,
+                                         &changelog_limit)) {
+        return NULL;
+    }
+
+    if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+        PyErr_Format(PyExc_IOError, "File %s doesn't exist", filename);
+        return NULL;
+    }
+
+    pkg = cr_package_from_rpm(filename, checksum_type, location_href,
+                              location_base, changelog_limit, NULL);
+    if (!pkg) {
+        PyErr_Format(CrErr_Exception, "Cannot load %s", filename);
+        return NULL;
+    }
+
+    ret = Object_FromPackage(pkg, 1);
+    return ret;
+}
+
+PyObject *
+py_xml_from_rpm(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+
+    PyObject *tuple;
+    int checksum_type, changelog_limit;
+    char *filename, *location_href, *location_base;
+    struct cr_XmlStruct xml_res;
+
+    if (!PyArg_ParseTuple(args, "sizzi:py_xml_from_rpm",
+                                         &filename,
+                                         &checksum_type,
+                                         &location_href,
+                                         &location_base,
+                                         &changelog_limit)) {
+        return NULL;
+    }
+
+    if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+        PyErr_Format(PyExc_IOError, "File %s doesn't exist", filename);
+        return NULL;
+    }
+
+
+    xml_res = cr_xml_from_rpm(filename, checksum_type, location_href,
+                              location_base, changelog_limit, NULL);
+
+    if (!xml_res.primary && !xml_res.filelists && !xml_res.other) {
+        PyErr_Format(CrErr_Exception, "Cannot load %s", filename);
+        return NULL;
+    }
+
+    if ((tuple = PyTuple_New(3)) == NULL)
+        goto py_xml_from_rpm_end; // Free xml_res and return NULL
+
+    PyTuple_SetItem(tuple, 0, PyStringOrNone_FromString(xml_res.primary));
+    PyTuple_SetItem(tuple, 1, PyStringOrNone_FromString(xml_res.filelists));
+    PyTuple_SetItem(tuple, 2, PyStringOrNone_FromString(xml_res.other));
+
+py_xml_from_rpm_end:
+    free(xml_res.primary);
+    free(xml_res.filelists);
+    free(xml_res.other);
+
+    return tuple;
+}
+
diff --git a/src/python/parsepkg-py.h b/src/python/parsepkg-py.h
new file mode 100644 (file)
index 0000000..cf337c3
--- /dev/null
@@ -0,0 +1,28 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_PARSEPKG_PY_H
+#define CR_PARSEPKG_PY_H
+
+#include "src/createrepo_c.h"
+
+PyObject *py_package_from_rpm(PyObject *self, PyObject *args);
+PyObject *py_xml_from_rpm(PyObject *self, PyObject *args);
+
+#endif
diff --git a/src/python/repomd-py.c b/src/python/repomd-py.c
new file mode 100644 (file)
index 0000000..2c564bb
--- /dev/null
@@ -0,0 +1,227 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "repomd-py.h"
+#include "repomdrecord-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+typedef struct {
+    PyObject_HEAD
+    cr_Repomd repomd;
+} _RepomdObject;
+
+static int
+check_RepomdStatus(const _RepomdObject *self)
+{
+    assert(self != NULL);
+    assert(RepomdObject_Check(self));
+    if (self->repomd == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c Repomd object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+repomd_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+
+    _RepomdObject *self = (_RepomdObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->repomd = NULL;
+    }
+    return (PyObject *)self;
+}
+
+static int
+repomd_init(_RepomdObject *self, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+
+    /* Free all previous resources when reinitialization */
+    if (self->repomd) {
+        cr_repomd_free(self->repomd);
+    }
+
+    /* Init */
+    self->repomd = cr_repomd_new();
+    if (self->repomd == NULL) {
+        PyErr_SetString(CrErr_Exception, "Repomd initialization failed");
+        return -1;
+    }
+    return 0;
+}
+
+static void
+repomd_dealloc(_RepomdObject *self)
+{
+    if (self->repomd)
+        cr_repomd_free(self->repomd);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+repomd_repr(_RepomdObject *self)
+{
+    CR_UNUSED(self);
+    return PyString_FromFormat("<createrepo_c.Repomd object>");
+}
+
+/* Repomd methods */
+
+static PyObject *
+set_record(_RepomdObject *self, PyObject *args)
+{
+    PyObject *record;
+    char *type;
+
+    if (!PyArg_ParseTuple(args, "O!s:set_record", &RepomdRecord_Type, &record, &type))
+        return NULL;
+    if (check_RepomdStatus(self))
+        return NULL;
+    cr_repomd_set_record(self->repomd, RepomdRecord_FromPyObject(record), type);
+    Py_XINCREF(record);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+set_revision(_RepomdObject *self, PyObject *args)
+{
+    char *revision;
+    if (!PyArg_ParseTuple(args, "s:set_revision", &revision))
+        return NULL;
+    if (check_RepomdStatus(self))
+        return NULL;
+    cr_repomd_set_revision(self->repomd, revision);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+add_distro_tag(_RepomdObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "tag", "cpeid", NULL };
+
+    char *tag = NULL, *cpeid = NULL;
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|z:add_distro_tag",
+                                     kwlist, &tag, &cpeid))
+        return NULL;
+    if (check_RepomdStatus(self))
+        return NULL;
+    cr_repomd_add_distro_tag(self->repomd, cpeid, tag);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+add_repo_tag(_RepomdObject *self, PyObject *args)
+{
+    char *tag;
+    if (!PyArg_ParseTuple(args, "s:add_repo_tag", &tag))
+        return NULL;
+    if (check_RepomdStatus(self))
+        return NULL;
+    cr_repomd_add_repo_tag(self->repomd, tag);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+add_content_tag(_RepomdObject *self, PyObject *args)
+{
+    char *tag;
+    if (!PyArg_ParseTuple(args, "s:add_content_tag", &tag))
+        return NULL;
+    if (check_RepomdStatus(self))
+        return NULL;
+    cr_repomd_add_content_tag(self->repomd, tag);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+xml_dump(_RepomdObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    PyObject *py_str;
+    char *xml = cr_repomd_xml_dump(self->repomd);
+    py_str = PyStringOrNone_FromString(xml);
+    free(xml);
+    return py_str;
+}
+
+static struct PyMethodDef repomd_methods[] = {
+    {"set_record", (PyCFunction)set_record, METH_VARARGS, NULL},
+    {"set_revision", (PyCFunction)set_revision, METH_VARARGS, NULL},
+    {"add_distro_tag", (PyCFunction)add_distro_tag, METH_VARARGS|METH_KEYWORDS, NULL},
+    {"add_repo_tag", (PyCFunction)add_repo_tag, METH_VARARGS, NULL},
+    {"add_content_tag", (PyCFunction)add_content_tag, METH_VARARGS, NULL},
+    {"xml_dump", (PyCFunction)xml_dump, METH_NOARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+PyTypeObject Repomd_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.Repomd",              /* tp_name */
+    sizeof(_RepomdObject),          /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor) repomd_dealloc,    /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc) repomd_repr,         /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "Repomd object",                /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    repomd_methods,                 /* tp_methods */
+    0,                              /* tp_members */
+    0,                              /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc) repomd_init,         /* tp_init */
+    0,                              /* tp_alloc */
+    repomd_new,                     /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/repomd-py.h b/src/python/repomd-py.h
new file mode 100644 (file)
index 0000000..6d44bb5
--- /dev/null
@@ -0,0 +1,29 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_REPOMD_PY_H
+#define CR_REPOMD_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject Repomd_Type;
+
+#define RepomdObject_Check(o)   PyObject_TypeCheck(o, &Repomd_Type)
+
+#endif
diff --git a/src/python/repomdrecord-py.c b/src/python/repomdrecord-py.c
new file mode 100644 (file)
index 0000000..dae0ba9
--- /dev/null
@@ -0,0 +1,276 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "repomdrecord-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+typedef struct {
+    PyObject_HEAD
+    cr_RepomdRecord record;
+} _RepomdRecordObject;
+
+cr_RepomdRecord
+RepomdRecord_FromPyObject(PyObject *o)
+{
+    if (!RepomdRecordObject_Check(o)) {
+        PyErr_SetString(PyExc_TypeError, "Expected a RepomdRecord object.");
+        return NULL;
+    }
+    return ((_RepomdRecordObject *)o)->record;
+}
+
+static int
+check_RepomdRecordStatus(const _RepomdRecordObject *self)
+{
+    assert(self != NULL);
+    assert(RepomdRecordObject_Check(self));
+    if (self->record == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c RepomdRecord object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+repomdrecord_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+    _RepomdRecordObject *self = (_RepomdRecordObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->record = NULL;
+    }
+    return (PyObject *)self;
+}
+
+static int
+repomdrecord_init(_RepomdRecordObject *self, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(kwds);
+
+    char *path;
+
+    if (!PyArg_ParseTuple(args, "s|:repomdrecord_init", &path))
+        return -1;
+
+    /* Free all previous resources when reinitialization */
+    if (self->record) {
+        cr_repomd_record_free(self->record);
+    }
+
+    /* Init */
+    self->record = cr_repomd_record_new(path);
+    if (self->record == NULL) {
+        PyErr_SetString(CrErr_Exception, "RepomdRecord initialization failed");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+repomdrecord_dealloc(_RepomdRecordObject *self)
+{
+    if (self->record)
+        cr_repomd_record_free(self->record);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+repomdrecord_repr(_RepomdRecordObject *self)
+{
+    CR_UNUSED(self);
+    return PyString_FromFormat("<createrepo_c.RepomdRecord object>");
+}
+
+/* RepomdRecord methods */
+
+static PyObject *
+fill(_RepomdRecordObject *self, PyObject *args)
+{
+    int checksum_type;
+    if (!PyArg_ParseTuple(args, "i:fill", &checksum_type))
+        return NULL;
+    if (check_RepomdRecordStatus(self))
+        return NULL;
+    cr_repomd_record_fill(self->record, checksum_type);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+compress_and_fill(_RepomdRecordObject *self, PyObject *args)
+{
+    int checksum_type, compression_type;
+    PyObject *compressed_repomdrecord;
+    if (!PyArg_ParseTuple(args, "O!ii:fill",
+                          &RepomdRecord_Type,
+                          &compressed_repomdrecord,
+                          &checksum_type,
+                          &compression_type))
+        return NULL;
+    if (check_RepomdRecordStatus(self))
+        return NULL;
+    cr_repomd_record_groupfile(self->record,
+                               RepomdRecord_FromPyObject(compressed_repomdrecord),
+                               checksum_type,
+                               compression_type);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+rename_file(_RepomdRecordObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    cr_repomd_record_rename_file(self->record);
+    Py_RETURN_NONE;
+}
+
+static struct PyMethodDef repomdrecord_methods[] = {
+    {"fill", (PyCFunction)fill, METH_VARARGS, NULL},
+    {"compress_and_fill", (PyCFunction)compress_and_fill, METH_VARARGS, NULL},
+    {"rename_file", (PyCFunction)rename_file, METH_NOARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+/* getsetters */
+
+#define OFFSET(member) (void *) offsetof(struct _cr_RepomdRecord, member)
+
+static PyObject *
+get_num(_RepomdRecordObject *self, void *member_offset)
+{
+    if (check_RepomdRecordStatus(self))
+        return NULL;
+    cr_RepomdRecord rec = self->record;
+    gint64 val = *((gint64 *) ((size_t)rec + (size_t) member_offset));
+    return PyLong_FromLongLong((long long) val);
+}
+
+static PyObject *
+get_str(_RepomdRecordObject *self, void *member_offset)
+{
+    if (check_RepomdRecordStatus(self))
+        return NULL;
+    cr_RepomdRecord rec = self->record;
+    char *str = *((char **) ((size_t) rec + (size_t) member_offset));
+    if (str == NULL)
+        Py_RETURN_NONE;
+    return PyString_FromString(str);
+}
+
+static int
+set_num(_RepomdRecordObject *self, PyObject *value, void *member_offset)
+{
+    long val;
+    if (check_RepomdRecordStatus(self))
+        return -1;
+    if (PyLong_Check(value)) {
+        val = PyLong_AsLong(value);
+    } else if (PyInt_Check(value)) {
+        val = PyInt_AS_LONG(value);
+    } else {
+        PyErr_SetString(PyExc_ValueError, "Number expected!");
+        return -1;
+    }
+    cr_RepomdRecord rec = self->record;
+    *((long *) ((size_t) rec + (size_t) member_offset)) = val;
+    return 0;
+}
+
+static int
+set_str(_RepomdRecordObject *self, PyObject *value, void *member_offset)
+{
+    if (check_RepomdRecordStatus(self))
+        return -1;
+    if (!PyString_Check(value)) {
+        PyErr_SetString(PyExc_ValueError, "String expected!");
+        return -1;
+    }
+    cr_RepomdRecord rec = self->record;
+    char *str = g_string_chunk_insert(rec->chunk, PyString_AsString(value));
+    *((char **) ((size_t) rec + (size_t) member_offset)) = str;
+    return 0;
+}
+
+static PyGetSetDef repomdrecord_getsetters[] = {
+    {"location_real",       (getter)get_str, (setter)set_str, NULL, OFFSET(location_real)},
+    {"location_href",       (getter)get_str, (setter)set_str, NULL, OFFSET(location_href)},
+    {"checksum",            (getter)get_str, (setter)set_str, NULL, OFFSET(checksum)},
+    {"checksum_type",       (getter)get_str, (setter)set_str, NULL, OFFSET(checksum_type)},
+    {"checksum_open",       (getter)get_str, (setter)set_str, NULL, OFFSET(checksum_open)},
+    {"checksum_open_type",  (getter)get_str, (setter)set_str, NULL, OFFSET(checksum_open_type)},
+    {"timestamp",           (getter)get_num, (setter)set_num, NULL, OFFSET(timestamp)},
+    {"size",                (getter)get_num, (setter)set_num, NULL, OFFSET(size)},
+    {"size_open",           (getter)get_num, (setter)set_num, NULL, OFFSET(size_open)},
+    {"db_ver",              (getter)get_num, (setter)set_num, NULL, OFFSET(db_ver)},
+    {NULL, NULL, NULL, NULL, NULL} /* sentinel */
+};
+
+/* Object */
+
+PyTypeObject RepomdRecord_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.RepomdRecord",        /* tp_name */
+    sizeof(_RepomdRecordObject),    /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor) repomdrecord_dealloc, /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc) repomdrecord_repr,   /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "RepomdRecord object",          /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    repomdrecord_methods,           /* tp_methods */
+    0,                              /* tp_members */
+    repomdrecord_getsetters,        /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc) repomdrecord_init,   /* tp_init */
+    0,                              /* tp_alloc */
+    repomdrecord_new,               /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/repomdrecord-py.h b/src/python/repomdrecord-py.h
new file mode 100644 (file)
index 0000000..22c409d
--- /dev/null
@@ -0,0 +1,30 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_REPOMDRECORD_PY_H
+#define CR_REPOMDRECORD_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject RepomdRecord_Type;
+cr_RepomdRecord RepomdRecord_FromPyObject(PyObject *o);
+
+#define RepomdRecordObject_Check(o)   PyObject_TypeCheck(o, &RepomdRecord_Type)
+
+#endif
diff --git a/src/python/sqlite-py.c b/src/python/sqlite-py.c
new file mode 100644 (file)
index 0000000..ac36994
--- /dev/null
@@ -0,0 +1,304 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "sqlite-py.h"
+#include "package-py.h"
+#include "exception-py.h"
+#include "typeconversion.h"
+
+typedef struct {
+    PyObject_HEAD
+    int type;           // cr_DatabaseType value or -1
+    void *statements;
+    sqlite3 *db;
+    int closed;         // Is db closed?
+} _SqliteObject;
+
+// Forward declaration
+static PyObject *close_db(_SqliteObject *self, void *nothing);
+
+
+static int
+check_SqliteStatus(const _SqliteObject *self)
+{
+    assert(self != NULL);
+    assert(SqliteObject_Check(self));
+    if (self->db == NULL) {
+        PyErr_SetString(CrErr_Exception, "Improper createrepo_c Sqlite object.");
+        return -1;
+    }
+    return 0;
+}
+
+/* Function on the type */
+
+static PyObject *
+sqlite_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    CR_UNUSED(args);
+    CR_UNUSED(kwds);
+    _SqliteObject *self = (_SqliteObject *)type->tp_alloc(type, 0);
+    if (self) {
+        self->type = -1;
+        self->statements = NULL;
+        self->db = NULL;
+        self->closed = 0;
+    }
+    return (PyObject *)self;
+}
+
+static int
+sqlite_init(_SqliteObject *self, PyObject *args, PyObject *kwds)
+{
+    char *path;
+    int db_type;
+    GError *err = NULL;
+
+    CR_UNUSED(kwds);
+
+    if (!PyArg_ParseTuple(args, "si|:sqlite_init", &path, &db_type))
+        return -1;
+
+    /* Check arguments */
+    if (db_type < CR_DB_PRIMARY || db_type > CR_DB_OTHER) {
+        PyErr_SetString(PyExc_ValueError, "Unknown type value");
+        return -1;
+    }
+
+    /* Free all previous resources when reinitialization */
+    Py_XDECREF(close_db(self, NULL));
+    if (self->statements) {
+        if (self->type == CR_DB_PRIMARY)
+            cr_db_destroy_primary_statements(self->statements);
+        else if (self->type == CR_DB_FILELISTS)
+            cr_db_destroy_filelists_statements(self->statements);
+        else if (self->type == CR_DB_OTHER)
+            cr_db_destroy_other_statements(self->statements);
+        self->statements = NULL;
+    }
+
+    if (err) {
+        PyErr_Format(CrErr_Exception, "Error while freeing previous content: %s", err->message);
+        g_clear_error(&err);
+        return -1;
+    }
+
+    /* Init */
+    self->type = db_type;
+    self->closed = 0;
+    if (self->type == CR_DB_PRIMARY)
+        self->db = cr_db_open_primary(path, &err);
+    else if (self->type == CR_DB_FILELISTS)
+        self->db = cr_db_open_filelists(path, &err);
+    else if (self->type == CR_DB_OTHER)
+        self->db = cr_db_open_other(path, &err);
+
+    if (self->db == NULL) {
+        PyErr_SetString(CrErr_Exception, "Sqlite initialization failed");
+        return -1;
+    }
+
+    if (err) {
+        PyErr_Format(CrErr_Exception, "Sqlite initialization failed: %s", err->message);
+        g_clear_error(&err);
+        Py_XDECREF(close_db(self, NULL));
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+sqlite_dealloc(_SqliteObject *self)
+{
+    if (self->statements) {
+        if (self->type == CR_DB_PRIMARY)
+            cr_db_destroy_primary_statements(self->statements);
+        else if (self->type == CR_DB_FILELISTS)
+            cr_db_destroy_filelists_statements(self->statements);
+        else if (self->type == CR_DB_OTHER)
+            cr_db_destroy_other_statements(self->statements);
+        self->statements = NULL;
+    }
+    Py_XDECREF(close_db(self, NULL));
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+sqlite_repr(_SqliteObject *self)
+{
+    char *type;
+    if (self->type == CR_DB_PRIMARY)        type = "PrimaryDb";
+    else if (self->type == CR_DB_FILELISTS) type = "FilelistsDb";
+    else if (self->type == CR_DB_OTHER)     type = "OtherDb";
+    else                                    type = "UnknownDb";
+    return PyString_FromFormat("<createrepo_c.Sqlite %s object>", type);
+}
+
+/* getsetters */
+
+/* TODO:
+ * Export sqlite object - Maybe in future version?
+ */
+
+/* Sqlite methods */
+
+static PyObject *
+prepare_statements(_SqliteObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    GError *err = NULL;
+
+    if (self->statements)
+        Py_RETURN_NONE;
+
+    if (check_SqliteStatus(self))
+        return NULL;
+
+    if (self->type == CR_DB_PRIMARY)
+        self->statements = cr_db_prepare_primary_statements(self->db, &err);
+    else if (self->type == CR_DB_FILELISTS)
+        self->statements = cr_db_prepare_filelists_statements(self->db, &err);
+    else if (self->type == CR_DB_OTHER)
+        self->statements = cr_db_prepare_other_statements(self->db, &err);
+
+    if (err) {
+        PyErr_Format(CrErr_Exception, "Sqlite statement error: %s", err->message);
+        g_clear_error(&err);
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+add_pkg(_SqliteObject *self, PyObject *args)
+{
+    PyObject *py_pkg;
+    GError *err = NULL;
+
+    if (!PyArg_ParseTuple(args, "O!:add_pkg", &Package_Type, &py_pkg))
+        return NULL;
+    if (check_SqliteStatus(self))
+        return NULL;
+    if (!self->statements)
+        Py_XDECREF(prepare_statements(self, NULL));
+    if (self->type == CR_DB_PRIMARY)
+        cr_db_add_primary_pkg(self->statements, Package_FromPyObject(py_pkg), &err);
+    else if (self->type == CR_DB_FILELISTS)
+        cr_db_add_filelists_pkg(self->statements, Package_FromPyObject(py_pkg), &err);
+    else if (self->type == CR_DB_OTHER)
+        cr_db_add_other_pkg(self->statements, Package_FromPyObject(py_pkg), &err);
+
+    if (err) {
+        PyErr_Format(CrErr_Exception, "Cannot add package: %s", err->message);
+        g_clear_error(&err);
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+dbinfo_update(_SqliteObject *self, PyObject *args)
+{
+    char *checksum;
+    GError *err = NULL;
+
+    if (!PyArg_ParseTuple(args, "s:dbinfo_update", &checksum))
+        return NULL;
+    if (check_SqliteStatus(self))
+        return NULL;
+    cr_db_dbinfo_update(self->db, checksum, &err);
+
+    if (err) {
+        PyErr_Format(CrErr_Exception, "Sqlite dbinfo_update error: %s", err->message);
+        g_clear_error(&err);
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+close_db(_SqliteObject *self, void *nothing)
+{
+    CR_UNUSED(nothing);
+    if (self->db && !self->closed) {
+        self->closed = 1;
+        cr_db_close(self->db, self->type, NULL);
+    }
+    Py_RETURN_NONE;
+}
+
+static struct PyMethodDef sqlite_methods[] = {
+    {"_prepare_statements", (PyCFunction)prepare_statements, METH_NOARGS, NULL},
+    {"add_pkg", (PyCFunction)add_pkg, METH_VARARGS, NULL},
+    {"dbinfo_update", (PyCFunction)dbinfo_update, METH_VARARGS, NULL},
+    {"close", (PyCFunction)close_db, METH_NOARGS, NULL},
+    {NULL} /* sentinel */
+};
+
+PyTypeObject Sqlite_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                              /* ob_size */
+    "_librepo.Sqlite",              /* tp_name */
+    sizeof(_SqliteObject),          /* tp_basicsize */
+    0,                              /* tp_itemsize */
+    (destructor) sqlite_dealloc,    /* tp_dealloc */
+    0,                              /* tp_print */
+    0,                              /* tp_getattr */
+    0,                              /* tp_setattr */
+    0,                              /* tp_compare */
+    (reprfunc) sqlite_repr,         /* tp_repr */
+    0,                              /* tp_as_number */
+    0,                              /* tp_as_sequence */
+    0,                              /* tp_as_mapping */
+    0,                              /* tp_hash */
+    0,                              /* tp_call */
+    0,                              /* tp_str */
+    0,                              /* tp_getattro */
+    0,                              /* tp_setattro */
+    0,                              /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
+    "Sqlite object",                /* tp_doc */
+    0,                              /* tp_traverse */
+    0,                              /* tp_clear */
+    0,                              /* tp_richcompare */
+    0,                              /* tp_weaklistoffset */
+    PyObject_SelfIter,              /* tp_iter */
+    0,                              /* tp_iternext */
+    sqlite_methods,                 /* tp_methods */
+    0,                              /* tp_members */
+    0,                              /* tp_getset */
+    0,                              /* tp_base */
+    0,                              /* tp_dict */
+    0,                              /* tp_descr_get */
+    0,                              /* tp_descr_set */
+    0,                              /* tp_dictoffset */
+    (initproc) sqlite_init,         /* tp_init */
+    0,                              /* tp_alloc */
+    sqlite_new,                     /* tp_new */
+    0,                              /* tp_free */
+    0,                              /* tp_is_gc */
+};
diff --git a/src/python/sqlite-py.h b/src/python/sqlite-py.h
new file mode 100644 (file)
index 0000000..42b3492
--- /dev/null
@@ -0,0 +1,29 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_SQLITE_PY_H
+#define CR_SQLITE_PY_H
+
+#include "src/createrepo_c.h"
+
+extern PyTypeObject Sqlite_Type;
+
+#define SqliteObject_Check(o)   PyObject_TypeCheck(o, &Sqlite_Type)
+
+#endif
diff --git a/src/python/typeconversion.c b/src/python/typeconversion.c
new file mode 100644 (file)
index 0000000..6b778cd
--- /dev/null
@@ -0,0 +1,186 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2012-2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include "typeconversion.h"
+
+PyObject *
+PyStringOrNone_FromString(const char *str)
+{
+    if (str == NULL)
+        Py_RETURN_NONE;
+    return PyString_FromString(str);
+}
+
+char *
+PyObject_ToStrOrNull(PyObject *pyobj)
+{
+    // String returned by this function shoud not be freed or modified
+    if (PyString_Check(pyobj))
+        return PyString_AsString(pyobj);
+    // TODO: ? Add support for pyobj like: ("xxx",) and ["xxx"]
+    return NULL;
+}
+
+long long
+PyObject_ToLongLongOrZero(PyObject *pyobj)
+{
+    long long num = 0;
+    if (PyLong_Check(pyobj))
+        num = (long long) PyLong_AsLongLong(pyobj);
+    else if (PyInt_Check(pyobj))
+        num = (long long) PyInt_AS_LONG(pyobj);
+    return num;
+}
+
+PyObject *
+PyObject_FromDependency(cr_Dependency *dep)
+{
+    PyObject *tuple;
+
+    if ((tuple = PyTuple_New(6)) == NULL)
+        return NULL;
+
+    PyTuple_SetItem(tuple, 0, PyStringOrNone_FromString(dep->name));
+    PyTuple_SetItem(tuple, 1, PyStringOrNone_FromString(dep->flags));
+    PyTuple_SetItem(tuple, 2, PyStringOrNone_FromString(dep->epoch));
+    PyTuple_SetItem(tuple, 3, PyStringOrNone_FromString(dep->version));
+    PyTuple_SetItem(tuple, 4, PyStringOrNone_FromString(dep->release));
+    PyTuple_SetItem(tuple, 5, PyBool_FromLong((long) dep->pre));
+
+    return tuple;
+}
+
+cr_Dependency *
+PyObject_ToDependency(PyObject *tuple, GStringChunk *chunk)
+{
+    PyObject *pyobj;
+    cr_Dependency *dep = cr_dependency_new();
+
+    pyobj = PyTuple_GetItem(tuple, 0);
+    dep->name = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 1);
+    dep->flags = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 2);
+    dep->epoch = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 3);
+    dep->version = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 4);
+    dep->release = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 5);
+    dep->pre = (PyObject_IsTrue(pyobj)) ? TRUE : FALSE;
+
+    return dep;
+}
+
+PyObject *
+PyObject_FromPackageFile(cr_PackageFile *file)
+{
+    PyObject *tuple;
+
+    if ((tuple = PyTuple_New(3)) == NULL)
+        return NULL;
+
+    PyTuple_SetItem(tuple, 0, PyStringOrNone_FromString(file->type));
+    PyTuple_SetItem(tuple, 1, PyStringOrNone_FromString(file->path));
+    PyTuple_SetItem(tuple, 2, PyStringOrNone_FromString(file->name));
+
+    return tuple;
+}
+
+cr_PackageFile *
+PyObject_ToPackageFile(PyObject *tuple, GStringChunk *chunk)
+{
+    PyObject *pyobj;
+    cr_PackageFile *file = cr_package_file_new();
+
+    pyobj = PyTuple_GetItem(tuple, 0);
+    file->type = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 1);
+    file->path = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 2);
+    file->name = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    return file;
+}
+
+PyObject *
+PyObject_FromChangelogEntry(cr_ChangelogEntry *log)
+{
+    PyObject *tuple;
+
+    if ((tuple = PyTuple_New(3)) == NULL)
+        return NULL;
+
+    PyTuple_SetItem(tuple, 0, PyStringOrNone_FromString(log->author));
+    PyTuple_SetItem(tuple, 1, PyLong_FromLong((long) log->date));
+    PyTuple_SetItem(tuple, 2, PyStringOrNone_FromString(log->changelog));
+
+    return tuple;
+}
+
+cr_ChangelogEntry *
+PyObject_ToChangelogEntry(PyObject *tuple, GStringChunk *chunk)
+{
+    PyObject *pyobj;
+    cr_ChangelogEntry *log = cr_changelog_entry_new();
+
+    pyobj = PyTuple_GetItem(tuple, 0);
+    log->author = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    pyobj = PyTuple_GetItem(tuple, 1);
+    log->date = PyObject_ToLongLongOrZero(pyobj);
+
+    pyobj = PyTuple_GetItem(tuple, 2);
+    log->changelog = cr_safe_string_chunk_insert(chunk, PyObject_ToStrOrNull(pyobj));
+
+    return log;
+}
+
+GSList *
+GSList_FromPyList_Str(PyObject *py_list)
+{
+    GSList *list = NULL;
+
+    if (!py_list)
+        return NULL;
+
+    if (!PyList_Check(py_list))
+        return NULL;
+
+    Py_ssize_t size = PyList_Size(py_list);
+    for (Py_ssize_t x=0; x < size; x++) {
+        PyObject *py_str = PyList_GetItem(py_list, x);
+        assert(py_str != NULL);
+        if (!PyString_Check(py_str))
+            // Hmm, element is not a string, just skip it
+            continue;
+        list = g_slist_prepend(list, PyString_AsString(py_str));
+    }
+
+    return list;
+}
diff --git a/src/python/typeconversion.h b/src/python/typeconversion.h
new file mode 100644 (file)
index 0000000..c8492a0
--- /dev/null
@@ -0,0 +1,40 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2012-2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_TYPECONVERSION_PY_H
+#define CR_TYPECONVERSION_PY_H
+
+#include <glib.h>
+#include "src/createrepo_c.h"
+
+PyObject *PyStringOrNone_FromString(const char *str);
+char *PyObject_ToStrOrNull(PyObject *pyobj);
+
+PyObject *PyObject_FromDependency(cr_Dependency *dep);
+cr_Dependency *PyObject_ToDependency(PyObject *tuple, GStringChunk *chunk);
+
+PyObject *PyObject_FromPackageFile(cr_PackageFile *file);
+cr_PackageFile *PyObject_ToPackageFile(PyObject *tuple, GStringChunk *chunk);
+
+PyObject *PyObject_FromChangelogEntry(cr_ChangelogEntry *log);
+cr_ChangelogEntry *PyObject_ToChangelogEntry(PyObject *tuple, GStringChunk *chunk);
+
+GSList *GSList_FromPyList_Str(PyObject *py_list);
+
+#endif
diff --git a/src/python/xml_dump-py.c b/src/python/xml_dump-py.c
new file mode 100644 (file)
index 0000000..0654193
--- /dev/null
@@ -0,0 +1,96 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <Python.h>
+#include <assert.h>
+#include <stddef.h>
+
+#include "src/createrepo_c.h"
+
+#include "typeconversion.h"
+#include "package-py.h"
+#include "exception-py.h"
+
+PyObject *
+py_xml_dump_primary(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+    PyObject *py_pkg, *py_str;
+    char *xml;
+    if (!PyArg_ParseTuple(args, "O!:py_xml_dump_primary", &Package_Type, &py_pkg))
+        return NULL;
+    xml = cr_xml_dump_primary(Package_FromPyObject(py_pkg));
+    py_str = PyStringOrNone_FromString(xml);
+    free(xml);
+    return py_str;
+}
+
+PyObject *
+py_xml_dump_filelists(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+    PyObject *py_pkg, *py_str;
+    char *xml;
+    if (!PyArg_ParseTuple(args, "O!:py_xml_dump_filelists", &Package_Type, &py_pkg))
+        return NULL;
+    xml = cr_xml_dump_filelists(Package_FromPyObject(py_pkg));
+    py_str = PyStringOrNone_FromString(xml);
+    free(xml);
+    return py_str;
+}
+
+PyObject *
+py_xml_dump_other(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+    PyObject *py_pkg, *py_str;
+    char *xml;
+    if (!PyArg_ParseTuple(args, "O!:py_xml_dump_other", &Package_Type, &py_pkg))
+        return NULL;
+    xml = cr_xml_dump_other(Package_FromPyObject(py_pkg));
+    py_str = PyStringOrNone_FromString(xml);
+    free(xml);
+    return py_str;
+}
+
+PyObject *
+py_xml_dump(PyObject *self, PyObject *args)
+{
+    CR_UNUSED(self);
+    PyObject *py_pkg, *tuple;
+    struct cr_XmlStruct xml_res;
+
+    if (!PyArg_ParseTuple(args, "O!:py_xml_dump", &Package_Type, &py_pkg))
+        return NULL;
+
+    if ((tuple = PyTuple_New(3)) == NULL)
+        return NULL;
+
+    xml_res = cr_xml_dump(Package_FromPyObject(py_pkg));
+
+    PyTuple_SetItem(tuple, 0, PyStringOrNone_FromString(xml_res.primary));
+    PyTuple_SetItem(tuple, 1, PyStringOrNone_FromString(xml_res.filelists));
+    PyTuple_SetItem(tuple, 2, PyStringOrNone_FromString(xml_res.other));
+
+    free(xml_res.primary);
+    free(xml_res.filelists);
+    free(xml_res.other);
+
+    return tuple;
+}
diff --git a/src/python/xml_dump-py.h b/src/python/xml_dump-py.h
new file mode 100644 (file)
index 0000000..17c3170
--- /dev/null
@@ -0,0 +1,30 @@
+/* createrepo_c - Library of routines for manipulation with repodata
+ * Copyright (C) 2013  Tomas Mlcoch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef CR_XML_DUMP_PY_H
+#define CR_XML_DUMP_PY_H
+
+#include "src/createrepo_c.h"
+
+PyObject *py_xml_dump_primary(PyObject *self, PyObject *args);
+PyObject *py_xml_dump_filelists(PyObject *self, PyObject *args);
+PyObject *py_xml_dump_other(PyObject *self, PyObject *args);
+PyObject *py_xml_dump(PyObject *self, PyObject *args);
+
+#endif
index 4f03f99..cd373c6 100644 (file)
@@ -13,3 +13,9 @@ ADD_DEPENDENCIES(tests testmisc)
 ADD_EXECUTABLE(testsqlite testsqlite.c)
 TARGET_LINK_LIBRARIES(testsqlite libcreaterepo_c ${GLIB2_LIBRARIES})
 ADD_DEPENDENCIES(tests testsqlite)
+
+#ADD_TEST(test_main gtester "${CMAKE_CURRENT_SOURCE_DIR}/test_data")
+CONFIGURE_FILE("run_gtester.sh.in"  "run_gtester.sh")
+ADD_TEST(test_main run_gtester.sh)
+
+ADD_SUBDIRECTORY(python)
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
new file mode 100644 (file)
index 0000000..64ccc72
--- /dev/null
@@ -0,0 +1 @@
+ADD_SUBDIRECTORY (tests)
diff --git a/tests/python/tests/CMakeLists.txt b/tests/python/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cab63c6
--- /dev/null
@@ -0,0 +1,2 @@
+CONFIGURE_FILE("run_nosetests.sh.in"  "run_nosetests.sh")
+ADD_TEST(test_python run_nosetests.sh -s ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/tests/python/tests/__init__.py b/tests/python/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/python/tests/fixtures.py b/tests/python/tests/fixtures.py
new file mode 100644 (file)
index 0000000..33f741e
--- /dev/null
@@ -0,0 +1,51 @@
+import os.path
+
+TEST_DATA_PATH = os.path.normpath(os.path.join(__file__, "../../../test_data"))
+
+PACKAGES_PATH = os.path.join(TEST_DATA_PATH, "packages")
+REPOS_PATH = TEST_DATA_PATH
+COMPRESSED_FILES_PATH = os.path.join(TEST_DATA_PATH, "compressed_files")
+TEST_FILES_PATH = os.path.join(TEST_DATA_PATH, "test_files")
+
+# Test packages
+PKG_ARCHER = "Archer-3.4.5-6.x86_64.rpm"
+PKG_ARCHER_PATH = os.path.join(PACKAGES_PATH, PKG_ARCHER)
+
+PKG_BALICEK_ISO88591 = "balicek-iso88591-1.1.1-1.x86_64.rpm"
+PKG_BALICEK_ISO88591_PATH = os.path.join(PACKAGES_PATH, PKG_BALICEK_ISO88591)
+
+PKG_BALICEK_ISO88592 = "balicek-iso88592-1.1.1-1.x86_64.rpm"
+PKG_BALICEK_ISO88592_PATH = os.path.join(PACKAGES_PATH, PKG_BALICEK_ISO88592)
+
+PKG_BALICEK_UTF8 = "balicek-utf8-1.1.1-1.x86_64.rpm"
+PKG_BALICEK_UTF8_PATH = os.path.join(PACKAGES_PATH, PKG_BALICEK_UTF8)
+
+PKG_EMPTY = "empty-0-0.x86_64.rpm"
+PKG_EMPTY_PATH = os.path.join(PACKAGES_PATH, PKG_EMPTY)
+
+PKG_EMPTY_SRC = "empty-0-0.x86_64.rpm"
+PKG_EMPTY_SRC_PATH = os.path.join(PACKAGES_PATH, PKG_EMPTY_SRC)
+
+PKG_FAKE_BASH = "fake_bash-1.1.1-1.x86_64.rpm"
+PKG_FAKE_BASH_PATH = os.path.join(PACKAGES_PATH, PKG_FAKE_BASH)
+
+PKG_SUPER_KERNEL = "super_kernel-6.0.1-2.x86_64.rpm"
+PKG_SUPER_KERNEL_PATH = os.path.join(PACKAGES_PATH, PKG_SUPER_KERNEL)
+
+# Test repositories
+REPO_00_PATH = os.path.join(REPOS_PATH, "repo_00")
+REPO_00_PRIXML = os.path.join(REPO_00_PATH, "repodata/",
+                              "dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9-primary.xml.gz")
+
+REPO_01_PATH = os.path.join(REPOS_PATH, "repo_01")
+REPO_02_PATH = os.path.join(REPOS_PATH, "repo_02")
+
+# Other test files
+
+FILE_BINARY = "binary_file"
+FILE_BINARY_PATH = os.path.join(TEST_FILES_PATH, FILE_BINARY)
+FILE_TEXT = "text_file"
+FILE_TEXT = os.path.join(TEST_FILES_PATH, FILE_TEXT)
+FILE_EMPTY = "empty_file"
+FILE_EMPTY = os.path.join(TEST_FILES_PATH, FILE_EMPTY)
+
diff --git a/tests/python/tests/run_nosetests.sh.in b/tests/python/tests/run_nosetests.sh.in
new file mode 100755 (executable)
index 0000000..64041b8
--- /dev/null
@@ -0,0 +1 @@
+PYTHONPATH=${CMAKE_BINARY_DIR}/src/python/ nosetests -s ${CMAKE_CURRENT_SOURCE_DIR}/
diff --git a/tests/python/tests/test_load_metadata.py b/tests/python/tests/test_load_metadata.py
new file mode 100644 (file)
index 0000000..55d94ed
--- /dev/null
@@ -0,0 +1,66 @@
+import unittest
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseLoadMetadata(unittest.TestCase):
+    def test_load_metadata__repo00(self):
+        md = cr.Metadata()
+        md.locate_and_load_xml(REPO_00_PATH)
+        self.assertTrue(md)
+
+        self.assertEqual(md.key, cr.HT_KEY_DEFAULT)
+
+        self.assertEqual(md.len(), 0)
+        self.assertEqual(md.keys(), [])
+        self.assertFalse(md.has_key("foo"))
+        self.assertFalse(md.has_key(""))
+        self.assertFalse(md.remove("foo"))
+        self.assertFalse(md.get("xxx"))
+
+    def test_load_metadata_repo01(self):
+        md = cr.Metadata()
+        md.locate_and_load_xml(REPO_01_PATH)
+        self.assertTrue(md)
+
+        self.assertEqual(md.key, cr.HT_KEY_DEFAULT)
+
+        self.assertEqual(md.len(), 1)
+        self.assertEqual(md.keys(), ['152824bff2aa6d54f429d43e87a3ff3a0286505c6d93ec87692b5e3a9e3b97bf'])
+        self.assertFalse(md.has_key("foo"))
+        self.assertFalse(md.has_key(""))
+        self.assertFalse(md.remove("foo"))
+
+        pkg = md.get('152824bff2aa6d54f429d43e87a3ff3a0286505c6d93ec87692b5e3a9e3b97bf')
+        self.assertTrue(pkg)
+
+        self.assertEqual(pkg.name, "super_kernel")
+
+    def test_load_metadata_repo02(self):
+        md = cr.Metadata()
+        md.locate_and_load_xml(REPO_02_PATH)
+        self.assertTrue(md)
+
+        self.assertEqual(md.key, cr.HT_KEY_DEFAULT)
+
+        self.assertEqual(md.len(), 2)
+        self.assertEqual(md.keys(),
+            ['6d43a638af70ef899933b1fd86a866f18f65b0e0e17dcbf2e42bfd0cdd7c63c3',
+             '90f61e546938a11449b710160ad294618a5bd3062e46f8cf851fd0088af184b7'])
+        self.assertFalse(md.has_key("foo"))
+        self.assertFalse(md.has_key(""))
+        self.assertFalse(md.remove("foo"))
+
+        pkg = md.get('152824bff2aa6d54f429d43e87a3ff3a0286505c6d93ec87692b5e3a9e3b97bf')
+        self.assertEqual(pkg, None)
+
+        pkg = md.get('90f61e546938a11449b710160ad294618a5bd3062e46f8cf851fd0088af184b7')
+        self.assertEqual(pkg.name, "fake_bash")
+
+    def test_load_metadata_repo02_destructor(self):
+        md = cr.Metadata()
+        md.locate_and_load_xml(REPO_02_PATH)
+        pkg = md.get('90f61e546938a11449b710160ad294618a5bd3062e46f8cf851fd0088af184b7')
+        del(md)  # in fact, md shoudnot be destroyed yet, because it is
+                 # referenced from pkg!
+        self.assertEqual(pkg.name, "fake_bash")
diff --git a/tests/python/tests/test_locate_metadata.py b/tests/python/tests/test_locate_metadata.py
new file mode 100644 (file)
index 0000000..f6e77f6
--- /dev/null
@@ -0,0 +1,19 @@
+import unittest
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseMetadataLocation(unittest.TestCase):
+    def test_metadatalocation(self):
+        ml = cr.MetadataLocation(REPO_00_PATH, 1)
+        self.assertTrue(ml)
+        self.assertTrue(ml["primary"].endswith("/repodata/dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9-primary.xml.gz"))
+        self.assertTrue(ml["filelists"].endswith("/repodata/401dc19bda88c82c403423fb835844d64345f7e95f5b9835888189c03834cc93-filelists.xml.gz"))
+        self.assertTrue(ml["other"].endswith("/repodata/6bf9672d0862e8ef8b8ff05a2fd0208a922b1f5978e6589d87944c88259cb670-other.xml.gz"))
+        self.assertTrue(ml["primary_db"] is None)
+        self.assertTrue(ml["filelists_db"] is None)
+        self.assertTrue(ml["other_db"] is None)
+        self.assertTrue(ml["group"] is None)
+        self.assertTrue(ml["group_gz"] is None)
+        self.assertTrue(ml["updateinfo"] is None)
+        self.assertTrue(ml["foobarxyz"] is None)
diff --git a/tests/python/tests/test_package.py b/tests/python/tests/test_package.py
new file mode 100644 (file)
index 0000000..a493ab5
--- /dev/null
@@ -0,0 +1,215 @@
+import unittest
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCasePackage(unittest.TestCase):
+    def test_package_empty(self):
+        pkg = cr.package_from_rpm(PKG_EMPTY_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.pkgId, "91afc5e3a124eedfc5bc52737940950b42a37c611dccecad4692a4eb317f9810")
+        self.assertEqual(pkg.name, "empty")
+        self.assertEqual(pkg.arch, "x86_64")
+        self.assertEqual(pkg.version, "0")
+        self.assertEqual(pkg.epoch, "0")
+        self.assertEqual(pkg.release, "0")
+        self.assertEqual(pkg.summary, '""')
+        self.assertEqual(pkg.description, "")
+        self.assertEqual(pkg.url, None)
+        self.assertEqual(pkg.time_file, 1340709886)
+        self.assertEqual(pkg.time_build, 1340696582)
+        self.assertEqual(pkg.rpm_license, "LGPL")
+        self.assertEqual(pkg.rpm_vendor, None)
+        self.assertEqual(pkg.rpm_group, "Unspecified")
+        self.assertEqual(pkg.rpm_buildhost, "localhost.localdomain")
+        self.assertEqual(pkg.rpm_sourcerpm, "empty-0-0.src.rpm")
+        self.assertEqual(pkg.rpm_header_start, 280)
+        self.assertEqual(pkg.rpm_header_end, 1285)
+        self.assertEqual(pkg.rpm_packager, None)
+        self.assertEqual(pkg.size_package, 1401)
+        self.assertEqual(pkg.size_installed, 0)
+        self.assertEqual(pkg.size_archive, 124)
+        self.assertEqual(pkg.location_href, None)
+        self.assertEqual(pkg.location_base, None)
+        self.assertEqual(pkg.checksum_type, "sha256")
+        self.assertEqual(pkg.requires, [])
+        self.assertEqual(pkg.provides, [
+            ('empty', 'EQ', '0', '0', '0', False),
+            ('empty(x86-64)', 'EQ', '0', '0', '0', False)
+        ])
+        self.assertEqual(pkg.conflicts, [])
+        self.assertEqual(pkg.obsoletes, [])
+        self.assertEqual(pkg.files, [])
+        self.assertEqual(pkg.changelogs, [])
+
+        self.assertEqual(pkg.nvra(), "empty-0-0.x86_64")
+        self.assertEqual(pkg.nevra(), "empty-0:0-0.x86_64")
+
+    def test_package_archer(self):
+        pkg = cr.package_from_rpm(PKG_ARCHER_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.pkgId, "4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e")
+        self.assertEqual(pkg.name, "Archer")
+        self.assertEqual(pkg.arch, "x86_64")
+        self.assertEqual(pkg.version, "3.4.5")
+        self.assertEqual(pkg.epoch, "2")
+        self.assertEqual(pkg.release, "6")
+        self.assertEqual(pkg.summary, "Complex package.")
+        self.assertEqual(pkg.description, "Archer package")
+        self.assertEqual(pkg.url, "http://soo_complex_package.eu/")
+        self.assertEqual(pkg.time_file, 1365416502)
+        self.assertEqual(pkg.time_build, 1365416480)
+        self.assertEqual(pkg.rpm_license, "GPL")
+        self.assertEqual(pkg.rpm_vendor, "ISIS")
+        self.assertEqual(pkg.rpm_group, "Development/Tools")
+        self.assertEqual(pkg.rpm_buildhost, "localhost.localdomain")
+        self.assertEqual(pkg.rpm_sourcerpm, "Archer-3.4.5-6.src.rpm")
+        self.assertEqual(pkg.rpm_header_start, 280)
+        self.assertEqual(pkg.rpm_header_end, 2865)
+        self.assertEqual(pkg.rpm_packager, "Sterling Archer")
+        self.assertEqual(pkg.size_package, 3101)
+        self.assertEqual(pkg.size_installed, 0)
+        self.assertEqual(pkg.size_archive, 544)
+        self.assertEqual(pkg.location_href, None)
+        self.assertEqual(pkg.location_base, None)
+        self.assertEqual(pkg.checksum_type, "sha256")
+        self.assertEqual(pkg.requires, [
+            ('fooa', 'LE', '0', '2', None, False),
+            ('foob', 'GE', '0', '1.0.0', '1', False),
+            ('fooc', 'EQ', '0', '3', None, False),
+            ('food', 'LT', '0', '4', None, False),
+            ('fooe', 'GT', '0', '5', None, False),
+            ('foof', 'EQ', '0', '6', None, True)
+            ])
+        self.assertEqual(pkg.provides, [
+            ('bara', 'LE', '0', '22', None, False),
+            ('barb', 'GE', '0', '11.22.33', '44', False),
+            ('barc', 'EQ', '0', '33', None, False),
+            ('bard', 'LT', '0', '44', None, False),
+            ('bare', 'GT', '0', '55', None, False),
+            ('Archer', 'EQ', '2', '3.4.5', '6', False),
+            ('Archer(x86-64)', 'EQ', '2', '3.4.5', '6', False)
+            ])
+        self.assertEqual(pkg.conflicts, [
+            ('bba', 'LE', '0', '2222', None, False),
+            ('bbb', 'GE', '0', '1111.2222.3333', '4444', False),
+            ('bbc', 'EQ', '0', '3333', None, False),
+            ('bbd', 'LT', '0', '4444', None, False),
+            ('bbe', 'GT', '0', '5555', None, False)
+            ])
+        self.assertEqual(pkg.obsoletes, [
+            ('aaa', 'LE', '0', '222', None, False),
+            ('aab', 'GE', '0', '111.2.3', '4', False),
+            ('aac', 'EQ', '0', '333', None, False),
+            ('aad', 'LT', '0', '444', None, False),
+            ('aae', 'GT', '0', '555', None, False)
+            ])
+        self.assertEqual(pkg.files, [
+            ('', '/usr/bin/', 'complex_a'),
+            ('dir', '/usr/share/doc/', 'Archer-3.4.5'),
+            ('', '/usr/share/doc/Archer-3.4.5/', 'README')
+            ])
+        self.assertEqual(pkg.changelogs, [
+            ('Tomas Mlcoch <tmlcoch@redhat.com> - 1.1.1-1', 1334664000L,
+                '- First changelog.'),
+            ('Tomas Mlcoch <tmlcoch@redhat.com> - 2.2.2-2', 1334750400L,
+                '- That was totally ninja!'),
+            ('Tomas Mlcoch <tmlcoch@redhat.com> - 3.3.3-3', 1365422400L,
+                '- 3. changelog.')
+            ])
+
+        self.assertEqual(pkg.nvra(), "Archer-3.4.5-6.x86_64")
+        self.assertEqual(pkg.nevra(), "Archer-2:3.4.5-6.x86_64")
+
+    def test_package_setters(self):
+        pkg = cr.Package()
+        self.assertTrue(pkg)
+
+        pkg.pkgId = "foo"
+        self.assertEqual(pkg.pkgId, "foo")
+        pkg.name = "bar"
+        self.assertEqual(pkg.name, "bar")
+        pkg.arch = "quantum"
+        self.assertEqual(pkg.arch, "quantum")
+        pkg.version = "11"
+        self.assertEqual(pkg.version, "11")
+        pkg.epoch = "22"
+        self.assertEqual(pkg.epoch, "22")
+        pkg.release = "33"
+        self.assertEqual(pkg.release, "33")
+        pkg.summary = "sum"
+        self.assertEqual(pkg.summary, "sum")
+        pkg.description = "desc"
+        self.assertEqual(pkg.description, "desc")
+        pkg.url = "http://foo"
+        self.assertEqual(pkg.url, "http://foo")
+        pkg.time_file = 111
+        self.assertEqual(pkg.time_file, 111)
+        pkg.time_build = 112
+        self.assertEqual(pkg.time_build, 112)
+        pkg.rpm_license = "EULA"
+        self.assertEqual(pkg.rpm_license, "EULA")
+        pkg.rpm_vendor = "Me"
+        self.assertEqual(pkg.rpm_vendor, "Me")
+        pkg.rpm_group = "a-team"
+        self.assertEqual(pkg.rpm_group, "a-team")
+        pkg.rpm_buildhost = "hal3000.space"
+        self.assertEqual(pkg.rpm_buildhost, "hal3000.space")
+        pkg.rpm_sourcerpm = "source.src.rpm"
+        self.assertEqual(pkg.rpm_sourcerpm, "source.src.rpm")
+        pkg.rpm_header_start = 1
+        self.assertEqual(pkg.rpm_header_start, 1)
+        pkg.rpm_header_end = 2
+        self.assertEqual(pkg.rpm_header_end, 2)
+        pkg.rpm_packager = "Arnold Rimmer"
+        self.assertEqual(pkg.rpm_packager, "Arnold Rimmer")
+        pkg.size_package = 33
+        self.assertEqual(pkg.size_package, 33)
+        pkg.size_installed = 44
+        self.assertEqual(pkg.size_installed, 44)
+        pkg.size_archive = 55
+        self.assertEqual(pkg.size_archive, 55)
+        pkg.location_href = "package/foo.rpm"
+        self.assertEqual(pkg.location_href, "package/foo.rpm")
+        pkg.location_base = "file://dir/"
+        self.assertEqual(pkg.location_base, "file://dir/")
+        pkg.checksum_type = "crc"
+        self.assertEqual(pkg.checksum_type, "crc")
+
+        pkg.requires = [('bar', 'GE', '1', '3.2.1', None, True)]
+        self.assertEqual(pkg.requires, [('bar', 'GE', '1', '3.2.1', None, True)])
+        pkg.provides = [('foo', None, None, None, None, False)]
+        self.assertEqual(pkg.provides, [('foo', None, None, None, None, False)])
+        pkg.conflicts = [('foobar', 'LT', '0', '1.0.0', None, False)]
+        self.assertEqual(pkg.conflicts, [('foobar', 'LT', '0', '1.0.0', None, False)])
+        pkg.obsoletes = [('foobar', 'GE', '0', '1.1.0', None, False)]
+        self.assertEqual(pkg.obsoletes, [('foobar', 'GE', '0', '1.1.0', None, False)])
+        pkg.files = [(None, '/foo/', 'bar')]
+        self.assertEqual(pkg.files, [(None, '/foo/', 'bar')])
+        pkg.changelogs = [('me', 123456L, 'first commit')]
+        self.assertEqual(pkg.changelogs, [('me', 123456L, 'first commit')])
+
+        self.assertEqual(pkg.nvra(), "bar-11-33.quantum")
+        self.assertEqual(pkg.nevra(), "bar-22:11-33.quantum")
+
+    def test_package_copying(self):
+        import copy
+
+        pkg_a = cr.Package()
+        pkg_a.name = "FooPackage"
+        pkg_b = pkg_a
+        self.assertEqual(id(pkg_a), id(pkg_b))
+        pkg_c = copy.copy(pkg_a)
+        self.assertFalse(id(pkg_a) == id(pkg_c))
+        pkg_d = copy.deepcopy(pkg_a)
+        self.assertFalse(id(pkg_a) == id(pkg_d))
+        self.assertFalse(id(pkg_c) == id(pkg_d))
+
+        # Next lines shoud not fail (if copy really works)
+        del(pkg_a)
+        del(pkg_b)
+        self.assertEqual(pkg_c.name, "FooPackage")
+        del(pkg_c)
+        self.assertEqual(pkg_d.name, "FooPackage")
+        del(pkg_d)
+
diff --git a/tests/python/tests/test_parsepkg.py b/tests/python/tests/test_parsepkg.py
new file mode 100644 (file)
index 0000000..8139559
--- /dev/null
@@ -0,0 +1,68 @@
+import unittest
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseParsepkg(unittest.TestCase):
+    def test_package_from_rpm(self):
+        pkg = cr.package_from_rpm(PKG_ARCHER_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "Archer")
+
+        pkg = cr.package_from_rpm(PKG_BALICEK_ISO88591_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "balicek-iso88591")
+
+        pkg = cr.package_from_rpm(PKG_BALICEK_ISO88592_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "balicek-iso88592")
+
+        pkg = cr.package_from_rpm(PKG_BALICEK_UTF8_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "balicek-utf8")
+
+        pkg = cr.package_from_rpm(PKG_EMPTY_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "empty")
+
+        pkg = cr.package_from_rpm(PKG_EMPTY_SRC_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "empty")
+
+        pkg = cr.package_from_rpm(PKG_FAKE_BASH_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "fake_bash")
+
+        pkg = cr.package_from_rpm(PKG_SUPER_KERNEL_PATH)
+        self.assertTrue(pkg)
+        self.assertEqual(pkg.name, "super_kernel")
+
+        # Test error cases
+
+        # Rpm doesn't exists
+        self.assertRaises(IOError, cr.package_from_rpm, "this_foo_pkg_shoud_not_exists.rpm")
+
+        # Path is a directory, not a file
+        self.assertRaises(IOError, cr.package_from_rpm, "./")
+
+        # File is not a rpm
+        self.assertRaises(cr.CreaterepoCException, cr.package_from_rpm, FILE_BINARY_PATH)
+
+    def test_xml_from_rpm(self):
+        xml = cr.xml_from_rpm(PKG_ARCHER_PATH)
+        self.assertTrue(xml)
+        self.assertTrue(len(xml) == 3)
+        self.assertTrue("<name>Archer</name>" in xml[0])
+        self.assertTrue('<package pkgid="4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e" name="Archer" arch="x86_64">' in xml[1])
+        self.assertTrue('<package pkgid="4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e" name="Archer" arch="x86_64">' in xml[2])
+
+        # Test error cases
+
+        # Rpm doesn't exists
+        self.assertRaises(IOError, cr.xml_from_rpm, "this_foo_pkg_shoud_not_exists.rpm")
+
+        # Path is a directory, not a file
+        self.assertRaises(IOError, cr.xml_from_rpm, "./")
+
+        # File is not a rpm
+        self.assertRaises(cr.CreaterepoCException, cr.xml_from_rpm, FILE_BINARY_PATH)
diff --git a/tests/python/tests/test_repomd.py b/tests/python/tests/test_repomd.py
new file mode 100644 (file)
index 0000000..f55932e
--- /dev/null
@@ -0,0 +1,100 @@
+import re
+import unittest
+import shutil
+import tempfile
+import os.path
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseRepomd(unittest.TestCase):
+
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp(prefix="createrepo_ctest-")
+        self.FN_00 = "primary.xml.gz"
+        self.FN_01 = "primary.xml"
+        self.path00 = os.path.join(self.tmpdir, self.FN_00)
+        self.path01 = os.path.join(self.tmpdir, self.FN_01)
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+    def xxx_repomdrecord_fill(self):
+        self.assertRaises(TypeError, cr.RepomdRecord)
+        self.assertRaises(TypeError, cr.RepomdRecord, None)
+
+        shutil.copyfile(REPO_00_PRIXML, self.path00)
+        self.assertTrue(os.path.exists(self.path00))
+
+        rec = cr.RepomdRecord(self.path00)
+        self.assertTrue(rec)
+        rec.fill(cr.SHA256)
+        rec.rename_file()
+
+        # Filename shoud contain a (valid) checksum
+        self.assertEqual(os.listdir(self.tmpdir),
+            ['dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9-primary.xml.gz'])
+
+    def test_repomd(self):
+        shutil.copyfile(REPO_00_PRIXML, self.path00)
+        self.assertTrue(os.path.exists(self.path00))
+
+        md = cr.Repomd()
+        self.assertTrue(md)
+
+        xml = md.xml_dump()
+        # Revision shoud be current Unix time
+        self.assertTrue(re.search(r"<revision>[0-9]+</revision>", xml))
+
+        md.set_revision("foobar")
+
+        md.add_distro_tag("tag1")
+        md.add_distro_tag("tag2", "cpeid1")
+        md.add_distro_tag("tag3", cpeid="cpeid2")
+
+        md.add_repo_tag("repotag")
+
+        md.add_content_tag("contenttag")
+
+        xml = md.xml_dump()
+        self.assertEqual(xml,
+"""<?xml version="1.0" encoding="UTF-8"?>
+<repomd xmlns="http://linux.duke.edu/metadata/repo" xmlns:rpm="http://linux.duke.edu/metadata/rpm">
+  <revision>foobar</revision>
+  <tags>
+    <content>contenttag</content>
+    <repo>repotag</repo>
+    <distro cpeid="cpeid2">tag3</distro>
+    <distro cpeid="cpeid1">tag2</distro>
+    <distro>tag1</distro>
+  </tags>
+</repomd>
+""")
+
+        rec = cr.RepomdRecord(self.path00)
+        rec.fill(cr.SHA256)
+        rec.timestamp = 1
+        md.set_record(rec, "primary")
+
+        xml = md.xml_dump()
+        self.assertEqual(xml,
+"""<?xml version="1.0" encoding="UTF-8"?>
+<repomd xmlns="http://linux.duke.edu/metadata/repo" xmlns:rpm="http://linux.duke.edu/metadata/rpm">
+  <revision>foobar</revision>
+  <tags>
+    <content>contenttag</content>
+    <repo>repotag</repo>
+    <distro cpeid="cpeid2">tag3</distro>
+    <distro cpeid="cpeid1">tag2</distro>
+    <distro>tag1</distro>
+  </tags>
+  <data type="primary">
+    <checksum type="sha256">dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9</checksum>
+    <open-checksum type="sha256">e1e2ffd2fb1ee76f87b70750d00ca5677a252b397ab6c2389137a0c33e7b359f</open-checksum>
+    <location href="repodata/primary.xml.gz"/>
+    <timestamp>1</timestamp>
+    <size>134</size>
+    <open-size>167</open-size>
+  </data>
+</repomd>
+""")
diff --git a/tests/python/tests/test_repomdrecord.py b/tests/python/tests/test_repomdrecord.py
new file mode 100644 (file)
index 0000000..f425571
--- /dev/null
@@ -0,0 +1,134 @@
+import unittest
+import shutil
+import tempfile
+import os.path
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseRepomdRecord(unittest.TestCase):
+
+    # TODO:
+    #  - Test rename_file() for files with checksum
+
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp(prefix="createrepo_ctest-")
+        self.FN_00 = "primary.xml.gz"
+        self.FN_01 = "primary.xml"
+        self.path00 = os.path.join(self.tmpdir, self.FN_00)
+        self.path01 = os.path.join(self.tmpdir, self.FN_01)
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+    def test_repomdrecord_fill(self):
+        self.assertRaises(TypeError, cr.RepomdRecord)
+        self.assertRaises(TypeError, cr.RepomdRecord, None)
+
+        shutil.copyfile(REPO_00_PRIXML, self.path00)
+        self.assertTrue(os.path.exists(self.path00))
+
+        rec = cr.RepomdRecord(self.path00)
+        self.assertTrue(rec)
+
+        self.assertEqual(rec.location_real, self.path00)
+        self.assertEqual(rec.location_href, "repodata/primary.xml.gz")
+        self.assertEqual(rec.checksum, None)
+        self.assertEqual(rec.checksum_type, None)
+        self.assertEqual(rec.checksum_open, None)
+        self.assertEqual(rec.checksum_open_type, None)
+        self.assertEqual(rec.timestamp, 0)
+        self.assertEqual(rec.size, 0)
+        self.assertEqual(rec.size_open, 0)
+        self.assertEqual(rec.db_ver, 0)
+
+        rec.fill(cr.SHA256)
+
+        self.assertEqual(rec.location_real, self.path00)
+        self.assertEqual(rec.location_href, "repodata/primary.xml.gz")
+        self.assertEqual(rec.checksum, "dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9")
+        self.assertEqual(rec.checksum_type, "sha256")
+        self.assertEqual(rec.checksum_open, "e1e2ffd2fb1ee76f87b70750d00ca5677a252b397ab6c2389137a0c33e7b359f")
+        self.assertEqual(rec.checksum_open_type, "sha256")
+        self.assertTrue(rec.timestamp > 0)
+        self.assertEqual(rec.size, 134)
+        self.assertEqual(rec.size_open, 167)
+        self.assertEqual(rec.db_ver, 10)
+
+        rec.rename_file()
+
+        # Filename shoud contain a (valid) checksum
+        self.assertEqual(os.listdir(self.tmpdir),
+            ['dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9-primary.xml.gz'])
+
+    def test_repomdrecord_setters(self):
+        self.assertRaises(TypeError, cr.RepomdRecord)
+        self.assertRaises(TypeError, cr.RepomdRecord, None)
+
+        shutil.copyfile(REPO_00_PRIXML, self.path00)
+        self.assertTrue(os.path.exists(self.path00))
+
+        rec = cr.RepomdRecord(self.path00)
+        self.assertTrue(rec)
+
+        rec.fill(cr.SHA256)
+
+        self.assertEqual(rec.location_real, self.path00)
+        self.assertEqual(rec.location_href, "repodata/primary.xml.gz")
+        self.assertEqual(rec.checksum, "dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9")
+        self.assertEqual(rec.checksum_type, "sha256")
+        self.assertEqual(rec.checksum_open, "e1e2ffd2fb1ee76f87b70750d00ca5677a252b397ab6c2389137a0c33e7b359f")
+        self.assertEqual(rec.checksum_open_type, "sha256")
+        self.assertTrue(rec.timestamp > 0)
+        self.assertEqual(rec.size, 134)
+        self.assertEqual(rec.size_open, 167)
+        self.assertEqual(rec.db_ver, 10)
+
+        # Set new values
+
+        rec.location_href = "repodata/foo.xml.gz"
+        rec.checksum = "foobar11"
+        rec.checksum_type = "foo1"
+        rec.checksum_open = "foobar22"
+        rec.checksum_open_type = "foo2"
+        rec.timestamp = 123
+        rec.size = 456
+        rec.size_open = 789
+        rec.db_ver = 11
+
+        # Check
+
+        self.assertEqual(rec.location_real, self.path00)
+        self.assertEqual(rec.location_href, "repodata/foo.xml.gz")
+        self.assertEqual(rec.checksum, "foobar11")
+        self.assertEqual(rec.checksum_type, "foo1")
+        self.assertEqual(rec.checksum_open, "foobar22")
+        self.assertEqual(rec.checksum_open_type, "foo2")
+        self.assertEqual(rec.timestamp, 123)
+        self.assertEqual(rec.size, 456)
+        self.assertEqual(rec.size_open, 789)
+        self.assertEqual(rec.db_ver, 11)
+
+    def test_repomdrecord_compress_and_fill(self):
+        self.assertRaises(TypeError, cr.RepomdRecord)
+        self.assertRaises(TypeError, cr.RepomdRecord, None)
+
+        open(self.path01, "w").write("foobar\ncontent\nhh\n")
+        self.assertTrue(os.path.exists(self.path01))
+
+        rec = cr.RepomdRecord(self.path01)
+        self.assertTrue(rec)
+        rec_compressed = rec.compress_and_fill(cr.SHA256, cr.GZ_COMPRESSION)
+
+        # A new compressed file shoud be created
+        self.assertEqual(sorted(os.listdir(self.tmpdir)),
+            sorted(['primary.xml.gz', 'primary.xml']))
+
+        rec.rename_file()
+        rec_compressed.rename_file()
+
+        # Filename shoud contain a (valid) checksum
+        self.assertEqual(sorted(os.listdir(self.tmpdir)),
+            sorted(['10091f8e2e235ae875cb18c91c443891c7f1a599d41f44d518e8af759a6c8109-primary.xml.gz',
+                    'b33fc63178d852333a826385bc15d9b72cb6658be7fb927ec28c4e40b5d426fb-primary.xml']))
+
diff --git a/tests/python/tests/test_sqlite.py b/tests/python/tests/test_sqlite.py
new file mode 100644 (file)
index 0000000..b66c3ed
--- /dev/null
@@ -0,0 +1,230 @@
+import unittest
+import shutil
+import tempfile
+import os.path
+import sqlite3
+import createrepo_c as cr
+
+from fixtures import *
+
+class TestCaseSqlite(unittest.TestCase):
+
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp(prefix="createrepo_ctest-")
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+    def test_sqlite_basic_operations(self):
+        db_pri = cr.Sqlite(self.tmpdir+"/primary.db", cr.DB_PRIMARY)
+        self.assertTrue(db_pri)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/primary.db"))
+
+        db_pri = cr.PrimarySqlite(self.tmpdir+"/primary2.db")
+        self.assertTrue(db_pri)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/primary2.db"))
+
+        db_fil = cr.Sqlite(self.tmpdir+"/filelists.db", cr.DB_FILELISTS)
+        self.assertTrue(db_fil)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/filelists.db"))
+
+        db_fil = cr.FilelistsSqlite(self.tmpdir+"/filelists2.db")
+        self.assertTrue(db_fil)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/filelists2.db"))
+
+        db_oth = cr.Sqlite(self.tmpdir+"/other.db", cr.DB_OTHER)
+        self.assertTrue(db_oth)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/other.db"))
+
+        db_oth = cr.OtherSqlite(self.tmpdir+"/other2.db")
+        self.assertTrue(db_oth)
+        self.assertTrue(os.path.isfile(self.tmpdir+"/other2.db"))
+
+        # Error cases
+
+        self.assertRaises(cr.CreaterepoCException, cr.Sqlite, self.tmpdir, cr.DB_PRIMARY)
+
+        self.assertRaises(ValueError, cr.Sqlite, self.tmpdir+"/foo.db", 55)
+
+        self.assertRaises(TypeError, cr.Sqlite, self.tmpdir+"/foo.db", None)
+
+        self.assertRaises(TypeError, cr.Sqlite, None, cr.DB_PRIMARY)
+
+    def test_sqlite_primary_schema(self):
+        path = os.path.join(self.tmpdir, "primary.db")
+        cr.PrimarySqlite(path)
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+        # Check tables
+        self.assertEqual(con.execute("""select name from sqlite_master where type="table";""").fetchall(),
+            [(u'db_info',),
+             (u'packages',),
+             (u'files',),
+             (u'requires',),
+             (u'provides',),
+             (u'conflicts',),
+             (u'obsoletes',)])
+        # Check indexes
+        self.assertEqual(con.execute("""select name from sqlite_master where type="index";""").fetchall(),
+            [(u'packagename',),
+             (u'packageId',),
+             (u'filenames',),
+             (u'pkgfiles',),
+             (u'pkgrequires',),
+             (u'requiresname',),
+             (u'pkgprovides',),
+             (u'providesname',),
+             (u'pkgconflicts',),
+             (u'pkgobsoletes',)])
+        # Check triggers
+        self.assertEqual(con.execute("""select name from sqlite_master where type="trigger";""").fetchall(),
+            [(u'removals',)])
+
+    def test_sqlite_filelists_schema(self):
+        path = os.path.join(self.tmpdir, "filelists.db")
+        cr.FilelistsSqlite(path)
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+        # Check tables
+        self.assertEqual(con.execute("""select name from sqlite_master where type="table";""").fetchall(),
+            [(u'db_info',), (u'packages',), (u'filelist',)])
+        # Check indexes
+        self.assertEqual(con.execute("""select name from sqlite_master where type="index";""").fetchall(),
+            [(u'keyfile',), (u'pkgId',), (u'dirnames',)])
+        # Check triggers
+        self.assertEqual(con.execute("""select name from sqlite_master where type="trigger";""").fetchall(),
+            [(u'remove_filelist',)])
+
+    def test_sqlite_other_schema(self):
+        path = os.path.join(self.tmpdir, "other.db")
+        cr.OtherSqlite(path)
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+        # Check tables
+        self.assertEqual(con.execute("""select name from sqlite_master where type="table";""").fetchall(),
+            [(u'db_info',), (u'packages',), (u'changelog',)])
+        # Check indexes
+        self.assertEqual(con.execute("""select name from sqlite_master where type="index";""").fetchall(),
+            [(u'keychange',), (u'pkgId',)])
+        # Check triggers
+        self.assertEqual(con.execute("""select name from sqlite_master where type="trigger";""").fetchall(),
+            [(u'remove_changelogs',)])
+
+    def test_sqlite_primary(self):
+        path = os.path.join(self.tmpdir, "primary.db")
+        db = cr.Sqlite(path, cr.DB_PRIMARY)
+        pkg = cr.package_from_rpm(PKG_ARCHER_PATH)
+        db.add_pkg(pkg)
+        db.dbinfo_update("somechecksum")
+        db.close()
+
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+
+        # Check packages table
+        self.assertEqual(con.execute("select * from packages").fetchall(),
+            [(1, u'4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e',
+              u'Archer', u'x86_64', u'3.4.5', u'2', u'6', u'Complex package.',
+              u'Archer package', u'http://soo_complex_package.eu/',
+              1365416502, 1365416480, u'GPL', u'ISIS', u'Development/Tools',
+              u'localhost.localdomain', u'Archer-3.4.5-6.src.rpm', 280, 2865,
+              u'Sterling Archer', 3101, 0, 544, None, None, u'sha256')])
+
+        # Check provides table
+        self.assertEqual(con.execute("select * from provides").fetchall(),
+            [(u'bara', u'LE', u'0', u'22', None, 1),
+             (u'barb', u'GE', u'0', u'11.22.33', u'44', 1),
+             (u'barc', u'EQ', u'0', u'33', None, 1),
+             (u'bard', u'LT', u'0', u'44', None, 1),
+             (u'bare', u'GT', u'0', u'55', None, 1),
+             (u'Archer', u'EQ', u'2', u'3.4.5', u'6', 1),
+             (u'Archer(x86-64)', u'EQ', u'2', u'3.4.5', u'6', 1)])
+
+        # Check conflicts table
+        self.assertEqual(con.execute("select * from conflicts").fetchall(),
+            [(u'bba', u'LE', u'0', u'2222', None, 1),
+             (u'bbb', u'GE', u'0', u'1111.2222.3333', u'4444', 1),
+             (u'bbc', u'EQ', u'0', u'3333', None, 1),
+             (u'bbd', u'LT', u'0', u'4444', None, 1),
+             (u'bbe', u'GT', u'0', u'5555', None, 1)])
+
+        # Check obsoletes table
+        self.assertEqual(con.execute("select * from obsoletes").fetchall(),
+           [(u'aaa', u'LE', u'0', u'222', None, 1),
+            (u'aab', u'GE', u'0', u'111.2.3', u'4', 1),
+            (u'aac', u'EQ', u'0', u'333', None, 1),
+            (u'aad', u'LT', u'0', u'444', None, 1),
+            (u'aae', u'GT', u'0', u'555', None, 1)])
+
+        # Check requires table
+        self.assertEqual(con.execute("select * from requires").fetchall(),
+            [(u'fooa', u'LE', u'0', u'2', None, 1, u'FALSE'),
+             (u'foob', u'GE', u'0', u'1.0.0', u'1', 1, u'FALSE'),
+             (u'fooc', u'EQ', u'0', u'3', None, 1, u'FALSE'),
+             (u'food', u'LT', u'0', u'4', None, 1, u'FALSE'),
+             (u'fooe', u'GT', u'0', u'5', None, 1, u'FALSE'),
+             (u'foof', u'EQ', u'0', u'6', None, 1, u'TRUE')])
+
+        # Check files table
+        self.assertEqual(con.execute("select * from files").fetchall(),
+            [(u'/usr/bin/complex_a', u'file', 1)])
+
+        # Check db_info table
+        self.assertEqual(con.execute("select * from db_info").fetchall(),
+            [(10, u'somechecksum')])
+
+    def test_sqlite_filelists(self):
+        path = os.path.join(self.tmpdir, "filelists.db")
+        db = cr.Sqlite(path, cr.DB_FILELISTS)
+        pkg = cr.package_from_rpm(PKG_ARCHER_PATH)
+        db.add_pkg(pkg)
+        db.dbinfo_update("somechecksum2")
+        db.close()
+
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+
+        # Check packages table
+        self.assertEqual(con.execute("select * from packages").fetchall(),
+            [(1, u'4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e')])
+
+        # Check files table
+        self.assertEqual(con.execute("select * from filelist").fetchall(),
+            [(1, u'/usr/share/doc', u'Archer-3.4.5', u'd'),
+             (1, u'/usr/bin', u'complex_a', u'f'),
+             (1, u'/usr/share/doc/Archer-3.4.5', u'README', u'f')])
+
+        # Check db_info table
+        self.assertEqual(con.execute("select * from db_info").fetchall(),
+            [(10, u'somechecksum2')])
+
+    def test_sqlite_other(self):
+        path = os.path.join(self.tmpdir, "other.db")
+        db = cr.Sqlite(path, cr.DB_FILELISTS)
+        pkg = cr.package_from_rpm(PKG_ARCHER_PATH)
+        db.add_pkg(pkg)
+        db.dbinfo_update("somechecksum3")
+        db.close()
+
+        self.assertTrue(os.path.isfile(path))
+
+        con = sqlite3.connect(path)
+
+        # Check packages table
+        self.assertEqual(con.execute("select * from packages").fetchall(),
+            [(1, u'4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e')])
+
+        # Check filelist table
+        self.assertEqual(con.execute("select * from filelist").fetchall(),
+            [(1, u'/usr/share/doc', u'Archer-3.4.5', u'd'),
+             (1, u'/usr/bin', u'complex_a', u'f'),
+             (1, u'/usr/share/doc/Archer-3.4.5', u'README', u'f')])
+
+        # Check db_info table
+        self.assertEqual(con.execute("select * from db_info").fetchall(),
+            [(10, u'somechecksum3')])
diff --git a/tests/python/tests/test_version.py b/tests/python/tests/test_version.py
new file mode 100644 (file)
index 0000000..f2a142b
--- /dev/null
@@ -0,0 +1,10 @@
+import unittest
+import createrepo_c as cr
+
+import fixtures
+
+class TestCaseVersion(unittest.TestCase):
+    def test_version(self):
+        self.assertIsInstance(cr.VERSION_MAJOR, int);
+        self.assertIsInstance(cr.VERSION_MINOR, int);
+        self.assertIsInstance(cr.VERSION_PATCH, int);
diff --git a/tests/run_gtester.sh.in b/tests/run_gtester.sh.in
new file mode 100755 (executable)
index 0000000..58f8e39
--- /dev/null
@@ -0,0 +1 @@
+cd ${CMAKE_CURRENT_SOURCE_DIR} && gtester ${CMAKE_BINARY_DIR}/tests/test*