├── debian ├── install ├── source │ └── format ├── rules ├── control ├── copyright └── changelog ├── po ├── meson.build └── LINGUAS ├── data ├── org.aptkit.service ├── icons │ ├── 16x16 │ │ └── status │ │ │ ├── aptkit-add.png │ │ │ ├── aptkit-wait.png │ │ │ ├── aptkit-cleanup.png │ │ │ ├── aptkit-delete.png │ │ │ ├── aptkit-download.png │ │ │ ├── aptkit-resolve.png │ │ │ ├── aptkit-upgrade.png │ │ │ ├── aptkit-working.png │ │ │ └── aptkit-update-cache.png │ ├── 22x22 │ │ └── status │ │ │ ├── aptkit-add.png │ │ │ ├── aptkit-wait.png │ │ │ ├── aptkit-cleanup.png │ │ │ ├── aptkit-delete.png │ │ │ ├── aptkit-download.png │ │ │ ├── aptkit-resolve.png │ │ │ ├── aptkit-upgrade.png │ │ │ ├── aptkit-working.png │ │ │ └── aptkit-update-cache.png │ ├── 24x24 │ │ └── status │ │ │ ├── aptkit-add.png │ │ │ ├── aptkit-wait.png │ │ │ ├── aptkit-cleanup.png │ │ │ ├── aptkit-delete.png │ │ │ ├── aptkit-download.png │ │ │ ├── aptkit-resolve.png │ │ │ ├── aptkit-upgrade.png │ │ │ ├── aptkit-working.png │ │ │ └── aptkit-update-cache.png │ ├── 48x48 │ │ └── status │ │ │ ├── aptkit-add.png │ │ │ ├── aptkit-wait.png │ │ │ ├── aptkit-cleanup.png │ │ │ ├── aptkit-delete.png │ │ │ ├── aptkit-download.png │ │ │ ├── aptkit-resolve.png │ │ │ ├── aptkit-upgrade.png │ │ │ ├── aptkit-working.png │ │ │ └── aptkit-update-cache.png │ └── scalable │ │ └── status │ │ ├── aptkit-add.svg │ │ └── aptkit-download.svg ├── 20aptkit ├── org.aptkit.conf ├── meson.build ├── aptk.1 ├── aptkcon.1 ├── org.aptkit.7 ├── org.aptkit.policy.in └── org.aptkit.transaction.7 ├── doc ├── aptkit.enums.rst ├── aptkit.gtk3widgets.rst ├── index.rst ├── examples │ ├── chained.py │ └── gtk3-demo.py ├── plugins.rst ├── aptkit.client.rst └── dbus.rst ├── .gitignore ├── aptkit ├── worker │ ├── meson.build │ └── __init__.py ├── meson.build ├── loop.py ├── __init__.py ├── pkutils.py ├── logger.py ├── utils.py ├── lock.py ├── simpleclient.py ├── debconf.py ├── policykit1.py ├── errors.py ├── config.py └── networking.py ├── makepot ├── AUTHORS ├── meson.build ├── README.md ├── usr ├── bin │ └── aptkcon └── sbin │ └── aptk └── COPYRIGHT /debian/install: -------------------------------------------------------------------------------- 1 | usr/ 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext(gettext_package, 2 | preset: 'glib' 3 | ) 4 | -------------------------------------------------------------------------------- /data/org.aptkit.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.aptkit 3 | Exec=/usr/sbin/aptk 4 | User=root 5 | 6 | -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-add.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-add.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-add.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-add.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-wait.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-wait.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-wait.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-wait.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-cleanup.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-delete.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-download.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-resolve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-resolve.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-upgrade.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-working.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-cleanup.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-delete.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-download.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-resolve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-resolve.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-upgrade.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-working.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-cleanup.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-delete.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-download.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-resolve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-resolve.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-upgrade.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-working.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-cleanup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-cleanup.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-delete.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-download.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-resolve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-resolve.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-upgrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-upgrade.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-working.png -------------------------------------------------------------------------------- /data/icons/16x16/status/aptkit-update-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/16x16/status/aptkit-update-cache.png -------------------------------------------------------------------------------- /data/icons/22x22/status/aptkit-update-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/22x22/status/aptkit-update-cache.png -------------------------------------------------------------------------------- /data/icons/24x24/status/aptkit-update-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/24x24/status/aptkit-update-cache.png -------------------------------------------------------------------------------- /data/icons/48x48/status/aptkit-update-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/aptkit/HEAD/data/icons/48x48/status/aptkit-update-cache.png -------------------------------------------------------------------------------- /doc/aptkit.enums.rst: -------------------------------------------------------------------------------- 1 | :mod:`aptkit.enums` --- The enums module 2 | =========================================== 3 | 4 | .. automodule:: aptkit.enums 5 | :members: 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | debian/aptkit*/* 3 | debian/python3-aptkit*/ 4 | debian/tmp 5 | debian/*debhelper* 6 | debian/*substvars* 7 | debian/files 8 | .pybuild/ 9 | aptkit.egg-info/ -------------------------------------------------------------------------------- /aptkit/worker/meson.build: -------------------------------------------------------------------------------- 1 | python3.install_sources( 2 | [ 3 | '__init__.py', 4 | 'aptworker.py', 5 | 'pkworker.py' 6 | ], 7 | subdir: 'aptkit/worker' 8 | ) 9 | -------------------------------------------------------------------------------- /doc/aptkit.gtk3widgets.rst: -------------------------------------------------------------------------------- 1 | :mod:`aptkit.gtk3widgets` --- The gtk3widgets module 2 | ===================================================== 3 | 4 | .. automodule:: aptkit.gtk3widgets 5 | :members: 6 | -------------------------------------------------------------------------------- /data/20aptkit: -------------------------------------------------------------------------------- 1 | // Notify all clients to reload the cache 2 | APT::Update::Post-Invoke-Success { "[ ! -f /var/run/dbus/system_bus_socket ] || /usr/bin/dbus-send --system --dest=org.aptkit --type=signal /org/aptkit org.aptkit.CacheChanged || true"; }; 3 | -------------------------------------------------------------------------------- /makepot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | xgettext data/org.aptkit.policy.in --output=aptkit.pot 3 | xgettext --language=Python -cTRANSLATORS --keyword=_ --keyword=N_ --join-existing --output=aptkit.pot aptkit/core.py aptkit/enums.py aptkit/gtk3widgets.py aptkit/lock.py aptkit/progress.py aptkit/worker/aptworker.py aptkit/worker/pkworker.py 4 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Aptkit documentation 2 | ======================= 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | aptkit.client 10 | aptkit.enums 11 | aptkit.gtk3widgets 12 | 13 | dbus 14 | plugins 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Aptkit started as a fork of aptdaemon. 2 | 3 | Aptdaemon authors: 4 | https://launchpad.net/aptdaemon 5 | Maintainer: Sebastian Heinlein 6 | Developers: Michael Vogt 7 | Icon Artits: Mike Langlie and Richard Hughes 8 | 9 | Since then the authors are listed in git. 10 | https://github.com/linuxmint/aptkit/graphs/contributors 11 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('aptkit', version : '1.1.1', meson_version : '>=0.49.0') 2 | 3 | pymod = import('python') 4 | python3 = pymod.find_installation('python3') 5 | 6 | i18n = import('i18n') 7 | 8 | gettext_package = meson.project_name() 9 | 10 | prefix = get_option('prefix') 11 | datadir = get_option('datadir') 12 | 13 | podir = meson.source_root() / 'po' 14 | 15 | subdir('data') 16 | subdir('po') 17 | subdir('aptkit') 18 | -------------------------------------------------------------------------------- /aptkit/meson.build: -------------------------------------------------------------------------------- 1 | python3.install_sources( 2 | [ 3 | '__init__.py', 4 | 'client.py', 5 | 'config.py', 6 | 'console.py', 7 | 'core.py', 8 | 'debconf.py', 9 | 'enums.py', 10 | 'errors.py', 11 | 'gtk3widgets.py', 12 | 'lock.py', 13 | 'logger.py', 14 | 'loop.py', 15 | 'networking.py', 16 | 'pkenums.py', 17 | 'pkutils.py', 18 | 'policykit1.py', 19 | 'progress.py', 20 | 'utils.py', 21 | 'simpleclient.py' 22 | ], 23 | subdir: 'aptkit' 24 | ) 25 | 26 | subdir('worker') 27 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | af 2 | am 3 | ar 4 | ast 5 | az 6 | be 7 | bg 8 | bn 9 | br 10 | bs 11 | ca 12 | cs 13 | csb 14 | cy 15 | da 16 | de 17 | el 18 | en_CA 19 | en_GB 20 | eo 21 | es 22 | et 23 | eu 24 | fa 25 | fi 26 | fil 27 | fr 28 | fr_CA 29 | gd 30 | gl 31 | he 32 | hi 33 | hr 34 | hu 35 | ia 36 | id 37 | is 38 | it 39 | ja 40 | ka 41 | kab 42 | kk 43 | km 44 | ko 45 | lo 46 | lt 47 | lv 48 | mk 49 | ml 50 | ms 51 | my 52 | nb 53 | nds 54 | nl 55 | nn 56 | oc 57 | pl 58 | pt 59 | pt_BR 60 | ro 61 | ru 62 | sa 63 | si 64 | sk 65 | sl 66 | sn 67 | sq 68 | sr 69 | sv 70 | szl 71 | ta 72 | te 73 | th 74 | tr 75 | ug 76 | uk 77 | ur 78 | uz 79 | vi 80 | zh_CN 81 | zh_HK 82 | zh_TW 83 | -------------------------------------------------------------------------------- /data/org.aptkit.conf: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AptKit 2 | 3 | Aptkit allows to perform package management tasks in a background process 4 | controlled by DBus. It is the continuation of aptdaemon which is not actively 5 | maintained and only exists in Ubuntu. 6 | 7 | Aptdaemon was greatly inspired by PackageKit, which doesn't support 8 | essential features of apt by policy. 9 | 10 | # TRANSLATIONS 11 | 12 | This project is translated on Launchpad at https://translations.launchpad.net/linuxmint/latest/+pots/aptkit. 13 | 14 | Please do not make pull requests to modify `po/` files directly. These are overwritten when we import translations 15 | from Launchpad. 16 | 17 | # TODO 18 | 19 | - Document usage and examples 20 | - Add support for XApp Window Progress (already present in SimpleAPTClient) 21 | - Clean up code and copyrights with a reuse.toml and debian/copyright 22 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | i18n.merge_file( 2 | input: 'org.aptkit.policy.in', 3 | output: 'org.aptkit.policy', 4 | po_dir: podir, 5 | type: 'xml', 6 | install: true, 7 | install_dir: datadir / 'polkit-1/actions', 8 | ) 9 | 10 | install_data(['20aptkit'], install_dir : '/etc/apt/apt.conf.d/') 11 | install_data(['org.aptkit.conf'], install_dir: datadir / 'dbus-1/system.d/') 12 | install_data(['org.aptkit.service'], install_dir: datadir / 'dbus-1/system-services/') 13 | 14 | install_man('aptk.1') 15 | install_man('aptkcon.1') 16 | install_man('org.aptkit.7') 17 | install_man('org.aptkit.transaction.7') 18 | 19 | install_subdir('icons/16x16', install_dir : datadir / 'icons/hicolor/') 20 | install_subdir('icons/22x22', install_dir : datadir / 'icons/hicolor/') 21 | install_subdir('icons/24x24', install_dir : datadir / 'icons/hicolor/') 22 | install_subdir('icons/48x48', install_dir : datadir / 'icons/hicolor/') 23 | -------------------------------------------------------------------------------- /usr/bin/aptkcon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | aptkcon - command line interface client to aptkit 5 | """ 6 | # Copyright (C) 2008 Sebastian Heinlein 7 | # 8 | # This program 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 2 of the License, or 11 | # any later version. 12 | # 13 | # This program 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 along 19 | # with this program; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | __author__ = "Sebastian Heinlein " 23 | __state__ = "experimental" 24 | 25 | import aptkit.console 26 | 27 | if __name__ == "__main__": 28 | aptkit.console.main() 29 | 30 | # vim:ts=4:sw=4:et 31 | -------------------------------------------------------------------------------- /aptkit/loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Main loop for aptkit.""" 4 | # Copyright (C) 2008-2009 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("mainloop", "get_main_loop") 23 | 24 | from gi.repository import GLib 25 | 26 | mainloop = GLib.MainLoop() 27 | 28 | 29 | def get_main_loop(): 30 | """Return the glib main loop as a singleton.""" 31 | return mainloop 32 | 33 | # vim:ts=4:sw=4:et 34 | -------------------------------------------------------------------------------- /aptkit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Transaction based daemon and clients for package management tasks.""" 4 | # Copyright (C) 2008-2009 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | __state__ = "development" 22 | __version__ = '1.1.1' 23 | 24 | __all__ = ("client", "console", "core", "debconf", "defer", "enums", 25 | "errors", "gtk3widgets", "loop", "policykit1", "progress", 26 | "test", "utils", "worker") 27 | 28 | # vim:ts=4:sw=4:et 29 | -------------------------------------------------------------------------------- /aptkit/pkutils.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Provides helper functions for the PackageKit layer 5 | 6 | Copyright (C) 2007 Ali Sabil 7 | Copyright (C) 2007 Tom Parker 8 | Copyright (C) 2008-2013 Sebastian Heinlein 9 | 10 | Licensed under the GNU General Public License Version 2 11 | 12 | This program is free software; you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation; either version 2 of the License, or 15 | (at your option) any later version. 16 | """ 17 | 18 | __author__ = "Sebastian Heinlein " 19 | 20 | 21 | def bitfield_summarize(*enums): 22 | """Return the bitfield with the given PackageKit enums.""" 23 | field = 0 24 | for enum in enums: 25 | field |= 2 ** int(enum) 26 | return field 27 | 28 | 29 | def bitfield_add(field, enum): 30 | """Add a PackageKit enum to a given field""" 31 | field |= 2 ** int(enum) 32 | return field 33 | 34 | 35 | def bitfield_remove(field, enum): 36 | """Remove a PackageKit enum to a given field""" 37 | field = field ^ 2 ** int(enum) 38 | return field 39 | 40 | 41 | def bitfield_contains(field, enum): 42 | """Return True if a bitfield contains the given PackageKit enum""" 43 | return field & 2 ** int(enum) 44 | 45 | 46 | # vim: ts=4 et sts=4 47 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: aptkit 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Linux Mint 5 | Build-Depends: 6 | debhelper-compat (= 13), 7 | gettext, 8 | libpolkit-gobject-1-dev (>= 123) | polkitd (<< 123), 9 | meson, 10 | Standards-Version: 4.6.2 11 | Homepage: https://github.com/linuxmint/aptkit 12 | 13 | Package: aptkit 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | gir1.2-glib-2.0, 18 | gir1.2-gtk-3.0, 19 | gir1.2-packagekitglib-1.0, 20 | gir1.2-vte-2.91, 21 | gir1.2-xapp-1.0, 22 | iso-codes, 23 | polkitd, 24 | python3-apt, 25 | python3-dbus, 26 | python3-defer (>= 1.0.6), 27 | python3-gi, 28 | python3-pkg-resources, 29 | Description: transaction based package management service 30 | Aptkit allows users to perform package management tasks, 31 | refreshing the cache, upgrading the system, installing or removing software 32 | packages. 33 | . 34 | It provides the following main features: 35 | . 36 | - D-Bus interface to write clients in several languages 37 | - Modules and widgets to write clients in Python3/GTK3 38 | - CLI tools 39 | - Runs only if required (D-Bus activation) 40 | - Fine grained privilege management using PolicyKit, e.g. allowing all 41 | desktop user to query for updates without entering a password 42 | - Support for media changes during installation from DVD/CDROM 43 | - Support for debconf (Debian's package configuration system) 44 | - Support for attaching a terminal to the underlying dpkg call 45 | -------------------------------------------------------------------------------- /usr/sbin/aptk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | aptk - apt kit 5 | """ 6 | # Copyright (C) 2008 Sebastian Heinlein 7 | # 8 | # This program 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 2 of the License, or 11 | # any later version. 12 | # 13 | # This program 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 along 19 | # with this program; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | __author__ = "Sebastian Heinlein " 23 | __state__ = "experimental" 24 | 25 | import os 26 | import sys 27 | 28 | 29 | if __name__ == "__main__": 30 | # Ensure that the default encoding is set since Python's setlocale doesn't 31 | # allow to change it. This can be the case if D-Bus activation is used, 32 | # see LP: #1058038 and http://bugs.python.org/issue16162 33 | if sys.getfilesystemencoding() == "ascii" and not "LANG" in os.environ: 34 | os.environ["LANG"] = "C.UTF-8" 35 | os.execv(sys.argv[0], sys.argv) 36 | 37 | import aptkit.core 38 | 39 | aptkit.core.main() 40 | 41 | # vim:ts=4:sw=4:et 42 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: aptkit 3 | Upstream-Contact: Linux Mint Project 4 | Source: https://github.com/linuxmint/aptkit.git 5 | 6 | Files: * 7 | Copyright: © 2024 Aptkit developers 8 | License: GPL-2+ 9 | 10 | Files: data/icons/* 11 | Copyright: © 2008 Mike Langlie 12 | © 2008 Richard Hughes 13 | © 2024 Aptkit developers 14 | License: GPL-2+ 15 | 16 | Files: doc/* 17 | Copyright: © 2008 Sebastian Heinlein 18 | © 2024 Aptkit developers 19 | License: GPL-2+ 20 | 21 | Files: aptkit/* 22 | Copyright: © 2008 Canonical Ltd. 23 | © 2008 Aidan Skinner 24 | © 2008 Martin Pitt 25 | © 2008 Tim Lauridsen 26 | © 2008 Sebastian Heinlein 27 | © 2024 Aptkit developers 28 | License: GPL-2+ 29 | 30 | Files: aptkit/debconf.py 31 | Copyright: © 2009 Sebastian Heinlein 32 | © 2009 Michael Vogt 33 | © 2024 Aptkit developers 34 | License: GPL-2+ 35 | 36 | License: GPL-2+ 37 | This package is free software; you can redistribute it and/or modify 38 | it under the terms of the GNU General Public License as published by 39 | the Free Software Foundation; either version 2 of the License, or 40 | (at your option) any later version. 41 | . 42 | On Debian systems, the complete text of the GNU General 43 | Public License can be found in `/usr/share/common-licenses/GPL-2'. 44 | -------------------------------------------------------------------------------- /doc/examples/chained.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import dbus 4 | 5 | from gi.repository import GLib 6 | 7 | from aptkit.client import AptClient 8 | from aptkit.defer import inline_callbacks 9 | from aptkit import policykit1 10 | 11 | loop = GLib.MainLoop() 12 | 13 | def on_finished(trans, exit): 14 | loop.quit() 15 | print exit 16 | 17 | @inline_callbacks 18 | def main(): 19 | repo = ["deb", "http://packages.glatzor.de/silly-packages", "sid", ["main"], 20 | "Silly packages", "silly.list"] 21 | aptclient = AptClient() 22 | bus = dbus.SystemBus() 23 | name = bus.get_unique_name() 24 | # high level auth 25 | try: 26 | # Preauthentication 27 | action = policykit1.PK_ACTION_INSTALL_PURCHASED_PACKAGES 28 | flags = policykit1.CHECK_AUTH_ALLOW_USER_INTERACTION 29 | yield policykit1.check_authorization_by_name(name, action, flags=flags) 30 | # Setting up transactions 31 | trans_add = yield aptclient.add_repository(*repo) 32 | trans_update = yield aptclient.update_cache("silly.list") 33 | trans_inst = yield aptclient.install_packages(["silly-base"]) 34 | yield trans_inst.set_allow_unauthenticated(True) 35 | # Check when the last transaction was done 36 | trans_inst.connect("finished", on_finished) 37 | # Chaining transactions 38 | yield trans_update.run_after(trans_add) 39 | yield trans_inst.run_after(trans_update) 40 | yield trans_add.run() 41 | except Exception as error: 42 | print error 43 | loop.quit() 44 | 45 | if __name__ == "__main__": 46 | main() 47 | loop.run() 48 | -------------------------------------------------------------------------------- /data/aptk.1: -------------------------------------------------------------------------------- 1 | .\" groff -man -Tascii foo.1 2 | .TH APTK 1 "December 2009" aptkit "User manual" 3 | .SH NAME 4 | aptk \- package managing daemon proving a D-Bus interface 5 | .SH SYNOPSIS 6 | .B aptk 7 | .RI [ OPTIONS ] 8 | .SH DESCRIPTION 9 | .B aptk 10 | allows one to perform package management tasks, e.g. installing or removing 11 | software, using a D-Bus interface. The privileges are handled by PolicyKit 12 | so the client application doesn't need to run as root. Furthermore aptk is 13 | started by D-Bus activation only when an user calls a method. 14 | .SH OPTIONS 15 | .IP "-d, --debug" 16 | Show additional information on the command line. 17 | .IP "-h, --help" 18 | Show information about the usage of the command. 19 | .IP "-r, --replace" 20 | Replace another aptk instance if it is running. 21 | .IP "-p PROFILE_FILE" 22 | Write profiling data to 23 | .I PROFILE_FILE 24 | using Python's profiler. This is only of use to developers. 25 | .IP "-t, --disable-timeout" 26 | Do not shutdown the daemon after an idle time. 27 | .IP "--dummy" 28 | Instead of applying the changes to the system only show a progress. This option is only usable for developers to locate problems in client applications. 29 | .SH FILES 30 | .TP 31 | .I /etc/apt/apt.conf.d/20aptkit 32 | Adds a small post update hook which will emit the org.aptkit.CacheChanged signal on the system D-Bus to indicate that the cache has been changed and a possible running aptk instance should reloade its internal cache. 33 | .TP 34 | .I /usr/share/polkit-1/actions/org.aptkit.policy 35 | The PolicyKit definitions of the privileges used by aptkit, e.g. to install packages. To change the privileges please have a look at 36 | .BR PolicyKit.conf (1). 37 | .TP 38 | .I /etc/dbus-1/system.d/org.aptkit.conf 39 | The D-Bus configuration of the org.aptkit name space. 40 | .SH DIAGNOSTICS 41 | By default aptkit logs to the syslog facility AptKit. Furthermore you 42 | can use the -d option to get additional information on the command line. 43 | .SH SEE ALSO 44 | .BR aptkcon (1), 45 | .BR org.aptkit (7), 46 | .BR org.aptkit.transaction (7), 47 | .BR PolicyKit.conf (7) 48 | -------------------------------------------------------------------------------- /doc/plugins.rst: -------------------------------------------------------------------------------- 1 | Plugins 2 | ======= 3 | 4 | Aptkit provides a plugin mechanism by making use of setuptools' entry points. 5 | The group name is ``aptkit.plugins``. 6 | 7 | Cache modifiers 8 | --------------- 9 | 10 | Currently there are two types of plugins available which allow you to modify 11 | the marked changes of a transaction. Each plugin can be a function which 12 | accepts the resolver (apt.cache.ProblemResolver) and the cache (apt.cache.Cache) 13 | as arguments: 14 | 15 | * *modify_cache_before* 16 | The plugged-in function is called after the intentional changes of the 17 | transaction have been marked, but before the dependencies have been resolved. 18 | 19 | * *modify_cache_after* 20 | The plugged-in function is called after the dependency resolution of the 21 | transaction. The resolver will be called again afterwards. 22 | 23 | A short overview of the steps required to process a transaction: 24 | 25 | 1. Mark intentional changes (e.g. the to be installed packages of a 26 | InstallPackages transaction) 27 | 2. Call all modify_cache_before plugins 28 | 3. Run the dependency resolver 29 | 4. Call all modify_cache_after_plugins and re-run the resolver 30 | 5. Commit changes 31 | 32 | 33 | External helpers 34 | ---------------- 35 | 36 | There is also the *get_license_key* plugin which allows to retrieve the license 37 | key content and path to store. License keys are required by propriatary 38 | software. The plugged-in function gets called with uid of the user who started 39 | the transaction, the package name, a JSON OAuth tocken and the name of a 40 | server to query. 41 | 42 | Example 43 | ------- 44 | 45 | Here is an example which would install language packages :file:`./plugins/aptk.py`: 46 | 47 | >>> def install_language_packs(resolver, cache): 48 | >>> """Marks the required language packs for installation.""" 49 | >>> #... do the magic here ... 50 | >>> language_pack.mark_install(False, True) 51 | >>> # Only protect your changes if they are mantadory. If they cannot be 52 | >>> # be installed the transaction will fail. 53 | >>> resolver.clear(language_pack) 54 | >>> resolver.protect(language_pack) 55 | 56 | Finally you would have to register your function as entry point in :file:`setup.py`: 57 | 58 | >>> setup( 59 | >>> ... 60 | >>> entry_points="""[aptkit.plugins] 61 | >>> modify_cache_after=plugins.aptk:install_language_packs 62 | >>> """, 63 | >>> ... 64 | >>> ) 65 | 66 | .. note:: 67 | Keep in mind that you can only register one entry point per name/plugin per 68 | distribution. 69 | -------------------------------------------------------------------------------- /aptkit/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Logging facilities for aptkit 5 | """ 6 | # Copyright (C) 2013 Sebastian Heinlein 7 | # 8 | # This program 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 2 of the License, or 11 | # any later version. 12 | # 13 | # This program 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 along 19 | # with this program; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | __author__ = "Sebastian Heinlein " 23 | 24 | __all__ = ("ColoredFormatter") 25 | 26 | import logging 27 | import os 28 | 29 | # Define some foreground colors 30 | BLACK = 30 31 | RED = 31 32 | GREEN = 32 33 | YELLOW = 33 34 | BLUE = 34 35 | MAGENTA = 35 36 | CYAN = 36 37 | WHITE = 37 38 | 39 | # Terminal control sequences to format output 40 | RESET_SEQ = "\033[0m" 41 | COLOR_SEQ = "\033[1;%dm" 42 | BOLD_SEQ = "\033[1m" 43 | 44 | COLORS = { 45 | logging.WARN: YELLOW, 46 | logging.INFO: BLUE, 47 | logging.DEBUG: CYAN, 48 | logging.CRITICAL: RED, 49 | logging.ERROR: RED 50 | } 51 | 52 | 53 | class ColoredFormatter(logging.Formatter): 54 | 55 | """Adds some color to the log messages. 56 | 57 | http://stackoverflow.com/questions/384076/\ 58 | how-can-i-color-python-logging-output 59 | """ 60 | 61 | def __init__(self, fmt=None, datefmt=None, use_color=True): 62 | logging.Formatter.__init__(self, fmt, datefmt) 63 | if os.getenv("TERM") in ["xterm", "xterm-colored", "linux"]: 64 | self.use_color = use_color 65 | else: 66 | self.use_color = False 67 | 68 | def format(self, record): 69 | """Return the formated output string.""" 70 | if self.use_color and record.levelno in COLORS: 71 | record.levelname = (COLOR_SEQ % COLORS[record.levelno] + 72 | record.levelname + 73 | RESET_SEQ) 74 | record.name = COLOR_SEQ % GREEN + record.name + RESET_SEQ 75 | if record.levelno in [logging.CRITICAL, logging.ERROR]: 76 | record.msg = COLOR_SEQ % RED + record.msg + RESET_SEQ 77 | return logging.Formatter.format(self, record) 78 | -------------------------------------------------------------------------------- /data/aptkcon.1: -------------------------------------------------------------------------------- 1 | .\" groff -man -Tascii foo.1 2 | .TH APTKCON 1 "December 2009" aptkit "User manual" 3 | .SH NAME 4 | aptkcon \- command line client for aptkit 5 | .SH SYNOPSIS 6 | .B aptkcon 7 | .RI [ OPTIONS ] 8 | .SH DESCRIPTION 9 | .B aptkcon 10 | allows one to perform package management tasks, e.g. installing or removing 11 | software, using aptkit. There isn't any need to be root to run this 12 | programme. 13 | .SH OPTIONS 14 | .IP "-v, --version" 15 | Show the version number of the aptkcon. 16 | .IP "-h, --help" 17 | Show information about the usage of the command. 18 | .IP "-d, --debug" 19 | Show additional information on the command line. 20 | .IP "-i, --install PACKAGES" 21 | Install the list of PACKAGES. If you want to install more than one package you have to put the package names into quotation marks. 22 | .IP "--reinstall PACKAGES" 23 | Reinstall the list of PACKAGES. If you want to reinstall more than one package you have to put the package names into quotation marks. 24 | .IP "-r, --remove PACKAGES" 25 | Remove the list of PACKAGES. If you want to remove more than one package you have to put the package names into quotation marks. 26 | .IP "-p, --purge PACKAGES" 27 | Purge the list of PACKAGES. If you want to purge more than one package you have to put the package names into quotation marks. 28 | .IP "-u, --upgrade PACKAGES" 29 | Upgrade the list of PACKAGES. If you want to upgrade more than one package you have to put the package names into quotation marks. 30 | .IP --upgrade-system 31 | Upgrade the whole system. 32 | .IP --fix-install 33 | Try to complete a previously cancelled installation by calling "dpkg --configure -a". 34 | .IP --fix-depends 35 | Try to resolve unsatisified dependencies. Attention: Currently you don't get a confirmation of the changes, which makes this method quite dangerous since it could remove a lot of packages. 36 | .IP "--add-vendor-key PUBLIC_KEY_FILE" 37 | Install the PUBLIC_KEY_FILE to authenticate and trust packages signed by the 38 | vendor. 39 | .IP "--add-vendor-key-from-keyserver PUBLIC_KEY_ID" 40 | Download and install the PUBLIC_KEY_ID to authenticate and trust packages 41 | signed by the vendor. Requires the --keyserver to be set. 42 | .IP "--key-server KEYSERVER" 43 | Download vendor keys from the given KEYSERVER. 44 | .IP "--remove-vendor-key FINGERPRINT" 45 | Remove the vendor key of the given FINGERPRINT to no longer trust packages 46 | from this vendor. 47 | .IP "--add-repository \'DEB_LINE\'" 48 | Allow one to install software from the repository specified by the given 49 | DEB_LINE. You have to put quotation marks around the DEB_LINE since it 50 | normally contains spaces: 51 | .I \'deb http://ftp.de.debian.org/debian unstable main\' 52 | .IP "--sources-file SOURCES_FILE" 53 | Specify an alternative sources file to which the new repository should be 54 | written. SOURCES_FILE should be only the basename: 55 | .I backports.list 56 | .IP --list-trusted-vendors 57 | Show all trusted software vendors and theirs keys. 58 | .IP --hide-terminal 59 | Do not attach to the interactive terminal of the underlying dpkg call. 60 | .IP --allow-unauthenticated 61 | Allow one to install packages which are not from a trusted vendor. 62 | .SH EXAMPLES 63 | The following command will install the package xterm and remove the package eterm in the same run: 64 | .RS 65 | $ aptkcon --install "xterm" --remove "eterm" 66 | .RE 67 | .PP 68 | To handle more than one package the names have to be put into quotation marks. The following command will install xterm and eterm: 69 | .RS 70 | $ aptkcon --install "xterm eterm" 71 | .RE 72 | .PP 73 | The following command will add the backport repository to the sources.list in 74 | a separate file 75 | .I /etc/apt/sources.list.d/backports.list 76 | : 77 | .RS 78 | $ aptkcon --sources-file backports.list \\ 79 | .br 80 | --add-repostiry "deb http://archive.backports.org/debian stable main" 81 | .RE 82 | .SH DIAGNOSTICS 83 | By default aptkit logs to the syslog facility AptKit. Furthermore you 84 | can use the -d option to get additional information on the command line. 85 | .SH SEE ALSO 86 | .BR aptk (1), 87 | .BR org.aptkit (7), 88 | .BR org.aptkit.transaction (7) 89 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | aptkit (1.1.1) zena; urgency=medium 2 | 3 | * l10n: Update translations 4 | 5 | -- Clement Lefebvre Fri, 12 Dec 2025 16:45:27 +0000 6 | 7 | aptkit (1.1.0) zena; urgency=medium 8 | 9 | [ Serhan Tutar ] 10 | * Fix unused screen space in Update Manager download progress pane. (#12) 11 | 12 | [ Kyrill Zorin ] 13 | * aptworker: Fix downgrade behaviour when comparing package versions (#13) 14 | 15 | -- Clement Lefebvre Wed, 10 Dec 2025 16:12:24 +0000 16 | 17 | aptkit (1.0.9) zena; urgency=medium 18 | 19 | [ chowette ] 20 | * Refactor dialog layouts to use one additional top level VBox. (#11) 21 | 22 | [ Clement Lefebvre ] 23 | * l10n: Update POT 24 | 25 | -- Clement Lefebvre Thu, 20 Nov 2025 14:48:19 +0000 26 | 27 | aptkit (1.0.8) gigi; urgency=medium 28 | 29 | * Remove key management 30 | 31 | -- Clement Lefebvre Sat, 06 Sep 2025 14:00:18 +0100 32 | 33 | aptkit (1.0.7) zara; urgency=medium 34 | 35 | * l10n: Update translations 36 | 37 | -- Clement Lefebvre Tue, 26 Aug 2025 17:33:42 +0200 38 | 39 | aptkit (1.0.6) zara; urgency=medium 40 | 41 | [ Michael Webster ] 42 | * gtk3widgets.py: Fix get_size_string() argument for freed space. 43 | 44 | [ Clement Lefebvre ] 45 | * l10n: Update translations 46 | 47 | -- Clement Lefebvre Thu, 31 Jul 2025 11:31:50 +0200 48 | 49 | aptkit (1.0.5) zara; urgency=medium 50 | 51 | [ Bardia Moshiri ] 52 | * pkworker: drop the usage of ProvidesEnum 53 | 54 | [ Michael Webster ] 55 | * gtk3widgets.py: Show numeric progress and speed during operations. 56 | 57 | [ Bardia Moshiri ] 58 | * aptkit: fix syntax warning errors in python 3.12 and 3.13 59 | 60 | -- Clement Lefebvre Mon, 28 Jul 2025 13:41:07 +0200 61 | 62 | aptkit (1.0.4) xia; urgency=medium 63 | 64 | * l10n: Update translations 65 | * icons: Fix wrong icon name 66 | * icons: Switch to aptkit icon name 67 | * gtk3widgets: Use a generic package icon name 68 | 69 | -- Clement Lefebvre Fri, 10 Jan 2025 11:27:40 +0000 70 | 71 | aptkit (1.0.3) xia; urgency=medium 72 | 73 | * Don't add an upgraded pkg as dependency when it's marked for install 74 | 75 | -- Clement Lefebvre Sun, 08 Dec 2024 16:38:44 +0000 76 | 77 | aptkit (1.0.2) xia; urgency=medium 78 | 79 | * l10n: Update translations 80 | 81 | -- Clement Lefebvre Thu, 05 Dec 2024 10:29:44 +0000 82 | 83 | aptkit (1.0.1) xia; urgency=medium 84 | 85 | [ Clement Lefebvre ] 86 | * Packaging :Update build dependencies 87 | 88 | [ Fabio Fantoni ] 89 | * debian: switch to debhelper-compat and compat 13 90 | * d/control: bump standard-version to 4.6.2 91 | * debian/control: change homepage to github repository 92 | * debian/copyright: fix and complete headers fields 93 | 94 | [ Clement Lefebvre ] 95 | * Add SimpleAPTClient 96 | * Update README 97 | * simpleclient: Use python-apt resolver when installing pkgs 98 | * simpleclient: Add the ability to downgrade packages 99 | * man: Fix lintian warnings 100 | * icons: Fix icon size on 48x48 aptkit-wait.png 101 | * dbus: Move policy file to /usr/share/dbus-1/system.d 102 | 103 | [ Fabio Fantoni ] 104 | * d/control: add missed gettext to build-deps 105 | * d/control: replace obsolete policykit-1 with polkitd 106 | * d/control: replace deprecated priority extra with optional 107 | 108 | [ Clement Lefebvre ] 109 | * d/control: wrap and sort 110 | * dbus: Specify send_destination/send_interface 111 | * Update README 112 | * simpleclient: Add purge_packages() and commit_changes() 113 | * AptConfirmDialog: Sort pkgs, remove icons 114 | * AptConfirmDialog: Clear the dialog title 115 | * Remove outdated info/links 116 | * Rename org.debian.aptkit to org.aptkit 117 | * pkit: Simplify vendor name 118 | * gtk3widgets: Ignore not-authorized errors 119 | 120 | -- Clement Lefebvre Wed, 20 Nov 2024 15:46:58 +0000 121 | 122 | aptkit (1.0.0) wilma; urgency=medium 123 | 124 | * Initial release 125 | 126 | -- Clement Lefebvre Tue, 10 Sep 2024 14:31:00 +0100 127 | -------------------------------------------------------------------------------- /aptkit/utils.py: -------------------------------------------------------------------------------- 1 | """Module with little helper functions and classes: 2 | 3 | deprecated - decorator to emit a warning if a depreacted function is used 4 | """ 5 | # Copyright (C) 2008-2009 Sebastian Heinlein 6 | # 7 | # Licensed under the GNU General Public License Version 2 8 | # 9 | # This program 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 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program 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 this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | 23 | __author__ = "Sebastian Heinlein " 24 | 25 | __all__ = ("deprecated", "IsoCodes") 26 | 27 | import os 28 | import sys 29 | import contextlib 30 | import gettext 31 | import functools 32 | import warnings 33 | from xml.etree import ElementTree 34 | 35 | if sys.version >= '3': 36 | _gettext_method = "gettext" 37 | else: 38 | _gettext_method = "ugettext" 39 | 40 | 41 | def deprecated(func): 42 | """This is a decorator which can be used to mark functions 43 | as deprecated. It will result in a warning being emitted 44 | when the function is used. 45 | 46 | Taken from http://wiki.python.org/moin/PythonDecoratorLibrary 47 | #GeneratingDeprecationWarnings 48 | """ 49 | @functools.wraps(func) 50 | def new_func(*args, **kwargs): 51 | warnings.warn_explicit( 52 | "Call to deprecated function %(funcname)s." % { 53 | 'funcname': func.__name__, 54 | }, 55 | category=DeprecationWarning, 56 | filename=func.__code__.co_filename, 57 | lineno=func.__code__.co_firstlineno + 1 58 | ) 59 | return func(*args, **kwargs) 60 | return new_func 61 | 62 | 63 | @contextlib.contextmanager 64 | def set_euid_egid(uid, gid): 65 | # no need to drop privs 66 | if os.getuid() != 0 and os.getgid() != 0: 67 | yield 68 | return 69 | # temporary drop privs 70 | os.setegid(gid) 71 | old_groups = os.getgroups() 72 | os.setgroups([gid]) 73 | os.seteuid(uid) 74 | try: 75 | yield 76 | finally: 77 | os.seteuid(os.getuid()) 78 | os.setegid(os.getgid()) 79 | os.setgroups(old_groups) 80 | 81 | 82 | class IsoCodes(object): 83 | 84 | """Provides access to the iso-codes language, script and country 85 | database. 86 | """ 87 | 88 | def __init__(self, norm, tag, fallback_tag=None): 89 | filename = "/usr/share/xml/iso-codes/%s.xml" % norm 90 | et = ElementTree.ElementTree(file=filename) 91 | self._dict = {} 92 | self.norm = norm 93 | for element in list(et.iter()): 94 | iso_code = element.get(tag) 95 | if not iso_code and fallback_tag: 96 | iso_code = element.get(fallback_tag) 97 | if iso_code: 98 | self._dict[iso_code] = element.get("name") 99 | 100 | def get_localised_name(self, value, locale): 101 | try: 102 | name = self._dict[value] 103 | except KeyError: 104 | return None 105 | trans = gettext.translation(domain=self.norm, fallback=True, 106 | languages=[locale]) 107 | return getattr(trans, _gettext_method)(name) 108 | 109 | def get_name(self, value): 110 | try: 111 | return self._dict[value] 112 | except KeyError: 113 | return None 114 | 115 | 116 | def split_package_id(package): 117 | """Return the name, the version number and the release of the 118 | specified package.""" 119 | if "=" in package: 120 | name, version = package.split("=", 1) 121 | release = None 122 | elif "/" in package: 123 | name, release = package.split("/", 1) 124 | version = None 125 | else: 126 | name = package 127 | version = release = None 128 | return name, version, release 129 | 130 | 131 | # vim:ts=4:sw=4:et 132 | -------------------------------------------------------------------------------- /doc/aptkit.client.rst: -------------------------------------------------------------------------------- 1 | :mod:`aptkit.client` --- The client module 2 | ============================================= 3 | 4 | Introduction 5 | ------------ 6 | 7 | Aptkit comes with a client module which provides a smoother interface on top 8 | of the D-Bus interface. It provides GObjects of the daemon and each transaction. 9 | 10 | 11 | .. _life_cycle: 12 | 13 | Life cycle of a transaction based action 14 | ---------------------------------------- 15 | 16 | At first you initialize an AptClient instance. 17 | 18 | >>> from aptkit import client 19 | >>> 20 | >>> apt_client = client.AptClient() 21 | 22 | Secondly you call the whished action, e.g. updating the package cache. It will 23 | give you a new transaction instance. 24 | 25 | >>> transaction = apt_client.update() 26 | 27 | The transaction has not been started yet. So you can make some further 28 | adjustements to it, e.g. setting a different language: 29 | 30 | >>> transaction.set_locale("de_DE") 31 | 32 | ... or setup the monitoring of the transaction: 33 | 34 | >>> transaction.connect("finished", on_transaction_finished) 35 | 36 | You can then put the transaction on the queue by calling its :meth:`run()` 37 | method: 38 | 39 | >>> transaction.run() 40 | 41 | If you don't need the underlying transcation instance of an action, you can 42 | alternatively set the wait argument to True. The AptClient method will return 43 | after the transaction is done: 44 | 45 | >>> apt_client.update_cache(wait=True) 46 | 47 | This can also be used with asynchronous programming, see below. 48 | 49 | 50 | Asynchronous programming 51 | ------------------------ 52 | 53 | In the above examples simple synchronous calls have been made to the D-Bus. 54 | Until these calls return your whole application is blocked/frozen. 55 | In the case of password prompts this can be quite long. So aptkit supports 56 | the following asynchronous styles: 57 | 58 | D-Bus style callbacks 59 | ^^^^^^^^^^^^^^^^^^^^^ 60 | 61 | Since the client module is basically a wrapper around D-Bus calls, it provides 62 | a pair of reply_handler and error_handler for each method. You are perhaps 63 | already familiar with those if you have written a Python D-Bus client before: 64 | 65 | >>> def run(trans): 66 | ... trans.run(reply_handler=trans_has_started, error_handler=on_error) 67 | ... 68 | >>> def trans_has_started(): 69 | ... pass 70 | ... 71 | >>> def on_error(error): 72 | ... raise error 73 | ... 74 | >>> client = AptClient() 75 | >>> client.update(reply_handler=run, error_handler=on_error) 76 | 77 | 78 | Deferred callbacks 79 | ^^^^^^^^^^^^^^^^^^ 80 | 81 | Aptkit uses a simplified version of Twisted deferreds internally, called 82 | python-defer. But you can also make use of them in your application. 83 | 84 | The inline_callbacks decorator of python-defer allows to write asynchronous 85 | code in a synchronous way: 86 | 87 | >>> @defer.inline_callbacks 88 | ... def update_cache(): 89 | ... apt_client = client.AptClient() 90 | ... try: 91 | ... transaction = yield apt_client.update() 92 | ... except errors.NotAuthorizedError: 93 | ... print "You are not allowed to update the cache!" 94 | ... return 95 | ... yield transaction.set_locale("de_DE") 96 | ... yield transaction.run() 97 | ... print "Transaction has started" 98 | 99 | 100 | .. _chaining: 101 | 102 | Chaining Transactions 103 | --------------------- 104 | 105 | It is possible to chain transactions. This allows to add a simple 106 | dependency relation, e.g. you want to add a new repository, update 107 | the cache and finally install a new package. 108 | 109 | >>> client = aptkit.client.AptClient() 110 | >>> trans1 = client.add_repository("deb", [...]) 111 | >>> trans2 = client.update_cache() 112 | >>> trans3 = client.install_packages(["new-package"]) 113 | >>> trans2.run_after(trans1) 114 | >>> trans3.run_after(trans2) 115 | >>> trans1.run() 116 | 117 | If a transaction in a chain fails all other ones which follow will 118 | fail too with the :data:`enums.EXIT_PREVIOUS_FAILED` exit state. 119 | 120 | Class Reference 121 | --------------- 122 | 123 | .. autoclass:: aptkit.client.AptClient 124 | :members: 125 | 126 | .. autoclass:: aptkit.client.AptTransaction 127 | :members: 128 | 129 | Function Reference 130 | ------------------ 131 | 132 | .. autofunction:: aptkit.client.get_transaction 133 | 134 | .. autofunction:: aptkit.client.get_aptkit 135 | -------------------------------------------------------------------------------- /data/org.aptkit.7: -------------------------------------------------------------------------------- 1 | .\" groff -man -Tascii foo.1 2 | .TH org.aptkit 7 "December 2009" "aptkit" "D-Bus Interface" 3 | .SH NAME 4 | org.aptkit \- the main interface of aptkit 5 | .SH SYNOPSIS 6 | The daemon is accessed through the D-Bus object at 7 | .IR /org/aptkit . 8 | Which provides the following interface. 9 | .SH DESCRIPTION 10 | .SS METHODS 11 | .TP 12 | .B AddRepository 13 | .BI "AddRepository\t(in 's' " type "," 14 | .br 15 | .BI "\t\tin 's' " uri "," 16 | .br 17 | .BI "\t\tin 's' " dist "," 18 | .br 19 | .BI "\t\tin 'as' " comps "," 20 | .br 21 | .BI "\t\tin 's' " comment "," 22 | .br 23 | .BI "\t\tin 's' " sourcesfile ")" 24 | .RS 25 | .PP 26 | Add a new repository to the sources list file. The repository has to define the type (e.g. deb or deb-src), uri (e.g. http://archive.debian.org/debian), the distribution (e.g. stable) and a list of components (e.g. main). 27 | .PP 28 | Optionally a comment and an alternative sources.list file can be specified. 29 | .RE 30 | .TP 31 | .B EnableDistroComponent 32 | .BI "EnableDistroComponent\t(in 's' " comp ")" 33 | .RS 34 | .PP 35 | Enable a component for all distro repositories, e.g. main or universe. 36 | .RE 37 | .TP 38 | .B GetAtiveTransactions 39 | .BI "GetActiveTransactions\t(out 'as' " tids ) 40 | .RS 41 | .PP 42 | Return an array of the currently queued transactions. 43 | .RE 44 | .TP 45 | .B Quit 46 | .br 47 | .BI "Quit\t()" 48 | .RS 49 | .PP 50 | Request the shutdown of the daemon. The daemon will finish a currently running transaction before. 51 | .RE 52 | .SS TRANSACTION BASED METHODS 53 | The following methods are handled by transactions. By calling the methods a new transaction will be created for the task. The return value of the method is the D-Bus object path of the corresponding transaction. The transaction can be modified, to use an http proxy or to run in a specific language by setting the corresponding properties on the 54 | .BR org.aptkit.transaction (7) 55 | interface. Afterwards the transaction has to be queued for processing by calling its Run method on the 56 | .BR org.aptkit.transaction (7) 57 | interface. 58 | .TP 59 | .B UpdateCache 60 | .BI "UpdateCache\t(out 's' " tid ) 61 | .RS 62 | .PP 63 | Return the id of a newly created transaction which will fetch the latest meta data from the repositories and rebuild the cache of available and installed packages. 64 | .RE 65 | .TP 66 | .B UpdateCachePartially 67 | .BI "UpdateCachePartially\t(in 's' " sources_list , 68 | .br 69 | .BI "\t\tout 's' " tid ) 70 | .RS 71 | .PP 72 | Return the id of a newly created transaction which will fetch the latest meta data from the repositories specified in the given sources.list snippet only and rebuild the cache of available and installed packages. 73 | .RE 74 | .TP 75 | .B InstallPackages 76 | .BI "InstallPackages\t(in 'as' " package_names , 77 | .br 78 | .BI "\t\tout 's' " tid ) 79 | .RS 80 | .PP 81 | Return the id of a newly created transaction which will install the packages 82 | of the given names. 83 | .PP 84 | Optionally the to be installed version can be specified by 85 | appending a "=" and the version to the package name, e.g. "xterm=256-1". 86 | .RE 87 | .TP 88 | .B InstallFile 89 | .BI "InstallFile\t(in 's' " path , 90 | .br 91 | .BI "\t\tout 's' " tid ) 92 | .RS 93 | .PP 94 | Return the id of a newly create transaction which will install a local *.deb 95 | package file at the given 96 | .IR path . 97 | .RE 98 | .TP 99 | .B UpgradePackages 100 | .BI "UpgradePackages\t(in 'as' " package_names , 101 | .br 102 | .BI "\t\t\tout 's' " tid ) 103 | .RS 104 | .PP 105 | Return the id of a newly created transaction which will upgrade the packages of the given names to their latest version. 106 | .PP 107 | Optionally the to be installed version can be specified by 108 | appending a "=" and the version to the package name, e.g. "xterm=256-1". 109 | .RE 110 | .TP 111 | .B RemovePackages 112 | .BI "RemovePackages\t(in 'as' " package_names , 113 | .br 114 | .BI "\t\t\tout 's' " tid ) 115 | .RS 116 | .PP 117 | Return the id of a newly created transaction which will remove the packages of the given names. 118 | .PP 119 | Optionally the version of the to be removed packages can be specified by 120 | appending a "=" and the version to the package name, e.g. "xterm=256-1". 121 | .RE 122 | .TP 123 | .B UpgradeSystem 124 | .BI "UpgradeSystem\t(in 'b' " safe_mode , 125 | .br 126 | .BI "\t\tout 's' " tid ) 127 | .RS 128 | .PP 129 | Return the id of a newly created transaction which will upgrade the whole system. 130 | .PP 131 | If in safe mode only already installed packages will be updated. Updates which require to remove installed packages or to install additional packages will be skipped. 132 | .RE 133 | .TP 134 | .B CommitPackages 135 | .BI "CommitPackages\t(in 'as' " install , 136 | .br 137 | .BI "\t\t\tin 'as' " reinstall, 138 | .br 139 | .BI "\t\t\tin 'as' " remove, 140 | .br 141 | .BI "\t\t\tin 'as' " purge, 142 | .br 143 | .BI "\t\t\tin 'as' " upgrade , 144 | .br 145 | .BI "\t\t\tin 'as' " downgrade , 146 | .br 147 | .BI "\t\t\tout 's' " tid ) 148 | .RS 149 | .PP 150 | Return the id of a newly created transaction which will perform a complex install/removal task at once. 151 | .IR Install ", " reinstall ", " remove ", " purge " and " upgrade 152 | are lists of package names. 153 | .PP 154 | Optionally the version of the to be removed packages or the version of the 155 | package which should be installed can be specified by 156 | appending a "=" and the version to the package name, e.g. "xterm=256-1". 157 | .RE 158 | .TP 159 | .B FixBrokenDepends 160 | .BI "FixBrokenDepends\t(out 's' " tid ) 161 | .RS 162 | .PP 163 | Return the id of a newly created transaction which will try to resolve unsatisfied dependencies by installing required packages or removing conflicting ones. 164 | .RE 165 | .TP 166 | .B FixIncompleteInstall 167 | .BI "FixIncompleteInstall\t(out 's' " tid ) 168 | .RS 169 | .PP 170 | Return the id of a newly created transaction which will try to complete previously failed installations by calling "dpkg --configure -a". 171 | .RE 172 | .SS SIGNALS 173 | .TP 174 | .B ActiveTransactionsChanged 175 | .BI "ActiveTransactionsChanged\t('s' " active , 176 | .br 177 | .BI "\t\t\t\t'as' " queued ) 178 | .RS 179 | .PP 180 | The signal is used to report changes of the currently running or queued 181 | transactions. If there's any active transaction active will be an empty 182 | string. 183 | .RE 184 | .SH SEE ALSO 185 | .BR org.aptkit.transaction (7), 186 | .BR aptk (2), 187 | .BR aptkcon (2) 188 | -------------------------------------------------------------------------------- /aptkit/lock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Handles the apt system lock""" 4 | # Copyright (C) 2010 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("LockFailedError", "system") 23 | 24 | import fcntl 25 | import os 26 | import struct 27 | 28 | import apt_pkg 29 | from gi.repository import GLib 30 | 31 | from aptkit import enums 32 | from aptkit.errors import TransactionCancelled 33 | 34 | 35 | class LockFailedError(Exception): 36 | 37 | """The locking of file failed.""" 38 | 39 | def __init__(self, flock, process=None): 40 | """Return a new LockFailedError instance. 41 | 42 | Keyword arguments: 43 | flock -- the path of the file lock 44 | process -- the process which holds the lock or None 45 | """ 46 | msg = "Could not acquire lock on %s." % flock 47 | if process: 48 | msg += " The lock is held by %s." % process 49 | Exception.__init__(self, msg) 50 | self.flock = flock 51 | self.process = process 52 | 53 | 54 | class FileLock(object): 55 | 56 | """Represents a file lock.""" 57 | 58 | def __init__(self, path): 59 | self.path = path 60 | self.fd = None 61 | 62 | @property 63 | def locked(self): 64 | return self.fd is not None 65 | 66 | def acquire(self): 67 | """Return the file descriptor of the lock file or raise 68 | LockFailedError if the lock cannot be obtained. 69 | """ 70 | if self.fd: 71 | return self.fd 72 | fd_lock = apt_pkg.get_lock(self.path) 73 | if fd_lock < 0: 74 | process = get_locking_process_name(self.path) 75 | raise LockFailedError(self.path, process) 76 | else: 77 | self.fd = fd_lock 78 | return fd_lock 79 | 80 | def release(self): 81 | """Relase the lock.""" 82 | if self.fd: 83 | os.close(self.fd) 84 | self.fd = None 85 | 86 | 87 | def get_locking_process_name(lock_path): 88 | """Return the name of a process which holds a lock. It will be None if 89 | the name cannot be retrivied. 90 | """ 91 | try: 92 | fd_lock_read = open(lock_path, "r") 93 | except IOError: 94 | return None 95 | else: 96 | # Get the pid of the locking application 97 | flk = struct.pack('hhQQi', fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0) 98 | flk_ret = fcntl.fcntl(fd_lock_read, fcntl.F_GETLK, flk) 99 | pid = struct.unpack("hhQQi", flk_ret)[4] 100 | # Get the command of the pid 101 | try: 102 | with open("/proc/%s/status" % pid, "r") as fd_status: 103 | try: 104 | for key, value in (line.split(":") for line in 105 | fd_status.readlines()): 106 | if key == "Name": 107 | return value.strip() 108 | except Exception: 109 | return None 110 | except IOError: 111 | return None 112 | finally: 113 | fd_lock_read.close() 114 | return None 115 | 116 | apt_pkg.init() 117 | 118 | #: The lock for dpkg status file 119 | _status_dir = os.path.dirname(apt_pkg.config.find_file("Dir::State::status")) 120 | status_lock = FileLock(os.path.join(_status_dir, "lock")) 121 | frontend_lock = FileLock(os.path.join(_status_dir, "lock-frontend")) 122 | 123 | #: The lock for the package archive 124 | _archives_dir = apt_pkg.config.find_dir("Dir::Cache::Archives") 125 | archive_lock = FileLock(os.path.join(_archives_dir, "lock")) 126 | 127 | #: The lock for the repository indexes 128 | lists_lock = FileLock(os.path.join( 129 | apt_pkg.config.find_dir("Dir::State::lists"), "lock")) 130 | 131 | 132 | def acquire(): 133 | """Acquire an exclusive lock for the package management system.""" 134 | try: 135 | for lock in frontend_lock, status_lock, archive_lock, lists_lock: 136 | if not lock.locked: 137 | lock.acquire() 138 | except: 139 | release() 140 | raise 141 | 142 | os.environ['DPKG_FRONTEND_LOCKED'] = '1' 143 | 144 | def release(): 145 | """Release an exclusive lock for the package management system.""" 146 | for lock in lists_lock, archive_lock, status_lock, frontend_lock: 147 | lock.release() 148 | 149 | try: 150 | del os.environ['DPKG_FRONTEND_LOCKED'] 151 | except KeyError: 152 | pass 153 | 154 | 155 | def wait_for_lock(trans, alt_lock=None): 156 | """Acquire the system lock or the optionally given one. If the lock 157 | cannot be obtained pause the transaction in the meantime. 158 | 159 | :param trans: the transaction 160 | :param lock: optional alternative lock 161 | """ 162 | def watch_lock(): 163 | """Helper to unpause the transaction if the lock can be obtained. 164 | 165 | Keyword arguments: 166 | trans -- the corresponding transaction 167 | alt_lock -- alternative lock to the system lock 168 | """ 169 | try: 170 | if alt_lock: 171 | alt_lock.acquire() 172 | else: 173 | acquire() 174 | except LockFailedError: 175 | return True 176 | trans.paused = False 177 | return True 178 | 179 | try: 180 | if alt_lock: 181 | alt_lock.acquire() 182 | else: 183 | acquire() 184 | except LockFailedError as error: 185 | trans.paused = True 186 | trans.status = enums.STATUS_WAITING_LOCK 187 | if error.process: 188 | # TRANSLATORS: %s is the name of a package manager 189 | msg = trans.gettext("Waiting for %s to exit") 190 | trans.status_details = msg % error.process 191 | lock_watch = GLib.timeout_add_seconds(3, watch_lock) 192 | while trans.paused and not trans.cancelled: 193 | GLib.main_context_default().iteration() 194 | GLib.source_remove(lock_watch) 195 | if trans.cancelled: 196 | raise TransactionCancelled() 197 | 198 | # vim:ts=4:sw=4:et 199 | -------------------------------------------------------------------------------- /aptkit/simpleclient.py: -------------------------------------------------------------------------------- 1 | import gi 2 | gi.require_version('Gtk', '3.0') 3 | gi.require_version('XApp', '1.0') 4 | from gi.repository import Gtk, XApp 5 | 6 | import apt 7 | import aptkit.client 8 | import aptkit.enums 9 | import aptkit.errors 10 | import aptkit.gtk3widgets 11 | 12 | class SimpleAPTClient(object): 13 | 14 | def __init__(self, parent_window=None): 15 | self.parent_window = parent_window 16 | self.progress_callback = None 17 | self.finished_callback = None 18 | self.error_callback = None 19 | self.cancelled_callback = None 20 | 21 | def set_progress_callback(self, progress_callback): 22 | self.progress_callback = progress_callback 23 | 24 | def set_finished_callback(self, finished_callback): 25 | self.finished_callback = finished_callback 26 | 27 | def set_error_callback(self, error_callback): 28 | self.error_callback = error_callback 29 | 30 | def set_cancelled_callback(self, cancelled_callback): 31 | self.cancelled_callback = cancelled_callback 32 | 33 | def update_cache(self): 34 | client = aptkit.client.AptClient() 35 | update_transaction = client.update_cache() 36 | self._run_transaction(update_transaction) 37 | 38 | def install_file(self, path): 39 | client = aptkit.client.AptClient() 40 | client.install_file(path, force=True, wait=False, reply_handler=self._simulate_trans, error_handler=self._on_error) 41 | 42 | def install_packages(self, packages, use_apt_resolver=True): 43 | if use_apt_resolver: 44 | # Use the resolver from python-apt 45 | # it works better in complex scenarios 46 | # mark the packages as mark_install 47 | cache = apt.Cache() 48 | for name in packages: 49 | try: 50 | pkg = cache[name] 51 | pkg.mark_install() 52 | except: 53 | print(f"Package {name} not found in the cache!") 54 | # then find out all the resulting installations 55 | # via cache.get_changes() 56 | changes = cache.get_changes() 57 | for pkg in changes: 58 | if pkg.marked_install and pkg.name not in packages: 59 | packages.append(pkg.name) 60 | 61 | client = aptkit.client.AptClient() 62 | client.install_packages(packages, reply_handler=self._simulate_trans, error_handler=self._on_error) 63 | 64 | def remove_packages(self, packages): 65 | client = aptkit.client.AptClient() 66 | client.remove_packages(packages, reply_handler=self._simulate_trans, error_handler=self._on_error) 67 | 68 | def purge_packages(self, packages): 69 | self.commit_changes(purge=versioned_packages) 70 | 71 | def downgrade_packages(self, versioned_packages): 72 | self.commit_changes(downgrade=versioned_packages) 73 | 74 | def commit_changes(self, install=None, reinstall=None, remove=None, purge=None, upgrade=None, downgrade=None): 75 | client = aptkit.client.AptClient() 76 | client.commit_packages(install=install, reinstall=reinstall, remove=remove, purge=purge, upgrade=upgrade, downgrade=downgrade, 77 | reply_handler=self._simulate_trans, error_handler=self._on_error) 78 | 79 | def _run_transaction(self, transaction): 80 | if self.progress_callback is None: 81 | dia = aptkit.gtk3widgets.AptProgressDialog(transaction, parent=self.parent_window) 82 | dia.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self._on_error) 83 | transaction.connect("finished", self._on_finish) 84 | else: 85 | SimpleAptKitTransaction(transaction, self.progress_callback, self.finished_callback, self.error_callback, self.parent_window) 86 | 87 | def _simulate_trans(self, trans): 88 | trans.simulate(reply_handler=lambda: self._confirm_deps(trans), error_handler=self._on_error) 89 | 90 | def _confirm_deps(self, trans): 91 | try: 92 | if [pkgs for pkgs in trans.dependencies if pkgs]: 93 | dia = aptkit.gtk3widgets.AptConfirmDialog(trans, parent=self.parent_window) 94 | res = dia.run() 95 | dia.hide() 96 | if res != Gtk.ResponseType.OK: 97 | if self.cancelled_callback is not None: 98 | self.cancelled_callback() 99 | return 100 | self._run_transaction(trans) 101 | except Exception as e: 102 | print(e) 103 | 104 | def _on_error(self, error): 105 | if isinstance(error, aptkit.errors.NotAuthorizedError): 106 | if self.cancelled_callback is not None: 107 | self.cancelled_callback() 108 | return 109 | elif not isinstance(error, aptkit.errors.TransactionFailed): 110 | # Catch internal errors of the client 111 | error = aptkit.errors.TransactionFailed(aptkit.enums.ERROR_UNKNOWN, str(error)) 112 | dia = aptkit.gtk3widgets.AptErrorDialog(error) 113 | dia.run() 114 | dia.hide() 115 | 116 | def _on_finish(self, transaction, exit_state): 117 | if self.finished_callback is not None: 118 | self.finished_callback(transaction, exit_state) 119 | 120 | class SimpleAptKitTransaction(): 121 | 122 | def __init__(self, transaction, progress_callback, finished_callback, error_callback, parent_window): 123 | self.progress_callback = progress_callback 124 | self.finished_callback = finished_callback 125 | self.error_callback = error_callback 126 | self.transaction = transaction 127 | self.parent_window = parent_window 128 | transaction.set_debconf_frontend("gnome") 129 | transaction.connect("progress-changed", self.on_transaction_progress) 130 | # transaction.connect("cancellable-changed", self.on_driver_changes_cancellable_changed) 131 | transaction.connect("finished", self.on_transaction_finish) 132 | transaction.connect("error", self.on_transaction_error) 133 | transaction.run() 134 | 135 | def on_transaction_progress(self, transaction, progress): 136 | if self.progress_callback is not None: 137 | self.progress_callback(progress) 138 | if self.parent_window is not None: 139 | XApp.set_window_progress(self.parent_window, progress) 140 | 141 | def on_transaction_error(self, transaction, error_code, error_details): 142 | if self.error_callback is not None: 143 | self.error_callback(error_code, error_details) 144 | if self.parent_window is not None: 145 | XApp.set_window_progress(self.parent_window, 0) 146 | 147 | def on_transaction_finish(self, transaction, exit_state): 148 | if (exit_state == aptkit.enums.EXIT_SUCCESS): 149 | if self.finished_callback is not None: 150 | self.finished_callback(transaction, exit_state) 151 | -------------------------------------------------------------------------------- /doc/examples/gtk3-demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Provides a graphical demo application for aptkit 5 | """ 6 | # Copyright (C) 2008-2009 Sebastian Heinlein 7 | # 8 | # Licensed under the GNU General Public License Version 2 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 | 24 | __author__ = "Sebastian Heinlein " 25 | 26 | from optparse import OptionParser 27 | import logging 28 | 29 | # uncomment to use GTK 2.0 30 | #import gi 31 | #gi.require_version('Gtk', '2.0') 32 | #gi.require_version('Vte', '0.0') 33 | 34 | from gi.repository import GLib, Gtk 35 | 36 | import aptkit.client 37 | from aptkit.enums import * 38 | from aptkit.gtk3widgets import AptErrorDialog, \ 39 | AptConfirmDialog, \ 40 | AptProgressDialog 41 | import aptkit.errors 42 | 43 | 44 | class AptKitDemo(object): 45 | 46 | """Provides a graphical test application.""" 47 | 48 | def _run_transaction(self, transaction): 49 | dia = AptProgressDialog(transaction, parent=self.win) 50 | dia.run(close_on_finished=True, show_error=True, 51 | reply_handler=lambda: True, 52 | error_handler=self._on_error) 53 | 54 | def _simulate_trans(self, trans): 55 | trans.simulate(reply_handler=lambda: self._confirm_deps(trans), 56 | error_handler=self._on_error) 57 | 58 | def _confirm_deps(self, trans): 59 | if [pkgs for pkgs in trans.dependencies if pkgs]: 60 | dia = AptConfirmDialog(trans, parent=self.win) 61 | res = dia.run() 62 | dia.hide() 63 | if res != Gtk.ResponseType.OK: 64 | return 65 | self._run_transaction(trans) 66 | 67 | def _on_error(self, error): 68 | if isinstance(error, aptkit.errors.NotAuthorizedError): 69 | # Silently ignore auth failures 70 | return 71 | elif not isinstance(error, aptkit.errors.TransactionFailed): 72 | # Catch internal errors of the client 73 | error = aptkit.errors.TransactionFailed(ERROR_UNKNOWN, 74 | str(error)) 75 | dia = AptErrorDialog(error) 76 | dia.run() 77 | dia.hide() 78 | 79 | def _on_upgrade_clicked(self, *args): 80 | self.ac.upgrade_system(safe_mode=False, 81 | reply_handler=self._simulate_trans, 82 | error_handler=self._on_error) 83 | 84 | def _on_update_clicked(self, *args): 85 | self.ac.update_cache(reply_handler=self._run_transaction, 86 | error_handler=self._on_error) 87 | 88 | def _on_install_clicked(self, *args): 89 | self.ac.install_packages([self.package], 90 | reply_handler=self._simulate_trans, 91 | error_handler=self._on_error) 92 | 93 | def _on_install_file_clicked(self, *args): 94 | chooser = Gtk.FileChooserDialog(parent=self.win, 95 | action=Gtk.FileChooserAction.OPEN, 96 | buttons=(Gtk.STOCK_CANCEL, 97 | Gtk.ResponseType.CANCEL, 98 | Gtk.STOCK_OPEN, 99 | Gtk.ResponseType.OK)) 100 | chooser.set_local_only(True) 101 | chooser.run() 102 | chooser.hide() 103 | path = chooser.get_filename() 104 | self.ac.install_file(path, reply_handler=self._simulate_trans, 105 | error_handler=self._on_error) 106 | 107 | def _on_remove_clicked(self, *args): 108 | self.ac.remove_packages([self.package], 109 | reply_handler=self._simulate_trans, 110 | error_handler=self._on_error) 111 | 112 | def __init__(self, package): 113 | self.win = Gtk.Window() 114 | self.package = package 115 | self.win.set_resizable(False) 116 | self.win.set_title("Aptkit Demo") 117 | icon_theme = Gtk.IconTheme.get_default() 118 | try: 119 | Gtk.window_set_default_icon(icon_theme.load_icon("aptkit-working", 120 | 32, 0)) 121 | except (GLib.GError, AttributeError): 122 | pass 123 | button_update = Gtk.Button.new_with_mnemonic("_Update") 124 | button_install = Gtk.Button.new_with_mnemonic("_Install %s" % self.package) 125 | button_install_file = Gtk.Button.new_with_mnemonic("Install _file...") 126 | button_remove = Gtk.Button.new_with_mnemonic("_Remove %s" % self.package) 127 | button_upgrade = Gtk.Button.new_with_mnemonic("Upgrade _System") 128 | button_update.connect("clicked", self._on_update_clicked) 129 | button_install.connect("clicked", self._on_install_clicked) 130 | button_install_file.connect("clicked", self._on_install_file_clicked) 131 | button_remove.connect("clicked", self._on_remove_clicked) 132 | button_upgrade.connect("clicked", self._on_upgrade_clicked) 133 | vbox = Gtk.VBox() 134 | vbox.add(button_update) 135 | vbox.add(button_install) 136 | vbox.add(button_install_file) 137 | vbox.add(button_remove) 138 | vbox.add(button_upgrade) 139 | self.win.add(vbox) 140 | self.loop = GLib.MainLoop() 141 | self.win.connect("delete-event", lambda w, e: self.loop.quit()) 142 | self.win.show_all() 143 | self.ac = aptkit.client.AptClient() 144 | 145 | def run(self): 146 | self.loop.run() 147 | 148 | 149 | def main(): 150 | parser = OptionParser() 151 | parser.add_option("-p", "--package", default="cw", action="store", 152 | dest="package", 153 | help="Use this package for installation and removal") 154 | parser.add_option("-d", "--debug", default=False, action="store_true", 155 | help="Verbose debugging") 156 | options, args = parser.parse_args() 157 | if options.debug: 158 | logging.basicConfig(level=logging.DEBUG) 159 | 160 | demo = AptKitDemo(options.package) 161 | demo.run() 162 | 163 | if __name__ == "__main__": 164 | main() 165 | 166 | # vim:ts=4:sw=4:et 167 | -------------------------------------------------------------------------------- /aptkit/debconf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Integration of debconf on the client side 5 | 6 | Provides the DebconfProxy class which allows to run the debconf frontend 7 | as normal user by connecting to the root running debconf through the 8 | socket of the passthrough frontend. 9 | """ 10 | # Copyright (C) 2009 Sebastian Heinlein 11 | # Copyright (C) 2009 Michael Vogt 12 | # 13 | # This program is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation; either version 2 of the License, or 16 | # any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License along 24 | # with this program; if not, write to the Free Software Foundation, Inc., 25 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 26 | 27 | __all__ = ("DebconfProxy",) 28 | 29 | import copy 30 | import logging 31 | import os 32 | import os.path 33 | import socket 34 | import subprocess 35 | import tempfile 36 | import sys 37 | 38 | from gi.repository import GLib 39 | 40 | log = logging.getLogger("AptClient.DebconfProxy") 41 | 42 | 43 | class DebconfProxy(object): 44 | 45 | """The DebconfProxy class allows to run the debconf frontend 46 | as normal user by connecting to the root debconf through the socket of the 47 | passthrough frontend. 48 | """ 49 | 50 | def __init__(self, frontend="gnome", socket_path=None): 51 | """Initialize a new DebconfProxy instance. 52 | 53 | Keyword arguments: 54 | frontend -- the to be used debconf frontend (defaults to gnome) 55 | socket_path -- the path to the socket of the passthrough frontend. 56 | Will be created if not specified 57 | """ 58 | self.socket_path = socket_path 59 | self.temp_dir = None 60 | if socket_path is None: 61 | self.temp_dir = tempfile.mkdtemp(prefix="aptkit-") 62 | self.socket_path = os.path.join(self.temp_dir, "debconf.socket") 63 | log.debug("debconf socket: %s" % self.socket_path) 64 | self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 65 | self.socket.bind(self.socket_path) 66 | self.frontend = frontend 67 | self._listener_id = None 68 | self._active_conn = None 69 | self._watch_ids = [] 70 | 71 | def _get_debconf_env(self): 72 | """Returns a dictonary of the environment variables required by 73 | the debconf frontend. 74 | """ 75 | env = copy.copy(os.environ) 76 | env["DEBCONF_DB_REPLACE"] = "configdb" 77 | env["DEBCONF_DB_OVERRIDE"] = "Pipe{infd:none outfd:none}" 78 | env["DEBIAN_FRONTEND"] = self.frontend 79 | if log.level == logging.DEBUG: 80 | env["DEBCONF_DEBUG"] = "." 81 | return env 82 | 83 | def start(self): 84 | """Start listening on the socket.""" 85 | logging.debug("debconf.start()") 86 | self.socket.listen(1) 87 | self._listener_id = GLib.io_add_watch( 88 | self.socket, GLib.PRIORITY_DEFAULT_IDLE, 89 | GLib.IO_IN, self._accept_connection) 90 | 91 | def stop(self): 92 | """Stop listening on the socket.""" 93 | logging.debug("debconf.stop()") 94 | self.socket.close() 95 | if self._listener_id is not None: 96 | GLib.source_remove(self._listener_id) 97 | self._listener_id = None 98 | if self.temp_dir: 99 | try: 100 | os.remove(self.socket_path) 101 | os.rmdir(self.temp_dir) 102 | except OSError: 103 | pass 104 | 105 | def _accept_connection(self, socket, condition): 106 | if self._active_conn: 107 | log.debug("Delaying connection") 108 | return True 109 | conn, addr = socket.accept() 110 | self._active_conn = conn 111 | mask = GLib.IO_IN | GLib.IO_ERR | GLib.IO_HUP | GLib.IO_NVAL 112 | self.helper = subprocess.Popen(["debconf-communicate"], 113 | stdin=subprocess.PIPE, 114 | stdout=subprocess.PIPE, 115 | env=self._get_debconf_env()) 116 | GLib.io_add_watch(conn, GLib.PRIORITY_HIGH_IDLE, 117 | mask, self._copy_conn, self.helper.stdin) 118 | GLib.io_add_watch(self.helper.stdout, GLib.PRIORITY_HIGH_IDLE, 119 | mask, self._copy_stdout, conn) 120 | return True 121 | 122 | def _copy_stdout(self, source, condition, conn): 123 | """Callback to copy data from the stdout of debconf-communicate to 124 | the passthrough frontend.""" 125 | logging.debug("_copy_stdout") 126 | try: 127 | debconf_data = source.readline() 128 | if debconf_data: 129 | log.debug("From debconf: %s", debconf_data) 130 | conn.send(debconf_data) 131 | return True 132 | except (socket.error, IOError) as error: 133 | log.debug(error) 134 | # error, stop listening 135 | log.debug("Stop reading from stdout") 136 | self.helper.stdout.close() 137 | self._active_conn.close() 138 | self._active_conn = None 139 | return False 140 | 141 | def _copy_conn(self, source, condition, stdin): 142 | """Callback to copy data from the passthrough frontend to stdin of 143 | debconf-communicate.""" 144 | logging.debug("_copy_conn") 145 | try: 146 | socket_data = source.recv(1024) 147 | if socket_data: 148 | log.debug("From socket: %s", socket_data) 149 | stdin.write(socket_data) 150 | stdin.flush() 151 | return True 152 | except (socket.error, IOError) as error: 153 | log.debug(error) 154 | # error, stop listening 155 | log.debug("Stop reading from conn") 156 | self.helper.stdin.close() 157 | return False 158 | 159 | 160 | def _test(): 161 | """Run the DebconfProxy from the command line for testing purposes. 162 | 163 | You have to execute the following commands before in a separate terminal: 164 | $ echo "fset debconf/frontend seen false" | debconf-communicate 165 | $ export DEBCONF_PIPE=/tmp/debconf.socket 166 | $ dpkg-reconfigure debconf -f passthrough 167 | """ 168 | logging.basicConfig(level=logging.DEBUG) 169 | socket_path = "/tmp/debconf.socket" 170 | if os.path.exists(socket_path): 171 | os.remove(socket_path) 172 | proxy = DebconfProxy("gnome", socket_path) 173 | proxy.start() 174 | loop = GLib.MainLoop() 175 | loop.run() 176 | 177 | if __name__ == "__main__": 178 | _test() 179 | 180 | # vim:ts=4:sw=4:et 181 | -------------------------------------------------------------------------------- /data/org.aptkit.policy.in: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | AptKit 8 | https://github.com/linuxmint/aptkit/ 9 | package-x-generic 10 | 11 | 12 | 13 | List keys of trusted vendors 14 | 15 | 16 | To view the list of trusted keys, you need to authenticate. 17 | 18 | 19 | auth_admin 20 | auth_admin 21 | auth_admin_keep 22 | 23 | 24 | 25 | 26 | 27 | Remove downloaded package files 28 | 29 | 30 | To clean downloaded package files, you need to authenticate. 31 | 32 | 33 | auth_admin 34 | auth_admin 35 | yes 36 | 37 | 38 | 39 | 40 | 41 | Change software configuration 42 | 43 | 44 | To change software settings, you need to authenticate. 45 | 46 | 47 | auth_admin 48 | auth_admin 49 | auth_admin_keep 50 | 51 | 52 | 53 | 54 | 55 | Change software repository 56 | 57 | 58 | To change software repository settings, you need to authenticate. 59 | 60 | 61 | auth_admin 62 | auth_admin 63 | auth_admin_keep 64 | 65 | 66 | 67 | 68 | 69 | Install package file 70 | 71 | 72 | To install this package, you need to authenticate. 73 | 74 | 75 | auth_admin 76 | auth_admin 77 | auth_admin_keep 78 | 79 | 80 | 81 | 82 | 83 | Update package information 84 | 85 | 86 | To update the software catalog, you need to authenticate. 87 | 88 | 89 | auth_admin 90 | auth_admin 91 | yes 92 | 93 | 94 | 95 | 96 | 97 | Install or remove packages 98 | 99 | 100 | To install or remove software, you need to authenticate. 101 | 102 | 103 | auth_admin 104 | auth_admin 105 | auth_admin_keep 106 | 107 | 108 | 109 | 110 | 120 | 121 | Install software from a high-trust whitelisted repository. 122 | 123 | 124 | To install software, you need to authenticate. 125 | 126 | 127 | auth_admin 128 | auth_admin 129 | auth_admin_keep 130 | 131 | 132 | 133 | 134 | 139 | 140 | Add a new repository and install packages from it 141 | 142 | 143 | To install software from a new source, you need to authenticate. 144 | 145 | 146 | auth_admin 147 | auth_admin 148 | auth_admin_keep 149 | 150 | 151 | 152 | 153 | 161 | 162 | Add a new repository of purchased software and install packages from it 163 | 164 | 165 | To install purchased software, you need to authenticate. 166 | 167 | 168 | auth_admin 169 | auth_admin 170 | auth_admin_keep 171 | 172 | 173 | 174 | 175 | 176 | Upgrade packages 177 | 178 | To install updated software, you need to authenticate. 179 | 180 | auth_admin 181 | auth_admin 182 | auth_admin_keep 183 | 184 | 185 | 186 | 187 | 188 | Cancel the task of another user 189 | 190 | 191 | To cancel someone else's software changes, you need to authenticate. 192 | 193 | 194 | auth_admin 195 | auth_admin 196 | auth_admin 197 | 198 | 199 | 200 | 201 | 202 | Set a proxy for software downloads 203 | 204 | 205 | To use a proxy server for downloading software, you need to authenticate. 206 | 207 | 208 | auth_admin 209 | auth_admin 210 | auth_admin 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /aptkit/policykit1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Provides access to PolicyKit privilege mangement using gdefer Deferreds.""" 4 | # Copyright (C) 2008-2009 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("check_authorization_by_name", "check_authorization_by_pid", 23 | "get_pid_from_dbus_name", "get_uid_from_dbus_name", 24 | "CHECK_AUTH_ALLOW_USER_INTERACTION", "CHECK_AUTH_NONE", 25 | "PK_ACTION_CANCEL_FOREIGN", 26 | "PK_ACTION_CHANGE_REPOSITORY", 27 | "PK_ACTION_CHANGE_CONIFG", 28 | "PK_ACTION_INSTALL_FILE", 29 | "PK_ACTION_INSTALL_OR_REMOVE_PACKAGES", 30 | "PK_ACTION_INSTALL_PACKAGES_FROM_NEW_REPO", 31 | "PK_ACTION_INSTALL_PACKAGES_FROM_HIGH_TRUST_REPO", 32 | "PK_ACTION_INSTALL_PURCHASED_PACKAGES", 33 | "PK_ACTION_UPDATE_CACHE", "PK_ACTION_UPGRADE_PACKAGES", 34 | "PK_ACTION_SET_PROXY", "PK_ACTION_CLEAN") 35 | 36 | import dbus 37 | 38 | from defer import Deferred, inline_callbacks, return_value 39 | from .errors import NotAuthorizedError, AuthorizationFailed 40 | 41 | PK_ACTION_INSTALL_OR_REMOVE_PACKAGES = ( 42 | "org.aptkit.install-or-remove-packages") 43 | PK_ACTION_INSTALL_PURCHASED_PACKAGES = ( 44 | "org.aptkit.install-purchased-packages") 45 | PK_ACTION_INSTALL_PACKAGES_FROM_NEW_REPO = ( 46 | "org.aptkit.install-packages-from-new-repo") 47 | PK_ACTION_INSTALL_PACKAGES_FROM_HIGH_TRUST_REPO = ( 48 | "org.aptkit.install-packages.high-trust-repo") 49 | PK_ACTION_INSTALL_FILE = "org.aptkit.install-file" 50 | PK_ACTION_UPGRADE_PACKAGES = "org.aptkit.upgrade-packages" 51 | PK_ACTION_UPDATE_CACHE = "org.aptkit.update-cache" 52 | PK_ACTION_CANCEL_FOREIGN = "org.aptkit.cancel-foreign" 53 | PK_ACTION_CHANGE_REPOSITORY = "org.aptkit.change-repository" 54 | PK_ACTION_CHANGE_CONFIG = "org.aptkit.change-config" 55 | PK_ACTION_SET_PROXY = "org.aptkit.set-proxy" 56 | PK_ACTION_CLEAN = "org.aptkit.clean" 57 | 58 | CHECK_AUTH_NONE = 0 59 | CHECK_AUTH_ALLOW_USER_INTERACTION = 1 60 | 61 | 62 | def check_authorization_by_name(dbus_name, action_id, timeout=86400, bus=None, 63 | flags=None): 64 | """Check if the given sender is authorized for the specified action. 65 | 66 | If the sender is not authorized raise NotAuthorizedError. 67 | 68 | Keyword arguments: 69 | dbus_name -- D-Bus name of the subject 70 | action_id -- the PolicyKit policy name of the action 71 | timeout -- time in seconds for the user to authenticate 72 | bus -- the D-Bus connection (defaults to the system bus) 73 | flags -- optional flags to control the authentication process 74 | """ 75 | subject = ("system-bus-name", {"name": dbus_name}) 76 | return _check_authorization(subject, action_id, timeout, bus, flags) 77 | 78 | 79 | def check_authorization_by_pid(pid, action_id, timeout=86400, bus=None, 80 | flags=None): 81 | """Check if the given process is authorized for the specified action. 82 | 83 | If the sender is not authorized raise NotAuthorizedError. 84 | 85 | Keyword arguments: 86 | pid -- id of the process 87 | action_id -- the PolicyKit policy name of the action 88 | timeout -- time in seconds for the user to authenticate 89 | bus -- the D-Bus connection (defaults to the system bus) 90 | flags -- optional flags to control the authentication process 91 | """ 92 | subject = ("unix-process", {"pid": pid}) 93 | return _check_authorization(subject, action_id, timeout, bus, flags) 94 | 95 | 96 | def _check_authorization(subject, action_id, timeout, bus, flags=None): 97 | def policykit_done(xxx_todo_changeme): 98 | (authorized, challenged, auth_details) = xxx_todo_changeme 99 | if authorized: 100 | deferred.callback(auth_details) 101 | elif challenged: 102 | deferred.errback(AuthorizationFailed(subject, action_id)) 103 | else: 104 | deferred.errback(NotAuthorizedError(subject, action_id)) 105 | if not bus: 106 | bus = dbus.SystemBus() 107 | # Set the default flags 108 | if flags is None: 109 | flags = CHECK_AUTH_ALLOW_USER_INTERACTION 110 | deferred = Deferred() 111 | pk = bus.get_object("org.freedesktop.PolicyKit1", 112 | "/org/freedesktop/PolicyKit1/Authority") 113 | details = {} 114 | pk.CheckAuthorization( 115 | subject, action_id, details, flags, "", 116 | dbus_interface="org.freedesktop.PolicyKit1.Authority", 117 | timeout=timeout, 118 | reply_handler=policykit_done, 119 | error_handler=deferred.errback) 120 | return deferred 121 | 122 | 123 | def get_pid_from_dbus_name(dbus_name, bus=None): 124 | """Return a deferred that gets the id of process owning the given 125 | system D-Bus name. 126 | """ 127 | if not bus: 128 | bus = dbus.SystemBus() 129 | deferred = Deferred() 130 | bus_obj = bus.get_object("org.freedesktop.DBus", 131 | "/org/freedesktop/DBus/Bus") 132 | bus_obj.GetConnectionUnixProcessID(dbus_name, 133 | dbus_interface="org.freedesktop.DBus", 134 | reply_handler=deferred.callback, 135 | error_handler=deferred.errback) 136 | return deferred 137 | 138 | 139 | @inline_callbacks 140 | def get_uid_from_dbus_name(dbus_name, bus=None): 141 | """Return a deferred that gets the uid of the user owning the given 142 | system D-Bus name. 143 | """ 144 | if not bus: 145 | bus = dbus.SystemBus() 146 | pid = yield get_pid_from_dbus_name(dbus_name, bus) 147 | with open("/proc/%s/status" % pid) as proc: 148 | values = [v for v in proc.readlines() if v.startswith("Uid:")] 149 | uid = int(values[0].split()[1]) 150 | return_value(uid) 151 | 152 | 153 | @inline_callbacks 154 | def get_proc_info_from_dbus_name(dbus_name, bus=None): 155 | """Return a deferred that gets the pid, the uid of the user owning the 156 | given system D-Bus name and its command line. 157 | """ 158 | if not bus: 159 | bus = dbus.SystemBus() 160 | pid = yield get_pid_from_dbus_name(dbus_name, bus) 161 | with open("/proc/%s/status" % pid) as proc: 162 | lines = proc.readlines() 163 | uid_values = [v for v in lines if v.startswith("Uid:")] 164 | gid_values = [v for v in lines if v.startswith("Gid:")] 165 | # instead of ", encoding='utf8'" we use the "rb"/decode() here for 166 | # py2 compatibility 167 | with open("/proc/%s/cmdline" % pid, "rb") as cmdline_file: 168 | cmdline = cmdline_file.read().decode("utf-8") 169 | uid = int(uid_values[0].split()[1]) 170 | gid = int(gid_values[0].split()[1]) 171 | return_value((pid, uid, gid, cmdline)) 172 | 173 | # vim:ts=4:sw=4:et 174 | -------------------------------------------------------------------------------- /data/org.aptkit.transaction.7: -------------------------------------------------------------------------------- 1 | .\" groff -man -Tascii foo.1 2 | .TH org.aptkit.transaction 7 "December 2009" "aptkit" "D-Bus Interface" 3 | .SH NAME 4 | org.aptkit.transaction \- the main interface of an aptkit transaction 5 | .SH SYNOPSIS 6 | Each transaction is represented as an unique D-Bus object, e.g. at the D-Bus path 7 | .IR /or/debian/apt/transaction/12123-123-123213 . 8 | Which provides the following interface. 9 | .SH DESCRIPTION 10 | .SS METHODS 11 | .TP 12 | .B ProvideMedium 13 | .BI "ProvideMedium\t(in 's' " medium ) 14 | .RS 15 | .PP 16 | Continue a paused transaction which is waiting for the given 17 | .IR medium . 18 | .PP 19 | If a media change is required to e.g. install packages from a CD/DVD 20 | the transaction will be paused and the RequiredMedium property changed 21 | to an array of the 22 | .IR "medium label" " and its " "mount point" . 23 | Furthermore the MediumRequired signal will be emitted. 24 | .RE 25 | .TP 26 | .B ResolveConfigFileConflict 27 | .BI "ResolveConfigFilePrompt\t(in 's' " config , 28 | .br 29 | .BI "\t\t\t\tin 's' " answer ) 30 | .RS 31 | .PP 32 | Continue a paused transaction which is waiting for an answer to handle 33 | a configuration file conflict. 34 | .I Config 35 | is the path to the current configuration file and 36 | .I answer 37 | can be either "replace" or "keep". 38 | .PP 39 | If a later config file is shipped in a package which overrides some 40 | user modifications the transaction will be paused and the ConfigFileConflict 41 | property of the transaction will be set to an array of the old and new 42 | configuration file path. The later one is supposed to replace the former one. 43 | Furthermore the ConfigFileConflict signal will be emitted. 44 | .RE 45 | .TP 46 | .B Simulate 47 | .BI "Simulate\t()" 48 | .RS 49 | .PP 50 | Simulate the transaction and calculate dependencies, required disk space and 51 | download size. If a transaction would fail the corresponding error will be 52 | raised. 53 | .RE 54 | .TP 55 | .B Run 56 | .BI "Run\t()" 57 | .RS 58 | .PP 59 | Queue the transaction for processing. Afterwards there cannot be made any further changes to the transaction, e.g. modifying the http proxy. 60 | .RE 61 | .TP 62 | .B Cancel 63 | .BI "Cancel\t()" 64 | .RS 65 | .PP 66 | Cancel the transaction if possible. 67 | .RE 68 | .SS SIGNALS 69 | .TP 70 | .B Finished 71 | .BI "Finished\t('s' " exit_state ) 72 | .RS 73 | .PP 74 | The signal will be emitted when the transaction has been processed. The 75 | .I exit_state 76 | indicates if the transaction was completed, cancelled or failed. 77 | .RE 78 | .TP 79 | .B MediumRequired 80 | .BI "MediumRequired\t('s' " label , 81 | .br 82 | .BI "\t\t\t's' " mount_point ) 83 | .RS 84 | .PP 85 | The signal will be emitted if the transaction has been paused and waits for a media change to install packages. 86 | .RE 87 | .TP 88 | .B ConfigFileConflict 89 | .BI "ConfigFileConflict\t('s' " old , 90 | .br 91 | .BI "\t\t\t's' " new ) 92 | .RS 93 | .PP 94 | The signal will be emitted if the transaction has been paused because a 95 | .I new 96 | configuration file is shipped in a package which would override changes by the 97 | user in the 98 | .I old 99 | configuration file. 100 | .RE 101 | .TP 102 | .B PropertyChanged 103 | .BI PropertyChanged\t('s' " property_name , 104 | .br 105 | .BI "\t\t\t'v' " value ) 106 | .RS 107 | .PP 108 | The signal will be emitted if one of the following properties has changed. 109 | .RE 110 | .SS PROPERTIES 111 | The properties of the transaction are available through the Get And GetAll 112 | methods of the D-Bus properties interface and can be changed using the 113 | Set method. 114 | .TP 115 | .BR Role " read 's'" 116 | The role enum of the transaction, e.g. update-cache. 117 | .TP 118 | .BR Status " read 's'" 119 | The current status enum of the transaction, e.g. downloading 120 | .TP 121 | .BR StatusDetails " read 's'" 122 | A clear text message describing the current status. 123 | .TP 124 | .BR Progress " read 'i'" 125 | The progress of the transaction in percent. 126 | .TP 127 | .BR ProgressDownload " read 'sssiis'" 128 | The last download progress information received from APT. It is an array 129 | of the URI, status enum, short description, full size, already downloaded 130 | size and an error or status message. 131 | .TP 132 | .BR Space " read 'i'" 133 | The additional disk space in Bytes which will be required by the transaction. 134 | The 135 | .B Simulate 136 | method has to be called to calculate the download size. 137 | .TP 138 | .BR Download " read 'i'" 139 | The required download size in Bytes of the transaction. 140 | The 141 | .B Simulate 142 | method has to be called to calculate the download size. 143 | .TP 144 | .BR Packages " read 'a(a(s)a(s)a(s)a(s)a(s)a(s)a(s))'" 145 | The lists of packages which are initially queued to be installed, 146 | reinstalled, removed, purged, upgraded, downgraded or skipped from upgrading. 147 | .TP 148 | .BR Dependencies " read 'a(a(s)a(s)a(s)a(s)a(s)a(s)a(s))'" 149 | The lists of packages which are additionally required to be installed, 150 | reinstalled, removed, purged, upgraded, downgraded or skipped from upgrading. 151 | The 152 | .B Simulate 153 | method has to be called to calculate the dependencies. 154 | .TP 155 | .BR Cancellable " read 'b'" 156 | If the transaction can be cancelled. 157 | .TP 158 | .BR TerminalAttached " read 'b'" 159 | If the master pty which has to be set using the Terminal property is attached as controlling terminal to the underlying dpkg call. 160 | .TP 161 | .BR RequiredMedium " read 'a(ss)'" 162 | The transaction will be stopped if a media change is required to install packages. This property provides the 163 | .I label 164 | and the 165 | .I mount point 166 | of the requested medium. 167 | .TP 168 | .BR ConfigFileConflict " read 'a(ss)'" 169 | The transaction will be stopped if a configuration file shipped in the package overrides changes of the user. This property provides the path to 170 | .I current 171 | and the path to the 172 | .I new 173 | configuration file. 174 | .TP 175 | .BR ExitState " read 's'" 176 | The exit state enum of the transaction. 177 | .TP 178 | .BR Error " read 'a(ss)'" 179 | If an error occurred this property provides the 180 | .I error enum 181 | and the 182 | .I error message. 183 | .TP 184 | .BR Locale " read-write 's'" 185 | This is the locale which will be used for translating status and error messages of apt, e.g. de_DE@utf-8. 186 | .TP 187 | .BR Terminal " read-write 's'" 188 | The path to the master pty which will be the controlling terminal of the underlying dpkg call. This allows one to have an interactive terminal session. 189 | .TP 190 | .BR DebconfSocket " read-write 's'" 191 | The path to the socket which will be used by the debconf passthrough backend to forward the debconf communication to the user session. 192 | .TP 193 | .BR Paused " read 'b'" 194 | If the transaction is paused, e.g. waiting for a conflict file resolution or media change. 195 | .TP 196 | .BR Unauthenticated " read 'as'" 197 | List of unauthenticated packages which are going to be installed. 198 | .TP 199 | .BR RemoveObsoletedDepends " read-write 'b'" 200 | If obsoleted dependencies of to be removed packages which have been installed 201 | automatically should be removed too. 202 | .TP 203 | .BR AllowUnauthenticated " read-write 'b'" 204 | If it is allowed to install not authenticated software packages. 205 | .TP 206 | .BR HttpProxy " read-write 's'" 207 | The URL of an http proxy which should be used to download repository meta data and package files, e.g. http://myproxy.athome:8080. You should set the system wide proxy if you use this feature regularly. 208 | .BR MetaData " read-write 'a{ss}'" 209 | A dictonary which allows client application to store additional data in the transaction. The key name has to include an identifier of the client application separated by an unterscore from the key name, e.g. sc_icon for the application icon name stored by software-center. The property cannot be changed anymore after the transaction has been queued. 210 | .SH SEE ALSO 211 | .BR org.aptkit.transaction (7), 212 | .BR aptk (2), 213 | .BR aptkcon (2) 214 | -------------------------------------------------------------------------------- /aptkit/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Exception classes""" 4 | # Copyright (C) 2008-2009 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("AptKitError", "ForeignTransaction", "InvalidMetaDataError", 23 | "InvalidProxyError", "RepositoryInvalidError", 24 | "TransactionAlreadyRunning", "TransactionCancelled", 25 | "TransactionAlreadySimulating", 26 | "TransactionFailed", "TransactionRoleAlreadySet", 27 | "NotAuthorizedError", "convert_dbus_exception", 28 | "get_native_exception") 29 | 30 | import inspect 31 | from functools import wraps 32 | import sys 33 | 34 | import dbus 35 | 36 | import aptkit.enums 37 | 38 | PY3K = sys.version_info.major > 2 39 | 40 | 41 | class AptKitError(dbus.DBusException): 42 | 43 | """Internal error of the aptkit""" 44 | 45 | _dbus_error_name = "org.aptkit" 46 | 47 | def __init__(self, message=""): 48 | message = _convert_unicode(message) 49 | dbus.DBusException.__init__(self, message) 50 | self._message = message 51 | 52 | def get_dbus_message(self): 53 | """Overwrite the DBusException method, since it calls 54 | Exception.__str__() internally which doesn't support unicode or 55 | or non-ascii encodings.""" 56 | if PY3K: 57 | return dbus.DBusException.get_dbus_message(self) 58 | else: 59 | return self._message.encode("UTF-8") 60 | 61 | 62 | class TransactionRoleAlreadySet(AptKitError): 63 | 64 | """Error if a transaction has already been configured.""" 65 | 66 | _dbus_error_name = "org.aptkit.TransactionRoleAlreadySet" 67 | 68 | 69 | class TransactionAlreadyRunning(AptKitError): 70 | 71 | """Error if a transaction has already been configured.""" 72 | 73 | _dbus_error_name = "org.aptkit.TransactionAlreadyRunning" 74 | 75 | 76 | class TransactionAlreadySimulating(AptKitError): 77 | 78 | """Error if a transaction should be simulated but a simulation is 79 | already processed. 80 | """ 81 | 82 | _dbus_error_name = "org.aptkit.TransactionAlreadySimulating" 83 | 84 | 85 | class ForeignTransaction(AptKitError): 86 | 87 | """Error if a transaction was initialized by a different user.""" 88 | 89 | _dbus_error_name = "org.aptkit.TransactionAlreadyRunning" 90 | 91 | 92 | class TransactionFailed(AptKitError): 93 | 94 | """Internal error if a transaction could not be processed successfully.""" 95 | 96 | _dbus_error_name = "org.aptkit.TransactionFailed" 97 | 98 | def __init__(self, code, details="", *args): 99 | if not args: 100 | # Avoid string replacements if not used 101 | details = details.replace("%", "%%") 102 | args = tuple([_convert_unicode(arg) for arg in args]) 103 | details = _convert_unicode(details) 104 | self.code = code 105 | self.details = details 106 | self.details_args = args 107 | AptKitError.__init__(self, "%s: %s" % (code, details % args)) 108 | 109 | def __unicode__(self): 110 | return "Transaction failed: %s\n%s" % \ 111 | (aptkit.enums.get_error_string_from_enum(self.code), 112 | self.details) 113 | 114 | def __str__(self): 115 | if PY3K: 116 | return self.__unicode__() 117 | else: 118 | return self.__unicode__().encode("utf-8") 119 | 120 | 121 | class InvalidMetaDataError(AptKitError): 122 | 123 | """Invalid meta data given""" 124 | 125 | _dbus_error_name = "org.aptkit.InvalidMetaData" 126 | 127 | 128 | class InvalidProxyError(AptKitError): 129 | 130 | """Invalid proxy given""" 131 | 132 | _dbus_error_name = "org.aptkit.InvalidProxy" 133 | 134 | def __init__(self, proxy): 135 | AptKitError.__init__(self, "InvalidProxyError: %s" % proxy) 136 | 137 | 138 | class TransactionCancelled(AptKitError): 139 | 140 | """Internal error if a transaction was cancelled.""" 141 | 142 | _dbus_error_name = "org.aptkit.TransactionCancelled" 143 | 144 | 145 | class RepositoryInvalidError(AptKitError): 146 | 147 | """The added repository is invalid""" 148 | 149 | _dbus_error_name = "org.aptkit.RepositoryInvalid" 150 | 151 | 152 | class PolicyKitError(dbus.DBusException): 153 | pass 154 | 155 | 156 | class NotAuthorizedError(PolicyKitError): 157 | 158 | _dbus_error_name = "org.freedesktop.PolicyKit.Error.NotAuthorized" 159 | 160 | def __init__(self, subject, action_id): 161 | dbus.DBusException.__init__(self, "%s: %s" % (subject, action_id)) 162 | self.action_id = action_id 163 | self.subject = subject 164 | 165 | 166 | class AuthorizationFailed(NotAuthorizedError): 167 | 168 | _dbus_error_name = "org.freedesktop.PolicyKit.Error.Failed" 169 | 170 | 171 | def convert_dbus_exception(func): 172 | """A decorator which maps a raised DBbus exception to a native one. 173 | 174 | This decorator requires introspection to the decorated function. So it 175 | cannot be used on any already decorated method. 176 | """ 177 | argnames, varargs, kwargs, defaults = inspect.getfullargspec(func)[:4] 178 | 179 | @wraps(func) 180 | def _convert_dbus_exception(*args, **kwargs): 181 | try: 182 | error_handler = kwargs["error_handler"] 183 | except KeyError: 184 | _args = list(args) 185 | try: 186 | index = argnames.index("error_handler") 187 | error_handler = _args[index] 188 | except (IndexError, ValueError): 189 | pass 190 | else: 191 | _args[index] = lambda err: error_handler( 192 | get_native_exception(err)) 193 | args = tuple(_args) 194 | else: 195 | kwargs["error_handler"] = lambda err: error_handler( 196 | get_native_exception(err)) 197 | try: 198 | return func(*args, **kwargs) 199 | except dbus.exceptions.DBusException as error: 200 | raise get_native_exception(error) 201 | return _convert_dbus_exception 202 | 203 | 204 | def get_native_exception(error): 205 | """Map a DBus exception to a native one. This allows to make use of 206 | try/except on the client side without having to check for the error name. 207 | """ 208 | if not isinstance(error, dbus.DBusException): 209 | return error 210 | dbus_name = error.get_dbus_name() 211 | dbus_msg = error.get_dbus_message() 212 | if dbus_name == TransactionFailed._dbus_error_name: 213 | return TransactionFailed(*dbus_msg.split(":", 1)) 214 | elif dbus_name == AuthorizationFailed._dbus_error_name: 215 | return AuthorizationFailed(*dbus_msg.split(":", 1)) 216 | elif dbus_name == NotAuthorizedError._dbus_error_name: 217 | return NotAuthorizedError(*dbus_msg.split(":", 1)) 218 | for error_cls in [AptKitError, TransactionRoleAlreadySet, 219 | TransactionAlreadyRunning, ForeignTransaction, 220 | InvalidMetaDataError, InvalidProxyError, 221 | TransactionCancelled, RepositoryInvalidError]: 222 | if dbus_name == error_cls._dbus_error_name: 223 | return error_cls(dbus_msg) 224 | return error 225 | 226 | 227 | def _convert_unicode(text, encoding="UTF-8"): 228 | """Always return an unicode.""" 229 | if PY3K and not isinstance(text, str): 230 | text = str(text, encoding, errors="ignore") 231 | elif not PY3K and not isinstance(text, unicode): 232 | text = unicode(text, encoding, errors="ignore") 233 | return text 234 | 235 | # vim:ts=4:sw=4:et 236 | -------------------------------------------------------------------------------- /aptkit/config.py: -------------------------------------------------------------------------------- 1 | """Handling configuration files.""" 2 | # Copyright (C) 2010 Sebastian Heinlein 3 | # 4 | # Licensed under the GNU General Public License Version 2 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 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("ConfigWriter",) 23 | 24 | import logging 25 | import os 26 | 27 | import apt_pkg 28 | 29 | log = logging.getLogger("AptKit.ConfigWriter") 30 | 31 | 32 | class Value(object): 33 | 34 | """Represents a value with position information. 35 | 36 | .. attribute:: string 37 | The value string. 38 | 39 | .. attribute:: line 40 | The line number of the configuration file in which the value is set. 41 | 42 | .. attribute:: start 43 | The position in the line at which the value starts. 44 | 45 | .. attribute:: end 46 | The position in the line at which the value ends. 47 | 48 | .. attribute:: quotes 49 | The outer qoutes of the value: ' or " 50 | """ 51 | 52 | def __init__(self, line, start, quotes): 53 | self.string = "" 54 | self.start = start 55 | self.end = None 56 | self.line = line 57 | self.quotes = quotes 58 | 59 | def __cmp__(self, other): 60 | return self.string == other 61 | 62 | def __repr__(self): 63 | return "Value: '%s' (line %s: %s to %s)" % (self.string, self.line, 64 | self.start, self.end) 65 | 66 | 67 | class ConfigWriter(object): 68 | 69 | """Modifies apt configuration files.""" 70 | 71 | def parse(self, lines): 72 | """Parse an ISC based apt configuration. 73 | 74 | :param lines: The list of lines of a configuration file. 75 | 76 | :returns: Dictionary of key, values found in the parsed configuration. 77 | """ 78 | options = {} 79 | in_comment = False 80 | in_value = False 81 | prev_char = None 82 | option = [] 83 | value = None 84 | option_name = "" 85 | value_list = [] 86 | in_brackets = True 87 | level = 0 88 | for line_no, line in enumerate(lines): 89 | for char_no, char in enumerate(line): 90 | if not in_comment and char == "*" and prev_char == "/": 91 | in_comment = True 92 | prev_char = "" 93 | continue 94 | elif in_comment and char == "/" and prev_char == "*": 95 | # A multiline comment was closed 96 | in_comment = False 97 | prev_char = "" 98 | option_name = option_name[:-1] 99 | continue 100 | elif in_comment: 101 | # We ignore the content of multiline comments 102 | pass 103 | elif not in_value and ((char == "/" and prev_char == "/") or 104 | char == "#"): 105 | # In the case of a line comment continue processing 106 | # the next line 107 | prev_char = "" 108 | option_name = option_name[:-1] 109 | break 110 | elif char in "'\"": 111 | if in_value and value.quotes == char: 112 | value.end = char_no 113 | in_value = not in_value 114 | elif not value: 115 | value = Value(line_no, char_no, char) 116 | in_value = not in_value 117 | else: 118 | value.string += char 119 | elif in_value: 120 | value.string += char 121 | elif option_name and char == ":" and prev_char == ":": 122 | option.append(option_name[:-1]) 123 | option_name = "" 124 | elif char.isalpha() or char in "/-:._+": 125 | option_name += char.lower() 126 | elif char == ";": 127 | if in_brackets: 128 | value_list.append(value) 129 | value = None 130 | continue 131 | if value_list: 132 | log.debug("Found %s \"%s\"", "::".join(option), 133 | value_list) 134 | options["::".join(option)] = value_list 135 | value_list = [] 136 | elif value: 137 | log.debug("Found %s \"%s\"", "::".join(option), value) 138 | options["::".join(option)] = value 139 | else: 140 | log.debug("Skipping empty key %s", "::".join(option)) 141 | value = None 142 | if level > 0: 143 | option.pop() 144 | else: 145 | option = [] 146 | elif char == "}": 147 | level -= 1 148 | in_brackets = False 149 | elif char == "{": 150 | level += 1 151 | if option_name: 152 | option.append(option_name) 153 | option_name = "" 154 | in_brackets = True 155 | elif char in "\t\n ": 156 | if option_name: 157 | option.append(option_name) 158 | option_name = "" 159 | in_brackets = False 160 | else: 161 | raise ValueError("Unknown char '%s' in line: '%s'" % 162 | (char, line)) 163 | prev_char = char 164 | return options 165 | 166 | def set_value(self, option, value, defaultfile): 167 | """Change the value of an option in the configuration. 168 | 169 | :param option: The name of the option, e.g. 170 | 'apt::periodic::AutoCleanInterval'. 171 | :param value: The value of the option. Will be converted to string. 172 | :param defaultfile: The filename of the ``/etc/apt/apt.conf.d`` 173 | configuration snippet in which the option should be set. 174 | If the value is overriden by a later configuration file snippet 175 | it will be disabled in the corresponding configuration file. 176 | """ 177 | # FIXME: Support value lists 178 | # Convert the value to string 179 | if value is True: 180 | value = "true" 181 | elif value is False: 182 | value = "false" 183 | else: 184 | value = str(value) 185 | # Check all configuration file snippets 186 | etc_parts = os.path.join(apt_pkg.config.find_dir("Dir::Etc"), 187 | apt_pkg.config.find_dir("Dir::Etc::Parts")) 188 | for filename in os.listdir(etc_parts): 189 | if filename < defaultfile: 190 | continue 191 | with open(os.path.join(etc_parts, filename)) as fd: 192 | lines = fd.readlines() 193 | config = self.parse(lines) 194 | try: 195 | val = config[option.lower()] 196 | except KeyError: 197 | if filename == defaultfile: 198 | lines.append("%s '%s';\n" % (option, value)) 199 | else: 200 | continue 201 | else: 202 | # Check if the value needs to be changed at all 203 | if ((value == "true" and 204 | val.string.lower() in ["yes", "with", "on", 205 | "enable"]) or 206 | (value == "false" and 207 | val.string.lower() in ["no", "without", "off", 208 | "disable"]) or 209 | (str(value) == val.string)): 210 | continue 211 | if filename == defaultfile: 212 | line = lines[val.line] 213 | new_line = line[:val.start + 1] 214 | new_line += value 215 | new_line += line[val.end:] 216 | lines[val.line] = new_line 217 | else: 218 | # Comment out existing values instead in non default 219 | # configuration files 220 | # FIXME Quite dangerous for brackets 221 | lines[val.line] = "// %s" % lines[val.line] 222 | with open(os.path.join(etc_parts, filename), "w") as fd: 223 | log.debug("Writting %s", filename) 224 | fd.writelines(lines) 225 | if not os.path.exists(os.path.join(etc_parts, defaultfile)): 226 | with open(os.path.join(etc_parts, defaultfile), "w") as fd: 227 | log.debug("Writting %s", filename) 228 | line = "%s '%s';\n" % (option, value) 229 | fd.write(line) 230 | 231 | 232 | def main(): 233 | apt_pkg.init_config() 234 | cw = ConfigWriter() 235 | for filename in sorted(os.listdir("/etc/apt/apt.conf.d/")): 236 | lines = open("/etc/apt/apt.conf.d/%s" % filename).readlines() 237 | cw.parse(lines) 238 | print((cw.set_value("huhu::abc", "lumpi", "10glatzor"))) 239 | 240 | if __name__ == "__main__": 241 | main() 242 | 243 | # vim:ts=4:sw=4:et 244 | -------------------------------------------------------------------------------- /aptkit/networking.py: -------------------------------------------------------------------------------- 1 | # networking - Monitor the network status 2 | # 3 | # Copyright (c) 2010 Mohamed Amine IL Idrissi 4 | # Copyright (c) 2011 Canonical 5 | # Copyright (c) 2011 Sebastian Heinlein 6 | # 7 | # Author: Alex Chiang 8 | # Michael Vogt 9 | # Mohamed Amine IL Idrissi 10 | # Sebastian Heinlein 11 | # 12 | # This program is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU General Public License as 14 | # published by the Free Software Foundation; either version 2 of the 15 | # License, or (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License 23 | # along with this program; if not, write to the Free Software 24 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 25 | # USA 26 | 27 | from defer import Deferred, inline_callbacks, return_value 28 | from gi.repository import GObject 29 | from gi.repository import Gio 30 | from gi.repository import GLib 31 | from gi.repository import PackageKitGlib as pk 32 | import dbus 33 | from dbus.mainloop.glib import DBusGMainLoop 34 | DBusGMainLoop(set_as_default=True) 35 | import logging 36 | import os 37 | 38 | 39 | log = logging.getLogger("AptKit.NetMonitor") 40 | 41 | 42 | class NetworkMonitorBase(GObject.GObject): 43 | 44 | """Check the network state.""" 45 | 46 | __gsignals__ = {"network-state-changed": (GObject.SignalFlags.RUN_FIRST, 47 | None, 48 | (GObject.TYPE_PYOBJECT,))} 49 | 50 | def __init__(self): 51 | log.debug("Initializing network monitor") 52 | GObject.GObject.__init__(self) 53 | self._state = pk.NetworkEnum.ONLINE 54 | 55 | def _set_state(self, enum): 56 | if self._state != enum: 57 | log.debug("Network state changed: %s", enum) 58 | self._state = enum 59 | self.emit("network-state-changed", enum) 60 | 61 | def _get_state(self): 62 | return self._state 63 | 64 | state = property(_get_state, _set_state) 65 | 66 | @inline_callbacks 67 | def get_network_state(self): 68 | """Update the network state.""" 69 | return_value(self._state) 70 | 71 | 72 | class ProcNetworkMonitor(NetworkMonitorBase): 73 | 74 | """Use the route information of the proc filesystem to detect 75 | the network state. 76 | """ 77 | 78 | def __init__(self): 79 | log.debug("Initializing proc based network monitor") 80 | NetworkMonitorBase.__init__(self) 81 | self._state = pk.NetworkEnum.OFFLINE 82 | self._file = Gio.File.new_for_path("/proc/net/route") 83 | self._monitor = Gio.File.monitor(self._file, 84 | Gio.FileMonitorFlags.NONE, 85 | None) 86 | self._monitor.connect("changed", 87 | self._on_route_file_changed) 88 | 89 | def _on_route_file_changed(self, *args): 90 | self.get_network_state() 91 | 92 | def _parse_route_file(self): 93 | """Parse the route file - taken from PackageKit""" 94 | with open("/proc/net/route") as route_file: 95 | for line in route_file.readlines(): 96 | rows = line.split("\t") 97 | # The header line? 98 | if rows[0] == "Iface": 99 | continue 100 | # A loopback device? 101 | elif rows[0] == "lo": 102 | continue 103 | # Correct number of rows? 104 | elif len(rows) != 11: 105 | continue 106 | # The route is a default gateway 107 | elif rows[1] == "00000000": 108 | break 109 | # A gateway is set 110 | elif rows[2] != "00000000": 111 | break 112 | else: 113 | return pk.NetworkEnum.OFFLINE 114 | return pk.NetworkEnum.ONLINE 115 | 116 | @inline_callbacks 117 | def get_network_state(self): 118 | """Update the network state.""" 119 | self.state = self._parse_route_file() 120 | return_value(self.state) 121 | 122 | 123 | class NetworkManagerMonitor(NetworkMonitorBase): 124 | 125 | """Use NetworkManager to monitor network state.""" 126 | 127 | NM_DBUS_IFACE = "org.freedesktop.NetworkManager" 128 | NM_ACTIVE_CONN_DBUS_IFACE = NM_DBUS_IFACE + ".Connection.Active" 129 | NM_DEVICE_DBUS_IFACE = NM_DBUS_IFACE + ".Device" 130 | 131 | # The device type is unknown 132 | NM_DEVICE_TYPE_UNKNOWN = 0 133 | # The device is wired Ethernet device 134 | NM_DEVICE_TYPE_ETHERNET = 1 135 | # The device is an 802.11 WiFi device 136 | NM_DEVICE_TYPE_WIFI = 2 137 | # The device is a GSM-based cellular WAN device 138 | NM_DEVICE_TYPE_GSM = 3 139 | # The device is a CDMA/IS-95-based cellular WAN device 140 | NM_DEVICE_TYPE_CDMA = 4 141 | 142 | def __init__(self): 143 | log.debug("Initializing NetworkManager monitor") 144 | NetworkMonitorBase.__init__(self) 145 | self.bus = dbus.SystemBus() 146 | self.proxy = self.bus.get_object("org.freedesktop.NetworkManager", 147 | "/org/freedesktop/NetworkManager") 148 | self.proxy.connect_to_signal("PropertiesChanged", 149 | self._on_nm_properties_changed, 150 | dbus_interface=self.NM_DBUS_IFACE) 151 | self.bus.add_signal_receiver( 152 | self._on_nm_active_conn_props_changed, 153 | signal_name="PropertiesChanged", 154 | dbus_interface=self.NM_ACTIVE_CONN_DBUS_IFACE) 155 | 156 | @staticmethod 157 | def get_dbus_property(proxy, interface, property): 158 | """Small helper to get the property value of a dbus object.""" 159 | props = dbus.Interface(proxy, "org.freedesktop.DBus.Properties") 160 | deferred = Deferred() 161 | props.Get(interface, property, 162 | reply_handler=deferred.callback, 163 | error_handler=deferred.errback) 164 | return deferred 165 | 166 | @inline_callbacks 167 | def _on_nm_properties_changed(self, props): 168 | """Callback if NetworkManager properties changed.""" 169 | if "ActiveConnections" in props: 170 | if not props["ActiveConnections"]: 171 | log.debug("There aren't any active connections") 172 | self.state = pk.NetworkEnum.OFFLINE 173 | else: 174 | yield self.get_network_state() 175 | 176 | @inline_callbacks 177 | def _on_nm_active_conn_props_changed(self, props): 178 | """Callback if properties of the active connection changed.""" 179 | if "Default" not in props: 180 | return 181 | yield self.get_network_state() 182 | 183 | @inline_callbacks 184 | def _query_network_manager(self): 185 | """Query NetworkManager about the network state.""" 186 | state = pk.NetworkEnum.OFFLINE 187 | try: 188 | active_conns = yield self.get_dbus_property(self.proxy, 189 | self.NM_DBUS_IFACE, 190 | "ActiveConnections") 191 | except dbus.DBusException: 192 | log.warning("Failed to determinate network state") 193 | return_value(state) 194 | 195 | for conn in active_conns: 196 | conn_obj = self.bus.get_object(self.NM_DBUS_IFACE, conn) 197 | try: 198 | is_default = yield self.get_dbus_property( 199 | conn_obj, self.NM_ACTIVE_CONN_DBUS_IFACE, "Default") 200 | if not is_default: 201 | continue 202 | devs = yield self.get_dbus_property( 203 | conn_obj, self.NM_ACTIVE_CONN_DBUS_IFACE, "Devices") 204 | except dbus.DBusException: 205 | log.warning("Failed to determinate network state") 206 | break 207 | priority_device_type = -1 208 | for dev in devs: 209 | try: 210 | dev_obj = self.bus.get_object(self.NM_DBUS_IFACE, dev) 211 | dev_type = yield self.get_dbus_property( 212 | dev_obj, self.NM_DEVICE_DBUS_IFACE, "DeviceType") 213 | except dbus.DBusException: 214 | log.warning("Failed to determinate network state") 215 | return_value(pk.NetworkEnum.UNKNOWN) 216 | # prioterizse device types, since a bridged GSM/CDMA connection 217 | # should be returned as a GSM/CDMA one 218 | # The NM_DEVICE_TYPE_* enums are luckly ordered in this sense. 219 | if dev_type <= priority_device_type: 220 | continue 221 | priority_device_type = dev_type 222 | 223 | if dev_type in (self.NM_DEVICE_TYPE_GSM, 224 | self.NM_DEVICE_TYPE_CDMA): 225 | state = pk.NetworkEnum.MOBILE 226 | elif dev_type == self.NM_DEVICE_TYPE_ETHERNET: 227 | state = pk.NetworkEnum.WIRED 228 | elif dev_type == self.NM_DEVICE_TYPE_WIFI: 229 | state = pk.NetworkEnum.WIFI 230 | elif dev_type == self.NM_DEVICE_TYPE_UNKNOWN: 231 | state = pk.NetworkEnum.OFFLINE 232 | else: 233 | state = pk.NetworkEnum.ONLINE 234 | return_value(state) 235 | 236 | @inline_callbacks 237 | def get_network_state(self): 238 | """Update the network state.""" 239 | self.state = yield self._query_network_manager() 240 | return_value(self.state) 241 | 242 | 243 | def get_network_monitor(fallback=False): 244 | """Return a network monitor.""" 245 | if fallback: 246 | return ProcNetworkMonitor() 247 | try: 248 | return NetworkManagerMonitor() 249 | except dbus.DBusException: 250 | pass 251 | if os.path.exists("/proc/net/route"): 252 | return ProcNetworkMonitor() 253 | return NetworkMonitorBase() 254 | 255 | 256 | if __name__ == "__main__": 257 | @inline_callbacks 258 | def _call_monitor(): 259 | state = yield monitor.get_network_state() 260 | print(("Initial network state: %s" % state)) 261 | log_handler = logging.StreamHandler() 262 | log.addHandler(log_handler) 263 | log.setLevel(logging.DEBUG) 264 | monitor = get_network_monitor(True) 265 | _call_monitor() 266 | loop = GLib.MainLoop() 267 | loop.run() 268 | -------------------------------------------------------------------------------- /aptkit/worker/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Provides AptWorker which processes transactions.""" 4 | # Copyright (C) 2008-2009 Sebastian Heinlein 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 | # 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 along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | __author__ = "Sebastian Heinlein " 21 | 22 | __all__ = ("BaseWorker", "DummyWorker") 23 | 24 | import logging 25 | import os 26 | import pkg_resources 27 | import time 28 | import traceback 29 | 30 | from gi.repository import GObject, GLib 31 | 32 | from .. import enums 33 | from .. import errors 34 | 35 | log = logging.getLogger("AptKit.Worker") 36 | 37 | # Just required to detect translatable strings. The translation is done by 38 | # core.Transaction.gettext 39 | _ = lambda s: s 40 | 41 | 42 | class BaseWorker(GObject.GObject): 43 | 44 | """Worker which processes transactions from the queue.""" 45 | 46 | __gsignals__ = {"transaction-done": (GObject.SignalFlags.RUN_FIRST, 47 | None, 48 | (GObject.TYPE_PYOBJECT,)), 49 | "transaction-simulated": (GObject.SignalFlags.RUN_FIRST, 50 | None, 51 | (GObject.TYPE_PYOBJECT,))} 52 | NATIVE_ARCH = None 53 | 54 | def __init__(self, chroot=None, load_plugins=True): 55 | """Initialize a new AptWorker instance.""" 56 | GObject.GObject.__init__(self) 57 | self.trans = None 58 | self.last_action_timestamp = time.time() 59 | self.chroot = chroot 60 | # Store the the tid of the transaction whose changes are currently 61 | # marked in the cache 62 | self.marked_tid = None 63 | self.plugins = {} 64 | 65 | @staticmethod 66 | def _split_package_id(package): 67 | """Return the name, the version number and the release of the 68 | specified package.""" 69 | if "=" in package: 70 | name, version = package.split("=", 1) 71 | release = None 72 | elif "/" in package: 73 | name, release = package.split("/", 1) 74 | version = None 75 | else: 76 | name = package 77 | version = release = None 78 | return name, version, release 79 | 80 | def run(self, transaction): 81 | """Process the given transaction in the background. 82 | 83 | Keyword argument: 84 | transaction -- core.Transcation instance to run 85 | """ 86 | log.info("Processing transaction %s", transaction.tid) 87 | if self.trans: 88 | raise Exception("There is already a running transaction") 89 | self.trans = transaction 90 | GLib.idle_add(self._run_transaction_idle, transaction) 91 | 92 | def simulate(self, trans): 93 | """Return the dependencies which will be installed by the transaction, 94 | the content of the dpkg status file after the transaction would have 95 | been applied, the download size and the required disk space. 96 | 97 | Keyword arguments: 98 | trans -- the transaction which should be simulated 99 | """ 100 | log.info("Simulating trans: %s" % trans.tid) 101 | trans.status = enums.STATUS_RESOLVING_DEP 102 | GLib.idle_add(self._simulate_transaction_idle, trans) 103 | 104 | def _emit_transaction_simulated(self, trans): 105 | """Emit the transaction-simulated signal. 106 | 107 | Keyword argument: 108 | trans -- the simulated transaction 109 | """ 110 | log.debug("Emitting transaction-simulated: %s", trans.tid) 111 | self.emit("transaction-simulated", trans) 112 | 113 | def _emit_transaction_done(self, trans): 114 | """Emit the transaction-done signal. 115 | 116 | Keyword argument: 117 | trans -- the finished transaction 118 | """ 119 | log.debug("Emitting transaction-done: %s", trans.tid) 120 | self.emit("transaction-done", trans) 121 | 122 | def _run_transaction_idle(self, trans): 123 | """Run the transaction""" 124 | self.last_action_timestamp = time.time() 125 | trans.status = enums.STATUS_RUNNING 126 | trans.progress = 11 127 | try: 128 | self._run_transaction(trans) 129 | except errors.TransactionCancelled: 130 | trans.exit = enums.EXIT_CANCELLED 131 | except errors.TransactionFailed as excep: 132 | trans.error = excep 133 | trans.exit = enums.EXIT_FAILED 134 | except (KeyboardInterrupt, SystemExit): 135 | trans.exit = enums.EXIT_CANCELLED 136 | except Exception as excep: 137 | tbk = traceback.format_exc() 138 | trans.error = errors.TransactionFailed(enums.ERROR_UNKNOWN, tbk) 139 | trans.exit = enums.EXIT_FAILED 140 | else: 141 | trans.exit = enums.EXIT_SUCCESS 142 | finally: 143 | trans.progress = 100 144 | self.last_action_timestamp = time.time() 145 | tid = trans.tid[:] 146 | self.trans = None 147 | self.marked_tid = None 148 | self._emit_transaction_done(trans) 149 | log.info("Finished transaction %s", tid) 150 | return False 151 | 152 | def _simulate_transaction_idle(self, trans): 153 | try: 154 | (trans.depends, trans.download, trans.space, 155 | trans.unauthenticated, 156 | trans.high_trust_packages) = self._simulate_transaction(trans) 157 | except errors.TransactionFailed as excep: 158 | trans.error = excep 159 | trans.exit = enums.EXIT_FAILED 160 | except Exception as excep: 161 | tbk = traceback.format_exc() 162 | trans.error = errors.TransactionFailed(enums.ERROR_UNKNOWN, tbk) 163 | trans.exit = enums.EXIT_FAILED 164 | else: 165 | trans.status = enums.STATUS_SETTING_UP 166 | trans.simulated = time.time() 167 | self.marked_tid = trans.tid 168 | finally: 169 | self._emit_transaction_simulated(trans) 170 | self.last_action_timestamp = time.time() 171 | return False 172 | 173 | def _load_plugins(self, plugins, entry_point="aptkit.plugins"): 174 | """Load the plugins from setuptools' entry points.""" 175 | plugin_dirs = [os.path.join(os.path.dirname(__file__), "plugins")] 176 | env = pkg_resources.Environment(plugin_dirs) 177 | dists, errors = pkg_resources.working_set.find_plugins(env) 178 | for dist in dists: 179 | pkg_resources.working_set.add(dist) 180 | for name in plugins: 181 | for ept in pkg_resources.iter_entry_points(entry_point, 182 | name): 183 | try: 184 | self.plugins.setdefault(name, []).append(ept.load()) 185 | except: 186 | log.critical("Failed to load %s plugin: " 187 | "%s" % (name, ept.dist)) 188 | else: 189 | log.debug("Loaded %s plugin: %s", name, ept.dist) 190 | 191 | def _simulate_transaction(self, trans): 192 | """This method needs to be implemented by the backends.""" 193 | depends = [[], [], [], [], [], [], []] 194 | unauthenticated = [] 195 | high_trust_packages = [] 196 | skip_pkgs = [] 197 | size = 0 198 | installs = reinstalls = removals = purges = upgrades = upgradables = \ 199 | downgrades = [] 200 | 201 | return depends, 0, 0, [], [] 202 | 203 | def _run_transaction(self, trans): 204 | """This method needs to be implemented by the backends.""" 205 | raise errors.TransactionFailed(enums.ERROR_NOT_SUPPORTED) 206 | 207 | def set_config(self, option, value, filename): 208 | """Set a configuration option. 209 | 210 | This method needs to be implemented by the backends.""" 211 | raise NotImplementedError 212 | 213 | def get_config(self, option): 214 | """Get a configuration option. 215 | 216 | This method needs to be implemented by the backends.""" 217 | raise NotImplementedError 218 | 219 | def is_reboot_required(self): 220 | """This method needs to be implemented by the backends.""" 221 | return False 222 | 223 | 224 | class DummyWorker(BaseWorker): 225 | 226 | """Allows to test the daemon without making any changes to the system.""" 227 | 228 | def run(self, transaction): 229 | """Process the given transaction in the background. 230 | 231 | Keyword argument: 232 | transaction -- core.Transcation instance to run 233 | """ 234 | log.info("Processing transaction %s", transaction.tid) 235 | if self.trans: 236 | raise Exception("There is already a running transaction") 237 | self.trans = transaction 238 | self.last_action_timestamp = time.time() 239 | self.trans.status = enums.STATUS_RUNNING 240 | self.trans.progress = 0 241 | self.trans.cancellable = True 242 | GLib.timeout_add(200, self._run_transaction_idle, transaction) 243 | 244 | def _run_transaction_idle(self, trans): 245 | """Run the worker""" 246 | if trans.cancelled: 247 | trans.exit = enums.EXIT_CANCELLED 248 | elif trans.progress == 100: 249 | trans.exit = enums.EXIT_SUCCESS 250 | elif trans.role == enums.ROLE_UPDATE_CACHE: 251 | trans.exit = enums.EXIT_FAILED 252 | elif trans.role == enums.ROLE_UPGRADE_PACKAGES: 253 | trans.exit = enums.EXIT_SUCCESS 254 | elif trans.role == enums.ROLE_UPGRADE_SYSTEM: 255 | trans.exit = enums.EXIT_CANCELLED 256 | else: 257 | if trans.role == enums.ROLE_INSTALL_PACKAGES: 258 | if trans.progress == 1: 259 | trans.status = enums.STATUS_RESOLVING_DEP 260 | elif trans.progress == 5: 261 | trans.status = enums.STATUS_DOWNLOADING 262 | elif trans.progress == 50: 263 | trans.status = enums.STATUS_COMMITTING 264 | trans.status_details = "Heyas!" 265 | elif trans.progress == 55: 266 | trans.paused = True 267 | trans.status = enums.STATUS_WAITING_CONFIG_FILE_PROMPT 268 | trans.config_file_conflict = "/etc/fstab", "/etc/mtab" 269 | while trans.paused: 270 | GLib.main_context_default().iteration() 271 | trans.config_file_conflict_resolution = None 272 | trans.config_file_conflict = None 273 | trans.status = enums.STATUS_COMMITTING 274 | elif trans.progress == 60: 275 | trans.required_medium = ("Debian Lenny 5.0 CD 1", 276 | "USB CD-ROM") 277 | trans.paused = True 278 | trans.status = enums.STATUS_WAITING_MEDIUM 279 | while trans.paused: 280 | GLib.main_context_default().iteration() 281 | trans.status = enums.STATUS_DOWNLOADING 282 | elif trans.progress == 70: 283 | trans.status_details = "Servus!" 284 | elif trans.progress == 90: 285 | trans.status_deatils = "" 286 | trans.status = enums.STATUS_CLEANING_UP 287 | elif trans.role == enums.ROLE_REMOVE_PACKAGES: 288 | if trans.progress == 1: 289 | trans.status = enums.STATUS_RESOLVING_DEP 290 | elif trans.progress == 5: 291 | trans.status = enums.STATUS_COMMITTING 292 | trans.status_details = "Heyas!" 293 | elif trans.progress == 50: 294 | trans.status_details = "Hola!" 295 | elif trans.progress == 70: 296 | trans.status_details = "Servus!" 297 | elif trans.progress == 90: 298 | trans.status_deatils = "" 299 | trans.status = enums.STATUS_CLEANING_UP 300 | trans.progress += 1 301 | return True 302 | trans.status = enums.STATUS_FINISHED 303 | self.last_action_timestamp = time.time() 304 | tid = self.trans.tid[:] 305 | trans = self.trans 306 | self.trans = None 307 | self._emit_transaction_done(trans) 308 | log.info("Finished transaction %s", tid) 309 | return False 310 | 311 | def simulate(self, trans): 312 | depends = [[], [], [], [], [], [], []] 313 | return depends, 0, 0, [], [] 314 | 315 | 316 | # vim:ts=4:sw=4:et 317 | -------------------------------------------------------------------------------- /doc/dbus.rst: -------------------------------------------------------------------------------- 1 | The D-Bus API of the aptkit 2 | ============================== 3 | 4 | Aptkit provides two D-Bus interfaces on the system bus. 5 | 6 | org.aptkit --- The aptkit interface 7 | ------------------------------------------ 8 | 9 | This is the main interface which allows you to perform package managing tasks. 10 | It is proivded by the aptkit object at ``/org/aptkit``. 11 | 12 | There are two kind of tasks: ones which are performed immediately and ones 13 | which are queued up in transaction and performed in a sequence. 14 | 15 | Non-transaction based methods 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 18 | .. automethod:: aptkit.core.AptKit.Quit() 19 | 20 | 21 | Transaction based methos 22 | ^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | Instead of performing the task immediately, a new transaction will be created 25 | and the method will return the D-Bus path of it. Afterwards you can simulate 26 | the transaction or put it on the queue. 27 | 28 | :ref:`life_cycle` and :ref:`chaining` are described in the Python client 29 | documentation with code examples. 30 | 31 | .. automethod:: aptkit.core.AptKit.UpdateCache() -> string 32 | 33 | .. automethod:: aptkit.core.AptKit.UpdateCachePartially(sources_list : string) -> string 34 | 35 | .. automethod:: aptkit.core.AptKit.AddRepository(src_type : string, uri : string, dist : string, comps : array(string), comment : string, sourcesfile : string) -> string 36 | 37 | .. automethod:: aptkit.core.AptKit.EnableDistroComponent(component : string) -> string 38 | 39 | .. automethod:: aptkit.core.AptKit.InstallFile(path : string, force : boolean) -> string 40 | 41 | .. automethod:: aptkit.core.AptKit.InstallPackages(package_names : array(string)) -> string 42 | 43 | .. automethod:: aptkit.core.AptKit.RemovePackages(package_names : array(string)) -> string 44 | 45 | .. automethod:: aptkit.core.AptKit.UpgradePackages(package_names : array(string)) -> string 46 | 47 | .. automethod:: aptkit.core.AptKit.CommitPackages(install : array(string), reinstall : array(string), remove : array(string), purge : array(string), upgrade : array(string), downgrade : array(string)) -> string 48 | 49 | .. automethod:: aptkit.core.AptKit.UpgradeSystem(safe_mode : boolean) -> string 50 | 51 | .. automethod:: aptkit.core.AptKit.FixIncompleteInstall() -> string 52 | 53 | .. automethod:: aptkit.core.AptKit.FixBrokenDepends() -> string 54 | 55 | 56 | Signals 57 | ^^^^^^^ 58 | 59 | The following singal can be emitted on the org.aptkit interface. 60 | 61 | .. automethod:: aptkit.core.AptKit.ActiveTransactionsChanged(current : string, queued : array(string)) 62 | 63 | Properties 64 | ^^^^^^^^^^ 65 | 66 | The daemon interface provides a set of properties which can be accessed and modified through :meth:`Set()`, :meth:`Get()` and :meth:`GetAll()` methods of the ``org.freedesktop.DBus.Properties`` interface. 67 | See the `D-Bus specification `_ for more details. 68 | 69 | The following properties are available: 70 | 71 | .. attribute:: AutoCleanInterval : i 72 | 73 | The interval in days in which the cache of downloaded packages should 74 | should be cleaned. A value of 0 disables this feature. 75 | 76 | *writable* 77 | 78 | 79 | .. attribute:: AutoDownload : b 80 | 81 | If available upgrades should be already downloaded in the background. 82 | The upgrades won't get installed automatically. 83 | 84 | *writable* 85 | 86 | .. attribute:: AutoUpdateInterval : i 87 | 88 | The interval in days in which the software repositories should be checked 89 | for updates. A value of 0 disables the automatic check. 90 | 91 | *writable* 92 | 93 | .. attribute:: PopConParticipation : b 94 | 95 | If statistics about installed software and how often it is used should be 96 | sent to the distribution anonymously. This helps to determine which 97 | software should be shipped on the first install CDROMs and to make software 98 | recommendations. 99 | 100 | *writable* 101 | 102 | .. attribute:: UnattendedUpgrade : i 103 | 104 | The interval in days in which updates should be installed 105 | automatically. A value of 0 disables this feature. 106 | 107 | *writable* 108 | 109 | .. attribute:: RebootRequired : b 110 | 111 | Set if a reboot is required in order to complete the update. 112 | 113 | *readonly* 114 | 115 | 116 | org.aptkit.transaction --- The transaction interface 117 | -------------------------------------------------------- 118 | 119 | This is the main interface of a transaction object. It allows to control and 120 | monitor the transaction. Transactions are created by using the org.aptkit 121 | interface of aptkit. 122 | 123 | The path of a transaction object consists of ``/org/aptkit/transaction/`` and an unique identifier. 124 | 125 | Methods 126 | ^^^^^^^ 127 | 128 | .. automethod:: aptkit.core.Transaction.Run() 129 | 130 | .. automethod:: aptkit.core.Transaction.RunAfter(tid : string) 131 | 132 | .. automethod:: aptkit.core.Transaction.Cancel() 133 | 134 | .. automethod:: aptkit.core.Transaction.Simulate() 135 | 136 | .. automethod:: aptkit.core.Transaction.ProvideMedium(medium : string) 137 | 138 | .. automethod:: aptkit.core.Transaction.ResolveConfigFileConflict(config : string, answer : string) 139 | 140 | Signals 141 | ^^^^^^^ 142 | 143 | .. automethod:: aptkit.core.Transaction.Finished() -> string 144 | 145 | .. automethod:: aptkit.core.Transaction.PropertyChanged() -> string, variant 146 | 147 | Properties 148 | ^^^^^^^^^^ 149 | 150 | The transaction interface provides a set of properties which can be accessed and modified through :meth:`Set()`, :meth:`Get()` and :meth:`GetAll()` methods of the ``org.freedesktop.DBus.Properties`` interface. 151 | See the `D-Bus specification `_ for more details. 152 | 153 | For the documentation of the available string enumerations, see :mod:`aptkit.enums`. 154 | 155 | The following properties are available: 156 | 157 | 158 | .. attribute:: AllowUnauthenticated : b 159 | 160 | If it is allowed to install not authenticated packages by the transaction. 161 | Defaults to False. 162 | 163 | *writable* 164 | 165 | .. attribute:: Cancellable : b 166 | 167 | If the transaction can be cancelled at the moment. 168 | 169 | *read-only* 170 | 171 | .. attribute:: ConfigFileConflict : (ss) 172 | 173 | If the transaction waits for the resolution of a configuration file 174 | conflict, this property contains an array of the path to the old and 175 | the path to the new configuration file. See 176 | :meth:`ResolveConfigFileConflict()`. 177 | 178 | *read-only* 179 | 180 | .. attribute:: DebconfSocket : s 181 | 182 | The path to the socket which should be used to proxy debconf communication 183 | to the user. 184 | 185 | *writable* 186 | 187 | .. attribute:: Dependencies : (asasasasasasas) 188 | 189 | Array of package groups which will be modified as dependencies: 190 | 191 | * array of the packages to install 192 | * array of the packages to re-install 193 | * array of the packages to remove 194 | * array of the packages to purge 195 | * array of the packages to upgrade 196 | * array of the packages to downgrade 197 | * array of the packages to not upgrade (keep) 198 | 199 | The packages are specified by their name and a version number 200 | separated by an "=", e.g. "xterm=261-1". 201 | The dependencies are calculated after :meth:`Simulate()` or :meth:`Run()` 202 | was called. 203 | 204 | *read-only* 205 | 206 | .. attribute:: Download : x 207 | 208 | The required download size in Bytes. 209 | 210 | The property is available after :meth:`Simulate()` or :meth:`Run()` has 211 | been called. 212 | 213 | *read-only* 214 | 215 | .. attribute:: Error : (ss) 216 | 217 | If the transaction failed this property contains an array of the error 218 | code, e.g. ``error-no-lock`` and a detailed error message. 219 | 220 | *read-only* 221 | 222 | .. attribute:: ExitState : s 223 | 224 | If the transaction is completed it contains the exit status enum of 225 | the transaction, e.g. ``exit-failed``. If the transaction has not yet 226 | been completed it is ``exit-unfinished``. 227 | 228 | *read-only* 229 | 230 | .. attribute:: HttpProxy : s 231 | 232 | The URL of an http proxy which should be used for downloads by 233 | the transaction, e.g. ``http://proxy:8080``. 234 | 235 | *writable* 236 | 237 | .. attribute:: Locale : s 238 | 239 | The locale which is used to translate messages, e.g. ``de_DE@UTF-8``. 240 | 241 | *writable* 242 | 243 | .. attribute:: MetaData : a{sv} 244 | 245 | The meta data dictionary allows client applications to store additional 246 | data persistently in the transaction object. The key has to be a string 247 | and be prefixed by an underscore separated identifier of the client 248 | application, e.g. Software Center uses ``sc_app`` to store the application 249 | corresponding to the transaction. 250 | 251 | If a dict is written to the property it will be merged with the existing 252 | one. It is not allowed to override already existing keys. 253 | 254 | *writable* 255 | 256 | .. attribute:: Progress : i 257 | 258 | The progress in percent of the transaction. 259 | 260 | *read-only* 261 | 262 | .. attribute:: Packages : (asasasasasas) 263 | 264 | Array of package groups which have been specified by the user intially 265 | to be modified: 266 | 267 | * array of the packages to install 268 | * array of the packages to re-install 269 | * array of the packages to remove 270 | * array of the packages to purge 271 | * array of the packages to upgrade 272 | * array of the packages to downgrade 273 | 274 | The packages are specified by their name and an optional version number 275 | separated by an "=", e.g. "xterm=261-1". Furthermore if specified the 276 | target release of the package will be separated by a "/", e.g. 277 | "xterm/experimental". 278 | 279 | *read-only* 280 | 281 | .. attribute:: Paused : b 282 | 283 | If the transaction is paused, e.g. it is waiting for a medium or the 284 | resolution of a configuration file conflict. 285 | 286 | *read-only* 287 | 288 | .. attribute:: ProgressDetails : (iixxdx) 289 | 290 | A list with detailed progress information: 291 | 292 | * number of already processed items 293 | * number of total items 294 | * number of already downloaded bytes 295 | * number of total bytes which have to be downloaded 296 | * number of bytes downloaded per second 297 | * remaining time in seconds 298 | 299 | *read-only* 300 | 301 | .. attribute:: ProgressDownload : (sssxxs) 302 | 303 | The last progress information update of a currently running download. 304 | A list of .. 305 | 306 | * URL of the download 307 | * status enum of the download, e.g. ``download-fetching``. 308 | * short description of the download 309 | * number of already downloaded bytes 310 | * number of total bytes which have to be downloaded 311 | * Status message 312 | 313 | *read-only* 314 | 315 | .. attribute:: RemoveObsoletedDepends : b 316 | 317 | If in the case of a removal obsolete dependencies which have been installed 318 | automatically before should be removed, too. 319 | *writable* 320 | 321 | .. attribute:: RequiredMedium : (ss) 322 | 323 | If the transaction waits for a medium to be inserted this property contains 324 | an array of the medium name and the path to the drive in which 325 | it should be inserted. 326 | *read-only* 327 | 328 | .. attribute:: Role : s 329 | 330 | The enum representing the type of action performed by the transaction e.g. 331 | ``role-install-packages``. 332 | *read-only* 333 | 334 | .. attribute:: Space : x 335 | 336 | The required disk space in Bytes. If disk spaces is freed the value will 337 | be negative. 338 | 339 | The property is available after :meth:`Simulate()` or :meth:`Run()` has 340 | been called. 341 | 342 | *read-only* 343 | 344 | .. attribute:: Status : s 345 | 346 | The status enum of the transaction, e.g. ``status-loading-cache``. 347 | 348 | *read-only* 349 | 350 | .. attribute:: StatusDetails : s 351 | 352 | A human readable string with additional download information. 353 | 354 | *read-only* 355 | 356 | .. attribute:: Terminal : s 357 | 358 | The path to the slave end of the controlling terminal which should be 359 | used to controll the underlying :command:`dpkg` call. 360 | 361 | *writable* 362 | 363 | .. attribute:: TerminalAttached : b 364 | 365 | If the controlling terminal can be used to control the underlying 366 | :command:`dpkg` call. 367 | 368 | *read-only* 369 | 370 | .. attribute:: Unauthenticated : as 371 | 372 | List of packages which are going to be installed but are not from an 373 | authenticated repository. 374 | 375 | *read-only* 376 | 377 | 378 | .. _policykit: 379 | 380 | PolicyKit privileges 381 | -------------------- 382 | 383 | Most actions require the user to authenticate. The PolicyKit 384 | framework is used by aptkit for the authentication process. 385 | This allows to run aptkit as root and the client application as normal user. 386 | 387 | For non-transaction based actions the authentication will happen immediately. 388 | For transaction based actions the privilege will be checked after :meth:`Run()` 389 | has been called. If the privilege has not yet been granted the user will 390 | be requested to authenticate interactively. 391 | This allows the user to simulate a transaction before having to 392 | authenticate. 393 | 394 | Aptkit supports so called high level privileges which allow to perform 395 | a specified set of actions in a row without having to authenticate for each 396 | one separately. This only works if the client application authenticates for 397 | the high level privilge before running the transactions and the authentication 398 | is cached. 399 | 400 | There are two high level privileges ``install-packages-from-new-repo`` and 401 | ``install-purchased-software``. Both allow to add a repository, install the 402 | key of vendor from a keyserver, update the cache and to install packages. 403 | 404 | .. literalinclude:: ../examples/chained.py 405 | -------------------------------------------------------------------------------- /data/icons/scalable/status/aptkit-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 13 | 23 | 25 | 29 | 33 | 34 | 44 | 46 | 50 | 54 | 58 | 59 | 68 | 70 | 74 | 78 | 79 | 81 | 85 | 89 | 90 | 92 | 96 | 100 | 101 | 103 | 107 | 111 | 112 | 114 | 118 | 122 | 123 | 125 | 129 | 133 | 134 | 136 | 140 | 144 | 145 | 153 | 161 | 170 | 179 | 187 | 195 | 203 | 213 | 221 | 230 | 232 | 236 | 240 | 241 | 243 | 247 | 251 | 252 | 254 | 258 | 262 | 263 | 264 | 266 | 270 | 277 | 281 | 285 | 286 | 295 | 304 | 308 | 312 | 316 | 320 | 324 | 328 | 331 | 336 | 340 | 344 | 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /data/icons/scalable/status/aptkit-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 13 | 23 | 25 | 29 | 33 | 34 | 44 | 46 | 50 | 54 | 58 | 59 | 68 | 70 | 74 | 78 | 79 | 81 | 85 | 89 | 90 | 92 | 96 | 100 | 101 | 103 | 107 | 111 | 112 | 114 | 118 | 122 | 123 | 125 | 129 | 133 | 134 | 136 | 140 | 144 | 145 | 153 | 161 | 170 | 179 | 187 | 195 | 203 | 205 | 209 | 213 | 214 | 224 | 234 | 244 | 246 | 250 | 254 | 255 | 257 | 261 | 265 | 266 | 267 | 269 | 273 | 280 | 284 | 288 | 289 | 298 | 307 | 311 | 315 | 319 | 323 | 327 | 331 | 334 | 339 | 342 | 346 | 350 | 354 | 355 | 356 | 357 | 358 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------