#!/bin/sh
# -*- indent-tabs-mode: nil; -*-
: 'Check the version status of installed ports and compare them to
repository versions.

:Author:    Franz Glasner
:Copyright: (c) 2017-2019 Franz Glasner.
            All rights reserved.
:License:   BSD 3-Clause "New" or "Revised" License.
            See LICENSE for details.
            If you cannot find LICENSE see
            <https://opensource.org/licenses/BSD-3-Clause>
:ID:        @(#)@@PKGORIGIN@@ $HGid$

'

VERSION="@@VERSION@@"

#
# Configuration directory
#
: ${CONFIGDIR:=@@ETCDIR@@}

test -r "${CONFIGDIR}/tools.conf" && . "${CONFIGDIR}/tools.conf"

#
# Mapping configuration: installed package name -> original package name
# Note: This is independent of any repo
#
: ${PACKAGE_MAPPING:=${CONFIGDIR}/package-mapping.conf}

#
# Local repository with non-public packages and/or ports with changed
# OPTIONS (i.e. not using the defaults) or forks of official packages with
# other package names
#
: ${LOCAL_REPO:=LocalRepo}

#
# Local repository with ports with default OPTIONS (i.e. unchanged)
# but newer than the packages in the "FreeBSD" repository.
# Some sort of a fast-track repository.
#
: ${LOCALBSDPORTS_REPO:=LocalBSDPorts}

#
# The official FreeBSD binary repository
#
: ${FREEBSD_REPO:=FreeBSD}

#
# Directly installed from ports
#
: ${PORTS_DIRECT_INSTALLED_REPO:=unknown-repository}

#
# For the workaround of the bug in pkg rquery -I
#
: ${PORTSDIR:=/usr/ports}
: ${INDEXDIR:=${PORTSDIR}}
: ${INDEXFILE:=@@INDEXFILE@@}


get_remote_repo_versions() {
    : 'Determine the remote repository versions of all packages in
    repository `_repo`.

    Args:
        _repo: the name of the repote repository

    Returns:
        status 0 on success, 1 on errors

    Output (Globals):
        remote_versions_${_repo}: the versions of all packages in `_repo` and
                                  their extended version status with respect to
                                  locally installed packages

    '
    local _repo _data _rv

    _repo=$1

    _data=$(pkg version -U -R -r ${_repo} -v)
    _rv=$?
    eval remote_versions_${_repo}=\"\${_data}\"
    return ${_rv}
}

get_remote_repo_data() {
    : 'Get the extended package version information from the remote repository
    `_repo` for package `_name`.

    Args:
        _repo: the name of the remote repository
        _name: the package name

    Input (Globals):
        remote_versions_${_repo}: the extended version info for *all* packages
                                  in repo `_repo`.

    Returns:
        status 0 on success, 1 on errors or if the package is not found
        in the remote repository

    Output (Globals):
        remote_label_${_repo}:
        remote_descr_${_repo}:

    '
    local _repo _name _rversions _rfqp _rl _rdescr

    _repo=$1
    _name=$2

    eval _rversions=\"\${remote_versions_${_repo}}\"
    while read _rfqp _rl _rdescr ; do
        if [ ${_rfqp%-*} = ${_name} ] ; then
            eval remote_label_${_repo}=\"\${_rl}\"
            eval remote_descr_${_repo}=\"\${_rdescr}\"
            return 0
        fi
    done <<EOF884657
${_rversions}
EOF884657
    eval remote_label_${_repo}=""
    eval remote_descr_${_repo}=""
    return 1
}

get_local_index_versions() {
    : 'Determine the extendes versions of all packages in the local index
    (ports).

    Returns:
        status 0 on success or 1 on errors

    Output (Globals):
        local_index_versions:

    '
    local_index_versions=$(pkg version -I -v)
}

get_repo_for_installed_package() {
    : 'Determine for a package `_name` from which repository is has been
    installed.

    Args:
        _name: the name of the package to search for

    Input (Globals):
        installed_data  the output of ``pkg query "%n %v %R"`` of all
                        installed packages

    Returns:
        0 on success, 1 on errors or if the package is not installed

    Output (Globals):
        repository: the repository from which the installed packages `_name`
                    has been installed

    '
    local _name _n _v _r

    _name=$1

    while read _n _v _r ; do
        if [ ${_name} = ${_n} ] ; then
            repository=${_r}
            return 0
        fi
    done <<EOF223777
${installed_data}
EOF223777
    return 1
}

get_immediate_index_version() {
    : 'Determine for package `_package` the version of the package in the
    local ports index.

    Args:
        _package: the package name to search for

    Input (Globals):
        INDEXDIR:  the directory where to search the index file
        INDEXFILE: the name of the index file

    Returns:
        0 on success, 1 on errors or if the package is not in the index

    Output (Globals):
        immediate_index_version: the version number of `_package` in the
                                 index

    '
    local _package _line _fqpn _n _lines

    _package=$1

#    _val=$(pkg rquery -I "${_package}" | cut -f 1 -d '|')
#    _rv=$?
#    immediate_index_version=${_val##*-}
#    return ${_rv}

    if [ -r "${INDEXDIR}/${INDEXFILE}" ] ; then
        #
        # Note: Direct piping does not set immediate_index_version at return correctly
        #       "_line" is set correctly and parsing works, but the return 0 seems to kill
        #       some of the previous effects.
        #
        # "grep" does a fast pre-selection, reading, parsing and comparing is done for
        # exact matching.
        #
        _lines=$(egrep '^'"${_package}" "${INDEXDIR}/${INDEXFILE}")
        while read _line ; do
            _fqpn="${_line%%|*}"
            _n=${_fqpn%-*}
	    if [ "${_package}" = "${_n}" ] ; then
                immediate_index_version="${_fqpn##*-}"
                return 0
            fi
        done <<EOF1334TGH1
${_lines}
EOF1334TGH1
    fi

    immediate_index_version=""
    return 1
}

get_immediate_remote_repo_version() {
    :'Ask a remote repository for the version of a package.

    Args:
        _repo: the repository name
        _name: the name of the package

    Returns:
        0 on success and other status codes otherwise

    Output (Globals):
        immediate_remote_repo_version_${_repo}: the version of package `_name`
                                in repo `_repo`

    '
    local _repo _name _version _rv

    _repo=$1
    _name=$2

    _version=$(pkg rquery -U -r "${_repo}" '%v' "${_name}")
    _rv=$?
    eval immediate_remote_repo_version_${_repo}=\"\${_version}\"
    return ${_rv}
}

assert_local_version() {
    : 'Check whether an installed package `_name` has given
    version `_version`.

    Args:
        _name:    the package name
        _version: the version to check for

    Input (Globals):
        installed_data: the output of ``pkg query "%n %v %R"`` of all
                        installed packages

    Returns:
        - 0 if the installed version of package `_name` is equal to `_version`
        - 1 if the installed version of package `_name` differs from `_version`
        - 2 on other errors

    '
    local _name _version _n _v _r

    _name=$1
    _version=$2

    while read _n _v _r ; do
        if [ ${_name} = ${_n} ] ; then
            if [ ${_version} != ${_v} ] ; then
                return 1
            else
                return 0
            fi
        fi
    done <<EOF223
${installed_data}
EOF223
    return 2
}

get_mapping() {
    : 'Determine whether a package `_package` is essentially the same as
    another package.

    Args:
        _package: the new name of the package

    Returns:
        0 when a package mapping has been found, 1 otherwise

    Output (Globals):
        mapped_package_name: the name of the package on which `_package` is
                             based on

    This command reads from the mapping database in in file
    `/usr/local/etc/local-bsdtools/package-mapping.conf`.
    Example::

        #
        # _package                         mapped_package_name
        #
        fmg-nextcloud-php71                nextcloud-php71
        fmg-nextcloud-twofactor_totp-php71 nextcloud-twofactor_totp-php71

    '
    local _package _n _mapped

    _package=$1

    if [ -r "${PACKAGE_MAPPING}" ] ; then
        while read _n _mapped ; do
            if [ "${_n}" = "${_package}" ] ; then
                mapped_package_name="${_mapped}"
                return 0
            fi
        done < ${PACKAGE_MAPPING}
    fi
    mapped_package_name=""
    return 1
}

print_title() {
    : 'Print the output title line for a package

    Args:
        _package: the package name
        _repo:    the repository name

    Input (Globals).
        title_printed: a global that determines if the title really needs
                       to be printed.

                       If it is an empty string the the title is
                       really printed and the variable is set to
                       "yes".

    Output (Globals):
        title_printed: set to "yes" if the title has been printed

    '
    local _package _repo

    _package=$1
    _repo=$2
    if [ -z "${title_printed}" ] ; then
        echo "${_package}    (${_repo})"
        title_printed=yes
    fi
}


alldata_flag=""
alldata_flag_LocalBSDPorts=""
alldata_flag_LocalRepo=""
short_flag=""
verbose_flag=""

while getopts "VAasv" _opt ; do
    case ${_opt} in
	V)
            echo "check-ports v${VERSION} (rv:@@HGREVISION@@)"
            exit 0
	    ;;
        A)
            # print for every package the status of all repositories
            alldata_flag=1
            alldata_flag_LocalBSDPorts=1
            alldata_flag_LocalRepo=1
            ;;
        a)
            # print the data of all repos that have the package
            alldata_flag=1
            ;;
        s)
            # "short" output: if installed from FreeBSD repo: don't
            # report if only the index is newer
            short_flag=1
            ;;
        v)
            # print all titles and repo of every installed always
            verbose_flag=1
            ;;
        \?)
            exit 2
            ;;
        *)
            echo "option handling failed" >&2
            exit 2
            ;;
    esac
done

if [ -n "${short_flag}" -a -n "${alldata_flag}" ]; then
    echo "the -s option cannot be combined with -A or -a" >&2
    exit 2
fi

installed_packages=$(pkg query '%n')
installed_data="$(pkg query '%n %v %R' $installed_packages)"

get_remote_repo_versions ${LOCAL_REPO}
get_remote_repo_versions ${LOCALBSDPORTS_REPO}
get_remote_repo_versions ${FREEBSD_REPO}
get_local_index_versions

while read lfqp llabel ldescr ; do
    _installed_name=${lfqp%-*}
    _installed_version=${lfqp##*-}
    title_printed=""
    get_repo_for_installed_package ${_installed_name}
    get_mapping ${_installed_name}
    if [ -n "${verbose_flag}" ] ; then
        print_title "${lfqp}" "${repository}"
    fi
    if ! assert_local_version ${_installed_name} ${_installed_version} ; then
        echo "Assertion failed: $lfqp ${_installed_name} ${_installed_version} ${llabel}" >&2
        exit 1
    fi
    get_remote_repo_data ${LOCAL_REPO} ${_installed_name}
    get_remote_repo_data ${LOCALBSDPORTS_REPO} ${_installed_name}
    get_remote_repo_data ${FREEBSD_REPO} ${_installed_name}
    _print_detail=""
    if [ -n "${mapped_package_name}" ] ; then
        _print_detail=1
    fi
    if [ \( -n "${alldata_flag}" \) ]; then
        _print_detail=1
    else
        if [ -n "${short_flag}" ]; then
            #
            # NOTE: -s and -A/-a are incompatible: so "alldata_XXX" needs not
            #       to be checked!
            #
            case "${repository}" in
                "${FREEBSD_REPO}")
                    if [ \( "${llabel}" != '<' -a "${llabel}" != '=' \) -o "${remote_label_FreeBSD}" != '=' -o "${remote_label_LocalRepo}" != '?' -o "${remote_label_LocalBSDPorts}" != '?' ]; then
                        _print_detail=1
                    fi
                    ;;
                "${LOCAL_REPO}")
                    _print_detail=1
                    ;;
                "${LOCALBSDPORTS_REPO}")
                    if [ "${llabel}" != '=' -o "${remote_label_FreeBSD}" != '>' -o "${remote_label_LocalRepo}" != '?' -o "${remote_label_LocalBSDPorts}" = '?' -o "${remote_label_LocalBSDPorts}" = '<' ]; then
                        _print_detail=1
                    fi
                    ;;
                "${PORTS_DIRECT_INSTALLED_REPO}")
                    _print_detail=1
                    ;;
                *)
                    echo "ERROR: unhandled repository: ${repository}" >&2
                    exit 1
                    ;;
            esac
        else
            if [ \( \( "${llabel}" != '?' -a "${llabel}" != '=' \) -o \( "${remote_label_FreeBSD}" != '?' -a "${remote_label_FreeBSD}" != '=' \) -o \( "${remote_label_LocalBSDPorts}" != '?' -a "${remote_label_LocalBSDPorts}" != '=' \) -o \( "${remote_label_LocalRepo}" != '?' -a "${remote_label_LocalRepo}" != '=' \) \) -o \( "${repository}" = "${PORTS_DIRECT_INSTALLED_REPO}" \) ]; then
                _print_detail=1
            fi
        fi
    fi
    if [ -n "${_print_detail}" ]; then
        print_title "${lfqp}" "${repository}"
        echo "   INDEX        : ${llabel} ${ldescr}"
        echo "   FreeBSD      : ${remote_label_FreeBSD} ${remote_descr_FreeBSD}"
        if [ \( -n "${alldata_flag_LocalBSDPorts}" \) -o \( "${remote_label_LocalBSDPorts}" != '?' \) ] ; then
            echo "   LocalBSDPorts: ${remote_label_LocalBSDPorts} ${remote_descr_LocalBSDPorts}"
        fi
        if [ \( -n "${alldata_flag_LocalRepo}" \) -o \( "${remote_label_LocalRepo}" != '?' \) ] ; then
            echo "   LocalRepo    : ${remote_label_LocalRepo} ${remote_descr_LocalRepo}"
        fi
        if [ -n "${mapped_package_name}" ] ; then
	    echo "   ---> ${mapped_package_name}"
            get_immediate_index_version "${mapped_package_name}"
	    get_immediate_remote_repo_version ${LOCAL_REPO} ${mapped_package_name}
            get_immediate_remote_repo_version ${LOCALBSDPORTS_REPO} ${mapped_package_name}
            get_immediate_remote_repo_version ${FREEBSD_REPO} ${mapped_package_name}
            echo "      INDEX        : ${immediate_index_version}"
            echo "      FreeBSD      : ${immediate_remote_repo_version_FreeBSD}"
	    echo "      LocalBSDPorts: ${immediate_remote_repo_version_LocalBSDPorts}"
	    echo "      LocalRepo    : ${immediate_remote_repo_version_LocalRepo}"
        fi
    fi
done <<EOF856661111299999
${local_index_versions}
EOF856661111299999
