├── scripts ├── pycman-sync ├── pycman-query ├── pycman-remove ├── pycman-database ├── pycman-deptest ├── pycman-upgrade ├── pycman-version ├── lsoptdepends └── pycman ├── .flake8 ├── test ├── .gitignore ├── conftest.py ├── test_db.py ├── test_package.py ├── test_transaction.py ├── test_alpm.py ├── tests.py └── test_handle.py ├── .gitignore ├── MANIFEST.in ├── RELEASING.txt ├── INSTALL ├── AUTHORS ├── .travis.yml ├── PKGBUILD ├── doc ├── Makefile ├── index.rst ├── pyalpm │ ├── Database.rst │ ├── Handle.rst │ ├── pyalpm.rst │ └── Package.rst └── conf.py ├── TODO ├── Makefile ├── README.md ├── src ├── db.h ├── pyalpm.h ├── package.h ├── handle.h ├── util.h ├── options.h ├── pyalpm.c ├── util.c ├── db.c ├── options.c ├── package.c ├── handle.c └── transaction.c ├── pycman ├── __init__.py ├── action_version.py ├── action_deptest.py ├── action_database.py ├── action_upgrade.py ├── action_remove.py ├── transaction.py ├── pkginfo.py ├── action_query.py ├── action_sync.py └── config.py ├── setup.py ├── NEWS └── LICENSE /scripts/pycman-sync: -------------------------------------------------------------------------------- 1 | ../pycman/action_sync.py -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /scripts/pycman-query: -------------------------------------------------------------------------------- 1 | ../pycman/action_query.py -------------------------------------------------------------------------------- /scripts/pycman-remove: -------------------------------------------------------------------------------- 1 | ../pycman/action_remove.py -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | ext-coverage 3 | -------------------------------------------------------------------------------- /scripts/pycman-database: -------------------------------------------------------------------------------- 1 | ../pycman/action_database.py -------------------------------------------------------------------------------- /scripts/pycman-deptest: -------------------------------------------------------------------------------- 1 | ../pycman/action_deptest.py -------------------------------------------------------------------------------- /scripts/pycman-upgrade: -------------------------------------------------------------------------------- 1 | ../pycman/action_upgrade.py -------------------------------------------------------------------------------- /scripts/pycman-version: -------------------------------------------------------------------------------- 1 | ../pycman/action_version.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | MANIFEST 3 | build 4 | dist 5 | __pycache__ 6 | .coverage 7 | .cache 8 | docs/_build 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include NEWS 2 | include INSTALL 3 | include LICENSE 4 | include src/*.h 5 | include scripts/pycman 6 | include scripts/lsoptdepends 7 | 8 | -------------------------------------------------------------------------------- /RELEASING.txt: -------------------------------------------------------------------------------- 1 | # Releasing pyalpm 2 | 3 | 1. Bump version in setup.py 4 | 2. commit 5 | 3. git tag 6 | 7 | ## Uploading to PyPI 8 | 9 | 1. python3 setup.py sdist 10 | 2. twine upload dist/* --verbose 11 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Requirements: 2 | 3 | * Python 3 (at least version 3.1 for the C module, 3.2 for pycman) 4 | 5 | * pacman 3.5 (with libalpm.so.6) 6 | 7 | Installation instructions: 8 | 9 | $ python3 setup.py build 10 | $ python3 setup.py install --root=/usr 11 | 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Dan McGee 2 | Imanol Celaya 3 | Jelle van der Waa 4 | Øyvind Heggstad 5 | Rémy Oudompheng 6 | Tasos Latsas 7 | Xyne 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | sudo: required 4 | 5 | env: ARCH_TRAVIS_CLEAN_CHROOT=1 6 | 7 | arch: 8 | 9 | packages: 10 | - python 11 | - python-pytest 12 | - python-pytest-cov 13 | - python-sphinx 14 | 15 | script: 16 | - python3 -m py_compile $(git ls-files '*.py') 17 | - sudo python3 setup.py install 18 | - sudo mkdir /var/cache/pacman/pkg 19 | - make test 20 | - make -C doc html 21 | 22 | script: 'curl -s https://raw.githubusercontent.com/mikkeloscar/arch-travis/master/arch-travis.sh | bash' 23 | 24 | # vim: ft=yaml ts=2 sw=2 et: 25 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer : Rémy Oudompheng 2 | 3 | pkgname=pyalpm 4 | pkgver=0.4.2 5 | pkgrel=1 6 | pkgdesc="Libalpm bindings for Python 3" 7 | arch=('i686' 'x86_64') 8 | url="http://projects.archlinux.org/users/remy/pyalpm.git/" 9 | license=('GPL') 10 | depends=('python>=3.2' 'pacman<3.6') 11 | source=("ftp://ftp.archlinux.org/other/pyalpm/$pkgname-$pkgver.tar.gz") 12 | md5sums=('d5d45cafa98050a4d3c77e4a8f597ff3') 13 | 14 | build() { 15 | cd ${srcdir}/${pkgname}-${pkgver} 16 | python setup.py build 17 | } 18 | 19 | package() { 20 | cd ${srcdir}/${pkgname}-${pkgver} 21 | python setup.py install --root=${pkgdir} 22 | } 23 | 24 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | pacman.conf 2 | ----------- 3 | 4 | Databases 5 | --------- 6 | correct alpm_db_setserver interface 7 | alpm_db_unregister 8 | alpm_db_unregister_all 9 | 10 | Packages 11 | -------- 12 | * alpm_pkg_checkmd5sum 13 | * alpm_fetch_pkgurl 14 | 15 | Deltas 16 | ------ 17 | * everything 18 | 19 | Transactions 20 | ------------ 21 | * finish event callback 22 | * conversation callback 23 | 24 | Dependencies/Missing/Conflicts 25 | ------------------------------ 26 | * alpm_checkconflicts() 27 | * alpm_checkdeps() 28 | * more readable output of conversion functions. 29 | 30 | Options 31 | ------- 32 | * alpm_option_{get,set}_logcb() 33 | 34 | Pycman 35 | ------ 36 | Take all pacman.conf entries into account 37 | 38 | Implement missing options: 39 | * -v 40 | * -Qk 41 | * -Sc 42 | 43 | Polish -S/R/U user interface 44 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. pyalpm documentation master file, created by 2 | sphinx-quickstart on Mon Jan 7 22:46:50 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Pyalpm documentation 7 | ===================== 8 | 9 | Pyalpm is a python package that provides you native bindings to both libalpm 10 | and pacman. If you need to interact with alpm databases or programatically call 11 | pacman in a safe, sensible way, you're in the right place. 12 | 13 | 14 | Package structure 15 | ----------------- 16 | 17 | Pyalpm exposes two python modules: alpm (python bindings libalpm) and pycman (a 18 | python module exposing pacman functionalities). 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | :caption: Contents: 23 | 24 | pyalpm/pyalpm 25 | 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Inspired by https://github.com/archlinux/arch-security-tracker/blob/master/Makefile 2 | 3 | PYTEST?=py.test 4 | PYTEST_OPTIONS+=-s 5 | PYTEST_INPUT?=test 6 | PYTEST_COVERAGE_OPTIONS+=--cov-report=term-missing --cov-report=html:test/coverage --cov=pycman 7 | EXT_COVERAGE_DIR=test/ext-coverage 8 | BUILD_DIR=build/lib.linux-x86_64-3.7 9 | 10 | .PHONY: test 11 | 12 | test: test-py 13 | 14 | test-py coverage: 15 | PYTHONPATH="${BUILD_DIR}:.:${PYTHONPATH}" ${PYTEST} ${PYTEST_INPUT} ${PYTEST_OPTIONS} ${PYTEST_COVERAGE_OPTIONS} 16 | 17 | open-coverage: coverage 18 | ${BROWSER} test/coverage/index.html 19 | 20 | ext-coverage: 21 | LDFLAGS="-fprofile-arcs -ftest-coverage" CFLAGS="-fprofile-arcs -ftest-coverage" python setup.py build 22 | PYTHONPATH="${BUILD_DIR}:.:${PYTHONPATH}" ${PYTEST} ${PYTEST_INPUT} ${PYTEST_OPTIONS} 23 | 24 | test -d ${EXT_COVERAGE_DIR} || mkdir ${EXT_COVERAGE_DIR} 25 | gcovr -r . --html -o ${EXT_COVERAGE_DIR}/index.html --html-details --html-title="pyalpm C coverage" 26 | 27 | open-ext-coverage: ext-coverage 28 | ${BROWSER} ${EXT_COVERAGE_DIR}/index.html 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyalpm - python bindings for the libalpm library 2 | 3 | [![Build Status](https://travis-ci.org/archlinux/pyalpm.svg?branch=master)](https://travis-ci.com/archlinux/pyalpm) [![Documentation Status](https://readthedocs.org/projects/pyalpm/badge/?version=latest)](https://pyalpm.readthedocs.io/en/latest/?badge=latest) 4 | 5 | pyalpm is a C extension for Python 3 that give access to the 6 | libalpm API for package management, which is used, for example 7 | in the Arch Linux distribution. 8 | 9 | # Requirements 10 | 11 | * Python 3 or later 12 | 13 | * libalpm 10 or later 14 | 15 | * python-setuptools 16 | 17 | * python-sphinx (optional, to build the docs) 18 | 19 | # Building 20 | 21 | Building is as easy as running make 22 | 23 | make 24 | 25 | Building the docs: 26 | 27 | make -C doc html 28 | 29 | # Testing 30 | 31 | Required test dependency: 32 | 33 | * python-pytest 34 | * gcovr (optional, generating C coverage) 35 | 36 | Unit tests can be run with: 37 | 38 | make test 39 | 40 | Coverage for Python code can be generated as following: 41 | 42 | make open-coverage 43 | 44 | Coverage for the CPython code can be generated as following: 45 | 46 | make open-ext-coverage 47 | 48 | -------------------------------------------------------------------------------- /src/db.h: -------------------------------------------------------------------------------- 1 | /** 2 | * db.c : wrapper class around alpm_db_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #ifndef _PYALPM_DB_H 24 | #define _PYALPM_DB_H 25 | 26 | #include 27 | 28 | PyObject *pyalpm_db_from_pmdb(void* data); 29 | int pylist_db_to_alpmlist(PyObject *list, alpm_list_t **result); 30 | 31 | PyObject* pyalpm_find_grp_pkgs(PyObject* self, PyObject* args); 32 | PyObject* pyalpm_sync_newversion(PyObject *self, PyObject* args); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/pyalpm.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2008 Imanol Celaya 4 | 5 | This file is part of pyalpm. 6 | 7 | pyalpm is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | pyalpm is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with pyalpm. If not, see . 19 | 20 | */ 21 | #include 22 | 23 | #ifndef PYALPM_H 24 | #define PYALPM_H 25 | 26 | /*pyalpm internal variables*/ 27 | char *error, *warning, *debug, *function; 28 | unsigned short enable_messages_logcb = 0; 29 | 30 | /* submodules init */ 31 | int init_pyalpm_handle(PyObject *module); 32 | void init_pyalpm_db(PyObject *module); 33 | void init_pyalpm_package(PyObject *module); 34 | int init_pyalpm_transaction(PyObject *module); 35 | 36 | #endif /* PYALPM_H */ 37 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | from shutil import rmtree 2 | from tempfile import mkdtemp 3 | 4 | import pytest 5 | 6 | from pyalpm import Handle 7 | 8 | 9 | ARCH = 'x86_64' 10 | PKG = 'pacman' 11 | REPO_1 = 'core' 12 | TEST_MIRROR = 'http://mirror.rackspace.com/archlinux/{}/os/{}' 13 | 14 | 15 | @pytest.fixture() 16 | def handle(): 17 | return Handle('/', '/tmp') 18 | 19 | 20 | @pytest.fixture() 21 | def localdb(handle): 22 | return handle.get_localdb() 23 | 24 | 25 | @pytest.fixture(scope="module") 26 | def real_handle(): 27 | dbpath = mkdtemp(dir='/tmp') 28 | handle = Handle('/', dbpath) 29 | repo = handle.register_syncdb(REPO_1, 0) 30 | repo.servers = [TEST_MIRROR.format(REPO_1, ARCH)] 31 | yield handle 32 | rmtree(dbpath) 33 | 34 | 35 | @pytest.fixture(scope="module") 36 | def syncdb(real_handle, name=REPO_1): 37 | for repo in real_handle.get_syncdbs(): 38 | if repo.name == name: 39 | return repo 40 | 41 | 42 | @pytest.fixture(scope="module") 43 | def package(syncdb, name=PKG): 44 | syncdb.update(False) 45 | return syncdb.get_pkg(name) 46 | 47 | 48 | @pytest.fixture() 49 | def transaction(real_handle): 50 | transaction = real_handle.init_transaction() 51 | yield transaction 52 | transaction.release() 53 | -------------------------------------------------------------------------------- /test/test_db.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from conftest import handle, localdb, syncdb 4 | 5 | 6 | def test_empty_getsyncdb(handle): 7 | assert handle.get_syncdbs() == [] 8 | 9 | def test_localdb_empty(localdb): 10 | assert localdb.get_pkg('foo') is None 11 | 12 | def test_search_empty(localdb): 13 | assert localdb.search('bar') == [] 14 | 15 | def test_read_grp(localdb): 16 | assert localdb.read_grp('foo') is None 17 | 18 | def test_read_grp_error(localdb): 19 | with pytest.raises(TypeError) as excinfo: 20 | localdb.read_grp() 21 | assert 'expected string argument' in str(excinfo.value) 22 | 23 | def test_get_pkg(localdb): 24 | with pytest.raises(TypeError) as excinfo: 25 | localdb.get_pkg() 26 | assert 'takes a string argument' in str(excinfo.value) 27 | 28 | def test_update(syncdb): 29 | syncdb.update(False) 30 | assert not syncdb.search('pacman') is None 31 | 32 | # DB properties 33 | 34 | def test_db_name(localdb): 35 | assert localdb.name == 'local' 36 | 37 | def test_db_servers(localdb): 38 | assert localdb.servers == [] 39 | 40 | def test_db_pkgcache(localdb): 41 | assert localdb.pkgcache == [] 42 | 43 | def test_db_grpcache_empty(localdb): 44 | assert localdb.grpcache == [] 45 | 46 | # vim: set ts=4 sw=4 et: 47 | -------------------------------------------------------------------------------- /test/test_package.py: -------------------------------------------------------------------------------- 1 | from conftest import package, PKG 2 | 3 | from pyalpm import Package 4 | 5 | 6 | def test_db(package): 7 | assert not package.db is None 8 | 9 | def test_name(package): 10 | assert package.name == PKG 11 | 12 | def test_version(package): 13 | assert package.version != '' 14 | 15 | def test_arch(package): 16 | assert package.arch == 'x86_64' 17 | 18 | def test_size(package): 19 | assert isinstance(package.size, int) 20 | 21 | def test_isize(package): 22 | assert isinstance(package.isize, int) 23 | 24 | def test_reason(package): 25 | assert package.reason == 0 26 | 27 | def test_builddate(package): 28 | assert isinstance(package.builddate, int) 29 | 30 | def test_installdate(package): 31 | assert package.installdate == 0 32 | 33 | def test_files(package): 34 | assert package.files == [] 35 | 36 | def test_backup(package): 37 | assert package.backup == [] 38 | 39 | def test_deltas(package): 40 | assert package.deltas == [] 41 | 42 | def test_depends(package): 43 | assert package.depends != [] 44 | 45 | def test_has_scriptlet(package): 46 | assert isinstance(package.has_scriptlet, bool) 47 | 48 | def test_download_size(package): 49 | assert package.download_size > 0 50 | 51 | def test_compute_requiredby(package): 52 | assert package.compute_requiredby() == [] 53 | 54 | def test_repr(package): 55 | assert repr(Package) == "" 56 | assert PKG in repr(package) 57 | 58 | def test_str(package): 59 | assert PKG in str(package) 60 | -------------------------------------------------------------------------------- /src/package.h: -------------------------------------------------------------------------------- 1 | /** 2 | * package.c : wrapper class around alpm_pkg_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #ifndef _PYALPM_PACKAGE_H 24 | #define _PYALPM_PACKAGE_H 25 | 26 | #include 27 | 28 | typedef struct _AlpmPackage { 29 | PyObject_HEAD 30 | alpm_pkg_t *c_data; 31 | int needs_free; 32 | } AlpmPackage; 33 | 34 | #define ALPM_PACKAGE(self) (((AlpmPackage*)(self))->c_data) 35 | 36 | PyTypeObject AlpmPackageType; 37 | int PyAlpmPkg_Check(PyObject *object); 38 | 39 | void pyalpm_pkg_unref(PyObject *object); 40 | 41 | PyObject *pyalpm_package_from_pmpkg(void* data); 42 | alpm_pkg_t *pmpkg_from_pyalpm_pkg(PyObject *object); 43 | 44 | int pylist_pkg_to_alpmlist(PyObject *list, alpm_list_t **result); 45 | 46 | PyObject *pyalpm_package_load(PyObject *self, PyObject *args, PyObject *kwargs); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/handle.h: -------------------------------------------------------------------------------- 1 | /** 2 | * handle.h : wrapper class around alpm_handle_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #ifndef PYALPM_HANDLE_H 24 | #define PYALPM_HANDLE_H 25 | 26 | #include 27 | 28 | typedef enum _pyalpm_callback_id { 29 | CB_LOG, 30 | CB_DOWNLOAD, 31 | CB_FETCH, 32 | CB_TOTALDL, 33 | CB_EVENT, 34 | CB_QUESTION, 35 | CB_PROGRESS, 36 | N_CALLBACKS 37 | } pyalpm_callback_id; 38 | 39 | typedef struct _AlpmHandle { 40 | PyObject_HEAD 41 | alpm_handle_t *c_data; 42 | /* PyObject *py_callbacks[N_CALLBACKS]; */ 43 | } AlpmHandle; 44 | 45 | #define ALPM_HANDLE(self) (((AlpmHandle*)(self))->c_data) 46 | 47 | PyObject *pyalpm_release(PyObject* self, PyObject* args); 48 | 49 | /* from transaction.c */ 50 | PyObject *pyalpm_transaction_from_pmhandle(void* data); 51 | PyObject* pyalpm_trans_init(PyObject *self, PyObject *args, PyObject *kwargs); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /test/test_transaction.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | from pytest import raises 3 | 4 | from conftest import real_handle as handle, transaction, package, PKG 5 | 6 | from pyalpm import error 7 | 8 | 9 | def test_cb_download(handle): 10 | cb_dl = mock.Mock() 11 | handle.dlcb = cb_dl 12 | db = handle.get_syncdbs()[0] 13 | db.update(False) 14 | cb_dl.assert_called() 15 | 16 | def test_logcb(handle): 17 | cb_log = mock.Mock() 18 | handle.logcb = cb_log 19 | db = handle.get_syncdbs()[0] 20 | db.update(False) 21 | cb_log.assert_called() 22 | assert handle.logcb == cb_log 23 | 24 | 25 | def test_add_pkg_not_satisfied(handle, transaction, package): 26 | transaction.add_pkg(package) 27 | with raises(error) as excinfo: 28 | transaction.prepare() 29 | assert 'could not satisfy dependencies' in str(excinfo) 30 | 31 | def test_add_pkg_error(transaction): 32 | with raises(TypeError) as excinfo: 33 | transaction.add_pkg(PKG) 34 | assert 'must be alpm.Package' in str(excinfo.value) 35 | 36 | def test_remove_pkg_error(transaction): 37 | with raises(TypeError) as excinfo: 38 | transaction.remove_pkg(PKG) 39 | assert 'must be alpm.Package' in str(excinfo.value) 40 | 41 | def test_flags(transaction): 42 | assert isinstance(transaction.flags, dict) 43 | 44 | def test_to_add(transaction): 45 | assert transaction.to_add == [] 46 | 47 | def test_to_remove(transaction): 48 | assert transaction.to_remove == [] 49 | 50 | def test_sysupgrade(transaction): 51 | # No packages installed 52 | transaction.sysupgrade(False) 53 | assert transaction.to_add == [] 54 | assert transaction.to_remove == [] 55 | -------------------------------------------------------------------------------- /test/test_alpm.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pyalpm 3 | 4 | from conftest import package, syncdb, PKG 5 | 6 | 7 | def test_alpm_version(): 8 | assert pyalpm.alpmversion() != '' 9 | 10 | def test_version(): 11 | assert pyalpm.version() != '' 12 | 13 | def test_vercmp_smaller(): 14 | assert pyalpm.vercmp('1', '2') == -1 15 | 16 | def test_vercmp_greater(): 17 | assert pyalpm.vercmp('2', '1') == 1 18 | assert pyalpm.vercmp('2.0-1', '1.7-6') == 1 19 | 20 | def test_vercmp_equal(): 21 | assert pyalpm.vercmp('1', '1') == 0 22 | assert pyalpm.vercmp('1.0', '1.0-10') == 0 23 | 24 | def test_vercmp_epoch(): 25 | assert pyalpm.vercmp('4.34', '1:001') == -1 26 | 27 | def test_find_satisfier(package): 28 | assert pyalpm.find_satisfier([package], PKG).name == package.name 29 | assert pyalpm.find_satisfier([package], 'bar') is None 30 | 31 | def test_find_satisfier_error(): 32 | with pytest.raises(TypeError) as excinfo: 33 | pyalpm.find_satisfier() 34 | assert 'takes a Package list and a string' in str(excinfo.value) 35 | 36 | def test_find_grp_pkgs(syncdb): 37 | assert pyalpm.find_grp_pkgs([syncdb], 'test') == [] 38 | 39 | def test_find_grp_pkgs_error(): 40 | with pytest.raises(TypeError) as excinfo: 41 | pyalpm.find_grp_pkgs() 42 | assert 'expected arguments' in str(excinfo.value) 43 | 44 | def test_sync_newversion(syncdb, package): 45 | assert pyalpm.sync_newversion(package, [syncdb]) is None 46 | 47 | def test_sync_newversion_error(): 48 | with pytest.raises(TypeError) as excinfo: 49 | pyalpm.sync_newversion() 50 | assert 'takes a Package and a list of DBs' in str(excinfo.value) 51 | 52 | # vim: set ts=4 sw=4 et: 53 | -------------------------------------------------------------------------------- /pycman/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | from . import action_database 22 | from . import action_deptest 23 | from . import action_query 24 | from . import action_remove 25 | from . import action_sync 26 | from . import action_upgrade 27 | from . import action_version 28 | 29 | ACTIONS = { 30 | 'db': action_database.main, 31 | 'deptest': action_deptest.main, 32 | 'query': action_query.main, 33 | 'remove': action_remove.main, 34 | 'sync': action_sync.main, 35 | 'upgrade': action_upgrade.main, 36 | 'version': action_version.main 37 | } 38 | 39 | def run_action_with_args(action, args): 40 | if action not in ACTIONS: 41 | print("Invalid action specified (%s are supported)" % ', '.join(ACTIONS.keys())) 42 | return 1 43 | else: 44 | callback = ACTIONS[action] 45 | return callback(args) 46 | 47 | # vim: set ts=4 sw=4 tw=0 noet: 48 | 49 | -------------------------------------------------------------------------------- /pycman/action_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-version - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -V 23 | 24 | This script prints version information about pycman and pyalpm. 25 | """ 26 | 27 | import pyalpm 28 | 29 | VERSION_STRING = """ 30 | Pycman running on pyalpm v%s - libalpm v%s 31 | .--. 32 | / _.-' .-. .-. .-. libalpm: 33 | \\ '-. '-' '-' '-' Copyright (C) 2006-2011 Pacman Development Team 34 | '--' Copyright (C) 2002-2006 Judd Vinet 35 | pyalpm: 36 | Copyright (C) 2011 Rémy Oudompheng 37 | 38 | This program may be freely redistributed under 39 | the terms of the GNU General Public License. 40 | """ 41 | 42 | def main(args): 43 | print(VERSION_STRING % (pyalpm.version(), pyalpm.alpmversion())) 44 | return 2 45 | 46 | if __name__ == "__main__": 47 | import sys 48 | ret = main(sys.argv[1:]) 49 | sys.exit(ret) 50 | 51 | # vim: set ts=4 sw=4 tw=0 noet: 52 | -------------------------------------------------------------------------------- /test/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyalpm 3 | 4 | h = pyalpm.Handle("/", "/var/lib/pacman") 5 | 6 | localdb = h.get_localdb() 7 | print("Local database information") 8 | print(" DB name:", localdb.name, "servers:", localdb.servers) 9 | print(" Packages:", len(localdb.pkgcache)) 10 | 11 | for name, pkgs in localdb.grpcache: 12 | print(" Group:", name, [pkg.name for pkg in pkgs]) 13 | print("") 14 | 15 | print("Registering [core], [extra], [community]...") 16 | core = h.register_syncdb("core", pyalpm.SIG_DATABASE_OPTIONAL) 17 | extra = h.register_syncdb("extra", pyalpm.SIG_DATABASE_OPTIONAL) 18 | community = h.register_syncdb("community", pyalpm.SIG_DATABASE_OPTIONAL) 19 | print("") 20 | 21 | print("Available sync DBs") 22 | for db in h.get_syncdbs(): 23 | print(" DB:", db.name, "servers:", db.servers) 24 | print("") 25 | 26 | print("Package information about glibc") 27 | pkg = localdb.get_pkg("glibc") 28 | for attr in dir(pkg): 29 | if attr.startswith('_'): 30 | continue 31 | if attr == "files": 32 | print(" ", len(pkg.files), "files") 33 | else: 34 | print(" ", attr, ":", getattr(pkg, attr)) 35 | print(" Required by:", ' '.join(pkg.compute_requiredby())) 36 | print("") 37 | 38 | print("Package information about a tarball") 39 | for i in os.listdir("/var/cache/pacman/pkg"): 40 | filename = os.path.join("/var/cache/pacman/pkg", i) 41 | pkg = h.load_pkg(filename) 42 | print("Loaded", filename) 43 | break 44 | for attr in dir(pkg): 45 | if attr.startswith('_'): 46 | continue 47 | if attr == "files": 48 | print(" ", len(pkg.files), "files") 49 | else: 50 | print(" ", attr, ":", getattr(pkg, attr)) 51 | print("") 52 | 53 | print("Information about group gnome") 54 | l = pyalpm.find_grp_pkgs([core, extra, community], "gnome") 55 | for pkg in l: 56 | print(" ", pkg.name, "from", pkg.db.name) 57 | print("") 58 | 59 | # vim: set ts=4 sw=4 et: 60 | -------------------------------------------------------------------------------- /pycman/action_deptest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-deptest - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -T 23 | 24 | This script checks whether specified dependencies are satisfied 25 | and prints out a list of those which are missing. 26 | """ 27 | 28 | import sys 29 | import pyalpm 30 | from pycman import config 31 | 32 | handle = None 33 | 34 | def deptest(deps): 35 | db = handle.get_localdb() 36 | missing = [dep for dep in deps if pyalpm.find_satisfier(db.pkgcache, dep) is None] 37 | return missing 38 | 39 | def main(rawargs): 40 | global handle 41 | parser = config.make_parser() 42 | parser.add_argument('deps', metavar='dep', nargs='*', 43 | help="a dependency string, e.g. 'pacman>=3.4.0'") 44 | args = parser.parse_args(rawargs) 45 | handle = config.init_with_config_and_options(args) 46 | 47 | if args.verbose: 48 | print("deptest " + " ".join(rawargs), file=sys.stderr) 49 | missing = deptest(args.deps) 50 | 51 | if len(missing) == 0: 52 | return 0 53 | else: 54 | [print(dep) for dep in missing] 55 | return 127 56 | 57 | if __name__ == "__main__": 58 | ret = main(sys.argv[1:]) 59 | sys.exit(ret) 60 | 61 | # vim: set ts=4 sw=4 tw=0 noet: 62 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * util.h : utility functions for pyalpm 3 | * 4 | * Copyright 2008 Imanol Celaya 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #ifndef _PY_ALPM_UTIL_H 24 | #define _PY_ALPM_UTIL_H 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | /** error objects */ 31 | extern PyObject* alpm_error; 32 | void init_pyalpm_error(PyObject* module); 33 | 34 | #define RET_ERR(msg, errno, ret) do { \ 35 | PyObject *error_obj = Py_BuildValue("(siO)", msg, errno, Py_None); \ 36 | Py_INCREF(Py_None); \ 37 | PyErr_SetObject(alpm_error, error_obj); \ 38 | return ret; \ 39 | } while(0) 40 | 41 | /* macro to return Python objects in the exception: the use of 'N' 42 | * format char is to steal a reference to arg 43 | */ 44 | #define RET_ERR_DATA(msg, errno, arg, ret) do { \ 45 | PyObject *error_obj = Py_BuildValue("(siN)", msg, errno, arg); \ 46 | PyErr_SetObject(alpm_error, error_obj); \ 47 | return ret; \ 48 | } while(0) 49 | 50 | typedef PyObject *(pyobjectbuilder)(void*); 51 | PyObject* pyobject_from_string(void *s); 52 | 53 | /** List conversion functions */ 54 | PyObject* alpmlist_to_pylist(alpm_list_t *prt, pyobjectbuilder pybuilder); 55 | int pylist_string_to_alpmlist(PyObject *list, alpm_list_t* *result); 56 | 57 | #endif 58 | 59 | /* vim: set ts=2 sw=2 et: */ 60 | -------------------------------------------------------------------------------- /doc/pyalpm/Database.rst: -------------------------------------------------------------------------------- 1 | Databases 2 | ========= 3 | 4 | A database object represents a collection of packages. A database can be 5 | accessed by means of a Handle object, and can be used to query for package 6 | information. 7 | 8 | .. py:class:: Database 9 | 10 | 11 | .. py:attribute:: name (string) 12 | 13 | The name of this database (for example, "core") 14 | 15 | .. py:attribute:: servers (list) 16 | 17 | A list of servers from where this database was fetched 18 | 19 | .. py:attribute:: pkgcache (list) 20 | 21 | A list of references to the packages in this database 22 | 23 | .. py:attribute:: grpcache (list) 24 | 25 | A list of tuples of the form: 26 | :: 27 | 28 | [ 29 | ( 'GROUPNAME', [package-list] ), 30 | ( 'GROUPNAME2', [...]), 31 | ... 32 | ] 33 | 34 | 35 | 36 | .. py:method:: get_pkg(name: string) 37 | 38 | Retrieves a package instance with the name 'name' 39 | 40 | :param str name: The name of the package (e.g., 'coreutils') 41 | :returns: a reference to the Package object with the name 'name' or None 42 | if it doesn't exist 43 | 44 | .. py:method:: read_group(name: string) 45 | 46 | Retrieves the list of packages that belong to the group by the name passed 47 | 48 | :param str name: The name of the group (e.g., 'base') 49 | :returns: a list of package objects that correspond to the packages that 50 | belong to that group or None if the group is not found. 51 | 52 | .. py:method:: update(force: boolean) 53 | 54 | Attempts to update the sync database (i.e., alpm_db_update). 55 | 56 | :param bool force: If the database should be updated even if it's up-to-date 57 | :returns: True if the update was successful, or an error if that's not 58 | the case. 59 | 60 | .. py:method:: search(query: string) 61 | 62 | Search this database for a package with the name matching the query. 63 | 64 | :param str query: a regexp representing the package searched for. 65 | :returns: a list of package objects matching the query by name 66 | 67 | -------------------------------------------------------------------------------- /pycman/action_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-deptest - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -D 23 | 24 | This script allows to modify install reasons for packages in local 25 | database. 26 | """ 27 | 28 | import sys 29 | import pyalpm 30 | from pycman import config 31 | 32 | handle = None 33 | 34 | def commit(pkgs, mode): 35 | db = handle.get_localdb() 36 | for pkgname in pkgs: 37 | pkg = db.get_pkg(pkgname) 38 | handle.set_pkgreason(pkg, mode) 39 | 40 | def main(rawargs): 41 | global handle 42 | parser = config.make_parser() 43 | mode = parser.add_mutually_exclusive_group(required=True) 44 | mode.add_argument('--asdeps', dest='mode', 45 | action="store_const", 46 | const=pyalpm.PKG_REASON_DEPEND) 47 | mode.add_argument('--asexplicit', dest='mode', 48 | action="store_const", 49 | const=pyalpm.PKG_REASON_EXPLICIT) 50 | parser.add_argument('pkgs', metavar='pkg', nargs='*', 51 | help="a dependency string, e.g. 'pacman>=3.4.0'") 52 | args = parser.parse_args(rawargs) 53 | handle = config.init_with_config_and_options(args) 54 | 55 | if args.verbose: 56 | print("database " + " ".join(rawargs), file=sys.stderr) 57 | 58 | commit(args.pkgs, args.mode) 59 | return 0 60 | 61 | if __name__ == "__main__": 62 | ret = main(sys.argv[1:]) 63 | sys.exit(ret) 64 | 65 | # vim: set ts=4 sw=4 tw=0 noet: 66 | -------------------------------------------------------------------------------- /test/test_handle.py: -------------------------------------------------------------------------------- 1 | from conftest import handle, PKG 2 | 3 | 4 | def test_cachedirs(handle): 5 | handle.add_cachedir('/tmp/') 6 | assert '/tmp/' in handle.cachedirs 7 | 8 | handle.remove_cachedir('/tmp/') 9 | assert '/tmp/' not in handle.cachedirs 10 | 11 | def test_set_cachedirs(handle): 12 | handle.cachedirs = ['/tmp/'] 13 | assert handle.cachedirs == ['/tmp/'] 14 | 15 | def test_ignoregrps(handle): 16 | handle.add_ignoregrp('base') 17 | assert 'base' in handle.ignoregrps 18 | 19 | handle.remove_ignoregrp('base') 20 | assert 'base' not in handle.ignoregrps 21 | 22 | def test_set_ignoregrps(handle): 23 | handle.ignoregrps = ['base'] 24 | assert handle.ignoregrps == ['base'] 25 | 26 | def test_ignorepkg(handle): 27 | handle.add_ignorepkg(PKG) 28 | assert PKG in handle.ignorepkgs 29 | 30 | handle.remove_ignorepkg(PKG) 31 | assert PKG not in handle.ignorepkgs 32 | 33 | def test_set_ignorepkgs(handle): 34 | handle.ignorepkgs = [PKG] 35 | assert handle.ignorepkgs == [PKG] 36 | 37 | def test_noextracts(handle): 38 | handle.add_noextract('index.php') 39 | assert 'index.php' in handle.noextracts 40 | 41 | handle.remove_noextract('index.php') 42 | assert 'index.php' not in handle.noextracts 43 | 44 | def test_set_noextracts(handle): 45 | handle.noextracts = ['index.php'] 46 | assert handle.noextracts == ['index.php'] 47 | 48 | def test_noupgrade(handle): 49 | handle.add_noupgrade('linux') 50 | assert 'linux' in handle.noupgrades 51 | 52 | handle.remove_noupgrade('linux') 53 | assert 'linux' not in handle.noupgrades 54 | 55 | def test_usesyslog(handle): 56 | handle.usesyslog = True 57 | assert handle.usesyslog 58 | 59 | def test_deltaratio(handle): 60 | assert handle.deltaratio == 0.0 61 | 62 | def test_checkspace(handle): 63 | assert not handle.checkspace 64 | handle.checkspace = True 65 | assert handle.checkspace 66 | 67 | def test_noupgrades(handle): 68 | assert not handle.noupgrades 69 | 70 | def test_set_noupgrades(handle): 71 | handle.noupgrades = [PKG] 72 | assert handle.noupgrades == [PKG] 73 | 74 | def test_root(handle): 75 | assert handle.root == '/' 76 | 77 | # vim: set ts=4 sw=4 et: 78 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from setuptools import setup, Extension 4 | 5 | 6 | os.putenv('LC_CTYPE', 'en_US.UTF-8') 7 | 8 | pyalpm_version = '0.8.5' 9 | 10 | PYCMAN_SCRIPTS = ['database', 'deptest', 'query', 'remove', 'sync', 'upgrade', 'version'] 11 | 12 | if __name__ == "__main__": 13 | 14 | cflags = ['-Wall', '-Wextra', '-Werror', 15 | '-Wno-unused-parameter', '-Wno-incompatible-pointer-types', 16 | '-Wno-cast-function-type', '-std=c99', '-D_FILE_OFFSET_BITS=64'] 17 | 18 | alpm = Extension('pyalpm', 19 | libraries=['alpm'], 20 | extra_compile_args=cflags + ['-DVERSION="%s"' % pyalpm_version], 21 | language='C', 22 | sources=['src/pyalpm.c', 23 | 'src/util.c', 24 | 'src/package.c', 25 | 'src/db.c', 26 | 'src/options.c', 27 | 'src/handle.c', 28 | 'src/transaction.c'], 29 | depends=['src/handle.h', 30 | 'src/db.h', 31 | 'src/options.h', 32 | 'src/package.h', 33 | 'src/pyalpm.h', 34 | 'src/util.h']) 35 | 36 | with open("README.md", "r") as fh: 37 | long_description = fh.read() 38 | 39 | setup(name='pyalpm', 40 | version=pyalpm_version, 41 | description='libalpm bindings for Python 3', 42 | long_description=long_description, 43 | long_description_content_type="text/markdown", 44 | author="Rémy Oudompheng", 45 | author_email="remy@archlinux.org", 46 | url="https://projects.archlinux.org/pyalpm.git", 47 | packages=["pycman"], 48 | scripts=["scripts/lsoptdepends"] + [f'scripts/pycman-{p}' for p in PYCMAN_SCRIPTS], 49 | ext_modules=[alpm], 50 | classifiers=[ 51 | 'Development Status :: 6 - Mature', 52 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 53 | 'Topic :: System :: Software Distribution ' 54 | 'Topic :: System :: Systems Administration' 55 | 'Programming Language :: C', 56 | ]) 57 | 58 | # vim: set ts=4 sw=4 et tw=0: 59 | -------------------------------------------------------------------------------- /doc/pyalpm/Handle.rst: -------------------------------------------------------------------------------- 1 | Handles 2 | ======= 3 | 4 | Handles are objects that provide access to pacman databases and transactions. 5 | 6 | .. py:class:: Handle(rootpath: string, dbpath: string) 7 | 8 | A handle object is initialized with a root path (i.e.., where do packages 9 | get installed) and a dbpath (i.e., where is the database information 10 | located). Generally, these parameters default to root path being '/' and a 11 | dbpath being '/var/lib/pacman'. 12 | 13 | 14 | .. py:method:: get_localdb() 15 | 16 | Return a reference to the local database object 17 | 18 | :returns: an alpm database object for the localdb 19 | 20 | 21 | .. py:method:: get_syncdbs() 22 | 23 | Return a list of references to the sync databases currently registered. 24 | 25 | :returns: an alpm database object for the localdb. 26 | 27 | 28 | .. py:method:: register_syncdb(name: string, flags: int) 29 | 30 | Registers the database with the given name. 31 | 32 | :param str name: The name of the database to register (e.g., 'core') 33 | :param int flags: an integer constant representing the type of access 34 | (i.e., an ALPM_SIG_* constant as exported in the parent module) 35 | :returns: an alpm database object for this syncdb 36 | 37 | .. py:method:: set_pkgreason(package: string, reason: pkgreason) 38 | 39 | Sets the reason for this package installation's (e.g., explicitly or as a 40 | dependency) 41 | 42 | :param str path: the path to the cachedir to add. 43 | :param str reason: the reason to add (e.g., asdep) 44 | :returns: Nothing 45 | 46 | .. py:method:: add_cachedir() 47 | 48 | Adds a cachedir. 49 | 50 | :param str path: the path to the cachedir to add. 51 | :returns: Nothing 52 | 53 | .. py:method:: add_ignoregrp() 54 | 55 | Add an ignoregrp. 56 | 57 | :param str groupname: the groupname to ignore 58 | :returns: nothing 59 | 60 | .. py:method:: add_ignorepkg() 61 | 62 | Add an ignorepkg. 63 | 64 | :param str pkgname: the package name to ignore 65 | :returns: nothing 66 | 67 | .. py:method:: add_noextract() 68 | 69 | Add a noextract package. 70 | 71 | :param str pkgname: the package name to noextract 72 | :returns: nothing 73 | 74 | .. py:method:: add_noupgrade() 75 | 76 | Add a noupgrade package 77 | 78 | :param str pkgname: the package name to noextract 79 | :returns: nothing 80 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | pyalpm 8.8.5 : 2 | * resolve memory leaks caused by not releasing the alpm_handle 3 | 4 | pyalpm 0.8 : 5 | * compatible with pacman 5 6 | 7 | pyalpm 0.7 : 8 | * compatible with pacman 4.2 9 | * pycman: recognize Usage option of repositories 10 | 11 | pyalpm 0.6.2 : 12 | * pycman: recognize RemoteFileSigLevel and UseDelta=x options (FS#34791) 13 | * init_with_config now handles IgnorePkg and similar options (FS#29688) 14 | 15 | pyalpm 0.6.1 : 16 | * fix: incorrect warnings from new pacman.conf options (FS#34591). 17 | 18 | pyalpm 0.6 : 19 | * compatible with pacman 4.1 20 | 21 | pyalpm 0.5.3 : 22 | * fix: incorrect integer types in file lists 23 | 24 | pyalpm 0.5.2 : 25 | * fix: optimistic cast on 64-bit platforms 26 | * fix: UnicodeDecodeError on some packages with non-UTF8 27 | filenames (FS#26412) 28 | * remove alpm_list_get_data() usage for pacman 4.1 29 | 30 | pyalpm 0.5.1 : 31 | * pycman: parse ILoveCandy and SigLevel in pacman.conf 32 | 33 | pyalpm 0.5 : 34 | * now requires pacman 4.0 35 | * API: initialize() replaced by Handle() constructor 36 | * API: DB.url property replaced by DB.servers 37 | * API: Package.files now returns a list of (name, size, mode) 38 | * API: Handle has a new attribute gpgdir 39 | * pycman: support --gpgdir, --cachedir, --debug options 40 | * fix: repository order suhffled in pacman.conf parsing 41 | * fix: incorrect default values for options 42 | 43 | pyalpm 0.4.2 : 44 | * fix a typo in init_with_config() 45 | * add an example script to list optdepends (cf pacman FS#16108) 46 | 47 | pyalpm 0.4.1 : 48 | * better parser for pacman.conf files 49 | * interface tweaks for pycman 50 | 51 | pyalpm 0.4 : 52 | * wraps alpm_db_update(), alpm_db_search() 53 | * fix: extra free() after calling transaction.add_pkg() 54 | * pycman: correctly read configuration 55 | * pycman: nearly complete support for remove, sync, upgrade actions 56 | 57 | pyalpm 0.3 : 58 | * wraps alpm_db_set_pkgreason() 59 | * wraps alpm_sync_newversion() 60 | * wraps transaction control functions 61 | * initial support for getting/setting Python functions as libalpm callbacks 62 | * pycman: support -D, -Q[demotu] 63 | 64 | pyalpm 0.2 : 65 | * handles alpm error strings 66 | * wraps alpm_db_read_grp() 67 | * wraps alpm_pkg_get_reason() 68 | * wraps alpm_find_grp_pkgs() 69 | * wraps alpm_find_satisfier() 70 | * add pycman (Python clone of pacman), with read-only actions 71 | 72 | pyalpm 0.1 : 73 | * databases and packages are wrapped by Python classes 74 | * support for registering databases 75 | * support for getting/setting libalpm options (except callbacks) 76 | -------------------------------------------------------------------------------- /doc/pyalpm/pyalpm.rst: -------------------------------------------------------------------------------- 1 | Pyalpm 2 | ======= 3 | 4 | Pyalpm is a python module that provides native bindings libalpm to interact 5 | with Arch Linux package databases. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: Contents: 10 | 11 | Handle 12 | Database 13 | Package 14 | 15 | 16 | 17 | Basic Usage 18 | ----------- 19 | 20 | The easiest way to get access to a pacman database is by means of the handle 21 | object. To obtain a handle object, you need to initialize it with a rootdir 22 | parameter (i.e., the --root path parameter in pacman) and a database location 23 | (i.e., a --dbpath parameter): 24 | 25 | .. code-block:: python 26 | 27 | from pyalpm import Handle 28 | handle = Handle(".", "/var/lib/pacman") 29 | localdb = handle.get_localdb() 30 | 31 | 32 | The above code sample will provide you with a localdb object, that you can use 33 | to query, for example, package information: 34 | 35 | .. code-block:: python 36 | 37 | coreutils = localdb.get_pkg("coreutils") 38 | 39 | The call to get_pkg will provide you with the package passed as the name (in 40 | this case, "coreutils"), you can query information about the package using this 41 | reference. 42 | 43 | .. code-block:: python 44 | 45 | print(coreutils.packager) 46 | # 'John Doe ' 47 | 48 | print(coreutils.licenses) 49 | # ['GPL3'] 50 | 51 | print(coreutils.version) 52 | # '8.30-1' 53 | 54 | You can see what other properties belong to the package object by looking at 55 | the Package page. 56 | 57 | A Handle object can also provide a sync database to you: 58 | 59 | .. code-block:: python 60 | 61 | import pyalpm 62 | from pyalpm import Handle 63 | handle = Handle('.', '/var/lib/pacman/') 64 | core = handle.register_syncdb("core", pyalpm.SIG_DATABASE) 65 | 66 | The parameters required in this case are a database name ("core") and a flag to 67 | indicate the type of PGP level of verification that should be done on the 68 | database. You can read more about the second flag in the SIG section of this 69 | manual. 70 | 71 | A syncdb is similar to a localdb (they are, in fact, both DB objects), but some 72 | minor differences such as the installed size being 0 (as the packages are not 73 | installed yet) and the download size of packages not being necessarilly 0 (as 74 | the packages may have not been downloaded yet). 75 | 76 | You can use the syncdb to, for example, search for packages: 77 | 78 | .. code-block:: python 79 | 80 | core.search("linux.*") 81 | # a bunch of packages with linux in their name 82 | 83 | linux = core.get_pkg("linux") 84 | print(linux.download_size) 85 | # around 70 megabytes 86 | -------------------------------------------------------------------------------- /doc/pyalpm/Package.rst: -------------------------------------------------------------------------------- 1 | Packages 2 | ======== 3 | 4 | .. py:class:: Package 5 | 6 | .. py:attribute:: builddate (Long long) 7 | 8 | The date on which this package was built 9 | 10 | .. py:attribute:: installdate (Long Long) 11 | 12 | The date in which this package was installed (only in localdb) 13 | 14 | .. py:attribute:: size (Long Long) 15 | 16 | A list of references to the packages in this database 17 | 18 | .. py:attribute:: isize (Long Long) 19 | 20 | The installed size 21 | 22 | .. py:attribute:: files (list) 23 | 24 | A list of files in this package 25 | 26 | .. py:attribute:: db (Database) 27 | 28 | A reference to the database this package belongs to 29 | 30 | .. py:attribute:: has_scriptlet (boolean) 31 | 32 | Whether this package has a scriptlet 33 | 34 | .. py:attribute:: licenses (list) 35 | 36 | A list of licenses for this package. 37 | 38 | .. py:attribute:: deltas (list) 39 | 40 | A list of deltas for this package 41 | 42 | .. py:attribute:: desc (string) 43 | 44 | Package description. 45 | 46 | .. py:attribute:: optdepends (list) 47 | 48 | A list of the optional dependencies for this package 49 | 50 | .. py:attribute:: replaces (list) 51 | 52 | A list of packages this package replaces 53 | 54 | .. py:attribute:: provides (list) 55 | 56 | A list of strings of what this package provides 57 | 58 | .. py:attribute:: conflicts (list) 59 | 60 | A list of packages this package conflicts with 61 | 62 | .. py:attribute:: backup (list) 63 | 64 | A list of backup tuples (filename, md5sum) 65 | 66 | .. py:attribute:: groups (list) 67 | 68 | The groups this package belongs to 69 | 70 | .. py:attribute:: arch (string) 71 | 72 | The CPU architecture for this package 73 | 74 | .. py:attribute:: packager (string) 75 | 76 | The packager for this package 77 | 78 | .. py:attribute:: md5sum (string) 79 | 80 | The package md5sum as hexadecimal digits 81 | 82 | .. py:attribute:: sha256sum (string) 83 | 84 | The package sha256sum as hexadecimal digits 85 | 86 | .. py:attribute:: base64_sig (string) 87 | 88 | The package signature encoded as base64 89 | 90 | .. py:attribute:: filename (string) 91 | 92 | The package filename 93 | 94 | .. py:attribute:: url (string) 95 | 96 | The package URL 97 | 98 | .. py:method:: compute_requiredby() 99 | 100 | Computes a list of the packages required by this package 101 | 102 | :returns list[Packages]: the packages required by this package 103 | -------------------------------------------------------------------------------- /pycman/action_upgrade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-upgrade - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -U 23 | 24 | This script installs packages from tarballs. Various options control 25 | the effect of the transaction. 26 | """ 27 | 28 | import sys 29 | import pyalpm 30 | from pycman import config 31 | from pycman import transaction 32 | 33 | handle = None 34 | 35 | def upgrade(pkgs, options): 36 | # prepare target list 37 | db = handle.get_localdb() 38 | targets = [] 39 | for name in pkgs: 40 | pkg = pyalpm.load_pkg(name) 41 | targets.append(pkg) 42 | 43 | t = transaction.init_from_options(handle, options) 44 | 45 | for pkg in targets: 46 | t.add_pkg(pkg) 47 | 48 | ok = transaction.finalize(t) 49 | return (0 if ok else 1) 50 | 51 | def main(rawargs): 52 | global handle 53 | parser = config.make_parser() 54 | group = parser.add_argument_group("upgrade options") 55 | group.add_argument('-d', '--nodeps', 56 | action='store_true', default=False, 57 | help='skip dependency checks') 58 | group.add_argument('-f', '--force', 59 | action='store_true', default=False, 60 | help='force install, overwrite conflicting files') 61 | group.add_argument('-k', '--dbonly', 62 | action='store_true', default=False, 63 | help='only modify database entries, not package files') 64 | group.add_argument('--asdeps', dest='mode', 65 | action="store_const", 66 | const=pyalpm.PKG_REASON_DEPEND) 67 | group.add_argument('--asexplicit', dest='mode', 68 | action="store_const", 69 | const=pyalpm.PKG_REASON_EXPLICIT) 70 | group.add_argument('pkgs', metavar='pkg', nargs='*', 71 | help="a list of package URLs, e.g. package-1.0-1-x86_64.tar.xz") 72 | 73 | args = parser.parse_args(rawargs) 74 | handle = config.init_with_config_and_options(args) 75 | 76 | if args.verbose: 77 | print("upgrade " + " ".join(rawargs), file=sys.stderr) 78 | 79 | return upgrade(args.pkgs, args) 80 | 81 | if __name__ == "__main__": 82 | ret = main(sys.argv[1:]) 83 | sys.exit(ret) 84 | 85 | # vim: set ts=4 sw=4 tw=0 noet: 86 | -------------------------------------------------------------------------------- /scripts/lsoptdepends: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pyalpm - Python 3 Bindings for libalpm 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | Lists optional dependencies of installed packages. 23 | 24 | Options: 25 | -u : only list uninstalled optdepends 26 | -q : only print package names 27 | """ 28 | 29 | import re 30 | import argparse 31 | 32 | from pycman import config 33 | import pyalpm 34 | 35 | re_depstring = re.compile("([^<=>]*)([<>]?=.*)?") 36 | 37 | def enum_optdepends(h): 38 | for p in h.get_localdb().pkgcache: 39 | for o in p.optdepends: 40 | oname, colon, odesc = o.partition(':') 41 | yield (p.name, oname.strip(), odesc.strip()) 42 | 43 | def enum_depends(h): 44 | for p in h.get_localdb().pkgcache: 45 | for d in p.depends: 46 | m = re_depstring.match(d) 47 | yield (p.name, m.group(1)) 48 | 49 | def main(opts): 50 | h = config.init_with_config("/etc/pacman.conf") 51 | optdeps = {} 52 | installed = set(p.name for p in h.get_localdb().pkgcache) 53 | needed = set(q for (p, q) in enum_depends(h)) 54 | 55 | for suggester, optdep, reason in enum_optdepends(h): 56 | optdeps.setdefault(optdep, {})[suggester] = reason 57 | 58 | for optdep in sorted(optdeps): 59 | if optdep in installed and opts.u: 60 | continue 61 | if opts.orphan and not (optdep in installed and optdep not in needed): 62 | continue 63 | suggesters = optdeps[optdep] 64 | if opts.q: 65 | print(optdep) 66 | else: 67 | print(optdep, 'is suggested by') 68 | for name, reason in sorted(suggesters.items()): 69 | print(' ', name, ':', reason) 70 | 71 | def parse_options(): 72 | p = argparse.ArgumentParser() 73 | p.add_argument('-u', action = 'store_true', default = False, 74 | help='only list uninstalled optdepends') 75 | p.add_argument('--orphan', action = 'store_true', default = False, 76 | help='list installed optdepends that are not hard deps of any package') 77 | p.add_argument('-q', action = 'store_true', default = False, 78 | help='only print package names') 79 | return p.parse_args() 80 | 81 | if __name__ == "__main__": 82 | main(parse_options()) 83 | 84 | # vim: set ts=4 sw=4 tw=0 noet: 85 | -------------------------------------------------------------------------------- /pycman/action_remove.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-remove - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -R 23 | 24 | This script uninstalls packages. Various options control 25 | the effect on dependencies of/on given targets. 26 | """ 27 | 28 | import sys 29 | from pycman import config 30 | from pycman import transaction 31 | 32 | handle = None 33 | 34 | def remove(pkgs, options): 35 | # prepare target list 36 | db = handle.get_localdb() 37 | targets = [] 38 | for name in pkgs: 39 | pkg = db.get_pkg(name) 40 | if pkg is None: 41 | print("error: '%s': target not found" % name) 42 | return 1 43 | targets.append(pkg) 44 | 45 | t = transaction.init_from_options(handle, options) 46 | 47 | for pkg in targets: 48 | t.remove_pkg(pkg) 49 | 50 | ok = transaction.finalize(t) 51 | return (0 if ok else 1) 52 | 53 | def main(rawargs): 54 | global handle 55 | parser = config.make_parser() 56 | group = parser.add_argument_group("Remove options") 57 | group.add_argument('-c', '--cascade', 58 | action='store_true', default=False, 59 | help='remove packages and all packages that depend on them') 60 | group.add_argument('-d', '--nodeps', 61 | action='store_true', default=False, 62 | help='skip dependency checks') 63 | group.add_argument('-k', '--dbonly', 64 | action='store_true', default=False, 65 | help='only modify database entries, not package files') 66 | group.add_argument('-n', '--nosave', 67 | action='store_true', default=False, 68 | help='remove configuration files as well') 69 | group.add_argument('-s', '--recursive', 70 | action='store_true', default=False, 71 | help="remove dependencies also (that won't break packages)") 72 | group.add_argument('-u', '--unneeded', 73 | action='store_true', default=False, 74 | help="remove unneeded packages (that won't break packages)") 75 | group.add_argument('pkgs', metavar='pkg', nargs='*', 76 | help="a list of packages, e.g. libreoffice, openjdk6") 77 | 78 | args = parser.parse_args(rawargs) 79 | handle = config.init_with_config_and_options(args) 80 | 81 | if args.verbose: 82 | print("remove " + " ".join(rawargs), file=sys.stderr) 83 | 84 | if len(args.pkgs) == 0: 85 | print('error: no targets specified') 86 | return 1 87 | 88 | return remove(args.pkgs, args) 89 | 90 | if __name__ == "__main__": 91 | ret = main(sys.argv[1:]) 92 | sys.exit(ret) 93 | 94 | # vim: set ts=4 sw=4 tw=0 noet: 95 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | /** 2 | * options.h 3 | * 4 | * Copyright 2008 Imanol Celaya 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #ifndef _PY_ALPM_OPTIONS_METHODS_H 24 | #define _PY_ALPM_OPTIONS_METHODS_H 25 | 26 | #include 27 | 28 | /** boolean options */ 29 | 30 | PyObject* option_get_usesyslog_alpm(PyObject *self, void* closure); 31 | int option_set_usesyslog_alpm(PyObject *self, PyObject *value, void* closure); 32 | 33 | PyObject* option_get_deltaratio_alpm(PyObject *self, void* closure); 34 | int option_set_deltaratio_alpm(PyObject *self, PyObject *value, void* closure); 35 | 36 | PyObject* option_get_checkspace_alpm(PyObject *self, void* closure); 37 | int option_set_checkspace_alpm(PyObject *self, PyObject *value, void* closure); 38 | 39 | /** list options */ 40 | PyObject* option_get_cachedirs_alpm(PyObject *self, void* closure); 41 | PyObject* option_get_noupgrades_alpm(PyObject *self, void* closure); 42 | PyObject* option_get_noextracts_alpm(PyObject *self, void* closure); 43 | PyObject* option_get_ignorepkgs_alpm(PyObject *self, void* closure); 44 | PyObject* option_get_ignoregrps_alpm(PyObject *self, void* closure); 45 | 46 | int option_set_cachedirs_alpm(PyObject *self, PyObject *value, void *closure); 47 | int option_set_noupgrades_alpm(PyObject *self, PyObject *value, void *closure); 48 | int option_set_noextracts_alpm(PyObject *self, PyObject *value, void *closure); 49 | int option_set_ignorepkgs_alpm(PyObject *self, PyObject *value, void *closure); 50 | int option_set_ignoregrps_alpm(PyObject *self, PyObject *value, void *closure); 51 | 52 | PyObject * option_add_noupgrade_alpm(PyObject *self, PyObject *args); 53 | PyObject * option_remove_noupgrade_alpm(PyObject *self, PyObject *args); 54 | 55 | PyObject * option_add_cachedir_alpm(PyObject *self, PyObject *args); 56 | PyObject * option_remove_cachedir_alpm(PyObject *self, PyObject *args); 57 | 58 | PyObject * option_add_noextract_alpm(PyObject *self, PyObject *args); 59 | PyObject * option_remove_noextract_alpm(PyObject *self, PyObject *args); 60 | 61 | PyObject * option_add_ignorepkg_alpm(PyObject *self, PyObject *args); 62 | PyObject * option_remove_ignorepkg_alpm(PyObject *self, PyObject *args); 63 | 64 | PyObject * option_add_ignoregrp_alpm(PyObject *self, PyObject *args); 65 | PyObject * option_remove_ignoregrp_alpm(PyObject *self, PyObject *args); 66 | 67 | /** Callback options */ 68 | void pyalpm_logcb(alpm_loglevel_t level, const char *fmt, va_list va_args); 69 | void pyalpm_dlcb(const char *filename, off_t xfered, off_t total); 70 | void pyalpm_totaldlcb(off_t total); 71 | int pyalpm_fetchcb(const char *url, const char *localpath, int force); 72 | 73 | #endif 74 | 75 | /* vim: set ts=2 sw=2 et: */ 76 | -------------------------------------------------------------------------------- /scripts/pycman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman 23 | """ 24 | 25 | import sys 26 | import pycman 27 | 28 | SHORT_ACTIONS = { 29 | 'D': 'db', 30 | 'Q': 'query', 31 | 'R': 'remove', 32 | 'S': 'sync', 33 | 'T': 'deptest', 34 | 'U': 'upgrade', 35 | 'V': 'version' 36 | } 37 | 38 | HELP = """ 39 | Available actions: 40 | -D, db : modify local database 41 | -Q, query : list installed packages 42 | -R, remove : remove intalled packages 43 | -S, sync : list/install/update remote packages 44 | -T, deptest : test dependencies 45 | -U, upgrade : install packages from local files 46 | -V, version : print version information 47 | 48 | Use the '-h' or '--help' for more information. 49 | """ 50 | 51 | def usage(): 52 | print("Usage:", sys.argv[0], "action", "[options]", "[arguments]") 53 | print(HELP) 54 | 55 | def parse_action(args): 56 | # cases where to print usage 57 | if len(args) == 0: 58 | usage() 59 | return None, 2 60 | if args[0].startswith("-h"): 61 | usage() 62 | return None, 2 63 | elif args[0] == "--help": 64 | usage() 65 | return None, 2 66 | 67 | if not args[0].startswith("-"): 68 | # action name style (sync, query...) 69 | action = args[0] 70 | newargs = args[1:] 71 | return action, newargs 72 | else: 73 | # pacman style for action (-S, -Q...) 74 | actions = [char for char in args[0] if char.isupper()] 75 | if len(actions) == 0: 76 | print("No operation specified.") 77 | usage() 78 | return None, 2 79 | elif len(actions) == 1: 80 | if actions[0] not in SHORT_ACTIONS: 81 | print("Invalid action specified (%s are supported)" % ', '.join(SHORT_ACTIONS.keys())) 82 | return None, 1 83 | else: 84 | options = [char for char in args[0][1:] if not char.isupper()] 85 | if len(options) > 0: 86 | optarg = '-' + ''.join(options) 87 | newargs = [optarg] + args[1:] 88 | else: 89 | newargs = args[1:] 90 | return SHORT_ACTIONS[actions[0]], newargs 91 | elif len(actions) > 1: 92 | print("Only one operation may be used at a time (received %s)." 93 | % ", ".join(actions)) 94 | return None, 1 95 | 96 | def main(args): 97 | action, data = parse_action(args) 98 | if action is None: 99 | return data 100 | 101 | ret = pycman.run_action_with_args(action, data) 102 | return ret 103 | 104 | if __name__ == "__main__": 105 | ret = main(sys.argv[1:]) 106 | sys.exit(ret) 107 | 108 | # vim: set ts=4 sw=4 tw=0 noet: 109 | -------------------------------------------------------------------------------- /src/pyalpm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * pyalpm - a Python C module wrapping libalpm 3 | * 4 | * Copyright 2008 Imanol Celaya 5 | * Copyright (c) 2011 Rémy Oudompheng 6 | * 7 | * pyalpm is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * pyalpm is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with pyalpm. If not, see . 19 | */ 20 | 21 | #include "pyalpm.h" 22 | #include "util.h" 23 | #include "package.h" 24 | #include "db.h" 25 | 26 | static PyObject * alpmversion_alpm(PyObject *self, PyObject *dummy) 27 | { 28 | const char *str; 29 | str = alpm_version(); 30 | 31 | return Py_BuildValue("s", str); 32 | } 33 | 34 | static PyObject * version_alpm(PyObject *self, PyObject *dummy) 35 | { 36 | return Py_BuildValue("s", VERSION); 37 | } 38 | 39 | /** Finds a package satisfying a dependency constraint in a package list */ 40 | static PyObject* pyalpm_find_satisfier(PyObject *self, PyObject* args) { 41 | PyObject *pkglist; 42 | char *depspec; 43 | alpm_list_t *alpm_pkglist; 44 | alpm_pkg_t *p; 45 | 46 | if(!PyArg_ParseTuple(args, "Os", &pkglist, &depspec)) 47 | { 48 | PyErr_SetString(PyExc_TypeError, "find_satisfier() takes a Package list and a string"); 49 | return NULL; 50 | } 51 | 52 | if(pylist_pkg_to_alpmlist(pkglist, &alpm_pkglist) == -1) 53 | return NULL; 54 | 55 | p = alpm_find_satisfier(alpm_pkglist, depspec); 56 | alpm_list_free(alpm_pkglist); 57 | 58 | if (p == NULL) { 59 | Py_RETURN_NONE; 60 | } else { 61 | PyObject *result; 62 | result = pyalpm_package_from_pmpkg(p); 63 | if (result == NULL) { 64 | return NULL; 65 | } else { 66 | return result; 67 | } 68 | } 69 | } 70 | 71 | static PyObject *pyalpm_vercmp(PyObject *self, PyObject *args) { 72 | const char *x, *y; 73 | int result; 74 | if (!PyArg_ParseTuple(args, "ss", &x, &y)) 75 | return NULL; 76 | result = alpm_pkg_vercmp(x, y); 77 | return PyLong_FromLong(result); 78 | } 79 | 80 | static PyMethodDef methods[] = { 81 | {"version", version_alpm, METH_NOARGS, "returns pyalpm version."}, 82 | {"alpmversion", alpmversion_alpm, METH_NOARGS, "returns alpm version."}, 83 | {"vercmp", pyalpm_vercmp, METH_VARARGS, "compares version strings"}, 84 | 85 | { "find_satisfier", pyalpm_find_satisfier, METH_VARARGS, 86 | "finds a package satisfying the given dependency among a list\n" 87 | "args: a list of packages, a dependency string\n" 88 | "returns: a Package object or None" }, 89 | 90 | {"sync_newversion", pyalpm_sync_newversion, METH_VARARGS, 91 | "finds an available upgrade for a package in a list of databases\n" 92 | "args: a package, a list of databases\n" 93 | "returns: an upgrade candidate or None" }, 94 | 95 | /* from db.c */ 96 | {"find_grp_pkgs", pyalpm_find_grp_pkgs, METH_VARARGS, 97 | "find packages from a given group across databases\n" 98 | "args: a list of databases, a group name"}, 99 | 100 | {NULL, NULL, 0, NULL} 101 | }; 102 | 103 | static int pyalpm_clear(PyObject *m) 104 | { 105 | PyObject *dict = PyModule_GetDict(m); 106 | if (dict == NULL) { 107 | return 0; 108 | } 109 | 110 | PyObject *error = PyDict_GetItemString(dict, "error"); 111 | if (error == NULL) { 112 | return 0; 113 | } 114 | 115 | Py_DECREF(error); 116 | 117 | return 0; 118 | } 119 | 120 | static struct PyModuleDef pyalpm_def = { 121 | PyModuleDef_HEAD_INIT, 122 | "alpm", 123 | "This module wraps the libalpm library", 124 | -1, 125 | methods, 126 | NULL, NULL, 127 | pyalpm_clear, 128 | NULL, 129 | }; 130 | 131 | PyMODINIT_FUNC PyInit_pyalpm(void) 132 | { 133 | PyObject* m = PyModule_Create(&pyalpm_def); 134 | 135 | init_pyalpm_error(m); 136 | init_pyalpm_handle(m); 137 | init_pyalpm_package(m); 138 | init_pyalpm_db(m); 139 | init_pyalpm_transaction(m); 140 | 141 | return m; 142 | } 143 | 144 | /* vim: set ts=2 sw=2 et: */ 145 | 146 | -------------------------------------------------------------------------------- /pycman/transaction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | pycman transaction management 23 | 24 | This module defines convenient wrappers around pyalpm functions 25 | to initialize transactions according to options 26 | """ 27 | 28 | import math 29 | import sys 30 | import traceback 31 | import pyalpm 32 | 33 | # Callbacks 34 | def cb_event(*args): 35 | print("event", args) 36 | 37 | def cb_conv(*args): 38 | print("conversation", args) 39 | 40 | last_target = None 41 | last_percent = 100 42 | last_i = -1 43 | def cb_progress(target, percent, n, i): 44 | "Display progress percentage for target i/n" 45 | global last_target, last_percent, last_i 46 | if len(target) == 0: 47 | # abstract progress 48 | if percent < last_percent or i < last_i: 49 | sys.stdout.write("progress (%d targets)" % n) 50 | last_i = 0 51 | sys.stdout.write((i - last_i) * '.') 52 | sys.stdout.flush() 53 | last_i = i 54 | else: 55 | # progress for some target (write 25 dots for 100%) 56 | if target != last_target or percent < last_percent: 57 | last_target = target 58 | last_percent = 0 59 | sys.stdout.write("progress for %s (%d/%d)" % (target, i, n)) 60 | old_dots = last_percent // 4 61 | new_dots = percent // 4 62 | sys.stdout.write((new_dots - old_dots) * '.') 63 | sys.stdout.flush() 64 | 65 | # final newline 66 | if percent == 100 and last_percent < 100: 67 | sys.stdout.write('\n') 68 | sys.stdout.flush() 69 | last_percent = percent 70 | 71 | _last_dl_filename = None 72 | _last_dl_progress = None 73 | _last_dl_total = None 74 | def cb_dl(filename, tx, total): 75 | global _last_dl_filename, _last_dl_progress, _last_dl_total 76 | # check if a new file is coming 77 | if filename != _last_dl_filename or _last_dl_total != total: 78 | _last_dl_filename = filename 79 | _last_dl_total = total 80 | _last_dl_progress = 0 81 | sys.stdout.write("\ndownload %s: %d/%d" % (filename, tx, total)) 82 | sys.stdout.flush() 83 | # compute a progress indicator 84 | if _last_dl_total > 0: 85 | progress = (tx * 25) // _last_dl_total 86 | else: 87 | # if total is unknown, use log(kBytes)²/2 88 | progress = int(math.log(1 + tx / 1024) ** 2 / 2) 89 | if progress > _last_dl_progress: 90 | _last_dl_progress = progress 91 | sys.stdout.write("\rdownload %s: %s %d/%d" % (filename, '.' * progress, tx, total)) 92 | sys.stdout.flush() 93 | 94 | def init_from_options(handle, options): 95 | "Transaction initialization" 96 | handle.dlcb = cb_dl 97 | handle.eventcb = cb_event 98 | handle.questioncb = cb_conv 99 | handle.progresscb = cb_progress 100 | t = handle.init_transaction( 101 | cascade=getattr(options, "cascade", False), 102 | nodeps=getattr(options, "nodeps", False), 103 | force=getattr(options, 'force', False), 104 | dbonly=getattr(options, 'dbonly', False), 105 | downloadonly=getattr(options, 'downloadonly', False), 106 | nosave=getattr(options, 'nosave', False), 107 | recurse=(getattr(options, 'recursive', 0) > 0), 108 | recurseall=(getattr(options, 'recursive', 0) > 1), 109 | unneeded=getattr(options, 'unneeded', False), 110 | alldeps=(getattr(options, 'mode', None) == pyalpm.PKG_REASON_DEPEND), 111 | allexplicit=(getattr(options, 'mode', None) == pyalpm.PKG_REASON_EXPLICIT)) 112 | return t 113 | 114 | def finalize(t): 115 | "Commit a transaction" 116 | try: 117 | t.prepare() 118 | t.commit() 119 | except pyalpm.error: 120 | traceback.print_exc() 121 | t.release() 122 | return False 123 | t.release() 124 | return True 125 | 126 | # vim: set ts=4 sw=4 tw=0 noet: 127 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /** 2 | * util.c : utility functions for pyalpm 3 | * 4 | * Copyright 2008 Imanol Celaya 5 | * Copyright 2011 Rémy Oudompheng 6 | * 7 | * This file is part of pyalpm. 8 | * 9 | * pyalpm is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * pyalpm is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with pyalpm. If not, see . 21 | * 22 | */ 23 | 24 | /* Include pyconfig for feature test macros. */ 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | /** Errors */ 33 | 34 | PyObject* alpm_error = NULL; 35 | 36 | /** Formats an alpm.error exception using errno from libalpm. */ 37 | static PyObject* pyalpm_error_str(PyObject* exception) { 38 | PyObject* args = PyObject_GetAttrString(exception, "args"); 39 | PyObject* result; 40 | const char* errstring; 41 | enum _alpm_errno_t errcode; 42 | PyObject *data; 43 | 44 | int handle_format; 45 | { 46 | /* check whether alpm.error was set with standard args */ 47 | PyObject *exctype, *excvalue, *exctraceback; 48 | PyErr_Fetch(&exctype, &excvalue, &exctraceback); 49 | handle_format = PyArg_ParseTuple(args, "siO", 50 | &errstring, &errcode, &data); 51 | if (!handle_format) { 52 | PyErr_Clear(); 53 | result = PyObject_Str(args); 54 | } 55 | PyErr_Restore(exctype, excvalue, exctraceback); 56 | } 57 | 58 | if(handle_format) { 59 | if (data == Py_None) { 60 | result = PyUnicode_FromFormat("%s, pm_errno %d (%s)", errstring, errcode, alpm_strerror(errcode)); 61 | } else { 62 | result = PyUnicode_FromFormat("%s, pm_errno %d (%s), %S", errstring, errcode, alpm_strerror(errcode), data); 63 | } 64 | } 65 | 66 | Py_DECREF(args); 67 | return result; 68 | } 69 | 70 | void init_pyalpm_error(PyObject* module) { 71 | alpm_error = PyErr_NewExceptionWithDoc("alpm.error", 72 | "Exception raised when an error arises from libalpm\n" 73 | "The args attribute will usually contain a tuple " 74 | "(error message, errno from libalpm, extra data)\n", 75 | NULL, NULL); 76 | ((PyTypeObject*)alpm_error)->tp_str = pyalpm_error_str; 77 | PyModule_AddObject(module, "error", alpm_error); 78 | Py_INCREF(alpm_error); 79 | } 80 | 81 | /** Python lists and libalpm lists */ 82 | 83 | /** Converts a Python list of strings to an alpm_list_t linked list. 84 | * return 0 on success, -1 on failure 85 | */ 86 | int pylist_string_to_alpmlist(PyObject *list, alpm_list_t* *result) 87 | { 88 | alpm_list_t *ret = NULL; 89 | PyObject *iterator = PyObject_GetIter(list); 90 | PyObject *item; 91 | 92 | if(iterator == NULL) { 93 | PyErr_SetString(PyExc_TypeError, "object is not iterable"); 94 | return -1; 95 | } 96 | 97 | while((item = PyIter_Next(iterator))) 98 | { 99 | if (PyBytes_Check(item)) { 100 | ret = alpm_list_add(ret, strdup(PyBytes_AS_STRING(item))); 101 | } else if (PyUnicode_Check(item)) { 102 | PyObject* utf8 = PyUnicode_AsUTF8String(item); 103 | ret = alpm_list_add(ret, strdup(PyBytes_AS_STRING(utf8))); 104 | Py_DECREF(utf8); 105 | } else { 106 | PyErr_SetString(PyExc_TypeError, "list must contain only strings"); 107 | FREELIST(ret); 108 | Py_DECREF(item); 109 | return -1; 110 | } 111 | Py_DECREF(item); 112 | } 113 | Py_DECREF(iterator); 114 | 115 | *result = ret; 116 | return 0; 117 | } 118 | 119 | PyObject* pyobject_from_string(void *s) { 120 | return Py_BuildValue("s", (char*)s); 121 | } 122 | 123 | PyObject* alpmlist_to_pylist(alpm_list_t *prt, PyObject* pybuilder(void*)) 124 | { 125 | PyObject *output, *stritem; 126 | alpm_list_t *tmp; 127 | 128 | output = PyList_New(0); 129 | if(output == NULL) { 130 | PyErr_SetString(PyExc_RuntimeError, "unable to create list object"); 131 | return NULL; 132 | } 133 | 134 | for(tmp = prt; tmp; tmp = alpm_list_next(tmp)) { 135 | stritem = pybuilder(tmp->data); 136 | if (!stritem) { 137 | Py_CLEAR(stritem); 138 | return NULL; 139 | } 140 | PyList_Append(output, stritem); 141 | Py_CLEAR(stritem); 142 | } 143 | 144 | return output; 145 | } 146 | 147 | /* vim: set ts=2 sw=2 et: */ 148 | -------------------------------------------------------------------------------- /pycman/pkginfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman.pkginfo - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | Package information formatting 23 | 24 | This module defines utility function to format package information 25 | for terminal output. 26 | """ 27 | 28 | import sys 29 | import time 30 | import textwrap 31 | 32 | import struct 33 | import fcntl 34 | import termios 35 | 36 | import pyalpm 37 | 38 | ATTRNAME_FORMAT = '%-14s : ' 39 | ATTR_INDENT = 17 * ' ' 40 | 41 | def get_term_size(): 42 | if sys.stdout.isatty(): 43 | height, width = struct.unpack("HH", fcntl.ioctl(1, termios.TIOCGWINSZ, 4 * b"\x00")) 44 | return width 45 | else: 46 | return 80 47 | 48 | def format_attr(attrname, value, format=None): 49 | if isinstance(value, list): 50 | if len(value) == 0: 51 | valuestring = 'None' 52 | else: 53 | valuestring = ' '.join(str(v) for v in value) 54 | else: 55 | if format == "time": 56 | valuestring = time.strftime("%a %d %b %Y %X %Z", time.localtime(value)) 57 | else: 58 | valuestring = str(value) 59 | return textwrap.fill(valuestring, width=get_term_size(), 60 | initial_indent=ATTRNAME_FORMAT % attrname, 61 | subsequent_indent=ATTR_INDENT, 62 | break_on_hyphens=False, 63 | break_long_words=False) 64 | 65 | def format_attr_oneperline(attrname, value): 66 | if len(value) == 0: 67 | value = ['None'] 68 | s = ATTRNAME_FORMAT % attrname 69 | s += ('\n' + ATTR_INDENT).join(value) 70 | return s 71 | 72 | def display_pkginfo(pkg, level=1, style='local'): 73 | """ 74 | Displays pretty-printed package information. 75 | 76 | Args : 77 | pkg -- the package to display 78 | level -- the level of detail (1 or 2) 79 | style -- 'local' or 'sync' 80 | """ 81 | 82 | if style not in ['local', 'sync', 'file']: 83 | raise ValueError('Invalid style for package info formatting') 84 | 85 | if style == 'sync': 86 | print(format_attr('Repository', pkg.db.name)) 87 | print(format_attr('Name', pkg.name)) 88 | print(format_attr('Base', pkg.base)) 89 | print(format_attr('Version', pkg.version)) 90 | print(format_attr('URL', pkg.url)) 91 | print(format_attr('Licenses', pkg.licenses)) 92 | print(format_attr('Groups', pkg.groups)) 93 | print(format_attr('Provides', pkg.provides)) 94 | print(format_attr('Depends On', pkg.depends)) 95 | print(format_attr_oneperline('Optional Deps', pkg.optdepends)) 96 | if style == 'local' or level == 2: 97 | print(format_attr('Required By', pkg.compute_requiredby())) 98 | print(format_attr('Conflicts With', pkg.conflicts)) 99 | print(format_attr('Replaces', pkg.replaces)) 100 | if style == 'sync': 101 | print(format_attr('Download Size', '%.2f K' % (pkg.size / 1024))) 102 | if style == 'file': 103 | print(format_attr('Compressed Size', '%.2f K' % (pkg.size / 1024))) 104 | print(format_attr('Installed Size', '%.2f K' % (pkg.isize / 1024))) 105 | print(format_attr('Packager', pkg.packager)) 106 | print(format_attr('Architecture', pkg.arch)) 107 | print(format_attr('Build Date', pkg.builddate, format='time')) 108 | 109 | if style == 'local': 110 | # local installation information 111 | print(format_attr('Install Date', pkg.installdate, format='time')) 112 | if pkg.reason == pyalpm.PKG_REASON_EXPLICIT: 113 | reason = 'Explicitly installed' 114 | elif pkg.reason == pyalpm.PKG_REASON_DEPEND: 115 | reason = 'Installed as a dependency for another package' 116 | else: 117 | reason = 'N/A' 118 | print(format_attr('Install Reason', reason)) 119 | if style != 'sync': 120 | print(format_attr('Install Script', 'Yes' if pkg.has_scriptlet else 'No')) 121 | if style == 'sync': 122 | print(format_attr('MD5 Sum', pkg.md5sum)) 123 | print(format_attr('SHA256 Sum', pkg.sha256sum)) 124 | print(format_attr('Signatures', 'Yes' if pkg.base64_sig else 'No')) 125 | 126 | print(format_attr('Description', pkg.desc)) 127 | 128 | if level >= 2 and style == 'local': 129 | # print backup information 130 | print('Backup files:') 131 | if len(pkg.backup) == 0: 132 | print('(none)') 133 | else: 134 | print('\n'.join(["%s %s" % (md5, file) for (file, md5) in pkg.backup])) 135 | print('') 136 | 137 | # vim: set ts=4 sw=4 noet: 138 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('..')) 18 | 19 | # import as pyalpm_setup to avoid shadowing the setup method 20 | # used by sphinx 21 | import setup as pyalpm_setup 22 | 23 | 24 | # -- Project information ----------------------------------------------------- 25 | 26 | project = 'pyalpm' 27 | copyright = '2019, Dan McGee , Imanol Celaya , Jelle van der Waa , Øyvind Heggstad , Rémy Oudompheng , Tasos Latsas , Xyne ' 28 | author = 'Dan McGee , Imanol Celaya , Jelle van der Waa , Øyvind Heggstad , Rémy Oudompheng , Tasos Latsas , Xyne ' 29 | 30 | # The short X.Y version 31 | version = '' 32 | # The full version, including alpha/beta/rc tags 33 | release = pyalpm_setup.pyalpm_version 34 | 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | # 40 | # needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | extensions = [ 46 | 'sphinx.ext.autodoc', 47 | 'sphinx.ext.coverage', 48 | 'sphinx.ext.imgmath', 49 | 'sphinx.ext.viewcode', 50 | ] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix(es) of source filenames. 56 | # You can specify multiple suffix as a list of string: 57 | # 58 | # source_suffix = ['.rst', '.md'] 59 | source_suffix = '.rst' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path. 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = None 78 | 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'alabaster' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | #html_static_path = ['_static'] 97 | 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # The default sidebars (for documents that don't match any pattern) are 102 | # defined by theme itself. Builtin themes are using these templates by 103 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 104 | # 'searchbox.html']``. 105 | # 106 | # html_sidebars = {} 107 | 108 | 109 | # -- Options for HTMLHelp output --------------------------------------------- 110 | 111 | # Output file base name for HTML help builder. 112 | htmlhelp_basename = 'pyalpmdoc' 113 | 114 | 115 | # -- Options for LaTeX output ------------------------------------------------ 116 | 117 | latex_elements = { 118 | # The paper size ('letterpaper' or 'a4paper'). 119 | # 120 | # 'papersize': 'letterpaper', 121 | 122 | # The font size ('10pt', '11pt' or '12pt'). 123 | # 124 | # 'pointsize': '10pt', 125 | 126 | # Additional stuff for the LaTeX preamble. 127 | # 128 | # 'preamble': '', 129 | 130 | # Latex figure (float) alignment 131 | # 132 | # 'figure_align': 'htbp', 133 | } 134 | 135 | # Grouping the document tree into LaTeX files. List of tuples 136 | # (source start file, target name, title, 137 | # author, documentclass [howto, manual, or own class]). 138 | latex_documents = [ 139 | (master_doc, 'pyalpm.tex', 'pyalpm Documentation', 140 | 'Dan McGee \\textless{}dan@archlinux.org\\textgreater{}, Imanol Celaya \\textless{}ilcra1989@gmail.com\\textgreater{}, Jelle van der Waa \\textless{}jelle@vdwaa.nl\\textgreater{}, Øyvind Heggstad \\textless{}mrelendig@har-ikkje.net\\textgreater{}, Rémy Oudompheng \\textless{}remy@archlinux.org\\textgreater{}, Tasos Latsas \\textless{}tlatsas2000@gmail.com\\textgreater{}, Xyne \\textless{}xyne@archlinux.ca\\textgreater{}', 'manual'), 141 | ] 142 | 143 | 144 | # -- Options for manual page output ------------------------------------------ 145 | 146 | # One entry per manual page. List of tuples 147 | # (source start file, name, description, authors, manual section). 148 | man_pages = [ 149 | (master_doc, 'pyalpm', 'pyalpm Documentation', 150 | [author], 1) 151 | ] 152 | 153 | 154 | # -- Options for Texinfo output ---------------------------------------------- 155 | 156 | # Grouping the document tree into Texinfo files. List of tuples 157 | # (source start file, target name, title, author, 158 | # dir menu entry, description, category) 159 | texinfo_documents = [ 160 | (master_doc, 'pyalpm', 'pyalpm Documentation', 161 | author, 'pyalpm', 'One line description of project.', 162 | 'Miscellaneous'), 163 | ] 164 | 165 | 166 | # -- Options for Epub output ------------------------------------------------- 167 | 168 | # Bibliographic Dublin Core info. 169 | epub_title = project 170 | 171 | # The unique identifier of the text. This can be a ISBN number 172 | # or the project homepage. 173 | # 174 | # epub_identifier = '' 175 | 176 | # A unique identification for the text. 177 | # 178 | # epub_uid = '' 179 | 180 | # A list of files that should not be packed into the epub file. 181 | epub_exclude_files = ['search.html'] 182 | 183 | 184 | # -- Extension configuration ------------------------------------------------- 185 | -------------------------------------------------------------------------------- /pycman/action_query.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-query - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -Q 23 | 24 | This script displays information about installed packages. 25 | """ 26 | 27 | import os 28 | import sys 29 | 30 | import pyalpm 31 | from pycman import config 32 | from pycman import pkginfo 33 | 34 | handle = None 35 | 36 | def filter_pkglist(pkglist, options): 37 | result = [] 38 | if options.foreign: 39 | syncpkgs = set() 40 | for db in handle.get_syncdbs(): 41 | syncpkgs |= set(p.name for p in db.pkgcache) 42 | for pkg in pkglist: 43 | if options.deps and pkg.reason == pyalpm.PKG_REASON_EXPLICIT: 44 | continue 45 | if options.explicit and pkg.reason == pyalpm.PKG_REASON_DEPEND: 46 | continue 47 | if options.unrequired and len(pkg.compute_requiredby()) > 0: 48 | continue 49 | if options.foreign and pkg.name in syncpkgs: 50 | continue 51 | if options.upgrades and pyalpm.sync_newversion(pkg, handle.get_syncdbs()) is None: 52 | continue 53 | result.append(pkg) 54 | return result 55 | 56 | def display_pkg(pkg, options): 57 | displaystyle = 'file' if options.package else 'local' 58 | if options.info > 0: 59 | pkginfo.display_pkginfo(pkg, level=options.info, style=displaystyle) 60 | elif not options.listfiles: 61 | if options.quiet: 62 | print(pkg.name) 63 | else: 64 | print(pkg.name, pkg.version) 65 | 66 | if options.listfiles: 67 | if options.quiet: 68 | [print('/' + path) for path, size, mode in pkg.files] 69 | else: 70 | [print(pkg.name, '/' + path) for path, size, mode in pkg.files] 71 | 72 | def find_file(filenames, options): 73 | "lookup for files in local packages" 74 | ret = 0 75 | if len(filenames) == 0: 76 | print("error: no targets specified") 77 | ret = 1 78 | 79 | localpkgs = handle.get_localdb().pkgcache 80 | n_pkg = len(localpkgs) 81 | filelists = [None] * n_pkg 82 | 83 | for name in filenames: 84 | lookupname = None 85 | if not os.path.isabs(name): 86 | # lookup in PATH 87 | for dirname in os.getenv('PATH').split(':'): 88 | if os.path.lexists(os.path.join(dirname, name)): 89 | name = os.path.join(dirname, name) 90 | lookupname = name 91 | if lookupname is None: 92 | print("error: failed to find '%s' in PATH: No such file or directory" % name) 93 | ret = 1 94 | continue 95 | else: 96 | if not os.path.lexists(name): 97 | print("error: failed to read file '%s': No such file or directory" % name) 98 | ret = 1 99 | continue 100 | lookupname = name 101 | lookupname = os.path.normpath(lookupname) 102 | lookupname = lookupname.lstrip('/') 103 | found = False 104 | for i, pkg, files in zip(range(n_pkg), localpkgs, filelists): 105 | if files is None: 106 | files = pkg.files 107 | filelists[i] = files 108 | 109 | if lookupname in files: 110 | if options.quiet: 111 | print(pkg.name) 112 | else: 113 | print(pkg.name, "is owned by", pkg.name, pkg.version) 114 | found = True 115 | break 116 | 117 | if not found: 118 | print('error: no package owns', name) 119 | ret = 1 120 | 121 | return ret 122 | 123 | def find_search(patterns, options): 124 | db = handle.get_localdb() 125 | results = db.search(*patterns) 126 | if len(results) == 0: 127 | return 1 128 | for pkg in results: 129 | if options.quiet: 130 | print(pkg.name) 131 | else: 132 | print("%s/%s %s" % (pkg.db.name, pkg.name, pkg.version)) 133 | print(" " + pkg.desc) 134 | return 0 135 | 136 | def main(rawargs): 137 | global handle 138 | parser = config.make_parser(prog='pycman-query') 139 | group = parser.add_argument_group("Query options") 140 | group.add_argument('-d', '--deps', 141 | action='store_true', default=False, 142 | help='list packages installed as dependencies [filter]') 143 | group.add_argument('-e', '--explicit', 144 | action='store_true', default=False, 145 | help='list packages explicitly installed [filter]') 146 | group.add_argument('-i', '--info', 147 | action='count', dest='info', default=0, 148 | help='view package information') 149 | group.add_argument('-l', '--list', 150 | action='store_true', dest='listfiles', default=False, 151 | help='list the contents of the queried package') 152 | group.add_argument('-m', '--foreign', 153 | action='store_true', default=False, 154 | help='list installed packages not found in sync db(s) [filter]') 155 | group.add_argument('-o', '--owns', 156 | action='store_true', default=False, 157 | help='query the package that owns ') 158 | group.add_argument('-p', '--package', 159 | action='store_true', default=False, 160 | help='query a package file instead of the database') 161 | group.add_argument('-q', '--quiet', 162 | action='store_true', dest='quiet', default=False, 163 | help='show less information for query and search') 164 | group.add_argument('-s', '--search', action='store_true', default=False, 165 | help='search locally-installed packages for matching strings') 166 | group.add_argument('-t', '--unrequired', 167 | action='store_true', default=False, 168 | help="list packages not required by any package [filter]") 169 | group.add_argument('-u', '--upgrades', 170 | action='store_true', default=False, 171 | help="list outdated packages [filter]") 172 | group.add_argument('pkgnames', metavar='pkg', nargs='*', 173 | help='packages to show (show all packages if no arguments) ' 174 | '(when used with -o: a filename, ' 175 | 'when used with -p: the path to a package file)') 176 | 177 | args = parser.parse_args(rawargs) 178 | handle = config.init_with_config_and_options(args) 179 | 180 | if args.verbose: 181 | print("query " + " ".join(rawargs), file=sys.stderr) 182 | 183 | db = handle.get_localdb() 184 | retcode = 0 185 | 186 | # actions other than listing packages 187 | if args.owns: 188 | return find_file(args.pkgnames, args) 189 | if args.search: 190 | return find_search(args.pkgnames, args) 191 | 192 | pkglist = [] 193 | if len(args.pkgnames) > 0: 194 | # a list of package names was specified 195 | for pkgname in args.pkgnames: 196 | if args.package: 197 | pkg = handle.load_pkg(pkgname) 198 | else: 199 | pkg = db.get_pkg(pkgname) 200 | if pkg is None: 201 | print('error: package "%s" not found' % pkgname) 202 | retcode = 1 203 | else: 204 | pkglist.append(pkg) 205 | else: 206 | # no package was specified, display all 207 | pkglist = db.pkgcache 208 | # determine the list of package to actually display 209 | pkglist = filter_pkglist(pkglist, args) 210 | for pkg in pkglist: 211 | display_pkg(pkg, args) 212 | 213 | return retcode 214 | 215 | if __name__ == "__main__": 216 | ret = main(sys.argv[1:]) 217 | sys.exit(ret) 218 | 219 | # vim: set ts=4 sw=4 tw=0 noet: 220 | -------------------------------------------------------------------------------- /pycman/action_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman-sync - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | A Python implementation of pacman -S 23 | 24 | This script displays information about packages available in repositories, 25 | and is also used to install/upgrade/remove them. 26 | """ 27 | 28 | import sys 29 | 30 | import pyalpm 31 | from pycman import config 32 | from pycman import pkginfo 33 | from pycman import transaction 34 | 35 | handle = None 36 | 37 | def do_clean(options): 38 | raise NotImplementedError 39 | 40 | def do_refresh(options): 41 | "Sync databases like pacman -Sy" 42 | force = (options.refresh > 1) 43 | for db in handle.get_syncdbs(): 44 | t = transaction.init_from_options(handle, options) 45 | db.update(force) 46 | t.release() 47 | return 0 48 | 49 | def do_sysupgrade(options): 50 | "Upgrade a system like pacman -Su" 51 | downgrade = (options.sysupgrade > 1) 52 | t = transaction.init_from_options(handle, options) 53 | t.sysupgrade(downgrade) 54 | if len(t.to_add) + len(t.to_remove) == 0: 55 | print("nothing to do") 56 | t.release() 57 | return 0 58 | else: 59 | ok = transaction.finalize(t) 60 | return (0 if ok else 1) 61 | 62 | def do_install(pkgs, options): 63 | "Install a list of packages like pacman -S" 64 | repos = dict((db.name, db) for db in handle.get_syncdbs()) 65 | if len(pkgs) == 0: 66 | print("error: no targets specified") 67 | return 1 68 | 69 | targets = [] 70 | for name in pkgs: 71 | ok, pkg = find_sync_package(name, repos) 72 | if not ok: 73 | print('error:', pkg) 74 | return 1 75 | else: 76 | targets.append(pkg) 77 | t = transaction.init_from_options(handle, options) 78 | [t.add_pkg(pkg) for pkg in targets] 79 | ok = transaction.finalize(t) 80 | return (0 if ok else 1) 81 | 82 | def find_sync_package(pkgname, syncdbs): 83 | "Finds a package name of the form 'repo/pkgname' or 'pkgname' in a list of DBs" 84 | if '/' in pkgname: 85 | repo, pkgname = pkgname.split('/', 1) 86 | db = syncdbs.get(repo) 87 | if db is None: 88 | return False, "repository '%s' does not exist" % repo 89 | pkg = db.get_pkg(pkgname) 90 | if pkg is None: 91 | return False, "package '%s' was not found in repository '%s'" % (pkgname, repo) 92 | return True, pkg 93 | else: 94 | for db in syncdbs.values(): 95 | pkg = db.get_pkg(pkgname) 96 | if pkg is not None: 97 | return True, pkg 98 | return False, "package '%s' was not found" % pkgname 99 | 100 | # Query actions 101 | 102 | def show_groups(args): 103 | "Show groups like pacman -Sg" 104 | for repo in handle.get_syncdbs(): 105 | if len(args.args) == 0: 106 | # list all available groups 107 | [print(name) for name, pkgs in repo.grpcache] 108 | else: 109 | # only print chosen groups 110 | for group in args.args: 111 | grp = repo.read_grp(group) 112 | if grp is None: 113 | continue 114 | else: 115 | name, pkgs = grp 116 | if args.quiet: 117 | [print(pkg.name) for pkg in pkgs] 118 | else: 119 | [print(name, pkg.name) for pkg in pkgs] 120 | return 0 121 | 122 | def show_repo(args): 123 | "Show repository's list of packages like pacman -Sl" 124 | repos = handle.get_syncdbs() 125 | if len(args.args) > 0: 126 | repo_dict = dict((repo.name, repo) for repo in repos) 127 | try: 128 | repos = [repo_dict[name] for name in args.args] 129 | except KeyError as err: 130 | print("error: repository '%s' was not found" % err.args) 131 | return 1 132 | 133 | for repo in repos: 134 | if args.quiet: 135 | [print(pkg.name) for pkg in repo.pkgcache] 136 | else: 137 | [print(repo.name, pkg.name, pkg.version) for pkg in repo.pkgcache] 138 | return 0 139 | 140 | def show_packages(args): 141 | "Show information about packages like pacman -Si" 142 | retcode = 0 143 | if len(args.args) == 0: 144 | for repo in handle.get_syncdbs(): 145 | for pkg in repo.pkgcache: 146 | pkginfo.display_pkginfo(pkg, level=args.info, style='sync') 147 | else: 148 | repos = dict((db.name, db) for db in handle.get_syncdbs()) 149 | for pkgname in args.args: 150 | ok, value = find_sync_package(pkgname, repos) 151 | if ok: 152 | pkginfo.display_pkginfo(value, level=args.info, style='sync') 153 | else: 154 | retcode = 1 155 | print("error:", value) 156 | return retcode 157 | 158 | def show_search(patterns, options): 159 | results = [] 160 | for db in handle.get_syncdbs(): 161 | results += db.search(*patterns) 162 | if len(results) == 0: 163 | return 1 164 | for pkg in results: 165 | if options.quiet: 166 | print(pkg.name) 167 | else: 168 | print("%s/%s %s" % (pkg.db.name, pkg.name, pkg.version)) 169 | print(" " + pkg.desc) 170 | return 0 171 | 172 | def parse_options(rawargs): 173 | parser = config.make_parser(prog='pycman-sync') 174 | # Misc actions 175 | group0 = parser.add_argument_group("Actions (default is installing specified packages)") 176 | group0.add_argument("-c", "--clean", 177 | action='count', default=0, 178 | help='remove old packages from cache directory (-cc for all)') 179 | group0.add_argument("-u", "--sysupgrade", 180 | action='count', default=0, 181 | help='upgrade installed packages (-uu allows downgrade)') 182 | group0.add_argument("-y", "--refresh", 183 | action='count', default=0, 184 | help='download fresh package databases from the server') 185 | # Installation options 186 | grp_install = parser.add_argument_group("Install options") 187 | grp_install.add_argument('-d', '--nodeps', 188 | action='store_true', default=False, 189 | help='skip dependency checks') 190 | grp_install.add_argument('-f', '--force', 191 | action='store_true', default=False, 192 | help='force install, overwrite conflicting files') 193 | grp_install.add_argument('-k', '--dbonly', 194 | action='store_true', default=False, 195 | help='only modify database entries, not package files') 196 | grp_install.add_argument('-w', '--downloadonly', 197 | action='store_true', default=False, 198 | help='download packages but do not install/upgrade anything') 199 | grp_install.add_argument('--asdeps', dest='mode', 200 | action="store_const", 201 | const=pyalpm.PKG_REASON_DEPEND) 202 | grp_install.add_argument('--asexplicit', dest='mode', 203 | action="store_const", 204 | const=pyalpm.PKG_REASON_EXPLICIT) 205 | # Options to query sync databases 206 | group1 = parser.add_argument_group("Query actions") 207 | group1.add_argument('-g', '--groups', action='store_true', default=False, 208 | help='view list of groups, or all members of a package group') 209 | group1.add_argument('-i', '--info', 210 | action='count', dest='info', default=0, 211 | help='view package information') 212 | group1.add_argument('-l', '--list', action='store_true', default=False, 213 | help='list the contents of repositories') 214 | group1.add_argument('-s', '--search', action='store_true', default=False, 215 | help='search remote repositories for matching strings') 216 | group1.add_argument('-q', '--quiet', 217 | action='store_true', dest='quiet', default=False, 218 | help='show less information for query and search') 219 | group1.add_argument('args', metavar='arg', nargs='*', 220 | help='arguments (group names for -g, repo names for -l, ' 221 | 'package names for -i)') 222 | return parser.parse_args(rawargs) 223 | 224 | def main(rawargs): 225 | global handle 226 | args = parse_options(rawargs) 227 | handle = config.init_with_config_and_options(args) 228 | 229 | if args.verbose: 230 | print("sync " + " ".join(rawargs), file=sys.stderr) 231 | 232 | # Refresh databases if necessary 233 | if args.refresh > 0: 234 | ret = do_refresh(args) 235 | if ret != 0: 236 | return ret 237 | 238 | # If a query action is set 239 | if args.search: 240 | return show_search(args.args, args) 241 | elif args.groups: 242 | return show_groups(args) 243 | elif args.info: 244 | return show_packages(args) 245 | elif args.list: 246 | return show_repo(args) 247 | # If a cleanup is required 248 | elif args.clean > 0: 249 | return do_clean(args) 250 | # If a sysupgrade is required 251 | elif args.sysupgrade > 0: 252 | return do_sysupgrade(args) 253 | # If only a refresh was requested 254 | elif len(args.args) == 0 and args.refresh > 0: 255 | return 0 256 | # Otherwise it's a normal install 257 | else: 258 | return do_install(args.args, args) 259 | 260 | if __name__ == "__main__": 261 | ret = main(sys.argv[1:]) 262 | sys.exit(ret) 263 | 264 | # vim: set ts=4 sw=4 tw=0 noet: 265 | -------------------------------------------------------------------------------- /pycman/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # pycman - A Python implementation of Pacman 4 | # Copyright (C) 2011 Rémy Oudompheng 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # 20 | 21 | """ 22 | pycman configuration handling 23 | 24 | This module handles pacman.conf files as well as pycman options that 25 | are common to all action modes. 26 | """ 27 | 28 | import os 29 | import glob 30 | import sys 31 | import argparse 32 | import collections 33 | import warnings 34 | 35 | import pyalpm 36 | 37 | class InvalidSyntax(Warning): 38 | def __init__(self, filename, problem, arg): 39 | self.filename = filename 40 | self.problem = problem 41 | self.arg = arg 42 | 43 | def __str__(self): 44 | return "unable to parse %s, %s: %r" % (self.filename, self.problem, self.arg) 45 | 46 | # Options that may occur several times in a section. Their values should be 47 | # accumulated in a list. 48 | LIST_OPTIONS = ( 49 | 'CacheDir', 50 | 'HookDir', 51 | 'HoldPkg', 52 | 'SyncFirst', 53 | 'IgnoreGroup', 54 | 'IgnorePkg', 55 | 'NoExtract', 56 | 'NoUpgrade', 57 | 'Server' 58 | ) 59 | 60 | SINGLE_OPTIONS = ( 61 | 'RootDir', 62 | 'DBPath', 63 | 'GPGDir', 64 | 'LogFile', 65 | 'Architecture', 66 | 'XferCommand', 67 | 'CleanMethod', 68 | 'SigLevel', 69 | 'LocalFileSigLevel', 70 | 'RemoteFileSigLevel', 71 | 'UseDelta', 72 | ) 73 | 74 | BOOLEAN_OPTIONS = ( 75 | 'UseSyslog', 76 | 'ShowSize', 77 | 'UseDelta', 78 | 'TotalDownload', 79 | 'CheckSpace', 80 | 'VerbosePkgLists', 81 | 'ILoveCandy', 82 | 'Color', 83 | 'DisableDownloadTimeout', 84 | ) 85 | 86 | 87 | class PacmanConfEnumeratorSession(): 88 | 89 | def __init__(self, path): 90 | self.path = path 91 | self.file_descriptors = [] 92 | 93 | def _enumerator(self): 94 | filestack = [] 95 | current_section = None 96 | new_fd = open(self.path, 'rb') 97 | self.file_descriptors.append(new_fd) 98 | filestack.append(new_fd) 99 | while len(filestack) > 0: 100 | f = filestack[-1] 101 | line = f.readline() 102 | line = line.decode('utf-8') 103 | if len(line) == 0: 104 | # end of file 105 | filestack.pop() 106 | continue 107 | 108 | line = line.strip() 109 | if len(line) == 0: continue 110 | if line[0] == '#': 111 | continue 112 | if line[0] == '[' and line[-1] == ']': 113 | current_section = line[1:-1] 114 | continue 115 | if current_section is None: 116 | raise InvalidSyntax(f.name, 'statement outside of a section', line) 117 | # read key, value 118 | key, equal, value = [x.strip() for x in line.partition('=')] 119 | 120 | # include files 121 | if equal == '=' and key == 'Include': 122 | new_fds = [open(f, 'rb') for f in glob.glob(value)] 123 | filestack.extend(new_fds) 124 | self.file_descriptors.extend(new_fds) 125 | continue 126 | if current_section != 'options': 127 | # repos only have the Server, SigLevel, Usage options 128 | if key in ('Server', 'SigLevel', 'Usage') and equal == '=': 129 | yield (current_section, key, value) 130 | else: 131 | raise InvalidSyntax(f.name, 'invalid key for repository configuration', line) 132 | continue 133 | if equal == '=': 134 | if key in LIST_OPTIONS: 135 | for val in value.split(): 136 | yield (current_section, key, val) 137 | elif key in SINGLE_OPTIONS: 138 | yield (current_section, key, value) 139 | else: 140 | warnings.warn(InvalidSyntax(f.name, 'unrecognized option', key)) 141 | else: 142 | if key in BOOLEAN_OPTIONS: 143 | yield (current_section, key, True) 144 | else: 145 | warnings.warn(InvalidSyntax(f.name, 'unrecognized option', key)) 146 | 147 | def __enter__(self): 148 | return self._enumerator 149 | 150 | def __exit__(self, *exc_details): 151 | for fd in self.file_descriptors: 152 | fd.close() 153 | 154 | 155 | _logmask = pyalpm.LOG_ERROR | pyalpm.LOG_WARNING 156 | 157 | def cb_log(level, line): 158 | if not (level & _logmask): 159 | return 160 | if level & pyalpm.LOG_ERROR: 161 | line = "ERROR: " + line 162 | elif level & pyalpm.LOG_WARNING: 163 | line = "WARNING: " + line 164 | elif level & pyalpm.LOG_DEBUG: 165 | line = "DEBUG: " + line 166 | elif level & pyalpm.LOG_FUNCTION: 167 | line = "FUNC: " + line 168 | sys.stderr.write(line) 169 | 170 | class PacmanConfig(object): 171 | def __init__(self, conf=None, options=None): 172 | self.options = {} 173 | self.repos = collections.OrderedDict() 174 | self.options["RootDir"] = "/" 175 | self.options["DBPath"] = "/var/lib/pacman" 176 | self.options["GPGDir"] = "/etc/pacman.d/gnupg/" 177 | self.options["LogFile"] = "/var/log/pacman.log" 178 | self.options["Architecture"] = os.uname()[-1] 179 | if conf is not None: 180 | self.load_from_file(conf) 181 | if options is not None: 182 | self.load_from_options(options) 183 | 184 | def load_from_file(self, filename): 185 | with PacmanConfEnumeratorSession(filename) as pacman_conf_enumerator: 186 | for section, key, value in pacman_conf_enumerator(): 187 | if section == 'options': 188 | if key == 'Architecture' and value == 'auto': 189 | continue 190 | if key in LIST_OPTIONS: 191 | self.options.setdefault(key, []).append(value) 192 | else: 193 | self.options[key] = value 194 | else: 195 | servers = self.repos.setdefault(section, []) 196 | if key == 'Server': 197 | servers.append(value) 198 | if "CacheDir" not in self.options: 199 | self.options["CacheDir"] = ["/var/cache/pacman/pkg"] 200 | 201 | def load_from_options(self, options): 202 | global _logmask 203 | if options.root is not None: 204 | self.options["RootDir"] = options.root 205 | if options.dbpath is not None: 206 | self.options["DBPath"] = options.dbpath 207 | if options.gpgdir is not None: 208 | self.options["GPGDir"] = options.gpgdir 209 | if options.arch is not None: 210 | self.options["Architecture"] = options.arch 211 | if options.logfile is not None: 212 | self.options["LogFile"] = options.logfile 213 | if options.cachedir is not None: 214 | self.options["CacheDir"] = [options.cachedir] 215 | if options.debug: 216 | _logmask = 0xffff 217 | 218 | def apply(self, h): 219 | # File paths 220 | h.logfile = self.options["LogFile"] 221 | h.gpgdir = self.options["GPGDir"] 222 | # Strings 223 | h.arch = self.options["Architecture"] 224 | # Lists 225 | h.cachedirs = self.options["CacheDir"] 226 | if "NoUpgrade" in self.options: 227 | h.noupgrades = self.options["NoUpgrade"] 228 | if "NoExtract" in self.options: 229 | h.noextracts = self.options["NoExtract"] 230 | if "IgnorePkg" in self.options: 231 | h.ignorepkgs = self.options["IgnorePkg"] 232 | if "IgnoreGroup" in self.options: 233 | h.ignoregrps = self.options["IgnoreGroup"] 234 | 235 | h.logcb = cb_log 236 | 237 | # set sync databases 238 | for repo, servers in self.repos.items(): 239 | db = h.register_syncdb(repo, 0) 240 | db_servers = [] 241 | for rawurl in servers: 242 | url = rawurl.replace("$repo", repo) 243 | url = url.replace("$arch", self.options["Architecture"]) 244 | db_servers.append(url) 245 | db.servers = db_servers 246 | 247 | def initialize_alpm(self): 248 | h = pyalpm.Handle(self.options["RootDir"], self.options["DBPath"]) 249 | self.apply(h) 250 | return h 251 | 252 | def __str__(self): 253 | return("PacmanConfig(options=%s, repos=%s)" % (str(self.options), str(self.repos))) 254 | 255 | def make_parser(*args, **kwargs): 256 | parser = argparse.ArgumentParser(*args, **kwargs) 257 | common = parser.add_argument_group('Common options') 258 | common.add_argument('-b', '--dbpath', metavar='', 259 | action='store', dest='dbpath', type=str, 260 | help='set an alternate database location') 261 | common.add_argument('-r', '--root', metavar='', 262 | action='store', dest='root', type=str, 263 | help='set an alternate installation root') 264 | common.add_argument('-v', '--verbose', 265 | action='store_true', dest='verbose', default=False, 266 | help='be verbose') 267 | common.add_argument('--arch', metavar='', 268 | action='store', dest='arch', type=str, 269 | help='set an alternate architecture') 270 | common.add_argument('--config', metavar='', 271 | action='store', dest='config', type=str, 272 | help='set an alternate configuration file') 273 | common.add_argument('--debug', 274 | action='store_true', dest='debug', 275 | help='display debug messages') 276 | common.add_argument('--logfile', metavar='', 277 | action='store', dest='logfile', type=str, 278 | help='set an alternate log file') 279 | common.add_argument('--gpgdir', metavar='', 280 | action='store', dest='gpgdir', type=str, 281 | help='set an alternate log file') 282 | common.add_argument('--cachedir', metavar='', 283 | action='store', dest='cachedir', type=str, 284 | help='set an alternate cche location') 285 | return parser 286 | 287 | def init_with_config(configpath): 288 | "Reads configuration from given path and apply it to libalpm" 289 | config = PacmanConfig(conf=configpath) 290 | return config.initialize_alpm() 291 | 292 | def init_with_config_and_options(options): 293 | "Reads configuration from file and commandline options, and apply it to libalpm" 294 | # read config file 295 | if options.config is not None: 296 | config_file = options.config 297 | else: 298 | config_file = "/etc/pacman.conf" 299 | 300 | conf = PacmanConfig(conf=config_file, options=options) 301 | return conf.initialize_alpm() 302 | 303 | # vim: set ts=4 sw=4 tw=0 noet: 304 | -------------------------------------------------------------------------------- /src/db.c: -------------------------------------------------------------------------------- 1 | /** 2 | * db.c : wrapper class around alpm_db_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include "db.h" 27 | #include "package.h" 28 | #include "util.h" 29 | 30 | typedef struct _AlpmDB { 31 | PyObject_HEAD 32 | alpm_db_t *c_data; 33 | } AlpmDB; 34 | 35 | #define ALPM_DB(self) (((AlpmDB*)self)->c_data) 36 | 37 | static PyTypeObject AlpmDBType; 38 | 39 | static void pyalpm_db_dealloc(AlpmDB *self) { 40 | Py_TYPE(self)->tp_free((PyObject*)self); 41 | } 42 | 43 | static PyObject* _pyobject_from_pmgrp(void *group) { 44 | const alpm_group_t* grp = (alpm_group_t*)group; 45 | if (!grp) 46 | Py_RETURN_NONE; 47 | else { 48 | PyObject *fst = PyUnicode_FromString(grp->name); 49 | PyObject *snd = alpmlist_to_pylist(grp->packages, 50 | pyalpm_package_from_pmpkg); 51 | PyObject *tuple = PyTuple_Pack(2, fst, snd); 52 | Py_DECREF(fst); 53 | Py_DECREF(snd); 54 | return tuple; 55 | } 56 | } 57 | 58 | /** Converts a Python list of databases to an alpm_list_t linked list. 59 | * return 0 on success, -1 on failure 60 | */ 61 | int pylist_db_to_alpmlist(PyObject *list, alpm_list_t **result) { 62 | alpm_list_t *ret = NULL; 63 | PyObject *iterator = PyObject_GetIter(list); 64 | PyObject *item; 65 | 66 | if(iterator == NULL) { 67 | PyErr_SetString(PyExc_TypeError, "object is not iterable"); 68 | return -1; 69 | } 70 | 71 | while((item = PyIter_Next(iterator))) 72 | { 73 | if (PyObject_TypeCheck(item, &AlpmDBType)) { 74 | ret = alpm_list_add(ret, ((AlpmDB*)item)->c_data); 75 | } else { 76 | PyErr_SetString(PyExc_TypeError, "list must contain only Database objects"); 77 | FREELIST(ret); 78 | Py_DECREF(item); 79 | Py_DECREF(iterator); 80 | return -1; 81 | } 82 | Py_DECREF(item); 83 | } 84 | Py_DECREF(iterator); 85 | 86 | *result = ret; 87 | return 0; 88 | } 89 | 90 | #define CHECK_IF_INITIALIZED() if (! self->c_data) { \ 91 | PyErr_SetString(alpm_error, "data is not initialized"); \ 92 | return NULL; \ 93 | } 94 | 95 | /** Database properties */ 96 | 97 | static PyObject* pyalpm_db_get_name(AlpmDB* self, void* closure) { 98 | const char* name; 99 | CHECK_IF_INITIALIZED(); 100 | name = alpm_db_get_name(self->c_data); 101 | if (!name) 102 | Py_RETURN_NONE; 103 | return PyUnicode_FromString(name); 104 | } 105 | 106 | static PyObject* pyalpm_db_get_servers(PyObject *self, void* closure) { 107 | alpm_db_t *db = ALPM_DB(self); 108 | return alpmlist_to_pylist(alpm_db_get_servers(db), pyobject_from_string); 109 | } 110 | 111 | static int pyalpm_db_set_servers(PyObject* self, PyObject* value, void* closure) { 112 | alpm_db_t *db = ALPM_DB(self); 113 | alpm_list_t *target; 114 | if (pylist_string_to_alpmlist(value, &target) == -1) 115 | return -1; 116 | if (alpm_db_set_servers(db, target) == -1) 117 | RET_ERR("unable to set servers", 0, -1); 118 | return 0; 119 | } 120 | 121 | static PyObject* pyalpm_db_get_pkgcache(AlpmDB* self, void* closure) { 122 | alpm_list_t *pkglist = alpm_db_get_pkgcache(self->c_data); 123 | return alpmlist_to_pylist(pkglist, pyalpm_package_from_pmpkg); 124 | } 125 | 126 | static PyObject* pyalpm_db_get_grpcache(AlpmDB* self, void* closure) { 127 | alpm_list_t *grplist = alpm_db_get_groupcache(self->c_data); 128 | return alpmlist_to_pylist(grplist, _pyobject_from_pmgrp); 129 | } 130 | 131 | /** Package get/set operations */ 132 | 133 | static PyObject* pyalpm_db_get_pkg(PyObject *rawself, PyObject* args) { 134 | char *pkgname; 135 | alpm_pkg_t *p; 136 | AlpmDB *self = (AlpmDB*)rawself; 137 | 138 | if(!PyArg_ParseTuple(args, "s", &pkgname)) 139 | { 140 | PyErr_SetString(PyExc_TypeError, "get_pkg() takes a string argument"); 141 | return NULL; 142 | } 143 | 144 | CHECK_IF_INITIALIZED(); 145 | 146 | p = alpm_db_get_pkg(self->c_data, pkgname); 147 | 148 | if (p == NULL) { 149 | Py_RETURN_NONE; 150 | } else { 151 | PyObject *result; 152 | result = pyalpm_package_from_pmpkg(p); 153 | if (result == NULL) { 154 | return NULL; 155 | } else { 156 | return result; 157 | } 158 | } 159 | } 160 | 161 | static PyObject* pyalpm_db_get_group(PyObject* rawself, PyObject* args) { 162 | AlpmDB* self = (AlpmDB*)rawself; 163 | char *grpname; 164 | alpm_group_t *grp; 165 | if (!PyArg_ParseTuple(args, "s", &grpname)) { 166 | PyErr_SetString(PyExc_TypeError, "expected string argument"); 167 | return NULL; 168 | } 169 | 170 | grp = alpm_db_get_group(self->c_data, grpname); 171 | return _pyobject_from_pmgrp(grp); 172 | } 173 | 174 | static PyObject *pyalpm_db_update(PyObject *rawself, PyObject *args, PyObject *kwargs) { 175 | AlpmDB* self = (AlpmDB*)rawself; 176 | char* keyword[] = {"force", NULL}; 177 | int ret; 178 | PyObject *force; 179 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keyword, &PyBool_Type, &force)) 180 | return NULL; 181 | 182 | ret = alpm_db_update((force == Py_True), self->c_data); 183 | 184 | switch(ret) { 185 | case -1: 186 | RET_ERR("unable to update database", 0, NULL); 187 | case 0: 188 | Py_RETURN_TRUE; 189 | case 1: 190 | Py_RETURN_FALSE; 191 | default: 192 | RET_ERR("invalid return code from alpm_db_update()", 0, NULL); 193 | } 194 | } 195 | 196 | static PyObject* pyalpm_db_search(PyObject *rawself, PyObject *args) { 197 | AlpmDB* self = (AlpmDB *)rawself; 198 | alpm_list_t* rawargs; 199 | alpm_list_t* result; 200 | int ok = pylist_string_to_alpmlist(args, &rawargs); 201 | if (ok == -1) return NULL; 202 | 203 | result = alpm_db_search(self->c_data, rawargs); 204 | return alpmlist_to_pylist(result, pyalpm_package_from_pmpkg); 205 | } 206 | 207 | static struct PyMethodDef db_methods[] = { 208 | { "get_pkg", pyalpm_db_get_pkg, METH_VARARGS, 209 | "get a package by name\n" 210 | "args: a package name (string)\n" 211 | "returns: a Package object or None if not found" }, 212 | { "search", pyalpm_db_search, METH_VARARGS, 213 | "search for packages matching a list of regexps\n" 214 | "args: a variable number of regexps (strings)\n" 215 | "returns: packages matching all these regexps" }, 216 | { "read_grp", pyalpm_db_get_group, METH_VARARGS, 217 | "get contents of a group\n" 218 | "args: a group name (string)\n" 219 | "returns: a tuple (group name, list of packages)" }, 220 | { "update", (PyCFunction)pyalpm_db_update, METH_VARARGS | METH_KEYWORDS, 221 | "update a database from its url attribute\n" 222 | "args: force (update even if DB is up to date, boolean)\n" 223 | "returns: True if an update has been done" }, 224 | { NULL }, 225 | }; 226 | 227 | struct PyGetSetDef db_getset[] = { 228 | /* description properties */ 229 | { "name", (getter)pyalpm_db_get_name, 0, 230 | "database name (e.g. \"core\", \"extra\")", NULL } , 231 | { "servers", (getter)pyalpm_db_get_servers, (setter)pyalpm_db_set_servers, 232 | "a list of URLs (for sync DBs)", NULL } , 233 | { "pkgcache", (getter)pyalpm_db_get_pkgcache, 0, "(read only) list of packages", NULL } , 234 | { "grpcache", (getter)pyalpm_db_get_grpcache, 0, "(read only) list of package groups", NULL } , 235 | { NULL } 236 | }; 237 | 238 | static PyTypeObject AlpmDBType = { 239 | PyVarObject_HEAD_INIT(NULL, 0) 240 | "alpm.DB", /*tp_name*/ 241 | sizeof(AlpmDB), /*tp_basicsize*/ 242 | 0, /*tp_itemsize*/ 243 | .tp_dealloc = (destructor)pyalpm_db_dealloc, 244 | .tp_flags = Py_TPFLAGS_DEFAULT, 245 | .tp_doc = "libalpm DB object", 246 | .tp_methods = db_methods, 247 | .tp_getset = db_getset, 248 | }; 249 | 250 | /** Initializes Pb class in module */ 251 | void init_pyalpm_db(PyObject *module) { 252 | PyObject *type; 253 | 254 | if (PyType_Ready(&AlpmDBType) < 0) 255 | return; 256 | type = (PyObject*)&AlpmDBType; 257 | Py_INCREF(type); 258 | PyModule_AddObject(module, "DB", type); 259 | 260 | /* signature check levels */ 261 | PyModule_AddIntConstant(module, "SIG_DATABASE", ALPM_SIG_DATABASE); 262 | PyModule_AddIntConstant(module, "SIG_DATABASE_OPTIONAL", ALPM_SIG_DATABASE_OPTIONAL); 263 | PyModule_AddIntConstant(module, "SIG_DATABASE_MARGINAL_OK", ALPM_SIG_DATABASE_MARGINAL_OK); 264 | PyModule_AddIntConstant(module, "SIG_DATABASE_UNKNOWN_OK", ALPM_SIG_DATABASE_UNKNOWN_OK); 265 | } 266 | 267 | 268 | PyObject *pyalpm_db_from_pmdb(void* data) { 269 | alpm_db_t *db = (alpm_db_t*)data; 270 | AlpmDB *self; 271 | self = (AlpmDB*)AlpmDBType.tp_alloc(&AlpmDBType, 0); 272 | if (self == NULL) { 273 | PyErr_SetString(PyExc_RuntimeError, "unable to create DB object"); 274 | return NULL; 275 | } 276 | 277 | self->c_data = db; 278 | return (PyObject *)self; 279 | } 280 | 281 | /** non-class methods */ 282 | PyObject* pyalpm_find_grp_pkgs(PyObject* self, PyObject *args) { 283 | PyObject *dbs; 284 | char *grpname; 285 | alpm_list_t *db_list = NULL; 286 | alpm_list_t *pkg_list; 287 | PyObject *result; 288 | 289 | if (!PyArg_ParseTuple(args, "Os", &dbs, &grpname)) { 290 | PyErr_SetString(PyExc_TypeError, "expected arguments (list of dbs, group name)"); 291 | return NULL; 292 | } 293 | 294 | { 295 | int ret = pylist_db_to_alpmlist(dbs, &db_list); 296 | if (ret == -1) 297 | return NULL; 298 | } 299 | pkg_list = alpm_find_group_pkgs(db_list, grpname); 300 | result = alpmlist_to_pylist(pkg_list, pyalpm_package_from_pmpkg); 301 | alpm_list_free(db_list); 302 | alpm_list_free(pkg_list); 303 | return result; 304 | } 305 | 306 | /** Finds an available upgrade for a package in a list of databases */ 307 | PyObject* pyalpm_sync_newversion(PyObject *self, PyObject* args) { 308 | PyObject *pkg; 309 | PyObject *dbs; 310 | alpm_list_t *db_list; 311 | alpm_pkg_t *result = NULL; 312 | if(!PyArg_ParseTuple(args, "OO", &pkg, &dbs) 313 | || !PyAlpmPkg_Check(pkg) 314 | || pylist_db_to_alpmlist(dbs, &db_list) == -1) 315 | { 316 | PyErr_SetString(PyExc_TypeError, "sync_newversion() takes a Package and a list of DBs"); 317 | return NULL; 318 | } 319 | 320 | { 321 | alpm_pkg_t *rawpkg = pmpkg_from_pyalpm_pkg(pkg); 322 | if (rawpkg) { 323 | result = alpm_sync_newversion(rawpkg, db_list); 324 | } 325 | alpm_list_free(db_list); 326 | } 327 | if (!result) 328 | Py_RETURN_NONE; 329 | else 330 | return pyalpm_package_from_pmpkg(result); 331 | } 332 | 333 | /* vim: set ts=2 sw=2 et: */ 334 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | /** 2 | * options.c : options module for pyalpm 3 | * 4 | * Copyright 2008 Imanol Celaya 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include "handle.h" 26 | #include "options.h" 27 | #include "util.h" 28 | 29 | static int PyLong_to_int(PyObject *value, int overflow_val) 30 | { 31 | int overflow; 32 | long lval = PyLong_AsLongAndOverflow(value, &overflow); 33 | if (overflow != 0) return overflow_val; 34 | if (lval > INT_MAX || lval < INT_MIN) return overflow_val; 35 | return (int)lval; 36 | } 37 | 38 | /** Boolean options 39 | */ 40 | /* 41 | receives and returns an int type 42 | 1 = enabled 43 | 0 = disabled 44 | */ 45 | PyObject * option_get_usesyslog_alpm(PyObject *self, void* closure) 46 | { 47 | alpm_handle_t *handle = ALPM_HANDLE(self); 48 | int ret = alpm_option_get_usesyslog(handle); 49 | 50 | if(ret == -1) 51 | { 52 | RET_ERR("failed getting usesyslog", alpm_errno(handle), NULL); 53 | } 54 | else 55 | return PyLong_FromLong(ret); 56 | } 57 | 58 | int option_set_usesyslog_alpm(PyObject *self, PyObject *value, void* closure) 59 | { 60 | alpm_handle_t *handle = ALPM_HANDLE(self); 61 | if(!PyLong_Check(value)) 62 | { 63 | PyErr_SetString(PyExc_TypeError, "wrong arguments"); 64 | return -1; 65 | } 66 | 67 | alpm_option_set_usesyslog(handle, PyLong_to_int(value, -1)); 68 | return 0; 69 | } 70 | 71 | PyObject* option_get_deltaratio_alpm(PyObject *self, void* closure) { 72 | alpm_handle_t *handle = ALPM_HANDLE(self); 73 | double ret = alpm_option_get_deltaratio(handle); 74 | if (ret == -1) { 75 | RET_ERR("failed getting deltaratio", alpm_errno(handle), NULL); 76 | } else 77 | return PyFloat_FromDouble(ret); 78 | } 79 | 80 | int option_set_deltaratio_alpm(PyObject *self, PyObject *value, void* closure) 81 | { 82 | alpm_handle_t *handle = ALPM_HANDLE(self); 83 | double fval = PyFloat_AsDouble(value); 84 | 85 | if(PyErr_Occurred()) { 86 | return -1; 87 | } 88 | 89 | alpm_option_set_deltaratio(handle, fval); 90 | return 0; 91 | } 92 | 93 | PyObject* option_get_checkspace_alpm(PyObject *self, void* closure) { 94 | alpm_handle_t *handle = ALPM_HANDLE(self); 95 | int ret = alpm_option_get_checkspace(handle); 96 | if (ret == -1) { 97 | RET_ERR("failed getting checkspace", alpm_errno(handle), NULL); 98 | } else 99 | return PyLong_FromLong(ret); 100 | } 101 | 102 | int option_set_checkspace_alpm(PyObject *self, PyObject *value, void* closure) 103 | { 104 | alpm_handle_t *handle = ALPM_HANDLE(self); 105 | if(!PyLong_Check(value)) 106 | { 107 | PyErr_SetString(PyExc_TypeError, "wrong arguments"); 108 | return -1; 109 | } 110 | alpm_option_set_checkspace(handle, PyLong_to_int(value, -1)); 111 | return 0; 112 | } 113 | 114 | /** 115 | * List options 116 | * in addition to getters/setters, these have add/remove methods 117 | */ 118 | 119 | PyObject* option_get_cachedirs_alpm(PyObject *self, void* closure) { 120 | alpm_handle_t *handle = ALPM_HANDLE(self); 121 | return alpmlist_to_pylist(alpm_option_get_cachedirs(handle), pyobject_from_string); 122 | } 123 | 124 | int option_set_cachedirs_alpm(PyObject *self, PyObject *value, void *closure) 125 | { 126 | alpm_handle_t *handle = ALPM_HANDLE(self); 127 | alpm_list_t *target; 128 | if (pylist_string_to_alpmlist(value, &target) == -1) 129 | return -1; 130 | 131 | alpm_option_set_cachedirs(handle, target); 132 | return 0; 133 | } 134 | 135 | PyObject* option_get_noupgrades_alpm(PyObject *self, void* closure) { 136 | alpm_handle_t *handle = ALPM_HANDLE(self); 137 | return alpmlist_to_pylist(alpm_option_get_noupgrades(handle), pyobject_from_string); 138 | } 139 | 140 | int option_set_noupgrades_alpm(PyObject *self, PyObject *value, void *closure) 141 | { 142 | alpm_handle_t *handle = ALPM_HANDLE(self); 143 | alpm_list_t *target; 144 | if (pylist_string_to_alpmlist(value, &target) == -1) 145 | return -1; 146 | 147 | alpm_option_set_noupgrades(handle, target); 148 | return 0; 149 | } 150 | 151 | PyObject* option_get_noextracts_alpm(PyObject *self, void* closure) { 152 | alpm_handle_t *handle = ALPM_HANDLE(self); 153 | return alpmlist_to_pylist(alpm_option_get_noextracts(handle), pyobject_from_string); 154 | } 155 | 156 | int option_set_noextracts_alpm(PyObject *self, PyObject *value, void *closure) 157 | { 158 | alpm_handle_t *handle = ALPM_HANDLE(self); 159 | alpm_list_t *target; 160 | if (pylist_string_to_alpmlist(value, &target) == -1) 161 | return -1; 162 | 163 | alpm_option_set_noextracts(handle, target); 164 | return 0; 165 | } 166 | 167 | PyObject* option_get_ignorepkgs_alpm(PyObject *self, void* closure) { 168 | alpm_handle_t *handle = ALPM_HANDLE(self); 169 | return alpmlist_to_pylist(alpm_option_get_ignorepkgs(handle), pyobject_from_string); 170 | } 171 | 172 | int option_set_ignorepkgs_alpm(PyObject *self, PyObject *value, void *closure) 173 | { 174 | alpm_handle_t *handle = ALPM_HANDLE(self); 175 | alpm_list_t *target; 176 | if (pylist_string_to_alpmlist(value, &target) == -1) 177 | return -1; 178 | 179 | alpm_option_set_ignorepkgs(handle, target); 180 | return 0; 181 | } 182 | 183 | PyObject* option_get_ignoregrps_alpm(PyObject *self, void* closure) { 184 | alpm_handle_t *handle = ALPM_HANDLE(self); 185 | return alpmlist_to_pylist(alpm_option_get_ignoregroups(handle), pyobject_from_string); 186 | } 187 | 188 | int option_set_ignoregrps_alpm(PyObject *self, PyObject *value, void *closure) 189 | { 190 | alpm_handle_t *handle = ALPM_HANDLE(self); 191 | alpm_list_t *target; 192 | if (pylist_string_to_alpmlist(value, &target) == -1) 193 | return -1; 194 | 195 | alpm_option_set_ignoregroups(handle, target); 196 | return 0; 197 | } 198 | 199 | /* list options modifiers : add/remove */ 200 | 201 | PyObject* option_add_noupgrade_alpm(PyObject *self, PyObject *args) 202 | { 203 | alpm_handle_t *handle = ALPM_HANDLE(self); 204 | const char *str; 205 | 206 | if(!PyArg_ParseTuple(args, "s", &str)) { 207 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 208 | return NULL; 209 | } 210 | alpm_option_add_noupgrade(handle, str); 211 | Py_RETURN_NONE; 212 | } 213 | 214 | PyObject* option_remove_noupgrade_alpm(PyObject *self, PyObject *args) 215 | { 216 | alpm_handle_t *handle = ALPM_HANDLE(self); 217 | const char *str; 218 | 219 | if(!PyArg_ParseTuple(args, "s", &str)) { 220 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 221 | return NULL; 222 | } 223 | alpm_option_remove_noupgrade(handle, str); 224 | Py_RETURN_NONE; 225 | } 226 | 227 | PyObject* option_add_cachedir_alpm(PyObject *self, PyObject *args) 228 | { 229 | alpm_handle_t *handle = ALPM_HANDLE(self); 230 | const char *str; 231 | 232 | if(!PyArg_ParseTuple(args, "s", &str)) { 233 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 234 | return NULL; 235 | } 236 | alpm_option_add_cachedir(handle, str); 237 | Py_RETURN_NONE; 238 | } 239 | 240 | PyObject* option_remove_cachedir_alpm(PyObject *self, PyObject *args) 241 | { 242 | alpm_handle_t *handle = ALPM_HANDLE(self); 243 | const char *str; 244 | 245 | if(!PyArg_ParseTuple(args, "s", &str)) { 246 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 247 | return NULL; 248 | } 249 | alpm_option_remove_cachedir(handle, str); 250 | Py_RETURN_NONE; 251 | } 252 | 253 | PyObject* option_add_noextract_alpm(PyObject *self, PyObject *args) 254 | { 255 | alpm_handle_t *handle = ALPM_HANDLE(self); 256 | const char *str; 257 | 258 | if(!PyArg_ParseTuple(args, "s", &str)) { 259 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 260 | return NULL; 261 | } 262 | alpm_option_add_noextract(handle, str); 263 | Py_RETURN_NONE; 264 | } 265 | 266 | PyObject* option_remove_noextract_alpm(PyObject *self, PyObject *args) 267 | { 268 | alpm_handle_t *handle = ALPM_HANDLE(self); 269 | const char *str; 270 | 271 | if(!PyArg_ParseTuple(args, "s", &str)) { 272 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 273 | return NULL; 274 | } 275 | alpm_option_remove_noextract(handle, str); 276 | Py_RETURN_NONE; 277 | } 278 | 279 | PyObject* option_add_ignorepkg_alpm(PyObject *self, PyObject *args) 280 | { 281 | alpm_handle_t *handle = ALPM_HANDLE(self); 282 | const char *str; 283 | 284 | if(!PyArg_ParseTuple(args, "s", &str)) { 285 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 286 | return NULL; 287 | } 288 | alpm_option_add_ignorepkg(handle, str); 289 | Py_RETURN_NONE; 290 | } 291 | 292 | PyObject* option_remove_ignorepkg_alpm(PyObject *self, PyObject *args) 293 | { 294 | alpm_handle_t *handle = ALPM_HANDLE(self); 295 | const char *str; 296 | 297 | if(!PyArg_ParseTuple(args, "s", &str)) { 298 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 299 | return NULL; 300 | } 301 | alpm_option_remove_ignorepkg(handle, str); 302 | Py_RETURN_NONE; 303 | } 304 | 305 | PyObject* option_add_ignoregrp_alpm(PyObject *self, PyObject *args) 306 | { 307 | alpm_handle_t *handle = ALPM_HANDLE(self); 308 | const char *str; 309 | 310 | if(!PyArg_ParseTuple(args, "s", &str)) { 311 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 312 | return NULL; 313 | } 314 | alpm_option_add_ignoregroup(handle, str); 315 | Py_RETURN_NONE; 316 | } 317 | 318 | PyObject* option_remove_ignoregrp_alpm(PyObject *self, PyObject *args) 319 | { 320 | alpm_handle_t *handle = ALPM_HANDLE(self); 321 | const char *str; 322 | 323 | if(!PyArg_ParseTuple(args, "s", &str)) { 324 | PyErr_SetString(PyExc_TypeError, "expecting a string argument"); 325 | return NULL; 326 | } 327 | alpm_option_remove_ignoregroup(handle, str); 328 | Py_RETURN_NONE; 329 | } 330 | 331 | /** Callback wrappers */ 332 | extern PyObject *global_py_callbacks[N_CALLBACKS]; 333 | 334 | void pyalpm_logcb(alpm_loglevel_t level, const char *fmt, va_list va_args) { 335 | char *log; 336 | PyObject *result; 337 | int ret; 338 | 339 | ret = vasprintf(&log, fmt, va_args); 340 | if(ret == -1) 341 | log = "pyalpm_logcb: could not allocate memory"; 342 | result = PyObject_CallFunction(global_py_callbacks[CB_LOG], "is", level, log); 343 | if (!result) PyErr_Print(); 344 | Py_CLEAR(result); 345 | if (ret != -1) free(log); 346 | } 347 | 348 | void pyalpm_dlcb(const char *filename, off_t xfered, off_t total) { 349 | PyObject *result; 350 | result = PyObject_CallFunction(global_py_callbacks[CB_DOWNLOAD], "sii", filename, xfered, total); 351 | if (!result) PyErr_Print(); 352 | Py_CLEAR(result); 353 | } 354 | 355 | void pyalpm_totaldlcb(off_t total) { 356 | PyObject *result; 357 | result = PyObject_CallFunction(global_py_callbacks[CB_TOTALDL], "i", total); 358 | if (!result) PyErr_Print(); 359 | Py_CLEAR(result); 360 | } 361 | 362 | int pyalpm_fetchcb(const char *url, const char *localpath, int force) { 363 | PyObject *result; 364 | result = PyObject_CallFunction(global_py_callbacks[CB_FETCH], "ssi", url, localpath, force); 365 | if (!result) return -1; 366 | if (!PyLong_Check(result)) { 367 | return -1; 368 | } else { 369 | int ret = PyLong_to_int(result, -1); 370 | Py_DECREF(result); 371 | return ret; 372 | } 373 | } 374 | 375 | /* vim: set ts=2 sw=2 et: */ 376 | -------------------------------------------------------------------------------- /src/package.c: -------------------------------------------------------------------------------- 1 | /** 2 | * package.c : wrapper class around alpm_pkg_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "db.h" 28 | #include "util.h" 29 | #include "handle.h" 30 | #include "package.h" 31 | 32 | PyTypeObject AlpmPackageType; 33 | extern PyTypeObject AlpmHandleType; 34 | 35 | int PyAlpmPkg_Check(PyObject *object) { 36 | return PyObject_TypeCheck(object, &AlpmPackageType); 37 | } 38 | 39 | static PyObject* pyalpm_pkg_repr(PyObject *rawself) { 40 | AlpmPackage *self = (AlpmPackage *)rawself; 41 | return PyUnicode_FromFormat("", 42 | alpm_pkg_get_name(self->c_data), 43 | alpm_pkg_get_version(self->c_data), 44 | alpm_pkg_get_arch(self->c_data), 45 | self); 46 | } 47 | 48 | static PyObject* pyalpm_pkg_str(PyObject *rawself) { 49 | AlpmPackage *self = (AlpmPackage *)rawself; 50 | return PyUnicode_FromFormat("alpm.Package(\"%s-%s-%s\")", 51 | alpm_pkg_get_name(self->c_data), 52 | alpm_pkg_get_version(self->c_data), 53 | alpm_pkg_get_arch(self->c_data)); 54 | } 55 | 56 | void pyalpm_pkg_unref(PyObject *object) { 57 | if (PyAlpmPkg_Check(object)) 58 | ((AlpmPackage*)(object))->needs_free = 0; 59 | } 60 | 61 | static void pyalpm_package_dealloc(AlpmPackage *self) { 62 | if (self->needs_free) 63 | alpm_pkg_free(self->c_data); 64 | Py_TYPE(self)->tp_free((PyObject*)self); 65 | } 66 | 67 | /* Internal utility functions */ 68 | 69 | static PyObject* _pyobject_from_pmdepend(void* dep) { 70 | char *depstring = alpm_dep_compute_string((alpm_depend_t*)dep); 71 | PyObject *item = Py_BuildValue("s", depstring); 72 | free(depstring); 73 | return item; 74 | }; 75 | 76 | PyObject *pyalpm_package_from_pmpkg(void* data) { 77 | AlpmPackage *self; 78 | alpm_pkg_t *p = (alpm_pkg_t*)data; 79 | self = (AlpmPackage*)AlpmPackageType.tp_alloc(&AlpmPackageType, 0); 80 | if (self == NULL) { 81 | PyErr_SetString(PyExc_RuntimeError, "unable to create package object"); 82 | return NULL; 83 | } 84 | 85 | self->c_data = p; 86 | self->needs_free = 0; 87 | return (PyObject *)self; 88 | } 89 | 90 | #define CHECK_IF_INITIALIZED() if (! self->c_data) { \ 91 | PyErr_SetString(alpm_error, "data is not initialized"); \ 92 | return NULL; \ 93 | } 94 | 95 | static PyObject* _get_string_attribute(AlpmPackage *self, const char* getter(alpm_pkg_t*)) { 96 | const char *attr; 97 | if (! self->c_data) { 98 | PyErr_SetString(alpm_error, "data is not initialized"); 99 | return NULL; 100 | } 101 | attr = getter(self->c_data); 102 | if (attr == NULL) Py_RETURN_NONE; 103 | return Py_BuildValue("s", attr); 104 | } 105 | 106 | struct list_getter { 107 | alpm_list_t *(*getter)(alpm_pkg_t *); 108 | PyObject *(*item_converter)(void *); 109 | }; 110 | 111 | /** Returns a Python list attribute */ 112 | static PyObject* _get_list_attribute(AlpmPackage *self, const struct list_getter *closure) { 113 | alpm_list_t *result = NULL; 114 | CHECK_IF_INITIALIZED(); 115 | result = closure->getter(self->c_data); 116 | return alpmlist_to_pylist(result, closure->item_converter); 117 | } 118 | 119 | alpm_pkg_t *pmpkg_from_pyalpm_pkg(PyObject *object) { 120 | AlpmPackage *self = (AlpmPackage*)object; 121 | CHECK_IF_INITIALIZED(); 122 | return self->c_data; 123 | } 124 | 125 | /** Converts a Python list of packages to an alpm_list_t linked list. 126 | * return 0 on success, -1 on failure 127 | */ 128 | int pylist_pkg_to_alpmlist(PyObject *list, alpm_list_t **result) { 129 | alpm_list_t *ret = NULL; 130 | PyObject *iterator = PyObject_GetIter(list); 131 | PyObject *item; 132 | 133 | if(iterator == NULL) { 134 | PyErr_SetString(PyExc_TypeError, "object is not iterable"); 135 | return -1; 136 | } 137 | 138 | while((item = PyIter_Next(iterator))) 139 | { 140 | if (PyObject_TypeCheck(item, &AlpmPackageType)) { 141 | ret = alpm_list_add(ret, ((AlpmPackage*)item)->c_data); 142 | } else { 143 | PyErr_SetString(PyExc_TypeError, "list must contain only Package objects"); 144 | FREELIST(ret); 145 | return -1; 146 | } 147 | Py_DECREF(item); 148 | } 149 | Py_DECREF(iterator); 150 | 151 | *result = ret; 152 | return 0; 153 | } 154 | 155 | /* Python bindings */ 156 | 157 | PyObject *pyalpm_package_load(PyObject *self, PyObject *args, PyObject *kwargs) { 158 | alpm_handle_t *handle = ALPM_HANDLE(self); 159 | char *filename; 160 | int check_sig = ALPM_SIG_PACKAGE_OPTIONAL; 161 | char *kws[] = { "path", "check_sig", NULL }; 162 | alpm_pkg_t *result; 163 | AlpmPackage *pyresult; 164 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|i:load_pkg", kws, &filename, &check_sig)) { 165 | return NULL; 166 | } 167 | 168 | if ((alpm_pkg_load(handle, filename, 1, check_sig, &result) == -1) || !result) { 169 | RET_ERR("loading package failed", alpm_errno(handle), NULL); 170 | } 171 | 172 | pyresult = (AlpmPackage*)pyalpm_package_from_pmpkg(result); 173 | if (!pyresult) return NULL; 174 | pyresult->needs_free = 1; 175 | return (PyObject*)pyresult; 176 | } 177 | 178 | static PyObject* pyalpm_package_get_builddate(AlpmPackage *self, void *closure) { 179 | CHECK_IF_INITIALIZED(); 180 | return PyLong_FromLongLong(alpm_pkg_get_builddate(self->c_data)); 181 | } 182 | 183 | static PyObject* pyalpm_package_get_installdate(AlpmPackage *self, void *closure) { 184 | CHECK_IF_INITIALIZED(); 185 | return PyLong_FromLongLong(alpm_pkg_get_installdate(self->c_data)); 186 | } 187 | 188 | static PyObject* pyalpm_package_get_size(AlpmPackage *self, void *closure) { 189 | CHECK_IF_INITIALIZED(); 190 | return PyLong_FromLongLong(alpm_pkg_get_size(self->c_data)); 191 | } 192 | 193 | static PyObject* pyalpm_package_get_isize(AlpmPackage *self, void *closure) { 194 | CHECK_IF_INITIALIZED(); 195 | return PyLong_FromLongLong(alpm_pkg_get_isize(self->c_data)); 196 | } 197 | 198 | static PyObject* pyalpm_package_get_reason(AlpmPackage *self, void *closure) { 199 | CHECK_IF_INITIALIZED(); 200 | return PyLong_FromLong(alpm_pkg_get_reason(self->c_data)); 201 | } 202 | 203 | static PyObject* pyalpm_package_get_files(AlpmPackage *self, void *closure) { 204 | const alpm_filelist_t *flist = NULL; 205 | PyObject *result = NULL; 206 | 207 | CHECK_IF_INITIALIZED(); 208 | 209 | flist = alpm_pkg_get_files(self->c_data); 210 | if (!flist) 211 | Py_RETURN_NONE; 212 | else { 213 | ssize_t i; 214 | result = PyList_New((Py_ssize_t)flist->count); 215 | for (i = 0; i < (ssize_t)flist->count; i++) { 216 | const alpm_file_t *file = flist->files + i; 217 | PyObject *filename = PyUnicode_DecodeFSDefault(file->name); 218 | PyObject *filesize = PyLong_FromLongLong(file->size); 219 | PyObject *filemode = PyLong_FromUnsignedLong(file->mode); 220 | PyObject *item = PyTuple_New(3); 221 | if (item && filename && filesize && filemode) { 222 | PyTuple_SET_ITEM(item, 0, filename); 223 | PyTuple_SET_ITEM(item, 1, filesize); 224 | PyTuple_SET_ITEM(item, 2, filemode); 225 | PyList_SET_ITEM(result, i, item); 226 | } else { 227 | Py_CLEAR(item); 228 | Py_CLEAR(filename); 229 | Py_CLEAR(filesize); 230 | Py_CLEAR(filemode); 231 | return NULL; 232 | } 233 | } 234 | } 235 | return result; 236 | } 237 | 238 | /** Convert alpm_backup_t to Python tuples 239 | * The resulting tuple is (filename, hexadecimal hash) 240 | */ 241 | static PyObject* pyobject_from_alpm_backup(void* data) { 242 | alpm_backup_t* item = (alpm_backup_t*)data; 243 | PyObject* tuple = Py_BuildValue("(ss)", item->name, item->hash); 244 | return tuple; 245 | } 246 | 247 | static PyObject* pyalpm_package_get_db(AlpmPackage *self, void *closure) { 248 | alpm_db_t* db; 249 | CHECK_IF_INITIALIZED(); 250 | db = alpm_pkg_get_db(self->c_data); 251 | if (db) 252 | return pyalpm_db_from_pmdb(db); 253 | else 254 | Py_RETURN_NONE; 255 | } 256 | 257 | static PyObject* pyalpm_pkg_has_scriptlet(AlpmPackage *self, void *closure) { 258 | CHECK_IF_INITIALIZED(); 259 | return PyBool_FromLong(alpm_pkg_has_scriptlet(self->c_data)); 260 | } 261 | 262 | static PyObject* pyalpm_pkg_download_size(AlpmPackage *self, void *closure) { 263 | CHECK_IF_INITIALIZED(); 264 | return PyLong_FromLongLong(alpm_pkg_download_size(self->c_data)); 265 | } 266 | 267 | static PyObject* pyalpm_pkg_compute_requiredby(PyObject *rawself, PyObject *args) { 268 | AlpmPackage *self = (AlpmPackage*)rawself; 269 | PyObject *pyresult; 270 | CHECK_IF_INITIALIZED(); 271 | { 272 | alpm_list_t *result = alpm_pkg_compute_requiredby(self->c_data); 273 | pyresult = alpmlist_to_pylist(result, pyobject_from_string); 274 | FREELIST(result); 275 | } 276 | return pyresult; 277 | } 278 | 279 | struct list_getter get_licenses = { alpm_pkg_get_licenses, pyobject_from_string }; 280 | struct list_getter get_groups = { alpm_pkg_get_groups, pyobject_from_string }; 281 | struct list_getter get_backup = { alpm_pkg_get_backup, pyobject_from_alpm_backup }; 282 | struct list_getter get_deltas = { alpm_pkg_get_deltas, pyobject_from_string }; 283 | struct list_getter get_depends = { alpm_pkg_get_depends, _pyobject_from_pmdepend }; 284 | struct list_getter get_optdepends = { alpm_pkg_get_optdepends, _pyobject_from_pmdepend }; 285 | struct list_getter get_replaces = { alpm_pkg_get_replaces, _pyobject_from_pmdepend }; 286 | struct list_getter get_provides = { alpm_pkg_get_provides, _pyobject_from_pmdepend }; 287 | struct list_getter get_conflicts = { alpm_pkg_get_conflicts, _pyobject_from_pmdepend }; 288 | 289 | static struct PyGetSetDef AlpmPackageGetSet[] = { 290 | { "db", (getter)pyalpm_package_get_db, 0, "the database from which the package comes from, or None", NULL } , 291 | /* description properties */ 292 | { "name", (getter)_get_string_attribute, 0, "package name", alpm_pkg_get_name } , 293 | { "version", (getter)_get_string_attribute, 0, "package version", alpm_pkg_get_version } , 294 | { "desc", (getter)_get_string_attribute, 0, "package desc", alpm_pkg_get_desc } , 295 | { "url", (getter)_get_string_attribute, 0, "package URL", alpm_pkg_get_url } , 296 | { "arch", (getter)_get_string_attribute, 0, "target architecture", alpm_pkg_get_arch } , 297 | { "licenses", (getter)_get_list_attribute, 0, "list of licenses", &get_licenses } , 298 | { "groups", (getter)_get_list_attribute, 0, "list of package groups", &get_groups } , 299 | /* package properties */ 300 | { "packager", (getter)_get_string_attribute, 0, "packager name", alpm_pkg_get_packager } , 301 | { "md5sum", (getter)_get_string_attribute, 0, "package md5sum", alpm_pkg_get_md5sum } , 302 | { "sha256sum", (getter)_get_string_attribute, 0, "package sha256sum as hexadecimal digits", alpm_pkg_get_sha256sum } , 303 | { "base64_sig", (getter)_get_string_attribute, 0, "GPG signature encoded as base64", alpm_pkg_get_base64_sig } , 304 | { "filename", (getter)_get_string_attribute, 0, "package filename", alpm_pkg_get_filename } , 305 | { "base", (getter)_get_string_attribute, 0, "package base name", alpm_pkg_get_base }, 306 | { "size", (getter)pyalpm_package_get_size, 0, "package size", NULL } , 307 | { "isize", (getter)pyalpm_package_get_isize, 0, "installed size", NULL } , 308 | { "reason", (getter)pyalpm_package_get_reason, 0, "install reason (0 = explicit, 1 = depend)", NULL } , 309 | { "builddate", (getter)pyalpm_package_get_builddate, 0, "building time", NULL } , 310 | { "installdate", (getter)pyalpm_package_get_installdate, 0, "install time", NULL } , 311 | { "files", (getter)pyalpm_package_get_files, 0, "list of installed files", NULL } , 312 | { "backup", (getter)_get_list_attribute, 0, "list of tuples (filename, md5sum)", &get_backup } , 313 | { "deltas", (getter)_get_list_attribute, 0, "list of available deltas", &get_deltas } , 314 | /* dependency information */ 315 | { "depends", (getter)_get_list_attribute, 0, "list of dependencies", &get_depends } , 316 | { "optdepends", (getter)_get_list_attribute, 0, "list of optional dependencies", &get_optdepends } , 317 | { "conflicts", (getter)_get_list_attribute, 0, "list of conflicts", &get_conflicts } , 318 | { "provides", (getter)_get_list_attribute, 0, "list of provided package names", &get_provides } , 319 | { "replaces", (getter)_get_list_attribute, 0, "list of replaced packages", &get_replaces } , 320 | /* miscellaneous information */ 321 | { "has_scriptlet", (getter)pyalpm_pkg_has_scriptlet, 0, "True if the package has an install script", NULL }, 322 | { "download_size", (getter)pyalpm_pkg_download_size, 0, "predicted download size for this package", NULL }, 323 | { NULL } 324 | }; 325 | 326 | static struct PyMethodDef pyalpm_pkg_methods[] = { 327 | { "compute_requiredby", pyalpm_pkg_compute_requiredby, METH_NOARGS, 328 | "computes the list of packages requiring this package" }, 329 | { NULL } 330 | }; 331 | 332 | PyTypeObject AlpmPackageType = { 333 | PyVarObject_HEAD_INIT(NULL, 0) 334 | "alpm.Package", /*tp_name*/ 335 | sizeof(AlpmPackage), /*tp_basicsize*/ 336 | 0, /*tp_itemsize*/ 337 | .tp_dealloc = (destructor)pyalpm_package_dealloc, 338 | .tp_repr = pyalpm_pkg_repr, 339 | .tp_str = pyalpm_pkg_str, 340 | .tp_flags = Py_TPFLAGS_DEFAULT, 341 | .tp_doc = "Package object", 342 | .tp_methods = pyalpm_pkg_methods, 343 | .tp_getset = AlpmPackageGetSet, 344 | }; 345 | 346 | /** Initializes Package class in module */ 347 | void init_pyalpm_package(PyObject *module) { 348 | PyObject *type; 349 | 350 | if (PyType_Ready(&AlpmPackageType) < 0) 351 | return; 352 | type = (PyObject*)&AlpmPackageType; 353 | Py_INCREF(type); 354 | PyModule_AddObject(module, "Package", type); 355 | 356 | /* package reasons */ 357 | PyModule_AddIntConstant(module, "PKG_REASON_EXPLICIT", ALPM_PKG_REASON_EXPLICIT); 358 | PyModule_AddIntConstant(module, "PKG_REASON_DEPEND", ALPM_PKG_REASON_DEPEND); 359 | 360 | /* signature check levels */ 361 | PyModule_AddIntConstant(module, "SIG_PACKAGE", ALPM_SIG_PACKAGE); 362 | PyModule_AddIntConstant(module, "SIG_PACKAGE_OPTIONAL", ALPM_SIG_PACKAGE_OPTIONAL); 363 | PyModule_AddIntConstant(module, "SIG_PACKAGE_MARGINAL_OK", ALPM_SIG_PACKAGE_MARGINAL_OK); 364 | PyModule_AddIntConstant(module, "SIG_PACKAGE_UNKNOWN_OK", ALPM_SIG_PACKAGE_UNKNOWN_OK); 365 | } 366 | 367 | /* vim: set ts=2 sw=2 et: */ 368 | -------------------------------------------------------------------------------- /src/handle.c: -------------------------------------------------------------------------------- 1 | /** 2 | * handle.c : wrapper class around alpm_handle_t 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "handle.h" 28 | #include "package.h" 29 | #include "db.h" 30 | #include "options.h" 31 | #include "util.h" 32 | 33 | PyTypeObject AlpmHandleType; 34 | 35 | static PyObject *pyalpm_handle_from_pmhandle(void* data) { 36 | alpm_handle_t *handle = (alpm_handle_t*)data; 37 | AlpmHandle *self; 38 | self = (AlpmHandle*)AlpmHandleType.tp_alloc(&AlpmHandleType, 0); 39 | if (self == NULL) { 40 | PyErr_SetString(PyExc_RuntimeError, "unable to create pyalpm.Handle object"); 41 | return NULL; 42 | } 43 | 44 | self->c_data = handle; 45 | /* memset(self->py_callbacks, 0, N_CALLBACKS * sizeof(PyObject*)); */ 46 | return (PyObject *)self; 47 | } 48 | 49 | /*pyalpm functions*/ 50 | PyObject* pyalpm_initialize(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) 51 | { 52 | const char *root; 53 | const char *dbpath; 54 | alpm_handle_t *h; 55 | enum _alpm_errno_t errcode = 0; 56 | if(!PyArg_ParseTuple(args, "ss", &root, &dbpath)) { 57 | return NULL; 58 | } 59 | 60 | h = alpm_initialize(root, dbpath, &errcode); 61 | if (h) { 62 | return pyalpm_handle_from_pmhandle((void*)h); 63 | } else { 64 | RET_ERR("could not create a libalpm handle", errcode, NULL); 65 | } 66 | } 67 | 68 | PyObject* pyalpm_release(PyObject *self, PyObject *args) 69 | { 70 | AlpmHandle *pyhandle; 71 | if(!PyArg_ParseTuple(args, "O!", &AlpmHandleType, &pyhandle)) 72 | return NULL; 73 | 74 | alpm_release(pyhandle->c_data); 75 | pyhandle->c_data = NULL; 76 | Py_RETURN_NONE; 77 | } 78 | 79 | /* Database getters/setters */ 80 | 81 | static PyObject* pyalpm_get_localdb(PyObject *self, PyObject *dummy) { 82 | alpm_handle_t *handle = ALPM_HANDLE(self); 83 | return pyalpm_db_from_pmdb(alpm_get_localdb(handle)); 84 | } 85 | 86 | static PyObject* pyalpm_get_syncdbs(PyObject *self, PyObject *dummy) { 87 | alpm_handle_t *handle = ALPM_HANDLE(self); 88 | return alpmlist_to_pylist(alpm_get_syncdbs(handle), 89 | pyalpm_db_from_pmdb); 90 | } 91 | 92 | static PyObject* pyalpm_register_syncdb(PyObject *self, PyObject *args) { 93 | alpm_handle_t *handle = ALPM_HANDLE(self); 94 | const char *dbname; 95 | alpm_db_t *result; 96 | int pgp_level; 97 | 98 | if (!PyArg_ParseTuple(args, "si", &dbname, &pgp_level)) { 99 | PyErr_Format(PyExc_TypeError, "%s() takes a string and an integer", __func__); 100 | return NULL; 101 | } 102 | 103 | result = alpm_register_syncdb(handle, dbname, pgp_level); 104 | if (! result) { 105 | PyErr_Format(alpm_error, "unable to register sync database %s", dbname); 106 | return NULL; 107 | } 108 | 109 | return pyalpm_db_from_pmdb(result); 110 | } 111 | 112 | static PyObject* pyalpm_set_pkgreason(PyObject* self, PyObject* args) { 113 | alpm_handle_t *handle = ALPM_HANDLE(self); 114 | alpm_pkg_t *pmpkg = NULL; 115 | PyObject *pkg = NULL; 116 | alpm_pkgreason_t reason; 117 | int ret; 118 | if (!PyArg_ParseTuple(args, "O!i:set_pkgreason", &AlpmPackageType, &pkg, &reason)) { 119 | return NULL; 120 | } 121 | pmpkg = ALPM_PACKAGE(pkg); 122 | ret = alpm_pkg_set_reason(pmpkg, reason); 123 | 124 | if (ret == -1) RET_ERR("failed setting install reason", alpm_errno(handle), NULL); 125 | Py_RETURN_NONE; 126 | } 127 | 128 | /* String attributes get/setters */ 129 | struct _alpm_str_getset { 130 | const char *(*getter)(alpm_handle_t *); 131 | int (*setter)(alpm_handle_t *, const char *); 132 | }; 133 | 134 | static PyObject *_get_string_attr(PyObject *self, const struct _alpm_str_getset *closure) { 135 | alpm_handle_t *handle = ALPM_HANDLE(self); 136 | const char *str = closure->getter(handle); 137 | if(str == NULL) 138 | RET_ERR("failed getting option value", alpm_errno(handle), NULL); 139 | return Py_BuildValue("s", str); 140 | } 141 | 142 | static int _set_string_attr(PyObject *self, PyObject *value, const struct _alpm_str_getset *closure) { 143 | alpm_handle_t *handle = ALPM_HANDLE(self); 144 | char *path = NULL; 145 | int ret; 146 | if (PyBytes_Check(value)) { 147 | path = strdup(PyBytes_AS_STRING(value)); 148 | } else if (PyUnicode_Check(value)) { 149 | PyObject* utf8 = PyUnicode_AsUTF8String(value); 150 | path = strdup(PyBytes_AS_STRING(utf8)); 151 | Py_DECREF(utf8); 152 | } else { 153 | PyErr_SetString(PyExc_TypeError, "logfile path must be a string"); 154 | return -1; 155 | } 156 | 157 | ret = closure->setter(handle, path); 158 | free(path); 159 | if (ret == -1) RET_ERR("failed setting option value", alpm_errno(handle), -1); 160 | return 0; 161 | } 162 | 163 | static struct _alpm_str_getset root_getset = { alpm_option_get_root, NULL }; 164 | static struct _alpm_str_getset dbpath_getset = { alpm_option_get_dbpath, NULL }; 165 | static struct _alpm_str_getset lockfile_getset = { alpm_option_get_lockfile, NULL }; 166 | static struct _alpm_str_getset logfile_getset = { alpm_option_get_logfile, alpm_option_set_logfile }; 167 | static struct _alpm_str_getset gpgdir_getset = { alpm_option_get_gpgdir, alpm_option_set_gpgdir }; 168 | static struct _alpm_str_getset arch_getset = { alpm_option_get_arch, alpm_option_set_arch }; 169 | 170 | /* Callback attributes get/setters */ 171 | typedef int (*alpm_cb_setter)(alpm_handle_t*, void*); 172 | struct _alpm_cb_getset { 173 | alpm_cb_setter setter; 174 | void *cb_wrapper; 175 | pyalpm_callback_id id; 176 | }; 177 | 178 | void pyalpm_eventcb(alpm_event_t event, void* data1, void *data2); 179 | void pyalpm_questioncb(alpm_question_t question, 180 | void* data1, void *data2, void* data3, int* retcode); 181 | void pyalpm_progresscb(alpm_progress_t op, 182 | const char* target_name, int percentage, size_t n_targets, size_t cur_target); 183 | 184 | static struct _alpm_cb_getset cb_getsets[N_CALLBACKS] = { 185 | { (alpm_cb_setter)alpm_option_set_logcb, pyalpm_logcb, CB_LOG }, 186 | { (alpm_cb_setter)alpm_option_set_dlcb, pyalpm_dlcb, CB_DOWNLOAD }, 187 | { (alpm_cb_setter)alpm_option_set_fetchcb, pyalpm_fetchcb, CB_FETCH }, 188 | { (alpm_cb_setter)alpm_option_set_totaldlcb, pyalpm_totaldlcb, CB_TOTALDL }, 189 | { (alpm_cb_setter)alpm_option_set_eventcb, pyalpm_eventcb, CB_EVENT }, 190 | { (alpm_cb_setter)alpm_option_set_questioncb, pyalpm_questioncb, CB_QUESTION }, 191 | { (alpm_cb_setter)alpm_option_set_progresscb, pyalpm_progresscb, CB_PROGRESS }, 192 | }; 193 | 194 | /** Callback options 195 | * We use Python callable objects as callbacks: they are 196 | * stored in static variables, and the reference count is 197 | * increased accordingly. 198 | * 199 | * These Python functions are wrapped into C functions 200 | * that are passed to libalpm. 201 | */ 202 | PyObject *global_py_callbacks[N_CALLBACKS]; 203 | 204 | static PyObject* _get_cb_attr(PyObject *self, const struct _alpm_cb_getset *closure) { 205 | /* AlpmHandle *it = self; */ 206 | PyObject *pycb = global_py_callbacks[closure->id]; 207 | if (pycb == NULL) Py_RETURN_NONE; 208 | Py_INCREF(pycb); 209 | return pycb; 210 | } 211 | 212 | static int _set_cb_attr(PyObject *self, PyObject *value, const struct _alpm_cb_getset *closure) { 213 | AlpmHandle *it = (AlpmHandle *)self; 214 | if (value == Py_None) { 215 | Py_CLEAR(global_py_callbacks[closure->id]); 216 | closure->setter(it->c_data, NULL); 217 | } else if (PyCallable_Check(value)) { 218 | Py_CLEAR(global_py_callbacks[closure->id]); 219 | Py_INCREF(value); 220 | global_py_callbacks[closure->id] = value; 221 | closure->setter(it->c_data, closure->cb_wrapper); 222 | } else { 223 | PyErr_SetString(PyExc_TypeError, "value must be None or a function"); 224 | return -1; 225 | } 226 | 227 | return 0; 228 | } 229 | 230 | struct PyGetSetDef pyalpm_handle_getset[] = { 231 | /** filepaths */ 232 | { "root", 233 | (getter)_get_string_attr, 234 | NULL, 235 | "system root directory", &root_getset } , 236 | { "dbpath", 237 | (getter)_get_string_attr, 238 | NULL, 239 | "alpm database directory", &dbpath_getset } , 240 | { "logfile", 241 | (getter)_get_string_attr, 242 | (setter)_set_string_attr, 243 | "alpm logfile path", &logfile_getset } , 244 | { "lockfile", 245 | (getter)_get_string_attr, 246 | NULL, 247 | "alpm lockfile path", &lockfile_getset } , 248 | { "gpgdir", 249 | (getter)_get_string_attr, 250 | (setter)_set_string_attr, 251 | "alpm GnuPG home directory", &gpgdir_getset } , 252 | 253 | /** strings */ 254 | { "arch", 255 | (getter)_get_string_attr, 256 | (setter)_set_string_attr, 257 | "Target archichecture", &arch_getset } , 258 | 259 | /** booleans */ 260 | { "usesyslog", 261 | (getter)option_get_usesyslog_alpm, 262 | (setter)option_set_usesyslog_alpm, 263 | "use syslog (an integer, 0 = False, 1 = True)", NULL } , 264 | { "deltaratio", 265 | (getter)option_get_deltaratio_alpm, 266 | (setter)option_set_deltaratio_alpm, 267 | "set deltaratio (a float). Deltas are enabled if this is nonzero.", NULL } , 268 | { "checkspace", 269 | (getter)option_get_checkspace_alpm, 270 | (setter)option_set_checkspace_alpm, 271 | "check disk space before transactions (an integer, 0 = False, 1 = True)", NULL } , 272 | 273 | /** lists */ 274 | { "cachedirs", 275 | (getter)option_get_cachedirs_alpm, 276 | (setter)option_set_cachedirs_alpm, 277 | "list of package cache directories", NULL }, 278 | { "noupgrades", 279 | (getter)option_get_noupgrades_alpm, 280 | (setter)option_set_noupgrades_alpm, 281 | "list of ...", NULL }, 282 | { "noextracts", 283 | (getter)option_get_noextracts_alpm, 284 | (setter)option_set_noextracts_alpm, 285 | "list of ...", NULL }, 286 | { "ignorepkgs", 287 | (getter)option_get_ignorepkgs_alpm, 288 | (setter)option_set_ignorepkgs_alpm, 289 | "list of ignored packages", NULL }, 290 | { "ignoregrps", 291 | (getter)option_get_ignoregrps_alpm, 292 | (setter)option_set_ignoregrps_alpm, 293 | "list of ignored groups", NULL }, 294 | 295 | /** callbacks */ 296 | { "logcb", 297 | (getter)_get_cb_attr, (setter)_set_cb_attr, 298 | "logging callback, with arguments (loglevel, format string, tuple)", 299 | &cb_getsets[CB_LOG] }, 300 | { "dlcb", 301 | (getter)_get_cb_attr, (setter)_set_cb_attr, 302 | "download status callback (a function)\n" 303 | "args: filename :: str\n" 304 | " transferred :: int\n" 305 | " total :: int\n", 306 | &cb_getsets[CB_DOWNLOAD] }, 307 | { "totaldlcb", 308 | (getter)_get_cb_attr, (setter)_set_cb_attr, 309 | "total download size callback: totaldlcb(total_size)", 310 | &cb_getsets[CB_TOTALDL] }, 311 | { "fetchcb", 312 | (getter)_get_cb_attr, (setter)_set_cb_attr, 313 | "download function\n" 314 | "args: url :: string\n" 315 | " destination path :: string\n" 316 | " overwrite :: bool\n" 317 | "returns: 0 on success, 1 if file exists, -1 on error", 318 | &cb_getsets[CB_FETCH] }, 319 | { "eventcb", 320 | (getter)_get_cb_attr, (setter)_set_cb_attr, 321 | " a function called when an event occurs\n" 322 | " -- args: (event ID, event string, (object 1, object 2))\n", 323 | &cb_getsets[CB_EVENT] }, 324 | { "questioncb", 325 | (getter)_get_cb_attr, (setter)_set_cb_attr, 326 | " a function called to get user input\n", 327 | &cb_getsets[CB_QUESTION] }, 328 | { "progresscb", 329 | (getter)_get_cb_attr, (setter)_set_cb_attr, 330 | " -- a function called to indicate progress\n" 331 | " -- args: (target name, percentage, number of targets, target number)\n", 332 | &cb_getsets[CB_PROGRESS] }, 333 | 334 | /** terminator */ 335 | { NULL } 336 | }; 337 | 338 | static PyMethodDef pyalpm_handle_methods[] = { 339 | /* Transaction initialization */ 340 | {"init_transaction", (PyCFunction)pyalpm_trans_init, METH_VARARGS | METH_KEYWORDS, 341 | "Initializes a transaction.\n" 342 | "Arguments:\n" 343 | " nodeps, force, nosave, nodepversion, cascade, recurse,\n" 344 | " dbonly, alldeps, downloadonly, noscriptlet, noconflicts,\n" 345 | " needed, allexplicit, inneeded, recurseall, nolock\n" 346 | " -- the transaction options (booleans)\n" 347 | }, 348 | 349 | /* Package load */ 350 | {"load_pkg", (PyCFunction)pyalpm_package_load, METH_VARARGS | METH_KEYWORDS, 351 | "loads package information from a tarball"}, 352 | 353 | /* Database members */ 354 | {"register_syncdb", pyalpm_register_syncdb, METH_VARARGS, 355 | "registers the database with the given name\n" 356 | "returns the new database on success"}, 357 | {"get_localdb", pyalpm_get_localdb, METH_NOARGS, "returns an object representing the local DB"}, 358 | {"get_syncdbs", pyalpm_get_syncdbs, METH_NOARGS, "returns a list of sync DBs"}, 359 | {"set_pkgreason", pyalpm_set_pkgreason, METH_VARARGS, 360 | "set install reason for a package (PKG_REASON_DEPEND, PKG_REASON_EXPLICIT)\n"}, 361 | 362 | /* Option modifiers */ 363 | {"add_noupgrade", option_add_noupgrade_alpm, METH_VARARGS, "add a noupgrade package."}, 364 | {"remove_noupgrade", option_remove_noupgrade_alpm, METH_VARARGS, "removes a noupgrade package."}, 365 | 366 | {"add_cachedir", option_add_cachedir_alpm, METH_VARARGS, "adds a cachedir."}, 367 | {"remove_cachedir", option_remove_cachedir_alpm, METH_VARARGS, "removes a cachedir."}, 368 | 369 | {"add_noextract", option_add_noextract_alpm, METH_VARARGS, "add a noextract package."}, 370 | {"remove_noextract", option_remove_noextract_alpm, METH_VARARGS, "remove a noextract package."}, 371 | 372 | {"add_ignorepkg", option_add_ignorepkg_alpm, METH_VARARGS, "add an ignorepkg."}, 373 | {"remove_ignorepkg", option_remove_ignorepkg_alpm, METH_VARARGS, "remove an ignorepkg."}, 374 | 375 | {"add_ignoregrp", option_add_ignoregrp_alpm, METH_VARARGS, "add an ignoregrp."}, 376 | {"remove_ignoregrp", option_remove_ignoregrp_alpm, METH_VARARGS, "remove an ignoregrp."}, 377 | {NULL, NULL, 0, NULL}, 378 | }; 379 | 380 | static void pyalpm_dealloc(PyObject* self) { 381 | alpm_handle_t *handle = ALPM_HANDLE(self); 382 | int ret = alpm_release(handle); 383 | if (ret == -1) { 384 | PyErr_Format(alpm_error, "unable to release alpm handle"); 385 | } 386 | handle = NULL; 387 | Py_TYPE(self)->tp_free((PyObject *)self); 388 | } 389 | 390 | PyTypeObject AlpmHandleType = { 391 | PyVarObject_HEAD_INIT(NULL, 0) 392 | "alpm.Handle", /*tp_name*/ 393 | sizeof(AlpmHandle), /*tp_basicsize*/ 394 | 0, /*tp_itemsize*/ 395 | .tp_flags = Py_TPFLAGS_DEFAULT, 396 | .tp_doc = "An object wrapping a libalpm handle. Arguments: root path, DB path.", 397 | .tp_methods = pyalpm_handle_methods, 398 | .tp_getset = pyalpm_handle_getset, 399 | .tp_new = pyalpm_initialize, 400 | .tp_dealloc = (destructor) pyalpm_dealloc, 401 | }; 402 | 403 | /** Initializes Handle class in module */ 404 | int init_pyalpm_handle(PyObject *module) { 405 | PyObject *type; 406 | if (PyType_Ready(&AlpmHandleType) < 0) 407 | return -1; 408 | type = (PyObject*)&AlpmHandleType; 409 | Py_INCREF(type); 410 | PyModule_AddObject(module, "Handle", type); 411 | 412 | PyModule_AddIntConstant(module, "LOG_ERROR", ALPM_LOG_ERROR); 413 | PyModule_AddIntConstant(module, "LOG_WARNING", ALPM_LOG_WARNING); 414 | PyModule_AddIntConstant(module, "LOG_DEBUG", ALPM_LOG_DEBUG); 415 | PyModule_AddIntConstant(module, "LOG_FUNCTION", ALPM_LOG_FUNCTION); 416 | 417 | return 0; 418 | } 419 | 420 | /* vim: set ts=2 sw=2 et: */ 421 | -------------------------------------------------------------------------------- /src/transaction.c: -------------------------------------------------------------------------------- 1 | /** 2 | * transaction.c : wrapper class around libalpm transactions 3 | * 4 | * Copyright (c) 2011 Rémy Oudompheng 5 | * 6 | * This file is part of pyalpm. 7 | * 8 | * pyalpm is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * pyalpm is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with pyalpm. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "package.h" 28 | #include "handle.h" 29 | #include "util.h" 30 | 31 | /** Transaction callbacks */ 32 | extern PyObject *global_py_callbacks[N_CALLBACKS]; 33 | 34 | void pyalpm_eventcb(alpm_event_t *event) { 35 | const char *eventstr; 36 | switch(event->type) { 37 | case ALPM_EVENT_CHECKDEPS_START: 38 | eventstr = "Checking dependencies"; 39 | break; 40 | case ALPM_EVENT_CHECKDEPS_DONE: 41 | eventstr = "Done checking dependencies"; 42 | break; 43 | case ALPM_EVENT_FILECONFLICTS_START: 44 | eventstr = "Checking file conflicts"; 45 | break; 46 | case ALPM_EVENT_FILECONFLICTS_DONE: 47 | eventstr = "Done checking file conflicts"; 48 | break; 49 | case ALPM_EVENT_RESOLVEDEPS_START: 50 | eventstr = "Resolving dependencies"; 51 | break; 52 | case ALPM_EVENT_RESOLVEDEPS_DONE: 53 | eventstr = "Done resolving dependencies"; 54 | break; 55 | case ALPM_EVENT_INTERCONFLICTS_START: 56 | eventstr = "Checking inter conflicts"; 57 | break; 58 | case ALPM_EVENT_INTERCONFLICTS_DONE: 59 | eventstr = "Done checking inter conflicts"; 60 | break; 61 | case ALPM_EVENT_PACKAGE_OPERATION_START: 62 | eventstr = "Operating on a package"; 63 | switch(((alpm_event_package_operation_t*)event)->operation) { 64 | case ALPM_PACKAGE_INSTALL: 65 | eventstr = "Adding a package"; 66 | break; 67 | case ALPM_PACKAGE_UPGRADE: 68 | eventstr = "Upgrading a package"; 69 | break; 70 | case ALPM_PACKAGE_REINSTALL: 71 | eventstr = "Reinstalling a package"; 72 | break; 73 | case ALPM_PACKAGE_DOWNGRADE: 74 | eventstr = "Downgrading a package"; 75 | break; 76 | case ALPM_PACKAGE_REMOVE: 77 | eventstr = "Removing a package"; 78 | break; 79 | } 80 | break; 81 | case ALPM_EVENT_PACKAGE_OPERATION_DONE: 82 | eventstr = "Done operating on a package"; 83 | switch(((alpm_event_package_operation_t*)event)->operation) { 84 | case ALPM_PACKAGE_INSTALL: 85 | eventstr = "Done adding a package"; 86 | break; 87 | case ALPM_PACKAGE_UPGRADE: 88 | eventstr = "Done upgrading a package"; 89 | break; 90 | case ALPM_PACKAGE_REINSTALL: 91 | eventstr = "Done reinstalling a package"; 92 | break; 93 | case ALPM_PACKAGE_DOWNGRADE: 94 | eventstr = "Done downgrading a package"; 95 | break; 96 | case ALPM_PACKAGE_REMOVE: 97 | eventstr = "Done removing a package"; 98 | break; 99 | } 100 | break; 101 | case ALPM_EVENT_INTEGRITY_START: 102 | eventstr = "Checking integrity"; 103 | break; 104 | case ALPM_EVENT_INTEGRITY_DONE: 105 | eventstr = "Done checking integrity"; 106 | break; 107 | case ALPM_EVENT_LOAD_START: 108 | case ALPM_EVENT_LOAD_DONE: 109 | case ALPM_EVENT_DELTA_INTEGRITY_START: 110 | case ALPM_EVENT_DELTA_INTEGRITY_DONE: 111 | case ALPM_EVENT_DELTA_PATCHES_START: 112 | case ALPM_EVENT_DELTA_PATCHES_DONE: 113 | case ALPM_EVENT_DELTA_PATCH_START: 114 | /* info here */ 115 | case ALPM_EVENT_DELTA_PATCH_DONE: 116 | case ALPM_EVENT_DELTA_PATCH_FAILED: 117 | case ALPM_EVENT_SCRIPTLET_INFO: 118 | /* info here */ 119 | case ALPM_EVENT_RETRIEVE_START: 120 | case ALPM_EVENT_RETRIEVE_DONE: 121 | case ALPM_EVENT_RETRIEVE_FAILED: 122 | /* info here */ 123 | eventstr = "event not implemented"; 124 | break; 125 | case ALPM_EVENT_DISKSPACE_START: 126 | eventstr = "Checking disk space"; 127 | break; 128 | case ALPM_EVENT_DISKSPACE_DONE: 129 | eventstr = "Done checking disk space"; 130 | break; 131 | case ALPM_EVENT_OPTDEP_REMOVAL: 132 | case ALPM_EVENT_DATABASE_MISSING: 133 | eventstr = "event not implemented"; 134 | break; 135 | case ALPM_EVENT_KEYRING_START: 136 | eventstr = "Checking keys in keyring"; 137 | break; 138 | case ALPM_EVENT_KEYRING_DONE: 139 | eventstr = "Done checking keys in keyring"; 140 | break; 141 | case ALPM_EVENT_KEY_DOWNLOAD_START: 142 | case ALPM_EVENT_KEY_DOWNLOAD_DONE: 143 | case ALPM_EVENT_PACNEW_CREATED: 144 | case ALPM_EVENT_PACSAVE_CREATED: 145 | case ALPM_EVENT_HOOK_START: 146 | case ALPM_EVENT_HOOK_DONE: 147 | case ALPM_EVENT_HOOK_RUN_START: 148 | case ALPM_EVENT_HOOK_RUN_DONE: 149 | default: 150 | eventstr = "unknown event"; 151 | } 152 | { 153 | PyObject *result = NULL; 154 | if (global_py_callbacks[CB_PROGRESS]) { 155 | result = PyObject_CallFunction(global_py_callbacks[CB_EVENT], "is", event->type, eventstr); 156 | } else { 157 | PyErr_SetString(PyExc_RuntimeError, "event callback was called but it's not set!"); 158 | } 159 | if (PyErr_Occurred()) PyErr_Print(); 160 | Py_CLEAR(result); 161 | } 162 | } 163 | 164 | void pyalpm_questioncb(alpm_question_t question, 165 | void* data1, void *data2, void* data3, int* retcode) { 166 | } 167 | 168 | void pyalpm_progresscb(alpm_progress_t op, 169 | const char* target_name, int percentage, size_t n_targets, size_t cur_target) { 170 | PyObject *result = NULL; 171 | if (global_py_callbacks[CB_PROGRESS]) { 172 | result = PyObject_CallFunction(global_py_callbacks[CB_PROGRESS], "sinn", 173 | target_name, percentage, n_targets, cur_target); 174 | } else { 175 | PyErr_SetString(PyExc_RuntimeError, "progress callback was called but it's not set!"); 176 | } 177 | if (PyErr_Occurred()) { 178 | PyErr_Print(); 179 | /* alpm_trans_interrupt(handle); */ 180 | } 181 | Py_CLEAR(result); 182 | } 183 | 184 | /** Transaction info translation */ 185 | static PyObject* pyobject_from_pmdepmissing(void *item) { 186 | alpm_depmissing_t* miss = (alpm_depmissing_t*)item; 187 | char* needed = alpm_dep_compute_string(miss->depend); 188 | PyObject *result = Py_BuildValue("(sss)", 189 | miss->target, 190 | needed, 191 | miss->causingpkg); 192 | free(needed); 193 | return result; 194 | } 195 | 196 | static PyObject* pyobject_from_pmfileconflict(void *item) { 197 | alpm_fileconflict_t* conflict = (alpm_fileconflict_t*)item; 198 | const char *target = conflict->target; 199 | const char *filename = conflict->file; 200 | switch(conflict->type) { 201 | case ALPM_FILECONFLICT_TARGET: 202 | return Py_BuildValue("(sss)", target, filename, conflict->ctarget); 203 | case ALPM_FILECONFLICT_FILESYSTEM: 204 | return Py_BuildValue("(ssO)", target, filename, Py_None); 205 | default: 206 | PyErr_Format(PyExc_RuntimeError, "invalid type %d for alpm_fileconflict_t object", conflict->type); 207 | return NULL; 208 | } 209 | } 210 | 211 | /* Standard methods */ 212 | const char* flagnames[19] = { 213 | "nodeps", 214 | "force", 215 | "nosave", 216 | "nodepversion", 217 | "cascade", 218 | "recurse", 219 | "dbonly", 220 | NULL, 221 | "alldeps", 222 | "downloadonly", 223 | "noscriptlet", 224 | "noconflicts", 225 | NULL, 226 | "needed", 227 | "allexplicit", 228 | "unneeded", 229 | "recurseall", 230 | "nolock", 231 | NULL 232 | }; 233 | 234 | static PyObject *pyalpm_trans_get_flags(PyObject *self, void *closure) 235 | { 236 | PyObject *result; 237 | alpm_handle_t *handle = ALPM_HANDLE(self); 238 | int flags = alpm_trans_get_flags(handle); 239 | int i; 240 | if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL); 241 | result = PyDict_New(); 242 | for (i = 0; i < 18; i++) { 243 | if(flagnames[i]) 244 | PyDict_SetItemString(result, flagnames[i], flags & (1 << i) ? Py_True : Py_False); 245 | } 246 | return result; 247 | } 248 | 249 | static PyObject *pyalpm_trans_get_add(PyObject *self, void *closure) 250 | { 251 | alpm_handle_t *handle = ALPM_HANDLE(self); 252 | alpm_list_t *to_add; 253 | /* sanity check */ 254 | int flags = alpm_trans_get_flags(handle); 255 | if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL); 256 | 257 | to_add = alpm_trans_get_add(handle); 258 | return alpmlist_to_pylist(to_add, pyalpm_package_from_pmpkg); 259 | } 260 | 261 | static PyObject *pyalpm_trans_get_remove(PyObject *self, void *closure) 262 | { 263 | alpm_handle_t *handle = ALPM_HANDLE(self); 264 | alpm_list_t *to_remove; 265 | /* sanity check */ 266 | int flags = alpm_trans_get_flags(handle); 267 | if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL); 268 | 269 | to_remove = alpm_trans_get_remove(handle); 270 | return alpmlist_to_pylist(to_remove, pyalpm_package_from_pmpkg); 271 | } 272 | 273 | /** Transaction flow */ 274 | #define INDEX_FLAGS(array) \ 275 | array[0], array[1], array[2], \ 276 | array[3], array[4], array[5], \ 277 | array[6], array[8], array[9], \ 278 | array[10], array[11], array[13], \ 279 | array[14], array[15], array[16], \ 280 | array[17] 281 | 282 | /** Initializes a transaction 283 | * @param self a Handle object 284 | * ... 285 | * @return a Transaction object with the same underlying object 286 | */ 287 | PyObject* pyalpm_trans_init(PyObject *self, PyObject *args, PyObject *kwargs) { 288 | alpm_handle_t *handle = ALPM_HANDLE(self); 289 | PyObject *result; 290 | const char* keywords[] = { INDEX_FLAGS(flagnames), NULL }; 291 | char flags[18] = "\0\0\0\0\0" /* 5 */ "\0\0\0\0\0" /* 10 */ "\0\0\0\0\0" /* 15 */ "\0\0\0"; 292 | 293 | /* check all arguments */ 294 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, 295 | "|bbbbbbbbbbbbbbbbOOO", (char**)keywords, 296 | INDEX_FLAGS(&flags))) { 297 | return NULL; 298 | } 299 | 300 | /* run alpm_trans_init() */ 301 | { 302 | alpm_transflag_t flag_int = 0; 303 | int i, ret; 304 | for (i = 0; i < 18; i++) { 305 | if (flags[i]) flag_int |= 1U << i; 306 | } 307 | ret = alpm_trans_init(handle, flag_int); 308 | if (ret == -1) { 309 | RET_ERR("transaction could not be initialized", alpm_errno(handle), NULL); 310 | } 311 | } 312 | result = pyalpm_transaction_from_pmhandle(handle); 313 | return result; 314 | } 315 | 316 | static PyObject* pyalpm_trans_prepare(PyObject *self, PyObject *args) { 317 | alpm_handle_t *handle = ALPM_HANDLE(self); 318 | alpm_list_t *data; 319 | 320 | int ret = alpm_trans_prepare(handle, &data); 321 | if (ret == -1) { 322 | /* return the list of package conflicts in the exception */ 323 | PyObject *info = alpmlist_to_pylist(data, pyobject_from_pmdepmissing); 324 | if (!info) return NULL; 325 | RET_ERR_DATA("transaction preparation failed", alpm_errno(handle), info, NULL); 326 | } 327 | 328 | Py_RETURN_NONE; 329 | } 330 | 331 | static PyObject* pyalpm_trans_commit(PyObject *self, PyObject *args) { 332 | alpm_handle_t *handle = ALPM_HANDLE(self); 333 | alpm_list_t *data = NULL; 334 | int ret; 335 | enum _alpm_errno_t err; 336 | PyObject *err_info = NULL; 337 | 338 | ret = alpm_trans_commit(handle, &data); 339 | if (ret == 0) Py_RETURN_NONE; 340 | if (ret != -1) { 341 | PyErr_Format(PyExc_RuntimeError, 342 | "unexpected return value %d from alpm_trans_commit()", ret); 343 | return NULL; 344 | } 345 | 346 | err = alpm_errno(handle); 347 | switch(err) { 348 | case ALPM_ERR_FILE_CONFLICTS: 349 | /* return the list of file conflicts in the exception */ 350 | err_info = alpmlist_to_pylist(data, pyobject_from_pmfileconflict); 351 | break; 352 | case ALPM_ERR_PKG_INVALID: 353 | case ALPM_ERR_PKG_INVALID_CHECKSUM: 354 | case ALPM_ERR_PKG_INVALID_SIG: 355 | case ALPM_ERR_DLT_INVALID: 356 | err_info = alpmlist_to_pylist(data, pyobject_from_string); 357 | break; 358 | default: 359 | break; 360 | } 361 | if (err_info) 362 | RET_ERR_DATA("transaction failed", err, err_info, NULL); 363 | else 364 | RET_ERR("transaction failed", err, NULL); 365 | } 366 | 367 | static PyObject* pyalpm_trans_interrupt(PyObject *self, PyObject *args) { 368 | alpm_handle_t *handle = ALPM_HANDLE(self); 369 | int ret = alpm_trans_interrupt(handle); 370 | if (ret == -1) RET_ERR("unable to interrupt transaction", alpm_errno(handle), NULL); 371 | Py_RETURN_NONE; 372 | } 373 | 374 | PyObject* pyalpm_trans_release(PyObject *self, PyObject *args) { 375 | alpm_handle_t *handle = ALPM_HANDLE(self); 376 | int ret = alpm_trans_release(handle); 377 | if (ret == -1) RET_ERR("unable to release transaction", alpm_errno(handle), NULL); 378 | Py_RETURN_NONE; 379 | } 380 | 381 | /** Transaction contents */ 382 | static PyObject* pyalpm_trans_add_pkg(PyObject *self, PyObject *args) { 383 | alpm_handle_t *handle = ALPM_HANDLE(self); 384 | alpm_pkg_t *pmpkg; 385 | PyObject *pkg; 386 | int ret; 387 | 388 | if (!PyArg_ParseTuple(args, "O!", &AlpmPackageType, &pkg)) { 389 | return NULL; 390 | } 391 | 392 | pmpkg = pmpkg_from_pyalpm_pkg(pkg); 393 | ret = alpm_add_pkg(handle, pmpkg); 394 | if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL); 395 | /* alpm_add_pkg eats the reference to pkg */ 396 | pyalpm_pkg_unref(pkg); 397 | Py_RETURN_NONE; 398 | } 399 | 400 | static PyObject* pyalpm_trans_remove_pkg(PyObject *self, PyObject *args) { 401 | alpm_handle_t *handle = ALPM_HANDLE(self); 402 | PyObject *pkg; 403 | alpm_pkg_t *pmpkg; 404 | int ret; 405 | 406 | if (!PyArg_ParseTuple(args, "O!", &AlpmPackageType, &pkg)) { 407 | return NULL; 408 | } 409 | 410 | pmpkg = pmpkg_from_pyalpm_pkg(pkg); 411 | ret = alpm_remove_pkg(handle, pmpkg); 412 | if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL); 413 | Py_RETURN_NONE; 414 | } 415 | 416 | static PyObject* pyalpm_trans_sysupgrade(PyObject *self, PyObject *args, PyObject *kwargs) { 417 | alpm_handle_t *handle = ALPM_HANDLE(self); 418 | char* keyword[] = {"downgrade", NULL}; 419 | PyObject *downgrade; 420 | int do_downgrade, ret; 421 | 422 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keyword, &PyBool_Type, &downgrade)) 423 | return NULL; 424 | 425 | do_downgrade = (downgrade == Py_True) ? 1 : 0; 426 | ret = alpm_sync_sysupgrade(handle, do_downgrade); 427 | if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL); 428 | Py_RETURN_NONE; 429 | } 430 | 431 | /** Properties and methods */ 432 | 433 | static struct PyGetSetDef pyalpm_trans_getset[] = { 434 | /** filepaths */ 435 | { "flags", (getter)pyalpm_trans_get_flags, NULL, "Transaction flags", NULL } , 436 | { "to_add", (getter)pyalpm_trans_get_add, NULL, "Packages added by the transaction", NULL }, 437 | { "to_remove", (getter)pyalpm_trans_get_remove, NULL, "Packages added by the transaction", NULL }, 438 | { NULL } 439 | }; 440 | 441 | static struct PyMethodDef pyalpm_trans_methods[] = { 442 | /* Execution flow */ 443 | {"prepare", pyalpm_trans_prepare, METH_NOARGS, "prepare" }, 444 | {"commit", pyalpm_trans_commit, METH_NOARGS, "commit" }, 445 | {"interrupt", pyalpm_trans_interrupt,METH_NOARGS, "Interrupt the transaction." }, 446 | {"release", pyalpm_trans_release, METH_NOARGS, "Release the transaction." }, 447 | 448 | /* Transaction contents */ 449 | {"add_pkg", pyalpm_trans_add_pkg, METH_VARARGS, 450 | "append a package addition to transaction"}, 451 | {"remove_pkg", pyalpm_trans_remove_pkg, METH_VARARGS, 452 | "append a package removal to transaction"}, 453 | {"sysupgrade", (PyCFunction)pyalpm_trans_sysupgrade, METH_VARARGS | METH_KEYWORDS, 454 | "set the transaction to perform a system upgrade\n" 455 | "args:\n" 456 | " transaction (boolean) : whether to enable downgrades\n" }, 457 | { NULL } 458 | }; 459 | 460 | /* The Transaction object have the same underlying C structure 461 | * as the Handle objects. Only the method table changes. 462 | */ 463 | static PyTypeObject AlpmTransactionType = { 464 | PyVarObject_HEAD_INIT(NULL, 0) 465 | "alpm.Transaction", /*tp_name*/ 466 | sizeof(AlpmHandle), /*tp_basicsize*/ 467 | .tp_flags = Py_TPFLAGS_DEFAULT, 468 | .tp_doc = "This class is the main interface to get/set libalpm options", 469 | .tp_methods = pyalpm_trans_methods, 470 | .tp_getset = pyalpm_trans_getset, 471 | }; 472 | 473 | PyObject *pyalpm_transaction_from_pmhandle(void* data) { 474 | alpm_handle_t *handle = (alpm_handle_t*)data; 475 | AlpmHandle *self; 476 | self = (AlpmHandle*)AlpmTransactionType.tp_alloc(&AlpmTransactionType, 0); 477 | if (self == NULL) { 478 | PyErr_SetString(PyExc_RuntimeError, "unable to create pyalpm.Transaction object"); 479 | return NULL; 480 | } 481 | 482 | self->c_data = handle; 483 | return (PyObject *)self; 484 | } 485 | 486 | /* Initialization */ 487 | int init_pyalpm_transaction(PyObject *module) { 488 | if (PyType_Ready(&AlpmTransactionType) < 0) 489 | return -1; 490 | Py_INCREF(&AlpmTransactionType); 491 | PyModule_AddObject(module, "Transaction", (PyObject*)(&AlpmTransactionType)); 492 | return 0; 493 | } 494 | 495 | /* vim: set ts=2 sw=2 tw=0 et: */ 496 | 497 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | --------------------------------------------------------------------------------