diff --git a/launchers/win32/Launcher.rc b/launchers/win32/Launcher.rc index f0288fa795..6e59b0731c 100644 --- a/launchers/win32/Launcher.rc +++ b/launchers/win32/Launcher.rc @@ -92,7 +92,8 @@ STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION EXSTYLE WS_EX_APPWINDOW FONT 10, "MS Shell Dlg", 400, 0, 0x0 BEGIN - CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,27,174,123 + CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,3,174,123, NOT WS_VISIBLE + CONTROL "", IDC_PROGRESS, "Static", SS_BLACKRECT, 35, 165, 239, 5, NOT WS_VISIBLE EDITTEXT IDC_ORGNAME,44,68,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER EDITTEXT IDC_USERNAME,44,95,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER EDITTEXT IDC_PASSWORD,44,122,219,12,ES_PASSWORD | ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER @@ -101,8 +102,8 @@ BEGIN LTEXT "Password",IDC_PASSWORD_BANNER,48,122,219,12,NOT WS_VISIBLE CTEXT "",IDC_MESSAGE_LABEL,5,39,299,23,NOT WS_VISIBLE CTEXT "",IDC_ACTION_LABEL,10,15,286,25,NOT WS_VISIBLE - CTEXT "",IDC_MESSAGE2_LABEL,35,172,239,15,NOT WS_VISIBLE - CTEXT "",IDC_ACTION2_LABEL,15,147,278,25,NOT WS_VISIBLE + CTEXT "",IDC_MESSAGE2_LABEL,35,148,239,15,NOT WS_VISIBLE + CTEXT "",IDC_ACTION2_LABEL,15,123,278,25,NOT WS_VISIBLE RTEXT "",IDC_TERMS,15,172,180,15,NOT WS_VISIBLE CONTROL "",IDC_TERMS_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,197,172,80,15 CTEXT "",IDC_TROUBLE,65,203,174,15,NOT WS_VISIBLE diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index 5c69add723..e8f948c42b 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -109,8 +109,10 @@ BOOL CLauncherDlg::OnInitDialog() { m_trouble = (CStatic *)GetDlgItem(IDC_TROUBLE); m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL); + m_progress = (CStatic *)GetDlgItem(IDC_PROGRESS); m_voxel->EnableD2DSupport(); + m_progress->EnableD2DSupport(); m_pRenderTarget = GetRenderTarget(); @@ -292,8 +294,9 @@ afx_msg void CLauncherDlg::OnNextClicked() { void CLauncherDlg::drawBackground(CHwndRenderTarget* pRenderTarget) { CD2DBitmap m_pBitmamBackground(pRenderTarget, IDB_PNG1, _T("PNG")); - auto size = pRenderTarget->GetSize(); + auto size = GetRenderTarget()->GetSize(); CD2DRectF backRec(0.0f, 0.0f, size.width, size.height); + GetRenderTarget()->DrawBitmap(&m_pBitmamBackground, backRec); pRenderTarget->DrawBitmap(&m_pBitmamBackground, backRec); } @@ -303,7 +306,7 @@ void CLauncherDlg::drawLogo(CHwndRenderTarget* pRenderTarget) { int logoWidth = 231; int logoHeight = 173; float logoPosX = 0.5f * (size.width - logoWidth); - float logoPosY = 0.95f * (size.height - logoHeight); + float logoPosY = 0.5f * (size.height - logoHeight); CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec); } @@ -338,6 +341,26 @@ void CLauncherDlg::drawVoxel(CHwndRenderTarget* pRenderTarget) { pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); } +void CLauncherDlg::drawProgress(CHwndRenderTarget* pRenderTarget, float progress, const D2D1::ColorF& color) { + auto size = pRenderTarget->GetPixelSize(); + if (progress == 0.0f) { + return; + } + CRect winRec; + CD2DRectF bkCircleRect1 = CD2DRectF(0,0,(float)size.height, (float)size.height); + progress = min(1.0f, progress); + CD2DRectF bkCircleRect2 = CD2DRectF((float)size.width * progress - (float)size.height, 0, + (float)size.width * progress, (float)size.height); + CD2DRectF bkRect = CD2DRectF(0.5f*(float)size.height, 0, + (float)size.width*progress - 0.5f*(float)size.height, (float)size.height); + CD2DEllipse bkCircle1 = CD2DEllipse(bkCircleRect1); + CD2DEllipse bkCircle2 = CD2DEllipse(bkCircleRect2); + CD2DSolidColorBrush brush(pRenderTarget, color); + pRenderTarget->FillEllipse(bkCircle1, &brush); + pRenderTarget->FillEllipse(bkCircle2, &brush); + pRenderTarget->FillRectangle(bkRect, &brush); +} + void CLauncherDlg::showWindows(std::vector windows, bool show) { for (auto window : windows) { window->ShowWindow(show ? SW_SHOW : SW_HIDE); @@ -346,6 +369,7 @@ void CLauncherDlg::showWindows(std::vector windows, bool show) { void CLauncherDlg::prepareLogin(DrawStep step) { m_voxel->ShowWindow(SW_HIDE); + m_progress->ShowWindow(SW_HIDE); m_orgname_banner->SetWindowTextW(_T("Organization Name")); m_username_banner->SetWindowTextW(_T("Username")); m_password_banner->SetWindowTextW(_T("Password")); @@ -370,7 +394,6 @@ void CLauncherDlg::prepareLogin(DrawStep step) { m_trouble->SetWindowTextW(_T("Having Trouble?")); m_trouble->ShowWindow(SW_SHOW); m_trouble_link.ShowWindow(SW_SHOW); - } void CLauncherDlg::prepareChoose() { @@ -410,6 +433,7 @@ void CLauncherDlg::prepareProcess(DrawStep step) { m_action_label->ShowWindow(SW_HIDE); m_message_label->ShowWindow(SW_HIDE); m_voxel->ShowWindow(SW_SHOW); + m_progress->ShowWindow(SW_SHOW); CString actionText = _T(""); CString messageText = _T(""); @@ -440,6 +464,7 @@ void CLauncherDlg::prepareProcess(DrawStep step) { setVerticalElement(m_message2_label, 0, 5, false); setVerticalElement(&m_btnNext, 10); m_btnNext.ShowWindow(SW_SHOW); + m_progress->ShowWindow(SW_HIDE); break; default: break; @@ -503,7 +528,6 @@ BOOL CLauncherDlg::getTextFormat(int resID, TextFormat& formatOut) { HBRUSH CLauncherDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { - HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); TextFormat textFormat; int resId = pWnd->GetDlgCtrlID(); @@ -523,6 +547,7 @@ HBRUSH CLauncherDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) CRect lineRect = CRect(rect.left + padding, rect.bottom, rect.right - padding, rect.bottom + borderThick); lineRect.MoveToY(lineRect.bottom + 1); pDC->FillSolidRect(lineRect, COLOR_GREY); + } } return (HBRUSH)GetStockObject(BLACK_BRUSH); @@ -622,7 +647,9 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { if (_drawStep != DrawStep::DrawError) { if (_drawStep == DrawStep::DrawProcessSetup || _drawStep == DrawStep::DrawProcessUpdate || - _drawStep == DrawStep::DrawProcessUninstall) { + _drawStep == DrawStep::DrawProcessUninstall || + _drawStep == DrawStep::DrawProcessFinishHq || + _drawStep == DrawStep::DrawProcessFinishUpdate) { // Refresh setDrawDialog(_drawStep, true); } @@ -631,13 +658,11 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { if (theApp._manager.needsUninstall()) { theApp._manager.addToLog(_T("Waiting to uninstall")); setDrawDialog(DrawStep::DrawProcessUninstall); - } - else { + } else { theApp._manager.addToLog(_T("Start splash screen")); setDrawDialog(DrawStep::DrawLogo); } - } - else if (_splashStep > 100) { + } else if (_splashStep > 100) { _showSplash = false; if (theApp._manager.shouldShutDown()) { if (_applicationWND != NULL) { @@ -647,21 +672,17 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { if (LauncherUtils::isProcessWindowOpened(L"interface.exe")) { exit(0); } - } - else if (theApp._manager.needsUpdate()) { + } else if (theApp._manager.needsUpdate()) { startProcess(); - } - else if (theApp._manager.needsUninstall()) { + } else if (theApp._manager.needsUninstall()) { if (theApp._manager.uninstallApplication()) { theApp._manager.addToLog(_T("HQ uninstalled successfully.")); exit(0); - } - else { + } else { theApp._manager.addToLog(_T("HQ failed to uninstall.")); theApp._manager.setFailed(true); } - } - else { + } else { theApp._manager.addToLog(_T("Starting login")); setDrawDialog(DrawStep::DrawLoginLogin); } @@ -673,6 +694,10 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { } } if (theApp._manager.shouldLaunch()) { + if (theApp._manager.needsInstall() || theApp._manager.needsUpdate()) { + auto finishProcess = theApp._manager.needsUpdate() ? DrawStep::DrawProcessFinishUpdate : DrawStep::DrawProcessFinishHq; + setDrawDialog(finishProcess); + } _applicationWND = theApp._manager.launchApplication(); } } @@ -699,16 +724,16 @@ void CLauncherDlg::setVerticalElement(CWnd* element, int verticalOffset, int hei void CLauncherDlg::setDrawDialog(DrawStep step, BOOL isUpdate) { _drawStep = step; + float progress = 0.0f; auto m_pRenderTarget = GetRenderTarget(); auto m_voxelRenderTarget = m_voxel->GetRenderTarget(); + auto m_progressRenderTarget = m_progress->GetRenderTarget(); switch (_drawStep) { case DrawStep::DrawLogo: m_pRenderTarget->BeginDraw(); drawBackground(m_pRenderTarget); + drawLogo(m_pRenderTarget); m_pRenderTarget->EndDraw(); - m_voxelRenderTarget->BeginDraw(); - drawLogo(m_voxelRenderTarget); - m_voxelRenderTarget->EndDraw(); break; case DrawStep::DrawLoginLogin: case DrawStep::DrawLoginErrorOrg: @@ -744,7 +769,12 @@ void CLauncherDlg::setDrawDialog(DrawStep step, BOOL isUpdate) { drawSmallLogo(m_pRenderTarget); m_pRenderTarget->EndDraw(); RedrawWindow(); - } + } + m_progressRenderTarget->BeginDraw(); + m_progressRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f)); + drawProgress(m_progressRenderTarget, 1.0f, D2D1::ColorF(0.2f, 0.2f, 0.2f)); + drawProgress(m_progressRenderTarget, theApp._manager.getProgress(), D2D1::ColorF(0.0f, 0.62f, 0.9f)); + m_progressRenderTarget->EndDraw(); m_voxelRenderTarget->BeginDraw(); drawVoxel(m_voxelRenderTarget); m_voxelRenderTarget->EndDraw(); diff --git a/launchers/win32/LauncherDlg.h b/launchers/win32/LauncherDlg.h index 4d830c2e21..faebc8a822 100644 --- a/launchers/win32/LauncherDlg.h +++ b/launchers/win32/LauncherDlg.h @@ -53,9 +53,7 @@ protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support void startProcess(); void setCustomDialog(); - - void setVerticalElement(CWnd* element, int verticalOffset, int heightOffset = 0, bool fromMainWindowBottom = true); - + void setVerticalElement(CWnd* element, int verticalOffset, int heightOffset = 0, bool fromMainWindowBottom = true); BOOL getHQInfo(const CString& orgname); DrawStep _drawStep { DrawStep::DrawLogo }; BOOL getTextFormat(int ResID, TextFormat& formatOut); @@ -86,6 +84,7 @@ protected: CStatic* m_terms; CStatic* m_trouble; CStatic* m_voxel; + CStatic* m_progress; CEdit m_orgname; CEdit m_username; @@ -101,6 +100,7 @@ protected: void drawLogo(CHwndRenderTarget* pRenderTarget); void drawSmallLogo(CHwndRenderTarget* pRenderTarget); void drawVoxel(CHwndRenderTarget* pRenderTarget); + void drawProgress(CHwndRenderTarget* pRenderTarget, float progress, const D2D1::ColorF& color); void prepareLogin(DrawStep step); void prepareProcess(DrawStep step); diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index 146e871028..66a691a4b4 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -24,7 +24,8 @@ LauncherManager::~LauncherManager() { void LauncherManager::init() { initLog(); addToLog(_T("Getting most recent build")); - LauncherUtils::ResponseError error = getMostRecentBuild(_latestApplicationURL, _latestVersion); + CString response; + LauncherUtils::ResponseError error = getMostRecentBuild(_latestApplicationURL, _latestVersion, response); if (error == LauncherUtils::ResponseError::NoError) { addToLog(_T("Latest version: ") + _latestVersion); CString currentVersion; @@ -41,14 +42,17 @@ void LauncherManager::init() { } else if (_loggedIn) { addToLog(_T("Interface not found but logged in. Reinstalling")); _shouldUpdate = TRUE; + } else { + _shouldInstall = TRUE; } } else { _hasFailed = true; CString msg; msg.Format(_T("Getting most recent build has failed with error: %d"), error); addToLog(msg); + msg.Format(_T("Response: %s"), response); + addToLog(msg); } - } BOOL LauncherManager::initLog() { @@ -146,6 +150,37 @@ BOOL LauncherManager::restartLauncher() { return FALSE; } +void LauncherManager::updateProgress(ProcessType processType, float progress) { + switch (processType) { + case LauncherManager::DownloadContent: + _progress = DOWNLOAD_CONTENT_INSTALL_WEIGHT * progress; + break; + case LauncherManager::UnzipContent: + _progress = DOWNLOAD_CONTENT_INSTALL_WEIGHT + + EXTRACT_CONTENT_INSTALL_WEIGHT * progress; + break; + case LauncherManager::DownloadApplication: + _progress = !_shouldUpdate ? + (DOWNLOAD_CONTENT_INSTALL_WEIGHT + + EXTRACT_CONTENT_INSTALL_WEIGHT + + DOWNLOAD_APPLICATION_INSTALL_WEIGHT * progress) : + DOWNLOAD_APPLICATION_UPDATE_WEIGHT * progress; + break; + case LauncherManager::UnzipApplication: + _progress = !_shouldUpdate ? + (DOWNLOAD_CONTENT_INSTALL_WEIGHT + + EXTRACT_CONTENT_INSTALL_WEIGHT + + DOWNLOAD_APPLICATION_INSTALL_WEIGHT + + EXTRACT_APPLICATION_INSTALL_WEIGHT * progress) : + (DOWNLOAD_APPLICATION_UPDATE_WEIGHT + + EXTRACT_APPLICATION_UPDATE_WEIGHT * progress); + break; + default: + break; + } + TRACE("progress = %f\n", _progress); +} + BOOL LauncherManager::createShortcuts() { CString desktopLnkPath; addToLog(_T("Creating shortcuts.")); @@ -301,7 +336,8 @@ BOOL LauncherManager::createConfigJSON() { return TRUE; } -LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, CString& domain, CString& content, bool& loggedIn) { +LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, CString& domain, + CString& content, bool& loggedIn) { CString configPath; getAndCreatePaths(PathType::Interface_Directory, configPath); configPath += "\\config.json"; @@ -329,8 +365,10 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString CString contentTypeJson = L"content-type:application/json"; CString response; CString url = _T("/organizations/") + hash + _T(".json"); - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"orgs.highfidelity.com", url, - contentTypeJson, CStringA(), response, false); + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", + L"orgs.highfidelity.com", url, + contentTypeJson, CStringA(), + response, false); if (error != LauncherUtils::ResponseError::NoError) { return error; } @@ -345,11 +383,14 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString return LauncherUtils::ResponseError::ParsingJSON; } -LauncherUtils::ResponseError LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut) { +LauncherUtils::ResponseError LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut, + CString& response) { CString contentTypeJson = L"content-type:application/json"; - CString response; - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"thunder.highfidelity.com", L"/builds/api/tags/latest?format=json", - contentTypeJson, CStringA(), response, false); + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", + L"thunder.highfidelity.com", + L"/builds/api/tags/latest?format=json", + contentTypeJson, CStringA(), + response, false); if (error != LauncherUtils::ResponseError::NoError) { return error; } @@ -377,7 +418,8 @@ LauncherUtils::ResponseError LauncherManager::getMostRecentBuild(CString& urlOut return LauncherUtils::ResponseError::ParsingJSON; } -LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username, const CString& password) { +LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username, + const CString& password) { CStringA post = "grant_type=password&username="; post += username; post += "&password="; @@ -386,8 +428,11 @@ LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString contentTypeText = L"content-type:application/x-www-form-urlencoded"; CString response; - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"metaverse.highfidelity.com", L"/oauth/token", - contentTypeText, post, response, true); + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", + L"metaverse.highfidelity.com", + L"/oauth/token", + contentTypeText, post, + response, true); if (error != LauncherUtils::ResponseError::NoError) { return error; } @@ -438,11 +483,11 @@ BOOL LauncherManager::uninstallApplication() { return success; } -void LauncherManager::onZipExtracted(ZipType type, int size) { - if (type == ZipType::ZipContent) { +void LauncherManager::onZipExtracted(ProcessType type, int size) { + if (type == ProcessType::UnzipContent) { addToLog(_T("Downloading application.")); downloadApplication(); - } else if (type == ZipType::ZipApplication) { + } else if (type == ProcessType::UnzipApplication) { createShortcuts(); addToLog(_T("Launching application.")); _shouldLaunch = TRUE; @@ -457,17 +502,23 @@ void LauncherManager::onZipExtracted(ZipType type, int size) { BOOL LauncherManager::extractApplication() { CString installPath; getAndCreatePaths(LauncherManager::PathType::Interface_Directory, installPath); - addToLog(_T("Creating config.json")); - createConfigJSON(); - BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipApplication, LauncherUtils::cStringToStd(_applicationZipPath), - LauncherUtils::cStringToStd(installPath), [&](int type, int size) { + std::function onExtractFinished = [&](int type, int size) { + addToLog(_T("Creating config.json")); + createConfigJSON(); if (size > 0) { - onZipExtracted((ZipType)type, size); + onZipExtracted((ProcessType)type, size); } else { addToLog(_T("Error decompressing application zip file.")); _hasFailed = true; } - }); + }; + std::function onProgress = [&](float progress) { + updateProgress(ProcessType::UnzipApplication, progress); + }; + BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipApplication, + LauncherUtils::cStringToStd(_applicationZipPath), + LauncherUtils::cStringToStd(installPath), + onExtractFinished, onProgress); if (success) { addToLog(_T("Created thread for unzipping application.")); } else { @@ -476,8 +527,8 @@ BOOL LauncherManager::extractApplication() { return success; } -void LauncherManager::onFileDownloaded(DownloadType type) { - if (type == DownloadType::DownloadContent) { +void LauncherManager::onFileDownloaded(ProcessType type) { + if (type == ProcessType::DownloadContent) { addToLog(_T("Deleting content directory before install")); CString contentDir; getAndCreatePaths(PathType::Content_Directory, contentDir); @@ -490,7 +541,7 @@ void LauncherManager::onFileDownloaded(DownloadType type) { setFailed(true); } }); - } else if (type == DownloadType::DownloadApplication) { + } else if (type == ProcessType::DownloadApplication) { addToLog(_T("Deleting application directory before install")); CString applicationDir; getAndCreatePaths(PathType::Interface_Directory, applicationDir); @@ -511,16 +562,21 @@ BOOL LauncherManager::installContent() { std::string contentZipFile = LauncherUtils::cStringToStd(_contentZipPath); CString contentPath; getAndCreatePaths(LauncherManager::PathType::Content_Directory, contentPath); - BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipContent, contentZipFile, - LauncherUtils::cStringToStd(contentPath), [&](int type, int size) { + std::function onInstallFinished = [&](int type, int size) { if (size > 0) { addToLog(_T("Content zip decompresed.")); - onZipExtracted((ZipType)type, size); - } else { + onZipExtracted((ProcessType)type, size); + } + else { addToLog(_T("Error decompressing content zip file.")); _hasFailed = true; } - }); + }; + std::function onProgress = [&](float progress) { + updateProgress(ProcessType::UnzipContent, progress); + }; + BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipContent, contentZipFile, + LauncherUtils::cStringToStd(contentPath), onInstallFinished, onProgress); if (success) { addToLog(_T("Created thread for unzipping content.")); } else { @@ -530,25 +586,32 @@ BOOL LauncherManager::installContent() { } -BOOL LauncherManager::downloadFile(DownloadType type, const CString& url, CString& outPath) { +BOOL LauncherManager::downloadFile(ProcessType type, const CString& url, CString& outPath) { CString fileName = url.Mid(url.ReverseFind('/') + 1); CString downloadDirectory; BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory); outPath = downloadDirectory + fileName; + _currentProcess = type; if (success) { addToLog(_T("Downloading: ") + url); - if (!LauncherUtils::downloadFileOnThread(type, url, outPath, [&](int type, bool error) { + std::function onDownloadFinished = [&](int type, bool error) { if (!error) { - onFileDownloaded((DownloadType)type); - } else { - if (type == DownloadType::DownloadApplication) { + onFileDownloaded((ProcessType)type); + } + else { + if (type == ProcessType::DownloadApplication) { addToLog(_T("Error downloading content.")); - } else { + } + else { addToLog(_T("Error downloading application.")); } _hasFailed = true; } - })) { + }; + std::function onProgress = [&](float progress) { + updateProgress(_currentProcess, progress); + }; + if (!LauncherUtils::downloadFileOnThread(type, url, outPath, onDownloadFinished, onProgress)) { success = FALSE; } } @@ -558,10 +621,10 @@ BOOL LauncherManager::downloadFile(DownloadType type, const CString& url, CStrin BOOL LauncherManager::downloadContent() { addToLog(_T("Downloading content.")); CString contentURL = getContentURL(); - return downloadFile(DownloadType::DownloadContent, contentURL, _contentZipPath); + return downloadFile(ProcessType::DownloadContent, contentURL, _contentZipPath); } BOOL LauncherManager::downloadApplication() { CString applicationURL = getLatestInterfaceURL(); - return downloadFile(DownloadType::DownloadApplication, applicationURL, _applicationZipPath); -} + return downloadFile(ProcessType::DownloadApplication, applicationURL, _applicationZipPath); +} \ No newline at end of file diff --git a/launchers/win32/LauncherManager.h b/launchers/win32/LauncherManager.h index e1b1557faf..7c1590a512 100644 --- a/launchers/win32/LauncherManager.h +++ b/launchers/win32/LauncherManager.h @@ -19,6 +19,12 @@ const CString DIRECTORY_NAME_CONTENT = _T("content"); const CString EXTRA_PARAMETERS = _T(" --suppress-settings-reset --no-launcher --no-updater"); const CString LAUNCHER_EXE_FILENAME = _T("HQ Launcher.exe"); const bool INSTALL_ZIP = true; +const float DOWNLOAD_CONTENT_INSTALL_WEIGHT = 0.2f; +const float EXTRACT_CONTENT_INSTALL_WEIGHT = 0.1f; +const float DOWNLOAD_APPLICATION_INSTALL_WEIGHT = 0.5f; +const float EXTRACT_APPLICATION_INSTALL_WEIGHT = 0.2f; +const float DOWNLOAD_APPLICATION_UPDATE_WEIGHT = 0.75f; +const float EXTRACT_APPLICATION_UPDATE_WEIGHT = 0.25f; class LauncherManager { @@ -33,16 +39,8 @@ public: StartMenu_Directory, Temp_Directory }; - enum ZipType { - ZipContent = 0, - ZipApplication - }; - enum DownloadType { - DownloadContent = 0, - DownloadApplication - }; enum ErrorType { - ErrorNetworkAuth, + ErrorNetworkAuth = 0, ErrorNetworkUpdate, ErrorNetworkHq, ErrorDownloading, @@ -50,6 +48,12 @@ public: ErrorInstall, ErrorIOFiles }; + enum ProcessType { + DownloadContent, + DownloadApplication, + UnzipContent, + UnzipApplication + }; LauncherManager(); ~LauncherManager(); void init(); @@ -62,7 +66,7 @@ public: BOOL isApplicationInstalled(CString& version, CString& domain, CString& content, bool& loggedIn); LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password); - LauncherUtils::ResponseError getMostRecentBuild(CString& urlOut, CString& versionOut); + LauncherUtils::ResponseError getMostRecentBuild(CString& urlOut, CString& versionOut, CString& response); LauncherUtils::ResponseError readOrganizationJSON(const CString& hash); LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain, CString& content, bool& loggedIn); @@ -84,6 +88,7 @@ public: BOOL shouldLaunch() const { return _shouldLaunch; } BOOL needsUpdate() { return _shouldUpdate; } BOOL needsUninstall() { return _shouldUninstall; } + BOOL needsInstall() { return _shouldInstall; } void setDisplayName(const CString& displayName) { _displayName = displayName; } bool isLoggedIn() { return _loggedIn; } bool hasFailed() { return _hasFailed; } @@ -91,15 +96,18 @@ public: const CString& getLatestInterfaceURL() const { return _latestApplicationURL; } void uninstall() { _shouldUninstall = true; }; - BOOL downloadFile(DownloadType type, const CString& url, CString& localPath); + BOOL downloadFile(ProcessType type, const CString& url, CString& localPath); BOOL downloadContent(); BOOL downloadApplication(); BOOL installContent(); BOOL extractApplication(); - void onZipExtracted(ZipType type, int size); - void onFileDownloaded(DownloadType type); + void onZipExtracted(ProcessType type, int size); + void onFileDownloaded(ProcessType type); + float getProgress() { return _progress; } private: + void updateProgress(ProcessType processType, float progress); + ProcessType _currentProcess { ProcessType::DownloadApplication }; CString _latestApplicationURL; CString _latestVersion; CString _contentURL; @@ -109,12 +117,14 @@ private: CString _tokensJSON; CString _applicationZipPath; CString _contentZipPath; - bool _loggedIn{ false }; - bool _hasFailed{ false }; - BOOL _shouldUpdate{ FALSE }; - BOOL _shouldUninstall{ FALSE }; - BOOL _shouldShutdown{ FALSE }; - BOOL _shouldLaunch{ FALSE }; + bool _loggedIn { false }; + bool _hasFailed { false }; + BOOL _shouldUpdate { FALSE }; + BOOL _shouldUninstall { FALSE }; + BOOL _shouldInstall { FALSE }; + BOOL _shouldShutdown { FALSE }; + BOOL _shouldLaunch { FALSE }; + float _progress { 0.0f }; CStdioFile _logFile; }; diff --git a/launchers/win32/LauncherUtils.cpp b/launchers/win32/LauncherUtils.cpp index 094ab09307..3ba4b26901 100644 --- a/launchers/win32/LauncherUtils.cpp +++ b/launchers/win32/LauncherUtils.cpp @@ -272,7 +272,9 @@ BOOL LauncherUtils::getFont(const CString& fontName, int fontSize, bool isBold, return TRUE; } -uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, std::vector& files) { +uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, + std::vector& files, + std::function progressCallback) { { CString msg; msg.Format(_T("Reading zip file %s, extracting to %s"), CString(zipFile.c_str()), CString(path.c_str())); @@ -292,7 +294,6 @@ uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string theApp._manager.addToLog(msg); return 0; } - int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); { CString msg; @@ -313,6 +314,7 @@ uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string // Get root folder CString lastDir = _T(""); uint64_t totalSize = 0; + uint64_t totalCompressedSize = 0; bool _shouldFail = false; for (int i = 0; i < fileCount; i++) { if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; @@ -335,7 +337,9 @@ uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string } CT2A destFile(fullFilename); if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile, 0)) { + totalCompressedSize += (uint64_t)file_stat.m_comp_size; totalSize += (uint64_t)file_stat.m_uncomp_size; + progressCallback((float)totalCompressedSize / (float)zip_archive.m_archive_size); files.emplace_back(destFile); } else { CString msg; @@ -466,7 +470,7 @@ BOOL LauncherUtils::hMac256(const CString& cmessage, const char* keystr, CString DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) { UnzipThreadData& data = *((UnzipThreadData*)lpParameter); - uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector()); + uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector(), data.progressCallback); int mb_size = (int)(size * 0.001f); data.callback(data._type, mb_size); delete &data; @@ -475,7 +479,10 @@ DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) { DWORD WINAPI LauncherUtils::downloadThread(LPVOID lpParameter) { DownloadThreadData& data = *((DownloadThreadData*)lpParameter); - auto hr = URLDownloadToFile(0, data._url, data._file, 0, NULL); + ProgressCallback progressCallback; + progressCallback.setProgressCallback(data.progressCallback); + auto hr = URLDownloadToFile(0, data._url, data._file, 0, + static_cast(&progressCallback)); data.callback(data._type, hr != S_OK); return 0; } @@ -487,13 +494,16 @@ DWORD WINAPI LauncherUtils::deleteDirectoryThread(LPVOID lpParameter) { return 0; } -BOOL LauncherUtils::unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback) { +BOOL LauncherUtils::unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, + std::function callback, + std::function progressCallback) { DWORD myThreadID; UnzipThreadData* unzipThreadData = new UnzipThreadData(); unzipThreadData->_type = type; unzipThreadData->_zipFile = zipFile; unzipThreadData->_path = path; unzipThreadData->setCallback(callback); + unzipThreadData->setProgressCallback(progressCallback); HANDLE myHandle = CreateThread(0, 0, unzipThread, unzipThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); @@ -502,13 +512,16 @@ BOOL LauncherUtils::unzipFileOnThread(int type, const std::string& zipFile, cons return FALSE; } -BOOL LauncherUtils::downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback) { +BOOL LauncherUtils::downloadFileOnThread(int type, const CString& url, const CString& file, + std::function callback, + std::function progressCallback) { DWORD myThreadID; DownloadThreadData* downloadThreadData = new DownloadThreadData(); downloadThreadData->_type = type; downloadThreadData->_url = url; downloadThreadData->_file = file; downloadThreadData->setCallback(callback); + downloadThreadData->setProgressCallback(progressCallback); HANDLE myHandle = CreateThread(0, 0, downloadThread, downloadThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); diff --git a/launchers/win32/LauncherUtils.h b/launchers/win32/LauncherUtils.h index a373428ad6..3e07d2af05 100644 --- a/launchers/win32/LauncherUtils.h +++ b/launchers/win32/LauncherUtils.h @@ -15,9 +15,57 @@ #include "libs/json/json.h" #include "libs/miniz.h" -class LauncherUtils -{ +class LauncherUtils { public: + class ProgressCallback : public IBindStatusCallback { + public: + HRESULT __stdcall QueryInterface(const IID &, void **) { + return E_NOINTERFACE; + } + ULONG STDMETHODCALLTYPE AddRef(void) { + return 1; + } + ULONG STDMETHODCALLTYPE Release(void) { + return 1; + } + HRESULT STDMETHODCALLTYPE OnStartBinding(DWORD dwReserved, IBinding *pib) { + return E_NOTIMPL; + } + virtual HRESULT STDMETHODCALLTYPE GetPriority(LONG *pnPriority) { + return E_NOTIMPL; + } + virtual HRESULT STDMETHODCALLTYPE OnLowResource(DWORD reserved) { + return S_OK; + } + virtual HRESULT STDMETHODCALLTYPE OnStopBinding(HRESULT hresult, LPCWSTR szError) { + return E_NOTIMPL; + } + virtual HRESULT STDMETHODCALLTYPE GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo) { + return E_NOTIMPL; + } + virtual HRESULT STDMETHODCALLTYPE OnDataAvailable(DWORD grfBSCF, DWORD dwSize, + FORMATETC *pformatetc, STGMEDIUM *pstgmed) { + return E_NOTIMPL; + } + virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(REFIID riid, IUnknown *punk) { + return E_NOTIMPL; + } + + virtual HRESULT __stdcall OnProgress(ULONG ulProgress, ULONG ulProgressMax, + ULONG ulStatusCode, LPCWSTR szStatusText) { + float progress = (float)ulProgress / ulProgressMax; + if (!isnan(progress)) { + onProgressCallback(progress); + } + return S_OK; + } + void setProgressCallback(std::function fn) { + onProgressCallback = std::bind(fn, std::placeholders::_1); + } + private: + std::function onProgressCallback; + }; + enum ResponseError { Open = 0, Connect, @@ -35,10 +83,14 @@ public: CString _url; CString _file; std::function callback; + std::function progressCallback; // function(type, errorType) void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2); } + void setProgressCallback(std::function fn) { + progressCallback = std::bind(fn, std::placeholders::_1); + } }; struct UnzipThreadData { @@ -47,15 +99,23 @@ public: std::string _path; // function(type, size) std::function callback; + std::function progressCallback; void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2); } + void setProgressCallback(std::function fn) { + progressCallback = std::bind(fn, std::placeholders::_1); + } }; struct DeleteThreadData { CString _dirPath; std::function callback; + std::function progressCallback; void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1); } + void setProgressCallback(std::function fn) { + progressCallback = std::bind(fn, std::placeholders::_1); + } }; struct ProcessData { @@ -79,11 +139,18 @@ public: static BOOL deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin = true); static HRESULT createLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs = _T("")); static BOOL hMac256(const CString& message, const char* key, CString& hashOut); - static uint64_t extractZip(const std::string& zipFile, const std::string& path, std::vector& files); + static uint64_t extractZip(const std::string& zipFile, const std::string& path, + std::vector& files, + std::function progressCallback); static BOOL deleteRegistryKey(const CString& registryPath); - static BOOL unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback); - static BOOL downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback); + static BOOL unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, + std::function callback, + std::function progressCallback); + static BOOL downloadFileOnThread(int type, const CString& url, const CString& file, + std::function callback, + std::function progressCallback); static BOOL deleteDirectoryOnThread(const CString& dirPath, std::function callback); + static CString urlEncodeString(const CString& url); static HWND executeOnForeground(const CString& path, const CString& params); diff --git a/launchers/win32/resource.h b/launchers/win32/resource.h index f5a1e3ef07..74c62b75cb 100644 --- a/launchers/win32/resource.h +++ b/launchers/win32/resource.h @@ -26,6 +26,7 @@ #define IDC_TERMS_LINK 1022 #define IDC_TROUBLE 1023 #define IDC_VOXEL 1024 +#define IDC_PROGRESS 1025 #define IDC_TROUBLE_LINK 1027 // Next default values for new objects