#!/usr/bin/env python # -*- coding: utf-8 -*- """ updateManager/__version__.py 파일에서 정보를 읽어 Inno Setup 스크립트 파일을 생성합니다. """ import os import sys import datetime import importlib.util from pathlib import Path def load_version_module(version_file_path): """버전 정보 모듈을 동적으로 로드합니다.""" spec = importlib.util.spec_from_file_location("version_module", version_file_path) version_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(version_module) return version_module def generate_iss_file(): """버전 정보를 읽어 .iss 파일을 생성합니다.""" # 현재 디렉토리 확인 current_dir = os.path.dirname(os.path.abspath(__file__)) # 버전 파일 경로 version_file = os.path.join(current_dir, 'updateManager', '__version__.py') if not os.path.exists(version_file): print(f"오류: 버전 파일을 찾을 수 없습니다: {version_file}") sys.exit(1) try: # 버전 모듈 로드 version_module = load_version_module(version_file) # 현재 날짜와 시간 now = datetime.datetime.now() date_str = now.strftime("%Y%m%d_%H%M%S") # 생성할 .iss 파일 이름 iss_filename = f"AutoPercenty_{date_str}.iss" # 템플릿 작성 iss_template = fr"""; AutoPercenty3 Inno Setup Script ; 이 스크립트는 cx_Freeze로 빌드된 결과물이 있는 "build\exe.win-amd64-3.11" 폴더를 기반으로 인스톨러를 제작합니다. ; {date_str}에 생성됨 #define AppId "{version_module.__program_id__}" #define MyAppName "{version_module.__title__}" #define MyAppVersion "{version_module.__version__}" #define MyAppPublisher "{version_module.__company_name__}" #define MyAppProgramName "{version_module.__program_name__}" #define MyAppDescription "{version_module.__description__}" #define MyAppCopyright "{version_module.__copyright__}" #define MyAppExeName "{version_module.__exe_name__}" #define MySetupName "{version_module.__setup_name__}" #define MySetupIcon "{version_module.__icon_file__}" #define MySetupOutputDir "{version_module.__setup_output_dir__}" #define OldAppName "AutoPercenty3" [Setup] ; 기본 설정 AppId={{#AppId}} AppName={{#MyAppProgramName}} AppVersion={{#MyAppVersion}} AppPublisher={{#MyAppPublisher}} DefaultDirName={{autopf}}\{{#MyAppName}} DefaultGroupName={{#MyAppPublisher}} OutputDir={{#MySetupOutputDir}} OutputBaseFilename={{#MySetupName}} SetupIconFile={{#MySetupIcon}} Compression=lzma SolidCompression=yes ; 업데이트 관련 설정 - 권한 최적화 PrivilegesRequired=admin PrivilegesRequiredOverridesAllowed=dialog UpdateUninstallLogAppName=yes AppMutex={{#MyAppName}} CloseApplications=yes RestartApplications=no ; 보안 및 호환성 설정 ArchitecturesAllowed=x64 ArchitecturesInstallIn64BitMode=x64 AllowNoIcons=yes ; 버전 정보 VersionInfoVersion={{#MyAppVersion}} VersionInfoCompany={{#MyAppPublisher}} VersionInfoDescription={{#MyAppDescription}} VersionInfoCopyright={{#MyAppCopyright}} VersionInfoProductName={{#MyAppProgramName}} VersionInfoProductVersion={{#MyAppVersion}} [Languages] Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl" [Tasks] Name: "desktopicon"; Description: "{{cm:CreateDesktopIcon}}"; GroupDescription: "{{cm:AdditionalIcons}}" [Dirs] ; 설치 시 {{app}}\logs 폴더를 생성하고, ; Users 그룹에 'modify' 권한(=쓰기 가능)을 부여 Name: "{{app}}\logs"; Permissions: users-modify ; 설치 시 {{app}}\user_data 폴더를 생성하고, ; Users 그룹에 'modify' 권한(=쓰기 가능)을 부여 Name: "{{app}}\user_data"; Permissions: users-modify ; Playwright 브라우저 폴더를 Program Files 내부에 생성 Name: "{{app}}\lib\src\browsers\chromium-1140"; Permissions: users-modify ; Playwright 브라우저 사용자폴더를 Program Files 내부에 생성 Name: "{{app}}\lib\src\browsers\user_data"; Permissions: users-modify [Files] ; 프로그램 파일만 설치 (항상 덮어쓰기) Source: "build\exe.win-amd64-3.11\*"; DestDir: "{{app}}"; Flags: ignoreversion recursesubdirs createallsubdirs ; VC++ 재배포 패키지 파일을 임시 폴더({{tmp}})에 복사 Source: "VC_redist.x64.exe"; DestDir: "{{tmp}}"; Flags: deleteafterinstall [Registry] ; Playwright 브라우저 경로를 Program Files 내부로 설정 Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "PLAYWRIGHT_BROWSERS_PATH"; ValueData: "{{app}}\lib\src\browsers"; Flags: preservestringtype [Icons] ; 시작 메뉴 바로가기 Name: "{{group}}\{{#MyAppProgramName}}"; Filename: "{{app}}\{{#MyAppExeName}}.exe" ; 바탕화면 바로가기 Name: "{{autodesktop}}\{{#MyAppProgramName}}"; Filename: "{{app}}\{{#MyAppExeName}}.exe"; Tasks: desktopicon [Run] ; VC++ 재배포 패키지 설치 (필요할 경우) Filename: "{{tmp}}\VC_redist.x64.exe"; Parameters: "/install /passive /norestart"; StatusMsg: "VC++ 재배포 패키지 설치 중..."; Check: NeedsVCredist ; 설치 후 프로그램 실행 (원할 경우) Filename: "{{app}}\{{#MyAppExeName}}.exe"; Description: "{{cm:LaunchProgram,{{#MyAppProgramName}}}}"; Flags: nowait postinstall skipifsilent [Code] function CompareVersion(V1, V2: string): Integer; var P1, P2, N1, N2: Integer; begin P1 := 1; P2 := 1; Result := 0; while (Result = 0) and ((P1 <= Length(V1)) or (P2 <= Length(V2))) do begin while (P1 <= Length(V1)) and (V1[P1] = '.') do Inc(P1); while (P2 <= Length(V2)) and (V2[P2] = '.') do Inc(P2); if (P1 <= Length(V1)) and (P2 <= Length(V2)) then begin N1 := 0; while (P1 <= Length(V1)) and (V1[P1] >= '0') and (V1[P1] <= '9') do begin N1 := N1 * 10 + Ord(V1[P1]) - Ord('0'); Inc(P1); end; N2 := 0; while (P2 <= Length(V2)) and (V2[P2] >= '0') and (V2[P2] <= '9') do begin N2 := N2 * 10 + Ord(V2[P2]) - Ord('0'); Inc(P2); end; if N1 < N2 then Result := -1 else if N1 > N2 then Result := 1; end else begin if P1 <= Length(V1) then Result := 1 else if P2 <= Length(V2) then Result := -1; end; while (P1 <= Length(V1)) and (V1[P1] <> '.') do Inc(P1); while (P2 <= Length(V2)) and (V2[P2] <> '.') do Inc(P2); end; end; // 파일 또는 폴더 복사 함수 procedure CopyDir(const SourcePath, DestPath: string); var FindRec: TFindRec; SourceFilePath: string; DestFilePath: string; begin ForceDirectories(DestPath); if FindFirst(SourcePath + '\*', FindRec) then begin try repeat if (FindRec.Name <> '.') and (FindRec.Name <> '..') then begin SourceFilePath := SourcePath + '\' + FindRec.Name; DestFilePath := DestPath + '\' + FindRec.Name; if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin if FileCopy(SourceFilePath, DestFilePath, False) then Log('파일 복사 성공: ' + SourceFilePath + ' -> ' + DestFilePath) else Log('파일 복사 실패: ' + SourceFilePath); end else CopyDir(SourceFilePath, DestFilePath); end; until not FindNext(FindRec); finally FindClose(FindRec); end; end; end; // 프로그램 실행 여부 확인 function IsAppRunning(const FileName: string): Boolean; var Handle: THandle; begin Handle := FindWindowByWindowName('{{#MyAppProgramName}}'); // 프로그램의 윈도우 타이틀로 찾기 Result := (Handle <> 0); end; // 프로그램 종료 procedure CloseApplication(const FileName: string); var Handle: THandle; begin Handle := FindWindowByWindowName('{{#MyAppProgramName}}'); if Handle <> 0 then begin PostMessage(Handle, 18, 0, 0); // WM_QUIT Sleep(1000); // 종료 대기 end; end; // VC++ 재배포 패키지 필요 여부 확인 function NeedsVCredist: Boolean; begin if RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64') then Result := False // 이미 설치됨 else Result := True; // 미설치 -> 설치 필요 end; // 설치 완료 후 실행 여부 확인 function InitializeFinish(): Boolean; var ResultCode: Integer; begin Result := True; if MsgBox('설치가 완료되었습니다. 프로그램을 실행하시겠습니까?' + #13#10 + '(실행 시 서버와 동기화하여 설정이 업데이트됩니다)', mbConfirmation, MB_YESNO) = IDYES then begin Exec(ExpandConstant('{{app}}\{{#MyAppExeName}}.exe'), '', '', SW_SHOW, ewNoWait, ResultCode); end; end; function InitializeSetup(): Boolean; var OldVersion: String; NewVersion: String; OldAppPath: String; UserDataSourcePath, UserDataBackupPath: String; OldUninstallString: String; ResultCode: Integer; begin Result := True; NewVersion := '{{#MyAppVersion}}'; UserDataBackupPath := ExpandConstant('{{tmp}}\user_data_backup'); // 이전 버전(AutoPercenty3) 확인 if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#OldAppName}}_is1', 'DisplayVersion', OldVersion) then begin // 이전 설치 경로 가져오기 if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#OldAppName}}_is1', 'InstallLocation', OldAppPath) then begin Log('이전 버전({{#OldAppName}})이 발견되었습니다. 경로: ' + OldAppPath); // user_data 폴더 경로 설정 - 우선순위: Program Files → AppData\Local UserDataSourcePath := OldAppPath + '\user_data'; // Program Files에 user_data가 없으면 AppData\Local에서 찾기 if not DirExists(UserDataSourcePath) then begin UserDataSourcePath := ExpandConstant('{{localappdata}}\' + '{{#OldAppName}}' + '\lib\user_data'); Log('Program Files에 user_data 없음. AppData\Local 경로 확인: ' + UserDataSourcePath); end else begin Log('Program Files에서 user_data 발견: ' + UserDataSourcePath); end; // 프로그램이 실행 중인지 확인하고 종료 요청 if IsAppRunning('{{#OldAppName}}.exe') then begin if MsgBox('프로그램을 업데이트하기 위해 실행 중인 프로그램을 종료해야 합니다.' + #13#10 + '계속하시겠습니까?', mbConfirmation, MB_YESNO) = IDNO then begin Result := False; exit; end; CloseApplication('{{#OldAppName}}.exe'); // 프로세스가 완전히 종료될 때까지 대기 Sleep(2000); end; // user_data 폴더 백업 if DirExists(UserDataSourcePath) then begin Log('user_data 폴더 백업 중: ' + UserDataSourcePath + ' -> ' + UserDataBackupPath); ForceDirectories(UserDataBackupPath); CopyDir(UserDataSourcePath, UserDataBackupPath); end; // 이전 버전 제거 (제거 프로그램 실행) if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#OldAppName}}_is1', 'UninstallString', OldUninstallString) then begin // 사용자에게 확인 if MsgBox('이전 버전을 제거하고 새 버전을 설치하시겠습니까?' + #13#10 + '사용자 데이터는 유지됩니다.', mbConfirmation, MB_YESNO) = IDYES then begin // 자동 제거를 위해 /SILENT 매개변수 추가 OldUninstallString := OldUninstallString + ' /SILENT'; Log('이전 버전 제거 실행: ' + OldUninstallString); // 제거 프로그램 실행 및 완료 대기 if Exec(RemoveQuotes(OldUninstallString), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin Log('이전 버전 제거 완료: 결과 코드 ' + IntToStr(ResultCode)); end else begin Log('이전 버전 제거 실패'); // 제거에 실패해도 계속 진행 end; end; end; end; end // 현재 프로그램(Edit_PartTimer3) 버전 확인 else if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppName}}_is1', 'DisplayVersion', OldVersion) then begin // 같은 버전이거나 더 높은 버전이 설치되어 있는 경우 if CompareVersion(OldVersion, NewVersion) >= 0 then begin MsgBox('현재 설치된 버전(' + OldVersion + ')이 이 설치 프로그램의 버전(' + NewVersion + ')과 같거나 더 높습니다.' + #13#10 + '설치를 계속할 수 없습니다.', mbInformation, MB_OK); Result := False; exit; end; // 이전 버전이 설치되어 있는 경우 업데이트 모드로 진행 if CompareVersion(OldVersion, NewVersion) < 0 then begin Log('업데이트 설치 진행: ' + OldVersion + ' -> ' + NewVersion); // 프로그램이 실행 중인지 확인하고 종료 요청 if IsAppRunning('{{#MyAppExeName}}.exe') then begin if MsgBox('프로그램을 업데이트하기 위해 실행 중인 프로그램을 종료해야 합니다.' + #13#10 + '계속하시겠습니까?', mbConfirmation, MB_YESNO) = IDNO then begin Result := False; exit; end; CloseApplication('{{#MyAppExeName}}.exe'); end; // Edit_PartTimer3의 user_data 폴더 백업 - 우선순위: Program Files → AppData\Local if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{#MyAppName}}_is1', 'InstallLocation', OldAppPath) then begin UserDataSourcePath := OldAppPath + '\user_data'; // Program Files에 user_data가 없으면 AppData\Local에서 찾기 if not DirExists(UserDataSourcePath) then begin UserDataSourcePath := ExpandConstant('{{localappdata}}\' + '{{#MyAppName}}' + '\lib\user_data'); Log('Program Files에 user_data 없음. AppData\Local 경로 확인: ' + UserDataSourcePath); end else begin Log('Program Files에서 user_data 발견: ' + UserDataSourcePath); end; if DirExists(UserDataSourcePath) then begin Log('user_data 폴더 백업 중: ' + UserDataSourcePath + ' -> ' + UserDataBackupPath); ForceDirectories(UserDataBackupPath); CopyDir(UserDataSourcePath, UserDataBackupPath); end; end; end; end; end; procedure CurStepChanged(CurStep: TSetupStep); var UserDataBackupPath, UserDataDestPath: String; begin // 설치 완료 후 if CurStep = ssPostInstall then begin UserDataBackupPath := ExpandConstant('{{tmp}}\user_data_backup'); UserDataDestPath := ExpandConstant('{{app}}\user_data'); // 백업한 user_data 폴더가 있으면 복원 if DirExists(UserDataBackupPath) then begin Log('백업된 user_data 폴더 복원 중: ' + UserDataBackupPath + ' -> ' + UserDataDestPath); ForceDirectories(UserDataDestPath); CopyDir(UserDataBackupPath, UserDataDestPath); Log('user_data 폴더 복원 완료'); end; end; end; """ # .iss 파일 저장 with open(iss_filename, 'w', encoding='utf-8') as f: f.write(iss_template) print(f".iss 파일이 성공적으로 생성되었습니다: {iss_filename}") return iss_filename except Exception as e: print(f"오류 발생: {e}") sys.exit(1) if __name__ == "__main__": generate_iss_file()