12 #include <QCoreApplication>
22 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
23 #include <QtAndroidExtras>
25 #include <QJniEnvironment>
27 #define QAndroidJniEnvironment QJniEnvironment
28 #define QAndroidJniObject QJniObject
49 #include "libmythbase/mythversion.h"
67 #define LOC QString("MythContext: ")
77 : m_cachePath(std::move(cache_path))
79 m_cacheFilename = m_cachePath +
'/' + cache_filename;
80 if (m_cachePath.isEmpty() || cache_filename.isEmpty())
82 m_cacheFilename = m_cachePath = QString();
87 void loadOverrides()
const;
88 static void clearOverrides();
91 QString m_cacheFilename {
"cache/contextcache.xml"};
92 QString m_cachePath {
"cache"};
108 bool promptForBackend,
109 bool disableAutoDiscovery,
129 int UPnPautoconf(std::chrono::milliseconds milliSeconds = 2s);
133 bool checkPort(QString &host,
int port, std::chrono::seconds timeLimit)
const;
137 bool event(QEvent* )
override;
180 QStringList tokens = cmd.simplified().split(
" ");
183 bool cardidok =
false;
184 int wantcardid = tokens[0].toInt(&cardidok, 10);
186 if (cardidok && wantcardid > 0)
188 strlist << QString(
"LOCK_TUNER %1").arg(wantcardid);
189 s = s.replace(0, tokens[0].length() + 1,
"");
193 strlist <<
"LOCK_TUNER";
197 int cardid = strlist[0].toInt();
201 s = s.arg(qPrintable(strlist[1]),
202 qPrintable(strlist[2]),
203 qPrintable(strlist[3]));
207 strlist = QStringList(QString(
"FREE_TUNER %1").arg(cardid));
218 label = QObject::tr(
"Could not find specified tuner (%1).")
223 label = QObject::tr(
"Specified tuner (%1) is already in use.")
229 label = QObject::tr(
"All tuners are currently in use. If you want "
230 "to watch TV, you can cancel one of the "
231 "in-progress recordings from the delete menu");
234 LOG(VB_GENERAL, LOG_ALERT, QString(
"exec_program_tv: ") + label);
249 QObject::tr(
"Failed to configure plugin"));
263 QObject::tr(
"%1 failed to run for some reason").arg(cmd));
273 : m_loop(new QEventLoop(this))
321 if (m_guiStartup && !m_guiStartup->m_Exit)
326 mainStack->
PopScreen(m_guiStartup,
false);
327 m_guiStartup =
nullptr;
355 return checker.
checkPort(host, port, timeLimit);
360 const bool promptForBackend,
361 const bool disableAutoDiscovery,
368 m_GUISettingsCache.loadOverrides();
372 m_needsBackend =
true;
380 if (!ignoreDB && !FindDatabase(promptForBackend, disableAutoDiscovery))
403 mainStack->
PopScreen(m_guiStartup,
false);
404 m_guiStartup=
nullptr;
438 bool manualSelect = prompt && !noAutodetect;
443 bool loaded = LoadDatabaseSettings();
451 bool autoSelect = !manualSelect && !loaded && !noAutodetect;
460 if (DefaultUPnP(failure))
461 autoSelect = manualSelect =
false;
463 if (!failure.isEmpty())
464 LOG(VB_GENERAL, LOG_ALERT, failure);
466 failure = TestDBconnection(loaded);
467 if (failure.isEmpty())
469 if (m_guiStartup && m_guiStartup->m_Exit)
471 if (m_guiStartup && m_guiStartup->m_Search)
478 int count = UPnPautoconf();
481 failure = QObject::tr(
"No UPnP backends found",
"Backend Setup");
485 failure = TestDBconnection();
486 if (failure.isEmpty())
488 if (m_guiStartup && m_guiStartup->m_Exit)
493 manualSelect |= (count > 1 || count == -1);
495 if (m_guiStartup && m_guiStartup->m_Search)
499 manualSelect &= m_gui;
507 switch (ChooseBackend(failure))
512 manualSelect =
false;
525 || !PromptForDatabaseParams(failure))
528 failure = TestDBconnection();
529 if (!failure.isEmpty())
530 LOG(VB_GENERAL, LOG_ALERT, failure);
531 if (m_guiStartup && m_guiStartup->m_Exit)
533 if (m_guiStartup && m_guiStartup->m_Search)
535 if (m_guiStartup && m_guiStartup->m_Setup)
538 while (!failure.isEmpty());
541 LOG(VB_GENERAL, LOG_DEBUG,
"FindDatabase() - Success!");
549 GetMythDB()->SaveDatabaseParams(dbParams, !loaded || dbParamsFromFile != dbParams);
551 ResetDatabase(dbParams);
555 LOG(VB_GENERAL, LOG_DEBUG,
"FindDatabase() - failed");
590 GetMythDB()->SetDatabaseParams(dbParams);
597 hostname ==
"my-unique-identifier-goes-here")
599 LOG(VB_GENERAL, LOG_INFO,
"Empty LocalHostName. This is typical.");
600 hostname = QHostInfo::localHostName();
605 LOG(VB_GENERAL, LOG_ALERT,
606 "MCP: Error, could not determine host name." +
ENO);
608 #else //elif defined Q_OS_ANDROID
609 #define ANDROID_EXCEPTION_CHECK \
610 if (env->ExceptionCheck()) \
612 env->ExceptionClear(); \
619 bool exception=
false;
622 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
625 QJniObject activity = QNativeInterface::QAndroidApplication::context();
629 (
"getApplicationContext",
"()Landroid/content/Context;");
632 (
"getContentResolver",
"()Landroid/content/ContentResolver;");
635 (
"android/provider/Settings$Secure",
"getString",
636 "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;",
637 contentR.object<jobject>(),
638 myID.object<jstring>());
641 LOG(VB_GENERAL, LOG_ALERT,
642 "Java exception looking for android id");
644 hostname = QString(
"android-%1").arg(androidId.toString());
650 LOG(VB_GENERAL, LOG_INFO, QString(
"Using a profile name of: '%1' (Usually the "
651 "same as this host's name.)")
661 if (m_loop && m_loop->isRunning())
674 bool accepted =
false;
681 if (!
error.isEmpty())
696 if (!m_loop->isRunning())
711 std::this_thread::sleep_for(1s);
713 std::cout << std::endl <<
error.toLocal8Bit().constData() << std::endl << std::endl;
714 response =
getResponse(
"Would you like to configure the database "
717 if (!response.startsWith(
'y', Qt::CaseInsensitive))
722 response =
getResponse(
"Should I test connectivity to this host "
723 "using the ping command?",
"yes");
724 params.
m_dbHostPing = response.startsWith(
'y', Qt::CaseInsensitive);
736 "(if empty, the local host name "
741 response =
getResponse(
"Would you like to use Wake-On-LAN to retry "
742 "database connections?",
744 params.
m_wolEnabled = response.startsWith(
'y', Qt::CaseInsensitive);
749 std::chrono::seconds(
intResponse(
"Seconds to wait for "
758 accepted =
GetMythDB()->SaveDatabaseParams(params,
false);
782 enum startupStates : std::uint8_t {
790 } startupState = st_start;
792 static const std::array<const QString, 7> kGuiStatuses
793 {
"start",
"dbAwake",
"dbStarted",
"dbConnects",
"beWOL",
"beAwake",
"success"};
796 auto msStartupScreenDelay = std::chrono::duration_cast<std::chrono::milliseconds>(secondsStartupScreenDelay);
802 if (dbParams.
m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
809 std::chrono::seconds wakeupTime = 3s;
815 startupState = st_start;
819 startupState = st_dbAwake;
821 attempts = std::max(attempts, 6);
827 std::chrono::seconds progressTotal = wakeupTime * attempts;
829 if (m_guiStartup && !m_guiStartup->m_Exit)
830 m_guiStartup->setTotal(progressTotal);
832 QString beWOLCmd = QString();
833 QString backendIP = QString();
835 QString masterserver;
837 for (
int attempt = 0;
838 attempt < attempts && startupState != st_success;
845 LOG(VB_GENERAL, LOG_INFO,
846 QString(
"Start up testing connections. DB %1, BE %2, attempt %3, status %4, Delay: %5")
847 .arg(host, backendIP, QString::number(attempt),
848 kGuiStatuses[startupState],
849 QString::number(msStartupScreenDelay.count())) );
851 std::chrono::seconds useTimeout = wakeupTime;
855 if (m_gui && !m_guiStartup)
857 if (msStartupScreenDelay==0ms || timer.hasExpired(msStartupScreenDelay.count()))
861 m_guiStartup->setTotal(progressTotal);
864 if (m_guiStartup && !m_guiStartup->m_Exit)
867 m_guiStartup->setStatusState(kGuiStatuses[startupState]);
868 m_guiStartup->setMessageState(
"empty");
871 switch (startupState)
878 if (!checkPort(host, port, useTimeout))
881 startupState = st_dbAwake;
884 if (!checkPort(host, port, useTimeout))
886 startupState = st_dbStarted;
894 GetMythDB()->SetDatabaseParams(dbParams);
897 ResetDatabase(dbParams);
900 for (std::chrono::seconds temp = 0s; temp < useTimeout * 2 ; temp++)
903 std::this_thread::sleep_for(500ms);
907 startupState = st_dbConnects;
913 if (!beWOLCmd.isEmpty())
916 (
"WOLbackendReconnectWaitTime", 0s);
918 (
"WOLbackendConnectRetry", 0);
919 useTimeout = wakeupTime;
920 if (m_gui && !m_guiStartup && attempt == 0)
922 progressTotal = wakeupTime * attempts;
923 if (m_guiStartup && !m_guiStartup->m_Exit)
924 m_guiStartup->setTotal(progressTotal);
925 startupState = st_beWOL;
930 startupState = st_success;
934 (
"MasterServerName");
936 (
"BackendServerAddr", masterserver);
940 if (!beWOLCmd.isEmpty())
944 if (!checkPort(backendIP, backendPort, useTimeout))
947 startupState = st_beAwake;
950 if (!checkPort(backendIP, backendPort, useTimeout))
952 startupState = st_success;
960 if (m_guiStartup->m_Exit
961 || m_guiStartup->m_Setup
962 || m_guiStartup->m_Search
963 || m_guiStartup->m_Retry)
968 if (startupState == st_success)
971 QString stateMsg = kGuiStatuses[startupState];
972 stateMsg.append(
"Fail");
973 LOG(VB_GENERAL, LOG_INFO,
974 QString(
"Start up failure. host %1, status %2")
975 .arg(host, stateMsg));
977 if (m_gui && !m_guiStartup)
981 m_guiStartup->setTotal(progressTotal);
985 && !m_guiStartup->m_Exit
986 && !m_guiStartup->m_Setup
987 && !m_guiStartup->m_Search
988 && !m_guiStartup->m_Retry)
990 m_guiStartup->updateProgress(
true);
991 m_guiStartup->setStatusState(stateMsg);
992 m_guiStartup->setMessageState(
"makeselection");
996 while (m_guiStartup && m_guiStartup->m_Retry);
998 if (startupState < st_dbAwake)
1000 LOG(VB_GENERAL, LOG_WARNING, QString(
"Pinging to %1 failed, database will be unavailable").arg(host));
1003 "Cannot find (ping) database host %1 on the network",
1005 return err.arg(host);
1008 if (startupState < st_dbConnects)
1011 return QObject::tr(
"Cannot login to database",
"Backend Setup");
1014 if (startupState < st_success)
1016 return QObject::tr(
"Cannot connect to backend",
"Backend Setup");
1021 ResetDatabase(dbParams);
1040 m_guiStartup =
new GUIStartup(mainStack, m_loop);
1041 if (!m_guiStartup->Create())
1043 delete m_guiStartup;
1044 m_guiStartup =
nullptr;
1048 mainStack->
AddScreen(m_guiStartup,
false);
1076 GetMythDB()->SetDatabaseParams(dbParams);
1084 if (dbParams.
m_dbHostName.isNull() && !m_dbHostCp.isEmpty())
1087 GetMythDB()->SetDatabaseParams(dbParams);
1108 db->GetDBManager()->CloseDatabases();
1109 db->SetDatabaseParams(dbParams);
1110 db->ClearSettingsCache();
1122 if (!
error.isEmpty())
1124 LOG(VB_GENERAL, LOG_ERR, QString(
"Error: %1").arg(
error));
1128 LOG(VB_GENERAL, LOG_INFO,
"Putting up the UPnP backend chooser");
1133 GetMythDB()->SetDatabaseParams(dbParams);
1148 auto seconds = duration_cast<std::chrono::seconds>(milliSeconds);
1149 LOG(VB_GENERAL, LOG_INFO, QString(
"UPNP Search %1 secs")
1150 .arg(seconds.count()));
1158 while (totalTime.
elapsed() < milliSeconds)
1161 auto ttl = milliSeconds - totalTime.
elapsed();
1162 if ((searchTime.
elapsed() > 249ms) && (ttl > 1s))
1164 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1165 LOG(VB_GENERAL, LOG_INFO, QString(
"UPNP Search %1 secs")
1166 .arg(ttlSeconds.count()));
1176 LOG(VB_GENERAL, LOG_INFO,
"No UPnP backends found");
1180 int count = backends->
Count();
1183 LOG(VB_GENERAL, LOG_INFO,
1184 QString(
"Found %1 UPnP backends").arg(count));
1188 LOG(VB_GENERAL, LOG_ERR,
1189 "No UPnP backends found, but SSDP::Find() not NULL");
1205 int ret = (UPnPconnect(BE, QString())) ? 1 : -1;
1219 static const QString loc =
"DefaultUPnP() - ";
1232 LOG(VB_UPNP, LOG_INFO, loc +
"No default UPnP backend");
1236 LOG(VB_UPNP, LOG_INFO,
1238 QString(
" has default PIN '%1' and host USN: %2").arg(pin, usn));
1242 std::chrono::milliseconds timeout_ms {2s};
1243 auto timeout_s = duration_cast<std::chrono::seconds>(timeout_ms);
1244 LOG(VB_GENERAL, LOG_INFO, loc + QString(
"UPNP Search up to %1 secs")
1245 .arg(timeout_s.count()));
1257 while (totalTime.
elapsed() < timeout_ms)
1265 auto ttl = timeout_ms - totalTime.
elapsed();
1266 if ((searchTime.
elapsed() > 249ms) && (ttl > 1s))
1268 auto ttlSeconds = duration_cast<std::chrono::seconds>(ttl);
1269 LOG(VB_GENERAL, LOG_INFO, loc + QString(
"UPNP Search up to %1 secs")
1270 .arg(ttlSeconds.count()));
1278 if (!devicelocation)
1280 Error =
"Cannot find default UPnP backend";
1284 if (UPnPconnect(devicelocation, pin))
1291 Error =
"Cannot connect to default backend via UPnP. Wrong saved PIN?";
1302 QString loc =
"UPnPconnect() - ";
1307 LOG(VB_UPNP, LOG_INFO, loc + QString(
"Trying host at %1").arg(URL));
1311 GetMythDB()->SetDatabaseParams(dbParams);
1312 LOG(VB_UPNP, LOG_INFO, loc +
1320 LOG(VB_UPNP, LOG_ERR, loc +
"Wrong PIN?");
1324 LOG(VB_UPNP, LOG_ERR, loc +
error);
1332 URL = theURL.host();
1336 LOG(VB_UPNP, LOG_INFO,
"Trying default DB credentials at " + URL);
1338 GetMythDB()->SetDatabaseParams(dbParams);
1347 if (m_disableeventpopup)
1359 if (me->Message() ==
"VERSION_MISMATCH" && (1 == me->ExtraDataCount()))
1360 ShowVersionMismatchPopup(me->ExtraData(0).toUInt());
1361 else if (me->Message() ==
"CONNECTION_FAILURE")
1362 ShowConnectionFailurePopup(
false);
1363 else if (me->Message() ==
"PERSISTENT_CONNECTION_FAILURE")
1364 ShowConnectionFailurePopup(
true);
1365 else if (me->Message() ==
"CONNECTION_RESTABLISHED")
1366 HideConnectionFailurePopup();
1370 return QObject::event(e);
1380 if (m_lastCheck.isValid() && now < m_lastCheck)
1388 m_lastCheck = now.addMSecs(5000);
1390 QString description = (persistent) ?
1392 "The connection to the master backend "
1393 "server has gone away for some reason. "
1396 "Could not connect to the master backend server. Is "
1397 "it running? Is the IP address set for it in "
1398 "mythtv-setup correct?");
1400 QString message = QObject::tr(
"Could not connect to master backend");
1402 n.
SetId(m_registration);
1412 if (!m_lastCheck.isValid())
1416 n.
SetId(m_registration);
1420 m_lastCheck = QDateTime();
1425 if (m_mbeVersionPopup)
1430 "The server uses network protocol version %1, "
1431 "but this client only understands version %2. "
1432 "Make sure you are running compatible versions of "
1433 "the backend and frontend.")
1434 .arg(remote_version).arg(MYTH_PROTO_VERSION);
1443 LOG(VB_GENERAL, LOG_ERR,
LOC + message);
1455 qApp->processEvents(QEventLoop::AllEvents, 250);
1456 qApp->processEvents(QEventLoop::AllEvents, 250);
1465 const std::array<QString, 13> GUISettingsCache::kSettings
1466 {
"Theme",
"Language",
"Country",
"GuiHeight",
1467 "GuiOffsetX",
"GuiOffsetY",
"GuiWidth",
"RunFrontendInWindow",
1468 "AlwaysOnTop",
"HideMouseCursor",
"ThemePainter",
"libCECEnabled",
1469 "StartupScreenDelay" };
1472 bool GUISettingsCache::save()
1474 QString cacheDirName =
GetConfDir() +
'/' + m_cachePath;
1475 QDir dir(cacheDirName);
1476 dir.mkpath(cacheDirName);
1479 for (
const auto & setting : kSettings)
1481 QString cacheValue = config.
GetValue(
"Settings/" + setting, QString());
1484 if (value != cacheValue)
1486 config.
SetValue(
"Settings/" + setting, value);
1494 #ifndef Q_OS_ANDROID
1497 return config.
Save();
1502 void GUISettingsCache::loadOverrides()
const
1505 for (
const auto & setting : kSettings)
1509 QString value = config.GetValue(
"Settings/" + setting, QString());
1510 if (!value.isEmpty())
1519 void GUISettingsCache::clearOverrides()
1522 for (
const auto & setting : kSettings)
1535 m_mbeVersionPopup =
nullptr;
1544 static bool WSAStarted =
false;
1548 int res = WSAStartup(MAKEWORD(2, 0), &wsadata);
1549 LOG(VB_SOCKET, LOG_INFO,
1550 QString(
"WSAStartup returned %1").arg(res));
1561 LOG(VB_GENERAL, LOG_EMERG,
LOC +
"Unable to allocate MythCoreContext");
1567 const bool promptForBackend,
1568 const bool disableAutoDiscovery,
1569 const bool ignoreDB)
1573 LOG(VB_GENERAL, LOG_EMERG,
LOC +
"Init() Out-of-memory");
1577 qRegisterMetaType<std::chrono::seconds>(
"std::chrono::seconds");
1578 qRegisterMetaType<std::chrono::milliseconds>(
"std::chrono::milliseconds");
1579 qRegisterMetaType<std::chrono::microseconds>(
"std::chrono::microseconds");
1586 QString warning = QObject::tr(
"mythtv-setup is deprecated.\n"
1587 "To set up MythTV, start mythbackend and use:\n"
1588 "http://localhost:6544/setupwizard");
1594 LOG(VB_GENERAL, LOG_EMERG,
1595 QString(
"Application binary version (%1) does not "
1596 "match libraries (%2)")
1599 QString warning = QObject::tr(
1600 "This application is not compatible "
1601 "with the installed MythTV libraries.");
1607 LOG(VB_GENERAL, LOG_WARNING, warning);
1615 QString home = getenv(
"HOME");
1618 home = getenv(
"LOCALAPPDATA");
1620 home = getenv(
"APPDATA");
1622 home = QString(
".");
1624 _putenv(QString(
"HOME=%1").arg(home).toLocal8Bit().constData());
1630 QString homedir = QDir::homePath();
1631 QString
confdir = qEnvironmentVariable(
"MYTHCONFDIR");
1632 if ((homedir.isEmpty() || homedir ==
"/") &&
1635 QString warning =
"Cannot locate your home directory."
1636 " Please set the environment variable HOME";
1642 LOG(VB_GENERAL, LOG_WARNING, warning);
1647 if (!
m_impl->
Init(gui, promptForBackend, disableAutoDiscovery, ignoreDB))
1680 LOG(VB_GENERAL, LOG_INFO,
"Waiting for threads to exit.");
1686 LOG(VB_GENERAL, LOG_INFO,
"Exiting");
1717 #include "mythcontext.moc"