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 <unistd.h> // for usleep(), gethostname
10#include <vector>
11
12#include <QtGlobal>
13#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
14#include <QtSystemDetection>
15#endif
16#include <QCoreApplication>
17#include <QDateTime>
18#include <QDebug>
19#include <QDir>
20#include <QEventLoop>
21#include <QFileInfo>
22#include <QHostInfo>
23#include <QMutex>
24#include <QTcpSocket>
25#ifdef Q_OS_ANDROID
26#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
27#include <QtAndroidExtras>
28#else
29#include <QJniEnvironment>
30#include <QJniObject>
31#define QAndroidJniEnvironment QJniEnvironment
32#define QAndroidJniObject QJniObject
33#endif
34#endif
35
36#ifdef Q_OS_WINDOWS
37#include "libmythbase/compat.h"
38#endif
40#include "libmythbase/dbutil.h"
45#include "libmythbase/mythdb.h"
53#include "libmythbase/mythversion.h"
62#include "libmythui/mythimage.h"
66#include "libmythupnp/ssdp.h"
69#include "libmythupnp/upnp.h"
70
71#include "backendselect.h"
72#include "guistartup.h"
73
74#define LOC QString("MythContext: ")
75static const QString sLocation = "MythContext";
76
77namespace
78{
80{
81 public:
82 GUISettingsCache() = default;
83 GUISettingsCache(const QString& cache_filename, QString cache_path)
84 : m_cachePath(std::move(cache_path))
85 {
86 m_cacheFilename = m_cachePath + '/' + cache_filename;
87 if (m_cachePath.isEmpty() || cache_filename.isEmpty())
88 {
89 m_cacheFilename = m_cachePath = QString();
90 }
91 }
92
93 bool save();
94 void loadOverrides() const;
95 static void clearOverrides();
96
97 private:
98 QString m_cacheFilename {"cache/contextcache.xml"};
99 QString m_cachePath {"cache"};
100
101 static const std::array<QString, 13> kSettings;
102};
103
104} // anonymous namespace
105
106class MythContext::Impl : public QObject
107{
108 Q_OBJECT;
109
110 public:
111 explicit Impl();
112 ~Impl() override;
113
114 bool Init (bool gui,
115 bool promptForBackend,
116 bool disableAutoDiscovery,
117 bool ignoreDB);
118 bool FindDatabase(bool prompt, bool noAutodetect);
119 bool FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect);
120
121 void TempMainWindow();
122 void EndTempWindow();
123 static void LanguagePrompt();
124
125 static bool LoadDatabaseSettings();
126
127 static QString setLocalHostName(QString hostname);
128
129 bool PromptForDatabaseParams(const QString &error);
130 QString TestDBconnection(bool prompt=true);
131 void SilenceDBerrors();
132 void EnableDBerrors() const;
133 static void ResetDatabase(const DatabaseParams& dbParams);
134
136 ChooseBackend(const QString &error);
137 static int UPnPautoconf(std::chrono::milliseconds milliSeconds = 2s);
138 static bool DefaultUPnP(QString& Error);
139 static bool UPnPconnect(const DeviceLocation *backend, const QString &PIN);
140 void ShowGuiStartup();
141 bool checkPort(QString &host, int port, std::chrono::seconds timeLimit) const;
142 static void processEvents();
143
144 protected:
145 bool event(QEvent* /*e*/) override; // QObject
146
147 void ShowConnectionFailurePopup(bool persistent);
149
150 void ShowVersionMismatchPopup(uint remote_version);
151
152 public slots:
153 void OnCloseDialog() const;
155
156 public:
158 bool m_gui {false};
159
161
162 QString m_dbHostCp;
163
165
166 MythUIHelper *m_ui {nullptr};
168 QEventLoop *m_loop {nullptr};
169 bool m_needsBackend {false};
170
171 GUISettingsCache m_GUISettingsCache;
172
173 private:
176 QDateTime m_lastCheck;
177};
178
179static void exec_program_cb(const QString &cmd)
180{
181 myth_system(cmd);
182}
183
184static void exec_program_tv_cb(const QString &cmd)
185{
186 QString s = cmd;
187 QStringList tokens = cmd.simplified().split(" ");
188 QStringList strlist;
189
190 bool cardidok = false;
191 int wantcardid = tokens[0].toInt(&cardidok, 10);
192
193 if (cardidok && wantcardid > 0)
194 {
195 strlist << QString("LOCK_TUNER %1").arg(wantcardid);
196 s = s.replace(0, tokens[0].length() + 1, "");
197 }
198 else
199 {
200 strlist << "LOCK_TUNER";
201 }
202
204 int cardid = strlist[0].toInt();
205
206 if (cardid >= 0)
207 {
208 s = s.arg(qPrintable(strlist[1]),
209 qPrintable(strlist[2]),
210 qPrintable(strlist[3]));
211
212 myth_system(s);
213
214 strlist = QStringList(QString("FREE_TUNER %1").arg(cardid));
216 }
217 else
218 {
219 QString label;
220
221 if (cardidok)
222 {
223 if (cardid == -1)
224 {
225 label = QObject::tr("Could not find specified tuner (%1).")
226 .arg(wantcardid);
227 }
228 else
229 {
230 label = QObject::tr("Specified tuner (%1) is already in use.")
231 .arg(wantcardid);
232 }
233 }
234 else
235 {
236 label = QObject::tr("All tuners are currently in use. If you want "
237 "to watch TV, you can cancel one of the "
238 "in-progress recordings from the delete menu");
239 }
240
241 LOG(VB_GENERAL, LOG_ALERT, QString("exec_program_tv: ") + label);
242
243 ShowOkPopup(label);
244 }
245}
246
247static void configplugin_cb(const QString &cmd)
248{
250 if (!pmanager)
251 return;
252
253 if (GetNotificationCenter() && pmanager->config_plugin(cmd.trimmed()))
254 {
256 QObject::tr("Failed to configure plugin"));
257 }
258}
259
260static void plugin_cb(const QString &cmd)
261{
263 if (!pmanager)
264 return;
265
266 if (GetNotificationCenter() && pmanager->run_plugin(cmd.trimmed()))
267 {
268 ShowNotificationError(QObject::tr("Plugin failure"),
269 sLocation,
270 QObject::tr("%1 failed to run for some reason").arg(cmd));
271 }
272}
273
274static void eject_cb()
275{
277}
278
280 : m_loop(new QEventLoop(this))
281{
283}
284
286{
287 if (GetNotificationCenter() && m_registration > 0)
288 {
289 GetNotificationCenter()->UnRegister(this, m_registration, true);
290 }
291
292 delete m_loop;
293
294 if (m_ui)
296}
297
309{
310 if (HasMythMainWindow())
311 return;
312
313 SilenceDBerrors();
314
315#ifdef Q_OS_DARWIN
316 // Qt 4.4 has window-focus problems
317 gCoreContext->OverrideSettingForSession("RunFrontendInWindow", "1");
318#endif
319 GetMythUI()->Init();
321 mainWindow->Init();
322}
323
325{
326 if (HasMythMainWindow())
327 {
328 if (m_guiStartup && !m_guiStartup->m_Exit)
329 {
331 if (mainStack)
332 {
333 mainStack->PopScreen(m_guiStartup, false);
334 m_guiStartup = nullptr;
335 }
336 }
337 }
338 EnableDBerrors();
339}
340
342{
343 // ask user for language settings
345 MythTranslation::load("mythfrontend");
346}
347
357bool MythContext::Impl::checkPort(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)
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 << std::endl << error.toLocal8Bit().constData() << std::endl << std::endl;
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 // 9 = all ok, 10 = quit
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 do
821 {
822 QElapsedTimer timer;
823 timer.start();
824 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
825 host = m_dbHostCp;
826 else
827 host = dbParams.m_dbHostName;
828 port = dbParams.m_dbPort;
829 if (port == 0)
830 port = 3306;
831 std::chrono::seconds wakeupTime = 3s;
832 int attempts = 11;
833 if (dbParams.m_wolEnabled)
834 {
835 wakeupTime = dbParams.m_wolReconnect;
836 attempts = dbParams.m_wolRetry + 1;
837 startupState = st_start;
838 }
839 else
840 {
841 startupState = st_dbAwake;
842 }
843 attempts = std::max(attempts, 6);
844 if (!prompt)
845 attempts=1;
846 if (wakeupTime < 5s)
847 wakeupTime = 5s;
848
849 std::chrono::seconds progressTotal = wakeupTime * attempts;
850
851 if (m_guiStartup && !m_guiStartup->m_Exit)
852 m_guiStartup->setTotal(progressTotal);
853
854 QString beWOLCmd = QString();
855 QString backendIP = QString();
856 int backendPort = 0;
857 QString masterserver;
858
859 for (int attempt = 0;
860 attempt < attempts && startupState != st_success;
861 ++attempt)
862 {
863 // The first time do everything with minimum timeout and
864 // no GUI, for the normal case where all is OK
865 // After that show the GUI (if this is a GUI program)
866
867 LOG(VB_GENERAL, LOG_INFO,
868 QString("Start up testing connections. DB %1, BE %2, attempt %3, status %4, Delay: %5")
869 .arg(host, backendIP, QString::number(attempt),
870 kGuiStatuses[startupState],
871 QString::number(msStartupScreenDelay.count())) );
872
873 std::chrono::seconds useTimeout = wakeupTime;
874 if (attempt == 0)
875 useTimeout=1s;
876
877 if (m_gui && !m_guiStartup)
878 {
879 if (msStartupScreenDelay==0ms || timer.hasExpired(msStartupScreenDelay.count()))
880 {
881 ShowGuiStartup();
882 if (m_guiStartup)
883 m_guiStartup->setTotal(progressTotal);
884 }
885 }
886 if (m_guiStartup && !m_guiStartup->m_Exit)
887 {
888 if (attempt > 0)
889 m_guiStartup->setStatusState(kGuiStatuses[startupState]);
890 m_guiStartup->setMessageState("empty");
891 processEvents();
892 }
893 switch (startupState)
894 {
895 case st_start:
896 if (dbParams.m_wolEnabled)
897 {
898 if (attempt > 0)
899 MythWakeup(dbParams.m_wolCommand);
900 if (!checkPort(host, port, useTimeout))
901 break;
902 }
903 startupState = st_dbAwake;
904 [[fallthrough]];
905 case st_dbAwake:
906 if (!checkPort(host, port, useTimeout))
907 break;
908 startupState = st_dbStarted;
909 [[fallthrough]];
910 case st_dbStarted:
911 // If the database is connecting with link-local
912 // address, it may have changed
913 if (dbParams.m_dbHostName != host)
914 {
915 dbParams.m_dbHostName = host;
916 GetMythDB()->SetDatabaseParams(dbParams);
917 }
918 EnableDBerrors();
919 ResetDatabase(dbParams);
921 {
922 for (std::chrono::seconds temp = 0s; temp < useTimeout * 2 ; temp++)
923 {
924 processEvents();
925 std::this_thread::sleep_for(500ms);
926 }
927 break;
928 }
929 startupState = st_dbConnects;
930 [[fallthrough]];
931 case st_dbConnects:
932 if (m_needsBackend)
933 {
934 beWOLCmd = gCoreContext->GetSetting("WOLbackendCommand", "");
935 if (!beWOLCmd.isEmpty())
936 {
937 wakeupTime += gCoreContext->GetDurSetting<std::chrono::seconds>
938 ("WOLbackendReconnectWaitTime", 0s);
939 attempts += gCoreContext->GetNumSetting
940 ("WOLbackendConnectRetry", 0);
941 useTimeout = wakeupTime;
942 if (m_gui && !m_guiStartup && attempt == 0)
943 useTimeout=1s;
944 progressTotal = wakeupTime * attempts;
945 if (m_guiStartup && !m_guiStartup->m_Exit)
946 m_guiStartup->setTotal(progressTotal);
947 startupState = st_beWOL;
948 }
949 }
950 else
951 {
952 startupState = st_success;
953 break;
954 }
955 masterserver = gCoreContext->GetSetting
956 ("MasterServerName");
957 backendIP = gCoreContext->GetSettingOnHost
958 ("BackendServerAddr", masterserver);
960 [[fallthrough]];
961 case st_beWOL:
962 if (!beWOLCmd.isEmpty())
963 {
964 if (attempt > 0)
965 MythWakeup(beWOLCmd);
966 if (!checkPort(backendIP, backendPort, useTimeout))
967 break;
968 }
969 startupState = st_beAwake;
970 [[fallthrough]];
971 case st_beAwake:
972 if (!checkPort(backendIP, backendPort, useTimeout))
973 break;
974 startupState = st_success;
975 [[fallthrough]];
976 case st_success:
977 // Quiet compiler warning.
978 break;
979 }
980 if (m_guiStartup)
981 {
982 if (m_guiStartup->m_Exit
983 || m_guiStartup->m_Setup
984 || m_guiStartup->m_Search
985 || m_guiStartup->m_Retry)
986 break;
987 }
988 processEvents();
989 }
990 if (startupState == st_success)
991 break;
992
993 QString stateMsg = kGuiStatuses[startupState];
994 stateMsg.append("Fail");
995 LOG(VB_GENERAL, LOG_INFO,
996 QString("Start up failure. host %1, status %2")
997 .arg(host, stateMsg));
998
999 if (m_gui && !m_guiStartup)
1000 {
1001 ShowGuiStartup();
1002 if (m_guiStartup)
1003 m_guiStartup->setTotal(progressTotal);
1004 }
1005
1006 if (m_guiStartup
1007 && !m_guiStartup->m_Exit
1008 && !m_guiStartup->m_Setup
1009 && !m_guiStartup->m_Search
1010 && !m_guiStartup->m_Retry)
1011 {
1012 m_guiStartup->updateProgress(true);
1013 m_guiStartup->setStatusState(stateMsg);
1014 m_guiStartup->setMessageState("makeselection");
1015 m_loop->exec();
1016 }
1017 }
1018 while (m_guiStartup && m_guiStartup->m_Retry);
1019
1020 if (startupState < st_dbAwake)
1021 {
1022 LOG(VB_GENERAL, LOG_WARNING, QString("Pinging to %1 failed, database will be unavailable").arg(host));
1023 SilenceDBerrors();
1024 err = QObject::tr(
1025 "Cannot find (ping) database host %1 on the network",
1026 "Backend Setup");
1027 return err.arg(host);
1028 }
1029
1030 if (startupState < st_dbConnects)
1031 {
1032 SilenceDBerrors();
1033 return QObject::tr("Cannot login to database", "Backend Setup");
1034 }
1035
1036 if (startupState < st_success)
1037 {
1038 return QObject::tr("Cannot connect to backend", "Backend Setup");
1039 }
1040
1041 // Current DB connection may have been silenced (invalid):
1042 EnableDBerrors();
1043 ResetDatabase(dbParams);
1044
1045 return {};
1046}
1047
1048// Show the Gui Startup window.
1049// This is called if there is a delay in startup for any reason
1050// such as the database being unavailable
1052{
1053 if (!m_gui)
1054 return;
1055 TempMainWindow();
1056 MythMainWindow *mainWindow = GetMythMainWindow();
1057 MythScreenStack *mainStack = mainWindow->GetMainStack();
1058 if (mainStack)
1059 {
1060 if (!m_guiStartup)
1061 {
1062 m_guiStartup = new GUIStartup(mainStack, m_loop);
1063 if (!m_guiStartup->Create())
1064 {
1065 delete m_guiStartup;
1066 m_guiStartup = nullptr;
1067 }
1068 if (m_guiStartup)
1069 {
1070 mainStack->AddScreen(m_guiStartup, false);
1071 processEvents();
1072 }
1073 }
1074 }
1075}
1076
1086{
1087 // This silences any DB errors from Get*Setting(),
1088 // (which is the vast majority of them)
1089 gCoreContext->GetDB()->SetSuppressDBMessages(true);
1090
1091 // Save the configured hostname, so that we can
1092 // still display it in the DatabaseSettings screens
1093 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1094 if (!dbParams.m_dbHostName.isEmpty())
1095 {
1096 m_dbHostCp = dbParams.m_dbHostName;
1097 dbParams.m_dbHostName.clear();
1098 GetMythDB()->SetDatabaseParams(dbParams);
1099 }
1100}
1101
1103{
1104 // Restore (possibly) blanked hostname
1105 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1106 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
1107 {
1108 dbParams.m_dbHostName = m_dbHostCp;
1109 GetMythDB()->SetDatabaseParams(dbParams);
1110 }
1111
1112 gCoreContext->GetDB()->SetSuppressDBMessages(false);
1113}
1114
1115
1128{
1129 auto* db = GetMythDB();
1130 db->GetDBManager()->CloseDatabases();
1131 db->SetDatabaseParams(dbParams);
1132 db->ClearSettingsCache();
1133}
1134
1139{
1140 TempMainWindow();
1141 LanguagePrompt();
1142
1143 // Tell the user what went wrong:
1144 if (!error.isEmpty())
1145 {
1146 LOG(VB_GENERAL, LOG_ERR, QString("Error: %1").arg(error));
1148 }
1149
1150 LOG(VB_GENERAL, LOG_INFO, "Putting up the UPnP backend chooser");
1151
1152 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1155 GetMythDB()->SetDatabaseParams(dbParams);
1156
1157 EndTempWindow();
1158
1159 return ret;
1160}
1161
1168int MythContext::Impl::UPnPautoconf(const std::chrono::milliseconds milliSeconds)
1169{
1170 auto seconds = duration_cast<std::chrono::seconds>(milliSeconds);
1171 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1172 .arg(seconds.count()));
1173
1175
1176 // Search for a total of 'milliSeconds' ms, sending new search packet
1177 // about every 250 ms until less than one second remains.
1178 MythTimer totalTime; totalTime.start();
1179 MythTimer searchTime; searchTime.start();
1180 while (totalTime.elapsed() < milliSeconds)
1181 {
1182 usleep(25000);
1183 auto ttl = milliSeconds - totalTime.elapsed();
1184 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1185 {
1186 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1187 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1188 .arg(ttlSeconds.count()));
1190 searchTime.start();
1191 }
1192 }
1193
1195
1196 if (!backends)
1197 {
1198 LOG(VB_GENERAL, LOG_INFO, "No UPnP backends found");
1199 return 0;
1200 }
1201
1202 int count = backends->Count();
1203 if (count)
1204 {
1205 LOG(VB_GENERAL, LOG_INFO,
1206 QString("Found %1 UPnP backends").arg(count));
1207 }
1208 else
1209 {
1210 LOG(VB_GENERAL, LOG_ERR,
1211 "No UPnP backends found, but SSDPCache::Instance()->Find() not NULL");
1212 }
1213
1214 if (count != 1)
1215 {
1216 backends->DecrRef();
1217 return count;
1218 }
1219
1220 // Get this backend's location:
1221 DeviceLocation *BE = backends->GetFirst();
1222 backends->DecrRef();
1223 backends = nullptr;
1224
1225 // We don't actually know the backend's access PIN, so this will
1226 // only work for ones that have PIN access disabled (i.e. 0000)
1227 int ret = (UPnPconnect(BE, QString())) ? 1 : -1;
1228
1229 BE->DecrRef();
1230
1231 return ret;
1232}
1233
1240{
1241 static const QString loc = "DefaultUPnP() - ";
1242
1243 // potentially saved in backendselect
1244 QString pin;
1245 QString usn;
1246 {
1247 auto config = XmlConfiguration(); // read-only
1248 pin = config.GetValue(XmlConfiguration::kDefaultPIN, QString(""));
1249 usn = config.GetValue(XmlConfiguration::kDefaultUSN, QString(""));
1250 }
1251
1252 if (usn.isEmpty())
1253 {
1254 LOG(VB_UPNP, LOG_INFO, loc + "No default UPnP backend");
1255 return false;
1256 }
1257
1258 LOG(VB_UPNP, LOG_INFO,
1259 loc + QString(XmlConfiguration::kDefaultFilename) +
1260 QString(" has default PIN '%1' and host USN: %2").arg(pin, usn));
1261
1262 // ----------------------------------------------------------------------
1263
1264 std::chrono::milliseconds timeout_ms {2s};
1265 auto timeout_s = duration_cast<std::chrono::seconds>(timeout_ms);
1266 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1267 .arg(timeout_s.count()));
1269
1270 // ----------------------------------------------------------------------
1271 // We need to give the server time to respond...
1272 // ----------------------------------------------------------------------
1273
1274 DeviceLocation* devicelocation = nullptr;
1275 MythTimer totalTime;
1276 MythTimer searchTime;
1277 totalTime.start();
1278 searchTime.start();
1279 while (totalTime.elapsed() < timeout_ms)
1280 {
1281 devicelocation = SSDPCache::Instance()->Find(SSDP::kBackendURI, usn);
1282 if (devicelocation)
1283 break;
1284
1285 usleep(25000);
1286
1287 auto ttl = timeout_ms - totalTime.elapsed();
1288 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1289 {
1290 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1291 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1292 .arg(ttlSeconds.count()));
1294 searchTime.start();
1295 }
1296 }
1297
1298 // ----------------------------------------------------------------------
1299
1300 if (!devicelocation)
1301 {
1302 Error = "Cannot find default UPnP backend";
1303 return false;
1304 }
1305
1306 if (UPnPconnect(devicelocation, pin))
1307 {
1308 devicelocation->DecrRef();
1309 return true;
1310 }
1311
1312 devicelocation->DecrRef();
1313 Error = "Cannot connect to default backend via UPnP. Wrong saved PIN?";
1314 return false;
1315}
1316
1321 const QString &PIN)
1322{
1323 QString error;
1324 QString loc = "UPnPconnect() - ";
1325 QString URL = backend->m_sLocation;
1326 MythXMLClient client(URL);
1327 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1328
1329 LOG(VB_UPNP, LOG_INFO, loc + QString("Trying host at %1").arg(URL));
1330 switch (client.GetConnectionInfo(PIN, &dbParams, error))
1331 {
1332 case UPnPResult_Success:
1333 GetMythDB()->SetDatabaseParams(dbParams);
1334 LOG(VB_UPNP, LOG_INFO, loc +
1335 "Got database hostname: " + dbParams.m_dbHostName);
1336 return true;
1337
1339 // The stored PIN is probably not correct.
1340 // We could prompt for the PIN and try again, but that needs a UI.
1341 // Easier to fail for now, and put up the full UI selector later
1342 LOG(VB_UPNP, LOG_ERR, loc + "Wrong PIN?");
1343 return false;
1344
1345 default:
1346 LOG(VB_UPNP, LOG_ERR, loc + error);
1347 break;
1348 }
1349
1350 // This backend may have a local DB with the default user/pass/DBname.
1351 // For whatever reason, we have failed to get anything back via UPnP,
1352 // so we might as well try the database directly as a last resort.
1353 QUrl theURL(URL);
1354 URL = theURL.host();
1355 if (URL.isEmpty())
1356 return false;
1357
1358 LOG(VB_UPNP, LOG_INFO, "Trying default DB credentials at " + URL);
1359 dbParams.m_dbHostName = URL;
1360 GetMythDB()->SetDatabaseParams(dbParams);
1361
1362 return true;
1363}
1364
1366{
1367 if (e->type() == MythEvent::kMythEventMessage)
1368 {
1369 if (m_disableeventpopup)
1370 return true;
1371
1372 if (GetNotificationCenter() && m_registration < 0)
1373 {
1374 m_registration = GetNotificationCenter()->Register(this);
1375 }
1376
1377 auto *me = dynamic_cast<MythEvent*>(e);
1378 if (me == nullptr)
1379 return true;
1380
1381 if (me->Message() == "VERSION_MISMATCH" && (1 == me->ExtraDataCount()))
1382 ShowVersionMismatchPopup(me->ExtraData(0).toUInt());
1383 else if (me->Message() == "CONNECTION_FAILURE")
1384 ShowConnectionFailurePopup(false);
1385 else if (me->Message() == "PERSISTENT_CONNECTION_FAILURE")
1386 ShowConnectionFailurePopup(true);
1387 else if (me->Message() == "CONNECTION_RESTABLISHED")
1388 HideConnectionFailurePopup();
1389 return true;
1390 }
1391
1392 return QObject::event(e);
1393}
1394
1396{
1397 QDateTime now = MythDate::current();
1398
1399 if (!GetNotificationCenter() || !m_ui || !m_ui->IsScreenSetup())
1400 return;
1401
1402 if (m_lastCheck.isValid() && now < m_lastCheck)
1403 return;
1404
1405 // When WOL is disallowed, standy mode,
1406 // we should not show connection failures.
1407 if (!gCoreContext->IsWOLAllowed())
1408 return;
1409
1410 m_lastCheck = now.addMSecs(5000); // don't refresh notification more than every 5s
1411
1412 QString description = (persistent) ?
1413 QObject::tr(
1414 "The connection to the master backend "
1415 "server has gone away for some reason. "
1416 "Is it running?") :
1417 QObject::tr(
1418 "Could not connect to the master backend server. Is "
1419 "it running? Is the IP address set for it in "
1420 "mythtv-setup correct?");
1421
1422 QString message = QObject::tr("Could not connect to master backend");
1423 MythErrorNotification n(message, sLocation, description);
1424 n.SetId(m_registration);
1425 n.SetParent(this);
1427}
1428
1430{
1431 if (!GetNotificationCenter())
1432 return;
1433
1434 if (!m_lastCheck.isValid())
1435 return;
1436
1437 MythCheckNotification n(QObject::tr("Backend is online"), sLocation);
1438 n.SetId(m_registration);
1439 n.SetParent(this);
1440 n.SetDuration(5s);
1442 m_lastCheck = QDateTime();
1443}
1444
1446{
1447 if (m_mbeVersionPopup)
1448 return;
1449
1450 QString message =
1451 QObject::tr(
1452 "The server uses network protocol version %1, "
1453 "but this client only understands version %2. "
1454 "Make sure you are running compatible versions of "
1455 "the backend and frontend.")
1456 .arg(remote_version).arg(MYTH_PROTO_VERSION);
1457
1458 if (HasMythMainWindow() && m_ui && m_ui->IsScreenSetup())
1459 {
1460 m_mbeVersionPopup = ShowOkPopup(
1462 }
1463 else
1464 {
1465 LOG(VB_GENERAL, LOG_ERR, LOC + message);
1466 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1467 }
1468}
1469
1470// Process Events while waiting for connection
1471// return true if progress is 100%
1473{
1474// bool ret = false;
1475// if (m_guiStartup)
1476// ret = m_guiStartup->updateProgress();
1477 qApp->processEvents(QEventLoop::AllEvents, 250);
1478 qApp->processEvents(QEventLoop::AllEvents, 250);
1479// return ret;
1480}
1481
1482namespace
1483{
1484// cache some settings in GUISettingsCache::m_cacheFilename
1485// only call this if the database is available.
1486
1487const std::array<QString, 13> GUISettingsCache::kSettings
1488{ "Theme", "Language", "Country", "GuiHeight",
1489 "GuiOffsetX", "GuiOffsetY", "GuiWidth", "RunFrontendInWindow",
1490 "AlwaysOnTop", "HideMouseCursor", "ThemePainter", "libCECEnabled",
1491 "StartupScreenDelay" };
1492
1493
1494bool GUISettingsCache::save()
1495{
1496 QString cacheDirName = GetConfDir() + '/' + m_cachePath;
1497 QDir dir(cacheDirName);
1498 dir.mkpath(cacheDirName);
1499 XmlConfiguration config = XmlConfiguration(m_cacheFilename);
1500 bool dirty = false;
1501 for (const auto & setting : kSettings)
1502 {
1503 QString cacheValue = config.GetValue("Settings/" + setting, QString());
1505 QString value = gCoreContext->GetSetting(setting, QString());
1506 if (value != cacheValue)
1507 {
1508 config.SetValue("Settings/" + setting, value);
1509 dirty = true;
1510 }
1511 }
1512 clearOverrides();
1513
1514 if (dirty)
1515 {
1516#ifndef Q_OS_ANDROID
1518#endif
1519 return config.Save();
1520 }
1521 return true;
1522}
1523
1524void GUISettingsCache::loadOverrides() const
1525{
1526 auto config = XmlConfiguration(m_cacheFilename); // read only
1527 for (const auto & setting : kSettings)
1528 {
1529 if (!gCoreContext->GetSetting(setting, QString()).isEmpty())
1530 continue;
1531 QString value = config.GetValue("Settings/" + setting, QString());
1532 if (!value.isEmpty())
1533 gCoreContext->OverrideSettingForSession(setting, value);
1534 }
1535 // Prevent power off TV after temporary window
1536 gCoreContext->OverrideSettingForSession("PowerOffTVAllowed", nullptr);
1537
1538 MythTranslation::load("mythfrontend");
1539}
1540
1541void GUISettingsCache::clearOverrides()
1542{
1543 QString language = gCoreContext->GetSetting("Language", QString());
1544 for (const auto & setting : kSettings)
1546 // Restore power off TV setting
1547 gCoreContext->ClearOverrideSettingForSession("PowerOffTVAllowed");
1548
1549 if (language != gCoreContext->GetSetting("Language", QString()))
1550 MythTranslation::load("mythfrontend");
1551}
1552
1553} // anonymous namespace
1554
1556{
1557 m_mbeVersionPopup = nullptr;
1558 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1559}
1560
1561MythContext::MythContext(QString binversion, bool needsBackend)
1562 : m_impl(new MythContext::Impl()),
1563 m_appBinaryVersion(std::move(binversion))
1564{
1565#ifdef Q_OS_WINDOWS
1566 static bool WSAStarted = false;
1567 if (!WSAStarted)
1568 {
1569 WSADATA wsadata;
1570 int res = WSAStartup(MAKEWORD(2, 0), &wsadata);
1571 LOG(VB_SOCKET, LOG_INFO,
1572 QString("WSAStartup returned %1").arg(res));
1573 }
1574#endif
1575
1577 m_impl->m_needsBackend = needsBackend;
1578
1580
1581 if (!gCoreContext || !gCoreContext->Init())
1582 {
1583 LOG(VB_GENERAL, LOG_EMERG, LOC + "Unable to allocate MythCoreContext");
1584 qApp->exit(GENERIC_EXIT_NO_MYTHCONTEXT);
1585 }
1586}
1587
1588bool MythContext::Init(const bool gui,
1589 const bool promptForBackend,
1590 const bool disableAutoDiscovery,
1591 const bool ignoreDB)
1592{
1593 if (!m_impl)
1594 {
1595 LOG(VB_GENERAL, LOG_EMERG, LOC + "Init() Out-of-memory");
1596 return false;
1597 }
1598
1599 qRegisterMetaType<std::chrono::seconds>("std::chrono::seconds");
1600 qRegisterMetaType<std::chrono::milliseconds>("std::chrono::milliseconds");
1601 qRegisterMetaType<std::chrono::microseconds>("std::chrono::microseconds");
1602
1604
1605 if (gui && QCoreApplication::applicationName() == MYTH_APPNAME_MYTHTV_SETUP)
1606 {
1608 QString warning = QObject::tr("mythtv-setup is deprecated.\n"
1609 "To set up MythTV, start mythbackend and use:\n"
1610 "http://localhost:6544/setupwizard");
1611 WaitFor(ShowOkPopup(warning));
1612 }
1613
1614 if (m_appBinaryVersion != MYTH_BINARY_VERSION)
1615 {
1616 LOG(VB_GENERAL, LOG_EMERG,
1617 QString("Application binary version (%1) does not "
1618 "match libraries (%2)")
1619 .arg(m_appBinaryVersion, MYTH_BINARY_VERSION));
1620
1621 QString warning = QObject::tr(
1622 "This application is not compatible "
1623 "with the installed MythTV libraries.");
1624 if (gui)
1625 {
1627 WaitFor(ShowOkPopup(warning));
1628 }
1629 LOG(VB_GENERAL, LOG_WARNING, warning);
1630
1631 return false;
1632 }
1633
1634#ifdef Q_OS_WINDOWS
1635 // HOME environment variable might not be defined
1636 // some libraries will fail without it
1637 QString home = getenv("HOME");
1638 if (home.isEmpty())
1639 {
1640 home = getenv("LOCALAPPDATA"); // Vista
1641 if (home.isEmpty())
1642 home = getenv("APPDATA"); // XP
1643 if (home.isEmpty())
1644 home = QString("."); // getenv("TEMP")?
1645
1646 _putenv(QString("HOME=%1").arg(home).toLocal8Bit().constData());
1647 }
1648#endif
1649
1650 // If HOME isn't defined, we won't be able to use default confdir of
1651 // $HOME/.mythtv nor can we rely on a MYTHCONFDIR that references $HOME
1652 QString homedir = QDir::homePath();
1653 QString confdir = qEnvironmentVariable("MYTHCONFDIR");
1654 if ((homedir.isEmpty() || homedir == "/") &&
1655 (confdir.isEmpty() || confdir.contains("$HOME")))
1656 {
1657 QString warning = "Cannot locate your home directory."
1658 " Please set the environment variable HOME";
1659 if (gui)
1660 {
1662 WaitFor(ShowOkPopup(warning));
1663 }
1664 LOG(VB_GENERAL, LOG_WARNING, warning);
1665
1666 return false;
1667 }
1668
1669 if (!m_impl->Init(gui, promptForBackend, disableAutoDiscovery, ignoreDB))
1670 {
1671 return false;
1672 }
1673
1674 SetDisableEventPopup(false);
1675
1676 if (m_impl->m_gui)
1677 {
1679 }
1680
1683
1684 return true;
1685}
1686
1688{
1689 if (m_cleanup != nullptr)
1690 {
1691 m_cleanup();
1692 }
1693
1694 if (m_impl->m_gui)
1695 {
1697 }
1698
1700 gCoreContext->InitPower(false /*destroy*/);
1701 if (MThreadPool::globalInstance()->activeThreadCount())
1702 LOG(VB_GENERAL, LOG_INFO, "Waiting for threads to exit.");
1703
1707
1708 LOG(VB_GENERAL, LOG_INFO, "Exiting");
1709
1710 logStop();
1711
1712 delete gCoreContext;
1713 gCoreContext = nullptr;
1714
1715 delete m_impl;
1716
1718}
1719
1721{
1722 m_impl->m_disableeventpopup = check;
1723}
1724
1726{
1727 /* this check is technically redundant since this is only called from
1728 MythContext::Init() and mythfrontend::main(); however, it is for safety
1729 and clarity until MythGUIContext is refactored out.
1730 */
1731 if (m_impl->m_gui)
1732 {
1733 return m_impl->m_GUISettingsCache.save();
1734 }
1735 return true;
1736}
1737
1739#include "mythcontext.moc"
1740
1741/* 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:876
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 m_gui
Should this context use GUI elements?
static void LanguagePrompt()
QDateTime m_lastCheck
bool checkPort(QString &host, int port, std::chrono::seconds timeLimit) const
Check if a port is open and sort out the link-local scope.
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="")
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)
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
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 checkPort(QString &host, int port, std::chrono::milliseconds timeLimit=30s, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:78
void cancelPortCheck(void)
Cancel the checkPort operation currently in progress.
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:77
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:154
static void Shutdown()
Definition: ssdp.cpp:67
static void Done(void)
static void Init(QObject *parent=nullptr)
static void Shutdown()
Definition: taskqueue.cpp:65
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:83
static const std::array< QString, 13 > kSettings
unsigned int uint
Definition: compat.h:68
@ 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:688
static constexpr const char * MYTH_APPNAME_MYTHTV_SETUP
Definition: mythappname.h:7
#define LOC
Definition: mythcontext.cpp:74
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:75
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:51
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
STL namespace.
void(* exec_program)(const QString &cmd)
Definition: mythuihelper.h:16
@ UPnPResult_Success
Definition: upnp.h:35
@ UPnPResult_ActionNotAuthorized
Definition: upnp.h:46