Permalink
Browse files

MainServer: add a QUERY_FINDFILE command to the protocol

This command will search a storage group on a host for a file and return the
myth url's of any matching file(s) or 'NOT_FOUND' if nothing was found.

The filename can be a regular filename like 'front.jpg' or a regexp like
'front.(jpg|jpeg|png|gif)'.

If allowfallback is '0' only the given host is searched otherwise all hosts
will be searched until a match is found or there is an error.

The format of the command is:
QUERY_FINDFILE <host> <storagegroup> <filename> <useregex (optional)> <allowfallback (optional)>

NOTE: bumps the myth protocol version so all backends and frontends need to be
updated.
  • Loading branch information...
1 parent 411272f commit 1dab1907956b6b30b65d0da627f22e6dd00d24d4 Paul Harrison committed Jul 22, 2014
@@ -107,8 +107,8 @@ package MythTV;
# Note: as of July 21, 2010, this is actually a string, to account for proto
# versions of the form "58a". This will get used if protocol versions are
# changed on a fixes branch ongoing.
- our $PROTO_VERSION = "82";
- our $PROTO_TOKEN = "IdIdO";
+ our $PROTO_VERSION = "83";
+ our $PROTO_TOKEN = "BreakingGlass";
# currentDatabaseVersion is defined in libmythtv in
# mythtv/libs/libmythtv/dbcheck.cpp and should be the current MythTV core
@@ -11,8 +11,8 @@ class MythBackend {
// MYTH_PROTO_VERSION is defined in libmyth in mythtv/libs/libmyth/mythcontext.h
// and should be the current MythTV protocol version.
- static $protocol_version = '82';
- static $protocol_token = 'IdIdO';
+ static $protocol_version = '83';
+ static $protocol_token = 'BreakingGlass';
// The character string used by the backend to separate records
static $backend_separator = '[]:[]';
@@ -8,8 +8,8 @@
SCHEMA_VERSION = 1328
NVSCHEMA_VERSION = 1007
MUSICSCHEMA_VERSION = 1018
-PROTO_VERSION = '82'
-PROTO_TOKEN = 'IdIdO'
+PROTO_VERSION = '83'
+PROTO_TOKEN = 'BreakingGlass'
BACKEND_SEP = '[]:[]'
INSTALL_PREFIX = '/usr/local'
@@ -39,8 +39,8 @@
* http://www.mythtv.org/wiki/Category:Myth_Protocol_Commands
* http://www.mythtv.org/wiki/Category:Myth_Protocol
*/
-#define MYTH_PROTO_VERSION "82"
-#define MYTH_PROTO_TOKEN "IdIdO"
+#define MYTH_PROTO_VERSION "83"
+#define MYTH_PROTO_TOKEN "BreakingGlass"
/** \brief Increment this whenever the MythTV core database schema changes.
*
@@ -582,6 +582,13 @@ void MainServer::ProcessRequestWork(MythSocket *sock)
else
HandleQueryFileExists(listline, pbs);
}
+ else if (command == "QUERY_FINDFILE")
+ {
+ if (listline.size() < 4)
+ SendErrorResponse(pbs, "Bad QUERY_FINDFILE command");
+ else
+ HandleQueryFindFile(listline, pbs);
+ }
else if (command == "QUERY_FILE_HASH")
{
if (listline.size() < 3)
@@ -3570,6 +3577,206 @@ void MainServer::HandleSGGetFileList(QStringList &sList,
SendResponse(pbssock, strList);
}
+void MainServer::HandleQueryFindFile(QStringList &slist, PlaybackSock *pbs)
+{
+//format: QUERY_FINDFILE <host> <storagegroup> <filename> <useregex (optional)> <allowfallback (optional)>
+
+ QString hostname = slist[1];
+ QString storageGroup = slist[2];
+ QString filename = slist[3];
+ bool allowFallback = true;
+ bool useRegex = false;
+ QStringList fileList;
+
+ if (hostname.isEmpty())
+ hostname = gCoreContext->GetHostName();
+
+ if (storageGroup.isEmpty())
+ storageGroup = "Default";
+
+ if (filename.isEmpty() || filename.contains("/../") ||
+ filename.startsWith("../"))
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+ QString("ERROR QueryFindFile, filename '%1' "
+ "fails sanity checks").arg(filename));
+ fileList << "ERROR: Bad/Missing Filename";
+ SendResponse(pbs->getSocket(), fileList);
+ return;
+ }
+
+ if (slist.size() >= 5)
+ useRegex = (slist[4].toInt() > 0);
+
+ if (slist.size() >= 6)
+ allowFallback = (slist[5].toInt() > 0);
+
+ LOG(VB_FILE, LOG_INFO, LOC +
+ QString("Looking for file '%1' on host '%2' in group '%3' (useregex: %4, allowfallback: %5")
+ .arg(filename).arg(hostname).arg(storageGroup).arg(useRegex).arg(allowFallback));
+
+ // first check the given host
+ if (hostname == gCoreContext->GetHostName() || hostname == gCoreContext->GetBackendServerIP())
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Checking local host '%1' for file").arg(hostname));
+
+ // check the local storage group
+ StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
+
+ if (useRegex)
+ {
+ QFileInfo fi(filename);
+ QStringList files = sgroup.GetFileList('/' + fi.path());
+
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Looking in dir '%1' for '%2'").arg(fi.path()).arg(fi.fileName()));
+
+ for (int x = 0; x < files.size(); x++)
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Found '%1 - %2'").arg(x).arg(files[x]));
+ }
+
+ QStringList filteredFiles = files.filter(QRegExp(fi.fileName()));
+ for (int x = 0; x < filteredFiles.size(); x++)
+ {
+ fileList << gCoreContext->GenMythURL(gCoreContext->GetBackendServerIP(),
+ gCoreContext->GetBackendServerPort(),
+ fi.path() + '/' + filteredFiles[x],
+ storageGroup);
+ }
+ }
+ else
+ {
+ if (!sgroup.FindFile(filename).isEmpty())
+ {
+ fileList << gCoreContext->GenMythURL(gCoreContext->GetBackendServerIP(),
+ gCoreContext->GetBackendServerPort(),
+ filename, storageGroup);
+ }
+ }
+ }
+ else
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Checking remote host '%1' for file").arg(hostname));
+
+ // check the given slave hostname
+ PlaybackSock *slave = GetMediaServerByHostname(hostname);
+ if (slave)
+ {
+ QStringList slaveFiles = slave->GetFindFile(hostname, filename, storageGroup, useRegex);
+
+ if (!slaveFiles.isEmpty() && slaveFiles[0] != "NOT FOUND" && !slaveFiles[0].startsWith("ERROR: "))
+ fileList += slaveFiles;
+
+ slave->DecrRef();
+ }
+ else
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Slave '%1' was unreachable").arg(hostname));
+ fileList << QString("ERROR: SLAVE UNREACHABLE: %1").arg(hostname);
+ SendResponse(pbs->getSocket(), fileList);
+ return;
+ }
+ }
+
+ // if we still haven't found it and this is the master and fallback is enabled
+ // check all other slaves that have a directory in the storagegroup
+ if (ismaster && fileList.isEmpty() && allowFallback)
+ {
+ // get a list of hosts
+ MSqlQuery query(MSqlQuery::InitCon());
+
+ QString sql = "SELECT DISTINCT hostname "
+ "FROM storagegroup "
+ "WHERE groupname = :GROUP "
+ "AND hostname != :HOSTNAME";
+ query.prepare(sql);
+ query.bindValue(":GROUP", storageGroup);
+ query.bindValue(":HOSTNAME", hostname);
+
+ if (!query.exec() || !query.isActive())
+ {
+ MythDB::DBError(LOC + "FindFile() get host list", query);
+ fileList << "ERROR: failed to get host list";
+ SendResponse(pbs->getSocket(), fileList);
+ return;
+ }
+
+ while(query.next())
+ {
+ hostname = query.value(0).toString();
+
+ if (hostname == gCoreContext->GetMasterHostName())
+ {
+ StorageGroup sgroup(storageGroup, hostname);
+
+ if (useRegex)
+ {
+ QFileInfo fi(filename);
+ QStringList files = sgroup.GetFileList('/' + fi.path());
+
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Looking in dir '%1' for '%2'").arg(fi.path()).arg(fi.fileName()));
+
+ for (int x = 0; x < files.size(); x++)
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("Found '%1 - %2'").arg(x).arg(files[x]));
+ }
+
+ QStringList filteredFiles = files.filter(QRegExp(fi.fileName()));
+
+ for (int x = 0; x < filteredFiles.size(); x++)
+ {
+ fileList << gCoreContext->GenMythURL(gCoreContext->GetBackendServerIP(),
+ gCoreContext->GetBackendServerPort(),
+ fi.path() + '/' + filteredFiles[x],
+ storageGroup);
+ }
+ }
+ else
+ {
+ QString fname = sgroup.FindFile(filename);
+ if (!fname.isEmpty())
+ {
+ fileList << gCoreContext->GenMythURL(gCoreContext->GetMasterServerIP(),
+ gCoreContext->GetMasterServerPort(),
+ filename, storageGroup);
+ }
+ }
+ }
+ else
+ {
+ // check the slave host
+ PlaybackSock *slave = GetMediaServerByHostname(hostname);
+ if (slave)
+ {
+ QStringList slaveFiles = slave->GetFindFile(hostname, filename, storageGroup, useRegex);
+ if (!slaveFiles.isEmpty() && slaveFiles[0] != "NOT FOUND" && !slaveFiles[0].startsWith("ERROR: "))
+ fileList += slaveFiles;
+
+ slave->DecrRef();
+ }
+ }
+
+ if (!fileList.isEmpty())
+ break;
+ }
+ }
+
+ if (fileList.isEmpty())
+ {
+ fileList << "NOT FOUND";
+ LOG(VB_FILE, LOG_INFO, LOC + QString("File was not found"));
+ }
+ else
+ {
+ for (int x = 0; x < fileList.size(); x++)
+ {
+ LOG(VB_FILE, LOG_INFO, LOC + QString("File %1 was found at: '%2'").arg(x).arg(fileList[0]));
+ }
+ }
+
+ SendResponse(pbs->getSocket(), fileList);
+}
+
void MainServer::HandleSGFileQuery(QStringList &sList,
PlaybackSock *pbs)
{
@@ -170,6 +170,7 @@ class MainServer : public QObject, public MythSocketCBs
void HandleQueryFreeSpaceSummary(PlaybackSock *pbs);
void HandleQueryCheckFile(QStringList &slist, PlaybackSock *pbs);
void HandleQueryFileExists(QStringList &slist, PlaybackSock *pbs);
+ void HandleQueryFindFile(QStringList &slist, PlaybackSock *pbs);
void HandleQueryFileHash(QStringList &slist, PlaybackSock *pbs);
void HandleQueryGuideDataThrough(PlaybackSock *pbs);
void HandleGetPendingRecordings(PlaybackSock *pbs, QString table = "", int recordid=-1);
@@ -265,6 +265,20 @@ QString PlaybackSock::GetFileHash(QString filename, QString storageGroup)
return strlist[0];
}
+QStringList PlaybackSock::GetFindFile(const QString &host, const QString &filename,
+ const QString &storageGroup, bool useRegex)
+{
+ QStringList strlist(QString("QUERY_FINDFILE"));
+ strlist << host
+ << storageGroup
+ << filename
+ << (useRegex ? "1" : "0")
+ << "0";
+
+ SendReceiveStringList(strlist);
+ return strlist;
+}
+
QStringList PlaybackSock::GenPreviewPixmap(const QString &token,
const ProgramInfo *pginfo)
{
@@ -69,6 +69,8 @@ class PlaybackSock : public ReferenceCounter
QStringList GetSGFileQuery(QString &host, QString &groupname,
QString &filename);
QString GetFileHash(QString filename, QString storageGroup);
+ QStringList GetFindFile(const QString &host, const QString &filename,
+ const QString &storageGroup, bool useRegex);
QStringList GenPreviewPixmap(const QString &token,
const ProgramInfo *pginfo);

0 comments on commit 1dab190

Please sign in to comment.