| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2007, Sven2 |
| 6 | * Copyright (c) 2017-2021, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | // HTTP download dialog; downloads a file |
| 19 | |
| 20 | #include "C4GuiResource.h" |
| 21 | #include "C4Include.h" |
| 22 | #include "C4DownloadDlg.h" |
| 23 | |
| 24 | #include "C4Log.h" |
| 25 | |
| 26 | C4GUI::Label *pStatusLabel; |
| 27 | C4GUI::ProgressBar *pProgressBar; |
| 28 | |
| 29 | C4DownloadDlg::C4DownloadDlg(const char *szDLType) : C4GUI::Dialog(C4GUI_ProgressDlgWdt, 100, LoadResStr(id: C4ResStrTableKey::IDS_CTL_DL_TITLE, args&: szDLType).c_str(), false), szError(nullptr) |
| 30 | { |
| 31 | #ifdef _WIN32 |
| 32 | fWinSock = AcquireWinSock(); |
| 33 | #endif |
| 34 | // add all elements - will be reposisioned when text is displayed |
| 35 | AddElement(pChild: pIcon = new C4GUI::Icon(C4Rect(), C4GUI::Ico_NetWait)); |
| 36 | AddElement(pChild: pStatusLabel = new C4GUI::Label("" , C4Rect(), ACenter, C4GUI_MessageFontClr, &C4GUI::GetRes()->TextFont, false)); |
| 37 | pProgressBar = nullptr; // created when necessary |
| 38 | AddElement(pChild: pCancelBtn = C4GUI::newCancelButton(bounds: C4Rect())); |
| 39 | } |
| 40 | |
| 41 | C4DownloadDlg::~C4DownloadDlg() |
| 42 | { |
| 43 | #ifdef _WIN32 |
| 44 | if (fWinSock) ReleaseWinSock(); |
| 45 | #endif |
| 46 | } |
| 47 | |
| 48 | void C4DownloadDlg::SetStatus(const char *szNewText, int32_t iProgressPercent) |
| 49 | { |
| 50 | // get positions |
| 51 | C4GUI::ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); |
| 52 | // place icon |
| 53 | pIcon->SetBounds(caMain.GetFromLeft(C4GUI_IconWdt, C4GUI_IconWdt)); |
| 54 | // place message label |
| 55 | // use text with line breaks |
| 56 | StdStrBuf sMsgBroken; |
| 57 | int iMsgHeight = C4GUI::GetRes()->TextFont.BreakMessage(szMsg: szNewText, iWdt: caMain.GetInnerWidth(), pOut: &sMsgBroken, fCheckMarkup: true); |
| 58 | pStatusLabel->SetBounds(caMain.GetFromTop(iHgt: iMsgHeight)); |
| 59 | pStatusLabel->SetText(szText: sMsgBroken.getData()); |
| 60 | // place progress bar |
| 61 | if (iProgressPercent >= 0) |
| 62 | { |
| 63 | if (!pProgressBar) |
| 64 | { |
| 65 | AddElement(pChild: pProgressBar = new C4GUI::ProgressBar(caMain.GetFromTop(C4GUI_ProgressDlgPBHgt))); |
| 66 | } |
| 67 | else |
| 68 | { |
| 69 | pProgressBar->SetBounds(caMain.GetFromTop(C4GUI_ProgressDlgPBHgt)); |
| 70 | } |
| 71 | pProgressBar->SetProgress(iProgressPercent); |
| 72 | } |
| 73 | else |
| 74 | { |
| 75 | // no progress desired |
| 76 | delete pProgressBar; pProgressBar = nullptr; |
| 77 | } |
| 78 | // place button |
| 79 | caMain.ExpandLeft(C4GUI_DefDlgIndent * 2 + C4GUI_IconWdt); |
| 80 | C4GUI::ComponentAligner caButtonArea(caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0, 0); |
| 81 | pCancelBtn->SetBounds(caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt)); |
| 82 | pCancelBtn->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DL_CANCEL)); |
| 83 | // resize to actually needed size |
| 84 | SetClientSize(iToWdt: GetClientRect().Wdt, iToHgt: GetClientRect().Hgt - caMain.GetHeight()); |
| 85 | } |
| 86 | |
| 87 | void C4DownloadDlg::OnIdle() |
| 88 | { |
| 89 | // continue query process |
| 90 | if (!HTTPClient.Execute()) |
| 91 | { |
| 92 | // query aborted |
| 93 | Close(fOK: false); |
| 94 | return; |
| 95 | } |
| 96 | if (!HTTPClient.isBusy()) |
| 97 | { |
| 98 | // download done or aborted |
| 99 | Close(fOK: HTTPClient.isSuccess()); |
| 100 | return; |
| 101 | } |
| 102 | StdStrBuf sStatus; int32_t iProgress = -1; |
| 103 | // download in progress: Update status |
| 104 | if (!HTTPClient.isConnected()) |
| 105 | { |
| 106 | // still connecting |
| 107 | sStatus.Ref(pnData: LoadResStr(id: C4ResStrTableKey::IDS_DL_STATUSCONNECTING)); |
| 108 | } |
| 109 | else |
| 110 | { |
| 111 | // header received? |
| 112 | size_t iSize = HTTPClient.getTotalSize(); |
| 113 | if (!iSize) |
| 114 | { |
| 115 | // file size unknown: No header received. |
| 116 | sStatus.Ref(pnData: LoadResStr(id: C4ResStrTableKey::IDS_PRC_CONNECTED)); |
| 117 | } |
| 118 | else |
| 119 | { |
| 120 | // file size known: Download in progress |
| 121 | sStatus.Ref(pnData: LoadResStr(id: C4ResStrTableKey::IDS_CTL_DL_PROGRESS)); |
| 122 | iProgress = static_cast<int32_t>(100 * HTTPClient.getDownloadedSize() / iSize); |
| 123 | } |
| 124 | } |
| 125 | SetStatus(szNewText: LoadResStr(id: C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, args: GetFilename(path: HTTPClient.getURL())).c_str(), iProgressPercent: iProgress); |
| 126 | } |
| 127 | |
| 128 | void C4DownloadDlg::UserClose(bool fOK) |
| 129 | { |
| 130 | // user cancelled |
| 131 | HTTPClient.Cancel(reason: LoadResStr(id: C4ResStrTableKey::IDS_ERR_USERCANCEL)); |
| 132 | } |
| 133 | |
| 134 | const char *C4DownloadDlg::GetError() |
| 135 | { |
| 136 | // own error? |
| 137 | if (szError) return szError; |
| 138 | // fallback to HTTP error |
| 139 | return HTTPClient.GetError(); |
| 140 | } |
| 141 | |
| 142 | bool C4DownloadDlg::ShowModal(C4GUI::Screen *pScreen, const char *szURL, const char *szSaveAsFilename) |
| 143 | { |
| 144 | // reset error |
| 145 | szError = nullptr; |
| 146 | // initial text |
| 147 | if (!HTTPClient.Init()) return false; |
| 148 | HTTPClient.SetServer(serverAddress: szURL); |
| 149 | // show dlg |
| 150 | if (!Show(pOnScreen: pScreen, fCB: true)) return false; |
| 151 | // start query |
| 152 | if (!HTTPClient.Query(Data: StdBuf{}, binary: true)) return false; |
| 153 | // first time status update |
| 154 | OnIdle(); |
| 155 | // cycle until query is finished or aborted |
| 156 | if (!DoModal()) return false; |
| 157 | // download successful: Save file |
| 158 | if (!HTTPClient.getResultBin().SaveToFile(szFile: szSaveAsFilename)) |
| 159 | { |
| 160 | // file saving failed |
| 161 | szError = LoadResStr(id: C4ResStrTableKey::IDS_FAIL_SAVE); |
| 162 | return false; |
| 163 | } |
| 164 | return true; |
| 165 | } |
| 166 | |
| 167 | bool C4DownloadDlg::DownloadFile(const char *szDLType, C4GUI::Screen *pScreen, const char *szURL, const char *szSaveAsFilename, const char *szNotFoundMessage) |
| 168 | { |
| 169 | // log it |
| 170 | Log(id: C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, args&: szURL); |
| 171 | // show download dialog |
| 172 | C4DownloadDlg *pDlg = new C4DownloadDlg(szDLType); |
| 173 | if (!pDlg->ShowModal(pScreen, szURL, szSaveAsFilename)) |
| 174 | { |
| 175 | // an error occurred. Did the GUI get deleted? |
| 176 | if (!C4GUI::IsGUIValid()) |
| 177 | // then the dlg got deleted as well, and we should get out ASAP |
| 178 | return false; |
| 179 | // otherwise, show an appropriate error |
| 180 | const char *szError = pDlg->GetError(); |
| 181 | if (!szError || !*szError) szError = LoadResStr(id: C4ResStrTableKey::IDS_PRC_UNKOWNERROR); |
| 182 | std::string error{LoadResStr(id: C4ResStrTableKey::IDS_PRC_DOWNLOADERROR, args: GetFilename(path: szURL), args&: szError)}; |
| 183 | // it's a 404: display extended message |
| 184 | if (error.contains(x: "404" ) && szNotFoundMessage) |
| 185 | { |
| 186 | error += '|'; |
| 187 | error += szNotFoundMessage; |
| 188 | } |
| 189 | // display message |
| 190 | pScreen->ShowMessageModal(szMessage: error.c_str(), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_CTL_DL_TITLE, args&: szDLType).c_str(), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error, pbConfigDontShowAgainSetting: nullptr); |
| 191 | delete pDlg; |
| 192 | return false; |
| 193 | } |
| 194 | Log(id: C4ResStrTableKey::IDS_PRC_DOWNLOADCOMPLETE, args&: szURL); |
| 195 | delete pDlg; |
| 196 | return true; |
| 197 | } |
| 198 | |