doomseekerconfig.cpp
1 //------------------------------------------------------------------------------
2 // doomseekerconfig.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2010 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "doomseekerconfig.h"
24 
25 #include "configuration/queryspeed.h"
26 #include "datapaths.h"
27 #include "fileutils.h"
28 #include "gui/models/serverlistproxymodel.h"
29 #include "ini/ini.h"
30 #include "ini/inisection.h"
31 #include "ini/inivariable.h"
32 #include "ini/settingsproviderqt.h"
33 #include "localizationinfo.h"
34 #include "log.h"
35 #include "pathfinder/filealias.h"
36 #include "pathfinder/filesearchpath.h"
37 #include "plugins/engineplugin.h"
38 #include "scanner.h"
39 #include "strings.hpp"
40 #include "updater/updatechannel.h"
41 #include "version.h"
42 #include "wadseeker/wadseeker.h"
43 
44 #include <QLocale>
48 static PatternList readPre1Point2BuddiesList(const QString &configEntry)
49 {
50  PatternList patterns;
51 
52  Scanner listReader(configEntry.toUtf8().constData(), configEntry.length());
53  // Syntax: {basic|advanced} "pattern";...
54  while (listReader.tokensLeft())
55  {
56  if (!listReader.checkToken(TK_Identifier))
57  break; // Invalid so lets just use what we have.
58 
59  QRegExp::PatternSyntax syntax;
60  if (listReader->str().compare("basic") == 0)
61  syntax = QRegExp::Wildcard;
62  else
63  syntax = QRegExp::RegExp;
64 
65  if (!listReader.checkToken(TK_StringConst))
66  break;
67 
68  QRegExp pattern(listReader->str(), Qt::CaseInsensitive, syntax);
69  if (pattern.isValid())
70  patterns << pattern;
71 
72  if (!listReader.checkToken(';'))
73  break;
74  }
75  return patterns;
76 }
78 DoomseekerConfig *DoomseekerConfig::instance = nullptr;
79 
80 DoomseekerConfig::DoomseekerConfig()
81 {
82  this->dummySection = new IniSection(nullptr, QString());
83 }
84 
85 DoomseekerConfig::~DoomseekerConfig()
86 {
87  delete this->dummySection;
88 }
89 
91 {
92  if (instance == nullptr)
93  instance = new DoomseekerConfig();
94 
95  return *instance;
96 }
97 
99 {
100  if (instance != nullptr)
101  {
102  delete instance;
103  instance = nullptr;
104  }
105 }
106 
108 {
109  if (pluginName.isEmpty())
110  {
111  gLog << QObject::tr("DoomseekerConfig.iniSectionForPlugin(): empty plugin name has been specified, returning dummy IniSection.");
112  return *dummySection;
113  }
114 
115  if (!isValidPluginName(pluginName))
116  {
117  gLog << QObject::tr("DoomseekerConfig.iniSectionForPlugin(): plugin name is invalid: %1").arg(pluginName);
118  return *dummySection;
119  }
120 
121  if (this->pIni == nullptr)
122  setIniFile("");
123 
124  QString sectionName = pluginName;
125  sectionName = sectionName.replace(' ', "");
126  return this->pIni->section(sectionName);
127 }
128 
130 {
131  return iniSectionForPlugin(plugin->data()->name);
132 }
133 
134 bool DoomseekerConfig::isValidPluginName(const QString &pluginName) const
135 {
136  QString invalids[] = { "doomseeker", "wadseeker", "" };
137 
138  for (int i = 0; !invalids[i].isEmpty(); ++i)
139  {
140  if (pluginName.compare(invalids[i], Qt::CaseInsensitive) == 0)
141  return false;
142  }
143 
144  return true;
145 }
146 
148 {
149  if (pIni == nullptr)
150  return false;
151 
152  IniSection sectionDoomseeker = pIni->section(doomseeker.SECTION_NAME);
153  doomseeker.load(sectionDoomseeker);
154 
155  IniSection sectionServerFilter = pIni->section(serverFilter.SECTION_NAME);
156  serverFilter.load(sectionServerFilter);
157 
158  IniSection sectionWadseeker = pIni->section(wadseeker.SECTION_NAME);
159  wadseeker.load(sectionWadseeker);
160 
161  IniSection sectionAutoUpdates = pIni->section(autoUpdates.SECTION_NAME);
162  autoUpdates.load(sectionAutoUpdates);
163 
164  // Plugins should read their sections manually.
165 
166  return true;
167 }
168 
170 {
171  if (pIni == nullptr)
172  return false;
173 
174  /*
175  TODO:
176  Find a way to work around this.
177  const QString TOP_COMMENT = QObject::tr("This is %1 configuration file.\n\
178  Any modification done manually to this file is on your own risk.").arg(Version::fullVersionInfo());
179 
180  pIni->setIniTopComment(TOP_COMMENT);
181  */
182  IniSection sectionDoomseeker = pIni->section(doomseeker.SECTION_NAME);
183  doomseeker.save(sectionDoomseeker);
184 
185  IniSection sectionServerFilter = pIni->section(serverFilter.SECTION_NAME);
186  serverFilter.save(sectionServerFilter);
187 
188  IniSection sectionWadseeker = pIni->section(wadseeker.SECTION_NAME);
189  wadseeker.save(sectionWadseeker);
190 
191  IniSection sectionAutoUpdates = pIni->section(autoUpdates.SECTION_NAME);
192  autoUpdates.save(sectionAutoUpdates);
193 
194  // Plugins should save their sections manually.
195  if (this->settings->isWritable())
196  {
197  this->settings->sync();
198  return true;
199  }
200  return false;
201 }
202 
203 bool DoomseekerConfig::setIniFile(const QString &filePath)
204 {
205  // Delete old instances if necessary.
206  this->pIni.reset();
207  this->settingsProvider.reset();
208  this->settings.reset();
209 
210  gLog << QObject::tr("Setting INI file: %1").arg(filePath);
211  // Create new instances.
212  this->settings.reset(new QSettings(filePath, QSettings::IniFormat));
213  this->settingsProvider.reset(new SettingsProviderQt(this->settings.data()));
214  this->pIni.reset(new Ini(this->settingsProvider.data()));
215 
216  // Init.
217  IniSection section;
218 
219  section = this->pIni->section(doomseeker.SECTION_NAME);
220  doomseeker.init(section);
221 
222  section = this->pIni->section(serverFilter.SECTION_NAME);
223  serverFilter.init(section);
224 
225  section = this->pIni->section(wadseeker.SECTION_NAME);
226  wadseeker.init(section);
227 
228  section = this->pIni->section(autoUpdates.SECTION_NAME);
229  autoUpdates.init(section);
230 
231  return true;
232 }
233 
234 QList<FileSearchPath> DoomseekerConfig::combinedWadseekPaths() const
235 {
236  QList<FileSearchPath> paths = doomseeker.wadPaths;
237  paths << wadseeker.targetDirectory;
238  return paths;
239 }
240 
242 DClass<DoomseekerConfig::DoomseekerCfg>
243 {
244 public:
245  IniSection section;
246  QuerySpeed querySpeed;
247 
248  QString slotStyle() const
249  {
250  // Slot styles were indexed in older versions of Doomseeker.
251  // This here provides compatibility layer that allows to load configuration
252  // files from those versions.
253  const int NUM_SLOTSTYLES = 2;
254  const char *indexedSlotStyles[NUM_SLOTSTYLES] = { "marines", "blocks" };
255  bool isInt = false;
256  int numeric = section["SlotStyle"].value().toInt(&isInt);
257  if (isInt && numeric >= 0 && numeric < NUM_SLOTSTYLES)
258  return indexedSlotStyles[numeric];
259  else
260  return section["SlotStyle"].valueString();
261  }
262 };
263 
265 
266 const QString DoomseekerConfig::DoomseekerCfg::SECTION_NAME = "Doomseeker";
267 
268 DoomseekerConfig::DoomseekerCfg::DoomseekerCfg()
269 {
270  this->bBotsAreNotPlayers = true;
271  this->bCloseToTrayIcon = false;
272  this->bColorizeServerConsole = true;
273  this->bDrawGridInServerTable = false;
274  this->bHidePasswords = false;
275  this->bGroupServersWithPlayersAtTheTopOfTheList = true;
276  this->bIP2CountryAutoUpdate = true;
277  this->bLogCreatedServer = false;
278  this->bLookupHosts = true;
279  this->bQueryAutoRefreshDontIfActive = true;
280  this->bQueryAutoRefreshEnabled = false;
281  this->bQueryBeforeLaunch = true;
282  this->bQueryOnStartup = true;
283  this->bRecordDemo = false;
284  this->bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn = true;
285  this->bCheckTheIntegrityOfWads = true;
286  this->bUseTrayIcon = false;
287  this->bMarkServersWithBuddies = true;
288  this->buddyServersColor = "#5ecf75";
289  this->customServersColor = "#ffaa00";
290  this->lanServersColor = "#92ebe5";
291  this->localization = LocalizationInfo::SYSTEM_FOLLOW.localeName;
292  this->mainWindowState = "";
293  this->mainWindowGeometry = "";
294  this->queryAutoRefreshEverySeconds = 180;
295  setQuerySpeed(QuerySpeed::aggressive());
296  this->previousCreateServerConfigDir = "";
297  this->previousCreateServerExecDir = "";
298  this->previousCreateServerLogDir = "";
299  this->previousCreateServerWadDir = "";
300  this->slotStyle = 1;
301  this->serverListSortIndex = -1;
302  this->serverListSortDirection = Qt::DescendingOrder;
303  this->wadPaths = FileSearchPath::fromStringList(gDefaultDataPaths->defaultWadPaths());
304 }
305 
306 DoomseekerConfig::DoomseekerCfg::~DoomseekerCfg()
307 {
308 }
309 
310 QList<ColumnSort> DoomseekerConfig::DoomseekerCfg::additionalSortColumns() const
311 {
312  QList<ColumnSort> list;
313  QVariantList varList = d->section.value("AdditionalSortColumns").toList();
314  for (const QVariant &var : varList)
315  {
316  list << ColumnSort::deserializeQVariant(var);
317  }
318  return list;
319 }
320 
321 void DoomseekerConfig::DoomseekerCfg::setAdditionalSortColumns(const QList<ColumnSort> &val)
322 {
323  QVariantList varList;
324  for (const ColumnSort &elem : val)
325  {
326  varList << elem.serializeQVariant();
327  }
328  d->section.setValue("AdditionalSortColumns", varList);
329 }
330 
332 {
333  // TODO: Make all methods use d->section
334  d->section = section;
335  section.createSetting("Localization", this->localization);
336  section.createSetting("BotsAreNotPlayers", this->bBotsAreNotPlayers);
337  section.createSetting("CloseToTrayIcon", this->bCloseToTrayIcon);
338  section.createSetting("ColorizeServerConsole", this->bColorizeServerConsole);
339  section.createSetting("DrawGridInServerTable", this->bDrawGridInServerTable);
340  section.createSetting("HidePasswords", this->bHidePasswords);
341  section.createSetting("GroupServersWithPlayersAtTheTopOfTheList", this->bGroupServersWithPlayersAtTheTopOfTheList);
342  section.createSetting("IP2CAutoUpdate", this->bIP2CountryAutoUpdate);
343  section.createSetting("LogCreatedServers", this->bLogCreatedServer);
344  section.createSetting("LookupHosts", this->bLookupHosts);
345  section.createSetting("QueryAutoRefreshDontIfActive", this->bQueryAutoRefreshDontIfActive);
346  section.createSetting("QueryAutoRefreshEnabled", this->bQueryAutoRefreshEnabled);
347  section.createSetting("QueryOnStartup", this->bQueryOnStartup);
348  section.createSetting("RecordDemo", this->bRecordDemo);
349  section.createSetting("TellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn", this->bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn);
350  section.createSetting("CheckTheIntegrityOfWads", this->bCheckTheIntegrityOfWads);
351  section.createSetting("UseTrayIcon", this->bUseTrayIcon);
352  section.createSetting("MarkServersWithBuddies", this->bMarkServersWithBuddies);
353  section.createSetting("BuddyServersColor", this->buddyServersColor);
354  section.createSetting("CustomServersColor", this->customServersColor);
355  section.createSetting("LanServersColor", this->lanServersColor);
356  section.createSetting("QueryAutoRefreshEverySeconds", this->queryAutoRefreshEverySeconds);
357  section.createSetting("QueryServerInterval", this->querySpeed().intervalBetweenServers);
358  section.createSetting("QueryServerTimeout", this->querySpeed().delayBetweenSingleServerAttempts);
359  section.createSetting("QueryAttemptsPerServer", this->querySpeed().attemptsPerServer);
360  section.createSetting("SlotStyle", this->slotStyle);
361  section.createSetting("ServerListSortIndex", this->serverListSortIndex);
362  section.createSetting("ServerListSortDirection", this->serverListSortDirection);
363  section.createSetting("WadPaths", FileSearchPath::toVariantList(this->wadPaths));
364 
365  initWadAlias();
366 }
367 
368 void DoomseekerConfig::DoomseekerCfg::initWadAlias()
369 {
370  if (!d->section.hasSetting("WadAliases"))
371  setWadAliases(FileAlias::standardWadAliases());
372 }
373 
374 void DoomseekerConfig::DoomseekerCfg::load(IniSection &section)
375 {
376  this->localization = (const QString &)section["Localization"];
377  this->bBotsAreNotPlayers = section["BotsAreNotPlayers"];
378  this->bCloseToTrayIcon = section["CloseToTrayIcon"];
379  this->bColorizeServerConsole = section["ColorizeServerConsole"];
380  this->bDrawGridInServerTable = section["DrawGridInServerTable"];
381  this->bHidePasswords = section["HidePasswords"];
382  this->bGroupServersWithPlayersAtTheTopOfTheList = section["GroupServersWithPlayersAtTheTopOfTheList"];
383  this->bIP2CountryAutoUpdate = section["IP2CAutoUpdate"];
384  this->bLogCreatedServer = section["LogCreatedServers"];
385  this->bLookupHosts = section["LookupHosts"];
386  this->bQueryAutoRefreshDontIfActive = section["QueryAutoRefreshDontIfActive"];
387  this->bQueryAutoRefreshEnabled = section["QueryAutoRefreshEnabled"];
388  this->bQueryBeforeLaunch = section["QueryBeforeLaunch"];
389  this->bQueryOnStartup = section["QueryOnStartup"];
390  this->bRecordDemo = section["RecordDemo"];
391  this->bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn = section["TellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn"];
392  this->bCheckTheIntegrityOfWads = section["CheckTheIntegrityOfWads"];
393  this->bUseTrayIcon = section["UseTrayIcon"];
394  this->bMarkServersWithBuddies = section["MarkServersWithBuddies"];
395  this->buddyServersColor = (const QString &)section["BuddyServersColor"];
396  this->customServersColor = (const QString &)section["CustomServersColor"];
397  this->lanServersColor = (const QString &)section["LanServersColor"];
398  this->mainWindowState = (const QString &)section["MainWindowState"];
399  this->mainWindowGeometry = section.value("MainWindowGeometry", "").toByteArray();
400  this->queryAutoRefreshEverySeconds = section["QueryAutoRefreshEverySeconds"];
401  d->querySpeed.intervalBetweenServers = section["QueryServerInterval"];
402  d->querySpeed.delayBetweenSingleServerAttempts = section["QueryServerTimeout"];
403  d->querySpeed.attemptsPerServer = section["QueryAttemptsPerServer"];
404  this->previousCreateServerConfigDir = (const QString &)section["PreviousCreateServerConfigDir"];
405  this->previousCreateServerExecDir = (const QString &)section["PreviousCreateServerExecDir"];
406  this->previousCreateServerLogDir = (const QString &)section["PreviousCreateServerLogDir"];
407  this->previousCreateServerWadDir = (const QString &)section["PreviousCreateServerWadDir"];
408  this->serverListColumnState = (const QString &)section["ServerListColumnState"];
409  this->serverListSortIndex = section["ServerListSortIndex"];
410  this->serverListSortDirection = section["ServerListSortDirection"];
411  this->slotStyle = d->slotStyle();
412 
413  // Complex data variables.
414 
415  // Custom servers
416  // CustomServers2 is for Doomseeker >= 1.2.
417  // Deserialization of this setting is not backwards-compatible,
418  // hence we need an extra compatibility layer. The amount of
419  // information that the user could potentially lose if I had
420  // been too lazy to code this would be insufferable.
421  QList<CustomServerInfo> customServersList;
422  CustomServers::decodeConfigEntries(section["CustomServers2"], customServersList);
423  QList<CustomServerInfo> backwardCompatibleServers;
424  CustomServers::decodeConfigEntries(section["CustomServers"], backwardCompatibleServers);
425  for (const CustomServerInfo &backwardCompatibleServer : backwardCompatibleServers)
426  {
427  bool known = false;
428  for (const CustomServerInfo &knownServer : customServersList)
429  {
430  if (knownServer.isSameServer(backwardCompatibleServer))
431  {
432  known = true;
433  break;
434  }
435  }
436  if (!known)
437  customServersList << backwardCompatibleServer;
438  }
439  this->customServers = customServersList.toVector();
440 
441  // WAD paths
442  // Backward compatibility, XXX once new stable is released:
443  QVariant variantWadPaths = section["WadPaths"].value();
444  if (variantWadPaths.isValid() && variantWadPaths.toList().isEmpty())
445  {
446  // Backward compatibility continued:
447  wadPaths.clear();
448  QStringList paths = variantWadPaths.toString().split(";");
449  for (const QString &path : paths)
450  {
451  wadPaths << FileSearchPath(path);
452  }
453  }
454  else
455  {
456  // This is not a part of XXX, this is proper, current behavior:
457  wadPaths = FileSearchPath::fromVariantList(section["WadPaths"].value().toList());
458  }
459  // End of backward compatibility for WAD paths.
460 
461  // Buddies list
462  if (section.hasSetting("Buddies"))
463  this->buddies = PatternList::deserializeQVariant(section.value("Buddies"));
464  else if (section.hasSetting("BuddiesList"))
465  {
466  // Backward compatibility, pre 1.2.
467  this->buddies = readPre1Point2BuddiesList(section["BuddiesList"]);
468  }
469 }
470 
471 void DoomseekerConfig::DoomseekerCfg::save(IniSection &section)
472 {
473  section["Localization"] = this->localization;
474  section["BotsAreNotPlayers"] = this->bBotsAreNotPlayers;
475  section["CloseToTrayIcon"] = this->bCloseToTrayIcon;
476  section["ColorizeServerConsole"] = this->bColorizeServerConsole;
477  section["DrawGridInServerTable"] = this->bDrawGridInServerTable;
478  section["HidePasswords"] = this->bHidePasswords;
479  section["GroupServersWithPlayersAtTheTopOfTheList"] = this->bGroupServersWithPlayersAtTheTopOfTheList;
480  section["IP2CAutoUpdate"] = this->bIP2CountryAutoUpdate;
481  section["LogCreatedServers"] = this->bLogCreatedServer;
482  section["LookupHosts"] = this->bLookupHosts;
483  section["QueryAutoRefreshDontIfActive"] = this->bQueryAutoRefreshDontIfActive;
484  section["QueryAutoRefreshEnabled"] = this->bQueryAutoRefreshEnabled;
485  section["QueryBeforeLaunch"] = this->bQueryBeforeLaunch;
486  section["QueryOnStartup"] = this->bQueryOnStartup;
487  section["RecordDemo"] = this->bRecordDemo;
488  section["TellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn"] = this->bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn;
489  section["CheckTheIntegrityOfWads"] = this->bCheckTheIntegrityOfWads;
490  section["UseTrayIcon"] = this->bUseTrayIcon;
491  section["MarkServersWithBuddies"] = this->bMarkServersWithBuddies;
492  section["BuddyServersColor"] = this->buddyServersColor;
493  section["CustomServersColor"] = this->customServersColor;
494  section["LanServersColor"] = this->lanServersColor;
495  section["MainWindowState"] = this->mainWindowState;
496  section.setValue("MainWindowGeometry", this->mainWindowGeometry);
497  section["QueryAutoRefreshEverySeconds"] = this->queryAutoRefreshEverySeconds;
498  section["QueryServerInterval"] = this->querySpeed().intervalBetweenServers;
499  section["QueryServerTimeout"] = this->querySpeed().delayBetweenSingleServerAttempts;
500  section["QueryAttemptsPerServer"] = this->querySpeed().attemptsPerServer;
501  section["PreviousCreateServerConfigDir"] = this->previousCreateServerConfigDir;
502  section["PreviousCreateServerExecDir"] = this->previousCreateServerExecDir;
503  section["PreviousCreateServerLogDir"] = this->previousCreateServerLogDir;
504  section["PreviousCreateServerWadDir"] = this->previousCreateServerWadDir;
505  section["ServerListColumnState"] = this->serverListColumnState;
506  section["ServerListSortIndex"] = this->serverListSortIndex;
507  section["ServerListSortDirection"] = this->serverListSortDirection;
508  section["SlotStyle"] = this->slotStyle;
509 
510  // Complex data variables.
511 
512  // Custom servers
513  QStringList allCustomServers; // backward compatibility for Doomseeker <1.2
514  QStringList allCustomServers2; // Doomseeker >=1.2
515  for (const CustomServerInfo &customServer : this->customServers)
516  {
517  QString engineName = QUrl::toPercentEncoding(customServer.engine, "", "()");
518  QString address = QUrl::toPercentEncoding(customServer.host, "", "()");
519 
520  QString customServerStringPrefix = QString("(%1;%2;%3")
521  .arg(engineName).arg(address)
522  .arg(customServer.port);
523  QString customServerString2 = QString("%1;%2)")
524  .arg(customServerStringPrefix)
525  .arg(customServer.enabled ? 1 : 0);
526 
527  allCustomServers << customServerStringPrefix + ")";
528  allCustomServers2 << customServerString2;
529  }
530  section["CustomServers"] = allCustomServers.join(";");
531  section["CustomServers2"] = allCustomServers2.join(";");
532 
533  section["WadPaths"].setValue(FileSearchPath::toVariantList(this->wadPaths));
534  section["Buddies"].setValue(this->buddies.serializeQVariant());
535 }
536 
537 const QuerySpeed &DoomseekerConfig::DoomseekerCfg::querySpeed() const
538 {
539  return d->querySpeed;
540 }
541 
542 void DoomseekerConfig::DoomseekerCfg::setQuerySpeed(const QuerySpeed &val)
543 {
544  d->querySpeed = val;
545 }
546 
547 QList<FileAlias> DoomseekerConfig::DoomseekerCfg::wadAliases() const
548 {
549  QList<FileAlias> list;
550  QVariantList varList = d->section.value("WadAliases").toList();
551  for (const QVariant &var : varList)
552  {
553  list << FileAlias::deserializeQVariant(var);
554  }
555  return FileAliasList::mergeDuplicates(list);
556 }
557 
558 void DoomseekerConfig::DoomseekerCfg::setWadAliases(const QList<FileAlias> &val)
559 {
560  QVariantList varList;
561  for (const FileAlias &elem : val)
562  {
563  varList << elem.serializeQVariant();
564  }
565  d->section.setValue("WadAliases", varList);
566 }
567 
568 void DoomseekerConfig::DoomseekerCfg::enableFreedoomInstallation(const QString &dir)
569 {
570  if (!FileUtils::containsPath(wadPathsOnly(), dir))
571  wadPaths.prepend(dir);
572  QList<FileAlias> aliases = wadAliases();
573  aliases << FileAlias::freeDoom1Aliases();
574  aliases << FileAlias::freeDoom2Aliases();
575  aliases = FileAliasList::mergeDuplicates(aliases);
576  setWadAliases(aliases);
577 }
578 
579 QStringList DoomseekerConfig::DoomseekerCfg::wadPathsOnly() const
580 {
581  QStringList result;
582  for (const FileSearchPath &path : wadPaths)
583  {
584  result << path.path();
585  }
586  return result;
587 }
589 const QString DoomseekerConfig::AutoUpdates::SECTION_NAME = "Doomseeker_AutoUpdates";
590 
591 void DoomseekerConfig::AutoUpdates::init(IniSection &section)
592 {
593  section.createSetting("UpdateChannelName", UpdateChannel::mkStable().name());
594  section.createSetting("UpdateMode", (int) UM_NotifyOnly);
595  section.createSetting("LastKnownUpdateRevisions", QVariant());
596  section.createSetting("bPerformUpdateOnNextRun", false);
597 }
598 
599 void DoomseekerConfig::AutoUpdates::load(IniSection &section)
600 {
601  updateChannelName = (const QString &)section["UpdateChannelName"];
602  updateMode = (UpdateMode)section["UpdateMode"].value().toInt();
603  QVariantMap lastKnownUpdateRevisionsVariant = section["LastKnownUpdateRevisions"].value().toMap();
604  lastKnownUpdateRevisions.clear();
605  for (const QString &package : lastKnownUpdateRevisionsVariant.keys())
606  {
607  QVariant revisionVariant = lastKnownUpdateRevisionsVariant[package];
608  lastKnownUpdateRevisions.insert(package, revisionVariant.toString());
609  }
610  bPerformUpdateOnNextRun = section["bPerformUpdateOnNextRun"].value().toBool();
611 }
612 
613 void DoomseekerConfig::AutoUpdates::save(IniSection &section)
614 {
615  section["UpdateChannelName"] = updateChannelName;
616  section["UpdateMode"] = updateMode;
617  QVariantMap revisionsVariantMap;
618  for (const QString &package : lastKnownUpdateRevisions.keys())
619  {
620  revisionsVariantMap.insert(package, lastKnownUpdateRevisions[package]);
621  }
622  section["LastKnownUpdateRevisions"].setValue(revisionsVariantMap);
623  section["bPerformUpdateOnNextRun"].setValue(bPerformUpdateOnNextRun);
624 }
626 const QString DoomseekerConfig::ServerFilter::SECTION_NAME = "ServerFilter";
627 
628 void DoomseekerConfig::ServerFilter::init(IniSection &section)
629 {
630  section.createSetting("bEnabled", true);
631  section.createSetting("bShowEmpty", true);
632  section.createSetting("bShowFull", true);
633  section.createSetting("bShowOnlyValid", false);
634  section.createSetting("GameModes", QStringList());
635  section.createSetting("GameModesExcluded", QStringList());
636  section.createSetting("MaxPing", 0);
637  section.createSetting("ServerName", "");
638  section.createSetting("TestingServers", Doomseeker::Indifferent);
639  section.createSetting("WADs", QStringList());
640  section.createSetting("WADsExcluded", QStringList());
641 }
642 
643 void DoomseekerConfig::ServerFilter::load(IniSection &section)
644 {
645  info.bEnabled = section["bEnabled"];
646  info.bShowEmpty = section["bShowEmpty"];
647  info.bShowFull = section["bShowFull"];
648  info.bShowOnlyValid = section["bShowOnlyValid"];
649  info.gameModes = section["GameModes"].value().toStringList();
650  info.gameModesExcluded = section["GameModesExcluded"].value().toStringList();
651  info.maxPing = section["MaxPing"];
652  info.serverName = (const QString &)section["ServerName"];
653  info.testingServers = static_cast<Doomseeker::ShowMode>(section.value("TestingServers").toInt());
654  info.wads = section["WADs"].value().toStringList();
655  info.wadsExcluded = section["WADsExcluded"].value().toStringList();
656 }
657 
658 void DoomseekerConfig::ServerFilter::save(IniSection &section)
659 {
660  section["bEnabled"] = info.bEnabled;
661  section["bShowEmpty"] = info.bShowEmpty;
662  section["bShowFull"] = info.bShowFull;
663  section["bShowOnlyValid"] = info.bShowOnlyValid;
664  section["GameModes"].setValue(info.gameModes);
665  section["GameModesExcluded"].setValue(info.gameModesExcluded);
666  section["MaxPing"] = info.maxPing;
667  section["ServerName"] = info.serverName;
668  section["TestingServers"] = info.testingServers;
669  section["WADs"].setValue(info.wads);
670  section["WADsExcluded"].setValue(info.wadsExcluded);
671 }
673 const QString DoomseekerConfig::WadseekerCfg::SECTION_NAME = "Wadseeker";
674 
675 DoomseekerConfig::WadseekerCfg::WadseekerCfg()
676 {
677  this->bAlwaysUseDefaultSites = true;
678  this->bSearchInIdgames = true;
679  this->bSearchInWadArchive = true;
680  this->colorMessageCriticalError = "#ff0000";
681  this->colorMessageError = "#ff0000";
682  this->colorMessageNotice = "#000000";
683  this->connectTimeoutSeconds = WADSEEKER_CONNECT_TIMEOUT_SECONDS_DEFAULT;
684  this->downloadTimeoutSeconds = WADSEEKER_DOWNLOAD_TIMEOUT_SECONDS_DEFAULT;
685  this->idgamesURL = Wadseeker::defaultIdgamesUrl();
686  this->maxConcurrentSiteDownloads = 3;
687  this->maxConcurrentWadDownloads = 2;
688  this->targetDirectory = gDefaultDataPaths->programsDataDirectoryPath();
689 
690  // Search URLs remains uninitialized here. It will be initialized
691  // by init() and then load() since Doomseeker always calls these
692  // methods in this order.
693 }
694 
696 {
697  section.createSetting("AlwaysUseDefaultSites", this->bAlwaysUseDefaultSites);
698  section.createSetting("SearchInIdgames", this->bSearchInIdgames);
699  section.createSetting("SearchInWadArchive", this->bSearchInWadArchive);
700  section.createSetting("ColorMessageCriticalError", this->colorMessageCriticalError);
701  section.createSetting("ColorMessageError", this->colorMessageError);
702  section.createSetting("ColorMessageNotice", this->colorMessageNotice);
703  section.createSetting("ConnectTimeoutSeconds", this->connectTimeoutSeconds);
704  section.createSetting("DownloadTimeoutSeconds", this->downloadTimeoutSeconds);
705  section.createSetting("IdgamesApiURL", this->idgamesURL);
706  section.createSetting("MaxConcurrentSiteDownloads", this->maxConcurrentSiteDownloads);
707  section.createSetting("MaxConcurrentWadDownloads", this->maxConcurrentWadDownloads);
708  section.createSetting("SearchURLs", Wadseeker::defaultSitesListEncoded().join(";"));
709  section.createSetting("TargetDirectory", this->targetDirectory);
710 }
711 
712 void DoomseekerConfig::WadseekerCfg::load(IniSection &section)
713 {
714  this->bAlwaysUseDefaultSites = section["AlwaysUseDefaultSites"];
715  this->bSearchInIdgames = section["SearchInIdgames"];
716  this->bSearchInWadArchive = section["SearchInWadArchive"];
717  this->colorMessageCriticalError = (const QString &)section["ColorMessageCriticalError"];
718  this->colorMessageError = (const QString &)section["ColorMessageError"];
719  this->colorMessageNotice = (const QString &)section["ColorMessageNotice"];
720  this->connectTimeoutSeconds = section["ConnectTimeoutSeconds"];
721  this->downloadTimeoutSeconds = section["DownloadTimeoutSeconds"];
722  this->idgamesURL = (const QString &)section["IdgamesApiURL"];
723  this->maxConcurrentSiteDownloads = section["MaxConcurrentSiteDownloads"];
724  this->maxConcurrentWadDownloads = section["MaxConcurrentWadDownloads"];
725  this->targetDirectory = (const QString &)section["TargetDirectory"];
726 
727  // Complex data values
728  this->searchURLs.clear();
729  QStringList urlList = section["SearchURLs"].valueString().split(";", QString::SkipEmptyParts);
730  for (const QString &url : urlList)
731  {
732  this->searchURLs << QUrl::fromPercentEncoding(url.toUtf8());
733  }
734 }
735 
736 void DoomseekerConfig::WadseekerCfg::save(IniSection &section)
737 {
738  section["AlwaysUseDefaultSites"] = this->bAlwaysUseDefaultSites;
739  section["SearchInIdgames"] = this->bSearchInIdgames;
740  section["SearchInWadArchive"] = this->bSearchInWadArchive;
741  section["ColorMessageCriticalError"] = this->colorMessageCriticalError;
742  section["ColorMessageError"] = this->colorMessageError;
743  section["ColorMessageNotice"] = this->colorMessageNotice;
744  section["ConnectTimeoutSeconds"] = this->connectTimeoutSeconds;
745  section["DownloadTimeoutSeconds"] = this->downloadTimeoutSeconds;
746  section["IdgamesApiURL"] = this->idgamesURL;
747  section["MaxConcurrentSiteDownloads"] = this->maxConcurrentSiteDownloads;
748  section["MaxConcurrentWadDownloads"] = this->maxConcurrentWadDownloads;
749  section["TargetDirectory"] = this->targetDirectory;
750 
751  // Complex data values
752  QStringList urlEncodedList;
753  for (const QString &url : this->searchURLs)
754  {
755  urlEncodedList << QUrl::toPercentEncoding(url);
756  }
757  section["SearchURLs"] = urlEncodedList.join(";");
758 }