OpenClonk
C4StartupModsDlg.h
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2006-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2010-2017, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // Screen for mod handling
17 
18 #ifndef INC_C4StartupModsDlg
19 #define INC_C4StartupModsDlg
20 
21 #include "gui/C4Startup.h"
23 #include "platform/StdSync.h"
24 
25 #include <functional>
26 #include <tuple>
27 
28 class TiXmlElement;
29 class TiXmlNode;
30 class C4StartupModsDlg;
32 class C4HTTPClient;
33 
34 // The data that can be parsed from downloaded/cached XML files.
35 struct ModXMLData
36 {
37  struct FileInfo
38  {
39  std::string handle;
40  size_t size;
41  std::string name;
42  std::string sha1;
43  };
44  std::vector<FileInfo> files;
45  std::vector<std::string> dependencies;
46  std::vector<std::string> tags;
47  std::string title;
48  std::string id;
50  std::string slug;
51  bool metadataMissing{ false };
52 
53  // Depending on the origin of the data, we might need an update query before doing anything.
54  enum class Source
55  {
56  Unknown,
57  Local,
58  Overview,
60  };
62  bool requiresUpdate() const { return source != Source::DetailView; }
63 
64  ModXMLData(const TiXmlElement *xml, Source source = Source::Unknown);
65  ~ModXMLData();
66 
67  // Used to write the element to a file in case the mod gets installed.
68  TiXmlNode *originalXMLElement{ nullptr };
69 };
70 
72 {
73 protected:
74  void Execute() override;
75 public:
77  discoveryFinishedEvent(true),
78  parent(parent)
79  {
81  }
82 
83  struct ModsInfo
84  {
85  std::string id;
86  std::string path;
87  // Name parsed from the directory name.
88  std::string name;
89  };
90 
91  bool IsDiscoveryFinished() const { return discoveryFinished; }
92  void WaitForDiscoveryFinished() { discoveryFinishedEvent.WaitFor(-1); }
93 
94  const bool IsModInstalled(const std::string &id)
95  {
96  CStdLock lock(&modInformationModification);
97  assert(IsDiscoveryFinished());
98  return modsInformation.count(id) != 0;
99  }
100  ModsInfo GetModInformation(const std::string &id)
101  {
102  CStdLock lock(&modInformationModification);
103  assert(IsDiscoveryFinished());
104  return modsInformation[id];
105  }
106  void RemoveMod(const std::string &id)
107  {
108  CStdLock lock(&modInformationModification);
109  modsInformation.erase(id);
110  }
111  ModsInfo & AddMod(const std::string &id, const std::string &path, const std::string &name)
112  {
113  CStdLock lock(&modInformationModification);
114  modsInformation[id] = ModsInfo{ id, path, name };
115  return modsInformation[id];
116  }
117 
118  // Careful, lock for yourself when iterating through this.
119  const std::map<std::string, ModsInfo> & GetAllModInformation()
120  {
121  return modsInformation;
122  }
124  {
125  return std::move(CStdLock(&modInformationModification));
126  }
127 private:
128  C4StartupModsDlg *parent;
129  void ExecuteDiscovery();
130  bool discoveryFinished = false;
131  CStdEvent discoveryFinishedEvent;
132  CStdCSec modInformationModification;
133 
134  std::map<std::string, ModsInfo> modsInformation;
135 };
136 
137 // This contains the downloading and installation logic.
139 {
140 public:
143 
144 private:
145  class ModInfo
146  {
147  public:
148  struct FileInfo
149  {
150  std::string handle;
151  std::string name;
152  size_t size;
153  std::string sha1;
154  };
155 
156  std::string modID;
157  std::string name;
158  std::string slug;
159 
160  ModInfo() : localDiscoveryCheck(*this) {}
161  ModInfo(const C4StartupModsListEntry *entry);
162  // From minimal information, will require an update.
163  ModInfo(std::string modID, std::string name);
164  ~ModInfo() { Clear(); }
165 
166  std::vector<FileInfo> files;
167  std::vector<std::string> dependencies;
168  // All filenames are held separately, too, because the 'files' list will be manipulated.
169  std::set<std::string> requiredFilenames;
170 
171  void Clear();
172  void FromXMLData(const ModXMLData &entry);
173  void CheckProgress();
174  void CancelRequest();
175 
176  std::tuple<size_t, size_t> GetProgress() const { return std::make_tuple(downloadedBytes, totalBytes); }
177  void SetError(const std::string &e) { errorMessage += (errorMessage.empty() ? "" : " ") + e; }
178  bool HasError() const { return !errorMessage.empty(); }
179  bool WasSuccessful() const { return successful && !HasError(); }
180  bool IsBusy() const { return postClient.get() != nullptr; }
181  bool HasFilesRemaining() const { return !files.empty(); }
182  bool RequiresMetadataUpdate() const { return hasOnlyIncompleteInformation; }
183  std::string GetErrorMessage() const { if (errorMessage.empty()) return ""; return name + ": " + errorMessage; }
184  std::string GetPath();
185 
186  struct LocalDiscoveryCheck : protected StdThread
187  {
188  std::string basePath;
189  bool needsCheck{ true };
190  bool installed{ false };
191  bool atLeastOneFileExisted{ false };
192 
193  LocalDiscoveryCheck(ModInfo &mod) : mod(mod) { };
194  void Start() { StdThread::Start(); }
195  protected:
196  ModInfo &mod;
197  void Execute() override;
198  } localDiscoveryCheck;
199  private:
200  bool successful{ false };
201  // Whether the information might be outdated or incomplete and needs an update prior to hash-checking.
202  bool hasOnlyIncompleteInformation{ true };
203  size_t totalSuccesfullyDownloadedBytes{ 0 };
204  size_t downloadedBytes{ 0 };
205  size_t totalBytes{ 0 };
206  std::unique_ptr<C4HTTPClient> postClient;
207  std::string errorMessage;
208  TiXmlNode *originalXMLNode{ nullptr };
209  };
210 private:
211  std::vector<std::unique_ptr<ModInfo>> items;
212 
213  C4StartupModsDlg * parent;
214 
215  C4GUI::ProgressDialog *progressDialog = nullptr;
216  C4GUI::ProgressDialog * GetProgressDialog();
217  // For sync the GUI thread (e.g. abort button) with the background thread.
218  CStdCSec guiThreadResponse;
219  void CancelRequest();
220  void ExecuteCheckDownloadProgress();
221  void ExecutePreRequestChecks();
222  void ExecuteWaitForChecksums();
223  void ExecuteRequestConfirmation();
224 
225  void ExecuteMetadataUpdate();
226  std::unique_ptr<C4HTTPClient> postMetadataClient;
227  int metadataQueriedForModIdx{ -1 };
228 
229  std::function<void(void)> progressCallback;
230 
231  // Called by CStdTimerProc.
232  bool Execute(int, pollfd *) override
233  {
234  CStdLock lock(&guiThreadResponse);
235  if (CheckAndReset() && progressCallback)
236  progressCallback();
237  return true;
238  }
239 
240 public:
241  void AddModToQueue(std::string modID, std::string name);
242  void RequestConfirmation();
243  void OnConfirmInstallation(C4GUI::Element *element);
244  // callback from C4Network2ReferenceClient
245  virtual void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) {}
246 };
247 
249 {
250 public:
251  C4StartupModsListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, class C4StartupModsDlg *pModsDlg);
253 
254 
255  enum { InfoLabelCount = 2, MaxInfoIconCount = 10 };
256 
257 private:
258  C4StartupModsDlg *pModsDlg;
259  C4GUI::ListBox *pList;
260 
261  C4GUI::Icon *pIcon; // scenario icon
262  C4GUI::Label *pInfoLbl[InfoLabelCount]; // info labels for reference or query; left side
263  C4GUI::Label *pInfoLabelsRight[InfoLabelCount]; // info labels on the right side
264  C4GUI::Icon *pInfoIcons[MaxInfoIconCount]; // right-aligned status icons at topright position
265  int32_t iInfoIconCount;
266  bool isInfoEntry{ false };
267  C4Rect rctIconSmall; // bounds for small icon
268  C4Rect rctIconLarge; // bounds for large icon
269 
270  StdStrBuf sInfoText[InfoLabelCount];
271  StdStrBuf sInfoTextRight[InfoLabelCount];
272 
273  void AddStatusIcon(C4GUI::Icons eIcon, const char *szToolTip, bool insertLeft=true); // add a status icon with the specified tooltip
274 
275  void UpdateEntrySize();
276  void UpdateText();
277 
279 
280  bool isInstalled;
281  std::unique_ptr<ModXMLData> modXMLData;
282 
283 protected:
284  virtual int32_t GetListItemTopSpacing() { return 10; }
285  virtual void DrawElement(C4TargetFacet &cgo);
286 public:
287  void FromXML(const TiXmlElement *xml, ModXMLData::Source source, std::string fallbackID="", std::string fallbackName="");
288  const ModXMLData &GetModXMLData() const { assert(modXMLData); return *modXMLData.get(); }
289  void Clear(); // del any ref/refclient/error data
290 
291  bool Execute(); // update stuff - if false is returned, the item is to be removed
293  void SetVisibility(bool fToValue);
294 
295  // There is a special entry that conveys status information.
296  void MakeInfoEntry();
297  bool IsInfoEntry() const { return isInfoEntry; }
298  void OnNoResultsFound();
299  void OnError(std::string message);
300  void ShowPageInfo(int page, int totalPages, int totalResults);
301 
302  const TiXmlNode *GetXMLNode() const { return GetModXMLData().originalXMLElement; }
303  std::string GetTitle() const { return GetModXMLData().title; }
304  const std::vector<ModXMLData::FileInfo> & GetFileInfos() const { return GetModXMLData().files; }
305  std::string GetID() const { return GetModXMLData().id; }
306  bool IsInstalled() const { return isInstalled || IsLoadedFromLocal(); }
308 };
309 
310 // startup dialog: Network game selection
312 {
313 public:
314  C4StartupModsDlg(); // ctor
315  ~C4StartupModsDlg(); // dtor
316 
317 private:
318  C4GUI::Tabular *pMainTabular; // main tabular control: Contains game selection list and chat control
319  C4GUI::ListBox *pGameSelList; // game selection listbox
320  C4KeyBinding *pKeyRefresh, *pKeyBack, *pKeyForward;
321 
322  C4GUI::Button *btnInstall, *btnRemove;
323  C4GUI::IconButton* buttonShowInstalled{ nullptr };
324  C4GUI::Edit *pSearchFieldEdt;
325  struct _filters
326  {
327  C4GUI::CheckBox *showCompatible{ nullptr };
328  C4GUI::CheckBox *showPlayable{ nullptr };
329  } filters;
330  // Whether the last query was successful. No re-fetching will be done.
331  bool queryWasSuccessful = false;
332  // The query will be retried on unsuccessful queries after QueryRetryTimeout seconds.
333  time_t lastQueryEndTime = 0;
334  static const time_t QueryRetryTimeout = 5;
335  // Constructing this automatically checks for existing mods in a different thread.
336  C4StartupModsLocalModDiscovery modsDiscovery;
337  bool requiredSyncWithDiscovery{ false };
338 
339 protected:
340  virtual bool HasBackground() { return false; }
341  virtual void DrawElement(C4TargetFacet &cgo);
342 
343  virtual C4GUI::Control *GetDefaultControl(); // get Auto-Focus control
344  C4GUI::Control *GetDlgModeFocusControl(); // get control to be focused when main tabular sheet changes
345 
346  virtual bool OnEnter() { DoOK(); return true; }
347  virtual bool OnEscape() { DoBack(); return true; }
348  bool KeyBack() { return DoBack(); }
349  bool KeyRefresh();
350  bool KeyForward() { DoOK(); return true; }
351 
352  virtual void OnShown(); // callback when shown: Start searching for games
353  virtual void OnClosed(bool fOK); // callback when dlg got closed: Return to main screen
354  void OnBackBtn(C4GUI::Control *btn) { DoBack(); }
358  void OnUninstallModBtn(C4GUI::Control *btn) { CheckRemoveMod(); }
359  void OnUpdateAllBtn(C4GUI::Control *btn) { CheckUpdateAll(); }
360  void OnSelChange(class C4GUI::Element *pEl) { UpdateSelection(); }
361  void OnSelDblClick(class C4GUI::Element *pEl) { DoOK(); }
363  bool OnSortComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection);
364 
365  C4GUI::Edit::InputResult OnSearchFieldEnter(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
366  { DoOK(); return C4GUI::Edit::IR_Abort; }
367 
368 private:
369  // Deletes lingering updates etc.
370  void CancelRequest();
371 
372  static std::string GetBaseServerURL();
373  static std::string GetOpenClonkVersionStringTag();
374  void QueryModList(bool loadNextPage=false);
375  void ClearList();
376  void UpdateList(bool fGotReference = false, bool onlyWithLocalFiles = false);
377  // When loading from local files, we must pass a fallback ID in case the XML is corrupted.
378  // Otherwise, it wouldn't be possible to even delete installed mods without a valid XML file.
379  struct TiXmlElementLoaderInfo
380  {
381  TiXmlElementLoaderInfo(const TiXmlElement* element, std::string id="", std::string name="") :
382  element(element), id(id), name(name) {}
383  const TiXmlElement* element;
384  const TiXmlElement* operator->() const { return element; }
385  std::string id, name;
386  };
387  void AddToList(std::vector<TiXmlElementLoaderInfo> elements, ModXMLData::Source source);
388  void UpdateSelection();
389  void CheckRemoveMod();
390  void OnConfirmRemoveMod(C4GUI::Element *element);
391  void CheckUpdateAll();
392  //void AddReferenceQuery(const char *szAddress, C4StartupNetListEntry::QueryType eQueryType); // add a ref searcher entry and start searching
393 
394  // Set during update information retrieval.
395  std::unique_ptr<C4HTTPClient> postClient;
396  // Set during downloading of a mod.
397  std::unique_ptr<C4StartupModsDownloader> downloader;
398 
399  // callback from C4Network2ReferenceClient
400  virtual void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData);
401 
402  struct SortingOption
403  {
404  const char * key;
405  const char * titleAsc, * titleDesc;
406  SortingOption(const char * _key, const char * _titleAsc, const char * _titleDesc) :
407  key(_key), titleAsc(_titleAsc), titleDesc(_titleDesc) {}
408  };
409  std::vector<SortingOption> sortingOptions;
410  std::string sortKeySuffix = "";
411 
412  struct _PageInfo
413  {
414  const int maxResultsPerQuery{ 30 };
415  int totalResults{ 0 };
416  int currentlySkipped{ 0 };
417 
418  int getItemsToPage(int itemNumber) const
419  {
420  return itemNumber / maxResultsPerQuery + static_cast<int>(itemNumber % maxResultsPerQuery != 0);
421  }
422  int getCurrentPage() const
423  {
424  return getItemsToPage(currentlySkipped) + 1;
425  }
426  int getTotalPages() const
427  {
428  return getItemsToPage(totalResults);
429  }
430  } pageInfo;
431 public:
432  // The "subscreen" is used by the clonk://installmod protocol and gives a mod ID to search.
433  virtual bool SetSubscreen(const char *toScreen) override;
434  bool DoOK(); // join currently selected item
435  bool DoBack(); // abort dialog
436  void DoRefresh(); // restart network search
437  void QueueSyncWithDiscovery() { requiredSyncWithDiscovery = true; }
438  //void OnReferenceEntryAdd(C4StartupNetListEntry *pEntry);
439 
440  void OnSec1Timer(); // idle proc: update list
441 
444 };
445 
446 
447 #endif // INC_C4StartupModsDlg
C4InteractiveEventType
bool fOK
Definition: C4Gui.h:2083
@ IR_Abort
Definition: C4Gui.h:1258
Definition: C4Rect.h:28
virtual bool SetSubscreen(const char *toScreen) override
C4GUI::Control * GetDlgModeFocusControl()
void OnUpdateAllBtn(C4GUI::Control *btn)
virtual void DrawElement(C4TargetFacet &cgo)
virtual C4GUI::Control * GetDefaultControl()
void OnInstallModBtn(C4GUI::Control *btn)
virtual bool OnEscape()
void OnSelChange(class C4GUI::Element *pEl)
void OnSelDblClick(class C4GUI::Element *pEl)
virtual bool OnEnter()
bool OnSortComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
virtual void OnClosed(bool fOK)
virtual void OnShown()
virtual bool HasBackground()
void OnShowInstalledBtn(C4GUI::Control *btn)
void OnSortComboFill(C4GUI::ComboBox_FillCB *pFiller)
C4GUI::Edit::InputResult OnSearchFieldEnter(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
void OnSearchOnlineBtn(C4GUI::Control *btn)
void OnBackBtn(C4GUI::Control *btn)
void OnUninstallModBtn(C4GUI::Control *btn)
C4StartupModsDownloader(C4StartupModsDlg *parent, const C4StartupModsListEntry *entry)
void OnConfirmInstallation(C4GUI::Element *element)
void AddModToQueue(std::string modID, std::string name)
virtual void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData)
bool IsLoadedFromLocal() const
void UpdateInstalledState(C4StartupModsLocalModDiscovery::ModsInfo *modInfo)
bool Execute()
std::string GetTitle() const
void OnNoResultsFound()
void MakeInfoEntry()
bool IsInstalled() const
const ModXMLData & GetModXMLData() const
void ShowPageInfo(int page, int totalPages, int totalResults)
std::string GetID() const
const TiXmlNode * GetXMLNode() const
~C4StartupModsListEntry()
virtual void DrawElement(C4TargetFacet &cgo)
void FromXML(const TiXmlElement *xml, ModXMLData::Source source, std::string fallbackID="", std::string fallbackName="")
void SetVisibility(bool fToValue)
@ MaxInfoIconCount
@ InfoLabelCount
C4StartupModsListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, class C4StartupModsDlg *pModsDlg)
void Clear()
void OnError(std::string message)
bool IsInfoEntry() const
const std::vector< ModXMLData::FileInfo > & GetFileInfos() const
virtual int32_t GetListItemTopSpacing()
void RemoveMod(const std::string &id)
const std::map< std::string, ModsInfo > & GetAllModInformation()
const bool IsModInstalled(const std::string &id)
C4StartupModsLocalModDiscovery(C4StartupModsDlg *parent)
ModsInfo & AddMod(const std::string &id, const std::string &path, const std::string &name)
ModsInfo GetModInformation(const std::string &id)
bool WaitFor(int)
Definition: StdSync.h:161
bool CheckAndReset()
Definition: StdScheduler.h:107
bool Start()
Icons
Definition: C4Gui.h:638
@ Ico_Resource
Definition: C4Gui.h:651
std::string slug
std::string id
std::string longDescription
bool metadataMissing
std::string description
std::string title
std::vector< std::string > dependencies
std::vector< FileInfo > files
TiXmlNode * originalXMLElement
std::vector< std::string > tags
bool requiresUpdate() const
ModXMLData(const TiXmlElement *xml, Source source=Source::Unknown)