00001 #include <unistd.h>
00002 #include <qsqldatabase.h>
00003 #include <qsqlquery.h>
00004 #include <qregexp.h>
00005 #include <qstring.h>
00006 #include <qdatetime.h>
00007
00008 #include <iostream>
00009 #include <algorithm>
00010 using namespace std;
00011
00012 #ifdef __linux__
00013 # include <sys/vfs.h>
00014 #else // if !__linux__
00015 # include <sys/param.h>
00016 # ifndef USING_MINGW
00017 # include <sys/mount.h>
00018 # endif // USING_MINGW
00019 #endif // !__linux__
00020
00021 #include <sys/stat.h>
00022 #include <sys/time.h>
00023 #include <sys/types.h>
00024
00025 #include "scheduler.h"
00026 #include "encoderlink.h"
00027 #include "mainserver.h"
00028 #include "remoteutil.h"
00029 #include "backendutil.h"
00030 #include "libmyth/util.h"
00031 #include "libmyth/exitcodes.h"
00032 #include "libmyth/mythcontext.h"
00033 #include "libmyth/mythdbcon.h"
00034 #include "libmyth/compat.h"
00035 #include "libmyth/storagegroup.h"
00036 #include "libmythtv/programinfo.h"
00037 #include "libmythtv/scheduledrecording.h"
00038 #include "libmythtv/cardutil.h"
00039
00040 #define LOC QString("Scheduler: ")
00041 #define LOC_ERR QString("Scheduler, Error: ")
00042
00043 Scheduler::Scheduler(bool runthread, QMap<int, EncoderLink *> *tvList,
00044 QString tmptable, Scheduler *master_sched) :
00045 livetvTime(QDateTime())
00046 {
00047 m_tvList = tvList;
00048 specsched = false;
00049 schedulingEnabled = true;
00050
00051 expirer = NULL;
00052
00053 if (master_sched)
00054 {
00055 specsched = true;
00056 master_sched->getAllPending(&reclist);
00057 }
00058
00059
00060 if (runthread)
00061 dbConn = MSqlQuery::SchedCon();
00062 else
00063 dbConn = MSqlQuery::DDCon();
00064
00065 recordTable = tmptable;
00066 priorityTable = "powerpriority";
00067
00068 if (tmptable == "powerpriority_tmp")
00069 {
00070 priorityTable = tmptable;
00071 recordTable = "record";
00072 }
00073
00074 m_mainServer = NULL;
00075
00076 m_isShuttingDown = false;
00077 resetIdleTime = false;
00078
00079 verifyCards();
00080
00081 threadrunning = runthread;
00082
00083 fsInfoCacheFillTime = QDateTime::currentDateTime().addSecs(-1000);
00084
00085 reclist_lock = new QMutex(true);
00086
00087 if (runthread)
00088 {
00089 int err = pthread_create(&schedThread, NULL, SchedulerThread, this);
00090 if (err != 0)
00091 {
00092 VERBOSE(VB_IMPORTANT,
00093 QString("Failed to start scheduler thread: error %1")
00094 .arg(err));
00095 threadrunning = false;
00096 }
00097 }
00098 }
00099
00100 Scheduler::~Scheduler()
00101 {
00102 while (reclist.size() > 0)
00103 {
00104 ProgramInfo *pginfo = reclist.back();
00105 delete pginfo;
00106 reclist.pop_back();
00107 }
00108
00109 while (worklist.size() > 0)
00110 {
00111 ProgramInfo *pginfo = worklist.back();
00112 delete pginfo;
00113 worklist.pop_back();
00114 }
00115
00116 if (threadrunning)
00117 {
00118 pthread_cancel(schedThread);
00119 pthread_join(schedThread, NULL);
00120 }
00121
00122 delete reclist_lock;
00123 }
00124
00125 void Scheduler::SetMainServer(MainServer *ms)
00126 {
00127 m_mainServer = ms;
00128 }
00129
00130 void Scheduler::ResetIdleTime(void)
00131 {
00132 resetIdleTime_lock.lock();
00133 resetIdleTime = true;
00134 resetIdleTime_lock.unlock();
00135 }
00136
00137 void Scheduler::verifyCards(void)
00138 {
00139 QString thequery;
00140
00141 MSqlQuery query(dbConn);
00142 query.prepare("SELECT NULL FROM capturecard;");
00143
00144 int numcards = -1;
00145 if (query.exec() && query.isActive())
00146 numcards = query.size();
00147
00148 if (numcards <= 0)
00149 {
00150 cerr << "ERROR: no capture cards are defined in the database.\n";
00151 cerr << "Perhaps you should read the installation instructions?\n";
00152 exit(BACKEND_BUGGY_EXIT_NO_CAP_CARD);
00153 }
00154
00155 query.prepare("SELECT sourceid,name FROM videosource ORDER BY sourceid;");
00156
00157 int numsources = -1;
00158 if (query.exec() && query.isActive())
00159 {
00160 numsources = query.size();
00161
00162 int source = 0;
00163
00164 while (query.next())
00165 {
00166 source = query.value(0).toInt();
00167 MSqlQuery subquery(dbConn);
00168
00169 subquery.prepare("SELECT cardinputid FROM cardinput WHERE "
00170 "sourceid = :SOURCEID ORDER BY cardinputid;");
00171 subquery.bindValue(":SOURCEID", source);
00172 subquery.exec();
00173
00174 if (!subquery.isActive() || subquery.size() <= 0)
00175 cerr << query.value(1).toString() << " is defined, but isn't "
00176 << "attached to a cardinput.\n";
00177 }
00178 }
00179
00180 if (numsources <= 0)
00181 {
00182 VERBOSE(VB_IMPORTANT, "ERROR: No channel sources "
00183 "defined in the database");
00184 exit(BACKEND_BUGGY_EXIT_NO_CHAN_DATA);
00185 }
00186 }
00187
00188 static inline bool Recording(const ProgramInfo *p)
00189 {
00190 return (p->recstatus == rsRecording || p->recstatus == rsWillRecord);
00191 }
00192
00193 static bool comp_overlap(ProgramInfo *a, ProgramInfo *b)
00194 {
00195 if (a->startts != b->startts)
00196 return a->startts < b->startts;
00197 if (a->endts != b->endts)
00198 return a->endts < b->endts;
00199
00200
00201 if (a->title != b->title)
00202 return a->title < b->title;
00203 if (a->chanid != b->chanid)
00204 return a->chanid < b->chanid;
00205 if (a->inputid != b->inputid)
00206 return a->inputid < b->inputid;
00207
00208
00209
00210
00211
00212
00213 int apri = RecTypePriority(a->rectype);
00214 if (a->recstatus != rsUnknown && a->recstatus != rsDontRecord)
00215 apri += 100;
00216 int bpri = RecTypePriority(b->rectype);
00217 if (b->recstatus != rsUnknown && b->recstatus != rsDontRecord)
00218 bpri += 100;
00219 if (apri != bpri)
00220 return apri < bpri;
00221
00222 if (a->findid != b->findid)
00223 return a->findid > b->findid;
00224 return a->recordid < b->recordid;
00225 }
00226
00227 static bool comp_redundant(ProgramInfo *a, ProgramInfo *b)
00228 {
00229 if (a->startts != b->startts)
00230 return a->startts < b->startts;
00231 if (a->endts != b->endts)
00232 return a->endts < b->endts;
00233
00234
00235 if (a->title != b->title)
00236 return a->title < b->title;
00237 if (a->recordid != b->recordid)
00238 return a->recordid < b->recordid;
00239 if (a->chansign != b->chansign)
00240 return a->chansign < b->chansign;
00241 return a->recstatus < b->recstatus;
00242 }
00243
00244 static bool comp_recstart(ProgramInfo *a, ProgramInfo *b)
00245 {
00246 if (a->recstartts != b->recstartts)
00247 return a->recstartts < b->recstartts;
00248 if (a->recendts != b->recendts)
00249 return a->recendts < b->recendts;
00250 if (a->chansign != b->chansign)
00251 return a->chansign < b->chansign;
00252 return a->recstatus < b->recstatus;
00253 }
00254
00255 static QDateTime schedTime;
00256
00257 static bool comp_priority(ProgramInfo *a, ProgramInfo *b)
00258 {
00259 int arec = (a->recstatus != rsRecording);
00260 int brec = (b->recstatus != rsRecording);
00261
00262 if (arec != brec)
00263 return arec < brec;
00264
00265 if (a->recpriority != b->recpriority)
00266 return a->recpriority > b->recpriority;
00267
00268 if (a->recpriority2 != b->recpriority2)
00269 return a->recpriority2 > b->recpriority2;
00270
00271 int apast = (a->recstartts < schedTime.addSecs(-30) && !a->reactivate);
00272 int bpast = (b->recstartts < schedTime.addSecs(-30) && !b->reactivate);
00273
00274 if (apast != bpast)
00275 return apast < bpast;
00276
00277 int apri = RecTypePriority(a->rectype);
00278 int bpri = RecTypePriority(b->rectype);
00279
00280 if (apri != bpri)
00281 return apri < bpri;
00282
00283 if (a->recstartts != b->recstartts)
00284 {
00285 if (apast)
00286 return a->recstartts > b->recstartts;
00287 else
00288 return a->recstartts < b->recstartts;
00289 }
00290
00291 if (a->inputid != b->inputid)
00292 return a->inputid < b->inputid;
00293
00294 return a->recordid < b->recordid;
00295 }
00296
00297 static bool comp_timechannel(ProgramInfo *a, ProgramInfo *b)
00298 {
00299 if (a->recstartts != b->recstartts)
00300 return a->recstartts < b->recstartts;
00301 if (a->chanstr == b->chanstr)
00302 return a->chanid < b->chanid;
00303 if (a->chanstr.toInt() > 0 && b->chanstr.toInt() > 0)
00304 return a->chanstr.toInt() < b->chanstr.toInt();
00305 return a->chanstr < b->chanstr;
00306 }
00307
00308 bool Scheduler::FillRecordList(void)
00309 {
00310 schedMoveHigher = (bool)gContext->GetNumSetting("SchedMoveHigher");
00311 schedTime = QDateTime::currentDateTime();
00312
00313 VERBOSE(VB_SCHEDULE, "BuildWorkList...");
00314 BuildWorkList();
00315 VERBOSE(VB_SCHEDULE, "AddNewRecords...");
00316 AddNewRecords();
00317 VERBOSE(VB_SCHEDULE, "AddNotListed...");
00318 AddNotListed();
00319
00320 VERBOSE(VB_SCHEDULE, "Sort by time...");
00321 SORT_RECLIST(worklist, comp_overlap);
00322 VERBOSE(VB_SCHEDULE, "PruneOverlaps...");
00323 PruneOverlaps();
00324
00325 VERBOSE(VB_SCHEDULE, "Sort by priority...");
00326 SORT_RECLIST(worklist, comp_priority);
00327 VERBOSE(VB_SCHEDULE, "BuildListMaps...");
00328 BuildListMaps();
00329 VERBOSE(VB_SCHEDULE, "SchedNewRecords...");
00330 SchedNewRecords();
00331 VERBOSE(VB_SCHEDULE, "SchedPreserveLiveTV...");
00332 SchedPreserveLiveTV();
00333 VERBOSE(VB_SCHEDULE, "ClearListMaps...");
00334 ClearListMaps();
00335
00336 VERBOSE(VB_SCHEDULE, "Sort by time...");
00337 SORT_RECLIST(worklist, comp_redundant);
00338 VERBOSE(VB_SCHEDULE, "PruneRedundants...");
00339 PruneRedundants();
00340
00341 VERBOSE(VB_SCHEDULE, "Sort by time...");
00342 SORT_RECLIST(worklist, comp_recstart);
00343 VERBOSE(VB_SCHEDULE, "ClearWorkList...");
00344 bool res = ClearWorkList();
00345
00346 return res;
00347 }
00348
00353 void Scheduler::FillRecordListFromDB(int recordid)
00354 {
00355 struct timeval fillstart, fillend;
00356 float matchTime, placeTime;
00357
00358 MSqlQuery query(dbConn);
00359 QString thequery;
00360 QString where = "";
00361
00362
00363 if (recordid == -1)
00364 where = "WHERE recordid IS NULL ";
00365
00366 thequery = QString("CREATE TEMPORARY TABLE recordmatch ") +
00367 "SELECT * FROM recordmatch " + where + "; ";
00368
00369 query.prepare(thequery);
00370 recordmatchLock.lock();
00371 query.exec();
00372 recordmatchLock.unlock();
00373 if (!query.isActive())
00374 {
00375 MythContext::DBError("FillRecordListFromDB", query);
00376 return;
00377 }
00378
00379 thequery = "ALTER TABLE recordmatch ADD INDEX (recordid);";
00380 query.prepare(thequery);
00381 query.exec();
00382 if (!query.isActive())
00383 {
00384 MythContext::DBError("FillRecordListFromDB", query);
00385 return;
00386 }
00387
00388 gettimeofday(&fillstart, NULL);
00389 UpdateMatches(recordid);
00390 gettimeofday(&fillend, NULL);
00391 matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
00392 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
00393
00394 gettimeofday(&fillstart, NULL);
00395 FillRecordList();
00396 gettimeofday(&fillend, NULL);
00397 placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
00398 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
00399
00400 MSqlQuery queryDrop(dbConn);
00401 queryDrop.prepare("DROP TABLE recordmatch;");
00402 queryDrop.exec();
00403 if (!queryDrop.isActive())
00404 {
00405 MythContext::DBError("FillRecordListFromDB", queryDrop);
00406 return;
00407 }
00408
00409 QString msg;
00410 msg.sprintf("Speculative scheduled %d items in "
00411 "%.1f = %.2f match + %.2f place", (int)reclist.size(),
00412 matchTime + placeTime, matchTime, placeTime);
00413 VERBOSE(VB_GENERAL, msg);
00414 }
00415
00416 void Scheduler::FillRecordListFromMaster(void)
00417 {
00418 ProgramList schedList(false);
00419 schedList.FromScheduler();
00420
00421 QMutexLocker lockit(reclist_lock);
00422
00423 ProgramInfo *p;
00424 for (p = schedList.first(); p; p = schedList.next())
00425 reclist.push_back(p);
00426 }
00427
00428 void Scheduler::PrintList(RecList &list, bool onlyFutureRecordings)
00429 {
00430 if ((print_verbose_messages & VB_SCHEDULE) == 0)
00431 return;
00432
00433 QDateTime now = QDateTime::currentDateTime();
00434
00435 cout << "--- print list start ---\n";
00436 cout << "Title - Subtitle Ch Station "
00437 "Day Start End S C I T N Pri" << endl;
00438
00439 RecIter i = list.begin();
00440 for ( ; i != list.end(); i++)
00441 {
00442 ProgramInfo *first = (*i);
00443
00444 if (onlyFutureRecordings &&
00445 ((first->recendts < now && first->endts < now) ||
00446 (first->recstartts < now && !Recording(first))))
00447 continue;
00448
00449 PrintRec(first);
00450 }
00451
00452 cout << "--- print list end ---\n";
00453 }
00454
00455 void Scheduler::PrintRec(const ProgramInfo *p, const char *prefix)
00456 {
00457 if ((print_verbose_messages & VB_SCHEDULE) == 0)
00458 return;
00459
00460 QString episode;
00461
00462 if (prefix)
00463 cout << prefix;
00464
00465 if (p->subtitle > " ")
00466 episode = QString("%1 - \"%2\"").arg(p->title.local8Bit())
00467 .arg(p->subtitle.local8Bit());
00468 else
00469 episode = p->title.local8Bit();
00470
00471 cout << episode.leftJustify(30, ' ', true) << " "
00472 << p->chanstr.rightJustify(4, ' ') << " "
00473 << p->chansign.leftJustify(7, ' ', true) << " "
00474 << p->recstartts.toString("dd hh:mm-").local8Bit()
00475 << p->recendts.toString("hh:mm ").local8Bit()
00476 << p->sourceid << " " << p->cardid << " " << p->inputid << " "
00477 << p->RecTypeChar() << " " << p->RecStatusChar() << " "
00478 << (QString::number(p->recpriority) + "/" +
00479 QString::number(p->recpriority2)).rightJustify(5, ' ')
00480 << endl;
00481 }
00482
00483 void Scheduler::UpdateRecStatus(ProgramInfo *pginfo)
00484 {
00485 QMutexLocker lockit(reclist_lock);
00486
00487 RecIter dreciter = reclist.begin();
00488 for (; dreciter != reclist.end(); ++dreciter)
00489 {
00490 ProgramInfo *p = *dreciter;
00491 if (p->IsSameProgramTimeslot(*pginfo))
00492 {
00493 if (p->recstatus != pginfo->recstatus)
00494 {
00495 p->recstatus = pginfo->recstatus;
00496 reclist_changed = true;
00497 p->AddHistory(true);
00498 }
00499 return;
00500 }
00501 }
00502 }
00503
00504 void Scheduler::UpdateRecStatus(int cardid, const QString &chanid,
00505 const QDateTime &startts,
00506 RecStatusType recstatus,
00507 const QDateTime &recendts)
00508 {
00509 QMutexLocker lockit(reclist_lock);
00510
00511 RecIter dreciter = reclist.begin();
00512 for (; dreciter != reclist.end(); ++dreciter)
00513 {
00514 ProgramInfo *p = *dreciter;
00515 if (p->cardid == cardid &&
00516 p->chanid == chanid &&
00517 p->startts == startts)
00518 {
00519 p->recendts = recendts;
00520
00521 if (p->recstatus != recstatus)
00522 {
00523 p->recstatus = recstatus;
00524 reclist_changed = true;
00525 p->AddHistory(true);
00526 }
00527 return;
00528 }
00529 }
00530 }
00531
00532 bool Scheduler::ChangeRecordingEnd(ProgramInfo *oldp, ProgramInfo *newp)
00533 {
00534 QMutexLocker lockit(reclist_lock);
00535
00536 if (reclist_changed)
00537 return false;
00538
00539 RecordingType oldrectype = oldp->rectype;
00540 int oldrecordid = oldp->recordid;
00541 QDateTime oldrecendts = oldp->recendts;
00542
00543 oldp->rectype = newp->rectype;
00544 oldp->recordid = newp->recordid;
00545 oldp->recendts = newp->recendts;
00546
00547 if (specsched)
00548 {
00549 if (newp->recendts < QDateTime::currentDateTime())
00550 {
00551 oldp->recstatus = rsRecorded;
00552 newp->recstatus = rsRecorded;
00553 return false;
00554 }
00555 else
00556 return true;
00557 }
00558
00559 EncoderLink *tv = (*m_tvList)[oldp->cardid];
00560 RecStatusType rs = tv->StartRecording(oldp);
00561 if (rs != rsRecording)
00562 {
00563 VERBOSE(VB_IMPORTANT, QString("Failed to change end time on "
00564 "card %1 to %2")
00565 .arg(oldp->cardid).arg(oldp->recendts.toString()));
00566 oldp->rectype = oldrectype;
00567 oldp->recordid = oldrecordid;
00568 oldp->recendts = oldrecendts;
00569 }
00570 else
00571 {
00572 RecIter i = reclist.begin();
00573 for (; i != reclist.end(); i++)
00574 {
00575 ProgramInfo *recp = *i;
00576 if (recp->IsSameTimeslot(*oldp))
00577 {
00578 *recp = *oldp;
00579 break;
00580 }
00581 }
00582 }
00583
00584 return rs == rsRecording;
00585 }
00586
00587 void Scheduler::SlaveConnected(ProgramList &slavelist)
00588 {
00589 QMutexLocker lockit(reclist_lock);
00590
00591 ProgramInfo *sp;
00592 for (sp = slavelist.first(); sp; sp = slavelist.next())
00593 {
00594 bool found = false;
00595
00596 RecIter ri = reclist.begin();
00597 for ( ; ri != reclist.end(); ri++)
00598 {
00599 ProgramInfo *rp = *ri;
00600
00601 if (sp->inputid &&
00602 sp->startts == rp->startts &&
00603 sp->chansign == rp->chansign &&
00604 sp->title == rp->title)
00605 {
00606 if (sp->cardid == rp->cardid)
00607 {
00608 found = true;
00609 rp->recstatus = rsRecording;
00610 reclist_changed = true;
00611 rp->AddHistory(false);
00612 VERBOSE(VB_IMPORTANT, QString("setting %1/%2/\"%3\" as "
00613 "recording")
00614 .arg(sp->cardid).arg(sp->chansign).arg(sp->title));
00615 }
00616 else
00617 {
00618 VERBOSE(VB_IMPORTANT, QString("%1/%2/\"%3\" is already "
00619 "recording on card %4")
00620 .arg(sp->cardid).arg(sp->chansign).arg(sp->title)
00621 .arg(rp->cardid));
00622 }
00623 }
00624 else if (sp->cardid == rp->cardid &&
00625 rp->recstatus == rsRecording)
00626 {
00627 rp->recstatus = rsAborted;
00628 reclist_changed = true;
00629 rp->AddHistory(false);
00630 VERBOSE(VB_IMPORTANT, QString("setting %1/%2/\"%3\" as aborted")
00631 .arg(rp->cardid).arg(rp->chansign).arg(rp->title));
00632 }
00633 }
00634
00635 if (sp->inputid && !found)
00636 {
00637 reclist.push_back(new ProgramInfo(*sp));
00638 reclist_changed = true;
00639 sp->AddHistory(false);
00640 VERBOSE(VB_IMPORTANT, QString("adding %1/%2/\"%3\" as recording")
00641 .arg(sp->cardid).arg(sp->chansign).arg(sp->title));
00642 }
00643 }
00644 }
00645
00646 void Scheduler::SlaveDisconnected(int cardid)
00647 {
00648 QMutexLocker lockit(reclist_lock);
00649
00650 RecIter ri = reclist.begin();
00651 for ( ; ri != reclist.end(); ri++)
00652 {
00653 ProgramInfo *rp = *ri;
00654
00655 if (rp->cardid == cardid &&
00656 rp->recstatus == rsRecording)
00657 {
00658 rp->recstatus = rsAborted;
00659 reclist_changed = true;
00660 rp->AddHistory(false);
00661 VERBOSE(VB_IMPORTANT, QString("setting %1/%2/\"%3\" as aborted")
00662 .arg(rp->cardid).arg(rp->chansign).arg(rp->title));
00663 }
00664 }
00665 }
00666
00667 void Scheduler::BuildWorkList(void)
00668 {
00669 QMutexLocker lockit(reclist_lock);
00670 reclist_changed = false;
00671
00672 RecIter i = reclist.begin();
00673 for (; i != reclist.end(); i++)
00674 {
00675 ProgramInfo *p = *i;
00676 if (p->recstatus == rsRecording)
00677 worklist.push_back(new ProgramInfo(*p));
00678 }
00679 }
00680
00681 bool Scheduler::ClearWorkList(void)
00682 {
00683 QMutexLocker lockit(reclist_lock);
00684
00685 ProgramInfo *p;
00686
00687 if (reclist_changed)
00688 {
00689 while (worklist.size() > 0)
00690 {
00691 p = worklist.front();
00692 delete p;
00693 worklist.pop_front();
00694 }
00695
00696 return false;
00697 }
00698
00699 while (reclist.size() > 0)
00700 {
00701 p = reclist.front();
00702 delete p;
00703 reclist.pop_front();
00704 }
00705
00706 while (worklist.size() > 0)
00707 {
00708 p = worklist.front();
00709 reclist.push_back(p);
00710 worklist.pop_front();
00711 }
00712
00713 return true;
00714 }
00715
00716 static void erase_nulls(RecList &reclist)
00717 {
00718 RecIter it = reclist.begin();
00719 uint dst = 0;
00720 for (it = reclist.begin(); it != reclist.end(); ++it)
00721 {
00722 if (*it)
00723 {
00724 reclist[dst] = *it;
00725 dst++;
00726 }
00727 }
00728 reclist.resize(dst);
00729 }
00730
00731 void Scheduler::PruneOverlaps(void)
00732 {
00733 ProgramInfo *lastp = NULL;
00734
00735 RecIter dreciter = worklist.begin();
00736 while (dreciter != worklist.end())
00737 {
00738 ProgramInfo *p = *dreciter;
00739 if (lastp == NULL || lastp->recordid == p->recordid ||
00740 !lastp->IsSameTimeslot(*p))
00741 {
00742 lastp = p;
00743 dreciter++;
00744 }
00745 else
00746 {
00747 delete p;
00748 *(dreciter++) = NULL;
00749 }
00750 }
00751
00752 erase_nulls(worklist);
00753 }
00754
00755 void Scheduler::BuildListMaps(void)
00756 {
00757 RecIter i = worklist.begin();
00758 for ( ; i != worklist.end(); i++)
00759 {
00760 ProgramInfo *p = *i;
00761 if (p->recstatus == rsRecording ||
00762 p->recstatus == rsWillRecord ||
00763 p->recstatus == rsUnknown)
00764 {
00765 cardlistmap[p->cardid].push_back(p);
00766 titlelistmap[p->title].push_back(p);
00767 recordidlistmap[p->recordid].push_back(p);
00768 }
00769 }
00770 }
00771
00772 void Scheduler::ClearListMaps(void)
00773 {
00774 cardlistmap.clear();
00775 titlelistmap.clear();
00776 recordidlistmap.clear();
00777 cache_is_same_program.clear();
00778 }
00779
00780 bool Scheduler::IsSameProgram(
00781 const ProgramInfo *a, const ProgramInfo *b) const
00782 {
00783 IsSameKey X(a,b);
00784 IsSameCacheType::const_iterator it = cache_is_same_program.find(X);
00785 if (it != cache_is_same_program.end())
00786 return *it;
00787
00788 IsSameKey Y(b,a);
00789 it = cache_is_same_program.find(Y);
00790 if (it != cache_is_same_program.end())
00791 return *it;
00792
00793 return cache_is_same_program[X] = a->IsSameProgram(*b);
00794 }
00795
00796 bool Scheduler::FindNextConflict(
00797 const RecList &cardlist,
00798 const ProgramInfo *p,
00799 RecConstIter &j,
00800 bool openEnd) const
00801 {
00802 bool is_conflict_dbg = false;
00803
00804 for ( ; j != cardlist.end(); j++)
00805 {
00806 const ProgramInfo *q = *j;
00807
00808 if (p == q)
00809 continue;
00810
00811 if (!Recording(q))
00812 continue;
00813
00814 if (is_conflict_dbg)
00815 cout << QString("\n comparing with '%1' ").arg(q->title);
00816
00817 if (p->cardid != 0 && (p->cardid != q->cardid) &&
00818 !igrp.GetSharedInputGroup(p->inputid, q->inputid))
00819 {
00820 if (is_conflict_dbg)
00821 cout << " cardid== ";
00822 continue;
00823 }
00824
00825 if (openEnd && p->chanid != q->chanid)
00826 {
00827 if (p->recendts < q->recstartts || p->recstartts > q->recendts)
00828 {
00829 if (is_conflict_dbg)
00830 cout << " no-overlap ";
00831 continue;
00832 }
00833 }
00834 else
00835 {
00836 if (p->recendts <= q->recstartts || p->recstartts >= q->recendts)
00837 {
00838 if (is_conflict_dbg)
00839 cout << " no-overlap ";
00840 continue;
00841 }
00842 }
00843
00844 if (is_conflict_dbg)
00845 cout << "\n" <<
00846 QString(" cardid's: %1, %2 ").arg(p->cardid).arg(q->cardid) +
00847 QString("Shared input group: %1 ")
00848 .arg(igrp.GetSharedInputGroup(p->inputid, q->inputid)) +
00849 QString("mplexid's: %1, %2")
00850 .arg(p->GetMplexID()).arg(q->GetMplexID());
00851
00852
00853
00854 if (p->cardid && (p->cardid != q->cardid) &&
00855 igrp.GetSharedInputGroup(p->inputid, q->inputid) &&
00856 p->GetMplexID() && (p->GetMplexID() == q->GetMplexID()))
00857 {
00858 continue;
00859 }
00860
00861 if (is_conflict_dbg)
00862 cout << "\n Found conflict" << endl;
00863
00864 return true;
00865 }
00866
00867 if (is_conflict_dbg)
00868 cout << "\n No conflict" << endl;
00869
00870 return false;
00871 }
00872
00873 const ProgramInfo *Scheduler::FindConflict(
00874 const QMap<int, RecList> &reclists,
00875 const ProgramInfo *p,
00876 bool openend) const
00877 {
00878 bool is_conflict_dbg = false;
00879
00880 QMap<int, RecList>::const_iterator it = reclists.begin();
00881 for (; it != reclists.end(); ++it)
00882 {
00883 if (is_conflict_dbg)
00884 {
00885 cout << QString("Checking '%1' for conflicts on cardid %2")
00886 .arg(p->title).arg(it.key());
00887 }
00888
00889 const RecList &cardlist = *it;
00890 RecConstIter k = cardlist.begin();
00891 if (FindNextConflict(cardlist, p, k, openend))
00892 {
00893 return *k;
00894 }
00895 }
00896
00897 return NULL;
00898 }
00899
00900 void Scheduler::MarkOtherShowings(ProgramInfo *p)
00901 {
00902 RecList *showinglist = &titlelistmap[p->title];
00903
00904 MarkShowingsList(*showinglist, p);
00905
00906 if (p->rectype == kFindOneRecord ||
00907 p->rectype == kFindDailyRecord ||
00908 p->rectype == kFindWeeklyRecord)
00909 {
00910 showinglist = &recordidlistmap[p->recordid];
00911 MarkShowingsList(*showinglist, p);
00912 }
00913
00914 if (p->rectype == kOverrideRecord && p->findid > 0)
00915 {
00916 showinglist = &recordidlistmap[p->parentid];
00917 MarkShowingsList(*showinglist, p);
00918 }
00919 }
00920
00921 void Scheduler::MarkShowingsList(RecList &showinglist, ProgramInfo *p)
00922 {
00923 RecIter i = showinglist.begin();
00924 for ( ; i != showinglist.end(); i++)
00925 {
00926 ProgramInfo *q = *i;
00927 if (q == p)
00928 continue;
00929 if (q->recstatus != rsUnknown &&
00930 q->recstatus != rsWillRecord &&
00931 q->recstatus != rsEarlierShowing &&
00932 q->recstatus != rsLaterShowing)
00933 continue;
00934 if (q->IsSameTimeslot(*p))
00935 q->recstatus = rsLaterShowing;
00936 else if (q->rectype != kSingleRecord &&
00937 q->rectype != kOverrideRecord &&
00938 IsSameProgram(q,p))
00939 {
00940 if (q->recstartts < p->recstartts)
00941 q->recstatus = rsLaterShowing;
00942 else
00943 q->recstatus = rsEarlierShowing;
00944 }
00945 }
00946 }
00947
00948 void Scheduler::BackupRecStatus(void)
00949 {
00950 RecIter i = worklist.begin();
00951 for ( ; i != worklist.end(); i++)
00952 {
00953 ProgramInfo *p = *i;
00954 p->savedrecstatus = p->recstatus;
00955 }
00956 }
00957
00958 void Scheduler::RestoreRecStatus(void)
00959 {
00960 RecIter i = worklist.begin();
00961 for ( ; i != worklist.end(); i++)
00962 {
00963 ProgramInfo *p = *i;
00964 p->recstatus = p->savedrecstatus;
00965 }
00966 }
00967
00968 bool Scheduler::TryAnotherShowing(ProgramInfo *p, bool samePriority,
00969 bool preserveLive)
00970 {
00971 PrintRec(p, " >");
00972
00973 if (p->recstatus == rsRecording)
00974 return false;
00975
00976 RecList *showinglist = &titlelistmap[p->title];
00977
00978 if (p->rectype == kFindOneRecord ||
00979 p->rectype == kFindDailyRecord ||
00980 p->rectype == kFindWeeklyRecord)
00981 showinglist = &recordidlistmap[p->recordid];
00982
00983 RecStatusType oldstatus = p->recstatus;
00984 p->recstatus = rsLaterShowing;
00985
00986 bool hasLaterShowing = false;
00987
00988 RecIter j = showinglist->begin();
00989 for ( ; j != showinglist->end(); j++)
00990 {
00991 ProgramInfo *q = *j;
00992 if (q == p)
00993 continue;
00994
00995 if (samePriority && (q->recpriority != p->recpriority))
00996 continue;
00997
00998 hasLaterShowing = false;
00999
01000 if (q->recstatus != rsEarlierShowing &&
01001 q->recstatus != rsLaterShowing &&
01002 q->recstatus != rsUnknown)
01003 continue;
01004
01005 if (!p->IsSameTimeslot(*q))
01006 {
01007 if (!IsSameProgram(p,q))
01008 continue;
01009 if ((p->rectype == kSingleRecord ||
01010 p->rectype == kOverrideRecord))
01011 continue;
01012 if (q->recstartts < schedTime && p->recstartts >= schedTime)
01013 continue;
01014
01015 hasLaterShowing |= preserveLive;
01016 }
01017
01018 if (samePriority)
01019 PrintRec(q, " %");
01020 else
01021 PrintRec(q, " #");
01022
01023 bool failedLiveCheck = false;
01024 if (preserveLive)
01025 {
01026 failedLiveCheck |=
01027 (!livetvpriority ||
01028 p->recpriority - prefinputpri > q->recpriority);
01029
01030
01031
01032 RecConstIter k = retrylist.begin();
01033 if (FindNextConflict(retrylist, q, k))
01034 {
01035 PrintRec(*k, " L!");
01036 continue;
01037 }
01038 }
01039
01040 const ProgramInfo *conflict = FindConflict(cardlistmap, q);
01041 if (conflict)
01042 {
01043 PrintRec(conflict, " !");
01044 continue;
01045 }
01046
01047 if (hasLaterShowing)
01048 {
01049 QString id = p->schedulerid;
01050 hasLaterList[id] = true;
01051 continue;
01052 }
01053
01054 if (failedLiveCheck)
01055 {
01056
01057
01058
01059
01060 bool equiv = (p->sourceid == q->sourceid &&
01061 igrp.GetSharedInputGroup(p->inputid, q->inputid));
01062
01063 if (!equiv)
01064 continue;
01065 }
01066
01067 if (preserveLive)
01068 {
01069 QString msg = QString(
01070 "Moved \"%1\" on chanid: %2 from card: %3 to %4 "
01071 "to avoid LiveTV conflict")
01072 .arg(p->title.local8Bit()).arg(p->chanid)
01073 .arg(p->cardid).arg(q->cardid);
01074 VERBOSE(VB_SCHEDULE, msg);
01075 }
01076
01077 q->recstatus = rsWillRecord;
01078 MarkOtherShowings(q);
01079 PrintRec(p, " -");
01080 PrintRec(q, " +");
01081 return true;
01082 }
01083
01084 p->recstatus = oldstatus;
01085 return false;
01086 }
01087
01088 void Scheduler::SchedNewRecords(void)
01089 {
01090 VERBOSE(VB_SCHEDULE, "Scheduling:");
01091
01092 bool openEnd = (bool)gContext->GetNumSetting("SchedOpenEnd", 0);
01093
01094 RecIter i = worklist.begin();
01095 while (i != worklist.end())
01096 {
01097 ProgramInfo *p = *i;
01098 if (p->recstatus == rsRecording)
01099 MarkOtherShowings(p);
01100 else if (p->recstatus == rsUnknown)
01101 {
01102 const ProgramInfo *conflict = FindConflict(cardlistmap, p, openEnd);
01103 if (!conflict)
01104 {
01105 p->recstatus = rsWillRecord;
01106
01107 if (p->recstartts < schedTime.addSecs(90))
01108 {
01109 QString id = p->schedulerid;
01110 if (!recPendingList.contains(id))
01111 recPendingList[id] = false;
01112
01113 livetvTime = (livetvTime < schedTime) ?
01114 schedTime : livetvTime;
01115 }
01116
01117 MarkOtherShowings(p);
01118 PrintRec(p, " +");
01119 }
01120 else
01121 {
01122 retrylist.push_front(p);
01123 PrintRec(p, " #");
01124 PrintRec(conflict, " !");
01125 }
01126 }
01127
01128 int lastpri = p->recpriority;
01129 i++;
01130 if (i == worklist.end() || lastpri != (*i)->recpriority)
01131 {
01132 MoveHigherRecords();
01133 retrylist.clear();
01134 }
01135 }
01136 }
01137
01138 void Scheduler::MoveHigherRecords(bool move_this)
01139 {
01140 RecIter i = retrylist.begin();
01141 for ( ; move_this && i != retrylist.end(); i++)
01142 {
01143 ProgramInfo *p = *i;
01144 if (p->recstatus != rsUnknown)
01145 continue;
01146
01147 PrintRec(p, " /");
01148
01149 BackupRecStatus();
01150 p->recstatus = rsWillRecord;
01151 MarkOtherShowings(p);
01152
01153 RecList cardlist;
01154 QMap<int, RecList>::const_iterator it = cardlistmap.begin();
01155 for (; it != cardlistmap.end(); ++it)
01156 {
01157 RecConstIter it2 = (*it).begin();
01158 for (; it2 != (*it).end(); ++it2)
01159 cardlist.push_back(*it2);
01160 }
01161 RecConstIter k = cardlist.begin();
01162 for ( ; FindNextConflict(cardlist, p, k ); k++)
01163 {
01164 if (p->recpriority != (*k)->recpriority ||
01165 !TryAnotherShowing(*k, true))
01166 {
01167 RestoreRecStatus();
01168 break;
01169 }
01170 }
01171
01172 if (p->recstatus == rsWillRecord)
01173 PrintRec(p, " +");
01174 }
01175
01176 i = retrylist.begin();
01177 for ( ; i != retrylist.end(); i++)
01178 {
01179 ProgramInfo *p = *i;
01180 if (p->recstatus != rsUnknown)
01181 continue;
01182
01183 PrintRec(p, " ?");
01184
01185 if (move_this && TryAnotherShowing(p, false))
01186 continue;
01187
01188 BackupRecStatus();
01189 p->recstatus = rsWillRecord;
01190 if (move_this)
01191 MarkOtherShowings(p);
01192
01193 RecList cardlist;
01194 QMap<int, RecList>::const_iterator it = cardlistmap.begin();
01195 for (; it != cardlistmap.end(); ++it)
01196 {
01197 RecConstIter it2 = (*it).begin();
01198 for (; it2 != (*it).end(); ++it2)
01199 cardlist.push_back(*it2);
01200 }
01201
01202 RecConstIter k = cardlist.begin();
01203 for ( ; FindNextConflict(cardlist, p, k); k++)
01204 {
01205 if ((p->recpriority < (*k)->recpriority && !schedMoveHigher &&
01206 move_this) || !TryAnotherShowing(*k, false, !move_this))
01207 {
01208 RestoreRecStatus();
01209 break;
01210 }
01211 }
01212
01213 if (move_this && p->recstatus == rsWillRecord)
01214 PrintRec(p, " +");
01215 }
01216 }
01217
01218 void Scheduler::PruneRedundants(void)
01219 {
01220 ProgramInfo *lastp = NULL;
01221
01222 RecIter i = worklist.begin();
01223 while (i != worklist.end())
01224 {
01225 ProgramInfo *p = *i;
01226
01227
01228
01229 if (p->recstatus != rsRecording &&
01230 p->endts < schedTime &&
01231 p->recendts < schedTime)
01232 {
01233 delete p;
01234 *(i++) = NULL;
01235 continue;
01236 }
01237
01238
01239 if (p->recstatus == rsUnknown)
01240 p->recstatus = rsConflict;
01241
01242
01243 if (p->recstatus != rsWillRecord &&
01244 p->oldrecstatus != rsUnknown &&
01245 p->oldrecstatus != rsNotListed &&
01246 !p->reactivate)
01247 p->recstatus = p->oldrecstatus;
01248
01249 if (!Recording(p))
01250 {
01251 p->cardid = 0;
01252 p->inputid = 0;
01253 }
01254
01255
01256 if (lastp == NULL || lastp->recordid != p->recordid ||
01257 !lastp->IsSameTimeslot(*p))
01258 {
01259 lastp = p;
01260 i++;
01261 }
01262 else
01263 {
01264 delete p;
01265 *(i++) = NULL;
01266 }
01267 }
01268
01269 erase_nulls(worklist);
01270 }
01271
01272 void Scheduler::UpdateNextRecord(void)
01273 {
01274 if (specsched)
01275 return;
01276
01277 QMap<int, QDateTime> nextRecMap;
01278
01279 RecIter i = reclist.begin();
01280 while (i != reclist.end())
01281 {
01282 ProgramInfo *p = *i;
01283 if (p->recstatus == rsWillRecord && nextRecMap[p->recordid].isNull())
01284 nextRecMap[p->recordid] = p->recstartts;
01285
01286 if (p->rectype == kOverrideRecord && p->parentid > 0 &&
01287 p->recstatus == rsWillRecord && nextRecMap[p->parentid].isNull())
01288 nextRecMap[p->parentid] = p->recstartts;
01289 i++;
01290 }
01291
01292 MSqlQuery query(dbConn);
01293 query.prepare("SELECT recordid, next_record FROM record;");
01294
01295 if (query.exec() && query.isActive())
01296 {
01297 MSqlQuery subquery(dbConn);
01298
01299 while (query.next())
01300 {
01301 int recid = query.value(0).toInt();
01302 QDateTime next_record = query.value(1).toDateTime();
01303
01304 if (next_record == nextRecMap[recid])
01305 continue;
01306
01307 if (nextRecMap[recid].isNull() || !next_record.isValid())
01308 {
01309 subquery.prepare("UPDATE record "
01310 "SET next_record = '0000-00-00T00:00:00' "
01311 "WHERE recordid = :RECORDID;");
01312 subquery.bindValue(":RECORDID", recid);
01313 }
01314 else
01315 {
01316 subquery.prepare("UPDATE record SET next_record = :NEXTREC "
01317 "WHERE recordid = :RECORDID;");
01318 subquery.bindValue(":RECORDID", recid);
01319 subquery.bindValue(":NEXTREC", nextRecMap[recid]);
01320 }
01321 subquery.exec();
01322 if (!subquery.isActive())
01323 MythContext::DBError("Update next_record", subquery);
01324 else
01325 VERBOSE(VB_SCHEDULE, LOC +
01326 QString("Update next_record for %1").arg(recid));
01327 }
01328 }
01329 }
01330
01331 void Scheduler::getConflicting(ProgramInfo *pginfo, QStringList &strlist)
01332 {
01333 RecList retlist;
01334 getConflicting(pginfo, &retlist);
01335
01336 strlist << QString::number(retlist.size());
01337
01338 while (retlist.size() > 0)
01339 {
01340 ProgramInfo *p = retlist.front();
01341 p->ToStringList(strlist);
01342 delete p;
01343 retlist.pop_front();
01344 }
01345 }
01346
01347 void Scheduler::getConflicting(ProgramInfo *pginfo, RecList *retlist)
01348 {
01349 QMutexLocker lockit(reclist_lock);
01350
01351 RecConstIter i = reclist.begin();
01352 for (; FindNextConflict(reclist, pginfo, i); i++)
01353 {
01354 const ProgramInfo *p = *i;
01355 retlist->push_back(new ProgramInfo(*p));
01356 }
01357 }
01358
01359 bool Scheduler::getAllPending(RecList *retList)
01360 {
01361 QMutexLocker lockit(reclist_lock);
01362
01363 bool hasconflicts = false;
01364
01365 RecIter i = reclist.begin();
01366 for (; i != reclist.end(); i++)
01367 {
01368 ProgramInfo *p = *i;
01369 if (p->recstatus == rsConflict)
01370 hasconflicts = true;
01371 retList->push_back(new ProgramInfo(*p));
01372 }
01373
01374 SORT_RECLIST(*retList, comp_timechannel);
01375
01376 return hasconflicts;
01377 }
01378
01379 void Scheduler::getAllPending(QStringList &strList)
01380 {
01381 RecList retlist;
01382 bool hasconflicts = getAllPending(&retlist);
01383
01384 strList << QString::number(hasconflicts);
01385 strList << QString::number(retlist.size());
01386
01387 while (retlist.size() > 0)
01388 {
01389 ProgramInfo *p = retlist.front();
01390 p->ToStringList(strList);
01391 delete p;
01392 retlist.pop_front();
01393 }
01394 }
01395
01396 void Scheduler::getAllScheduled(QStringList &strList)
01397 {
01398 RecList schedlist;
01399
01400 findAllScheduledPrograms(schedlist);
01401
01402 strList << QString::number(schedlist.size());
01403
01404 while (schedlist.size() > 0)
01405 {
01406 ProgramInfo *pginfo = schedlist.front();
01407 pginfo->ToStringList(strList);
01408 delete pginfo;
01409 schedlist.pop_front();
01410 }
01411 }
01412
01413 void Scheduler::Reschedule(int recordid) {
01414 reschedLock.lock();
01415 if (recordid == -1)
01416 reschedQueue.clear();
01417 if (recordid != 0 || !reschedQueue.size())
01418 reschedQueue.append(recordid);
01419 reschedWait.wakeOne();
01420 reschedLock.unlock();
01421 }
01422
01423 void Scheduler::AddRecording(const ProgramInfo &pi)
01424 {
01425 QMutexLocker lockit(reclist_lock);
01426
01427 VERBOSE(VB_GENERAL, LOC + "AddRecording() recid: " << pi.recordid);
01428
01429 for (RecIter it = reclist.begin(); it != reclist.end(); ++it)
01430 {
01431 ProgramInfo *p = *it;
01432 if (p->recstatus == rsRecording && p->IsSameProgramTimeslot(pi))
01433 {
01434 VERBOSE(VB_IMPORTANT, LOC + "Not adding recording, " +
01435 QString("'%1' is already in reclist.").arg(pi.title));
01436 return;
01437 }
01438 }
01439
01440 VERBOSE(VB_SCHEDULE, LOC +
01441 QString("Adding '%1' to reclist.").arg(pi.title));
01442
01443 ProgramInfo * new_pi = new ProgramInfo(pi);
01444 reclist.push_back(new_pi);
01445 reclist_changed = true;
01446
01447
01448
01449 new_pi->AddHistory(false);
01450
01451
01452 new_pi->GetScheduledRecording();
01453
01454
01455 ScheduledRecording::signalChange(pi.recordid);
01456 }
01457
01458 bool Scheduler::IsBusyRecording(const ProgramInfo *rcinfo)
01459 {
01460 if (!m_tvList || !rcinfo)
01461 {
01462 VERBOSE(VB_IMPORTANT, LOC_ERR + "IsBusyRecording() -> true, "
01463 "no tvList or no rcinfo");
01464
01465 return true;
01466 }
01467
01468 EncoderLink *rctv = (*m_tvList)[rcinfo->cardid];
01469
01470 if (!rctv || rctv->IsBusyRecording())
01471 return true;
01472
01473
01474 TunedInputInfo busy_input;
01475 uint inputid = rcinfo->inputid;
01476 vector<uint> cardids = CardUtil::GetConflictingCards(
01477 inputid, rcinfo->cardid);
01478 for (uint i = 0; i < cardids.size(); i++)
01479 {
01480 rctv = (*m_tvList)[cardids[i]];
01481 if (!rctv)
01482 {
01483 VERBOSE(VB_SCHEDULE, LOC_ERR + "IsBusyRecording() -> true, "
01484 "rctv("<<rctv<<"==NULL) for card "<<cardids[i]);
01485
01486 return true;
01487 }
01488
01489 if (rctv->IsBusy(&busy_input, -1) &&
01490 igrp.GetSharedInputGroup(busy_input.inputid, inputid))
01491 {
01492 return true;
01493 }
01494 }
01495
01496 return false;
01497 }
01498
01499 void Scheduler::RunScheduler(void)
01500 {
01501 int prerollseconds = 0;
01502
01503 int secsleft;
01504 EncoderLink *nexttv = NULL;
01505
01506 ProgramInfo *nextRecording = NULL;
01507 QDateTime nextrectime;
01508 QString schedid;
01509
01510 QDateTime curtime;
01511 QDateTime lastupdate = QDateTime::currentDateTime().addDays(-1);
01512
01513 RecIter startIter = reclist.begin();
01514
01515 bool blockShutdown = gContext->GetNumSetting("blockSDWUwithoutClient", 1);
01516 QDateTime idleSince = QDateTime();
01517 int idleTimeoutSecs = 0;
01518 int idleWaitForRecordingTime = 0;
01519 bool firstRun = true;
01520
01521 struct timeval fillstart, fillend;
01522 float matchTime, placeTime;
01523
01524
01525
01526 MSqlQuery query(dbConn);
01527 query.prepare("UPDATE oldrecorded SET recstatus = :RSABORTED "
01528 " WHERE recstatus = :RSRECORDING");
01529 query.bindValue(":RSABORTED", rsAborted);
01530 query.bindValue(":RSRECORDING", rsRecording);
01531 query.exec();
01532 if (!query.isActive())
01533 MythContext::DBError("UpdateAborted", query);
01534
01535
01536 sleep(3);
01537
01538 Reschedule(-1);
01539
01540 while (1)
01541 {
01542 curtime = QDateTime::currentDateTime();
01543 bool statuschanged = false;
01544
01545 if ((startIter != reclist.end() &&
01546 curtime.secsTo((*startIter)->recstartts) < 30))
01547 sleep(1);
01548 else
01549 {
01550 reschedLock.lock();
01551 if (!reschedQueue.count())
01552 reschedWait.wait(&reschedLock, 1000);
01553 reschedLock.unlock();
01554
01555 if (reschedQueue.count())
01556 {
01557
01558
01559 dbConn = MSqlQuery::SchedCon();
01560
01561 gettimeofday(&fillstart, NULL);
01562 QString msg;
01563 while (reschedQueue.count())
01564 {
01565 int recordid = reschedQueue.front();
01566 reschedQueue.pop_front();
01567 msg.sprintf("Reschedule requested for id %d.", recordid);
01568 VERBOSE(VB_GENERAL, msg);
01569 if (recordid != 0)
01570 {
01571 if (recordid == -1)
01572 reschedQueue.clear();
01573 recordmatchLock.lock();
01574 UpdateMatches(recordid);
01575 recordmatchLock.unlock();
01576 }
01577 }
01578 gettimeofday(&fillend, NULL);
01579
01580 matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
01581 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
01582
01583 gettimeofday(&fillstart, NULL);
01584 bool worklistused = FillRecordList();
01585 gettimeofday(&fillend, NULL);
01586 if (worklistused)
01587 {
01588 UpdateNextRecord();
01589 PrintList();
01590 }
01591 else
01592 {
01593 VERBOSE(VB_GENERAL, "Reschedule interrupted, will retry");
01594 Reschedule(0);
01595 continue;
01596 }
01597
01598 placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 +
01599 (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0;
01600
01601 msg.sprintf("Scheduled %d items in "
01602 "%.1f = %.2f match + %.2f place", (int)reclist.size(),
01603 matchTime + placeTime, matchTime, placeTime);
01604
01605 VERBOSE(VB_GENERAL, msg);
01606 gContext->LogEntry("scheduler", LP_INFO, "Scheduled items", msg);
01607
01608 fsInfoCacheFillTime = QDateTime::currentDateTime().addSecs(-1000);
01609
01610 lastupdate = curtime;
01611 startIter = reclist.begin();
01612 statuschanged = true;
01613
01614
01615
01616 prerollseconds = gContext->GetNumSetting("RecordPreRoll");
01617
01618 idleTimeoutSecs = gContext->GetNumSetting("idleTimeoutSecs", 0);
01619 idleWaitForRecordingTime =
01620 gContext->GetNumSetting("idleWaitForRecordingTime", 15);
01621
01622 if (firstRun)
01623 {
01624
01625
01626 QString startupParam = "user";
01627
01628
01629 RecIter firstRunIter = reclist.begin();
01630 for ( ; firstRunIter != reclist.end(); firstRunIter++)
01631 if ((*firstRunIter)->recstatus == rsWillRecord)
01632 break;
01633
01634
01635 if (WasStartedAutomatically() ||
01636 ((firstRunIter != reclist.end()) &&
01637 ((curtime.secsTo((*firstRunIter)->recstartts) - prerollseconds)
01638 < (idleWaitForRecordingTime * 60))))
01639 {
01640 VERBOSE(VB_IMPORTANT, "AUTO-Startup assumed");
01641 startupParam = "auto";
01642
01643
01644
01645 blockShutdown = false;
01646 }
01647 else
01648 {
01649 VERBOSE(VB_IMPORTANT, "Seem to be woken up by USER");
01650 }
01651
01652 QString startupCommand = gContext->GetSetting("startupCommand",
01653 "");
01654 if (!startupCommand.isEmpty())
01655 {
01656 startupCommand.replace("$status", startupParam);
01657 myth_system(startupCommand.ascii());
01658 }
01659 firstRun = false;
01660 }
01661 }
01662 }
01663
01664 for ( ; startIter != reclist.end(); startIter++)
01665 if ((*startIter)->recstatus != (*startIter)->oldrecstatus)
01666 break;
01667
01668 curtime = QDateTime::currentDateTime();
01669
01670 RecIter recIter = startIter;
01671 for ( ; recIter != reclist.end(); recIter++)
01672 {
01673 QString msg, details;
01674 int fsID = -1;
01675
01676 nextRecording = *recIter;
01677
01678 if (nextRecording->recstatus != rsWillRecord)
01679 {
01680 if (nextRecording->recstatus != nextRecording->oldrecstatus &&
01681 nextRecording->recstartts <= curtime)
01682 nextRecording->AddHistory(false);
01683 continue;
01684 }
01685
01686 nextrectime = nextRecording->recstartts;
01687 secsleft = curtime.secsTo(nextrectime);
01688 schedid = nextRecording->schedulerid;
01689
01690 if (secsleft - prerollseconds < 60)
01691 {
01692 if (!recPendingList.contains(schedid))
01693 {
01694 recPendingList[schedid] = false;
01695
01696 livetvTime = (livetvTime < nextrectime) ?
01697 nextrectime : livetvTime;
01698
01699 Reschedule(0);
01700 }
01701 }
01702
01703 if (secsleft - prerollseconds > 35)
01704 break;
01705
01706 if (m_tvList->find(nextRecording->cardid) == m_tvList->end())
01707 {
01708 msg = QString("invalid cardid (%1) for %2")
01709 .arg(nextRecording->cardid)
01710 .arg(nextRecording->title);
01711 VERBOSE(VB_GENERAL, msg);
01712
01713 QMutexLocker lockit(reclist_lock);
01714 nextRecording->recstatus = rsTunerBusy;
01715 nextRecording->AddHistory(true);
01716 statuschanged = true;
01717 continue;
01718 }
01719
01720 nexttv = (*m_tvList)[nextRecording->cardid];
01721
01722
01723
01724 if (nexttv->IsTunerLocked())
01725 {
01726 msg = QString("SUPPRESSED recording \"%1\" on channel: "
01727 "%2 on cardid: %3, sourceid %4. Tuner "
01728 "is locked by an external application.")
01729 .arg(nextRecording->title.local8Bit())
01730 .arg(nextRecording->chanid)
01731 .arg(nextRecording->cardid)
01732 .arg(nextRecording->sourceid);
01733 VERBOSE(VB_GENERAL, msg);
01734
01735 QMutexLocker lockit(reclist_lock);
01736 nextRecording->recstatus = rsTunerBusy;
01737 nextRecording->AddHistory(true);
01738 statuschanged = true;
01739 continue;
01740 }
01741
01742 if (!IsBusyRecording(nextRecording))
01743 {
01744
01745
01746 secsleft -= prerollseconds;
01747 }
01748
01749
01750
01751 if (secsleft > 30)
01752 continue;
01753
01754 if (nextRecording->pathname == "")
01755 {
01756 QMutexLocker lockit(reclist_lock);
01757 fsID = FillRecordingDir(nextRecording, reclist);
01758 }
01759
01760 if (!recPendingList[schedid])
01761 {
01762 nexttv->RecordPending(nextRecording, max(secsleft, 0),
01763 hasLaterList.contains(schedid));
01764 recPendingList[schedid] = true;
01765 }
01766
01767 if (secsleft > -2)
01768 continue;
01769
01770 nextRecording->recstartts =
01771 mythCurrentDateTime().addSecs(30);
01772 nextRecording->recstartts.setTime(QTime(
01773 nextRecording->recstartts.time().hour(),
01774 nextRecording->recstartts.time().minute()));
01775
01776 QMutexLocker lockit(reclist_lock);
01777
01778 QString subtitle = nextRecording->subtitle.isEmpty() ? "" :
01779 QString(" \"%1\"").arg(nextRecording->subtitle);
01780
01781 details = QString("%1%2: "
01782 "channel %3 on cardid %4, sourceid %5")
01783 .arg(nextRecording->title)
01784 .arg(subtitle)
01785 .arg(nextRecording->chanid)
01786 .arg(nextRecording->cardid)
01787 .arg(nextRecording->sourceid);
01788
01789 if (schedulingEnabled)
01790 {
01791 nextRecording->recstatus =
01792 nexttv->StartRecording(nextRecording);
01793
01794 nextRecording->AddHistory(false);
01795 if (expirer)
01796 {
01797
01798 expirer->Update(nextRecording->cardid, fsID, true);
01799 }
01800 }
01801 else
01802 nextRecording->recstatus = rsOffLine;
01803 bool doSchedAfterStart =
01804 nextRecording->recstatus != rsRecording ||
01805 schedAfterStartMap[nextRecording->recordid] ||
01806 (nextRecording->parentid &&
01807 schedAfterStartMap[nextRecording->parentid]);
01808 nextRecording->AddHistory(doSchedAfterStart);
01809
01810 statuschanged = true;
01811
01812 bool is_rec = (nextRecording->recstatus == rsRecording);
01813 msg = is_rec ? "Started recording" : "Canceled recording (" +
01814 nextRecording->RecStatusText() + ")";
01815
01816 VERBOSE(VB_GENERAL, msg << ": " << details);
01817 gContext->LogEntry("scheduler", LP_NOTICE, msg, details);
01818
01819 if (is_rec)
01820 UpdateNextRecord();
01821
01822 if (nextRecording->recstatus == rsFailed)
01823 {
01824 MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2")
01825 .arg(nextRecording->chanid)
01826 .arg(nextRecording->recstartts.toString(Qt::ISODate)));
01827 gContext->dispatch(me);
01828 }
01829 }
01830
01831 if (statuschanged)
01832 {
01833 MythEvent me("SCHEDULE_CHANGE");
01834 gContext->dispatch(me);
01835 idleSince = QDateTime();
01836 }
01837
01838
01839 if ((idleTimeoutSecs > 0) && (m_mainServer != NULL))
01840 {
01841
01842 if (blockShutdown)
01843 blockShutdown &= !m_mainServer->isClientConnected();
01844 else
01845 {
01846
01847 bool recording = false;
01848 QMap<int, EncoderLink *>::Iterator it;
01849 for (it = m_tvList->begin(); (it != m_tvList->end()) &&
01850 !recording; ++it)
01851 {
01852 if (it.data()->IsBusy())
01853 recording = true;
01854 }
01855
01856 if (!(m_mainServer->isClientConnected()) && !recording)
01857 {
01858
01859 resetIdleTime_lock.lock();
01860 if (resetIdleTime)
01861 {
01862
01863 idleSince = QDateTime();
01864 resetIdleTime = false;
01865 }
01866 resetIdleTime_lock.unlock();
01867
01868 if (!idleSince.isValid())
01869 {
01870 RecIter idleIter = reclist.begin();
01871 for ( ; idleIter != reclist.end(); idleIter++)
01872 if ((*idleIter)->recstatus == rsWillRecord)
01873 break;
01874
01875 if (idleIter != reclist.end())
01876 {
01877 if (curtime.secsTo((*idleIter)->recstartts) -
01878 prerollseconds > idleWaitForRecordingTime * 60)
01879 {
01880 idleSince = curtime;
01881 }
01882 }
01883 else
01884 idleSince = curtime;
01885 }
01886 else
01887 {
01888
01889 if (idleSince.addSecs(idleTimeoutSecs) < curtime)
01890 {
01891
01892 if (m_isShuttingDown)
01893 {
01894
01895
01896 if (idleSince.addSecs(idleTimeoutSecs + 60) < curtime)
01897 {
01898 VERBOSE(VB_IMPORTANT, "Waited more than 60"
01899 " seconds for shutdown to complete"
01900 " - resetting idle time");
01901 idleSince = QDateTime();
01902 m_isShuttingDown = false;
01903 }
01904 }
01905 else if (!m_isShuttingDown &&
01906 CheckShutdownServer(prerollseconds, idleSince,
01907 blockShutdown))
01908 {
01909 ShutdownServer(prerollseconds, idleSince);
01910 }
01911 }
01912 else
01913 {
01914 int itime = idleSince.secsTo(curtime);
01915 QString msg;
01916 if (itime == 1)
01917 {
01918 msg = QString("I\'m idle now... shutdown will "
01919 "occur in %1 seconds.")
01920 .arg(idleTimeoutSecs);
01921 VERBOSE(VB_IMPORTANT, msg);
01922 MythEvent me(QString("SHUTDOWN_COUNTDOWN %1")
01923 .arg(idleTimeoutSecs));
01924 gContext->dispatch(me);
01925 }
01926 else if (itime % 10 == 0)
01927 {
01928 msg = QString("%1 secs left to system "
01929 "shutdown!")
01930 .arg(idleTimeoutSecs - itime);
01931 VERBOSE(VB_IDLE, msg);
01932 MythEvent me(QString("SHUTDOWN_COUNTDOWN %1")
01933 .arg(idleTimeoutSecs - itime));
01934 gContext->dispatch(me);
01935 }
01936 }
01937 }
01938 }
01939 else
01940 {
01941
01942 if (idleSince.isValid())
01943 {
01944 MythEvent me(QString("SHUTDOWN_COUNTDOWN -1"));
01945 gContext->dispatch(me);
01946 }
01947 idleSince = QDateTime();
01948 }
01949 }
01950 }
01951 }
01952 }
01953
01954
01955 bool Scheduler::CheckShutdownServer(int prerollseconds, QDateTime &idleSince,
01956 bool &blockShutdown)
01957 {
01958 (void)prerollseconds;
01959 bool retval = false;
01960 QString preSDWUCheckCommand = gContext->GetSetting("preSDWUCheckCommand",
01961 "");
01962
01963 int state = 0;
01964 if (!preSDWUCheckCommand.isEmpty())
01965 {
01966 state = myth_system(preSDWUCheckCommand.ascii());
01967
01968 if (GENERIC_EXIT_NOT_OK != state)
01969 {
01970 retval = false;
01971 switch(state)
01972 {
01973 case 0:
01974 VERBOSE(VB_GENERAL, "CheckShutdownServer returned - OK to shutdown");
01975 retval = true;
01976 break;
01977 case 1:
01978 VERBOSE(VB_IDLE, "CheckShutdownServer returned - Not OK to shutdown");
01979
01980 idleSince = QDateTime();
01981 break;
01982 case 2:
01983 VERBOSE(VB_IDLE, "CheckShutdownServer returned - Not OK to shutdown, need reconnect");
01984
01985
01986
01987 blockShutdown
01988 = gContext->GetNumSetting("blockSDWUwithoutClient",
01989 1);
01990 idleSince = QDateTime();
01991 break;
01992
01993
01994
01995
01996 default:
01997 break;
01998 }
01999 }
02000 }
02001 else
02002 retval = true;
02003
02004 return retval;
02005 }
02006
02007 void Scheduler::ShutdownServer(int prerollseconds, QDateTime &idleSince)
02008 {
02009 m_isShuttingDown = true;
02010
02011 RecIter recIter = reclist.begin();
02012 for ( ; recIter != reclist.end(); recIter++)
02013 if ((*recIter)->recstatus == rsWillRecord)
02014 break;
02015
02016
02017 if (recIter != reclist.end())
02018 {
02019 ProgramInfo *nextRecording = (*recIter);
02020 QDateTime restarttime = nextRecording->recstartts.addSecs((-1) *
02021 prerollseconds);
02022
02023 int add = gContext->GetNumSetting("StartupSecsBeforeRecording", 240);
02024 if (add)
02025 restarttime = restarttime.addSecs((-1) * add);
02026
02027 QString wakeup_timeformat = gContext->GetSetting("WakeupTimeFormat",
02028 "hh:mm yyyy-MM-dd");
02029 QString setwakeup_cmd = gContext->GetSetting("SetWakeuptimeCommand",
02030 "echo \'Wakeuptime would "
02031 "be $time if command "
02032 "set.\'");
02033
02034 if (setwakeup_cmd.isEmpty())
02035 {
02036 VERBOSE(VB_IMPORTANT, "SetWakeuptimeCommand is empty, shutdown aborted");
02037 idleSince = QDateTime();
02038 m_isShuttingDown = false;
02039 return;
02040 }
02041 if (wakeup_timeformat == "time_t")
02042 {
02043 QString time_ts;
02044 setwakeup_cmd.replace("$time",
02045 time_ts.setNum(restarttime.toTime_t()));
02046 }
02047 else
02048 setwakeup_cmd.replace("$time",
02049 restarttime.toString(wakeup_timeformat));
02050
02051 VERBOSE(VB_GENERAL, QString("Running the command to set the next "
02052 "scheduled wakeup time :-\n\t\t\t\t\t\t") +
02053 setwakeup_cmd);
02054
02055
02056 if (myth_system(setwakeup_cmd.ascii()))
02057 {
02058 VERBOSE(VB_IMPORTANT, "SetWakeuptimeCommand failed, "
02059 "shutdown aborted");
02060 idleSince = QDateTime();
02061 m_isShuttingDown = false;
02062 return;
02063 }
02064 }
02065
02066
02067 MythEvent me(QString("SHUTDOWN_NOW"));
02068 gContext->dispatch(me);
02069
02070 QString halt_cmd = gContext->GetSetting("ServerHaltCommand",
02071 "sudo /sbin/halt -p");
02072
02073 if (!halt_cmd.isEmpty())
02074 {
02075
02076 m_mainServer->ShutSlaveBackendsDown(halt_cmd);
02077
02078 VERBOSE(VB_GENERAL, QString("Running the command to shutdown "
02079 "this computer :-\n\t\t\t\t\t\t") + halt_cmd);
02080
02081
02082 if (!myth_system(halt_cmd.ascii()))
02083 return;
02084 else
02085 VERBOSE(VB_IMPORTANT, "ServerHaltCommand failed, shutdown aborted");
02086 }
02087
02088
02089
02090 idleSince = QDateTime();
02091 m_isShuttingDown = false;
02092 }
02093
02094 void *Scheduler::SchedulerThread(void *param)
02095 {
02096
02097 if (setpriority(PRIO_PROCESS, 0, 9))
02098 VERBOSE(VB_IMPORTANT, LOC + "Setting priority failed." + ENO);
02099 Scheduler *sched = (Scheduler *)param;
02100 sched->RunScheduler();
02101
02102 return NULL;
02103 }
02104
02105 void Scheduler::UpdateManuals(int recordid)
02106 {
02107 MSqlQuery query(dbConn);
02108
02109 query.prepare(QString("SELECT type,title,station,startdate,starttime, "
02110 " enddate,endtime "
02111 "FROM %1 WHERE recordid = :RECORDID").arg(recordTable));
02112 query.bindValue(":RECORDID", recordid);
02113 query.exec();
02114 if (!query.isActive() || query.size() != 1)
02115 {
02116 MythContext::DBError("UpdateManuals", query);
02117 return;
02118 }
02119
02120 query.next();
02121 RecordingType rectype = RecordingType(query.value(0).toInt());
02122 QString title = query.value(1).toString();
02123 QString station = query.value(2).toString() ;
02124 QDateTime startdt = QDateTime(query.value(3).asDate(),
02125 query.value(4).asTime());
02126 int duration = startdt.secsTo(QDateTime(query.value(5).asDate(),
02127 query.value(6).asTime())) / 60;
02128
02129 query.prepare("SELECT chanid from channel "
02130 "WHERE callsign = :STATION");
02131 query.bindValue(":STATION", station);
02132 query.exec();
02133 if (!query.isActive())
02134 {
02135 MythContext::DBError("UpdateManuals", query);
02136 return;
02137 }
02138
02139 QValueList<int> chanidlist;
02140 while (query.next())
02141 chanidlist.append(query.value(0).toInt());
02142
02143 int progcount;
02144 int skipdays;
02145 bool weekday;
02146 int weeksoff;
02147
02148 switch (rectype)
02149 {
02150 case kSingleRecord:
02151 case kOverrideRecord:
02152 case kDontRecord:
02153 progcount = 1;
02154 skipdays = 1;
02155 weekday = false;
02156 break;
02157 case kTimeslotRecord:
02158 progcount = 13;
02159 skipdays = 1;
02160 if (startdt.date().dayOfWeek() < 6)
02161 weekday = true;
02162 else
02163 weekday = false;
02164 startdt.setDate(QDate::currentDate());
02165 break;
02166 case kWeekslotRecord:
02167 progcount = 2;
02168 skipdays = 7;
02169 weekday = false;
02170 weeksoff = (startdt.date().daysTo(QDate::currentDate()) + 6) / 7;
02171 startdt = startdt.addDays(weeksoff * 7);
02172 break;
02173 default:
02174 VERBOSE(VB_IMPORTANT, QString("Invalid rectype for manual "
02175 "recordid %1").arg(recordid));
02176 return;
02177 }
02178
02179 while (progcount--)
02180 {
02181 for (int i = 0; i < (int)chanidlist.size(); i++)
02182 {
02183 if (weekday && startdt.date().dayOfWeek() >= 6)
02184 continue;
02185
02186 query.prepare("REPLACE INTO program (chanid,starttime,endtime,"
02187 " title,subtitle,manualid) "
02188 "VALUES (:CHANID,:STARTTIME,:ENDTIME,:TITLE,"
02189 " :SUBTITLE,:RECORDID)");
02190 query.bindValue(":CHANID", chanidlist[i]);
02191 query.bindValue(":STARTTIME", startdt);
02192 query.bindValue(":ENDTIME", startdt.addSecs(duration * 60));
02193 query.bindValue(":TITLE", title);
02194 query.bindValue(":SUBTITLE", startdt.toString());
02195 query.bindValue(":RECORDID", recordid);
02196 query.exec();
02197 if (!query.isActive())
02198 {
02199 MythContext::DBError("UpdateManuals", query);
02200 return;
02201 }
02202 }
02203 startdt = startdt.addDays(skipdays);
02204 }
02205 }
02206
02207 void Scheduler::BuildNewRecordsQueries(int recordid, QStringList &from,
02208 QStringList &where,
02209 MSqlBindings &bindings)
02210 {
02211 MSqlQuery result(dbConn);
02212 QString query;
02213 QString qphrase;
02214
02215 query = QString("SELECT recordid,search,subtitle,description "
02216 "FROM %1 WHERE search <> %2 AND "
02217 "(recordid = %3 OR %4 = -1) ")
02218 .arg(recordTable).arg(kNoSearch).arg(recordid).arg(recordid);
02219
02220 result.prepare(query);
02221
02222 if (!result.exec() || !result.isActive())
02223 {
02224 MythContext::DBError("BuildNewRecordsQueries", result);
02225 return;
02226 }
02227
02228 int count = 0;
02229 while (result.next())
02230 {
02231 QString prefix = QString(":NR%1").arg(count);
02232 qphrase = QString::fromUtf8(result.value(3).toString());
02233
02234 RecSearchType searchtype = RecSearchType(result.value(1).toInt());
02235
02236 if (qphrase == "" && searchtype != kManualSearch)
02237 {
02238 VERBOSE(VB_IMPORTANT, QString("Invalid search key in recordid %1")
02239 .arg(result.value(0).toString()));
02240 continue;
02241 }
02242
02243 QString bindrecid = prefix + "RECID";
02244 QString bindphrase = prefix + "PHRASE";
02245 QString bindlikephrase = prefix + "LIKEPHRASE";
02246
02247 bindings[bindrecid] = result.value(0).toString();
02248 bindings[bindphrase] = qphrase.utf8();
02249 bindings[bindlikephrase] = QString(QString("%") + qphrase + "%").utf8();
02250
02251 switch (searchtype)
02252 {
02253 case kPowerSearch:
02254 qphrase.remove(QRegExp("^\\s*AND\\s+", false));
02255 qphrase.remove(";", false);
02256 from << result.value(2).toString();
02257 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid +
02258 QString(" AND program.manualid = 0 AND ( %2 )")
02259 .arg(qphrase));
02260 break;
02261 case kTitleSearch:
02262 from << "";
02263 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid + " AND "
02264 "program.manualid = 0 AND "
02265 "program.title LIKE " + bindlikephrase);
02266 break;
02267 case kKeywordSearch:
02268 from << "";
02269 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid +
02270 " AND program.manualid = 0"
02271 " AND (program.title LIKE " + bindlikephrase +
02272 " OR program.subtitle LIKE " + bindlikephrase +
02273 " OR program.description LIKE " + bindlikephrase + ")");
02274 break;
02275 case kPeopleSearch:
02276 from << ", people, credits";
02277 where << (QString("%1.recordid = ").arg(recordTable) + bindrecid + " AND "
02278 "program.manualid = 0 AND "
02279 "people.name LIKE " + bindphrase + " AND "
02280 "credits.person = people.person AND "
02281 "program.chanid = credits.chanid AND "
02282 "program.starttime = credits.starttime");
02283 break;
02284 case kManualSearch:
02285 UpdateManuals(result.value(0).toInt());
02286 from << "";
02287 where << ((QString("%1.recordid = ").arg(recordTable)) + bindrecid + " AND " +
02288 QString("program.manualid = %1.recordid ").arg(recordTable));
02289 break;
02290 default:
02291 VERBOSE(VB_IMPORTANT, QString("Unknown RecSearchType "
02292 "(%1) for recordid %2")
02293 .arg(result.value(1).toInt())
02294 .arg(result.value(0).toString()));
02295 break;
02296 }
02297
02298 count++;
02299 }
02300
02301 if (recordid == -1 || from.count() == 0)
02302 {
02303 QString recidmatch = "";
02304 if (recordid != -1)
02305 recidmatch = "RECTABLE.recordid = :NRRECORDID AND ";
02306 QString s = recidmatch +
02307 "RECTABLE.search = :NRST AND "
02308 "program.manualid = 0 AND "
02309 "program.title = RECTABLE.title ";
02310
02311 while (1)
02312 {
02313 int i = s.find("RECTABLE");
02314 if (i == -1) break;
02315 s = s.replace(i, strlen("RECTABLE"), recordTable);
02316 }
02317
02318 from << "";
02319 where << s;
02320 bindings[":NRST"] = kNoSearch;
02321 bindings[":NRRECORDID"] = recordid;
02322 }
02323 }
02324
02325 void Scheduler::UpdateMatches(int recordid) {
02326 struct timeval dbstart, dbend;
02327
02328 if (recordid == 0)
02329 return;
02330
02331 MSqlQuery query(dbConn);
02332
02333 if (recordid == -1)
02334 query.prepare("DELETE FROM recordmatch");
02335 else
02336 {
02337 query.prepare("DELETE FROM recordmatch WHERE recordid = :RECORDID");
02338 query.bindValue(":RECORDID", recordid);
02339 }
02340
02341 query.exec();
02342 if (!query.isActive())
02343 {
02344 MythContext::DBError("UpdateMatches", query);
02345 return;
02346 }
02347
02348 if (recordid == -1)
02349 query.prepare("DELETE FROM program WHERE manualid <> 0");
02350 else
02351 {
02352 query.prepare("DELETE FROM program WHERE manualid = :RECORDID");
02353 query.bindValue(":RECORDID", recordid);
02354 }
02355 query.exec();
02356 if (!query.isActive())
02357 {
02358 MythContext::DBError("UpdateMatches", query);
02359 return;
02360 }
02361
02362
02363 query.prepare("SELECT NULL from record "
02364 "WHERE type = :FINDONE AND findid <= 0;");
02365 query.bindValue(":FINDONE", kFindOneRecord);
02366 query.exec();
02367 if (!query.isActive())
02368 {
02369 MythContext::DBError("UpdateMatches", query);
02370 return;
02371 }
02372 else if (query.size())
02373 {
02374 QDate epoch = QDate::QDate (1970, 1, 1);
02375 int findtoday = epoch.daysTo(QDate::currentDate()) + 719528;
02376 query.prepare("UPDATE record set findid = :FINDID "
02377 "WHERE type = :FINDONE AND findid <= 0;");
02378 query.bindValue(":FINDID", findtoday);
02379 query.bindValue(":FINDONE", kFindOneRecord);
02380 query.exec();
02381 }
02382
02383 unsigned clause;
02384 QStringList fromclauses, whereclauses;
02385 MSqlBindings bindings;
02386
02387 BuildNewRecordsQueries(recordid, fromclauses, whereclauses, bindings);
02388
02389 if (print_verbose_messages & VB_SCHEDULE)
02390 {
02391 for (clause = 0; clause < fromclauses.count(); clause++)
02392 cout << "Query " << clause << ": " << fromclauses[clause]
02393 << "/" << whereclauses[clause] << endl;
02394 }
02395
02396 for (clause = 0; clause < fromclauses.count(); clause++)
02397 {
02398 QString query = QString(
02399 "INSERT INTO recordmatch (recordid, chanid, starttime, manualid) "
02400 "SELECT RECTABLE.recordid, program.chanid, program.starttime, "
02401 " IF(search = %1, RECTABLE.recordid, 0) ").arg(kManualSearch) + QString(
02402 "FROM (RECTABLE, program INNER JOIN channel "
02403 " ON channel.chanid = program.chanid) ") + fromclauses[clause] + QString(
02404 " WHERE ") + whereclauses[clause] +
02405 QString(" AND (NOT ((RECTABLE.dupin & %1) AND program.previouslyshown)) "
02406 " AND (NOT ((RECTABLE.dupin & %2) AND program.generic > 0)) "
02407 " AND (NOT ((RECTABLE.dupin & %2) AND (program.previouslyshown "
02408 " OR program.first = 0))) ")
02409 .arg(kDupsExRepeats).arg(kDupsExGeneric).arg(kDupsFirstNew) +
02410 QString(" AND channel.visible = 1 AND "
02411 "((RECTABLE.type = %1 "
02412 "OR RECTABLE.type = %2 "
02413 "OR RECTABLE.type = %3 "
02414 "OR RECTABLE.type = %4) "
02415 " OR "
02416 " ((RECTABLE.station = channel.callsign) "
02417 " AND "
02418 " ((RECTABLE.type = %5) "
02419 " OR"
02420 " ((TIME_TO_SEC(RECTABLE.starttime) = TIME_TO_SEC(program.starttime)) "
02421 " AND "
02422 " ((RECTABLE.type = %6) "
02423 " OR"
02424 " ((DAYOFWEEK(RECTABLE.startdate) = DAYOFWEEK(program.starttime) "
02425 " AND "
02426 " ((RECTABLE.type = %7) "
02427 " OR"
02428 " ((TO_DAYS(RECTABLE.startdate) = TO_DAYS(program.starttime)) "
02429 " )"
02430 " )"
02431 " )"
02432 " )"
02433 " )"
02434 " )"
02435 " )"
02436 " )"
02437 ") ")
02438 .arg(kAllRecord)
02439 .arg(kFindOneRecord)
02440 .arg(kFindDailyRecord)
02441 .arg(kFindWeeklyRecord)
02442 .arg(kChannelRecord)
02443 .arg(kTimeslotRecord)
02444 .arg(kWeekslotRecord);
02445
02446 while (1)
02447 {
02448 int i = query.find("RECTABLE");
02449 if (i == -1) break;
02450 query = query.replace(i, strlen("RECTABLE"), recordTable);
02451 }
02452
02453 VERBOSE(VB_SCHEDULE, QString(" |-- Start DB Query %1...").arg(clause));
02454
02455 gettimeofday(&dbstart, NULL);
02456 MSqlQuery result(dbConn);
02457 result.prepare(query);
02458 result.bindValues(bindings);
02459 result.exec();
02460 gettimeofday(&dbend, NULL);
02461
02462 if (!result.isActive())
02463 {
02464 MythContext::DBError("UpdateMatches", result);
02465 continue;
02466 }
02467
02468 VERBOSE(VB_SCHEDULE, QString(" |-- %1 results in %2 sec.")
02469 .arg(result.size())
02470 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
02471 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
02472
02473 }
02474
02475 VERBOSE(VB_SCHEDULE, " +-- Done.");
02476 }
02477
02478 void Scheduler::AddNewRecords(void)
02479 {
02480 struct timeval dbstart, dbend;
02481
02482 QMap<RecordingType, int> recTypeRecPriorityMap;
02483 RecList tmpList;
02484
02485 QMap<int, bool> cardMap;
02486 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
02487 for (; enciter != m_tvList->end(); ++enciter)
02488 {
02489 EncoderLink *enc = enciter.data();
02490 if (enc->IsConnected())
02491 cardMap[enc->GetCardID()] = true;
02492 }
02493
02494 QMap<int, bool> tooManyMap;
02495 bool checkTooMany = false;
02496 schedAfterStartMap.clear();
02497
02498 MSqlQuery rlist(dbConn);
02499 rlist.prepare(QString("SELECT recordid,title,maxepisodes,maxnewest FROM %1;").arg(recordTable));
02500
02501 rlist.exec();
02502
02503 if (!rlist.isActive())
02504 {
02505 MythContext::DBError("CheckTooMany", rlist);
02506 return;
02507 }
02508
02509 while (rlist.next())
02510 {
02511 int recid = rlist.value(0).toInt();
02512 QString qtitle = QString::fromUtf8(rlist.value(1).toString());
02513 int maxEpisodes = rlist.value(2).toInt();
02514 int maxNewest = rlist.value(3).toInt();
02515
02516 tooManyMap[recid] = false;
02517 schedAfterStartMap[recid] = false;
02518
02519 if (maxEpisodes && !maxNewest)
02520 {
02521 MSqlQuery epicnt(dbConn);
02522
02523 epicnt.prepare("SELECT DISTINCT chanid, progstart, progend "
02524 "FROM recorded "
02525 "WHERE recordid = :RECID AND preserve = 0 "
02526 "AND duplicate <> 0 "
02527 "AND recgroup NOT IN ('LiveTV','Deleted');");
02528 epicnt.bindValue(":RECID", recid);
02529
02530 if (epicnt.exec() && epicnt.isActive())
02531 {
02532 if (epicnt.size() >= maxEpisodes - 1)
02533 {
02534 schedAfterStartMap[recid] = true;
02535 if (epicnt.size() >= maxEpisodes)
02536 {
02537 tooManyMap[recid] = true;
02538 checkTooMany = true;
02539 }
02540 }
02541 }
02542 }
02543 }
02544
02545 int complexpriority = gContext->GetNumSetting("ComplexPriority", 0);
02546 prefinputpri = gContext->GetNumSetting("PrefInputPriority", 2);
02547 int hdtvpriority = gContext->GetNumSetting("HDTVRecPriority", 0);
02548
02549 QString pwrpri = "channel.recpriority + cardinput.recpriority";
02550
02551 if (prefinputpri)
02552 pwrpri += QString(" + "
02553 "(cardinput.cardinputid = RECTABLE.prefinput) * %1").arg(prefinputpri);
02554
02555 if (hdtvpriority)
02556 pwrpri += QString(" + (program.hdtv > 0) * %1").arg(hdtvpriority);
02557
02558 QString schedTmpRecord = recordTable;
02559
02560 MSqlQuery result(dbConn);
02561
02562 if (schedTmpRecord == "record")
02563 {
02564 schedTmpRecord = "sched_temp_record";
02565
02566 result.prepare("DROP TABLE IF EXISTS sched_temp_record;");
02567 result.exec();
02568
02569 if (!result.isActive())
02570 {
02571 MythContext::DBError("Dropping sched_temp_record table", result);
02572 return;
02573 }
02574
02575 result.prepare("CREATE TEMPORARY TABLE sched_temp_record "
02576 "LIKE record;");
02577 result.exec();
02578
02579 if (!result.isActive())
02580 {
02581 MythContext::DBError("Creating sched_temp_record table",
02582 result);
02583 return;
02584 }
02585
02586 result.prepare("INSERT sched_temp_record SELECT * from record;");
02587 result.exec();
02588
02589 if (!result.isActive())
02590 {
02591 MythContext::DBError("Populating sched_temp_record table",
02592 result);
02593 return;
02594 }
02595 }
02596
02597 result.prepare("DROP TABLE IF EXISTS sched_temp_recorded;");
02598 result.exec();
02599
02600 if (!result.isActive())
02601 {
02602 MythContext::DBError("Dropping sched_temp_recorded table", result);
02603 return;
02604 }
02605
02606 result.prepare("CREATE TEMPORARY TABLE sched_temp_recorded "
02607 "LIKE recorded;");
02608 result.exec();
02609
02610 if (!result.isActive())
02611 {
02612 MythContext::DBError("Creating sched_temp_recorded table", result);
02613 return;
02614 }
02615
02616 result.prepare("INSERT sched_temp_recorded SELECT * from recorded;");
02617 result.exec();
02618
02619 if (!result.isActive())
02620 {
02621 MythContext::DBError("Populating sched_temp_recorded table", result);
02622 return;
02623 }
02624
02625 result.prepare(QString("SELECT recpriority, selectclause FROM %1;")
02626 .arg(priorityTable));
02627 result.exec();
02628
02629 if (!result.isActive())
02630 {
02631 MythContext::DBError("Power Priority", result);
02632 return;
02633 }
02634
02635 while (result.next())
02636 {
02637 if (result.value(0).toInt())
02638 {
02639 QString sclause = result.value(1).toString();
02640 sclause.remove(QRegExp("^\\s*AND\\s+", false));
02641 sclause.remove(";", false);
02642 pwrpri += QString(" + (%1) * %2").arg(sclause)
02643 .arg(result.value(0).toInt());
02644 }
02645 }
02646 pwrpri += QString(" AS powerpriority ");
02647
02648 QString progfindid = QString(
02649 "(CASE RECTABLE.type "
02650 " WHEN %1 "
02651 " THEN RECTABLE.findid "
02652 " WHEN %2 "
02653 " THEN to_days(date_sub(program.starttime, interval "
02654 " time_format(RECTABLE.findtime, '%H:%i') hour_minute)) "
02655 " WHEN %3 "
02656 " THEN floor((to_days(date_sub(program.starttime, interval "
02657 " time_format(RECTABLE.findtime, '%H:%i') hour_minute)) - "
02658 " RECTABLE.findday)/7) * 7 + RECTABLE.findday "
02659 " WHEN %4 "
02660 " THEN RECTABLE.findid "
02661 " ELSE 0 "
02662 " END) ")
02663 .arg(kFindOneRecord)
02664 .arg(kFindDailyRecord)
02665 .arg(kFindWeeklyRecord)
02666 .arg(kOverrideRecord);
02667
02668 QString rmquery = QString(
02669 "UPDATE recordmatch "
02670 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
02671 " INNER JOIN program ON (recordmatch.chanid = program.chanid AND "
02672 " recordmatch.starttime = program.starttime AND "
02673 " recordmatch.manualid = program.manualid) "
02674 " LEFT JOIN oldrecorded ON "
02675 " ( "
02676 " RECTABLE.dupmethod > 1 AND "
02677 " oldrecorded.duplicate <> 0 AND "
02678 " program.title = oldrecorded.title "
02679 " AND "
02680 " ( "
02681 " (program.programid <> '' AND program.generic = 0 "
02682 " AND program.programid = oldrecorded.programid) "
02683 " OR "
02684 " (oldrecorded.findid <> 0 AND "
02685 " oldrecorded.findid = ") + progfindid + QString(") "
02686 " OR "
02687 " ( "
02688 " program.generic = 0 "
02689 " AND "
02690 " (program.programid = '' OR oldrecorded.programid = '') "
02691 " AND "
02692 " (((RECTABLE.dupmethod & 0x02) = 0) OR (program.subtitle <> '' "
02693 " AND program.subtitle = oldrecorded.subtitle)) "
02694 " AND "
02695 " (((RECTABLE.dupmethod & 0x04) = 0) OR (program.description <> '' "
02696 " AND program.description = oldrecorded.description)) "
02697 " AND "
02698 " (((RECTABLE.dupmethod & 0x08) = 0) OR (program.subtitle <> '' "
02699 " AND program.subtitle = oldrecorded.subtitle) OR (program.subtitle = '' "
02700 " AND oldrecorded.subtitle = '' AND program.description <> '' "
02701 " AND program.description = oldrecorded.description)) "
02702 " ) "
02703 " ) "
02704 " ) "
02705 " LEFT JOIN sched_temp_recorded recorded ON "
02706 " ( "
02707 " RECTABLE.dupmethod > 1 AND "
02708 " recorded.duplicate <> 0 AND "
02709 " program.title = recorded.title AND "
02710 " recorded.recgroup NOT IN ('LiveTV','Deleted') "
02711 " AND "
02712 " ( "
02713 " (program.programid <> '' AND program.generic = 0 "
02714 " AND program.programid = recorded.programid) "
02715 " OR "
02716 " (recorded.findid <> 0 AND "
02717 " recorded.findid = ") + progfindid + QString(") "
02718 " OR "
02719 " ( "
02720 " program.generic = 0 "
02721 " AND "
02722 " (program.programid = '' OR recorded.programid = '') "
02723 " AND "
02724 " (((RECTABLE.dupmethod & 0x02) = 0) OR (program.subtitle <> '' "
02725 " AND program.subtitle = recorded.subtitle)) "
02726 " AND "
02727 " (((RECTABLE.dupmethod & 0x04) = 0) OR (program.description <> '' "
02728 " AND program.description = recorded.description)) "
02729 " AND "
02730 " (((RECTABLE.dupmethod & 0x08) = 0) OR (program.subtitle <> '' "
02731 " AND program.subtitle = recorded.subtitle) OR (program.subtitle = '' "
02732 " AND recorded.subtitle = '' AND program.description <> '' "
02733 " AND program.description = recorded.description)) "
02734 " ) "
02735 " ) "
02736 " ) "
02737 " LEFT JOIN oldfind ON "
02738 " (oldfind.recordid = recordmatch.recordid AND "
02739 " oldfind.findid = ") + progfindid + QString(") "
02740 " SET oldrecduplicate = (oldrecorded.endtime IS NOT NULL), "
02741 " recduplicate = (recorded.endtime IS NOT NULL), "
02742 " findduplicate = (oldfind.findid IS NOT NULL), "
02743 " oldrecstatus = oldrecorded.recstatus "
02744 );
02745 rmquery.replace("RECTABLE", schedTmpRecord);
02746
02747 QString query = QString(
02748 "SELECT DISTINCT channel.chanid, channel.sourceid, "
02749 "program.starttime, program.endtime, "
02750 "program.title, program.subtitle, program.description, "
02751 "channel.channum, channel.callsign, channel.name, "
02752 "oldrecduplicate, program.category, "
02753 "RECTABLE.recpriority, "
02754 "RECTABLE.dupin, "
02755 "recduplicate, "
02756 "findduplicate, "
02757 "RECTABLE.type, RECTABLE.recordid, "
02758 "program.starttime - INTERVAL RECTABLE.startoffset minute AS recstartts, "
02759 "program.endtime + INTERVAL RECTABLE.endoffset minute AS recendts, "
02760 "program.previouslyshown, RECTABLE.recgroup, RECTABLE.dupmethod, "
02761 "channel.commmethod, capturecard.cardid, "
02762 "cardinput.cardinputid, UPPER(cardinput.shareable) = 'Y' AS shareable, "
02763 "program.seriesid, program.programid, program.category_type, "
02764 "program.airdate, program.stars, program.originalairdate, RECTABLE.inactive, "
02765 "RECTABLE.parentid, ") + progfindid + ", RECTABLE.playgroup, "
02766 "oldrecstatus.recstatus, oldrecstatus.reactivate, "
02767 "program.videoprop+0, program.subtitletypes+0, program.audioprop+0, "
02768 "RECTABLE.storagegroup, capturecard.hostname, recordmatch.oldrecstatus, " +
02769 pwrpri + QString(
02770 "FROM recordmatch "
02771 " INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
02772 " INNER JOIN program ON (recordmatch.chanid = program.chanid AND "
02773 " recordmatch.starttime = program.starttime AND "
02774 " recordmatch.manualid = program.manualid) "
02775 " INNER JOIN channel ON (channel.chanid = program.chanid) "
02776 " INNER JOIN cardinput ON (channel.sourceid = cardinput.sourceid) "
02777 " INNER JOIN capturecard ON (capturecard.cardid = cardinput.cardid) "
02778 " LEFT JOIN oldrecorded as oldrecstatus ON "
02779 " ( oldrecstatus.station = channel.callsign AND "
02780 " oldrecstatus.starttime = program.starttime AND "
02781 " oldrecstatus.title = program.title ) "
02782 " ORDER BY RECTABLE.recordid DESC "
02783 );
02784 query.replace("RECTABLE", schedTmpRecord);
02785
02786 VERBOSE(VB_SCHEDULE, QString(" |-- Start DB Query..."));
02787
02788 gettimeofday(&dbstart, NULL);
02789 result.prepare(rmquery);
02790 result.exec();
02791 if (!result.isActive())
02792 {
02793 MythContext::DBError("AddNewRecords recordmatch", result);
02794 return;
02795 }
02796 result.prepare(query);
02797 result.exec();
02798 if (!result.isActive())
02799 {
02800 MythContext::DBError("AddNewRecords", result);
02801 return;
02802 }
02803 gettimeofday(&dbend, NULL);
02804
02805 VERBOSE(VB_SCHEDULE, QString(" |-- %1 results in %2 sec. Processing...")
02806 .arg(result.size())
02807 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
02808 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
02809
02810 while (result.next())
02811 {
02812 ProgramInfo *p = new ProgramInfo;
02813 p->reactivate = result.value(38).toInt();
02814 p->oldrecstatus = RecStatusType(result.value(37).toInt());
02815 if (p->oldrecstatus == rsAborted ||
02816 p->oldrecstatus == rsNotListed ||
02817 p->reactivate)
02818 p->recstatus = rsUnknown;
02819 else
02820 p->recstatus = p->oldrecstatus;
02821
02822 p->chanid = result.value(0).toString();
02823 p->sourceid = result.value(1).toInt();
02824 p->startts = result.value(2).toDateTime();
02825 p->endts = result.value(3).toDateTime();
02826 p->title = QString::fromUtf8(result.value(4).toString());
02827 p->subtitle = QString::fromUtf8(result.value(5).toString());
02828 p->description = QString::fromUtf8(result.value(6).toString());
02829 p->chanstr = result.value(7).toString();
02830 p->chansign = QString::fromUtf8(result.value(8).toString());
02831 p->channame = QString::fromUtf8(result.value(9).toString());
02832 p->category = QString::fromUtf8(result.value(11).toString());
02833 p->recpriority = result.value(12).toInt();
02834 p->dupin = RecordingDupInType(result.value(13).toInt());
02835 p->dupmethod = RecordingDupMethodType(result.value(22).toInt());
02836 p->rectype = RecordingType(result.value(16).toInt());
02837 p->recordid = result.value(17).toInt();
02838
02839 p->recstartts = result.value(18).toDateTime();
02840 p->recendts = result.value(19).toDateTime();
02841 p->repeat = result.value(20).toInt();
02842 p->recgroup = QString::fromUtf8(result.value(21).toString());
02843 p->storagegroup = QString::fromUtf8(result.value(42).toString());
02844 p->playgroup = QString::fromUtf8(result.value(36).toString());
02845 p->chancommfree = (result.value(23).toInt() == -2);
02846 p->hostname = result.value(43).toString();
02847 p->cardid = result.value(24).toInt();
02848 p->inputid = result.value(25).toInt();
02849 p->shareable = result.value(26).toInt();
02850 p->seriesid = result.value(27).toString();
02851 p->programid = result.value(28).toString();
02852 p->catType = result.value(29).toString();
02853 p->year = result.value(30).toString();
02854 p->stars = result.value(31).toDouble();
02855
02856 if (result.value(32).isNull())
02857 {
02858 p->originalAirDate = QDate::QDate (0, 1, 1);
02859 p->hasAirDate = false;
02860 }
02861 else
02862 {
02863 p->originalAirDate = QDate::fromString(result.value(32).toString(), Qt::ISODate);
02864 p->hasAirDate = true;
02865 }
02866
02867 bool inactive = result.value(33).toInt();
02868 p->parentid = result.value(34).toInt();
02869 p->findid = result.value(35).toInt();
02870
02871
02872 p->videoproperties = result.value(39).toInt();
02873 p->subtitleType = result.value(40).toInt();
02874 p->audioproperties = result.value(41).toInt();
02875
02876 if (!recTypeRecPriorityMap.contains(p->rectype))
02877 recTypeRecPriorityMap[p->rectype] =
02878 p->GetRecordingTypeRecPriority(p->rectype);
02879 p->recpriority += recTypeRecPriorityMap[p->rectype];
02880
02881 p->recpriority2 = result.value(45).toInt();
02882
02883 if (complexpriority == 0)
02884 {
02885 p->recpriority += p->recpriority2;
02886 p->recpriority2 = 0;
02887 }
02888
02889 if (p->recstartts >= p->recendts)
02890 {
02891
02892 p->recstartts = p->startts;
02893 p->recendts = p->endts;
02894 }
02895
02896 p->schedulerid =
02897 p->startts.toString() + "_" + p->chanid;
02898
02899
02900
02901
02902
02903
02904 RecIter rec = worklist.begin();
02905 for ( ; rec != worklist.end(); rec++)
02906 {
02907 ProgramInfo *r = *rec;
02908 if (p->IsSameTimeslot(*r))
02909 {
02910 if (r->inputid == p->inputid &&
02911 r->recendts != p->recendts &&
02912 (r->recordid == p->recordid ||
02913 p->rectype == kOverrideRecord))
02914 ChangeRecordingEnd(r, p);
02915 delete p;
02916 p = NULL;
02917 break;
02918 }
02919 }
02920 if (p == NULL)
02921 continue;
02922
02923
02924 if ((threadrunning || specsched) && !cardMap.contains(p->cardid))
02925 p->recstatus = rsOffLine;
02926
02927
02928 if (checkTooMany && tooManyMap[p->recordid] && !p->reactivate)
02929 p->recstatus = rsTooManyRecordings;
02930
02931
02932 if (p->rectype == kDontRecord)
02933 p->recstatus = rsDontRecord;
02934 else if (result.value(15).toInt() && !p->reactivate)
02935 p->recstatus = rsPreviousRecording;
02936 else if (p->rectype != kSingleRecord &&
02937 p->rectype != kOverrideRecord &&
02938 !p->reactivate &&
02939 !(p->dupmethod & kDupCheckNone))
02940 {
02941 if ((p->dupin & kDupsNewEpi) && p->repeat)
02942 p->recstatus = rsRepeat;
02943
02944 if ((p->dupin & kDupsInOldRecorded) && result.value(10).toInt())
02945 {
02946 if (result.value(44).toInt() == rsNeverRecord)
02947 p->recstatus = rsNeverRecord;
02948 else
02949 p->recstatus = rsPreviousRecording;
02950 }
02951 if ((p->dupin & kDupsInRecorded) && result.value(14).toInt())
02952 p->recstatus = rsCurrentRecording;
02953 }
02954
02955 if (inactive)
02956 p->recstatus = rsInactive;
02957
02958
02959
02960
02961 if (p->recendts < schedTime)
02962 p->recstatus = rsMissed;
02963
02964 tmpList.push_back(p);
02965 }
02966
02967 VERBOSE(VB_SCHEDULE, " +-- Cleanup...");
02968 RecIter tmp = tmpList.begin();
02969 for ( ; tmp != tmpList.end(); tmp++)
02970 worklist.push_back(*tmp);
02971
02972 if (schedTmpRecord = "sched_temp_record")
02973 {
02974 result.prepare("DROP TABLE IF EXISTS sched_temp_record;");
02975 result.exec();
02976 }
02977
02978 result.prepare("DROP TABLE IF EXISTS sched_temp_recorded;");
02979 result.exec();
02980 }
02981
02982 void Scheduler::AddNotListed(void) {
02983
02984 struct timeval dbstart, dbend;
02985 RecList tmpList;
02986
02987 QString query = QString(
02988 "SELECT RECTABLE.recordid, RECTABLE.type, RECTABLE.chanid, "
02989 "RECTABLE.starttime, RECTABLE.startdate, RECTABLE.endtime, RECTABLE.enddate, "
02990 "RECTABLE.startoffset, RECTABLE.endoffset, "
02991 "RECTABLE.title, RECTABLE.subtitle, RECTABLE.description, "
02992 "channel.channum, channel.callsign, channel.name "
02993 "FROM RECTABLE "
02994 " INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
02995 " LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
02996 "WHERE (type = %1 OR type = %2 OR type = %3 OR type = %4) "
02997 " AND recordmatch.chanid IS NULL")
02998 .arg(kSingleRecord)
02999 .arg(kTimeslotRecord)
03000 .arg(kWeekslotRecord)
03001 .arg(kOverrideRecord);
03002
03003 while (1)
03004 {
03005 int i = query.find("RECTABLE");
03006 if (i == -1) break;
03007 query = query.replace(i, strlen("RECTABLE"), recordTable);
03008 }
03009
03010 VERBOSE(VB_SCHEDULE, QString(" |-- Start DB Query..."));
03011
03012 gettimeofday(&dbstart, NULL);
03013 MSqlQuery result(dbConn);
03014 result.prepare(query);
03015 result.exec();
03016 gettimeofday(&dbend, NULL);
03017
03018 if (!result.isActive())
03019 {
03020 MythContext::DBError("AddNotListed", result);
03021 return;
03022 }
03023
03024 VERBOSE(VB_SCHEDULE, QString(" |-- %1 results in %2 sec. Processing...")
03025 .arg(result.size())
03026 .arg(((dbend.tv_sec - dbstart.tv_sec) * 1000000 +
03027 (dbend.tv_usec - dbstart.tv_usec)) / 1000000.0));
03028
03029 QDateTime now = QDateTime::currentDateTime();
03030
03031 while (result.next())
03032 {
03033 ProgramInfo *p = new ProgramInfo;
03034 p->recstatus = rsNotListed;
03035 p->recordid = result.value(0).toInt();
03036 p->rectype = RecordingType(result.value(1).toInt());
03037 p->chanid = result.value(2).toString();
03038
03039 p->startts.setTime(result.value(3).toTime());
03040 p->startts.setDate(result.value(4).toDate());
03041 p->endts.setTime(result.value(5).toTime());
03042 p->endts.setDate(result.value(6).toDate());
03043
03044 if (p->rectype == kTimeslotRecord)
03045 {
03046 int days = p->startts.daysTo(now);
03047
03048 p->startts = p->startts.addDays(days);
03049 p->endts = p->endts.addDays(days);
03050
03051 if (p->endts < now)
03052 {
03053 p->startts = p->startts.addDays(1);
03054 p->endts = p->endts.addDays(1);
03055 }
03056 }
03057 else if (p->rectype == kWeekslotRecord)
03058 {
03059 int weeks = (p->startts.daysTo(now) + 6) / 7;
03060
03061 p->startts = p->startts.addDays(weeks * 7);
03062 p->endts = p->endts.addDays(weeks * 7);
03063
03064 if (p->endts < now)
03065 {
03066 p->startts = p->startts.addDays(7);
03067 p->endts = p->endts.addDays(7);
03068 }
03069 }
03070
03071 p->recstartts = p->startts.addSecs(result.value(7).toInt() * -60);
03072 p->recendts = p->endts.addSecs(result.value(8).toInt() * 60);
03073
03074 if (p->recstartts >= p->recendts)
03075 {
03076
03077 p->recstartts = p->startts;
03078 p->recendts = p->endts;
03079 }
03080
03081
03082 if (p->recendts < schedTime)
03083 {
03084 delete p;
03085 continue;
03086 }
03087
03088 p->title = QString::fromUtf8(result.value(9).toString());
03089
03090 if (p->rectype == kSingleRecord || p->rectype == kOverrideRecord)
03091 {
03092 p->subtitle = QString::fromUtf8(result.value(10).toString());
03093 p->description = QString::fromUtf8(result.value(11).toString());
03094 }
03095 p->chanstr = result.value(12).toString();
03096 p->chansign = QString::fromUtf8(result.value(13).toString());
03097 p->channame = QString::fromUtf8(result.value(14).toString());
03098
03099 p->schedulerid = p->startts.toString() + "_" + p->chanid;
03100
03101 if (p == NULL)
03102 continue;
03103
03104 tmpList.push_back(p);
03105 }
03106
03107 RecIter tmp = tmpList.begin();
03108 for ( ; tmp != tmpList.end(); tmp++)
03109 worklist.push_back(*tmp);
03110 }
03111
03112 void Scheduler::findAllScheduledPrograms(RecList &proglist)
03113 {
03114 QString temptime, tempdate;
03115 QString query = QString("SELECT RECTABLE.chanid, RECTABLE.starttime, "
03116 "RECTABLE.startdate, RECTABLE.endtime, RECTABLE.enddate, RECTABLE.title, "
03117 "RECTABLE.subtitle, RECTABLE.description, RECTABLE.recpriority, RECTABLE.type, "
03118 "channel.name, RECTABLE.recordid, RECTABLE.recgroup, RECTABLE.dupin, "
03119 "RECTABLE.dupmethod, channel.commmethod, channel.channum, RECTABLE.station, "
03120 "RECTABLE.seriesid, RECTABLE.programid, RECTABLE.category, RECTABLE.findid, "
03121 "RECTABLE.playgroup "
03122 "FROM RECTABLE "
03123 "LEFT JOIN channel ON channel.callsign = RECTABLE.station "
03124 "GROUP BY recordid "
03125 "ORDER BY title ASC;");
03126
03127 while (1)
03128 {
03129 int i = query.find("RECTABLE");
03130 if (i == -1) break;
03131 query = query.replace(i, strlen("RECTABLE"), recordTable);
03132 }
03133
03134 MSqlQuery result(MSqlQuery::InitCon());
03135 result.prepare(query);
03136 result.exec();
03137
03138 if (!result.isActive())
03139 {
03140 MythContext::DBError("findAllScheduledPrograms", result);
03141 return;
03142 }
03143 if (result.size() > 0)
03144 {
03145 while (result.next())
03146 {
03147 ProgramInfo *proginfo = new ProgramInfo;
03148 proginfo->chanid = result.value(0).toString();
03149 proginfo->rectype = RecordingType(result.value(9).toInt());
03150 proginfo->recordid = result.value(11).toInt();
03151
03152 if (proginfo->rectype == kSingleRecord ||
03153 proginfo->rectype == kDontRecord ||
03154 proginfo->rectype == kOverrideRecord ||
03155 proginfo->rectype == kTimeslotRecord ||
03156 proginfo->rectype == kWeekslotRecord)
03157 {
03158 proginfo->startts = QDateTime(result.value(2).toDate(),
03159 result.value(1).toTime());
03160 proginfo->endts = QDateTime(result.value(4).toDate(),
03161 result.value(3).toTime());
03162 }
03163 else
03164 {
03165
03166
03167 proginfo->startts = QDateTime::currentDateTime();
03168 proginfo->startts.setTime(QTime(0,0));
03169 proginfo->endts = QDateTime::currentDateTime();
03170 proginfo->endts.setTime(QTime(0,0));
03171 }
03172
03173 proginfo->title = QString::fromUtf8(result.value(5).toString());
03174 proginfo->subtitle =
03175 QString::fromUtf8(result.value(6).toString());
03176 proginfo->description =
03177 QString::fromUtf8(result.value(7).toString());
03178
03179 proginfo->recpriority = result.value(8).toInt();
03180 proginfo->channame =
03181 QString::fromUtf8(result.value(10).toString());
03182 if (proginfo->channame.isNull())
03183 proginfo->channame = "";
03184 proginfo->recgroup =
03185 QString::fromUtf8(result.value(12).toString());
03186 proginfo->playgroup =
03187 QString::fromUtf8(result.value(22).toString());
03188 proginfo->dupin = RecordingDupInType(result.value(13).toInt());
03189 proginfo->dupmethod =
03190 RecordingDupMethodType(result.value(14).toInt());
03191 proginfo->chancommfree = (result.value(15).toInt() == -2);
03192 proginfo->chanstr = result.value(16).toString();
03193 if (proginfo->chanstr.isNull())
03194 proginfo->chanstr = "";
03195 proginfo->chansign =
03196 QString::fromUtf8(result.value(17).toString());
03197 proginfo->seriesid = result.value(18).toString();
03198 proginfo->programid = result.value(19).toString();
03199 proginfo->category =
03200 QString::fromUtf8(result.value(20).toString());
03201 proginfo->findid = result.value(21).toInt();
03202
03203 proginfo->recstartts = proginfo->startts;
03204 proginfo->recendts = proginfo->endts;
03205
03206 proglist.push_back(proginfo);
03207 }
03208 }
03209 }
03210
03211
03212 static bool comp_dirpreference(FileSystemInfo *a, FileSystemInfo *b)
03213 {
03214
03215 if (a->isLocal && !b->isLocal)
03216 {
03217 if (a->weight <= b->weight)
03218 {
03219 return true;
03220 }
03221 }
03222 else if (a->isLocal == b->isLocal)
03223 {
03224 if (a->weight < b->weight)
03225 {
03226 return true;
03227 }
03228 else if (a->weight > b->weight)
03229 {
03230 return false;
03231 }
03232 else if (a->freeSpaceKB > b->freeSpaceKB)
03233 {
03234 return true;
03235 }
03236 }
03237 else if (!a->isLocal && b->isLocal)
03238 {
03239 if (a->weight < b->weight)
03240 {
03241 return true;
03242 }
03243 }
03244
03245 return false;
03246 }
03247
03248 void Scheduler::GetNextLiveTVDir(int cardid)
03249 {
03250 QMutexLocker lockit(reclist_lock);
03251
03252 ProgramInfo *pginfo = new ProgramInfo;
03253
03254 if (!pginfo)
03255 return;
03256
03257 EncoderLink *tv = (*m_tvList)[cardid];
03258
03259 if (tv->IsLocal())
03260 pginfo->hostname = gContext->GetHostName();
03261 else
03262 pginfo->hostname = tv->GetHostName();
03263
03264 pginfo->storagegroup = "LiveTV";
03265 pginfo->recstartts = mythCurrentDateTime();
03266 pginfo->recendts = pginfo->recstartts.addSecs(3600);
03267 pginfo->title = "LiveTV";
03268 pginfo->cardid = cardid;
03269
03270 int fsID = FillRecordingDir(pginfo, reclist);
03271 if (expirer)
03272 {
03273
03274 expirer->Update(cardid, fsID, true);
03275 }
03276
03277 VERBOSE(VB_FILE, LOC + QString("FindNextLiveTVDir: next dir is '%1'")
03278 .arg(pginfo->pathname));
03279
03280 tv->SetNextLiveTVDir(pginfo->pathname);
03281
03282 delete pginfo;
03283 }
03284
03285 int Scheduler::FillRecordingDir(ProgramInfo *pginfo, RecList& reclist)
03286 {
03287
03288 VERBOSE(VB_SCHEDULE, LOC + "FillRecordingDir: Starting");
03289
03290 int fsID = -1;
03291 MSqlQuery query(MSqlQuery::InitCon());
03292 QMap<QString, FileSystemInfo>::Iterator fsit;
03293 QMap<QString, FileSystemInfo>::Iterator fsit2;
03294 QString dirKey;
03295 QStringList strlist;
03296 ProgramInfo *thispg;
03297 RecIter recIter;
03298 StorageGroup mysgroup(pginfo->storagegroup, pginfo->hostname);
03299 QStringList dirlist = mysgroup.GetDirList();
03300 QStringList recsCounted;
03301 list<FileSystemInfo *> fsInfoList;
03302 list<FileSystemInfo *>::iterator fslistit;
03303
03304 if (dirlist.size() == 1)
03305 {
03306 VERBOSE(VB_FILE|VB_SCHEDULE, LOC + QString("FillRecordingDir: The only "
03307 "directory in the %1 Storage Group is %2, so it will be used "
03308 "by default.")
03309 .arg(pginfo->storagegroup)
03310 .arg(dirlist[0]));
03311 pginfo->pathname = dirlist[0];
03312 VERBOSE(VB_SCHEDULE, LOC + "FillRecordingDir: Finished");
03313
03314 return -1;
03315 }
03316
03317 int weightPerRecording =
03318 gContext->GetNumSetting("SGweightPerRecording", 10);
03319 int weightPerPlayback =
03320 gContext->GetNumSetting("SGweightPerPlayback", 5);
03321 int weightPerCommFlag =
03322 gContext->GetNumSetting("SGweightPerCommFlag", 5);
03323 int weightPerTranscode =
03324 gContext->GetNumSetting("SGweightPerTranscode", 5);
03325 int localStartingWeight =
03326 gContext->GetNumSetting("SGweightLocalStarting",
03327 (int)(-1.99 * weightPerRecording));
03328 int maxOverlap = gContext->GetNumSetting("SGmaxRecOverlapMins", 3) * 60;
03329
03330 FillDirectoryInfoCache();
03331
03332 VERBOSE(VB_FILE|VB_SCHEDULE, LOC +
03333 "FillRecordingDir: Calculating initial FS Weights.");
03334
03335 for (fsit = fsInfoCache.begin(); fsit != fsInfoCache.end(); fsit++)
03336 {
03337 FileSystemInfo *fs = &(fsit.data());
03338 int tmpWeight = 0;
03339
03340 QString msg = QString(" %1:%2").arg(fs->hostname)
03341 .arg(fs->directory);
03342
03343 if (fs->isLocal)
03344 {
03345 tmpWeight = localStartingWeight;
03346 msg += " is local (" + QString::number(tmpWeight) + ")";
03347 }
03348 else
03349 {
03350 tmpWeight = 0;
03351 msg += " is remote (+" + QString::number(tmpWeight) + ")";
03352 }
03353
03354 fs->weight = tmpWeight;
03355
03356 tmpWeight = gContext->GetNumSetting(QString("SGweightPerDir:%1:%2")
03357 .arg(fs->hostname).arg(fs->directory), 0);
03358 fs->weight += tmpWeight;
03359
03360 if (tmpWeight)
03361 msg += ", has SGweightPerDir offset of "
03362 + QString::number(tmpWeight) + ")";
03363
03364 msg += ". initial dir weight = " + QString::number(fs->weight);
03365 VERBOSE(VB_FILE|VB_SCHEDULE, msg);
03366
03367 fsInfoList.push_back(fs);
03368 }
03369
03370 VERBOSE(VB_FILE|VB_SCHEDULE, LOC +
03371 "FillRecordingDir: Adjusting FS Weights from inuseprograms.");
03372
03373 query.prepare("SELECT i.chanid, i.starttime, r.endtime, recusage, "
03374 "rechost, recdir "
03375 "FROM inuseprograms i, recorded r "
03376 "WHERE DATE_ADD(lastupdatetime, INTERVAL 16 MINUTE) > NOW() "
03377 "AND recdir <> '' "
03378 "AND i.chanid = r.chanid "
03379 "AND i.starttime = r.starttime;");
03380 if (!query.exec() || !query.isActive())
03381 MythContext::DBError(LOC + "FillRecordingDir", query);
03382 else
03383 {
03384 int recChanid;
03385 QDateTime recStart;
03386 QDateTime recEnd;
03387 QString recUsage;
03388 QString recHost;
03389 QString recDir;
03390
03391 while (query.next())
03392 {
03393 recChanid = query.value(0).toInt();
03394 recStart = query.value(1).toDateTime();
03395 recEnd = query.value(2).toDateTime();
03396 recUsage = query.value(3).toString();
03397 recHost = query.value(4).toString();
03398 recDir = query.value(5).toString();
03399
03400 for (fslistit = fsInfoList.begin();
03401 fslistit != fsInfoList.end(); fslistit++)
03402 {
03403 FileSystemInfo *fs = *fslistit;
03404 if ((recHost == fs->hostname) &&
03405 (recDir == fs->directory))
03406 {
03407 int weightOffset = 0;
03408
03409 if (recUsage == "recorder")
03410 {
03411 if (recEnd > pginfo->recstartts.addSecs(maxOverlap))
03412 {
03413 weightOffset += weightPerRecording;
03414 recsCounted << QString::number(recChanid) + ":" +
03415 recStart.toString(Qt::ISODate);
03416 }
03417 }
03418 else if (recUsage == "player")
03419 weightOffset += weightPerPlayback;
03420 else if (recUsage == "flagger")
03421 weightOffset += weightPerCommFlag;
03422 else if (recUsage == "transcoder")
03423 weightOffset += weightPerTranscode;
03424
03425 if (weightOffset)
03426 {
03427 VERBOSE(VB_FILE|VB_SCHEDULE, QString(
03428 " %1 @ %2 in use by '%3' on %4:%5, FSID #%6, "
03429 "FSID weightOffset +%7.")
03430 .arg(recChanid)
03431 .arg(recStart.toString(Qt::ISODate))
03432 .arg(recUsage).arg(recHost).arg(recDir)
03433 .arg(fs->fsID).arg(weightOffset));
03434
03435
03436 for (fsit2 = fsInfoCache.begin();
03437 fsit2 != fsInfoCache.end(); fsit2++)
03438 {
03439 FileSystemInfo *fs2 = &(fsit2.data());
03440 if (fs2->fsID == fs->fsID)
03441 {
03442 VERBOSE(VB_FILE|VB_SCHEDULE, QString(" "
03443 "%1:%2 => old weight %3 plus %4 = %5")
03444 .arg(fs2->hostname).arg(fs2->directory)
03445 .arg(fs2->weight).arg(weightOffset)
03446 .arg(fs2->weight + weightOffset));
03447
03448 fs2->weight += weightOffset;
03449 }
03450 }
03451 }
03452 break;
03453 }
03454 }
03455 }
03456 }
03457
03458 VERBOSE(VB_FILE|VB_SCHEDULE, LOC +
03459 "FillRecordingDir: Adjusting FS Weights from scheduler.");
03460
03461 for (recIter = reclist.begin(); recIter != reclist.end(); recIter++)
03462 {
03463 thispg = *recIter;
03464
03465 if ((pginfo->recendts < thispg->recstartts) ||
03466 (pginfo->recstartts > thispg->recendts) ||
03467 (thispg->recstatus != rsWillRecord) ||
03468 (thispg->cardid == 0) ||
03469 (recsCounted.contains(thispg->chanid + ":" +
03470 thispg->recstartts.toString(Qt::ISODate))) ||
03471 (thispg->pathname == ""))
03472 continue;
03473
03474 if (thispg->pathname != "")
03475 {
03476 for (fslistit = fsInfoList.begin();
03477 fslistit != fsInfoList.end(); fslistit++)
03478 {
03479 FileSystemInfo *fs = *fslistit;
03480 if ((fs->hostname == thispg->hostname) &&
03481 (fs->directory == thispg->pathname))
03482 {
03483 VERBOSE(VB_FILE|VB_SCHEDULE, QString(
03484 "%1 @ %2 will record on %3:%4, FSID #%5, "
03485 "weightPerRecording +%6.")
03486 .arg(thispg->chanid)
03487 .arg(thispg->recstartts.toString(Qt::ISODate))
03488 .arg(fs->hostname).arg(fs->directory)
03489 .arg(fs->fsID).arg(weightPerRecording));
03490
03491 for (fsit2 = fsInfoCache.begin();
03492 fsit2 != fsInfoCache.end(); fsit2++)
03493 {
03494 FileSystemInfo *fs2 = &(fsit2.data());
03495 if (fs2->fsID == fs->fsID)
03496 {
03497 VERBOSE(VB_FILE|VB_SCHEDULE, QString(" "
03498 "%1:%2 => old weight %3 plus %4 = %5")
03499 .arg(fs2->hostname).arg(fs2->directory)
03500 .arg(fs2->weight).arg(weightPerRecording)
03501 .arg(fs2->weight + weightPerRecording));
03502
03503 fs2->weight += weightPerRecording;
03504 }
03505 }
03506 break;
03507 }
03508 }
03509 }
03510 }
03511
03512 fsInfoList.sort(comp_dirpreference);
03513
03514 if (print_verbose_messages & (VB_FILE|VB_SCHEDULE))
03515 {
03516 cout << "--- FillRecordingDir Sorted fsInfoList start ---\n";
03517 for (fslistit = fsInfoList.begin();fslistit != fsInfoList.end();
03518 fslistit++)
03519 {
03520 FileSystemInfo *fs = *fslistit;
03521 cout << fs->hostname << ":" << fs->directory << endl;
03522 cout << " Location : ";
03523 if (fs->isLocal)
03524 cout << "local" << endl;
03525 else
03526 cout << "remote" << endl;
03527 cout << " weight : " << fs->weight << endl;
03528 cout << " free space : " << fs->freeSpaceKB << endl;
03529 cout << endl;
03530 }
03531 cout << "--- FillRecordingDir Sorted fsInfoList end ---\n";
03532 }
03533
03534
03535
03536 EncoderLink *nexttv = (*m_tvList)[pginfo->cardid];
03537 long long maxByterate = nexttv->GetMaxBitrate() / 8;
03538 long long maxSizeKB = maxByterate *
03539 pginfo->recstartts.secsTo(pginfo->recendts) / 1024;
03540
03541
03542
03543
03544
03545
03546 for (unsigned int pass = 1; pass <= 2; pass++)
03547 {
03548 bool foundDir = false;
03549 for (fslistit = fsInfoList.begin();
03550 fslistit != fsInfoList.end(); fslistit++)
03551 {
03552 long long desiredSpaceKB = 0;
03553 FileSystemInfo *fs = *fslistit;
03554 if (expirer)
03555 desiredSpaceKB = expirer->GetDesiredSpace(fs->fsID);
03556
03557 if ((fs->hostname == pginfo->hostname) &&
03558 (dirlist.contains(fs->directory)) &&
03559 ((pass == 2) ||
03560 (fs->freeSpaceKB > (desiredSpaceKB + maxSizeKB))))
03561 {
03562 pginfo->pathname = fs->directory;
03563 fsID = fs->fsID;
03564
03565 if (pass == 1)
03566 VERBOSE(VB_FILE, QString("'%1' will record in '%2' which "
03567 "has %3 MiB free. This recording could use a max "
03568 "of %4 MiB and the AutoExpirer wants to keep %5 "
03569 "MiB free.")
03570 .arg(pginfo->title).arg(pginfo->pathname)
03571 .arg(fs->freeSpaceKB / 1024).arg(maxSizeKB / 1024)
03572 .arg(desiredSpaceKB / 1024));
03573 else
03574 VERBOSE(VB_FILE, QString("'%1' will record in '%2' "
03575 "although there is only %3 MiB free and the "
03576 "AutoExpirer wants at least %4 MiB. Something "
03577 "will have to be deleted or expired in order for "
03578 "this recording to complete successfully.")
03579 .arg(pginfo->title).arg(pginfo->pathname)
03580 .arg(fs->freeSpaceKB / 1024)
03581 .arg(desiredSpaceKB / 1024));
03582
03583 foundDir = true;
03584 break;
03585 }
03586 }
03587
03588 if (foundDir)
03589 break;
03590 }
03591
03592 VERBOSE(VB_SCHEDULE, LOC + "FillRecordingDir: Finished");
03593 return fsID;
03594 }
03595
03596 void Scheduler::FillDirectoryInfoCache(bool force)
03597 {
03598 if ((!force) &&
03599 (fsInfoCacheFillTime > QDateTime::currentDateTime().addSecs(-180)))
03600 return;
03601
03602 vector<FileSystemInfo> fsInfos;
03603
03604 fsInfoCache.clear();
03605
03606 GetFilesystemInfos(m_tvList, fsInfos);
03607
03608 QMap <int, bool> fsMap;
03609 vector<FileSystemInfo>::iterator it1;
03610 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); it1++)
03611 {
03612 fsMap[it1->fsID] = true;
03613 fsInfoCache[it1->hostname + ":" + it1->directory] = *it1;
03614 }
03615
03616 VERBOSE(VB_FILE, LOC + QString("FillDirectoryInfoCache: found %1 unique "
03617 "filesystems").arg(fsMap.size()));
03618
03619 fsInfoCacheFillTime = QDateTime::currentDateTime();
03620 }
03621
03622 void Scheduler::SchedPreserveLiveTV(void)
03623 {
03624 if (!livetvTime.isValid())
03625 return;
03626
03627 if (livetvTime < schedTime)
03628 {
03629 livetvTime = QDateTime();
03630 return;
03631 }
03632
03633 livetvpriority = gContext->GetNumSetting("LiveTVPriority", 0);
03634
03635
03636 QMap<int, EncoderLink *>::Iterator enciter = m_tvList->begin();
03637 for (; enciter != m_tvList->end(); ++enciter)
03638 {
03639 EncoderLink *enc = enciter.data();
03640
03641 if (kState_WatchingLiveTV != enc->GetState())
03642 continue;
03643
03644 TunedInputInfo in;
03645 enc->IsBusy(&in);
03646
03647 if (!in.inputid)
03648 continue;
03649
03650
03651
03652 ProgramInfo *dummy =
03653 dummy->GetProgramAtDateTime(QString::number(in.chanid),
03654 livetvTime, true, 4);
03655 if (!dummy)
03656 continue;
03657
03658 dummy->cardid = enc->GetCardID();
03659 dummy->inputid = in.inputid;
03660 dummy->recstatus = rsUnknown;
03661
03662 retrylist.push_front(dummy);
03663 }
03664
03665 if (!retrylist.size())
03666 return;
03667
03668 MoveHigherRecords(false);
03669
03670 while (retrylist.size() > 0)
03671 {
03672 ProgramInfo *p = retrylist.back();
03673 delete p;
03674 retrylist.pop_back();
03675 }
03676 }
03677
03678
03679 bool Scheduler::WasStartedAutomatically()
03680 {
03681 bool autoStart = false;
03682
03683 QDateTime startupTime = QDateTime();
03684 QString s = gContext->GetSetting("MythShutdownWakeupTime", "");
03685 if (s != "")
03686 startupTime = QDateTime::fromString(s, Qt::ISODate);
03687
03688
03689 if (startupTime.isValid())
03690 {
03691
03692
03693
03694 if (abs(startupTime.secsTo(QDateTime::currentDateTime())) < (15 * 60))
03695 {
03696 VERBOSE(VB_SCHEDULE,
03697 "Close to auto-start time, AUTO-Startup assumed");
03698 autoStart = true;
03699 }
03700 }
03701
03702 return autoStart;
03703 }
03704
03705