00001
00002 #include <sys/time.h>
00003
00004 #include <qapplication.h>
00005 #include <qsqldatabase.h>
00006 #include <qfile.h>
00007 #include <qmap.h>
00008 #include <qregexp.h>
00009 #include <unistd.h>
00010 #include <sys/types.h>
00011 #include <sys/stat.h>
00012 #include <fcntl.h>
00013 #include <libgen.h>
00014 #include <signal.h>
00015 #include <cerrno>
00016
00017 #include "mythconfig.h"
00018 #ifdef CONFIG_DARWIN
00019 #include <sys/aio.h>
00020 #endif
00021
00022 #include <iostream>
00023 #include <fstream>
00024 #include <cstdlib>
00025 using namespace std;
00026
00027 #include "tv_rec.h"
00028 #include "autoexpire.h"
00029 #include "scheduler.h"
00030 #include "mainserver.h"
00031 #include "encoderlink.h"
00032 #include "remoteutil.h"
00033 #include "housekeeper.h"
00034
00035 #include "libmyth/mythcontext.h"
00036 #include "libmyth/mythdbcon.h"
00037 #include "libmyth/exitcodes.h"
00038 #include "libmyth/compat.h"
00039 #include "libmyth/storagegroup.h"
00040 #include "libmythtv/programinfo.h"
00041 #include "libmythtv/dbcheck.h"
00042 #include "libmythtv/jobqueue.h"
00043 #include "libmythtv/previewgenerator.h"
00044
00045 #include "mediaserver.h"
00046 #include "httpstatus.h"
00047
00048 QMap<int, EncoderLink *> tvList;
00049 AutoExpire *expirer = NULL;
00050 Scheduler *sched = NULL;
00051 JobQueue *jobqueue = NULL;
00052 QString pidfile;
00053 HouseKeeper *housekeeping = NULL;
00054 QString logfile = "";
00055
00056 MediaServer *g_pUPnp = NULL;
00057
00058 bool setupTVs(bool ismaster, bool &error)
00059 {
00060 error = false;
00061 QString localhostname = gContext->GetHostName();
00062
00063 MSqlQuery query(MSqlQuery::InitCon());
00064
00065 if (ismaster)
00066 {
00067
00068
00069
00070 if (!query.exec("UPDATE recorded SET basename = CONCAT(chanid, '_', "
00071 "DATE_FORMAT(starttime, '%Y%m%d%H%i00'), '_', "
00072 "DATE_FORMAT(endtime, '%Y%m%d%H%i00'), '.nuv') "
00073 "WHERE basename = '';"))
00074 MythContext::DBError("Updating record basename",
00075 query.lastQuery());
00076
00077
00078
00079
00080 if (!query.exec("UPDATE channel SET callsign=chanid "
00081 "WHERE callsign IS NULL OR callsign='';"))
00082 MythContext::DBError("Updating channel callsign",
00083 query.lastQuery());
00084
00085 if (query.exec("SELECT MIN(chanid) FROM channel;"))
00086 {
00087 query.first();
00088 int min_chanid = query.value(0).toInt();
00089 if (!query.exec(QString("UPDATE record SET chanid = %1 "
00090 "WHERE chanid IS NULL;").arg(min_chanid)))
00091 MythContext::DBError("Updating record chanid",
00092 query.lastQuery());
00093 }
00094 else
00095 MythContext::DBError("Querying minimum chanid",
00096 query.lastQuery());
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 records_without_station.exec();
00103 if (records_without_station.first())
00104 {
00105 MSqlQuery update_record(MSqlQuery::InitCon());
00106 update_record.prepare("UPDATE record SET station = :CALLSIGN"
00107 " WHERE chanid = :CHANID;");
00108 do
00109 {
00110 update_record.bindValue(":CALLSIGN",
00111 records_without_station.value(1));
00112 update_record.bindValue(":CHANID",
00113 records_without_station.value(0));
00114 if (!update_record.exec())
00115 {
00116 MythContext::DBError("Updating record station",
00117 update_record.lastQuery());
00118 }
00119 } while (records_without_station.next());
00120 }
00121 }
00122
00123 if (!query.exec(
00124 "SELECT cardid, hostname "
00125 "FROM capturecard "
00126 "ORDER BY cardid"))
00127 {
00128 MythContext::DBError("Querying Recorders", query);
00129 return false;
00130 }
00131
00132 vector<uint> cardids;
00133 vector<QString> hosts;
00134 while (query.next())
00135 {
00136 uint cardid = query.value(0).toUInt();
00137 QString host = query.value(1).toString();
00138 QString cidmsg = QString("Card %1").arg(cardid);
00139
00140 if (host.isEmpty())
00141 {
00142 QString msg = cidmsg + " does not have a hostname defined.\n"
00143 "Please run setup and confirm all of the capture cards.\n";
00144
00145 VERBOSE(VB_IMPORTANT, msg);
00146 gContext->LogEntry("mythbackend", LP_CRITICAL,
00147 "Problem with capture cards", msg);
00148 continue;
00149 }
00150
00151 cardids.push_back(cardid);
00152 hosts.push_back(host);
00153 }
00154
00155 for (uint i = 0; i < cardids.size(); i++)
00156 {
00157 if (hosts[i] == localhostname)
00158 new TVRec(cardids[i]);
00159 }
00160
00161 for (uint i = 0; i < cardids.size(); i++)
00162 {
00163 uint cardid = cardids[i];
00164 QString host = hosts[i];
00165 QString cidmsg = QString("Card %1").arg(cardid);
00166
00167 if (!ismaster)
00168 {
00169 if (host == localhostname)
00170 {
00171 TVRec *tv = TVRec::GetTVRec(cardid);
00172 if (tv->Init())
00173 {
00174 EncoderLink *enc = new EncoderLink(cardid, tv);
00175 tvList[cardid] = enc;
00176 }
00177 else
00178 {
00179 gContext->LogEntry("mythbackend", LP_CRITICAL,
00180 "Problem with capture cards",
00181 cidmsg + " failed init");
00182 delete tv;
00183
00184
00185 error = true;
00186 }
00187 }
00188 }
00189 else
00190 {
00191 if (host == localhostname)
00192 {
00193 TVRec *tv = TVRec::GetTVRec(cardid);
00194 if (tv->Init())
00195 {
00196 EncoderLink *enc = new EncoderLink(cardid, tv);
00197 tvList[cardid] = enc;
00198 }
00199 else
00200 {
00201 gContext->LogEntry("mythbackend", LP_CRITICAL,
00202 "Problem with capture cards",
00203 cidmsg + "failed init");
00204 delete tv;
00205 }
00206 }
00207 else
00208 {
00209 EncoderLink *enc = new EncoderLink(cardid, NULL, host);
00210 tvList[cardid] = enc;
00211 }
00212 }
00213 }
00214
00215 if (tvList.empty())
00216 {
00217 cerr << "ERROR: no valid capture cards are defined in the database.\n";
00218 cerr << "Perhaps you should read the installation instructions?\n";
00219 gContext->LogEntry("mythbackend", LP_CRITICAL,
00220 "No capture cards are defined",
00221 "Please run the setup program.");
00222 return false;
00223 }
00224
00225 return true;
00226 }
00227
00228 void cleanup(void)
00229 {
00230 delete gContext;
00231
00232 if (sched)
00233 delete sched;
00234
00235 if (g_pUPnp)
00236 delete g_pUPnp;
00237
00238 if (pidfile != "")
00239 unlink(pidfile.ascii());
00240
00241 signal(SIGHUP, SIG_DFL);
00242 signal(SIGUSR1, SIG_DFL);
00243 }
00244
00245 int log_rotate(int report_error)
00246 {
00247
00248
00249 int new_logfd = open(logfile, O_WRONLY|O_CREAT|O_APPEND|O_SYNC, 0664);
00250 if (new_logfd < 0)
00251 {
00252
00253 if (report_error)
00254 {
00255 cerr << "cannot open logfile " << logfile << endl;
00256 return -1;
00257 }
00258 new_logfd = open("/dev/null", O_WRONLY);
00259 if (new_logfd < 0)
00260 {
00261
00262 return -1;
00263 }
00264 }
00265 while (dup2(new_logfd, 1) < 0 && errno == EINTR) ;
00266 while (dup2(new_logfd, 2) < 0 && errno == EINTR) ;
00267 while (close(new_logfd) < 0 && errno == EINTR) ;
00268 return 0;
00269 }
00270
00271 void log_rotate_handler(int)
00272 {
00273 log_rotate(0);
00274 }
00275
00276 void upnp_rebuild(int)
00277 {
00278 if (gContext->IsMasterHost())
00279 {
00280 g_pUPnp->RebuildMediaMap();
00281 }
00282
00283 }
00284
00285 int preview_helper(const QString &chanid, const QString &starttime,
00286 long long previewFrameNumber, long long previewSeconds,
00287 const QSize &previewSize,
00288 const QString &infile, const QString &outfile)
00289 {
00290
00291 if (setpriority(PRIO_PROCESS, 0, 9))
00292 VERBOSE(VB_GENERAL, "Setting priority failed." + ENO);
00293
00294 ProgramInfo *pginfo = NULL;
00295 if (!chanid.isEmpty() && !starttime.isEmpty())
00296 {
00297 pginfo = ProgramInfo::GetProgramFromRecorded(chanid, starttime);
00298 if (!pginfo)
00299 {
00300 VERBOSE(VB_IMPORTANT, QString(
00301 "Can not locate recording made on '%1' at '%2'")
00302 .arg(chanid).arg(starttime));
00303 return GENERIC_EXIT_NOT_OK;
00304 }
00305 }
00306 else if (!infile.isEmpty())
00307 {
00308 pginfo = ProgramInfo::GetProgramFromBasename(infile);
00309 if (!pginfo)
00310 {
00311 VERBOSE(VB_IMPORTANT, QString(
00312 "Can not locate recording '%1'").arg(infile));
00313 return GENERIC_EXIT_NOT_OK;
00314 }
00315 }
00316 else
00317 {
00318 VERBOSE(VB_IMPORTANT, "Can not locate recording for preview");
00319 return GENERIC_EXIT_NOT_OK;
00320 }
00321
00322 PreviewGenerator *previewgen = new PreviewGenerator(pginfo, true);
00323
00324 if (previewFrameNumber >= 0)
00325 previewgen->SetPreviewTimeAsFrameNumber(previewFrameNumber);
00326
00327 if (previewSeconds >= 0)
00328 previewgen->SetPreviewTimeAsSeconds(previewSeconds);
00329
00330 previewgen->SetOutputSize(previewSize);
00331 previewgen->SetOutputFilename(outfile);
00332 previewgen->RunReal();
00333 previewgen->deleteLater();
00334
00335 delete pginfo;
00336
00337 return GENERIC_EXIT_OK;
00338 }
00339
00340
00341 bool parse_preview_info(const QString ¶m,
00342 long long &previewFrameNumber,
00343 long long &previewSeconds,
00344 QSize &previewSize)
00345 {
00346 previewFrameNumber = -1;
00347 previewSeconds = -1;
00348 previewSize = QSize(0,0);
00349 if (param.isEmpty())
00350 return true;
00351
00352 int xat = param.find("x", 0, false);
00353 int aat = param.find("@", 0, false);
00354 if (xat > 0)
00355 {
00356 QString widthStr = param.left(xat);
00357 QString heightStr = QString::null;
00358 if (aat > xat)
00359 heightStr = param.mid(xat + 1, aat - xat - 1);
00360 else
00361 heightStr = param.mid(xat + 1);
00362
00363 bool ok1, ok2;
00364 previewSize = QSize(widthStr.toInt(&ok1), heightStr.toInt(&ok2));
00365 if (!ok1 || !ok2)
00366 {
00367 VERBOSE(VB_IMPORTANT, QString(
00368 "Error: Failed to parse --generate-preview "
00369 "param '%1'").arg(param));
00370 }
00371 }
00372 if ((xat > 0) && (aat < xat))
00373 return true;
00374
00375 QString lastChar = param.at(param.length() - 1).lower();
00376 QString frameNumStr = QString::null;
00377 QString secsStr = QString::null;
00378 if (lastChar == "f")
00379 frameNumStr = param.mid(aat + 1, param.length() - aat - 2);
00380 else if (lastChar == "s")
00381 secsStr = param.mid(aat + 1, param.length() - aat - 2);
00382 else
00383 secsStr = param.mid(aat + 1, param.length() - aat - 1);
00384
00385 bool ok = false;
00386 if (!frameNumStr.isEmpty())
00387 previewFrameNumber = frameNumStr.toUInt(&ok);
00388 else if (!secsStr.isEmpty())
00389 previewSeconds = secsStr.toUInt(&ok);
00390
00391 if (!ok)
00392 {
00393 VERBOSE(VB_IMPORTANT, QString(
00394 "Error: Failed to parse --generate-preview "
00395 "param '%1'").arg(param));
00396 }
00397
00398 return ok;
00399 }
00400
00401 int main(int argc, char **argv)
00402 {
00403 bool need_gui = false;
00404 #ifndef _WIN32
00405 for (int i = 3; i < sysconf(_SC_OPEN_MAX) - 1; ++i)
00406 close(i);
00407 #else
00408
00409
00410 need_gui = true;
00411 #endif
00412
00413 QApplication a(argc, argv, need_gui);
00414
00415 QMap<QString, QString> settingsOverride;
00416 QString binname = basename(a.argv()[0]);
00417
00418 long long previewFrameNumber = -2;
00419 long long previewSeconds = -2;
00420 QSize previewSize(0,0);
00421 QString chanid = QString::null;
00422 QString starttime = QString::null;
00423 QString infile = QString::null;
00424 QString outfile = QString::null;
00425
00426 bool daemonize = false;
00427 bool printsched = false;
00428 bool testsched = false;
00429 bool resched = false;
00430 bool nosched = false;
00431 bool noupnp = false;
00432 bool nojobqueue = false;
00433 bool nohousekeeper = false;
00434 bool noexpirer = false;
00435 QString printexpire = "";
00436 bool clearsettingscache = false;
00437 bool wantupnprebuild = false;
00438
00439 for (int argpos = 1; argpos < a.argc(); ++argpos)
00440 {
00441 if (!strcmp(a.argv()[argpos],"-l") ||
00442 !strcmp(a.argv()[argpos],"--logfile"))
00443 {
00444 if (a.argc() > argpos)
00445 {
00446 logfile = a.argv()[argpos+1];
00447 if (logfile.startsWith("-"))
00448 {
00449 cerr << "Invalid or missing argument to -l/--logfile option\n";
00450 return BACKEND_EXIT_INVALID_CMDLINE;
00451 }
00452 else
00453 {
00454 ++argpos;
00455 }
00456 }
00457 }
00458 else if (!strcmp(a.argv()[argpos],"-p") ||
00459 !strcmp(a.argv()[argpos],"--pidfile"))
00460 {
00461 if (a.argc() > argpos)
00462 {
00463 pidfile = a.argv()[argpos+1];
00464 if (pidfile.startsWith("-"))
00465 {
00466 cerr << "Invalid or missing argument to -p/--pidfile option\n";
00467 return BACKEND_EXIT_INVALID_CMDLINE;
00468 }
00469 else
00470 {
00471 ++argpos;
00472 }
00473 }
00474 }
00475 else if (!strcmp(a.argv()[argpos],"-d") ||
00476 !strcmp(a.argv()[argpos],"--daemon"))
00477 {
00478 daemonize = true;
00479
00480 }
00481 else if (!strcmp(a.argv()[argpos],"-O") ||
00482 !strcmp(a.argv()[argpos],"--override-setting"))
00483 {
00484 if (a.argc()-1 > argpos)
00485 {
00486 QString tmpArg = a.argv()[argpos+1];
00487 if (tmpArg.startsWith("-"))
00488 {
00489 cerr << "Invalid or missing argument to -O/--override-setting option\n";
00490 return BACKEND_EXIT_INVALID_CMDLINE;
00491 }
00492
00493 QStringList pairs = QStringList::split(",", tmpArg);
00494 for (unsigned int index = 0; index < pairs.size(); ++index)
00495 {
00496 QStringList tokens = QStringList::split("=", pairs[index]);
00497 tokens[0].replace(QRegExp("^[\"']"), "");
00498 tokens[0].replace(QRegExp("[\"']$"), "");
00499 tokens[1].replace(QRegExp("^[\"']"), "");
00500 tokens[1].replace(QRegExp("[\"']$"), "");
00501 settingsOverride[tokens[0]] = tokens[1];
00502 }
00503 }
00504 else
00505 {
00506 cerr << "Invalid or missing argument to -O/--override-setting option\n";
00507 return BACKEND_EXIT_INVALID_CMDLINE;
00508 }
00509
00510 ++argpos;
00511 }
00512 else if (!strcmp(a.argv()[argpos],"-v") ||
00513 !strcmp(a.argv()[argpos],"--verbose"))
00514 {
00515 if (a.argc()-1 > argpos)
00516 {
00517 if (parse_verbose_arg(a.argv()[argpos+1]) ==
00518 GENERIC_EXIT_INVALID_CMDLINE)
00519 return BACKEND_EXIT_INVALID_CMDLINE;
00520
00521 ++argpos;
00522 }
00523 else
00524 {
00525 cerr << "Missing argument to -v/--verbose option\n";
00526 return BACKEND_EXIT_INVALID_CMDLINE;
00527 }
00528 }
00529 else if (!strcmp(a.argv()[argpos],"--printsched"))
00530 {
00531 printsched = true;
00532 }
00533 else if (!strcmp(a.argv()[argpos],"--testsched"))
00534 {
00535 testsched = true;
00536 }
00537 else if (!strcmp(a.argv()[argpos],"--resched"))
00538 {
00539 resched = true;
00540 }
00541 else if (!strcmp(a.argv()[argpos],"--nosched"))
00542 {
00543 nosched = true;
00544 }
00545 else if (!strcmp(a.argv()[argpos],"--noupnp"))
00546 {
00547 noupnp = true;
00548 }
00549 else if (!strcmp(a.argv()[argpos],"--upnprebuild"))
00550 {
00551 wantupnprebuild = true;
00552 }
00553 else if (!strcmp(a.argv()[argpos],"--nojobqueue"))
00554 {
00555 nojobqueue = true;
00556 }
00557 else if (!strcmp(a.argv()[argpos],"--nohousekeeper"))
00558 {
00559 nohousekeeper = true;
00560 }
00561 else if (!strcmp(a.argv()[argpos],"--noautoexpire"))
00562 {
00563 noexpirer = true;
00564 }
00565 else if (!strcmp(a.argv()[argpos],"--printexpire"))
00566 {
00567 printexpire = "ALL";
00568 if ((a.argc()-1 > argpos) && a.argv()[argpos+1][0] != '-')
00569 {
00570 printexpire = a.argv()[argpos+1];
00571 ++argpos;
00572 }
00573 }
00574 else if (!strcmp(a.argv()[argpos],"--clearcache"))
00575 {
00576 clearsettingscache = true;
00577 }
00578 else if (!strcmp(a.argv()[argpos],"--version"))
00579 {
00580 extern const char *myth_source_version;
00581 extern const char *myth_source_path;
00582 cout << "Please include all output in bug reports." << endl;
00583 cout << "MythTV Version : " << myth_source_version << endl;
00584 cout << "MythTV Branch : " << myth_source_path << endl;
00585 cout << "Network Protocol : " << MYTH_PROTO_VERSION << endl;
00586 cout << "Library API : " << MYTH_BINARY_VERSION << endl;
00587 #ifdef MYTH_BUILD_CONFIG
00588 cout << "Options compiled in:" <<endl;
00589 cout << MYTH_BUILD_CONFIG << endl;
00590 #endif
00591 return BACKEND_EXIT_OK;
00592 }
00593 else if (!strcmp(a.argv()[argpos],"--generate-preview"))
00594 {
00595 QString tmp = QString::null;
00596 if ((argpos + 1) < a.argc())
00597 {
00598 tmp = a.argv()[argpos+1];
00599 bool ok = true;
00600 if (tmp.left(1) == "-")
00601 tmp.left(2).toInt(&ok);
00602 if (ok)
00603 argpos++;
00604 else
00605 tmp = QString::null;
00606 }
00607
00608 if (!parse_preview_info(tmp, previewFrameNumber, previewSeconds,
00609 previewSize))
00610 {
00611 VERBOSE(VB_IMPORTANT,
00612 QString("Unable to parse --generate-preview "
00613 "option '%1'").arg(tmp));
00614
00615 return BACKEND_EXIT_INVALID_CMDLINE;
00616 }
00617 }
00618 else if (!strcmp(a.argv()[argpos],"-c") ||
00619 !strcmp(a.argv()[argpos],"--chanid"))
00620 {
00621 if (((argpos + 1) >= a.argc()) ||
00622 !strncmp(a.argv()[argpos + 1], "-", 1))
00623 {
00624 VERBOSE(VB_IMPORTANT,
00625 "Missing or invalid parameters for --chanid option");
00626
00627 return BACKEND_EXIT_INVALID_CMDLINE;
00628 }
00629
00630 chanid = a.argv()[++argpos];
00631 }
00632 else if (!strcmp(a.argv()[argpos],"-s") ||
00633 !strcmp(a.argv()[argpos],"--starttime"))
00634 {
00635 if (((argpos + 1) >= a.argc()) ||
00636 !strncmp(a.argv()[argpos + 1], "-", 1))
00637 {
00638 VERBOSE(VB_IMPORTANT,
00639 "Missing or invalid parameters for --starttime option");
00640 return BACKEND_EXIT_INVALID_CMDLINE;
00641 }
00642
00643 starttime = a.argv()[++argpos];
00644 }
00645 else if (!strcmp(a.argv()[argpos],"--infile"))
00646 {
00647 if (((argpos + 1) >= a.argc()) ||
00648 !strncmp(a.argv()[argpos + 1], "-", 1))
00649 {
00650 VERBOSE(VB_IMPORTANT,
00651 "Missing or invalid parameters for --infile option");
00652
00653 return BACKEND_EXIT_INVALID_CMDLINE;
00654 }
00655
00656 infile = a.argv()[++argpos];
00657 }
00658 else if (!strcmp(a.argv()[argpos],"--outfile"))
00659 {
00660 if (((argpos + 1) >= a.argc()) ||
00661 !strncmp(a.argv()[argpos + 1], "-", 1))
00662 {
00663 VERBOSE(VB_IMPORTANT,
00664 "Missing or invalid parameters for --outfile option");
00665
00666 return BACKEND_EXIT_INVALID_CMDLINE;
00667 }
00668
00669 outfile = a.argv()[++argpos];
00670 }
00671 else
00672 {
00673 if (!(!strcmp(a.argv()[argpos],"-h") ||
00674 !strcmp(a.argv()[argpos],"--help")))
00675 cerr << "Invalid argument: " << a.argv()[argpos] << endl;
00676 cerr << "Valid options are: " << endl <<
00677 "-h or --help List valid command line parameters" << endl <<
00678 "-l or --logfile filename Writes STDERR and STDOUT messages to filename" << endl <<
00679 "-p or --pidfile filename Write PID of mythbackend " <<
00680 "to filename" << endl <<
00681 "-d or --daemon Runs mythbackend as a daemon" << endl <<
00682 "-v or --verbose debug-level Use '-v help' for level info" << endl <<
00683 "--printexpire List of auto-expire programs" << endl <<
00684 "--printsched Upcoming scheduled programs" << endl <<
00685 "--testsched Test run scheduler (ignore existing schedule)" << endl <<
00686 "--resched Force the scheduler to update" << endl <<
00687 "--nosched Do not perform any scheduling" << endl <<
00688 "--noupnp Do not enable the UPNP server" << endl <<
00689 "--nojobqueue Do not start the JobQueue" << endl <<
00690 "--nohousekeeper Do not start the Housekeeper" << endl <<
00691 "--noautoexpire Do not start the AutoExpire thread" << endl <<
00692 "--clearcache Clear the settings cache on all myth servers" << endl <<
00693 "--version Version information" << endl <<
00694 "--generate-preview Generate a preview image" << endl <<
00695 "--upnprebuild Force an update of UPNP media" << endl <<
00696 "--infile Input file for preview generation" << endl <<
00697 "--outfile Optional output file for preview generation" << endl <<
00698 "--chanid Channel ID for preview generation" << endl <<
00699 "--starttime Recording start time for preview generation" << endl;
00700 return BACKEND_EXIT_INVALID_CMDLINE;
00701 }
00702 }
00703
00704 if (((previewFrameNumber >= -1) || previewSeconds >= -1) &&
00705 (chanid.isEmpty() || starttime.isEmpty()) && infile.isEmpty())
00706 {
00707 cerr << "--generate-preview must be accompanied by either " <<endl
00708 << "\nboth --chanid and --starttime paramaters, " << endl
00709 << "\nor the --infile paramater." << endl;
00710 return BACKEND_EXIT_INVALID_CMDLINE;
00711 }
00712
00713 if (logfile != "" )
00714 {
00715 if (log_rotate(1) < 0)
00716 cerr << "cannot open logfile; using stdout/stderr" << endl;
00717 else
00718 signal(SIGHUP, &log_rotate_handler);
00719 }
00720
00721 ofstream pidfs;
00722 if (pidfile != "")
00723 {
00724 pidfs.open(pidfile.ascii());
00725 if (!pidfs)
00726 {
00727 perror(pidfile.ascii());
00728 cerr << "Error opening pidfile";
00729 return BACKEND_EXIT_OPENING_PIDFILE_ERROR;
00730 }
00731 }
00732
00733 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
00734 cerr << "Unable to ignore SIGPIPE\n";
00735
00736 if (daemonize)
00737 if (daemon(0, 1) < 0)
00738 {
00739 perror("daemon");
00740 return BACKEND_EXIT_DAEMONIZING_ERROR;
00741 }
00742
00743
00744 if (pidfs)
00745 {
00746 pidfs << getpid() << endl;
00747 pidfs.close();
00748 }
00749
00750 gContext = NULL;
00751 gContext = new MythContext(MYTH_BINARY_VERSION);
00752 if (!gContext->Init(false))
00753 {
00754 VERBOSE(VB_IMPORTANT, "Failed to init MythContext, exiting.");
00755 return BACKEND_EXIT_NO_MYTHCONTEXT;
00756 }
00757 gContext->SetBackend(true);
00758
00759 if (wantupnprebuild)
00760 {
00761 VERBOSE(VB_GENERAL, "Rebuilding UPNP Media Map");
00762
00763 UPnpMedia *rebuildit = new UPnpMedia(false,false);
00764 rebuildit->BuildMediaMap();
00765
00766 return BACKEND_EXIT_OK;
00767 }
00768
00769 if (clearsettingscache)
00770 {
00771 if (gContext->ConnectToMasterServer())
00772 {
00773 RemoteSendMessage("CLEAR_SETTINGS_CACHE");
00774 VERBOSE(VB_IMPORTANT, "Sent CLEAR_SETTINGS_CACHE message");
00775 return BACKEND_EXIT_OK;
00776 }
00777 else
00778 {
00779 VERBOSE(VB_IMPORTANT, "Unable to connect to backend, settings "
00780 "cache will not be cleared.");
00781 return BACKEND_EXIT_NO_CONNECT;
00782 }
00783 }
00784
00785 if (settingsOverride.size())
00786 {
00787 QMap<QString, QString>::iterator it;
00788 for (it = settingsOverride.begin(); it != settingsOverride.end(); ++it)
00789 {
00790 VERBOSE(VB_IMPORTANT, QString("Setting '%1' being forced to '%2'")
00791 .arg(it.key()).arg(it.data()));
00792 gContext->OverrideSettingForSession(it.key(), it.data());
00793 }
00794 }
00795
00796 gContext->ActivateSettingsCache(false);
00797 if ((CompareTVDatabaseSchemaVersion() > 0) &&
00798
00799 (gContext->GetNumSetting("DBSchemaAutoUpgrade") != -1))
00800 {
00801 VERBOSE(VB_IMPORTANT, "The schema version of your existing database "
00802 "is newer than this version of MythTV understands. Please "
00803 "ensure that you have selected the proper database server or "
00804 "upgrade this and all other frontends and backends to the "
00805 "same MythTV version and revision.");
00806 return BACKEND_EXIT_DB_OUTOFDATE;
00807 }
00808 if (!UpgradeTVDatabaseSchema())
00809 {
00810 VERBOSE(VB_IMPORTANT, "Couldn't upgrade database to new schema");
00811 return BACKEND_EXIT_DB_OUTOFDATE;
00812 }
00813 gContext->ActivateSettingsCache(true);
00814
00815 close(0);
00816
00817 if (printsched || testsched)
00818 {
00819 gContext->SetBackend(false);
00820 sched = new Scheduler(false, &tvList);
00821 if (!testsched && gContext->ConnectToMasterServer())
00822 {
00823 cout << "Retrieving Schedule from Master backend.\n";
00824 sched->FillRecordListFromMaster();
00825 }
00826 else
00827 {
00828 cout << "Calculating Schedule from database.\n" <<
00829 "Inputs, Card IDs, and Conflict info may be invalid "
00830 "if you have multiple tuners.\n";
00831 sched->FillRecordListFromDB();
00832 }
00833
00834 print_verbose_messages |= VB_SCHEDULE;
00835 sched->PrintList(true);
00836 cleanup();
00837 return BACKEND_EXIT_OK;
00838 }
00839
00840 if (resched)
00841 {
00842 gContext->SetBackend(false);
00843
00844 bool ok = false;
00845 if (gContext->ConnectToMasterServer())
00846 {
00847 VERBOSE(VB_IMPORTANT, "Connected to master for reschedule");
00848 ScheduledRecording::signalChange(-1);
00849 ok = true;
00850 }
00851 else
00852 VERBOSE(VB_IMPORTANT, "Cannot connect to master for reschedule");
00853
00854 cleanup();
00855 return (ok) ? BACKEND_EXIT_OK : BACKEND_EXIT_NO_CONNECT;
00856 }
00857
00858 if (printexpire != "")
00859 {
00860 expirer = new AutoExpire();
00861 expirer->PrintExpireList(printexpire);
00862 cleanup();
00863 return BACKEND_EXIT_OK;
00864 }
00865
00866 if ((previewFrameNumber >= -1) || (previewSeconds >= -1))
00867 {
00868 return preview_helper(
00869 chanid, starttime,
00870 previewFrameNumber, previewSeconds, previewSize,
00871 infile, outfile);
00872 }
00873
00874 int port = gContext->GetNumSetting("BackendServerPort", 6543);
00875
00876 QString myip = gContext->GetSetting("BackendServerIP");
00877 if (myip.isNull() || myip.isEmpty())
00878 {
00879 cerr << "No setting found for this machine's BackendServerIP.\n"
00880 << "Please run setup on this machine and modify the first page\n"
00881 << "of the general settings.\n";
00882 return BACKEND_EXIT_NO_IP_ADDRESS;
00883 }
00884
00885 bool ismaster = gContext->IsMasterHost();
00886
00887 if (ismaster)
00888 {
00889 cerr << "Starting up as the master server.\n";
00890 gContext->LogEntry("mythbackend", LP_INFO,
00891 "MythBackend started as master server", "");
00892
00893 if (nosched)
00894 cerr << "********** The Scheduler has been DISABLED with "
00895 "the --nosched option **********\n";
00896
00897
00898 signal(SIGUSR1, &upnp_rebuild);
00899 }
00900 else
00901 {
00902 cerr << "Running as a slave backend.\n";
00903 gContext->LogEntry("mythbackend", LP_INFO,
00904 "MythBackend started as a slave backend", "");
00905 }
00906
00907
00908 if (nohousekeeper)
00909 cerr << "****** The Housekeeper has been DISABLED with "
00910 "the --nohousekeeper option ******\n";
00911 else
00912 housekeeping = new HouseKeeper(true, ismaster);
00913
00914 bool fatal_error = false;
00915 bool runsched = setupTVs(ismaster, fatal_error);
00916 if (fatal_error)
00917 return BACKEND_EXIT_CAP_CARD_SETUP_ERROR;
00918
00919 if (ismaster && runsched)
00920 {
00921 sched = new Scheduler(true, &tvList);
00922
00923 if (nosched)
00924 sched->DisableScheduling();
00925 }
00926
00927 if (ismaster)
00928 {
00929 if (noexpirer)
00930 cerr << "********* Auto-Expire has been DISABLED with "
00931 "the --noautoexpire option ********\n";
00932 else
00933 expirer = new AutoExpire(&tvList);
00934 }
00935
00936 if (sched && expirer)
00937 sched->SetExpirer(expirer);
00938
00939 if (nojobqueue)
00940 cerr << "********* The JobQueue has been DISABLED with "
00941 "the --nojobqueue option *********\n";
00942 else
00943 jobqueue = new JobQueue(ismaster);
00944
00945
00946
00947 g_pUPnp = new MediaServer( ismaster, noupnp );
00948
00949 HttpServer *pHS = g_pUPnp->GetHttpServer();
00950 if (pHS)
00951 {
00952 VERBOSE(VB_IMPORTANT, "Main::Registering HttpStatus Extension");
00953
00954 pHS->RegisterExtension( new HttpStatus( &tvList, sched,
00955 expirer, ismaster ));
00956 }
00957
00958 VERBOSE(VB_IMPORTANT, QString("%1 version: %2 www.mythtv.org")
00959 .arg(binname).arg(MYTH_BINARY_VERSION));
00960
00961 VERBOSE(VB_IMPORTANT, QString("Enabled verbose msgs: %1").arg(verboseString));
00962
00963 new MainServer(ismaster, port, &tvList, sched, expirer);
00964
00965 if (ismaster)
00966 {
00967 QString WOLslaveBackends
00968 = gContext->GetSetting("WOLslaveBackendsCommand","");
00969 if (!WOLslaveBackends.isEmpty())
00970 {
00971 VERBOSE(VB_IMPORTANT, "Waking slave Backends now.");
00972 system(WOLslaveBackends.ascii());
00973 }
00974 }
00975
00976 StorageGroup::CheckAllStorageGroupDirs();
00977
00978 a.exec();
00979
00980 gContext->LogEntry("mythbackend", LP_INFO, "MythBackend exiting", "");
00981 cleanup();
00982
00983 return BACKEND_EXIT_OK;
00984 }
00985
00986