|
MythTV
0.26-pre
|
00001 // POSIX headers 00002 #include <sys/time.h> // for setpriority 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> // O_SYNC 00013 #endif 00014 00015 // C headers 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 // Hack to make sure recorded.basename gets set if the user 00072 // downgrades to a prior version and creates new entries 00073 // without it. 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 // Hack to make sure record.station gets set if the user 00081 // downgrades to a prior version and creates new entries 00082 // without it. 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 // The master assumes card comes up so we need to 00178 // set error and exit if a non-master card fails. 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 // This should never actually be reached.. 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 // Check for different time zones, different offsets, different 00471 // times 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 // Setup status server 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 }
1.7.6.1