From a170ab0248767120725e94fa6528117b3b9ddaf3 Mon Sep 17 00:00:00 2001 From: Tomas Mlcoch Date: Mon, 6 May 2013 14:04:22 +0200 Subject: [PATCH] Python: Experimantal implementation of Python bindings + tests. --- CMakeLists.txt | 1 + README.md | 16 + src/CMakeLists.txt | 2 + src/python/CMakeLists.txt | 41 +++ src/python/__init__.py | 93 ++++++ src/python/createrepo_cmodule.c | 135 ++++++++ src/python/exception-py.c | 34 ++ src/python/exception-py.h | 29 ++ src/python/load_metadata-py.c | 339 +++++++++++++++++++ src/python/load_metadata-py.h | 29 ++ src/python/locate_metadata-py.c | 216 +++++++++++++ src/python/locate_metadata-py.h | 30 ++ src/python/metadata_hashtable.c | 254 +++++++++++++++ src/python/metadata_hashtable.h | 31 ++ src/python/package-py.c | 503 +++++++++++++++++++++++++++++ src/python/package-py.h | 33 ++ src/python/parsepkg-py.c | 113 +++++++ src/python/parsepkg-py.h | 28 ++ src/python/repomd-py.c | 227 +++++++++++++ src/python/repomd-py.h | 29 ++ src/python/repomdrecord-py.c | 276 ++++++++++++++++ src/python/repomdrecord-py.h | 30 ++ src/python/sqlite-py.c | 304 +++++++++++++++++ src/python/sqlite-py.h | 29 ++ src/python/typeconversion.c | 186 +++++++++++ src/python/typeconversion.h | 40 +++ src/python/xml_dump-py.c | 96 ++++++ src/python/xml_dump-py.h | 30 ++ tests/CMakeLists.txt | 6 + tests/python/CMakeLists.txt | 1 + tests/python/tests/CMakeLists.txt | 2 + tests/python/tests/__init__.py | 0 tests/python/tests/fixtures.py | 51 +++ tests/python/tests/run_nosetests.sh.in | 1 + tests/python/tests/test_load_metadata.py | 66 ++++ tests/python/tests/test_locate_metadata.py | 19 ++ tests/python/tests/test_package.py | 215 ++++++++++++ tests/python/tests/test_parsepkg.py | 68 ++++ tests/python/tests/test_repomd.py | 100 ++++++ tests/python/tests/test_repomdrecord.py | 134 ++++++++ tests/python/tests/test_sqlite.py | 230 +++++++++++++ tests/python/tests/test_version.py | 10 + tests/run_gtester.sh.in | 1 + 43 files changed, 4078 insertions(+) create mode 100644 src/python/CMakeLists.txt create mode 100644 src/python/__init__.py create mode 100644 src/python/createrepo_cmodule.c create mode 100644 src/python/exception-py.c create mode 100644 src/python/exception-py.h create mode 100644 src/python/load_metadata-py.c create mode 100644 src/python/load_metadata-py.h create mode 100644 src/python/locate_metadata-py.c create mode 100644 src/python/locate_metadata-py.h create mode 100644 src/python/metadata_hashtable.c create mode 100644 src/python/metadata_hashtable.h create mode 100644 src/python/package-py.c create mode 100644 src/python/package-py.h create mode 100644 src/python/parsepkg-py.c create mode 100644 src/python/parsepkg-py.h create mode 100644 src/python/repomd-py.c create mode 100644 src/python/repomd-py.h create mode 100644 src/python/repomdrecord-py.c create mode 100644 src/python/repomdrecord-py.h create mode 100644 src/python/sqlite-py.c create mode 100644 src/python/sqlite-py.h create mode 100644 src/python/typeconversion.c create mode 100644 src/python/typeconversion.h create mode 100644 src/python/xml_dump-py.c create mode 100644 src/python/xml_dump-py.h create mode 100644 tests/python/CMakeLists.txt create mode 100644 tests/python/tests/CMakeLists.txt create mode 100644 tests/python/tests/__init__.py create mode 100644 tests/python/tests/fixtures.py create mode 100755 tests/python/tests/run_nosetests.sh.in create mode 100644 tests/python/tests/test_load_metadata.py create mode 100644 tests/python/tests/test_locate_metadata.py create mode 100644 tests/python/tests/test_package.py create mode 100644 tests/python/tests/test_parsepkg.py create mode 100644 tests/python/tests/test_repomd.py create mode 100644 tests/python/tests/test_repomdrecord.py create mode 100644 tests/python/tests/test_sqlite.py create mode 100644 tests/python/tests/test_version.py create mode 100755 tests/run_gtester.sh.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 53b6f89..1ceea1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,4 +115,5 @@ ADD_CUSTOM_TARGET(tests) ADD_SUBDIRECTORY (src) ADD_SUBDIRECTORY (doc) +ENABLE_TESTING() ADD_SUBDIRECTORY (tests EXCLUDE_FROM_ALL) diff --git a/README.md b/README.md index 80e400e..6a352bd 100644 --- 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c68e6d8..8c6e526 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 index 0000000..ed69687 --- /dev/null +++ b/src/python/CMakeLists.txt @@ -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 index 0000000..f1cae00 --- /dev/null +++ b/src/python/__init__.py @@ -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 index 0000000..500b042 --- /dev/null +++ b/src/python/createrepo_cmodule.c @@ -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 + +#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 index 0000000..c1d1f63 --- /dev/null +++ b/src/python/exception-py.c @@ -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 +#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 index 0000000..dfeb02c --- /dev/null +++ b/src/python/exception-py.h @@ -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 index 0000000..74da3b6 --- /dev/null +++ b/src/python/load_metadata-py.c @@ -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 +#include +#include + +#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(""); +} + +/* 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 index 0000000..36cb51d --- /dev/null +++ b/src/python/load_metadata-py.h @@ -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 index 0000000..90463aa --- /dev/null +++ b/src/python/locate_metadata-py.c @@ -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 +#include +#include + +#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(""); +} + +/* 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 index 0000000..384b662 --- /dev/null +++ b/src/python/locate_metadata-py.h @@ -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 index 0000000..421e094 --- /dev/null +++ b/src/python/metadata_hashtable.c @@ -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 +#include +#include +#include + +#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(""); +} + +/* 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 index 0000000..05ec8af --- /dev/null +++ b/src/python/metadata_hashtable.h @@ -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 index 0000000..9db2324 --- /dev/null +++ b/src/python/package-py.c @@ -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 +#include +#include + +#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("", + (pkg->pkgId ? pkg->pkgId : "-"), + (pkg->name ? pkg->name : "-")); + } else { + repr = PyString_FromFormat(""); + } + 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 index 0000000..335d57c --- /dev/null +++ b/src/python/package-py.h @@ -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 index 0000000..bac5231 --- /dev/null +++ b/src/python/parsepkg-py.c @@ -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 +#include +#include + +#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 index 0000000..cf337c3 --- /dev/null +++ b/src/python/parsepkg-py.h @@ -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 index 0000000..2c564bb --- /dev/null +++ b/src/python/repomd-py.c @@ -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 +#include +#include + +#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(""); +} + +/* 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 index 0000000..6d44bb5 --- /dev/null +++ b/src/python/repomd-py.h @@ -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 index 0000000..dae0ba9 --- /dev/null +++ b/src/python/repomdrecord-py.c @@ -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 +#include +#include + +#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(""); +} + +/* 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 index 0000000..22c409d --- /dev/null +++ b/src/python/repomdrecord-py.h @@ -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 index 0000000..ac36994 --- /dev/null +++ b/src/python/sqlite-py.c @@ -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 +#include +#include + +#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("", 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 index 0000000..42b3492 --- /dev/null +++ b/src/python/sqlite-py.h @@ -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 index 0000000..6b778cd --- /dev/null +++ b/src/python/typeconversion.c @@ -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 +#include +#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 index 0000000..c8492a0 --- /dev/null +++ b/src/python/typeconversion.h @@ -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 +#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 index 0000000..0654193 --- /dev/null +++ b/src/python/xml_dump-py.c @@ -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 +#include +#include + +#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 index 0000000..17c3170 --- /dev/null +++ b/src/python/xml_dump-py.h @@ -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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f03f99..cd373c6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 index 0000000..64ccc72 --- /dev/null +++ b/tests/python/CMakeLists.txt @@ -0,0 +1 @@ +ADD_SUBDIRECTORY (tests) diff --git a/tests/python/tests/CMakeLists.txt b/tests/python/tests/CMakeLists.txt new file mode 100644 index 0000000..cab63c6 --- /dev/null +++ b/tests/python/tests/CMakeLists.txt @@ -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 index 0000000..e69de29 diff --git a/tests/python/tests/fixtures.py b/tests/python/tests/fixtures.py new file mode 100644 index 0000000..33f741e --- /dev/null +++ b/tests/python/tests/fixtures.py @@ -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 index 0000000..64041b8 --- /dev/null +++ b/tests/python/tests/run_nosetests.sh.in @@ -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 index 0000000..55d94ed --- /dev/null +++ b/tests/python/tests/test_load_metadata.py @@ -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 index 0000000..f6e77f6 --- /dev/null +++ b/tests/python/tests/test_locate_metadata.py @@ -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 index 0000000..a493ab5 --- /dev/null +++ b/tests/python/tests/test_package.py @@ -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 - 1.1.1-1', 1334664000L, + '- First changelog.'), + ('Tomas Mlcoch - 2.2.2-2', 1334750400L, + '- That was totally ninja!'), + ('Tomas Mlcoch - 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 index 0000000..8139559 --- /dev/null +++ b/tests/python/tests/test_parsepkg.py @@ -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("Archer" in xml[0]) + self.assertTrue('' in xml[1]) + self.assertTrue('' 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 index 0000000..f55932e --- /dev/null +++ b/tests/python/tests/test_repomd.py @@ -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"[0-9]+", 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, +""" + + foobar + + contenttag + repotag + tag3 + tag2 + tag1 + + +""") + + rec = cr.RepomdRecord(self.path00) + rec.fill(cr.SHA256) + rec.timestamp = 1 + md.set_record(rec, "primary") + + xml = md.xml_dump() + self.assertEqual(xml, +""" + + foobar + + contenttag + repotag + tag3 + tag2 + tag1 + + + dabe2ce5481d23de1f4f52bdcfee0f9af98316c9e0de2ce8123adeefa0dd08b9 + e1e2ffd2fb1ee76f87b70750d00ca5677a252b397ab6c2389137a0c33e7b359f + + 1 + 134 + 167 + + +""") diff --git a/tests/python/tests/test_repomdrecord.py b/tests/python/tests/test_repomdrecord.py new file mode 100644 index 0000000..f425571 --- /dev/null +++ b/tests/python/tests/test_repomdrecord.py @@ -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 index 0000000..b66c3ed --- /dev/null +++ b/tests/python/tests/test_sqlite.py @@ -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 index 0000000..f2a142b --- /dev/null +++ b/tests/python/tests/test_version.py @@ -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 index 0000000..58f8e39 --- /dev/null +++ b/tests/run_gtester.sh.in @@ -0,0 +1 @@ +cd ${CMAKE_CURRENT_SOURCE_DIR} && gtester ${CMAKE_BINARY_DIR}/tests/test* -- 2.7.4