From b4065f6ce25b9a0afeca4d224ca4fb2fc2e67d50 Mon Sep 17 00:00:00 2001 From: DH K Date: Wed, 18 Mar 2026 11:08:20 +0900 Subject: [PATCH] add version --- watchdog_gui.py | 123 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/watchdog_gui.py b/watchdog_gui.py index d627e69..d0fbbb9 100644 --- a/watchdog_gui.py +++ b/watchdog_gui.py @@ -5,6 +5,8 @@ import json import time import traceback import subprocess +import ctypes +from ctypes import wintypes from pathlib import Path from logging.handlers import RotatingFileHandler import logging @@ -14,6 +16,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets APP_NAME = "Watchdog GUI" +APP_VERSION = "1.1.0" + BASE_DIR = Path(__file__).resolve().parent CONFIG_PATH = BASE_DIR / "watchdog_config.json" LOG_DIR = BASE_DIR / "watchdog_logs" @@ -113,6 +117,68 @@ def save_config(cfg: dict): LOGGER.exception("설정 파일 저장 실패") +# ========================= +# 버전 유틸 +# ========================= +def get_file_version(file_path: str) -> str: + """ + Windows EXE/DLL 파일 버전 읽기 + 실패 시 '알 수 없음' 반환 + """ + try: + if not file_path or not os.path.exists(file_path): + return "알 수 없음" + + size = ctypes.windll.version.GetFileVersionInfoSizeW(file_path, None) + if not size: + return "알 수 없음" + + res = ctypes.create_string_buffer(size) + ctypes.windll.version.GetFileVersionInfoW(file_path, 0, size, res) + + u_len = wintypes.UINT() + u_ptr = ctypes.c_void_p() + + ok = ctypes.windll.version.VerQueryValueW( + res, + "\\", + ctypes.byref(u_ptr), + ctypes.byref(u_len) + ) + if not ok: + return "알 수 없음" + + class VS_FIXEDFILEINFO(ctypes.Structure): + _fields_ = [ + ("dwSignature", wintypes.DWORD), + ("dwStrucVersion", wintypes.DWORD), + ("dwFileVersionMS", wintypes.DWORD), + ("dwFileVersionLS", wintypes.DWORD), + ("dwProductVersionMS", wintypes.DWORD), + ("dwProductVersionLS", wintypes.DWORD), + ("dwFileFlagsMask", wintypes.DWORD), + ("dwFileFlags", wintypes.DWORD), + ("dwFileOS", wintypes.DWORD), + ("dwFileType", wintypes.DWORD), + ("dwFileSubtype", wintypes.DWORD), + ("dwFileDateMS", wintypes.DWORD), + ("dwFileDateLS", wintypes.DWORD), + ] + + ffi = ctypes.cast(u_ptr, ctypes.POINTER(VS_FIXEDFILEINFO)).contents + + major = (ffi.dwFileVersionMS >> 16) & 0xFFFF + minor = ffi.dwFileVersionMS & 0xFFFF + build = (ffi.dwFileVersionLS >> 16) & 0xFFFF + revision = ffi.dwFileVersionLS & 0xFFFF + + return f"{major}.{minor}.{build}.{revision}" + + except Exception: + LOGGER.exception("파일 버전 읽기 실패") + return "알 수 없음" + + # ========================= # 감시 유틸 # ========================= @@ -184,9 +250,9 @@ def start_target_program(target_exe_path: str) -> bool: class WatchdogWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() - self.setWindowTitle(APP_NAME) - self.resize(520, 250) - self.setMinimumSize(500, 230) + self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}") + self.resize(560, 300) + self.setMinimumSize(520, 270) self.cfg = load_config() self.monitoring = False @@ -204,7 +270,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.timer.timeout.connect(self.check_target_once) LOGGER.info("=" * 60) - LOGGER.info("Watchdog GUI 시작") + LOGGER.info(f"{APP_NAME} 시작 - 버전 {APP_VERSION}") LOGGER.info(f"로그 파일: {LOG_FILE}") LOGGER.info("=" * 60) @@ -212,6 +278,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.start_monitoring() self.update_target_status_label() + self.update_version_labels() if self.cfg.get("start_minimized_to_tray", False): QtCore.QTimer.singleShot(300, self.hide_to_tray) @@ -227,6 +294,20 @@ class WatchdogWindow(QtWidgets.QMainWindow): main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(8) + # 버전 표시 + version_layout = QtWidgets.QGridLayout() + version_layout.setHorizontalSpacing(8) + version_layout.setVerticalSpacing(4) + + self.lbl_watchdog_version = QtWidgets.QLabel(f"Watchdog 버전: {APP_VERSION}") + self.lbl_watchdog_version.setStyleSheet("font-weight: bold; color: #1f1f1f;") + + self.lbl_target_version = QtWidgets.QLabel("대상 버전: 알 수 없음") + self.lbl_target_version.setStyleSheet("font-weight: bold; color: #1f1f1f;") + + version_layout.addWidget(self.lbl_watchdog_version, 0, 0) + version_layout.addWidget(self.lbl_target_version, 0, 1) + # 경로 행 path_layout = QtWidgets.QHBoxLayout() path_layout.setSpacing(6) @@ -320,6 +401,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): log_layout.addWidget(self.text_log) log_layout.addWidget(self.btn_clear_log) + main_layout.addLayout(version_layout) main_layout.addLayout(path_layout) main_layout.addLayout(option_layout) main_layout.addLayout(top_btn_layout) @@ -334,7 +416,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.btn_open_log_file.clicked.connect(self.open_log_file) self.btn_clear_log.clicked.connect(self.text_log.clear) - self.edit_exe_path.editingFinished.connect(self.save_ui_to_config) + self.edit_exe_path.editingFinished.connect(self.on_path_edited) self.spin_interval.valueChanged.connect(self.save_ui_to_config) self.spin_cooldown.valueChanged.connect(self.save_ui_to_config) self.chk_auto_start.stateChanged.connect(self.save_ui_to_config) @@ -361,9 +443,9 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.btn_toggle_log.setText("로그 숨기기" if show_log else "로그 보기") if show_log: - self.resize(560, 500) + self.resize(600, 540) else: - self.resize(520, 250) + self.resize(560, 300) def save_ui_to_config(self): self.cfg["target_exe_path"] = self.edit_exe_path.text().strip() @@ -375,6 +457,17 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.cfg["show_log_panel"] = bool(self.log_group.isVisible()) save_config(self.cfg) + def on_path_edited(self): + self.save_ui_to_config() + self.update_target_status_label() + self.update_version_labels() + + def update_version_labels(self): + exe_path = self.edit_exe_path.text().strip() + target_ver = get_file_version(exe_path) + self.lbl_watchdog_version.setText(f"Watchdog 버전: {APP_VERSION}") + self.lbl_target_version.setText(f"대상 버전: {target_ver}") + # --------------------- # 트레이 # --------------------- @@ -444,6 +537,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): if path: self.edit_exe_path.setText(path) self.save_ui_to_config() + self.update_version_labels() LOGGER.info(f"감시 대상 선택: {path}") self.update_target_status_label() @@ -453,9 +547,9 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.btn_toggle_log.setText("로그 숨기기" if visible else "로그 보기") if visible: - self.resize(max(self.width(), 560), 500) + self.resize(max(self.width(), 600), 540) else: - self.resize(max(520, self.width()), 250) + self.resize(max(560, self.width()), 300) self.save_ui_to_config() @@ -493,6 +587,8 @@ class WatchdogWindow(QtWidgets.QMainWindow): self.lbl_monitor.setText("감시 상태: 실행 중") self.lbl_monitor.setStyleSheet("font-weight: bold; color: #007a1f;") + self.update_version_labels() + LOGGER.info(f"감시 시작: {exe_path}") LOGGER.info(f"체크 주기: {self.spin_interval.value()}초 / 쿨다운: {self.spin_cooldown.value()}초") @@ -532,16 +628,19 @@ class WatchdogWindow(QtWidgets.QMainWindow): if not exe_path: self.update_target_status_label(False) + self.update_version_labels() LOGGER.warning("감시 대상 경로가 비어 있습니다.") return if not os.path.exists(exe_path): self.update_target_status_label(False) + self.update_version_labels() LOGGER.error(f"감시 대상 파일이 존재하지 않습니다: {exe_path}") return running = is_process_running_by_path(exe_path) self.update_target_status_label(running) + self.update_version_labels() if running: return @@ -566,7 +665,7 @@ class WatchdogWindow(QtWidgets.QMainWindow): QtWidgets.QSystemTrayIcon.Warning, 2500 ) - QtCore.QTimer.singleShot(1500, self.update_target_status_label) + QtCore.QTimer.singleShot(1500, self._refresh_after_restart) else: self.update_target_status_label(False) @@ -574,6 +673,10 @@ class WatchdogWindow(QtWidgets.QMainWindow): LOGGER.error("감시 루프 오류 발생") LOGGER.error(traceback.format_exc()) + def _refresh_after_restart(self): + self.update_target_status_label() + self.update_version_labels() + # --------------------- # 종료 / 최소화 # ---------------------