MythTV  0.26-pre
scheduler.cpp
Go to the documentation of this file.
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     // Note: the PruneOverlaps logic depends on the following
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     // In cases where two recording rules match the same showing, one
00249     // of them needs to take precedence.  Penalize any entry that
00250     // won't record except for those from kDontRecord rules.  This
00251     // will force them to yield to a rule that might record.
00252     // Otherwise, more specific record type beats less specific.
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     // Note: the PruneRedundants logic depends on the following
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     // This will cause our temp copy of recordmatch to be empty
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             // FIXME!  If we are passed an rsUnknown recstatus, an
00581             // in-progress recording might be being stopped.  Try
00582             // to handle it sensibly until a better fix can be
00583             // made after the 0.25 code freeze.
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         // if two inputs are in the same input group we have a conflict
01021         // unless the programs are on the same multiplex.
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             // It is pointless to preempt another livetv session.
01186             // (the retrylist contains dummy livetv pginfo's)
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             // Failed the priority check or "Move scheduled shows to
01212             // avoid LiveTV feature" is turned off.
01213             // However, there is no conflict so if this alternate showing
01214             // is on an equivalent virtual card, allow the move.
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         // Delete anything that has already passed since we can't
01388         // change history, can we?
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         // Check for rsConflict
01401         if (p->GetRecordingStatus() == rsUnknown)
01402             p->SetRecordingStatus(rsConflict);
01403 
01404         // Restore the old status for some selected cases.
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             // Re-mark rsMissedFuture entries so non-future history
01416             // will be saved in the scheduler thread.
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         // Check for redundant against last non-deleted
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             // Flag lower priority showings that will recorded so we
01438             // can warn the user about them
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     // Save rsRecording recstatus to DB
01667     // This allows recordings to resume on backend restart
01668     new_pi->AddHistory(false);
01669 
01670     // Make sure we have a ScheduledRecording instance
01671     new_pi->GetRecordingRule();
01672 
01673     // Trigger reschedule..
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     // first check the card we will be recording on...
01691     if (!rctv || rctv->IsBusyRecording())
01692         return true;
01693 
01694     // now check other cards in the same input group as the recording.
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     // Mark anything that was recording as aborted.
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     // Mark anything that was going to record as missed.
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     // Mark anything that was set to rsCurrentRecording as
01744     // rsPreviousRecording.
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     // Clear the "future" status of anything older than the maximum
01753     // endoffset.  Anything more recent will bee handled elsewhere
01754     // during normal processing.
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     // Notify constructor that we're actually running
01769     {
01770         QMutexLocker lockit(&schedLock);
01771         reschedWait.wakeAll();
01772     }
01773 
01774     OldRecordedFixups();
01775 
01776     // wait for slaves to connect
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; // in minutes
01788     int       tuningTimeout   = 180; // in seconds
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; // maximum sleep time in milliseconds
01796     int       schedRunTime    = 30; // max scheduler run time in seconds
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         // If we're about to start a recording don't do any reschedules...
01806         // instead sleep for a bit
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                 // The master backend is a long lived program, so
01842                 // we reload some key settings on each reschedule.
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                 // HandleRunSchedulerStartup releases the schedLock so the
01869                 // reclist may have changed. If it has go to top of loop
01870                 // and update secs_to_next...
01871                 if (reclist_changed)
01872                     continue;
01873             }
01874 
01875             // Unless a recording is about to start, check for slaves
01876             // that can be put to sleep if it has been at least five
01877             // minutes since we last put slaves to sleep.
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         // Skip past recordings that are already history
01890         // (i.e. AddHistory() has been called setting oldrecstatus)
01891         for ( ; startIter != reclist.end(); ++startIter)
01892         {
01893             if ((*startIter)->GetRecordingStatus() !=
01894                 (*startIter)->oldrecstatus)
01895             {
01896                 break;
01897             }
01898         }
01899 
01900         // Start any recordings that are due to be started
01901         // & call RecordPending for recordings due to start in 30 seconds
01902         // & handle rsTuning updates
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         // if idletimeout is 0, the user disabled the auto-shutdown feature
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 /*ms*/)
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         // Check on recordings that we've told to start, but have
01952         // not yet started every second or so.
01953         if ((*i)->GetRecordingStatus() == rsTuning)
01954         {
01955             msecs = min(msecs, 1000);
01956             continue;
01957         }
01958 
01959         // These recordings have already been handled..
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         // This is what normally breaks us out of the loop...
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     // "**any**" is special value set in ProgLister::DeleteOldSeries()
02022     if (programid != "**any**")
02023     {
02024         filterClause += "AND (0 ";
02025         if (!subtitle.isEmpty())
02026         {
02027             // Need to check both for kDupCheckSubThenDesc
02028             filterClause += "OR p.subtitle = :SUBTITLE "
02029                             "OR p.description = :SUBTITLE ";
02030             bindings[":SUBTITLE"] = subtitle;
02031         }
02032         if (!descrip.isEmpty())
02033         {
02034             // Need to check both for kDupCheckSubThenDesc
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     // We might have been inactive for a long time, so make
02084     // sure our DB connection is fresh before continuing.
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     // Delete future oldrecorded entries that no longer
02167     // match any potential recordings.
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     // Write changed entries to oldrecorded.
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             // Force a non-future, oldrecorded entry to
02245             // get written when the time comes.
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     // The parameter given to the startup_cmd. "user" means a user
02262     // probably started the backend process, "auto" means it was
02263     // started probably automatically.
02264     QString startupParam = "user";
02265 
02266     // find the first recording that WILL be recorded
02267     RecIter firstRunIter = reclist.begin();
02268     for ( ; firstRunIter != reclist.end(); ++firstRunIter)
02269     {
02270         if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord)
02271             break;
02272     }
02273 
02274     // have we been started automatically?
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         // Since we've started automatically, don't wait for
02285         // client to connect before allowing shutdown.
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 // If a recording is about to start on a backend in a few minutes, wake it...
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     // cleanup old sysEvents once in a while
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         // Will use pre-roll settings only if no other
02486         // program is currently being recorded
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             // activate auto expirer
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     // Determine current recording status
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             // If tuning is still taking place this long after we
02650             // started give up on it so the scheduler can try to
02651             // find another broadcast of the same material.
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     // If the status has changed, handle it
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     // we release the block when a client connects
02685     if (blockShutdown)
02686         blockShutdown &= !m_mainServer->isClientConnected();
02687     else
02688     {
02689         QDateTime curtime = QDateTime::currentDateTime();
02690 
02691         // find out, if we are currently recording (or LiveTV)
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             // have we received a RESET_IDLETIME message?
02704             resetIdleTime_lock.lock();
02705             if (resetIdleTime)
02706             {
02707                 // yes - so reset the idleSince time
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                 // is the machine already idling the timeout time?
02740                 if (idleSince.addSecs(idleTimeoutSecs) < curtime)
02741                 {
02742                     // are we waiting for shutdown?
02743                     if (m_isShuttingDown)
02744                     {
02745                         // if we have been waiting more that 60secs then assume
02746                         // something went wrong so reset and try again
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             // not idle, make the time invalid
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 //returns true, if the shutdown is not blocked
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                     // just reset idle'ing on retval == 1
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                     // reset shutdown status on retval = 2
02838                     // (needs a clientconnection again,
02839                     // before shutdown is executed)
02840                     blockShutdown =
02841                         gCoreContext->GetNumSetting("blockSDWUwithoutClient",
02842                                                     1);
02843                     idleSince = QDateTime();
02844                     break;
02845 #if 0
02846                 case 3:
02847                     //disable shutdown routine generally
02848                     m_noAutoShutdown = true;
02849                     break;
02850 #endif
02851                 default:
02852                     break;
02853             }
02854         }
02855     }
02856     else
02857         retval = true; // allow shutdown if now command is set.
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     // set the wakeuptime if needed
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         // now run the command to set the wakeup time
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     // tell anyone who is listening the master server is going down now
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         // now we shut the slave backends down...
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         // and now shutdown myself
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     // If we make it here then either the shutdown failed
02949     // OR we suspended or hibernated the OS instead
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     // Make sure all FindOne rules have a valid findid before scheduling.
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 " // allrecord
03564 "OR RECTABLE.type = %2 " // findonerecord
03565 "OR RECTABLE.type = %3 " // finddailyrecord
03566 "OR RECTABLE.type = %4) " // findweeklyrecord
03567 " OR "
03568 " ((RECTABLE.station = channel.callsign) " // channel matches
03569 "  AND "
03570 "  ((RECTABLE.type = %5) " // channelrecord
03571 "   OR"
03572 "   ((TIME_TO_SEC(RECTABLE.starttime) = TIME_TO_SEC(program.starttime)) " // timeslot matches
03573 "    AND "
03574 "    ((RECTABLE.type = %6) " // timeslotrecord
03575 "     OR"
03576 "     ((DAYOFWEEK(RECTABLE.startdate) = DAYOFWEEK(program.starttime) "
03577 "      AND "
03578 "      ((RECTABLE.type = %7) " // weekslotrecord
03579 "       OR"
03580 "       ((TO_DAYS(RECTABLE.startdate) = TO_DAYS(program.starttime)) " // date matches
03581 "        AND (RECTABLE.type <> %8)" // single,override,don't,etc.
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,       "// 0-2
03952         "    p.endtime,        p.title,              p.subtitle,        "// 3-5
03953         "    p.description,    c.channum,            c.callsign,        "// 6-8
03954         "    c.name,           oldrecduplicate,      p.category,        "// 9-11
03955         "    RECTABLE.recpriority, RECTABLE.dupin,   recduplicate,      "//12-14
03956         "    findduplicate,    RECTABLE.type,        RECTABLE.recordid, "//15-17
03957         "    p.starttime - INTERVAL RECTABLE.startoffset "
03958         "    minute AS recstartts, "                                     //18
03959         "    p.endtime + INTERVAL RECTABLE.endoffset "
03960         "    minute AS recendts, "                                       //19
03961         "                                            p.previouslyshown, "//20
03962         "    RECTABLE.recgroup, RECTABLE.dupmethod,  c.commmethod,      "//21-23
03963         "    capturecard.cardid, cardinput.cardinputid,p.seriesid,      "//24-26
03964         "    p.programid,       RECTABLE.inetref,    p.category_type,   "//27-29
03965         "    p.airdate,         p.stars,             p.originalairdate, "//30-32
03966         "    RECTABLE.inactive, RECTABLE.parentid,   recordmatch.findid, "//33-35
03967         "    RECTABLE.playgroup, oldrecstatus.recstatus, "//36-37
03968         "    oldrecstatus.reactivate, p.videoprop+0,     "//38-39
03969         "    p.subtitletypes+0, p.audioprop+0,   RECTABLE.storagegroup, "//40-42
03970         "    capturecard.hostname, recordmatch.oldrecstatus, "
03971         "                                           RECTABLE.avg_delay, "//43-45
03972         "    oldrecstatus.future, cardinput.schedorder, ") +             //46-47
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         // If this is the same program we saw in the last pass and it
04015         // wasn't a viable candidate, then neither is this one so
04016         // don't bother with it.  This is essentially an early call to
04017         // PruneRedundants().
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(),//subtitle
04034             result.value(6).toString(),//description
04035             0, // season
04036             0, // episode
04037             result.value(11).toString(),//category
04038 
04039             result.value(0).toUInt(),//chanid
04040             result.value(7).toString(),//channum
04041             callsign,
04042             result.value(9).toString(),//channame
04043 
04044             result.value(21).toString(),//recgroup
04045             result.value(36).toString(),//playgroup
04046 
04047             result.value(43).toString(),//hostname
04048             result.value(42).toString(),//storagegroup
04049 
04050             result.value(30).toUInt(),//year
04051 
04052             result.value(26).toString(),//seriesid
04053             result.value(27).toString(),//programid
04054             result.value(28).toString(),//inetref
04055             result.value(29).toString(),//catType
04056 
04057             result.value(12).toInt(),//recpriority
04058 
04059             startts,
04060             result.value(3).toDateTime(),//endts
04061             result.value(18).toDateTime(),//recstartts
04062             result.value(19).toDateTime(),//recendts
04063 
04064             result.value(31).toDouble(),//stars
04065             (result.value(32).isNull()) ? QDate() :
04066             QDate::fromString(result.value(32).toString(), Qt::ISODate),
04067             //originalAirDate
04068 
04069             result.value(20).toInt(),//repeat
04070 
04071             RecStatusType(result.value(37).toInt()),//oldrecstatus
04072             result.value(38).toInt(),//reactivate
04073 
04074             recordid,
04075             result.value(34).toUInt(),//parentid
04076             RecordingType(result.value(16).toInt()),//rectype
04077             RecordingDupInType(result.value(13).toInt()),//dupin
04078             RecordingDupMethodType(result.value(22).toInt()),//dupmethod
04079 
04080             result.value(1).toUInt(),//sourceid
04081             result.value(25).toUInt(),//inputid
04082             result.value(24).toUInt(),//cardid
04083 
04084             result.value(35).toUInt(),//findid
04085 
04086             result.value(23).toInt() == COMM_DETECT_COMMFREE,//commfree
04087             result.value(40).toUInt(),//subtitleType
04088             result.value(39).toUInt(),//videoproperties
04089             result.value(41).toUInt(),//audioproperties
04090             result.value(46).toInt());//future
04091         p->SetRecordingPriority2(result.value(47).toInt()); // schedorder
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         // Check to see if the program is currently recording and if
04113         // the end time was changed.  Ideally, checking for a new end
04114         // time should be done after PruneOverlaps, but that would
04115         // complicate the list handling.  Do it here unless it becomes
04116         // problematic.
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         // Check for rsOffLine
04146         if ((doRun || specsched) && 
04147             (!cardMap.contains(p->GetCardID()) || !p->GetRecordingPriority2()))
04148             newrecstatus = rsOffLine;
04149 
04150         // Check for rsTooManyRecordings
04151         if (checkTooMany && tooManyMap[p->GetRecordingRuleID()] &&
04152             !p->IsReactivated())
04153         {
04154             newrecstatus = rsTooManyRecordings;
04155         }
04156 
04157         // Check for rsCurrentRecording and rsPreviousRecording
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         // Mark anything that has already passed as some type of
04189         // missed.  If it survives PruneOverlaps, it will get deleted
04190         // or have its old status restored in PruneRedundants.
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,    " //  0,1
04217         "       RECTABLE.description, RECTABLE.season,       " //  2,3
04218         "       RECTABLE.episode,     RECTABLE.category,    " //  4,5
04219         "       RECTABLE.chanid,      channel.channum,      " //  6,7
04220         "       RECTABLE.station,     channel.name,         " //  8,9
04221         "       RECTABLE.recgroup,    RECTABLE.playgroup,   " // 10,11
04222         "       RECTABLE.seriesid,    RECTABLE.programid,   " // 12,13
04223         "       RECTABLE.inetref,     RECTABLE.recpriority, " // 14,15
04224         "       RECTABLE.startdate,   RECTABLE.starttime,   " // 16,17
04225         "       RECTABLE.enddate,     RECTABLE.endtime,     " // 18,19
04226         "       RECTABLE.recordid,    RECTABLE.type,        " // 20,21
04227         "       RECTABLE.dupin,       RECTABLE.dupmethod,   " // 22,23
04228         "       RECTABLE.findid,                            " // 24
04229         "       RECTABLE.startoffset, RECTABLE.endoffset,   " // 25,26
04230         "       channel.commmethod                          " // 27
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             // start/end-offsets are invalid so ignore
04304             recstartts = startts;
04305             recendts   = endts;
04306         }
04307 
04308         // Don't bother if the end time has already passed
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(), // Title
04316             (sor) ? result.value(1).toString() : QString(), // Subtitle
04317             (sor) ? result.value(2).toString() : QString(), // Description
04318             result.value(3).toUInt(), // Season
04319             result.value(4).toUInt(), // Episode
04320             QString(), // Category
04321 
04322             result.value(6).toUInt(), // Chanid
04323             result.value(7).toString(), // Channel number
04324             result.value(8).toString(), // Call Sign
04325             result.value(9).toString(), // Channel name
04326 
04327             result.value(10).toString(), // Recgroup
04328             result.value(11).toString(), // Playgroup
04329 
04330             result.value(12).toString(), // Series ID
04331             result.value(13).toString(), // Program ID
04332             result.value(14).toString(), // Inetref
04333 
04334             result.value(15).toInt(), // Rec priority
04335 
04336             startts,                     endts,
04337             recstartts,                  recendts,
04338 
04339             rsNotListed, // Recording Status
04340 
04341             result.value(20).toUInt(), // Recording ID
04342             RecordingType(result.value(21).toInt()), // Recording type
04343 
04344             RecordingDupInType(result.value(22).toInt()), // DupIn type
04345             RecordingDupMethodType(result.value(23).toInt()), // Dup method
04346 
04347             result.value(24).toUInt(), // Find ID
04348 
04349             result.value(27).toInt() == COMM_DETECT_COMMFREE); // Comm Free
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,    " //  0,1
04367         "       record.description, record.season,      " //  2,3
04368         "       record.episode,     record.category,    " //  4,5
04369         "       record.chanid,      channel.channum,    " //  6,7
04370         "       record.station,     channel.name,       " //  8,9
04371         "       record.recgroup,    record.playgroup,   " // 10,11
04372         "       record.seriesid,    record.programid,   " // 12,13
04373         "       record.inetref,     record.recpriority, " // 14,15
04374         "       record.startdate,   record.starttime,   " // 16,17
04375         "       record.enddate,     record.endtime,     " // 18,19
04376         "       record.recordid,    record.type,        " // 20,21
04377         "       record.dupin,       record.dupmethod,   " // 22,23
04378         "       record.findid,                          " // 24
04379         "       channel.commmethod                      " // 25
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             // put currentDateTime() in time fields to prevent
04413             // Invalid date/time warnings later
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 // Storage Scheduler sort order routines
04451 // Sort mode-preferred to least-preferred (true == a more preferred than b)
04452 //
04453 // Prefer local over remote and to balance Disk I/O (weight), then free space
04454 static bool comp_storage_combination(FileSystemInfo *a, FileSystemInfo *b)
04455 {
04456     // local over remote
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 // prefer dirs with more percentage free space over dirs with less
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 // prefer dirs with more absolute free space over dirs with less
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 // prefer dirs with less weight (disk I/O) over dirs with more weight
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) // update auto 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                         // need to offset all directories on this filesystem
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 // default to using original method
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     // This code could probably be expanded to check the actual bitrate the
04843     // recording will record at for analog broadcasts that are encoded locally.
04844     // maxSizeKB is 1/3 larger than required as this is what the auto expire
04845     // uses
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     // Loop though looking for a directory to put the file in.  The first time
04857     // through we look for directories with enough free space in them.  If we
04858     // can't find a directory that way we loop through and pick the first good
04859     // one from the list no matter how much free space it has.  We assume that
04860     // something will have to be expired for us to finish the recording.
04861     // pass 1: try to fit onto an existing file system with enough free space
04862     // pass 2: fit onto the file system with the lowest priority files to be
04863     //         expired this is used only with multiple file systems
04864     //         Estimates are made by simulating each expiry until one of
04865     //         the file  systems has enough sapce to fit the new file.
04866     // pass 3: fit onto the first file system that will take it with lowest
04867     //         priority files on this file system expired
04868     for (unsigned int pass = 1; pass <= 3; pass++)
04869     {
04870         bool foundDir = false;
04871 
04872         if ((pass == 2) && simulateAutoExpire)
04873         {
04874             // setup a container of remaining space for all the file systems
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             // get list of expirable programs
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                 // find the filesystem its on
04891                 FileSystemInfo *fs = NULL;
04892                 for (fslistit = fsInfoList.begin();
04893                     fslistit != fsInfoList.end(); ++fslistit)
04894                 {
04895                     // recording is not on this filesystem's host
04896                     if ((*it)->GetHostname() != (*fslistit)->getHostname())
04897                         continue;
04898 
04899                     // directory is not in the Storage Group dir list
04900                     if (!dirlist.contains((*fslistit)->getPath()))
04901                         continue;
04902 
04903                     QString filename =
04904                         (*fslistit)->getPath() + "/" + (*it)->GetPathname();
04905 
04906                     // recording is local
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 // recording is remote
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                 // add this files size to the remaining free space
04956                 remainingSpaceKB[fs->getFSysID()] +=
04957                     (*it)->GetFilesize() / 1024;
04958 
04959                 // check if we have enough space for new file
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 // passes 1 & 3 (or 1 & 2 if !simulateAutoExpire)
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     // Build a list of active livetv programs
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         // Get the program that will be recording on this channel
05102         // at record start time, if this LiveTV session continues.
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 /* Determines if the system was started by the auto-wakeup process */
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     // if we don't have a valid startup time assume we were started manually
05137     if (startupTime.isValid())
05138     {
05139         // if we started within 15mins of the saved wakeup time assume we
05140         // started automatically to record or for a daily wakeup/shutdown period
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 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends