Blob Blame History Raw
# coding=UTF-8

## Copyright (C) 2015 ABRT team <abrt-devel-list@redhat.com>
## Copyright (C) 2015 Red Hat, Inc.

## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.

## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.

## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
import sys

import dnf
import dnf.rpm
from dnf.exceptions import DownloadError, RepoError
from dnf.callback import (STATUS_OK,
                          STATUS_DRPM,
                          STATUS_ALREADY_EXISTS,
                          STATUS_MIRROR,
                          STATUS_FAILED)

from reportclient import (_, log1, log2)
from reportclient.debuginfo import DebugInfoDownload


class DNFProgress(dnf.callback.DownloadProgress):

    def __init__(self, observer):
        super(DNFProgress, self).__init__()

        self.observer = observer

    def end(self, payload, status, msg):
        # One may objects that this call back isn't necessary because the
        # progress() callback is called when downloading finishes, but if the
        # downloaded package is already in a local cache, DNF doesn't call
        # progress() callback at all but yet we want to inform user about that
        # progress.
        if status in [STATUS_OK, STATUS_DRPM, STATUS_ALREADY_EXISTS]:
            self.observer.update(str(payload), 100)
        elif status == STATUS_MIRROR:
            # In this case dnf (librepo) tries other mirror if available
            log1("Mirror failed: %s" % (msg or "DNF did not provide more details"))
        elif status == STATUS_FAILED:
            log2("Downloading failed: %s" % (msg or "DNF did not provide more details"))
        else:
            sys.stderr.write("Unknown DNF download status: %s\n" % (msg))

    def progress(self, payload, done):
        log2("Updated a package")
        self.observer.update(str(payload), int(100 * (done / payload.download_size)))

    def start(self, total_files, total_size):
        log2("Started downloading of a package")


class DNFDebugInfoDownload(DebugInfoDownload):

    def __init__(self, cache, tmp, repo_pattern="*debug*", keep_rpms=False,
                 noninteractive=True, releasever=None):
        super(DNFDebugInfoDownload, self).__init__(cache, tmp, repo_pattern, keep_rpms, noninteractive)

        self.progress = None

        self.base = dnf.Base()
        if not releasever is None:
            self.base.conf.substitutions['releasever'] = releasever

        # bug resurfaces in different forms. if it appears again try uncommenting
        ######   dnf pre API enforced
        # self.base.logging.presetup()
        ######   dnf 1.9 API enforced
        # self.base._logging.presetup()
        ######   dnf 2.0
        # self.base._logging._presetup()

    def prepare(self):
        try:
            self.base.read_all_repos()
        except dnf.exceptions.Error as ex:
            print(_("Error reading repository configuration: '{0!s}'").format(str(ex)))

    def initialize_progress(self, updater):
        self.progress = DNFProgress(updater)

    def initialize_repositories(self):
        # enable only debug repositories
        for repo in self.base.repos.all():
            repo.disable()

        for repo in self.base.repos.get_matching(self.repo_pattern):
            repo.skip_if_unavailable = True
            repo.enable()

        try:
            self.base.fill_sack()
        except RepoError as ex:
            print(_("Error setting up repositories: '{0!s}'").format(str(ex)))

    def triage(self, files):
        dnf_query = self.base.sack.query()
        dnf_available = dnf_query.available()
        package_files_dict = {}
        not_found = []
        todownload_size = 0
        installed_size = 0

        def required_packages(query, package, origin):
            """
            Recursive function to find all required packages of required packages of ...
              origin - should stop infinite recursion (A => B => ... => X => A)
            """
            required_pkg_list = []
            if package.requires:
                pkg_reqs = query.filter(provides=package.requires, arch=package.arch)
                for p in pkg_reqs:
                    if p.name != origin.name and p not in required_pkg_list:
                        required_pkg_list.append(p)
                        required_pkg_list += required_packages(query, p, origin)

            return required_pkg_list


        for debuginfo_path in files:
            di_package_list = []
            packages = dnf_available.filter(file=debuginfo_path)

            if not packages:
                log2("not found package for %s", debuginfo_path)
                not_found.append(debuginfo_path)
            else:
                di_package_list.append(packages[0])
                if packages[0].requires:
                    package_reqs = required_packages(dnf_available, packages[0], packages[0])
                    for pkg in package_reqs:
                        if pkg not in di_package_list:
                            di_package_list.append(pkg)
                            log2("found required package {0} for {1}".format(pkg, packages[0]))


                for pkg in di_package_list:
                    if pkg in package_files_dict.keys():
                        package_files_dict[pkg].append(debuginfo_path)
                    else:
                        package_files_dict[pkg] = [debuginfo_path]
                        todownload_size += float(pkg.downloadsize)
                        installed_size += float(pkg.installsize)

                    log2("found packages for %s: %s", debuginfo_path, pkg)
        return (package_files_dict, not_found, todownload_size, installed_size)

    def download_package(self, pkg):
        try:
            self.base.download_packages([pkg], self.progress)
        except DownloadError as ex:
            return (None, str(ex))

        return (pkg.localPkg(), None)