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