00001 #include <unistd.h>
00002
00003 #include <iostream>
00004 #include <algorithm>
00005 #include <list>
00006 using namespace std;
00007
00008 #ifdef __linux__
00009 # include <sys/vfs.h>
00010 #else // if !__linux__
00011 # include <sys/param.h>
00012 # ifndef USING_MINGW
00013 # include <sys/mount.h>
00014 # endif // USING_MINGW
00015 #endif // !__linux__
00016
00017 #include <sys/stat.h>
00018 #include <sys/time.h>
00019 #include <sys/types.h>
00020
00021 #include <QStringList>
00022 #include <QDateTime>
00023 #include <QString>
00024 #include <QRegExp>
00025 #include <QMutex>
00026 #include <QFile>
00027 #include <QMap>
00028
00029 #include "scheduler.h"
00030 #include "encoderlink.h"
00031 #include "mainserver.h"
00032 #include "remoteutil.h"
00033 #include "backendutil.h"
00034 #include "mythmiscutil.h"
00035 #include "exitcodes.h"
00036 #include "mythcontext.h"
00037 #include "mythdb.h"
00038 #include "compat.h"
00039 #include "storagegroup.h"
00040 #include "recordinginfo.h"
00041 #include "recordingrule.h"
00042 #include "scheduledrecording.h"
00043 #include "cardutil.h"
00044 #include "mythdb.h"
00045 #include "mythsystemevent.h"
00046 #include "mythlogging.h"
00047
00048 #define LOC QString("Scheduler: ")
00049 #define LOC_WARN QString("Scheduler, Warning: ")
00050 #define LOC_ERR QString("Scheduler, Error: ")
00051
00052 bool debugConflicts = false;
00053
00054 Scheduler::Scheduler(bool runthread, QMap<int, EncoderLink *> *tvList,
00055 QString tmptable, Scheduler *master_sched) :
00056 MThread("Scheduler"),
00057 recordTable(tmptable),
00058 priorityTable("powerpriority"),
00059 schedLock(),
00060 reclist_changed(false),
00061 specsched(master_sched),
00062 schedMoveHigher(false),
00063 schedulingEnabled(true),
00064 m_tvList(tvList),
00065 m_expirer(NULL),
00066 doRun(runthread),
00067 m_mainServer(NULL),
00068 resetIdleTime(false),
00069 m_isShuttingDown(false),
00070 error(0),
00071 livetvTime(QDateTime()),
00072 livetvpriority(0),
00073 prefinputpri(0)
00074 {
00075 char *debug = getenv("DEBUG_CONFLICTS");
00076 debugConflicts = (debug != NULL);
00077
00078 if (master_sched)
00079 master_sched->GetAllPending(reclist);
00080
00081 if (!doRun)
00082 dbConn = MSqlQuery::DDCon();
00083
00084 if (tmptable == "powerpriority_tmp")
00085 {
00086 priorityTable = tmptable;
00087 recordTable = "record";
00088 }
00089
00090 if (!VerifyCards())
00091 {
00092 error = true;
00093 return;
00094 }
00095
00096 fsInfoCacheFillTime = QDateTime::currentDateTime().addSecs(-1000);
00097
00098 if (doRun)
00099 {
00100 ProgramInfo::CheckProgramIDAuthorities();
00101 {
00102 QMutexLocker locker(&schedLock);
00103 start(QThread::LowPriority);
00104 while (doRun && !isRunning())
00105 reschedWait.wait(&schedLock);
00106 }
00107 WakeUpSlaves();
00108 }
00109 }
00110
00111 Scheduler::~Scheduler()
00112 {
00113 QMutexLocker locker(&schedLock);
00114 if (doRun)
00115 {
00116 doRun = false;
00117 reschedWait.wakeAll();
00118 locker.unlock();
00119 wait();
00120 locker.relock();
00121 }
00122
00123 while (!reclist.empty())
00124 {
00125 delete reclist.back();
00126 reclist.pop_back();
00127 }
00128
00129 while (!worklist.empty())
00130 {
00131 delete worklist.back();
00132 worklist.pop_back();
00133 }
00134
00135 locker.unlock();
00136 wait();
00137 }
00138
00139 void Scheduler::Stop(void)
00140 {
00141 QMutexLocker locker(&schedLock);
00142 doRun = false;
00143 reschedWait.wakeAll();
00144 }
00145
00146 void Scheduler::SetMainServer(MainServer *ms)
00147 {
00148 m_mainServer = ms;
00149 }
00150
00151 void Scheduler::ResetIdleTime(void)
00152 {
00153 resetIdleTime_lock.lock();
00154 resetIdleTime = true;
00155 resetIdleTime_lock.unlock();
00156 }
00157
00158 bool Scheduler::VerifyCards(void)
00159 {
00160 MSqlQuery query(MSqlQuery::InitCon());
00161 if (!query.exec("SELECT count(*) FROM capturecard") || !query.next())
00162 {
00163 MythDB::DBError("verifyCards() -- main query 1", query);
00164 error = GENERIC_EXIT_DB_ERROR;
00165 return false;
00166 }
00167
00168 uint numcards = query.value(0).toUInt();
00169 if (!numcards)
00170 {
00171 LOG(VB_GENERAL, LOG_ERR, LOC +
00172 "No capture cards are defined in the database.\n\t\t\t"
00173 "Perhaps you should re-read the installation instructions?");
00174 error = GENERIC_EXIT_SETUP_ERROR;
00175 return false;
00176 }
00177
00178 query.prepare("SELECT sourceid,name FROM videosource ORDER BY sourceid;");
00179
00180 if (!query.exec())
00181 {
00182 MythDB::DBError("verifyCards() -- main query 2", query);
00183 error = GENERIC_EXIT_DB_ERROR;
00184 return false;
00185 }
00186
00187 uint numsources = 0;
00188 MSqlQuery subquery(MSqlQuery::InitCon());
00189 while (query.next())
00190 {
00191 subquery.prepare(
00192 "SELECT cardinputid "
00193 "FROM cardinput "
00194 "WHERE sourceid = :SOURCEID "
00195 "ORDER BY cardinputid;");
00196 subquery.bindValue(":SOURCEID", query.value(0).toUInt());
00197
00198 if (!subquery.exec())
00199 {
00200 MythDB::DBError("verifyCards() -- sub query", subquery);
00201 }
00202 else if (!subquery.next())
00203 {
00204 LOG(VB_GENERAL, LOG_WARNING, LOC +
00205 QString("Listings source '%1' is defined, "
00206 "but is not attached to a card input.")
00207 .arg(query.value(1).toString()));
00208 }
00209 else
00210 {
00211 numsources++;
00212 }
00213 }
00214
00215 if (!numsources)
00216 {
00217 LOG(VB_GENERAL, LOG_ERR, LOC +
00218 "No channel sources defined in the database");
00219 error = GENERIC_EXIT_SETUP_ERROR;
00220 return false;
00221 }
00222
00223 return true;
00224 }
00225
00226 static inline bool Recording(const RecordingInfo *p)
00227 {
00228 return (p->GetRecordingStatus() == rsRecording ||
00229 p->GetRecordingStatus() == rsTuning ||
00230 p->GetRecordingStatus() == rsWillRecord);
00231 }
00232
00233 static bool comp_overlap(RecordingInfo *a, RecordingInfo *b)
00234 {
00235 if (a->GetScheduledStartTime() != b->GetScheduledStartTime())
00236 return a->GetScheduledStartTime() < b->GetScheduledStartTime();
00237 if (a->GetScheduledEndTime() != b->GetScheduledEndTime())
00238 return a->GetScheduledEndTime() < b->GetScheduledEndTime();
00239
00240
00241 if (a->GetTitle() != b->GetTitle())
00242 return a->GetTitle() < b->GetTitle();
00243 if (a->GetChanID() != b->GetChanID())
00244 return a->GetChanID() < b->GetChanID();
00245 if (a->GetInputID() != b->GetInputID())
00246 return a->GetInputID() < b->GetInputID();
00247
00248
00249
00250
00251
00252
00253 int apri = RecTypePriority(a->GetRecordingRuleType());
00254 if (a->GetRecordingStatus() != rsUnknown &&
00255 a->GetRecordingStatus() != rsDontRecord)
00256 {
00257 apri += 100;
00258 }
00259 int bpri = RecTypePriority(b->GetRecordingRuleType());
00260 if (b->GetRecordingStatus() != rsUnknown &&
00261 b->GetRecordingStatus() != rsDontRecord)
00262 {
00263 bpri += 100;
00264 }
00265 if (apri != bpri)
00266 return apri < bpri;
00267
00268 if (a->GetFindID() != b->GetFindID())
00269 return a->GetFindID() > b->GetFindID();
00270 return a->GetRecordingRuleID() < b->GetRecordingRuleID();
00271 }
00272
00273 static bool comp_redundant(RecordingInfo *a, RecordingInfo *b)
00274 {
00275 if (a->GetScheduledStartTime() != b->GetScheduledStartTime())
00276 return a->GetScheduledStartTime() < b->GetScheduledStartTime();
00277 if (a->GetScheduledEndTime() != b->GetScheduledEndTime())
00278 return a->GetScheduledEndTime() < b->GetScheduledEndTime();
00279
00280
00281 int cmp = a->GetTitle().compare(b->GetTitle(), Qt::CaseInsensitive);
00282 if (cmp != 0)
00283 return cmp < 0;
00284 if (a->GetRecordingRuleID() != b->GetRecordingRuleID())
00285 return a->GetRecordingRuleID() < b->GetRecordingRuleID();
00286 cmp = a->GetChannelSchedulingID().compare(b->GetChannelSchedulingID(),
00287 Qt::CaseInsensitive);
00288 if (cmp != 0)
00289 return cmp < 0;
00290 if (a->GetRecordingStatus() != b->GetRecordingStatus())
00291 return a->GetRecordingStatus() < b->GetRecordingStatus();
00292 cmp = a->GetChanNum().compare(b->GetChanNum(), Qt::CaseInsensitive);
00293 return cmp < 0;
00294 }
00295
00296 static bool comp_recstart(RecordingInfo *a, RecordingInfo *b)
00297 {
00298 if (a->GetRecordingStartTime() != b->GetRecordingStartTime())
00299 return a->GetRecordingStartTime() < b->GetRecordingStartTime();
00300 if (a->GetRecordingEndTime() != b->GetRecordingEndTime())
00301 return a->GetRecordingEndTime() < b->GetRecordingEndTime();
00302 int cmp = a->GetChannelSchedulingID().compare(b->GetChannelSchedulingID(),
00303 Qt::CaseInsensitive);
00304 if (cmp != 0)
00305 return cmp < 0;
00306 if (a->GetRecordingStatus() != b->GetRecordingStatus())
00307 return a->GetRecordingStatus() < b->GetRecordingStatus();
00308 if (a->GetChanNum() != b->GetChanNum())
00309 return a->GetChanNum() < b->GetChanNum();
00310 return a->GetChanID() < b->GetChanID();
00311 }
00312
00313 static bool comp_priority(RecordingInfo *a, RecordingInfo *b)
00314 {
00315 int arec = (a->GetRecordingStatus() != rsRecording &&
00316 a->GetRecordingStatus() != rsTuning);
00317 int brec = (b->GetRecordingStatus() != rsRecording &&
00318 b->GetRecordingStatus() != rsTuning);
00319
00320 if (arec != brec)
00321 return arec < brec;
00322
00323 if (a->GetRecordingPriority() != b->GetRecordingPriority())
00324 return a->GetRecordingPriority() > b->GetRecordingPriority();
00325
00326 QDateTime pasttime = QDateTime::currentDateTime().addSecs(-30);
00327 int apast = (a->GetRecordingStartTime() < pasttime &&
00328 !a->IsReactivated());
00329 int bpast = (b->GetRecordingStartTime() < pasttime &&
00330 !b->IsReactivated());
00331
00332 if (apast != bpast)
00333 return apast < bpast;
00334
00335 int apri = RecTypePriority(a->GetRecordingRuleType());
00336 int bpri = RecTypePriority(b->GetRecordingRuleType());
00337
00338 if (apri != bpri)
00339 return apri < bpri;
00340
00341 if (a->GetRecordingStartTime() != b->GetRecordingStartTime())
00342 {
00343 if (apast)
00344 return a->GetRecordingStartTime() > b->GetRecordingStartTime();
00345 else
00346 return a->GetRecordingStartTime() < b->GetRecordingStartTime();
00347 }
00348
00349 if (a->GetRecordingPriority2() != b->GetRecordingPriority2())
00350 return a->GetRecordingPriority2() < b->GetRecordingPriority2();
00351
00352 if (a->GetInputID() != b->GetInputID())
00353 return a->GetInputID() < b->GetInputID();
00354
00355 return a->GetRecordingRuleID() < b->GetRecordingRuleID();
00356 }
00357
00358 bool Scheduler::FillRecordList(void)
00359 {
00360 schedMoveHigher = (bool)gCoreContext->GetNumSetting("SchedMoveHigher");
00361 schedTime = QDateTime::currentDateTime();
00362
00363 LOG(VB_SCHEDULE, LOG_INFO, "BuildWorkList...");
00364 BuildWorkList();
00365
00366 schedLock.unlock();
00367
00368 LOG(VB_SCHEDULE, LOG_INFO, "AddNewRecords...");
00369 AddNewRecords();
00370 LOG(VB_SCHEDULE, LOG_INFO, "AddNotListed...");
00371 AddNotListed();
00372
00373 LOG(VB_SCHEDULE, LOG_INFO, "Sort by time...");
00374 SORT_RECLIST(worklist, comp_overlap);
00375 LOG(VB_SCHEDULE, LOG_INFO, "PruneOverlaps...");
00376 PruneOverlaps();
00377
00378 LOG(VB_SCHEDULE, LOG_INFO, "Sort by priority...");
00379 SORT_RECLIST(worklist, comp_priority);
00380 LOG(VB_SCHEDULE, LOG_INFO, "BuildListMaps...");
00381 BuildListMaps();
00382 LOG(VB_SCHEDULE, LOG_INFO, "SchedNewRecords...");
00383 SchedNewRecords();
00384 LOG(VB_SCHEDULE, LOG_INFO, "SchedPreserveLiveTV...");
00385 SchedPreserveLiveTV();
00386 LOG(VB_SCHEDULE, LOG_INFO, "ClearListMaps...");
00387 ClearListMaps();
00388
00389 schedLock.lock();
00390
00391 LOG(VB_SCHEDULE, LOG_INFO, "Sort by time...");
00392 SORT_RECLIST(worklist, comp_redundant);
00393 LOG(VB_SCHEDULE, LOG_INFO, "PruneRedundants...");
00394 PruneRedundants();
00395
00396 LOG(VB_SCHEDULE, LOG_INFO, "Sort by time...");
00397 SORT_RECLIST(worklist, comp_recstart);
00398 LOG(VB_SCHEDULE, LOG_INFO, "ClearWorkList...");
00399 bool res = ClearWorkList();
00400
00401 return res;
00402 }
00403
00408 void Scheduler::FillRecordListFromDB(uint recordid)
00409 {
00410 struct timeval fillstart, fillend;
00411 float matchTime, checkTime, placeTime;
00412
00413 MSqlQuery query(dbConn);
00414 QString thequery;
00415 QString where = "";
00416
00417
00418 if (recordid == 0)
00419 where = "WHERE recordid IS NULL ";
00420
00421 thequery = QString("CREATE TEMPORARY TABLE recordmatch ") +
00422 "SELECT * FROM recordmatch " + where + "; ";
00423
00424 query.prepare(thequery);
00425 recordmatchLock.lock();
00426 bool ok = query.exec();
00427 recordmatchLock.unlock();
00428 if (!ok)
00429 {
00430 MythDB::DBError("FillRecordListFromDB", query);
00431 return;
00432 }
00433
00434 thequery = "ALTER TABLE recordmatch "
00435 " ADD UNIQUE INDEX (recordid, chanid, starttime); ";
00436 query.prepare(thequery);
00437 if (!query.exec())
00438 {
00439 MythDB::DBError("FillRecordListFromDB", query);
00440 return;
00441 }
00442
00443 thequery = "ALTER TABLE recordmatch "
00444 " ADD INDEX (chanid, starttime, manualid); ";
00445 query.prepare(thequery);
00446 if (!query.exec())
00447 {
00448 MythDB::DBError("FillRecordListFromDB", query);
00449 return;
00450 }
00451
00452 QMutexLocker locker(&schedLock);
00453
00454 gettimeofday(&fillstart, NULL);
00455 UpdateMatches(recordid, 0, 0, QDateTime());
00456 gettimeofday(&fillend, NULL);
00457 matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
00458 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
00459
00460 LOG(VB_SCHEDULE, LOG_INFO, "CreateTempTables...");
00461 CreateTempTables();
00462
00463 gettimeofday(&fillstart, NULL);
00464 LOG(VB_SCHEDULE, LOG_INFO, "UpdateDuplicates...");
00465 UpdateDuplicates();
00466 gettimeofday(&fillend, NULL);
00467 checkTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
00468 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
00469
00470 gettimeofday(&fillstart, NULL);
00471 FillRecordList();
00472 gettimeofday(&fillend, NULL);
00473 placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
00474 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
00475
00476 LOG(VB_SCHEDULE, LOG_INFO, "DeleteTempTables...");
00477 DeleteTempTables();
00478
00479 MSqlQuery queryDrop(dbConn);
00480 queryDrop.prepare("DROP TABLE recordmatch;");
00481 if (!queryDrop.exec())
00482 {
00483 MythDB::DBError("FillRecordListFromDB", queryDrop);
00484 return;
00485 }
00486
00487 QString msg;
00488 msg.sprintf("Speculative scheduled %d items in %.1f "
00489 "= %.2f match + %.2f check + %.2f place",
00490 (int)reclist.size(),
00491 matchTime + checkTime + placeTime,
00492 matchTime, checkTime, placeTime);
00493 LOG(VB_GENERAL, LOG_INFO, msg);
00494 }
00495
00496 void Scheduler::FillRecordListFromMaster(void)
00497 {
00498 RecordingList schedList(false);
00499 bool dummy;
00500 LoadFromScheduler(schedList, dummy);
00501
00502 QMutexLocker lockit(&schedLock);
00503
00504 RecordingList::iterator it = schedList.begin();
00505 for (; it != schedList.end(); ++it)
00506 reclist.push_back(*it);
00507 }
00508
00509 void Scheduler::PrintList(RecList &list, bool onlyFutureRecordings)
00510 {
00511 if (!VERBOSE_LEVEL_CHECK(VB_SCHEDULE, LOG_DEBUG))
00512 return;
00513
00514 QDateTime now = QDateTime::currentDateTime();
00515
00516 LOG(VB_SCHEDULE, LOG_INFO, "--- print list start ---");
00517 LOG(VB_SCHEDULE, LOG_INFO, "Title - Subtitle Ch Station "
00518 "Day Start End S C I T N Pri");
00519
00520 RecIter i = list.begin();
00521 for ( ; i != list.end(); ++i)
00522 {
00523 RecordingInfo *first = (*i);
00524
00525 if (onlyFutureRecordings &&
00526 ((first->GetRecordingEndTime() < now &&
00527 first->GetScheduledEndTime() < now) ||
00528 (first->GetRecordingStartTime() < now && !Recording(first))))
00529 continue;
00530
00531 PrintRec(first);
00532 }
00533
00534 LOG(VB_SCHEDULE, LOG_INFO, "--- print list end ---");
00535 }
00536
00537 void Scheduler::PrintRec(const RecordingInfo *p, const char *prefix)
00538 {
00539 if (!VERBOSE_LEVEL_CHECK(VB_SCHEDULE, LOG_DEBUG))
00540 return;
00541
00542 QString outstr;
00543
00544 if (prefix)
00545 outstr = QString(prefix);
00546
00547 QString episode = p->toString(ProgramInfo::kTitleSubtitle, " - ", "");
00548 episode = episode.leftJustified(34 - (prefix ? strlen(prefix) : 0),
00549 ' ', true);
00550
00551 outstr += QString("%1 %2 %3 %4-%5 %6 %7 %8 ")
00552 .arg(episode)
00553 .arg(p->GetChanNum().rightJustified(4, ' '))
00554 .arg(p->GetChannelSchedulingID().leftJustified(7, ' ', true))
00555 .arg(p->GetRecordingStartTime().toString("dd hh:mm"))
00556 .arg(p->GetRecordingEndTime().toString("hh:mm"))
00557 .arg(p->GetSourceID())
00558 .arg(p->GetCardID())
00559 .arg(p->GetInputID());
00560 outstr += QString("%1 %2 %3")
00561 .arg(toQChar(p->GetRecordingRuleType()))
00562 .arg(toString(p->GetRecordingStatus(), p->GetCardID()))
00563 .arg(p->GetRecordingPriority());
00564 if (p->GetRecordingPriority2())
00565 outstr += QString("/%1").arg(p->GetRecordingPriority2());
00566
00567 LOG(VB_SCHEDULE, LOG_INFO, outstr);
00568 }
00569
00570 void Scheduler::UpdateRecStatus(RecordingInfo *pginfo)
00571 {
00572 QMutexLocker lockit(&schedLock);
00573
00574 RecIter dreciter = reclist.begin();
00575 for (; dreciter != reclist.end(); ++dreciter)
00576 {
00577 RecordingInfo *p = *dreciter;
00578 if (p->IsSameProgramTimeslot(*pginfo))
00579 {
00580
00581
00582
00583
00584 if (pginfo->GetRecordingStatus() == rsUnknown)
00585 {
00586 if (p->GetRecordingStatus() == rsTuning)
00587 pginfo->SetRecordingStatus(rsFailed);
00588 else if (p->GetRecordingStatus() == rsRecording)
00589 pginfo->SetRecordingStatus(rsRecorded);
00590 else
00591 pginfo->SetRecordingStatus(p->GetRecordingStatus());
00592 }
00593
00594 if (p->GetRecordingStatus() != pginfo->GetRecordingStatus())
00595 {
00596 LOG(VB_GENERAL, LOG_INFO,
00597 QString("Updating status for %1 on cardid %2 (%3 => %4)")
00598 .arg(p->toString(ProgramInfo::kTitleSubtitle))
00599 .arg(p->GetCardID())
00600 .arg(toString(p->GetRecordingStatus(),
00601 p->GetRecordingRuleType()))
00602 .arg(toString(pginfo->GetRecordingStatus(),
00603 p->GetRecordingRuleType())));
00604 bool resched =
00605 ((p->GetRecordingStatus() != rsRecording &&
00606 p->GetRecordingStatus() != rsTuning) ||
00607 (pginfo->GetRecordingStatus() != rsRecording &&
00608 pginfo->GetRecordingStatus() != rsTuning));
00609 p->SetRecordingStatus(pginfo->GetRecordingStatus());
00610 reclist_changed = true;
00611 p->AddHistory(false);
00612 if (resched)
00613 {
00614 EnqueueCheck(*p, "UpdateRecStatus1");
00615 reschedWait.wakeOne();
00616 }
00617 else
00618 {
00619 MythEvent me("SCHEDULE_CHANGE");
00620 gCoreContext->dispatch(me);
00621 }
00622 }
00623 return;
00624 }
00625 }
00626 }
00627
00628 void Scheduler::UpdateRecStatus(uint cardid, uint chanid,
00629 const QDateTime &startts,
00630 RecStatusType recstatus,
00631 const QDateTime &recendts)
00632 {
00633 QMutexLocker lockit(&schedLock);
00634
00635 RecIter dreciter = reclist.begin();
00636 for (; dreciter != reclist.end(); ++dreciter)
00637 {
00638 RecordingInfo *p = *dreciter;
00639 if (p->GetCardID() == cardid && p->GetChanID() == chanid &&
00640 p->GetScheduledStartTime() == startts)
00641 {
00642 p->SetRecordingEndTime(recendts);
00643
00644 if (p->GetRecordingStatus() != recstatus)
00645 {
00646 LOG(VB_GENERAL, LOG_INFO,
00647 QString("Updating status for %1 on cardid %2 (%3 => %4)")
00648 .arg(p->toString(ProgramInfo::kTitleSubtitle))
00649 .arg(p->GetCardID())
00650 .arg(toString(p->GetRecordingStatus(),
00651 p->GetRecordingRuleType()))
00652 .arg(toString(recstatus,
00653 p->GetRecordingRuleType())));
00654 bool resched =
00655 ((p->GetRecordingStatus() != rsRecording &&
00656 p->GetRecordingStatus() != rsTuning) ||
00657 (recstatus != rsRecording &&
00658 recstatus != rsTuning));
00659 p->SetRecordingStatus(recstatus);
00660 reclist_changed = true;
00661 p->AddHistory(false);
00662 if (resched)
00663 {
00664 EnqueueCheck(*p, "UpdateRecStatus2");
00665 reschedWait.wakeOne();
00666 }
00667 else
00668 {
00669 MythEvent me("SCHEDULE_CHANGE");
00670 gCoreContext->dispatch(me);
00671 }
00672 }
00673 return;
00674 }
00675 }
00676 }
00677
00678 bool Scheduler::ChangeRecordingEnd(RecordingInfo *oldp, RecordingInfo *newp)
00679 {
00680 QMutexLocker lockit(&schedLock);
00681
00682 if (reclist_changed)
00683 return false;
00684
00685 RecordingType oldrectype = oldp->GetRecordingRuleType();
00686 uint oldrecordid = oldp->GetRecordingRuleID();
00687 QDateTime oldrecendts = oldp->GetRecordingEndTime();
00688
00689 oldp->SetRecordingRuleType(newp->GetRecordingRuleType());
00690 oldp->SetRecordingRuleID(newp->GetRecordingRuleID());
00691 oldp->SetRecordingEndTime(newp->GetRecordingEndTime());
00692
00693 if (specsched)
00694 {
00695 if (newp->GetRecordingEndTime() < QDateTime::currentDateTime())
00696 {
00697 oldp->SetRecordingStatus(rsRecorded);
00698 newp->SetRecordingStatus(rsRecorded);
00699 return false;
00700 }
00701 else
00702 return true;
00703 }
00704
00705 EncoderLink *tv = (*m_tvList)[oldp->GetCardID()];
00706 RecStatusType rs = tv->StartRecording(oldp);
00707 if (rs != rsRecording)
00708 {
00709 LOG(VB_GENERAL, LOG_ERR,
00710 QString("Failed to change end time on card %1 to %2")
00711 .arg(oldp->GetCardID())
00712 .arg(newp->GetRecordingEndTime(ISODate)));
00713 oldp->SetRecordingRuleType(oldrectype);
00714 oldp->SetRecordingRuleID(oldrecordid);
00715 oldp->SetRecordingEndTime(oldrecendts);
00716 }
00717 else
00718 {
00719 RecIter i = reclist.begin();
00720 for (; i != reclist.end(); ++i)
00721 {
00722 RecordingInfo *recp = *i;
00723 if (recp->IsSameTimeslot(*oldp))
00724 {
00725 *recp = *oldp;
00726 break;
00727 }
00728 }
00729 }
00730
00731 return rs == rsRecording;
00732 }
00733
00734 void Scheduler::SlaveConnected(RecordingList &slavelist)
00735 {
00736 QMutexLocker lockit(&schedLock);
00737
00738 RecordingList::iterator it = slavelist.begin();
00739 for (; it != slavelist.end(); ++it)
00740 {
00741 RecordingInfo *sp = *it;
00742 bool found = false;
00743
00744 RecIter ri = reclist.begin();
00745 for ( ; ri != reclist.end(); ++ri)
00746 {
00747 RecordingInfo *rp = *ri;
00748
00749 if (sp->GetInputID() &&
00750 sp->GetScheduledStartTime() == rp->GetScheduledStartTime() &&
00751 sp->GetChannelSchedulingID().compare(
00752 rp->GetChannelSchedulingID(), Qt::CaseInsensitive) == 0 &&
00753 sp->GetTitle().compare(rp->GetTitle(),
00754 Qt::CaseInsensitive) == 0)
00755 {
00756 if (sp->GetCardID() == rp->GetCardID())
00757 {
00758 found = true;
00759 rp->SetRecordingStatus(sp->GetRecordingStatus());
00760 reclist_changed = true;
00761 rp->AddHistory(false);
00762 LOG(VB_GENERAL, LOG_INFO,
00763 QString("setting %1/%2/\"%3\" as %4")
00764 .arg(sp->GetCardID())
00765 .arg(sp->GetChannelSchedulingID())
00766 .arg(sp->GetTitle())
00767 .arg(toUIState(sp->GetRecordingStatus())));
00768 }
00769 else
00770 {
00771 LOG(VB_GENERAL, LOG_NOTICE,
00772 QString("%1/%2/\"%3\" is already recording on card %4")
00773 .arg(sp->GetCardID())
00774 .arg(sp->GetChannelSchedulingID())
00775 .arg(sp->GetTitle())
00776 .arg(rp->GetCardID()));
00777 }
00778 }
00779 else if (sp->GetCardID() == rp->GetCardID() &&
00780 (rp->GetRecordingStatus() == rsRecording ||
00781 rp->GetRecordingStatus() == rsTuning))
00782 {
00783 rp->SetRecordingStatus(rsAborted);
00784 reclist_changed = true;
00785 rp->AddHistory(false);
00786 LOG(VB_GENERAL, LOG_INFO,
00787 QString("setting %1/%2/\"%3\" as aborted")
00788 .arg(rp->GetCardID())
00789 .arg(rp->GetChannelSchedulingID())
00790 .arg(rp->GetTitle()));
00791 }
00792 }
00793
00794 if (sp->GetInputID() && !found)
00795 {
00796 reclist.push_back(new RecordingInfo(*sp));
00797 reclist_changed = true;
00798 sp->AddHistory(false);
00799 LOG(VB_GENERAL, LOG_INFO,
00800 QString("adding %1/%2/\"%3\" as recording")
00801 .arg(sp->GetCardID())
00802 .arg(sp->GetChannelSchedulingID())
00803 .arg(sp->GetTitle()));
00804 }
00805 }
00806 }
00807
00808 void Scheduler::SlaveDisconnected(uint cardid)
00809 {
00810 QMutexLocker lockit(&schedLock);
00811
00812 RecIter ri = reclist.begin();
00813 for ( ; ri != reclist.end(); ++ri)
00814 {
00815 RecordingInfo *rp = *ri;
00816
00817 if (rp->GetCardID() == cardid &&
00818 (rp->GetRecordingStatus() == rsRecording ||
00819 rp->GetRecordingStatus() == rsTuning))
00820 {
00821 rp->SetRecordingStatus(rsAborted);
00822 reclist_changed = true;
00823 rp->AddHistory(false);
00824 LOG(VB_GENERAL, LOG_INFO, QString("setting %1/%2/\"%3\" as aborted")
00825 .arg(rp->GetCardID()).arg(rp->GetChannelSchedulingID())
00826 .arg(rp->GetTitle()));
00827 }
00828 }
00829 }
00830
00831 void Scheduler::BuildWorkList(void)
00832 {
00833 reclist_changed = false;
00834
00835 RecIter i = reclist.begin();
00836 for (; i != reclist.end(); ++i)
00837 {
00838 RecordingInfo *p = *i;
00839 if (p->GetRecordingStatus() == rsRecording ||
00840 p->GetRecordingStatus() == rsTuning)
00841 worklist.push_back(new RecordingInfo(*p));
00842 }
00843 }
00844
00845 bool Scheduler::ClearWorkList(void)
00846 {
00847 RecordingInfo *p;
00848
00849 if (reclist_changed)
00850 {
00851 while (!worklist.empty())
00852 {
00853 p = worklist.front();
00854 delete p;
00855 worklist.pop_front();
00856 }
00857
00858 return false;
00859 }
00860
00861 while (!reclist.empty())
00862 {
00863 p = reclist.front();
00864 delete p;
00865 reclist.pop_front();
00866 }
00867
00868 while (!worklist.empty())
00869 {
00870 p = worklist.front();
00871 reclist.push_back(p);
00872 worklist.pop_front();
00873 }
00874
00875 return true;
00876 }
00877
00878 static void erase_nulls(RecList &reclist)
00879 {
00880 RecIter it = reclist.begin();
00881 uint dst = 0;
00882 for (it = reclist.begin(); it != reclist.end(); ++it)
00883 {
00884 if (*it)
00885 {
00886 reclist[dst] = *it;
00887 dst++;
00888 }
00889 }
00890 reclist.resize(dst);
00891 }
00892
00893 void Scheduler::PruneOverlaps(void)
00894 {
00895 RecordingInfo *lastp = NULL;
00896
00897 RecIter dreciter = worklist.begin();
00898 while (dreciter != worklist.end())
00899 {
00900 RecordingInfo *p = *dreciter;
00901 if (!lastp || lastp->GetRecordingRuleID() == p->GetRecordingRuleID() ||
00902 !lastp->IsSameTimeslot(*p))
00903 {
00904 lastp = p;
00905 ++dreciter;
00906 }
00907 else
00908 {
00909 delete p;
00910 *(dreciter++) = NULL;
00911 }
00912 }
00913
00914 erase_nulls(worklist);
00915 }
00916
00917 void Scheduler::BuildListMaps(void)
00918 {
00919 RecIter i = worklist.begin();
00920 for ( ; i != worklist.end(); ++i)
00921 {
00922 RecordingInfo *p = *i;
00923 if (p->GetRecordingStatus() == rsRecording ||
00924 p->GetRecordingStatus() == rsTuning ||
00925 p->GetRecordingStatus() == rsWillRecord ||
00926 p->GetRecordingStatus() == rsUnknown)
00927 {
00928 conflictlist.push_back(p);
00929 titlelistmap[p->GetTitle().toLower()].push_back(p);
00930 recordidlistmap[p->GetRecordingRuleID()].push_back(p);
00931 }
00932 }
00933 }
00934
00935 void Scheduler::ClearListMaps(void)
00936 {
00937 conflictlist.clear();
00938 titlelistmap.clear();
00939 recordidlistmap.clear();
00940 cache_is_same_program.clear();
00941 }
00942
00943 bool Scheduler::IsSameProgram(
00944 const RecordingInfo *a, const RecordingInfo *b) const
00945 {
00946 IsSameKey X(a,b);
00947 IsSameCacheType::const_iterator it = cache_is_same_program.find(X);
00948 if (it != cache_is_same_program.end())
00949 return *it;
00950
00951 IsSameKey Y(b,a);
00952 it = cache_is_same_program.find(Y);
00953 if (it != cache_is_same_program.end())
00954 return *it;
00955
00956 return cache_is_same_program[X] = a->IsSameProgram(*b);
00957 }
00958
00959 bool Scheduler::FindNextConflict(
00960 const RecList &cardlist,
00961 const RecordingInfo *p,
00962 RecConstIter &j,
00963 int openEnd) const
00964 {
00965 for ( ; j != cardlist.end(); ++j)
00966 {
00967 const RecordingInfo *q = *j;
00968 QString msg;
00969
00970 if (p == q)
00971 continue;
00972
00973 if (!Recording(q))
00974 continue;
00975
00976 if (debugConflicts)
00977 msg = QString("comparing with '%1' ").arg(q->GetTitle());
00978
00979 if (p->GetCardID() != q->GetCardID() &&
00980 !igrp.GetSharedInputGroup(p->GetInputID(), q->GetInputID()))
00981 {
00982 if (debugConflicts)
00983 msg += " cardid== ";
00984 continue;
00985 }
00986
00987 if (openEnd == 2 || (openEnd == 1 && p->GetChanID() != q->GetChanID()))
00988 {
00989 if (p->GetRecordingEndTime() < q->GetRecordingStartTime() ||
00990 p->GetRecordingStartTime() > q->GetRecordingEndTime())
00991 {
00992 if (debugConflicts)
00993 msg += " no-overlap ";
00994 continue;
00995 }
00996 }
00997 else
00998 {
00999 if (p->GetRecordingEndTime() <= q->GetRecordingStartTime() ||
01000 p->GetRecordingStartTime() >= q->GetRecordingEndTime())
01001 {
01002 if (debugConflicts)
01003 msg += " no-overlap ";
01004 continue;
01005 }
01006 }
01007
01008 if (debugConflicts)
01009 {
01010 LOG(VB_SCHEDULE, LOG_INFO, msg);
01011 LOG(VB_SCHEDULE, LOG_INFO,
01012 QString(" cardid's: %1, %2 Shared input group: %3 "
01013 "mplexid's: %4, %5")
01014 .arg(p->GetCardID()).arg(q->GetCardID())
01015 .arg(igrp.GetSharedInputGroup(
01016 p->GetInputID(), q->GetInputID()))
01017 .arg(p->QueryMplexID()).arg(q->QueryMplexID()));
01018 }
01019
01020
01021
01022 if (p->GetCardID() != q->GetCardID())
01023 {
01024 uint p_mplexid = p->QueryMplexID();
01025 if (p_mplexid && (p_mplexid == q->QueryMplexID()))
01026 continue;
01027 }
01028
01029 if (debugConflicts)
01030 LOG(VB_SCHEDULE, LOG_INFO, "Found conflict");
01031
01032 return true;
01033 }
01034
01035 if (debugConflicts)
01036 LOG(VB_SCHEDULE, LOG_INFO, "No conflict");
01037
01038 return false;
01039 }
01040
01041 const RecordingInfo *Scheduler::FindConflict(
01042 const RecordingInfo *p,
01043 int openend) const
01044 {
01045 RecConstIter k = conflictlist.begin();
01046 if (FindNextConflict(conflictlist, p, k, openend))
01047 return *k;
01048
01049 return NULL;
01050 }
01051
01052 void Scheduler::MarkOtherShowings(RecordingInfo *p)
01053 {
01054 RecList *showinglist;
01055
01056 showinglist = &titlelistmap[p->GetTitle().toLower()];
01057 MarkShowingsList(*showinglist, p);
01058
01059 if (p->GetRecordingRuleType() == kFindOneRecord ||
01060 p->GetRecordingRuleType() == kFindDailyRecord ||
01061 p->GetRecordingRuleType() == kFindWeeklyRecord)
01062 {
01063 showinglist = &recordidlistmap[p->GetRecordingRuleID()];
01064 MarkShowingsList(*showinglist, p);
01065 }
01066 else if (p->GetRecordingRuleType() == kOverrideRecord && p->GetFindID())
01067 {
01068 showinglist = &recordidlistmap[p->GetParentRecordingRuleID()];
01069 MarkShowingsList(*showinglist, p);
01070 }
01071 }
01072
01073 void Scheduler::MarkShowingsList(RecList &showinglist, RecordingInfo *p)
01074 {
01075 RecIter i = showinglist.begin();
01076 for ( ; i != showinglist.end(); ++i)
01077 {
01078 RecordingInfo *q = *i;
01079 if (q == p)
01080 continue;
01081 if (q->GetRecordingStatus() != rsUnknown &&
01082 q->GetRecordingStatus() != rsWillRecord &&
01083 q->GetRecordingStatus() != rsEarlierShowing &&
01084 q->GetRecordingStatus() != rsLaterShowing)
01085 continue;
01086 if (q->IsSameTimeslot(*p))
01087 q->SetRecordingStatus(rsLaterShowing);
01088 else if (q->GetRecordingRuleType() != kSingleRecord &&
01089 q->GetRecordingRuleType() != kOverrideRecord &&
01090 IsSameProgram(q,p))
01091 {
01092 if (q->GetRecordingStartTime() < p->GetRecordingStartTime())
01093 q->SetRecordingStatus(rsLaterShowing);
01094 else
01095 q->SetRecordingStatus(rsEarlierShowing);
01096 }
01097 }
01098 }
01099
01100 void Scheduler::BackupRecStatus(void)
01101 {
01102 RecIter i = worklist.begin();
01103 for ( ; i != worklist.end(); ++i)
01104 {
01105 RecordingInfo *p = *i;
01106 p->savedrecstatus = p->GetRecordingStatus();
01107 }
01108 }
01109
01110 void Scheduler::RestoreRecStatus(void)
01111 {
01112 RecIter i = worklist.begin();
01113 for ( ; i != worklist.end(); ++i)
01114 {
01115 RecordingInfo *p = *i;
01116 p->SetRecordingStatus(p->savedrecstatus);
01117 }
01118 }
01119
01120 bool Scheduler::TryAnotherShowing(RecordingInfo *p, bool samePriority,
01121 bool preserveLive)
01122 {
01123 PrintRec(p, " >");
01124
01125 if (p->GetRecordingStatus() == rsRecording ||
01126 p->GetRecordingStatus() == rsTuning)
01127 return false;
01128
01129 RecList *showinglist = &recordidlistmap[p->GetRecordingRuleID()];
01130
01131 RecStatusType oldstatus = p->GetRecordingStatus();
01132 p->SetRecordingStatus(rsLaterShowing);
01133
01134 bool hasLaterShowing = false;
01135
01136 RecIter j = showinglist->begin();
01137 for ( ; j != showinglist->end(); ++j)
01138 {
01139 RecordingInfo *q = *j;
01140 if (q == p)
01141 continue;
01142
01143 if (samePriority &&
01144 (q->GetRecordingPriority() < p->GetRecordingPriority()))
01145 {
01146 continue;
01147 }
01148
01149 hasLaterShowing = false;
01150
01151 if (q->GetRecordingStatus() != rsEarlierShowing &&
01152 q->GetRecordingStatus() != rsLaterShowing &&
01153 q->GetRecordingStatus() != rsUnknown)
01154 {
01155 continue;
01156 }
01157
01158 if (!p->IsSameTimeslot(*q))
01159 {
01160 if (!IsSameProgram(p,q))
01161 continue;
01162 if ((p->GetRecordingRuleType() == kSingleRecord ||
01163 p->GetRecordingRuleType() == kOverrideRecord))
01164 continue;
01165 if (q->GetRecordingStartTime() < schedTime &&
01166 p->GetRecordingStartTime() >= schedTime)
01167 continue;
01168
01169 hasLaterShowing |= preserveLive;
01170 }
01171
01172 if (samePriority)
01173 PrintRec(q, " %");
01174 else
01175 PrintRec(q, " $");
01176
01177 bool failedLiveCheck = false;
01178 if (preserveLive)
01179 {
01180 failedLiveCheck |=
01181 (!livetvpriority ||
01182 p->GetRecordingPriority() - prefinputpri >
01183 q->GetRecordingPriority());
01184
01185
01186
01187 RecConstIter k = retrylist.begin();
01188 if (FindNextConflict(retrylist, q, k))
01189 {
01190 PrintRec(*k, " L!");
01191 continue;
01192 }
01193 }
01194
01195 const RecordingInfo *conflict = FindConflict(q);
01196 if (conflict)
01197 {
01198 PrintRec(conflict, " !");
01199 continue;
01200 }
01201
01202 if (hasLaterShowing)
01203 {
01204 QString id = p->MakeUniqueSchedulerKey();
01205 hasLaterList[id] = true;
01206 continue;
01207 }
01208
01209 if (failedLiveCheck)
01210 {
01211
01212
01213
01214
01215 bool equiv = (p->GetSourceID() == q->GetSourceID() &&
01216 igrp.GetSharedInputGroup(
01217 p->GetInputID(), q->GetInputID()));
01218
01219 if (!equiv)
01220 continue;
01221 }
01222
01223 if (preserveLive)
01224 {
01225 QString msg = QString(
01226 "Moved \"%1\" on chanid: %2 from card: %3 to %4 "
01227 "to avoid LiveTV conflict")
01228 .arg(p->GetTitle()).arg(p->GetChanID())
01229 .arg(p->GetCardID()).arg(q->GetCardID());
01230 LOG(VB_SCHEDULE, LOG_INFO, msg);
01231 }
01232
01233 q->SetRecordingStatus(rsWillRecord);
01234 MarkOtherShowings(q);
01235 PrintRec(p, " -");
01236 PrintRec(q, " +");
01237 return true;
01238 }
01239
01240 p->SetRecordingStatus(oldstatus);
01241 return false;
01242 }
01243
01244 void Scheduler::SchedNewRecords(void)
01245 {
01246 if (VERBOSE_LEVEL_CHECK(VB_SCHEDULE, LOG_DEBUG))
01247 {
01248 LOG(VB_SCHEDULE, LOG_DEBUG,
01249 "+ = schedule this showing to be recorded");
01250 LOG(VB_SCHEDULE, LOG_DEBUG,
01251 "# = could not schedule this showing, retry later");
01252 LOG(VB_SCHEDULE, LOG_DEBUG,
01253 "! = conflict caused by this showing");
01254 LOG(VB_SCHEDULE, LOG_DEBUG,
01255 "/ = retry this showing, same priority pass");
01256 LOG(VB_SCHEDULE, LOG_DEBUG,
01257 "? = retry this showing, lower priority pass");
01258 LOG(VB_SCHEDULE, LOG_DEBUG,
01259 "> = try another showing for this program");
01260 LOG(VB_SCHEDULE, LOG_DEBUG,
01261 "% = found another showing, same priority required");
01262 LOG(VB_SCHEDULE, LOG_DEBUG,
01263 "$ = found another showing, lower priority allowed");
01264 LOG(VB_SCHEDULE, LOG_DEBUG,
01265 "- = unschedule a showing in favor of another one");
01266 }
01267
01268 int openEnd = gCoreContext->GetNumSetting("SchedOpenEnd", 0);
01269
01270 RecIter i = worklist.begin();
01271 while (i != worklist.end())
01272 {
01273 RecordingInfo *p = *i;
01274 if (p->GetRecordingStatus() == rsRecording ||
01275 p->GetRecordingStatus() == rsTuning)
01276 MarkOtherShowings(p);
01277 else if (p->GetRecordingStatus() == rsUnknown)
01278 {
01279 const RecordingInfo *conflict = FindConflict(p, openEnd);
01280 if (!conflict)
01281 {
01282 p->SetRecordingStatus(rsWillRecord);
01283
01284 if (p->GetRecordingStartTime() < schedTime.addSecs(90))
01285 {
01286 QString id = p->MakeUniqueSchedulerKey();
01287 if (!recPendingList.contains(id))
01288 recPendingList[id] = false;
01289
01290 livetvTime = (livetvTime < schedTime) ?
01291 schedTime : livetvTime;
01292 }
01293
01294 MarkOtherShowings(p);
01295 PrintRec(p, " +");
01296 }
01297 else
01298 {
01299 retrylist.push_front(p);
01300 PrintRec(p, " #");
01301 PrintRec(conflict, " !");
01302 }
01303 }
01304
01305 int lastpri = p->GetRecordingPriority();
01306 ++i;
01307 if (i == worklist.end() || lastpri != (*i)->GetRecordingPriority())
01308 {
01309 MoveHigherRecords();
01310 retrylist.clear();
01311 }
01312 }
01313 }
01314
01315 void Scheduler::MoveHigherRecords(bool move_this)
01316 {
01317 RecIter i = retrylist.begin();
01318 for ( ; move_this && i != retrylist.end(); ++i)
01319 {
01320 RecordingInfo *p = *i;
01321 if (p->GetRecordingStatus() != rsUnknown)
01322 continue;
01323
01324 PrintRec(p, " /");
01325
01326 BackupRecStatus();
01327 p->SetRecordingStatus(rsWillRecord);
01328 MarkOtherShowings(p);
01329
01330 RecConstIter k = conflictlist.begin();
01331 for ( ; FindNextConflict(conflictlist, p, k); ++k)
01332 {
01333 if (!TryAnotherShowing(*k, true))
01334 {
01335 RestoreRecStatus();
01336 break;
01337 }
01338 }
01339
01340 if (p->GetRecordingStatus() == rsWillRecord)
01341 PrintRec(p, " +");
01342 }
01343
01344 i = retrylist.begin();
01345 for ( ; i != retrylist.end(); ++i)
01346 {
01347 RecordingInfo *p = *i;
01348 if (p->GetRecordingStatus() != rsUnknown)
01349 continue;
01350
01351 PrintRec(p, " ?");
01352
01353 if (move_this && TryAnotherShowing(p, false))
01354 continue;
01355
01356 BackupRecStatus();
01357 p->SetRecordingStatus(rsWillRecord);
01358 if (move_this)
01359 MarkOtherShowings(p);
01360
01361 RecConstIter k = conflictlist.begin();
01362 for ( ; FindNextConflict(conflictlist, p, k); ++k)
01363 {
01364 if ((p->GetRecordingPriority() < (*k)->GetRecordingPriority() &&
01365 !schedMoveHigher && move_this) ||
01366 !TryAnotherShowing(*k, false, !move_this))
01367 {
01368 RestoreRecStatus();
01369 break;
01370 }
01371 }
01372
01373 if (move_this && p->GetRecordingStatus() == rsWillRecord)
01374 PrintRec(p, " +");
01375 }
01376 }
01377
01378 void Scheduler::PruneRedundants(void)
01379 {
01380 RecordingInfo *lastp = NULL;
01381
01382 RecIter i = worklist.begin();
01383 while (i != worklist.end())
01384 {
01385 RecordingInfo *p = *i;
01386
01387
01388
01389 if (p->GetRecordingStatus() != rsRecording &&
01390 p->GetRecordingStatus() != rsTuning &&
01391 p->GetRecordingStatus() != rsMissedFuture &&
01392 p->GetScheduledEndTime() < schedTime &&
01393 p->GetRecordingEndTime() < schedTime)
01394 {
01395 delete p;
01396 *(i++) = NULL;
01397 continue;
01398 }
01399
01400
01401 if (p->GetRecordingStatus() == rsUnknown)
01402 p->SetRecordingStatus(rsConflict);
01403
01404
01405 if (p->GetRecordingStatus() == rsMissedFuture ||
01406 (p->GetRecordingStatus() == rsMissed &&
01407 p->oldrecstatus != rsUnknown) ||
01408 (p->GetRecordingStatus() == rsCurrentRecording &&
01409 p->oldrecstatus == rsPreviousRecording && !p->future) ||
01410 (p->GetRecordingStatus() != rsWillRecord &&
01411 p->oldrecstatus == rsAborted))
01412 {
01413 RecStatusType rs = p->GetRecordingStatus();
01414 p->SetRecordingStatus(p->oldrecstatus);
01415
01416
01417 if (rs == rsMissedFuture)
01418 p->oldrecstatus = rsMissedFuture;
01419 }
01420
01421 if (!Recording(p))
01422 {
01423 p->SetCardID(0);
01424 p->SetInputID(0);
01425 }
01426
01427
01428 if (!lastp || lastp->GetRecordingRuleID() != p->GetRecordingRuleID() ||
01429 !lastp->IsSameTimeslot(*p))
01430 {
01431 lastp = p;
01432 lastp->SetRecordingPriority2(0);
01433 ++i;
01434 }
01435 else
01436 {
01437
01438
01439 if (lastp->GetRecordingStatus() == rsWillRecord &&
01440 p->GetRecordingPriority() >
01441 lastp->GetRecordingPriority() - lastp->GetRecordingPriority2())
01442 {
01443 lastp->SetRecordingPriority2(
01444 lastp->GetRecordingPriority() - p->GetRecordingPriority());
01445 }
01446 delete p;
01447 *(i++) = NULL;
01448 }
01449 }
01450
01451 erase_nulls(worklist);
01452 }
01453
01454 void Scheduler::UpdateNextRecord(void)
01455 {
01456 if (specsched)
01457 return;
01458
01459 QMap<int, QDateTime> nextRecMap;
01460
01461 RecIter i = reclist.begin();
01462 while (i != reclist.end())
01463 {
01464 RecordingInfo *p = *i;
01465 if (p->GetRecordingStatus() == rsWillRecord &&
01466 nextRecMap[p->GetRecordingRuleID()].isNull())
01467 {
01468 nextRecMap[p->GetRecordingRuleID()] = p->GetRecordingStartTime();
01469 }
01470
01471 if (p->GetRecordingRuleType() == kOverrideRecord &&
01472 p->GetParentRecordingRuleID() > 0 &&
01473 p->GetRecordingStatus() == rsWillRecord &&
01474 nextRecMap[p->GetParentRecordingRuleID()].isNull())
01475 {
01476 nextRecMap[p->GetParentRecordingRuleID()] =
01477 p->GetRecordingStartTime();
01478 }
01479 ++i;
01480 }
01481
01482 MSqlQuery query(dbConn);
01483 query.prepare("SELECT recordid, next_record FROM record;");
01484
01485 if (query.exec() && query.isActive())
01486 {
01487 MSqlQuery subquery(dbConn);
01488
01489 while (query.next())
01490 {
01491 int recid = query.value(0).toInt();
01492 QDateTime next_record = query.value(1).toDateTime();
01493
01494 if (next_record == nextRecMap[recid])
01495 continue;
01496
01497 if (nextRecMap[recid].isValid())
01498 {
01499 subquery.prepare("UPDATE record SET next_record = :NEXTREC "
01500 "WHERE recordid = :RECORDID;");
01501 subquery.bindValue(":RECORDID", recid);
01502 subquery.bindValue(":NEXTREC", nextRecMap[recid]);
01503 if (!subquery.exec())
01504 MythDB::DBError("Update next_record", subquery);
01505 }
01506 else if (next_record.isValid())
01507 {
01508 subquery.prepare("UPDATE record "
01509 "SET next_record = '0000-00-00 00:00:00' "
01510 "WHERE recordid = :RECORDID;");
01511 subquery.bindValue(":RECORDID", recid);
01512 if (!subquery.exec())
01513 MythDB::DBError("Clear next_record", subquery);
01514 }
01515 }
01516 }
01517 }
01518
01519 void Scheduler::getConflicting(RecordingInfo *pginfo, QStringList &strlist)
01520 {
01521 RecList retlist;
01522 getConflicting(pginfo, &retlist);
01523
01524 strlist << QString::number(retlist.size());
01525
01526 while (!retlist.empty())
01527 {
01528 RecordingInfo *p = retlist.front();
01529 p->ToStringList(strlist);
01530 delete p;
01531 retlist.pop_front();
01532 }
01533 }
01534
01535 void Scheduler::getConflicting(RecordingInfo *pginfo, RecList *retlist)
01536 {
01537 QMutexLocker lockit(&schedLock);
01538
01539 RecConstIter i = reclist.begin();
01540 for (; FindNextConflict(reclist, pginfo, i); ++i)
01541 {
01542 const RecordingInfo *p = *i;
01543 retlist->push_back(new RecordingInfo(*p));
01544 }
01545 }
01546
01547 bool Scheduler::GetAllPending(RecList &retList) const
01548 {
01549 QMutexLocker lockit(&schedLock);
01550
01551 bool hasconflicts = false;
01552
01553 RecConstIter it = reclist.begin();
01554 for (; it != reclist.end(); ++it)
01555 {
01556 if ((*it)->GetRecordingStatus() == rsConflict)
01557 hasconflicts = true;
01558 retList.push_back(new RecordingInfo(**it));
01559 }
01560
01561 return hasconflicts;
01562 }
01563
01564 QMap<QString,ProgramInfo*> Scheduler::GetRecording(void) const
01565 {
01566 QMutexLocker lockit(&schedLock);
01567
01568 QMap<QString,ProgramInfo*> recMap;
01569 RecConstIter it = reclist.begin();
01570 for (; it != reclist.end(); ++it)
01571 {
01572 if (rsRecording == (*it)->GetRecordingStatus() ||
01573 rsTuning == (*it)->GetRecordingStatus())
01574 recMap[(*it)->MakeUniqueKey()] = new ProgramInfo(**it);
01575 }
01576
01577 return recMap;
01578 }
01579
01580 RecStatusType Scheduler::GetRecStatus(const ProgramInfo &pginfo)
01581 {
01582 QMutexLocker lockit(&schedLock);
01583
01584 for (RecConstIter it = reclist.begin(); it != reclist.end(); ++it)
01585 {
01586 if (pginfo.IsSameRecording(**it))
01587 {
01588 return (rsRecording == (**it).GetRecordingStatus() ||
01589 rsTuning == (**it).GetRecordingStatus()) ?
01590 (**it).GetRecordingStatus() : pginfo.GetRecordingStatus();
01591 }
01592 }
01593
01594 return pginfo.GetRecordingStatus();
01595 }
01596
01597 void Scheduler::GetAllPending(QStringList &strList) const
01598 {
01599 RecList retlist;
01600 bool hasconflicts = GetAllPending(retlist);
01601
01602 strList << QString::number(hasconflicts);
01603 strList << QString::number(retlist.size());
01604
01605 while (!retlist.empty())
01606 {
01607 RecordingInfo *p = retlist.front();
01608 p->ToStringList(strList);
01609 delete p;
01610 retlist.pop_front();
01611 }
01612 }
01613
01615 void Scheduler::GetAllScheduled(QStringList &strList)
01616 {
01617 RecList schedlist;
01618
01619 GetAllScheduled(schedlist);
01620
01621 strList << QString::number(schedlist.size());
01622
01623 while (!schedlist.empty())
01624 {
01625 RecordingInfo *pginfo = schedlist.front();
01626 pginfo->ToStringList(strList);
01627 delete pginfo;
01628 schedlist.pop_front();
01629 }
01630 }
01631
01632 void Scheduler::Reschedule(const QStringList &request)
01633 {
01634 QMutexLocker locker(&schedLock);
01635 reschedQueue.enqueue(request);
01636 reschedWait.wakeOne();
01637 }
01638
01639 void Scheduler::AddRecording(const RecordingInfo &pi)
01640 {
01641 QMutexLocker lockit(&schedLock);
01642
01643 LOG(VB_GENERAL, LOG_INFO, LOC + QString("AddRecording() recid: %1")
01644 .arg(pi.GetRecordingRuleID()));
01645
01646 for (RecIter it = reclist.begin(); it != reclist.end(); ++it)
01647 {
01648 RecordingInfo *p = *it;
01649 if (p->GetRecordingStatus() == rsRecording &&
01650 p->IsSameProgramTimeslot(pi))
01651 {
01652 LOG(VB_GENERAL, LOG_INFO, LOC + "Not adding recording, " +
01653 QString("'%1' is already in reclist.")
01654 .arg(pi.GetTitle()));
01655 return;
01656 }
01657 }
01658
01659 LOG(VB_SCHEDULE, LOG_INFO, LOC +
01660 QString("Adding '%1' to reclist.").arg(pi.GetTitle()));
01661
01662 RecordingInfo * new_pi = new RecordingInfo(pi);
01663 reclist.push_back(new_pi);
01664 reclist_changed = true;
01665
01666
01667
01668 new_pi->AddHistory(false);
01669
01670
01671 new_pi->GetRecordingRule();
01672
01673
01674 EnqueueMatch(pi.GetRecordingRuleID(), 0, 0, QDateTime(),
01675 QString("AddRecording %1").arg(pi.GetTitle()));
01676 reschedWait.wakeOne();
01677 }
01678
01679 bool Scheduler::IsBusyRecording(const RecordingInfo *rcinfo)
01680 {
01681 if (!m_tvList || !rcinfo)
01682 {
01683 LOG(VB_GENERAL, LOG_ERR, LOC +
01684 "IsBusyRecording() -> true, no tvList or no rcinfo");
01685
01686 return true;
01687 }
01688
01689 EncoderLink *rctv = (*m_tvList)[rcinfo->GetCardID()];
01690
01691 if (!rctv || rctv->IsBusyRecording())
01692 return true;
01693
01694
01695 TunedInputInfo busy_input;
01696 uint inputid = rcinfo->GetInputID();
01697 vector<uint> cardids = CardUtil::GetConflictingCards(
01698 inputid, rcinfo->GetCardID());
01699 for (uint i = 0; i < cardids.size(); i++)
01700 {
01701 rctv = (*m_tvList)[cardids[i]];
01702 if (!rctv)
01703 {
01704 #if 0
01705 LOG(VB_SCHEDULE, LOG_ERR, LOC +
01706 QString("IsBusyRecording() -> true, rctv(NULL) for card %2")
01707 .arg(cardids[i]));
01708 #endif
01709 return true;
01710 }
01711
01712 if (rctv->IsBusy(&busy_input, -1) &&
01713 igrp.GetSharedInputGroup(busy_input.inputid, inputid))
01714 {
01715 return true;
01716 }
01717 }
01718
01719 return false;
01720 }
01721
01722 void Scheduler::OldRecordedFixups(void)
01723 {
01724 MSqlQuery query(dbConn);
01725
01726
01727 query.prepare("UPDATE oldrecorded SET recstatus = :RSABORTED "
01728 " WHERE recstatus = :RSRECORDING OR recstatus = :RSTUNING");
01729 query.bindValue(":RSABORTED", rsAborted);
01730 query.bindValue(":RSRECORDING", rsRecording);
01731 query.bindValue(":RSTUNING", rsTuning);
01732 if (!query.exec())
01733 MythDB::DBError("UpdateAborted", query);
01734
01735
01736 query.prepare("UPDATE oldrecorded SET recstatus = :RSMISSED "
01737 "WHERE recstatus = :RSWILLRECORD");
01738 query.bindValue(":RSMISSED", rsMissed);
01739 query.bindValue(":RSWILLRECORD", rsWillRecord);
01740 if (!query.exec())
01741 MythDB::DBError("UpdateMissed", query);
01742
01743
01744
01745 query.prepare("UPDATE oldrecorded SET recstatus = :RSPREVIOUS "
01746 "WHERE recstatus = :RSCURRENT");
01747 query.bindValue(":RSPREVIOUS", rsPreviousRecording);
01748 query.bindValue(":RSCURRENT", rsCurrentRecording);
01749 if (!query.exec())
01750 MythDB::DBError("UpdateCurrent", query);
01751
01752
01753
01754
01755 query.prepare("UPDATE oldrecorded SET future = 0 "
01756 "WHERE future > 0 AND "
01757 " endtime < (NOW() - INTERVAL 475 MINUTE)");
01758 if (!query.exec())
01759 MythDB::DBError("UpdateFuture", query);
01760 }
01761
01762 void Scheduler::run(void)
01763 {
01764 RunProlog();
01765
01766 dbConn = MSqlQuery::SchedCon();
01767
01768
01769 {
01770 QMutexLocker lockit(&schedLock);
01771 reschedWait.wakeAll();
01772 }
01773
01774 OldRecordedFixups();
01775
01776
01777 sleep(3);
01778
01779 QMutexLocker lockit(&schedLock);
01780
01781 reschedQueue.clear();
01782 EnqueueMatch(0, 0, 0, QDateTime(), "SchedulerInit");
01783
01784 int prerollseconds = 0;
01785 int wakeThreshold = 300;
01786 int idleTimeoutSecs = 0;
01787 int idleWaitForRecordingTime = 15;
01788 int tuningTimeout = 180;
01789 bool blockShutdown =
01790 gCoreContext->GetNumSetting("blockSDWUwithoutClient", 1);
01791 bool firstRun = true;
01792 QDateTime lastSleepCheck = QDateTime::currentDateTime().addDays(-1);
01793 RecIter startIter = reclist.begin();
01794 QDateTime idleSince = QDateTime();
01795 int maxSleep = 60000;
01796 int schedRunTime = 30;
01797
01798 while (doRun)
01799 {
01800 QDateTime curtime = QDateTime::currentDateTime();
01801 bool statuschanged = false;
01802 int secs_to_next = (startIter != reclist.end()) ?
01803 curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60;
01804
01805
01806
01807 if (secs_to_next < (schedRunTime + 2))
01808 {
01809 int msecs = CalcTimeToNextHandleRecordingEvent(
01810 curtime, startIter, reclist, prerollseconds, maxSleep);
01811 LOG(VB_SCHEDULE, LOG_INFO,
01812 QString("sleeping for %1 ms (s2n: %2 sr: %3)")
01813 .arg(msecs).arg(secs_to_next).arg(schedRunTime));
01814 if (msecs < 100)
01815 (void) ::usleep(msecs * 1000);
01816 else
01817 reschedWait.wait(&schedLock, msecs);
01818 }
01819 else
01820 {
01821 if (reschedQueue.empty())
01822 {
01823 int sched_sleep = (secs_to_next - schedRunTime - 1) * 1000;
01824 sched_sleep = min(sched_sleep, maxSleep);
01825 if (secs_to_next < prerollseconds + (maxSleep/1000))
01826 sched_sleep = min(sched_sleep, 5000);
01827 LOG(VB_SCHEDULE, LOG_INFO,
01828 QString("sleeping for %1 ms (interuptable)")
01829 .arg(sched_sleep));
01830 reschedWait.wait(&schedLock, sched_sleep);
01831 if (!doRun)
01832 break;
01833 }
01834
01835 QTime t; t.start();
01836 if (!reschedQueue.empty() && HandleReschedule())
01837 {
01838 statuschanged = true;
01839 startIter = reclist.begin();
01840
01841
01842
01843 prerollseconds =
01844 gCoreContext->GetNumSetting("RecordPreRoll", 0);
01845 wakeThreshold =
01846 gCoreContext->GetNumSetting("WakeUpThreshold", 300);
01847 idleTimeoutSecs =
01848 gCoreContext->GetNumSetting("idleTimeoutSecs", 0);
01849 idleWaitForRecordingTime =
01850 gCoreContext->GetNumSetting("idleWaitForRecordingTime", 15);
01851 tuningTimeout =
01852 gCoreContext->GetNumSetting("tuningTimeout", 180);
01853 }
01854 int e = t.elapsed();
01855 if (e > 0)
01856 {
01857 schedRunTime = (firstRun) ? 0 : schedRunTime;
01858 schedRunTime =
01859 max((int)(((e + 999) / 1000) * 1.5f), schedRunTime);
01860 }
01861
01862 if (firstRun)
01863 {
01864 blockShutdown &= HandleRunSchedulerStartup(
01865 prerollseconds, idleWaitForRecordingTime);
01866 firstRun = false;
01867
01868
01869
01870
01871 if (reclist_changed)
01872 continue;
01873 }
01874
01875
01876
01877
01878 curtime = QDateTime::currentDateTime();
01879 secs_to_next = (startIter != reclist.end()) ?
01880 curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60;
01881 if ((secs_to_next > schedRunTime * 1.5f) &&
01882 (lastSleepCheck.secsTo(curtime) > 300))
01883 {
01884 PutInactiveSlavesToSleep();
01885 lastSleepCheck = QDateTime::currentDateTime();
01886 }
01887 }
01888
01889
01890
01891 for ( ; startIter != reclist.end(); ++startIter)
01892 {
01893 if ((*startIter)->GetRecordingStatus() !=
01894 (*startIter)->oldrecstatus)
01895 {
01896 break;
01897 }
01898 }
01899
01900
01901
01902
01903 bool done = false;
01904 for (RecIter it = startIter; it != reclist.end() && !done; ++it)
01905 {
01906 done = HandleRecording(
01907 **it, statuschanged, prerollseconds, tuningTimeout);
01908 }
01909
01911 curtime = QDateTime::currentDateTime();
01912 for (RecIter it = startIter; it != reclist.end(); ++it)
01913 {
01914 int secsleft = curtime.secsTo((*it)->GetRecordingStartTime());
01915 if ((secsleft - prerollseconds) <= wakeThreshold)
01916 HandleWakeSlave(**it, prerollseconds);
01917 else
01918 break;
01919 }
01920
01921 if (statuschanged)
01922 {
01923 MythEvent me("SCHEDULE_CHANGE");
01924 gCoreContext->dispatch(me);
01925 idleSince = QDateTime();
01926 }
01927
01928
01929 if ((idleTimeoutSecs > 0) && (m_mainServer != NULL))
01930 {
01931 HandleIdleShutdown(blockShutdown, idleSince, prerollseconds,
01932 idleTimeoutSecs, idleWaitForRecordingTime,
01933 statuschanged);
01934 }
01935 }
01936
01937 RunEpilog();
01938 }
01939
01940 int Scheduler::CalcTimeToNextHandleRecordingEvent(
01941 const QDateTime &curtime,
01942 RecConstIter startIter, const RecList &reclist,
01943 int prerollseconds, int max_sleep )
01944 {
01945 if (startIter == reclist.end())
01946 return max_sleep;
01947
01948 int msecs = max_sleep;
01949 for (RecConstIter i = startIter; i != reclist.end() && (msecs > 0); ++i)
01950 {
01951
01952
01953 if ((*i)->GetRecordingStatus() == rsTuning)
01954 {
01955 msecs = min(msecs, 1000);
01956 continue;
01957 }
01958
01959
01960 if ((*i)->GetRecordingStatus() == (*i)->oldrecstatus)
01961 continue;
01962
01963 int secs_to_next = curtime.secsTo((*i)->GetRecordingStartTime());
01964
01965 if (!recPendingList[(*i)->MakeUniqueSchedulerKey()])
01966 secs_to_next -= 30;
01967
01968 if (secs_to_next < 0)
01969 {
01970 msecs = 0;
01971 break;
01972 }
01973
01974
01975 if (secs_to_next > max_sleep)
01976 {
01977 msecs = min(msecs, max_sleep);
01978 break;
01979 }
01980
01981 if (secs_to_next > 31)
01982 {
01983 msecs = min(msecs, 30 * 1000);
01984 continue;
01985 }
01986
01987 if ((secs_to_next-1) * 1000 > msecs)
01988 continue;
01989
01990 if (secs_to_next < 15)
01991 {
01992 QTime st = (*i)->GetRecordingStartTime().time();
01993 int tmp = curtime.time().msecsTo(st);
01994 tmp = (tmp < 0) ? tmp + 86400000 : tmp;
01995 msecs = (tmp > 15*1000) ? 0 : min(msecs, tmp);
01996 }
01997 else
01998 {
01999 msecs = min(msecs, (secs_to_next-1) * 1000);
02000 }
02001 }
02002
02003 return min(msecs, max_sleep);
02004 }
02005
02006 void Scheduler::ResetDuplicates(uint recordid, uint findid,
02007 const QString &title, const QString &subtitle,
02008 const QString &descrip,
02009 const QString &programid)
02010 {
02011 MSqlQuery query(dbConn);
02012 QString filterClause;
02013 MSqlBindings bindings;
02014
02015 if (!title.isEmpty())
02016 {
02017 filterClause += "AND p.title = :TITLE ";
02018 bindings[":TITLE"] = title;
02019 }
02020
02021
02022 if (programid != "**any**")
02023 {
02024 filterClause += "AND (0 ";
02025 if (!subtitle.isEmpty())
02026 {
02027
02028 filterClause += "OR p.subtitle = :SUBTITLE "
02029 "OR p.description = :SUBTITLE ";
02030 bindings[":SUBTITLE"] = subtitle;
02031 }
02032 if (!descrip.isEmpty())
02033 {
02034
02035 filterClause += "OR p.description = :DESCRIP "
02036 "OR p.subtitle = :DESCRIP ";
02037 bindings[":DESCRIP"] = descrip;
02038 }
02039 if (!programid.isEmpty())
02040 {
02041 filterClause += "OR p.programid = :PROGRAMID ";
02042 bindings[":PROGRAMID"] = programid;
02043 }
02044 filterClause += ") ";
02045 }
02046
02047 query.prepare(QString("UPDATE recordmatch rm "
02048 "INNER JOIN %1 r "
02049 " ON rm.recordid = r.recordid "
02050 "INNER JOIN program p "
02051 " ON rm.chanid = p.chanid "
02052 " AND rm.starttime = p.starttime "
02053 " AND rm.manualid = p.manualid "
02054 "SET oldrecduplicate = -1 "
02055 "WHERE p.generic = 0 "
02056 " AND r.type NOT IN (%2, %3, %4) ")
02057 .arg(recordTable)
02058 .arg(kSingleRecord)
02059 .arg(kOverrideRecord)
02060 .arg(kDontRecord)
02061 + filterClause);
02062 MSqlBindings::const_iterator it;
02063 for (it = bindings.begin(); it != bindings.end(); ++it)
02064 query.bindValue(it.key(), it.value());
02065 if (!query.exec())
02066 MythDB::DBError("ResetDuplicates1", query);
02067
02068 if (findid && programid != "**any**")
02069 {
02070 query.prepare("UPDATE recordmatch rm "
02071 "SET oldrecduplicate = -1 "
02072 "WHERE rm.recordid = :RECORDID "
02073 " AND rm.findid = :FINDID");
02074 query.bindValue("RECORDID", recordid);
02075 query.bindValue("FINDID", findid);
02076 if (!query.exec())
02077 MythDB::DBError("ResetDuplicates2", query);
02078 }
02079 }
02080
02081 bool Scheduler::HandleReschedule(void)
02082 {
02083
02084
02085 dbConn = MSqlQuery::SchedCon();
02086
02087 struct timeval fillstart, fillend;
02088 float matchTime, checkTime, placeTime;
02089
02090 gettimeofday(&fillstart, NULL);
02091 QString msg;
02092 bool deleteFuture = false;
02093 bool runCheck = false;
02094
02095 while (!reschedQueue.empty())
02096 {
02097 QStringList request = reschedQueue.dequeue();
02098 QStringList tokens;
02099 if (request.size() >= 1)
02100 tokens = request[0].split(' ', QString::SkipEmptyParts);
02101
02102 if (request.size() < 1 || tokens.size() < 1)
02103 {
02104 LOG(VB_GENERAL, LOG_ERR, "Empty Reschedule request received");
02105 return false;
02106 }
02107
02108 LOG(VB_GENERAL, LOG_INFO, QString("Reschedule requested for %1")
02109 .arg(request.join(" | ")));
02110
02111 if (tokens[0] == "MATCH")
02112 {
02113 if (tokens.size() < 5)
02114 {
02115 LOG(VB_GENERAL, LOG_ERR,
02116 QString("Invalid RescheduleMatch request received (%1)")
02117 .arg(request[0]));
02118 continue;
02119 }
02120
02121 uint recordid = tokens[1].toUInt();
02122 uint sourceid = tokens[2].toUInt();
02123 uint mplexid = tokens[3].toUInt();
02124 QDateTime maxstarttime =
02125 QDateTime::fromString(tokens[4], Qt::ISODate);
02126 deleteFuture = true;
02127 runCheck = true;
02128 schedLock.unlock();
02129 recordmatchLock.lock();
02130 UpdateMatches(recordid, sourceid, mplexid, maxstarttime);
02131 recordmatchLock.unlock();
02132 schedLock.lock();
02133 }
02134 else if (tokens[0] == "CHECK")
02135 {
02136 if (tokens.size() < 4 || request.size() < 5)
02137 {
02138 LOG(VB_GENERAL, LOG_ERR,
02139 QString("Invalid RescheduleCheck request received (%1)")
02140 .arg(request[0]));
02141 continue;
02142 }
02143
02144 uint recordid = tokens[2].toUInt();
02145 uint findid = tokens[3].toUInt();
02146 QString title = request[1];
02147 QString subtitle = request[2];
02148 QString descrip = request[3];
02149 QString programid = request[4];
02150 runCheck = true;
02151 schedLock.unlock();
02152 recordmatchLock.lock();
02153 ResetDuplicates(recordid, findid, title, subtitle, descrip,
02154 programid);
02155 recordmatchLock.unlock();
02156 schedLock.lock();
02157 }
02158 else if (tokens[0] != "PLACE")
02159 {
02160 LOG(VB_GENERAL, LOG_ERR,
02161 QString("Unknown Reschedule request received (%1)")
02162 .arg(request[0]));
02163 }
02164 }
02165
02166
02167
02168 if (deleteFuture)
02169 {
02170 MSqlQuery query(dbConn);
02171 query.prepare("DELETE oldrecorded FROM oldrecorded "
02172 "LEFT JOIN recordmatch ON "
02173 " recordmatch.chanid = oldrecorded.chanid AND "
02174 " recordmatch.starttime = oldrecorded.starttime "
02175 "WHERE oldrecorded.future > 0 AND "
02176 " recordmatch.recordid IS NULL");
02177 if (!query.exec())
02178 MythDB::DBError("DeleteFuture", query);
02179 }
02180
02181 gettimeofday(&fillend, NULL);
02182 matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
02183 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
02184
02185 LOG(VB_SCHEDULE, LOG_INFO, "CreateTempTables...");
02186 CreateTempTables();
02187
02188 gettimeofday(&fillstart, NULL);
02189 if (runCheck)
02190 {
02191 LOG(VB_SCHEDULE, LOG_INFO, "UpdateDuplicates...");
02192 UpdateDuplicates();
02193 }
02194 gettimeofday(&fillend, NULL);
02195 checkTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
02196 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
02197
02198 gettimeofday(&fillstart, NULL);
02199 bool worklistused = FillRecordList();
02200 gettimeofday(&fillend, NULL);
02201 placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
02202 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
02203
02204 LOG(VB_SCHEDULE, LOG_INFO, "DeleteTempTables...");
02205 DeleteTempTables();
02206
02207 if (worklistused)
02208 {
02209 UpdateNextRecord();
02210 PrintList();
02211 }
02212 else
02213 {
02214 LOG(VB_GENERAL, LOG_INFO, "Reschedule interrupted, will retry");
02215 EnqueuePlace("Interrupted");
02216 return false;
02217 }
02218
02219 msg.sprintf("Scheduled %d items in %.1f "
02220 "= %.2f match + %.2f check + %.2f place",
02221 (int)reclist.size(), matchTime + checkTime + placeTime,
02222 matchTime, checkTime, placeTime);
02223 LOG(VB_GENERAL, LOG_INFO, msg);
02224
02225 fsInfoCacheFillTime = QDateTime::currentDateTime().addSecs(-1000);
02226
02227
02228 RecIter it = reclist.begin();
02229 for ( ; it != reclist.end(); ++it)
02230 {
02231 RecordingInfo *p = *it;
02232 if (p->GetRecordingStatus() != p->oldrecstatus)
02233 {
02234 if (p->GetRecordingEndTime() < schedTime)
02235 p->AddHistory(false, false, false);
02236 else if (p->GetRecordingStartTime() < schedTime &&
02237 p->GetRecordingStatus() != rsWillRecord)
02238 p->AddHistory(false, false, false);
02239 else
02240 p->AddHistory(false, false, true);
02241 }
02242 else if (p->future)
02243 {
02244
02245
02246 p->oldrecstatus = rsUnknown;
02247 }
02248 p->future = false;
02249 }
02250
02251 gCoreContext->SendSystemEvent("SCHEDULER_RAN");
02252
02253 return true;
02254 }
02255
02256 bool Scheduler::HandleRunSchedulerStartup(
02257 int prerollseconds, int idleWaitForRecordingTime)
02258 {
02259 bool blockShutdown = true;
02260
02261
02262
02263
02264 QString startupParam = "user";
02265
02266
02267 RecIter firstRunIter = reclist.begin();
02268 for ( ; firstRunIter != reclist.end(); ++firstRunIter)
02269 {
02270 if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord)
02271 break;
02272 }
02273
02274
02275 QDateTime curtime = QDateTime::currentDateTime();
02276 if (WasStartedAutomatically() ||
02277 ((firstRunIter != reclist.end()) &&
02278 ((curtime.secsTo((*firstRunIter)->GetRecordingStartTime()) -
02279 prerollseconds) < (idleWaitForRecordingTime * 60))))
02280 {
02281 LOG(VB_GENERAL, LOG_INFO, LOC + "AUTO-Startup assumed");
02282 startupParam = "auto";
02283
02284
02285
02286 blockShutdown = false;
02287 }
02288 else
02289 {
02290 LOG(VB_GENERAL, LOG_INFO, LOC + "Seem to be woken up by USER");
02291 }
02292
02293 QString startupCommand = gCoreContext->GetSetting("startupCommand", "");
02294 if (!startupCommand.isEmpty())
02295 {
02296 startupCommand.replace("$status", startupParam);
02297 schedLock.unlock();
02298 myth_system(startupCommand);
02299 schedLock.lock();
02300 }
02301
02302 return blockShutdown;
02303 }
02304
02305
02306 void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds)
02307 {
02308 static const int sysEventSecs[5] = { 120, 90, 60, 30, 0 };
02309
02310 QDateTime curtime = QDateTime::currentDateTime();
02311 QDateTime nextrectime = ri.GetRecordingStartTime();
02312 int secsleft = curtime.secsTo(nextrectime);
02313
02314 QMap<int, EncoderLink*>::iterator tvit = m_tvList->find(ri.GetCardID());
02315 if (tvit == m_tvList->end())
02316 return;
02317
02318 QString sysEventKey = ri.MakeUniqueKey();
02319
02320 int i = 0;
02321 bool pendingEventSent = false;
02322 while (sysEventSecs[i] != 0)
02323 {
02324 if ((secsleft <= sysEventSecs[i]) &&
02325 (!sysEvents[i].contains(sysEventKey)))
02326 {
02327 if (!pendingEventSent)
02328 {
02329 SendMythSystemRecEvent(
02330 QString("REC_PENDING SECS %1").arg(secsleft), &ri);
02331 }
02332
02333 sysEvents[i].insert(sysEventKey);
02334 pendingEventSent = true;
02335 }
02336 i++;
02337 }
02338
02339
02340 QSet<QString> keys;
02341 for (i = 0; sysEventSecs[i] != 0; i++)
02342 {
02343 if (sysEvents[i].size() < 20)
02344 continue;
02345
02346 if (keys.empty())
02347 {
02348 RecConstIter it = reclist.begin();
02349 for ( ; it != reclist.end(); ++it)
02350 keys.insert((*it)->MakeUniqueKey());
02351 keys.insert("something");
02352 }
02353
02354 QSet<QString>::iterator sit = sysEvents[i].begin();
02355 while (sit != sysEvents[i].end())
02356 {
02357 if (!keys.contains(*sit))
02358 sit = sysEvents[i].erase(sit);
02359 else
02360 ++sit;
02361 }
02362 }
02363
02364 EncoderLink *nexttv = *tvit;
02365
02366 if (nexttv->IsAsleep() && !nexttv->IsWaking())
02367 {
02368 LOG(VB_SCHEDULE, LOG_INFO, LOC +
02369 QString("Slave Backend %1 is being awakened to record: %2")
02370 .arg(nexttv->GetHostName()).arg(ri.GetTitle()));
02371
02372 if (!WakeUpSlave(nexttv->GetHostName()))
02373 EnqueuePlace("HandleWakeSlave1");
02374 }
02375 else if ((nexttv->IsWaking()) &&
02376 ((secsleft - prerollseconds) < 210) &&
02377 (nexttv->GetSleepStatusTime().secsTo(curtime) < 300) &&
02378 (nexttv->GetLastWakeTime().secsTo(curtime) > 10))
02379 {
02380 LOG(VB_SCHEDULE, LOG_INFO, LOC +
02381 QString("Slave Backend %1 not available yet, "
02382 "trying to wake it up again.")
02383 .arg(nexttv->GetHostName()));
02384
02385 if (!WakeUpSlave(nexttv->GetHostName(), false))
02386 EnqueuePlace("HandleWakeSlave2");
02387 }
02388 else if ((nexttv->IsWaking()) &&
02389 ((secsleft - prerollseconds) < 150) &&
02390 (nexttv->GetSleepStatusTime().secsTo(curtime) < 300))
02391 {
02392 LOG(VB_GENERAL, LOG_WARNING, LOC +
02393 QString("Slave Backend %1 has NOT come "
02394 "back from sleep yet in 150 seconds. Setting "
02395 "slave status to unknown and attempting "
02396 "to reschedule around its tuners.")
02397 .arg(nexttv->GetHostName()));
02398
02399 QMap<int, EncoderLink*>::iterator it = m_tvList->begin();
02400 for (; it != m_tvList->end(); ++it)
02401 {
02402 if ((*it)->GetHostName() == nexttv->GetHostName())
02403 (*it)->SetSleepStatus(sStatus_Undefined);
02404 }
02405
02406 EnqueuePlace("HandleWakeSlave3");
02407 }
02408 }
02409
02410 bool Scheduler::HandleRecording(
02411 RecordingInfo &ri, bool &statuschanged,
02412 int prerollseconds, int tuningTimeout)
02413 {
02414 if (ri.GetRecordingStatus() == rsTuning)
02415 {
02416 HandleTuning(ri, statuschanged, tuningTimeout);
02417 return false;
02418 }
02419
02420 if (ri.GetRecordingStatus() != rsWillRecord)
02421 {
02422 if (ri.GetRecordingStatus() != ri.oldrecstatus &&
02423 ri.GetRecordingStartTime() <= QDateTime::currentDateTime())
02424 {
02425 ri.AddHistory(false);
02426 }
02427 return false;
02428 }
02429
02430 QDateTime nextrectime = ri.GetRecordingStartTime();
02431 QDateTime curtime = QDateTime::currentDateTime();
02432 int secsleft = curtime.secsTo(nextrectime);
02433 QString schedid = ri.MakeUniqueSchedulerKey();
02434
02435 if (secsleft - prerollseconds < 60)
02436 {
02437 if (!recPendingList.contains(schedid))
02438 {
02439 recPendingList[schedid] = false;
02440
02441 livetvTime = (livetvTime < nextrectime) ?
02442 nextrectime : livetvTime;
02443
02444 EnqueuePlace("PrepareToRecord");
02445 }
02446 }
02447
02448 if (secsleft - prerollseconds > 35)
02449 return true;
02450
02451 QMap<int, EncoderLink*>::iterator tvit = m_tvList->find(ri.GetCardID());
02452 if (tvit == m_tvList->end())
02453 {
02454 QString msg = QString("Invalid cardid (%1) for %2")
02455 .arg(ri.GetCardID()).arg(ri.GetTitle());
02456 LOG(VB_GENERAL, LOG_ERR, LOC + msg);
02457
02458 ri.SetRecordingStatus(rsTunerBusy);
02459 ri.AddHistory(true);
02460 statuschanged = true;
02461 return false;
02462 }
02463
02464 EncoderLink *nexttv = *tvit;
02465
02466 if (nexttv->IsTunerLocked())
02467 {
02468 QString msg = QString("SUPPRESSED recording \"%1\" on channel: "
02469 "%2 on cardid: %3, sourceid %4. Tuner "
02470 "is locked by an external application.")
02471 .arg(ri.GetTitle())
02472 .arg(ri.GetChanID())
02473 .arg(ri.GetCardID())
02474 .arg(ri.GetSourceID());
02475 LOG(VB_GENERAL, LOG_NOTICE, msg);
02476
02477 ri.SetRecordingStatus(rsTunerBusy);
02478 ri.AddHistory(true);
02479 statuschanged = true;
02480 return false;
02481 }
02482
02483 if ((prerollseconds > 0) && !IsBusyRecording(&ri))
02484 {
02485
02486
02487 secsleft -= prerollseconds;
02488 }
02489
02490 #if 0
02491 LOG(VB_GENERAL, LOG_DEBUG, QString("%1 seconds until %2").
02492 .arg(secsleft) .arg(ri.GetTitle()));
02493 #endif
02494
02495 if (secsleft > 30)
02496 return false;
02497
02498 if (nexttv->IsWaking())
02499 {
02500 if (secsleft > 0)
02501 {
02502 LOG(VB_SCHEDULE, LOG_WARNING,
02503 QString("WARNING: Slave Backend %1 has NOT come "
02504 "back from sleep yet. Recording can "
02505 "not begin yet for: %2")
02506 .arg(nexttv->GetHostName())
02507 .arg(ri.GetTitle()));
02508 }
02509 else if (nexttv->GetLastWakeTime().secsTo(curtime) > 300)
02510 {
02511 LOG(VB_SCHEDULE, LOG_WARNING,
02512 QString("WARNING: Slave Backend %1 has NOT come "
02513 "back from sleep yet. Setting slave "
02514 "status to unknown and attempting "
02515 "to reschedule around its tuners.")
02516 .arg(nexttv->GetHostName()));
02517
02518 QMap<int, EncoderLink *>::Iterator enciter =
02519 m_tvList->begin();
02520 for (; enciter != m_tvList->end(); ++enciter)
02521 {
02522 EncoderLink *enc = *enciter;
02523 if (enc->GetHostName() == nexttv->GetHostName())
02524 enc->SetSleepStatus(sStatus_Undefined);
02525 }
02526
02527 EnqueuePlace("SlaveNotAwake");
02528 }
02529
02530 return false;
02531 }
02532
02533 int fsID = -1;
02534 if (ri.GetPathname().isEmpty())
02535 {
02536 QString recording_dir;
02537 fsID = FillRecordingDir(ri.GetTitle(),
02538 ri.GetHostname(),
02539 ri.GetStorageGroup(),
02540 ri.GetRecordingStartTime(),
02541 ri.GetRecordingEndTime(),
02542 ri.GetCardID(),
02543 recording_dir,
02544 reclist);
02545 ri.SetPathname(recording_dir);
02546 }
02547
02548 if (!recPendingList[schedid])
02549 {
02550 nexttv->RecordPending(&ri, max(secsleft, 0),
02551 hasLaterList.contains(schedid));
02552 recPendingList[schedid] = true;
02553 }
02554
02555 if (secsleft > 0)
02556 return false;
02557
02558 QDateTime recstartts = mythCurrentDateTime().addSecs(30);
02559 recstartts.setTime(
02560 QTime(recstartts.time().hour(), recstartts.time().minute()));
02561 ri.SetRecordingStartTime(recstartts);
02562
02563 QString details = QString("%1: channel %2 on cardid %3, sourceid %4")
02564 .arg(ri.toString(ProgramInfo::kTitleSubtitle))
02565 .arg(ri.GetChanID())
02566 .arg(ri.GetCardID())
02567 .arg(ri.GetSourceID());
02568
02569 RecStatusTypes recStatus = rsOffLine;
02570 if (schedulingEnabled && nexttv->IsConnected())
02571 {
02572 if (ri.GetRecordingStatus() == rsWillRecord)
02573 {
02574 recStatus = nexttv->StartRecording(&ri);
02575 ri.AddHistory(false);
02576
02577
02578 if (m_expirer)
02579 m_expirer->Update(ri.GetCardID(), fsID, true);
02580 }
02581 }
02582
02583 HandleRecordingStatusChange(ri, recStatus, details);
02584 statuschanged = true;
02585
02586 return false;
02587 }
02588
02589 void Scheduler::HandleRecordingStatusChange(
02590 RecordingInfo &ri, RecStatusTypes recStatus, const QString &details)
02591 {
02592 if (ri.GetRecordingStatus() == recStatus)
02593 return;
02594
02595 ri.SetRecordingStatus(recStatus);
02596
02597 if (rsTuning != recStatus)
02598 {
02599 bool doSchedAfterStart =
02600 ((rsRecording != recStatus) && (rsTuning != recStatus)) ||
02601 schedAfterStartMap[ri.GetRecordingRuleID()] ||
02602 (ri.GetParentRecordingRuleID() &&
02603 schedAfterStartMap[ri.GetParentRecordingRuleID()]);
02604 ri.AddHistory(doSchedAfterStart);
02605 }
02606
02607 QString msg = (rsRecording == recStatus) ?
02608 QString("Started recording") :
02609 ((rsTuning == recStatus) ?
02610 QString("Tuning recording") :
02611 QString("Canceled recording (%1)")
02612 .arg(toString(ri.GetRecordingStatus(), ri.GetRecordingRuleType())));
02613
02614 LOG(VB_GENERAL, LOG_INFO, QString("%1: %2").arg(msg).arg(details));
02615
02616 if ((rsRecording == recStatus) || (rsTuning == recStatus))
02617 {
02618 UpdateNextRecord();
02619 }
02620 else if (rsFailed == recStatus)
02621 {
02622 MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2")
02623 .arg(ri.GetChanID())
02624 .arg(ri.GetRecordingStartTime(ISODate)));
02625 gCoreContext->dispatch(me);
02626 }
02627 }
02628
02629 void Scheduler::HandleTuning(
02630 RecordingInfo &ri, bool &statuschanged, int tuningTimeout)
02631 {
02632 if (rsTuning != ri.GetRecordingStatus())
02633 return;
02634
02635
02636 QMap<int, EncoderLink*>::iterator tvit = m_tvList->find(ri.GetCardID());
02637 RecStatusTypes recStatus = rsTunerBusy;
02638 if (tvit == m_tvList->end())
02639 {
02640 QString msg = QString("Invalid cardid (%1) for %2")
02641 .arg(ri.GetCardID()).arg(ri.GetTitle());
02642 LOG(VB_GENERAL, LOG_ERR, LOC + msg);
02643 }
02644 else if (tuningTimeout > 0)
02645 {
02646 recStatus = (*tvit)->GetRecordingStatus();
02647 if (rsTuning == recStatus)
02648 {
02649
02650
02651
02652 QDateTime curtime = QDateTime::currentDateTime();
02653 if ((ri.GetRecordingStartTime().secsTo(curtime) > tuningTimeout) &&
02654 (ri.GetScheduledStartTime().secsTo(curtime) > tuningTimeout))
02655 {
02656 recStatus = rsFailed;
02657 LOG(VB_GENERAL, LOG_INFO,
02658 QString("Canceling recording since tuning timeout, "
02659 "%1 seconds, has been exceeded.")
02660 .arg(tuningTimeout));
02661 }
02662 }
02663 }
02664
02665
02666 if (rsTuning != recStatus)
02667 {
02668 QString details = QString("%1: channel %2 on cardid %3, sourceid %4")
02669 .arg(ri.toString(ProgramInfo::kTitleSubtitle))
02670 .arg(ri.GetChanID()).arg(ri.GetCardID()).arg(ri.GetSourceID());
02671 HandleRecordingStatusChange(ri, recStatus, details);
02672 statuschanged = true;
02673 }
02674 }
02675
02676 void Scheduler::HandleIdleShutdown(
02677 bool &blockShutdown, QDateTime &idleSince,
02678 int prerollseconds, int idleTimeoutSecs, int idleWaitForRecordingTime,
02679 bool &statuschanged)
02680 {
02681 if ((idleTimeoutSecs <= 0) || (m_mainServer == NULL))
02682 return;
02683
02684
02685 if (blockShutdown)
02686 blockShutdown &= !m_mainServer->isClientConnected();
02687 else
02688 {
02689 QDateTime curtime = QDateTime::currentDateTime();
02690
02691
02692 bool recording = false;
02693 QMap<int, EncoderLink *>::Iterator it;
02694 for (it = m_tvList->begin(); (it != m_tvList->end()) &&
02695 !recording; ++it)
02696 {
02697 if ((*it)->IsBusy())
02698 recording = true;
02699 }
02700
02701 if (!(m_mainServer->isClientConnected()) && !recording)
02702 {
02703
02704 resetIdleTime_lock.lock();
02705 if (resetIdleTime)
02706 {
02707
02708 idleSince = QDateTime();
02709 resetIdleTime = false;
02710 }
02711 resetIdleTime_lock.unlock();
02712
02713 if (statuschanged || !idleSince.isValid())
02714 {
02715 if (!idleSince.isValid())
02716 idleSince = curtime;
02717
02718 RecIter idleIter = reclist.begin();
02719 for ( ; idleIter != reclist.end(); ++idleIter)
02720 if ((*idleIter)->GetRecordingStatus() ==
02721 rsWillRecord)
02722 break;
02723
02724 if (idleIter != reclist.end())
02725 {
02726 if (curtime.secsTo
02727 ((*idleIter)->GetRecordingStartTime()) -
02728 prerollseconds <
02729 (idleWaitForRecordingTime * 60) +
02730 idleTimeoutSecs)
02731 {
02732 idleSince = QDateTime();
02733 }
02734 }
02735 }
02736
02737 if (idleSince.isValid())
02738 {
02739
02740 if (idleSince.addSecs(idleTimeoutSecs) < curtime)
02741 {
02742
02743 if (m_isShuttingDown)
02744 {
02745
02746
02747 if (idleSince.addSecs(idleTimeoutSecs + 60) <
02748 curtime)
02749 {
02750 LOG(VB_GENERAL, LOG_WARNING,
02751 "Waited more than 60"
02752 " seconds for shutdown to complete"
02753 " - resetting idle time");
02754 idleSince = QDateTime();
02755 m_isShuttingDown = false;
02756 }
02757 }
02758 else if (!m_isShuttingDown &&
02759 CheckShutdownServer(prerollseconds,
02760 idleSince,
02761 blockShutdown))
02762 {
02763 ShutdownServer(prerollseconds, idleSince);
02764 }
02765 }
02766 else
02767 {
02768 int itime = idleSince.secsTo(curtime);
02769 QString msg;
02770 if (itime == 1)
02771 {
02772 msg = QString("I\'m idle now... shutdown will "
02773 "occur in %1 seconds.")
02774 .arg(idleTimeoutSecs);
02775 LOG(VB_GENERAL, LOG_NOTICE, msg);
02776 MythEvent me(QString("SHUTDOWN_COUNTDOWN %1")
02777 .arg(idleTimeoutSecs));
02778 gCoreContext->dispatch(me);
02779 }
02780 else if (itime % 10 == 0)
02781 {
02782 msg = QString("%1 secs left to system shutdown!")
02783 .arg(idleTimeoutSecs - itime);
02784 LOG(VB_IDLE, LOG_NOTICE, msg);
02785 MythEvent me(QString("SHUTDOWN_COUNTDOWN %1")
02786 .arg(idleTimeoutSecs - itime));
02787 gCoreContext->dispatch(me);
02788 }
02789 }
02790 }
02791 }
02792 else
02793 {
02794
02795 if (idleSince.isValid())
02796 {
02797 MythEvent me(QString("SHUTDOWN_COUNTDOWN -1"));
02798 gCoreContext->dispatch(me);
02799 }
02800 idleSince = QDateTime();
02801 }
02802 }
02803 }
02804
02805
02806 bool Scheduler::CheckShutdownServer(int prerollseconds, QDateTime &idleSince,
02807 bool &blockShutdown)
02808 {
02809 (void)prerollseconds;
02810 bool retval = false;
02811 QString preSDWUCheckCommand = gCoreContext->GetSetting("preSDWUCheckCommand",
02812 "");
02813 if (!preSDWUCheckCommand.isEmpty())
02814 {
02815 uint state = myth_system(preSDWUCheckCommand);
02816
02817 if (state != GENERIC_EXIT_NOT_OK)
02818 {
02819 retval = false;
02820 switch(state)
02821 {
02822 case 0:
02823 LOG(VB_GENERAL, LOG_INFO,
02824 "CheckShutdownServer returned - OK to shutdown");
02825 retval = true;
02826 break;
02827 case 1:
02828 LOG(VB_IDLE, LOG_NOTICE,
02829 "CheckShutdownServer returned - Not OK to shutdown");
02830
02831 idleSince = QDateTime();
02832 break;
02833 case 2:
02834 LOG(VB_IDLE, LOG_NOTICE,
02835 "CheckShutdownServer returned - Not OK to shutdown, "
02836 "need reconnect");
02837
02838
02839
02840 blockShutdown =
02841 gCoreContext->GetNumSetting("blockSDWUwithoutClient",
02842 1);
02843 idleSince = QDateTime();
02844 break;
02845 #if 0
02846 case 3:
02847
02848 m_noAutoShutdown = true;
02849 break;
02850 #endif
02851 default:
02852 break;
02853 }
02854 }
02855 }
02856 else
02857 retval = true;
02858
02859 return retval;
02860 }
02861
02862 void Scheduler::ShutdownServer(int prerollseconds, QDateTime &idleSince)
02863 {
02864 m_isShuttingDown = true;
02865
02866 RecIter recIter = reclist.begin();
02867 for ( ; recIter != reclist.end(); ++recIter)
02868 if ((*recIter)->GetRecordingStatus() == rsWillRecord)
02869 break;
02870
02871
02872 if (recIter != reclist.end())
02873 {
02874 RecordingInfo *nextRecording = (*recIter);
02875 QDateTime restarttime = nextRecording->GetRecordingStartTime()
02876 .addSecs((-1) * prerollseconds);
02877
02878 int add = gCoreContext->GetNumSetting("StartupSecsBeforeRecording", 240);
02879 if (add)
02880 restarttime = restarttime.addSecs((-1) * add);
02881
02882 QString wakeup_timeformat = gCoreContext->GetSetting("WakeupTimeFormat",
02883 "hh:mm yyyy-MM-dd");
02884 QString setwakeup_cmd = gCoreContext->GetSetting("SetWakeuptimeCommand",
02885 "echo \'Wakeuptime would "
02886 "be $time if command "
02887 "set.\'");
02888
02889 if (setwakeup_cmd.isEmpty())
02890 {
02891 LOG(VB_GENERAL, LOG_NOTICE,
02892 "SetWakeuptimeCommand is empty, shutdown aborted");
02893 idleSince = QDateTime();
02894 m_isShuttingDown = false;
02895 return;
02896 }
02897 if (wakeup_timeformat == "time_t")
02898 {
02899 QString time_ts;
02900 setwakeup_cmd.replace("$time",
02901 time_ts.setNum(restarttime.toTime_t()));
02902 }
02903 else
02904 setwakeup_cmd.replace("$time",
02905 restarttime.toString(wakeup_timeformat));
02906
02907 LOG(VB_GENERAL, LOG_NOTICE,
02908 QString("Running the command to set the next "
02909 "scheduled wakeup time :-\n\t\t\t\t\t\t") + setwakeup_cmd);
02910
02911
02912 if (myth_system(setwakeup_cmd) != GENERIC_EXIT_OK)
02913 {
02914 LOG(VB_GENERAL, LOG_ERR,
02915 "SetWakeuptimeCommand failed, shutdown aborted");
02916 idleSince = QDateTime();
02917 m_isShuttingDown = false;
02918 return;
02919 }
02920 }
02921
02922
02923 MythEvent me(QString("SHUTDOWN_NOW"));
02924 gCoreContext->dispatch(me);
02925
02926 QString halt_cmd = gCoreContext->GetSetting("ServerHaltCommand",
02927 "sudo /sbin/halt -p");
02928
02929 if (!halt_cmd.isEmpty())
02930 {
02931
02932 m_mainServer->ShutSlaveBackendsDown(halt_cmd);
02933
02934 LOG(VB_GENERAL, LOG_NOTICE,
02935 QString("Running the command to shutdown "
02936 "this computer :-\n\t\t\t\t\t\t") + halt_cmd);
02937
02938
02939 schedLock.unlock();
02940 uint res = myth_system(halt_cmd);
02941 schedLock.lock();
02942 if (res == GENERIC_EXIT_OK)
02943 return;
02944
02945 LOG(VB_GENERAL, LOG_ERR, "ServerHaltCommand failed, shutdown aborted");
02946 }
02947
02948
02949
02950 idleSince = QDateTime();
02951 m_isShuttingDown = false;
02952 }
02953
02954 void Scheduler::PutInactiveSlavesToSleep(void)
02955 {
02956 int prerollseconds = 0;
02957 int secsleft = 0;
02958 EncoderLink *enc = NULL;
02959
02960 bool someSlavesCanSleep = false;
02961 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
02962 for (; enciter != m_tvList->end(); ++enciter)
02963 {
02964 EncoderLink *enc = *enciter;
02965
02966 if (enc->CanSleep())
02967 someSlavesCanSleep = true;
02968 }
02969
02970 if (!someSlavesCanSleep)
02971 return;
02972
02973 LOG(VB_SCHEDULE, LOG_INFO,
02974 "Scheduler, Checking for slaves that can be shut down");
02975
02976 int sleepThreshold =
02977 gCoreContext->GetNumSetting( "SleepThreshold", 60 * 45);
02978
02979 LOG(VB_SCHEDULE, LOG_DEBUG,
02980 QString(" Getting list of slaves that will be active in the "
02981 "next %1 minutes.") .arg(sleepThreshold / 60));
02982
02983 LOG(VB_SCHEDULE, LOG_DEBUG, "Checking scheduler's reclist");
02984 RecIter recIter = reclist.begin();
02985 QDateTime curtime = QDateTime::currentDateTime();
02986 QStringList SlavesInUse;
02987 for ( ; recIter != reclist.end(); ++recIter)
02988 {
02989 RecordingInfo *pginfo = *recIter;
02990
02991 if ((pginfo->GetRecordingStatus() != rsRecording) &&
02992 (pginfo->GetRecordingStatus() != rsTuning) &&
02993 (pginfo->GetRecordingStatus() != rsWillRecord))
02994 continue;
02995
02996 secsleft = curtime.secsTo(
02997 pginfo->GetRecordingStartTime()) - prerollseconds;
02998 if (secsleft > sleepThreshold)
02999 continue;
03000
03001 if (m_tvList->find(pginfo->GetCardID()) != m_tvList->end())
03002 {
03003 enc = (*m_tvList)[pginfo->GetCardID()];
03004 if ((!enc->IsLocal()) &&
03005 (!SlavesInUse.contains(enc->GetHostName())))
03006 {
03007 if (pginfo->GetRecordingStatus() == rsWillRecord)
03008 LOG(VB_SCHEDULE, LOG_DEBUG,
03009 QString(" Slave %1 will be in use in %2 minutes")
03010 .arg(enc->GetHostName()) .arg(secsleft / 60));
03011 else
03012 LOG(VB_SCHEDULE, LOG_DEBUG,
03013 QString(" Slave %1 is in use currently "
03014 "recording '%1'")
03015 .arg(enc->GetHostName()).arg(pginfo->GetTitle()));
03016 SlavesInUse << enc->GetHostName();
03017 }
03018 }
03019 }
03020
03021 LOG(VB_SCHEDULE, LOG_DEBUG, " Checking inuseprograms table:");
03022 QDateTime oneHourAgo = QDateTime::currentDateTime().addSecs(-61 * 60);
03023 MSqlQuery query(MSqlQuery::InitCon());
03024 query.prepare("SELECT DISTINCT hostname, recusage FROM inuseprograms "
03025 "WHERE lastupdatetime > :ONEHOURAGO ;");
03026 query.bindValue(":ONEHOURAGO", oneHourAgo);
03027 if (query.exec())
03028 {
03029 while(query.next()) {
03030 SlavesInUse << query.value(0).toString();
03031 LOG(VB_SCHEDULE, LOG_DEBUG,
03032 QString(" Slave %1 is marked as in use by a %2")
03033 .arg(query.value(0).toString())
03034 .arg(query.value(1).toString()));
03035 }
03036 }
03037
03038 LOG(VB_SCHEDULE, LOG_DEBUG, QString(" Shutting down slaves which will "
03039 "be inactive for the next %1 minutes and can be put to sleep.")
03040 .arg(sleepThreshold / 60));
03041
03042 enciter = m_tvList->begin();
03043 for (; enciter != m_tvList->end(); ++enciter)
03044 {
03045 enc = *enciter;
03046
03047 if ((!enc->IsLocal()) &&
03048 (enc->IsAwake()) &&
03049 (!SlavesInUse.contains(enc->GetHostName())) &&
03050 (!enc->IsFallingAsleep()))
03051 {
03052 QString sleepCommand =
03053 gCoreContext->GetSettingOnHost("SleepCommand",
03054 enc->GetHostName());
03055 QString wakeUpCommand =
03056 gCoreContext->GetSettingOnHost("WakeUpCommand",
03057 enc->GetHostName());
03058
03059 if (!sleepCommand.isEmpty() && !wakeUpCommand.isEmpty())
03060 {
03061 QString thisHost = enc->GetHostName();
03062
03063 LOG(VB_SCHEDULE, LOG_DEBUG,
03064 QString(" Commanding %1 to go to sleep.")
03065 .arg(thisHost));
03066
03067 if (enc->GoToSleep())
03068 {
03069 QMap<int, EncoderLink *>::Iterator slviter =
03070 m_tvList->begin();
03071 for (; slviter != m_tvList->end(); ++slviter)
03072 {
03073 EncoderLink *slv = *slviter;
03074 if (slv->GetHostName() == thisHost)
03075 {
03076 LOG(VB_SCHEDULE, LOG_DEBUG,
03077 QString(" Marking card %1 on slave %2 "
03078 "as falling asleep.")
03079 .arg(slv->GetCardID())
03080 .arg(slv->GetHostName()));
03081 slv->SetSleepStatus(sStatus_FallingAsleep);
03082 }
03083 }
03084 }
03085 else
03086 {
03087 LOG(VB_GENERAL, LOG_ERR, LOC +
03088 QString("Unable to shutdown %1 slave backend, setting "
03089 "sleep status to undefined.").arg(thisHost));
03090 QMap<int, EncoderLink *>::Iterator slviter =
03091 m_tvList->begin();
03092 for (; slviter != m_tvList->end(); ++slviter)
03093 {
03094 EncoderLink *slv = *slviter;
03095 if (slv->GetHostName() == thisHost)
03096 slv->SetSleepStatus(sStatus_Undefined);
03097 }
03098 }
03099 }
03100 }
03101 }
03102 }
03103
03104 bool Scheduler::WakeUpSlave(QString slaveHostname, bool setWakingStatus)
03105 {
03106 if (slaveHostname == gCoreContext->GetHostName())
03107 {
03108 LOG(VB_GENERAL, LOG_NOTICE,
03109 QString("Tried to Wake Up %1, but this is the "
03110 "master backend and it is not asleep.")
03111 .arg(slaveHostname));
03112 return false;
03113 }
03114
03115 QString wakeUpCommand = gCoreContext->GetSettingOnHost( "WakeUpCommand",
03116 slaveHostname);
03117
03118 if (wakeUpCommand.isEmpty()) {
03119 LOG(VB_GENERAL, LOG_NOTICE,
03120 QString("Trying to Wake Up %1, but this slave "
03121 "does not have a WakeUpCommand set.").arg(slaveHostname));
03122
03123 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
03124 for (; enciter != m_tvList->end(); ++enciter)
03125 {
03126 EncoderLink *enc = *enciter;
03127 if (enc->GetHostName() == slaveHostname)
03128 enc->SetSleepStatus(sStatus_Undefined);
03129 }
03130
03131 return false;
03132 }
03133
03134 QDateTime curtime = QDateTime::currentDateTime();
03135 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
03136 for (; enciter != m_tvList->end(); ++enciter)
03137 {
03138 EncoderLink *enc = *enciter;
03139 if (setWakingStatus && (enc->GetHostName() == slaveHostname))
03140 enc->SetSleepStatus(sStatus_Waking);
03141 enc->SetLastWakeTime(curtime);
03142 }
03143
03144 if (!IsMACAddress(wakeUpCommand))
03145 {
03146 LOG(VB_SCHEDULE, LOG_NOTICE, QString("Executing '%1' to wake up slave.")
03147 .arg(wakeUpCommand));
03148 myth_system(wakeUpCommand);
03149 return true;
03150 }
03151
03152 return WakeOnLAN(wakeUpCommand);
03153 }
03154
03155 void Scheduler::WakeUpSlaves(void)
03156 {
03157 QStringList SlavesThatCanWake;
03158 QString thisSlave;
03159 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
03160 for (; enciter != m_tvList->end(); ++enciter)
03161 {
03162 EncoderLink *enc = *enciter;
03163
03164 if (enc->IsLocal())
03165 continue;
03166
03167 thisSlave = enc->GetHostName();
03168
03169 if ((!gCoreContext->GetSettingOnHost("WakeUpCommand", thisSlave)
03170 .isEmpty()) &&
03171 (!SlavesThatCanWake.contains(thisSlave)))
03172 SlavesThatCanWake << thisSlave;
03173 }
03174
03175 int slave = 0;
03176 for (; slave < SlavesThatCanWake.count(); slave++)
03177 {
03178 thisSlave = SlavesThatCanWake[slave];
03179 LOG(VB_SCHEDULE, LOG_NOTICE,
03180 QString("Scheduler, Sending wakeup command to slave: %1")
03181 .arg(thisSlave));
03182 WakeUpSlave(thisSlave, false);
03183 }
03184 }
03185
03186 void Scheduler::UpdateManuals(uint recordid)
03187 {
03188 MSqlQuery query(dbConn);
03189
03190 query.prepare(QString("SELECT type,title,station,startdate,starttime, "
03191 " enddate,endtime "
03192 "FROM %1 WHERE recordid = :RECORDID").arg(recordTable));
03193 query.bindValue(":RECORDID", recordid);
03194 if (!query.exec() || query.size() != 1)
03195 {
03196 MythDB::DBError("UpdateManuals", query);
03197 return;
03198 }
03199
03200 if (!query.next())
03201 return;
03202
03203 RecordingType rectype = RecordingType(query.value(0).toInt());
03204 QString title = query.value(1).toString();
03205 QString station = query.value(2).toString() ;
03206 QDateTime startdt = QDateTime(query.value(3).toDate(),
03207 query.value(4).toTime());
03208 int duration = startdt.secsTo(QDateTime(query.value(5).toDate(),
03209 query.value(6).toTime())) / 60;
03210
03211 query.prepare("SELECT chanid from channel "
03212 "WHERE callsign = :STATION");
03213 query.bindValue(":STATION", station);
03214 if (!query.exec())
03215 {
03216 MythDB::DBError("UpdateManuals", query);
03217 return;
03218 }
03219
03220 vector<uint> chanidlist;
03221 while (query.next())
03222 chanidlist.push_back(query.value(0).toUInt());
03223
03224 int progcount;
03225 int skipdays;
03226 bool weekday;
03227 int weeksoff;
03228
03229 switch (rectype)
03230 {
03231 case kSingleRecord:
03232 case kOverrideRecord:
03233 case kDontRecord:
03234 progcount = 1;
03235 skipdays = 1;
03236 weekday = false;
03237 break;
03238 case kTimeslotRecord:
03239 progcount = 13;
03240 skipdays = 1;
03241 if (startdt.date().dayOfWeek() < 6)
03242 weekday = true;
03243 else
03244 weekday = false;
03245 startdt.setDate(QDate::currentDate());
03246 break;
03247 case kWeekslotRecord:
03248 progcount = 2;
03249 skipdays = 7;
03250 weekday = false;
03251 weeksoff = (startdt.date().daysTo(QDate::currentDate()) + 6) / 7;
03252 startdt = startdt.addDays(weeksoff * 7);
03253 break;
03254 default:
03255 LOG(VB_GENERAL, LOG_ERR,
03256 QString("Invalid rectype for manual recordid %1").arg(recordid));
03257 return;
03258 }
03259
03260 while (progcount--)
03261 {
03262 for (int i = 0; i < (int)chanidlist.size(); i++)
03263 {
03264 if (weekday && startdt.date().dayOfWeek() >= 6)
03265 continue;
03266
03267 query.prepare("REPLACE INTO program (chanid, starttime, endtime,"
03268 " title, subtitle, manualid, generic) "
03269 "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
03270 " :SUBTITLE, :RECORDID, 1)");
03271 query.bindValue(":CHANID", chanidlist[i]);
03272 query.bindValue(":STARTTIME", startdt);
03273 query.bindValue(":ENDTIME", startdt.addSecs(duration * 60));
03274 query.bindValue(":TITLE", title);
03275 query.bindValue(":SUBTITLE", startdt.toString());
03276 query.bindValue(":RECORDID", recordid);
03277 if (!query.exec())
03278 {
03279 MythDB::DBError("UpdateManuals", query);
03280 return;
03281 }
03282 }
03283 startdt = startdt.addDays(skipdays);
03284 }
03285 }
03286
03287 void Scheduler::BuildNewRecordsQueries(uint recordid, QStringList &from,
03288 QStringList &where,
03289 MSqlBindings &bindings)
03290 {
03291 MSqlQuery result(dbConn);
03292 QString query;
03293 QString qphrase;
03294
03295 query = QString("SELECT recordid,search,subtitle,description "
03296 "FROM %1 WHERE search <> %2 AND "
03297 "(recordid = %3 OR %4 = 0) ")
03298 .arg(recordTable).arg(kNoSearch).arg(recordid).arg(recordid);
03299
03300 result.prepare(query);
03301
03302 if (!result.exec() || !result.isActive())
03303 {
03304 MythDB::DBError("BuildNewRecordsQueries", result);
03305 return;
03306 }
03307
03308 int count = 0;
03309 while (result.next())
03310 {
03311 QString prefix = QString(":NR%1").arg(count);
03312 qphrase = result.value(3).toString();
03313
03314 RecSearchType searchtype = RecSearchType(result.value(1).toInt());
03315
03316 if (qphrase.isEmpty() && searchtype != kManualSearch)
03317 {
03318 LOG(VB_GENERAL, LOG_ERR,
03319 QString("Invalid search key in recordid %1")
03320 .arg(result.value(0).toString()));
03321 continue;
03322 }
03323
03324 QString bindrecid = prefix + "RECID";
03325 QString bindphrase = prefix + "PHRASE";
03326 QString bindlikephrase1 = prefix + "LIKEPHRASE1";
03327 QString bindlikephrase2 = prefix + "LIKEPHRASE2";
03328 QString bindlikephrase3 = prefix + "LIKEPHRASE3";
03329
03330 bindings[bindrecid] = result.value(0).toString();
03331
03332 switch (searchtype)
03333 {
03334 case kPowerSearch:
03335 qphrase.remove(QRegExp("^\\s*AND\\s+", Qt::CaseInsensitive));
03336 qphrase.remove(';');
03337 from << result.value(2).toString();
03338 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid +
03339 QString(" AND program.manualid = 0 AND ( %2 )")
03340 .arg(qphrase));
03341 break;
03342 case kTitleSearch:
03343 bindings[bindlikephrase1] = QString(QString("%") + qphrase + "%");
03344 from << "";
03345 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid + " AND "
03346 "program.manualid = 0 AND "
03347 "program.title LIKE " + bindlikephrase1);
03348 break;
03349 case kKeywordSearch:
03350 bindings[bindlikephrase1] = QString(QString("%") + qphrase + "%");
03351 bindings[bindlikephrase2] = QString(QString("%") + qphrase + "%");
03352 bindings[bindlikephrase3] = QString(QString("%") + qphrase + "%");
03353 from << "";
03354 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid +
03355 " AND program.manualid = 0"
03356 " AND (program.title LIKE " + bindlikephrase1 +
03357 " OR program.subtitle LIKE " + bindlikephrase2 +
03358 " OR program.description LIKE " + bindlikephrase3 + ")");
03359 break;
03360 case kPeopleSearch:
03361 bindings[bindphrase] = qphrase;
03362 from << ", people, credits";
03363 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid + " AND "
03364 "program.manualid = 0 AND "
03365 "people.name LIKE " + bindphrase + " AND "
03366 "credits.person = people.person AND "
03367 "program.chanid = credits.chanid AND "
03368 "program.starttime = credits.starttime");
03369 break;
03370 case kManualSearch:
03371 UpdateManuals(result.value(0).toInt());
03372 from << "";
03373 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid +
03374 " AND " +
03375 QString("program.manualid = %1.recordid ")
03376 .arg(recordTable));
03377 break;
03378 default:
03379 LOG(VB_GENERAL, LOG_ERR,
03380 QString("Unknown RecSearchType (%1) for recordid %2")
03381 .arg(result.value(1).toInt())
03382 .arg(result.value(0).toString()));
03383 bindings.remove(bindrecid);
03384 break;
03385 }
03386
03387 count++;
03388 }
03389
03390 if (recordid == 0 || from.count() == 0)
03391 {
03392 QString recidmatch = "";
03393 if (recordid != 0)
03394 recidmatch = "RECTABLE.recordid = :NRRECORDID AND ";
03395 QString s1 = recidmatch +
03396 "RECTABLE.type <> :NRTEMPLATE AND "
03397 "RECTABLE.search = :NRST AND "
03398 "program.manualid = 0 AND "
03399 "program.title = RECTABLE.title ";
03400 s1.replace("RECTABLE", recordTable);
03401 QString s2 = recidmatch +
03402 "RECTABLE.type <> :NRTEMPLATE AND "
03403 "RECTABLE.search = :NRST AND "
03404 "program.manualid = 0 AND "
03405 "program.seriesid <> '' AND "
03406 "program.seriesid = RECTABLE.seriesid ";
03407 s2.replace("RECTABLE", recordTable);
03408
03409 from << "";
03410 where << s1;
03411 from << "";
03412 where << s2;
03413 bindings[":NRTEMPLATE"] = kTemplateRecord;
03414 bindings[":NRST"] = kNoSearch;
03415 if (recordid != 0)
03416 bindings[":NRRECORDID"] = recordid;
03417 }
03418 }
03419
03420 static QString progdupinit = QString(
03421 "(CASE "
03422 " WHEN RECTABLE.type IN (%1, %2, %3) THEN 0 "
03423 " WHEN RECTABLE.type IN (%4, %5, %6) THEN -1 "
03424 " ELSE (program.generic - 1) "
03425 " END) ")
03426 .arg(kSingleRecord).arg(kOverrideRecord).arg(kDontRecord)
03427 .arg(kFindOneRecord).arg(kFindDailyRecord).arg(kFindWeeklyRecord);
03428
03429 static QString progfindid = QString(
03430 "(CASE RECTABLE.type "
03431 " WHEN %1 "
03432 " THEN RECTABLE.findid "
03433 " WHEN %2 "
03434 " THEN to_days(date_sub(program.starttime, interval "
03435 " time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
03436 " WHEN %3 "
03437 " THEN floor((to_days(date_sub(program.starttime, interval "
03438 " time_format(RECTABLE.findtime, '%H:%i') hour_minute)) - "
03439 " RECTABLE.findday)/7) * 7 + RECTABLE.findday "
03440 " WHEN %4 "
03441 " THEN RECTABLE.findid "
03442 " ELSE 0 "
03443 " END) ")
03444 .arg(kFindOneRecord)
03445 .arg(kFindDailyRecord)
03446 .arg(kFindWeeklyRecord)
03447 .arg(kOverrideRecord);
03448
03449 void Scheduler::UpdateMatches(uint recordid, uint sourceid, uint mplexid,
03450 const QDateTime maxstarttime)
03451 {
03452 struct timeval dbstart, dbend;
03453
03454 MSqlQuery query(dbConn);
03455 MSqlBindings bindings;
03456 QString deleteClause;
03457 QString filterClause = QString(" AND program.endtime > "
03458 "(NOW() - INTERVAL 480 MINUTE)");
03459
03460 if (recordid)
03461 {
03462 deleteClause += " AND recordmatch.recordid = :RECORDID";
03463 bindings[":RECORDID"] = recordid;
03464 }
03465 if (sourceid)
03466 {
03467 deleteClause += " AND channel.sourceid = :SOURCEID";
03468 filterClause += " AND channel.sourceid = :SOURCEID";
03469 bindings[":SOURCEID"] = sourceid;
03470 }
03471 if (mplexid)
03472 {
03473 deleteClause += " AND channel.mplexid = :MPLEXID";
03474 filterClause += " AND channel.mplexid = :MPLEXID";
03475 bindings[":MPLEXID"] = mplexid;
03476 }
03477 if (maxstarttime.isValid())
03478 {
03479 deleteClause += " AND recordmatch.starttime <= :MAXSTARTTIME";
03480 filterClause += " AND program.starttime <= :MAXSTARTTIME";
03481 bindings[":MAXSTARTTIME"] = maxstarttime;
03482 }
03483
03484 query.prepare(QString("DELETE recordmatch FROM recordmatch, channel "
03485 "WHERE recordmatch.chanid = channel.chanid")
03486 + deleteClause);
03487 MSqlBindings::const_iterator it;
03488 for (it = bindings.begin(); it != bindings.end(); ++it)
03489 query.bindValue(it.key(), it.value());
03490 if (!query.exec())
03491 {
03492 MythDB::DBError("UpdateMatches1", query);
03493 return;
03494 }
03495 if (recordid)
03496 bindings.remove(":RECORDID");
03497
03498 query.prepare("SELECT filterid, clause FROM recordfilter "
03499 "WHERE filterid >= 0 AND filterid < :NUMFILTERS AND "
03500 " TRIM(clause) <> ''");
03501 query.bindValue(":NUMFILTERS", RecordingRule::kNumFilters);
03502 if (!query.exec())
03503 {
03504 MythDB::DBError("UpdateMatches2", query);
03505 return;
03506 }
03507 while (query.next())
03508 {
03509 filterClause += QString(" AND (((RECTABLE.filter & %1) = 0) OR (%2))")
03510 .arg(1 << query.value(0).toInt()).arg(query.value(1).toString());
03511 }
03512
03513
03514 query.prepare("SELECT NULL from record "
03515 "WHERE type = :FINDONE AND findid <= 0;");
03516 query.bindValue(":FINDONE", kFindOneRecord);
03517 if (!query.exec())
03518 {
03519 MythDB::DBError("UpdateMatches3", query);
03520 return;
03521 }
03522 else if (query.size())
03523 {
03524 QDate epoch(1970, 1, 1);
03525 int findtoday = epoch.daysTo(QDate::currentDate()) + 719528;
03526 query.prepare("UPDATE record set findid = :FINDID "
03527 "WHERE type = :FINDONE AND findid <= 0;");
03528 query.bindValue(":FINDID", findtoday);
03529 query.bindValue(":FINDONE", kFindOneRecord);
03530 if (!query.exec())
03531 MythDB::DBError("UpdateMatches4", query);
03532 }
03533
03534 int clause;
03535 QStringList fromclauses, whereclauses;
03536
03537 BuildNewRecordsQueries(recordid, fromclauses, whereclauses, bindings);
03538
03539 if (VERBOSE_LEVEL_CHECK(VB_SCHEDULE, LOG_INFO))
03540 {
03541 for (clause = 0; clause < fromclauses.count(); ++clause)
03542 {
03543 LOG(VB_SCHEDULE, LOG_INFO, QString("Query %1: %2/%3")
03544 .arg(clause).arg(fromclauses[clause])
03545 .arg(whereclauses[clause]));
03546 }
03547 }
03548
03549 for (clause = 0; clause < fromclauses.count(); ++clause)
03550 {
03551 QString query = QString(
03552 "REPLACE INTO recordmatch (recordid, chanid, starttime, manualid, "
03553 " oldrecduplicate, findid) "
03554 "SELECT RECTABLE.recordid, program.chanid, program.starttime, "
03555 " IF(search = %1, RECTABLE.recordid, 0), ").arg(kManualSearch) +
03556 progdupinit + ", " + progfindid + QString(
03557 "FROM (RECTABLE, program INNER JOIN channel "
03558 " ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
03559 " WHERE ") + whereclauses[clause] +
03560 QString(" AND channel.visible = 1 ") +
03561 filterClause + QString(" AND "
03562
03563 "((RECTABLE.type = %1 "
03564 "OR RECTABLE.type = %2 "
03565 "OR RECTABLE.type = %3 "
03566 "OR RECTABLE.type = %4) "
03567 " OR "
03568 " ((RECTABLE.station = channel.callsign) "
03569 " AND "
03570 " ((RECTABLE.type = %5) "
03571 " OR"
03572 " ((TIME_TO_SEC(RECTABLE.starttime) = TIME_TO_SEC(program.starttime)) "
03573 " AND "
03574 " ((RECTABLE.type = %6) "
03575 " OR"
03576 " ((DAYOFWEEK(RECTABLE.startdate) = DAYOFWEEK(program.starttime) "
03577 " AND "
03578 " ((RECTABLE.type = %7) "
03579 " OR"
03580 " ((TO_DAYS(RECTABLE.startdate) = TO_DAYS(program.starttime)) "
03581 " AND (RECTABLE.type <> %8)"
03582 " )"
03583 " )"
03584 " )"
03585 " )"
03586 " )"
03587 " )"
03588 " )"
03589 " )"
03590 ") ")
03591 .arg(kAllRecord)
03592 .arg(kFindOneRecord)
03593 .arg(kFindDailyRecord)
03594 .arg(kFindWeeklyRecord)
03595 .arg(kChannelRecord)
03596 .arg(kTimeslotRecord)
03597 .arg(kWeekslotRecord)
03598 .arg(kNotRecording);
03599
03600 query.replace("RECTABLE", recordTable);
03601
03602 LOG(VB_SCHEDULE, LOG_INFO, QString(" |-- Start DB Query %1...")
03603 .arg(clause));
03604
03605 gettimeofday(&dbstart, NULL);
03606 MSqlQuery result(dbConn);
03607 result.prepare(query);
03608
03609 for (it = bindings.begin(); it != bindings.end(); ++it)
03610 {
03611 if (query.contains(it.key()))
03612 result.bindValue(it.key(), it.value());
03613 }
03614
03615 bool ok = result.exec();
03616 gettimeofday(&dbend, NULL);
03617
03618 if (!ok)
03619 {
03620 MythDB::DBError("UpdateMatches3", result);
03621 continue;
03622 }
03623
03624 LOG(VB_SCHEDULE, LOG_INFO, QString(" |-- %1 results in %2 sec.")
03625 .arg(result.size())
03626 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
03627 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
03628
03629 }
03630
03631 LOG(VB_SCHEDULE, LOG_INFO, " +-- Done.");
03632 }
03633
03634 void Scheduler::CreateTempTables(void)
03635 {
03636 MSqlQuery result(dbConn);
03637
03638 if (recordTable == "record")
03639 {
03640 result.prepare("DROP TABLE IF EXISTS sched_temp_record;");
03641 if (!result.exec())
03642 {
03643 MythDB::DBError("Dropping sched_temp_record table", result);
03644 return;
03645 }
03646 result.prepare("CREATE TEMPORARY TABLE sched_temp_record "
03647 "LIKE record;");
03648 if (!result.exec())
03649 {
03650 MythDB::DBError("Creating sched_temp_record table", result);
03651 return;
03652 }
03653 result.prepare("INSERT sched_temp_record SELECT * from record;");
03654 if (!result.exec())
03655 {
03656 MythDB::DBError("Populating sched_temp_record table", result);
03657 return;
03658 }
03659 }
03660
03661 result.prepare("DROP TABLE IF EXISTS sched_temp_recorded;");
03662 if (!result.exec())
03663 {
03664 MythDB::DBError("Dropping sched_temp_recorded table", result);
03665 return;
03666 }
03667 result.prepare("CREATE TEMPORARY TABLE sched_temp_recorded "
03668 "LIKE recorded;");
03669 if (!result.exec())
03670 {
03671 MythDB::DBError("Creating sched_temp_recorded table", result);
03672 return;
03673 }
03674 result.prepare("INSERT sched_temp_recorded SELECT * from recorded;");
03675 if (!result.exec())
03676 {
03677 MythDB::DBError("Populating sched_temp_recorded table", result);
03678 return;
03679 }
03680 }
03681
03682 void Scheduler::DeleteTempTables(void)
03683 {
03684 MSqlQuery result(dbConn);
03685
03686 if (recordTable == "record")
03687 {
03688 result.prepare("DROP TABLE IF EXISTS sched_temp_record;");
03689 if (!result.exec())
03690 MythDB::DBError("DeleteTempTables sched_temp_record", result);
03691 }
03692
03693 result.prepare("DROP TABLE IF EXISTS sched_temp_recorded;");
03694 if (!result.exec())
03695 MythDB::DBError("DeleteTempTables drop table", result);
03696 }
03697
03698 void Scheduler::UpdateDuplicates(void)
03699 {
03700 QString schedTmpRecord = recordTable;
03701 if (schedTmpRecord == "record")
03702 schedTmpRecord = "sched_temp_record";
03703
03704 QString rmquery = QString(
03705 "UPDATE recordmatch "
03706 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
03707 " INNER JOIN program p ON (recordmatch.chanid = p.chanid AND "
03708 " recordmatch.starttime = p.starttime AND "
03709 " recordmatch.manualid = p.manualid) "
03710 " LEFT JOIN oldrecorded ON "
03711 " ( "
03712 " RECTABLE.dupmethod > 1 AND "
03713 " oldrecorded.duplicate <> 0 AND "
03714 " p.title = oldrecorded.title AND "
03715 " p.generic = 0 "
03716 " AND "
03717 " ( "
03718 " (p.programid <> '' "
03719 " AND p.programid = oldrecorded.programid) "
03720 " OR "
03721 " ( ") +
03722 (ProgramInfo::UsingProgramIDAuthority() ?
03723 " (p.programid = '' OR oldrecorded.programid = '' OR "
03724 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
03725 " LEFT(oldrecorded.programid, LOCATE('/', oldrecorded.programid))) " :
03726 " (p.programid = '' OR oldrecorded.programid = '') " )
03727 + QString(
03728 " AND "
03729 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
03730 " AND p.subtitle = oldrecorded.subtitle)) "
03731 " AND "
03732 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
03733 " AND p.description = oldrecorded.description)) "
03734 " AND "
03735 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
03736 " (p.subtitle <> '' AND "
03737 " (p.subtitle = oldrecorded.subtitle OR "
03738 " (oldrecorded.subtitle = '' AND "
03739 " p.subtitle = oldrecorded.description))) OR "
03740 " (p.subtitle = '' AND p.description <> '' AND "
03741 " (p.description = oldrecorded.subtitle OR "
03742 " (oldrecorded.subtitle = '' AND "
03743 " p.description = oldrecorded.description)))) "
03744 " ) "
03745 " ) "
03746 " ) "
03747 " LEFT JOIN sched_temp_recorded recorded ON "
03748 " ( "
03749 " RECTABLE.dupmethod > 1 AND "
03750 " recorded.duplicate <> 0 AND "
03751 " p.title = recorded.title AND "
03752 " p.generic = 0 AND "
03753 " recorded.recgroup NOT IN ('LiveTV','Deleted') "
03754 " AND "
03755 " ( "
03756 " (p.programid <> '' "
03757 " AND p.programid = recorded.programid) "
03758 " OR "
03759 " ( ") +
03760 (ProgramInfo::UsingProgramIDAuthority() ?
03761 " (p.programid = '' OR recorded.programid = '' OR "
03762 " LEFT(p.programid, LOCATE('/', p.programid)) <> "
03763 " LEFT(recorded.programid, LOCATE('/', recorded.programid))) " :
03764 " (p.programid = '' OR recorded.programid = '') ")
03765 + QString(
03766 " AND "
03767 " (((RECTABLE.dupmethod & 0x02) = 0) OR (p.subtitle <> '' "
03768 " AND p.subtitle = recorded.subtitle)) "
03769 " AND "
03770 " (((RECTABLE.dupmethod & 0x04) = 0) OR (p.description <> '' "
03771 " AND p.description = recorded.description)) "
03772 " AND "
03773 " (((RECTABLE.dupmethod & 0x08) = 0) OR "
03774 " (p.subtitle <> '' AND "
03775 " (p.subtitle = recorded.subtitle OR "
03776 " (recorded.subtitle = '' AND "
03777 " p.subtitle = recorded.description))) OR "
03778 " (p.subtitle = '' AND p.description <> '' AND "
03779 " (p.description = recorded.subtitle OR "
03780 " (recorded.subtitle = '' AND "
03781 " p.description = recorded.description)))) "
03782 " ) "
03783 " ) "
03784 " ) "
03785 " LEFT JOIN oldfind ON "
03786 " (oldfind.recordid = recordmatch.recordid AND "
03787 " oldfind.findid = recordmatch.findid) "
03788 " SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
03789 " recduplicate = (recorded.endtime IS NOT NULL), "
03790 " findduplicate = (oldfind.findid IS NOT NULL), "
03791 " oldrecstatus = oldrecorded.recstatus "
03792 " WHERE p.endtime >= (NOW() - INTERVAL 480 MINUTE) "
03793 " AND oldrecduplicate = -1 "
03794 );
03795 rmquery.replace("RECTABLE", schedTmpRecord);
03796
03797 MSqlQuery result(dbConn);
03798 result.prepare(rmquery);
03799 if (!result.exec())
03800 {
03801 MythDB::DBError("UpdateDuplicates", result);
03802 return;
03803 }
03804 }
03805
03806 void Scheduler::AddNewRecords(void)
03807 {
03808 QString schedTmpRecord = recordTable;
03809 if (schedTmpRecord == "record")
03810 schedTmpRecord = "sched_temp_record";
03811
03812 struct timeval dbstart, dbend;
03813
03814 QMap<RecordingType, int> recTypeRecPriorityMap;
03815 RecList tmpList;
03816
03817 QMap<int, bool> cardMap;
03818 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
03819 for (; enciter != m_tvList->end(); ++enciter)
03820 {
03821 EncoderLink *enc = *enciter;
03822 if (enc->IsConnected() || enc->IsAsleep())
03823 cardMap[enc->GetCardID()] = true;
03824 }
03825
03826 QMap<int, bool> tooManyMap;
03827 bool checkTooMany = false;
03828 schedAfterStartMap.clear();
03829
03830 MSqlQuery rlist(dbConn);
03831 rlist.prepare(QString("SELECT recordid, title, maxepisodes, maxnewest "
03832 "FROM %1").arg(schedTmpRecord));
03833
03834 if (!rlist.exec())
03835 {
03836 MythDB::DBError("CheckTooMany", rlist);
03837 return;
03838 }
03839
03840 while (rlist.next())
03841 {
03842 int recid = rlist.value(0).toInt();
03843 QString qtitle = rlist.value(1).toString();
03844 int maxEpisodes = rlist.value(2).toInt();
03845 int maxNewest = rlist.value(3).toInt();
03846
03847 tooManyMap[recid] = false;
03848 schedAfterStartMap[recid] = false;
03849
03850 if (maxEpisodes && !maxNewest)
03851 {
03852 MSqlQuery epicnt(dbConn);
03853
03854 epicnt.prepare("SELECT DISTINCT chanid, progstart, progend "
03855 "FROM recorded "
03856 "WHERE recordid = :RECID AND preserve = 0 "
03857 "AND recgroup NOT IN ('LiveTV','Deleted');");
03858 epicnt.bindValue(":RECID", recid);
03859
03860 if (epicnt.exec())
03861 {
03862 if (epicnt.size() >= maxEpisodes - 1)
03863 {
03864 schedAfterStartMap[recid] = true;
03865 if (epicnt.size() >= maxEpisodes)
03866 {
03867 tooManyMap[recid] = true;
03868 checkTooMany = true;
03869 }
03870 }
03871 }
03872 }
03873 }
03874
03875 prefinputpri = gCoreContext->GetNumSetting("PrefInputPriority", 2);
03876 int hdtvpriority = gCoreContext->GetNumSetting("HDTVRecPriority", 0);
03877 int wspriority = gCoreContext->GetNumSetting("WSRecPriority", 0);
03878 int autopriority = gCoreContext->GetNumSetting("AutoRecPriority", 0);
03879 int slpriority = gCoreContext->GetNumSetting("SignLangRecPriority", 0);
03880 int onscrpriority = gCoreContext->GetNumSetting("OnScrSubRecPriority", 0);
03881 int ccpriority = gCoreContext->GetNumSetting("CCRecPriority", 0);
03882 int hhpriority = gCoreContext->GetNumSetting("HardHearRecPriority", 0);
03883 int adpriority = gCoreContext->GetNumSetting("AudioDescRecPriority", 0);
03884
03885 int autostrata = autopriority * 2 + 1;
03886
03887 QString pwrpri = "channel.recpriority + cardinput.recpriority";
03888
03889 if (prefinputpri)
03890 pwrpri += QString(" + "
03891 "(cardinput.cardinputid = RECTABLE.prefinput) * %1").arg(prefinputpri);
03892
03893 if (hdtvpriority)
03894 pwrpri += QString(" + (program.hdtv > 0 OR "
03895 "FIND_IN_SET('HDTV', program.videoprop) > 0) * %1").arg(hdtvpriority);
03896
03897 if (wspriority)
03898 pwrpri += QString(" + "
03899 "(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0) * %1").arg(wspriority);
03900
03901 if (slpriority)
03902 pwrpri += QString(" + "
03903 "(FIND_IN_SET('SIGNED', program.subtitletypes) > 0) * %1").arg(slpriority);
03904
03905 if (onscrpriority)
03906 pwrpri += QString(" + "
03907 "(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0) * %1").arg(onscrpriority);
03908
03909 if (ccpriority)
03910 pwrpri += QString(" + "
03911 "(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
03912 "program.closecaptioned > 0 OR program.subtitled > 0) * %1").arg(ccpriority);
03913
03914 if (hhpriority)
03915 pwrpri += QString(" + "
03916 "(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
03917 "FIND_IN_SET('HARDHEAR', program.audioprop) > 0) * %1").arg(hhpriority);
03918
03919 if (adpriority)
03920 pwrpri += QString(" + "
03921 "(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0) * %1").arg(adpriority);
03922
03923 MSqlQuery result(dbConn);
03924
03925 result.prepare(QString("SELECT recpriority, selectclause FROM %1;")
03926 .arg(priorityTable));
03927
03928 if (!result.exec())
03929 {
03930 MythDB::DBError("Power Priority", result);
03931 return;
03932 }
03933
03934 while (result.next())
03935 {
03936 if (result.value(0).toInt())
03937 {
03938 QString sclause = result.value(1).toString();
03939 sclause.remove(QRegExp("^\\s*AND\\s+", Qt::CaseInsensitive));
03940 sclause.remove(';');
03941 pwrpri += QString(" + (%1) * %2").arg(sclause)
03942 .arg(result.value(0).toInt());
03943 }
03944 }
03945 pwrpri += QString(" AS powerpriority ");
03946
03947 pwrpri.replace("program.","p.");
03948 pwrpri.replace("channel.","c.");
03949 QString query = QString(
03950 "SELECT "
03951 " c.chanid, c.sourceid, p.starttime, "
03952 " p.endtime, p.title, p.subtitle, "
03953 " p.description, c.channum, c.callsign, "
03954 " c.name, oldrecduplicate, p.category, "
03955 " RECTABLE.recpriority, RECTABLE.dupin, recduplicate, "
03956 " findduplicate, RECTABLE.type, RECTABLE.recordid, "
03957 " p.starttime - INTERVAL RECTABLE.startoffset "
03958 " minute AS recstartts, "
03959 " p.endtime + INTERVAL RECTABLE.endoffset "
03960 " minute AS recendts, "
03961 " p.previouslyshown, "
03962 " RECTABLE.recgroup, RECTABLE.dupmethod, c.commmethod, "
03963 " capturecard.cardid, cardinput.cardinputid,p.seriesid, "
03964 " p.programid, RECTABLE.inetref, p.category_type, "
03965 " p.airdate, p.stars, p.originalairdate, "
03966 " RECTABLE.inactive, RECTABLE.parentid, recordmatch.findid, "
03967 " RECTABLE.playgroup, oldrecstatus.recstatus, "
03968 " oldrecstatus.reactivate, p.videoprop+0, "
03969 " p.subtitletypes+0, p.audioprop+0, RECTABLE.storagegroup, "
03970 " capturecard.hostname, recordmatch.oldrecstatus, "
03971 " RECTABLE.avg_delay, "
03972 " oldrecstatus.future, cardinput.schedorder, ") +
03973 pwrpri + QString(
03974 "FROM recordmatch "
03975 "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
03976 "INNER JOIN program AS p "
03977 "ON ( recordmatch.chanid = p.chanid AND "
03978 " recordmatch.starttime = p.starttime AND "
03979 " recordmatch.manualid = p.manualid ) "
03980 "INNER JOIN channel AS c "
03981 "ON ( c.chanid = p.chanid ) "
03982 "INNER JOIN cardinput ON (c.sourceid = cardinput.sourceid) "
03983 "INNER JOIN capturecard ON (capturecard.cardid = cardinput.cardid) "
03984 "LEFT JOIN oldrecorded as oldrecstatus "
03985 "ON ( oldrecstatus.station = c.callsign AND "
03986 " oldrecstatus.starttime = p.starttime AND "
03987 " oldrecstatus.title = p.title ) "
03988 "WHERE p.endtime > (NOW() - INTERVAL 480 MINUTE) "
03989 "ORDER BY RECTABLE.recordid DESC, p.starttime, p.title, c.callsign, "
03990 " c.channum ");
03991 query.replace("RECTABLE", schedTmpRecord);
03992
03993 LOG(VB_SCHEDULE, LOG_INFO, QString(" |-- Start DB Query..."));
03994
03995 gettimeofday(&dbstart, NULL);
03996 result.prepare(query);
03997 if (!result.exec())
03998 {
03999 MythDB::DBError("AddNewRecords", result);
04000 return;
04001 }
04002 gettimeofday(&dbend, NULL);
04003
04004 LOG(VB_SCHEDULE, LOG_INFO,
04005 QString(" |-- %1 results in %2 sec. Processing...")
04006 .arg(result.size())
04007 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
04008 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
04009
04010 RecordingInfo *lastp = NULL;
04011
04012 while (result.next())
04013 {
04014
04015
04016
04017
04018 uint recordid = result.value(17).toUInt();
04019 QDateTime startts = result.value(2).toDateTime();
04020 QString title = result.value(4).toString();
04021 QString callsign = result.value(8).toString();
04022 if (lastp && lastp->GetRecordingStatus() != rsUnknown
04023 && lastp->GetRecordingStatus() != rsOffLine
04024 && lastp->GetRecordingStatus() != rsDontRecord
04025 && recordid == lastp->GetRecordingRuleID()
04026 && startts == lastp->GetScheduledStartTime()
04027 && title == lastp->GetTitle()
04028 && callsign == lastp->GetChannelSchedulingID())
04029 continue;
04030
04031 RecordingInfo *p = new RecordingInfo(
04032 title,
04033 result.value(5).toString(),
04034 result.value(6).toString(),
04035 0,
04036 0,
04037 result.value(11).toString(),
04038
04039 result.value(0).toUInt(),
04040 result.value(7).toString(),
04041 callsign,
04042 result.value(9).toString(),
04043
04044 result.value(21).toString(),
04045 result.value(36).toString(),
04046
04047 result.value(43).toString(),
04048 result.value(42).toString(),
04049
04050 result.value(30).toUInt(),
04051
04052 result.value(26).toString(),
04053 result.value(27).toString(),
04054 result.value(28).toString(),
04055 result.value(29).toString(),
04056
04057 result.value(12).toInt(),
04058
04059 startts,
04060 result.value(3).toDateTime(),
04061 result.value(18).toDateTime(),
04062 result.value(19).toDateTime(),
04063
04064 result.value(31).toDouble(),
04065 (result.value(32).isNull()) ? QDate() :
04066 QDate::fromString(result.value(32).toString(), Qt::ISODate),
04067
04068
04069 result.value(20).toInt(),
04070
04071 RecStatusType(result.value(37).toInt()),
04072 result.value(38).toInt(),
04073
04074 recordid,
04075 result.value(34).toUInt(),
04076 RecordingType(result.value(16).toInt()),
04077 RecordingDupInType(result.value(13).toInt()),
04078 RecordingDupMethodType(result.value(22).toInt()),
04079
04080 result.value(1).toUInt(),
04081 result.value(25).toUInt(),
04082 result.value(24).toUInt(),
04083
04084 result.value(35).toUInt(),
04085
04086 result.value(23).toInt() == COMM_DETECT_COMMFREE,
04087 result.value(40).toUInt(),
04088 result.value(39).toUInt(),
04089 result.value(41).toUInt(),
04090 result.value(46).toInt());
04091 p->SetRecordingPriority2(result.value(47).toInt());
04092
04093 if (!p->future && !p->IsReactivated() &&
04094 p->oldrecstatus != rsAborted &&
04095 p->oldrecstatus != rsNotListed)
04096 {
04097 p->SetRecordingStatus(p->oldrecstatus);
04098 }
04099
04100 if (!recTypeRecPriorityMap.contains(p->GetRecordingRuleType()))
04101 {
04102 recTypeRecPriorityMap[p->GetRecordingRuleType()] =
04103 p->GetRecordingTypeRecPriority(p->GetRecordingRuleType());
04104 }
04105
04106 p->SetRecordingPriority(
04107 p->GetRecordingPriority() + recTypeRecPriorityMap[p->GetRecordingRuleType()] +
04108 result.value(48).toInt() +
04109 ((autopriority) ?
04110 autopriority - (result.value(45).toInt() * autostrata / 200) : 0));
04111
04112
04113
04114
04115
04116
04117 RecIter rec = worklist.begin();
04118 for ( ; rec != worklist.end(); ++rec)
04119 {
04120 RecordingInfo *r = *rec;
04121 if (p->IsSameTimeslot(*r))
04122 {
04123 if (r->GetInputID() == p->GetInputID() &&
04124 r->GetRecordingEndTime() != p->GetRecordingEndTime() &&
04125 (r->GetRecordingRuleID() == p->GetRecordingRuleID() ||
04126 p->GetRecordingRuleType() == kOverrideRecord))
04127 ChangeRecordingEnd(r, p);
04128 delete p;
04129 p = NULL;
04130 break;
04131 }
04132 }
04133 if (p == NULL)
04134 continue;
04135
04136 lastp = p;
04137
04138 if (p->GetRecordingStatus() != rsUnknown)
04139 {
04140 tmpList.push_back(p);
04141 continue;
04142 }
04143
04144 RecStatusType newrecstatus = rsUnknown;
04145
04146 if ((doRun || specsched) &&
04147 (!cardMap.contains(p->GetCardID()) || !p->GetRecordingPriority2()))
04148 newrecstatus = rsOffLine;
04149
04150
04151 if (checkTooMany && tooManyMap[p->GetRecordingRuleID()] &&
04152 !p->IsReactivated())
04153 {
04154 newrecstatus = rsTooManyRecordings;
04155 }
04156
04157
04158 if (p->GetRecordingRuleType() == kDontRecord)
04159 newrecstatus = rsDontRecord;
04160 else if (result.value(15).toInt() && !p->IsReactivated())
04161 newrecstatus = rsPreviousRecording;
04162 else if (p->GetRecordingRuleType() != kSingleRecord &&
04163 p->GetRecordingRuleType() != kOverrideRecord &&
04164 !p->IsReactivated() &&
04165 !(p->GetDuplicateCheckMethod() & kDupCheckNone))
04166 {
04167 const RecordingDupInType dupin = p->GetDuplicateCheckSource();
04168
04169 if ((dupin & kDupsNewEpi) && p->IsRepeat())
04170 newrecstatus = rsRepeat;
04171
04172 if ((dupin & kDupsInOldRecorded) && result.value(10).toInt())
04173 {
04174 if (result.value(44).toInt() == rsNeverRecord)
04175 newrecstatus = rsNeverRecord;
04176 else
04177 newrecstatus = rsPreviousRecording;
04178 }
04179
04180 if ((dupin & kDupsInRecorded) && result.value(14).toInt())
04181 newrecstatus = rsCurrentRecording;
04182 }
04183
04184 bool inactive = result.value(33).toInt();
04185 if (inactive)
04186 newrecstatus = rsInactive;
04187
04188
04189
04190
04191 if (p->GetRecordingEndTime() < schedTime)
04192 {
04193 if (p->future)
04194 newrecstatus = rsMissedFuture;
04195 else
04196 newrecstatus = rsMissed;
04197 }
04198
04199 p->SetRecordingStatus(newrecstatus);
04200
04201 tmpList.push_back(p);
04202 }
04203
04204 LOG(VB_SCHEDULE, LOG_INFO, " +-- Cleanup...");
04205 RecIter tmp = tmpList.begin();
04206 for ( ; tmp != tmpList.end(); ++tmp)
04207 worklist.push_back(*tmp);
04208 }
04209
04210 void Scheduler::AddNotListed(void) {
04211
04212 struct timeval dbstart, dbend;
04213 RecList tmpList;
04214
04215 QString query = QString(
04216 "SELECT RECTABLE.title, RECTABLE.subtitle, "
04217 " RECTABLE.description, RECTABLE.season, "
04218 " RECTABLE.episode, RECTABLE.category, "
04219 " RECTABLE.chanid, channel.channum, "
04220 " RECTABLE.station, channel.name, "
04221 " RECTABLE.recgroup, RECTABLE.playgroup, "
04222 " RECTABLE.seriesid, RECTABLE.programid, "
04223 " RECTABLE.inetref, RECTABLE.recpriority, "
04224 " RECTABLE.startdate, RECTABLE.starttime, "
04225 " RECTABLE.enddate, RECTABLE.endtime, "
04226 " RECTABLE.recordid, RECTABLE.type, "
04227 " RECTABLE.dupin, RECTABLE.dupmethod, "
04228 " RECTABLE.findid, "
04229 " RECTABLE.startoffset, RECTABLE.endoffset, "
04230 " channel.commmethod "
04231 "FROM RECTABLE "
04232 "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
04233 "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
04234 "WHERE (type = %1 OR type = %2 OR type = %3 OR type = %4) AND "
04235 " recordmatch.chanid IS NULL")
04236 .arg(kSingleRecord)
04237 .arg(kTimeslotRecord)
04238 .arg(kWeekslotRecord)
04239 .arg(kOverrideRecord);
04240
04241 query.replace("RECTABLE", recordTable);
04242
04243 LOG(VB_SCHEDULE, LOG_INFO, QString(" |-- Start DB Query..."));
04244
04245 gettimeofday(&dbstart, NULL);
04246 MSqlQuery result(dbConn);
04247 result.prepare(query);
04248 bool ok = result.exec();
04249 gettimeofday(&dbend, NULL);
04250
04251 if (!ok)
04252 {
04253 MythDB::DBError("AddNotListed", result);
04254 return;
04255 }
04256
04257 LOG(VB_SCHEDULE, LOG_INFO,
04258 QString(" |-- %1 results in %2 sec. Processing...")
04259 .arg(result.size())
04260 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
04261 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
04262
04263 QDateTime now = QDateTime::currentDateTime();
04264
04265 while (result.next())
04266 {
04267 RecordingType rectype = RecordingType(result.value(21).toInt());
04268 QDateTime startts(result.value(16).toDate(), result.value(17).toTime());
04269 QDateTime endts( result.value(18).toDate(), result.value(19).toTime());
04270
04271 if (rectype == kTimeslotRecord)
04272 {
04273 int days = startts.daysTo(now);
04274
04275 startts = startts.addDays(days);
04276 endts = endts.addDays(days);
04277
04278 if (endts < now)
04279 {
04280 startts = startts.addDays(1);
04281 endts = endts.addDays(1);
04282 }
04283 }
04284 else if (rectype == kWeekslotRecord)
04285 {
04286 int weeks = (startts.daysTo(now) + 6) / 7;
04287
04288 startts = startts.addDays(weeks * 7);
04289 endts = endts.addDays(weeks * 7);
04290
04291 if (endts < now)
04292 {
04293 startts = startts.addDays(7);
04294 endts = endts.addDays(7);
04295 }
04296 }
04297
04298 QDateTime recstartts = startts.addSecs(result.value(25).toInt() * -60);
04299 QDateTime recendts = endts.addSecs( result.value(26).toInt() * +60);
04300
04301 if (recstartts >= recendts)
04302 {
04303
04304 recstartts = startts;
04305 recendts = endts;
04306 }
04307
04308
04309 if (recendts < schedTime)
04310 continue;
04311
04312 bool sor = (kSingleRecord == rectype) || (kOverrideRecord == rectype);
04313
04314 RecordingInfo *p = new RecordingInfo(
04315 result.value(0).toString(),
04316 (sor) ? result.value(1).toString() : QString(),
04317 (sor) ? result.value(2).toString() : QString(),
04318 result.value(3).toUInt(),
04319 result.value(4).toUInt(),
04320 QString(),
04321
04322 result.value(6).toUInt(),
04323 result.value(7).toString(),
04324 result.value(8).toString(),
04325 result.value(9).toString(),
04326
04327 result.value(10).toString(),
04328 result.value(11).toString(),
04329
04330 result.value(12).toString(),
04331 result.value(13).toString(),
04332 result.value(14).toString(),
04333
04334 result.value(15).toInt(),
04335
04336 startts, endts,
04337 recstartts, recendts,
04338
04339 rsNotListed,
04340
04341 result.value(20).toUInt(),
04342 RecordingType(result.value(21).toInt()),
04343
04344 RecordingDupInType(result.value(22).toInt()),
04345 RecordingDupMethodType(result.value(23).toInt()),
04346
04347 result.value(24).toUInt(),
04348
04349 result.value(27).toInt() == COMM_DETECT_COMMFREE);
04350
04351 tmpList.push_back(p);
04352 }
04353
04354 RecIter tmp = tmpList.begin();
04355 for ( ; tmp != tmpList.end(); ++tmp)
04356 worklist.push_back(*tmp);
04357 }
04358
04363 void Scheduler::GetAllScheduled(RecList &proglist)
04364 {
04365 QString query = QString(
04366 "SELECT record.title, record.subtitle, "
04367 " record.description, record.season, "
04368 " record.episode, record.category, "
04369 " record.chanid, channel.channum, "
04370 " record.station, channel.name, "
04371 " record.recgroup, record.playgroup, "
04372 " record.seriesid, record.programid, "
04373 " record.inetref, record.recpriority, "
04374 " record.startdate, record.starttime, "
04375 " record.enddate, record.endtime, "
04376 " record.recordid, record.type, "
04377 " record.dupin, record.dupmethod, "
04378 " record.findid, "
04379 " channel.commmethod "
04380 "FROM record "
04381 "LEFT JOIN channel ON channel.callsign = record.station "
04382 "GROUP BY recordid "
04383 "ORDER BY title ASC");
04384
04385 MSqlQuery result(MSqlQuery::InitCon());
04386 result.prepare(query);
04387
04388 if (!result.exec())
04389 {
04390 MythDB::DBError("GetAllScheduled", result);
04391 return;
04392 }
04393
04394 while (result.next())
04395 {
04396 RecordingType rectype = RecordingType(result.value(21).toInt());
04397 QDateTime startts;
04398 QDateTime endts;
04399 if (rectype == kSingleRecord ||
04400 rectype == kDontRecord ||
04401 rectype == kOverrideRecord ||
04402 rectype == kTimeslotRecord ||
04403 rectype == kWeekslotRecord)
04404 {
04405 startts = QDateTime(result.value(16).toDate(),
04406 result.value(17).toTime());
04407 endts = QDateTime(result.value(18).toDate(),
04408 result.value(19).toTime());
04409 }
04410 else
04411 {
04412
04413
04414 startts = mythCurrentDateTime();
04415 startts.setTime(QTime(0,0));
04416 endts = startts;
04417 }
04418
04419 proglist.push_back(new RecordingInfo(
04420 result.value(0).toString(), result.value(1).toString(),
04421 result.value(2).toString(), result.value(3).toUInt(),
04422 result.value(4).toUInt(), result.value(5).toString(),
04423
04424 result.value(6).toUInt(), result.value(7).toString(),
04425 result.value(8).toString(), result.value(9).toString(),
04426
04427 result.value(10).toString(), result.value(11).toString(),
04428
04429 result.value(12).toString(), result.value(13).toString(),
04430 result.value(14).toString(),
04431
04432 result.value(15).toInt(),
04433
04434 startts, endts,
04435 startts, endts,
04436
04437 rsUnknown,
04438
04439 result.value(20).toUInt(), rectype,
04440 RecordingDupInType(result.value(22).toInt()),
04441 RecordingDupMethodType(result.value(23).toInt()),
04442
04443 result.value(24).toUInt(),
04444
04445 result.value(25).toInt() == COMM_DETECT_COMMFREE));
04446 }
04447 }
04448
04450
04451
04452
04453
04454 static bool comp_storage_combination(FileSystemInfo *a, FileSystemInfo *b)
04455 {
04456
04457 if (a->isLocal() && !b->isLocal())
04458 {
04459 if (a->getWeight() <= b->getWeight())
04460 {
04461 return true;
04462 }
04463 }
04464 else if (a->isLocal() == b->isLocal())
04465 {
04466 if (a->getWeight() < b->getWeight())
04467 {
04468 return true;
04469 }
04470 else if (a->getWeight() > b->getWeight())
04471 {
04472 return false;
04473 }
04474 else if (a->getFreeSpace() > b->getFreeSpace())
04475 {
04476 return true;
04477 }
04478 }
04479 else if (!a->isLocal() && b->isLocal())
04480 {
04481 if (a->getWeight() < b->getWeight())
04482 {
04483 return true;
04484 }
04485 }
04486
04487 return false;
04488 }
04489
04490
04491 static bool comp_storage_perc_free_space(FileSystemInfo *a, FileSystemInfo *b)
04492 {
04493 if (a->getTotalSpace() == 0)
04494 return false;
04495
04496 if (b->getTotalSpace() == 0)
04497 return true;
04498
04499 if ((a->getFreeSpace() * 100.0) / a->getTotalSpace() >
04500 (b->getFreeSpace() * 100.0) / b->getTotalSpace())
04501 return true;
04502
04503 return false;
04504 }
04505
04506
04507 static bool comp_storage_free_space(FileSystemInfo *a, FileSystemInfo *b)
04508 {
04509 if (a->getFreeSpace() > b->getFreeSpace())
04510 return true;
04511
04512 return false;
04513 }
04514
04515
04516 static bool comp_storage_disk_io(FileSystemInfo *a, FileSystemInfo *b)
04517 {
04518 if (a->getWeight() < b->getWeight())
04519 return true;
04520
04521 return false;
04522 }
04523
04525
04526 void Scheduler::GetNextLiveTVDir(uint cardid)
04527 {
04528 QMutexLocker lockit(&schedLock);
04529
04530 EncoderLink *tv = (*m_tvList)[cardid];
04531
04532 if (!tv)
04533 return;
04534
04535 QDateTime cur = mythCurrentDateTime();
04536 QString recording_dir;
04537 int fsID = FillRecordingDir(
04538 "LiveTV",
04539 (tv->IsLocal()) ? gCoreContext->GetHostName() : tv->GetHostName(),
04540 "LiveTV", cur, cur.addSecs(3600), cardid,
04541 recording_dir, reclist);
04542
04543 tv->SetNextLiveTVDir(recording_dir);
04544
04545 LOG(VB_FILE, LOG_INFO, LOC + QString("FindNextLiveTVDir: next dir is '%1'")
04546 .arg(recording_dir));
04547
04548 if (m_expirer)
04549 m_expirer->Update(cardid, fsID, true);
04550 }
04551
04552 int Scheduler::FillRecordingDir(
04553 const QString &title,
04554 const QString &hostname,
04555 const QString &storagegroup,
04556 const QDateTime &recstartts,
04557 const QDateTime &recendts,
04558 uint cardid,
04559 QString &recording_dir,
04560 const RecList &reclist)
04561 {
04562 LOG(VB_SCHEDULE, LOG_INFO, LOC + "FillRecordingDir: Starting");
04563
04564 int fsID = -1;
04565 MSqlQuery query(MSqlQuery::InitCon());
04566 QMap<QString, FileSystemInfo>::Iterator fsit;
04567 QMap<QString, FileSystemInfo>::Iterator fsit2;
04568 QString dirKey;
04569 QStringList strlist;
04570 RecordingInfo *thispg;
04571 StorageGroup mysgroup(storagegroup, hostname);
04572 QStringList dirlist = mysgroup.GetDirList();
04573 QStringList recsCounted;
04574 list<FileSystemInfo *> fsInfoList;
04575 list<FileSystemInfo *>::iterator fslistit;
04576
04577 recording_dir.clear();
04578
04579 if (dirlist.size() == 1)
04580 {
04581 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
04582 QString("FillRecordingDir: The only directory in the %1 Storage "
04583 "Group is %2, so it will be used by default.")
04584 .arg(storagegroup) .arg(dirlist[0]));
04585 recording_dir = dirlist[0];
04586 LOG(VB_SCHEDULE, LOG_INFO, LOC + "FillRecordingDir: Finished");
04587
04588 return -1;
04589 }
04590
04591 int weightPerRecording =
04592 gCoreContext->GetNumSetting("SGweightPerRecording", 10);
04593 int weightPerPlayback =
04594 gCoreContext->GetNumSetting("SGweightPerPlayback", 5);
04595 int weightPerCommFlag =
04596 gCoreContext->GetNumSetting("SGweightPerCommFlag", 5);
04597 int weightPerTranscode =
04598 gCoreContext->GetNumSetting("SGweightPerTranscode", 5);
04599
04600 QString storageScheduler =
04601 gCoreContext->GetSetting("StorageScheduler", "Combination");
04602 int localStartingWeight =
04603 gCoreContext->GetNumSetting("SGweightLocalStarting",
04604 (storageScheduler != "Combination") ? 0
04605 : (int)(-1.99 * weightPerRecording));
04606 int remoteStartingWeight =
04607 gCoreContext->GetNumSetting("SGweightRemoteStarting", 0);
04608 int maxOverlap = gCoreContext->GetNumSetting("SGmaxRecOverlapMins", 3) * 60;
04609
04610 FillDirectoryInfoCache();
04611
04612 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
04613 "FillRecordingDir: Calculating initial FS Weights.");
04614
04615 for (fsit = fsInfoCache.begin(); fsit != fsInfoCache.end(); ++fsit)
04616 {
04617 FileSystemInfo *fs = &(*fsit);
04618 int tmpWeight = 0;
04619
04620 QString msg = QString(" %1:%2").arg(fs->getHostname())
04621 .arg(fs->getPath());
04622 if (fs->isLocal())
04623 {
04624 tmpWeight = localStartingWeight;
04625 msg += " is local (" + QString::number(tmpWeight) + ")";
04626 }
04627 else
04628 {
04629 tmpWeight = remoteStartingWeight;
04630 msg += " is remote (+" + QString::number(tmpWeight) + ")";
04631 }
04632
04633 fs->setWeight(tmpWeight);
04634
04635 tmpWeight = gCoreContext->GetNumSetting(QString("SGweightPerDir:%1:%2")
04636 .arg(fs->getHostname()).arg(fs->getPath()), 0);
04637 fs->setWeight(fs->getWeight() + tmpWeight);
04638
04639 if (tmpWeight)
04640 msg += ", has SGweightPerDir offset of "
04641 + QString::number(tmpWeight) + ")";
04642
04643 msg += ". initial dir weight = " + QString::number(fs->getWeight());
04644 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg);
04645
04646 fsInfoList.push_back(fs);
04647 }
04648
04649 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
04650 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
04651
04652 MSqlQuery saveRecDir(MSqlQuery::InitCon());
04653 saveRecDir.prepare("UPDATE inuseprograms "
04654 "SET recdir = :RECDIR "
04655 "WHERE chanid = :CHANID AND "
04656 " starttime = :STARTTIME");
04657
04658 query.prepare(
04659 "SELECT i.chanid, i.starttime, r.endtime, recusage, rechost, recdir "
04660 "FROM inuseprograms i, recorded r "
04661 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() AND "
04662 " i.chanid = r.chanid AND "
04663 " i.starttime = r.starttime");
04664
04665 if (!query.exec())
04666 {
04667 MythDB::DBError(LOC + "FillRecordingDir", query);
04668 }
04669 else
04670 {
04671 while (query.next())
04672 {
04673 uint recChanid = query.value(0).toUInt();
04674 QDateTime recStart( query.value(1).toDateTime());
04675 QDateTime recEnd( query.value(2).toDateTime());
04676 QString recUsage( query.value(3).toString());
04677 QString recHost( query.value(4).toString());
04678 QString recDir( query.value(5).toString());
04679
04680 if (recDir.isEmpty())
04681 {
04682 ProgramInfo pginfo(recChanid, recStart);
04683 recDir = pginfo.DiscoverRecordingDirectory();
04684 recDir = recDir.isEmpty() ? "_UNKNOWN_" : recDir;
04685
04686 saveRecDir.bindValue(":RECDIR", recDir);
04687 saveRecDir.bindValue(":CHANID", recChanid);
04688 saveRecDir.bindValue(":STARTTIME", recStart);
04689 if (!saveRecDir.exec())
04690 MythDB::DBError(LOC + "FillRecordingDir", saveRecDir);
04691 }
04692 if (recDir == "_UNKNOWN_")
04693 continue;
04694
04695 for (fslistit = fsInfoList.begin();
04696 fslistit != fsInfoList.end(); ++fslistit)
04697 {
04698 FileSystemInfo *fs = *fslistit;
04699 if ((recHost == fs->getHostname()) &&
04700 (recDir == fs->getPath()))
04701 {
04702 int weightOffset = 0;
04703
04704 if (recUsage == kRecorderInUseID)
04705 {
04706 if (recEnd > recstartts.addSecs(maxOverlap))
04707 {
04708 weightOffset += weightPerRecording;
04709 recsCounted << QString::number(recChanid) + ":" +
04710 recStart.toString(Qt::ISODate);
04711 }
04712 }
04713 else if (recUsage.contains(kPlayerInUseID))
04714 weightOffset += weightPerPlayback;
04715 else if (recUsage == kFlaggerInUseID)
04716 weightOffset += weightPerCommFlag;
04717 else if (recUsage == kTranscoderInUseID)
04718 weightOffset += weightPerTranscode;
04719
04720 if (weightOffset)
04721 {
04722 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04723 QString(" %1 @ %2 in use by '%3' on %4:%5, FSID "
04724 "#%6, FSID weightOffset +%7.")
04725 .arg(recChanid)
04726 .arg(recStart.toString(Qt::ISODate))
04727 .arg(recUsage).arg(recHost).arg(recDir)
04728 .arg(fs->getFSysID()).arg(weightOffset));
04729
04730
04731 for (fsit2 = fsInfoCache.begin();
04732 fsit2 != fsInfoCache.end(); ++fsit2)
04733 {
04734 FileSystemInfo *fs2 = &(*fsit2);
04735 if (fs2->getFSysID() == fs->getFSysID())
04736 {
04737 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04738 QString(" %1:%2 => old weight %3 plus "
04739 "%4 = %5")
04740 .arg(fs2->getHostname())
04741 .arg(fs2->getPath())
04742 .arg(fs2->getWeight())
04743 .arg(weightOffset)
04744 .arg(fs2->getWeight() + weightOffset));
04745
04746 fs2->setWeight(fs2->getWeight() + weightOffset);
04747 }
04748 }
04749 }
04750 break;
04751 }
04752 }
04753 }
04754 }
04755
04756 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
04757 "FillRecordingDir: Adjusting FS Weights from scheduler.");
04758
04759 RecConstIter recIter;
04760 for (recIter = reclist.begin(); recIter != reclist.end(); ++recIter)
04761 {
04762 thispg = *recIter;
04763
04764 if ((recendts < thispg->GetRecordingStartTime()) ||
04765 (recstartts > thispg->GetRecordingEndTime()) ||
04766 (thispg->GetRecordingStatus() != rsWillRecord) ||
04767 (thispg->GetCardID() == 0) ||
04768 (recsCounted.contains(QString("%1:%2").arg(thispg->GetChanID())
04769 .arg(thispg->GetRecordingStartTime(ISODate)))) ||
04770 (thispg->GetPathname().isEmpty()))
04771 continue;
04772
04773 for (fslistit = fsInfoList.begin();
04774 fslistit != fsInfoList.end(); ++fslistit)
04775 {
04776 FileSystemInfo *fs = *fslistit;
04777 if ((fs->getHostname() == thispg->GetHostname()) &&
04778 (fs->getPath() == thispg->GetPathname()))
04779 {
04780 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04781 QString("%1 @ %2 will record on %3:%4, FSID #%5, "
04782 "weightPerRecording +%6.")
04783 .arg(thispg->GetChanID())
04784 .arg(thispg->GetRecordingStartTime(ISODate))
04785 .arg(fs->getHostname()).arg(fs->getPath())
04786 .arg(fs->getFSysID()).arg(weightPerRecording));
04787
04788 for (fsit2 = fsInfoCache.begin();
04789 fsit2 != fsInfoCache.end(); ++fsit2)
04790 {
04791 FileSystemInfo *fs2 = &(*fsit2);
04792 if (fs2->getFSysID() == fs->getFSysID())
04793 {
04794 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04795 QString(" %1:%2 => old weight %3 plus %4 = %5")
04796 .arg(fs2->getHostname()).arg(fs2->getPath())
04797 .arg(fs2->getWeight()).arg(weightPerRecording)
04798 .arg(fs2->getWeight() + weightPerRecording));
04799
04800 fs2->setWeight(fs2->getWeight() + weightPerRecording);
04801 }
04802 }
04803 break;
04804 }
04805 }
04806 }
04807
04808 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04809 QString("Using '%1' Storage Scheduler directory sorting algorithm.")
04810 .arg(storageScheduler));
04811
04812 if (storageScheduler == "BalancedFreeSpace")
04813 fsInfoList.sort(comp_storage_free_space);
04814 else if (storageScheduler == "BalancedPercFreeSpace")
04815 fsInfoList.sort(comp_storage_perc_free_space);
04816 else if (storageScheduler == "BalancedDiskIO")
04817 fsInfoList.sort(comp_storage_disk_io);
04818 else
04819 fsInfoList.sort(comp_storage_combination);
04820
04821 if (VERBOSE_LEVEL_CHECK(VB_FILE | VB_SCHEDULE, LOG_INFO))
04822 {
04823 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04824 "--- FillRecordingDir Sorted fsInfoList start ---");
04825 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
04826 ++fslistit)
04827 {
04828 FileSystemInfo *fs = *fslistit;
04829 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString("%1:%2")
04830 .arg(fs->getHostname()) .arg(fs->getPath()));
04831 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" Location : %1")
04832 .arg((fs->isLocal()) ? "local" : "remote"));
04833 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" weight : %1")
04834 .arg(fs->getWeight()));
04835 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" free space : %5")
04836 .arg(fs->getFreeSpace()));
04837 }
04838 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
04839 "--- FillRecordingDir Sorted fsInfoList end ---");
04840 }
04841
04842
04843
04844
04845
04846 EncoderLink *nexttv = (*m_tvList)[cardid];
04847 long long maxByterate = nexttv->GetMaxBitrate() / 8;
04848 long long maxSizeKB = (maxByterate + maxByterate/3) *
04849 recstartts.secsTo(recendts) / 1024;
04850
04851 bool simulateAutoExpire =
04852 ((gCoreContext->GetSetting("StorageScheduler") == "BalancedFreeSpace") &&
04853 (m_expirer) &&
04854 (fsInfoList.size() > 1));
04855
04856
04857
04858
04859
04860
04861
04862
04863
04864
04865
04866
04867
04868 for (unsigned int pass = 1; pass <= 3; pass++)
04869 {
04870 bool foundDir = false;
04871
04872 if ((pass == 2) && simulateAutoExpire)
04873 {
04874
04875 QMap <int , long long> remainingSpaceKB;
04876 for (fslistit = fsInfoList.begin();
04877 fslistit != fsInfoList.end(); ++fslistit)
04878 {
04879 remainingSpaceKB[(*fslistit)->getFSysID()] =
04880 (*fslistit)->getFreeSpace();
04881 }
04882
04883
04884 pginfolist_t expiring;
04885 m_expirer->GetAllExpiring(expiring);
04886
04887 for(pginfolist_t::iterator it=expiring.begin();
04888 it != expiring.end(); ++it)
04889 {
04890
04891 FileSystemInfo *fs = NULL;
04892 for (fslistit = fsInfoList.begin();
04893 fslistit != fsInfoList.end(); ++fslistit)
04894 {
04895
04896 if ((*it)->GetHostname() != (*fslistit)->getHostname())
04897 continue;
04898
04899
04900 if (!dirlist.contains((*fslistit)->getPath()))
04901 continue;
04902
04903 QString filename =
04904 (*fslistit)->getPath() + "/" + (*it)->GetPathname();
04905
04906
04907 if ((*it)->GetHostname() == gCoreContext->GetHostName())
04908 {
04909 QFile checkFile(filename);
04910
04911 if (checkFile.exists())
04912 {
04913 fs = *fslistit;
04914 break;
04915 }
04916 }
04917 else
04918 {
04919 QString backuppath = (*it)->GetPathname();
04920 ProgramInfo *programinfo = *it;
04921 bool foundSlave = false;
04922
04923 QMap<int, EncoderLink *>::Iterator enciter =
04924 m_tvList->begin();
04925 for (; enciter != m_tvList->end(); ++enciter)
04926 {
04927 if ((*enciter)->GetHostName() ==
04928 programinfo->GetHostname())
04929 {
04930 (*enciter)->CheckFile(programinfo);
04931 foundSlave = true;
04932 break;
04933 }
04934 }
04935 if (foundSlave &&
04936 programinfo->GetPathname() == filename)
04937 {
04938 fs = *fslistit;
04939 programinfo->SetPathname(backuppath);
04940 break;
04941 }
04942 programinfo->SetPathname(backuppath);
04943 }
04944 }
04945
04946 if (!fs)
04947 {
04948 LOG(VB_GENERAL, LOG_ERR,
04949 QString("Unable to match '%1' "
04950 "to any file system. Ignoring it.")
04951 .arg((*it)->GetBasename()));
04952 continue;
04953 }
04954
04955
04956 remainingSpaceKB[fs->getFSysID()] +=
04957 (*it)->GetFilesize() / 1024;
04958
04959
04960 long long desiredSpaceKB =
04961 m_expirer->GetDesiredSpace(fs->getFSysID());
04962
04963 if (remainingSpaceKB[fs->getFSysID()] >
04964 (desiredSpaceKB + maxSizeKB))
04965 {
04966 recording_dir = fs->getPath();
04967 fsID = fs->getFSysID();
04968
04969 LOG(VB_FILE, LOG_INFO,
04970 QString("pass 2: '%1' will record in '%2' "
04971 "although there is only %3 MB free and the "
04972 "AutoExpirer wants at least %4 MB. This "
04973 "directory has the highest priority files "
04974 "to be expired from the AutoExpire list and "
04975 "there are enough that the Expirer should "
04976 "be able to free up space for this recording.")
04977 .arg(title).arg(recording_dir)
04978 .arg(fs->getFreeSpace() / 1024)
04979 .arg(desiredSpaceKB / 1024));
04980
04981 foundDir = true;
04982 break;
04983 }
04984 }
04985
04986 m_expirer->ClearExpireList(expiring);
04987 }
04988 else
04989 {
04990 for (fslistit = fsInfoList.begin();
04991 fslistit != fsInfoList.end(); ++fslistit)
04992 {
04993 long long desiredSpaceKB = 0;
04994 FileSystemInfo *fs = *fslistit;
04995 if (m_expirer)
04996 desiredSpaceKB =
04997 m_expirer->GetDesiredSpace(fs->getFSysID());
04998
04999 if ((fs->getHostname() == hostname) &&
05000 (dirlist.contains(fs->getPath())) &&
05001 ((pass > 1) ||
05002 (fs->getFreeSpace() > (desiredSpaceKB + maxSizeKB))))
05003 {
05004 recording_dir = fs->getPath();
05005 fsID = fs->getFSysID();
05006
05007 if (pass == 1)
05008 LOG(VB_FILE, LOG_INFO,
05009 QString("pass 1: '%1' will record in "
05010 "'%2' which has %3 MB free. This recording "
05011 "could use a max of %4 MB and the "
05012 "AutoExpirer wants to keep %5 MB free.")
05013 .arg(title)
05014 .arg(recording_dir)
05015 .arg(fs->getFreeSpace() / 1024)
05016 .arg(maxSizeKB / 1024)
05017 .arg(desiredSpaceKB / 1024));
05018 else
05019 LOG(VB_FILE, LOG_INFO,
05020 QString("pass %1: '%2' will record in "
05021 "'%3' although there is only %4 MB free and "
05022 "the AutoExpirer wants at least %5 MB. "
05023 "Something will have to be deleted or expired "
05024 "in order for this recording to complete "
05025 "successfully.")
05026 .arg(pass).arg(title)
05027 .arg(recording_dir)
05028 .arg(fs->getFreeSpace() / 1024)
05029 .arg(desiredSpaceKB / 1024));
05030
05031 foundDir = true;
05032 break;
05033 }
05034 }
05035 }
05036
05037 if (foundDir)
05038 break;
05039 }
05040
05041 LOG(VB_SCHEDULE, LOG_INFO, LOC + "FillRecordingDir: Finished");
05042 return fsID;
05043 }
05044
05045 void Scheduler::FillDirectoryInfoCache(bool force)
05046 {
05047 if ((!force) &&
05048 (fsInfoCacheFillTime > QDateTime::currentDateTime().addSecs(-180)))
05049 return;
05050
05051 QList<FileSystemInfo> fsInfos;
05052
05053 fsInfoCache.clear();
05054
05055 if (m_mainServer)
05056 m_mainServer->GetFilesystemInfos(fsInfos);
05057
05058 QMap <int, bool> fsMap;
05059 QList<FileSystemInfo>::iterator it1;
05060 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
05061 {
05062 fsMap[it1->getFSysID()] = true;
05063 fsInfoCache[it1->getHostname() + ":" + it1->getPath()] = *it1;
05064 }
05065
05066 LOG(VB_FILE, LOG_INFO, LOC +
05067 QString("FillDirectoryInfoCache: found %1 unique filesystems")
05068 .arg(fsMap.size()));
05069
05070 fsInfoCacheFillTime = QDateTime::currentDateTime();
05071 }
05072
05073 void Scheduler::SchedPreserveLiveTV(void)
05074 {
05075 if (!livetvTime.isValid())
05076 return;
05077
05078 if (livetvTime < schedTime)
05079 {
05080 livetvTime = QDateTime();
05081 return;
05082 }
05083
05084 livetvpriority = gCoreContext->GetNumSetting("LiveTVPriority", 0);
05085
05086
05087 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
05088 for (; enciter != m_tvList->end(); ++enciter)
05089 {
05090 EncoderLink *enc = *enciter;
05091
05092 if (kState_WatchingLiveTV != enc->GetState())
05093 continue;
05094
05095 TunedInputInfo in;
05096 enc->IsBusy(&in);
05097
05098 if (!in.inputid)
05099 continue;
05100
05101
05102
05103 RecordingInfo *dummy = new RecordingInfo(
05104 in.chanid, livetvTime, true, 4);
05105
05106 dummy->SetCardID(enc->GetCardID());
05107 dummy->SetInputID(in.inputid);
05108 dummy->SetRecordingStatus(rsUnknown);
05109
05110 retrylist.push_front(dummy);
05111 }
05112
05113 if (retrylist.empty())
05114 return;
05115
05116 MoveHigherRecords(false);
05117
05118 while (!retrylist.empty())
05119 {
05120 RecordingInfo *p = retrylist.back();
05121 delete p;
05122 retrylist.pop_back();
05123 }
05124 }
05125
05126
05127 bool Scheduler::WasStartedAutomatically()
05128 {
05129 bool autoStart = false;
05130
05131 QDateTime startupTime = QDateTime();
05132 QString s = gCoreContext->GetSetting("MythShutdownWakeupTime", "");
05133 if (s.length())
05134 startupTime = QDateTime::fromString(s, Qt::ISODate);
05135
05136
05137 if (startupTime.isValid())
05138 {
05139
05140
05141
05142 if (abs(startupTime.secsTo(QDateTime::currentDateTime())) < (15 * 60))
05143 {
05144 LOG(VB_SCHEDULE, LOG_INFO,
05145 "Close to auto-start time, AUTO-Startup assumed");
05146 autoStart = true;
05147 }
05148 else
05149 LOG(VB_SCHEDULE, LOG_DEBUG,
05150 "NOT close to auto-start time, USER-initiated startup assumed");
05151 }
05152 else if (!s.isEmpty())
05153 LOG(VB_GENERAL, LOG_ERR, LOC +
05154 QString("Invalid MythShutdownWakeupTime specified in database (%1)")
05155 .arg(s));
05156
05157 return autoStart;
05158 }
05159
05160