MythTV master
mythcontext.cpp
Go to the documentation of this file.
1#include "mythcontext.h"
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <iostream>
7#include <queue>
8#include <thread>
9#include <vector>
10
11#include <QtGlobal>
12#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
13#include <QtSystemDetection>
14#endif
15#include <QCoreApplication>
16#include <QDateTime>
17#include <QDebug>
18#include <QDir>
19#include <QEventLoop>
20#include <QFileInfo>
21#include <QHostInfo>
22#include <QMutex>
23#include <QTcpSocket>
24#ifdef Q_OS_ANDROID
25#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
26#include <QtAndroidExtras>
27#else
28#include <QJniEnvironment>
29#include <QJniObject>
30#define QAndroidJniEnvironment QJniEnvironment
31#define QAndroidJniObject QJniObject
32#endif
33#endif
34
35#ifdef Q_OS_WINDOWS
36#include <cstdlib> // getenv, _putenv
37#include "libmythbase/compat.h"
38#endif
40#include "libmythbase/dbutil.h"
46#include "libmythbase/mythdb.h"
54#include "libmythbase/mythversion.h"
63#include "libmythui/mythimage.h"
67#include "libmythupnp/ssdp.h"
71
72#include "backendselect.h"
73#include "guistartup.h"
74
75#define LOC QString("MythContext: ")
76static const QString sLocation = "MythContext";
77
78namespace
79{
81{
82 public:
83 GUISettingsCache() = default;
84 GUISettingsCache(const QString& cache_filename, QString cache_path)
85 : m_cachePath(std::move(cache_path))
86 {
87 m_cacheFilename = m_cachePath + '/' + cache_filename;
88 if (m_cachePath.isEmpty() || cache_filename.isEmpty())
89 {
90 m_cacheFilename = m_cachePath = QString();
91 }
92 }
93
94 bool save();
95 void loadOverrides() const;
96 static void clearOverrides();
97
98 private:
99 QString m_cacheFilename {"cache/contextcache.xml"};
100 QString m_cachePath {"cache"};
101
102 static const std::array<QString, 13> kSettings;
103};
104
105} // anonymous namespace
106
107class MythContext::Impl : public QObject
108{
109 Q_OBJECT;
110
111 public:
112 explicit Impl();
113 ~Impl() override;
114
115 bool Init (bool gui,
116 bool promptForBackend,
117 bool disableAutoDiscovery,
118 bool ignoreDB);
119 bool FindDatabase(bool prompt, bool noAutodetect);
120 bool FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect);
121
122 void TempMainWindow();
123 void EndTempWindow();
124 static void LanguagePrompt();
125
126 static bool LoadDatabaseSettings();
127
128 static QString setLocalHostName(QString hostname);
129
130 bool PromptForDatabaseParams(const QString &error);
131 QString TestDBconnection(bool prompt=true);
132 void SilenceDBerrors();
133 void EnableDBerrors() const;
134 static void ResetDatabase(const DatabaseParams& dbParams);
135
137 ChooseBackend(const QString &error);
138 static int UPnPautoconf(std::chrono::milliseconds milliSeconds = 2s);
139 static bool DefaultUPnP(QString& Error);
140 static bool UPnPconnect(const DeviceLocation *backend, const QString &PIN);
141 void ShowGuiStartup();
142 bool checkPort(const QString &host, int port, std::chrono::seconds timeLimit) const;
143 static void processEvents();
144
145 bool event(QEvent* /*e*/) override; // QObject
146
147 protected:
148 void ShowConnectionFailurePopup(bool persistent);
150
151 void ShowVersionMismatchPopup(uint remote_version);
152
153 public slots:
154 void OnCloseDialog() const;
156
157 public:
159 bool m_gui {false};
160
162
163 QString m_dbHostCp;
164
166
167 MythUIHelper *m_ui {nullptr};
169 QEventLoop *m_loop {nullptr};
170 bool m_needsBackend {false};
171
172 GUISettingsCache m_GUISettingsCache;
173
174 private:
177 QDateTime m_lastCheck;
178};
179
180static void exec_program_cb(const QString &cmd)
181{
182 myth_system(cmd);
183}
184
185static void exec_program_tv_cb(const QString &cmd)
186{
187 QString s = cmd;
188 QStringList tokens = cmd.simplified().split(" ");
189 QStringList strlist;
190
191 bool cardidok = false;
192 int wantcardid = tokens[0].toInt(&cardidok, 10);
193
194 if (cardidok && wantcardid > 0)
195 {
196 strlist << QString("LOCK_TUNER %1").arg(wantcardid);
197 s = s.replace(0, tokens[0].length() + 1, "");
198 }
199 else
200 {
201 strlist << "LOCK_TUNER";
202 }
203
205 int cardid = strlist[0].toInt();
206
207 if (cardid >= 0)
208 {
209 s = s.arg(qPrintable(strlist[1]),
210 qPrintable(strlist[2]),
211 qPrintable(strlist[3]));
212
213 myth_system(s);
214
215 strlist = QStringList(QString("FREE_TUNER %1").arg(cardid));
217 }
218 else
219 {
220 QString label;
221
222 if (cardidok)
223 {
224 if (cardid == -1)
225 {
226 label = QObject::tr("Could not find specified tuner (%1).")
227 .arg(wantcardid);
228 }
229 else
230 {
231 label = QObject::tr("Specified tuner (%1) is already in use.")
232 .arg(wantcardid);
233 }
234 }
235 else
236 {
237 label = QObject::tr("All tuners are currently in use. If you want "
238 "to watch TV, you can cancel one of the "
239 "in-progress recordings from the delete menu");
240 }
241
242 LOG(VB_GENERAL, LOG_ALERT, QString("exec_program_tv: ") + label);
243
244 ShowOkPopup(label);
245 }
246}
247
248static void configplugin_cb(const QString &cmd)
249{
251 if (!pmanager)
252 return;
253
254 if (GetNotificationCenter() && pmanager->config_plugin(cmd.trimmed()))
255 {
257 QObject::tr("Failed to configure plugin"));
258 }
259}
260
261static void plugin_cb(const QString &cmd)
262{
264 if (!pmanager)
265 return;
266
267 if (GetNotificationCenter() && pmanager->run_plugin(cmd.trimmed()))
268 {
269 ShowNotificationError(QObject::tr("Plugin failure"),
270 sLocation,
271 QObject::tr("%1 failed to run for some reason").arg(cmd));
272 }
273}
274
275static void eject_cb()
276{
278}
279
281 : m_loop(new QEventLoop(this))
282{
284}
285
287{
288 if (GetNotificationCenter() && m_registration > 0)
289 {
290 GetNotificationCenter()->UnRegister(this, m_registration, true);
291 }
292
293 delete m_loop;
294
295 if (m_ui)
297}
298
310{
311 if (HasMythMainWindow())
312 return;
313
314 SilenceDBerrors();
315
316#ifdef Q_OS_DARWIN
317 // Qt 4.4 has window-focus problems
318 gCoreContext->OverrideSettingForSession("RunFrontendInWindow", "1");
319#endif
320 GetMythUI()->Init();
322 mainWindow->Init();
323}
324
326{
327 if (HasMythMainWindow())
328 {
329 if (m_guiStartup && !m_guiStartup->m_Exit)
330 {
332 if (mainStack)
333 {
334 mainStack->PopScreen(m_guiStartup, false);
335 m_guiStartup = nullptr;
336 }
337 }
338 }
339 EnableDBerrors();
340}
341
343{
344 // ask user for language settings
346 MythTranslation::load("mythfrontend");
347}
348
357bool MythContext::Impl::checkPort(const QString &host, int port, std::chrono::seconds timeLimit) const
358{
359 PortChecker checker;
360 if (m_guiStartup)
361 QObject::connect(m_guiStartup, &GUIStartup::cancelPortCheck, &checker, &PortChecker::cancelPortCheck);
362 return checker.checkPort(host, port, timeLimit);
363}
364
365
366bool MythContext::Impl::Init(const bool gui,
367 const bool promptForBackend,
368 const bool disableAutoDiscovery,
369 const bool ignoreDB)
370{
371 gCoreContext->GetDB()->IgnoreDatabase(ignoreDB);
372 m_gui = gui;
373 if (gui)
374 {
375 m_GUISettingsCache.loadOverrides();
376 }
377
379 m_needsBackend = true;
380
381 // Creates screen saver control if we will have a GUI
382 if (gui)
383 m_ui = GetMythUI();
384
385 // ---- database connection stuff ----
386
387 if (!ignoreDB && !FindDatabase(promptForBackend, disableAutoDiscovery))
388 {
389 EndTempWindow();
390 return false;
391 }
392
393 // ---- keep all DB-using stuff below this line ----
394
395 // Prompt for language if this is a first time install and
396 // we didn't already do so.
397 if (m_gui && !gCoreContext->GetDB()->HaveSchema())
398 {
399 TempMainWindow();
400 LanguagePrompt();
401 }
404
405 // Close GUI Startup Window.
406 if (m_guiStartup)
407 {
409 if (mainStack)
410 mainStack->PopScreen(m_guiStartup, false);
411 m_guiStartup=nullptr;
412 }
413 EndTempWindow();
414
415 if (gui)
416 {
417 MythUIMenuCallbacks cbs {};
419 cbs.exec_program_tv = exec_program_tv_cb;
420 cbs.configplugin = configplugin_cb;
421 cbs.plugin = plugin_cb;
422 cbs.eject = eject_cb;
423
424 m_ui->Init(cbs);
425 }
426
427 return true;
428}
429
435bool MythContext::Impl::FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect)
436{
437 QString failure;
438
439 // 2. If the user isn't forcing up the chooser UI, look for a default
440 // backend in XmlConfiguration::k_default_filename, then test DB settings we've got so far:
441 if (!manualSelect && XmlConfiguration().FileExists())
442 {
443 // XmlConfiguration::k_default_filename may contain a backend host UUID and PIN.
444 // If so, try to AutoDiscover UPnP server, and use its DB settings:
445
446 if (DefaultUPnP(failure)) // Probably a valid backend,
447 autoSelect = manualSelect = false; // so disable any further UPnP
448 else
449 if (!failure.isEmpty())
450 LOG(VB_GENERAL, LOG_ALERT, failure);
451
452 failure = TestDBconnection(loaded);
453 if (failure.isEmpty())
454 return true;
455 if (m_guiStartup && m_guiStartup->m_Exit)
456 return false;
457 if (m_guiStartup && m_guiStartup->m_Search)
458 autoSelect=true;
459 }
460
461 // 3. Try to automatically find the single backend:
462 if (autoSelect)
463 {
464 int count = UPnPautoconf();
465
466 if (count == 0)
467 failure = QObject::tr("No UPnP backends found", "Backend Setup");
468
469 if (count == 1)
470 {
471 failure = TestDBconnection();
472 if (failure.isEmpty())
473 return true;
474 if (m_guiStartup && m_guiStartup->m_Exit)
475 return false;
476 }
477
478 // Multiple BEs, or needs PIN.
479 manualSelect |= (count > 1 || count == -1);
480 // Search requested
481 if (m_guiStartup && m_guiStartup->m_Search)
482 manualSelect=true;
483 }
484
485 manualSelect &= m_gui; // no interactive command-line chooser yet
486
487 // Queries the user for the DB info
488 bool haveDbInfo {false};
489 while (!haveDbInfo)
490 {
491 if (manualSelect)
492 {
493 // Get the user to select a backend from a possible list:
494 switch (ChooseBackend(failure))
495 {
497 break;
499 manualSelect = false;
500 break;
502 {
503 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - failed");
504 return false;
505 }
506 }
507 }
508
509 if (!manualSelect)
510 {
511 // If this is a backend, No longer prompt for database.
512 // Instead allow the web server to start so that the
513 // database can be set up there
515 || !PromptForDatabaseParams(failure))
516 {
517 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - failed");
518 return false;
519 }
520 }
521 failure = TestDBconnection();
522 haveDbInfo = failure.isEmpty();
523 if (!failure.isEmpty())
524 LOG(VB_GENERAL, LOG_ALERT, failure);
525 if (m_guiStartup && m_guiStartup->m_Exit)
526 return false;
527 if (m_guiStartup && m_guiStartup->m_Search)
528 manualSelect=true;
529 if (m_guiStartup && m_guiStartup->m_Setup)
530 manualSelect=false;
531 }
532
533 return true;
534}
535
548bool MythContext::Impl::FindDatabase(bool prompt, bool noAutodetect)
549{
550 // We can only prompt if autodiscovery is enabled..
551 bool manualSelect = prompt && !noAutodetect;
552
553 // 1. Either load XmlConfiguration::k_default_filename or use sensible "localhost" defaults:
554 bool loaded = LoadDatabaseSettings();
555 const DatabaseParams dbParamsFromFile = GetMythDB()->GetDatabaseParams();
556 setLocalHostName(dbParamsFromFile.m_localHostName);
557
558 // In addition to the UI chooser, we can also try to autoSelect later,
559 // but only if we're not doing manualSelect and there was no
560 // valid XmlConfiguration::k_default_filename
561 bool autoSelect = !manualSelect && !loaded && !noAutodetect;
562
563 if (!FindDatabaseChoose(loaded, manualSelect, autoSelect))
564 return false;
565
566 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - Success!");
567 // If we got the database from UPNP then the wakeup settings are lost.
568 // Restore them.
569 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
570 dbParams.m_wolEnabled = dbParamsFromFile.m_wolEnabled;
571 dbParams.m_wolReconnect = dbParamsFromFile.m_wolReconnect;
572 dbParams.m_wolRetry = dbParamsFromFile.m_wolRetry;
573 dbParams.m_wolCommand = dbParamsFromFile.m_wolCommand;
574
575 GetMythDB()->SaveDatabaseParams(dbParams, !loaded || dbParamsFromFile != dbParams);
576 EnableDBerrors();
577 ResetDatabase(dbParams);
578 return true;
579}
580
585{
586 auto config = XmlConfiguration(); // read-only
587
588 DatabaseParams dbParams;
589
590 dbParams.m_localHostName = config.GetValue("LocalHostName", "");
591 dbParams.m_dbHostPing = config.GetValue(XmlConfiguration::kDefaultDB + "PingHost", true);
592 dbParams.m_dbHostName = config.GetValue(XmlConfiguration::kDefaultDB + "Host", "");
593 dbParams.m_dbUserName = config.GetValue(XmlConfiguration::kDefaultDB + "UserName", "");
594 dbParams.m_dbPassword = config.GetValue(XmlConfiguration::kDefaultDB + "Password", "");
595 dbParams.m_dbName = config.GetValue(XmlConfiguration::kDefaultDB + "DatabaseName", "");
596 dbParams.m_dbPort = config.GetValue(XmlConfiguration::kDefaultDB + "Port", 0);
597
598 dbParams.m_wolEnabled = config.GetValue(XmlConfiguration::kDefaultWOL + "Enabled", false);
599 dbParams.m_wolReconnect =
600 config.GetDuration<std::chrono::seconds>(XmlConfiguration::kDefaultWOL + "SQLReconnectWaitTime", 0s);
601 dbParams.m_wolRetry = config.GetValue(XmlConfiguration::kDefaultWOL + "SQLConnectRetry", 5);
602 dbParams.m_wolCommand = config.GetValue(XmlConfiguration::kDefaultWOL + "Command", "");
603
605
606 if (!ok)
607 dbParams = {};
608
609 dbParams.m_localEnabled = !(dbParams.m_localHostName.isEmpty() ||
610 dbParams.m_localHostName == "my-unique-identifier-goes-here");
611
612 GetMythDB()->SetDatabaseParams(dbParams);
613 return ok;
614}
615
617{
618 if (hostname.isEmpty() ||
619 hostname == "my-unique-identifier-goes-here")
620 {
621 LOG(VB_GENERAL, LOG_INFO, "Empty LocalHostName. This is typical.");
622 hostname = QHostInfo::localHostName();
623
624#ifndef Q_OS_ANDROID
625 if (hostname.isEmpty())
626 {
627 LOG(VB_GENERAL, LOG_ALERT,
628 "MCP: Error, could not determine host name." + ENO);
629 }
630#else //elif defined Q_OS_ANDROID
631#define ANDROID_EXCEPTION_CHECK \
632 if (env->ExceptionCheck()) \
633 { \
634 env->ExceptionClear(); \
635 exception=true; \
636 }
637
638 if ((hostname == "localhost") || hostname.isEmpty())
639 {
640 hostname = "android";
641 bool exception=false;
644#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
645 QAndroidJniObject activity = QtAndroid::androidActivity();
646#else
647 QJniObject activity = QNativeInterface::QAndroidApplication::context();
648#endif
650 QAndroidJniObject appctx = activity.callObjectMethod
651 ("getApplicationContext", "()Landroid/content/Context;");
653 QAndroidJniObject contentR = appctx.callObjectMethod
654 ("getContentResolver", "()Landroid/content/ContentResolver;");
656 QAndroidJniObject androidId = QAndroidJniObject::callStaticObjectMethod
657 ("android/provider/Settings$Secure", "getString",
658 "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;",
659 contentR.object<jobject>(),
660 myID.object<jstring>());
662 if (exception)
663 LOG(VB_GENERAL, LOG_ALERT,
664 "Java exception looking for android id");
665 else
666 hostname = QString("android-%1").arg(androidId.toString());
667 }
668#endif
669
670 }
671
672 LOG(VB_GENERAL, LOG_INFO, QString("Using a profile name of: '%1' (Usually the "
673 "same as this host's name.)")
674 .arg(hostname));
676
677 return hostname;
678}
679
680
682{
683 if (m_loop && m_loop->isRunning())
684 {
685 m_loop->exit();
686 }
687}
688
689
690// No longer prompt for database, instaed allow the
691// web server to start so that the datbase can be
692// set up there
693
695{
696 bool accepted = false;
697 if (m_gui)
698 {
699 TempMainWindow();
700 LanguagePrompt();
701
702 // Tell the user what went wrong:
703 if (!error.isEmpty())
705
706 // ask user for database parameters
707
708 EnableDBerrors();
710 auto *dbsetting = new DatabaseSettings();
711 auto *ssd = new StandardSettingDialog(mainStack, "databasesettings",
712 dbsetting);
713 if (ssd->Create())
714 {
715 mainStack->AddScreen(ssd);
716 connect(dbsetting, &DatabaseSettings::isClosing,
718 if (!m_loop->isRunning())
719 m_loop->exec();
720 }
721 else
722 {
723 delete ssd;
724 }
725 SilenceDBerrors();
726 EndTempWindow();
727 accepted = true;
728 }
729 else
730 {
731 DatabaseParams params = GetMythDB()->GetDatabaseParams();
732 QString response;
733 std::this_thread::sleep_for(1s);
734 // give user chance to skip config
735 std::cout << '\n' << error.toLocal8Bit().constData() << "\n\n";
736 response = getResponse("Would you like to configure the database "
737 "connection now?",
738 "no");
739 if (!response.startsWith('y', Qt::CaseInsensitive))
740 return false;
741
742 params.m_dbHostName = getResponse("Database host name:",
743 params.m_dbHostName);
744 response = getResponse("Should I test connectivity to this host "
745 "using the ping command?", "yes");
746 params.m_dbHostPing = response.startsWith('y', Qt::CaseInsensitive);
747
748 params.m_dbPort = intResponse("Database non-default port:",
749 params.m_dbPort);
750 params.m_dbName = getResponse("Database name:",
751 params.m_dbName);
752 params.m_dbUserName = getResponse("Database user name:",
753 params.m_dbUserName);
754 params.m_dbPassword = getResponse("Database password:",
755 params.m_dbPassword);
756
757 params.m_localHostName = getResponse("Unique identifier for this machine "
758 "(if empty, the local host name "
759 "will be used):",
760 params.m_localHostName);
761 params.m_localEnabled = !params.m_localHostName.isEmpty();
762
763 response = getResponse("Would you like to use Wake-On-LAN to retry "
764 "database connections?",
765 (params.m_wolEnabled ? "yes" : "no"));
766 params.m_wolEnabled = response.startsWith('y', Qt::CaseInsensitive);
767
768 if (params.m_wolEnabled)
769 {
770 params.m_wolReconnect =
771 std::chrono::seconds(intResponse("Seconds to wait for "
772 "reconnection:",
773 params.m_wolReconnect.count()));
774 params.m_wolRetry = intResponse("Number of times to retry:",
775 params.m_wolRetry);
776 params.m_wolCommand = getResponse("Command to use to wake server or server MAC address:",
777 params.m_wolCommand);
778 }
779
780 accepted = GetMythDB()->SaveDatabaseParams(params, false);
781 }
782 return accepted;
783}
784
791{
792 QString err;
793 QString host;
794
795 // Jan 20, 2017
796 // Changed to use port check instead of ping
797
798 int port = 0;
799
800 // 1 = db awake, 2 = db listening, 3 = db connects,
801 // 4 = backend awake, 5 = backend listening
802 // 6 = success
803
804 enum startupStates : std::uint8_t {
805 st_start = 0,
806 st_dbAwake = 1,
807 st_dbStarted = 2,
808 st_dbConnects = 3,
809 st_beWOL = 4,
810 st_beAwake = 5,
811 st_success = 6
812 } startupState = st_start;
813
814 static const std::array<const QString, 7> kGuiStatuses
815 {"start", "dbAwake", "dbStarted", "dbConnects", "beWOL", "beAwake", "success"};
816
817 auto secondsStartupScreenDelay = gCoreContext->GetDurSetting<std::chrono::seconds>("StartupScreenDelay", 2s);
818 auto msStartupScreenDelay = std::chrono::duration_cast<std::chrono::milliseconds>(secondsStartupScreenDelay);
819 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
820 bool keep_trying = true;
821
822 while ((startupState < st_success) && keep_trying)
823 {
824 QElapsedTimer timer;
825 timer.start();
826 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
827 host = m_dbHostCp;
828 else
829 host = dbParams.m_dbHostName;
830 port = dbParams.m_dbPort;
831 if (port == 0)
832 port = 3306;
833 std::chrono::seconds wakeupTime = 3s;
834 int attempts = 11;
835 if (dbParams.m_wolEnabled)
836 {
837 wakeupTime = dbParams.m_wolReconnect;
838 attempts = dbParams.m_wolRetry + 1;
839 startupState = st_start;
840 }
841 else
842 {
843 startupState = st_dbAwake;
844 }
845 attempts = std::max(attempts, 6);
846 if (!prompt)
847 attempts=1;
848 if (wakeupTime < 5s)
849 wakeupTime = 5s;
850
851 std::chrono::seconds progressTotal = wakeupTime * attempts;
852
853 if (m_guiStartup && !m_guiStartup->m_Exit)
854 m_guiStartup->setTotal(progressTotal);
855
856 QString beWOLCmd = QString();
857 QString backendIP = QString();
858 int backendPort = 0;
859 QString masterserver;
860
861 for (int attempt = 0;
862 attempt < attempts && startupState != st_success;
863 ++attempt)
864 {
865 // The first time do everything with minimum timeout and
866 // no GUI, for the normal case where all is OK
867 // After that show the GUI (if this is a GUI program)
868
869 LOG(VB_GENERAL, LOG_INFO,
870 QString("Start up testing connections. DB %1, BE %2, attempt %3, status %4, Delay: %5")
871 .arg(host, backendIP, QString::number(attempt),
872 kGuiStatuses[startupState],
873 QString::number(msStartupScreenDelay.count())) );
874
875 std::chrono::seconds useTimeout = wakeupTime;
876 if (attempt == 0)
877 useTimeout=1s;
878
879 if (m_gui && !m_guiStartup)
880 {
881 if (msStartupScreenDelay==0ms || timer.hasExpired(msStartupScreenDelay.count()))
882 {
883 ShowGuiStartup();
884 if (m_guiStartup)
885 m_guiStartup->setTotal(progressTotal);
886 }
887 }
888 if (m_guiStartup && !m_guiStartup->m_Exit)
889 {
890 if (attempt > 0)
891 m_guiStartup->setStatusState(kGuiStatuses[startupState]);
892 m_guiStartup->setMessageState("empty");
893 processEvents();
894 }
895 switch (startupState)
896 {
897 case st_start:
898 if (dbParams.m_wolEnabled)
899 {
900 if (attempt > 0)
901 MythWakeup(dbParams.m_wolCommand);
902 if (!checkPort(host, port, useTimeout))
903 break;
904 }
905 startupState = st_dbAwake;
906 [[fallthrough]];
907 case st_dbAwake:
908 if (!checkPort(host, port, useTimeout))
909 break;
910 startupState = st_dbStarted;
911 [[fallthrough]];
912 case st_dbStarted:
913 // If the database is connecting with link-local
914 // address, it may have changed
915 PortChecker{}.resolveLinkLocal(host, port, useTimeout);
916 if (dbParams.m_dbHostName != host)
917 {
918 dbParams.m_dbHostName = host;
919 GetMythDB()->SetDatabaseParams(dbParams);
920 }
921 EnableDBerrors();
922 ResetDatabase(dbParams);
924 {
925 for (std::chrono::seconds temp = 0s; temp < useTimeout * 2 ; temp++)
926 {
927 processEvents();
928 std::this_thread::sleep_for(500ms);
929 }
930 break;
931 }
932 startupState = st_dbConnects;
933 [[fallthrough]];
934 case st_dbConnects:
935 if (m_needsBackend)
936 {
937 beWOLCmd = gCoreContext->GetSetting("WOLbackendCommand", "");
938 if (!beWOLCmd.isEmpty())
939 {
940 wakeupTime += gCoreContext->GetDurSetting<std::chrono::seconds>
941 ("WOLbackendReconnectWaitTime", 0s);
942 attempts += gCoreContext->GetNumSetting
943 ("WOLbackendConnectRetry", 0);
944 useTimeout = wakeupTime;
945 if (m_gui && !m_guiStartup && attempt == 0)
946 useTimeout=1s;
947 progressTotal = wakeupTime * attempts;
948 if (m_guiStartup && !m_guiStartup->m_Exit)
949 m_guiStartup->setTotal(progressTotal);
950 startupState = st_beWOL;
951 }
952 }
953 else
954 {
955 startupState = st_success;
956 break;
957 }
958 masterserver = gCoreContext->GetSetting
959 ("MasterServerName");
960 backendIP = gCoreContext->GetSettingOnHost
961 ("BackendServerAddr", masterserver);
963 [[fallthrough]];
964 case st_beWOL:
965 if (!beWOLCmd.isEmpty())
966 {
967 if (attempt > 0)
968 MythWakeup(beWOLCmd);
969 if (!checkPort(backendIP, backendPort, useTimeout))
970 break;
971 }
972 startupState = st_beAwake;
973 [[fallthrough]];
974 case st_beAwake:
975 if (!checkPort(backendIP, backendPort, useTimeout))
976 break;
977 startupState = st_success;
978 [[fallthrough]];
979 case st_success:
980 // Quiet compiler warning.
981 break;
982 }
983 if (m_guiStartup)
984 {
985 if (m_guiStartup->m_Exit)
986 {
987 keep_trying = false;
988 break;
989 }
990 if (m_guiStartup->m_Setup
991 || m_guiStartup->m_Search
992 || m_guiStartup->m_Retry)
993 break;
994 }
995 processEvents();
996 }
997 if (startupState == st_success)
998 break;
999
1000 QString stateMsg = kGuiStatuses[startupState];
1001 stateMsg.append("Fail");
1002 LOG(VB_GENERAL, LOG_INFO,
1003 QString("Start up failure. host %1, status %2")
1004 .arg(host, stateMsg));
1005
1006 if (!m_gui)
1007 keep_trying = false;
1008 if (m_gui && !m_guiStartup)
1009 {
1010 ShowGuiStartup();
1011 if (m_guiStartup)
1012 m_guiStartup->setTotal(progressTotal);
1013 }
1014
1015 if (m_guiStartup
1016 && !m_guiStartup->m_Exit
1017 && !m_guiStartup->m_Setup
1018 && !m_guiStartup->m_Search
1019 && !m_guiStartup->m_Retry)
1020 {
1021 m_guiStartup->updateProgress(true);
1022 m_guiStartup->setStatusState(stateMsg);
1023 m_guiStartup->setMessageState("makeselection");
1024 m_loop->exec();
1025 if (!m_guiStartup->m_Retry)
1026 keep_trying = false;
1027 }
1028 }
1029
1030 if (startupState < st_dbAwake)
1031 {
1032 LOG(VB_GENERAL, LOG_WARNING, QString("Pinging to %1 failed, database will be unavailable").arg(host));
1033 SilenceDBerrors();
1034 err = QObject::tr(
1035 "Cannot find (ping) database host %1 on the network",
1036 "Backend Setup");
1037 return err.arg(host);
1038 }
1039
1040 if (startupState < st_dbConnects)
1041 {
1042 SilenceDBerrors();
1043 return QObject::tr("Cannot login to database", "Backend Setup");
1044 }
1045
1046 if (startupState < st_success)
1047 {
1048 return QObject::tr("Cannot connect to backend", "Backend Setup");
1049 }
1050
1051 // Current DB connection may have been silenced (invalid):
1052 EnableDBerrors();
1053 ResetDatabase(dbParams);
1054
1055 return {};
1056}
1057
1058// Show the Gui Startup window.
1059// This is called if there is a delay in startup for any reason
1060// such as the database being unavailable
1062{
1063 if (!m_gui)
1064 return;
1065 TempMainWindow();
1066 MythMainWindow *mainWindow = GetMythMainWindow();
1067 MythScreenStack *mainStack = mainWindow->GetMainStack();
1068 if (mainStack)
1069 {
1070 if (!m_guiStartup)
1071 {
1072 m_guiStartup = new GUIStartup(mainStack, m_loop);
1073 if (!m_guiStartup->Create())
1074 {
1075 delete m_guiStartup;
1076 m_guiStartup = nullptr;
1077 }
1078 if (m_guiStartup)
1079 {
1080 mainStack->AddScreen(m_guiStartup, false);
1081 processEvents();
1082 }
1083 }
1084 }
1085}
1086
1096{
1097 // This silences any DB errors from Get*Setting(),
1098 // (which is the vast majority of them)
1099 gCoreContext->GetDB()->SetSuppressDBMessages(true);
1100
1101 // Save the configured hostname, so that we can
1102 // still display it in the DatabaseSettings screens
1103 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1104 if (!dbParams.m_dbHostName.isEmpty())
1105 {
1106 m_dbHostCp = dbParams.m_dbHostName;
1107 dbParams.m_dbHostName.clear();
1108 GetMythDB()->SetDatabaseParams(dbParams);
1109 }
1110}
1111
1113{
1114 // Restore (possibly) blanked hostname
1115 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1116 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
1117 {
1118 dbParams.m_dbHostName = m_dbHostCp;
1119 GetMythDB()->SetDatabaseParams(dbParams);
1120 }
1121
1122 gCoreContext->GetDB()->SetSuppressDBMessages(false);
1123}
1124
1125
1138{
1139 auto* db = GetMythDB();
1140 db->GetDBManager()->CloseDatabases();
1141 db->SetDatabaseParams(dbParams);
1142 db->ClearSettingsCache();
1143}
1144
1149{
1150 TempMainWindow();
1151 LanguagePrompt();
1152
1153 // Tell the user what went wrong:
1154 if (!error.isEmpty())
1155 {
1156 LOG(VB_GENERAL, LOG_ERR, QString("Error: %1").arg(error));
1158 }
1159
1160 LOG(VB_GENERAL, LOG_INFO, "Putting up the UPnP backend chooser");
1161
1162 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1165 GetMythDB()->SetDatabaseParams(dbParams);
1166
1167 EndTempWindow();
1168
1169 return ret;
1170}
1171
1178int MythContext::Impl::UPnPautoconf(const std::chrono::milliseconds milliSeconds)
1179{
1180 auto seconds = duration_cast<std::chrono::seconds>(milliSeconds);
1181 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1182 .arg(seconds.count()));
1183
1185
1186 // Search for a total of 'milliSeconds' ms, sending new search packet
1187 // about every 250 ms until less than one second remains.
1188 MythTimer totalTime; totalTime.start();
1189 MythTimer searchTime; searchTime.start();
1190 while (totalTime.elapsed() < milliSeconds)
1191 {
1192 std::this_thread::sleep_for(25ms);
1193 auto ttl = milliSeconds - totalTime.elapsed();
1194 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1195 {
1196 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1197 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1198 .arg(ttlSeconds.count()));
1200 searchTime.start();
1201 }
1202 }
1203
1204 std::this_thread::sleep_for(25ms);
1205 processEvents();
1206
1208
1209 if (!backends)
1210 {
1211 LOG(VB_GENERAL, LOG_INFO, "No UPnP backends found");
1212 return 0;
1213 }
1214
1215 int count = backends->Count();
1216 if (count)
1217 {
1218 LOG(VB_GENERAL, LOG_INFO,
1219 QString("Found %1 UPnP backends").arg(count));
1220 }
1221 else
1222 {
1223 LOG(VB_GENERAL, LOG_ERR,
1224 "No UPnP backends found, but SSDPCache::Instance()->Find() not NULL");
1225 }
1226
1227 if (count != 1)
1228 {
1229 backends->DecrRef();
1230 return count;
1231 }
1232
1233 // Get this backend's location:
1234 DeviceLocation *BE = backends->GetFirst();
1235 backends->DecrRef();
1236 backends = nullptr;
1237
1238 // We don't actually know the backend's access PIN, so this will
1239 // only work for ones that have PIN access disabled (i.e. 0000)
1240 int ret = (UPnPconnect(BE, QString())) ? 1 : -1;
1241
1242 BE->DecrRef();
1243
1244 return ret;
1245}
1246
1253{
1254 static const QString loc = "DefaultUPnP() - ";
1255
1256 // potentially saved in backendselect
1257 QString pin;
1258 QString usn;
1259 {
1260 auto config = XmlConfiguration(); // read-only
1261 pin = config.GetValue(XmlConfiguration::kDefaultPIN, QString(""));
1262 usn = config.GetValue(XmlConfiguration::kDefaultUSN, QString(""));
1263 }
1264
1265 if (usn.isEmpty())
1266 {
1267 LOG(VB_UPNP, LOG_INFO, loc + "No default UPnP backend");
1268 return false;
1269 }
1270
1271 LOG(VB_UPNP, LOG_INFO,
1272 loc + QString(XmlConfiguration::kDefaultFilename) +
1273 QString(" has default PIN '%1' and host USN: %2").arg(pin, usn));
1274
1275 // ----------------------------------------------------------------------
1276
1277 std::chrono::milliseconds timeout_ms {2s};
1278 auto timeout_s = duration_cast<std::chrono::seconds>(timeout_ms);
1279 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1280 .arg(timeout_s.count()));
1282
1283 // ----------------------------------------------------------------------
1284 // We need to give the server time to respond...
1285 // ----------------------------------------------------------------------
1286
1287 DeviceLocation* devicelocation = nullptr;
1288 MythTimer totalTime;
1289 MythTimer searchTime;
1290 totalTime.start();
1291 searchTime.start();
1292 while (totalTime.elapsed() < timeout_ms)
1293 {
1294 devicelocation = SSDPCache::Instance()->Find(SSDP::kBackendURI, usn);
1295 if (devicelocation)
1296 break;
1297
1298 std::this_thread::sleep_for(25ms);
1299
1300 auto ttl = timeout_ms - totalTime.elapsed();
1301 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1302 {
1303 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1304 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1305 .arg(ttlSeconds.count()));
1307 searchTime.start();
1308 }
1309 }
1310
1311 // ----------------------------------------------------------------------
1312
1313 if (!devicelocation)
1314 {
1315 Error = "Cannot find default UPnP backend";
1316 return false;
1317 }
1318
1319 if (UPnPconnect(devicelocation, pin))
1320 {
1321 devicelocation->DecrRef();
1322 return true;
1323 }
1324
1325 devicelocation->DecrRef();
1326 Error = "Cannot connect to default backend via UPnP. Wrong saved PIN?";
1327 return false;
1328}
1329
1334 const QString &PIN)
1335{
1336 QString error;
1337 QString loc = "UPnPconnect() - ";
1338 QString URL = backend->m_sLocation;
1339 QUrl theURL(URL);
1340 MythXMLClient client(theURL);
1341 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1342
1343 LOG(VB_UPNP, LOG_INFO, loc + QString("Trying host at %1").arg(URL));
1344 switch (client.GetConnectionInfo(PIN, &dbParams, error))
1345 {
1346 case UPnPResult_Success:
1347 GetMythDB()->SetDatabaseParams(dbParams);
1348 LOG(VB_UPNP, LOG_INFO, loc +
1349 "Got database hostname: " + dbParams.m_dbHostName);
1350 return true;
1351
1353 // The stored PIN is probably not correct.
1354 // We could prompt for the PIN and try again, but that needs a UI.
1355 // Easier to fail for now, and put up the full UI selector later
1356 LOG(VB_UPNP, LOG_ERR, loc + "Wrong PIN?");
1357 return false;
1358
1359 default:
1360 LOG(VB_UPNP, LOG_ERR, loc + error);
1361 break;
1362 }
1363
1364 // This backend may have a local DB with the default user/pass/DBname.
1365 // For whatever reason, we have failed to get anything back via UPnP,
1366 // so we might as well try the database directly as a last resort.
1367 URL = theURL.host();
1368 if (URL.isEmpty())
1369 return false;
1370
1371 LOG(VB_UPNP, LOG_INFO, "Trying default DB credentials at " + URL);
1372 dbParams.m_dbHostName = URL;
1373 GetMythDB()->SetDatabaseParams(dbParams);
1374
1375 return true;
1376}
1377
1379{
1380 if (e->type() == MythEvent::kMythEventMessage)
1381 {
1382 if (m_disableeventpopup)
1383 return true;
1384
1385 if (GetNotificationCenter() && m_registration < 0)
1386 {
1387 m_registration = GetNotificationCenter()->Register(this);
1388 }
1389
1390 auto *me = dynamic_cast<MythEvent*>(e);
1391 if (me == nullptr)
1392 return true;
1393
1394 if (me->Message() == "VERSION_MISMATCH" && (1 == me->ExtraDataCount()))
1395 ShowVersionMismatchPopup(me->ExtraData(0).toUInt());
1396 else if (me->Message() == "CONNECTION_FAILURE")
1397 ShowConnectionFailurePopup(false);
1398 else if (me->Message() == "PERSISTENT_CONNECTION_FAILURE")
1399 ShowConnectionFailurePopup(true);
1400 else if (me->Message() == "CONNECTION_RESTABLISHED")
1401 HideConnectionFailurePopup();
1402 return true;
1403 }
1404
1405 return QObject::event(e);
1406}
1407
1409{
1410 QDateTime now = MythDate::current();
1411
1412 if (!GetNotificationCenter() || !m_ui || !m_ui->IsScreenSetup())
1413 return;
1414
1415 if (m_lastCheck.isValid() && now < m_lastCheck)
1416 return;
1417
1418 // When WOL is disallowed, standy mode,
1419 // we should not show connection failures.
1420 if (!gCoreContext->IsWOLAllowed())
1421 return;
1422
1423 m_lastCheck = now.addMSecs(5000); // don't refresh notification more than every 5s
1424
1425 QString description = persistent ?
1426 QObject::tr(
1427 "The connection to the master backend "
1428 "server has gone away for some reason. "
1429 "Is it running?") :
1430 QObject::tr(
1431 "Could not connect to the master backend server. Is "
1432 "it running? Is the IP address set for it in "
1433 "mythtv-setup correct?");
1434
1435 QString message = QObject::tr("Could not connect to master backend");
1436 MythErrorNotification n(message, sLocation, description);
1437 n.SetId(m_registration);
1438 n.SetParent(this);
1440}
1441
1443{
1444 if (!GetNotificationCenter())
1445 return;
1446
1447 if (!m_lastCheck.isValid())
1448 return;
1449
1450 MythCheckNotification n(QObject::tr("Backend is online"), sLocation);
1451 n.SetId(m_registration);
1452 n.SetParent(this);
1453 n.SetDuration(5s);
1455 m_lastCheck = QDateTime();
1456}
1457
1459{
1460 if (m_mbeVersionPopup)
1461 return;
1462
1463 QString message =
1464 QObject::tr(
1465 "The server uses network protocol version %1, "
1466 "but this client only understands version %2. "
1467 "Make sure you are running compatible versions of "
1468 "the backend and frontend.")
1469 .arg(remote_version).arg(MYTH_PROTO_VERSION);
1470
1471 if (HasMythMainWindow() && m_ui && m_ui->IsScreenSetup())
1472 {
1473 m_mbeVersionPopup = ShowOkPopup(
1475 }
1476 else
1477 {
1478 LOG(VB_GENERAL, LOG_ERR, LOC + message);
1479 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1480 }
1481}
1482
1483// Process Events while waiting for connection
1484// return true if progress is 100%
1486{
1487// bool ret = false;
1488// if (m_guiStartup)
1489// ret = m_guiStartup->updateProgress();
1490 qApp->processEvents(QEventLoop::AllEvents, 250);
1491 qApp->processEvents(QEventLoop::AllEvents, 250);
1492// return ret;
1493}
1494
1495namespace
1496{
1497// cache some settings in GUISettingsCache::m_cacheFilename
1498// only call this if the database is available.
1499
1500const std::array<QString, 13> GUISettingsCache::kSettings
1501{ "Theme", "Language", "Country", "GuiHeight",
1502 "GuiOffsetX", "GuiOffsetY", "GuiWidth", "RunFrontendInWindow",
1503 "AlwaysOnTop", "HideMouseCursor", "ThemePainter", "libCECEnabled",
1504 "StartupScreenDelay" };
1505
1506
1507bool GUISettingsCache::save()
1508{
1509 QString cacheDirName = GetConfDir() + '/' + m_cachePath;
1510 QDir dir(cacheDirName);
1511 dir.mkpath(cacheDirName);
1512 XmlConfiguration config = XmlConfiguration(m_cacheFilename);
1513 bool dirty = false;
1514 for (const auto & setting : kSettings)
1515 {
1516 QString cacheValue = config.GetValue("Settings/" + setting, QString());
1518 QString value = gCoreContext->GetSetting(setting, QString());
1519 if (value != cacheValue)
1520 {
1521 config.SetValue("Settings/" + setting, value);
1522 dirty = true;
1523 }
1524 }
1525 clearOverrides();
1526
1527 if (dirty)
1528 {
1529#ifndef Q_OS_ANDROID
1531#endif
1532 return config.Save();
1533 }
1534 return true;
1535}
1536
1537void GUISettingsCache::loadOverrides() const
1538{
1539 auto config = XmlConfiguration(m_cacheFilename); // read only
1540 for (const auto & setting : kSettings)
1541 {
1542 if (!gCoreContext->GetSetting(setting, QString()).isEmpty())
1543 continue;
1544 QString value = config.GetValue("Settings/" + setting, QString());
1545 if (!value.isEmpty())
1546 gCoreContext->OverrideSettingForSession(setting, value);
1547 }
1548 // Prevent power off TV after temporary window
1549 gCoreContext->OverrideSettingForSession("PowerOffTVAllowed", nullptr);
1550
1551 MythTranslation::load("mythfrontend");
1552}
1553
1554void GUISettingsCache::clearOverrides()
1555{
1556 QString language = gCoreContext->GetSetting("Language", QString());
1557 for (const auto & setting : kSettings)
1559 // Restore power off TV setting
1560 gCoreContext->ClearOverrideSettingForSession("PowerOffTVAllowed");
1561
1562 if (language != gCoreContext->GetSetting("Language", QString()))
1563 MythTranslation::load("mythfrontend");
1564}
1565
1566} // anonymous namespace
1567
1569{
1570 m_mbeVersionPopup = nullptr;
1571 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1572}
1573
1574MythContext::MythContext(QString binversion, bool needsBackend)
1575 : m_impl(new MythContext::Impl()),
1576 m_appBinaryVersion(std::move(binversion))
1577{
1578#ifdef Q_OS_WINDOWS
1579 static bool WSAStarted = false;
1580 if (!WSAStarted)
1581 {
1582 WSADATA wsadata;
1583 int res = WSAStartup(MAKEWORD(2, 0), &wsadata);
1584 LOG(VB_SOCKET, LOG_INFO,
1585 QString("WSAStartup returned %1").arg(res));
1586 }
1587#endif
1588
1590 m_impl->m_needsBackend = needsBackend;
1591
1593
1594 if (!gCoreContext || !gCoreContext->Init())
1595 {
1596 LOG(VB_GENERAL, LOG_EMERG, LOC + "Unable to allocate MythCoreContext");
1597 qApp->exit(GENERIC_EXIT_NO_MYTHCONTEXT);
1598 }
1599}
1600
1601bool MythContext::Init(const bool gui,
1602 const bool promptForBackend,
1603 const bool disableAutoDiscovery,
1604 const bool ignoreDB)
1605{
1606 if (!m_impl)
1607 {
1608 LOG(VB_GENERAL, LOG_EMERG, LOC + "Init() Out-of-memory");
1609 return false;
1610 }
1611
1612 qRegisterMetaType<std::chrono::seconds>("std::chrono::seconds");
1613 qRegisterMetaType<std::chrono::milliseconds>("std::chrono::milliseconds");
1614 qRegisterMetaType<std::chrono::microseconds>("std::chrono::microseconds");
1615
1617
1618 if (gui && QCoreApplication::applicationName() == MYTH_APPNAME_MYTHTV_SETUP)
1619 {
1621 QString warning = QObject::tr("mythtv-setup is deprecated.\n"
1622 "To set up MythTV, start mythbackend and use:\n"
1623 "http://localhost:6544/setupwizard");
1624 WaitFor(ShowOkPopup(warning));
1625 }
1626
1627 if (m_appBinaryVersion != MYTH_BINARY_VERSION)
1628 {
1629 LOG(VB_GENERAL, LOG_EMERG,
1630 QString("Application binary version (%1) does not "
1631 "match libraries (%2)")
1632 .arg(m_appBinaryVersion, MYTH_BINARY_VERSION));
1633
1634 QString warning = QObject::tr(
1635 "This application is not compatible "
1636 "with the installed MythTV libraries.");
1637 if (gui)
1638 {
1640 WaitFor(ShowOkPopup(warning));
1641 }
1642 LOG(VB_GENERAL, LOG_WARNING, warning);
1643
1644 return false;
1645 }
1646
1647#ifdef Q_OS_WINDOWS
1648 // HOME environment variable might not be defined
1649 // some libraries will fail without it
1650 QString home = getenv("HOME");
1651 if (home.isEmpty())
1652 {
1653 home = getenv("LOCALAPPDATA"); // Vista
1654 if (home.isEmpty())
1655 home = getenv("APPDATA"); // XP
1656 if (home.isEmpty())
1657 home = QString("."); // getenv("TEMP")?
1658
1659 _putenv(QString("HOME=%1").arg(home).toLocal8Bit().constData());
1660 }
1661#endif
1662
1663 // If HOME isn't defined, we won't be able to use default confdir of
1664 // $HOME/.mythtv nor can we rely on a MYTHCONFDIR that references $HOME
1665 QString homedir = QDir::homePath();
1666 QString confdir = qEnvironmentVariable("MYTHCONFDIR");
1667 if ((homedir.isEmpty() || homedir == "/") &&
1668 (confdir.isEmpty() || confdir.contains("$HOME")))
1669 {
1670 QString warning = "Cannot locate your home directory."
1671 " Please set the environment variable HOME";
1672 if (gui)
1673 {
1675 WaitFor(ShowOkPopup(warning));
1676 }
1677 LOG(VB_GENERAL, LOG_WARNING, warning);
1678
1679 return false;
1680 }
1681
1682 if (!m_impl->Init(gui, promptForBackend, disableAutoDiscovery, ignoreDB))
1683 {
1684 return false;
1685 }
1686
1687 SetDisableEventPopup(false);
1688
1689 if (m_impl->m_gui)
1690 {
1692 }
1693
1696
1697 return true;
1698}
1699
1701{
1702 if (m_cleanup != nullptr)
1703 {
1704 m_cleanup();
1705 }
1706
1707 if (m_impl->m_gui)
1708 {
1710 }
1711
1713 gCoreContext->InitPower(false /*destroy*/);
1714 if (MThreadPool::globalInstance()->activeThreadCount())
1715 LOG(VB_GENERAL, LOG_INFO, "Waiting for threads to exit.");
1716
1720
1721 LOG(VB_GENERAL, LOG_INFO, "Exiting");
1722
1723 logStop();
1724
1725 delete gCoreContext;
1726 gCoreContext = nullptr;
1727
1728 delete m_impl;
1729
1731}
1732
1734{
1735 m_impl->m_disableeventpopup = check;
1736}
1737
1739{
1740 /* this check is technically redundant since this is only called from
1741 MythContext::Init() and mythfrontend::main(); however, it is for safety
1742 and clarity until MythGUIContext is refactored out.
1743 */
1744 if (m_impl->m_gui)
1745 {
1746 return m_impl->m_GUISettingsCache.save();
1747 }
1748 return true;
1749}
1750
1752#include "mythcontext.moc"
1753
1754/* vim: set expandtab tabstop=4 shiftwidth=4: */
#define QAndroidJniEnvironment
#define ANDROID_EXCEPTION_CHECK
#define QAndroidJniObject
static Decision Prompt(DatabaseParams *dbParams, const QString &config_filename)
Structure containing the basic Database parameters.
Definition: mythdbparams.h:11
QString m_dbName
database name
Definition: mythdbparams.h:26
QString m_dbPassword
DB password.
Definition: mythdbparams.h:25
std::chrono::seconds m_wolReconnect
seconds to wait for reconnect
Definition: mythdbparams.h:34
QString m_localHostName
name used for loading/saving settings
Definition: mythdbparams.h:30
bool m_localEnabled
true if localHostName is not default
Definition: mythdbparams.h:29
bool IsValid(const QString &source=QString("Unknown")) const
Definition: mythdbparams.cpp:4
bool m_dbHostPing
No longer used.
Definition: mythdbparams.h:22
QString m_dbUserName
DB user name.
Definition: mythdbparams.h:24
QString m_wolCommand
command to use for wake-on-lan
Definition: mythdbparams.h:36
bool m_wolEnabled
true if wake-on-lan params are used
Definition: mythdbparams.h:33
int m_dbPort
database port
Definition: mythdbparams.h:23
int m_wolRetry
times to retry to reconnect
Definition: mythdbparams.h:35
QString m_dbHostName
database server
Definition: mythdbparams.h:21
void isClosing(void)
QString m_sLocation
Definition: upnpdevice.h:239
void cancelPortCheck(void)
static bool prompt(bool force=false)
Ask the user for the language to use.
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:877
static MThreadPool * globalInstance(void)
void waitForDone(void)
static void ejectOpticalDisc(void)
Eject a disk, unmount a drive, open a tray.
Dialog asking for user confirmation.
bool event(QEvent *) override
static bool LoadDatabaseSettings()
Load database and host settings from XmlConfiguration::k_default_filename, or set some defaults.
QString m_dbHostCp
dbHostName backup
void OnCloseDialog() const
static void processEvents()
bool checkPort(const QString &host, int port, std::chrono::seconds timeLimit) const
Check if a port is open.
bool m_gui
Should this context use GUI elements?
static void LanguagePrompt()
QDateTime m_lastCheck
QString TestDBconnection(bool prompt=true)
Some quick sanity checks before opening a database connection.
bool PromptForDatabaseParams(const QString &error)
static bool DefaultUPnP(QString &Error)
Get the default backend from XmlConfiguration::kDefaultFilename, use UPnP to find it.
QEventLoop * m_loop
BackendSelection::Decision ChooseBackend(const QString &error)
Search for backends via UPnP, put up a UI for the user to choose one.
bool FindDatabase(bool prompt, bool noAutodetect)
Get database connection settings and test connectivity.
void ShowVersionMismatchPopup(uint remote_version)
bool Init(bool gui, bool promptForBackend, bool disableAutoDiscovery, bool ignoreDB)
static QString setLocalHostName(QString hostname)
MythUIHelper * m_ui
static bool UPnPconnect(const DeviceLocation *backend, const QString &PIN)
Query a backend via UPnP for its database connection parameters.
void EnableDBerrors() const
bool FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect)
Helper function for getting database connection settings and test connectivity.
static void ResetDatabase(const DatabaseParams &dbParams)
Called when the user changes the DB connection settings.
void VersionMismatchPopupClosed()
static int UPnPautoconf(std::chrono::milliseconds milliSeconds=2s)
If there is only a single UPnP backend, use it.
GUIStartup * m_guiStartup
void TempMainWindow()
Setup a minimal themed main window, and prompt for user's language.
MythConfirmationDialog * m_mbeVersionPopup
GUISettingsCache m_GUISettingsCache
void ShowConnectionFailurePopup(bool persistent)
void HideConnectionFailurePopup()
void SilenceDBerrors()
Cause MSqlDatabase::OpenDatabase() and MSqlQuery to fail silently.
QString m_masterhostname
master backend hostname
Startup context for MythTV.
Definition: mythcontext.h:20
CleanupFunction m_cleanup
This is used to destroy global state before main() returns.
Definition: mythcontext.h:47
QString m_appBinaryVersion
Definition: mythcontext.h:42
MythContext(QString binversion, bool needsBackend=false)
virtual ~MythContext()
bool Init(bool gui=true, bool promptForBackend=false, bool disableAutoDiscovery=false, bool ignoreDB=false)
bool saveSettingsCache()
void SetDisableEventPopup(bool check)
Impl * m_impl
PIMPL idiom.
Definition: mythcontext.h:41
This class contains the runtime context for MythTV.
bool IsFrontend(void) const
is this process a frontend process
MythDB * GetDB(void)
void ActivateSettingsCache(bool activate=true)
void ClearOverrideSettingForSession(const QString &key)
void SetLocalHostname(const QString &hostname)
QString GetSetting(const QString &key, const QString &defaultval="")
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
MythPluginManager * GetPluginManager(void)
void OverrideSettingForSession(const QString &key, const QString &value)
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
T GetDurSetting(const QString &key, T defaultval=T::zero())
bool IsBackend(void) const
is this process a backend process
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
bool IsWOLAllowed() const
void SaveLocaleDefaults(void)
int GetNumSetting(const QString &key, int defaultval=0)
void InitPower(bool Create=true)
This class is used as a container for messages.
Definition: mythevent.h:17
static const Type kMythEventMessage
Definition: mythevent.h:79
MythScreenStack * GetMainStack()
static MythMainWindow * getMainWindow(bool UseDB=true)
Return the existing main window, or create one.
void Init(bool MayReInit=true)
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void SetId(int Id)
Contains the application registration id.
void SetParent(void *Parent)
Contains the parent address. Required if id is set Id provided must match the parent address as provi...
void SetDuration(std::chrono::seconds Duration)
Contains a duration during which the notification will be displayed for. The duration is informative ...
bool config_plugin(const QString &plugname)
Definition: mythplugin.cpp:185
bool run_plugin(const QString &plugname)
Definition: mythplugin.cpp:167
virtual void PopScreen(MythScreenType *screen=nullptr, bool allowFade=true, bool deleteScreen=true)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
static void load(const QString &module_name)
Load a QTranslator for the user's preferred language.
void Init(MythUIMenuCallbacks &cbs)
UPnPResultCode GetConnectionInfo(const QString &sPin, DatabaseParams *pParams, QString &sMsg)
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:45
bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
void cancelPortCheck(void)
Cancel the checkPort operation currently in progress.
bool checkPort(const QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Check if a port is open.
Definition: portchecker.cpp:63
static void PrintDebug(void)
Print out any leaks if that level of debugging is enabled.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
uint Count(void) const
Definition: ssdpcache.h:50
DeviceLocation * GetFirst(void)
Returns random entry in cache, returns nullptr when list is empty.
Definition: ssdpcache.cpp:75
static SSDPCache * Instance()
Definition: ssdpcache.cpp:285
SSDPCacheEntries * Find(const QString &sURI)
Finds the SSDPCacheEntries in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:341
static const QString kBackendURI
Definition: ssdp.h:82
static SSDP * Instance()
Definition: ssdp.cpp:57
void PerformSearch(const QString &sST, std::chrono::seconds timeout=2s)
Send a SSDP discover multicast datagram.
Definition: ssdp.cpp:159
static void Shutdown()
Definition: ssdp.cpp:67
static void Done(void)
static void Init(QObject *parent=nullptr)
static void Shutdown()
Definition: taskqueue.cpp:67
static const QString kDefaultUSN
Definition: configuration.h:63
static const QString kDefaultWOL
Definition: configuration.h:60
static const QString kDefaultDB
Definition: configuration.h:59
QString GetValue(const QString &setting)
static constexpr auto kDefaultFilename
Definition: configuration.h:57
void SetValue(const QString &setting, bool value)
static const QString kDefaultPIN
Definition: configuration.h:62
GUISettingsCache(const QString &cache_filename, QString cache_path)
Definition: mythcontext.cpp:84
static const std::array< QString, 13 > kSettings
unsigned int uint
Definition: compat.h:60
@ GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:16
@ GENERIC_EXIT_SOCKET_ERROR
Socket error.
Definition: exitcodes.h:21
void logStop(void)
Entry point for stopping logging for an application.
Definition: logging.cpp:683
static constexpr const char * MYTH_APPNAME_MYTHTV_SETUP
Definition: mythappname.h:7
#define LOC
Definition: mythcontext.cpp:75
static void plugin_cb(const QString &cmd)
static void exec_program_cb(const QString &cmd)
static void exec_program_tv_cb(const QString &cmd)
static void configplugin_cb(const QString &cmd)
static void eject_cb()
static const QString sLocation
Definition: mythcontext.cpp:76
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
bool WaitFor(MythConfirmationDialog *dialog)
Blocks until confirmation dialog exits.
static QString confdir
Definition: mythdirs.cpp:25
void InitializeMythDirs(void)
Definition: mythdirs.cpp:35
QString GetConfDir(void)
Definition: mythdirs.cpp:285
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythNotificationCenter * GetNotificationCenter(void)
bool HasMythMainWindow(void)
MythMainWindow * GetMythMainWindow(void)
void DestroyMythMainWindow(void)
bool MythWakeup(const QString &wakeUpCommand, uint flags, std::chrono::seconds timeout)
int intResponse(const QString &query, int def)
In an interactive shell, prompt the user to input a number.
QString getResponse(const QString &query, const QString &def)
In an interactive shell, prompt the user to input a string.
void ShowNotificationError(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
convenience utility to display error message as notification
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
void DestroyMythUI()
MythUIHelper * GetMythUI()
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
def error(message)
Definition: smolt.py:409
string hostname
Definition: caa.py:17
void(* exec_program)(const QString &cmd)
Definition: mythuihelper.h:16
@ UPnPResult_Success
@ UPnPResult_ActionNotAuthorized