~ubuntu-branches/ubuntu/wily/sessioninstaller/wily

« back to all changes in this revision

Viewing changes to sessioninstaller/core.py

  • Committer: Bazaar Package Importer
  • Author(s): Julian Andres Klode
  • Date: 2010-07-12 14:03:40 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20100712140340-wjac73e62gfmfnnl
Tags: 0.20-1
* New upstream release.
* debian/control: Set Standards-Version to 3.9.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
import time
33
33
 
34
34
import apt
35
 
from aptdaemon.defer import Deferred, defer, dbus_deferred_method
 
35
import apt.debfile
 
36
from aptdaemon.policykit1 import get_pid_from_dbus_name
 
37
from aptdaemon.defer import Deferred, defer, dbus_deferred_method, \
 
38
                            inline_callbacks, return_value
36
39
import dbus
37
40
import dbus.service
38
41
import dbus.mainloop.glib
97
100
logging.basicConfig(format="%(levelname)s:%(message)s")
98
101
log = logging.getLogger("sessioninstaller")
99
102
 
100
 
(COLUMN_PACKAGE,
 
103
(COLUMN_NAME,
 
104
 COLUMN_DESC,
101
105
 COLUMN_INSTALL,
102
106
 COLUMN_DETAILS,
103
107
 COLUMN_TOOLTIP,
104
 
 COLUMN_SCORE) = range(5)
 
108
 COLUMN_SCORE) = range(6)
105
109
 
106
110
DAEMON_IDLE_TIMEOUT = 3 * 60
107
111
DAEMON_IDLE_CHECK_INTERVAL = 30
215
219
    """Allow to confirm an installation."""
216
220
 
217
221
    def __init__(self, title, message, pkgs=[], parent=None, details=None,
218
 
                 package_type=None, selectable=False):
 
222
                 package_type=None, selectable=False, action=None):
219
223
        gtk.Dialog.__init__(self, buttons=(gtk.STOCK_CANCEL,
220
224
                                           gtk.RESPONSE_CANCEL))
221
225
        if parent:
242
246
        self.set_has_separator(False)
243
247
        self.cancelled = False
244
248
        self.vbox.pack_start(hbox_base, True, True)
245
 
        self.install_button = self.add_button(_("_Install"), gtk.RESPONSE_OK)
 
249
        if not action:
 
250
            action = _("_Install")
 
251
        self.install_button = self.add_button(action, gtk.RESPONSE_OK)
246
252
        self.set_default_response(gtk.RESPONSE_OK)
247
253
        # Show a list of the plugin packages
248
 
        self.pkg_store = gtk.ListStore(gobject.TYPE_PYOBJECT,
 
254
        self.pkg_store = gtk.ListStore(gobject.TYPE_STRING,
 
255
                                       gobject.TYPE_STRING,
249
256
                                       gobject.TYPE_BOOLEAN,
250
257
                                       gobject.TYPE_STRING,
251
258
                                       gobject.TYPE_STRING,
271
278
        renderer_desc = gtk.CellRendererText()
272
279
        renderer_desc.props.ellipsize = pango.ELLIPSIZE_END
273
280
        column_desc.pack_start(renderer_desc, expand=True)
274
 
        column_desc.set_cell_data_func(renderer_desc, self._render_description)
 
281
        column_desc.add_attribute(renderer_desc, "markup", COLUMN_DESC)
275
282
        column_desc.props.expand = True
276
283
        column_desc.props.resizable = True
277
284
        column_desc.props.min_width = 50
281
288
            renderer_details.props.ellipsize = pango.ELLIPSIZE_END
282
289
            column_details = gtk.TreeViewColumn(details,
283
290
                                                renderer_details)
284
 
            column_details.set_cell_data_func(renderer_details,
285
 
                                              self._render_details)
 
291
            column_details.add_attribute(renderer_details, "markup",
 
292
                                         COLUMN_DETAILS)
286
293
            column_details.props.resizable = True
287
294
            column_details.props.min_width = 50
288
295
            self.pkg_view.append_column(column_details)
295
302
        vbox_left.pack_start(self.scrolled_window, True, True)
296
303
        self.install_button.props.sensitive = False
297
304
        for pkg in pkgs:
298
 
            self.add_package(pkg)
299
 
    def add_package(self, pkg, active=True, details="", score=0):
300
 
        """Show the package in the confirmation dialog.
 
305
            self.add_confirm_package(pkg)
 
306
 
 
307
    def add_confirm(self, name, summary, active=True, details="", score=0,
 
308
                    restricted=False):
 
309
        """Add an entry to the confirmation dialog.
301
310
 
302
311
        Keyword arguments:
303
 
        pkg -- the apt.package.Package instance
 
312
        name -- name of the package or file
 
313
        summary -- short description of the package
304
314
        active -- if the package should be selected by default
305
315
        details -- additional information about the package
306
316
        score -- the ranking of the package which should be used for ordering
 
317
        restricted -- if the use or redistribution is restriceted
307
318
        """
308
 
        if pkg.name in RESTRICTED_PACKAGES or \
309
 
           pkg.candidate.origins[0].component in ("non-free", "multiverse"):
 
319
        if name in RESTRICTED_PACKAGES or restricted:
310
320
            #TRANSLATORS: %s is the name of a piece of software
311
321
            tooltip = _("The use of %s may be restricted in some "
312
322
                        "countries. You must verify that one of the following "
316
326
                        "• You have permission to use this software (for "
317
327
                        "example, a patent license)\n"
318
328
                        "• You are using this software for research "
319
 
                        "purposes only") % pkg.name
 
329
                        "purposes only") % name
320
330
            # Set the dialog default to cancel if a restricted packages is
321
331
            # selected for installation
322
332
            if active:
323
333
                self.set_default_response(gtk.RESPONSE_CANCEL)
324
334
        else:
325
335
            tooltip = ""
326
 
        self.pkg_store.append((pkg, active, details, tooltip, score))
 
336
        desc = utils.get_package_desc(name, summary)
 
337
        self.pkg_store.append((name, desc, active, details, tooltip, score))
327
338
        if active:
328
339
            self.install_button.props.sensitive = True
329
340
 
 
341
    def add_confirm_package(self, pkg, active=True, details="", score=0):
 
342
        """Show an apt.package.Package instance in the confirmation dialog.
 
343
 
 
344
        Keyword arguments:
 
345
        pkg -- the apt.package.Package instance
 
346
        active -- if the package should be selected by default
 
347
        details -- additional information about the package
 
348
        score -- the ranking of the package which should be used for ordering
 
349
        """
 
350
        restricted = pkg.candidate.origins[0].component in ("non-free",
 
351
                                                            "multiverse")
 
352
        self.add_confirm(pkg.name, pkg.summary, active, details, score,
 
353
                         restricted)
 
354
 
330
355
    def get_selected_pkgs(self):
331
356
        """Return a list of the package names which are selected."""
332
 
        return [pkg.name for \
333
 
                pkg, active, _details, _tooltip, _score in self.pkg_store if
334
 
                active]
 
357
        return [pkg \
 
358
                for pkg, _desc, active, _details, _tooltip, _score \
 
359
                in self.pkg_store \
 
360
                if active]
335
361
 
336
362
    def _on_query_tooltip(self, treeview, x, y, keyboard_tip, tooltip):
337
363
        """Handle tooltips for restrcited packages."""
365
391
        else:
366
392
            renderer.props.stock_id = 0
367
393
 
368
 
    def _render_description(self, cell, renderer, model, iter):
369
 
        """Helper to render the package description."""
370
 
        pkg = model.get_value(iter, COLUMN_PACKAGE)
371
 
        markup = utils.get_package_desc(pkg)
372
 
        renderer.set_property("markup", markup)
373
 
 
374
 
    def _render_details(self, cell, renderer, model, iter):
375
 
        """Helper to render the package details."""
376
 
        details = model.get_value(iter, COLUMN_DETAILS)
377
 
        markup = "%s" % details
378
 
        renderer.set_property("markup", details)
379
 
 
380
394
    def run(self):
381
395
        """Run the dialog."""
382
396
        if len(self.pkg_store) > 4:
399
413
        self.loop = gobject.MainLoop()
400
414
        if not bus:
401
415
            bus = dbus.SessionBus()
 
416
        self.bus = bus
402
417
        bus_name = dbus.service.BusName(PACKAGEKIT_DBUS_SERVICE, bus)
403
418
        dbus.service.Object.__init__(self, bus_name, PACKAGEKIT_DBUS_PATH)
404
419
        self._cache = None
428
443
        if not self._cache:
429
444
            self._cache = apt.Cache(GtkOpProgress(progress))
430
445
 
431
 
    def _get_window_title(self, xid):
432
 
        """Get the title of the window by its window id."""
433
 
        win = gtk.gdk.window_foreign_new(xid)
434
 
        if win:
435
 
            return win.property_get("WM_NAME")[2]
436
 
        else:
437
 
            return None
 
446
    @inline_callbacks
 
447
    def _get_sender_name(self, sender):
 
448
        """Try to resolve the name of the calling application."""
 
449
        pid = yield get_pid_from_dbus_name(sender, self.bus)
 
450
        try:
 
451
            exe = os.readlink("/proc/%s/exe" % pid)
 
452
        except:
 
453
            return_value(None)
 
454
        # Try to get the name of an interpreted script
 
455
        if exe in ["/usr/bin/python", "/usr/bin/python2.6", "/usr/bin/perl"]:
 
456
            try:
 
457
                fcmd = open("/proc/%s/cmdline" % pid)
 
458
                exe = fcmd.read().split("\0")[1]
 
459
                fcmd.close()
 
460
            except (IndexError, OSError):
 
461
                pass
 
462
        # Special case for the GStreamer codec installation via the helper
 
463
        # gnome-packagekit returns the name of parent window in this case,
 
464
        # But this could be misleading:
 
465
        # return_value(parent.property_get("WM_NAME")[2])
 
466
        if exe in ["/usr/libexec/pk-gstreamer-install",
 
467
                   "/usr/lib/pk-gstreamer-install",
 
468
                   "/usr/bin/gst-install"]:
 
469
            return_value(None)
 
470
        # Return the application name in the case of an installed application
 
471
        for app in gio.app_info_get_all():
 
472
            if app.get_executable() == exe:
 
473
                return_value(app.get_name())
 
474
        return_value(os.path.basename(exe))
438
475
 
439
476
    def _parse_interaction(self, interaction):
440
477
        mode = 0
559
596
 
560
597
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
561
598
                          in_signature="uass", out_signature="",
 
599
                          sender_keyword="sender",
562
600
                          utf8_strings=True)
563
 
    def InstallPackageFiles(self, xid, files, interaction):
 
601
    def InstallPackageFiles(self, xid, files, interaction, sender):
564
602
        """Install local package files.
565
603
 
566
604
        Keyword arguments:
567
605
        xid -- the window id of the requesting application
568
606
        files -- the list of package file paths
569
 
        interaction -- the interaction mode: which ui elements shoudl be
 
607
        interaction -- the interaction mode: which ui elements should be
570
608
                       shown e.g. hide-finished or hide-confirm-search
571
609
        """
572
610
        log.info("InstallPackageFiles was called: %s, %s, %s", xid, files,
573
611
                 interaction)
574
 
        return self._install_package_files(xid, files, interaction)
 
612
        return self._install_package_files(xid, files, interaction, sender)
575
613
 
576
614
    @track_usage
577
 
    def _install_package_files(self, xid, files, interaction):
 
615
    @inline_callbacks
 
616
    def _install_package_files(self, xid, files, interaction, sender):
 
617
        parent = gtk.gdk.window_foreign_new(xid)
578
618
        header = ""
579
619
        if len(files) != 1:
580
620
            header = _("Failed to install multiple package files")
581
 
            #FIXME: should provide some information about how to get fonts
582
621
            message = _("Installing more than one package file at the same "
583
622
                        "time isn't supported. Please install one after the "
584
623
                        "other.")
589
628
        elif not files[0].endswith(".deb"):
590
629
            header = _("Unsupported package format")
591
630
            message = _("Only Debian packages are supported (*.deb)")
 
631
        try:
 
632
            debfile = apt.debfile.DebPackage(files[0])
 
633
            desc = debfile["Description"].split("\n", 1)[0]
 
634
        except:
 
635
            header = _("Unsupported package format")
 
636
            message = _("Only Debian packages are supported (*.deb)")
592
637
        if header:
593
638
            dia = ErrorDialog(header, message)
594
639
            dia.run()
595
640
            dia.hide()
596
641
            raise errors.ModifyFailed("%s - %s" % (header, message))
597
 
        return self.backend.install_package_files(xid, files, interaction)
 
642
        title = gettext.ngettext("Install package file?",
 
643
                                 "Install package files?",
 
644
                                 len(files))
 
645
        sender_name = yield self._get_sender_name(sender)
 
646
        if sender_name:
 
647
            message = gettext.ngettext("%s requests to install the following "
 
648
                                       "package file.",
 
649
                                       "%s requests to install the following "
 
650
                                       "package files.",
 
651
                                       len(files)) % sender_name
 
652
            message += "\n\n"
 
653
        else:
 
654
            message = ""
 
655
        message += _("Software from foreign sources could be malicious, "
 
656
                     "could contain security risks and or even break your "
 
657
                     "system."
 
658
                     "Install packages from your distribution's "
 
659
                     "repositories as far as possible.")
 
660
        confirm = ConfirmInstallDialog(title, message, parent=parent)
 
661
        confirm.add_confirm(files[0], desc)
 
662
        if confirm.run() == gtk.RESPONSE_CANCEL:
 
663
            raise errors.ModifyCancelled
 
664
        yield self.backend.install_package_files(xid,
 
665
                                                 confirm.get_selected_pkgs(),
 
666
                                                 interaction, sender)
598
667
 
599
668
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
600
669
                          in_signature="uass", out_signature="",
 
670
                          sender_keyword="sender",
601
671
                          utf8_strings=True)
602
 
    def InstallProvideFiles(self, xid, files, interaction,):
 
672
    def InstallProvideFiles(self, xid, files, interaction, sender):
603
673
        """Install packages which provide the given files.
604
674
 
605
675
        Keyword arguments:
606
676
        xid -- the window id of the requesting application
607
677
        files -- the list of package file paths
608
 
        interaction -- the interaction mode: which ui elements shoudl be
 
678
        interaction -- the interaction mode: which ui elements should be
609
679
                       shown e.g. hide-finished or hide-confirm-search
610
680
        """
611
681
        log.info("InstallProvideFiles() was called: %s, %s, %s", xid, files,
612
682
                 interaction)
613
 
        self._install_provide_files(xid, files, interaction)
 
683
        return self._install_provide_files(xid, files, interaction, sender)
614
684
 
615
685
    @track_usage
616
 
    def _install_provide_files(self, xid, files, interaction):
 
686
    @inline_callbacks
 
687
    def _install_provide_files(self, xid, files, interaction, sender):
617
688
        header = _("Installing packages by files isn't supported")
618
689
        message = _("This method hasn't yet been implemented.")
619
690
        #FIXME: should provide some information about how to find apps
624
695
 
625
696
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
626
697
                          in_signature="uass", out_signature="",
 
698
                          sender_keyword="sender",
627
699
                          utf8_strings=True)
628
 
    def InstallCatalogs(self, xid, files, interaction,):
 
700
    def InstallCatalogs(self, xid, files, interaction, sender):
629
701
        """Install packages which provide the given files.
630
702
 
631
703
        Keyword arguments:
632
704
        xid -- the window id of the requesting application
633
705
        files -- the list of catalog file paths
634
 
        interaction -- the interaction mode: which ui elements shoudl be
 
706
        interaction -- the interaction mode: which ui elements should be
635
707
                       shown e.g. hide-finished or hide-confirm-search
636
708
        """
637
709
        log.info("InstallCatalogs() was called: %s, %s, %s", xid, files,
638
710
                 interaction)
639
 
        return self._install_catalogs(xid, files, interaction)
 
711
        return self._install_catalogs(xid, files, interaction, sender)
640
712
 
641
713
    @track_usage
642
 
    def _install_catalogs(self, xid, files, interaction):
 
714
    @inline_callbacks
 
715
    def _install_catalogs(self, xid, files, interaction, sender):
 
716
        parent = gtk.gdk.window_foreign_new(xid)
643
717
        self._init_cache()
644
718
        arch = os.popen("/usr/bin/dpkg --print-architecture").read().strip()
645
719
        distro, code, release = os.popen("/usr/bin/lsb_release "
713
787
                                   "available: %s", len(missing)) % pkgs
714
788
            self._show_error(header, msg)
715
789
            raise errors.ModifyNoPackagesFound(msg)
716
 
        # Get parent title and window
717
790
        parent = gtk.gdk.window_foreign_new(xid)
718
 
        parent_title = None
719
 
        if parent:
720
 
            parent_title = parent.property_get("WM_NAME")[2]
721
791
        # Create nice messages
 
792
        sender_name = yield self._get_sender_name(sender)
722
793
        title = gettext.ngettext("Install the following software package?",
723
794
                                 "Install the following software packages?",
724
795
                                 len(pkgs))
725
 
        if parent_title:
 
796
        if sender_name:
726
797
            #TRANSLATORS: %s is the name of the application which requested
727
798
            #             the installation
728
799
            message = gettext.ngettext("%s requires the installation of an "
729
800
                                       "additional software package.",
730
801
                                       "%s requires the installation of "
731
802
                                       "additional software packages.",
732
 
                                       len(pkgs)) % parent_title
 
803
                                       len(pkgs)) % sender_name
733
804
        else:
734
805
            #TRANSLATORS: %s is an absolute file path, e.g. /usr/bin/xterm
735
806
            message = gettext.ngettext("The package catalog %s requests to "
742
813
        confirm = ConfirmInstallDialog(title, message, pkgs, parent)
743
814
        res = confirm.run()
744
815
        if res == gtk.RESPONSE_OK:
745
 
            return self.backend.install_packages(xid,
746
 
                                                 confirm.get_selected_pkgs(),
747
 
                                                 interaction)
 
816
            yield self.backend.install_packages(xid,
 
817
                                                confirm.get_selected_pkgs(),
 
818
                                                interaction)
748
819
        else:
749
820
            raise errors.ModifyCancelled
750
821
 
751
822
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
752
823
                          in_signature="uass", out_signature="",
 
824
                          sender_keyword="sender",
753
825
                          utf8_strings=True)
754
 
    def InstallPackageNames(self, xid, packages, interaction):
 
826
    def InstallPackageNames(self, xid, packages, interaction, sender):
755
827
        """Install packages from a preconfigured software source.
756
828
 
757
829
        Keyword arguments:
758
830
        xid -- the window id of the requesting application
759
831
        packages -- the list of package names
760
 
        interaction -- the interaction mode: which ui elements shoudl be
 
832
        interaction -- the interaction mode: which ui elements should be
761
833
                       shown e.g. hide-finished or hide-confirm-search
762
834
        """
763
835
        log.info("InstallPackageNames() was called: %s, %s, %s", xid, packages,
764
836
                 interaction)
765
 
        return self._install_package_names(xid, packages, interaction)
 
837
        return self._install_package_names(xid, packages, interaction, sender)
766
838
 
767
839
    @track_usage
768
 
    def _install_package_names(self, xid, packages, interaction):
769
 
        deferred = self.backend.install_packages(xid, packages, interaction)
770
 
        return deferred
 
840
    @inline_callbacks
 
841
    def _install_package_names(self, xid, packages, interaction, sender):
 
842
        parent = gtk.gdk.window_foreign_new(xid)
 
843
        title = gettext.ngettext("Install additional software package?",
 
844
                                 "Install additional software packages?",
 
845
                                 len(packages))
 
846
        sender_name = yield self._get_sender_name(sender)
 
847
        if sender_name:
 
848
            message = gettext.ngettext("%s requests to install the following "
 
849
                                       "software package to provide additional "
 
850
                                       "features.",
 
851
                                       "%s requests to install the following "
 
852
                                       "software packages to provide "
 
853
                                       "additional features.",
 
854
                                       len(packages)) % sender_name
 
855
        else:
 
856
            message = gettext.ngettext("The following software package is "
 
857
                                       "required to provide additional "
 
858
                                       "features.",
 
859
                                       "The following software packages are "
 
860
                                       "required to provide additional "
 
861
                                       "features.",
 
862
                                       len(packages)) % sender_name
 
863
        confirm = ConfirmInstallDialog(title, message, parent=parent)
 
864
        #FIXME: We should use the cache to provide additional information
 
865
        for pkg in packages:
 
866
            confirm.add_confirm(pkg, "")
 
867
        if confirm.run() == gtk.RESPONSE_CANCEL:
 
868
            raise errors.ModifyCancelled
 
869
        yield self.backend.install_packages(xid, packages, interaction)
771
870
 
772
871
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
773
872
                          in_signature="uass", out_signature="",
 
873
                          sender_keyword="sender",
774
874
                          utf8_strings=True)
775
 
    def InstallMimeTypes(self, xid, mime_types, interaction,):
 
875
    def InstallMimeTypes(self, xid, mime_types, interaction, sender):
776
876
        """Install mime type handler from a preconfigured software source.
777
877
 
778
878
        Keyword arguments:
779
879
        xid -- the window id of the requesting application
780
880
        mime_types -- list of mime types whose handlers should be installed
781
 
        interaction -- the interaction mode: which ui elements shoudl be
 
881
        interaction -- the interaction mode: which ui elements should be
782
882
                       shown e.g. hide-finished or hide-confirm-search
783
883
        """
784
884
        log.info("InstallMimeTypes() was called: %s, %s, %s", xid, mime_types,
785
885
                 interaction)
786
 
        return self._install_mime_types(xid, mime_types, interaction)
 
886
        return self._install_mime_types(xid, mime_types, interaction, sender)
787
887
 
788
888
    @track_usage
789
 
    def _install_mime_types(self, xid, mime_types_list, interaction):
 
889
    @inline_callbacks
 
890
    def _install_mime_types(self, xid, mime_types_list, interaction, sender):
 
891
        parent = gtk.gdk.window_foreign_new(xid)
790
892
        if not os.path.exists(utils.APP_INSTALL_DATA):
791
893
            #FIXME: should provide some information about how to find apps
792
894
            header = _("Installing mime type handlers isn't supported")
797
899
            dia.run()
798
900
            dia.hide()
799
901
            raise errors.ModifyInternalError(message)
800
 
        parent = gtk.gdk.window_foreign_new(xid)
801
 
        parent_title = None
802
 
        if parent:
803
 
            parent_title = parent.property_get("WM_NAME")[2]
804
 
        title = _("Searching for mime type handlers")
 
902
        sender_name = yield self._get_sender_name(sender)
 
903
        title = _("Searching for suitable software to open files")
805
904
        mime_types = set(mime_types_list)
806
905
        mime_names = [gio.content_type_get_description(mt) for mt in mime_types]
807
 
        if parent_title:
808
 
            message = gettext.ngettext("%s requires to install additional "
809
 
                                       "software which can open files of "
810
 
                                       "the mime type %s.",
811
 
                                       "%s requires to install additional "
812
 
                                       "software which can open files of "
813
 
                                       "the following mime types:\n%s",
814
 
                                       len(mime_types)) % (parent_title,
815
 
                                                          ", ".join(mime_names))
 
906
        if sender_name:
 
907
            #TRANSLATORS: %s is an application
 
908
            message = gettext.ngettext("%s requires to install software to "
 
909
                                       "open files of the following file type:",
 
910
                                       "%s requires to install software to "
 
911
                                       "open files of the following file "
 
912
                                       "types:",
 
913
                                       len(mime_types)) % sender_name
816
914
        else:
817
 
            message = gettext.ngettext("You have to install additional "
818
 
                                       "software to open files of the "
819
 
                                       "mime type %s.",
820
 
                                       "You have to install additional "
821
 
                                       "software to open files of the "
822
 
                                       "following mime types:\n%s",
823
 
                                       len(mime_types)) % (parent_title,
824
 
                                                          ", ".join(mime_names))
 
915
            message = gettext.ngettext("Software to open files of the "
 
916
                                       "following file type is required "
 
917
                                       "but is not installed:",
 
918
                                       "Software to open files of the "
 
919
                                       "following file types is required "
 
920
                                       "but is not installed:",
 
921
                                       len(mime_types))
 
922
        mime_types_desc = [gio.content_type_get_description(mime_type) \
 
923
                           for mime_type in mime_types]
 
924
        message += self._get_bullet_list(mime_types_desc)
825
925
        progress = ProgressDialog(title, message, parent)
826
926
        progress.show_all()
827
927
        while gtk.events_pending():
852
952
                    continue
853
953
            except KeyError:
854
954
                continue
855
 
            for mime_type in desktop_entry.getMimeTypes():
 
955
            supported_mime_types = set(desktop_entry.getMimeTypes())
 
956
            for mime_type in supported_mime_types:
856
957
                if not mime_type in mime_types:
857
 
                    mixed = True
858
958
                    continue
859
959
                package_map.setdefault(pkg_name, [[], set(), 0])
860
960
                #FIXME: Don't add desktop entries twice
866
966
                if package_map[pkg_name][2] < popcon:
867
967
                    package_map[pkg_name][2] = popcon
868
968
                unsatisfied.discard(mime_type)
 
969
                if not mixed and \
 
970
                   not supported_mime_types.issuperset(mime_types):
 
971
                    mixed = True
869
972
        progress.hide()
870
973
        progress.destroy()
871
 
        if mixed:
872
 
            details=_("Mime types")
 
974
        if mixed or unsatisfied:
 
975
            details = _("Supported file types")
873
976
        else:
874
 
            details=None
875
 
        title = _("Install mime type handler?")
 
977
            details = None
 
978
        title = _("Install software to open files?")
876
979
        if unsatisfied:
877
 
            #TRANSLATORS: list separator
878
 
            unsatisfied_str = _(", ").join(unsatisfied)
 
980
            unsatisfied_desc = [gio.content_type_get_description(mime_type) \
 
981
                                for mime_type in unsatisfied]
 
982
            unsatisfied_str = self._get_bullet_list(unsatisfied_desc)
879
983
            message += "\n\n"
880
 
            message += gettext.ngettext("%s isn't supported.",
881
 
                                        "Unsupported mime types: %s",
 
984
            #TRANSLATORS: %s is either a single file type or a bullet list of
 
985
            #             file types
 
986
            message += gettext.ngettext("%s is not supported.",
 
987
                                        "Unsupported file types: %s",
882
988
                                        len(unsatisfied)) % unsatisfied_str
883
989
        confirm = ConfirmInstallDialog(title, message, parent=parent,
884
990
                                       details=details,
885
991
                                       selectable=len(package_map) > 1,
886
 
                                       package_type=_("Handler"))
 
992
                                       package_type=_("Application"))
887
993
        for pkg, (entries, provides, score) in package_map.iteritems():
888
994
            if len(provides) == len(mime_types):
889
995
                details = _("All")
890
996
            else:
891
997
                #TRANSLATORS: Separator for a list of plugins
892
998
                details = _(",\n").join(provides)
893
 
            confirm.add_package(self._cache[pkg], len(package_map) == 1,
894
 
                                details, score)
 
999
            confirm.add_confirm_package(self._cache[pkg], len(package_map) == 1,
 
1000
                                        details, score)
895
1001
        res = confirm.run()
896
1002
        if res == gtk.RESPONSE_OK:
897
 
            return self.backend.install_packages(xid,
898
 
                                                 confirm.get_selected_pkgs(),
899
 
                                                 interaction)
 
1003
            yield self.backend.install_packages(xid,
 
1004
                                                confirm.get_selected_pkgs(),
 
1005
                                                interaction)
900
1006
        else:
901
1007
            raise errors.ModifyCancelled
902
1008
 
903
 
 
904
1009
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
905
1010
                          in_signature="uass", out_signature="",
906
1011
                          utf8_strings=True)
911
1016
        xid -- the window id of the requesting application
912
1017
        resources -- a list of printer model descriptors in IEEE 1284
913
1018
               Device ID format e.g. "MFG:Hewlett-Packard" "MDL:HP LaserJet6MP"
914
 
        interaction -- the interaction mode: which ui elements shoudl be
 
1019
        interaction -- the interaction mode: which ui elements should be
915
1020
               shown e.g. hide-finished or hide-confirm-search
916
1021
        """
917
1022
        log.info("InstallPrinterDrivers() was called: %s, %s, %s", xid,
940
1045
        Keyword arguments:
941
1046
        xid -- the window id of the requesting application
942
1047
        resources -- list of fontconfig resources (usually fonts)
943
 
        interaction -- the interaction mode: which ui elements shoudl be
 
1048
        interaction -- the interaction mode: which ui elements should be
944
1049
                       shown e.g. hide-finished or hide-confirm-search
945
1050
        """
946
1051
        log.info("InstallFontconfigResources() was called: %s, %s, %s", xid,
960
1065
 
961
1066
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
962
1067
                          in_signature="uass", out_signature="",
 
1068
                          sender_keyword="sender",
963
1069
                          utf8_strings=True)
964
 
    def InstallGStreamerResources(self, xid, resources, interaction):
 
1070
    def InstallGStreamerResources(self, xid, resources, interaction, sender):
965
1071
        """Install GStreamer resources from from a preconfigured
966
1072
        software source.
967
1073
 
969
1075
        xid -- the window id of the requesting application
970
1076
        resources -- list of GStreamer structures, e.g.
971
1077
                     "gstreamer0.10(decoder-video/x-wmv)(wmvversion=3)"
972
 
        interaction -- the interaction mode: which ui elements shoudl be
 
1078
        interaction -- the interaction mode: which ui elements should be
973
1079
                       shown e.g. hide-finished or hide-confirm-search
974
1080
        """
975
1081
        log.info("InstallGstreamerResources() was called: %s, %s, %s", xid,
976
1082
                 resources, interaction)
977
 
        return self._install_gstreamer_resources(xid, resources, interaction)
 
1083
        return self._install_gstreamer_resources(xid, resources, interaction,
 
1084
                                                 sender)
978
1085
 
979
1086
    @track_usage
980
 
    def _install_gstreamer_resources(self, xid, resources, interaction):
 
1087
    @inline_callbacks
 
1088
    def _install_gstreamer_resources(self, xid, resources, interaction, sender):
981
1089
        def parse_gstreamer_structure(resource):
982
1090
            # E.g. "MS Video|gstreamer0.10(decoder-video/x-wmv)(wmvversion=3)"
983
1091
            match = re.match("^(?P<name>.*)\|gstreamer(?P<version>[0-9\.]+)"
1000
1108
                # gst.Caps.__init__ cannot handle unicode instances
1001
1109
                caps = gst.Caps(str(caps_str))
1002
1110
            else:
1003
 
                element = match.groups("structname")
 
1111
                element = match.group("structname")
1004
1112
            record = GSTREAMER_RECORD_MAP[match.group("kind")]
1005
1113
            return GStreamerStructure(match.group("name"),
1006
1114
                                      match.group("version"),
1008
1116
                                      record, caps, element)
1009
1117
 
1010
1118
        structures = [parse_gstreamer_structure(res) for res in resources]
 
1119
        kinds = set([struct.kind for struct in structures])
1011
1120
        # Show a progress dialog
1012
1121
        parent = gtk.gdk.window_foreign_new(xid)
1013
 
        title = "Searching for multimedia plugins"
1014
 
        parent_title = None
1015
 
        if parent:
1016
 
            parent_title = parent.property_get("WM_NAME")[2]
 
1122
        sender_name = yield self._get_sender_name(sender)
 
1123
        title = _("Searching for multimedia plugins")
1017
1124
        # Get a nice dialog message
1018
 
        if parent_title:
1019
 
            #TRANSLATORS: %s is the application requesting the plugins
1020
 
            message = gettext.ngettext("%s requires extra plugins to handle "
1021
 
                                       "the following media content type:",
1022
 
                                       "%s requires extra plugins to handle "
1023
 
                                       "following media content types:",
1024
 
                                       len(structures)) % parent_title
1025
 
        else:
1026
 
            message = gettext.ngettext("Extra plugins to handle "
1027
 
                                       "the following media content type are "
1028
 
                                       "required:",
1029
 
                                       "Extra plugins to handle "
1030
 
                                       "following media content types are "
1031
 
                                       "required:",
1032
 
                                       len(structures))
1033
 
        if len(structures) == 1:
1034
 
            message += " "
1035
 
            message += structures[0].name
1036
 
        else:
1037
 
            for struct in structures:
1038
 
                if struct.name:
1039
 
                    message += "\n• %s" % struct.name
1040
 
                else:
1041
 
                    message += "\n• %s" % struct.data
 
1125
        import pdb; pdb.set_trace()
 
1126
        if kinds.issubset(set(["encoder"])):
 
1127
            if sender_name:
 
1128
                #TRANSLATORS: %s is the application requesting the plugins
 
1129
                message = gettext.ngettext("%s requires to install plugins to "
 
1130
                                           "create media files of the "
 
1131
                                           "following type:",
 
1132
                                           "%s requires to install plugins to "
 
1133
                                           "create files of the following "
 
1134
                                           "types:",
 
1135
                                           len(structures)) % sender_name
 
1136
            else:
 
1137
                message = gettext.ngettext("The plugin to create media files "
 
1138
                                           "of the following type is not "
 
1139
                                           "installed:",
 
1140
                                           "The plugin to create media files "
 
1141
                                           "of the following types is not "
 
1142
                                           "installed:",
 
1143
                                           len(structures))
 
1144
        elif kinds.issubset(set(["decoder"])):
 
1145
            if sender_name:
 
1146
                #TRANSLATORS: %s is the application requesting the plugins
 
1147
                message = gettext.ngettext("%s requires to install plugins to "
 
1148
                                           "play media files of the "
 
1149
                                           "following type:",
 
1150
                                           "%s requires to install plugins to "
 
1151
                                           "play files of the following "
 
1152
                                           "types:",
 
1153
                                           len(structures)) % sender_name
 
1154
            else:
 
1155
                message = gettext.ngettext("The plugin to play media files "
 
1156
                                           "of the following type is not "
 
1157
                                           "installed:",
 
1158
                                           "The plugin to play media files "
 
1159
                                           "of the following types is not "
 
1160
                                           "installed:",
 
1161
                                           len(structures))
 
1162
        elif kinds.issubset(set(["encoder", "decoder"])):
 
1163
            if sender_name:
 
1164
                #TRANSLATORS: %s is the application requesting the plugins
 
1165
                message = gettext.ngettext("%s requires to install plugins to "
 
1166
                                           "create and play media files of the "
 
1167
                                           "following type:",
 
1168
                                           "%s requires to install plugins to "
 
1169
                                           "create and play media files of the "
 
1170
                                           "following types:",
 
1171
                                           len(structures)) % sender_name
 
1172
            else:
 
1173
                message = gettext.ngettext("The plugins to create and play "
 
1174
                                           "media files of the following type "
 
1175
                                           "are not installed:",
 
1176
                                           "The plugins to create and play "
 
1177
                                           "media files of the following types "
 
1178
                                           "are not installed:",
 
1179
                                           len(structures))
 
1180
        else:
 
1181
            if sender_name:
 
1182
                #TRANSLATORS: %s is the application requesting the plugins
 
1183
                message = gettext.ngettext("%s requires to install plugins to "
 
1184
                                           "support the following "
 
1185
                                           "multimedia feature:",
 
1186
                                           "%s requires to install plugins to "
 
1187
                                           "support the following multimedia "
 
1188
                                           "features:",
 
1189
                                           len(structures)) % sender_name
 
1190
            else:
 
1191
                message = gettext.ngettext("Extra plugins to provide the "
 
1192
                                           "following multimedia feature are "
 
1193
                                           "not installed:",
 
1194
                                           "Extra plugins to provide the "
 
1195
                                           "following multimedia features are "
 
1196
                                           "not installed:",
 
1197
                                           len(structures))
 
1198
        message += self._get_bullet_list([struct.name or struct.record \
 
1199
                                         for struct in structures])
1042
1200
        progress = ProgressDialog(title, message, parent)
1043
1201
        progress.show_all()
1044
1202
        while gtk.events_pending():
1109
1267
        title = gettext.ngettext("Install extra multimedia plugin?",
1110
1268
                                 "Install extra multimedia plugins?",
1111
1269
                                 len(pkgs))
1112
 
        if parent_title:
1113
 
            #TRANSLATORS: %s is the name of an application
1114
 
            message = gettext.ngettext("Confirm the installation of the "
1115
 
                                       "plugin which was requested by %s.",
1116
 
                                       "Confirm the installation of "
1117
 
                                       "the selected plugins which have been "
1118
 
                                       "requested by %s.",
1119
 
                                       len(pkgs)) % parent_title
1120
 
        else:
1121
 
            message = gettext.ngettext("Confirm the installation of the "
1122
 
                                       "requested plugin.",
1123
 
                                       "Confirm the installation of the "
1124
 
                                       "selected plugins.", len(pkgs))
1125
1270
        unsatisfied = [stru.name for stru in structures if not stru.satisfied]
1126
1271
        if unsatisfied:
1127
1272
            message += "\n\n"
1128
 
            message += gettext.ngettext("The following plugin isn't available:",
1129
 
                                        "The following plugins aren't "
1130
 
                                        "available:", len(unsatisfied))
 
1273
            message += gettext.ngettext("The following plugin is not "
 
1274
                                        "available:",
 
1275
                                        "The following plugins are not "
 
1276
                                        "available:",
 
1277
                                        len(unsatisfied))
1131
1278
            message += " "
1132
1279
            #TRANSLATORS: list separator
1133
 
            message += _(", ").join(unsatisfied)
 
1280
            message += self._get_bullet_list(unsatisfied)
1134
1281
 
1135
1282
        # We only show the details if there are packages which would only
1136
1283
        # provide a subset of the requests
1150
1297
                #TRANSLATORS: Separator for a list of plugins
1151
1298
                details = _(",\n").join(provides)
1152
1299
            install = pkg.name in best_providers
1153
 
            confirm.add_package(pkg, install, details, score)
 
1300
            confirm.add_confirm_package(pkg, install, details, score)
1154
1301
        res = confirm.run()
1155
1302
        if res == gtk.RESPONSE_OK:
1156
 
            return self.backend.install_packages(xid,
1157
 
                                                 [pkg.name for pkg in pkgs],
1158
 
                                                 interaction)
 
1303
            yield self.backend.install_packages(xid,
 
1304
                                                confirm.get_selected_pkgs(),
 
1305
                                                interaction)
1159
1306
        else:
1160
1307
            raise errors.ModifyCancelled
1161
1308
 
1162
1309
    @dbus_deferred_method(PACKAGEKIT_MODIFY_DBUS_INTERFACE,
1163
1310
                          in_signature="uass", out_signature="",
 
1311
                          sender_keyword="sender",
1164
1312
                          utf8_strings=True)
1165
 
    def RemovePackageByFiles(self, xid, files, interaction,):
 
1313
    def RemovePackageByFiles(self, xid, files, interaction, sender):
1166
1314
        """Remove packages which provide the given files.
1167
1315
 
1168
1316
        Keyword arguments:
1169
1317
        xid -- the window id of the requesting application
1170
1318
        files -- the list of file paths
1171
 
        interaction -- the interaction mode: which ui elements shoudl be
 
1319
        interaction -- the interaction mode: which ui elements should be
1172
1320
                       shown e.g. hide-finished or hide-confirm-search
1173
1321
        """
1174
1322
        log.info("RemovePackageByFiles() was called: %s, %s, %s", xid, files,
1175
1323
                 interaction)
1176
 
        return self._remove_package_by_files(xid, files, interaction)
 
1324
        return self._remove_package_by_files(xid, files, interaction, sender)
1177
1325
 
1178
1326
    @track_usage
1179
 
    def _remove_package_by_files(self, xid, files, interaction):
 
1327
    @inline_callbacks
 
1328
    def _remove_package_by_files(self, xid, files, interaction, sender):
 
1329
        parent = gtk.gdk.window_foreign_new(xid)
 
1330
        sender_name = yield self._get_sender_name(sender)
1180
1331
        if [filename for filename in files if not filename.startswith("/")]:
1181
1332
            raise errors.ModifyFailed("Only absolute file names")
1182
1333
        pkgs = []
1183
 
        self._init_cache()
 
1334
        title = _("Searching software to be removed")
 
1335
        if sender_name:
 
1336
            message = gettext.ngettext("%s wants to remove the software "
 
1337
                                       "which provides the following file:",
 
1338
                                       "%s wants to remove the software which "
 
1339
                                       "provides the following files:",
 
1340
                                       len(files)) % sender_name
 
1341
        else:
 
1342
            message = gettext.ngettext("The software which provides the "
 
1343
                                       "following file is requested to be "
 
1344
                                       "removed:"
 
1345
                                       "The software which provides the "
 
1346
                                       "following files is requested to be "
 
1347
                                       "removed:",
 
1348
                                       len(files))
 
1349
        message += self._get_bullet_list(files)
 
1350
        progress = ProgressDialog(title, message, parent)
 
1351
        progress.show_all()
 
1352
        self._init_cache(progress)
1184
1353
        for pkg in self._cache:
1185
 
            # FIXME: Fix python-apt
1186
1354
            try:
1187
1355
                for installed_file in pkg.installed_files:
1188
1356
                    if installed_file in files:
1189
 
                        pkgs.append(pkg.name)
 
1357
                        pkgs.append(pkg)
1190
1358
            except:
1191
1359
                pass
 
1360
        progress.hide()
1192
1361
        if not pkgs:
1193
1362
            self._show_error(_("Files are not installed"),
1194
1363
                             _("The files which should be removed are not "
1195
1364
                               "part of any installed software."))
1196
1365
            raise errors.ModifyNoPackagesFound
1197
 
        return self.backend.remove_packages(xid, pkgs, interaction)
 
1366
        title = gettext.ngettext("Remove software package?",
 
1367
                                 "Remove software packages?", len(pkgs))
 
1368
        if sender_name:
 
1369
            #TRANSLATORS: %s is the name of an application
 
1370
            message = gettext.ngettext("%s wants to remove the following "
 
1371
                                       "software package from your computer.",
 
1372
                                       "%s wants to remove the following "
 
1373
                                       "software packages from your computer.",
 
1374
                                       len(pkgs)) % sender_name
 
1375
        else:
 
1376
            message = gettext.ngettext("The following software package "
 
1377
                                       "will be removed from your computer.",
 
1378
                                       "The following software packages "
 
1379
                                       "will be removed from your computer.",
 
1380
                                       len(pkgs))
 
1381
        confirm = ConfirmInstallDialog(title, message, parent=parent,
 
1382
                                       selectable=len(pkgs) > 1, pkgs=pkgs,
 
1383
                                       action=_("_Remove"))
 
1384
        res = confirm.run()
 
1385
        if res == gtk.RESPONSE_OK:
 
1386
            yield self.backend.remove_packages(xid,
 
1387
                                               confirm.get_selected_pkgs(),
 
1388
                                               interaction)
 
1389
        else:
 
1390
            raise errors.ModifyCancelled
1198
1391
 
1199
1392
    def _show_error(self, header, message):
1200
1393
        """Show an error dialog."""
1202
1395
        dialog.run()
1203
1396
        dialog.hide()
1204
1397
 
 
1398
    def _get_bullet_list(self, lst):
 
1399
        """Return a string with a bullet list for the given list."""
 
1400
        text = ""
 
1401
        if len(lst) == 1:
 
1402
            text += " "
 
1403
            text += lst[0]
 
1404
        else:
 
1405
            for element in lst:
 
1406
                text += "\n• %s" % element
 
1407
        return text
1205
1408
 
1206
1409
def main():
1207
1410
    log.setLevel(logging.DEBUG)