# -*- coding: utf-8 -*-

# Copyright 2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.


import sys
import os
from os import path
from calculate.core.server.core_interfaces import MethodsInterface
from calculate.lib.utils.files import (makeDirectory, removeDir, tar_directory,
                                       FilePermission, find, listDirectory,
                                       readFile, pathJoin, FindFileType)
from calculate.lib.utils.content import FileOwnersRestricted, ContentsStorage
from calculate.lib.utils.portage import getInstalledAtom
from calculate.lib.configparser import ConfigParserCaseSens

from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from variables.action import Actions
import tarfile
import shutil

_ = lambda x: x
setLocalTranslate('cl_core3', sys.modules[__name__])

__ = getLazyLocalTranslate(_)


class BackupError(Exception):
    """
    Исключение вызванное во время резервного копирования настроек
    """


class Backup(MethodsInterface):
    """
    Выполнение резервного копирования настроек
    """

    def prepare_backup(self, dn, rootname):
        makeDirectory(path.join(dn, rootname))
        return True

    def remove_directory(self, dn):
        removeDir(dn)
        return True

    def prepare_contents(self, dn, contents_file, rootname):
        dn_root = path.join(dn, rootname)
        fo = FileOwnersRestricted(
            "/", ["/%s" % x for x in find(path.join(dn, rootname),
                                          fullpath=False)])
        cs = ContentsStorage(contents_file, fo)
        cs.keep(dn_root, dn_root)
        return True

    def create_archive(self, dn, archfile):
        arch_dn = path.dirname(archfile)
        if not path.exists(arch_dn):
            makeDirectory(arch_dn)
            # TODO: !!!! восстановить смену прав
            # os.chmod(arch_dn, FilePermission.UserAll)
        tar_directory(dn, archfile)
        return True

    def open_archive(self, dn, archfile):
        makeDirectory(dn)
        with tarfile.open(archfile, 'r:bz2') as f:
            f.extractall(dn)
        return True

    def restore_configs(self, archfile, dn, contents_name, root_name):
        """
        Восстановить все файлы настроек
        :param archfile:
        :param dn:
        :return:
        """
        with tarfile.open(archfile, 'r:bz2') as f:
            try:
                # исключить из переноса файлы, которые принадлежат пакетам,
                # которые не установлены в системе
                contents = f.extractfile(f.getmember(contents_name))
                pkg_file = [x.split()[:3:2] for x in contents]
                not_installed_files = [x for x in pkg_file
                                       if not any(getInstalledAtom(x[0]))]
                skip_packages = sorted(list(
                    set([x[0] for x in not_installed_files])))
                if skip_packages:
                    self.printWARNING(
                        _("Ignored settings the following packages: %s") %
                        ", ".join(skip_packages))
                not_installed_files = [x[0] for x in not_installed_files]
            except KeyError:
                raise BackupError(_("CONTENTS file is not found"))
            for ti in (x for x in f if x.name.startswith("%s/" % root_name)):
                if ti.name[4:] in not_installed_files:
                    continue
                if ti.issym() and not path.exists(ti.linkpath):
                    continue
                ti.name = ti.name[5:]
                if ti.name:
                    f.extract(ti, dn)
        return True

    def sava_ini(self, section, key, val):
        ini = ConfigParserCaseSens(strict=False)
        ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8")
        if not ini.has_section(section):
            ini.add_section(section)
        ini[section][key] = str(val)
        with open(self.clVars.Get('cl_backup_ini_env'), 'w') as f:
            ini.write(f)
        return True

    def load_ini(self, section, key):
        ini = ConfigParserCaseSens(strict=False)
        ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8")
        return ini.get(section, key, fallback="")

    def save_initd(self, dn, root_name):
        """
        Сохранить список init.d
        :param initd_list:
        :return:
        """
        self.sava_ini("install", "init",
                      ','.join(listDirectory('/etc/init.d', fullPath=False)))
        dn_root = path.join(dn, root_name)
        for dn in ('/etc/runlevels/sysinit',
                   '/etc/runlevels/default',
                   '/etc/runlevels/boot'):
            try:
                dn_backup = pathJoin(dn_root, dn)
                if not path.exists(dn_backup):
                    makeDirectory(dn_backup)
                for fn in listDirectory(dn, fullPath=True):
                    if path.islink(fn):
                        link = os.readlink(fn)
                        symname = pathJoin(dn_root, fn)
                        if not path.lexists(symname):
                            os.symlink(link, symname)
                        else:
                            # TODO: отладочная информация
                            print "SKIP", symname
            except (OSError, IOError) as e:
                raise BackupError(_("Failed to save service with autorun") +
                                  (_(": %s") % (str(e))))
        return True

    def backup_marked(self, source_dn, target_dn, subdn, root_name):
        """
        Сохранить файлы из указанного каталога, отмеченного комментариями
        выполнения шаблонов
        :return:
        """
        source_dn = path.join(source_dn, subdn)
        len_source_dn = len(source_dn)
        target_dn = path.join(target_dn, root_name, subdn)
        try:
            for fn in find(source_dn, filetype=FindFileType.RegularFile):
                if " Modified Calculate" in readFile(fn, headbyte=300):
                    target_fn = "%s%s" % (target_dn, fn[len_source_dn:])
                    if not path.exists(path.dirname(target_fn)):
                        makeDirectory(path.dirname(target_fn))
                    shutil.copy2(fn, target_fn)
        except (OSError, IOError) as e:
            raise BackupError(_("Failed to backup templates configured files") +
                              (_(": %s") % (str(e))))
        return True


    def clear_autorun(self):
        """
        Удалить все файлы из автозапуска, которые сслаются на файлы из списка
        init.d
        :param initd_list:
        :return:
        """
        files = ["/etc/init.d/%s" % x.strip()
                 for x in self.load_ini("install", "init").split(',')]
        for dn in ('/etc/runlevels/sysinit',
                   '/etc/runlevels/default',
                   '/etc/runlevels/boot'):
            for fn in listDirectory(dn, fullPath=True):
                if path.islink(fn) and os.readlink(fn) in files:
                    os.unlink(fn)
        return True

    def restore_contents(self, contentsfile, dn):
        cs = ContentsStorage(contentsfile)
        cs.restore(dn)
        return True

    def set_service_action(self):
        self.clVars.Set('core.cl_backup_action', Actions.Service, force=True)
        return True
