add version
windows-exe-build / build (push) Successful in 38s

This commit is contained in:
2026-03-18 11:08:20 +09:00
parent 3e29bc197b
commit b4065f6ce2
+113 -10
View File
@@ -5,6 +5,8 @@ import json
import time import time
import traceback import traceback
import subprocess import subprocess
import ctypes
from ctypes import wintypes
from pathlib import Path from pathlib import Path
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import logging import logging
@@ -14,6 +16,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets
APP_NAME = "Watchdog GUI" APP_NAME = "Watchdog GUI"
APP_VERSION = "1.1.0"
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
CONFIG_PATH = BASE_DIR / "watchdog_config.json" CONFIG_PATH = BASE_DIR / "watchdog_config.json"
LOG_DIR = BASE_DIR / "watchdog_logs" LOG_DIR = BASE_DIR / "watchdog_logs"
@@ -113,6 +117,68 @@ def save_config(cfg: dict):
LOGGER.exception("설정 파일 저장 실패") 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): class WatchdogWindow(QtWidgets.QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setWindowTitle(APP_NAME) self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
self.resize(520, 250) self.resize(560, 300)
self.setMinimumSize(500, 230) self.setMinimumSize(520, 270)
self.cfg = load_config() self.cfg = load_config()
self.monitoring = False self.monitoring = False
@@ -204,7 +270,7 @@ class WatchdogWindow(QtWidgets.QMainWindow):
self.timer.timeout.connect(self.check_target_once) self.timer.timeout.connect(self.check_target_once)
LOGGER.info("=" * 60) LOGGER.info("=" * 60)
LOGGER.info("Watchdog GUI 시작") LOGGER.info(f"{APP_NAME} 시작 - 버전 {APP_VERSION}")
LOGGER.info(f"로그 파일: {LOG_FILE}") LOGGER.info(f"로그 파일: {LOG_FILE}")
LOGGER.info("=" * 60) LOGGER.info("=" * 60)
@@ -212,6 +278,7 @@ class WatchdogWindow(QtWidgets.QMainWindow):
self.start_monitoring() self.start_monitoring()
self.update_target_status_label() self.update_target_status_label()
self.update_version_labels()
if self.cfg.get("start_minimized_to_tray", False): if self.cfg.get("start_minimized_to_tray", False):
QtCore.QTimer.singleShot(300, self.hide_to_tray) 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.setContentsMargins(10, 10, 10, 10)
main_layout.setSpacing(8) 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 = QtWidgets.QHBoxLayout()
path_layout.setSpacing(6) path_layout.setSpacing(6)
@@ -320,6 +401,7 @@ class WatchdogWindow(QtWidgets.QMainWindow):
log_layout.addWidget(self.text_log) log_layout.addWidget(self.text_log)
log_layout.addWidget(self.btn_clear_log) log_layout.addWidget(self.btn_clear_log)
main_layout.addLayout(version_layout)
main_layout.addLayout(path_layout) main_layout.addLayout(path_layout)
main_layout.addLayout(option_layout) main_layout.addLayout(option_layout)
main_layout.addLayout(top_btn_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_open_log_file.clicked.connect(self.open_log_file)
self.btn_clear_log.clicked.connect(self.text_log.clear) 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_interval.valueChanged.connect(self.save_ui_to_config)
self.spin_cooldown.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) 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 "로그 보기") self.btn_toggle_log.setText("로그 숨기기" if show_log else "로그 보기")
if show_log: if show_log:
self.resize(560, 500) self.resize(600, 540)
else: else:
self.resize(520, 250) self.resize(560, 300)
def save_ui_to_config(self): def save_ui_to_config(self):
self.cfg["target_exe_path"] = self.edit_exe_path.text().strip() 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()) self.cfg["show_log_panel"] = bool(self.log_group.isVisible())
save_config(self.cfg) 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: if path:
self.edit_exe_path.setText(path) self.edit_exe_path.setText(path)
self.save_ui_to_config() self.save_ui_to_config()
self.update_version_labels()
LOGGER.info(f"감시 대상 선택: {path}") LOGGER.info(f"감시 대상 선택: {path}")
self.update_target_status_label() self.update_target_status_label()
@@ -453,9 +547,9 @@ class WatchdogWindow(QtWidgets.QMainWindow):
self.btn_toggle_log.setText("로그 숨기기" if visible else "로그 보기") self.btn_toggle_log.setText("로그 숨기기" if visible else "로그 보기")
if visible: if visible:
self.resize(max(self.width(), 560), 500) self.resize(max(self.width(), 600), 540)
else: else:
self.resize(max(520, self.width()), 250) self.resize(max(560, self.width()), 300)
self.save_ui_to_config() self.save_ui_to_config()
@@ -493,6 +587,8 @@ class WatchdogWindow(QtWidgets.QMainWindow):
self.lbl_monitor.setText("감시 상태: 실행 중") self.lbl_monitor.setText("감시 상태: 실행 중")
self.lbl_monitor.setStyleSheet("font-weight: bold; color: #007a1f;") self.lbl_monitor.setStyleSheet("font-weight: bold; color: #007a1f;")
self.update_version_labels()
LOGGER.info(f"감시 시작: {exe_path}") LOGGER.info(f"감시 시작: {exe_path}")
LOGGER.info(f"체크 주기: {self.spin_interval.value()}초 / 쿨다운: {self.spin_cooldown.value()}") LOGGER.info(f"체크 주기: {self.spin_interval.value()}초 / 쿨다운: {self.spin_cooldown.value()}")
@@ -532,16 +628,19 @@ class WatchdogWindow(QtWidgets.QMainWindow):
if not exe_path: if not exe_path:
self.update_target_status_label(False) self.update_target_status_label(False)
self.update_version_labels()
LOGGER.warning("감시 대상 경로가 비어 있습니다.") LOGGER.warning("감시 대상 경로가 비어 있습니다.")
return return
if not os.path.exists(exe_path): if not os.path.exists(exe_path):
self.update_target_status_label(False) self.update_target_status_label(False)
self.update_version_labels()
LOGGER.error(f"감시 대상 파일이 존재하지 않습니다: {exe_path}") LOGGER.error(f"감시 대상 파일이 존재하지 않습니다: {exe_path}")
return return
running = is_process_running_by_path(exe_path) running = is_process_running_by_path(exe_path)
self.update_target_status_label(running) self.update_target_status_label(running)
self.update_version_labels()
if running: if running:
return return
@@ -566,7 +665,7 @@ class WatchdogWindow(QtWidgets.QMainWindow):
QtWidgets.QSystemTrayIcon.Warning, QtWidgets.QSystemTrayIcon.Warning,
2500 2500
) )
QtCore.QTimer.singleShot(1500, self.update_target_status_label) QtCore.QTimer.singleShot(1500, self._refresh_after_restart)
else: else:
self.update_target_status_label(False) self.update_target_status_label(False)
@@ -574,6 +673,10 @@ class WatchdogWindow(QtWidgets.QMainWindow):
LOGGER.error("감시 루프 오류 발생") LOGGER.error("감시 루프 오류 발생")
LOGGER.error(traceback.format_exc()) LOGGER.error(traceback.format_exc())
def _refresh_after_restart(self):
self.update_target_status_label()
self.update_version_labels()
# --------------------- # ---------------------
# 종료 / 최소화 # 종료 / 최소화
# --------------------- # ---------------------