#!/usr/bin/python3
#   Copyright (C) 2013 Canonical Ltd.
#
#   Author: Scott Moser <scott.moser@canonical.com>
#
#   Simplestreams is free software: you can redistribute it and/or modify it
#   under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or (at your
#   option) any later version.
#
#   Simplestreams 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 Affero General Public
#   License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with Simplestreams.  If not, see <http://www.gnu.org/licenses/>.

from simplestreams import mirrors
from simplestreams.mirrors import command_hook
from simplestreams import log
from simplestreams import util

import argparse
import errno
import os
import signal
import sys
import yaml


def which(program):
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, _fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None


def warn(msg):
    sys.stderr.write("WARN: %s" % msg)


def main():
    parser = argparse.ArgumentParser()
    defhook = command_hook.DEFAULT_HOOK_NAME

    hooks = [("--hook-%s" % hook.replace("_", "-"), hook, False)
             for hook in command_hook.HOOK_NAMES]
    hooks.append(('--hook', defhook, False,))

    parser.add_argument('--config', '-c',
                        help='read config file',
                        type=argparse.FileType('rb'))

    for (argname, cfgname, _required) in hooks:
        parser.add_argument(argname, dest=cfgname, required=False)

    parser.add_argument('--keep', action='store_true', default=False,
                        dest='keep_items',
                        help='keep items in target up to MAX items '
                             'even after they have fallen out of the source')
    parser.add_argument('--max', type=int, default=None, dest='max_items',
                        help='store at most MAX items in the target')
    parser.add_argument('--item-skip-download', action='store_true',
                        default=False,
                        help='Do not download items that are to be inserted.')
    parser.add_argument('--delete', action='store_true', default=False,
                        dest='delete_filtered_items',
                        help='remove filtered items from the target')
    parser.add_argument('--path', default=None,
                        help='sync from index or products file in mirror')

    parser.add_argument('--verbose', '-v', action='count', default=0)
    parser.add_argument('--log-file', default=sys.stderr,
                        type=argparse.FileType('w'))

    parser.add_argument('--keyring', action='store', default=None,
                        help='keyring to be specified to gpg via --keyring')

    parser.add_argument('mirror_url')
    cmdargs = parser.parse_args()

    known_cfg = [('--item-skip-download', 'item_skip_download', False),
                 ('--max', 'max_items', False),
                 ('--keep', 'keep_items', False),
                 ('--delete', 'delete_filtered_items', False),
                 ('mirror_url', 'mirror_url', True),
                 ('--path', 'path', True)]
    known_cfg.extend(hooks)

    cfg = {}
    if cmdargs.config:
        cfg = yaml.safe_load(cmdargs.config)
        if not cfg:
            cfg = {}

    known_names = [i[1] for i in known_cfg]
    unknown = [key for key in cfg if key not in known_names]
    if unknown:
        warn("unknown keys in config: %s\n" % str(unknown))

    missing = []
    fallback = cfg.get(defhook, getattr(cmdargs, defhook, None))

    for (argname, cfgname, _required) in known_cfg:
        val = getattr(cmdargs, cfgname)
        if val is not None:
            cfg[cfgname] = val
        if val == "":
            cfg[cfgname] = None

        if ((cfgname in command_hook.HOOK_NAMES or cfgname == defhook)
                and cfg.get(cfgname) is not None):
            if which(cfg[cfgname]) is None:
                msg = "invalid input for %s. '%s' is not executable\n"
                sys.stderr.write(msg % (argname, val))
                sys.exit(1)

        if (cfgname in command_hook.REQUIRED_FIELDS and
                cfg.get(cfgname) is None and not fallback):
            missing.append((argname, cfgname,))

    pfm = util.path_from_mirror_url
    (cfg['mirror_url'], cfg['path']) = pfm(cfg['mirror_url'], cfg.get('path'))

    if missing:
        sys.stderr.write("must provide input for (--hook/%s for default):\n"
                         % defhook)
        for (flag, cfg) in missing:
            sys.stderr.write("  cmdline '%s' or cfgname '%s'\n" % (flag, cfg))
        sys.exit(1)

    level = (log.ERROR, log.INFO, log.DEBUG)[min(cmdargs.verbose, 2)]
    log.basicConfig(stream=cmdargs.log_file, level=level)

    def policy(content, path):  # pylint: disable=W0613
        if cfg['path'].endswith('sjson'):
            return util.read_signed(content, keyring=cmdargs.keyring)
        else:
            return content

    smirror = mirrors.UrlMirrorReader(cfg['mirror_url'], policy=policy)
    tmirror = command_hook.CommandHookMirror(config=cfg)
    try:
        tmirror.sync(smirror, cfg['path'])
    except IOError as e:
        if e.errno == errno.EPIPE:
            sys.exit(0x80 | signal.SIGPIPE)
        raise

if __name__ == '__main__':
    main()

# vi: ts=4 expandtab syntax=python
