00001
00002 #include <sys/time.h>
00003 #include <unistd.h>
00004 #include <sys/types.h>
00005 #include <sys/stat.h>
00006 #include <fcntl.h>
00007 #include <libgen.h>
00008 #include <signal.h>
00009
00010 #include "mythconfig.h"
00011 #if CONFIG_DARWIN
00012 #include <sys/aio.h>
00013 #endif
00014
00015
00016 #include <cstdlib>
00017 #include <cerrno>
00018
00019 #include <QCoreApplication>
00020 #include <QFileInfo>
00021 #include <QRegExp>
00022 #include <QFile>
00023 #include <QDir>
00024 #include <QMap>
00025
00026 #include "tv_rec.h"
00027 #include "scheduledrecording.h"
00028 #include "mythsocketthread.h"
00029 #include "autoexpire.h"
00030 #include "scheduler.h"
00031 #include "mainserver.h"
00032 #include "encoderlink.h"
00033 #include "remoteutil.h"
00034 #include "housekeeper.h"
00035
00036 #include "mythcontext.h"
00037 #include "mythversion.h"
00038 #include "mythdb.h"
00039 #include "exitcodes.h"
00040 #include "compat.h"
00041 #include "storagegroup.h"
00042 #include "programinfo.h"
00043 #include "dbcheck.h"
00044 #include "jobqueue.h"
00045 #include "previewgenerator.h"
00046 #include "commandlineparser.h"
00047 #include "mythsystemevent.h"
00048 #include "main_helpers.h"
00049 #include "backendcontext.h"
00050 #include "mythtranslation.h"
00051
00052 #include "mediaserver.h"
00053 #include "httpstatus.h"
00054 #include "mythlogging.h"
00055
00056 #define LOC QString("MythBackend: ")
00057 #define LOC_WARN QString("MythBackend, Warning: ")
00058 #define LOC_ERR QString("MythBackend, Error: ")
00059
00060 static MainServer *mainServer = NULL;
00061
00062 bool setupTVs(bool ismaster, bool &error)
00063 {
00064 error = false;
00065 QString localhostname = gCoreContext->GetHostName();
00066
00067 MSqlQuery query(MSqlQuery::InitCon());
00068
00069 if (ismaster)
00070 {
00071
00072
00073
00074 if (!query.exec("UPDATE recorded SET basename = CONCAT(chanid, '_', "
00075 "DATE_FORMAT(starttime, '%Y%m%d%H%i00'), '_', "
00076 "DATE_FORMAT(endtime, '%Y%m%d%H%i00'), '.nuv') "
00077 "WHERE basename = '';"))
00078 MythDB::DBError("Updating record basename", query);
00079
00080
00081
00082
00083 if (!query.exec("UPDATE channel SET callsign=chanid "
00084 "WHERE callsign IS NULL OR callsign='';"))
00085 MythDB::DBError("Updating channel callsign", query);
00086
00087 if (query.exec("SELECT MIN(chanid) FROM channel;"))
00088 {
00089 query.first();
00090 int min_chanid = query.value(0).toInt();
00091 if (!query.exec(QString("UPDATE record SET chanid = %1 "
00092 "WHERE chanid IS NULL;").arg(min_chanid)))
00093 MythDB::DBError("Updating record chanid", query);
00094 }
00095 else
00096 MythDB::DBError("Querying minimum chanid", query);
00097
00098 MSqlQuery records_without_station(MSqlQuery::InitCon());
00099 records_without_station.prepare("SELECT record.chanid,"
00100 " channel.callsign FROM record LEFT JOIN channel"
00101 " ON record.chanid = channel.chanid WHERE record.station='';");
00102 if (records_without_station.exec() && records_without_station.next())
00103 {
00104 MSqlQuery update_record(MSqlQuery::InitCon());
00105 update_record.prepare("UPDATE record SET station = :CALLSIGN"
00106 " WHERE chanid = :CHANID;");
00107 do
00108 {
00109 update_record.bindValue(":CALLSIGN",
00110 records_without_station.value(1));
00111 update_record.bindValue(":CHANID",
00112 records_without_station.value(0));
00113 if (!update_record.exec())
00114 {
00115 MythDB::DBError("Updating record station", update_record);
00116 }
00117 } while (records_without_station.next());
00118 }
00119 }
00120
00121 if (!query.exec(
00122 "SELECT cardid, hostname "
00123 "FROM capturecard "
00124 "ORDER BY cardid"))
00125 {
00126 MythDB::DBError("Querying Recorders", query);
00127 return false;
00128 }
00129
00130 vector<uint> cardids;
00131 vector<QString> hosts;
00132 while (query.next())
00133 {
00134 uint cardid = query.value(0).toUInt();
00135 QString host = query.value(1).toString();
00136 QString cidmsg = QString("Card %1").arg(cardid);
00137
00138 if (host.isEmpty())
00139 {
00140 LOG(VB_GENERAL, LOG_ERR, cidmsg +
00141 " does not have a hostname defined.\n"
00142 "Please run setup and confirm all of the capture cards.\n");
00143 continue;
00144 }
00145
00146 cardids.push_back(cardid);
00147 hosts.push_back(host);
00148 }
00149
00150 for (uint i = 0; i < cardids.size(); i++)
00151 {
00152 if (hosts[i] == localhostname)
00153 new TVRec(cardids[i]);
00154 }
00155
00156 for (uint i = 0; i < cardids.size(); i++)
00157 {
00158 uint cardid = cardids[i];
00159 QString host = hosts[i];
00160 QString cidmsg = QString("Card %1").arg(cardid);
00161
00162 if (!ismaster)
00163 {
00164 if (host == localhostname)
00165 {
00166 TVRec *tv = TVRec::GetTVRec(cardid);
00167 if (tv && tv->Init())
00168 {
00169 EncoderLink *enc = new EncoderLink(cardid, tv);
00170 tvList[cardid] = enc;
00171 }
00172 else
00173 {
00174 LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards. " +
00175 cidmsg + " failed init");
00176 delete tv;
00177
00178
00179 error = true;
00180 }
00181 }
00182 }
00183 else
00184 {
00185 if (host == localhostname)
00186 {
00187 TVRec *tv = TVRec::GetTVRec(cardid);
00188 if (tv && tv->Init())
00189 {
00190 EncoderLink *enc = new EncoderLink(cardid, tv);
00191 tvList[cardid] = enc;
00192 }
00193 else
00194 {
00195 LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards" +
00196 cidmsg + "failed init");
00197 delete tv;
00198 }
00199 }
00200 else
00201 {
00202 EncoderLink *enc = new EncoderLink(cardid, NULL, host);
00203 tvList[cardid] = enc;
00204 }
00205 }
00206 }
00207
00208 if (tvList.empty())
00209 {
00210 LOG(VB_GENERAL, LOG_WARNING, LOC +
00211 "No valid capture cards are defined in the database.");
00212 }
00213
00214 return true;
00215 }
00216
00217 void cleanup(void)
00218 {
00219 signal(SIGTERM, SIG_DFL);
00220 #ifndef _MSC_VER
00221 signal(SIGUSR1, SIG_DFL);
00222 #endif
00223
00224 if (mainServer)
00225 mainServer->Stop();
00226
00227 delete housekeeping;
00228 housekeeping = NULL;
00229
00230 if (gCoreContext)
00231 {
00232 delete gCoreContext->GetScheduler();
00233 gCoreContext->SetScheduler(NULL);
00234 }
00235
00236 delete expirer;
00237 expirer = NULL;
00238
00239 delete jobqueue;
00240 jobqueue = NULL;
00241
00242 delete g_pUPnp;
00243 g_pUPnp = NULL;
00244
00245 if (SSDP::Instance())
00246 {
00247 SSDP::Instance()->RequestTerminate();
00248 SSDP::Instance()->wait();
00249 }
00250
00251 if (TaskQueue::Instance())
00252 {
00253 TaskQueue::Instance()->RequestTerminate();
00254 TaskQueue::Instance()->wait();
00255 }
00256
00257 while (!TVRec::cards.empty())
00258 {
00259 TVRec *rec = *TVRec::cards.begin();
00260 delete rec;
00261 }
00262
00263 delete gContext;
00264 gContext = NULL;
00265
00266 delete mainServer;
00267 mainServer = NULL;
00268
00269 if (pidfile.size())
00270 {
00271 unlink(pidfile.toAscii().constData());
00272 pidfile.clear();
00273 }
00274 }
00275
00276 int handle_command(const MythBackendCommandLineParser &cmdline)
00277 {
00278 QString eventString;
00279
00280 if (cmdline.toBool("event"))
00281 eventString = cmdline.toString("event");
00282 else if (cmdline.toBool("systemevent"))
00283 eventString = "SYSTEM_EVENT " +
00284 cmdline.toString("systemevent") +
00285 QString(" SENDER %1").arg(gCoreContext->GetHostName());
00286
00287 if (!eventString.isEmpty())
00288 {
00289 if (gCoreContext->ConnectToMasterServer())
00290 {
00291 gCoreContext->SendMessage(eventString);
00292 return GENERIC_EXIT_OK;
00293 }
00294 return GENERIC_EXIT_NO_MYTHCONTEXT;
00295 }
00296
00297 if (cmdline.toBool("setverbose"))
00298 {
00299 if (gCoreContext->ConnectToMasterServer())
00300 {
00301 QString message = "SET_VERBOSE ";
00302 message += cmdline.toString("setverbose");
00303
00304 gCoreContext->SendMessage(message);
00305 LOG(VB_GENERAL, LOG_INFO,
00306 QString("Sent '%1' message").arg(message));
00307 return GENERIC_EXIT_OK;
00308 }
00309 else
00310 {
00311 LOG(VB_GENERAL, LOG_ERR,
00312 "Unable to connect to backend, verbose mask unchanged ");
00313 return GENERIC_EXIT_CONNECT_ERROR;
00314 }
00315 }
00316
00317 if (cmdline.toBool("setloglevel"))
00318 {
00319 if (gCoreContext->ConnectToMasterServer())
00320 {
00321 QString message = "SET_LOG_LEVEL ";
00322 message += cmdline.toString("setloglevel");
00323
00324 gCoreContext->SendMessage(message);
00325 LOG(VB_GENERAL, LOG_INFO,
00326 QString("Sent '%1' message").arg(message));
00327 return GENERIC_EXIT_OK;
00328 }
00329 else
00330 {
00331 LOG(VB_GENERAL, LOG_ERR,
00332 "Unable to connect to backend, log level unchanged ");
00333 return GENERIC_EXIT_CONNECT_ERROR;
00334 }
00335 }
00336
00337 if (cmdline.toBool("clearcache"))
00338 {
00339 if (gCoreContext->ConnectToMasterServer())
00340 {
00341 gCoreContext->SendMessage("CLEAR_SETTINGS_CACHE");
00342 LOG(VB_GENERAL, LOG_INFO, "Sent CLEAR_SETTINGS_CACHE message");
00343 return GENERIC_EXIT_OK;
00344 }
00345 else
00346 {
00347 LOG(VB_GENERAL, LOG_ERR, "Unable to connect to backend, settings "
00348 "cache will not be cleared.");
00349 return GENERIC_EXIT_CONNECT_ERROR;
00350 }
00351 }
00352
00353 if (cmdline.toBool("printsched") ||
00354 cmdline.toBool("testsched"))
00355 {
00356 Scheduler *sched = new Scheduler(false, &tvList);
00357 if (!cmdline.toBool("testsched") &&
00358 gCoreContext->ConnectToMasterServer())
00359 {
00360 cout << "Retrieving Schedule from Master backend.\n";
00361 sched->FillRecordListFromMaster();
00362 }
00363 else
00364 {
00365 cout << "Calculating Schedule from database.\n" <<
00366 "Inputs, Card IDs, and Conflict info may be invalid "
00367 "if you have multiple tuners.\n";
00368 ProgramInfo::CheckProgramIDAuthorities();
00369 sched->FillRecordListFromDB();
00370 }
00371
00372 verboseMask |= VB_SCHEDULE;
00373 LogLevel_t oldLogLevel = logLevel;
00374 logLevel = LOG_DEBUG;
00375 sched->PrintList(true);
00376 logLevel = oldLogLevel;
00377 delete sched;
00378 return GENERIC_EXIT_OK;
00379 }
00380
00381 if (cmdline.toBool("resched"))
00382 {
00383 bool ok = false;
00384 if (gCoreContext->ConnectToMasterServer())
00385 {
00386 LOG(VB_GENERAL, LOG_INFO, "Connected to master for reschedule");
00387 ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
00388 "MythBackendCommand");
00389 ok = true;
00390 }
00391 else
00392 LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for reschedule");
00393
00394 return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
00395 }
00396
00397 if (cmdline.toBool("scanvideos"))
00398 {
00399 bool ok = false;
00400 if (gCoreContext->ConnectToMasterServer())
00401 {
00402 gCoreContext->SendReceiveStringList(QStringList() << "SCAN_VIDEOS");
00403 LOG(VB_GENERAL, LOG_INFO, "Requested video scan");
00404 ok = true;
00405 }
00406 else
00407 LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for video scan");
00408
00409 return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
00410 }
00411
00412 if (!cmdline.toBool("printexpire"))
00413 {
00414 expirer = new AutoExpire();
00415 expirer->PrintExpireList(cmdline.toString("printexpire"));
00416 return GENERIC_EXIT_OK;
00417 }
00418
00419
00420 return GENERIC_EXIT_OK;
00421 }
00422
00423 int connect_to_master(void)
00424 {
00425 MythSocket *tempMonitorConnection = new MythSocket();
00426 if (tempMonitorConnection->connect(
00427 gCoreContext->GetSetting("MasterServerIP", "127.0.0.1"),
00428 gCoreContext->GetNumSetting("MasterServerPort", 6543)))
00429 {
00430 if (!gCoreContext->CheckProtoVersion(tempMonitorConnection))
00431 {
00432 LOG(VB_GENERAL, LOG_ERR, "Master backend is incompatible with "
00433 "this backend.\nCannot become a slave.");
00434 return GENERIC_EXIT_CONNECT_ERROR;
00435 }
00436
00437 QStringList tempMonitorDone("DONE");
00438
00439 QStringList tempMonitorAnnounce("ANN Monitor tzcheck 0");
00440 tempMonitorConnection->writeStringList(tempMonitorAnnounce);
00441 tempMonitorConnection->readStringList(tempMonitorAnnounce);
00442 if (tempMonitorAnnounce.empty() ||
00443 tempMonitorAnnounce[0] == "ERROR")
00444 {
00445 tempMonitorConnection->DownRef();
00446 tempMonitorConnection = NULL;
00447 if (tempMonitorAnnounce.empty())
00448 {
00449 LOG(VB_GENERAL, LOG_ERR, LOC +
00450 "Failed to open event socket, timeout");
00451 }
00452 else
00453 {
00454 LOG(VB_GENERAL, LOG_ERR, LOC +
00455 "Failed to open event socket" +
00456 ((tempMonitorAnnounce.size() >= 2) ?
00457 QString(", error was %1").arg(tempMonitorAnnounce[1]) :
00458 QString(", remote error")));
00459 }
00460 }
00461
00462 QStringList tzCheck("QUERY_TIME_ZONE");
00463 if (tempMonitorConnection)
00464 {
00465 tempMonitorConnection->writeStringList(tzCheck);
00466 tempMonitorConnection->readStringList(tzCheck);
00467 }
00468 if (tzCheck.size() && !checkTimeZone(tzCheck))
00469 {
00470
00471
00472 LOG(VB_GENERAL, LOG_ERR, "The time and/or time zone settings on "
00473 "this system do not match those in use on the master "
00474 "backend. Please ensure all frontend and backend "
00475 "systems are configured to use the same time zone and "
00476 "have the current time properly set.");
00477 LOG(VB_GENERAL, LOG_ERR,
00478 "Unable to run with invalid time settings. Exiting.");
00479 tempMonitorConnection->writeStringList(tempMonitorDone);
00480 tempMonitorConnection->DownRef();
00481 return GENERIC_EXIT_INVALID_TIMEZONE;
00482 }
00483 else
00484 {
00485 LOG(VB_GENERAL, LOG_INFO,
00486 QString("Backend is running in %1 time zone.")
00487 .arg(getTimeZoneID()));
00488 }
00489 if (tempMonitorConnection)
00490 tempMonitorConnection->writeStringList(tempMonitorDone);
00491 }
00492 if (tempMonitorConnection)
00493 tempMonitorConnection->DownRef();
00494
00495 return GENERIC_EXIT_OK;
00496 }
00497
00498
00499 void print_warnings(const MythBackendCommandLineParser &cmdline)
00500 {
00501 if (cmdline.toBool("nohousekeeper"))
00502 {
00503 LOG(VB_GENERAL, LOG_WARNING, LOC +
00504 "****** The Housekeeper has been DISABLED with "
00505 "the --nohousekeeper option ******");
00506 }
00507 if (cmdline.toBool("nosched"))
00508 {
00509 LOG(VB_GENERAL, LOG_WARNING, LOC +
00510 "********** The Scheduler has been DISABLED with "
00511 "the --nosched option **********");
00512 }
00513 if (cmdline.toBool("noautoexpire"))
00514 {
00515 LOG(VB_GENERAL, LOG_WARNING, LOC +
00516 "********* Auto-Expire has been DISABLED with "
00517 "the --noautoexpire option ********");
00518 }
00519 if (cmdline.toBool("nojobqueue"))
00520 {
00521 LOG(VB_GENERAL, LOG_WARNING, LOC +
00522 "********* The JobQueue has been DISABLED with "
00523 "the --nojobqueue option *********");
00524 }
00525 }
00526
00527 int run_backend(MythBackendCommandLineParser &cmdline)
00528 {
00529 bool ismaster = gCoreContext->IsMasterHost();
00530
00531 if (!UpgradeTVDatabaseSchema(ismaster, ismaster))
00532 {
00533 LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema");
00534 return GENERIC_EXIT_DB_OUTOFDATE;
00535 }
00536
00537 MythTranslation::load("mythfrontend");
00538
00539 if (!ismaster)
00540 {
00541 int ret = connect_to_master();
00542 if (ret != GENERIC_EXIT_OK)
00543 return ret;
00544 }
00545
00546 int port = gCoreContext->GetNumSetting("BackendServerPort", 6543);
00547 if (gCoreContext->GetSetting("BackendServerIP").isEmpty() &&
00548 gCoreContext->GetSetting("BackendServerIP6").isEmpty())
00549 {
00550 cerr << "No setting found for this machine's BackendServerIP.\n"
00551 << "Please run setup on this machine and modify the first page\n"
00552 << "of the general settings.\n";
00553 return GENERIC_EXIT_SETUP_ERROR;
00554 }
00555
00556 MythSystemEventHandler *sysEventHandler = new MythSystemEventHandler();
00557
00558 if (ismaster)
00559 {
00560 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Starting up as the master server.");
00561 }
00562 else
00563 {
00564 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Running as a slave backend.");
00565 }
00566
00567 print_warnings(cmdline);
00568
00569 bool fatal_error = false;
00570 bool runsched = setupTVs(ismaster, fatal_error);
00571 if (fatal_error)
00572 {
00573 delete sysEventHandler;
00574 return GENERIC_EXIT_SETUP_ERROR;
00575 }
00576
00577 Scheduler *sched = NULL;
00578 if (ismaster)
00579 {
00580 if (runsched)
00581 {
00582 sched = new Scheduler(true, &tvList);
00583 int err = sched->GetError();
00584 if (err)
00585 return err;
00586
00587 if (cmdline.toBool("nosched"))
00588 sched->DisableScheduling();
00589 }
00590
00591 if (!cmdline.toBool("nohousekeeper"))
00592 housekeeping = new HouseKeeper(true, ismaster, sched);
00593
00594 if (!cmdline.toBool("noautoexpire"))
00595 {
00596 expirer = new AutoExpire(&tvList);
00597 if (sched)
00598 sched->SetExpirer(expirer);
00599 }
00600 gCoreContext->SetScheduler(sched);
00601 }
00602 else if (!cmdline.toBool("nohousekeeper"))
00603 {
00604 housekeeping = new HouseKeeper(true, ismaster, NULL);
00605 }
00606
00607 if (!cmdline.toBool("nojobqueue"))
00608 jobqueue = new JobQueue(ismaster);
00609
00610
00611
00612
00613
00614 if (g_pUPnp == NULL)
00615 {
00616 g_pUPnp = new MediaServer();
00617
00618 g_pUPnp->Init(ismaster, cmdline.toBool("noupnp"));
00619 }
00620
00621
00622
00623
00624
00625 HttpStatus *httpStatus = NULL;
00626 HttpServer *pHS = g_pUPnp->GetHttpServer();
00627
00628 if (pHS)
00629 {
00630 LOG(VB_GENERAL, LOG_INFO, "Main::Registering HttpStatus Extension");
00631
00632 httpStatus = new HttpStatus( &tvList, sched, expirer, ismaster );
00633 pHS->RegisterExtension( httpStatus );
00634 }
00635
00636 mainServer = new MainServer(
00637 ismaster, port, &tvList, sched, expirer);
00638
00639 int exitCode = mainServer->GetExitCode();
00640 if (exitCode != GENERIC_EXIT_OK)
00641 {
00642 LOG(VB_GENERAL, LOG_CRIT,
00643 "Backend exiting, MainServer initialization error.");
00644 delete mainServer;
00645 return exitCode;
00646 }
00647
00648 if (httpStatus && mainServer)
00649 httpStatus->SetMainServer(mainServer);
00650
00651 StorageGroup::CheckAllStorageGroupDirs();
00652
00653 if (gCoreContext->IsMasterBackend())
00654 gCoreContext->SendSystemEvent("MASTER_STARTED");
00655
00658 exitCode = qApp->exec();
00661
00662 if (gCoreContext->IsMasterBackend())
00663 {
00664 gCoreContext->SendSystemEvent("MASTER_SHUTDOWN");
00665 qApp->processEvents();
00666 }
00667
00668 LOG(VB_GENERAL, LOG_NOTICE, "MythBackend exiting");
00669
00670 delete sysEventHandler;
00671
00672 return exitCode;
00673 }