#!/usr/bin/python
#
# yum-plugin-priorities 0.0.7
#
# Copyright (c) 2006-2007 Daniel de Kok
#
# 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.
#
# This plugins is inspired by the protectbase plugin, and enables/disables
# packages based on a repository priority.
#
# You can install this plugin by copying it to /usr/lib/yum-plugins. To
# enable this plugin, make sure that you have 'plugins=1' in /etc/yum.conf,
# and create the file /etc/yum/pluginconf.d/priorities.conf with the
# following content:
#
# [main]
# enabled=1
#
# If you also want the plugin to protect high-priority repositories against
# obsoletes in low-priority repositories, enable the 'check_obsoletes' bool:
#
# check_obsoletes=1
#
# By default, this plugin excludes packages from lower priority repositories
# based on the package name. If you want to exclude packages based only the
# package name and architecture, enable the 'only_samearch' bool:
#
# only_samearch=1
#
# If you want to be able to set package as the fully qualified package name
# including architecture and package version, enable 'full_match' bool:
#
# full_match=1
#
# If full_match is enabled then check_obsoletes will be forced to disable
#
# You can add priorities to repositories, by adding the line:
#
# priority=N
#
# to the repository entry, where N is an integer number. The default
# priority for repositories is 99. The repositories with the lowest
# number have the highest priority.
#
# Please report errors to Daniel de Kok <danieldk@pobox.com>

from yum.constants import *
from yum.plugins import TYPE_CORE
from yum import config
import yum

check_obsoletes = False
only_samearch = False
full_match = False

requires_api_version = '2.1'
plugin_type = (TYPE_CORE,)


def config_hook(conduit):
    global check_obsoletes
    global only_samearch
    global full_match

    # Plugin configuration
    check_obsoletes = conduit.confBool('main', 'check_obsoletes',
                                       default=False)
    only_samearch = conduit.confBool('main', 'only_samearch', default=False)
    full_match = conduit.confBool('main', 'full_match', default=False)

    if full_match:
        check_obsoletes = False

    # Repo priorities
    if yum.__version__ >= '2.5.0':
        # New style : yum >= 2.5
        config.RepoConf.priority = config.IntOption(99)
    else:
        # Old add extra options style
        conduit.registerOpt('priority', PLUG_OPT_INT, PLUG_OPT_WHERE_REPO, 99)

    # Command-line options.
    parser = conduit.getOptParser()
    if parser:
        if hasattr(parser, 'plugin_option_group'):
            parser = parser.plugin_option_group
        parser.add_option(
            '', '--samearch-priorities', dest='samearch', action='store_true',
            default=False,
            help="Priority-exclude packages based on name + arch")


def _all_repo_priorities_same(allrepos):
    """ Are all repos at the same priority """
    first = None
    for repo in allrepos:
        if first is None:
            first = repo.priority
        elif first != repo.priority:
            return False
    return True


def exclude_hook(conduit):
    global only_samearch
    global check_obsoletes
    global full_match

    allrepos = conduit.getRepos().listEnabled()

    # If they haven't done anything, don't do any work
    if _all_repo_priorities_same(allrepos):
        return

    # Check whether the user specified the --samearch option.
    opts, commands = conduit.getCmdLine()
    if opts and opts.samearch:
        only_samearch = True

    cnt = 0
    if check_obsoletes and not conduit._base.conf.obsoletes:
        check_obsoletes = False
    if check_obsoletes:
        obsoletes = conduit._base.up.rawobsoletes

    # Build a dictionary with package priorities. Either with arch or
    # archless, based on the user's settings.
    if only_samearch:
        pkg_priorities = dict()
    if check_obsoletes or not only_samearch:
        pkg_priorities_archless = dict()
    for repo in allrepos:
        if repo.enabled:
            if only_samearch:
                repopkgs = _pkglist_to_dict(conduit.getPackages(repo),
                                            repo.priority, True)
                _mergeprioritydicts(pkg_priorities, repopkgs)

            if check_obsoletes or not only_samearch:
                repopkgs_archless = _pkglist_to_dict(
                    conduit.getPackages(repo), repo.priority)
                _mergeprioritydicts(pkg_priorities_archless, repopkgs_archless)

    # Eliminate packages that have a low priority
    for repo in allrepos:
        if repo.enabled:
            for po in conduit.getPackages(repo):
                delPackage = False

                if full_match:
                    pname = str(po)
                else:
                    pname = po.name

                if only_samearch:
                    key = "%s.%s" % (pname, po.arch)
                    if (key in pkg_priorities and
                            pkg_priorities[key] < repo.priority):
                        delPackage = True
                else:
                    key = "%s" % pname
                    if (key in pkg_priorities_archless and
                            pkg_priorities_archless[key] < repo.priority):
                        delPackage = True

                if delPackage:
                    conduit.delPackage(po)
                    cnt += 1
                    conduit.info(3, " --> %s from %s excluded "
                                    "(priority)" % (po, po.repoid))

                # If this packages obsoletes other packages, check whether
                # one of the obsoleted packages is not available through
                # a repo with a higher priority. If so, remove this package.
                if check_obsoletes:
                    if po.pkgtup in obsoletes:
                        obsolete_pkgs = obsoletes[po.pkgtup]
                        for obsolete_pkg in obsolete_pkgs:
                            pkg_name = obsolete_pkg[0]
                            if (pkg_name in pkg_priorities_archless
                                and pkg_priorities_archless[pkg_name] <
                                    repo.priority):
                                conduit.delPackage(po)
                                cnt += 1
                                conduit.info(
                                    3, " --> %s from %s excluded "
                                       "(priority)" % (po, po.repoid))
                                break
    if cnt:
        conduit.info(2, '%d packages excluded due to repository '
                        'priority protections' % cnt)
    if check_obsoletes:
        #  Atm. the update object doesn't get updated when we manually exclude
        # things ... so delete it. This needs to be re-written.
        conduit._base.up = None


def _pkglist_to_dict(pl, priority, addArch=False):
    global full_match
    out = dict()
    for p in pl:
        if full_match:
            pname = str(p)
        else:
            pname = p.name
        if addArch:
            key = "%s.%s" % (pname, p.arch)
            out[key] = priority
        else:
            out[pname] = priority
    return out


def _mergeprioritydicts(dict1, dict2):
    for package in dict2.keys():
        if package not in dict1 or dict2[package] < dict1[package]:
            dict1[package] = dict2[package]
