add version
This commit is contained in:
+113
-10
@@ -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()
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# 종료 / 최소화
|
# 종료 / 최소화
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user