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
26C4GUI::Label *pStatusLabel;
27C4GUI::ProgressBar *pProgressBar;
28
29C4DownloadDlg::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
41C4DownloadDlg::~C4DownloadDlg()
42{
43#ifdef _WIN32
44 if (fWinSock) ReleaseWinSock();
45#endif
46}
47
48void 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
87void 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
128void C4DownloadDlg::UserClose(bool fOK)
129{
130 // user cancelled
131 HTTPClient.Cancel(reason: LoadResStr(id: C4ResStrTableKey::IDS_ERR_USERCANCEL));
132}
133
134const char *C4DownloadDlg::GetError()
135{
136 // own error?
137 if (szError) return szError;
138 // fallback to HTTP error
139 return HTTPClient.GetError();
140}
141
142bool 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
167bool 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