949 lines
38 KiB
Python
949 lines
38 KiB
Python
|
|
#!/opt/homebrew/bin/python3
|
|||
|
|
# -*- coding:utf-8 -*-
|
|||
|
|
import re
|
|||
|
|
import shutil
|
|||
|
|
import zipfile
|
|||
|
|
import hashlib
|
|||
|
|
import json
|
|||
|
|
import sqlite3
|
|||
|
|
import os
|
|||
|
|
from dog import dog
|
|||
|
|
from dog import suDog
|
|||
|
|
from markdown import markdown
|
|||
|
|
from PyQt6.QtCore import *
|
|||
|
|
from logs import log
|
|||
|
|
from PyQt6.QtGui import *
|
|||
|
|
from PyQt6.QtWidgets import *
|
|||
|
|
from PyQt6.QtSerialPort import QSerialPortInfo
|
|||
|
|
from configparser import ConfigParser
|
|||
|
|
from config import config
|
|||
|
|
from influxDB import influxdb
|
|||
|
|
from globals import _G
|
|||
|
|
from devmodelModel.devModelManager import devModelManager
|
|||
|
|
from deviceModel.deviceManager import deviceManager
|
|||
|
|
from userModel.userManager import userManager
|
|||
|
|
from taskModel.taskManager import taskManager
|
|||
|
|
from instructionModel.instructionManager import instructionManager
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
class QxStandardItem(QStandardItem):
|
|||
|
|
def __init__(self, text="", max_length=200, *args):
|
|||
|
|
super().__init__(*args)
|
|||
|
|
self.max_length = max_length
|
|||
|
|
self.setText(text)
|
|||
|
|
|
|||
|
|
def setText(self, text):
|
|||
|
|
super().setText(text)
|
|||
|
|
# 检查文本长度并设置工具提示
|
|||
|
|
if len(text) > self.max_length:
|
|||
|
|
self.setToolTip(text) # 设置工具提示为完整文本
|
|||
|
|
else:
|
|||
|
|
self.setToolTip("") # 清空工具提示
|
|||
|
|
|
|||
|
|
class AlignDelegate(QStyledItemDelegate):
|
|||
|
|
def __init__(self, view):
|
|||
|
|
super(AlignDelegate, self).__init__()
|
|||
|
|
self.view = view
|
|||
|
|
def paint(self, painter, option, index):
|
|||
|
|
# 设置文本对齐方式为右对齐和垂直居中
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
|
|||
|
|
# 获取单元格的文本
|
|||
|
|
text = index.model().data(index, role=Qt.ItemDataRole.DisplayRole)
|
|||
|
|
|
|||
|
|
# 创建一个新的 QStyleOptionViewItem,因为我们不能直接修改 option.text
|
|||
|
|
option_text = QStyleOptionViewItem()
|
|||
|
|
option_text.rect = option.rect
|
|||
|
|
option_text.state = option.state
|
|||
|
|
option_text.text = text
|
|||
|
|
option_text.displayAlignment = option.displayAlignment
|
|||
|
|
|
|||
|
|
# 使用 QPainter 绘制文本,这里我们不直接使用 option 来绘制,因为我们想要自定义文本的绘制
|
|||
|
|
painter.setFont(option.font)
|
|||
|
|
# 14 像素的缩进量
|
|||
|
|
fm = QFontMetricsF(option.font)
|
|||
|
|
xIndent = 14 # 缩进量,单位为像素
|
|||
|
|
# Access the selection model from the view
|
|||
|
|
selection_model = self.view.selectionModel()
|
|||
|
|
|
|||
|
|
# if not index.flags() & Qt.ItemFlag.ItemIsEditable: # 判断 item 是否可编辑
|
|||
|
|
# painter.setPen(QColor("#BFBFBF"))
|
|||
|
|
# else:
|
|||
|
|
# Check if the index is selected using the selection model
|
|||
|
|
if selection_model.isSelected(index):
|
|||
|
|
# 设置选中状态下的文本颜色
|
|||
|
|
painter.setPen(option.palette.highlightedText().color())
|
|||
|
|
else:
|
|||
|
|
# 设置未选中状态下的文本颜色
|
|||
|
|
painter.setPen(option.palette.text().color())
|
|||
|
|
|
|||
|
|
rect = QRectF(option.rect.left() + xIndent, option.rect.top(), option.rect.width() - xIndent, option.rect.height())
|
|||
|
|
elidedText = painter.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, int(rect.width()))
|
|||
|
|
painter.drawText(rect, option.displayAlignment, elidedText)
|
|||
|
|
|
|||
|
|
class AlignDevDelegate(QStyledItemDelegate):
|
|||
|
|
def __init__(self, view, hideCloumn = 1, devCloumn = 3):
|
|||
|
|
super(AlignDevDelegate, self).__init__()
|
|||
|
|
self.view = view
|
|||
|
|
self.hideCloumn = hideCloumn
|
|||
|
|
self.devCloumn = devCloumn
|
|||
|
|
|
|||
|
|
def paint(self, painter, option, index):
|
|||
|
|
if index.column() == self.hideCloumn: # 只对第一列进行居中设置
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
else:
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
|
|||
|
|
# 以下是你的原始代码
|
|||
|
|
text = index.model().data(index, role=Qt.ItemDataRole.DisplayRole)
|
|||
|
|
option_text = QStyleOptionViewItem()
|
|||
|
|
option_text.rect = option.rect
|
|||
|
|
option_text.state = option.state
|
|||
|
|
option_text.text = text
|
|||
|
|
option_text.displayAlignment = option.displayAlignment
|
|||
|
|
xIndent = 2 # 缩进量,单位为像素
|
|||
|
|
painter.setFont(option.font)
|
|||
|
|
fm = QFontMetricsF(option.font)
|
|||
|
|
selection_model = self.view.selectionModel()
|
|||
|
|
icontype = index.sibling(index.row(), 3).data(Qt.ItemDataRole.DisplayRole)
|
|||
|
|
if index.column() == self.devCloumn:
|
|||
|
|
if len(text) <= 0:
|
|||
|
|
#改变背景色rgba(255, 255, 255, 0)
|
|||
|
|
#画红色框
|
|||
|
|
painter.setPen(QColor(255, 0, 0))
|
|||
|
|
painter.setBrush(QColor(255, 0, 0, 0))
|
|||
|
|
#画缩小2px的矩形
|
|||
|
|
painter.drawRect(option.rect.adjusted(0, 6, -2, -6))
|
|||
|
|
|
|||
|
|
if selection_model.isSelected(index):
|
|||
|
|
painter.setPen(option.palette.highlightedText().color())
|
|||
|
|
else:
|
|||
|
|
painter.setPen(option.palette.text().color())
|
|||
|
|
rect = QRectF(option.rect.left() + xIndent, option.rect.top(), option.rect.width() - xIndent, option.rect.height())
|
|||
|
|
elidedText = painter.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, int(rect.width()))
|
|||
|
|
painter.drawText(rect, option.displayAlignment, elidedText)
|
|||
|
|
if index.column() == 3:
|
|||
|
|
if icontype == "任务":
|
|||
|
|
icon = QIcon(":/resource/child.png") # 替换为你的图标路径
|
|||
|
|
icon_rect = QRectF(option.rect.left() + 25, option.rect.top(), 20, option.rect.height()) # 图标的位置和大小
|
|||
|
|
icon.paint(painter, icon_rect.toRect())
|
|||
|
|
|
|||
|
|
class AlignNoDelegate(QStyledItemDelegate):
|
|||
|
|
def __init__(self, view, hideCloumn = 1):
|
|||
|
|
super(AlignNoDelegate, self).__init__()
|
|||
|
|
self.view = view
|
|||
|
|
self.hideCloumn = hideCloumn
|
|||
|
|
|
|||
|
|
def paint(self, painter, option, index):
|
|||
|
|
if index.column() == self.hideCloumn: # 只对第一列进行居中设置
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
else:
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
|
|||
|
|
# 以下是你的原始代码
|
|||
|
|
text = index.model().data(index, role=Qt.ItemDataRole.DisplayRole)
|
|||
|
|
option_text = QStyleOptionViewItem()
|
|||
|
|
option_text.rect = option.rect
|
|||
|
|
option_text.state = option.state
|
|||
|
|
option_text.text = text
|
|||
|
|
option_text.displayAlignment = option.displayAlignment
|
|||
|
|
xIndent = 2 # 缩进量,单位为像素
|
|||
|
|
painter.setFont(option.font)
|
|||
|
|
fm = QFontMetricsF(option.font)
|
|||
|
|
selection_model = self.view.selectionModel()
|
|||
|
|
if selection_model.isSelected(index):
|
|||
|
|
painter.setPen(option.palette.highlightedText().color())
|
|||
|
|
else:
|
|||
|
|
painter.setPen(option.palette.text().color())
|
|||
|
|
rect = QRectF(option.rect.left() + xIndent, option.rect.top(), option.rect.width() - xIndent, option.rect.height())
|
|||
|
|
elidedText = painter.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, int(rect.width()))
|
|||
|
|
painter.drawText(rect, option.displayAlignment, elidedText)
|
|||
|
|
|
|||
|
|
class AlignTaskNoDelegate(QStyledItemDelegate):
|
|||
|
|
def __init__(self, view, hideCloumn = 1):
|
|||
|
|
super(AlignTaskNoDelegate, self).__init__()
|
|||
|
|
self.view = view
|
|||
|
|
self.hideCloumn = hideCloumn
|
|||
|
|
|
|||
|
|
def paint(self, painter, option, index):
|
|||
|
|
if index.column() == self.hideCloumn: # 只对第一列进行居中设置
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
else:
|
|||
|
|
option.displayAlignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
|||
|
|
|
|||
|
|
# 以下是你的原始代码
|
|||
|
|
text = index.model().data(index, role=Qt.ItemDataRole.DisplayRole)
|
|||
|
|
option_text = QStyleOptionViewItem()
|
|||
|
|
option_text.rect = option.rect
|
|||
|
|
option_text.state = option.state
|
|||
|
|
option_text.text = text
|
|||
|
|
option_text.displayAlignment = option.displayAlignment
|
|||
|
|
|
|||
|
|
painter.setFont(option.font)
|
|||
|
|
fm = QFontMetricsF(option.font)
|
|||
|
|
icontype = index.sibling(index.row(), 8).data(Qt.ItemDataRole.DisplayRole)
|
|||
|
|
selection_model = self.view.selectionModel()
|
|||
|
|
if selection_model.isSelected(index):
|
|||
|
|
painter.setPen(option.palette.highlightedText().color())
|
|||
|
|
else:
|
|||
|
|
painter.setPen(option.palette.text().color())
|
|||
|
|
rect = QRectF(option.rect.left(), option.rect.top(), option.rect.width() , option.rect.height())
|
|||
|
|
elidedText = painter.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, int(rect.width()))
|
|||
|
|
if index.column() == self.hideCloumn: # 只对第一列进行居中设置
|
|||
|
|
painter.drawText(rect, option.displayAlignment, elidedText)
|
|||
|
|
else:
|
|||
|
|
rect = QRectF(option.rect.left(), option.rect.top(), option.rect.width()-22 , option.rect.height())
|
|||
|
|
painter.drawText(rect, option.displayAlignment, elidedText)
|
|||
|
|
if index.column() == 2:
|
|||
|
|
if icontype == "parent":
|
|||
|
|
icon = QIcon(":/resource/parent.png") # 替换为你的图标路径
|
|||
|
|
icon_rect = QRectF(option.rect.right() - 22, option.rect.top(), 20, option.rect.height()) # 图标的位置和大小
|
|||
|
|
icon.paint(painter, icon_rect.toRect())
|
|||
|
|
elif icontype == "child":
|
|||
|
|
icon = QIcon(":/resource/child.png") # 替换为你的图标路径
|
|||
|
|
icon_rect = QRectF(option.rect.right() - 22, option.rect.top(), 20, option.rect.height()) # 图标的位置和大小
|
|||
|
|
icon.paint(painter, icon_rect.toRect())
|
|||
|
|
|
|||
|
|
class DragDropInfo:
|
|||
|
|
def __init__(self, source_index, target_index):
|
|||
|
|
self.source_index = source_index
|
|||
|
|
self.target_index = target_index
|
|||
|
|
|
|||
|
|
class MarkdownViewer(QDialog):
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__()
|
|||
|
|
self.initUI()
|
|||
|
|
|
|||
|
|
def initUI(self):
|
|||
|
|
self.setWindowTitle("帮助文档")
|
|||
|
|
self.setGeometry(685, 60, 800, 900)
|
|||
|
|
self.textEdit = QTextEdit()
|
|||
|
|
self.textEdit.setReadOnly(True)
|
|||
|
|
self.textEdit.setAcceptRichText(True)
|
|||
|
|
layout = QVBoxLayout()
|
|||
|
|
layout.addWidget(self.textEdit)
|
|||
|
|
self.setLayout(layout)
|
|||
|
|
|
|||
|
|
def showMarkdown(self, markdown_file):
|
|||
|
|
self.fileName = markdown_file
|
|||
|
|
|
|||
|
|
helpPath = os.path.join(common.appPath, self.fileName)
|
|||
|
|
with open(helpPath, 'r',encoding='utf-8') as file:
|
|||
|
|
self.setMarkdown(file.read())
|
|||
|
|
self.exec()
|
|||
|
|
|
|||
|
|
def setFileName(self, fileName):
|
|||
|
|
self.fileName = fileName
|
|||
|
|
helpPath = os.path.join(self.appPath, self.fileName)
|
|||
|
|
with open(helpPath, 'r',encoding='utf-8') as file:
|
|||
|
|
self.setMarkdown(file.read())
|
|||
|
|
|
|||
|
|
def setMarkdown(self, markdown_text):
|
|||
|
|
html_text = markdown(markdown_text)
|
|||
|
|
self.textEdit.setHtml(html_text)
|
|||
|
|
|
|||
|
|
class TaskProgressBar(QProgressBar):
|
|||
|
|
def __init__(self, taskId= "",parent=None):
|
|||
|
|
super(TaskProgressBar, self).__init__(parent)
|
|||
|
|
self.taskId = taskId
|
|||
|
|
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|||
|
|
self.setValue(0)
|
|||
|
|
self.setTextVisible(False)
|
|||
|
|
|
|||
|
|
def updateProgress(self, taskId, value, maxValue):
|
|||
|
|
if self.taskId != taskId:
|
|||
|
|
return
|
|||
|
|
if maxValue == 0:
|
|||
|
|
value = 0
|
|||
|
|
self.setTextVisible(True)
|
|||
|
|
if maxValue == -1:
|
|||
|
|
self.setTextVisible(False)
|
|||
|
|
maxValue = value
|
|||
|
|
self.setMaximum(maxValue)
|
|||
|
|
self.setValue(value)
|
|||
|
|
|
|||
|
|
class Alert(QMessageBox):
|
|||
|
|
def __init__(self, parent=None):
|
|||
|
|
super(Alert, self).__init__(parent)
|
|||
|
|
self.setIcon(QMessageBox.Icon.Information)
|
|||
|
|
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
|
|||
|
|
self.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|||
|
|
def event(self, event):
|
|||
|
|
if event.type() == QEvent.Type.WindowDeactivate:
|
|||
|
|
# 在这里处理QMessageBox失去焦点事件
|
|||
|
|
self.close()
|
|||
|
|
return super().event(event)
|
|||
|
|
def showMsg(self, title, msg, time):
|
|||
|
|
self.setWindowTitle(title)
|
|||
|
|
self.setText(msg)
|
|||
|
|
self.show()
|
|||
|
|
if time != 0:
|
|||
|
|
timer = QTimer(self)
|
|||
|
|
timer.singleShot(time, self.close)
|
|||
|
|
|
|||
|
|
def onWindowClose(self):
|
|||
|
|
self.close()
|
|||
|
|
|
|||
|
|
class Scanf(QInputDialog):
|
|||
|
|
scanfFinish = pyqtSignal(str)
|
|||
|
|
def __init__(self, parent=None):
|
|||
|
|
super(Scanf, self).__init__(parent)
|
|||
|
|
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
|
|||
|
|
self.setInputMode(QInputDialog.InputMode.TextInput)
|
|||
|
|
self.setOkButtonText("确定")
|
|||
|
|
self.setCancelButtonText("取消")
|
|||
|
|
self.setWindowFlags(Qt.WindowType.WindowCloseButtonHint)
|
|||
|
|
self.accepted.connect(self.sendScanf)
|
|||
|
|
self.rejected.connect(self.sendEmputy)
|
|||
|
|
def event(self, event):
|
|||
|
|
if event.type() == QEvent.Type.WindowDeactivate:
|
|||
|
|
self.close()
|
|||
|
|
return super().event(event)
|
|||
|
|
def sendScanf(self):
|
|||
|
|
self.scanfFinish.emit(self.textValue())
|
|||
|
|
def sendEmputy(self):
|
|||
|
|
self.scanfFinish.emit("")
|
|||
|
|
|
|||
|
|
def showMsg(self, title, msg, value):
|
|||
|
|
self.setWindowTitle(title)
|
|||
|
|
self.setLabelText(msg)
|
|||
|
|
self.setTextValue(value)
|
|||
|
|
self.show()
|
|||
|
|
def onWindowClose(self):
|
|||
|
|
self.close()
|
|||
|
|
|
|||
|
|
# QComboBox禁用鼠标滚轮
|
|||
|
|
class NoWheelComboBox(QComboBox):
|
|||
|
|
def __init__(self, parent=None):
|
|||
|
|
super().__init__(parent)
|
|||
|
|
|
|||
|
|
def wheelEvent(self, event):
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
class Common(QObject):
|
|||
|
|
sig_appChanged = pyqtSignal()
|
|||
|
|
sig_changeQml = pyqtSignal(str)
|
|||
|
|
sig_unChecked = pyqtSignal()
|
|||
|
|
sig_changeMainQml = pyqtSignal(str)
|
|||
|
|
showAlert = pyqtSignal(str, str, int)
|
|||
|
|
showScanf = pyqtSignal(str, str, str)
|
|||
|
|
scanfFinish = pyqtSignal(str)
|
|||
|
|
appReady = pyqtSignal(str)
|
|||
|
|
registQml = pyqtSignal(str,str)
|
|||
|
|
windowClose = pyqtSignal()
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__()
|
|||
|
|
self.scriptFile = dict()
|
|||
|
|
self.appList = []
|
|||
|
|
self.appPath = os.path.dirname(__file__)
|
|||
|
|
self.fstPath = ""
|
|||
|
|
if self.fstPath == "":
|
|||
|
|
self.fstPath = "qml"
|
|||
|
|
if "_internal" in self.appPath:
|
|||
|
|
log.error("存在 _internal")
|
|||
|
|
self.fstPath = "_internal/qml"
|
|||
|
|
else:
|
|||
|
|
log.error("不存在 _internal")
|
|||
|
|
self.emputyProId = "00000000-0000-0000-0000-000000000000"
|
|||
|
|
self.defaultGroupId = "00000000-0000-0000-0000-000000000000"
|
|||
|
|
self.systemInterfaceUuid = "00000000-0000-0000-0000-000000000000"
|
|||
|
|
self.noChooseUuid = "0"
|
|||
|
|
self.scanfResult = ""
|
|||
|
|
|
|||
|
|
self.qscript = {}
|
|||
|
|
self.setSriptFile()
|
|||
|
|
self.updateAppList()
|
|||
|
|
self.showAlert.connect(self.onShowAlert)
|
|||
|
|
self.showScanf.connect(self.onShowScanf)
|
|||
|
|
|
|||
|
|
def onWindowClose(self):
|
|||
|
|
self.windowClose.emit()
|
|||
|
|
|
|||
|
|
@pyqtSlot(result=QVariant)
|
|||
|
|
def getConfig(self):
|
|||
|
|
return config.data
|
|||
|
|
|
|||
|
|
@pyqtSlot(result=str)
|
|||
|
|
def getCurrentUser(self):
|
|||
|
|
currentUser = userManager.getCurrentUser()
|
|||
|
|
if "name" in currentUser:
|
|||
|
|
return currentUser["name"]
|
|||
|
|
else:
|
|||
|
|
return ""
|
|||
|
|
@pyqtSlot(result=QVariant)
|
|||
|
|
def getAllG(self):
|
|||
|
|
return _G.getall()
|
|||
|
|
|
|||
|
|
@pyqtSlot(str, result=QVariant)
|
|||
|
|
def getG(self,name):
|
|||
|
|
return _G.get(name)
|
|||
|
|
|
|||
|
|
@pyqtSlot(str,str, result=bool)
|
|||
|
|
def setG(self, name, json_str):
|
|||
|
|
value = json.loads(json_str)
|
|||
|
|
_G.set(name, value)
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
@pyqtSlot(result=str)
|
|||
|
|
def firstPath(self):
|
|||
|
|
return self.fstPath
|
|||
|
|
|
|||
|
|
def resizeDefTableView(self, tableView, indexs):
|
|||
|
|
itemWidth = int( (tableView.width()) / len(indexs) )
|
|||
|
|
for index in indexs:
|
|||
|
|
tableView.setColumnWidth(index, itemWidth)
|
|||
|
|
def resizeTableView(self, tableView, indexs):
|
|||
|
|
itemWidth = int( (tableView.width()) / len(indexs) )
|
|||
|
|
for index in indexs:
|
|||
|
|
tableView.setColumnWidth(index, itemWidth)
|
|||
|
|
|
|||
|
|
def resizeNumberTableView(self, tableView, indexs):
|
|||
|
|
totalColumns = len(indexs)
|
|||
|
|
scollbarWidth = 0
|
|||
|
|
if tableView.verticalScrollBar().isVisible():
|
|||
|
|
scollbarWidth = tableView.verticalScrollBar().width() # Subtract the fixed width of the first column
|
|||
|
|
|
|||
|
|
totalWidth = tableView.width() - tableView.verticalHeader().width() - scollbarWidth # Subtract the fixed width of the first column
|
|||
|
|
avgWidth = int( totalWidth / totalColumns ) # Calculate the average width for the remaining columns
|
|||
|
|
for index in indexs:
|
|||
|
|
tableView.setColumnWidth(index, avgWidth) # Set the average width for each column in the index list
|
|||
|
|
|
|||
|
|
|
|||
|
|
def resizeNoTableView(self, tableView, indexs, colIndex = 1):
|
|||
|
|
totalColumns = len(indexs)
|
|||
|
|
totalWidth = tableView.width() - tableView.columnWidth(colIndex) # Subtract the fixed width of the first column
|
|||
|
|
avgWidth = int( totalWidth / (totalColumns - 1) ) # Calculate the average width for the remaining columns
|
|||
|
|
for index in indexs:
|
|||
|
|
tableView.setColumnWidth(index, avgWidth) # Set the average width for each column in the index list
|
|||
|
|
|
|||
|
|
def onShowAlert(self, title, msg, time):
|
|||
|
|
self.alert = Alert()
|
|||
|
|
self.windowClose.connect(self.alert.onWindowClose)
|
|||
|
|
self.alert.showMsg(title, msg, time)
|
|||
|
|
|
|||
|
|
def onShowScanf(self, title, msg, value):
|
|||
|
|
self.scanf = Scanf()
|
|||
|
|
self.windowClose.connect(self.scanf.onWindowClose)
|
|||
|
|
self.scanf.scanfFinish.connect(self.onScanfFinish)
|
|||
|
|
self.scanf.showMsg(title, msg, value)
|
|||
|
|
|
|||
|
|
def onScanfFinish(self, msg):
|
|||
|
|
self.scanfResult = msg
|
|||
|
|
|
|||
|
|
@pyqtSlot(str,str,str,str, result=bool)
|
|||
|
|
def initAppScript(self, appId, instancName, scriptFile, plusFile="plus"):
|
|||
|
|
try:
|
|||
|
|
if dog.isDogAlive() == 1 or suDog.isDogAlive() == 1:
|
|||
|
|
scriptPath = os.path.join(self.appPath,"qml",plusFile,appId,"script", scriptFile)
|
|||
|
|
with open(scriptPath, "r") as f:
|
|||
|
|
script = f.read()
|
|||
|
|
exec(script, self.qscript)
|
|||
|
|
if self.qscript != {}:
|
|||
|
|
self.appReady.emit(instancName)
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
print(e)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
@pyqtSlot(str,str,str,str,str, result=bool)
|
|||
|
|
def registAppQml(self, appId, instancName, className, scriptFile, plusFile="plus"):
|
|||
|
|
try:
|
|||
|
|
if dog.isDogAlive() == 1 or suDog.isDogAlive() == 1:
|
|||
|
|
scriptPath = os.path.join(self.appPath,"qml",plusFile,appId,"script", scriptFile)
|
|||
|
|
with open(scriptPath, "r") as f:
|
|||
|
|
script = f.read()
|
|||
|
|
exec(script, self.qscript)
|
|||
|
|
if self.qscript != {}:
|
|||
|
|
self.registQml.emit(instancName, className)
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
print(e)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 任务详细信息
|
|||
|
|
def getTaskDetails(self, taskInstructions):
|
|||
|
|
for taskInstruction in taskInstructions:
|
|||
|
|
if taskInstruction["target_type"] == "instruction":
|
|||
|
|
target_param = taskInstruction["target_param"] # 指令参数
|
|||
|
|
instructionInfo = instructionManager.getInfo(str(taskInstruction["target_id"]))
|
|||
|
|
instructionInfo["target_param"] = target_param
|
|||
|
|
taskInstruction["instructionInfo"] = instructionInfo #指令
|
|||
|
|
device_id = taskInstruction["device_id"]
|
|||
|
|
interfaceIndex = taskInstruction["interface_index"]
|
|||
|
|
if interfaceIndex == "" or interfaceIndex == None:
|
|||
|
|
interfaceIndex = 0
|
|||
|
|
else:
|
|||
|
|
interfaceIndex = int(interfaceIndex)
|
|||
|
|
device = deviceManager.getInfo(device_id)
|
|||
|
|
|
|||
|
|
if device != None:
|
|||
|
|
devInterface = device["interface"]
|
|||
|
|
dev_model_id = device["dev_model_id"]
|
|||
|
|
devModel = devModelManager.getInfo(dev_model_id)
|
|||
|
|
devInterfaceKeys = []
|
|||
|
|
if devModel != None:
|
|||
|
|
attr_json_dict = devModel["attr"]
|
|||
|
|
devmodelInterfacetypes = {}
|
|||
|
|
if "interfaceType" in attr_json_dict:
|
|||
|
|
devmodelInterfacetypes = attr_json_dict["interfaceType"]
|
|||
|
|
devInterfaceKeys = list(devmodelInterfacetypes.keys())
|
|||
|
|
if(interfaceIndex < len(devInterface)):
|
|||
|
|
if (len(devInterfaceKeys) > interfaceIndex):
|
|||
|
|
interfaceName = devInterfaceKeys[interfaceIndex]
|
|||
|
|
interface = devInterface[interfaceIndex]
|
|||
|
|
device["interfaceName"] = interfaceName #接口名称
|
|||
|
|
taskInstruction["interface"] = interface #接口
|
|||
|
|
taskInstruction["device"] = device #设备
|
|||
|
|
elif taskInstruction["target_type"] == "task":
|
|||
|
|
task_id = taskInstruction["target_id"]
|
|||
|
|
taskInfo = taskManager.getInfo(str(task_id))
|
|||
|
|
taskInstruction["taskInfo"] = taskInfo #任务
|
|||
|
|
|
|||
|
|
|
|||
|
|
def getInstructionDetail(self, instructionId, attr):
|
|||
|
|
try:
|
|||
|
|
taskInstruction = {
|
|||
|
|
"target_type": "instruction",
|
|||
|
|
"target_param": attr["param"],
|
|||
|
|
"target_id": instructionId
|
|||
|
|
}
|
|||
|
|
deviceId = attr["deviceId"]
|
|||
|
|
interfaceIndex = int(attr["interfaceIndex"])
|
|||
|
|
instructionInfo = instructionManager.getInfo(str(instructionId))
|
|||
|
|
taskInstruction["instructionInfo"] = instructionInfo #指令
|
|||
|
|
device = deviceManager.getInfo(deviceId)
|
|||
|
|
if device != None:
|
|||
|
|
devInterface = device["interface"]
|
|||
|
|
dev_model_id = device["dev_model_id"]
|
|||
|
|
devModel = devModelManager.getInfo(dev_model_id)
|
|||
|
|
devInterfaceKeys = []
|
|||
|
|
if devModel != None:
|
|||
|
|
attr_json_dict = devModel["attr"]
|
|||
|
|
devmodelInterfacetypes = {}
|
|||
|
|
if "interfaceType" in attr_json_dict:
|
|||
|
|
devmodelInterfacetypes = attr_json_dict["interfaceType"]
|
|||
|
|
devInterfaceKeys = list(devmodelInterfacetypes.keys())
|
|||
|
|
if(interfaceIndex < len(devInterface)):
|
|||
|
|
if (len(devInterfaceKeys) > interfaceIndex):
|
|||
|
|
interfaceName = devInterfaceKeys[interfaceIndex]
|
|||
|
|
interface = devInterface[interfaceIndex]
|
|||
|
|
device["interfaceName"] = interfaceName
|
|||
|
|
taskInstruction["interface"] = interface #接口
|
|||
|
|
taskInstruction["device"] = device #设备
|
|||
|
|
else:
|
|||
|
|
return None
|
|||
|
|
return taskInstruction
|
|||
|
|
except Exception as e:
|
|||
|
|
print(e)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
#日志等级转换
|
|||
|
|
def level2Search(self, level):
|
|||
|
|
if level == "DEBUG":
|
|||
|
|
return "0"
|
|||
|
|
elif level == "INFO":
|
|||
|
|
return "01"
|
|||
|
|
elif level == "WARNING":
|
|||
|
|
return "012"
|
|||
|
|
elif level == "ERROR":
|
|||
|
|
return "0123"
|
|||
|
|
|
|||
|
|
@pyqtSlot(str)
|
|||
|
|
def changeQml(self, qmlfile):
|
|||
|
|
self.sig_changeQml.emit(qmlfile)
|
|||
|
|
|
|||
|
|
def changeMainQml(self, qmlfile):
|
|||
|
|
self.sig_changeMainQml.emit(qmlfile)
|
|||
|
|
|
|||
|
|
#显示隐藏App
|
|||
|
|
@pyqtSlot(str,bool, result=bool)
|
|||
|
|
def showApp(self, id, show):
|
|||
|
|
_path = os.path.join(self.appPath, "qml", "plus")
|
|||
|
|
try:
|
|||
|
|
appDir = os.path.join(_path,id)
|
|||
|
|
with open(os.path.join(appDir,'info'), 'r', encoding="utf-8") as file:
|
|||
|
|
info = file.read()
|
|||
|
|
info = json.loads(info)
|
|||
|
|
info["General"]["Show"] = show
|
|||
|
|
with open(os.path.join(appDir,'info'), 'w', encoding="utf-8") as file:
|
|||
|
|
json.dump(info, file, indent=4)
|
|||
|
|
self.sig_appChanged.emit()
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error showApp: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 卸载App
|
|||
|
|
@pyqtSlot(str, result=bool)
|
|||
|
|
def deleteApp(self, id):
|
|||
|
|
_path = os.path.join(self.appPath, "qml", "plus")
|
|||
|
|
try:
|
|||
|
|
appDir = os.path.join(_path, id)
|
|||
|
|
shutil.rmtree(appDir, ignore_errors=False, onerror=None)
|
|||
|
|
self.sig_appChanged.emit()
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error deleteApp: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 安装App
|
|||
|
|
@pyqtSlot(str, result=bool)
|
|||
|
|
def installApp(self, filePath):
|
|||
|
|
_path = os.path.join(self.appPath, "qml", "plus")
|
|||
|
|
filePath = self.appfilePath(filePath)
|
|||
|
|
# 检查文件是否存在
|
|||
|
|
if not os.path.isfile(filePath):
|
|||
|
|
print(f"File does not exist: {filePath}")
|
|||
|
|
return False
|
|||
|
|
# 检查文件是否可读
|
|||
|
|
try:
|
|||
|
|
with open(filePath, "rb") as file:
|
|||
|
|
file.read()
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
print(f"File not found: {filePath}")
|
|||
|
|
return False
|
|||
|
|
except PermissionError:
|
|||
|
|
print(f"Permission denied: {filePath}")
|
|||
|
|
return False
|
|||
|
|
# 解压文件
|
|||
|
|
try:
|
|||
|
|
# 获取解压目录名
|
|||
|
|
zipJson = self.zipJson(filePath)
|
|||
|
|
# 获取配置项的值
|
|||
|
|
appId = zipJson["General"]["AppId"]
|
|||
|
|
appName = zipJson["General"]["Name"]
|
|||
|
|
# extracted_dirname , ext = os.path.splitext(filePath)
|
|||
|
|
appDir = os.path.join(_path,appId)
|
|||
|
|
os.makedirs( appDir, exist_ok=True)
|
|||
|
|
shutil.unpack_archive(filePath, appDir, "zip")
|
|||
|
|
os.remove(filePath)
|
|||
|
|
if not self.compare_md5_txt(appDir):
|
|||
|
|
print("md5 校验失败")
|
|||
|
|
shutil.rmtree(appDir, ignore_errors=False, onerror=None)
|
|||
|
|
return False
|
|||
|
|
self.sig_appChanged.emit()
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error unpacking archive: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
@pyqtSlot()
|
|||
|
|
def updateAppList(self):
|
|||
|
|
_path = os.path.join(self.appPath, "qml", "plus")
|
|||
|
|
self.appList = []
|
|||
|
|
# 遍历目录下的所有子目录
|
|||
|
|
for root, dirs, files in os.walk(_path):
|
|||
|
|
# 遍历当前目录下的所有文件,找到info文件
|
|||
|
|
for file in files:
|
|||
|
|
if file == 'info':
|
|||
|
|
# 获取info文件的路径
|
|||
|
|
file_path = os.path.join(root, file)
|
|||
|
|
# 读取文件内容
|
|||
|
|
with open(file_path, 'r', encoding="utf-8") as f:
|
|||
|
|
content = f.read()
|
|||
|
|
info = json.loads(content)
|
|||
|
|
# 将文件内容保存到列表中
|
|||
|
|
self.appList.append( {
|
|||
|
|
"id": info["General"]["AppId"],
|
|||
|
|
"info": info,
|
|||
|
|
"show": info["General"]["Show"]
|
|||
|
|
} )
|
|||
|
|
|
|||
|
|
# # 将app_list列表转换为JSON文件
|
|||
|
|
# with open(os.path.join(_path,'apps.json'), 'w', encoding="utf-8") as f:
|
|||
|
|
# json.dump(self.appList, f, indent=4)
|
|||
|
|
|
|||
|
|
@pyqtSlot(result=list)
|
|||
|
|
def appList(self):
|
|||
|
|
return self.appList
|
|||
|
|
|
|||
|
|
@pyqtSlot(result=str)
|
|||
|
|
def getSystemInterfaceUuid(self):
|
|||
|
|
return self.systemInterfaceUuid
|
|||
|
|
|
|||
|
|
#获取校验脚本
|
|||
|
|
@pyqtSlot(result=list)
|
|||
|
|
def getCheckFileName(self):
|
|||
|
|
try:
|
|||
|
|
keys = self.scriptFile.keys()
|
|||
|
|
result = []
|
|||
|
|
for key in keys:
|
|||
|
|
if "check_" in key:
|
|||
|
|
result.append(key)
|
|||
|
|
return result
|
|||
|
|
except Exception as e:
|
|||
|
|
log.error(e)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
#获取全部脚本
|
|||
|
|
@pyqtSlot(result=list)
|
|||
|
|
def getAllFileNames(self):
|
|||
|
|
try:
|
|||
|
|
return list(self.scriptFile.keys())
|
|||
|
|
except Exception as e:
|
|||
|
|
log.error(e)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 获取串口列表
|
|||
|
|
@pyqtSlot(result=list)
|
|||
|
|
def getComList(self):
|
|||
|
|
comList = list()
|
|||
|
|
for a in QSerialPortInfo.availablePorts():
|
|||
|
|
comList.append(a.portName())
|
|||
|
|
return comList
|
|||
|
|
|
|||
|
|
|
|||
|
|
def calculate_file_md5(self, file_path):
|
|||
|
|
# 计算给定文件的MD5值。
|
|||
|
|
md5 = hashlib.md5()
|
|||
|
|
with open(file_path, "rb") as f:
|
|||
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|||
|
|
md5.update(chunk)
|
|||
|
|
return md5.hexdigest()
|
|||
|
|
|
|||
|
|
def calculate_directory_md5(self, directory_path):
|
|||
|
|
#计算给定目录下除md5.txt文件以外的全部文件的MD5值。
|
|||
|
|
md5_values = {}
|
|||
|
|
with os.scandir(directory_path) as it:
|
|||
|
|
for entry in it:
|
|||
|
|
if entry.is_file() and entry.name != "md5.txt":
|
|||
|
|
file_md5 = self.calculate_file_md5(entry.path)
|
|||
|
|
md5_values[entry.name] = file_md5
|
|||
|
|
elif entry.is_dir():
|
|||
|
|
sub_directory_md5 = self.calculate_directory_md5(entry.path)
|
|||
|
|
md5_values[entry.name] = sub_directory_md5
|
|||
|
|
return md5_values
|
|||
|
|
|
|||
|
|
def compare_md5_txt(self, directory_path):
|
|||
|
|
#读取md5.txt文件中的内容,与calculate_directory_md5函数返回的字典进行比较。
|
|||
|
|
with open(os.path.join(directory_path, "md5.txt"), "r") as f:
|
|||
|
|
md5_txt = f.read()
|
|||
|
|
directory_md5 = self.calculate_directory_md5(directory_path)
|
|||
|
|
json_str = json.dumps(directory_md5, indent=4, sort_keys=True)
|
|||
|
|
# print(json_str)
|
|||
|
|
md5_str = hashlib.md5(json_str.encode("utf-8")).hexdigest()
|
|||
|
|
# print(md5_str)
|
|||
|
|
# print(md5_txt)
|
|||
|
|
if md5_str == md5_txt:
|
|||
|
|
return True
|
|||
|
|
else:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def appfilePath(self, path):
|
|||
|
|
_path = os.path.join(self.appPath, "qml", "plus")
|
|||
|
|
path = path.replace("file:///", "")
|
|||
|
|
filename = os.path.basename(path)
|
|||
|
|
filename, ext = os.path.splitext(filename)
|
|||
|
|
new_path = os.path.join(_path , filename +".zip")
|
|||
|
|
shutil.copyfile(path, new_path)
|
|||
|
|
return new_path
|
|||
|
|
|
|||
|
|
def zipJson(self, zipPath):
|
|||
|
|
# 打开zip文件
|
|||
|
|
with zipfile.ZipFile(zipPath, "r") as zip_ref:
|
|||
|
|
with zip_ref.open("info") as f:
|
|||
|
|
info_txt = f.read()
|
|||
|
|
return json.loads(info_txt)
|
|||
|
|
|
|||
|
|
def setSriptFile(self, conf = None):
|
|||
|
|
_path = os.path.join(self.appPath, "scripts")
|
|||
|
|
if conf:
|
|||
|
|
_path = conf["path"]
|
|||
|
|
|
|||
|
|
for file_name in os.listdir(_path):
|
|||
|
|
file_path = os.path.join(_path, file_name)
|
|||
|
|
|
|||
|
|
if os.path.isfile(file_path):
|
|||
|
|
with open(file_path, "r", encoding="utf-8") as file:
|
|||
|
|
self.scriptFile[file_name] = file.read()
|
|||
|
|
return self.scriptFile
|
|||
|
|
|
|||
|
|
def parseCodecPys(self, s):
|
|||
|
|
try:
|
|||
|
|
result = re.search(r"codec_(\w+).pys", s)
|
|||
|
|
if result:
|
|||
|
|
return result.group(1)
|
|||
|
|
else:
|
|||
|
|
return ""
|
|||
|
|
except:
|
|||
|
|
return ""
|
|||
|
|
def parseCheckPys(self, s):
|
|||
|
|
try:
|
|||
|
|
result = re.search(r"check_(\w+).pys", s)
|
|||
|
|
if result:
|
|||
|
|
return result.group(1)
|
|||
|
|
else:
|
|||
|
|
return ""
|
|||
|
|
except:
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
# base64转Pixmap
|
|||
|
|
def base64ToPixmap(self, base64_str):
|
|||
|
|
return QPixmap.fromImage(QImage.fromData(QByteArray.fromBase64(base64_str.encode("utf-8"))))
|
|||
|
|
|
|||
|
|
#QImage转base64
|
|||
|
|
def base64Data(self, image):
|
|||
|
|
ba = QByteArray()
|
|||
|
|
buffer = QBuffer(ba)
|
|||
|
|
buffer.open(QIODevice.OpenModeFlag.WriteOnly)
|
|||
|
|
image.save(buffer, "PNG")
|
|||
|
|
return ba.toBase64().data().decode()
|
|||
|
|
|
|||
|
|
def is_json_string(self, string):
|
|||
|
|
try:
|
|||
|
|
if string.isdigit():
|
|||
|
|
return False
|
|||
|
|
json_object = json.loads(string)
|
|||
|
|
return True
|
|||
|
|
except:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def writeJson(self, path, table_name, data):
|
|||
|
|
try:
|
|||
|
|
data = common.ensure_serializable(data)
|
|||
|
|
if '_sa_instance_state' in data:
|
|||
|
|
del data['_sa_instance_state']
|
|||
|
|
# 拼接完整的文件路径
|
|||
|
|
file_path = os.path.join(path, table_name)
|
|||
|
|
# 检查路径是否存在,如果不存在,则创建路径
|
|||
|
|
if not os.path.exists(file_path):
|
|||
|
|
os.makedirs(file_path)
|
|||
|
|
# 写入JSON文件
|
|||
|
|
with open(os.path.join(file_path, f'{data["id"]}.json'), 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
|||
|
|
except Exception as e:
|
|||
|
|
log.error(table_name,data,e)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def writeZip(self, target_path, directory_path):
|
|||
|
|
# 创建ZIP文件并添加目录内容
|
|||
|
|
with zipfile.ZipFile(target_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|||
|
|
# 递归添加目录和文件到ZIP文件中
|
|||
|
|
self.add_dir_to_zip(directory_path, zipf, directory_path)
|
|||
|
|
|
|||
|
|
def clearDir(self, path):
|
|||
|
|
shutil.rmtree(path)
|
|||
|
|
|
|||
|
|
def createDir(self, path):
|
|||
|
|
if not os.path.exists(path):
|
|||
|
|
os.makedirs(path)
|
|||
|
|
|
|||
|
|
def add_dir_to_zip(self, dir_path, zipf, parent_dir):
|
|||
|
|
for root, dirs, files in os.walk(dir_path):
|
|||
|
|
for file in files:
|
|||
|
|
file_path = os.path.join(root, file)
|
|||
|
|
# 使用os.path.relpath获取相对于parent_dir的路径作为归档名称
|
|||
|
|
arcname = os.path.relpath(file_path, parent_dir)
|
|||
|
|
zipf.write(file_path, arcname=arcname)
|
|||
|
|
for subdir in dirs:
|
|||
|
|
subdir_path = os.path.join(root, subdir)
|
|||
|
|
# 递归调用add_dir_to_zip()函数,将空目录也添加到ZIP文件中
|
|||
|
|
self.add_dir_to_zip(subdir_path, zipf, parent_dir)
|
|||
|
|
|
|||
|
|
# 确保所有数据都是可序列化的
|
|||
|
|
def ensure_serializable(self, obj):
|
|||
|
|
try:
|
|||
|
|
if 'script' in obj:
|
|||
|
|
# 删掉b''
|
|||
|
|
if isinstance(obj['script'], bytes):
|
|||
|
|
obj['script'] = obj['script'].decode('utf-8')
|
|||
|
|
#如果存在_sa_instance_state 则删除
|
|||
|
|
if "attr" in obj:
|
|||
|
|
obj["attr"] = obj["attr"] and json.loads(obj["attr"]) or {}
|
|||
|
|
if "interface" in obj:
|
|||
|
|
obj["interface"] = obj["interface"] and json.loads(obj["interface"]) or []
|
|||
|
|
if isinstance(obj, dict):
|
|||
|
|
return {k: self.ensure_serializable(v) for k, v in obj.items()}
|
|||
|
|
elif isinstance(obj, list):
|
|||
|
|
return [self.ensure_serializable(v) for v in obj]
|
|||
|
|
elif isinstance(obj, bytes):
|
|||
|
|
return obj.decode('utf-8') # 将bytes转换为字符串
|
|||
|
|
else:
|
|||
|
|
return obj
|
|||
|
|
except Exception as e:
|
|||
|
|
return obj
|
|||
|
|
|
|||
|
|
# 导出表结构和数据到SQL文件
|
|||
|
|
def export_table_structure_and_data(db_path, table_name):
|
|||
|
|
# 连接到SQLite数据库
|
|||
|
|
conn = sqlite3.connect(db_path)
|
|||
|
|
cursor = conn.cursor()
|
|||
|
|
output_file = f"{table_name}.sql"
|
|||
|
|
try:
|
|||
|
|
# 打开文件准备写入
|
|||
|
|
with open(output_file, 'w', encoding='utf-8') as sql_file:
|
|||
|
|
sql_file.write("PRAGMA foreign_keys = false;\n")
|
|||
|
|
sql_file.write(f"DROP TABLE IF EXISTS \"{table_name}\";\n")
|
|||
|
|
# 写入表结构
|
|||
|
|
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
|||
|
|
for create_table_sql in cursor.fetchall():
|
|||
|
|
sql_file.write(create_table_sql[0] + ";\n\n")
|
|||
|
|
|
|||
|
|
# 写入表数据
|
|||
|
|
cursor.execute(f"SELECT * FROM {table_name}")
|
|||
|
|
rows = cursor.fetchall()
|
|||
|
|
columns = [column[0] for column in cursor.description]
|
|||
|
|
|
|||
|
|
for row in rows:
|
|||
|
|
formatted_values =", ".join(
|
|||
|
|
"'{}'".format(v.replace("'", "''") if isinstance(v, str) else v) for v in row
|
|||
|
|
)
|
|||
|
|
sql_file.write(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({formatted_values});\n")
|
|||
|
|
|
|||
|
|
# 写入与表相关的触发器
|
|||
|
|
cursor.execute("SELECT sql FROM sqlite_master WHERE type='trigger' AND tbl_name=?", (table_name,))
|
|||
|
|
triggers = cursor.fetchall()
|
|||
|
|
for trigger_sql in triggers:
|
|||
|
|
sql_file.write(trigger_sql[0] + ";\n")
|
|||
|
|
sql_file.write("PRAGMA foreign_keys = true;\n")
|
|||
|
|
except sqlite3.Error as e:
|
|||
|
|
print(f"SQLite error1: {e}")
|
|||
|
|
finally:
|
|||
|
|
# 关闭数据库连接
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
# 使用示例
|
|||
|
|
# export_table_structure_and_data('data.db', 'dev_model')
|
|||
|
|
# export_table_structure_and_data('data.db', 'device')
|
|||
|
|
# export_table_structure_and_data('data.db', 'instruction')
|
|||
|
|
# export_table_structure_and_data('data.db', 'task')
|
|||
|
|
# export_table_structure_and_data('data.db', 'task_group')
|
|||
|
|
# export_table_structure_and_data('data.db', 'task_instruction')
|
|||
|
|
# 导入表结构和数据
|
|||
|
|
def import_sql_to_database(self,db_path, table_bame):
|
|||
|
|
# 连接到SQLite数据库
|
|||
|
|
conn = sqlite3.connect(db_path)
|
|||
|
|
cursor = conn.cursor()
|
|||
|
|
sql_file_path = f"{table_bame}.sql"
|
|||
|
|
try:
|
|||
|
|
# 打开SQL文件
|
|||
|
|
with open(sql_file_path, 'r', encoding='utf-8') as file:
|
|||
|
|
# 读取整个文件内容
|
|||
|
|
sql_script = file.read()
|
|||
|
|
|
|||
|
|
# 逐个处理SQL语句,这里假设每个语句以分号结束
|
|||
|
|
# 使用conn的executescript方法来执行多条语句
|
|||
|
|
try:
|
|||
|
|
conn.executescript(sql_script)
|
|||
|
|
except sqlite3.Error as e:
|
|||
|
|
# 如果执行失败,打印错误信息
|
|||
|
|
print(f"SQLite error: {e}")
|
|||
|
|
return # 终止函数执行
|
|||
|
|
|
|||
|
|
# 提交事务
|
|||
|
|
conn.commit()
|
|||
|
|
print("SQL file imported successfully.")
|
|||
|
|
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
print(f"The file {sql_file_path} was not found.")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"An unexpected error occurred: {e}")
|
|||
|
|
finally:
|
|||
|
|
# 无论是否发生异常,都确保关闭数据库连接
|
|||
|
|
conn.close()
|
|||
|
|
common = Common()
|