code sync

This commit is contained in:
leimingsheng 2025-09-05 15:26:52 +08:00
parent 667f10e7d6
commit 6ec22eb909
12 changed files with 348 additions and 29 deletions

@ -36,7 +36,7 @@
<widget class="QComboBox" name="comboBox_proj_select"/>
</item>
<item>
<widget class="QTextEdit" name="textEdit_console"/>
<widget class="QTextBrowser" name="textBrowser_console"/>
</item>
</layout>
</widget>

@ -1,5 +1,6 @@
import os
import importlib
from common import record_log
class AppService:
def __init__(self):
@ -95,10 +96,16 @@ def get_project_logname_list(key):
return result
def do_proj_parse(key="app"):
def do_proj_parse(parseStatus, filepath, key="app", progress_callback = None):
porj_package = "proj_" + key
pHandlers = dynamic_import_lib(porj_package)
if hasattr(pHandlers, "start_parse"):
# Step 1 . 解压日志文件, 并记录到历史信息
# Step 2 . 调用具体handler进行自定义解析
pHandlers.start_parse()
else:
record_log("ERROR", f"Project : {key} | No 'start_parse' function found, failed")
if progress_callback:
progress_callback(-1, "处理出错")

@ -15,7 +15,8 @@ from .file_tool import(
from .cache_mgmt import(
clean_app_cache,
clean_running_log
clean_running_log,
add_onekeylog_info_cache
)
# From ._globals
@ -28,4 +29,4 @@ __all__ += [ record_log ]
__all__ += [ force_empty_folder, add_to_json_array, get_user_appdata_path ]
# From .cache_mgmt
__all__ += [ clean_app_cache, clean_running_log]
__all__ += [ clean_app_cache, clean_running_log, add_onekeylog_info_cache ]

@ -1,5 +1,6 @@
import os
import time
import tarfile
from ._globals import cache_dir
from .file_tool import force_empty_folder, add_to_json_array, get_user_appdata_path
@ -21,7 +22,7 @@ def app_cache_create():
os.makedirs(app_plugin_path)
record_log("Info", f"Create Dir:{app_plugin_path}")
def add_onekeylog_info_cache(log_name):
def add_onekeylog_info_cache(log_name, proj_key):
"""
添加一份新日志时触发这个动作, 将日志名称以及解析日期全部记录到
parse_history.json
@ -35,6 +36,7 @@ def add_onekeylog_info_cache(log_name):
# 生成新的记录Dict
new_entry = {
"project" : proj_key,
"filename" : log_name,
"timestamp" : current_time
}
@ -44,6 +46,41 @@ def add_onekeylog_info_cache(log_name):
if not result_b:
record_log("ERROR", "Fail to add parse history cache")
def unzip_log(tar_path, extract_path='.'):
"""
解压 tar.gz 文件到指定目录
参数:
tar_path (str): tar.gz 文件的路径
extract_path (str): 解压目标目录默认为当前目录
"""
try:
# 检查文件是否存在
if not os.path.exists(tar_path):
raise FileNotFoundError(f"文件不存在: {tar_path}")
# 创建解压目录(如果不存在)
os.makedirs(extract_path, exist_ok=True)
# 打开 tar.gz 文件并解压
with tarfile.open(tar_path, "r:gz") as tar:
# 列出所有文件(可选)
print(f"解压文件列表:")
for member in tar.getmembers():
print(f"- {member.name}")
# 解压所有文件到目标目录
tar.extractall(path=extract_path)
print(f"\n成功解压到: {os.path.abspath(extract_path)}")
except tarfile.TarError as e:
print(f"tar 文件处理错误: {e}")
except Exception as e:
print(f"解压失败: {e}")
def send_log_to_cache(filepath, key):
pass
def clean_app_cache():
force_empty_folder(cache_dir)

@ -0,0 +1,64 @@
from PyQt6.QtCore import QThread, pyqtSignal
from backend import do_proj_parse
class DiagnoseThread(QThread):
"""后台线程,用于执行耗时的诊断任务"""
# 定义信号:更新进度(值, 消息)、任务完成(是否成功, 消息)
progress_updated = pyqtSignal(int, str)
task_completed = pyqtSignal(bool, str)
def __init__(self, parse_status, file_path, key):
super().__init__()
self.parse_status = parse_status
self.file_path = file_path
self.is_running = True
self.proj_key = key
def run(self):
"""线程执行函数:执行耗时任务"""
try:
self.progress_updated.emit(1, "正在初始化解析...")
# 执行耗时任务
do_proj_parse(self.parse_status, self.file_path, self.proj_key, progress_callback=self.update_progress)
# 确保最终进度为100%
self.progress_updated.emit(100, "处理完成")
self.task_completed.emit(True, "日志解析任务已完成")
except Exception as e:
# 出错时也允许关闭对话框
self.progress_updated.emit(100, f"处理失败:{str(e)}")
self.task_completed.emit(False, f"处理失败:{str(e)}")
def update_progress(self, value, message):
"""接收service层的进度更新"""
if 0 <= value <= 100 and self.is_running:
self.progress_updated.emit(value, message)
def stop(self):
"""停止线程"""
self.is_running = False
self.wait()
class FileReaderThread(QThread):
# 定义信号,用于传递读取到的文本块和完成状态
text_chunk_ready = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, file_path, chunk_size=4096):
super().__init__()
self.file_path = file_path
self.chunk_size = chunk_size # 每次读取的块大小
def run(self):
try:
with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f:
while True:
chunk = f.read(self.chunk_size) # 分块读取
if not chunk:
break
self.text_chunk_ready.emit(chunk) # 发送文本块到主线程
self.finished.emit()
except Exception as e:
self.text_chunk_ready.emit(f"读取文件错误: {str(e)}")
self.finished.emit()

@ -1,12 +1,17 @@
import os
from PyQt6.QtWidgets import QFileDialog
from PyQt6.QtGui import QDragEnterEvent, QDropEvent
from common import record_log
class EventHandler:
def __init__(self, main_window):
self.main_window = main_window # 持有主窗口引用
self.file_processor = main_window.file_processor
def bind_event(self):
record_log("INFO", "Start Bind Event")
# 菜单项事件绑定
self.main_window.action_uploadFile.triggered.connect(self.upload_file_in_resource)
self.main_window.action_cleanCache.triggered.connect(self.on_click_clean_cache)
self.main_window.action_cleanLog.triggered.connect(self.on_click_clean_running_log)
@ -40,15 +45,42 @@ class EventHandler:
def on_click_clean_running_log(self):
from common import clean_running_log
self.main_window.textEdit_console.append("系统信息: Clean apprunning.log")
self.main_window.textBrowser_console.insertPlainText("系统信息: Clean apprunning.log")
clean_running_log()
def on_click_clean_cache(self):
from common import clean_app_cache
self.main_window.textEdit_console.append("系统信息: Clean all app cache")
self.main_window.textBrowser_console.insertPlainText("系统信息: Clean all app cache")
clean_app_cache()
def upload_file_in_resource(self):
"""打开文件选择对话框"""
file_path, _ = QFileDialog.getOpenFileName(
self.main_window,
"选择文件",
os.getcwd(),
"所有文件 (*);;日志压缩文件 (*.gz)"
)
if file_path:
self.file_processor.process_uploaded_file(file_path)
def dragEnterEvent(self, event: QDragEnterEvent):
"""拖入事件:判断是否为文件"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event: QDropEvent):
"""放下事件:处理拖放的文件"""
file_paths = [url.toLocalFile() for url in event.mimeData().urls()]
for file_path in file_paths:
self.file_processor.process_uploaded_file(file_path)
def patch_drag_event(self):
"""将拖放事件绑定到主窗口(需在主窗口初始化时调用)"""
self.main_window.dragEnterEvent = self.dragEnterEvent
self.main_window.dropEvent = self.dropEvent

@ -1,3 +1,91 @@
import os
from .progress_diaglog import ProgressDialog
from .background_task import DiagnoseThread
from common import record_log
from .utils import (show_upload_error_message,
show_critical_message,
show_info_message)
class FileProcessor:
def __init__(self, main_window):
self.main_window = main_window
self.main_window = main_window
self.serviceStatus = main_window.serviceStatus
def process_uploaded_file(self, file_path):
record_log("INFO", f"Upload file : {file_path}")
current_project_key = self.main_window.comboBox_proj_select.currentText()
# 格式校验
if not self.is_valid_file_format(file_path):
show_upload_error_message(self.main_window, file_path)
return
try:
# 显示文件信息
file_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path) / 1024
info = (
f"已上传文件:\n"
f"文件名:{file_name}\n"
f"路径:{file_path}\n"
f"大小:{file_size:.2f} KB\n"
f"-------------------------------------------" + "\n"
)
self.main_window.textBrowser_console.insertPlainText(info)
# 初始化进度对话框
self.progress_dialog = ProgressDialog(
parent=self.main_window,
title="正在处理文件",
label_text="准备开始处理..."
)
# 创建并配置后台线程
self.diagnose_thread = DiagnoseThread(self.serviceStatus, file_path, current_project_key)
self.diagnose_thread.progress_updated.connect(self.progress_dialog.update_progress)
self.diagnose_thread.task_completed.connect(self.on_diagnose_completed)
# 显示进度条并启动线程
self.progress_dialog.show()
self.diagnose_thread.start()
except Exception as e:
show_critical_message(self.main_window, "处理失败", f"文件处理出错:{str(e)}")
def is_valid_file_format(self, file_path):
# 原有实现...
_, ext = os.path.splitext(file_path)
if ext.lower() == '.gz':
base, ext2 = os.path.splitext(os.path.splitext(file_path)[0])
return ext2.lower() == '.tar'
return False
def _update_view_after_parse(self):
"""
解析任务完成, 将数据同步到视图中
"""
pass
def on_diagnose_completed(self, success, message):
"""处理诊断任务完成后的逻辑"""
if hasattr(self, 'progress_dialog') and self.progress_dialog:
# 明确允许对话框关闭
self.progress_dialog.set_allow_close(True)
# 关闭对话框
self.progress_dialog.close()
# 释放引用
self.progress_dialog = None
# 显示结果信息
self.main_window.textBrowser_console.insertPlainText(f"{message}\n")
if success:
# 处理成功,更新视图
self._update_view_after_parse()
show_info_message(self.main_window, "成功", "文件解析完成")
else:
# 处理失败,显示错误
show_critical_message(self.main_window, "失败", message)
# 清理线程引用
self.diagnose_thread = None

@ -36,9 +36,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.ui_init.init_comboBox_project_select()
self.ui_init.init_comboBox_temperature_select()
self.ui_init.init_comboBox_logreader_select()
self.ui_init.init_textEdit_console_style()
self.ui_init.init_textBrowser_console_style()
self.ui_init.init_textBrowser_baseinfo_style()
self.ui_init.init_textBrowser_logreader_style()
# Event_Handler
self.event_handler.bind_event()
self.event_handler.bind_event()
self.event_handler.patch_drag_event()
# 完成UI初始化
record_log("INFO", "Complete UI Init")
def reinit_main_ui(self):
"""
在使用日志更新时重新刷新界面整体布局
"""
record_log("INFO", "Triggered reinit main window")

@ -29,9 +29,9 @@ class Ui_MainWindow(object):
self.comboBox_proj_select = QtWidgets.QComboBox(parent=self.tab_console)
self.comboBox_proj_select.setObjectName("comboBox_proj_select")
self.verticalLayout_4.addWidget(self.comboBox_proj_select)
self.textEdit_console = QtWidgets.QTextEdit(parent=self.tab_console)
self.textEdit_console.setObjectName("textEdit_console")
self.verticalLayout_4.addWidget(self.textEdit_console)
self.textBrowser_console = QtWidgets.QTextBrowser(parent=self.tab_console)
self.textBrowser_console.setObjectName("textBrowser_console")
self.verticalLayout_4.addWidget(self.textBrowser_console)
self.tabWidget.addTab(self.tab_console, "")
self.tab_baseinfo = QtWidgets.QWidget()
self.tab_baseinfo.setObjectName("tab_baseinfo")

@ -0,0 +1,46 @@
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QProgressBar, QLabel
from PyQt6.QtCore import Qt, pyqtSignal
class ProgressDialog(QDialog):
"""进度条对话框,支持程序控制自动关闭"""
def __init__(self, parent=None, title="处理中", label_text="正在处理,请稍候..."):
super().__init__(parent)
self.setWindowTitle(title)
self.setModal(True) # 模态对话框
self.setFixedSize(400, 120)
self.allow_close = False # 新增:允许关闭标记
# 布局
layout = QVBoxLayout()
# 提示文本
self.label = QLabel(label_text)
layout.addWidget(self.label)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def update_progress(self, value, message=None):
"""更新进度值和提示信息"""
self.progress_bar.setValue(value)
if message:
self.label.setText(message)
# 当进度完成时,允许关闭
if value >= 100:
self.allow_close = True
def set_allow_close(self, allow):
"""设置是否允许关闭对话框"""
self.allow_close = allow
def closeEvent(self, event):
"""根据允许关闭标记决定是否关闭"""
if self.allow_close:
event.accept() # 允许关闭
else:
event.ignore() # 禁止关闭

@ -38,19 +38,18 @@ class UIInitializer:
if logname_count > 0:
self.main_window.comboBox_logreader.addItems(logname_list)
def init_textEdit_console_style(self):
# self.main_window.textEdit_console.setStyleSheet("""
# QTextBrowser {
# font-family: 'SimHei';
# font-size: 20px;
# color: #2c3e50;
# background-color: #f8f9fa;
# border: 1px solid #ddd;
# border-radius: 4px;
# padding: 8px;
# }
# """)
pass
def init_textBrowser_console_style(self):
self.main_window.textBrowser_console.setStyleSheet("""
QTextBrowser {
font-family: 'SimHei';
font-size: 20px;
color: #2c3e50;
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
}
""")
def init_textBrowser_baseinfo_style(self):
self.main_window.textBrowser_baseinfo.setStyleSheet("""

35
src/frontend/utils.py Normal file

@ -0,0 +1,35 @@
from PyQt6.QtWidgets import QMessageBox
def show_upload_error_message(parent, file_path):
"""显示文件格式错误提示弹窗"""
QMessageBox.critical(
parent,
"文件格式错误",
f"不支持的文件格式:\n{file_path}\n\n请上传.tar.gz格式的压缩包。"
)
def show_critical_message(parent, title, message):
"""显示通用错误提示弹窗"""
QMessageBox.critical(
parent,
title,
message
)
def show_info_message(parent, title, message):
"""显示信息提示弹窗"""
QMessageBox.information(
parent,
title,
message
)
def show_question_message(parent, title, message):
"""显示询问提示弹窗返回用户选择Yes/No"""
return QMessageBox.question(
parent,
title,
message,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)