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