MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
main_helpers.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <sys/time.h> // for setpriority
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <libgen.h>
8 #include <signal.h>
9 
10 #include "mythconfig.h"
11 #if CONFIG_DARWIN
12  #include <sys/aio.h> // O_SYNC
13 #endif
14 
15 // C headers
16 #include <cstdlib>
17 #include <cerrno>
18 
19 #include <QCoreApplication>
20 #include <QFileInfo>
21 #include <QRegExp>
22 #include <QFile>
23 #include <QDir>
24 #include <QMap>
25 
26 #include "tv_rec.h"
27 #include "scheduledrecording.h"
28 #include "autoexpire.h"
29 #include "scheduler.h"
30 #include "mainserver.h"
31 #include "encoderlink.h"
32 #include "remoteutil.h"
33 #include "backendhousekeeper.h"
34 
35 #include "mythcontext.h"
36 #include "mythversion.h"
37 #include "mythdb.h"
38 #include "dbutil.h"
39 #include "exitcodes.h"
40 #include "compat.h"
41 #include "storagegroup.h"
42 #include "programinfo.h"
43 #include "dbcheck.h"
44 #include "jobqueue.h"
45 #include "previewgenerator.h"
46 #include "commandlineparser.h"
47 #include "mythsystemevent.h"
48 #include "main_helpers.h"
49 #include "backendcontext.h"
50 #include "mythtranslation.h"
51 #include "mythtimezone.h"
52 #include "signalhandling.h"
53 #include "hardwareprofile.h"
54 
55 #include "mediaserver.h"
56 #include "httpstatus.h"
57 #include "mythlogging.h"
58 
59 #define LOC QString("MythBackend: ")
60 #define LOC_WARN QString("MythBackend, Warning: ")
61 #define LOC_ERR QString("MythBackend, Error: ")
62 
63 static MainServer *mainServer = NULL;
64 
65 bool setupTVs(bool ismaster, bool &error)
66 {
67  error = false;
68  QString localhostname = gCoreContext->GetHostName();
69 
71 
72  if (ismaster)
73  {
74  // Hack to make sure recorded.basename gets set if the user
75  // downgrades to a prior version and creates new entries
76  // without it.
77  if (!query.exec("UPDATE recorded SET basename = CONCAT(chanid, '_', "
78  "DATE_FORMAT(starttime, '%Y%m%d%H%i00'), '_', "
79  "DATE_FORMAT(endtime, '%Y%m%d%H%i00'), '.nuv') "
80  "WHERE basename = '';"))
81  MythDB::DBError("Updating record basename", query);
82 
83  // Hack to make sure record.station gets set if the user
84  // downgrades to a prior version and creates new entries
85  // without it.
86  if (!query.exec("UPDATE channel SET callsign=chanid "
87  "WHERE callsign IS NULL OR callsign='';"))
88  MythDB::DBError("Updating channel callsign", query);
89 
90  if (query.exec("SELECT MIN(chanid) FROM channel;"))
91  {
92  query.first();
93  int min_chanid = query.value(0).toInt();
94  if (!query.exec(QString("UPDATE record SET chanid = %1 "
95  "WHERE chanid IS NULL;").arg(min_chanid)))
96  MythDB::DBError("Updating record chanid", query);
97  }
98  else
99  MythDB::DBError("Querying minimum chanid", query);
100 
101  MSqlQuery records_without_station(MSqlQuery::InitCon());
102  records_without_station.prepare("SELECT record.chanid,"
103  " channel.callsign FROM record LEFT JOIN channel"
104  " ON record.chanid = channel.chanid WHERE record.station='';");
105  if (records_without_station.exec() && records_without_station.next())
106  {
107  MSqlQuery update_record(MSqlQuery::InitCon());
108  update_record.prepare("UPDATE record SET station = :CALLSIGN"
109  " WHERE chanid = :CHANID;");
110  do
111  {
112  update_record.bindValue(":CALLSIGN",
113  records_without_station.value(1));
114  update_record.bindValue(":CHANID",
115  records_without_station.value(0));
116  if (!update_record.exec())
117  {
118  MythDB::DBError("Updating record station", update_record);
119  }
120  } while (records_without_station.next());
121  }
122  }
123 
124  if (!query.exec(
125  "SELECT cardid, hostname "
126  "FROM capturecard "
127  "ORDER BY cardid"))
128  {
129  MythDB::DBError("Querying Recorders", query);
130  return false;
131  }
132 
133  vector<uint> cardids;
134  vector<QString> hosts;
135  while (query.next())
136  {
137  uint cardid = query.value(0).toUInt();
138  QString host = query.value(1).toString();
139  QString cidmsg = QString("Card %1").arg(cardid);
140 
141  if (host.isEmpty())
142  {
143  LOG(VB_GENERAL, LOG_ERR, cidmsg +
144  " does not have a hostname defined.\n"
145  "Please run setup and confirm all of the capture cards.\n");
146  continue;
147  }
148 
149  cardids.push_back(cardid);
150  hosts.push_back(host);
151  }
152 
153  for (uint i = 0; i < cardids.size(); i++)
154  {
155  if (hosts[i] == localhostname)
156  new TVRec(cardids[i]);
157  }
158 
159  for (uint i = 0; i < cardids.size(); i++)
160  {
161  uint cardid = cardids[i];
162  QString host = hosts[i];
163  QString cidmsg = QString("Card %1").arg(cardid);
164 
165  if (!ismaster)
166  {
167  if (host == localhostname)
168  {
169  TVRec *tv = TVRec::GetTVRec(cardid);
170  if (tv && tv->Init())
171  {
172  EncoderLink *enc = new EncoderLink(cardid, tv);
173  tvList[cardid] = enc;
174  }
175  else
176  {
177  LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards. " +
178  cidmsg + " failed init");
179  delete tv;
180  // The master assumes card comes up so we need to
181  // set error and exit if a non-master card fails.
182  error = true;
183  }
184  }
185  }
186  else
187  {
188  if (host == localhostname)
189  {
190  TVRec *tv = TVRec::GetTVRec(cardid);
191  if (tv && tv->Init())
192  {
193  EncoderLink *enc = new EncoderLink(cardid, tv);
194  tvList[cardid] = enc;
195  }
196  else
197  {
198  LOG(VB_GENERAL, LOG_ERR, "Problem with capture cards" +
199  cidmsg + "failed init");
200  delete tv;
201  }
202  }
203  else
204  {
205  EncoderLink *enc = new EncoderLink(cardid, NULL, host);
206  tvList[cardid] = enc;
207  }
208  }
209  }
210 
211  if (tvList.empty())
212  {
213  LOG(VB_GENERAL, LOG_WARNING, LOC +
214  "No valid capture cards are defined in the database.");
215  }
216 
217  return true;
218 }
219 
220 void cleanup(void)
221 {
222  if (mainServer)
223  mainServer->Stop();
224 
225  delete housekeeping;
226  housekeeping = NULL;
227 
228  if (gCoreContext)
229  {
230  delete gCoreContext->GetScheduler();
231  gCoreContext->SetScheduler(NULL);
232  }
233 
234  delete expirer;
235  expirer = NULL;
236 
237  delete jobqueue;
238  jobqueue = NULL;
239 
240  delete g_pUPnp;
241  g_pUPnp = NULL;
242 
243  if (SSDP::Instance())
244  {
246  SSDP::Instance()->wait();
247  }
248 
249  if (TaskQueue::Instance())
250  {
253  }
254 
255  while (!TVRec::cards.empty())
256  {
257  TVRec *rec = *TVRec::cards.begin();
258  delete rec;
259  }
260 
261  delete gContext;
262  gContext = NULL;
263 
264  delete mainServer;
265  mainServer = NULL;
266 
267  if (pidfile.size())
268  {
269  unlink(pidfile.toLatin1().constData());
270  pidfile.clear();
271  }
272 
274 }
275 
277 {
278  QString eventString;
279 
280  if (cmdline.toBool("event"))
281  eventString = cmdline.toString("event");
282  else if (cmdline.toBool("systemevent"))
283  eventString = "SYSTEM_EVENT " +
284  cmdline.toString("systemevent") +
285  QString(" SENDER %1").arg(gCoreContext->GetHostName());
286 
287  if (!eventString.isEmpty())
288  {
290  {
291  gCoreContext->SendMessage(eventString);
292  return GENERIC_EXIT_OK;
293  }
294  return GENERIC_EXIT_NO_MYTHCONTEXT;
295  }
296 
297  if (cmdline.toBool("setverbose"))
298  {
300  {
301  QString message = "SET_VERBOSE ";
302  message += cmdline.toString("setverbose");
303 
304  gCoreContext->SendMessage(message);
305  LOG(VB_GENERAL, LOG_INFO,
306  QString("Sent '%1' message").arg(message));
307  return GENERIC_EXIT_OK;
308  }
309  else
310  {
311  LOG(VB_GENERAL, LOG_ERR,
312  "Unable to connect to backend, verbose mask unchanged ");
313  return GENERIC_EXIT_CONNECT_ERROR;
314  }
315  }
316 
317  if (cmdline.toBool("setloglevel"))
318  {
320  {
321  QString message = "SET_LOG_LEVEL ";
322  message += cmdline.toString("setloglevel");
323 
324  gCoreContext->SendMessage(message);
325  LOG(VB_GENERAL, LOG_INFO,
326  QString("Sent '%1' message").arg(message));
327  return GENERIC_EXIT_OK;
328  }
329  else
330  {
331  LOG(VB_GENERAL, LOG_ERR,
332  "Unable to connect to backend, log level unchanged ");
333  return GENERIC_EXIT_CONNECT_ERROR;
334  }
335  }
336 
337  if (cmdline.toBool("clearcache"))
338  {
340  {
341  gCoreContext->SendMessage("CLEAR_SETTINGS_CACHE");
342  LOG(VB_GENERAL, LOG_INFO, "Sent CLEAR_SETTINGS_CACHE message");
343  return GENERIC_EXIT_OK;
344  }
345  else
346  {
347  LOG(VB_GENERAL, LOG_ERR, "Unable to connect to backend, settings "
348  "cache will not be cleared.");
349  return GENERIC_EXIT_CONNECT_ERROR;
350  }
351  }
352 
353  if (cmdline.toBool("printsched") ||
354  cmdline.toBool("testsched"))
355  {
356  Scheduler *sched = new Scheduler(false, &tvList);
357  if (!cmdline.toBool("testsched") &&
359  {
360  cout << "Retrieving Schedule from Master backend.\n";
361  sched->FillRecordListFromMaster();
362  }
363  else
364  {
365  cout << "Calculating Schedule from database.\n" <<
366  "Inputs, Card IDs, and Conflict info may be invalid "
367  "if you have multiple tuners.\n";
369  sched->FillRecordListFromDB();
370  }
371 
372  verboseMask |= VB_SCHEDULE;
373  LogLevel_t oldLogLevel = logLevel;
374  logLevel = LOG_DEBUG;
375  sched->PrintList(true);
376  logLevel = oldLogLevel;
377  delete sched;
378  return GENERIC_EXIT_OK;
379  }
380 
381  if (cmdline.toBool("resched"))
382  {
383  bool ok = false;
385  {
386  LOG(VB_GENERAL, LOG_INFO, "Connected to master for reschedule");
387  ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
388  "MythBackendCommand");
389  ok = true;
390  }
391  else
392  LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for reschedule");
393 
394  return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
395  }
396 
397  if (cmdline.toBool("scanvideos"))
398  {
399  bool ok = false;
401  {
402  gCoreContext->SendReceiveStringList(QStringList() << "SCAN_VIDEOS");
403  LOG(VB_GENERAL, LOG_INFO, "Requested video scan");
404  ok = true;
405  }
406  else
407  LOG(VB_GENERAL, LOG_ERR, "Cannot connect to master for video scan");
408 
409  return (ok) ? GENERIC_EXIT_OK : GENERIC_EXIT_CONNECT_ERROR;
410  }
411 
412  if (cmdline.toBool("printexpire"))
413  {
414  expirer = new AutoExpire();
415  expirer->PrintExpireList(cmdline.toString("printexpire"));
416  return GENERIC_EXIT_OK;
417  }
418 
419  // This should never actually be reached..
420  return GENERIC_EXIT_OK;
421 }
422 using namespace MythTZ;
423 
425 {
426  MythSocket *tempMonitorConnection = new MythSocket();
427  if (tempMonitorConnection->ConnectToHost(
428  gCoreContext->GetSetting("MasterServerIP", "127.0.0.1"),
429  gCoreContext->GetNumSetting("MasterServerPort", 6543)))
430  {
431  if (!gCoreContext->CheckProtoVersion(tempMonitorConnection))
432  {
433  LOG(VB_GENERAL, LOG_ERR, "Master backend is incompatible with "
434  "this backend.\nCannot become a slave.");
435  return GENERIC_EXIT_CONNECT_ERROR;
436  }
437 
438  QStringList tempMonitorDone("DONE");
439 
440  QStringList tempMonitorAnnounce("ANN Monitor tzcheck 0");
441  tempMonitorConnection->SendReceiveStringList(tempMonitorAnnounce);
442  if (tempMonitorAnnounce.empty() ||
443  tempMonitorAnnounce[0] == "ERROR")
444  {
445  tempMonitorConnection->DecrRef();
446  tempMonitorConnection = NULL;
447  if (tempMonitorAnnounce.empty())
448  {
449  LOG(VB_GENERAL, LOG_ERR, LOC +
450  "Failed to open event socket, timeout");
451  }
452  else
453  {
454  LOG(VB_GENERAL, LOG_ERR, LOC +
455  "Failed to open event socket" +
456  ((tempMonitorAnnounce.size() >= 2) ?
457  QString(", error was %1").arg(tempMonitorAnnounce[1]) :
458  QString(", remote error")));
459  }
460  }
461 
462  QStringList timeCheck;
463  if (tempMonitorConnection)
464  {
465  timeCheck.push_back("QUERY_TIME_ZONE");
466  tempMonitorConnection->SendReceiveStringList(timeCheck);
467  }
468  if (timeCheck.size() < 3)
469  {
470  return GENERIC_EXIT_SOCKET_ERROR;
471  }
472  tempMonitorConnection->WriteStringList(tempMonitorDone);
473 
474  QDateTime our_time = MythDate::current();
475  QDateTime master_time = MythDate::fromString(timeCheck[2]);
476  int timediff = abs(our_time.secsTo(master_time));
477 
478  if (timediff > 300)
479  {
480  LOG(VB_GENERAL, LOG_ERR,
481  QString("Current time on the master backend differs by "
482  "%1 seconds from time on this system. Exiting.")
483  .arg(timediff));
484  tempMonitorConnection->DecrRef();
485  return GENERIC_EXIT_INVALID_TIME;
486  }
487 
488  if (timediff > 20)
489  {
490  LOG(VB_GENERAL, LOG_WARNING,
491  QString("Time difference between the master "
492  "backend and this system is %1 seconds.")
493  .arg(timediff));
494  }
495  }
496  if (tempMonitorConnection)
497  tempMonitorConnection->DecrRef();
498 
499  return GENERIC_EXIT_OK;
500 }
501 
502 
504 {
505  if (cmdline.toBool("nohousekeeper"))
506  {
507  LOG(VB_GENERAL, LOG_WARNING, LOC +
508  "****** The Housekeeper has been DISABLED with "
509  "the --nohousekeeper option ******");
510  }
511  if (cmdline.toBool("nosched"))
512  {
513  LOG(VB_GENERAL, LOG_WARNING, LOC +
514  "********** The Scheduler has been DISABLED with "
515  "the --nosched option **********");
516  }
517  if (cmdline.toBool("noautoexpire"))
518  {
519  LOG(VB_GENERAL, LOG_WARNING, LOC +
520  "********* Auto-Expire has been DISABLED with "
521  "the --noautoexpire option ********");
522  }
523  if (cmdline.toBool("nojobqueue"))
524  {
525  LOG(VB_GENERAL, LOG_WARNING, LOC +
526  "********* The JobQueue has been DISABLED with "
527  "the --nojobqueue option *********");
528  }
529 }
530 
532 {
534  {
535  LOG(VB_GENERAL, LOG_ERR,
536  "MySQL time zone support is missing. "
537  "Please install it and try again. "
538  "See 'mysql_tzinfo_to_sql' for assistance.");
539  return GENERIC_EXIT_DB_NOTIMEZONE;
540  }
541 
542  bool ismaster = gCoreContext->IsMasterHost();
543 
544  if (!UpgradeTVDatabaseSchema(ismaster, ismaster))
545  {
546  LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema");
547  return GENERIC_EXIT_DB_OUTOFDATE;
548  }
549 
550  MythTranslation::load("mythfrontend");
551 
552  if (!ismaster)
553  {
554  int ret = connect_to_master();
555  if (ret != GENERIC_EXIT_OK)
556  return ret;
557  }
558 
559  int port = gCoreContext->GetNumSetting("BackendServerPort", 6543);
560  if (gCoreContext->GetSetting("BackendServerIP").isEmpty() &&
561  gCoreContext->GetSetting("BackendServerIP6").isEmpty())
562  {
563  cerr << "No setting found for this machine's BackendServerIP.\n"
564  << "Please run setup on this machine and modify the first page\n"
565  << "of the general settings.\n";
566  return GENERIC_EXIT_SETUP_ERROR;
567  }
568 
569  MythSystemEventHandler *sysEventHandler = new MythSystemEventHandler();
570 
571  if (ismaster)
572  {
573  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Starting up as the master server.");
574  }
575  else
576  {
577  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Running as a slave backend.");
578  }
579 
580  print_warnings(cmdline);
581 
582  bool fatal_error = false;
583  bool runsched = setupTVs(ismaster, fatal_error);
584  if (fatal_error)
585  {
586  delete sysEventHandler;
587  return GENERIC_EXIT_SETUP_ERROR;
588  }
589 
590  Scheduler *sched = NULL;
591  if (ismaster)
592  {
593  if (runsched)
594  {
595  sched = new Scheduler(true, &tvList);
596  int err = sched->GetError();
597  if (err)
598  return err;
599 
600  if (cmdline.toBool("nosched"))
601  sched->DisableScheduling();
602  }
603 
604  if (!cmdline.toBool("noautoexpire"))
605  {
606  expirer = new AutoExpire(&tvList);
607  if (sched)
608  sched->SetExpirer(expirer);
609  }
610  gCoreContext->SetScheduler(sched);
611  }
612 
613  if (!cmdline.toBool("nohousekeeper"))
614  {
615  housekeeping = new HouseKeeper();
616 
617  if (ismaster)
618  {
624  }
625 
627 #ifdef __linux__
628  #ifdef CONFIG_BINDINGS_PYTHON
630  #endif
631 #endif
632 
633  housekeeping->Start();
634  }
635 
636  if (!cmdline.toBool("nojobqueue"))
637  jobqueue = new JobQueue(ismaster);
638 
639  // ----------------------------------------------------------------------
640  //
641  // ----------------------------------------------------------------------
642 
643  if (g_pUPnp == NULL)
644  {
645  g_pUPnp = new MediaServer();
646 
647  g_pUPnp->Init(ismaster, cmdline.toBool("noupnp"));
648  }
649 
650  // ----------------------------------------------------------------------
651  // Setup status server
652  // ----------------------------------------------------------------------
653 
654  HttpStatus *httpStatus = NULL;
655  HttpServer *pHS = g_pUPnp->GetHttpServer();
656 
657  if (pHS)
658  {
659  LOG(VB_GENERAL, LOG_INFO, "Main::Registering HttpStatus Extension");
660 
661  httpStatus = new HttpStatus( &tvList, sched, expirer, ismaster );
662  pHS->RegisterExtension( httpStatus );
663  }
664 
665  mainServer = new MainServer(
666  ismaster, port, &tvList, sched, expirer);
667 
668  int exitCode = mainServer->GetExitCode();
669  if (exitCode != GENERIC_EXIT_OK)
670  {
671  LOG(VB_GENERAL, LOG_CRIT,
672  "Backend exiting, MainServer initialization error.");
673  cleanup();
674  return exitCode;
675  }
676 
677  if (httpStatus && mainServer)
678  httpStatus->SetMainServer(mainServer);
679 
681 
683  gCoreContext->SendSystemEvent("MASTER_STARTED");
684 
687  exitCode = qApp->exec();
690 
692  {
693  gCoreContext->SendSystemEvent("MASTER_SHUTDOWN");
694  qApp->processEvents();
695  }
696 
697  LOG(VB_GENERAL, LOG_NOTICE, "MythBackend exiting");
698 
699  delete sysEventHandler;
700 
701  return exitCode;
702 }