
import udp
import ftp
import sys
import os
import shutil
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
import time
import portalocker
import encodings.idna
import base64
import memory_pic
import ctypes
import select_list
import dhcp.dhcp as dhcp
import mysql
import console_uart
import file_detail
import float_lable

ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

STR_RED="\033[1;31m"
STR_BLUE="\033[1;34m"
STR_END="\033[0m"





WARN_TXT="警告：\n程序升级会删除主板中以前的程序，为保证升级成功，请耐心等待升级完成，不要退出本界面，不要给主板断电、重启。"
QUESTION_TXT="程序升级会删除主板中以前的程序，非专业人员不要操作。\n\
为保证升级成功，请耐心等待升级完成，不要退出升级界面，期间不要给主板断电、重启。\n\
点击[YES]以进行升级。"
QUESTION_SSH_TXT="升级文件中包含系统服务。\n\
升级系统服务会导致远程登陆失效，即本程序将无法与设备建立通信。\n\
请等待升级完成之后重新启动设备，以保证系统服务正常运行。\n\
点击[YES]以进行升级。"

def warn_creat(father:QDialog,text:str,rect:QRect):
    lable=QLabel(father)
    lable.setWordWrap(True)
    lable.setText(text)
    lable.setGeometry(rect)
    lable.setMargin(10)
    lable.setAlignment(Qt.AlignmentFlag.AlignCenter)
    lable.setStyleSheet("QLabel{font:20px;color:red;background-color:rgb(62, 213, 255);}")
    return lable


class progress_box(QObject):

    rate_signal =pyqtSignal([int])

    def __init__(self) -> None:
        QObject.__init__(self)
    
    def init(self,father:QDialog,text:str,x:int):
        # print("text=",text)
        self.lable = QLabel(father)
        self.lable.setObjectName(u"label")
        self.lable.setGeometry(QRect(40, x, 120, 15))
        self.lable.setText(text)
        self.stat = QLabel(father)
        self.stat.setObjectName(u"label")
        self.stat.setGeometry(QRect(500, x+20, 120, 15))
        self.stat.setText("进行中...")
        self.p=QProgressBar(father)
        self.p.setValue(0)
        self.p.setGeometry(QRect(40,x+20,450,20))
        self.rate_signal.connect(self.p.setValue)
    def set_rate(self,text:str,rate:int):
        if(text==self.lable.text()):
            # print(text)
            self.rate_signal.emit(rate)
    def set_txt(self,text:str,ack:bool,stat:int):
        if(text==self.lable.text()):
            self.stat.setText(stat)



class data_box(QObject):
    def __init__(self) -> None:
        QObject.__init__(self)
    
    def init(self,father:QDialog,title:str,text:str,x:int):
        # print("text=",text)
        self.lable = QLabel(father)
        self.lable.setObjectName(u"label")
        self.lable.setGeometry(QRect(40, x, 120, 20))
        self.lable.setText(title)
        self.text = QLabel(father)
        self.text.setObjectName(u"text")
        self.text.setGeometry(QRect(40,x+20,550,30))
        self.text.setText(text)





class updata_dlg(QWidget):
    # 进度信号，ip，1~100
    rate_signal =pyqtSignal([str,int])
    # 结束信号，ip，成败，描述
    end_signal = pyqtSignal([str,bool,str])
    # 数据库下载文件结束信号
    sql_download_end_signal =pyqtSignal([])
    # 扫描从机结束信号
    scan_slave_end_signal=pyqtSignal([list])

    def __init__(self):
        QWidget.__init__(self)
        self.widget = self
        self.widget.resize(900, 410)
        self.widget.setWindowTitle("批检仪程序升级")
        self.widget.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
        self.addrs=""
        self.udp=None
        self.dhcp_server=None
        # 如果要升级服务，则在升级之后ssh会连接不上
        self.systemd_stop=False
        self.but_y=60
        self.but_y_step=40
        self.file_list_init()
        self.slave_list_init()
        self.save_but_init()
        self.dhcp_but_init()
        self.console_but_init()
        self.settime_but_init()
        self.upjwt_but_init()
        # self.restart_but_init()
        self.cmd_but_init()
        self.refresh_but_init()
        self.sstate_but_init()
        self.hand_but_init()
        self.addfile_but_init()
        self.scheme_but_init()
        self.download_but_init()
        self.ip_prefix_init()
        self.ip_hand_init()
        self.channel_init()
        self.scan_file()
        self.scan_slave()
        self.infotext_init()
        self.widget.destroyed.connect(self.quit)
        self.sql_download_end_signal.connect(self.scan_file)
        self.scan_slave_end_signal.connect(self.scan_slave_end_slot)

        Logo = QPixmap()
        Logo.loadFromData(base64.b64decode(memory_pic.icon_ico))
        icon = QIcon()
        icon.addPixmap(Logo, QIcon.Normal, QIcon.Off)
        self.widget.setWindowIcon(icon)


    def quit(self):
        if(self.udp is not None):
            print("close udp thread")
            self.udp.close()
        if(self.dhcp_server is not None):
            self.dhcp_server.close()

        # 初始化文件列表
    def file_list_init(self):
        self.file_list = QListWidget(self.widget)
        self.file_list.setObjectName(u"file_list")
        self.file_list.setGeometry(QRect(25, 230, 531, 141))
        self.file_list.setFrameShape(QFrame.Shape.Box)
        self.file_list.setMidLineWidth(1)
        self.file_list.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        self.file_list.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
        self.file_list_label = QLabel(self.widget)
        self.file_list_label.setObjectName(u"label")
        self.file_list_label.setGeometry(QRect(30, 210, 531, 15))
        self.file_list_label.setText("默认文件列表（选中以发送）：")
        self.file_list_label.setToolTip("请选择要升级的文件，只有选中的文件会发送到主板中。")
        self.file_list.setToolTip("请选择要升级的文件，只有选中的文件会发送到主板中。")
        ack=self.file_list.setProperty("contextMenuPolicy",Qt.ContextMenuPolicy.CustomContextMenu)
        self.file_list.customContextMenuRequested.connect(self.file_item_menu_slot)
    def file_item_menu_slot(self,pos:QPoint):
        print("show pop menu")
        menu=QMenu(self.file_list)
        action=QAction("删除选中条目",self.file_list)
        action.triggered.connect(self.file_item_delete)
        menu.addAction(action)
        action=QAction("查看选中条目详情",self.file_list)
        action.triggered.connect(self.file_item_detail)
        menu.addAction(action)
        action=QAction("发送选中条目到主板",self.file_list)
        action.triggered.connect(self.save_but_clicked)
        menu.addAction(action)
        menu.addAction(action)
        action=QAction("刷新文件列表",self.file_list)
        action.triggered.connect(self.scan_file)
        menu.addAction(action)
        menu.exec(self.file_list.mapToGlobal(pos))
    def file_item_delete(self):
        print("delete slected items.")
        items=self.file_list.selectedItems()
        for i in items:
            os.remove("file\\"+i.text())
        self.scan_file()
    def file_item_detail(self):
        print("show detail with slected items.")
        items=self.file_list.selectedItems()
        details=[]
        for i in items:
            details+=file_detail.detail("file/"+i.text())
        if(len(details)==0):
            return
        pos=QCursor.pos()
        lab=float_lable.floatBox(self.widget,details,x=pos.x(),y=pos.y())
        lab.show()

        # 初始化从机列表
    def slave_list_init(self):
        self.slave_list = QListWidget(self.widget)
        self.slave_list.setObjectName(u"slave_list")
        self.slave_list.setGeometry(QRect(25, 60, 531, 141))
        self.slave_list.setFrameShape(QFrame.Shape.Box)
        self.slave_list.setMidLineWidth(1)
        self.slave_list.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        self.slave_list.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
        self.slave_list.doubleClicked.connect(self.slave_list.editItem)
        self.slave_list_label = QLabel(self.widget)
        self.slave_list_label.setObjectName(u"label")
        self.slave_list_label.setGeometry(QRect(30, 40, 200, 15))
        self.slave_list_label.setText("在线主板列表（选中以发送）：")
        self.slave_list_label.setToolTip("请选择要升级的主板设备，只有选中的主板会升级。")
        self.slave_list.setToolTip("请选择要升级的主板设备，只有选中的主板会升级。")
        # self.slave_list_label.setToolTipDuration(1)

        # 初始化手动添加IP按钮
    def hand_but_init(self):
        self.hand_but = QPushButton(self.widget)
        self.hand_but.setObjectName(u"hand_but")
        self.hand_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.hand_but.setText("手动添加IP到列表")
        self.hand_but.clicked.connect(self.hand_but_clicked)
        self.hand_but.setToolTip("请先在[手动添加IP地址]输入框中输入要添加的地址，然后点击此按钮添加到设备列表中。")
        # self.hand_but.setToolTipDuration(1)

        # 初始化发送文件按钮
    def save_but_init(self):
        self.save_but = QPushButton(self.widget)
        self.save_but.setObjectName(u"save_but")
        self.save_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.save_but.setText("发送文件到主板")
        self.save_but.clicked.connect(self.save_but_clicked)
        self.save_but.setToolTip("请先选中要升级的主板和文件，然后点击此按钮发送到设备中。")
        # self.save_but.setToolTipDuration(1)

        # 初始化DHCP服务器按钮
    def dhcp_but_init(self):
        self.dhcp_but = QPushButton(self.widget)
        self.dhcp_but.setObjectName(u"dhcp_but")
        self.dhcp_but.setGeometry(QRect(750, 60, 150, 28))
        self.dhcp_but.setText("打开DHCP")
        self.dhcp_but.clicked.connect(self.dhcp_but_clicked)
        self.dhcp_but.setToolTip("如果没有搜索到从机，则打开DHCP服务器。")

        # 初始化控制台按钮
    def console_but_init(self):
        self.console_but = QPushButton(self.widget)
        self.console_but.setObjectName(u"console_but")
        self.console_but.setGeometry(QRect(750, 100, 150, 28))
        self.console_but.setText("串口控制台")
        self.console_but.clicked.connect(self.console_but_clicked)
        self.console_but.setToolTip("通过设备的串口控制台升级程序，这种方式需要使用到U盘。")

        # 初始化同步时间按钮
    def settime_but_init(self):
        self.settime_but = QPushButton(self.widget)
        self.settime_but.setObjectName(u"settime_but")
        self.settime_but.setGeometry(QRect(750, 140, 150, 28))
        self.settime_but.setText("同步时间")
        self.settime_but.clicked.connect(self.settime_but_clicked)
        self.settime_but.setToolTip("同步主板时间。")

        # 初始化升级jwt
    def upjwt_but_init(self):
        self.upjwt_but = QPushButton(self.widget)
        self.upjwt_but.setObjectName(u"settime_but")
        self.upjwt_but.setGeometry(QRect(750, 180, 150, 28))
        self.upjwt_but.setText("升级JWT程序")
        self.upjwt_but.clicked.connect(self.upjwt_but_clicked)
        self.upjwt_but.setToolTip("升级JWT模块程序到小板，需要先把程序发送到主板中。")

        # 初始化重新启动按钮
    def restart_but_init(self):
        self.restart_but = QPushButton(self.widget)
        self.restart_but.setObjectName(u"restart_but")
        self.restart_but.setGeometry(QRect(700, 180, 150, 28))
        self.restart_but.setText("重启主板软件")
        self.restart_but.clicked.connect(self.restart_but_clicked)
        self.restart_but.setToolTip("如果已知设备ip地址，但刷新不出，可尝试重启设备软件。")

        # 初始化发送命令按钮
    def cmd_but_init(self):
        self.cmd_but = QPushButton(self.widget)
        self.cmd_but.setObjectName(u"save_but")
        self.cmd_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.cmd_but.setText("升级小板程序")
        self.cmd_but.clicked.connect(self.cmd_but_clicked)
        self.cmd_but.setToolTip("请先将要升级的小板程序发送到主板中，然后点击此按钮开始升级小板程序。")
        # self.cmd_but.setToolTipDuration(1)

        # 初始化刷新按钮
    def refresh_but_init(self):
        self.refresh_but = QPushButton(self.widget)
        self.refresh_but.setObjectName(u"save_but")
        self.refresh_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.refresh_but.setText("刷新主板IP地址")
        self.refresh_but.clicked.connect(self.refresh_but_clicked)
        self.refresh_but.setToolTip("点击此按钮刷新主板列表。")
        # self.refresh_but.setToolTipDuration(1)

        # 初始化在线状态按钮
    def sstate_but_init(self):
        self.sstate_but = QPushButton(self.widget)
        self.sstate_but.setObjectName(u"sstate_but")
        self.sstate_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.sstate_but.setText("小板在线状态")
        self.sstate_but.clicked.connect(self.sstate_but_clicked)
        self.sstate_but.setToolTip("点击此按钮查看小板在线情况，显示在线小板的地址。")
        # self.sstate_but.setEnabled(False)
        # self.sstate_but.setToolTipDuration(1)

        # 初始化添加文件按钮
    def addfile_but_init(self):
        self.addfile_but = QPushButton(self.widget)
        self.addfile_but.setObjectName(u"addfile_but")
        self.addfile_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.addfile_but.setText("添加外部文件到列表")
        self.addfile_but.clicked.connect(self.addfile_but_clicked)
        self.addfile_but.setToolTip("如果文件列表中不存在要升级的文件，点击此按钮从外部添加。")
        # self.addfile_but.setToolTipDuration(1)

        # 初始化升级方案按钮
    def scheme_but_init(self):
        self.scheme_but = QPushButton(self.widget)
        self.scheme_but.setObjectName(u"scheme_but")
        self.scheme_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.scheme_but.setText("升级方案到小板")
        self.scheme_but.clicked.connect(self.scheme_but_clicked)
        self.scheme_but.setToolTip("请先将要升级的方案文件发送到主板中，然后点击此按钮开始升级方案到MCU。")
        # self.addfile_but.setToolTipDuration(1)

        # 初始化下载文件按钮
    def download_but_init(self):
        self.download_but = QPushButton(self.widget)
        self.download_but.setObjectName(u"download_but")
        self.download_but.setGeometry(QRect(590, self.but_y, 150, 28))
        self.but_y+=self.but_y_step
        self.download_but.setText("从服务器下载文件")
        self.download_but.clicked.connect(self.download_but_clicked)
        self.download_but.setToolTip("从服务器下载文件到本地。")

        # 提示信息
    def infotext_init(self):
        self.infotext=QLabel(self.widget)
        self.infotext.setGeometry(QRect(25, 380, 800, 15))
        self.infotext.setText("欢迎使用云铭公司设备维护工具.")
    def set_infotext(self,text:str):
        try:
            self.infotext.setText(text)
        except Exception as e:
            pass

        # ip前缀
    def ip_prefix_init(self):
        self.ip_prefix = QLineEdit(self.widget)
        self.ip_prefix.setObjectName(u"ip_prefix")
        self.ip_prefix.setGeometry(QRect(110, 10, 113, 21))
        self.ip_prefix.setText("192.168.80")
        self.ip_prefix_label = QLabel(self.widget)
        self.ip_prefix_label.setObjectName(u"label")
        self.ip_prefix_label.setGeometry(QRect(30, 10, 72, 21))
        self.ip_prefix_label.setText("IP前缀：")
        self.ip_prefix_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.ip_prefix_label.setToolTip("如果扫描不到设备，请确保局域网网段与此相符，将此IP前缀改为和局域网相同。")
        self.ip_prefix.setToolTip("如果扫描不到设备，请确保局域网网段与此相符，将此IP前缀改为和局域网相同。")
        # self.ip_prefix_label.setToolTipDuration(1)

        # 手动添加ip
    def ip_hand_init(self):
        self.ip_hand = QLineEdit(self.widget)
        self.ip_hand.setObjectName(u"ip_hand")
        self.ip_hand.setGeometry(QRect(360, 10, 120, 21))
        ip=self.ip_prefix.text()
        self.ip_hand.setText(ip+".81")
        self.ip_hand_label = QLabel(self.widget)
        self.ip_hand_label.setObjectName(u"ip_hand_label")
        self.ip_hand_label.setGeometry(QRect(230, 10, 120, 21))
        self.ip_hand_label.setText("手动添加IP地址：")
        self.ip_hand_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.ip_hand_label.setToolTip("如果扫描不到设备，可以尝试手动添加设备的IP地址，然后升级。")
        self.ip_hand.setToolTip("如果扫描不到设备，可以尝试手动添加设备的IP地址，然后升级。")
        # self.ip_hand_label.setToolTipDuration(1)

        # 选择通道数
    def channel_init(self):
        self.channel = QComboBox(self.widget)
        self.channel.setObjectName(u"channel")
        self.channel.setGeometry(QRect(560, 10, 100, 25))
        self.channel.setEditable(True)
        self.channel.addItem("10通道")
        self.channel.addItem("20通道")
        self.channel.currentIndexChanged.connect(self.channel_changed)
        self.channel_changed(0)
        # self.channel_label = QLabel(self.widget)
        # self.channel_label.setObjectName(u"label")
        # self.channel_label.setGeometry(QRect(410, 16, 72, 15))
        # self.channel_label.setText("波特率:")

        # 显示消息框
    def show_msg(self,msg:str):
        m=QMessageBox(self.widget)
        m.setText(msg)
        m.setWindowTitle("提示")
        m.show()
    def show_question(self,title:str,msg:str):
        ret=QMessageBox.question(self.widget,title,msg,QMessageBox.StandardButton.Yes|QMessageBox.StandardButton.No,
                                 QMessageBox.StandardButton.No)
        if(ret==QMessageBox.StandardButton.Yes):
            return True
        else:
            return False

        # 找到已选择的文件
    def get_selected_file_by_type(self,types:list):
        file_list=[]    
        items=self.file_list.selectedItems()
        for i in items:
            sp=i.text().split(".")[-1]
            sp='.'+sp
            if(sp in types):
                file_list.append(i.text())
        if(len(file_list)!=1):
            self.set_infotext("请选择一个并且只选择一个 "+str(types)+" 文件")
            return ""
        return file_list[0]

        # 获取已选择的文件列表
    def get_selected_fils(self):
        file_list=[]    
        items=self.file_list.selectedItems()
        if(len(items)==0):
            self.set_infotext("请选择至少一个文件")
            return []
        have_elf=False
        have_bin=False
        have_lua=False
        for i in items:
            if(i.text()[-4:]==".elf"):
                if(have_elf==True):
                    self.set_infotext("只可选择一个 .elf 文件")
                    return []
                have_elf=True
            if(i.text()[-4:]==".bin"):
                if(have_bin==True):
                    self.set_infotext("只可选择一个 .bin 文件")
                    return []
                have_bin=True
            if(i.text()[-4:]==".lua"):
                if(have_lua==True):
                    self.set_infotext("只可选择一个 .lua 文件")
                    return []
                have_lua=True
            file_list.append(i.text())
        return file_list

    def get_selected_slave(self):
        slave_list=[]
        items=self.slave_list.selectedItems()
        if(len(items)==0):
            self.set_infotext("请选择至少一个主板ip地址")
            return []
        for i in items:
            str_list=i.text().split(",")
            slave_list.append((str_list[0],str_list[1]))
        return slave_list

        # 根据文件列表生成目标列表
    def build_dst_list(self,file_list):
        self.systemd_stop=False
        dst_list=[]
        for i in file_list:
            if(i[-4:]==".elf"):
                dst_list.append("/usr/local/QDesktop-fb")
            elif(i[-4:]==".dtb"):
                dst_list.append("/run/media/mmcblk2p2/"+i)
            elif(i[-5:]==".json"):
                if(i!="cfg.json"):
                    dst_list.append("/home/root/config/"+"checker_ye_cfg.json")
                else:
                    dst_list.append("/home/root/config/"+"cfg.json")
            elif(i[-4:]==".lua"):
                dst_list.append("/home/root/config/"+"judge.lua")
            elif(i[-4:]==".axf"):
                dst_list.append("/lib/firmware/"+"checker_m4.axf")
            elif(i[-3:]==".py"):
                dst_list.append("/home/root/"+"daemon.py")
            elif(i[-8:]==".service"):
                dst_list.append("/lib/systemd/system/pydeamon.service")
                self.systemd_stop=True
            else:
                dst_list.append("/home/root/config/"+i)
        return dst_list

    def hand_but_clicked(self):
        print("hand_but clicked.")
        ip=self.ip_hand.text()
        self.slave_list.addItem(ip+",local_id_0")

    def channel_changed(self,index:int):
        s=self.channel.itemText(index)
        print("channel_changed,str=",s)
        if(s=="10通道"):
            self.addrs="1,2,3,4,5,6,7,8,9,10 "
        elif(s=="20通道"):
            self.addrs="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 "
        print("addrs:",self.addrs)

    def console_but_clicked(self):
        print("console_but_clicked.")
        dlg=console_uart.console_dlg(self.widget,"串口控制台")
        dlg.show()
        print("Console end.")
    def dhcp_but_clicked(self):
        print("dhcp_but clicked.")
        if(self.dhcp_server==None):
            self.dhcp_but.setEnabled(False)
            self.dhcp_server=dhcp.creat_dhcp_server()
            self.dhcp_server.new_ip_addr_signal.connect(self.add_slave_slot)
            self.dhcp_server.server_start_signal.connect(self.dhcp_start_slot)
            self.dhcp_server.server_end_signal.connect(self.dhcp_end_slot)
            self.dhcp_server.run_in_thread()
        else:
            self.dhcp_but.setEnabled(False)
            self.dhcp_server.new_ip_addr_signal.disconnect(self.add_slave_slot)
            self.dhcp_server.close()
    def dhcp_start_slot(self):
            self.dhcp_but.setText("关闭DHCP")
            self.dhcp_but.setEnabled(True)
    def dhcp_end_slot(self):
            self.dhcp_but.setText("打开DHCP")
            self.dhcp_but.setEnabled(True)
            self.dhcp_server.server_start_signal.disconnect(self.dhcp_start_slot)
            self.dhcp_server.server_end_signal.disconnect(self.dhcp_end_slot)
            self.dhcp_server=None

        # 发送文件按钮按下
    def save_but_clicked(self):
        print("save_but_clicked.")
        path = self.getpath()+"file\\"
        file_list=self.get_selected_fils()
        if(len(file_list)==0):
            return
        dst_list=self.build_dst_list(file_list)
        for i in range(len(file_list)):
            file_list[i]=path+file_list[i]
        for i in zip(file_list,dst_list):
            print("file:",i[0],i[1])
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        if(self.show_question("警告",QUESTION_TXT)==False):
            return
        if(self.systemd_stop==True):
            if(self.show_question("警告",QUESTION_SSH_TXT)==False):
                return
        print("slaves:",slave_list)
        w=QDialog(self.widget)
        warn_creat(w,WARN_TXT,QRect(0,0,700-100,90))
        w.resize(700-100, len(slave_list)*40+20+100)
        w.setWindowTitle("上传文件")
        self.updata(slave_list,dst_list,file_list)
        self.creat_progress(w,100,slave_list)
        w.show()

        # 创建进度条
    def creat_progress(self,father:QDialog,y_off:int,ip_list:list):
        self.probar_list=[]
        for i in range(len(ip_list)):
            p=progress_box()
            p.init(father,ip_list[i][0],+i*40+y_off)
            self.rate_signal.connect(p.set_rate)
            self.end_signal.connect(p.set_txt)
            self.probar_list.append(p)

        # 发送命令按钮按下
    def cmd_but_clicked(self):
        print("cmd_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        print("slaves:",slave_list)
        file=self.get_selected_file_by_type([".bin",".pkt"])
        if(len(file)==0):
            return
        print("file:",file)
        w=QDialog(self.widget)
        w.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        w.destroyed.connect(self.quit)
        w.resize(700-100, len(slave_list)*40+20)
        w.setWindowTitle("升级mcu")
        self.updata_mcu(slave_list,file)
        self.creat_progress(w,0,slave_list)
        w.show()
        # 升级方案按钮按下
    def scheme_but_clicked(self):
        print("scheme_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        print("slaves:",slave_list)
        w=QDialog(self.widget)
        w.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        w.destroyed.connect(self.quit)
        w.resize(700-100, len(slave_list)*40+20)
        w.setWindowTitle("升级方案")
        self.scheme_mcu(slave_list)
        self.creat_progress(w,0,slave_list)
        w.show()
        # 升级JWT按钮按下
    def upjwt_but_clicked(self):
        print("upjwt_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        print("slaves:",slave_list)
        file=self.get_selected_file_by_type([".jwt"])
        if(len(file)==0):
            return
        print("file:",file)
        w=QDialog(self.widget)
        w.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        w.destroyed.connect(self.quit)
        w.resize(700-100, len(slave_list)*40+20)
        w.setWindowTitle("升级jwt程序")
        self.updata_jwt(slave_list,file)
        self.creat_progress(w,0,slave_list)
        w.show()

    def refresh_but_clicked(self):
        print("refresh_but clicked.")
        self.slave_list.clear()
        self.scan_slave()

    def sstate_but_clicked(self):
        print("sstate_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        self.data_list=[]
        self.comm_test(slave_list)

        # 时间同步按下
    def settime_but_clicked(self):
        print("settime_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        self.data_list=[]
        self.settime_host(slave_list)

        # 重新启动按下
    def restart_but_clicked(self):
        print("restart_but clicked.")
        slave_list=self.get_selected_slave()
        if(len(slave_list)==0):
            return
        self.data_list=[]
        self.restart_host(slave_list)

    def addfile_but_clicked(self):
        print("addfile_but clicked")
        fileName,fileType = QFileDialog.getOpenFileNames(None, "选取文件", os.getcwd(), 
                """主板程序(*.elf);;主板m4程序(*.axf);;小板程序(*.bin);;检测方案(*.json);;启动脚本(*.sh);;
                判定脚本(*.lua);;守护进程脚本(*.py);;守护进程服务(*.service);;设备树文件(*.dtb);;任意文件(*)""")
        print(fileName,fileType)
        path=self.getpath()+"file\\"
        for i in fileName:
            shutil.copy(i,path+i.split("/")[-1])
        self.scan_file()

    def download_but_clicked(self):
        sql=mysql.sql()
        if(sql.init("")):
            str_list=sql.show_tables()
            sel=select_list.select_list(self.widget,"选择文件目录",str_list)
            path=sel.show()
            # print("path:",path)
            if(len(path)<=0):
                print("not select any item,break.")
                return
            sql.table_name=path
            items=sql.items()
            str_list=[]
            for i in items:
                item_name=i[2].replace("\\","/")
                item_name=item_name.split("/")[-1]
                if(i[4]!=None):
                    s=str(i[0])+"|"+i[1]+"|"+item_name+"|"+i[4]
                else:
                    s=str(i[0])+"|"+i[1]+"|"+item_name
                # print(s)
                str_list.append((s,))
            sel=select_list.file_list(self.widget,"选择文件",str_list)
            item=sel.show()
            # print("item:",item)
            if(len(item)<=0):
                print("not select any item,break.")
                return
            item=item.split("|")
            self.set_infotext("正在从服务器下载文件,可能需要一些时间,请耐心等待.")
            t = threading.Thread(target=self.sql_download, args=(sql,int(item[0]),))
            t.start()
    # 从数据库下载文件,在后台运行
    def sql_download(self,sql:mysql.sql,index:int):
        file_name=sql.download(index)
        dst="file/"+file_name.split("/")[-1]
        shutil.copy(file_name,dst)
        self.sql_download_end_signal.emit()
        self.set_infotext("已下载文件:"+dst)

        # 开始运行
    def run(self):
        self.widget.show()
    def end(self):
        print("run end.")
        if(self.dhcp_server!=None):
            self.dhcp_server.close()

        # 扫描文件
    def scan_file(self):
        self.file_list.clear()
        self.file_list.addItems(self.find_type([".sh",".elf",".bin",".pkt",".jwt",".lua",".json",".dtb",".axf",".py",".service"]))

        # 扫描从机
    def scan_slave(self):
        self.set_infotext("正在刷新主板IP地址")
        ip_prefix=self.ip_prefix.text()
        t=threading.Thread(target=self.scan_slave_thread, args=(ip_prefix,))
        t.start()
    def scan_slave_thread(self,ip_prefix:str):
        u=udp.myudp(1,255)
        u.find(ip_prefix)
        self.scan_slave_end_signal.emit(u.dst_ip_list)

    def scan_slave_end_slot(self,ips:list):
        list_str=[]
        for i in ips:
            list_str.append(i[0]+','+i[1])
        if(len(list_str)==0):
            self.set_infotext("未找到主板IP地址,请确保主板已正常连接并运行")
        else:
            self.set_infotext("找到 {d} 个设备ip地址".format(d=len(ips)))
        self.slave_list.addItems(list_str)

    # 添加单个从机地址
    def add_slave_slot(self,mac:str,ip:str,name:str):
        for i in range(self.slave_list.count()):
            item=self.slave_list.item(i).text()
            if(item.split(",")[0]==ip):
                return
        self.slave_list.addItem(ip+",dhcp_find")

        # 扫描指定类型的文件
    def find_type(self,types:list):
        path = self.getpath()+"file\\"
        if not os.path.exists(path):
            os.makedirs(path)
        list=os.listdir(path)
        file_list=[]
        for t in types:
            for i in list:
                if(len(t)>0):
                    if(i[-len(t):]==t):
                        file_list.append(i)
                else:
                    file_list.append(i)
        return file_list
    
        # 获得文件绝对路径
    def getpath(self):
        path=os.path.abspath(sys.argv[0])
        list_str=path.split("\\")
        return path.replace(list_str[-1],"")
    
        # 调用此函数开始升级
    def updata(self,ip_list,dst_list,src_list):
        t = threading.Thread(target=self.updata_thread, args=(ip_list,dst_list,src_list))
        # t = threading.Thread(target=self.thread_test, args=(ip_list,dst_list,src_list))
        t.start()

        # 开始升级mcu
    def updata_mcu(self,ip_list,file):
        u=udp.myudp(1,255)
        self.udp=u
        u.dst_ip_list=ip_list
        u.rate_signal.connect(self.rate_slot)
        u.end_signal.connect(self.end_slot)
        print("addrs:",self.addrs)
        updata_cmd="mcu updata "+self.addrs+' '
        updata_cmd+="/home/root/config/"+file
        cmd_list=[]
        cmd_list.append((updata_cmd,1,900))
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()
        # 开始升级jwt
    def updata_jwt(self,ip_list,file):
        u=udp.myudp(1,255)
        self.udp=u
        u.dst_ip_list=ip_list
        u.rate_signal.connect(self.rate_slot)
        u.end_signal.connect(self.end_slot)
        print("addrs:",self.addrs)
        updata_cmd="mcu upjwt "+self.addrs+' '
        updata_cmd+="/home/root/config/"+file
        cmd_list=[]
        cmd_list.append((updata_cmd,1,30))
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()

        # 开始升级方案
    def scheme_mcu(self,ip_list):
        u=udp.myudp(1,255)
        self.udp=u
        u.dst_ip_list=ip_list
        u.rate_signal.connect(self.rate_slot)
        u.end_signal.connect(self.end_slot)
        updata_cmd="mcu scheme "+self.addrs+' '
        cmd_list=[]
        cmd_list.append((updata_cmd,1,15))
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()

        # 同步时间
    def settime_host(self,ip_list):
        u=udp.myudp(1,255)
        u.dst_ip_list=ip_list
        # u.rate_signal.connect(self.rate_slot)
        # u.end_signal.connect(self.end_slot)
        updata_cmd="local settime "+mysql.get_time()+' '
        cmd_list=[]
        cmd_list.append((updata_cmd,1,9))
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()

        # 重新启动
    def restart_host(self,ip_list):
        u=udp.myudp(1,255)
        u.dst_ip_list=ip_list
        # u.rate_signal.connect(self.rate_slot)
        # u.end_signal.connect(self.end_slot)
        updata_cmd="systemctl restart atk-qtapp-start.service "
        cmd_list=[]
        cmd_list.append((updata_cmd,1,9))
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()

        # 小板通信测试
    def comm_test(self,ip_list):
        u=udp.myudp(1,255)
        u.dst_ip_list=ip_list
        # u.rate_signal.connect(self.rate_slot)
        # u.end_signal.connect(self.end_slot)
        u.data_signal.connect(self.data_slot)
        updata_cmd="slave_info"
        cmd_list=[]
        cmd_list.append((updata_cmd,1,5))# 1次返回，五秒超时
        t = threading.Thread(target=u.bordcast, args=(cmd_list,))
        t.start()


        # 测试线程
    def thread_test(self,ip_list,dst_list,src_list):
        for i in range(100):
            for ip in ip_list:
                self.rate_slot(ip[0],i+1)
            time.sleep(0.02)

    def updata_thread(self,ip_list,dst_list,src_list):
        threads=[]
        for i in ip_list:
            t = threading.Thread(target=self.save_file, args=(i[0],True,dst_list ,src_list))
            threads.append(t)
            t.start()
            #等待所有线程任务结束。
        for t in threads:
            t.join()

    def end_slot(self,ip,ack,err):
        # print(ip,ack,err)
        if(ack==False):
            self.show_msg(ip+":"+err)
        else:
            self.end_signal.emit(ip,True,"完成")


    def rate_slot(self,ip,rate):
        # print("rate signal:",ip,rate)
        self.rate_signal.emit(ip,rate)

    def data_slot(self,ip,text):
        self.data_list.append((ip,text))
        # 全部返回完时显示数据
        slave_len=len(self.get_selected_slave())
        if(len(self.data_list)>=slave_len):
            w=QDialog(self.widget)
            w.resize(800-150, slave_len*50+20)
            w.setWindowTitle("返回数据展示")
            self.creat_databoxs(w,self.data_list)
            w.show()

        # 创建数据显示
    def creat_databoxs(self,father:QDialog,data_list:list):
        self.datab_list=[]
        for i in range(len(data_list)):
            p=data_box()
            p.init(father,data_list[i][0],data_list[i][1],+i*50)
            self.datab_list.append(p)


    def save_file(self,ip,restart:bool,dst_list,src_list):
        try:
            f=ftp.myftp(ip,"root","")
            # f.end_signal.connect(self.end_slot)
            f.rate_signal.connect(self.rate_slot)
            if(restart==True):
                print(ip,"|stop app.")
                f.send_cmd("systemctl stop atk-qtapp-start.service",sleep_s=2)
                f.send_cmd("systemctl stop pydeamon.service",sleep_s=2)
                if(self.systemd_stop==True):
                    f.send_cmd("systemctl disable pydeamon.service",sleep_s=2)
            f.put_file(ip,dst_list,src_list)
            if(restart==True):
                print(ip,"|start app.")
                f.send_cmd("systemctl restart atk-qtapp-start.service")
                # 控制enable disable会导致ssh无法连接
                if(self.systemd_stop==True):
                    f.send_cmd("systemctl enable pydeamon.service",sleep_s=2)
                f.send_cmd("systemctl start pydeamon.service",sleep_s=2)
            f.send_cmd("sync",sleep_s=3)
            f.close()
            self.end_signal.emit(ip,True,"完成")
            # print(ip,"|save file end.")
        except Exception as r:
            err_str=str(r)
            print(STR_RED,ip,"|"+"try exception "+err_str,STR_END)
            # self.end_slot(ip,False,err_str)
    




class locker():
    def _get_lock(self):
        file_name = os.path.basename(__file__)
        # linux等平台依然使用标准的/var/run，其他nt等平台使用当前目录
        if os.name == "posix":
            lock_file_name = f"/var/run/{file_name}.pid"
        else:
            lock_file_name = f"{file_name}.pid"
        self.fd = open(lock_file_name, "w")
        try:
            portalocker.lock(self.fd, portalocker.LOCK_EX | portalocker.LOCK_NB)
            # 将当前进程号写入文件
            # 如果获取不到锁上一步就已经异常了，所以不用担心覆盖
            self.fd.writelines(str(os.getpid()))
            # 写入的数据太少，默认会先被放在缓冲区，我们强制同步写入到文件
            self.fd.flush()
        except:
            print(f"{file_name} have another instance running.")
            exit(1)
 
    def __init__(self):
        self._get_lock()
 
    # 和fcntl有点区别，portalocker释放锁直接有unlock()方法
    # 还是一样，其实并不需要在最后自己主动释放锁
    def __del__(self):
        portalocker.unlock(self.fd)



if __name__ == "__main__":
    lock=locker()
    app = QApplication(sys.argv)
    dlg=updata_dlg()
    dlg.run()
    app.exec()
    dlg.end()
