2
# -*- coding: utf-8 -*-
3
"""core - APT based installer using the PackageKit DBus interface"""
4
# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
5
# Copyright (C) 2009 Richard Hughes <richard@hughsie.com>
6
# Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
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
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.
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.
22
__author__ = "Sebastian Heinlein <devel@glatzor.de>"
23
__state__ = "experimental"
25
from ConfigParser import ConfigParser
37
from aptdaemon.policykit1 import get_pid_from_dbus_name
38
from defer import Deferred, defer, inline_callbacks, return_value
39
from defer.utils import dbus_deferred_method
42
import dbus.mainloop.glib
43
from gettext import gettext as _
45
from gi.repository import Gio
46
from gi.repository import GObject
47
from gi.repository import Gst
48
from gi.repository import Gtk
49
from gi.repository import Pango
50
from xdg.DesktopEntry import DesktopEntry
55
_backend_env = os.getenv("SESSIONINSTALLER_BACKEND", "aptdaemon")
56
if _backend_env == "synaptic":
57
from backends.synaptic import SynapticBackend as Backend
58
elif _backend_env == "aptdaemon":
59
from backends.aptd import AptDaemonBackend as Backend
61
from backends.dummy import DummyBackend as Backend
64
gettext.textdomain("sessioninstaller")
65
gettext.bindtextdomain("sessioninstaller")
67
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
69
PACKAGEKIT_QUERY_DBUS_INTERFACE = "org.freedesktop.PackageKit.Query"
70
PACKAGEKIT_MODIFY_DBUS_INTERFACE = "org.freedesktop.PackageKit.Modify"
71
PACKAGEKIT_DBUS_PATH = "/org/freedesktop/PackageKit"
72
PACKAGEKIT_DBUS_SERVICE = "org.freedesktop.PackageKit"
75
INTERACT_CONFIRM_SEARCH = 1
76
INTERACT_CONFIRM_DEPS = 2
77
INTERACT_CONFIRM_INSTALL = 4
79
INTERACT_FINISHED = 16
84
GSTREAMER_RECORD_MAP = {"encoder": "Gstreamer-Encoders",
85
"decoder": "Gstreamer-Decoders",
86
"urisource": "Gstreamer-Uri-Sources",
87
"urisink": "Gstreamer-Uri-Sinks",
88
"element": "Gstreamer-Elements"}
90
RESTRICTED_010_PACKAGES = ["gstreamer0.10-plugins-bad",
91
"gstreamer0.10-plugins-bad-multiverse",
92
"gstreamer0.10-ffmpeg",
93
"gstreamer0.10-plugins-ugly"]
95
RESTRICTED_10_PACKAGES = ["gstreamer1.0-plugins-bad",
97
"gstreamer1.0-plugins-ugly"]
99
GSTREAMER_010_SCORING = {"gstreamer0.10-plugins-good": 100,
100
"gstreamer0.10-fluendo-mp3": 90,
101
"gstreamer0.10-ffmpeg": 79,
102
"gstreamer0.10-plugins-ugly": 80,
103
"gstreamer0.10-plugins-bad": 70,
104
"gstreamer0.10-plugins-bad-multiverse": 60}
106
GSTREAMER_10_SCORING = {"gstreamer1.0-plugins-good": 100,
107
# "gstreamer1.0-fluendo-mp3": 90,
108
"gstreamer1.0-libav": 79,
109
"gstreamer1.0-plugins-ugly": 80,
110
"gstreamer1.0-plugins-bad": 70}
113
logging.basicConfig(format="%(levelname)s:%(message)s")
114
log = logging.getLogger("sessioninstaller")
121
COLUMN_SCORE) = range(6)
123
DAEMON_IDLE_TIMEOUT = 3 * 60
124
DAEMON_IDLE_CHECK_INTERVAL = 30
126
# Required to get translated descriptions
128
locale.setlocale(locale.LC_ALL, "")
130
log.debug("Failed to unset LC_ALL")
132
def track_usage(func):
133
"""Decorator to keep track of running methods and to update the time
134
stamp of the last action.
136
@functools.wraps(func)
137
def _track_usage(*args, **kwargs):
138
def _release_track(ret, si):
140
si._last_timestamp = time.time()
141
log.debug("Updating last_timestamp")
145
si._last_timestamp = time.time()
146
log.debug("Updating last_timestamp")
147
deferred = defer(func, *args, **kwargs)
148
deferred.add_callbacks(_release_track, _release_track,
155
class GStreamerStructure(object):
157
"""Abstraction class of GStramer structure."""
159
def __init__(self, name="", version="", kind="", record="", caps="",
162
self.version = version
166
self.element = element
167
self.satisfied = False
168
self.best_provider = None
172
class GtkOpProgress(apt.progress.base.OpProgress):
174
"""A simple helper that keeps the GUI alive."""
176
def __init__(self, progress=None):
177
apt.progress.base.OpProgress.__init__(self)
178
self.progress_dialog = progress
180
def update(self, percent):
181
while Gtk.events_pending():
183
if self.progress_dialog:
184
self.progress_dialog.progress.pulse()
187
class ErrorDialog(Gtk.MessageDialog):
189
"""Allows to show an error message to the user."""
191
def __init__(self, title, message, parent=None):
192
GObject.GObject.__init__(self, message_type=Gtk.MessageType.ERROR,
193
buttons=Gtk.ButtonsType.CLOSE)
196
self.set_transient_for(parent)
197
self.set_markup("<b><big>%s</big></b>\n\n%s" % (title, message))
200
class ProgressDialog(Gtk.Dialog):
202
"""Allows to show the progress of an ongoing action to the user."""
204
def __init__(self, title, message, parent=None):
205
Gtk.Dialog.__init__(self)
206
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
209
self.set_transient_for(parent)
210
self.set_title(title)
211
self.set_resizable(False)
212
self.set_border_width(12)
213
self.vbox.set_spacing(24)
214
self.label = Gtk.Label()
215
self.label.set_markup("<b><big>%s</big></b>\n\n%s" % (title, message))
216
self.label.set_line_wrap(True)
217
self.vbox.add(self.label)
218
self.progress = Gtk.ProgressBar()
219
self.progress.set_pulse_step(0.01)
220
self.vbox.add(self.progress)
221
self.cancelled = False
222
self.connect("response", self._on_response)
224
def _on_response(self, dialog, response):
225
if response == Gtk.ResponseType.CANCEL:
226
self.cancelled = True
229
class ConfirmInstallDialog(Gtk.Dialog):
231
"""Allow to confirm an installation."""
233
def __init__(self, title, message, pkgs=[], parent=None, details=None,
234
package_type=None, selectable=False, action=None):
235
Gtk.Dialog.__init__(self)
236
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
239
self.set_transient_for(parent)
240
self.set_title(title)
241
self.set_resizable(True)
242
self.set_border_width(12)
243
self.vbox.set_spacing(12)
244
self.icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_QUESTION,
246
self.icon.set_alignment(0 ,0)
247
hbox_base = Gtk.HBox()
248
hbox_base.set_spacing(24)
249
vbox_left = Gtk.VBox()
250
vbox_left.set_spacing(12)
251
hbox_base.pack_start(self.icon, False, True, 0)
252
hbox_base.pack_start(vbox_left, True, True, 0)
253
self.label = Gtk.Label()
254
self.label.set_alignment(0, 0)
255
self.label.set_markup("<b><big>%s</big></b>\n\n%s" % (title, message))
256
self.label.set_line_wrap(True)
257
vbox_left.pack_start(self.label, False, True, 0)
258
self.cancelled = False
259
self.vbox.pack_start(hbox_base, True, True, 0)
261
action = _("_Install")
262
self.install_button = self.add_button(action, Gtk.ResponseType.OK)
263
self.set_default_response(Gtk.ResponseType.OK)
264
# Show a list of the plugin packages
265
self.pkg_store = Gtk.ListStore(GObject.TYPE_STRING,
267
GObject.TYPE_BOOLEAN,
271
self.pkg_store.set_sort_column_id(COLUMN_SCORE, Gtk.SortType.DESCENDING)
272
self.pkg_view = Gtk.TreeView(self.pkg_store)
273
self.pkg_view.set_rules_hint(True)
274
self.pkg_view.props.has_tooltip = True
275
self.pkg_view.connect("query-tooltip", self._on_query_tooltip)
277
toggle_install = Gtk.CellRendererToggle()
278
toggle_install.connect("toggled", self._on_toggled_install)
279
column_install = Gtk.TreeViewColumn(_("Install"), toggle_install,
280
active=COLUMN_INSTALL)
281
self.pkg_view.append_column(column_install)
283
package_type = _("Package")
284
column_desc = Gtk.TreeViewColumn(package_type)
285
renderer_warn = Gtk.CellRendererPixbuf()
286
renderer_warn.props.stock_size = Gtk.IconSize.MENU
287
column_desc.pack_start(renderer_warn, False)
288
column_desc.set_cell_data_func(renderer_warn, self._render_warning)
289
renderer_desc = Gtk.CellRendererText()
290
renderer_desc.props.ellipsize = Pango.EllipsizeMode.END
291
column_desc.pack_start(renderer_desc, True)
292
column_desc.add_attribute(renderer_desc, "markup", COLUMN_DESC)
293
column_desc.props.expand = True
294
column_desc.props.resizable = True
295
column_desc.props.min_width = 50
296
self.pkg_view.append_column(column_desc)
298
renderer_details = Gtk.CellRendererText()
299
renderer_details.props.ellipsize = Pango.EllipsizeMode.END
300
column_details = Gtk.TreeViewColumn(details,
302
column_details.add_attribute(renderer_details, "markup",
304
column_details.props.resizable = True
305
column_details.props.min_width = 50
306
self.pkg_view.append_column(column_details)
307
if not (selectable or details):
308
self.pkg_view.props.headers_visible = False
309
self.scrolled_window = Gtk.ScrolledWindow()
310
self.scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
311
self.scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
312
self.scrolled_window.add(self.pkg_view)
313
vbox_left.pack_start(self.scrolled_window, True, True, 0)
314
self.install_button.props.sensitive = False
316
self.add_confirm_package(pkg)
318
def add_confirm(self, name, summary, active=True, details="", score=0,
320
"""Add an entry to the confirmation dialog.
323
name -- name of the package or file
324
summary -- short description of the package
325
active -- if the package should be selected by default
326
details -- additional information about the package
327
score -- the ranking of the package which should be used for ordering
328
restricted -- if the use or redistribution is restriceted
331
#TRANSLATORS: %s is the name of a piece of software
332
tooltip = _("The use of %s may be restricted in some "
333
"countries. You must verify that one of the following "
335
"- These restrictions do not apply in your country "
336
"of legal residence\n"
337
"- You have permission to use this software (for "
338
"example, a patent license)\n"
339
"- You are using this software for research "
340
"purposes only") % name
341
# Set the dialog default to cancel if a restricted packages is
342
# selected for installation
344
self.set_default_response(Gtk.ResponseType.CANCEL)
347
desc = utils.get_package_desc(name, summary)
348
self.pkg_store.append((name, desc, active, details, tooltip, score))
350
self.install_button.props.sensitive = True
352
def add_confirm_package(self, pkg, active=True, details="", score=0):
353
"""Show an apt.package.Package instance in the confirmation dialog.
356
pkg -- the apt.package.Package instance
357
active -- if the package should be selected by default
358
details -- additional information about the package
359
score -- the ranking of the package which should be used for ordering
361
self.add_confirm(pkg.name, pkg.summary, active, details, score,
362
_is_package_restricted(pkg))
364
def get_selected_pkgs(self):
365
"""Return a list of the package names which are selected."""
367
for pkg, _desc, active, _details, _tooltip, _score \
371
def _on_query_tooltip(self, treeview, x, y, keyboard_tip, tooltip):
372
"""Handle tooltips for restrcited packages."""
374
result, out_x, out_y, model, path, iter = treeview.get_tooltip_context(x, y, keyboard_tip)
380
text = model[path][COLUMN_TOOLTIP]
383
tooltip.set_icon_from_icon_name(Gtk.STOCK_DIALOG_WARNING,
385
tooltip.set_markup(text)
386
treeview.set_tooltip_row(tooltip, path)
389
def _on_toggled_install(self, toggle, path):
390
"""Handle an activated package."""
391
cur = toggle.get_active()
392
self.pkg_store[path][COLUMN_INSTALL] = not cur
393
for row in self.pkg_store:
394
if row[COLUMN_INSTALL]:
395
self.install_button.props.sensitive = True
397
self.install_button.props.sensitive = False
399
def _render_warning(self, cell, renderer, model, iter, data):
400
"""Show a warning icon for restricted packages."""
401
if model.get_value(iter, COLUMN_TOOLTIP):
402
renderer.props.stock_id = Gtk.STOCK_DIALOG_WARNING
404
renderer.props.stock_id = None
407
"""Run the dialog."""
408
if len(self.pkg_store) > 4:
409
self.scrolled_window.set_policy(Gtk.PolicyType.NEVER,
410
Gtk.PolicyType.AUTOMATIC)
411
self.pkg_view.set_size_request(-1, 240)
413
res = Gtk.Dialog.run(self)
419
class SessionInstaller(dbus.service.Object):
421
"""Provides the PackageKit session API."""
423
def __init__(self, bus=None):
424
log.info("Starting service")
425
self.loop = GObject.MainLoop()
427
bus = dbus.SessionBus()
429
bus_name = dbus.service.BusName(PACKAGEKIT_DBUS_SERVICE, bus)
430
dbus.service.Object.__init__(self, bus_name, PACKAGEKIT_DBUS_PATH)
432
self.backend = Backend()
433
GObject.timeout_add_seconds(DAEMON_IDLE_CHECK_INTERVAL,
434
self._check_for_inactivity)
436
self._last_timestamp = time.time()
438
def _check_for_inactivity(self):
439
"""Quit after a period of inactivity."""
440
idle_time = time.time() - self._last_timestamp
441
log.debug("Checking for inactivity (%is)", idle_time)
442
if not self._tracks and \
443
not GObject.main_context_default().pending() and \
444
idle_time > DAEMON_IDLE_TIMEOUT:
446
log.info("Shutting down because of inactivity")
450
"""Start listening for requests."""
453
def _init_cache(self, progress=None):
454
"""Helper to set up the package cache."""
456
self._cache = apt.Cache(GtkOpProgress(progress))
459
def _get_sender_name(self, sender):
460
"""Try to resolve the name of the calling application."""
461
pid = yield get_pid_from_dbus_name(sender, self.bus)
463
exe = os.readlink("/proc/%s/exe" % pid)
466
# Try to get the name of an interpreted script
467
if exe in ["/usr/bin/python", "/usr/bin/python2.6", "/usr/bin/perl"]:
469
fcmd = open("/proc/%s/cmdline" % pid)
470
exe = fcmd.read().split("\0")[1]
472
except (IndexError, OSError):
474
# Special case for the GStreamer codec installation via the helper
475
# gnome-packagekit returns the name of parent window in this case,
476
# But this could be misleading:
477
# return_value(parent.property_get("WM_NAME")[2])
478
if exe in ["/usr/libexec/pk-gstreamer-install",
479
"/usr/lib/pk-gstreamer-install",
480
"/usr/bin/gst-install"]:
482
# Return the application name in the case of an installed application
483
for app in Gio.app_info_get_all():
484
if app.get_executable() == exe:
485
return_value(app.get_name())
486
return_value(os.path.basename(exe))
488
def _parse_interaction(self, interaction):
490
interact_list = interaction.split(",")
491
if "always" in interact_list:
492
mode = INTERACT_ALWAYS
493
elif "never" in interact_list:
494
mode = INTERACT_NEVER
495
if "show-confirm-progress" in interact_list:
496
mode &= INTERACT_CONFIRM_PROGRESS
497
elif "show-confirm-deps" in interact_list:
498
mode &= INTERACT_CONFIRM_DEPS
499
elif "show-confirm-install" in interact_list:
500
mode &= INTERACT_CONFIRM_INSTALL
501
elif "show-progress" in interact_list:
502
mode &= INTERACT_PROGRESS
503
elif "show-finished" in interact_list:
504
mode &= INTERACT_FINISHED
505
elif "show-warning" in interact_list:
506
mode &= INTERACT_WARNING
507
elif "hide-confirm-progress" in interact_list:
508
mode |= INTERACT_CONFIRM_PROGRESS
509
elif "hide-confirm-deps" in interact_list:
510
mode |= INTERACT_CONFIRM_DEPS
511
elif "hide-confirm-install" in interact_list:
512
mode |= INTERACT_CONFIRM_INSTALL
513
elif "hide-progress" in interact_list:
514
mode |= INTERACT_PROGRESS
515
elif "hide-finished" in interact_list:
516
mode |= INTERACT_FINISHED
517
elif "hide-warning" in interact_list:
518
mode |= INTERACT_WARNING
521
@dbus_deferred_method(PACKAGEKIT_QUERY_DBUS_INTERFACE,
522
in_signature="ss", out_signature="b",
524
def IsInstalled(self, package_name, interaction):
525
"""Return True if the given package is installed.
528
package-name -- the name of the package, e.g. xterm
529
interaction -- the interaction mode, e.g. timeout=10
531
log.info("IsInstalled() was called: %s, %s", package_name, interaction)
532
return self._is_installed(package_name, interaction)
535
def _is_installed(self, package_name, interaction):
538
return self._cache[package_name].is_installed
540
raise errors.QueryNoPackagesFound
542
@dbus_deferred_method(PACKAGEKIT_QUERY_DBUS_INTERFACE,
543
in_signature="ss", out_signature="bs",
545
def SearchFile(self, file_name, interaction):
546
"""Return the installation state and name of the package which
547
contains the given file.
550
file_name -- the to be searched file name
551
interaction -- the interaction mode, e.g. timeout=10
553
log.info("SearchFile() was called: %s, %s", file_name, interaction)
554
return self._search_file(file_name, interaction)
557
def _search_file(self, file_name, interaction):
559
# Search for installed files
560
if file_name.startswith("/"):
561
pattern = "^%s$" % file_name.replace("/", "\/")
563
pattern = ".*\/%s$" % file_name
564
re_file = re.compile(pattern)
565
for pkg in self._cache:
566
# FIXME: Fix python-apt
568
for installed_file in pkg.installed_files:
569
if re_file.match(installed_file):
570
#FIXME: What about a file in multiple conflicting
572
return pkg.is_installed, pkg.name
575
# Optionally make use of apt-file's Contents cache to search for not
576
# installed files. But still search for installed files additionally
577
# to make sure that we provide up-to-date results
578
if os.path.exists("/usr/bin/apt-file"):
579
#FIXME: Make use of rapt-file on Debian if the network is available
580
#FIXME: Show a warning to the user if the apt-file cache is several
582
log.debug("Using apt-file")
583
if file_name.startswith("/"):
584
pattern = "^%s$" % file_name[1:].replace("/", "\/")
586
pattern = "\/%s$" % file_name
587
cmd = ["/usr/bin/apt-file", "--regexp", "--non-interactive",
588
"--package-only", "find", pattern]
589
log.debug("Calling: %s" % cmd)
590
apt_file = subprocess.Popen(cmd, stdout=subprocess.PIPE,
591
stderr=subprocess.PIPE)
592
stdout, stderr = apt_file.communicate()
593
if apt_file.returncode == 0:
594
#FIXME: Actually we should check if the file is part of the
595
# candidate, e.g. if unstable and experimental are
596
# enabled and a file would only be part of the
597
# experimental version
598
#FIXME: Handle multiple packages
599
for pkg_name in stdout.split():
601
pkg = self._cache[pkg_name]
604
return pkg.isInstalled, pkg.name
606
raise errors.QueryInternatlError("apt-file call failed")
609
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
610
in_signature="uass", out_signature="",
611
sender_keyword="sender",
613
def InstallPackageFiles(self, xid, files, interaction, sender):
614
"""Install local package files.
617
xid -- the window id of the requesting application
618
files -- the list of package file paths
619
interaction -- the interaction mode: which ui elements should be
620
shown e.g. hide-finished or hide-confirm-search
622
log.info("InstallPackageFiles was called: %s, %s, %s", xid, files,
624
return self._install_package_files(xid, files, interaction, sender)
628
def _install_package_files(self, xid, files, interaction, sender):
629
parent = None # should get from XID, but removed from Gdk 3
632
header = _("Failed to install multiple package files")
633
message = _("Installing more than one package file at the same "
634
"time isn't supported. Please install one after the "
636
elif not files[0][0] == "/":
637
header = _("Relative path to package file")
638
message = _("You have to specify the absolute path to the "
640
elif not files[0].endswith(".deb"):
641
header = _("Unsupported package format")
642
message = _("Only Debian packages are supported (*.deb)")
644
debfile = apt.debfile.DebPackage(files[0])
645
desc = debfile["Description"].split("\n", 1)[0]
647
header = _("Unsupported package format")
648
message = _("Only Debian packages are supported (*.deb)")
650
dia = ErrorDialog(header, message)
653
raise errors.ModifyFailed("%s - %s" % (header, message))
654
title = gettext.ngettext("Install package file?",
655
"Install package files?",
657
sender_name = yield self._get_sender_name(sender)
659
message = gettext.ngettext("%s requests to install the following "
661
"%s requests to install the following "
663
len(files)) % sender_name
667
message += _("Software from foreign sources could be malicious, "
668
"could contain security risks and or even break your "
670
"Install packages from your distribution's "
671
"repositories as far as possible.")
672
confirm = ConfirmInstallDialog(title, message, parent=parent)
673
confirm.add_confirm(files[0], desc)
674
if confirm.run() == Gtk.ResponseType.CANCEL:
675
raise errors.ModifyCancelled
676
yield self.backend.install_package_files(xid,
677
confirm.get_selected_pkgs(),
680
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
681
in_signature="uass", out_signature="",
682
sender_keyword="sender",
684
def InstallProvideFiles(self, xid, files, interaction, sender):
685
"""Install packages which provide the given files.
688
xid -- the window id of the requesting application
689
files -- the list of package file paths
690
interaction -- the interaction mode: which ui elements should be
691
shown e.g. hide-finished or hide-confirm-search
693
log.info("InstallProvideFiles() was called: %s, %s, %s", xid, files,
695
return self._install_provide_files(xid, files, interaction, sender)
699
def _install_provide_files(self, xid, files, interaction, sender):
700
#FIXME: Reuse apt-file from the search_file method
702
from CommandNotFound import CommandNotFound
704
log.warning("command-not-found not supported")
706
cnf = CommandNotFound("/usr/share/command-not-found")
708
for executable in [os.path.basename(f) for f in files]:
709
list_of_packages_and_components = cnf.getPackages(executable)
710
if list_of_packages_and_components:
711
(package, component) = list_of_packages_and_components[0]
712
to_install.append(package)
714
yield self._install_package_names(xid, to_install, interaction,
717
# FIXME: show a message here that the binaries were not
718
# found instead of falling through to the misleading
719
# other error message
721
# FIXME: use a different error message
722
header = _("Installing packages by files isn't supported")
723
message = _("This method hasn't yet been implemented.")
724
#FIXME: should provide some information about how to find apps
725
dia = ErrorDialog(header, message)
728
#raise errors.ModifyInternalError(message)
730
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
731
in_signature="uass", out_signature="",
732
sender_keyword="sender",
734
def InstallCatalogs(self, xid, files, interaction, sender):
735
"""Install packages which provide the given files.
738
xid -- the window id of the requesting application
739
files -- the list of catalog file paths
740
interaction -- the interaction mode: which ui elements should be
741
shown e.g. hide-finished or hide-confirm-search
743
log.info("InstallCatalogs() was called: %s, %s, %s", xid, files,
745
return self._install_catalogs(xid, files, interaction, sender)
749
def _install_catalogs(self, xid, files, interaction, sender):
750
parent = None # should get from XID, but removed from Gdk 3
752
arch = os.popen("/usr/bin/dpkg --print-architecture").read().strip()
753
distro, code, release = os.popen("/usr/bin/lsb_release "
754
"--id --code --release "
755
"--short").read().split()
756
regex = "^(?P<action>[a-z]+)(\(%s(;((%s)|(%s))(;%s)?)?\))?$" % \
757
(distro, code, release, arch)
758
re_action = re.compile(regex, flags=re.IGNORECASE)
761
for catalog_file in files:
762
if not os.path.exists(catalog_file):
763
header = _("Catalog could not be read")
764
#TRANSLATORS: %s is a file path
765
message = _("The catalog file '%s' doesn't "
766
"exist.") % catalog_file
767
self._show_error(header, message)
768
raise errors.ModifyFailed(message)
769
catalog = ConfigParser()
771
catalog.read(catalog_file)
773
header = _("Catalog could not be read")
774
#TRANSLATORS: %s is a file path
775
message = _("The catalog file '%s' could not be opened "
776
"and read.") % catalog_file
777
self._show_error(header, message)
778
raise errors.ModifyFailed(message)
779
if not catalog.sections() == ["PackageKit Catalog"]:
780
header = _("Catalog could not be read")
781
#TRANSLATORS: %s is a file path
782
message = _("The file '%s' isn't a valid software catalog. "
783
"Please redownload or contact the "
784
"provider.") % catalog_file
785
self._show_error(header, message)
786
raise errors.ModifyFailed(message)
787
for key, value in catalog.items("PackageKit Catalog"):
788
match = re_action.match(key)
790
if match.group("action") != "installpackages":
791
header = _("Catalog is not supported")
792
message = _("The method '%s' which is used to specify "
793
"packages isn't supported.\n"
794
"Please contact the provider of the "
795
"catalog about this "
796
"issue.") % match.group("action")
797
self._show_error(header, message)
798
raise errors.ModifyFailed(message)
799
for pkg_name in value.split(";"):
800
if pkg_name in self._cache:
801
pkg = self._cache[pkg_name]
802
if not pkg.is_installed and not pkg.candidate:
803
missing.add(pkg_name)
805
pkgs.add(self._cache[pkg_name])
807
missing.add(pkg_name)
809
log.debug("Ignoring catalog instruction: %s" % key)
810
# Error out if packages are not available
812
header = gettext.ngettext("A required package is not installable",
813
"Required packages are not installable",
815
pkgs = " ".join(missing)
816
#TRANSLATORS: %s is the name of the missing packages
817
msg = gettext.ngettext("The catalog requires the installation of "
818
"the package %s which is not available.",
819
"The catalog requires the installation of "
820
"the following packages which are not "
821
"available: %s", len(missing)) % pkgs
822
self._show_error(header, msg)
823
raise errors.ModifyNoPackagesFound(msg)
824
parent = None # should get from XID, but removed from Gdk 3
825
# Create nice messages
826
sender_name = yield self._get_sender_name(sender)
827
title = gettext.ngettext("Install the following software package?",
828
"Install the following software packages?",
831
#TRANSLATORS: %s is the name of the application which requested
833
message = gettext.ngettext("%s requires the installation of an "
834
"additional software package.",
835
"%s requires the installation of "
836
"additional software packages.",
837
len(pkgs)) % sender_name
839
#TRANSLATORS: %s is an absolute file path, e.g. /usr/bin/xterm
840
message = gettext.ngettext("The package catalog %s requests to "
841
"install the following software.",
842
#TRANSLATORS: %s is a list of absoulte file paths
843
"The following catalogs request to "
844
"install software: %s",
845
#TRANSLATORS: %s is an absolute file path, e.g. /usr/bin/xterm
846
len(files)) % " ".join("'%s'")
847
confirm = ConfirmInstallDialog(title, message, pkgs, parent)
849
if res == Gtk.ResponseType.OK:
850
yield self.backend.install_packages(xid,
851
confirm.get_selected_pkgs(),
854
raise errors.ModifyCancelled
856
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
857
in_signature="uass", out_signature="",
858
sender_keyword="sender",
860
def InstallPackageNames(self, xid, packages, interaction, sender):
861
"""Install packages from a preconfigured software source.
864
xid -- the window id of the requesting application
865
packages -- the list of package names
866
interaction -- the interaction mode: which ui elements should be
867
shown e.g. hide-finished or hide-confirm-search
869
log.info("InstallPackageNames() was called: %s, %s, %s", xid, packages,
871
return self._install_package_names(xid, packages, interaction, sender)
875
def _install_package_names(self, xid, packages, interaction, sender):
876
parent = None # should get from XID, but removed from Gdk 3
877
title = gettext.ngettext("Install additional software package?",
878
"Install additional software packages?",
880
sender_name = yield self._get_sender_name(sender)
882
message = gettext.ngettext("%s requests to install the following "
883
"software package to provide additional "
885
"%s requests to install the following "
886
"software packages to provide "
887
"additional features.",
888
len(packages)) % sender_name
890
message = gettext.ngettext("The following software package is "
891
"required to provide additional "
893
"The following software packages are "
894
"required to provide additional "
898
confirm = ConfirmInstallDialog(title, message, parent=parent)
900
for pkg_name in packages:
902
pkg = self._cache[pkg_name]
904
failed_packages.append(pkg_name)
906
if not pkg.candidate:
907
failed_packages.append(pkg_name)
909
if not pkg.is_installed:
910
confirm.add_confirm_package(pkg)
913
header = gettext.ngettext("Could not find requested package",
914
"Could not find requested packages",
915
len(failed_packages))
917
message = gettext.ngettext("%s requests to install the "
918
"following software package to "
920
"additional features:",
921
"%s requests to install the "
923
"software packages to provide "
924
"additional features:",
925
len(packages)) % sender_name
927
message = gettext.ngettext("The following software package "
928
"is required to provide "
929
"additional features but cannot "
931
"The following software "
932
"packages are required to "
934
"additional features but cannot "
936
len(failed_packages))
937
message += self._get_bullet_list(failed_packages)
938
self._show_error(header, message)
939
raise errors.ModifyNoPackagesFound(header)
940
# If all packages are already installed we return silently
941
if len(confirm.pkg_store) > 0:
942
if confirm.run() == Gtk.ResponseType.CANCEL:
943
raise errors.ModifyCancelled
944
yield self.backend.install_packages(xid,
945
confirm.get_selected_pkgs(),
948
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
949
in_signature="uass", out_signature="",
950
sender_keyword="sender",
952
def InstallMimeTypes(self, xid, mime_types, interaction, sender):
953
"""Install mime type handler from a preconfigured software source.
956
xid -- the window id of the requesting application
957
mime_types -- list of mime types whose handlers should be installed
958
interaction -- the interaction mode: which ui elements should be
959
shown e.g. hide-finished or hide-confirm-search
961
log.info("InstallMimeTypes() was called: %s, %s, %s", xid, mime_types,
963
return self._install_mime_types(xid, mime_types, interaction, sender)
967
def _install_mime_types(self, xid, mime_types_list, interaction, sender):
968
parent = None # should get from XID, but removed from Gdk 3
969
if not os.path.exists(utils.APP_INSTALL_DATA):
970
#FIXME: should provide some information about how to find apps
971
header = _("Installing mime type handlers isn't supported")
972
message = _("To search and install software which can open "
973
"certain file types you have to install "
975
dia = ErrorDialog(header, message)
978
raise errors.ModifyInternalError(message)
979
sender_name = yield self._get_sender_name(sender)
980
title = _("Searching for suitable software to open files")
981
mime_types = set(mime_types_list)
982
mime_names = [Gio.content_type_get_description(mt) for mt in mime_types]
984
#TRANSLATORS: %s is an application
985
message = gettext.ngettext("%s requires to install software to "
986
"open files of the following file type:",
987
"%s requires to install software to "
988
"open files of the following file "
990
len(mime_types)) % sender_name
992
message = gettext.ngettext("Software to open files of the "
993
"following file type is required "
994
"but is not installed:",
995
"Software to open files of the "
996
"following file types is required "
997
"but is not installed:",
999
mime_types_desc = [Gio.content_type_get_description(mime_type) \
1000
for mime_type in mime_types]
1001
message += self._get_bullet_list(mime_types_desc)
1002
progress = ProgressDialog(title, message, parent)
1004
while Gtk.events_pending():
1005
Gtk.main_iteration()
1006
# Search the app-install-data desktop files for mime type handlers
1008
partial_providers = False
1009
self._init_cache(progress)
1011
unsatisfied = mime_types.copy()
1013
for count, path in enumerate(os.listdir(utils.APP_INSTALL_DATA)):
1014
if path[0] == "." or not path.endswith(".desktop"):
1017
while Gtk.events_pending():
1018
Gtk.main_iteration()
1019
progress.progress.pulse()
1020
if progress.cancelled:
1023
raise errors.ModifyCancelled
1024
desktop_entry = DesktopEntry(os.path.join(utils.APP_INSTALL_DATA,
1026
pkg_name = desktop_entry.get("X-AppInstall-Package")
1028
if self._cache[pkg_name].is_installed:
1032
supported_mime_types = set(desktop_entry.getMimeTypes())
1033
for mime_type in supported_mime_types:
1034
if not mime_type in mime_types:
1036
package_map.setdefault(pkg_name, [[], set(), 0])
1037
#FIXME: Don't add desktop entries twice
1038
package_map[pkg_name][0].append(desktop_entry)
1039
desc = Gio.content_type_get_description(mime_type)
1040
package_map[pkg_name][1].add(desc)
1041
popcon = desktop_entry.get("X-AppInstall-Popcon",
1043
if package_map[pkg_name][2] < popcon:
1044
package_map[pkg_name][2] = popcon
1045
unsatisfied.discard(mime_type)
1047
not supported_mime_types.issuperset(mime_types):
1051
if mixed or unsatisfied:
1052
details = _("Supported file types")
1055
title = _("Install software to open files?")
1057
unsatisfied_desc = [Gio.content_type_get_description(mime_type) \
1058
for mime_type in unsatisfied]
1059
unsatisfied_str = self._get_bullet_list(unsatisfied_desc)
1061
#TRANSLATORS: %s is either a single file type or a bullet list of
1063
message += gettext.ngettext("%s is not supported.",
1064
"Unsupported file types: %s",
1065
len(unsatisfied)) % unsatisfied_str
1066
confirm = ConfirmInstallDialog(title, message, parent=parent,
1068
selectable=len(package_map) > 1,
1069
package_type=_("Application"))
1070
for pkg, (entries, provides, score) in package_map.iteritems():
1071
if len(provides) == len(mime_types):
1074
#TRANSLATORS: Separator for a list of plugins
1075
details = _(",\n").join(provides)
1076
confirm.add_confirm_package(self._cache[pkg], len(package_map) == 1,
1079
if res == Gtk.ResponseType.OK:
1080
yield self.backend.install_packages(xid,
1081
confirm.get_selected_pkgs(),
1084
raise errors.ModifyCancelled
1086
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
1087
in_signature="uass", out_signature="",
1089
def InstallPrinterDrivers(self, xid, resources, interaction):
1090
"""Install printer drivers from from a preconfigured software source.
1093
xid -- the window id of the requesting application
1094
resources -- a list of printer model descriptors in IEEE 1284
1095
Device ID format e.g. "MFG:Hewlett-Packard" "MDL:HP LaserJet6MP"
1096
interaction -- the interaction mode: which ui elements should be
1097
shown e.g. hide-finished or hide-confirm-search
1099
log.info("InstallPrinterDrivers() was called: %s, %s, %s", xid,
1100
resources, interaction)
1101
return self._install_printer_drivers(xid, resources, interaction)
1104
def _install_printer_drivers(self, xid, resources, interaction):
1106
header = _("Installing printer drivers on request isn't supported")
1107
message = _("Currently autodetection and installation of "
1108
"missing printer drivers is not supported.")
1109
#FIXME: should provide some information about how to get printers
1110
dia = ErrorDialog(header, message)
1113
raise errors.ModifyInternalError(message)
1115
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
1116
in_signature="uass", out_signature="",
1118
def InstallFontconfigResources(self, xid, resources, interaction,):
1119
"""Install fontconfig resources from from a
1120
preconfigured software source.
1123
xid -- the window id of the requesting application
1124
resources -- list of fontconfig resources (usually fonts)
1125
interaction -- the interaction mode: which ui elements should be
1126
shown e.g. hide-finished or hide-confirm-search
1128
log.info("InstallFontconfigResources() was called: %s, %s, %s", xid,
1129
resources, interaction)
1130
return self._install_fontconfig_resources(xid, resources, interaction)
1133
def _install_fontconfig_resources(self, xid, resources, interaction):
1134
header = _("Installing fonts on request isn't supported")
1135
message = _("Currently autodetection and installation of "
1136
"missing fonts is not supported.")
1137
#FIXME: should provide some information about how to get fonts
1138
dia = ErrorDialog(header, message)
1141
raise errors.ModifyInternalError(message)
1143
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
1144
in_signature="uass", out_signature="",
1145
sender_keyword="sender",
1147
def InstallGStreamerResources(self, xid, resources, interaction, sender):
1148
"""Install GStreamer resources from from a preconfigured
1152
xid -- the window id of the requesting application
1153
resources -- list of GStreamer structures, e.g.
1154
"gstreamer0.10(decoder-video/x-wmv)(wmvversion=3)"
1155
interaction -- the interaction mode: which ui elements should be
1156
shown e.g. hide-finished or hide-confirm-search
1158
log.info("InstallGstreamerResources() was called: %s, %s, %s", xid,
1159
resources, interaction)
1161
return self._install_gstreamer_resources(xid, resources, interaction,
1166
def _install_gstreamer_resources(self, xid, resources, interaction, sender):
1167
def parse_gstreamer_structure(resource):
1168
# E.g. "MS Video|gstreamer0.10(decoder-video/x-wmv)(wmvversion=3)"
1169
match = re.match("^(?P<name>.*)\|gstreamer(?P<version>[0-9\.]+)"
1170
"\((?P<kind>.+?)-(?P<structname>.+?)\)"
1171
"(?P<fields>\(.+\))?$", resource)
1175
title = _("Invalid search term")
1176
message = _("The following term doesn't describe a "
1177
"GStreamer resource: %s") % resource
1178
self._show_error(title, message)
1179
raise errors.ModifyFailed(message)
1180
if match.group("kind") in ["encoder", "decoder"]:
1181
caps_str = "%s" % match.group("structname")
1182
if match.group("fields"):
1183
for field in re.findall("\((.+?=(\(.+?\))?.+?)\)",
1184
match.group("fields")):
1185
caps_str += ", %s" % field[0]
1186
# gst.Caps.__init__ cannot handle unicode instances
1187
caps = Gst.Caps.from_string(str(caps_str))
1189
element = match.group("structname")
1190
record = GSTREAMER_RECORD_MAP[match.group("kind")]
1191
return GStreamerStructure(match.group("name"),
1192
match.group("version"),
1193
match.group("kind"),
1194
record, caps, element)
1196
structures = [parse_gstreamer_structure(res) for res in resources]
1197
kinds = set([struct.kind for struct in structures])
1198
# Show a progress dialog
1199
parent = None # should get from XID, but removed from Gdk 3
1200
sender_name = yield self._get_sender_name(sender)
1201
title = _("Searching for multimedia plugins")
1202
# Get a nice dialog message
1203
if kinds.issubset(set(["encoder"])):
1205
#TRANSLATORS: %s is the application requesting the plugins
1206
message = gettext.ngettext("%s requires to install plugins to "
1207
"create media files of the "
1209
"%s requires to install plugins to "
1210
"create files of the following "
1212
len(structures)) % sender_name
1214
message = gettext.ngettext("The plugin to create media files "
1215
"of the following type is not "
1217
"The plugin to create media files "
1218
"of the following types is not "
1221
elif kinds.issubset(set(["decoder"])):
1223
#TRANSLATORS: %s is the application requesting the plugins
1224
message = gettext.ngettext("%s requires to install plugins to "
1225
"play media files of the "
1227
"%s requires to install plugins to "
1228
"play files of the following "
1230
len(structures)) % sender_name
1232
message = gettext.ngettext("The plugin to play media files "
1233
"of the following type is not "
1235
"The plugin to play media files "
1236
"of the following types is not "
1239
elif kinds.issubset(set(["encoder", "decoder"])):
1241
#TRANSLATORS: %s is the application requesting the plugins
1242
message = gettext.ngettext("%s requires to install plugins to "
1243
"create and play media files of the "
1245
"%s requires to install plugins to "
1246
"create and play media files of the "
1248
len(structures)) % sender_name
1250
message = gettext.ngettext("The plugins to create and play "
1251
"media files of the following type "
1252
"are not installed:",
1253
"The plugins to create and play "
1254
"media files of the following types "
1255
"are not installed:",
1259
#TRANSLATORS: %s is the application requesting the plugins
1260
message = gettext.ngettext("%s requires to install plugins to "
1261
"support the following "
1262
"multimedia feature:",
1263
"%s requires to install plugins to "
1264
"support the following multimedia "
1266
len(structures)) % sender_name
1268
message = gettext.ngettext("Extra plugins to provide the "
1269
"following multimedia feature are "
1271
"Extra plugins to provide the "
1272
"following multimedia features are "
1275
message += self._get_bullet_list([struct.name or struct.record \
1276
for struct in structures])
1277
progress = ProgressDialog(title, message, parent)
1279
while Gtk.events_pending():
1280
Gtk.main_iteration()
1281
# Search the package cache for packages providing the plugins
1283
partial_providers = False
1284
self._init_cache(progress)
1286
# Get the architectures with an installed gstreamer library
1287
# Unfortunately the architecture isn't part of the request. So we
1288
# have to detect for which architectuers gstreamer has been installed
1289
# on the system, to avoid showing codecs for not used but enabeled
1290
# architecures, see LP #899001
1291
architectures = apt_pkg.get_architectures()
1292
supported_archs = set()
1293
if len(architectures) > 1:
1294
for gst_version in set([struct.version for struct in structures]):
1295
for arch in architectures:
1297
pkg = self._cache["libgstreamer%s-0:%s" % (gst_version,
1301
if pkg.is_installed:
1302
supported_archs.add(arch)
1304
supported_archs = architectures
1306
for count, pkg in enumerate(self._cache):
1308
while Gtk.events_pending():
1309
Gtk.main_iteration()
1310
progress.progress.pulse()
1311
if progress.cancelled:
1314
raise errors.ModifyCancelled
1315
if (pkg.is_installed or
1316
not pkg.candidate or
1317
not "Gstreamer-Version" in pkg.candidate.record or
1318
not pkg.candidate.architecture in supported_archs):
1320
# Check if the package could not be free in usage or distribution
1321
# Allow to prefer special packages
1323
pkg_name = pkg.name.split(":")[0]
1324
if pkg_name in GSTREAMER_010_SCORING.keys():
1325
score = GSTREAMER_010_SCORING[pkg_name]
1326
elif pkg_name in GSTREAMER_10_SCORING.keys():
1327
score = GSTREAMER_10_SCORING[pkg_name]
1332
if _is_package_restricted(pkg):
1335
for struct in structures:
1336
if pkg.candidate.record["Gstreamer-Version"] != struct.version:
1340
pkg_caps = Gst.Caps.from_string(pkg.candidate.record[struct.record])
1343
if not pkg_caps.intersect(struct.caps):
1347
elements = pkg.candidate.record[struct.record]
1350
if not struct.element in elements:
1352
provides.append(struct.name)
1353
struct.satisfied = True
1354
if score > struct.best_score:
1355
struct.best_provider = pkg.name.split(":")[0]
1356
struct.best_score = score
1358
provides_all = len(structures) == len(provides)
1359
if not provides_all:
1360
partial_providers = True
1361
pkgs.append((pkg, provides_all, provides, score))
1364
# Error out if there isn't any package available
1366
#FIXME: Add more info and possible solutions for the user
1367
dia = ErrorDialog(_("Required plugin could not be found"),
1371
raise errors.ModifyNoPackagesFound
1372
# Show a confirmation dialog
1373
title = gettext.ngettext("Install extra multimedia plugin?",
1374
"Install extra multimedia plugins?",
1376
unsatisfied = [stru.name for stru in structures if not stru.satisfied]
1379
message += gettext.ngettext("The following plugin is not "
1381
"The following plugins are not "
1385
#TRANSLATORS: list separator
1386
message += self._get_bullet_list(unsatisfied)
1388
# We only show the details if there are packages which would only
1389
# provide a subset of the requests
1390
if partial_providers:
1391
details = _("Provides")
1394
best_providers = set([struct.best_provider for struct in structures])
1395
confirm = ConfirmInstallDialog(title, message, parent=parent,
1397
selectable=len(pkgs) > 1,
1398
package_type=_("Plugin Package"))
1399
for pkg, provides_all, provides, score in pkgs:
1403
#TRANSLATORS: Separator for a list of plugins
1404
details = _(",\n").join(provides)
1405
# Skip the architecture from the name
1406
install = pkg.name.split(":")[0] in best_providers
1407
confirm.add_confirm_package(pkg, install, details, score)
1409
if res == Gtk.ResponseType.OK:
1410
yield self.backend.install_packages(xid,
1411
confirm.get_selected_pkgs(),
1414
raise errors.ModifyCancelled
1416
@dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
1417
in_signature="uass", out_signature="",
1418
sender_keyword="sender",
1420
def RemovePackageByFiles(self, xid, files, interaction, sender):
1421
"""Remove packages which provide the given files.
1424
xid -- the window id of the requesting application
1425
files -- the list of file paths
1426
interaction -- the interaction mode: which ui elements should be
1427
shown e.g. hide-finished or hide-confirm-search
1429
log.info("RemovePackageByFiles() was called: %s, %s, %s", xid, files,
1431
return self._remove_package_by_files(xid, files, interaction, sender)
1435
def _remove_package_by_files(self, xid, files, interaction, sender):
1436
parent = None # should get from XID, but removed from Gdk 3
1437
sender_name = yield self._get_sender_name(sender)
1438
if [filename for filename in files if not filename.startswith("/")]:
1439
raise errors.ModifyFailed("Only absolute file names")
1441
title = _("Searching software to be removed")
1443
message = gettext.ngettext("%s wants to remove the software "
1444
"which provides the following file:",
1445
"%s wants to remove the software which "
1446
"provides the following files:",
1447
len(files)) % sender_name
1449
message = gettext.ngettext("The software which provides the "
1450
"following file is requested to be "
1452
"The software which provides the "
1453
"following files is requested to be "
1456
message += self._get_bullet_list(files)
1457
progress = ProgressDialog(title, message, parent)
1459
self._init_cache(progress)
1460
for pkg in self._cache:
1462
for installed_file in pkg.installed_files:
1463
if installed_file in files:
1469
self._show_error(_("Files are not installed"),
1470
_("The files which should be removed are not "
1471
"part of any installed software."))
1472
raise errors.ModifyNoPackagesFound
1473
title = gettext.ngettext("Remove software package?",
1474
"Remove software packages?", len(pkgs))
1476
#TRANSLATORS: %s is the name of an application
1477
message = gettext.ngettext("%s wants to remove the following "
1478
"software package from your computer.",
1479
"%s wants to remove the following "
1480
"software packages from your computer.",
1481
len(pkgs)) % sender_name
1483
message = gettext.ngettext("The following software package "
1484
"will be removed from your computer.",
1485
"The following software packages "
1486
"will be removed from your computer.",
1488
confirm = ConfirmInstallDialog(title, message, parent=parent,
1489
selectable=len(pkgs) > 1, pkgs=pkgs,
1490
action=_("_Remove"))
1492
if res == Gtk.ResponseType.OK:
1493
yield self.backend.remove_packages(xid,
1494
confirm.get_selected_pkgs(),
1497
raise errors.ModifyCancelled
1499
def _show_error(self, header, message):
1500
"""Show an error dialog."""
1501
dialog = ErrorDialog(header, message)
1505
def _get_bullet_list(self, lst):
1506
"""Return a string with a bullet list for the given list."""
1513
text += "\n• %s" % element
1517
def _is_package_restricted(pkg):
1518
"""If a package is possibly restricted in use."""
1519
return (pkg.name.split(":")[0] in RESTRICTED_010_PACKAGES + RESTRICTED_10_PACKAGES \
1520
or pkg.candidate.origins[0].component in ("non-free",
1525
log.setLevel(logging.DEBUG)
1526
si = SessionInstaller()
1529
if __name__ == "__main__":