#!/usr/bin/python3 -s

import subprocess
import sys
import argparse
import os
import re
import time
import shutil

parser = argparse.ArgumentParser(description='Remove old packages from rpm-md repository')

parser.add_argument('path', action='store',
                   help='local path to a yum repository')
parser.add_argument('--days', type=int, action='store', default=0,
                   help='only remove packages (and build directories when --cleancopr is used)\
                   that are DAYS old or older (for packages by their build date, for directories\
                   the last modification time is considered')
parser.add_argument('--cleancopr', action='store_true',
                   help='additionaly remove whole copr build dirs and logs if the associated package gets deleted')
parser.add_argument('--nocreaterepo', action='store_true',
                   help='repository is not automatically recreated after deletion')
parser.add_argument('--verbose', action='store_true',
                   help='print all deleted items to stdout')
parser.add_argument('--quiet', action='store_true',
                   help='do not print any info messages, just do your job')
parser.add_argument('-v', '--version', action='version', version='1.5',
                   help='print program version and exit')

args = parser.parse_args()


get_all_packages_cmd = [
    'dnf',
    'repoquery',
    '--disablerepo=*',
    '--repofrompath=prunerepo_query,'+os.path.abspath(args.path),
    '--repoid=prunerepo_query',
    '--enablerepo=prunerepo_query',
    '--refresh',
    '--queryformat="%{location}"',
    '--quiet',
]

get_latest_packages_cmd = get_all_packages_cmd + [ '--latest-limit=1' ]


def is_srpm(package):
    return re.match(r'.*\.src\.rpm$', package)


def rm_file(path):
    """
    Remove file given its absolute path
    """
    if args.verbose:
        log_info("Removing: "+path)
    if os.path.exists(path) and os.path.isfile(path):
        os.remove(path)


def log_info(msg):
    if not args.quiet:
        print(msg)


def run_cmd(cmd, silent=False):
    """
    Run given command in a subprocess
    """
    if not silent:
        log_info("Executing: "+' '.join(cmd))
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = process.communicate()
    if process.returncode != 0:
        print(stderr.decode(encoding='utf-8'), file=sys.stderr)
        sys.exit(1)
    return [line.strip('"') for line in stdout.decode(encoding='utf-8').split()] # NOTE: for some reason the get_all_packages_cmd gives output as b'"..."\n"..."\n', hence line.strip('"')


def get_package_build_time(package_path):
    """
    Get build time by reading package metadata
    """
    createrepo_cmd = ['/usr/bin/rpm', '-qp', '--queryformat', '%{BUILDTIME}'] + [ package_path ]
    stdout = run_cmd(createrepo_cmd, silent=True)
    return int(stdout[0])


def get_rpms(repoquery_cmd):
    """
    Get paths to rpm packages in the repository according to given repoquery_cmd
    """
    stdout = run_cmd(repoquery_cmd) # returns srpms as well
    rel_rpms_paths = [relpath.strip('"') for relpath in stdout if not is_srpm(relpath)]
    abs_rpms_paths = [os.path.abspath(os.path.join(args.path, relpath)) for relpath in rel_rpms_paths]
    return abs_rpms_paths


def rm_srpm(rpm):
    """
    If there is matching srpm in the same directory as given rpm (described by its absolute path), delete it
    """
    get_srpm_cmd = get_all_packages_cmd + [ '--srpm', os.path.splitext(os.path.basename(rpm))[0] ]
    try:
        srpm_name = os.path.basename(run_cmd(get_srpm_cmd, silent=True)[0])
        rm_file(os.path.abspath(os.path.join(os.path.dirname(rpm), srpm_name)))
    except IndexError:
        # no srpm found, just skip it
        pass


def prune_packages():
    """
    Remove obsoleted packages
    """
    log_info('Removing obsoleted packages...')
    latest_rpms = get_rpms(get_latest_packages_cmd)
    all_rpms = get_rpms(get_all_packages_cmd)
    to_remove_rpms = set(all_rpms) - set(latest_rpms)
    for rpm in to_remove_rpms:
        if time.time() - get_package_build_time(rpm) > args.days * 24 * 3600:
            rm_srpm(rpm)
            rm_file(rpm)


def recreate_repo():
    """
    Recreate the repository by using createrepo_c
    """
    log_info("Recreating repository...")
    createrepo_cmd = ['/usr/bin/createrepo_c', '--database', '--update'] + [ args.path ]
    return run_cmd(createrepo_cmd)


def clean_copr():
    """
    Remove whole copr build dirs if they no longer contain a srpm/rpm file
    """
    log_info("Cleaning COPR repository...")
    for dir_name in os.listdir(args.path):
        dir_path = os.path.abspath(os.path.join(args.path, dir_name))

        if not os.path.isdir(dir_path):
            continue
        if not os.path.isfile(os.path.join(dir_path, 'build.info')):
            continue
        if [item for item in os.listdir(dir_path) if re.match(r'.*\.rpm$', item)]:
            continue
        if time.time() - os.stat(dir_path).st_mtime <= args.days * 24 * 3600:
            continue

        if args.verbose:
            log_info('Removing: '+dir_path)
        shutil.rmtree(dir_path)

        # also remove the associated log in the main dir
        build_id = os.path.basename(dir_path).split('-')[0]
        buildlog_name = 'build-' + build_id + '.log'
        buildlog_path = os.path.abspath(os.path.join(args.path, buildlog_name))
        rm_file(os.path.join(args.path, buildlog_path))


if __name__ == '__main__':
    prune_packages()
    if not args.nocreaterepo:
        recreate_repo()
    if args.cleancopr:
        clean_copr()
