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 <QtEnvironmentVariables>
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
37#include "libmythbase/dbutil.h"
43#include "libmythbase/mythdb.h"
51#include "libmythbase/mythversion.h"
60#include "libmythui/mythimage.h"
64#include "libmythupnp/ssdp.h"
68
69#include "backendselect.h"
70#include "guistartup.h"
71
72#define LOC QString("MythContext: ")
73static const QString sLocation = "MythContext";
74
75namespace
76{
78{
79 public:
80 GUISettingsCache() = default;
81 GUISettingsCache(const QString& cache_filename, QString cache_path)
82 : m_cachePath(std::move(cache_path))
83 {
84 m_cacheFilename = m_cachePath + '/' + cache_filename;
85 if (m_cachePath.isEmpty() || cache_filename.isEmpty())
86 {
87 m_cacheFilename = m_cachePath = QString();
88 }
89 }
90
91 bool save();
92 void loadOverrides() const;
93 static void clearOverrides();
94
95 private:
96 QString m_cacheFilename {"cache/contextcache.xml"};
97 QString m_cachePath {"cache"};
98
99 static const std::array<QString, 13> kSettings;
100};
101
102} // anonymous namespace
103
104class MythContext::Impl : public QObject
105{
106 Q_OBJECT;
107
108 public:
109 explicit Impl();
110 ~Impl() override;
111
112 bool Init (bool gui,
113 bool promptForBackend,
114 bool disableAutoDiscovery,
115 bool ignoreDB);
116 bool FindDatabase(bool prompt, bool noAutodetect);
117 bool FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect);
118
119 void TempMainWindow();
120 void EndTempWindow();
121 static void LanguagePrompt();
122
123 static bool LoadDatabaseSettings();
124
125 static QString setLocalHostName(QString hostname);
126
127 bool PromptForDatabaseParams(const QString &error);
128 QString TestDBconnection(bool prompt=true);
129 void SilenceDBerrors();
130 void EnableDBerrors() const;
131 static void ResetDatabase(const DatabaseParams& dbParams);
132
134 ChooseBackend(const QString &error);
135 static int UPnPautoconf(std::chrono::milliseconds milliSeconds = 2s);
136 static bool DefaultUPnP(QString& Error);
137 static bool UPnPconnect(const DeviceLocation *backend, const QString &PIN);
138 void ShowGuiStartup();
139 bool checkPort(const QString &host, int port, std::chrono::seconds timeLimit) const;
140 static void processEvents();
141
142 bool event(QEvent* /*e*/) override; // QObject
143
144 protected:
145 void ShowConnectionFailurePopup(bool persistent);
147
148 void ShowVersionMismatchPopup(unsigned remote_version);
149
150 public slots:
151 void OnCloseDialog() const;
153
154 public:
156 bool m_gui {false};
157
159
160 QString m_dbHostCp;
161
163
164 MythUIHelper *m_ui {nullptr};
166 QEventLoop *m_loop {nullptr};
167 bool m_needsBackend {false};
168
169 GUISettingsCache m_GUISettingsCache;
170
171 private:
174 QDateTime m_lastCheck;
175};
176
177static void exec_program_cb(const QString &cmd)
178{
179 myth_system(cmd);
180}
181
182static void exec_program_tv_cb(const QString &cmd)
183{
184 QString s = cmd;
185 QStringList tokens = cmd.simplified().split(" ");
186 QStringList strlist;
187
188 bool cardidok = false;
189 int wantcardid = tokens[0].toInt(&cardidok, 10);
190
191 if (cardidok && wantcardid > 0)
192 {
193 strlist << QString("LOCK_TUNER %1").arg(wantcardid);
194 s = s.replace(0, tokens[0].length() + 1, "");
195 }
196 else
197 {
198 strlist << "LOCK_TUNER";
199 }
200
202 int cardid = strlist[0].toInt();
203
204 if (cardid >= 0)
205 {
206 s = s.arg(qPrintable(strlist[1]),
207 qPrintable(strlist[2]),
208 qPrintable(strlist[3]));
209
210 myth_system(s);
211
212 strlist = QStringList(QString("FREE_TUNER %1").arg(cardid));
214 }
215 else
216 {
217 QString label;
218
219 if (cardidok)
220 {
221 if (cardid == -1)
222 {
223 label = QObject::tr("Could not find specified tuner (%1).")
224 .arg(wantcardid);
225 }
226 else
227 {
228 label = QObject::tr("Specified tuner (%1) is already in use.")
229 .arg(wantcardid);
230 }
231 }
232 else
233 {
234 label = QObject::tr("All tuners are currently in use. If you want "
235 "to watch TV, you can cancel one of the "
236 "in-progress recordings from the delete menu");
237 }
238
239 LOG(VB_GENERAL, LOG_ALERT, QString("exec_program_tv: ") + label);
240
241 ShowOkPopup(label);
242 }
243}
244
245static void configplugin_cb(const QString &cmd)
246{
248 if (!pmanager)
249 return;
250
251 if (GetNotificationCenter() && pmanager->config_plugin(cmd.trimmed()))
252 {
254 QObject::tr("Failed to configure plugin"));
255 }
256}
257
258static void plugin_cb(const QString &cmd)
259{
261 if (!pmanager)
262 return;
263
264 if (GetNotificationCenter() && pmanager->run_plugin(cmd.trimmed()))
265 {
266 ShowNotificationError(QObject::tr("Plugin failure"),
267 sLocation,
268 QObject::tr("%1 failed to run for some reason").arg(cmd));
269 }
270}
271
272static void eject_cb()
273{
275}
276
278 : m_loop(new QEventLoop(this))
279{
281}
282
284{
285 if (GetNotificationCenter() && m_registration > 0)
286 {
287 GetNotificationCenter()->UnRegister(this, m_registration, true);
288 }
289
290 delete m_loop;
291
292 if (m_ui)
294}
295
307{
308 if (HasMythMainWindow())
309 return;
310
311 SilenceDBerrors();
312
313#ifdef Q_OS_DARWIN
314 // Qt 4.4 has window-focus problems
315 gCoreContext->OverrideSettingForSession("RunFrontendInWindow", "1");
316#endif
317 GetMythUI()->Init();
319 mainWindow->Init();
320}
321
323{
324 if (HasMythMainWindow())
325 {
326 if (m_guiStartup && !m_guiStartup->m_Exit)
327 {
329 if (mainStack)
330 {
331 mainStack->PopScreen(m_guiStartup, false);
332 m_guiStartup = nullptr;
333 }
334 }
335 }
336 EnableDBerrors();
337}
338
340{
341 // ask user for language settings
343 MythTranslation::load("mythfrontend");
344}
345
354bool MythContext::Impl::checkPort(const QString &host, int port, std::chrono::seconds timeLimit) const
355{
356 PortChecker checker;
357 if (m_guiStartup)
358 QObject::connect(m_guiStartup, &GUIStartup::cancelPortCheck, &checker, &PortChecker::cancelPortCheck);
359 return checker.checkPort(host, port, timeLimit);
360}
361
362
363bool MythContext::Impl::Init(const bool gui,
364 const bool promptForBackend,
365 const bool disableAutoDiscovery,
366 const bool ignoreDB)
367{
368 gCoreContext->GetDB()->IgnoreDatabase(ignoreDB);
369 m_gui = gui;
370 if (gui)
371 {
372 m_GUISettingsCache.loadOverrides();
373 }
374
376 m_needsBackend = true;
377
378 // Creates screen saver control if we will have a GUI
379 if (gui)
380 m_ui = GetMythUI();
381
382 // ---- database connection stuff ----
383
384 if (!ignoreDB && !FindDatabase(promptForBackend, disableAutoDiscovery))
385 {
386 EndTempWindow();
387 return false;
388 }
389
390 // ---- keep all DB-using stuff below this line ----
391
392 // Prompt for language if this is a first time install and
393 // we didn't already do so.
394 if (m_gui && !gCoreContext->GetDB()->HaveSchema())
395 {
396 TempMainWindow();
397 LanguagePrompt();
398 }
401
402 // Close GUI Startup Window.
403 if (m_guiStartup)
404 {
406 if (mainStack)
407 mainStack->PopScreen(m_guiStartup, false);
408 m_guiStartup=nullptr;
409 }
410 EndTempWindow();
411
412 if (gui)
413 {
414 MythUIMenuCallbacks cbs {};
416 cbs.exec_program_tv = exec_program_tv_cb;
417 cbs.configplugin = configplugin_cb;
418 cbs.plugin = plugin_cb;
419 cbs.eject = eject_cb;
420
421 m_ui->Init(cbs);
422 }
423
424 return true;
425}
426
432bool MythContext::Impl::FindDatabaseChoose(bool loaded, bool manualSelect, bool autoSelect)
433{
434 QString failure;
435
436 // 2. If the user isn't forcing up the chooser UI, look for a default
437 // backend in XmlConfiguration::k_default_filename, then test DB settings we've got so far:
438 if (!manualSelect && XmlConfiguration().FileExists())
439 {
440 // XmlConfiguration::k_default_filename may contain a backend host UUID and PIN.
441 // If so, try to AutoDiscover UPnP server, and use its DB settings:
442
443 if (DefaultUPnP(failure)) // Probably a valid backend,
444 autoSelect = manualSelect = false; // so disable any further UPnP
445 else
446 if (!failure.isEmpty())
447 LOG(VB_GENERAL, LOG_ALERT, failure);
448
449 failure = TestDBconnection(loaded);
450 if (failure.isEmpty())
451 return true;
452 if (m_guiStartup && m_guiStartup->m_Exit)
453 return false;
454 if (m_guiStartup && m_guiStartup->m_Search)
455 autoSelect=true;
456 }
457
458 // 3. Try to automatically find the single backend:
459 if (autoSelect)
460 {
461 int count = UPnPautoconf();
462
463 if (count == 0)
464 failure = QObject::tr("No UPnP backends found", "Backend Setup");
465
466 if (count == 1)
467 {
468 failure = TestDBconnection();
469 if (failure.isEmpty())
470 return true;
471 if (m_guiStartup && m_guiStartup->m_Exit)
472 return false;
473 }
474
475 // Multiple BEs, or needs PIN.
476 manualSelect |= (count > 1 || count == -1);
477 // Search requested
478 if (m_guiStartup && m_guiStartup->m_Search)
479 manualSelect=true;
480 }
481
482 manualSelect &= m_gui; // no interactive command-line chooser yet
483
484 // Queries the user for the DB info
485 bool haveDbInfo {false};
486 while (!haveDbInfo)
487 {
488 if (manualSelect)
489 {
490 // Get the user to select a backend from a possible list:
491 switch (ChooseBackend(failure))
492 {
494 break;
496 manualSelect = false;
497 break;
499 {
500 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - failed");
501 return false;
502 }
503 }
504 }
505
506 if (!manualSelect)
507 {
508 // If this is a backend, No longer prompt for database.
509 // Instead allow the web server to start so that the
510 // database can be set up there
512 || !PromptForDatabaseParams(failure))
513 {
514 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - failed");
515 return false;
516 }
517 }
518 failure = TestDBconnection();
519 haveDbInfo = failure.isEmpty();
520 if (!failure.isEmpty())
521 LOG(VB_GENERAL, LOG_ALERT, failure);
522 if (m_guiStartup && m_guiStartup->m_Exit)
523 return false;
524 if (m_guiStartup && m_guiStartup->m_Search)
525 manualSelect=true;
526 if (m_guiStartup && m_guiStartup->m_Setup)
527 manualSelect=false;
528 }
529
530 return true;
531}
532
545bool MythContext::Impl::FindDatabase(bool prompt, bool noAutodetect)
546{
547 // We can only prompt if autodiscovery is enabled..
548 bool manualSelect = prompt && !noAutodetect;
549
550 // 1. Either load XmlConfiguration::k_default_filename or use sensible "localhost" defaults:
551 bool loaded = LoadDatabaseSettings();
552 const DatabaseParams dbParamsFromFile = GetMythDB()->GetDatabaseParams();
553 setLocalHostName(dbParamsFromFile.m_localHostName);
554
555 // In addition to the UI chooser, we can also try to autoSelect later,
556 // but only if we're not doing manualSelect and there was no
557 // valid XmlConfiguration::k_default_filename
558 bool autoSelect = !manualSelect && !loaded && !noAutodetect;
559
560 if (!FindDatabaseChoose(loaded, manualSelect, autoSelect))
561 return false;
562
563 LOG(VB_GENERAL, LOG_DEBUG, "FindDatabase() - Success!");
564 // If we got the database from UPNP then the wakeup settings are lost.
565 // Restore them.
566 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
567 dbParams.m_wolEnabled = dbParamsFromFile.m_wolEnabled;
568 dbParams.m_wolReconnect = dbParamsFromFile.m_wolReconnect;
569 dbParams.m_wolRetry = dbParamsFromFile.m_wolRetry;
570 dbParams.m_wolCommand = dbParamsFromFile.m_wolCommand;
571
572 GetMythDB()->SaveDatabaseParams(dbParams, !loaded || dbParamsFromFile != dbParams);
573 EnableDBerrors();
574 ResetDatabase(dbParams);
575 return true;
576}
577
582{
583 auto config = XmlConfiguration(); // read-only
584
585 DatabaseParams dbParams;
586
587 dbParams.m_localHostName = config.GetValue("LocalHostName", "");
588 dbParams.m_dbHostPing = config.GetValue(XmlConfiguration::kDefaultDB + "PingHost", true);
589 dbParams.m_dbHostName = config.GetValue(XmlConfiguration::kDefaultDB + "Host", "");
590 dbParams.m_dbUserName = config.GetValue(XmlConfiguration::kDefaultDB + "UserName", "");
591 dbParams.m_dbPassword = config.GetValue(XmlConfiguration::kDefaultDB + "Password", "");
592 dbParams.m_dbName = config.GetValue(XmlConfiguration::kDefaultDB + "DatabaseName", "");
593 dbParams.m_dbPort = config.GetValue(XmlConfiguration::kDefaultDB + "Port", 0);
594
595 dbParams.m_wolEnabled = config.GetValue(XmlConfiguration::kDefaultWOL + "Enabled", false);
596 dbParams.m_wolReconnect =
597 config.GetDuration<std::chrono::seconds>(XmlConfiguration::kDefaultWOL + "SQLReconnectWaitTime", 0s);
598 dbParams.m_wolRetry = config.GetValue(XmlConfiguration::kDefaultWOL + "SQLConnectRetry", 5);
599 dbParams.m_wolCommand = config.GetValue(XmlConfiguration::kDefaultWOL + "Command", "");
600
602
603 if (!ok)
604 dbParams = {};
605
606 dbParams.m_localEnabled = !(dbParams.m_localHostName.isEmpty() ||
607 dbParams.m_localHostName == "my-unique-identifier-goes-here");
608
609 GetMythDB()->SetDatabaseParams(dbParams);
610 return ok;
611}
612
614{
615 if (hostname.isEmpty() ||
616 hostname == "my-unique-identifier-goes-here")
617 {
618 LOG(VB_GENERAL, LOG_INFO, "Empty LocalHostName. This is typical.");
619 hostname = QHostInfo::localHostName();
620
621#ifndef Q_OS_ANDROID
622 if (hostname.isEmpty())
623 {
624 LOG(VB_GENERAL, LOG_ALERT,
625 "MCP: Error, could not determine host name." + ENO);
626 }
627#else //elif defined Q_OS_ANDROID
628#define ANDROID_EXCEPTION_CHECK \
629 if (env->ExceptionCheck()) \
630 { \
631 env->ExceptionClear(); \
632 exception=true; \
633 }
634
635 if ((hostname == "localhost") || hostname.isEmpty())
636 {
637 hostname = "android";
638 bool exception=false;
641#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
642 QAndroidJniObject activity = QtAndroid::androidActivity();
643#else
644 QJniObject activity = QNativeInterface::QAndroidApplication::context();
645#endif
647 QAndroidJniObject appctx = activity.callObjectMethod
648 ("getApplicationContext", "()Landroid/content/Context;");
650 QAndroidJniObject contentR = appctx.callObjectMethod
651 ("getContentResolver", "()Landroid/content/ContentResolver;");
653 QAndroidJniObject androidId = QAndroidJniObject::callStaticObjectMethod
654 ("android/provider/Settings$Secure", "getString",
655 "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;",
656 contentR.object<jobject>(),
657 myID.object<jstring>());
659 if (exception)
660 LOG(VB_GENERAL, LOG_ALERT,
661 "Java exception looking for android id");
662 else
663 hostname = QString("android-%1").arg(androidId.toString());
664 }
665#endif
666
667 }
668
669 LOG(VB_GENERAL, LOG_INFO, QString("Using a profile name of: '%1' (Usually the "
670 "same as this host's name.)")
671 .arg(hostname));
673
674 return hostname;
675}
676
677
679{
680 if (m_loop && m_loop->isRunning())
681 {
682 m_loop->exit();
683 }
684}
685
686
687// No longer prompt for database, instaed allow the
688// web server to start so that the datbase can be
689// set up there
690
692{
693 bool accepted = false;
694 if (m_gui)
695 {
696 TempMainWindow();
697 LanguagePrompt();
698
699 // Tell the user what went wrong:
700 if (!error.isEmpty())
702
703 // ask user for database parameters
704
705 EnableDBerrors();
707 auto *dbsetting = new DatabaseSettings();
708 auto *ssd = new StandardSettingDialog(mainStack, "databasesettings",
709 dbsetting);
710 if (ssd->Create())
711 {
712 mainStack->AddScreen(ssd);
713 connect(dbsetting, &DatabaseSettings::isClosing,
715 if (!m_loop->isRunning())
716 m_loop->exec();
717 }
718 else
719 {
720 delete ssd;
721 }
722 SilenceDBerrors();
723 EndTempWindow();
724 accepted = true;
725 }
726 else
727 {
728 DatabaseParams params = GetMythDB()->GetDatabaseParams();
729 QString response;
730 std::this_thread::sleep_for(1s);
731 // give user chance to skip config
732 std::cout << '\n' << error.toLocal8Bit().constData() << "\n\n";
733 response = getResponse("Would you like to configure the database "
734 "connection now?",
735 "no");
736 if (!response.startsWith('y', Qt::CaseInsensitive))
737 return false;
738
739 params.m_dbHostName = getResponse("Database host name:",
740 params.m_dbHostName);
741 response = getResponse("Should I test connectivity to this host "
742 "using the ping command?", "yes");
743 params.m_dbHostPing = response.startsWith('y', Qt::CaseInsensitive);
744
745 params.m_dbPort = intResponse("Database non-default port:",
746 params.m_dbPort);
747 params.m_dbName = getResponse("Database name:",
748 params.m_dbName);
749 params.m_dbUserName = getResponse("Database user name:",
750 params.m_dbUserName);
751 params.m_dbPassword = getResponse("Database password:",
752 params.m_dbPassword);
753
754 params.m_localHostName = getResponse("Unique identifier for this machine "
755 "(if empty, the local host name "
756 "will be used):",
757 params.m_localHostName);
758 params.m_localEnabled = !params.m_localHostName.isEmpty();
759
760 response = getResponse("Would you like to use Wake-On-LAN to retry "
761 "database connections?",
762 (params.m_wolEnabled ? "yes" : "no"));
763 params.m_wolEnabled = response.startsWith('y', Qt::CaseInsensitive);
764
765 if (params.m_wolEnabled)
766 {
767 params.m_wolReconnect =
768 std::chrono::seconds(intResponse("Seconds to wait for "
769 "reconnection:",
770 params.m_wolReconnect.count()));
771 params.m_wolRetry = intResponse("Number of times to retry:",
772 params.m_wolRetry);
773 params.m_wolCommand = getResponse("Command to use to wake server or server MAC address:",
774 params.m_wolCommand);
775 }
776
777 accepted = GetMythDB()->SaveDatabaseParams(params, false);
778 }
779 return accepted;
780}
781
788{
789 QString err;
790 QString host;
791
792 // Jan 20, 2017
793 // Changed to use port check instead of ping
794
795 int port = 0;
796
797 // 1 = db awake, 2 = db listening, 3 = db connects,
798 // 4 = backend awake, 5 = backend listening
799 // 6 = success
800
801 enum startupStates : std::uint8_t {
802 st_start = 0,
803 st_dbAwake = 1,
804 st_dbStarted = 2,
805 st_dbConnects = 3,
806 st_beWOL = 4,
807 st_beAwake = 5,
808 st_success = 6
809 } startupState = st_start;
810
811 static const std::array<const QString, 7> kGuiStatuses
812 {"start", "dbAwake", "dbStarted", "dbConnects", "beWOL", "beAwake", "success"};
813
814 auto secondsStartupScreenDelay = gCoreContext->GetDurSetting<std::chrono::seconds>("StartupScreenDelay", 2s);
815 auto msStartupScreenDelay = std::chrono::duration_cast<std::chrono::milliseconds>(secondsStartupScreenDelay);
816 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
817 bool keep_trying = true;
818
819 while ((startupState < st_success) && keep_trying)
820 {
821 QElapsedTimer timer;
822 timer.start();
823 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
824 host = m_dbHostCp;
825 else
826 host = dbParams.m_dbHostName;
827 port = dbParams.m_dbPort;
828 if (port == 0)
829 port = 3306;
830 std::chrono::seconds wakeupTime = 3s;
831 int attempts = 11;
832 if (dbParams.m_wolEnabled)
833 {
834 wakeupTime = dbParams.m_wolReconnect;
835 attempts = dbParams.m_wolRetry + 1;
836 startupState = st_start;
837 }
838 else
839 {
840 startupState = st_dbAwake;
841 }
842 attempts = std::max(attempts, 6);
843 if (!prompt)
844 attempts=1;
845 if (wakeupTime < 5s)
846 wakeupTime = 5s;
847
848 std::chrono::seconds progressTotal = wakeupTime * attempts;
849
850 if (m_guiStartup && !m_guiStartup->m_Exit)
851 m_guiStartup->setTotal(progressTotal);
852
853 QString beWOLCmd = QString();
854 QString backendIP = QString();
855 int backendPort = 0;
856 QString masterserver;
857
858 for (int attempt = 0;
859 attempt < attempts && startupState != st_success;
860 ++attempt)
861 {
862 // The first time do everything with minimum timeout and
863 // no GUI, for the normal case where all is OK
864 // After that show the GUI (if this is a GUI program)
865
866 LOG(VB_GENERAL, LOG_INFO,
867 QString("Start up testing connections. DB %1, BE %2, attempt %3, status %4, Delay: %5")
868 .arg(host, backendIP, QString::number(attempt),
869 kGuiStatuses[startupState],
870 QString::number(msStartupScreenDelay.count())) );
871
872 std::chrono::seconds useTimeout = wakeupTime;
873 if (attempt == 0)
874 useTimeout=1s;
875
876 if (m_gui && !m_guiStartup)
877 {
878 if (msStartupScreenDelay==0ms || timer.hasExpired(msStartupScreenDelay.count()))
879 {
880 ShowGuiStartup();
881 if (m_guiStartup)
882 m_guiStartup->setTotal(progressTotal);
883 }
884 }
885 if (m_guiStartup && !m_guiStartup->m_Exit)
886 {
887 if (attempt > 0)
888 m_guiStartup->setStatusState(kGuiStatuses[startupState]);
889 m_guiStartup->setMessageState("empty");
890 processEvents();
891 }
892 switch (startupState)
893 {
894 case st_start:
895 if (dbParams.m_wolEnabled)
896 {
897 if (attempt > 0)
898 MythWakeup(dbParams.m_wolCommand);
899 if (!checkPort(host, port, useTimeout))
900 break;
901 }
902 startupState = st_dbAwake;
903 [[fallthrough]];
904 case st_dbAwake:
905 if (!checkPort(host, port, useTimeout))
906 break;
907 startupState = st_dbStarted;
908 [[fallthrough]];
909 case st_dbStarted:
910 // If the database is connecting with link-local
911 // address, it may have changed
912 PortChecker{}.resolveLinkLocal(host, port, useTimeout);
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 {
984 keep_trying = false;
985 break;
986 }
987 if (m_guiStartup->m_Setup
988 || m_guiStartup->m_Search
989 || m_guiStartup->m_Retry)
990 break;
991 }
992 processEvents();
993 }
994 if (startupState == st_success)
995 break;
996
997 QString stateMsg = kGuiStatuses[startupState];
998 stateMsg.append("Fail");
999 LOG(VB_GENERAL, LOG_INFO,
1000 QString("Start up failure. host %1, status %2")
1001 .arg(host, stateMsg));
1002
1003 if (!m_gui)
1004 keep_trying = false;
1005 if (m_gui && !m_guiStartup)
1006 {
1007 ShowGuiStartup();
1008 if (m_guiStartup)
1009 m_guiStartup->setTotal(progressTotal);
1010 }
1011
1012 if (m_guiStartup
1013 && !m_guiStartup->m_Exit
1014 && !m_guiStartup->m_Setup
1015 && !m_guiStartup->m_Search
1016 && !m_guiStartup->m_Retry)
1017 {
1018 m_guiStartup->updateProgress(true);
1019 m_guiStartup->setStatusState(stateMsg);
1020 m_guiStartup->setMessageState("makeselection");
1021 m_loop->exec();
1022 if (!m_guiStartup->m_Retry)
1023 keep_trying = false;
1024 }
1025 }
1026
1027 if (startupState < st_dbAwake)
1028 {
1029 LOG(VB_GENERAL, LOG_WARNING, QString("Pinging to %1 failed, database will be unavailable").arg(host));
1030 SilenceDBerrors();
1031 err = QObject::tr(
1032 "Cannot find (ping) database host %1 on the network",
1033 "Backend Setup");
1034 return err.arg(host);
1035 }
1036
1037 if (startupState < st_dbConnects)
1038 {
1039 SilenceDBerrors();
1040 return QObject::tr("Cannot login to database", "Backend Setup");
1041 }
1042
1043 if (startupState < st_success)
1044 {
1045 return QObject::tr("Cannot connect to backend", "Backend Setup");
1046 }
1047
1048 // Current DB connection may have been silenced (invalid):
1049 EnableDBerrors();
1050 ResetDatabase(dbParams);
1051
1052 return {};
1053}
1054
1055// Show the Gui Startup window.
1056// This is called if there is a delay in startup for any reason
1057// such as the database being unavailable
1059{
1060 if (!m_gui)
1061 return;
1062 TempMainWindow();
1063 MythMainWindow *mainWindow = GetMythMainWindow();
1064 MythScreenStack *mainStack = mainWindow->GetMainStack();
1065 if (mainStack)
1066 {
1067 if (!m_guiStartup)
1068 {
1069 m_guiStartup = new GUIStartup(mainStack, m_loop);
1070 if (!m_guiStartup->Create())
1071 {
1072 delete m_guiStartup;
1073 m_guiStartup = nullptr;
1074 }
1075 if (m_guiStartup)
1076 {
1077 mainStack->AddScreen(m_guiStartup, false);
1078 processEvents();
1079 }
1080 }
1081 }
1082}
1083
1093{
1094 // This silences any DB errors from Get*Setting(),
1095 // (which is the vast majority of them)
1096 gCoreContext->GetDB()->SetSuppressDBMessages(true);
1097
1098 // Save the configured hostname, so that we can
1099 // still display it in the DatabaseSettings screens
1100 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1101 if (!dbParams.m_dbHostName.isEmpty())
1102 {
1103 m_dbHostCp = dbParams.m_dbHostName;
1104 dbParams.m_dbHostName.clear();
1105 GetMythDB()->SetDatabaseParams(dbParams);
1106 }
1107}
1108
1110{
1111 // Restore (possibly) blanked hostname
1112 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1113 if (dbParams.m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
1114 {
1115 dbParams.m_dbHostName = m_dbHostCp;
1116 GetMythDB()->SetDatabaseParams(dbParams);
1117 }
1118
1119 gCoreContext->GetDB()->SetSuppressDBMessages(false);
1120}
1121
1122
1135{
1136 auto* db = GetMythDB();
1137 db->GetDBManager()->CloseDatabases();
1138 db->SetDatabaseParams(dbParams);
1139 db->ClearSettingsCache();
1140}
1141
1146{
1147 TempMainWindow();
1148 LanguagePrompt();
1149
1150 // Tell the user what went wrong:
1151 if (!error.isEmpty())
1152 {
1153 LOG(VB_GENERAL, LOG_ERR, QString("Error: %1").arg(error));
1155 }
1156
1157 LOG(VB_GENERAL, LOG_INFO, "Putting up the UPnP backend chooser");
1158
1159 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1162 GetMythDB()->SetDatabaseParams(dbParams);
1163
1164 EndTempWindow();
1165
1166 return ret;
1167}
1168
1175int MythContext::Impl::UPnPautoconf(const std::chrono::milliseconds milliSeconds)
1176{
1177 auto seconds = duration_cast<std::chrono::seconds>(milliSeconds);
1178 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1179 .arg(seconds.count()));
1180
1182
1183 // Search for a total of 'milliSeconds' ms, sending new search packet
1184 // about every 250 ms until less than one second remains.
1185 MythTimer totalTime; totalTime.start();
1186 MythTimer searchTime; searchTime.start();
1187 while (totalTime.elapsed() < milliSeconds)
1188 {
1189 std::this_thread::sleep_for(25ms);
1190 auto ttl = milliSeconds - totalTime.elapsed();
1191 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1192 {
1193 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1194 LOG(VB_GENERAL, LOG_INFO, QString("UPNP Search %1 secs")
1195 .arg(ttlSeconds.count()));
1197 searchTime.start();
1198 }
1199 }
1200
1201 std::this_thread::sleep_for(25ms);
1202 processEvents();
1203
1205
1206 if (!backends)
1207 {
1208 LOG(VB_GENERAL, LOG_INFO, "No UPnP backends found");
1209 return 0;
1210 }
1211
1212 int count = backends->Count();
1213 if (count)
1214 {
1215 LOG(VB_GENERAL, LOG_INFO,
1216 QString("Found %1 UPnP backends").arg(count));
1217 }
1218 else
1219 {
1220 LOG(VB_GENERAL, LOG_ERR,
1221 "No UPnP backends found, but SSDPCache::Instance()->Find() not NULL");
1222 }
1223
1224 if (count != 1)
1225 {
1226 backends->DecrRef();
1227 return count;
1228 }
1229
1230 // Get this backend's location:
1231 DeviceLocation *BE = backends->GetFirst();
1232 backends->DecrRef();
1233 backends = nullptr;
1234
1235 // We don't actually know the backend's access PIN, so this will
1236 // only work for ones that have PIN access disabled (i.e. 0000)
1237 int ret = (UPnPconnect(BE, QString())) ? 1 : -1;
1238
1239 BE->DecrRef();
1240
1241 return ret;
1242}
1243
1250{
1251 static const QString loc = "DefaultUPnP() - ";
1252
1253 // potentially saved in backendselect
1254 QString pin;
1255 QString usn;
1256 {
1257 auto config = XmlConfiguration(); // read-only
1258 pin = config.GetValue(XmlConfiguration::kDefaultPIN, QString(""));
1259 usn = config.GetValue(XmlConfiguration::kDefaultUSN, QString(""));
1260 }
1261
1262 if (usn.isEmpty())
1263 {
1264 LOG(VB_UPNP, LOG_INFO, loc + "No default UPnP backend");
1265 return false;
1266 }
1267
1268 LOG(VB_UPNP, LOG_INFO,
1269 loc + QString(XmlConfiguration::kDefaultFilename) +
1270 QString(" has default PIN '%1' and host USN: %2").arg(pin, usn));
1271
1272 // ----------------------------------------------------------------------
1273
1274 std::chrono::milliseconds timeout_ms {2s};
1275 auto timeout_s = duration_cast<std::chrono::seconds>(timeout_ms);
1276 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1277 .arg(timeout_s.count()));
1279
1280 // ----------------------------------------------------------------------
1281 // We need to give the server time to respond...
1282 // ----------------------------------------------------------------------
1283
1284 DeviceLocation* devicelocation = nullptr;
1285 MythTimer totalTime;
1286 MythTimer searchTime;
1287 totalTime.start();
1288 searchTime.start();
1289 while (totalTime.elapsed() < timeout_ms)
1290 {
1291 devicelocation = SSDPCache::Instance()->Find(SSDP::kBackendURI, usn);
1292 if (devicelocation)
1293 break;
1294
1295 std::this_thread::sleep_for(25ms);
1296
1297 auto ttl = timeout_ms - totalTime.elapsed();
1298 if ((searchTime.elapsed() > 249ms) && (ttl > 1s))
1299 {
1300 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1301 LOG(VB_GENERAL, LOG_INFO, loc + QString("UPNP Search up to %1 secs")
1302 .arg(ttlSeconds.count()));
1304 searchTime.start();
1305 }
1306 }
1307
1308 // ----------------------------------------------------------------------
1309
1310 if (!devicelocation)
1311 {
1312 Error = "Cannot find default UPnP backend";
1313 return false;
1314 }
1315
1316 if (UPnPconnect(devicelocation, pin))
1317 {
1318 devicelocation->DecrRef();
1319 return true;
1320 }
1321
1322 devicelocation->DecrRef();
1323 Error = "Cannot connect to default backend via UPnP. Wrong saved PIN?";
1324 return false;
1325}
1326
1331 const QString &PIN)
1332{
1333 QString error;
1334 QString loc = "UPnPconnect() - ";
1335 QString URL = backend->m_sLocation;
1336 QUrl theURL(URL);
1337 MythXMLClient client(theURL);
1338 DatabaseParams dbParams = GetMythDB()->GetDatabaseParams();
1339
1340 LOG(VB_UPNP, LOG_INFO, loc + QString("Trying host at %1").arg(URL));
1341 switch (client.GetConnectionInfo(PIN, &dbParams, error))
1342 {
1343 case UPnPResult_Success:
1344 GetMythDB()->SetDatabaseParams(dbParams);
1345 LOG(VB_UPNP, LOG_INFO, loc +
1346 "Got database hostname: " + dbParams.m_dbHostName);
1347 return true;
1348
1350 // The stored PIN is probably not correct.
1351 // We could prompt for the PIN and try again, but that needs a UI.
1352 // Easier to fail for now, and put up the full UI selector later
1353 LOG(VB_UPNP, LOG_ERR, loc + "Wrong PIN?");
1354 return false;
1355
1356 default:
1357 LOG(VB_UPNP, LOG_ERR, loc + error);
1358 break;
1359 }
1360
1361 // This backend may have a local DB with the default user/pass/DBname.
1362 // For whatever reason, we have failed to get anything back via UPnP,
1363 // so we might as well try the database directly as a last resort.
1364 URL = theURL.host();
1365 if (URL.isEmpty())
1366 return false;
1367
1368 LOG(VB_UPNP, LOG_INFO, "Trying default DB credentials at " + URL);
1369 dbParams.m_dbHostName = URL;
1370 GetMythDB()->SetDatabaseParams(dbParams);
1371
1372 return true;
1373}
1374
1376{
1377 if (e->type() == MythEvent::kMythEventMessage)
1378 {
1379 if (m_disableeventpopup)
1380 return true;
1381
1382 if (GetNotificationCenter() && m_registration < 0)
1383 {
1384 m_registration = GetNotificationCenter()->Register(this);
1385 }
1386
1387 auto *me = dynamic_cast<MythEvent*>(e);
1388 if (me == nullptr)
1389 return true;
1390
1391 if (me->Message() == "VERSION_MISMATCH" && (1 == me->ExtraDataCount()))
1392 ShowVersionMismatchPopup(me->ExtraData(0).toUInt());
1393 else if (me->Message() == "CONNECTION_FAILURE")
1394 ShowConnectionFailurePopup(false);
1395 else if (me->Message() == "PERSISTENT_CONNECTION_FAILURE")
1396 ShowConnectionFailurePopup(true);
1397 else if (me->Message() == "CONNECTION_RESTABLISHED")
1398 HideConnectionFailurePopup();
1399 return true;
1400 }
1401
1402 return QObject::event(e);
1403}
1404
1406{
1407 QDateTime now = MythDate::current();
1408
1409 if (!GetNotificationCenter() || !m_ui || !m_ui->IsScreenSetup())
1410 return;
1411
1412 if (m_lastCheck.isValid() && now < m_lastCheck)
1413 return;
1414
1415 // When WOL is disallowed, standy mode,
1416 // we should not show connection failures.
1417 if (!gCoreContext->IsWOLAllowed())
1418 return;
1419
1420 m_lastCheck = now.addMSecs(5000); // don't refresh notification more than every 5s
1421
1422 QString description = persistent ?
1423 QObject::tr(
1424 "The connection to the master backend "
1425 "server has gone away for some reason. "
1426 "Is it running?") :
1427 QObject::tr(
1428 "Could not connect to the master backend server. Is "
1429 "it running? Is the IP address set for it in "
1430 "mythtv-setup correct?");
1431
1432 QString message = QObject::tr("Could not connect to master backend");
1433 MythErrorNotification n(message, sLocation, description);
1434 n.SetId(m_registration);
1435 n.SetParent(this);
1437}
1438
1440{
1441 if (!GetNotificationCenter())
1442 return;
1443
1444 if (!m_lastCheck.isValid())
1445 return;
1446
1447 MythCheckNotification n(QObject::tr("Backend is online"), sLocation);
1448 n.SetId(m_registration);
1449 n.SetParent(this);
1450 n.SetDuration(5s);
1452 m_lastCheck = QDateTime();
1453}
1454
1456{
1457 if (m_mbeVersionPopup)
1458 return;
1459
1460 QString message =
1461 QObject::tr(
1462 "The server uses network protocol version %1, "
1463 "but this client only understands version %2. "
1464 "Make sure you are running compatible versions of "
1465 "the backend and frontend.")
1466 .arg(remote_version).arg(MYTH_PROTO_VERSION);
1467
1468 if (HasMythMainWindow() && m_ui && m_ui->IsScreenSetup())
1469 {
1470 m_mbeVersionPopup = ShowOkPopup(
1472 }
1473 else
1474 {
1475 LOG(VB_GENERAL, LOG_ERR, LOC + message);
1476 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1477 }
1478}
1479
1480// Process Events while waiting for connection
1481// return true if progress is 100%
1483{
1484// bool ret = false;
1485// if (m_guiStartup)
1486// ret = m_guiStartup->updateProgress();
1487 qApp->processEvents(QEventLoop::AllEvents, 250);
1488 qApp->processEvents(QEventLoop::AllEvents, 250);
1489// return ret;
1490}
1491
1492namespace
1493{
1494// cache some settings in GUISettingsCache::m_cacheFilename
1495// only call this if the database is available.
1496
1497const std::array<QString, 13> GUISettingsCache::kSettings
1498{ "Theme", "Language", "Country", "GuiHeight",
1499 "GuiOffsetX", "GuiOffsetY", "GuiWidth", "RunFrontendInWindow",
1500 "AlwaysOnTop", "HideMouseCursor", "ThemePainter", "libCECEnabled",
1501 "StartupScreenDelay" };
1502
1503
1504bool GUISettingsCache::save()
1505{
1506 QString cacheDirName = GetConfDir() + '/' + m_cachePath;
1507 QDir dir(cacheDirName);
1508 dir.mkpath(cacheDirName);
1509 XmlConfiguration config = XmlConfiguration(m_cacheFilename);
1510 bool dirty = false;
1511 for (const auto & setting : kSettings)
1512 {
1513 QString cacheValue = config.GetValue("Settings/" + setting, QString());
1515 QString value = gCoreContext->GetSetting(setting, QString());
1516 if (value != cacheValue)
1517 {
1518 config.SetValue("Settings/" + setting, value);
1519 dirty = true;
1520 }
1521 }
1522 clearOverrides();
1523
1524 if (dirty)
1525 {
1526#ifndef Q_OS_ANDROID
1528#endif
1529 return config.Save();
1530 }
1531 return true;
1532}
1533
1534void GUISettingsCache::loadOverrides() const
1535{
1536 auto config = XmlConfiguration(m_cacheFilename); // read only
1537 for (const auto & setting : kSettings)
1538 {
1539 if (!gCoreContext->GetSetting(setting, QString()).isEmpty())
1540 continue;
1541 QString value = config.GetValue("Settings/" + setting, QString());
1542 if (!value.isEmpty())
1543 gCoreContext->OverrideSettingForSession(setting, value);
1544 }
1545 // Prevent power off TV after temporary window
1546 gCoreContext->OverrideSettingForSession("PowerOffTVAllowed", nullptr);
1547
1548 MythTranslation::load("mythfrontend");
1549}
1550
1551void GUISettingsCache::clearOverrides()
1552{
1553 QString language = gCoreContext->GetSetting("Language", QString());
1554 for (const auto & setting : kSettings)
1556 // Restore power off TV setting
1557 gCoreContext->ClearOverrideSettingForSession("PowerOffTVAllowed");
1558
1559 if (language != gCoreContext->GetSetting("Language", QString()))
1560 MythTranslation::load("mythfrontend");
1561}
1562
1563} // anonymous namespace
1564
1566{
1567 m_mbeVersionPopup = nullptr;
1568 qApp->exit(GENERIC_EXIT_SOCKET_ERROR);
1569}
1570
1571MythContext::MythContext(QString binversion, bool needsBackend)
1572 : m_impl(new MythContext::Impl()),
1573 m_appBinaryVersion(std::move(binversion))
1574{
1575#ifdef Q_OS_WINDOWS
1576 static bool WSAStarted = false;
1577 if (!WSAStarted)
1578 {
1579 WSADATA wsadata;
1580 int res = WSAStartup(MAKEWORD(2, 0), &wsadata);
1581 LOG(VB_SOCKET, LOG_INFO,
1582 QString("WSAStartup returned %1").arg(res));
1583 }
1584#endif
1585
1587 m_impl->m_needsBackend = needsBackend;
1588
1590
1591 if (!gCoreContext || !gCoreContext->Init())
1592 {
1593 LOG(VB_GENERAL, LOG_EMERG, LOC + "Unable to allocate MythCoreContext");
1594 qApp->exit(GENERIC_EXIT_NO_MYTHCONTEXT);
1595 }
1596}
1597
1598bool MythContext::Init(const bool gui,
1599 const bool promptForBackend,
1600 const bool disableAutoDiscovery,
1601 const bool ignoreDB)
1602{
1603 if (!m_impl)
1604 {
1605 LOG(VB_GENERAL, LOG_EMERG, LOC + "Init() Out-of-memory");
1606 return false;
1607 }
1608
1609 qRegisterMetaType<std::chrono::seconds>("std::chrono::seconds");
1610 qRegisterMetaType<std::chrono::milliseconds>("std::chrono::milliseconds");
1611 qRegisterMetaType<std::chrono::microseconds>("std::chrono::microseconds");
1612
1614
1615 if (gui && QCoreApplication::applicationName() == MYTH_APPNAME_MYTHTV_SETUP)
1616 {
1618 QString warning = QObject::tr("mythtv-setup is deprecated.\n"
1619 "To set up MythTV, start mythbackend and use:\n"
1620 "http://localhost:6544/setupwizard");
1621 WaitFor(ShowOkPopup(warning));
1622 }
1623
1624 if (m_appBinaryVersion != MYTH_BINARY_VERSION)
1625 {
1626 LOG(VB_GENERAL, LOG_EMERG,
1627 QString("Application binary version (%1) does not "
1628 "match libraries (%2)")
1629 .arg(m_appBinaryVersion, MYTH_BINARY_VERSION));
1630
1631 QString warning = QObject::tr(
1632 "This application is not compatible "
1633 "with the installed MythTV libraries.");
1634 if (gui)
1635 {
1637 WaitFor(ShowOkPopup(warning));
1638 }
1639 LOG(VB_GENERAL, LOG_WARNING, warning);
1640
1641 return false;
1642 }
1643
1644#ifdef Q_OS_WINDOWS
1645 // HOME environment variable might not be defined
1646 // some libraries will fail without it
1647 QString home = qEnvironmentVariable("HOME");
1648 if (home.isEmpty())
1649 {
1650 home = qEnvironmentVariable("LOCALAPPDATA"); // Vista
1651 if (home.isEmpty())
1652 home = qEnvironmentVariable("APPDATA"); // XP
1653 if (home.isEmpty())
1654 home = QString("."); // qEnvironmentVariable("TEMP")?
1655
1656 qputenv("HOME", home.toLocal8Bit().constData());
1657 }
1658#endif
1659
1660 // If HOME isn't defined, we won't be able to use default confdir of
1661 // $HOME/.mythtv nor can we rely on a MYTHCONFDIR that references $HOME
1662 QString homedir = QDir::homePath();
1663 QString confdir = qEnvironmentVariable("MYTHCONFDIR");
1664 if ((homedir.isEmpty() || homedir == "/") &&
1665 (confdir.isEmpty() || confdir.contains("$HOME")))
1666 {
1667 QString warning = "Cannot locate your home directory."
1668 " Please set the environment variable HOME";
1669 if (gui)
1670 {
1672 WaitFor(ShowOkPopup(warning));
1673 }
1674 LOG(VB_GENERAL, LOG_WARNING, warning);
1675
1676 return false;
1677 }
1678
1679 if (!m_impl->Init(gui, promptForBackend, disableAutoDiscovery, ignoreDB))
1680 {
1681 return false;
1682 }
1683
1684 SetDisableEventPopup(false);
1685
1686 if (m_impl->m_gui)
1687 {
1689 }
1690
1693
1694 return true;
1695}
1696
1698{
1699 if (m_cleanup != nullptr)
1700 {
1701 m_cleanup();
1702 }
1703
1704 if (m_impl->m_gui)
1705 {
1707 }
1708
1710 gCoreContext->InitPower(false /*destroy*/);
1711 if (MThreadPool::globalInstance()->activeThreadCount())
1712 LOG(VB_GENERAL, LOG_INFO, "Waiting for threads to exit.");
1713
1717
1718 LOG(VB_GENERAL, LOG_INFO, "Exiting");
1719
1720 logStop();
1721
1722 delete gCoreContext;
1723 gCoreContext = nullptr;
1724
1725 delete m_impl;
1726
1728}
1729
1731{
1732 m_impl->m_disableeventpopup = check;
1733}
1734
1736{
1737 /* this check is technically redundant since this is only called from
1738 MythContext::Init() and mythfrontend::main(); however, it is for safety
1739 and clarity until MythGUIContext is refactored out.
1740 */
1741 if (m_impl->m_gui)
1742 {
1743 return m_impl->m_GUISettingsCache.save();
1744 }
1745 return true;
1746}
1747
1749#include "mythcontext.moc"
1750
1751/* 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:878
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
void ShowVersionMismatchPopup(unsigned remote_version)
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.
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:81
static const std::array< QString, 13 > kSettings
Definition: mythcontext.cpp:99
@ 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:684
static constexpr const char * MYTH_APPNAME_MYTHTV_SETUP
Definition: mythappname.h:7
#define LOC
Definition: mythcontext.cpp:72
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:73
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:26
void InitializeMythDirs(void)
Definition: mythdirs.cpp:35
QString GetConfDir(void)
Definition: mythdirs.cpp:282
#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