#!/usr/bin/env python # -*- coding: utf-8 -*- """命令执行器模块""" import os import sys import time import subprocess import datetime import re import tkinter as tk from tkinter import messagebox from threading import Thread from .logging_ui import LogRedirector from .result_previews import show_result_preview # 任务状态跟踪 _RUNNING_TASK = None def get_running_task(): return _RUNNING_TASK def set_running_task(val): global _RUNNING_TASK _RUNNING_TASK = val def run_command_with_logging(command, log_widget, status_bar=None, on_complete=None): """运行命令并将输出重定向到日志窗口""" if _RUNNING_TASK is not None: messagebox.showinfo("任务进行中", "请等待当前任务完成后再执行新的操作。") return def run_in_thread(): global _RUNNING_TASK _RUNNING_TASK = command if status_bar: status_bar.set_running(True) start_time = datetime.datetime.now() start_perf = time.perf_counter() log_widget.configure(state=tk.NORMAL) log_widget.delete(1.0, tk.END) log_widget.insert(tk.END, f"执行命令: {' '.join(command)}\n", "command") log_widget.insert(tk.END, f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n", "time") log_widget.insert(tk.END, "=" * 50 + "\n\n", "separator") log_widget.configure(state=tk.DISABLED) old_stdout = sys.stdout old_stderr = sys.stderr log_redirector = LogRedirector(log_widget) env = os.environ.copy() try: from app.config.settings import ConfigManager cfg = ConfigManager() env["OCR_OUTPUT_DIR"] = cfg.get_path('Paths', 'output_folder', fallback='data/output', create=True) env["OCR_INPUT_DIR"] = cfg.get_path('Paths', 'input_folder', fallback='data/input', create=True) env["OCR_TEMP_DIR"] = cfg.get_path('Paths', 'temp_folder', fallback='data/temp', create=True) except Exception: env["OCR_OUTPUT_DIR"] = os.path.abspath("data/output") env["OCR_INPUT_DIR"] = os.path.abspath("data/input") env["OCR_TEMP_DIR"] = os.path.abspath("data/temp") env["OCR_LOG_LEVEL"] = "DEBUG" try: sys.stdout = log_redirector sys.stderr = log_redirector print("日志重定向已启动,现在同时输出到终端和GUI") process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, env=env ) output_data = [] for line in process.stdout: output_data.append(line) print(line.rstrip()) if status_bar: progress = extract_progress_from_log(line) if progress is not None: log_widget.after(0, lambda p=progress: status_bar.set_status(f"处理中: {p}%完成", p)) process.wait() end_time = datetime.datetime.now() duration_sec = max(0.0, time.perf_counter() - start_perf) print(f"\n{'=' * 50}") print(f"执行完毕!返回码: {process.returncode}") print(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"耗时: {duration_sec:.2f} 秒") output_text = ''.join(output_data) is_pipeline = "pipeline" in command no_merge_files = "未找到采购单文件" in output_text single_file = "只有1个采购单文件" in output_text if is_pipeline and (no_merge_files or single_file): print("完整流程中没有需要合并的文件,但其他步骤执行成功,视为成功完成") if status_bar: log_widget.after(0, lambda: status_bar.set_status("处理完成", 100)) log_widget.after(0, lambda: show_result_preview(command, output_text)) else: if on_complete: log_widget.after(0, lambda: on_complete(process.returncode, output_text)) elif process.returncode == 0: if status_bar: log_widget.after(0, lambda: status_bar.set_status("处理完成", 100)) log_widget.after(0, lambda: show_result_preview(command, output_text)) else: if status_bar: log_widget.after(0, lambda: status_bar.set_status(f"处理失败 (返回码: {process.returncode})", 0)) log_widget.after(0, lambda: messagebox.showerror("操作失败", f"处理失败,返回码:{process.returncode}")) except Exception as e: print(f"\n执行出错: {str(e)}") if status_bar: log_widget.after(0, lambda: status_bar.set_status(f"执行出错: {str(e)}", 0)) log_widget.after(0, lambda: messagebox.showerror("执行错误", f"执行命令时出错: {str(e)}")) finally: sys.stdout = old_stdout sys.stderr = old_stderr _RUNNING_TASK = None if status_bar: log_widget.after(0, lambda: status_bar.set_running(False)) Thread(target=run_in_thread).start() def extract_progress_from_log(log_line): """从日志行中提取进度信息""" batch_match = re.search(r'处理批次 (\d+)/(\d+)', log_line) if batch_match: current = int(batch_match.group(1)) total = int(batch_match.group(2)) return int(current / total * 100) percent_match = re.search(r'(\d+)%', log_line) if percent_match: return int(percent_match.group(1)) return None