""" Clock-out Time Calculator 퇴근시간 계산 프로그램 - 메인 실행 파일 """ import sys import os # PyQt5 임포트 from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtGui import QFont from PyQt5.QtCore import QLockFile, QDir from PyQt5.QtNetwork import QLocalServer, QLocalSocket # 프로젝트 루트를 경로에 추가 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from ui.main_window import MainWindow from core.database import Database def _ensure_updater_extracted(): """main.exe에 내장된 updater.exe를 같은 폴더에 추출. main.exe만 단독 배포해도 자동 업데이트가 동작하도록, 시작 시 한 번 내장본을 같은 폴더로 복사. 이미 같은 사이즈로 있으면 건너뜀. 개발 환경(.py 실행)에서는 별도 빌드된 updater.exe를 사용해야 하므로 no-op. """ if not getattr(sys, 'frozen', False): return import shutil from pathlib import Path main_dir = Path(sys.executable).parent target = main_dir / 'updater.exe' bundled = Path(getattr(sys, '_MEIPASS', '')) / 'updater.exe' if not bundled.exists(): return # 빌드 시 포함 안 됨 needs_extract = (not target.exists() or target.stat().st_size != bundled.stat().st_size) if needs_extract: try: shutil.copy2(str(bundled), str(target)) except OSError: pass # 권한 부족 등은 silent — 자동 업데이트만 안 됨 def check_requirements(): """필수 요구사항 확인""" try: import win32evtlog return True except ImportError: return False def main(): """메인 함수""" # PyInstaller frozen exe에 내장된 updater.exe를 옆 폴더에 자동 추출 _ensure_updater_extracted() app = QApplication(sys.argv) # 애플리케이션 정보 app.setApplicationName("Clock-out Time Calculator") app.setOrganizationName("DevUtil") app.setApplicationVersion("1.0.0") # 중복 실행 방지 - 로컬 서버로 체크 server_name = "ClockOutCalculatorInstance" # 이미 실행 중인지 확인 socket = QLocalSocket() socket.connectToServer(server_name) if socket.waitForConnected(500): # 이미 실행 중 - 기존 인스턴스에 "show" 신호 전송 socket.write(b"show") socket.flush() socket.waitForBytesWritten(1000) socket.disconnectFromServer() return 0 # 새로운 인스턴스 - 서버 시작 server = QLocalServer() # 기존 서버가 남아있을 수 있으므로 제거 QLocalServer.removeServer(server_name) if not server.listen(server_name): QMessageBox.warning( None, "서버 오류", "프로그램 인스턴스 서버를 시작할 수 없습니다." ) return 1 # 폰트 설정 app.setFont(QFont("Segoe UI", 9)) # 필수 패키지 확인 if not check_requirements(): QMessageBox.critical( None, "요구사항 오류", "필수 패키지가 설치되지 않았습니다.\n\n" "다음 명령어를 실행하세요:\n" "pip install -r requirements.txt" ) return 1 # 데이터베이스 초기화 — db_path_override 설정 시 그 경로 사용 (클라우드 폴더 등) # 부트스트랩: 기본 DB로 한 번 열어 override 키 확인 from core.settings_keys import DB_PATH_OVERRIDE bootstrap = Database() override_path = bootstrap.get_setting(DB_PATH_OVERRIDE, '') or '' if override_path and os.path.exists(os.path.dirname(override_path) or '.'): db = Database(override_path) else: db = bootstrap # 1일 1회 자동 백업 (조용히 실패 — 백업 실패가 앱 실행을 막으면 안 됨) try: from utils.backup import backup_db_if_needed backup_db_if_needed(db) except Exception as e: from utils.debug_log import dlog dlog(f"backup failed: {e}") # 메인 윈도우 생성 및 표시 try: window = MainWindow() # 서버 연결 처리 - 다른 인스턴스에서 show 신호를 받으면 창을 보여줌 def on_new_connection(): client_socket = server.nextPendingConnection() if client_socket: client_socket.waitForReadyRead(1000) data = client_socket.readAll().data() if data == b"show": # 창 표시 window.show() window.raise_() window.activateWindow() client_socket.disconnectFromServer() server.newConnection.connect(on_new_connection) window.show() result = app.exec_() # 서버 종료 server.close() return result except Exception as e: QMessageBox.critical( None, "오류", f"프로그램 실행 중 오류가 발생했습니다:\n\n{str(e)}" ) server.close() return 1 if __name__ == "__main__": sys.exit(main())