main.cpp
1 //------------------------------------------------------------------------------
2 // main.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program 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
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; 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) 2009 "Blzut3" <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 
24 #include <QApplication>
25 #include <QDir>
26 #include <QHashIterator>
27 #include <QLabel>
28 #include <QMainWindow>
29 #include <QMessageBox>
30 #include <QObject>
31 #include <QThreadPool>
32 #include <QTimer>
33 
34 #include "configuration/doomseekerconfig.h"
35 #include "configuration/passwordscfg.h"
36 #include "configuration/queryspeed.h"
37 #include "connectionhandler.h"
38 #include "gui/mainwindow.h"
39 #include "gui/remoteconsole.h"
40 #include "ip2c/ip2c.h"
41 #include "ini/ini.h"
42 #include "irc/configuration/ircconfig.h"
43 #include "serverapi/server.h"
44 #include "application.h"
45 #include "cmdargshelp.h"
46 #include "commandlinetokenizer.h"
47 #include "doomseekerfilepaths.h"
48 #include "localization.h"
49 #include "log.h"
50 #include "main.h"
51 #include "plugins/engineplugin.h"
52 #include "plugins/pluginloader.h"
53 #include "refresher/refresher.h"
54 #include "serverapi/server.h"
55 #include "strings.h"
56 #include "tests/testruns.h"
57 #include "wadseeker/wadseeker.h"
58 #include "updater/updateinstaller.h"
59 #include "lookuphost.h"
60 #include "versiondump.h"
61 
62 QString Main::argDataDir;
64 QList<LocalizationInfo> Main::localizations;
65 
66 
67 Main::Main(int argc, char* argv[])
68 : arguments(argv), argumentsCount(argc),
69  startRcon(false)
70 {
71  bIsFirstRun = false;
72  bTestMode = false;
73  bPortableMode = false;
74  updateFailedCode = 0;
75 
76  qRegisterMetaType<ServerPtr>("ServerPtr");
77  qRegisterMetaType<ServerCPtr>("ServerCPtr");
78 }
79 
80 Main::~Main()
81 {
82  if (Application::isInit())
83  {
84  gApp->stopRunning();
85  }
86 
87  if (Refresher::isInstantiated())
88  {
89  Refresher::instance()->quit();
90  Refresher::deinstantiate();
91  }
92 
93  // We can't save a config if we haven't initalized the program!
94  if (Application::isInit())
95  {
96  gConfig.saveToFile();
97  gConfig.dispose();
98 
99  gIRCConfig.saveToFile();
100  gIRCConfig.dispose();
101  }
102 
103  IP2C::deinstantiate();
104 
107 }
108 
109 int Main::connectToServerByURL()
110 {
111  ConnectionHandler *handler = ConnectionHandler::connectByUrl(connectUrl);
112 
113  if(handler)
114  {
115  connect(handler, SIGNAL(finished(int)), gApp, SLOT(quit()));
116  int ret = gApp->exec();
117  delete handler;
118  return ret;
119  }
120  return 0;
121 }
122 
123 // This method is an exception to sorting everything in alphabetical order
124 // because it's... the main method.
126 {
128  {
129  return 0;
130  }
131 
132  Application::init(argumentsCount, arguments);
133 #ifdef Q_OS_MAC
134  // In Mac OS X it is abnormal to have menu icons unless it's a shortcut to a file of some kind.
135  gApp->setAttribute(Qt::AA_DontShowIconsInMenus);
136 #endif
137 
138  gLog << "Starting Doomseeker. Hello World! :)";
139  gLog << "Setting up data directories.";
140 
141  if (!initDataDirectories())
142  {
143  // Inform the user which directories cannot be created and QUIT.
144  QStringList failedDirsList = gDefaultDataPaths->directoriesExist();
145  QString failedDirsString = failedDirsList.join("\n");
146 
147  QString errorMessage = tr("Doomseeker will not run because following directories cannot be created:");
148  errorMessage += "\n" + failedDirsString;
149 
150  QMessageBox::critical(NULL, tr("Doomseeker startup error"), errorMessage);
151  return 0;
152  }
153 
154  PluginLoader::init(Strings::combineManyPaths(dataDirectories, "engines/"));
155  PluginUrlHandler::registerAll();
156 
157  if (bTestMode)
158  {
159  return runTestMode();
160  }
161 
162  initMainConfig();
163  #ifdef WITH_AUTOUPDATES
164  // Handle pending update installations.
165  UpdateInstaller::ErrorCode updateInstallerResult
166  = (UpdateInstaller::ErrorCode)installPendingUpdates();
167  if (updateInstallerResult == UpdateInstaller::EC_Ok)
168  {
169  return 0;
170  }
171  #endif
172 
173  initLocalizationsDefinitions();
174  initIP2C();
175  initPasswordsConfig();
176  initPluginConfig();
177  initIRCConfig();
178 
179  if (startRcon)
180  {
181  if (!createRemoteConsole())
182  return 0;
183  }
184  else if (connectUrl.isValid())
185  {
186  setupRefreshingThread();
187  return connectToServerByURL();
188  }
189  else
190  {
191  setupRefreshingThread();
192  createMainWindow();
193  #ifdef WITH_AUTOUPDATES
194  // Handle auto update: display update failure or start auto update
195  // check/download.
196  if (updateFailedCode != 0)
197  {
198  // This is when updater program failed to install the update.
199  gApp->mainWindow()->setDisplayUpdaterProcessFailure(updateFailedCode);
200  }
201  else if (updateInstallerResult != UpdateInstaller::EC_NothingToUpdate)
202  {
203  // This is when Doomseeker failed to start the updater program.
204  gApp->mainWindow()->setDisplayUpdateInstallerError(updateInstallerResult);
205  }
206  else
207  {
208  if (gConfig.autoUpdates.updateMode != DoomseekerConfig::AutoUpdates::UM_Disabled)
209  {
210  QTimer::singleShot(0, gApp->mainWindow(), SLOT(checkForUpdatesAuto()));
211  }
212  }
213  #endif
214  }
215 
216  gLog << tr("Init finished.");
217  gLog.addUnformattedEntry("================================\n");
218 
219  int returnCode = gApp->exec();
220 
221  LookupHost::finalizeAndJoin();
222 
223  #ifdef WITH_AUTOUPDATES
225  {
226  // Code must be reset because the install method
227  // doesn't do the actual installation if it's not equal to zero.
228  updateFailedCode = 0;
229  int installResult = installPendingUpdates();
230  if (installResult != UpdateInstaller::EC_Ok
231  && installResult != UpdateInstaller::EC_NothingToUpdate)
232  {
233  QMessageBox::critical(NULL, tr("Doomseeker - Updates Install Failure"),
234  UpdateInstaller::errorCodeToStr((UpdateInstaller::ErrorCode)installResult));
235  }
236  }
237  #endif
238 
239  return returnCode;
240 }
241 
242 int Main::runTestMode()
243 {
244  // Setup
245  gLog << "Entering test mode.";
246  gLog << "";
247  TestCore testCore;
248 
249  // Call tests here.
250  TestRuns::pTestCore = &testCore;
251  TestRuns::callTests();
252 
253  // Summary
254  QString strSucceded = "Tests succeeded: %1";
255  QString strFailed = "Tests failed: %1";
256  QString strPercentage = "Pass percentage: %1%";
257 
258  float passPercentage = (float)testCore.numTestsSucceeded() / (float)testCore.numTests();
259  passPercentage *= 100.0f;
260 
261  gLog << "==== TESTS SUMMARY: ====";
262  gLog << strSucceded.arg(testCore.numTestsSucceeded(), 6);
263  gLog << strFailed.arg(testCore.numTestsFailed(), 6);
264  gLog << strPercentage.arg(passPercentage, 6, 'f', 2);
265  gLog << "==== Done. ====";
266 
267  return testCore.numTestsFailed();
268 }
269 
270 void Main::createMainWindow()
271 {
272  gLog << tr("Preparing GUI.");
273 
274  gApp->setMainWindow(new MainWindow(gApp, argumentsCount, arguments));
275  gApp->mainWindow()->show();
276 
277  if (bIsFirstRun)
278  {
279  gApp->mainWindow()->notifyFirstRun();
280  }
281 }
282 
283 bool Main::createRemoteConsole()
284 {
285  gLog << tr("Starting RCon client.");
286  if(rconPluginName.isEmpty())
287  {
288  RemoteConsole *rc = new RemoteConsole();
289  if(rc->isValid())
290  rc->show();
291  }
292  else
293  {
294  // Find plugin
295  int pIndex = gPlugins->pluginIndexFromName(rconPluginName);
296  if(pIndex == -1)
297  {
298  gLog << tr("Couldn't find specified plugin: ") + rconPluginName;
299  return false;
300  }
301 
302  // Check for RCon Availability.
303  const EnginePlugin *plugin = gPlugins->plugin(pIndex)->info();
304  ServerPtr server = plugin->server(QHostAddress(rconAddress), rconPort);
305  if(!server->hasRcon())
306  {
307  gLog << tr("Plugin does not support RCon.");
308  return false;
309  }
310 
311  // Start it!
312  RemoteConsole *rc = new RemoteConsole(server);
313  rc->show();
314  }
315  return true;
316 }
317 
319 {
320  DataPaths::initDefault(bPortableMode);
321  DoomseekerFilePaths::pDataPaths = gDefaultDataPaths;
322  gDefaultDataPaths->setWorkingDirectory(QCoreApplication::applicationDirPath());
323  if (!gDefaultDataPaths->createDirectories())
324  {
325  return false;
326  }
327 
328  // I think this directory should take priority, if user, for example,
329  // wants to update the ip2country file.
330  dataDirectories << gDefaultDataPaths->programsDataSupportDirectoryPath();
331  dataDirectories << gDefaultDataPaths->workingDirectory();
332 
333  // Continue with standard dirs:
334  dataDirectories << "./";
335 #if defined(Q_OS_LINUX)
336  #ifndef INSTALL_PREFIX // For safety lets check for the defintion
337  #define INSTALL_PREFIX "/usr"
338  #endif
339  // check in /usr/local/share/doomseeker/ on Linux
340  dataDirectories << INSTALL_PREFIX "/share/doomseeker/";
341 #endif
342 
343  dataDirectories << ":/";
344  QDir::setSearchPaths("data", dataDirectories);
345 
346  return true;
347 }
348 
350 {
351  gLog << tr("Initializing IP2C database.");
352  IP2C::instance();
353 
354  return 0;
355 }
356 
357 void Main::initIRCConfig()
358 {
359  gLog << tr("Initializing IRC configuration file.");
360 
361  // This macro initializes the Singleton.
362  gIRCConfig;
363 
364  // Now try to access the configuration stored on drive.
365  QString configPath = DoomseekerFilePaths::ircIni();
366  if (!configPath.isEmpty())
367  {
368  if (gIRCConfig.setIniFile(configPath))
369  {
370  gIRCConfig.readFromFile();
371  }
372  }
373 }
374 
375 void Main::initLocalizationsDefinitions()
376 {
377  gLog << tr("Loading translations definitions");
378  localizations = Localization::loadLocalizationsList(
379  DataPaths::staticDataSearchDirs(DataPaths::TRANSLATIONS_DIR_NAME));
380 
381  QString localization = gConfig.doomseeker.localization;
382  gLog << tr("Loading translation \"%1\".").arg(localization);
383  bool bSuccess = Localization::loadTranslation(localization);
384  if (bSuccess)
385  {
386  gLog << tr("Translation loaded.");
387  }
388  else
389  {
390  gLog << tr("Failed to load translation.");
391  }
392 }
393 
394 void Main::initMainConfig()
395 {
396  gLog << tr("Initializing configuration file.");
397 
398  // This macro initializes the Singleton.
399  gConfig;
400 
401  // Now try to access the configuration stored on drive.
402  QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
403  if (configDirPath.isEmpty())
404  {
405  gLog << tr("Could not get an access to the settings directory. Configuration will not be saved.");
406  return;
407  }
408 
409  QString filePath = DoomseekerFilePaths::ini();
410 
411  // Check for first run.
412  QFileInfo iniFileInfo(filePath);
413  bIsFirstRun = !iniFileInfo.exists();
414 
415  // Init the config.
416  if (gConfig.setIniFile(filePath))
417  {
418  gConfig.readFromFile();
419  }
420 }
421 
422 void Main::initPasswordsConfig()
423 {
424  gLog << tr("Initializing passwords configuration file.");
425  // Now try to access the configuration stored on drive.
426  QString configDirPath = gDefaultDataPaths->programsDataDirectoryPath();
427  if (configDirPath.isEmpty())
428  {
429  return;
430  }
431  QString filePath = DoomseekerFilePaths::passwordIni();
432  PasswordsCfg::initIni(filePath);
433 }
434 
435 void Main::initPluginConfig()
436 {
437  gLog << tr("Initializing configuration for plugins.");
438  gPlugins->initConfig();
439 }
440 
441 int Main::installPendingUpdates()
442 {
444  if (gConfig.autoUpdates.bPerformUpdateOnNextRun)
445  {
446  gConfig.autoUpdates.bPerformUpdateOnNextRun = false;
447  gConfig.saveToFile();
448  // Update should only be attempted if program was not called
449  // with "--update-failed" arg (previous update didn't fail).
450  if (updateFailedCode == 0)
451  {
452  UpdateInstaller updateInstaller;
453  updateInstallerResult = updateInstaller.startInstallation();
454  }
455  }
456  return updateInstallerResult;
457 }
458 
460 {
461  for(int i = 0; i < argumentsCount; ++i)
462  {
463  const char* arg = arguments[i];
464 
465  if(strcmp(arg, "--connect") == 0 && i+1 < argumentsCount)
466  {
467  connectUrl = QUrl(arguments[++i]);
468  }
469  else if(strcmp(arg, "--datadir") == 0 && i+1 < argumentsCount)
470  {
471  ++i;
472  dataDirectories.prepend(arguments[i]);
473  argDataDir = arguments[i];
474  }
475  else if(strcmp(arg, "--rcon") == 0)
476  {
477  startRcon = true;
478  if(i+2 < argumentsCount)
479  {
480  rconPluginName = arguments[i+1];
481  Strings::translateServerAddress(arguments[i+2], rconAddress, rconPort, "localhost:10666");
482  i += 2;
483  }
484  }
485  else if(strcmp(arg, "--help") == 0)
486  {
487  gLog.setTimestampsEnabled(false);
488  // Print information to the log and terminate.
489  gLog << tr("Available command line parameters:\n");
490  gLog << CmdArgsHelp::argsHelp();
491  return false;
492  }
493  else if (strcmp(arg, "--update-failed") == 0)
494  {
495  ++i;
496  updateFailedCode = QString(arguments[i]).toInt();
497  }
498  else if (strcmp(arg, "--portable") == 0)
499  {
500  bPortableMode = true;
501  }
502  else if (strcmp(arg, "--tests") == 0)
503  {
504  bTestMode = true;
505  }
506  else if (strcmp(arg, "--version-json") == 0)
507  {
508  if (i + 1 < argumentsCount)
509  {
510  QString filename = arguments[i + 1];
511  QFile f(filename);
512  if (!f.open(QIODevice::WriteOnly))
513  {
514  gLog << tr("Failed to open file.");
515  return false;
516  }
517  // Plugins generate QPixmaps which need a QApplication active
518  Application::init(argumentsCount, arguments);
520  PluginLoader::init(Strings::combineManyPaths(dataDirectories, "engines/"));
521  gLog << tr("Dumping version info to file in JSON format.");
522  VersionDump::dumpJsonToIO(f);
523  return false;
524  }
525  else
526  {
527  gLog << tr("No file specified!");
528  }
529  return false;
530  }
531  }
532 
533  return true;
534 }
535 
536 void Main::setupRefreshingThread()
537 {
538  gLog << tr("Starting refreshing thread.");
539  gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
540  gRefresher->start();
541 }
542 
543 //==============================================================================
544 
545 #ifdef _MSC_VER
546  #ifdef NDEBUG
547  #define USE_WINMAIN_AS_ENTRY_POINT
548  #endif
549 #endif
550 
551 #ifdef USE_WINMAIN_AS_ENTRY_POINT
552 #include <windows.h>
553 QStringList getCommandLineArgs()
554 {
555  CommandLineTokenizer tokenizer;
556  return tokenizer.tokenize(QString::fromUtf16((const ushort*)GetCommandLineW()));
557 }
558 
559 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
560 {
561  int argc = 0;
562  char** argv = NULL;
563 
564  QStringList commandLine = getCommandLineArgs();
565 
566  // At least one is ensured to be here.
567  argc = commandLine.size();
568  argv = new char*[argc];
569 
570  for (int i = 0; i < commandLine.size(); ++i)
571  {
572  const QString& parameter = commandLine[i];
573  argv[i] = new char[parameter.size() + 1];
574  strcpy(argv[i], parameter.toAscii().constData());
575  }
576 
577  Main* pMain = new Main(argc, argv);
578  int returnValue = pMain->run();
579 
580  // Cleans up after the program.
581  delete pMain;
582 
583  // On the other hand we could just ignore the fact that this array is left
584  // hanging in the memory because Windows will clean it up for us...
585  for (int i = 0; i < argc; ++i)
586  {
587  delete [] argv[i];
588  }
589  delete [] argv;
590 
591  return returnValue;
592 }
593 #else
594 int main(int argc, char* argv[])
595 {
596  Main* pMain = new Main(argc, argv);
597  int returnValue = pMain->run();
598 
599  // Cleans up after the program.
600  delete pMain;
601 
602  return returnValue;
603 }
604 #endif
605 
void quit()
Definition: refresher.cpp:240
static void translateServerAddress(const QString &addressString, QString &hostname, unsigned short &port, const QString &defaultAddress)
Translates string in format "hostname:port" to atomic values.
Definition: strings.cpp:404
static void deinit()
Destroys the init() instance.
static void init(const QStringList &directories)
Attempts to load plugins from given set of directories.
static bool bInstallUpdatesAndRestart
If true then program will install updates and restart instead of quitting if quit is requested...
Definition: main.h:51
int initIP2C()
Definition: main.cpp:349
int run()
Replaces main().
Definition: main.cpp:125
virtual ServerPtr server(const QHostAddress &address, unsigned short port) const
Creates an instance of server object from this plugin.
bool isValid() const
bool initDataDirectories()
Definition: main.cpp:318
QString rconAddress
If not empty assume we want to launch an rcon client.
Definition: main.h:112
static QStringList combineManyPaths(const QStringList &fronts, const QString &pathEnd)
Combines path suffix with all fronts, returns new list.
Definition: strings.cpp:135
Splits command line into separate arguments in a manner appropriate for current OS.
ErrorCode startInstallation()
Starts update process.
static void deinit()
Deinitializes the program; executed when program is shutting down.
Definition: application.cpp:51
Core for developer tests.
Definition: testcore.h:48
Update started properly.
bool interpretCommandLineParameters()
Definition: main.cpp:459
static QStringList staticDataSearchDirs(const QString &subdir=QString())
Paths to directories where program should search for its static data.
Definition: datapaths.cpp:255
Definition: main.h:37