4 #include <QCryptographicHash>
5 #include <QHostAddress>
7 #include <QNetworkRequest>
20 #include "libmythbase/mythversion.h"
22 #include "libmythbase/version.h"
34 #if CONFIG_SYSTEMD_NOTIFY
35 #include <systemd/sd-daemon.h>
36 static inline void api_sd_notify(
const char *str) { sd_notify(0, str); };
47 qRegisterMetaType<V2ConnectionInfo*>(
"V2ConnectionInfo");
48 qRegisterMetaType<V2VersionInfo*>(
"V2VersionInfo");
49 qRegisterMetaType<V2DatabaseInfo*>(
"V2DatabaseInfo");
50 qRegisterMetaType<V2WOLInfo*>(
"V2WOLInfo");
51 qRegisterMetaType<V2StorageGroupDirList*>(
"V2StorageGroupDirList");
52 qRegisterMetaType<V2StorageGroupDir*>(
"V2StorageGroupDir");
53 qRegisterMetaType<V2TimeZoneInfo*>(
"V2TimeZoneInfo");
54 qRegisterMetaType<V2LogMessage*>(
"V2LogMessage");
55 qRegisterMetaType<V2LogMessageList*>(
"V2LogMessageList");
56 qRegisterMetaType<V2LabelValue*>(
"V2LabelValue");
57 qRegisterMetaType<V2Frontend*>(
"V2Frontend");
58 qRegisterMetaType<V2FrontendList*>(
"V2FrontendList");
59 qRegisterMetaType<V2SettingList*>(
"V2SettingList");
60 qRegisterMetaType<V2BackendInfo*>(
"V2BackendInfo");
61 qRegisterMetaType<V2EnvInfo*>(
"V2EnvInfo");
62 qRegisterMetaType<V2LogInfo*>(
"V2LogInfo");
63 qRegisterMetaType<V2BuildInfo*>(
"V2BuildInfo");
80 if ( sSecurityPin.isEmpty() )
81 throw( QString(
"No Security Pin assigned. Run mythtv-setup to set one." ));
84 if ((sSecurityPin !=
"0000" ) && ( sPin != sSecurityPin ))
85 throw( QString(
"Not Authorized" ));
97 if ((params.
m_dbHostName.compare(
"localhost",Qt::CaseInsensitive)==0
100 && !sServerIP.isEmpty())
111 if (addr.setAddress(dbHostName))
113 addr.setScopeId(QString());
114 dbHostName = addr.toString();
125 pDatabase->setHost ( dbHostName );
127 pDatabase->setPort ( params.
m_dbPort );
130 pDatabase->setName ( params.
m_dbName );
131 pDatabase->setType ( params.
m_dbType );
140 pVersion->setVersion ( MYTH_SOURCE_VERSION );
141 pVersion->setBranch ( MYTH_SOURCE_PATH );
142 pVersion->setProtocol ( MYTH_PROTO_VERSION );
143 pVersion->setBinary ( MYTH_BINARY_VERSION );
144 pVersion->setSchema ( MYTH_DATABASE_VERSION );
158 bool bResult =
false;
160 QString db(
"mythconverg");
170 throw( QString(
"Database test failed. Not saving database connection information." ));
185 bResult =
GetMythDB()->SaveDatabaseParams(dbparms,
false);
197 throw( QString(
"No MythCoreContext in GetHostName." ));
211 throw( QString(
"Database not open while trying to load list of hosts" ));
214 "SELECT DISTINCTROW hostname "
216 "WHERE (not isNull( hostname ))");
222 throw( QString(
"Database Error executing query." ));
232 oList.append( query.
value(0).toString() );
246 throw( QString(
"Database not open while trying to load settings"));
248 query.
prepare(
"SELECT DISTINCTROW value FROM settings;" );
254 throw( QString(
"Database Error executing query." ));
261 QStringList oResults;
266 oResults.append( query.
value(0).toString() );
277 QDir directory(DirName);
278 QDir::Filters filts = QDir::AllDirs|QDir::NoDotAndDotDot;
282 filts = QDir::AllDirs|QDir::NoDotAndDotDot;
283 return directory.entryList(filts, QDir::Name);
291 const QString &sHostName )
296 throw( QString(
"Database not open while trying to list "
297 "Storage Group Dirs"));
299 if (!sGroupName.isEmpty() && !sHostName.isEmpty())
301 query.
prepare(
"SELECT id, groupname, hostname, dirname "
303 "WHERE groupname = :GROUP AND hostname = :HOST "
304 "ORDER BY groupname, hostname, dirname" );
308 else if (!sHostName.isEmpty())
310 query.
prepare(
"SELECT id, groupname, hostname, dirname "
312 "WHERE hostname = :HOST "
313 "ORDER BY groupname, hostname, dirname" );
316 else if (!sGroupName.isEmpty())
318 query.
prepare(
"SELECT id, groupname, hostname, dirname "
320 "WHERE groupname = :GROUP "
321 "ORDER BY groupname, hostname, dirname" );
326 query.
prepare(
"SELECT id, groupname, hostname, dirname "
328 "ORDER BY groupname, hostname, dirname" );
335 throw( QString(
"Database Error executing query." ));
347 QFileInfo fi(query.
value(3).toString());
354 pStorageGroupDir->setId ( query.
value(0).toInt() );
355 pStorageGroupDir->setGroupName ( query.
value(1).toString() );
356 pStorageGroupDir->setHostName ( query.
value(2).toString() );
357 pStorageGroupDir->setDirName ( query.
value(3).toString() );
358 pStorageGroupDir->setDirRead ( fi.isReadable() );
359 pStorageGroupDir->setDirWrite ( fi.isWritable() );
360 pStorageGroupDir->setKiBFree ( free );
371 const QString &sDirName,
372 const QString &sHostName )
377 throw( QString(
"Database not open while trying to add Storage Group "
380 if (sGroupName.isEmpty())
381 throw ( QString(
"Storage Group Required" ));
383 if (sDirName.isEmpty())
384 throw ( QString(
"Directory Name Required" ));
386 if (sHostName.isEmpty())
387 throw ( QString(
"HostName Required" ));
389 query.
prepare(
"SELECT COUNT(*) "
391 "WHERE groupname = :GROUPNAME "
392 "AND dirname = :DIRNAME "
393 "AND hostname = :HOSTNAME;");
394 query.
bindValue(
":GROUPNAME", sGroupName );
396 query.
bindValue(
":HOSTNAME" , sHostName );
401 throw( QString(
"Database Error executing query." ));
406 if (query.
value(0).toInt() > 0)
410 query.
prepare(
"INSERT storagegroup "
411 "( groupname, dirname, hostname ) "
413 "( :GROUPNAME, :DIRNAME, :HOSTNAME );");
414 query.
bindValue(
":GROUPNAME", sGroupName );
416 query.
bindValue(
":HOSTNAME" , sHostName );
422 throw( QString(
"Database Error executing query." ));
433 const QString &sDirName,
434 const QString &sHostName )
439 throw( QString(
"Database not open while trying to remove Storage "
442 if (sGroupName.isEmpty())
443 throw ( QString(
"Storage Group Required" ));
445 if (sDirName.isEmpty())
446 throw ( QString(
"Directory Name Required" ));
448 if (sHostName.isEmpty())
449 throw ( QString(
"HostName Required" ));
453 "WHERE groupname = :GROUPNAME "
454 "AND dirname = :DIRNAME "
455 "AND hostname = :HOSTNAME;");
456 query.
bindValue(
":GROUPNAME", sGroupName );
458 query.
bindValue(
":HOSTNAME" , sHostName );
463 throw( QString(
"Database Error executing query." ));
527 if (!dateTime.isValid())
528 throw QString(
"Unable to parse DateTime" );
538 const QString &Application,
541 const QString &Thread,
542 const QString &Filename,
544 const QString &Function,
545 const QDateTime &FromTime,
546 const QDateTime &ToTime,
547 const QString &Level,
548 const QString &MsgContains )
555 QString sql =
"SELECT DISTINCT host FROM logging ORDER BY host ASC";
556 if (!query.
exec(sql))
560 throw( QString(
"Database Error executing query." ));
565 QString availableHostName = query.
value(0).toString();
566 pLabelValue->setValue ( availableHostName );
567 pLabelValue->setActive ( availableHostName == HostName );
568 pLabelValue->setSelected( availableHostName == HostName );
571 sql =
"SELECT DISTINCT application FROM logging ORDER BY application ASC";
572 if (!query.
exec(sql))
576 throw( QString(
"Database Error executing query." ));
581 QString availableApplication = query.
value(0).toString();
582 pLabelValue->setValue ( availableApplication );
583 pLabelValue->setActive ( availableApplication == Application );
584 pLabelValue->setSelected( availableApplication == Application );
587 if (!HostName.isEmpty() && !Application.isEmpty())
590 sql =
"SELECT host, application, pid, tid, thread, filename, "
591 " line, function, msgtime, level, message "
593 " WHERE host = COALESCE(:HOSTNAME, host) "
594 " AND application = COALESCE(:APPLICATION, application) "
595 " AND pid = COALESCE(:PID, pid) "
596 " AND tid = COALESCE(:TID, tid) "
597 " AND thread = COALESCE(:THREAD, thread) "
598 " AND filename = COALESCE(:FILENAME, filename) "
599 " AND line = COALESCE(:LINE, line) "
600 " AND function = COALESCE(:FUNCTION, function) "
601 " AND msgtime >= COALESCE(:FROMTIME, msgtime) "
602 " AND msgtime <= COALESCE(:TOTIME, msgtime) "
603 " AND level <= COALESCE(:LEVEL, level) "
605 if (!MsgContains.isEmpty())
607 sql.append(
" AND message LIKE :MSGCONTAINS ");
609 sql.append(
" ORDER BY msgtime ASC;");
611 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
612 QVariant ullNull = QVariant(QVariant::ULongLong);
614 QVariant ullNull = QVariant(QMetaType(QMetaType::ULongLong));
618 query.
bindValue(
":HOSTNAME", (HostName.isEmpty()) ? QString() : HostName);
619 query.
bindValue(
":APPLICATION", (Application.isEmpty()) ? QString() :
622 query.
bindValue(
":TID", ( TID == 0 ) ? ullNull : (qint64)TID);
623 query.
bindValue(
":THREAD", (Thread.isEmpty()) ? QString() : Thread);
624 query.
bindValue(
":FILENAME", (Filename.isEmpty()) ? QString() : Filename);
625 query.
bindValue(
":LINE", ( Line == 0 ) ? ullNull : (qint64)Line);
626 query.
bindValue(
":FUNCTION", (Function.isEmpty()) ? QString() : Function);
627 query.
bindValue(
":FROMTIME", (FromTime.isValid()) ? FromTime : QDateTime());
628 query.
bindValue(
":TOTIME", (ToTime.isValid()) ? ToTime : QDateTime());
629 query.
bindValue(
":LEVEL", (Level.isEmpty()) ? ullNull :
632 if (!MsgContains.isEmpty())
634 query.
bindValue(
":MSGCONTAINS",
"%" + MsgContains +
"%" );
641 throw( QString(
"Database Error executing query." ));
648 pLogMessage->setHostName( query.
value(0).toString() );
649 pLogMessage->setApplication( query.
value(1).toString() );
650 pLogMessage->setPID( query.
value(2).toInt() );
651 pLogMessage->setTID( query.
value(3).toInt() );
652 pLogMessage->setThread( query.
value(4).toString() );
653 pLogMessage->setFilename( query.
value(5).toString() );
654 pLogMessage->setLine( query.
value(6).toInt() );
655 pLogMessage->setFunction( query.
value(7).toString() );
658 (LogLevel_t)query.
value(9).toInt()) );
659 pLogMessage->setMessage( query.
value(10).toString() );
685 const QString &sDefault )
688 throw( QString(
"Missing or empty Key (settings.value)") );
690 if (sHostName ==
"_GLOBAL_")
694 query.
prepare(
"SELECT data FROM settings "
695 "WHERE value = :VALUE "
696 "AND (hostname IS NULL)" );
703 throw( QString(
"Database Error executing query." ));
706 return query.
next() ? query.
value(0).toString() : sDefault;
711 if (sHostName.isEmpty())
728 throw( QString(
"Database not open while trying to load settings for host: %1")
734 pList->setHostName ( sHostName );
740 if (sHostName.isEmpty())
742 query.
prepare(
"SELECT value, data FROM settings "
743 "WHERE (hostname IS NULL)" );
747 query.
prepare(
"SELECT value, data FROM settings "
748 "WHERE (hostname = :HOSTNAME)" );
750 query.
bindValue(
":HOSTNAME", sHostName );
760 throw( QString(
"Database Error executing query." ));
764 pList->Settings().insert( query.
value(0).toString(), query.
value(1) );
775 const QString &sValue )
777 QString hostName = sHostName;
779 if (hostName ==
"_GLOBAL_")
787 throw ( QString(
"Key Required" ));
797 QString hostName = sHostName;
799 if (hostName ==
"_GLOBAL_")
807 throw ( QString(
"Key Required" ));
816 const QString &sUserName,
817 const QString &sPassword,
818 const QString &sDBName,
821 bool bResult =
false;
823 QString db(
"mythconverg");
826 if (!sDBName.isEmpty())
832 bResult =
TestDatabase(sHostName, sUserName, sPassword, db, port);
842 const QString &sAddress,
846 bool bResult =
false;
848 if (sMessage.isEmpty())
851 if (Timeout < 0 || Timeout > 999)
855 "<mythmessage version=\"1\">\n"
856 " <text>" + sMessage +
"</text>\n"
857 " <timeout>" + QString::number(Timeout) +
"</timeout>\n"
860 QHostAddress address = QHostAddress::Broadcast;
861 unsigned short port = 6948;
863 if (!sAddress.isEmpty())
864 address.setAddress(sAddress);
869 auto *sock =
new QUdpSocket();
870 QByteArray utf8 = xmlMessage.toUtf8();
871 int size = utf8.length();
873 if (sock->writeDatagram(utf8.constData(), size, address, port) < 0)
875 LOG(VB_GENERAL, LOG_ERR,
876 QString(
"Failed to send UDP/XML packet (Message: %1 "
877 "Address: %2 Port: %3")
878 .arg(sMessage, sAddress, QString::number(port)));
882 LOG(VB_GENERAL, LOG_DEBUG,
883 QString(
"UDP/XML packet sent! (Message: %1 Address: %2 Port: %3")
885 address.toString().toLocal8Bit(),
886 QString::number(port)));
897 const QString &sMessage,
898 const QString &sOrigin,
899 const QString &sDescription,
900 const QString &sImage,
901 const QString &sExtra,
902 const QString &sProgressText,
908 const QString &sAddress,
911 bool bResult =
false;
913 if (sMessage.isEmpty())
916 if (Timeout < 0 || Timeout > 999)
920 "<mythnotification version=\"1\">\n"
921 " <text>" + sMessage +
"</text>\n"
922 " <origin>" + (sOrigin.isNull() ? tr(
"MythServices") : sOrigin) +
"</origin>\n"
923 " <description>" + sDescription +
"</description>\n"
924 " <timeout>" + QString::number(Timeout) +
"</timeout>\n"
925 " <image>" + sImage +
"</image>\n"
926 " <extra>" + sExtra +
"</extra>\n"
927 " <progress_text>" + sProgressText +
"</progress_text>\n"
928 " <progress>" + QString::number(fProgress) +
"</progress>\n"
929 " <fullscreen>" + (bFullscreen ?
"true" :
"false") +
"</fullscreen>\n"
930 " <visibility>" + QString::number(Visibility) +
"</visibility>\n"
931 " <priority>" + QString::number(
Priority) +
"</priority>\n"
932 " <type>" + (bError ?
"error" : Type) +
"</type>\n"
933 "</mythnotification>";
935 QHostAddress address = QHostAddress::Broadcast;
936 unsigned short port = 6948;
938 if (!sAddress.isEmpty())
939 address.setAddress(sAddress);
944 auto *sock =
new QUdpSocket();
945 QByteArray utf8 = xmlMessage.toUtf8();
946 int size = utf8.length();
948 if (sock->writeDatagram(utf8.constData(), size, address, port) < 0)
950 LOG(VB_GENERAL, LOG_ERR,
951 QString(
"Failed to send UDP/XML packet (Notification: %1 "
952 "Address: %2 Port: %3")
953 .arg(sMessage, sAddress, QString::number(port)));
957 LOG(VB_GENERAL, LOG_DEBUG,
958 QString(
"UDP/XML packet sent! (Notification: %1 Address: %2 Port: %3")
960 address.toString().toLocal8Bit(), QString::number(port)));
975 bool bResult =
false;
981 LOG(VB_GENERAL, LOG_NOTICE,
"Performing API invoked DB Backup.");
987 LOG(VB_GENERAL, LOG_NOTICE,
"Database backup succeeded.");
992 LOG(VB_GENERAL, LOG_ERR,
"Database backup failed.");
1004 LOG(VB_GENERAL, LOG_NOTICE,
"Performing API invoked DB Check.");
1009 LOG(VB_GENERAL, LOG_NOTICE,
"Database check complete.");
1011 LOG(VB_GENERAL, LOG_ERR,
"Database check failed.");
1019 if (scheduler ==
nullptr)
1022 LOG(VB_GENERAL, LOG_NOTICE,
"Shutdown delayed 5 minutes for external application.");
1033 LOG(VB_GENERAL, LOG_NOTICE,
"Profile Submission...");
1035 bool bResult =
profile.SubmitProfile();
1037 LOG(VB_GENERAL, LOG_NOTICE,
"Profile Submitted.");
1049 LOG(VB_GENERAL, LOG_NOTICE,
"Profile Deletion...");
1051 bool bResult =
profile.DeleteProfile();
1053 LOG(VB_GENERAL, LOG_NOTICE,
"Profile Deleted.");
1064 QString sProfileURL;
1068 sProfileURL =
profile.GetProfileURL();
1069 LOG(VB_GENERAL, LOG_NOTICE, QString(
"ProfileURL: %1").arg(sProfileURL));
1080 QString sProfileUpdate;
1085 tUpdated =
profile.GetLastUpdate();
1086 sProfileUpdate = tUpdated.toString(
1089 return sProfileUpdate;
1098 QString sProfileText;
1103 return sProfileText;
1122 pBuild->setVersion ( MYTH_SOURCE_VERSION );
1123 pBuild->setLibX264 ( CONFIG_LIBX264 );
1124 pBuild->setLibDNS_SD ( CONFIG_LIBDNS_SD );
1125 pEnv->setLANG ( qEnvironmentVariable(
"LANG") );
1126 pEnv->setLCALL ( qEnvironmentVariable(
"LC_ALL") );
1127 pEnv->setLCCTYPE ( qEnvironmentVariable(
"LC_CTYPE") );
1128 pEnv->setHOME ( qEnvironmentVariable(
"HOME") );
1129 pEnv->setHttpRootDir (
m_request->m_root );
1131 pEnv->setUSER ( qEnvironmentVariable(
"USER",
1132 qEnvironmentVariable(
"USERNAME")) );
1133 pEnv->setMYTHCONFDIR ( qEnvironmentVariable(
"MYTHCONFDIR") );
1135 if (scheduler !=
nullptr)
1136 pEnv->setSchedulingEnabled(scheduler->QueryScheduling());
1146 webOnly =
"DBSETUP";
1149 webOnly =
"DBTIMEZONE";
1152 webOnly =
"WEBONLYPARM";
1155 webOnly =
"IPADDRESS";
1158 webOnly =
"SCHEMAUPDATE";
1161 pEnv->setWebOnlyStartup (webOnly);
1176 const QString &sUserName,
1177 const QString &sPassword,
1178 const QString &sNewPassword,
1179 const QString &sAdminPassword )
1184 if (sAction ==
"Add")
1186 else if (sAction ==
"Remove")
1188 else if (sAction ==
"ChangePassword")
1192 LOG(VB_GENERAL, LOG_ERR, QString(
"Action must be Add, Remove or "
1193 "ChangePassword, not '%1'")
1199 sPassword, sNewPassword,
1208 const QString &sAdminPassword )
1210 LOG(VB_GENERAL, LOG_WARNING, QString(
"ManageUrlProtection is deprecated."
1211 "Protection unavailable in API V2."));
1215 LOG(VB_GENERAL, LOG_ERR, QString(
"Backend has no '%1' user!")
1223 LOG(VB_GENERAL, LOG_ERR, QString(
"Incorrect password for user: %1")
1228 QStringList serviceList = sServices.split(
",");
1230 serviceList.removeDuplicates();
1232 QStringList protectedURLs;
1234 if (serviceList.size() == 1 && serviceList.first() ==
"All")
1237 protectedURLs <<
'/' + service;
1239 else if (serviceList.size() == 1 && serviceList.first() ==
"None")
1241 protectedURLs <<
"Unprotected";
1245 for (
const QString& service : std::as_const(serviceList))
1248 protectedURLs <<
'/' + service;
1250 LOG(VB_GENERAL, LOG_ERR, QString(
"Invalid service name: '%1'")
1255 if (protectedURLs.isEmpty())
1257 LOG(VB_GENERAL, LOG_ERR,
"No valid Services were found");
1262 protectedURLs.join(
';'),
"");
1268 if (scheduler ==
nullptr)
1269 throw QString(
"Scheduler is null");
1271 if (Enable == Disable)
1274 scheduler->EnableScheduling();
1277 scheduler->DisableScheduling();
1278 api_sd_notify(
"STATUS=Scheduling disabled via Services API/Web App.");
1282 while (iter.hasNext())
1285 auto *tvrec = iter.value();
1286 tvrec->EnableActiveScan(Enable);
1293 if (Retcode < 0 || Retcode > 255)
1311 QCoreApplication::exit(Retcode);
1317 QUrl url(urlString);
1321 auto *req =
new QNetworkRequest(url);
1322 req->setHeader(QNetworkRequest::ContentTypeHeader, QString(
"application/x-www-form-urlencoded"));