#! /usr/bin/env python3

import os
import sys
import dbus
import signal
import fcntl
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import apt_pkg
import time
import logging
#add config relevant functions
import configparser
from enum import Enum
import apt
#from kylindbus.enums import JSON_PKG_NAME_LIST
BASE_DIR="/usr/share/kylin-update-manager/kylingetinfo" # __file__获取执行文件相对路径，整行为取上一级的上一级目录
sys.path.append(BASE_DIR) # 添加路径，这个是临时的
from cinfo import UpdateInfo, GetUpdateInfos
from formatconv import Convert
import kylin_info_collect

#config file path
AUTOUPDATE_CFGFILE_PATH="/var/lib/kylin-auto-upgrade/kylin-autoupgrade.conf"
AUTOUPDATE_PKGLIST_CFGFILE_PATH="/var/lib/kylin-auto-upgrade/kylin-autoupgrade-pkglist.conf"
TIMESTAMP_PATH="/var/lib/kylin-software-properties/template/kylin-source-status"

BATTERY_CAPACITY_PATH = "/sys/class/power_supply/battery/capacity"

BACKUP_PATH = "/usr/bin/kybackup"

LOCK_FILE = "/var/run/kylin-autouupdate-install.lock"
CONTROL_CENTER_LOCK = "/tmp/auto-upgrade/"

JSON_PKG_NAME_LIST = ['kylin-update-desktop-system', 'kylin-update-desktop-security']

timeStamp=""
uninst_pkg=[]
pkg_installed_ver={}
cv=Convert()
udi=UpdateInfo()
gudi=GetUpdateInfos()

class mount_result(Enum):
    MOUNT_RESULT_INIT = 0
    CANNOT_GET_BACKUPUUID = 1
    NO_BLKID_EXIST = 2
    NO_MOUNTED = 3
    GENERATE_IMPORT_FILE_FAIL = 4
    MOUNTED = 5

def init_flag():
    config_to_result = configparser.ConfigParser(allow_no_value=True)
    config_to_result.read(AUTOUPDATE_CFGFILE_PATH)
    config_to_result.set("CONTROL_CENTER","autoupdate_run_status","idle")
    with open(AUTOUPDATE_CFGFILE_PATH,"w+") as f:
        config_to_result.write(f)

def signal_handler(signal, frame):
    # type: (int, object) -> None
    logging.warning("system signal received, will stop")
    init_flag()
    sys.exit(1)

def get_backup_proxy():
    """ Get backup dbus proxy object """
    #if not backup_proxy:
    bus = dbus.SystemBus()

    backup_proxy = bus.get_object(
        'com.kylin.backup', '/')
    return backup_proxy

def get_update_proxy():
    """ Get update dbus proxy object """
    #if not update_proxy:
    bus = dbus.SystemBus()

    update_proxy = bus.get_object(
        'cn.kylinos.KylinUpdateManager', '/cn/kylinos/KylinUpdateManager')
    return update_proxy

def get_timestamp():

    global timeStamp
    config=configparser.ConfigParser(allow_no_value=True)
    config.read(TIMESTAMP_PATH)
    time_value=time.localtime(int(config.get("Server","UpdateTime")))
    logging.debug(("获取软件源时间戳：%s"),time_value)
    timeStamp="自动备份："+time.strftime("%Y-%m-%d %H:%M:%S",time_value)+" "+config.get("Server","UpdateTime")

def get_file_lock(lockdir):

    lockdir = "/tmp/auto-upgrade/"
    lockfile = os.path.join(
        lockdir,"ukui-control-center.lock")
    if not os.path.exists(lockdir):
        os.makedirs(lockdir)

    file = open(lockfile, "w+")
    try:
        '''加锁，同步锁，其他进程获取不到需等待'''
        fcntl.flock(file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
        time.sleep(1)
        '''解锁'''
        fcntl.flock(file.fileno(), fcntl.LOCK_UN)
        file.close()
        return True
    except Exception as e:
        return False

def get_bachup_status():

    #备份工具是否存在
    if os.path.isfile(BACKUP_PATH)!= 1:
        logging.info("请检查备份还原工具是否存在")
        return -8
    try:
        backup_proxy = get_backup_proxy()
        getter_interface = dbus.Interface(
            backup_proxy,
            dbus_interface='com.kylin.backup.manager')
        backup_partition_status = getter_interface.Mount_backup_partition()
        logging.debug(("获取备份分区状态为 backup_partition_status: %d "),backup_partition_status)
        if backup_partition_status == mount_result.CANNOT_GET_BACKUPUUID.value or backup_partition_status == mount_result.GENERATE_IMPORT_FILE_FAIL.value or \
            backup_partition_status == mount_result.NO_BLKID_EXIST.value or backup_partition_status == mount_result.NO_MOUNTED.value:
            logging.error("请检查备份还原分区是否存在，在安装操作系统时必须勾选'创建备份还原分区'选项来创建此分区")
            init_flag()
            sys.exit(1)
           
        status_code,result = getter_interface.getBackupState()
        logging.debug(("获取备份状态为 status_code：%d result：%d"),status_code,result)
        if result == 1:
            if status_code ==2 or status_code == 5:
                #正在备份  
                return -2
            if status_code != 99:
                #备份工具占用
                return -3
        if result == 0 and status_code == 99:
            return 99

    except dbus.exceptions.DBusException:
        return -1

def get_backup_comment():
    logging.info("检测备份版本是否已存在")
    get_timestamp()
    try:
        backup_proxy = get_backup_proxy()
        getter_interface = dbus.Interface(
            backup_proxy,
            dbus_interface='com.kylin.backup.manager')
        node_name,node_status = getter_interface.getBackupCommentForSystemUpdate()
        logging.debug(("获取本地已备份版本信息为 node_name：%s node_status：%s timeStamp:%s"),node_name,node_status,timeStamp)
        if node_name != timeStamp:
        
            logging.info("未找到相同版本备份镜像，需要备份")
            return 1
        
        logging.info("找到相同版本镜像")
        if node_status == "backup finished":
        
            logging.info("已存在相同版本备份镜像，无需备份")
            return 2
        return 3

    except dbus.exceptions.DBusException:
            return 0

def auto_backup():
    logging.debug("进入系统自动备份流程")
    create_note="系统升级新建备份"
    inc_note="系统升级增量备份"
    userName="root"
    uid=os.getuid()
    try:
        backup_proxy = get_backup_proxy()
        getter_interface = dbus.Interface(
            backup_proxy,
            dbus_interface='com.kylin.backup.manager')
        logging.debug(("本次自动备份信息为 timeStamp:%s create_note:%s inc_note:%s userName:%s uid:%s "),timeStamp,create_note,inc_note,userName,uid)
        logging.info("系统备份已开始...")

        config_to_result = configparser.ConfigParser(allow_no_value=True)
        config_to_result.read(AUTOUPDATE_CFGFILE_PATH)
        config_to_result.set("CONTROL_CENTER","autoupdate_run_status","backup")
        with open(AUTOUPDATE_CFGFILE_PATH,"w+") as f:
            config_to_result.write(f)

        getter_interface.autoBackUpForSystemUpdate_noreturn(timeStamp,create_note,inc_note,userName,uid)

    except dbus.exceptions.DBusException:
        return -1

def install_result_signal_handler(result,dict,**kwargs):
    logging.debug(("receive install_result_signal_handler result:%s apt_appname:%s apt_percent:%s"),result,dict['apt_appname'],dict['apt_percent'])
    global uninst_pkg
    if result == "apt_start":
        # 收集 已安装 的包版本& so on
        # print("uninst_pkg: ", uninst_pkg)
        udi.install_info['uuid'] = gudi.GenerateUUID()
        udi.install_info['pkg-name'] = str(dict['apt_appname'])
        # print("pkg_installed_ver: ", pkg_installed_ver)
        # print("dict['apt_appname']: ", dict['apt_appname'])
        if str(dict['apt_appname']) in pkg_installed_ver.keys():
            udi.install_info['init-version'] = pkg_installed_ver[str(dict['apt_appname'])]
            # print(udi.install_info['pkg-name'], udi.install_info['init-version'])
    if result == "apt_finish":
        if dict['apt_appname'] in uninst_pkg:
            uninst_pkg.remove(dict['apt_appname'])
            config_to_pkglist = configparser.ConfigParser(allow_no_value=True)
            config_to_pkglist.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
            config_to_pkglist.set("DOWNLOAD","uninstpkg"," ".join(uninst_pkg))
            with open(AUTOUPDATE_PKGLIST_CFGFILE_PATH,"w+") as f:
                config_to_pkglist.write(f)
            stat = "success"
            # 获得更新后的包版本
            udi.install_info['inst-version'] = gudi.GetAppVersion(str(dict['apt_appname']))
            udi.install_info['status'] = stat
            udi.install_info['errormsg'] = str(stat+" "+str(dict['action']))
            kc = kylin_info_collect.collect()
            kc.getinfo(udi)
        if len(uninst_pkg) == 0:
            set_install_status_cfg()
            try:
                update_proxy = get_update_proxy()
                getter_interface = dbus.Interface(
                    update_proxy,
                    dbus_interface='cn.kylinos.KylinUpdateManager')
                getter_interface.set_autoupdate_state(0)
            except dbus.exceptions.DBusException:
                return -1
            logging.info("所有软件包均已安装完成")
            os._exit(0)
    if result == "apt_error":
        logging.debug("软件包%s安装失败，自动更新退出！"
                  % dict['apt_appname'])
        init_flag()
        if udi.install_info['uuid'] == '0':
            udi.install_info['uuid'] = gudi.GenerateUUID()
            udi.install_info['pkg-name'] = str(dict['apt_appname'])
            udi.install_info['init-version'] = "unKnown"
        stat = "fail"
        # 获得更新后的包版本
        udi.install_info['inst-version'] = udi.install_info['init-version']
        udi.install_info['status'] = stat
        udi.install_info['errormsg'] = str(dict['error_message'])
        kc = kylin_info_collect.collect()
        kc.getinfo(udi)
        os._exit(1)
        

def install_and_upgrade():
    logging.info("进入软件包安装流程")

    config=configparser.ConfigParser(allow_no_value=True)
    config.read(AUTOUPDATE_CFGFILE_PATH)
    if get_file_lock(CONTROL_CENTER_LOCK) == False:
        logging.warning("自动更新装包时控制面板已打开，程序退出")
        init_flag()
        os._exit(0)

    global uninst_pkg

    apt_pkg.init()
    cache=apt_pkg.Cache()
    cache1=apt.Cache()

    pkgnum = 0
    config=configparser.ConfigParser(allow_no_value=True)
    config.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
    pkgnames=config.get("DOWNLOAD","pkgname").split()
    pkgs = [pkgname for pkgname in pkgnames]
    print(pkgs)
    for pkgname in pkgs[::-1]:
        # print(cache1[pkgname].installed)
        # print(cache1[pkgname].versions[0])
        # print(pkgname)
        if pkgname in JSON_PKG_NAME_LIST:
        #if pkg.installed >= pkg.versions:
            #if cache1[pkgname].installed and cache1[pkgname].installed >= cache1[pkgname].versions[0]:
            pkgs.remove(pkgname)
            print("包不需要安装",pkgname)
            print("包需要安装",pkgs)
        elif cache1[pkgname].installed and cache1[pkgname].installed >= cache1[pkgname].versions[0]:
            
            pkgs.remove(pkgname)
            print("包不需要安装",pkgname)
            print("包需要安装",pkgs)
    #config=configparser.ConfigParser(allow_no_value=True)
    #config.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
    #config.set("DOWNLOAD","pkgname","\n".join(pkgs))
    #with open(AUTOUPDATE_PKGLIST_CFGFILE_PATH,"w+") as f:
        #config.write(f)
    if len(pkgs) == 0:
        logging.error("获取安装包列表失败. 无软件包可以安装!")
        init_flag()
        os._exit(0)
    logging.debug("下列软件包将被安装: %s"
                  % "\n".join(pkgs))

    uninst_pkg = pkgs.copy()
    config_to_pkglist = configparser.ConfigParser(allow_no_value=True)
    config_to_pkglist.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
    config_to_pkglist.set("DOWNLOAD","uninstpkg"," ".join(uninst_pkg))
    with open(AUTOUPDATE_PKGLIST_CFGFILE_PATH,"w+") as f:
        config_to_pkglist.write(f)

    config_to_result = configparser.ConfigParser(allow_no_value=True)
    config_to_result.read(AUTOUPDATE_CFGFILE_PATH)
    #config_to_result.add_section("CONTROL_CENTER")
    config_to_result.set("CONTROL_CENTER","autoupdate_run_status","install")
    with open(AUTOUPDATE_CFGFILE_PATH,"w+") as f:
        config_to_result.write(f)

    # depcache=apt_pkg.DepCache(cache)

    # mark_pkgs_to_upgrade(depcache,cache, pkgs)


    # fetcher = apt_pkg.Acquire()
    # list = apt_pkg.SourceList()
    # list.read_main_list()
    
    # recs = apt_pkg.PackageRecords(cache)
    # pm = apt_pkg.PackageManager(depcache)
    # # don't start downloading during shutdown
    # # TODO: download files one by one and check for stop request after each of
    # # them
    # try:
    #     pm.get_archives(fetcher, list, recs)
    # except SystemError as e:
    #     logging.error(_("GetArchives() failed: %s"), e)

    for pkgname in pkgnames:
        logging.info(("正在安装软件包：%s"),pkgname)
        try:
            update_proxy = get_update_proxy()
            getter_interface = dbus.Interface(
                update_proxy,
                dbus_interface='cn.kylinos.KylinUpdateManager')
            getter_interface.set_autoupdate_state(1)
            result=getter_interface.install_and_upgrade(pkgname)            
            # uninst_pkg.remove(pkgname)
            # config_to_pkglist = configparser.ConfigParser(allow_no_value=True)
            # config_to_pkglist.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
            # config_to_pkglist.set("DOWNLOAD","uninstpkg"," ".join(uninst_pkg))
            # with open(AUTOUPDATE_PKGLIST_CFGFILE_PATH,"w+") as f:
            #     config_to_pkglist.write(f)
            if result == 1:
                pkgnum=pkgnum+1
                logging.info(("软件包 %s 安装成功！"),pkgname)
            else:
                logging.error(("软件包 %s 安装失败！"),pkgname)
                set_install_status_cfg()
                sys.exit(1)
        except dbus.exceptions.DBusException:
            return -1
    if result == 1:
        logging.info(("软件包已开始安装,预计安装%d个软件包"),pkgnum)
        # clean_downloaded_packages(fetcher)                        //调dbus接口安装只是把安装任务放到队列，并没有开始安装，故无法及时删除包缓存
    return result

def set_install_status_cfg():
    config_to_result = configparser.ConfigParser(allow_no_value=True)
    config_to_result.read(AUTOUPDATE_CFGFILE_PATH)
    config_to_result.set("CONTROL_CENTER","autoupdate_run_status","idle")
    config_to_result.set("DOWNLOAD","finish_status","false")
    with open(AUTOUPDATE_CFGFILE_PATH,"w+") as f:
        config_to_result.write(f)

# def backup_result_signal_handler(result,**kwargs):
#     logging.debug(("receive backup_result_signal result:%d"),result)
#     if result == 1:
#         logging.info("系统备份已完成！")
#         install_result=install_and_upgrade()
#         if(install_result==1):
#             set_install_status_cfg()
#             os._exit(0)

# def start_backup_result_signal_handler(result,**kwargs):
#     logging.debug(("receive start_backup_result_signal_handler result:%d"),result)
#     if result == 30:
#         logging.info("系统备份已开始...")

def backup_rate_signal_handler(sta,pro,**kwargs):
    logging.debug(("receive backup_rate_signal_handler 状态:%d 进度:%d"),sta,pro)
    if sta == 2 and pro == 100:
        logging.info("系统更新全盘备份已完成！")
        install_result=install_and_upgrade()

    if sta == 5 and pro == 100:
        logging.info("系统更新增量备份已完成！")
        install_result=install_and_upgrade()

def _get_logdir():
    # type: () -> str
    logdir = "/var/log/kylin-autoupdate/"
    return logdir

def _setup_logging():

    # ensure this is run only once
    if len(logging.root.handlers) > 0:
        return None

    # init the logging
    logdir = _get_logdir()
    logfile = os.path.join(
        logdir,"kylin-autoupdate-install.log")
    if not os.path.exists(logdir):
        os.makedirs(logdir)

    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(levelname)s %(message)s',
                        filename=logfile)
    logger = logging.getLogger()
    stdout_handler = logging.StreamHandler(sys.stdout)
    logger.addHandler(stdout_handler)

def clean_downloaded_packages(fetcher):
    # type: (apt_pkg.Acquire) -> None
    archivedir = os.path.dirname(
        apt_pkg.config.find_dir("Dir::Cache::archives"))
    for item in fetcher.items:
        if os.path.dirname(os.path.abspath(item.destfile)) == archivedir:
            try:
                os.unlink(item.destfile)
            except OSError:
                pass

def mark_pkgs_to_upgrade(depcache,cache, pkgs_to_upgrade):
    # type (apt.Cache, List[str]) -> None
    for pkg_name in pkgs_to_upgrade:
        pkg=cache[pkg_name] 
        if depcache.is_upgradable(pkg) \
           or (depcache.is_auto_installed(pkg)
               and pkg.candidate.version != pkg.installed.version): 
            depcache.mark_install(pkg)
        elif not depcache.is_auto_installed(pkg):
            depcache.mark_keep(pkg)

def backup_start_signal_handler(state,**kwargs):
    logging.debug(("receive backup_start_signal_handler 状态:%d" ),state)
    if state == 31 or state == 30:
        pass
    else:
        logging.error("返回值错误，备份还原工具不可用")
        init_flag()
        os._exit(2)

def pre_install_check():
    cache1=apt.Cache()
    config=configparser.ConfigParser(allow_no_value=True)
    config.read(AUTOUPDATE_PKGLIST_CFGFILE_PATH)
    pkgnames=config.get("DOWNLOAD","pkgname").split()
    pkgs = [pkgname for pkgname in pkgnames]
#    for pkgname in pkgs[::-1]:
#        print(cache1[pkgname].installed)
#        print(cache1[pkgname].versions[0])
#        print(pkgname)
#        #if pkg.installed >= pkg.versions:
#        if cache1[pkgname].installed and cache1[pkgname].installed >= cache1[pkgname].versions[0]:
#            pkgs.remove(pkgname)
#            print("包不需要安装",pkgname)
#            print("包需要安装",pkgs)
    if len(pkgs) == 0:
        logging.error("获取安装包列表失败. 无软件包可以安装!")
        init_flag()
        os._exit(0)

def run():

    pre_install_check()

    if get_bachup_status() != 99:
        logging.error("备份还原工具不可用")
        sys.exit(1)
    bus = dbus.SystemBus()

    if get_backup_comment() == 1:
        bus.add_signal_receiver(backup_start_signal_handler,interface_keyword="com.kylin.backup.manager",signal_name="sendStartBackupResult")
        result=auto_backup()
        bus.add_signal_receiver(backup_rate_signal_handler, interface_keyword="com.kylin.backup.manager",signal_name="sendRate")
        bus.add_signal_receiver(install_result_signal_handler, interface_keyword="cn.kylinos.KylinUpdateManager",signal_name="kum_apt_signal")
    elif get_backup_comment() == 2:
        install_result=install_and_upgrade()
        bus.add_signal_receiver(install_result_signal_handler, interface_keyword="cn.kylinos.KylinUpdateManager",signal_name="kum_apt_signal")
    else:
        logging.error("系统备份存在异常，系统退出!")
        sys.exit(1)

    loop = GLib.MainLoop()
    loop.run()

def main():

    # setup signal handler for graceful stopping
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGHUP, signal_handler)
    signal.signal(signal.SIG_IGN, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)

    # setup logging
    _setup_logging()

    # lock for the shutdown check
    shutdown_lock = apt_pkg.get_lock(LOCK_FILE)
    if shutdown_lock < 0:
        logging.error("锁文件已存在, 程序退出")
        return 1

    config=configparser.ConfigParser(allow_no_value=True)
    config.read(AUTOUPDATE_CFGFILE_PATH)

    if config.get("CONTROL_CENTER","autoupdate_allow") == "false":
        logging.warning("系统自动更新不允许，可通过控制面板-升级界面开启自动更新功能")
        sys.exit(1)

    #only check the battery capacity during installation
    if os.path.exists(BATTERY_CAPACITY_PATH):
        batfd = open(BATTERY_CAPACITY_PATH, "r")
        batterycapacity = int(batfd.readline())
        batfd.close()
        batterythreshold = 50   
        if batterycapacity < batterythreshold:
            logging.warning("电池电量低于更新安装要求阈值, 程序退出")
            return 1               
        else:
            pass

    logging.info("*******自动更新安装程序已启动********")
    DBusGMainLoop(set_as_default=True)  # You must do this before connecting to the bus.

    if get_file_lock(CONTROL_CENTER_LOCK) == False or config.get("DOWNLOAD","finish_status") == "false":
        logging.warning("控制面板已打开或更新包下载异常，自动更新安装退出")
        sys.exit(1)

    # result=install_and_upgrade()
    # logging.debug(("获取安装状态为  result：%d"),result)
    run()

if __name__ == "__main__":
    main()
