// ManualPatchPage.cpp : 구현 파일입니다.
//

#include "stdafx.h"
#include "PatchMaker.h"
#include "ManualPatchPage.h"

#include "UtilityFunc.h"
#include "PMSetting.h"
#include "PMSettingConstant.h"

#include "atlenc.h"

#include <process.h>
#include <ZipArchive.h>
#include ".\manualpatchpage.h"

// CManualPatchPage 대화 상자입니다.

IMPLEMENT_DYNAMIC(CManualPatchPage, CPropertyPage)
CManualPatchPage::CManualPatchPage(CPMSetting& pmSetting, DWORD dwPatchVersion)
    : CPropertyPage(CManualPatchPage::IDD)
    , m_szMPFolder(_T(""))
    , m_szMPSFXFile(_T(""))
    , m_szMPFileName(_T(""))
    , m_dwMPMinver(0)
    , m_dwMPMaxver(dwPatchVersion)
    , m_szPatchType(_T(""))
    , m_szFileProgress(_T("0/0"))
    , m_pmSettingOption(pmSetting)
    , m_hManualPatchThread(0)
{
    m_ProgressData.m_bStopWorkerThread = FALSE;
    m_ProgressData.m_nTotalFiles = 0;
    m_ProgressData.m_nAddedFiles = 0;
    m_ProgressData.m_dwTotalFileSize = 0LL;
    m_ProgressData.m_dwCurrentFileSize = 0LL;
}

CManualPatchPage::~CManualPatchPage()
{

}

void CManualPatchPage::DoDataExchange(CDataExchange* pDX)
{
    CPropertyPage::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_ED_MP_MANUAL_PATCH_FILE, m_szMPFolder);
    DDX_Text(pDX, IDC_ED_MP_MANUAL_SFX_FILE, m_szMPSFXFile);
    DDX_Text(pDX, IDC_ED_MP_PATCH_FILE_NAME, m_szMPFileName);
    DDX_Text(pDX, IDC_ED_MP_MIN_VER, m_dwMPMinver);
    DDX_Text(pDX, IDC_ED_MP_CUR_VER, m_dwMPMaxver);
    DDX_Text(pDX, IDC_ED_MP_PATCH_TYPE, m_szPatchType);
    DDX_Control(pDX, IDC_ED_MP_CONSOLE, m_edProgressLog);
    DDX_Control(pDX, IDC_PRG_MP, m_TotalProgressBar);
    DDX_Control(pDX, IDC_ED_MP_ERR_CONSOLE, m_edErrLog);
    DDX_Text(pDX, IDC_ST_MP_PRORGRESS, m_szFileProgress);
}


BEGIN_MESSAGE_MAP(CManualPatchPage, CPropertyPage)
    ON_BN_CLICKED(ID_MP_CREATE, OnBnClickedMpCreate)
    ON_BN_CLICKED(ID_MP_CANCEL, OnBnClickedMpCancel)
    ON_BN_CLICKED(IDC_BTN_MP_SELECT_FILE, OnBnClickedBtnMpSelectFile)
    ON_WM_CREATE()
    ON_WM_TIMER()
    ON_WM_CLOSE()
    ON_BN_CLICKED(IDC_CLEAR_LOG, OnBnClickedClearLog)
END_MESSAGE_MAP()


// CManualPatchPage 메시지 처리기입니다.

struct MPWorkerData
{
    CManualPatchPage* m_lpPage;

    CString m_szMPFolder;
    CString m_szMPSFXFile;
    CString m_szMPFileName;
    CString m_szPatchType;
    
    DWORD   m_dwMPMaxver;
    DWORD   m_dwMPMinver;    
};


void CManualPatchPage::UpdateManualPatchStatus(MPProgressData& progressData)
{    
    //! MANUAL_PATCH_LOCK
    m_ProgressLock.Lock();

    // 데이터를 갱신한다
    progressData.m_bStopWorkerThread = m_ProgressData.m_bStopWorkerThread;

    m_ProgressData.m_nTotalFiles = progressData.m_nTotalFiles;        
    m_ProgressData.m_nAddedFiles = progressData.m_nAddedFiles;
    m_ProgressData.m_dwCurrentFileSize = progressData.m_dwCurrentFileSize;
    m_ProgressData.m_dwTotalFileSize = progressData.m_dwTotalFileSize;

    m_ProgressData.m_ProgressList.AddTail(&progressData.m_ProgressList);
    m_ProgressData.m_ErrorList.AddTail(&progressData.m_ErrorList);
    m_ProgressData.m_TotalProgressList.AddTail((&progressData.m_TotalProgressList));

    //! MANUAL_PATCH_UNLOCK
    m_ProgressLock.Unlock();

    progressData.m_ProgressList.RemoveAll();
    progressData.m_ErrorList.RemoveAll();
}

template<class FnProcess>
bool EnumerateAllLeafFiles(FnProcess fnProcess, LPCTSTR lpRootPath)
{
    CString         szTemp;
    CList<CString>  dirStack;
    CFileFind       fileFind;
    
    szTemp.SetString(lpRootPath);
    szTemp.Trim();

    int nLength = szTemp.GetLength();

    // 뒤에 \\*.* 혹은 *.*를 붙여 준다.
    szTemp.Append(
        (1 < nLength  && _T('\\') != szTemp.GetString()[nLength - 1]) 
        ? _T("\\*.*") : _T("*.*"));

    dirStack.AddTail(szTemp);

    BOOL bWorking = FALSE;
    while(!dirStack.IsEmpty())
    {   
        szTemp.SetString(dirStack.RemoveTail());
        if (bWorking = fileFind.FindFile(szTemp))
        {
            while(bWorking)
            {
                bWorking = fileFind.FindNextFile();            
                if (!fileFind.IsDots())
                {
                    szTemp.SetString(fileFind.GetFilePath());
                    if (fileFind.IsDirectory())
                    {
                        // 폴더인 경우                        
                        szTemp.Append(_T("\\*.*"));
                        dirStack.AddTail(szTemp);
                    }
                    // vssver.scc는 포함 안 함
                    else if(0 != szTemp.CompareNoCase(_T("vssver.scc")) && !fnProcess(fileFind))
                    {
                        // 작업을 취소하였음
                        return false;
                    }
                }
            }
        }
        else
        {
            return false;
        }
    }

    return true;
}

class CGetTotalFileNumAndSize
{
public:

    CGetTotalFileNumAndSize(ULONGLONG& dwTotalFileSize, int& nTotalFileNum)
        : m_dwTotalFileSize(dwTotalFileSize)
        , m_nTotalFileNum(nTotalFileNum)
    {

    }

    bool operator () (const CFileFind& fileFind)
    {
        m_dwTotalFileSize += fileFind.GetLength();
        ++m_nTotalFileNum;

        return true;
    }

private:

    ULONGLONG&  m_dwTotalFileSize;
    int&        m_nTotalFileNum;
};

class CCompressFiles
{
public:

    CCompressFiles(        
        CManualPatchPage& mpPage, 
        MPProgressData& progressData, 
        CZipArchive& zipArchive,
        CString& szErrorString)
        : m_MPPage(mpPage)
        , m_ProgressData(progressData)
        , m_ZipArchive(zipArchive)
        , m_szErrorString(szErrorString)
    {

    }

    bool operator () (const CFileFind& fileFind)
    {
        // 파일을 압축해서 추가한다.
        m_szFileName = fileFind.GetFilePath();
        
        // 파일 속성을 기본 속성으로 바꿈
        SetFileAttributes(m_szFileName, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE);
        if (m_ZipArchive.AddNewFile(m_szFileName, Z_BEST_COMPRESSION, false))
        {
            // 데이터를 갱신한다.
            ULONGLONG dwFileSize = fileFind.GetLength();
            m_ProgressData.m_dwCurrentFileSize += dwFileSize;
            ++m_ProgressData.m_nAddedFiles;

            PMUtil::SetCurrentTime(m_szErrorString);
            m_szErrorString.AppendFormat(_T("파일 추가 : %s"), m_szFileName);
            m_ProgressData.m_ProgressList.AddTail(m_szErrorString);
        }
        else
        {
            // 에러 로그를 남긴다.
            DWORD dwError = GetLastError();

            PMUtil::SetCurrentTime(m_szErrorString);
            m_szErrorString.AppendFormat(_T("작업 실패 : %s : "), m_szFileName);
            PMUtil::AppendErrorMessage(m_szErrorString, dwError);

            m_ProgressData.m_ErrorList.AddTail(m_szErrorString);
        }

        // 데이터를 업데이트한다.
        m_MPPage.UpdateManualPatchStatus(m_ProgressData);
        return (FALSE == m_ProgressData.m_bStopWorkerThread);
    }

private:

    CManualPatchPage&   m_MPPage;
    MPProgressData&     m_ProgressData;
    CZipArchive&        m_ZipArchive;
    CString&            m_szErrorString;
    CString             m_szFileName;
};

unsigned __stdcall CManualPatchPage::ManualPatchWorker(void* pArg)
{
    MPWorkerData* lpWorkerData = reinterpret_cast<MPWorkerData*>(pArg);
    CManualPatchPage* lpPage = lpWorkerData->m_lpPage;

    // 데이터 정의
    CZipArchive zipArchive;
    
    CString szSFXFileName;
    CString szZipFileName;

    CString szErrorString;

    const int MAX_ERROR_LEN = 256;
    TCHAR   szErrorMsg[MAX_ERROR_LEN];

    MPProgressData progressData;
    progressData.m_bStopWorkerThread = FALSE;
    progressData.m_dwTotalFileSize = 0LL;
    progressData.m_dwCurrentFileSize = 0LL;
    progressData.m_nTotalFiles = 0;
    progressData.m_nAddedFiles = 0;

    szSFXFileName.SetString(lpWorkerData->m_szMPFileName);
    szZipFileName.SetString(lpWorkerData->m_szMPFileName);
    int nPos = szZipFileName.ReverseFind(_T('.'));
    if (0 < nPos) { szZipFileName.Truncate(nPos); szZipFileName.Append(_T(".zip"), nPos); }

    // 전체 파일 개수 및 파일 용량을 계산한다.
    if (!EnumerateAllLeafFiles(CGetTotalFileNumAndSize(
        progressData.m_dwTotalFileSize, progressData.m_nTotalFiles), lpWorkerData->m_szMPFolder))
    {
        DWORD dwError = GetLastError();

        PMUtil::SetCurrentTime(szErrorString);        
        if (progressData.m_bStopWorkerThread)
        {   
            szErrorString.AppendFormat(_T("작업 실패 : 작업을 취소하였습니다"));
        }
        else
        {
            szErrorString.AppendFormat(_T("작업 실패 : 압축할 파일을 찾을 수 없습니다 : "));
            PMUtil::AppendErrorMessage(szErrorString, dwError);
        }
        
        progressData.m_ErrorList.AddTail(szErrorString);
        progressData.m_TotalProgressList.AddTail(szErrorString);
    }
    else
    {
        TRY
        {
            bool bSucceededJob = false;

            zipArchive.Open(szZipFileName, CZipArchive::zipCreate);
            zipArchive.SetRootPath(lpWorkerData->m_szMPFolder);

            // 파일을 추가/압축한다. 에러 로그는 내부에서 남긴다.
            if (!EnumerateAllLeafFiles(
                CCompressFiles(*lpPage, progressData, zipArchive, szErrorString), 
                lpWorkerData->m_szMPFolder))
            {
                DWORD dwError = GetLastError();
                PMUtil::SetCurrentTime(szErrorString);

                if (progressData.m_bStopWorkerThread)
                {   
                    szErrorString.AppendFormat(_T("작업 실패 : 작업을 취소하였습니다"));
                }
                else
                {
                    szErrorString.AppendFormat(_T("작업 실패 : 압축할 파일을 찾을 수 없습니다 : "));
                    PMUtil::AppendErrorMessage(szErrorString, dwError);
                }

                progressData.m_ErrorList.AddTail(szErrorString);
                progressData.m_TotalProgressList.AddTail(szErrorString);
            }
            else
            {
                /*
                    TODO : 필요한 데이터를 추리고, BASE64인코딩을 한 후 Comment에 쑤셔 박는다.

                    필요 데이터
                    1. 압축 전 용량 : 디스크 용량 체크                
                    2. 최소버전 : 깔릴 위치에 클라이언트가 있다면, 최소버전을 체크한다.
                    3. 패치버전 : 깔릴 위치에 클라이언트가 있다면, 현재 버전을 체크해서 더 높은가 확인한다.
                    4. 기본으로 읽어올 레지스트리 위치
                */
                
                CMemFile srcData;
                srcData.Write(&progressData.m_dwTotalFileSize, sizeof(progressData.m_dwTotalFileSize));

                // 패치 타입에 따라서 버전을 다시 세팅.
                PMConst::AdjustVersionInfoByPatchType(lpWorkerData->m_szPatchType,
                    lpWorkerData->m_dwMPMinver, lpWorkerData->m_dwMPMaxver);

                srcData.Write(&lpWorkerData->m_dwMPMinver, sizeof(lpWorkerData->m_dwMPMinver));
                srcData.Write(&lpWorkerData->m_dwMPMaxver, sizeof(lpWorkerData->m_dwMPMaxver));

                int nInstalledPathLen = 0;
                int nValueNameLen = 0;

                CString szInstalledPath, szValueName;
                if (PMConst::GetRYLInstalledRegKey(
                    lpWorkerData->m_szPatchType, szInstalledPath, szValueName))
                {
                    nInstalledPathLen = szInstalledPath.GetLength();
                    nValueNameLen = szValueName.GetLength();

                    srcData.Write(&nInstalledPathLen, sizeof(nInstalledPathLen));
                    srcData.Write(&nValueNameLen, sizeof(nValueNameLen));

                    srcData.Write(szInstalledPath.GetString(), nInstalledPathLen * sizeof(TCHAR));
                    srcData.Write(szValueName.GetString(), nValueNameLen * sizeof(TCHAR));
                }
                else
                {
                    srcData.Write(&nInstalledPathLen, sizeof(nInstalledPathLen));
                    srcData.Write(&nValueNameLen, sizeof(nValueNameLen));
                }

                int nSrcLen = static_cast<int>(srcData.GetLength());
                int nDstLen = Base64EncodeGetRequiredLength(nSrcLen);

                LPSTR lpEncodedData = (LPSTR)malloc(sizeof(CHAR) * (nDstLen + 1));
                if (0 != lpEncodedData)
                {   
                    BYTE* lpSrcData = srcData.Detach();

                    if (Base64Encode(lpSrcData, nSrcLen, lpEncodedData, &nDstLen))
                    {
                        lpEncodedData[nDstLen] = (CHAR)0;

                        bSucceededJob = 
                            zipArchive.SetGlobalComment(lpEncodedData);
                    }
                    
                    free(lpSrcData);
                    free(lpEncodedData);
                }
            }
                
            zipArchive.Close();

            if (!progressData.m_bStopWorkerThread)
            {
                PMUtil::SetCurrentTime(szErrorString);
                szErrorString.AppendFormat(_T("수동패치 작업 : 압축을 완료하였습니다. SFX파일을 생성하는 중입니다.."));

                progressData.m_ProgressList.AddTail(szErrorString);
                lpPage->UpdateManualPatchStatus(progressData);

                if (!bSucceededJob)
                {
                    PMUtil::SetCurrentTime(szErrorString);
                    szErrorString.AppendFormat(_T("수동패치 작업 실패 : 수동패치 데이터를 Comment로 남기는 데 실패했습니다"));
                    progressData.m_ErrorList.AddTail(szErrorString);
                }
                else
                {
                    // SFX헤더를 붙여서 파일을 하나 더 만든다.
                    CFile sfxFile, zipFile, resultFile;
                    PMUtil::SetCurrentTime(szErrorString);

                    if (!sfxFile.Open(lpWorkerData->m_szMPSFXFile, 
                        CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary))
                    {                    
                        szErrorString.AppendFormat(_T("수동패치 작업 실패 : SFX파일 %s를 읽기 위해 여는 데 실패했습니다"),
                            lpWorkerData->m_szMPSFXFile);                    
                    }
                    else if(!zipFile.Open(szZipFileName, 
                        CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary))
                    {                    
                        szErrorString.AppendFormat(
                            _T("수동패치 작업 실패 : ZIP파일 %s를 읽기 위해 여는 데 실패했습니다"), szZipFileName);
                    }
                    else if(!resultFile.Open(szSFXFileName, 
                        CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite | CFile::typeBinary))
                    {
                        szErrorString.AppendFormat(
                            _T("수동패치 작업 실패 : 수동패치 파일 %s를 쓰기 위해 여는 데 실패했습니다"), szSFXFileName);                
                    }
                    else 
                    {
                        const int MAX_FILE_BUFFER = 8192;
                        char szTempBuffer[MAX_FILE_BUFFER];

                        UINT nReadBytes = 0;
                        UINT nCount = 0;

                        progressData.m_dwTotalFileSize = 
                            sfxFile.GetLength() + zipFile.GetLength();

                        progressData.m_dwCurrentFileSize = 0;

                        lpPage->UpdateManualPatchStatus(progressData);

                        // 프로그리스 바를 확실히 갱신할 수 있도록 잠시 기다려 준다.
                        Sleep(300);

                        // 수동패치 파일에 SFX실행부 붙임
                        while(0 < (nReadBytes = sfxFile.Read(szTempBuffer, MAX_FILE_BUFFER))) 
                        { 
                            resultFile.Write(szTempBuffer, nReadBytes); 
                            progressData.m_dwCurrentFileSize += nReadBytes;
                            if (0 == (++nCount % 10)) { lpPage->UpdateManualPatchStatus(progressData); }
                        }
                        
                        // 수동패치 파일에 ZIP파일 붙임
                        while(0 < (nReadBytes = zipFile.Read(szTempBuffer, MAX_FILE_BUFFER))) 
                        { 
                            resultFile.Write(szTempBuffer, nReadBytes);
                            progressData.m_dwCurrentFileSize += nReadBytes;
                            if (0 == (++nCount % 10)) { lpPage->UpdateManualPatchStatus(progressData); }
                        }

                        szErrorString.Empty();
                    }

                    if (!szErrorString.IsEmpty())
                    {
                        progressData.m_ErrorList.AddTail(szErrorString);
                        lpPage->UpdateManualPatchStatus(progressData);
                    }
                }

                PMUtil::SetCurrentTime(szErrorString);
                szErrorString.AppendFormat(_T("수동패치 작업 완료 : %d 개 파일 중 %d개를 완료하였습니다. 작업을 %s하였습니다"),
                    progressData.m_nTotalFiles, progressData.m_nAddedFiles,
                    bSucceededJob && progressData.m_nTotalFiles == progressData.m_nAddedFiles ? _T("성공") : _T("실패"));

                progressData.m_ProgressList.AddTail(szErrorString);
                progressData.m_TotalProgressList.AddTail(szErrorString);
            }
        }
        CATCH_ALL(e)
        {
            e->GetErrorMessage(szErrorMsg, MAX_ERROR_LEN - 1);
            szErrorMsg[MAX_ERROR_LEN - 1] = 0;

            PMUtil::SetCurrentTime(szErrorString);
            if (e->IsKindOf(RUNTIME_CLASS(CZipException)))
            {
                szErrorString.AppendFormat(
                    _T("작업 실패 : CZipArchive 라이브러리에서 에러가 발생했습니다. %s"), szErrorMsg);
            }
            else if (e->IsKindOf(RUNTIME_CLASS(CFileException)))
            {
                szErrorString.AppendFormat(
                    _T("작업 실패 : 파일 관련 작업에서 에러가 발생했습니다. %s"), szErrorMsg);
            }
            else 
            {
                szErrorString.AppendFormat(
                    _T("작업 실패 : 알 수 없는 작업에서 에러가 발생했습니다. %s"), szErrorMsg);
            }

            progressData.m_ErrorList.AddTail(szErrorString);
            progressData.m_TotalProgressList.AddTail(szErrorString);

            e->Delete();
        }
        END_CATCH_ALL
    }

    // 데이터를 업데이트한다.
    lpPage->UpdateManualPatchStatus(progressData);

    delete lpWorkerData;
    return 0;
}

void CManualPatchPage::OnBnClickedMpCreate()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    UpdateData(TRUE);

    CFile file;

    if (0 == m_szMPFileName.GetLength() ||
        !file.Open(m_szMPFileName, CFile::modeCreate))
    {
        MessageBox(_T("수동패치 파일 이름이 없거나, 올바른 파일 이름이 아닙니다. 다시 입력해주세요"), 
            _T("올바르지 않은 파일 이름"), MB_OK | MB_ICONERROR);
    }
    else
    {
        file.Close();
        DeleteFile(m_szMPFileName);
        
        if (!theApp.IsManualPatchOperate())
        {
            MPWorkerData* lpWorkerData = new MPWorkerData;
            if (lpWorkerData)
            {
                lpWorkerData->m_lpPage = this;
                lpWorkerData->m_szMPFolder.SetString(m_szMPFolder);
                lpWorkerData->m_szMPSFXFile.SetString(m_szMPSFXFile);
                lpWorkerData->m_szMPFileName.SetString(m_szMPFileName);
                lpWorkerData->m_szPatchType.SetString(m_szPatchType);
                lpWorkerData->m_dwMPMaxver = m_dwMPMaxver;
                lpWorkerData->m_dwMPMinver = m_dwMPMinver;

                InitMPWorkerData();
                
                unsigned int nThreadID = 0;
                m_hManualPatchThread = reinterpret_cast<HANDLE>(
                    _beginthreadex(0, 0, ManualPatchWorker, lpWorkerData, 0, &nThreadID));

                if (0 == m_hManualPatchThread)
                {            
                    MessageBox(_T("수동패치 스레드를 생성할 수 없습니다"), 
                        _T("생성 에러"), MB_OK | MB_ICONERROR);
                }
                else
                {
                    static UINT nManualPatchDlgItems[] = 
                    {     
                        ID_MP_CREATE,
                        IDC_ED_MP_PATCH_FILE_NAME,
                        IDC_BTN_MP_SELECT_FILE
                    };

                    PMUtil::EnableDlgItems(*this, nManualPatchDlgItems, 
                        sizeof(nManualPatchDlgItems)/sizeof(UINT), false);

                    CWnd* lpWnd = GetDlgItem(ID_MP_CANCEL);
                    if (lpWnd) { lpWnd->EnableWindow(TRUE); }           

                    // 프로그리스 바 출력을 위한 타이머를 돌린다.
                    SetTimer(PMConst::MP_PROGRESS_TIMER_ID, 
                        PMConst::PROGRESS_TIMER_UPDATE_TIME, 0);

                    // 수동패치가 돌고 있다고 설정한다.
                    theApp.SetManualPatchOperate(true);
                }
            }
        }
    }
}

void CManualPatchPage::OnBnClickedMpCancel()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    if (theApp.IsManualPatchOperate())
    {
        // 이미 스레드가 작동하고 있다면, 스레드를 취소하도록 변수를 세팅한다.
        StopWorker();
    }
}

void CManualPatchPage::OnBnClickedBtnMpSelectFile()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    PMUtil::BrowseForNewFile(GetSafeHwnd(), 
        _T("수동패치 파일 이름을 선택해주세요"), 
        _T("Manual Patch File\0*.exe"), _T("exe"), m_szMPFileName);

    UpdateData(FALSE);
}

BOOL CManualPatchPage::OnSetActive()
{
    // TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.

    // 데이터를 셋업에서 읽어옵니다.

    if (!theApp.IsManualPatchOperate())
    {
        // 스레드가 동작중이 아닌 경우에만 데이터를 갱신합니다.
        m_pmSettingOption.GetSettingData(PMConst::PatchType, m_szPatchType);
        m_pmSettingOption.GetSettingData(PMConst::MP_Folder, m_szMPFolder);
        m_pmSettingOption.GetSettingData(PMConst::MP_SFXFile, m_szMPSFXFile);
        
        CString szValue;
        m_pmSettingOption.GetSettingData(PMConst::MP_MinVer, szValue);
        m_dwMPMinver = atol(szValue);

        UpdateData(FALSE);
    }

    return CPropertyPage::OnSetActive();
}

BOOL CManualPatchPage::OnInitDialog()
{
    CPropertyPage::OnInitDialog();

    // TODO:  여기에 추가 초기화 작업을 추가합니다.

    // Cancel버튼은 처음에는 Disable한다.
    CWnd* lpWnd = GetDlgItem(ID_MP_CANCEL);
    if (lpWnd) { lpWnd->EnableWindow(FALSE); }

    m_edProgressLog.SetLimitText(UINT_MAX);
    m_edErrLog.SetLimitText(UINT_MAX);

    return TRUE;  // return TRUE unless you set the focus to a control
    // 예외: OCX 속성 페이지는 FALSE를 반환해야 합니다.
}



afx_msg void CManualPatchPage::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == PMConst::MP_PROGRESS_TIMER_ID)
    {
        int nLowerRange = 0;
        int nUpperRange = 0;        
        int nCurrentPos = m_TotalProgressBar.GetPos();
        m_TotalProgressBar.GetRange(nLowerRange, nUpperRange);

        //! MANUAL_PATCH_LOCK
        m_ProgressLock.Lock();

        if (m_ProgressData.m_dwTotalFileSize != nUpperRange)
        {
            // 전체 Range가 변경되면 갱신한다 - 2.1기가 이상을 수동패치 할 일은 없다;;
            m_TotalProgressBar.SetRange32(0, 
                static_cast<int>(m_ProgressData.m_dwTotalFileSize));
        }

        if (m_ProgressData.m_dwCurrentFileSize != nCurrentPos)
        {
            // 파일 위치가 변경되면 갱신한다.
            m_TotalProgressBar.SetPos(
                static_cast<int>(m_ProgressData.m_dwCurrentFileSize));
        }
       
        m_szFileProgress.Format(_T("%d/%d"), 
            m_ProgressData.m_nAddedFiles, m_ProgressData.m_nTotalFiles);

        // 로그를 출력한다.        
        POSITION pos = m_ProgressData.m_ProgressList.GetHeadPosition();
        while(0 != pos) { m_edProgressLog.AddLine(m_ProgressData.m_ProgressList.GetNext(pos)); }
        
        pos = m_ProgressData.m_TotalProgressList.GetHeadPosition();
        while(0 != pos) { theApp.AddTotalProgressLog(m_ProgressData.m_TotalProgressList.GetNext(pos)); }

        pos = m_ProgressData.m_ErrorList.GetHeadPosition();
        while(0 != pos) { m_edErrLog.AddLine(m_ProgressData.m_ErrorList.GetNext(pos)); }

        m_ProgressData.m_ProgressList.RemoveAll();
        m_ProgressData.m_TotalProgressList.RemoveAll();
        m_ProgressData.m_ErrorList.RemoveAll();

        //! MANUAL_PATCH_UNLOCK
        m_ProgressLock.Unlock();

        UpdateData(FALSE);

        if (WAIT_OBJECT_0 == WaitForSingleObject(m_hManualPatchThread, 0))
        {
            // 스레드가 종료되었다. 타이머를 제거하고, 다시 수동패치 옵션을 Enable한다.
            StopWorker();
        }
    }
}

void CManualPatchPage::InitMPWorkerData()
{
    m_hManualPatchThread = 0;

    m_ProgressData.m_bStopWorkerThread = FALSE;
    m_ProgressData.m_nTotalFiles = 0;
    m_ProgressData.m_nAddedFiles = 0;
    m_ProgressData.m_ProgressList.RemoveAll();
    m_ProgressData.m_ErrorList.RemoveAll();
}

void CManualPatchPage::StopWorker()
{
    if (0 != m_hManualPatchThread)
    {        
        m_ProgressLock.Lock();      //! MANUAL_PATCH_LOCK
        m_ProgressData.m_bStopWorkerThread = TRUE;
        m_ProgressLock.Unlock();    //! MANUAL_PATCH_UNLOCK

        WaitForSingleObject(m_hManualPatchThread, INFINITE);
        CloseHandle(m_hManualPatchThread);

        KillTimer(PMConst::MP_PROGRESS_TIMER_ID);
        InitMPWorkerData();

        static UINT nManualPatchDlgItems[] = 
        {     
            ID_MP_CREATE,
            IDC_ED_MP_PATCH_FILE_NAME,
            IDC_BTN_MP_SELECT_FILE
        };

        PMUtil::EnableDlgItems(*this, nManualPatchDlgItems, 
            sizeof(nManualPatchDlgItems)/sizeof(UINT), true);

        CWnd* lpWnd = GetDlgItem(ID_MP_CANCEL);
        if (lpWnd) { lpWnd->EnableWindow(FALSE); }

        theApp.SetManualPatchOperate(false);
    }
}

void CManualPatchPage::OnClose()
{
    // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
    StopWorker();
    CPropertyPage::OnClose();
}

void CManualPatchPage::OnBnClickedClearLog()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    m_edProgressLog.SetWindowText(_T(""));
    m_edErrLog.SetWindowText(_T(""));
}
