diff --git a/resource/OneKeyLogDiag_v2.ui b/resource/OneKeyLogDiag_v2.ui
index 6a28a90..66c6741 100644
--- a/resource/OneKeyLogDiag_v2.ui
+++ b/resource/OneKeyLogDiag_v2.ui
@@ -36,7 +36,7 @@
-
-
+
diff --git a/src/backend/back_service.py b/src/backend/back_service.py
index 86b14f2..c7c81d0 100644
--- a/src/backend/back_service.py
+++ b/src/backend/back_service.py
@@ -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()
-
\ No newline at end of file
+ else:
+ record_log("ERROR", f"Project : {key} | No 'start_parse' function found, failed")
+ if progress_callback:
+ progress_callback(-1, "处理出错")
\ No newline at end of file
diff --git a/src/common/__init__.py b/src/common/__init__.py
index 2548c29..326c0b0 100644
--- a/src/common/__init__.py
+++ b/src/common/__init__.py
@@ -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]
\ No newline at end of file
+__all__ += [ clean_app_cache, clean_running_log, add_onekeylog_info_cache ]
\ No newline at end of file
diff --git a/src/common/cache_mgmt.py b/src/common/cache_mgmt.py
index 685bb8d..e046905 100644
--- a/src/common/cache_mgmt.py
+++ b/src/common/cache_mgmt.py
@@ -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)
diff --git a/src/frontend/background_task.py b/src/frontend/background_task.py
new file mode 100644
index 0000000..0701b28
--- /dev/null
+++ b/src/frontend/background_task.py
@@ -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()
\ No newline at end of file
diff --git a/src/frontend/event_handler.py b/src/frontend/event_handler.py
index f3af934..c488e02 100644
--- a/src/frontend/event_handler.py
+++ b/src/frontend/event_handler.py
@@ -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
\ No newline at end of file
diff --git a/src/frontend/file_processor.py b/src/frontend/file_processor.py
index dd3899d..e45bce2 100644
--- a/src/frontend/file_processor.py
+++ b/src/frontend/file_processor.py
@@ -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
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/src/frontend/main_window.py b/src/frontend/main_window.py
index 293e834..180729a 100644
--- a/src/frontend/main_window.py
+++ b/src/frontend/main_window.py
@@ -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()
\ No newline at end of file
+ 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")
\ No newline at end of file
diff --git a/src/frontend/onekeydiag_ui.py b/src/frontend/onekeydiag_ui.py
index b5ddcbf..4bac610 100644
--- a/src/frontend/onekeydiag_ui.py
+++ b/src/frontend/onekeydiag_ui.py
@@ -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")
diff --git a/src/frontend/progress_diaglog.py b/src/frontend/progress_diaglog.py
new file mode 100644
index 0000000..66d24d3
--- /dev/null
+++ b/src/frontend/progress_diaglog.py
@@ -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() # 禁止关闭
\ No newline at end of file
diff --git a/src/frontend/ui_initializer.py b/src/frontend/ui_initializer.py
index e2f54bb..98ff15f 100644
--- a/src/frontend/ui_initializer.py
+++ b/src/frontend/ui_initializer.py
@@ -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("""
diff --git a/src/frontend/utils.py b/src/frontend/utils.py
new file mode 100644
index 0000000..2d84a92
--- /dev/null
+++ b/src/frontend/utils.py
@@ -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
+ )
\ No newline at end of file