00001 #include "livetvchain.h"
00002 #include "mythcontext.h"
00003 #include "mythdb.h"
00004 #include "mythlogging.h"
00005 #include "programinfo.h"
00006 #include "mythsocket.h"
00007 #include "cardutil.h"
00008
00009 #define LOC QString("LiveTVChain(%1): ").arg(m_id)
00010
00011 static inline void clear(LiveTVChainEntry &entry)
00012 {
00013 entry.chanid = 0;
00014 entry.starttime.setTime_t(0);
00015 entry.endtime = QDateTime();
00016 entry.discontinuity = true;
00017 entry.hostprefix = QString();
00018 entry.cardtype = QString();
00019 entry.channum = QString();
00020 entry.inputname = QString();
00021 }
00022
00026 LiveTVChain::LiveTVChain() :
00027 m_id(""), m_maxpos(0), m_lock(QMutex::Recursive),
00028 m_curpos(0), m_cur_chanid(0),
00029 m_switchid(-1), m_jumppos(0)
00030 {
00031 clear(m_switchentry);
00032 }
00033
00034 LiveTVChain::~LiveTVChain()
00035 {
00036 }
00037
00038 QString LiveTVChain::InitializeNewChain(const QString &seed)
00039 {
00040 QDateTime curdt = QDateTime::currentDateTime();
00041 m_id = QString("live-%1-%2").arg(seed).arg(curdt.toString(Qt::ISODate));
00042 return m_id;
00043 }
00044
00045 void LiveTVChain::SetHostPrefix(const QString &prefix)
00046 {
00047 m_hostprefix = prefix;
00048 }
00049
00050 void LiveTVChain::SetCardType(const QString &type)
00051 {
00052 m_cardtype = type;
00053 }
00054
00055 void LiveTVChain::LoadFromExistingChain(const QString &id)
00056 {
00057 m_id = id;
00058 ReloadAll();
00059 }
00060
00061 void LiveTVChain::AppendNewProgram(ProgramInfo *pginfo, QString channum,
00062 QString inputname, bool discont)
00063 {
00064 QMutexLocker lock(&m_lock);
00065
00066 QTime tmptime = pginfo->GetRecordingStartTime().time();
00067
00068 LiveTVChainEntry newent;
00069 newent.chanid = pginfo->GetChanID();
00070 newent.starttime = pginfo->GetRecordingStartTime();
00071 newent.starttime.setTime(QTime(tmptime.hour(), tmptime.minute(),
00072 tmptime.second()));
00073 newent.discontinuity = discont;
00074 newent.hostprefix = m_hostprefix;
00075 newent.cardtype = m_cardtype;
00076 newent.channum = channum;
00077 newent.inputname = inputname;
00078
00079 m_chain.append(newent);
00080
00081 MSqlQuery query(MSqlQuery::InitCon());
00082 query.prepare("INSERT INTO tvchain (chanid, starttime, endtime, chainid,"
00083 " chainpos, discontinuity, watching, hostprefix, cardtype, "
00084 " channame, input) "
00085 "VALUES(:CHANID, :START, :END, :CHAINID, :CHAINPOS, "
00086 " :DISCONT, :WATCHING, :PREFIX, :CARDTYPE, :CHANNAME, "
00087 " :INPUT );");
00088 query.bindValue(":CHANID", pginfo->GetChanID());
00089 query.bindValue(":START", pginfo->GetRecordingStartTime());
00090 query.bindValue(":END", pginfo->GetRecordingEndTime());
00091 query.bindValue(":CHAINID", m_id);
00092 query.bindValue(":CHAINPOS", m_maxpos);
00093 query.bindValue(":DISCONT", discont);
00094 query.bindValue(":WATCHING", 0);
00095 query.bindValue(":PREFIX", m_hostprefix);
00096 query.bindValue(":CARDTYPE", m_cardtype);
00097 query.bindValue(":CHANNAME", channum);
00098 query.bindValue(":INPUT", inputname);
00099
00100 if (!query.exec() || !query.isActive())
00101 MythDB::DBError("Chain: AppendNewProgram", query);
00102 else
00103 LOG(VB_RECORD, LOG_INFO, QString("Chain: Appended@%3 '%1_%2'")
00104 .arg(newent.chanid)
00105 .arg(newent.starttime.toString("yyyyMMddhhmmss"))
00106 .arg(m_maxpos));
00107
00108 m_maxpos++;
00109 BroadcastUpdate();
00110 }
00111
00112 void LiveTVChain::FinishedRecording(ProgramInfo *pginfo)
00113 {
00114 QMutexLocker lock(&m_lock);
00115
00116 MSqlQuery query(MSqlQuery::InitCon());
00117 query.prepare("UPDATE tvchain SET endtime = :END "
00118 "WHERE chanid = :CHANID AND starttime = :START ;");
00119 query.bindValue(":END", pginfo->GetRecordingEndTime());
00120 query.bindValue(":CHANID", pginfo->GetChanID());
00121 query.bindValue(":START", pginfo->GetRecordingStartTime());
00122
00123 if (!query.exec() || !query.isActive())
00124 MythDB::DBError("Chain: FinishedRecording", query);
00125 else
00126 LOG(VB_RECORD, LOG_INFO,
00127 QString("Chain: Updated endtime for '%1_%2' to %3")
00128 .arg(pginfo->GetChanID())
00129 .arg(pginfo->GetRecordingStartTime(MythDate))
00130 .arg(pginfo->GetRecordingEndTime(MythDate)));
00131
00132 QList<LiveTVChainEntry>::iterator it;
00133 for (it = m_chain.begin(); it != m_chain.end(); ++it)
00134 {
00135 if ((*it).chanid == pginfo->GetChanID() &&
00136 (*it).starttime == pginfo->GetRecordingStartTime())
00137 {
00138 (*it).endtime = pginfo->GetRecordingEndTime();
00139 }
00140 }
00141 BroadcastUpdate();
00142 }
00143
00144 void LiveTVChain::DeleteProgram(ProgramInfo *pginfo)
00145 {
00146 QMutexLocker lock(&m_lock);
00147
00148 QList<LiveTVChainEntry>::iterator it, del;
00149 for (it = m_chain.begin(); it != m_chain.end(); ++it)
00150 {
00151 if ((*it).chanid == pginfo->GetChanID() &&
00152 (*it).starttime == pginfo->GetRecordingStartTime())
00153 {
00154 del = it;
00155 ++it;
00156
00157 MSqlQuery query(MSqlQuery::InitCon());
00158 if (it != m_chain.end())
00159 {
00160 (*it).discontinuity = true;
00161 query.prepare("UPDATE tvchain SET discontinuity = :DISCONT "
00162 "WHERE chanid = :CHANID AND starttime = :START "
00163 "AND chainid = :CHAINID ;");
00164 query.bindValue(":CHANID", (*it).chanid);
00165 query.bindValue(":START", (*it).starttime);
00166 query.bindValue(":CHAINID", m_id);
00167 query.bindValue(":DISCONT", true);
00168 if (!query.exec())
00169 MythDB::DBError("LiveTVChain::DeleteProgram -- "
00170 "discontinuity", query);
00171 }
00172
00173 query.prepare("DELETE FROM tvchain WHERE chanid = :CHANID "
00174 "AND starttime = :START AND chainid = :CHAINID ;");
00175 query.bindValue(":CHANID", (*del).chanid);
00176 query.bindValue(":START", (*del).starttime);
00177 query.bindValue(":CHAINID", m_id);
00178 if (!query.exec())
00179 MythDB::DBError("LiveTVChain::DeleteProgram -- delete", query);
00180
00181 m_chain.erase(del);
00182
00183 BroadcastUpdate();
00184 break;
00185 }
00186 }
00187 }
00188
00189 void LiveTVChain::BroadcastUpdate(void)
00190 {
00191 QString message = QString("LIVETV_CHAIN UPDATE %1").arg(m_id);
00192 MythEvent me(message);
00193 gCoreContext->dispatch(me);
00194 }
00195
00196 void LiveTVChain::DestroyChain(void)
00197 {
00198 QMutexLocker lock(&m_lock);
00199
00200 m_chain.clear();
00201
00202 MSqlQuery query(MSqlQuery::InitCon());
00203 query.prepare("DELETE FROM tvchain WHERE chainid = :CHAINID ;");
00204 query.bindValue(":CHAINID", m_id);
00205
00206 if (!query.exec())
00207 MythDB::DBError("LiveTVChain::DestroyChain", query);
00208 }
00209
00210 void LiveTVChain::ReloadAll(void)
00211 {
00212 QMutexLocker lock(&m_lock);
00213
00214 int prev_size = m_chain.size();
00215 m_chain.clear();
00216
00217 MSqlQuery query(MSqlQuery::InitCon());
00218 query.prepare("SELECT chanid, starttime, endtime, discontinuity, "
00219 "chainpos, hostprefix, cardtype, channame, input "
00220 "FROM tvchain "
00221 "WHERE chainid = :CHAINID ORDER BY chainpos;");
00222 query.bindValue(":CHAINID", m_id);
00223
00224 if (query.exec() && query.isActive() && query.size() > 0)
00225 {
00226 while (query.next())
00227 {
00228
00229 LiveTVChainEntry entry;
00230 entry.chanid = query.value(0).toUInt();
00231 entry.starttime = query.value(1).toDateTime();
00232 entry.endtime = query.value(2).toDateTime();
00233 entry.discontinuity = query.value(3).toInt();
00234 entry.hostprefix = query.value(5).toString();
00235 entry.cardtype = query.value(6).toString();
00236 entry.channum = query.value(7).toString();
00237 entry.inputname = query.value(8).toString();
00238
00239 m_maxpos = query.value(4).toInt() + 1;
00240
00241 m_chain.append(entry);
00242 }
00243 }
00244
00245 m_curpos = ProgramIsAt(m_cur_chanid, m_cur_startts);
00246 if (m_curpos < 0)
00247 m_curpos = 0;
00248
00249 if (m_switchid >= 0)
00250 m_switchid = ProgramIsAt(m_switchentry.chanid,m_switchentry.starttime);
00251
00252 if (prev_size!=m_chain.size())
00253 {
00254 LOG(VB_PLAYBACK, LOG_INFO, LOC + "ReloadAll(): Added new recording");
00255 LOG(VB_PLAYBACK, LOG_INFO, LOC + toString());
00256 }
00257 }
00258
00259 void LiveTVChain::GetEntryAt(int at, LiveTVChainEntry &entry) const
00260 {
00261 QMutexLocker lock(&m_lock);
00262
00263 int size = m_chain.count();
00264 int new_at = (size && (at < 0 || at >= size)) ? size - 1 : at;
00265
00266 if (size && new_at >= 0 && new_at < size)
00267 entry = m_chain[new_at];
00268 else
00269 {
00270 LOG(VB_GENERAL, LOG_ERR, QString("GetEntryAt(%1) failed.").arg(at));
00271 if (at == -1)
00272 LOG(VB_GENERAL, LOG_ERR, "It appears that your backend may "
00273 "be misconfigured. Check your backend logs to determine "
00274 "whether your capture cards, lineups, channels, or storage "
00275 "configuration are reporting errors. This issue is commonly "
00276 "caused by failing to complete all setup steps properly. You "
00277 "may wish to review the documentation for mythtv-setup.");
00278 clear(entry);
00279 }
00280 }
00281
00282 ProgramInfo *LiveTVChain::EntryToProgram(const LiveTVChainEntry &entry)
00283 {
00284 ProgramInfo *pginfo = new ProgramInfo(entry.chanid, entry.starttime);
00285
00286 if (pginfo->GetChanID())
00287 {
00288 pginfo->SetPathname(entry.hostprefix + pginfo->GetBasename());
00289 return pginfo;
00290 }
00291
00292 LOG(VB_GENERAL, LOG_ERR,
00293 QString("EntryToProgram(%1@%2) failed to get pginfo")
00294 .arg(entry.chanid).arg(entry.starttime.toString()));
00295 delete pginfo;
00296 return NULL;
00297 }
00298
00306 ProgramInfo *LiveTVChain::GetProgramAt(int at) const
00307 {
00308 LiveTVChainEntry entry;
00309 GetEntryAt(at, entry);
00310
00311 return EntryToProgram(entry);
00312 }
00313
00317 int LiveTVChain::ProgramIsAt(uint chanid, const QDateTime &starttime) const
00318 {
00319 QMutexLocker lock(&m_lock);
00320
00321 int count = 0;
00322 QList<LiveTVChainEntry>::const_iterator it;
00323 for (it = m_chain.begin(); it != m_chain.end(); ++it, ++count)
00324 {
00325 if ((*it).chanid == chanid &&
00326 (*it).starttime == starttime)
00327 {
00328 return count;
00329 }
00330 }
00331
00332 return -1;
00333 }
00334
00338 int LiveTVChain::ProgramIsAt(const ProgramInfo &pginfo) const
00339 {
00340 return ProgramIsAt(pginfo.GetChanID(), pginfo.GetRecordingStartTime());
00341 }
00342
00346 int LiveTVChain::GetLengthAtCurPos(void)
00347 {
00348 QMutexLocker lock(&m_lock);
00349 LiveTVChainEntry entry;
00350
00351 entry = m_chain[m_curpos];
00352 if (m_curpos == ((int)m_chain.count() - 1))
00353 return entry.starttime.secsTo(QDateTime::currentDateTime());
00354 else
00355 return entry.starttime.secsTo(entry.endtime);
00356 }
00357
00358 int LiveTVChain::TotalSize(void) const
00359 {
00360 return m_chain.count();
00361 }
00362
00363 void LiveTVChain::SetProgram(const ProgramInfo &pginfo)
00364 {
00365 QMutexLocker lock(&m_lock);
00366
00367 m_cur_chanid = pginfo.GetChanID();
00368 m_cur_startts = pginfo.GetRecordingStartTime();
00369
00370 m_curpos = ProgramIsAt(pginfo);
00371 m_switchid = -1;
00372 }
00373
00374 bool LiveTVChain::HasNext(void) const
00375 {
00376 return ((int)m_chain.count() - 1 > m_curpos);
00377 }
00378
00379 void LiveTVChain::ClearSwitch(void)
00380 {
00381 QMutexLocker lock(&m_lock);
00382
00383 m_switchid = -1;
00384 m_jumppos = 0;
00385 }
00386
00397 ProgramInfo *LiveTVChain::GetSwitchProgram(bool &discont, bool &newtype,
00398 int &newid)
00399 {
00400 ReloadAll();
00401 QMutexLocker lock(&m_lock);
00402
00403 if (m_switchid < 0 || m_curpos == m_switchid)
00404 {
00405 ClearSwitch();
00406 return NULL;
00407 }
00408
00409 LiveTVChainEntry oldentry, entry;
00410 GetEntryAt(m_curpos, oldentry);
00411
00412 ProgramInfo *pginfo = NULL;
00413 while (!pginfo && m_switchid < (int)m_chain.count() && m_switchid >= 0)
00414 {
00415 GetEntryAt(m_switchid, entry);
00416
00417 bool at_last_entry =
00418 ((m_switchid > m_curpos) &&
00419 (m_switchid == (int)(m_chain.count()-1))) ||
00420 ((m_switchid <= m_curpos) && (m_switchid == 0));
00421
00422
00423 if (at_last_entry || (entry.cardtype != "DUMMY"))
00424 pginfo = EntryToProgram(entry);
00425
00426
00427 if (pginfo && (0 == pginfo->GetFilesize()) &&
00428 m_switchid < (int)(m_chain.count()-1))
00429 {
00430 LOG(VB_GENERAL, LOG_WARNING,
00431 QString("Skipping empty program %1")
00432 .arg(pginfo->MakeUniqueKey()));
00433 delete pginfo;
00434 pginfo = NULL;
00435 }
00436
00437 if (!pginfo)
00438 {
00439 if (m_switchid > m_curpos)
00440 m_switchid++;
00441 else
00442 m_switchid--;
00443 }
00444 }
00445
00446 if (!pginfo)
00447 {
00448 ClearSwitch();
00449 return NULL;
00450 }
00451
00452 discont = true;
00453 if (m_curpos == m_switchid - 1)
00454 discont = entry.discontinuity;
00455
00456 newtype = (oldentry.cardtype != entry.cardtype);
00457
00458
00459 if (discont)
00460 newtype |= CardUtil::IsChannelChangeDiscontinuous(entry.cardtype);
00461
00462 newid = m_switchid;
00463
00464 ClearSwitch();
00465
00466 return pginfo;
00467 }
00468
00473 void LiveTVChain::SwitchTo(int num)
00474 {
00475 QMutexLocker lock(&m_lock);
00476
00477 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SwitchTo(%1)").arg(num));
00478
00479 int size = m_chain.count();
00480 if ((num < 0) || (num >= size))
00481 num = size - 1;
00482
00483 if (m_curpos != num)
00484 {
00485 m_switchid = num;
00486 GetEntryAt(num, m_switchentry);
00487 }
00488 else
00489 LOG(VB_GENERAL, LOG_ERR, LOC + "SwitchTo() not switching to current");
00490
00491 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
00492 {
00493 LiveTVChainEntry e;
00494 GetEntryAt(num, e);
00495 QString msg = QString("%1_%2")
00496 .arg(e.chanid).arg(e.starttime.toString("yyyyMMddhhmmss"));
00497 LOG(VB_PLAYBACK, LOG_DEBUG,
00498 LOC + QString("Entry@%1: '%2')").arg(num).arg(msg));
00499 }
00500 }
00501
00507 void LiveTVChain::SwitchToNext(bool up)
00508 {
00509 #if 0
00510 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "SwitchToNext("<<(up?"up":"down")<<")");
00511 #endif
00512 if (up && HasNext())
00513 SwitchTo(m_curpos + 1);
00514 else if (!up && HasPrev())
00515 SwitchTo(m_curpos - 1);
00516 }
00517
00518 void LiveTVChain::JumpTo(int num, int pos)
00519 {
00520 m_jumppos = pos;
00521 SwitchTo(num);
00522 }
00523
00524 void LiveTVChain::JumpToNext(bool up, int pos)
00525 {
00526 m_jumppos = pos;
00527 SwitchToNext(up);
00528 }
00529
00533 int LiveTVChain::GetJumpPos(void)
00534 {
00535 int ret = m_jumppos;
00536 m_jumppos = 0;
00537 return ret;
00538 }
00539
00540 QString LiveTVChain::GetChannelName(int pos) const
00541 {
00542 LiveTVChainEntry entry;
00543 GetEntryAt(pos, entry);
00544
00545 return entry.channum;
00546 }
00547
00548 QString LiveTVChain::GetInputName(int pos) const
00549 {
00550 LiveTVChainEntry entry;
00551 GetEntryAt(pos, entry);
00552
00553 return entry.inputname;
00554 }
00555
00556 QString LiveTVChain::GetCardType(int pos) const
00557 {
00558 LiveTVChainEntry entry;
00559 GetEntryAt(pos, entry);
00560
00561 return entry.cardtype;
00562 }
00563
00564 void LiveTVChain::SetHostSocket(MythSocket *sock)
00565 {
00566 QMutexLocker lock(&m_sockLock);
00567
00568 if (!m_inUseSocks.contains(sock))
00569 m_inUseSocks.append(sock);
00570 }
00571
00572 bool LiveTVChain::IsHostSocket(const MythSocket *sock) const
00573 {
00574 QMutexLocker lock(&m_sockLock);
00575 return m_inUseSocks.contains(const_cast<MythSocket*>(sock));
00576 }
00577
00578 uint LiveTVChain::HostSocketCount(void) const
00579 {
00580 QMutexLocker lock(&m_sockLock);
00581 return m_inUseSocks.count();
00582 }
00583
00584 void LiveTVChain::DelHostSocket(MythSocket *sock)
00585 {
00586 QMutexLocker lock(&m_sockLock);
00587 m_inUseSocks.removeAll(sock);
00588 }
00589
00590 static QString toString(const LiveTVChainEntry &v)
00591 {
00592 return QString("%1: %2 (%3 to %4)%5")
00593 .arg(v.cardtype,6).arg(v.chanid,4)
00594 .arg(v.starttime.time().toString())
00595 .arg(v.endtime.time().toString())
00596 .arg(v.discontinuity?" discontinuous":"");
00597 }
00598
00599 QString LiveTVChain::toString() const
00600 {
00601 QMutexLocker lock(&m_lock);
00602 QString ret = QString("LiveTVChain has %1 entries\n").arg(m_chain.size());
00603 for (uint i = 0; i < (uint)m_chain.size(); i++)
00604 {
00605 ret += (QString((i==(uint)m_curpos) ? "* " : " ") +
00606 ::toString(m_chain[i]) + "\n");
00607 }
00608 return ret;
00609 }