Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

670 lines (579 sloc) 19.814 kB
// POSIX headers
#include <sys/time.h> // for setpriority
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include "mythconfig.h"
#if CONFIG_DARWIN
#include <sys/aio.h> // O_SYNC
#endif
// C headers
#include <cstdlib>
#include <cerrno>
#include <QCoreApplication>
#include <QFileInfo>
#include <QRegExp>
#include <QFile>
#include <QDir>
#include <QMap>
#include "tv_rec.h"
#include "scheduledrecording.h"
#include "mythsocketthread.h"
#include "autoexpire.h"
#include "scheduler.h"
#include "mainserver.h"
#include "encoderlink.h"
#include "remoteutil.h"
#include "housekeeper.h"
#include "mythcontext.h"
#include "mythversion.h"
#include "mythdb.h"
#include "exitcodes.h"
#include "compat.h"
#include "storagegroup.h"
#include "programinfo.h"
#include "dbcheck.h"
#include "jobqueue.h"
#include "previewgenerator.h"
#include "commandlineparser.h"
#include "mythsystemevent.h"
#include "main_helpers.h"
#include "backendcontext.h"
#include "mythtranslation.h"
#include "mediaserver.h"
#include "httpstatus.h"
#include "mythlogging.h"
#define LOC QString("MythBackend: ")
#define LOC_WARN QString("MythBackend, Warning: ")
#define LOC_ERR QString("MythBackend, Error: ")
static MainServer *mainServer = NULL;
bool setupTVs(bool ismaster, bool &error)
{
error = false;
QString localhostname = gCoreContext->GetHostName();
MSqlQuery query(MSqlQuery::InitCon());
if (ismaster)
{
// Hack to make sure recorded.basename gets set if the user
// downgrades to a prior version and creates new entries
// without it.
if (!query.exec("UPDATE recorded SET basename = CONCAT(chanid, '_', "
"DATE_FORMAT(starttime, '%Y%m%d%H%i00'), '_', "
"DATE_FORMAT(endtime, '%Y%m%d%H%i00'), '.nuv') "
"WHERE basename = '';"))
MythDB::DBError("Updating record basename", query);
// Hack to make sure record.station gets set if the user
// downgrades to a prior version and creates new entries
// without it.
if (!query.exec("UPDATE channel SET callsign=chanid "
"WHERE callsign IS NULL OR callsign='';"))
MythDB::DBError("Updating channel callsign", query);
if (query.exec("SELECT MIN(chanid) FROM channel;"))
{
query.first();
int min_chanid = query.value(0).toInt();
if (!query.exec(QString("UPDATE record SET chanid = %1 "
"WHERE chanid IS NULL;").arg(min_chanid)))
MythDB::DBError("Updating record chanid", query);
}
else
MythDB::DBError("Querying minimum chanid", query);
MSqlQuery records_without_station(MSqlQuery::InitCon());
records_without_station.prepare("SELECT record.chanid,"
" channel.callsign FROM record LEFT JOIN channel"
" ON record.chanid = channel.chanid WHERE record.station='';");
if (records_without_station.exec() && records_without_station.next())
{
MSqlQuery update_record(MSqlQuery::InitCon());
update_record.prepare("UPDATE record SET station = :CALLSIGN"
" WHERE chanid = :CHANID;");
do
{
update_record.bindValue(":CALLSIGN",
records_without_station.value(1));
update_record.bindValue(":CHANID",
records_without_station.value(0));
if (!update_record.exec())
{
MythDB::DBError("Updating record station", update_record);
}
} while (records_without_station.next());
}
}
if (!query.exec(
"SELECT cardid, hostname "
"FROM capturecard "
"ORDER BY cardid"))
{
MythDB::DBError("Querying Recorders", query);
return false;
}
vector<uint> cardids;
vector<QString> hosts;
while (query.next())
{
uint cardid = query.value(0).toUInt();
QString host = query.value(1).toString();
QString cidmsg = QString("Card %1").arg(cardid);
if (host.isEmpty())
{
LOG(VB_GENERAL, LOG_ERR, cidmsg +
" does not have a hostname defined.\n"
"Please run setup and confirm all of the capture cards.\n");
continue;
}
cardids.push_back(cardid);
hosts.push_back(host);
}
for (uint i = 0; i < cardids.size(); i++)
{
if (hosts[i] == localhostname)
new TVRec(cardids[i]);
}
for (uint i = 0; i < cardids.size(); i++)
{
uint cardid = cardids[i];
QString host = hosts[i];
QString cidmsg = QString("Card %1").arg(cardid);
if (!ismaster)
{
if (host == localhostname)
{
TVRec *tv = TVRec::GetTVRec(cardid);
if (tv && tv->Init())
{
EncoderLink *enc = new EncoderLink(cardid, tv);
tvList[cardid] = enc;
}
else
{
LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards. " +
cidmsg + " failed init");
delete tv;
// The master assumes card comes up so we need to
// set error and exit if a non-master card fails.
error = true;
}
}
}
else
{
if (host == localhostname)
{
TVRec *tv = TVRec::GetTVRec(cardid);
if (tv && tv->Init())
{
EncoderLink *enc = new EncoderLink(cardid, tv);
tvList[cardid] = enc;
}
else
{
LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards" +
cidmsg + "failed init");
delete tv;
}
}
else
{
EncoderLink *enc = new EncoderLink(cardid, NULL, host);
tvList[cardid] = enc;
}
}
}
if (tvList.empty())
{
LOG(VB_GENERAL, LOG_WARNING, LOC +
"No valid capture cards are defined in the database.");
}
return true;
}
void cleanup(void)
{
signal(SIGTERM, SIG_DFL);
#ifndef _MSC_VER
signal(SIGUSR1, SIG_DFL);
#endif
if (mainServer)
mainServer->Stop();
delete housekeeping;
housekeeping = NULL;
if (gCoreContext)
{
delete gCoreContext->GetScheduler();
gCoreContext->SetScheduler(NULL);
}
delete expirer;
expirer = NULL;
delete jobqueue;
jobqueue = NULL;
delete g_pUPnp;
g_pUPnp = NULL;
if (SSDP::Instance())
{
SSDP::Instance()->RequestTerminate();
SSDP::Instance()->wait();
}
if (TaskQueue::Instance())
{
TaskQueue::Instance()->RequestTerminate();
TaskQueue::Instance()->wait();
}
while (!TVRec::cards.empty())
{
TVRec *rec = *TVRec::cards.begin();
delete rec;
}
delete gContext;
gContext = NULL;
delete mainServer;
mainServer = NULL;
if (pidfile.size())
{
unlink(pidfile.toAscii().constData());
pidfile.clear();
}
}
int handle_command(const MythBackendCommandLineParser &cmdline)
{
QString eventString;
if (cmdline.toBool("event"))
eventString = cmdline.toString("event");
else if (cmdline.toBool("systemevent"))
eventString = "SYSTEM_EVENT " +
cmdline.toString("systemevent") +
QString(" SENDER %1").arg(gCoreContext->GetHostName());
if (!eventString.isEmpty())
{
if (gCoreContext->ConnectToMasterServer())
{
gCoreContext->SendMessage(eventString);
return GENERIC_EXIT_OK;
}
return GENERIC_EXIT_NO_MYTHCONTEXT;
}
if (cmdline.toBool("setverbose"))
{
if (gCoreContext->ConnectToMasterServer())
{
QString message = "SET_VERBOSE ";
message += cmdline.toString("setverbose");
gCoreContext->SendMessage(message);
LOG(VB_GENERAL, LOG_INFO,
QString("Sent '%1' message").arg(message));
return GENERIC_EXIT_OK;
}
else
{
LOG(VB_GENERAL, LOG_ERR,
"Unable to connect to backend, verbose mask unchanged ");
return GENERIC_EXIT_CONNECT_ERROR;
}
}
if (cmdline.toBool("setloglevel"))
{
if (gCoreContext->ConnectToMasterServer())
{
QString message = "SET_LOG_LEVEL ";
message += cmdline.toString("setloglevel");
gCoreContext->SendMessage(message);
LOG(VB_GENERAL, LOG_INFO,
QString("Sent '%1' message").arg(message));
return GENERIC_EXIT_OK;
}
else
{
LOG(VB_GENERAL, LOG_ERR,
"Unable to connect to backend, log level unchanged ");
return GENERIC_EXIT_CONNECT_ERROR;
}
}
if (cmdline.toBool("clearcache"))
{
if (gCoreContext->ConnectToMasterServer())
{
gCoreContext->SendMessage("CLEAR_SETTINGS_CACHE");
LOG(VB_GENERAL, LOG_INFO, "Sent CLEAR_SETTINGS_CACHE message");
return GENERIC_EXIT_OK;
}
else
{
LOG(VB_GENERAL, LOG_ERR, "Unable to connect to backend, settings "
"cache will not be cleared.");
return GENERIC_EXIT_CONNECT_ERROR;
}
}
if (cmdline.toBool("printsched") ||
cmdline.toBool("testsched"))
{
Scheduler *sched = new Scheduler(false, &tvList);
if (!cmdline.toBool("testsched") &&
gCoreContext->ConnectToMasterServer())
{
cout << "Retrieving Schedule from Master backend.\n";
sched->FillRecordListFromMaster();
}
else
{
cout << "Calculating Schedule from database.\n" <<
"Inputs, Card IDs, and Conflict info may be invalid "
"if you have multiple tuners.\n";
ProgramInfo::CheckProgramIDAuthorities();
sched->FillRecordListFromDB();
}
verboseMask |= VB_SCHEDULE;
sched->PrintList(true);
delete sched;
return GENERIC_EXIT_OK;
}
if (cmdline.toBool("resched"))
{
bool ok = false;
if (gCoreContext->ConnectToMasterServer())
{
LOG(VB_GENERAL, LOG_INFO, "Connected to master for reschedule");
ScheduledRecording::signalChange(-1);
ok = true;
}
else
LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for reschedule");
return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
}
if (cmdline.toBool("scanvideos"))
{
bool ok = false;
if (gCoreContext->ConnectToMasterServer())
{
gCoreContext->SendReceiveStringList(QStringList() << "SCAN_VIDEOS");
LOG(VB_GENERAL, LOG_INFO, "Requested video scan");
ok = true;
}
else
LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for video scan");
return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
}
if (cmdline.toBool("printexpire"))
{
expirer = new AutoExpire();
expirer->PrintExpireList(cmdline.toString("printexpire"));
return GENERIC_EXIT_OK;
}
// This should never actually be reached..
return GENERIC_EXIT_OK;
}
int connect_to_master(void)
{
MythSocket *tempMonitorConnection = new MythSocket();
if (tempMonitorConnection->connect(
gCoreContext->GetSetting("MasterServerIP", "127.0.0.1"),
gCoreContext->GetNumSetting("MasterServerPort", 6543)))
{
if (!gCoreContext->CheckProtoVersion(tempMonitorConnection))
{
LOG(VB_GENERAL, LOG_ERR, "Master backend is incompatible with "
"this backend.\nCannot become a slave.");
return GENERIC_EXIT_CONNECT_ERROR;
}
QStringList tempMonitorDone("DONE");
QStringList tempMonitorAnnounce("ANN Monitor tzcheck 0");
tempMonitorConnection->writeStringList(tempMonitorAnnounce);
tempMonitorConnection->readStringList(tempMonitorAnnounce);
if (tempMonitorAnnounce.empty() ||
tempMonitorAnnounce[0] == "ERROR")
{
tempMonitorConnection->DownRef();
tempMonitorConnection = NULL;
if (tempMonitorAnnounce.empty())
{
LOG(VB_GENERAL, LOG_ERR, LOC +
"Failed to open event socket, timeout");
}
else
{
LOG(VB_GENERAL, LOG_ERR, LOC +
"Failed to open event socket" +
((tempMonitorAnnounce.size() >= 2) ?
QString(", error was %1").arg(tempMonitorAnnounce[1]) :
QString(", remote error")));
}
}
QStringList tzCheck("QUERY_TIME_ZONE");
if (tempMonitorConnection)
{
tempMonitorConnection->writeStringList(tzCheck);
tempMonitorConnection->readStringList(tzCheck);
}
if (tzCheck.size() && !checkTimeZone(tzCheck))
{
// Check for different time zones, different offsets, different
// times
LOG(VB_GENERAL, LOG_ERR, "The time and/or time zone settings on "
"this system do not match those in use on the master "
"backend. Please ensure all frontend and backend "
"systems are configured to use the same time zone and "
"have the current time properly set.");
LOG(VB_GENERAL, LOG_ERR,
"Unable to run with invalid time settings. Exiting.");
tempMonitorConnection->writeStringList(tempMonitorDone);
tempMonitorConnection->DownRef();
return GENERIC_EXIT_INVALID_TIMEZONE;
}
else
{
LOG(VB_GENERAL, LOG_INFO,
QString("Backend is running in %1 time zone.")
.arg(getTimeZoneID()));
}
if (tempMonitorConnection)
tempMonitorConnection->writeStringList(tempMonitorDone);
}
if (tempMonitorConnection)
tempMonitorConnection->DownRef();
return GENERIC_EXIT_OK;
}
void print_warnings(const MythBackendCommandLineParser &cmdline)
{
if (cmdline.toBool("nohousekeeper"))
{
LOG(VB_GENERAL, LOG_WARNING, LOC +
"****** The Housekeeper has been DISABLED with "
"the --nohousekeeper option ******");
}
if (cmdline.toBool("nosched"))
{
LOG(VB_GENERAL, LOG_WARNING, LOC +
"********** The Scheduler has been DISABLED with "
"the --nosched option **********");
}
if (cmdline.toBool("noautoexpire"))
{
LOG(VB_GENERAL, LOG_WARNING, LOC +
"********* Auto-Expire has been DISABLED with "
"the --noautoexpire option ********");
}
if (cmdline.toBool("nojobqueue"))
{
LOG(VB_GENERAL, LOG_WARNING, LOC +
"********* The JobQueue has been DISABLED with "
"the --nojobqueue option *********");
}
}
int run_backend(MythBackendCommandLineParser &cmdline)
{
bool ismaster = gCoreContext->IsMasterHost();
if (!UpgradeTVDatabaseSchema(ismaster, ismaster))
{
LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema");
return GENERIC_EXIT_DB_OUTOFDATE;
}
MythTranslation::load("mythfrontend");
if (!ismaster)
{
int ret = connect_to_master();
if (ret != GENERIC_EXIT_OK)
return ret;
}
int port = gCoreContext->GetNumSetting("BackendServerPort", 6543);
if (gCoreContext->GetSetting("BackendServerIP").isEmpty() &&
gCoreContext->GetSetting("BackendServerIP6").isEmpty())
{
cerr << "No setting found for this machine's BackendServerIP.\n"
<< "Please run setup on this machine and modify the first page\n"
<< "of the general settings.\n";
return GENERIC_EXIT_SETUP_ERROR;
}
MythSystemEventHandler *sysEventHandler = new MythSystemEventHandler();
if (ismaster)
{
LOG(VB_GENERAL, LOG_NOTICE, LOC + "Starting up as the master server.");
}
else
{
LOG(VB_GENERAL, LOG_NOTICE, LOC + "Running as a slave backend.");
}
print_warnings(cmdline);
bool fatal_error = false;
bool runsched = setupTVs(ismaster, fatal_error);
if (fatal_error)
{
delete sysEventHandler;
return GENERIC_EXIT_SETUP_ERROR;
}
Scheduler *sched = NULL;
if (ismaster)
{
if (runsched)
{
sched = new Scheduler(true, &tvList);
int err = sched->GetError();
if (err)
return err;
if (cmdline.toBool("nosched"))
sched->DisableScheduling();
}
if (!cmdline.toBool("nohousekeeper"))
housekeeping = new HouseKeeper(true, ismaster, sched);
if (!cmdline.toBool("noautoexpire"))
{
expirer = new AutoExpire(&tvList);
if (sched)
sched->SetExpirer(expirer);
}
gCoreContext->SetScheduler(sched);
}
else if (!cmdline.toBool("nohousekeeper"))
{
housekeeping = new HouseKeeper(true, ismaster, NULL);
}
if (!cmdline.toBool("nojobqueue"))
jobqueue = new JobQueue(ismaster);
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
if (g_pUPnp == NULL)
{
g_pUPnp = new MediaServer();
g_pUPnp->Init(ismaster, cmdline.toBool("noupnp"));
}
// ----------------------------------------------------------------------
// Setup status server
// ----------------------------------------------------------------------
HttpStatus *httpStatus = NULL;
HttpServer *pHS = g_pUPnp->GetHttpServer();
if (pHS)
{
LOG(VB_GENERAL, LOG_INFO, "Main::Registering HttpStatus Extension");
httpStatus = new HttpStatus( &tvList, sched, expirer, ismaster );
pHS->RegisterExtension( httpStatus );
}
mainServer = new MainServer(
ismaster, port, &tvList, sched, expirer);
int exitCode = mainServer->GetExitCode();
if (exitCode != GENERIC_EXIT_OK)
{
LOG(VB_GENERAL, LOG_CRIT,
"Backend exiting, MainServer initialization error.");
delete mainServer;
return exitCode;
}
if (httpStatus && mainServer)
httpStatus->SetMainServer(mainServer);
StorageGroup::CheckAllStorageGroupDirs();
if (gCoreContext->IsMasterBackend())
gCoreContext->SendSystemEvent("MASTER_STARTED");
///////////////////////////////
///////////////////////////////
exitCode = qApp->exec();
///////////////////////////////
///////////////////////////////
if (gCoreContext->IsMasterBackend())
{
gCoreContext->SendSystemEvent("MASTER_SHUTDOWN");
qApp->processEvents();
}
LOG(VB_GENERAL, LOG_NOTICE, "MythBackend exiting");
delete sysEventHandler;
return exitCode;
}
Jump to Line
Something went wrong with that request. Please try again.