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  Pattern::Syntax syntax;
60  if (listReader->str().compare("basic") == 0)
61  syntax = Pattern::Wildcard;
62  else
63  syntax = Pattern::RegExp;
64 
65  if (!listReader.checkToken(TK_StringConst))
66  break;
67 
68  Pattern pattern(listReader->str(), QRegularExpression::CaseInsensitiveOption, 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->bHonorServerCountries = true;
276  this->bGroupServersWithPlayersAtTheTopOfTheList = 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->bUseTrayIcon = false;
288  this->bMarkServersWithBuddies = true;
289  this->buddyServersColor = "#5ecf75";
290  this->customServersColor = "#ffaa00";
291  this->lanServersColor = "#92ebe5";
292  this->localization = LocalizationInfo::SYSTEM_FOLLOW.localeName;
293  this->mainWindowState = "";
294  this->mainWindowGeometry = "";
295  this->queryAutoRefreshEverySeconds = 180;
296  setQuerySpeed(QuerySpeed::aggressive());
297  this->previousCreateServerConfigDir = "";
298  this->previousCreateServerExecDir = "";
299  this->previousCreateServerLogDir = "";
300  this->previousCreateServerWadDir = "";
301  this->slotStyle = "blocks";
302  this->serverListSortIndex = -1;
303  this->serverListSortDirection = Qt::DescendingOrder;
304  this->wadPaths = FileSearchPath::fromStringList(gDefaultDataPaths->defaultWadPaths());
305 }
306 
307 DoomseekerConfig::DoomseekerCfg::~DoomseekerCfg()
308 {
309 }
310 
311 QList<ColumnSort> DoomseekerConfig::DoomseekerCfg::additionalSortColumns() const
312 {
313  QList<ColumnSort> list;
314  QVariantList varList = d->section.value("AdditionalSortColumns").toList();
315  for (const QVariant &var : varList)
316  {
317  list << ColumnSort::deserializeQVariant(var);
318  }
319  return list;
320 }
321 
322 void DoomseekerConfig::DoomseekerCfg::setAdditionalSortColumns(const QList<ColumnSort> &val)
323 {
324  QVariantList varList;
325  for (const ColumnSort &elem : val)
326  {
327  varList << elem.serializeQVariant();
328  }
329  d->section.setValue("AdditionalSortColumns", varList);
330 }
331 
333 {
334  // TODO: Make all methods use d->section
335  d->section = section;
336  section.createSetting("Localization", this->localization);
337  section.createSetting("BotsAreNotPlayers", this->bBotsAreNotPlayers);
338  section.createSetting("CloseToTrayIcon", this->bCloseToTrayIcon);
339  section.createSetting("ColorizeServerConsole", this->bColorizeServerConsole);
340  section.createSetting("DrawGridInServerTable", this->bDrawGridInServerTable);
341  section.createSetting("HidePasswords", this->bHidePasswords);
342  section.createSetting("HonorServerCountries", this->bHonorServerCountries);
343  section.createSetting("GroupServersWithPlayersAtTheTopOfTheList", this->bGroupServersWithPlayersAtTheTopOfTheList);
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("UseTrayIcon", this->bUseTrayIcon);
354  section.createSetting("MarkServersWithBuddies", this->bMarkServersWithBuddies);
355  section.createSetting("BuddyServersColor", this->buddyServersColor);
356  section.createSetting("CustomServersColor", this->customServersColor);
357  section.createSetting("LanServersColor", this->lanServersColor);
358  section.createSetting("QueryAutoRefreshEverySeconds", this->queryAutoRefreshEverySeconds);
359  section.createSetting("QueryServerInterval", this->querySpeed().intervalBetweenServers);
360  section.createSetting("QueryServerTimeout", this->querySpeed().delayBetweenSingleServerAttempts);
361  section.createSetting("QueryAttemptsPerServer", this->querySpeed().attemptsPerServer);
362  section.createSetting("SlotStyle", this->slotStyle);
363  section.createSetting("ServerListSortIndex", this->serverListSortIndex);
364  section.createSetting("ServerListSortDirection", this->serverListSortDirection);
365  section.createSetting("WadPaths", FileSearchPath::toVariantList(this->wadPaths));
366 
367  initWadAlias();
368 }
369 
370 void DoomseekerConfig::DoomseekerCfg::initWadAlias()
371 {
372  if (!d->section.hasSetting("WadAliases"))
373  setWadAliases(FileAlias::standardWadAliases());
374 }
375 
376 void DoomseekerConfig::DoomseekerCfg::load(IniSection &section)
377 {
378  this->localization = (const QString &)section["Localization"];
379  this->bBotsAreNotPlayers = section["BotsAreNotPlayers"];
380  this->bCloseToTrayIcon = section["CloseToTrayIcon"];
381  this->bColorizeServerConsole = section["ColorizeServerConsole"];
382  this->bDrawGridInServerTable = section["DrawGridInServerTable"];
383  this->bHidePasswords = section["HidePasswords"];
384  this->bHonorServerCountries = section["HonorServerCountries"];
385  this->bGroupServersWithPlayersAtTheTopOfTheList = section["GroupServersWithPlayersAtTheTopOfTheList"];
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->bUseTrayIcon = section["UseTrayIcon"];
397  this->bMarkServersWithBuddies = section["MarkServersWithBuddies"];
398  this->buddyServersColor = (const QString &)section["BuddyServersColor"];
399  this->customServersColor = (const QString &)section["CustomServersColor"];
400  this->lanServersColor = (const QString &)section["LanServersColor"];
401  this->mainWindowState = (const QString &)section["MainWindowState"];
402  this->mainWindowGeometry = section.value("MainWindowGeometry", "").toByteArray();
403  this->queryAutoRefreshEverySeconds = section["QueryAutoRefreshEverySeconds"];
404  d->querySpeed.intervalBetweenServers = section["QueryServerInterval"];
405  d->querySpeed.delayBetweenSingleServerAttempts = section["QueryServerTimeout"];
406  d->querySpeed.attemptsPerServer = section["QueryAttemptsPerServer"];
407  this->previousCreateServerConfigDir = (const QString &)section["PreviousCreateServerConfigDir"];
408  this->previousCreateServerExecDir = (const QString &)section["PreviousCreateServerExecDir"];
409  this->previousCreateServerLogDir = (const QString &)section["PreviousCreateServerLogDir"];
410  this->previousCreateServerWadDir = (const QString &)section["PreviousCreateServerWadDir"];
411  this->serverListColumnState = (const QString &)section["ServerListColumnState"];
412  this->serverListSortIndex = section["ServerListSortIndex"];
413  this->serverListSortDirection = section["ServerListSortDirection"];
414  this->slotStyle = d->slotStyle();
415 
416  // Complex data variables.
417 
418  // Custom servers
419  // CustomServers2 is for Doomseeker >= 1.2.
420  // Deserialization of this setting is not backwards-compatible,
421  // hence we need an extra compatibility layer. The amount of
422  // information that the user could potentially lose if I had
423  // been too lazy to code this would be insufferable.
424  QList<CustomServerInfo> customServersList;
425  CustomServers::decodeConfigEntries(section["CustomServers2"], customServersList);
426  QList<CustomServerInfo> backwardCompatibleServers;
427  CustomServers::decodeConfigEntries(section["CustomServers"], backwardCompatibleServers);
428  for (const CustomServerInfo &backwardCompatibleServer : backwardCompatibleServers)
429  {
430  bool known = false;
431  for (const CustomServerInfo &knownServer : customServersList)
432  {
433  if (knownServer.isSameServer(backwardCompatibleServer))
434  {
435  known = true;
436  break;
437  }
438  }
439  if (!known)
440  customServersList << backwardCompatibleServer;
441  }
442  this->customServers = customServersList.toVector();
443 
444  // WAD paths
445  // Backward compatibility, XXX once new stable is released:
446  QVariant variantWadPaths = section["WadPaths"].value();
447  if (variantWadPaths.isValid() && variantWadPaths.toList().isEmpty())
448  {
449  // Backward compatibility continued:
450  wadPaths.clear();
451  QStringList paths = variantWadPaths.toString().split(";");
452  for (const QString &path : paths)
453  {
454  wadPaths << FileSearchPath(path);
455  }
456  }
457  else
458  {
459  // This is not a part of XXX, this is proper, current behavior:
460  wadPaths = FileSearchPath::fromVariantList(section["WadPaths"].value().toList());
461  }
462  // End of backward compatibility for WAD paths.
463 
464  // Buddies list
465  if (section.hasSetting("Buddies"))
466  this->buddies = PatternList::deserializeQVariant(section.value("Buddies"));
467  else if (section.hasSetting("BuddiesList"))
468  {
469  // Backward compatibility, pre 1.2.
470  this->buddies = readPre1Point2BuddiesList(section["BuddiesList"]);
471  }
472 }
473 
474 void DoomseekerConfig::DoomseekerCfg::save(IniSection &section)
475 {
476  section["Localization"] = this->localization;
477  section["BotsAreNotPlayers"] = this->bBotsAreNotPlayers;
478  section["CloseToTrayIcon"] = this->bCloseToTrayIcon;
479  section["ColorizeServerConsole"] = this->bColorizeServerConsole;
480  section["DrawGridInServerTable"] = this->bDrawGridInServerTable;
481  section["HidePasswords"] = this->bHidePasswords;
482  section["HonorServerCountries"] = this->bHonorServerCountries;
483  section["GroupServersWithPlayersAtTheTopOfTheList"] = this->bGroupServersWithPlayersAtTheTopOfTheList;
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["UseTrayIcon"] = this->bUseTrayIcon;
495  section["MarkServersWithBuddies"] = this->bMarkServersWithBuddies;
496  section["BuddyServersColor"] = this->buddyServersColor;
497  section["CustomServersColor"] = this->customServersColor;
498  section["LanServersColor"] = this->lanServersColor;
499  section["MainWindowState"] = this->mainWindowState;
500  section.setValue("MainWindowGeometry", this->mainWindowGeometry);
501  section["QueryAutoRefreshEverySeconds"] = this->queryAutoRefreshEverySeconds;
502  section["QueryServerInterval"] = this->querySpeed().intervalBetweenServers;
503  section["QueryServerTimeout"] = this->querySpeed().delayBetweenSingleServerAttempts;
504  section["QueryAttemptsPerServer"] = this->querySpeed().attemptsPerServer;
505  section["PreviousCreateServerConfigDir"] = this->previousCreateServerConfigDir;
506  section["PreviousCreateServerExecDir"] = this->previousCreateServerExecDir;
507  section["PreviousCreateServerLogDir"] = this->previousCreateServerLogDir;
508  section["PreviousCreateServerWadDir"] = this->previousCreateServerWadDir;
509  section["ServerListColumnState"] = this->serverListColumnState;
510  section["ServerListSortIndex"] = this->serverListSortIndex;
511  section["ServerListSortDirection"] = this->serverListSortDirection;
512  section["SlotStyle"] = this->slotStyle;
513 
514  // Complex data variables.
515 
516  // Custom servers
517  QStringList allCustomServers; // backward compatibility for Doomseeker <1.2
518  QStringList allCustomServers2; // Doomseeker >=1.2
519  for (const CustomServerInfo &customServer : this->customServers)
520  {
521  QString engineName = QUrl::toPercentEncoding(customServer.engine, "", "()");
522  QString address = QUrl::toPercentEncoding(customServer.host, "", "()");
523 
524  QString customServerStringPrefix = QString("(%1;%2;%3")
525  .arg(engineName).arg(address)
526  .arg(customServer.port);
527  QString customServerString2 = QString("%1;%2)")
528  .arg(customServerStringPrefix)
529  .arg(customServer.enabled ? 1 : 0);
530 
531  allCustomServers << customServerStringPrefix + ")";
532  allCustomServers2 << customServerString2;
533  }
534  section["CustomServers"] = allCustomServers.join(";");
535  section["CustomServers2"] = allCustomServers2.join(";");
536 
537  section["WadPaths"].setValue(FileSearchPath::toVariantList(this->wadPaths));
538  section["Buddies"].setValue(this->buddies.serializeQVariant());
539 }
540 
541 const QuerySpeed &DoomseekerConfig::DoomseekerCfg::querySpeed() const
542 {
543  return d->querySpeed;
544 }
545 
546 void DoomseekerConfig::DoomseekerCfg::setQuerySpeed(const QuerySpeed &val)
547 {
548  d->querySpeed = val;
549 }
550 
551 QList<FileAlias> DoomseekerConfig::DoomseekerCfg::wadAliases() const
552 {
553  QList<FileAlias> list;
554  QVariantList varList = d->section.value("WadAliases").toList();
555  for (const QVariant &var : varList)
556  {
557  list << FileAlias::deserializeQVariant(var);
558  }
559  return FileAliasList::mergeDuplicates(list);
560 }
561 
562 void DoomseekerConfig::DoomseekerCfg::setWadAliases(const QList<FileAlias> &val)
563 {
564  QVariantList varList;
565  for (const FileAlias &elem : val)
566  {
567  varList << elem.serializeQVariant();
568  }
569  d->section.setValue("WadAliases", varList);
570 }
571 
572 void DoomseekerConfig::DoomseekerCfg::enableFreedoomInstallation(const QString &dir)
573 {
574  if (!FileUtils::containsPath(wadPathsOnly(), dir))
575  wadPaths.prepend(dir);
576  QList<FileAlias> aliases = wadAliases();
577  aliases << FileAlias::freeDoom1Aliases();
578  aliases << FileAlias::freeDoom2Aliases();
579  aliases = FileAliasList::mergeDuplicates(aliases);
580  setWadAliases(aliases);
581 }
582 
583 QStringList DoomseekerConfig::DoomseekerCfg::wadPathsOnly() const
584 {
585  QStringList result;
586  for (const FileSearchPath &path : wadPaths)
587  {
588  result << path.path();
589  }
590  return result;
591 }
593 const QString DoomseekerConfig::AutoUpdates::SECTION_NAME = "Doomseeker_AutoUpdates";
594 
595 void DoomseekerConfig::AutoUpdates::init(IniSection &section)
596 {
597  section.createSetting("UpdateChannelName", UpdateChannel::mkStable().name());
598  section.createSetting("UpdateMode", (int) UM_NotifyOnly);
599  section.createSetting("LastKnownUpdateRevisions", QVariant());
600  section.createSetting("bPerformUpdateOnNextRun", false);
601 }
602 
603 void DoomseekerConfig::AutoUpdates::load(IniSection &section)
604 {
605  updateChannelName = (const QString &)section["UpdateChannelName"];
606  updateMode = (UpdateMode)section["UpdateMode"].value().toInt();
607  QVariantMap lastKnownUpdateRevisionsVariant = section["LastKnownUpdateRevisions"].value().toMap();
608  lastKnownUpdateRevisions.clear();
609  for (const QString &package : lastKnownUpdateRevisionsVariant.keys())
610  {
611  QVariant revisionVariant = lastKnownUpdateRevisionsVariant[package];
612  lastKnownUpdateRevisions.insert(package, revisionVariant.toString());
613  }
614  bPerformUpdateOnNextRun = section["bPerformUpdateOnNextRun"].value().toBool();
615 }
616 
617 void DoomseekerConfig::AutoUpdates::save(IniSection &section)
618 {
619  section["UpdateChannelName"] = updateChannelName;
620  section["UpdateMode"] = updateMode;
621  QVariantMap revisionsVariantMap;
622  for (const QString &package : lastKnownUpdateRevisions.keys())
623  {
624  revisionsVariantMap.insert(package, lastKnownUpdateRevisions[package]);
625  }
626  section["LastKnownUpdateRevisions"].setValue(revisionsVariantMap);
627  section["bPerformUpdateOnNextRun"].setValue(bPerformUpdateOnNextRun);
628 }
630 const QString DoomseekerConfig::ServerFilter::SECTION_NAME = "ServerFilter";
631 
632 void DoomseekerConfig::ServerFilter::init(IniSection &section)
633 {
634  section.createSetting("bEnabled", true);
635  section.createSetting("bShowEmpty", true);
636  section.createSetting("bShowFull", true);
637  section.createSetting("bShowOnlyValid", false);
638  section.createSetting("GameModes", QStringList());
639  section.createSetting("GameModesExcluded", QStringList());
640  section.createSetting("MaxPing", 0);
641  section.createSetting("ServerName", "");
642  section.createSetting("TestingServers", Doomseeker::Indifferent);
643  section.createSetting("WADs", QStringList());
644  section.createSetting("WADsExcluded", QStringList());
645 }
646 
647 void DoomseekerConfig::ServerFilter::load(IniSection &section)
648 {
649  info.bEnabled = section["bEnabled"];
650  info.bShowEmpty = section["bShowEmpty"];
651  info.bShowFull = section["bShowFull"];
652  info.bShowOnlyValid = section["bShowOnlyValid"];
653  info.gameModes = section["GameModes"].value().toStringList();
654  info.gameModesExcluded = section["GameModesExcluded"].value().toStringList();
655  info.maxPing = section["MaxPing"];
656  info.serverName = (const QString &)section["ServerName"];
657  info.testingServers = static_cast<Doomseeker::ShowMode>(section.value("TestingServers").toInt());
658  info.wads = section["WADs"].value().toStringList();
659  info.wadsExcluded = section["WADsExcluded"].value().toStringList();
660 }
661 
662 void DoomseekerConfig::ServerFilter::save(IniSection &section)
663 {
664  section["bEnabled"] = info.bEnabled;
665  section["bShowEmpty"] = info.bShowEmpty;
666  section["bShowFull"] = info.bShowFull;
667  section["bShowOnlyValid"] = info.bShowOnlyValid;
668  section["GameModes"].setValue(info.gameModes);
669  section["GameModesExcluded"].setValue(info.gameModesExcluded);
670  section["MaxPing"] = info.maxPing;
671  section["ServerName"] = info.serverName;
672  section["TestingServers"] = info.testingServers;
673  section["WADs"].setValue(info.wads);
674  section["WADsExcluded"].setValue(info.wadsExcluded);
675 }
677 const QString DoomseekerConfig::WadseekerCfg::SECTION_NAME = "Wadseeker";
678 
679 DoomseekerConfig::WadseekerCfg::WadseekerCfg()
680 {
681  this->bAlwaysUseDefaultSites = true;
682  this->bSearchInIdgames = true;
683  this->bSearchInWadArchive = true;
684  this->colorMessageCriticalError = "#ff0000";
685  this->colorMessageError = "#ff0000";
686  this->colorMessageNotice = "#000000";
687 ;
688  this->idgamesURL = Wadseeker::defaultIdgamesUrl();
689  this->maxConcurrentSiteDownloads = 3;
690  this->maxConcurrentWadDownloads = 2;
691  this->targetDirectory = gDefaultDataPaths->programsDataDirectoryPath();
692 
693  // Search URLs remains uninitialized here. It will be initialized
694  // by init() and then load() since Doomseeker always calls these
695  // methods in this order.
696 }
697 
699 {
700  section.createSetting("AlwaysUseDefaultSites", this->bAlwaysUseDefaultSites);
701  section.createSetting("SearchInIdgames", this->bSearchInIdgames);
702  section.createSetting("SearchInWadArchive", this->bSearchInWadArchive);
703  section.createSetting("ColorMessageCriticalError", this->colorMessageCriticalError);
704  section.createSetting("ColorMessageError", this->colorMessageError);
705  section.createSetting("ColorMessageNotice", this->colorMessageNotice);
706  section.createSetting("IdgamesApiURL", this->idgamesURL);
707  section.createSetting("MaxConcurrentSiteDownloads", this->maxConcurrentSiteDownloads);
708  section.createSetting("MaxConcurrentWadDownloads", this->maxConcurrentWadDownloads);
709  section.createSetting("SearchURLs", Wadseeker::defaultSitesListEncoded().join(";"));
710  section.createSetting("TargetDirectory", this->targetDirectory);
711 }
712 
713 void DoomseekerConfig::WadseekerCfg::load(IniSection &section)
714 {
715  this->bAlwaysUseDefaultSites = section["AlwaysUseDefaultSites"];
716  this->bSearchInIdgames = section["SearchInIdgames"];
717  this->bSearchInWadArchive = section["SearchInWadArchive"];
718  this->colorMessageCriticalError = (const QString &)section["ColorMessageCriticalError"];
719  this->colorMessageError = (const QString &)section["ColorMessageError"];
720  this->colorMessageNotice = (const QString &)section["ColorMessageNotice"];
721  this->idgamesURL = (const QString &)section["IdgamesApiURL"];
722  this->maxConcurrentSiteDownloads = section["MaxConcurrentSiteDownloads"];
723  this->maxConcurrentWadDownloads = section["MaxConcurrentWadDownloads"];
724  this->targetDirectory = (const QString &)section["TargetDirectory"];
725 
726  // Complex data values
727  this->searchURLs.clear();
728  QStringList urlList = section["SearchURLs"].valueString().split(";", Qt::SkipEmptyParts);
729  for (const QString &url : urlList)
730  {
731  this->searchURLs << QUrl::fromPercentEncoding(url.toUtf8());
732  }
733 }
734 
735 void DoomseekerConfig::WadseekerCfg::save(IniSection &section)
736 {
737  section["AlwaysUseDefaultSites"] = this->bAlwaysUseDefaultSites;
738  section["SearchInIdgames"] = this->bSearchInIdgames;
739  section["SearchInWadArchive"] = this->bSearchInWadArchive;
740  section["ColorMessageCriticalError"] = this->colorMessageCriticalError;
741  section["ColorMessageError"] = this->colorMessageError;
742  section["ColorMessageNotice"] = this->colorMessageNotice;
743  section["IdgamesApiURL"] = this->idgamesURL;
744  section["MaxConcurrentSiteDownloads"] = this->maxConcurrentSiteDownloads;
745  section["MaxConcurrentWadDownloads"] = this->maxConcurrentWadDownloads;
746  section["TargetDirectory"] = this->targetDirectory;
747 
748  // Complex data values
749  QStringList urlEncodedList;
750  for (const QString &url : this->searchURLs)
751  {
752  urlEncodedList << QUrl::toPercentEncoding(url);
753  }
754  section["SearchURLs"] = urlEncodedList.join(";");
755 }