# DistUpgradeMain.py
#
#  Copyright (c) 2004-2008 Canonical
#
#  Author: Michael Vogt <michael.vogt@ubuntu.com>
#
#  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.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA

import apt
import gettext
import glob
import logging
import os
import shutil
import subprocess
import sys

from datetime import datetime
from optparse import OptionParser
from gettext import gettext as _

# dirs that the packages will touch, this is needed for the coherence check
# before the upgrade
SYSTEM_DIRS = ["/bin",
              "/boot",
              "/etc",
              "/initrd",
              "/lib",
              "/lib32", # ???
              "/lib64", # ???
              "/sbin",
              "/usr",
              "/var",
              ]


from .DistUpgradeConfigParser import DistUpgradeConfig


def do_commandline():
    " setup option parser and parse the commandline "
    parser = OptionParser()
    parser.add_option("--frontend", dest="frontend",default=None,
                      help=_("Use frontend. Currently available: \n"\
                             "DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE"))
    parser.add_option("--mode", dest="mode",default="desktop",
                      help=_("*DEPRECATED* this option will be ignored"))
    parser.add_option("--partial", dest="partial", default=False,
                      action="store_true",
                      help=_("Perform a partial upgrade only (no sources.list rewriting)"))
    parser.add_option("--disable-gnu-screen", action="store_true",
                      default=False,
                      help=_("Disable GNU screen support"))
    parser.add_option("--datadir", dest="datadir", default=".",
                      help=_("Set datadir"))
    parser.add_option("--devel-release", action="store_true",
                      dest="devel_release", default=False,
                      help=_("Upgrade to the development release"))
    return parser.parse_args()

def setup_logging(options, config):
    " setup the logging "
    logdir = config.getWithDefault("Files","LogDir","/var/log/dist-upgrade/")
    if not os.path.exists(logdir):
        os.mkdir(logdir)
    # check if logs exists and move logs into place
    if glob.glob(logdir+"/*.log"):
        now = datetime.now()
        backup_dir = logdir+"/%04i%02i%02i-%02i%02i" % (now.year,now.month,now.day,now.hour,now.minute)
        if not os.path.exists(backup_dir):
            os.mkdir(backup_dir)
        for f in glob.glob(logdir+"/*.log"):
            shutil.move(f, os.path.join(backup_dir,os.path.basename(f)))
    fname = os.path.join(logdir,"main.log")
    # do not overwrite the default main.log
    if options.partial:
        fname += ".partial"
    with open(fname, "a"):
        pass
    logging.basicConfig(level=logging.DEBUG,
                        filename=fname,
                        format='%(asctime)s %(levelname)s %(message)s',
                        filemode='w')
    # log what config files are in use here to detect user
    # changes
    logging.info("Using config files '%s'" % config.config_files)
    logging.info("uname information: '%s'" % " ".join(os.uname()))
    cache = apt.apt_pkg.Cache(None)
    apt_version = cache['apt'].current_ver.ver_str
    logging.info("apt version: '%s'" % apt_version)
    logging.info("python version: '%s'" % sys.version)
    return logdir

def save_system_state(logdir):
    # save package state to be able to re-create failures
    try:
        from apt_clone import AptClone
    except ImportError:
        logging.error("failed to import AptClone")
        return
    target = os.path.join(logdir, "apt-clone_system_state.tar.gz")
    logging.debug("creating statefile: '%s'" % target)
    # this file may contain sensitive data so ensure we create with the
    # right umask
    old_umask = os.umask(0o0066)
    clone = AptClone()
    clone.save_state(sourcedir="/", target=target, with_dpkg_status=True,
        scrub_sources=True)
    # reset umask
    os.umask(old_umask)
    # lspci output
    try:
        s=subprocess.Popen(["lspci","-nn"], stdout=subprocess.PIPE,
                           universal_newlines=True).communicate()[0]
        with open(os.path.join(logdir, "lspci.txt"), "w") as f:
            f.write(s)
    except OSError as e:
        logging.debug("lspci failed: %s" % e)

def setup_view(options, config, logdir):
    " setup view based on the config and commandline "

    # the commandline overwrites the configfile
    for requested_view in [options.frontend]+config.getlist("View","View"):
        if not requested_view:
            continue
        try:
            # this should work with py3 and py2.7
            from importlib import import_module
            # use relative imports
            view_modul = import_module("."+requested_view, "DistUpgrade")
            # won't work with py3
            #view_modul = __import__(requested_view, globals())
            view_class = getattr(view_modul, requested_view)
            instance = view_class(logdir=logdir, datadir=options.datadir)
            break
        except Exception as e:
            logging.warning("can't import view '%s' (%s)" % (requested_view,e))
            print("can't load %s (%s)" % (requested_view, e))
    else:
        logging.error("No view can be imported, aborting")
        print("No view can be imported, aborting")
        sys.exit(1)
    return instance

def run_new_gnu_screen_window_or_reattach():
    """ check if there is a upgrade already running inside gnu screen,
        if so, reattach
        if not, create new screen window
    """
    SCREENNAME = "ubuntu-release-upgrade-screen-window"
    # get the active screen sockets
    try:
        out = subprocess.Popen(
            ["screen","-ls"], stdout=subprocess.PIPE,
            universal_newlines=True).communicate()[0]
        logging.debug("screen returned: '%s'" % out)
    except OSError:
        logging.info("screen could not be run")
        return
    # check if a release upgrade is among them
    if SCREENNAME in out:
        logging.info("found active screen session, re-attaching")
        # if we have it, attach to it
        os.execv("/usr/bin/screen",  ["screen", "-d", "-r", "-p", SCREENNAME])
    # otherwise re-exec inside screen with (-L) for logging enabled
    os.environ["RELEASE_UPGRADER_NO_SCREEN"]="1"
    # unset escape key to avoid confusing people who are not used to
    # screen. people who already run screen will not be affected by this
    # unset escape key with -e, enable log with -L, set name with -S
    cmd = ["screen",
           "-e", "\\0\\0",
           "-c", "screenrc",
           "-S", SCREENNAME]+sys.argv
    logging.info("re-exec inside screen: '%s'" % cmd)
    os.execv("/usr/bin/screen", cmd)


def main():
    """ main method """

    # commandline setup and config
    (options, args) = do_commandline()
    config = DistUpgradeConfig(options.datadir)
    logdir = setup_logging(options, config)

    from .DistUpgradeVersion import VERSION
    logging.info("release-upgrader version '%s' started" % VERSION)
    # ensure that DistUpgradeView translations are displayed
    gettext.textdomain("ubuntu-release-upgrader")
    if options.datadir is None or options.datadir == '.':
        localedir = os.path.join(os.getcwd(), "mo")
        gettext.bindtextdomain("ubuntu-release-upgrader", localedir)

    # create view and app objects
    view = setup_view(options, config, logdir)

    # gnu screen support
    if (view.needs_screen and
        not "RELEASE_UPGRADER_NO_SCREEN" in os.environ and
        not options.disable_gnu_screen):
        run_new_gnu_screen_window_or_reattach()

    # A reboot is required at the end of the upgrade,
    # so there is no need to run needrestart during upgrade.
    if not os.getenv('NEEDRESTART_SUSPEND'):
        os.environ['NEEDRESTART_SUSPEND'] = 'y'

    from .DistUpgradeController import DistUpgradeController
    app = DistUpgradeController(view, options, datadir=options.datadir)

    # partial upgrade only
    if options.partial:
        if not app.doPartialUpgrade():
            sys.exit(1)
        sys.exit(0)

    # save system state (only if not doing just a partial upgrade)
    save_system_state(logdir)

    # full upgrade, return error code for success/failure
    if app.run():
        return 0
    return 1

