#!/usr/bin/python -u
#
# Copyright (c) 2008--2015 Red Hat, Inc.
# Copyright (c) 2011 SUSE LLC
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#

LOCK = None
import re
import StringIO
import simplejson as json
import shutil
import sys
import os
import socket
from optparse import OptionParser


def systemExit(code, msg=None):
    "Exit with a code and optional message(s). Saved a few lines of code."
    sys.stderr.write(str(msg)+'\n')
    sys.exit(code)

try:
    from rhn import rhnLockfile
    from spacewalk.common.rhnConfig import CFG, initCFG
    from spacewalk.common.rhnTB import fetchTraceback
    from spacewalk.satellite_tools import reposync
except KeyboardInterrupt:
    systemExit(0, "\nUser interrupted process.")
except ImportError:
    systemExit(1, "Unable to find code tree.\n"
               "Path not correct? " + sys.path)

def releaseLOCK():
    global LOCK
    if LOCK:
        LOCK.release()
        LOCK = None

def clear_interrupted_downloads():
    initCFG('server.satellite')
    pkgdir = os.path.join(CFG.MOUNT_POINT, CFG.PREPENDED_DIR, '1', 'stage')
    if os.path.exists(pkgdir):
        shutil.rmtree(pkgdir)

def main():

    # quick check to see if you are a super-user.
    if os.getuid() != 0:
        systemExit(8, 'ERROR: must be root to execute.')

    global LOCK
    try:
        LOCK = rhnLockfile.Lockfile('/var/run/spacewalk-repo-sync.pid')
    except rhnLockfile.LockfileLockedException:
        systemExit(1, "ERROR: attempting to run more than one instance of "
                      "spacewalk-repo-sync Exiting.")

    parser = OptionParser()
    parser.add_option('-l', '--list', action='store_true', dest='list',
                      help='List the custom channels with the assosiated repositories.')
    parser.add_option('-u', '--url', action='append', dest='url',
                      default=[], help='The url of the repository. Can be used multiple times.')
    parser.add_option('-c', '--channel', action='store',
                      dest='channel_label',
                      help='The label of the channel to sync packages to. In order to specify more than one channel see option --config.')
    parser.add_option('-p', '--parent-channel', action='append',
                      dest='parent_label', default=[],
                      help='The label of the channel to sync packages to')
    parser.add_option('-d', '--dry-run', action='store_true',
                      dest='dry_run',
                      help='Test run. No sync takes place.')
    parser.add_option('-n', '--latest', action='store_true',
                      dest='latest',
                      help='Sync lastest packages only. Use carefully - you might need to fix some dependencies on your own.')
    parser.add_option('-g', '--config', action='store', dest='config',
               help='Configuration file')
    parser.add_option('-t', '--type', action='store', dest='repo_type',
                      help='The type of repo, currently only "yum" and "uln" are supported',
                      default='yum')
    parser.add_option('-f', '--fail', action='store_true', dest='fail',
                      default=False,
                      help="If a package import fails, fail the entire operation")
    parser.add_option('-q', '--quiet', action='store_true', dest='quiet',
                      default=False, help="Print no output, still logs output")
    parser.add_option('-i', '--include', action='callback',
                      callback=reposync.set_filter_opt, type='str', nargs=1,
                      dest='filters', default=[],
                      help="List of included packages")
    parser.add_option('-e', '--exclude', action='callback',
                      callback=reposync.set_filter_opt,
                      type='str', nargs=1, dest='filters', default=[],
                      help="List of excluded packages")
    parser.add_option('', '--no-errata', action='store_true', dest='no_errata',
                      default=False, help="Do not sync errata")
    parser.add_option('', '--sync-kickstart', action='store_true', dest='sync_kickstart',
                      default=False, help="Sync kickstartable tree")
    (options, args) = parser.parse_args()

    l_params=["no_errata", "sync_kickstart", "quiet", "fail"]
    d_chan_repo=reposync.getChannelRepo()
    l_ch_custom=reposync.getCustomChannels()
    d_parent_child=reposync.getParentsChilds()
    d_ch_repo_sync={}

    if options.list:
        print "======================================"
        print "|   Channel Label   |   Repository   |"
        print "======================================"
        for ch in d_chan_repo:
            for repo in d_chan_repo[ch]:
                print "%s | %s" %(ch,repo)
        for ch in list(set(l_ch_custom)-set(d_chan_repo)):
            print "%s | No repository set" % ch
        return 0

    # Automatically remove all the remains inside of /var/spacewalk/packages/1/stage
    # Yum does not seem to handle download resumes properly. This can lead to
    # errors like:
    #
    # > Repo Sync Errors: (50, u'checksums did not match 326a904c2fbd7a0e20033c87fc84ebba6b24d937 vs
    # > afd8c60d7908b2b0e2d95ad0b333920aea9892eb', 'Invalid information uploaded to the server')
    clear_interrupted_downloads()

    if not options.channel_label and not options.parent_label and not options.config:
        systemExit(1, "--channel, --parent-channel or --config must be specifed.")

    if options.config:
        try:
            config_file = open(options.config).read()
            # strip  all whitespace
            config_file = re.sub(r'\s', '', config_file)
            config = json.load(StringIO.StringIO(config_file))

        except Exception as e:
            systemExit(1, "Configuration file is invalid, please check syntax. Error [%s]" % e )

        for key in l_params:
            if config.has_key(key) and not getattr(options, key):
                setattr(options, key, config[key])

        # Channels
        if config.has_key('channel'):
            for ch,repo in config['channel'].iteritems():
                 d_ch_repo_sync[ch]=repo

        if config.has_key('parent_channel'):
            options.parent_label+=config['parent_channel']

    if options.channel_label:
        d_ch_repo_sync[options.channel_label]=options.url

    if options.parent_label:
        for pch in options.parent_label:
            if pch in d_parent_child:
               for ch in [pch]+d_parent_child[pch]:
                    if ch in l_ch_custom:
                        d_ch_repo_sync[ch]=[]
            else:
                systemExit(1, "Channel %s is not custom base channel." % pch)

    for ch in d_ch_repo_sync:
        if ch not in l_ch_custom:
            systemExit(1, "Channel %s is not custom or does not exist." % ch)
        if not d_ch_repo_sync[ch] and not ch in d_chan_repo:
            systemExit(1, "Channel %s Channel has no URL associated" % ch)

    if options.dry_run:
         print "======================================"
         print "|   Channel Label   |   Repository   |"
         print "======================================"

         for ch,repo in d_ch_repo_sync.items():
             if repo:
                print " %s : %s" % (ch,", ".join(repo))
             else:
                print " %s : %s" % (ch,", ".join(d_chan_repo[ch]))

         print "======================================"
         print "|             Parameters             |"
         print "======================================"
         for key in  l_params:
             print " %s: %s" % (key,str(getattr(options, key)))
         return 0

    for ch,repo in d_ch_repo_sync.items():

        print "======================================"
        print "| Channel: %s" % ch
        print "======================================"
        sync = reposync.RepoSync(channel_label=ch,
                      repo_type=options.repo_type,
                      url=repo,
                      fail=options.fail,
                      quiet=options.quiet,
                      filters=options.filters,
                      no_errata=options.no_errata,
                      sync_kickstart=options.sync_kickstart,
                      latest=options.latest)
        sync.sync()
    releaseLOCK()
    return 0


if __name__ == '__main__':
    try:
        sys.exit(abs(main() or 0))
    except KeyboardInterrupt:
        systemExit(0, "\nUser interrupted process.")
    except SystemExit, e:
        releaseLOCK()
        sys.exit(e.code)
    except Exception, e:
        releaseLOCK()
        raise
