MythTV  0.26-pre
programinfo.cpp
Go to the documentation of this file.
00001 // -*- Mode: c++ -*-
00002 
00003 // POSIX headers
00004 #include <sys/types.h>
00005 #include <unistd.h>
00006 
00007 // C headers
00008 #include <cstdlib>
00009 
00010 // C++ headers
00011 #include <iostream>
00012 #include <algorithm>
00013 using namespace std;
00014 
00015 // Qt headers
00016 #include <QRegExp>
00017 #include <QMap>
00018 #include <QUrl>
00019 #include <QFile>
00020 #include <QFileInfo>
00021 #include <QDir>
00022 
00023 // MythTV headers
00024 #include "programinfo.h"
00025 #include "mythmiscutil.h"
00026 #include "mythcorecontext.h"
00027 #include "dialogbox.h"
00028 #include "remoteutil.h"
00029 #include "netutils.h"
00030 #include "mythdb.h"
00031 #include "mythlogging.h"
00032 #include "storagegroup.h"
00033 #include "programinfoupdater.h"
00034 #include "mythscheduler.h"
00035 #include "remotefile.h"
00036 
00037 #define LOC      QString("ProgramInfo(%1): ").arg(GetBasename())
00038 
00039 //#define DEBUG_IN_USE
00040 
00041 static int init_tr(void);
00042 
00043 int pginfo_init_statics() { return ProgramInfo::InitStatics(); }
00044 QMutex ProgramInfo::staticDataLock;
00045 ProgramInfoUpdater *ProgramInfo::updater;
00046 int dummy = pginfo_init_statics();
00047 bool ProgramInfo::usingProgIDAuth = true;
00048 
00049 
00050 const QString ProgramInfo::kFromRecordedQuery =
00051     "SELECT r.title,            r.subtitle,     r.description,     "// 0-2
00052     "       r.season,           r.episode,      r.category,        "// 3-5
00053     "       r.chanid,           c.channum,      c.callsign,        "// 6-8
00054     "       c.name,             c.outputfilters,r.recgroup,        "// 9-11
00055     "       r.playgroup,        r.storagegroup, r.basename,        "//12-14
00056     "       r.hostname,         r.recpriority,  r.seriesid,        "//15-17
00057     "       r.programid,        r.inetref,      r.filesize,        "//18-20
00058     "       r.progstart,        r.progend,      r.stars,           "//21-23
00059     "       r.starttime,        r.endtime,      p.airdate+0,       "//24-26
00060     "       r.originalairdate,  r.lastmodified, r.recordid,        "//27-29
00061     "       c.commmethod,       r.commflagged,  r.previouslyshown, "//30-32
00062     "       r.transcoder,       r.transcoded,   r.deletepending,   "//33-35
00063     "       r.preserve,         r.cutlist,      r.autoexpire,      "//36-38
00064     "       r.editing,          r.bookmark,     r.watched,         "//39-41
00065     "       p.audioprop+0,      p.videoprop+0,  p.subtitletypes+0, "//42-44
00066     "       r.findid,           rec.dupin,      rec.dupmethod      "//45-47
00067     "FROM recorded AS r "
00068     "LEFT JOIN channel AS c "
00069     "ON (r.chanid    = c.chanid) "
00070     "LEFT JOIN recordedprogram AS p "
00071     "ON (r.chanid    = p.chanid AND "
00072     "    r.progstart = p.starttime) "
00073     "LEFT JOIN record AS rec "
00074     "ON (r.recordid = rec.recordid) ";
00075 
00076 static void set_flag(uint32_t &flags, int flag_to_set, bool is_set)
00077 {
00078     flags &= ~flag_to_set;
00079     if (is_set)
00080         flags |= flag_to_set;
00081 }
00082 
00086 ProgramInfo::ProgramInfo(void) :
00087     title(),
00088     subtitle(),
00089     description(),
00090     season(0),
00091     episode(0),
00092     category(),
00093 
00094     recpriority(0),
00095 
00096     chanid(0),
00097     chanstr(),
00098     chansign(),
00099     channame(),
00100     chanplaybackfilters(),
00101 
00102     recgroup("Default"),
00103     playgroup("Default"),
00104 
00105     pathname(),
00106 
00107     hostname(),
00108     storagegroup("Default"),
00109 
00110     seriesid(),
00111     programid(),
00112     inetref(),
00113     catType(),
00114 
00115 
00116     filesize(0ULL),
00117 
00118     startts(mythCurrentDateTime()),
00119     endts(startts),
00120     recstartts(startts),
00121     recendts(startts),
00122 
00123     stars(0.0f),
00124 
00125     originalAirDate(),
00126     lastmodified(startts),
00127     lastInUseTime(startts.addSecs(-4 * 60 * 60)),
00128 
00129     prefinput(0),
00130     recpriority2(0),
00131     recordid(0),
00132     parentid(0),
00133 
00134     sourceid(0),
00135     inputid(0),
00136     cardid(0),
00137 
00138     findid(0),
00139 
00140     programflags(FL_NONE),
00141     properties(0),
00142     year(0),
00143 
00144     recstatus(rsUnknown),
00145     oldrecstatus(rsUnknown),
00146     rectype(kNotRecording),
00147     dupin(kDupsInAll),
00148     dupmethod(kDupCheckSubDesc),
00149 
00150     // everything below this line is not serialized
00151     availableStatus(asAvailable),
00152     spread(-1),
00153     startCol(-1),
00154     sortTitle(),
00155 
00156     // Private
00157     inUseForWhat(),
00158     positionMapDBReplacement(NULL)
00159 {
00160 }
00161 
00165 ProgramInfo::ProgramInfo(const ProgramInfo &other) :
00166     title(other.title),
00167     subtitle(other.subtitle),
00168     description(other.description),
00169     season(other.season),
00170     episode(other.episode),
00171     category(other.category),
00172 
00173     recpriority(other.recpriority),
00174 
00175     chanid(other.chanid),
00176     chanstr(other.chanstr),
00177     chansign(other.chansign),
00178     channame(other.channame),
00179     chanplaybackfilters(other.chanplaybackfilters),
00180 
00181     recgroup(other.recgroup),
00182     playgroup(other.playgroup),
00183 
00184     pathname(other.pathname),
00185 
00186     hostname(other.hostname),
00187     storagegroup(other.storagegroup),
00188 
00189     seriesid(other.seriesid),
00190     programid(other.programid),
00191     inetref(other.inetref),
00192     catType(other.catType),
00193 
00194     filesize(other.filesize),
00195 
00196     startts(other.startts),
00197     endts(other.endts),
00198     recstartts(other.recstartts),
00199     recendts(other.recendts),
00200 
00201     stars(other.stars),
00202 
00203     originalAirDate(other.originalAirDate),
00204     lastmodified(other.lastmodified),
00205     lastInUseTime(QDateTime::currentDateTime().addSecs(-4 * 60 * 60)),
00206 
00207     prefinput(other.prefinput),
00208     recpriority2(other.recpriority2),
00209     recordid(other.recordid),
00210     parentid(other.parentid),
00211 
00212     sourceid(other.sourceid),
00213     inputid(other.inputid),
00214     cardid(other.cardid),
00215 
00216     findid(other.findid),
00217     programflags(other.programflags),
00218     properties(other.properties),
00219     year(other.year),
00220 
00221     recstatus(other.recstatus),
00222     oldrecstatus(other.oldrecstatus),
00223     rectype(other.rectype),
00224     dupin(other.dupin),
00225     dupmethod(other.dupmethod),
00226 
00227     // everything below this line is not serialized
00228     availableStatus(other.availableStatus),
00229     spread(other.spread),
00230     startCol(other.startCol),
00231     sortTitle(other.sortTitle),
00232 
00233     // Private
00234     inUseForWhat(),
00235     positionMapDBReplacement(other.positionMapDBReplacement)
00236 {
00237 }
00238 
00239 ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts) :
00240     chanid(0),
00241     positionMapDBReplacement(NULL)
00242 {
00243     LoadProgramFromRecorded(_chanid, _recstartts);
00244 }
00245 
00246 ProgramInfo::ProgramInfo(
00247     const QString &_title,
00248     const QString &_subtitle,
00249     const QString &_description,
00250     uint  _season,
00251     uint  _episode,
00252     const QString &_category,
00253 
00254     uint _chanid,
00255     const QString &_channum,
00256     const QString &_chansign,
00257     const QString &_channame,
00258     const QString &_chanplaybackfilters,
00259 
00260     const QString &_recgroup,
00261     const QString &_playgroup,
00262 
00263     const QString &_pathname,
00264 
00265     const QString &_hostname,
00266     const QString &_storagegroup,
00267 
00268     const QString &_seriesid,
00269     const QString &_programid,
00270     const QString &_inetref,
00271 
00272     int _recpriority,
00273 
00274     uint64_t _filesize,
00275 
00276     const QDateTime &_startts,
00277     const QDateTime &_endts,
00278     const QDateTime &_recstartts,
00279     const QDateTime &_recendts,
00280 
00281     float _stars,
00282 
00283     uint _year,
00284     const QDate &_originalAirDate,
00285     const QDateTime &_lastmodified,
00286 
00287     RecStatusType _recstatus,
00288 
00289     uint _recordid,
00290 
00291     RecordingDupInType _dupin,
00292     RecordingDupMethodType _dupmethod,
00293 
00294     uint _findid,
00295 
00296     uint _programflags,
00297     uint _audioproperties,
00298     uint _videoproperties,
00299     uint _subtitleType) :
00300     title(_title),
00301     subtitle(_subtitle),
00302     description(_description),
00303     season(_season),
00304     episode(_episode),
00305     category(_category),
00306 
00307     recpriority(_recpriority),
00308 
00309     chanid(_chanid),
00310     chanstr(_channum),
00311     chansign(_chansign),
00312     channame(_channame),
00313     chanplaybackfilters(_chanplaybackfilters),
00314 
00315     recgroup(_recgroup),
00316     playgroup(_playgroup),
00317 
00318     pathname(_pathname),
00319 
00320     hostname(_hostname),
00321     storagegroup(_storagegroup),
00322 
00323     seriesid(_seriesid),
00324     programid(_programid),
00325     inetref(_inetref),
00326     catType(),
00327 
00328     filesize(_filesize),
00329 
00330     startts(_startts),
00331     endts(_endts),
00332     recstartts(_recstartts),
00333     recendts(_recendts),
00334 
00335     stars(clamp(_stars, 0.0f, 1.0f)),
00336 
00337     originalAirDate(_originalAirDate),
00338     lastmodified(_lastmodified),
00339     lastInUseTime(QDateTime::currentDateTime().addSecs(-4 * 60 * 60)),
00340 
00341     prefinput(0),
00342     recpriority2(0),
00343     recordid(_recordid),
00344     parentid(0),
00345 
00346     sourceid(0),
00347     inputid(0),
00348     cardid(0),
00349 
00350     findid(_findid),
00351 
00352     programflags(_programflags),
00353     properties((_subtitleType    << kSubtitlePropertyOffset) |
00354                (_videoproperties << kVideoPropertyOffset)    |
00355                (_audioproperties << kAudioPropertyOffset)),
00356     year(_year),
00357 
00358     recstatus(_recstatus),
00359     oldrecstatus(rsUnknown),
00360     rectype(kNotRecording),
00361     dupin(_dupin),
00362     dupmethod(_dupmethod),
00363 
00364     // everything below this line is not serialized
00365     availableStatus(asAvailable),
00366     spread(-1),
00367     startCol(-1),
00368     sortTitle(),
00369 
00370     // Private
00371     inUseForWhat(),
00372     positionMapDBReplacement(NULL)
00373 {
00374     if (originalAirDate.isValid() && originalAirDate < QDate(1940, 1, 1))
00375         originalAirDate = QDate();
00376 
00377     SetPathname(_pathname);
00378 }
00379 
00380 ProgramInfo::ProgramInfo(
00381     const QString &_title,
00382     const QString &_subtitle,
00383     const QString &_description,
00384     uint  _season,
00385     uint  _episode,
00386     const QString &_category,
00387 
00388     uint _chanid,
00389     const QString &_channum,
00390     const QString &_chansign,
00391     const QString &_channame,
00392 
00393     const QString &_seriesid,
00394     const QString &_programid,
00395     const QString &_inetref,
00396 
00397     const QDateTime &_startts,
00398     const QDateTime &_endts,
00399     const QDateTime &_recstartts,
00400     const QDateTime &_recendts,
00401 
00402     RecStatusType _recstatus,
00403 
00404     uint _recordid,
00405 
00406     RecordingType _rectype,
00407 
00408     uint _findid,
00409 
00410     bool duplicate) :
00411     title(_title),
00412     subtitle(_subtitle),
00413     description(_description),
00414     season(_season),
00415     episode(_episode),
00416     category(_category),
00417 
00418     recpriority(0),
00419 
00420     chanid(_chanid),
00421     chanstr(_channum),
00422     chansign(_chansign),
00423     channame(_channame),
00424     chanplaybackfilters(),
00425 
00426     recgroup("Default"),
00427     playgroup("Default"),
00428 
00429     pathname(),
00430 
00431     hostname(),
00432     storagegroup("Default"),
00433 
00434     seriesid(_seriesid),
00435     programid(_programid),
00436     inetref(_inetref),
00437     catType(),
00438 
00439     filesize(0ULL),
00440 
00441     startts(_startts),
00442     endts(_endts),
00443     recstartts(_recstartts),
00444     recendts(_recendts),
00445 
00446     stars(0.0f),
00447 
00448     originalAirDate(),
00449     lastmodified(startts),
00450     lastInUseTime(QDateTime::currentDateTime().addSecs(-4 * 60 * 60)),
00451 
00452     prefinput(0),
00453     recpriority2(0),
00454     recordid(_recordid),
00455     parentid(0),
00456 
00457     sourceid(0),
00458     inputid(0),
00459     cardid(0),
00460 
00461     findid(_findid),
00462 
00463     programflags((duplicate) ? FL_DUPLICATE : 0),
00464     properties(0),
00465     year(0),
00466 
00467     recstatus(_recstatus),
00468     oldrecstatus(rsUnknown),
00469     rectype(_rectype),
00470     dupin(kDupsInAll),
00471     dupmethod(kDupCheckSubDesc),
00472 
00473     // everything below this line is not serialized
00474     availableStatus(asAvailable),
00475     spread(-1),
00476     startCol(-1),
00477     sortTitle(),
00478 
00479     // Private
00480     inUseForWhat(),
00481     positionMapDBReplacement(NULL)
00482 {
00483 }
00484 
00485 ProgramInfo::ProgramInfo(
00486     const QString &_title,
00487     const QString &_subtitle,
00488     const QString &_description,
00489     const QString &_category,
00490 
00491     uint _chanid,
00492     const QString &_channum,
00493     const QString &_chansign,
00494     const QString &_channame,
00495     const QString &_chanplaybackfilters,
00496 
00497     const QDateTime &_startts,
00498     const QDateTime &_endts,
00499     const QDateTime &_recstartts,
00500     const QDateTime &_recendts,
00501 
00502     const QString &_seriesid,
00503     const QString &_programid,
00504     const QString &_catType,
00505 
00506     float _stars,
00507     uint _year,
00508     const QDate &_originalAirDate,
00509     RecStatusType _recstatus,
00510     uint _recordid,
00511     RecordingType _rectype,
00512     uint _findid,
00513 
00514     bool commfree,
00515     bool repeat,
00516 
00517     uint _videoproperties,
00518     uint _audioproperties,
00519     uint _subtitleType,
00520 
00521     const ProgramList &schedList) :
00522     title(_title),
00523     subtitle(_subtitle),
00524     description(_description),
00525     season(0),
00526     episode(0),
00527     category(_category),
00528 
00529     recpriority(0),
00530 
00531     chanid(_chanid),
00532     chanstr(_channum),
00533     chansign(_chansign),
00534     channame(_channame),
00535     chanplaybackfilters(_chanplaybackfilters),
00536 
00537     recgroup("Default"),
00538     playgroup("Default"),
00539 
00540     pathname(),
00541 
00542     hostname(),
00543     storagegroup("Default"),
00544 
00545     seriesid(_seriesid),
00546     programid(_programid),
00547     inetref(),
00548     catType(_catType),
00549 
00550     filesize(0ULL),
00551 
00552     startts(_startts),
00553     endts(_endts),
00554     recstartts(_recstartts),
00555     recendts(_recendts),
00556 
00557     stars(clamp(_stars, 0.0f, 1.0f)),
00558 
00559     originalAirDate(_originalAirDate),
00560     lastmodified(startts),
00561     lastInUseTime(startts.addSecs(-4 * 60 * 60)),
00562 
00563     prefinput(0),
00564     recpriority2(0),
00565     recordid(_recordid),
00566     parentid(0),
00567 
00568     sourceid(0),
00569     inputid(0),
00570     cardid(0),
00571 
00572     findid(_findid),
00573 
00574     programflags(FL_NONE),
00575     properties((_subtitleType    << kSubtitlePropertyOffset) |
00576                (_videoproperties << kVideoPropertyOffset)    |
00577                (_audioproperties << kAudioPropertyOffset)),
00578     year(_year),
00579 
00580     recstatus(_recstatus),
00581     oldrecstatus(rsUnknown),
00582     rectype(_rectype),
00583     dupin(kDupsInAll),
00584     dupmethod(kDupCheckSubDesc),
00585 
00586     // everything below this line is not serialized
00587     availableStatus(asAvailable),
00588     spread(-1),
00589     startCol(-1),
00590     sortTitle(),
00591 
00592     // Private
00593     inUseForWhat(),
00594     positionMapDBReplacement(NULL)
00595 {
00596     programflags |= (commfree) ? FL_CHANCOMMFREE : 0;
00597     programflags |= (repeat)   ? FL_REPEAT       : 0;
00598 
00599     if (originalAirDate.isValid() && originalAirDate < QDate(1940, 1, 1))
00600         originalAirDate = QDate();
00601 
00602     ProgramList::const_iterator it = schedList.begin();
00603     for (; it != schedList.end(); ++it)
00604     {
00605         if (!IsSameTimeslot(**it))
00606             continue;
00607 
00608         const ProgramInfo &s = **it;
00609         recordid    = s.recordid;
00610         recstatus   = s.recstatus;
00611         rectype     = s.rectype;
00612         recpriority = s.recpriority;
00613         recstartts  = s.recstartts;
00614         recendts    = s.recendts;
00615         cardid      = s.cardid;
00616         inputid     = s.inputid;
00617         dupin       = s.dupin;
00618         dupmethod   = s.dupmethod;
00619         findid      = s.findid;
00620 
00621         if (chanstr != s.chanstr)
00622         {
00623             if (s.recstatus == rsWillRecord)
00624                 recstatus = rsOtherShowing;
00625             else if (s.recstatus == rsRecording)
00626                 recstatus = rsOtherRecording;
00627             else if (s.recstatus == rsTuning)
00628                 recstatus = rsOtherTuning;
00629         }
00630 
00631         // Stop recording keys on chanid (and recstarts).  If the
00632         // recording is running, override the chanid, so we can stop
00633         // it from any matching program.  Remove this hack when we
00634         // replace chanid/recstartts as the primary key for the
00635         // recorded and related tables with something better.
00636         if (s.recstatus == rsRecording ||
00637             s.recstatus == rsTuning)
00638         {
00639             chanid = s.chanid;
00640         }
00641     }
00642 }
00643 
00644 ProgramInfo::ProgramInfo(
00645     const QString &_title,
00646     const QString &_subtitle,
00647     const QString &_description,
00648     uint  _season,
00649     uint  _episode,
00650     const QString &_category,
00651 
00652     uint _chanid,
00653     const QString &_channum,
00654     const QString &_chansign,
00655     const QString &_channame,
00656     const QString &_chanplaybackfilters,
00657 
00658     const QString &_recgroup,
00659     const QString &_playgroup,
00660 
00661     const QDateTime &_startts,
00662     const QDateTime &_endts,
00663     const QDateTime &_recstartts,
00664     const QDateTime &_recendts,
00665 
00666     const QString &_seriesid,
00667     const QString &_programid,
00668     const QString &_inetref) :
00669     title(_title),
00670     subtitle(_subtitle),
00671     description(_description),
00672     season(_season),
00673     episode(_episode),
00674     category(_category),
00675 
00676     recpriority(0),
00677 
00678     chanid(_chanid),
00679     chanstr(_channum),
00680     chansign(_chansign),
00681     channame(_channame),
00682     chanplaybackfilters(_chanplaybackfilters),
00683 
00684     recgroup(_recgroup),
00685     playgroup(_playgroup),
00686 
00687     pathname(),
00688 
00689     hostname(),
00690     storagegroup("Default"),
00691 
00692     seriesid(_seriesid),
00693     programid(_programid),
00694     inetref(_inetref),
00695     catType(),
00696 
00697     filesize(0ULL),
00698 
00699     startts(_startts),
00700     endts(_endts),
00701     recstartts(_recstartts),
00702     recendts(_recendts),
00703 
00704     stars(0.0f),
00705 
00706     originalAirDate(),
00707     lastmodified(QDateTime::currentDateTime()),
00708     lastInUseTime(lastmodified.addSecs(-4 * 60 * 60)),
00709 
00710     prefinput(0),
00711     recpriority2(0),
00712     recordid(0),
00713     parentid(0),
00714 
00715     sourceid(0),
00716     inputid(0),
00717     cardid(0),
00718 
00719     findid(0),
00720 
00721     programflags(FL_NONE),
00722     properties(0),
00723     year(0),
00724 
00725     recstatus(rsUnknown),
00726     oldrecstatus(rsUnknown),
00727     rectype(kNotRecording),
00728     dupin(kDupsInAll),
00729     dupmethod(kDupCheckSubDesc),
00730 
00731     // everything below this line is not serialized
00732     availableStatus(asAvailable),
00733     spread(-1),
00734     startCol(-1),
00735     sortTitle(),
00736 
00737     // Private
00738     inUseForWhat(),
00739     positionMapDBReplacement(NULL)
00740 {
00741 }
00742 
00743 ProgramInfo::ProgramInfo(const QString &_pathname) :
00744     chanid(0),
00745     positionMapDBReplacement(NULL)
00746 {
00747     if (_pathname.isEmpty())
00748     {
00749         clear();
00750         return;
00751     }
00752 
00753     uint _chanid;
00754     QDateTime _recstartts;
00755     if (!gCoreContext->IsDatabaseIgnored() &&
00756         QueryKeyFromPathname(_pathname, _chanid, _recstartts) &&
00757         LoadProgramFromRecorded(_chanid, _recstartts))
00758     {
00759         return;
00760     }
00761 
00762     clear();
00763 
00764     QDateTime cur = QDateTime::currentDateTime();
00765     recstartts = startts = cur.addSecs(-4 * 60 * 60 - 1);
00766     recendts   = endts   = cur.addSecs(-1);
00767 
00768     QString basename = _pathname.section('/', -1);
00769     if (_pathname == basename)
00770         SetPathname(QDir::currentPath() + '/' + _pathname);
00771     else if (_pathname.contains("./") && !_pathname.contains(":"))
00772         SetPathname(QFileInfo(_pathname).absoluteFilePath());
00773     else
00774         SetPathname(_pathname);
00775 }
00776 
00777 ProgramInfo::ProgramInfo(const QString &_pathname,
00778                          const QString &_plot,
00779                          const QString &_title,
00780                          const QString &_subtitle,
00781                          const QString &_director,
00782                          int _season, int _episode,
00783                          const QString &_inetref,
00784                          uint _length_in_minutes,
00785                          uint _year,
00786                          const QString &_programid) :
00787     positionMapDBReplacement(NULL)
00788 {
00789     clear();
00790 
00791     QDateTime cur = QDateTime::currentDateTime();
00792     recstartts = cur.addSecs((_length_in_minutes + 1) * -60);
00793     recendts   = recstartts.addSecs(_length_in_minutes * 60);
00794     startts.setDate(
00795         QDate::fromString(QString("%1-01-01").arg(year), Qt::ISODate));
00796     startts.setTime(QTime(0,0,0));
00797     endts      = startts.addSecs(_length_in_minutes * 60);
00798 
00799     QString pn = _pathname;
00800     if ((!_pathname.startsWith("myth://")) &&
00801         (_pathname.right(4).toLower() == ".iso" ||
00802          _pathname.right(4).toLower() == ".img" ||
00803          QDir(_pathname + "/VIDEO_TS").exists()))
00804     {
00805         pn = QString("dvd:%1").arg(_pathname);
00806     }
00807     else if (QDir(_pathname+"/BDMV").exists())
00808     {
00809         pn = QString("bd:%1").arg(_pathname);
00810     }
00811     SetPathname(pn);
00812 
00813     if (!_director.isEmpty())
00814     {
00815         description = QString("%1: %2.  ")
00816             .arg(QObject::tr("Directed By")).arg(_director);
00817     }
00818 
00819     description += _plot;
00820 
00821     if (!_subtitle.isEmpty())
00822         subtitle = _subtitle;
00823 
00824     season = _season;
00825     episode = _episode;
00826     inetref = _inetref;
00827     title = _title;
00828     programid = _programid;
00829 }
00830 
00831 ProgramInfo::ProgramInfo(const QString &_title, uint _chanid,
00832                          const QDateTime &_startts,
00833                          const QDateTime &_endts) :
00834     positionMapDBReplacement(NULL)
00835 {
00836     clear();
00837 
00838     MSqlQuery query(MSqlQuery::InitCon());
00839     query.prepare(
00840         "SELECT chanid, channum, callsign, name, outputfilters, commmethod "
00841         "FROM channel "
00842         "WHERE chanid=:CHANID");
00843     query.bindValue(":CHANID", _chanid);
00844     if (query.exec() && query.next())
00845     {
00846         chanstr  = query.value(1).toString();
00847         chansign = query.value(2).toString();
00848         channame = query.value(3).toString();
00849         chanplaybackfilters = query.value(4).toString();
00850         set_flag(programflags, FL_CHANCOMMFREE,
00851                  query.value(5).toInt() == COMM_DETECT_COMMFREE);
00852     }
00853 
00854     chanid  = _chanid;
00855     startts = _startts;
00856     endts   = _endts;
00857 
00858     title = _title;
00859     if (title.isEmpty())
00860     {
00861         QString channelFormat =
00862             gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
00863 
00864         title = QString("%1 - %2").arg(ChannelText(channelFormat))
00865             .arg(MythDateTimeToString(startts, kTime));
00866     }
00867 
00868     description = title =
00869         QString("%1 (%2)").arg(title).arg(QObject::tr("Manual Record"));
00870 }
00871 
00872 ProgramInfo::ProgramInfo(
00873     const QString   &_title,   const QString   &_category,
00874     const QDateTime &_startts, const QDateTime &_endts) :
00875     positionMapDBReplacement(NULL)
00876 {
00877     clear();
00878     title    = _title;
00879     category = _category;
00880     startts  = _startts;
00881     endts    = _endts;
00882 }
00883 
00884 
00888 ProgramInfo &ProgramInfo::operator=(const ProgramInfo &other)
00889 {
00890     clone(other);
00891     return *this;
00892 }
00893 
00895 void ProgramInfo::clone(const ProgramInfo &other,
00896                         bool ignore_non_serialized_data)
00897 {
00898     bool is_same =
00899         (chanid && recstartts.isValid() && startts.isValid() &&
00900          chanid == other.chanid && recstartts == other.recstartts &&
00901          startts == other.startts);
00902 
00903     title = other.title;
00904     subtitle = other.subtitle;
00905     description = other.description;
00906     season = other.season;
00907     episode = other.episode;
00908     category = other.category;
00909 
00910     chanid = other.chanid;
00911     chanstr = other.chanstr;
00912     chansign = other.chansign;
00913     channame = other.channame;
00914     chanplaybackfilters = other.chanplaybackfilters;
00915 
00916     recgroup = other.recgroup;
00917     playgroup = other.playgroup;
00918 
00919     if (!ignore_non_serialized_data || !is_same ||
00920         (GetBasename() != other.GetBasename()))
00921     {
00922         pathname = other.pathname;
00923     }
00924 
00925     hostname = other.hostname;
00926     storagegroup = other.storagegroup;
00927 
00928     seriesid = other.seriesid;
00929     programid = other.programid;
00930     inetref = other.inetref;
00931     catType = other.catType;
00932 
00933     recpriority = other.recpriority;
00934 
00935     filesize = other.filesize;
00936 
00937     startts = other.startts;
00938     endts = other.endts;
00939     recstartts = other.recstartts;
00940     recendts = other.recendts;
00941 
00942     stars = other.stars;
00943 
00944     year = other.year;
00945     originalAirDate = other.originalAirDate;
00946     lastmodified = other.lastmodified;
00947     lastInUseTime = QDateTime::currentDateTime().addSecs(-4 * 60 * 60);
00948 
00949     recstatus = other.recstatus;
00950 
00951     prefinput = other.prefinput;
00952     recpriority2 = other.recpriority2;
00953     recordid = other.recordid;
00954     parentid = other.parentid;
00955 
00956     rectype = other.rectype;
00957     dupin = other.dupin;
00958     dupmethod = other.dupmethod;
00959 
00960     sourceid = other.sourceid;
00961     inputid = other.inputid;
00962     cardid = other.cardid;
00963 
00964     findid = other.findid;
00965     programflags = other.programflags;
00966     properties = other.properties;
00967 
00968     if (!ignore_non_serialized_data)
00969     {
00970         spread = other.spread;
00971         startCol = other.startCol;
00972         sortTitle = other.sortTitle;
00973         availableStatus = other.availableStatus;
00974 
00975         inUseForWhat = other.inUseForWhat;
00976         positionMapDBReplacement = other.positionMapDBReplacement;
00977     }
00978 
00979     title.detach();
00980     subtitle.detach();
00981     description.detach();
00982     category.detach();
00983 
00984     chanstr.detach();
00985     chansign.detach();
00986     channame.detach();
00987     chanplaybackfilters.detach();
00988 
00989     recgroup.detach();
00990     playgroup.detach();
00991 
00992     pathname.detach();
00993     hostname.detach();
00994     storagegroup.detach();
00995 
00996     seriesid.detach();
00997     programid.detach();
00998     inetref.detach();
00999     catType.detach();
01000 
01001     sortTitle.detach();
01002     inUseForWhat.detach();
01003 }
01004 
01005 void ProgramInfo::clear(void)
01006 {
01007     title.clear();
01008     subtitle.clear();
01009     description.clear();
01010     season = 0;
01011     episode = 0;
01012     category.clear();
01013 
01014     chanid = 0;
01015     chanstr.clear();
01016     chansign.clear();
01017     channame.clear();
01018     chanplaybackfilters.clear();
01019 
01020     recgroup = "Default";
01021     playgroup = "Default";
01022 
01023     pathname.clear();
01024 
01025     hostname.clear();
01026     storagegroup = "Default";
01027 
01028     year = 0;
01029 
01030     seriesid.clear();
01031     programid.clear();
01032     inetref.clear();
01033     catType.clear();
01034 
01035     sortTitle.clear();
01036 
01037     recpriority = 0;
01038 
01039     filesize = 0ULL;
01040 
01041     startts = mythCurrentDateTime();
01042     endts = startts;
01043     recstartts = startts;
01044     recendts = startts;
01045 
01046     stars = 0.0f;
01047 
01048     originalAirDate = QDate();
01049     lastmodified = startts;
01050     lastInUseTime = startts.addSecs(-4 * 60 * 60);
01051 
01052     recstatus = rsUnknown;
01053 
01054     prefinput = 0;
01055     recpriority2 = 0;
01056     recordid = 0;
01057     parentid = 0;
01058 
01059     rectype = kNotRecording;
01060     dupin = kDupsInAll;
01061     dupmethod = kDupCheckSubDesc;
01062 
01063     sourceid = 0;
01064     inputid = 0;
01065     cardid = 0;
01066 
01067     findid = 0;
01068 
01069     programflags = FL_NONE;
01070     properties = 0;
01071 
01072     // everything below this line is not serialized
01073     spread = -1;
01074     startCol = -1;
01075     availableStatus = asAvailable;
01076 
01077     // Private
01078     inUseForWhat.clear();
01079     positionMapDBReplacement = NULL;
01080 }
01081 
01085 ProgramInfo::~ProgramInfo()
01086 {
01087 }
01088 
01090 QString ProgramInfo::MakeUniqueKey(
01091     uint chanid, const QDateTime &recstartts)
01092 {
01093     return QString("%1_%2").arg(chanid).arg(recstartts.toString(Qt::ISODate));
01094 }
01095 
01099 bool ProgramInfo::ExtractKey(const QString &uniquekey,
01100                              uint &chanid, QDateTime &recstartts)
01101 {
01102     QStringList keyParts = uniquekey.split('_');
01103     if (keyParts.size() != 2)
01104         return false;
01105     chanid     = keyParts[0].toUInt();
01106     recstartts = QDateTime::fromString(keyParts[1], Qt::ISODate);
01107     return chanid && recstartts.isValid();
01108 }
01109 
01110 bool ProgramInfo::ExtractKeyFromPathname(
01111     const QString &pathname, uint &chanid, QDateTime &recstartts)
01112 {
01113     QString basename = pathname.section('/', -1);
01114     if (basename.isEmpty())
01115         return false;
01116 
01117     QStringList lr = basename.split("_");
01118     if (lr.size() == 2)
01119     {
01120         chanid = lr[0].toUInt();
01121         QStringList ts = lr[1].split(".");
01122         if (chanid && !ts.empty())
01123         {
01124             recstartts = myth_dt_from_string(ts[0]);
01125             return recstartts.isValid();
01126         }
01127     }
01128 
01129     return false;
01130 }
01131 
01132 bool ProgramInfo::QueryKeyFromPathname(
01133     const QString &pathname, uint &chanid, QDateTime &recstartts)
01134 {
01135     QString basename = pathname.section('/', -1);
01136     if (basename.isEmpty())
01137         return false;
01138 
01139     MSqlQuery query(MSqlQuery::InitCon());
01140     query.prepare(
01141         "SELECT chanid, starttime "
01142         "FROM recorded "
01143         "WHERE basename = :BASENAME");
01144     query.bindValue(":BASENAME", basename);
01145     if (query.exec() && query.next())
01146     {
01147         chanid     = query.value(0).toUInt();
01148         recstartts = query.value(1).toDateTime();
01149         return true;
01150     }
01151 
01152     return ExtractKeyFromPathname(pathname, chanid, recstartts);
01153 }
01154 
01155 #define INT_TO_LIST(x)       do { list << QString::number(x); } while (0)
01156 
01157 #define DATETIME_TO_LIST(x)  INT_TO_LIST((x).toTime_t())
01158 
01159 #define LONGLONG_TO_LIST(x)  do { list << QString::number(x); } while (0)
01160 
01161 #define STR_TO_LIST(x)       do { list << (x); } while (0)
01162 #define DATE_TO_LIST(x)      do { list << (x).toString(Qt::ISODate); } while (0)
01163 
01164 #define FLOAT_TO_LIST(x)     do { list << QString("%1").arg(x); } while (0)
01165 
01172 void ProgramInfo::ToStringList(QStringList &list) const
01173 {
01174     STR_TO_LIST(title);        // 0
01175     STR_TO_LIST(subtitle);     // 1
01176     STR_TO_LIST(description);  // 2
01177     INT_TO_LIST(season);       // 3
01178     INT_TO_LIST(episode);      // 4
01179     STR_TO_LIST(category);     // 5
01180     INT_TO_LIST(chanid);       // 6
01181     STR_TO_LIST(chanstr);      // 7
01182     STR_TO_LIST(chansign);     // 8
01183     STR_TO_LIST(channame);     // 9
01184     STR_TO_LIST(pathname);     // 10
01185     INT_TO_LIST(filesize);     // 11
01186 
01187     DATETIME_TO_LIST(startts); // 12
01188     DATETIME_TO_LIST(endts);   // 13
01189     INT_TO_LIST(findid);       // 14
01190     STR_TO_LIST(hostname);     // 15
01191     INT_TO_LIST(sourceid);     // 16
01192     INT_TO_LIST(cardid);       // 17
01193     INT_TO_LIST(inputid);      // 18
01194     INT_TO_LIST(recpriority);  // 19
01195     INT_TO_LIST(recstatus);    // 20
01196     INT_TO_LIST(recordid);     // 21
01197 
01198     INT_TO_LIST(rectype);      // 22
01199     INT_TO_LIST(dupin);        // 23
01200     INT_TO_LIST(dupmethod);    // 24
01201     DATETIME_TO_LIST(recstartts);//25
01202     DATETIME_TO_LIST(recendts);// 26
01203     INT_TO_LIST(programflags); // 27
01204     STR_TO_LIST((!recgroup.isEmpty()) ? recgroup : "Default"); // 28
01205     STR_TO_LIST(chanplaybackfilters); // 29
01206     STR_TO_LIST(seriesid);     // 30
01207     STR_TO_LIST(programid);    // 31
01208     STR_TO_LIST(inetref);      // 32
01209 
01210     DATETIME_TO_LIST(lastmodified); // 33
01211     FLOAT_TO_LIST(stars);           // 34
01212     DATE_TO_LIST(originalAirDate);  // 35
01213     STR_TO_LIST((!playgroup.isEmpty()) ? playgroup : "Default"); // 36
01214     INT_TO_LIST(recpriority2);      // 37
01215     INT_TO_LIST(parentid);          // 38
01216     STR_TO_LIST((!storagegroup.isEmpty()) ? storagegroup : "Default"); // 39
01217     INT_TO_LIST(GetAudioProperties()); // 40
01218     INT_TO_LIST(GetVideoProperties()); // 41
01219     INT_TO_LIST(GetSubtitleType());    // 42
01220 
01221     INT_TO_LIST(year);              // 43
01222 /* do not forget to update the NUMPROGRAMLINES defines! */
01223 }
01224 
01225 // QStringList::const_iterator it = list.begin()+offset;
01226 
01227 #define NEXT_STR()        do { if (it == listend)                    \
01228                                {                                     \
01229                                    LOG(VB_GENERAL, LOG_ERR, listerror); \
01230                                    clear();                          \
01231                                    return false;                     \
01232                                }                                     \
01233                                ts = *it++; } while (0)
01234 
01235 #define INT_FROM_LIST(x)     do { NEXT_STR(); (x) = ts.toLongLong(); } while (0)
01236 #define ENUM_FROM_LIST(x, y) do { NEXT_STR(); (x) = ((y)ts.toInt()); } while (0)
01237 
01238 #define DATETIME_FROM_LIST(x) \
01239     do { NEXT_STR(); (x).setTime_t(ts.toUInt()); } while (0)
01240 #define DATE_FROM_LIST(x) \
01241     do { NEXT_STR(); (x) = ((ts.isEmpty()) || (ts == "0000-00-00")) ? \
01242                          QDate() : QDate::fromString(ts, Qt::ISODate); \
01243     } while (0)
01244 
01245 #define STR_FROM_LIST(x)     do { NEXT_STR(); (x) = ts; } while (0)
01246 
01247 #define FLOAT_FROM_LIST(x)   do { NEXT_STR(); (x) = ts.toFloat(); } while (0)
01248 
01260 bool ProgramInfo::FromStringList(QStringList::const_iterator &it,
01261                                  QStringList::const_iterator  listend)
01262 {
01263     QString listerror = LOC + "FromStringList, not enough items in list.";
01264     QString ts;
01265 
01266     uint      origChanid     = chanid;
01267     QDateTime origRecstartts = recstartts;
01268 
01269     STR_FROM_LIST(title);            // 0
01270     STR_FROM_LIST(subtitle);         // 1
01271     STR_FROM_LIST(description);      // 2
01272     INT_FROM_LIST(season);           // 3
01273     INT_FROM_LIST(episode);          // 4
01274     STR_FROM_LIST(category);         // 5
01275     INT_FROM_LIST(chanid);           // 6
01276     STR_FROM_LIST(chanstr);          // 7
01277     STR_FROM_LIST(chansign);         // 8
01278     STR_FROM_LIST(channame);         // 9
01279     STR_FROM_LIST(pathname);         // 10
01280     INT_FROM_LIST(filesize);         // 11
01281 
01282     DATETIME_FROM_LIST(startts);     // 12
01283     DATETIME_FROM_LIST(endts);       // 13
01284     INT_FROM_LIST(findid);           // 14
01285     STR_FROM_LIST(hostname);         // 15
01286     INT_FROM_LIST(sourceid);         // 16
01287     INT_FROM_LIST(cardid);           // 17
01288     INT_FROM_LIST(inputid);          // 18
01289     INT_FROM_LIST(recpriority);      // 19
01290     ENUM_FROM_LIST(recstatus, RecStatusType); // 20
01291     INT_FROM_LIST(recordid);         // 21
01292 
01293     ENUM_FROM_LIST(rectype, RecordingType);            // 22
01294     ENUM_FROM_LIST(dupin, RecordingDupInType);         // 23
01295     ENUM_FROM_LIST(dupmethod, RecordingDupMethodType); // 24
01296     DATETIME_FROM_LIST(recstartts);   // 25
01297     DATETIME_FROM_LIST(recendts);     // 26
01298     INT_FROM_LIST(programflags);      // 27
01299     STR_FROM_LIST(recgroup);          // 28
01300     STR_FROM_LIST(chanplaybackfilters);//29
01301     STR_FROM_LIST(seriesid);          // 30
01302     STR_FROM_LIST(programid);         // 31
01303     STR_FROM_LIST(inetref);           // 32
01304 
01305     DATETIME_FROM_LIST(lastmodified); // 33
01306     FLOAT_FROM_LIST(stars);           // 34
01307     DATE_FROM_LIST(originalAirDate);; // 35
01308     STR_FROM_LIST(playgroup);         // 36
01309     INT_FROM_LIST(recpriority2);      // 37
01310     INT_FROM_LIST(parentid);          // 38
01311     STR_FROM_LIST(storagegroup);      // 39
01312     uint audioproperties, videoproperties, subtitleType;
01313     INT_FROM_LIST(audioproperties);   // 40
01314     INT_FROM_LIST(videoproperties);   // 41
01315     INT_FROM_LIST(subtitleType);      // 42
01316     properties = ((subtitleType    << kSubtitlePropertyOffset) |
01317                   (videoproperties << kVideoPropertyOffset)    |
01318                   (audioproperties << kAudioPropertyOffset));
01319 
01320     INT_FROM_LIST(year);              // 43
01321 
01322     if (!origChanid || !origRecstartts.isValid() ||
01323         (origChanid != chanid) || (origRecstartts != recstartts))
01324     {
01325         availableStatus = asAvailable;
01326         spread = -1;
01327         startCol = -1;
01328         sortTitle = QString();
01329         inUseForWhat = QString();
01330         positionMapDBReplacement = NULL;
01331     }
01332 
01333     return true;
01334 }
01335 
01339 void ProgramInfo::ToMap(InfoMap &progMap,
01340                         bool showrerecord,
01341                         uint star_range) const
01342 {
01343     QLocale locale = gCoreContext->GetQLocale();
01344     // NOTE: Format changes and relevant additions made here should be
01345     //       reflected in RecordingRule
01346     QString channelFormat =
01347         gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
01348     QString longChannelFormat =
01349         gCoreContext->GetSetting("LongChannelFormat", "<num> <name>");
01350 
01351     QDateTime timeNow = QDateTime::currentDateTime();
01352 
01353     int hours, minutes, seconds;
01354 
01355     progMap["title"] = title;
01356     progMap["subtitle"] = subtitle;
01357 
01358     QString tempSubTitle = title;
01359     if (!subtitle.trimmed().isEmpty())
01360     {
01361         tempSubTitle = QString("%1 - \"%2\"")
01362             .arg(tempSubTitle).arg(subtitle);
01363     }
01364 
01365     progMap["titlesubtitle"] = tempSubTitle;
01366 
01367     progMap["description"] = description;
01368 
01369     if (season > 0 || episode > 0)
01370     {
01371         progMap["season"] = GetDisplaySeasonEpisode(season, 1);
01372         progMap["episode"] = GetDisplaySeasonEpisode(episode, 1);
01373         progMap["s00e00"] = QString("s%1e%2").arg(GetDisplaySeasonEpisode
01374                                              (GetSeason(), 2))
01375                         .arg(GetDisplaySeasonEpisode(GetEpisode(), 2));
01376         progMap["00x00"] = QString("%1x%2").arg(GetDisplaySeasonEpisode
01377                                              (GetSeason(), 1))
01378                         .arg(GetDisplaySeasonEpisode(GetEpisode(), 2));
01379     }
01380     else
01381     {
01382         progMap["season"] = progMap["episode"] = "";
01383         progMap["s00e00"] = progMap["00x00"] = "";
01384     }
01385 
01386     progMap["category"] = category;
01387     progMap["callsign"] = chansign;
01388     progMap["commfree"] = (programflags & FL_CHANCOMMFREE) ? 1 : 0;
01389     progMap["outputfilters"] = chanplaybackfilters;
01390     if (IsVideo())
01391     {
01392         progMap["starttime"] = "";
01393         progMap["startdate"] = "";
01394         progMap["endtime"] = "";
01395         progMap["enddate"] = "";
01396         progMap["recstarttime"] = "";
01397         progMap["recstartdate"] = "";
01398         progMap["recendtime"] = "";
01399         progMap["recenddate"] = "";
01400 
01401         if (startts.date().year() == 1895)
01402         {
01403            progMap["startdate"] = "";
01404            progMap["recstartdate"] = "";
01405         }
01406         else
01407         {
01408             progMap["startdate"] = startts.toString("yyyy");
01409             progMap["recstartdate"] = startts.toString("yyyy");
01410         }
01411     }
01412     else // if (IsRecording())
01413     {
01414         progMap["starttime"] = MythDateTimeToString(startts, kTime);
01415         progMap["startdate"] = MythDateTimeToString(startts, kDateFull | kSimplify);
01416         progMap["shortstartdate"] = MythDateTimeToString(startts, kDateShort);
01417         if (timeNow.date().year() != startts.date().year())
01418             progMap["startyear"] = startts.toString("yyyy");
01419         progMap["endtime"] = MythDateTimeToString(endts, kTime);
01420         progMap["enddate"] = MythDateTimeToString(endts, kDateFull | kSimplify);
01421         progMap["shortenddate"] = MythDateTimeToString(endts, kDateShort);
01422         if (timeNow.date().year() != endts.date().year())
01423             progMap["endyear"] = endts.toString("yyyy");
01424         progMap["recstarttime"] = MythDateTimeToString(recstartts, kTime);
01425         progMap["recstartdate"] = MythDateTimeToString(recstartts, kDateShort);
01426         progMap["recendtime"] = MythDateTimeToString(recendts, kTime);
01427         progMap["recenddate"] = MythDateTimeToString(recendts, kDateShort);
01428         progMap["startts"] = QString::number(startts.toTime_t());
01429         progMap["endts"]   = QString::number(endts.toTime_t());
01430     }
01431 
01432     progMap["timedate"] = MythDateTimeToString(recstartts,
01433                                             kDateTimeFull | kSimplify) + " - " +
01434                           MythDateTimeToString(recendts, kTime);
01435 
01436     progMap["shorttimedate"] = MythDateTimeToString(recstartts,
01437                                             kDateTimeShort | kSimplify) + " - " +
01438                                MythDateTimeToString(recendts, kTime);
01439 
01440     progMap["starttimedate"] = MythDateTimeToString(recstartts,
01441                                             kDateTimeFull | kSimplify);
01442 
01443     progMap["shortstarttimedate"] = MythDateTimeToString(recstartts,
01444                                             kDateTimeShort | kSimplify);
01445 
01446     progMap["lastmodifiedtime"] = MythDateTimeToString(lastmodified, kTime);
01447     progMap["lastmodifieddate"] = MythDateTimeToString(lastmodified,
01448                                                        kDateFull | kSimplify);
01449     progMap["lastmodified"] = MythDateTimeToString(lastmodified,
01450                                             kDateTimeFull | kSimplify);
01451 
01452     progMap["channum"] = chanstr;
01453     progMap["chanid"] = chanid;
01454     progMap["channame"] = channame;
01455     progMap["channel"] = ChannelText(channelFormat);
01456     progMap["longchannel"] = ChannelText(longChannelFormat);
01457 
01458     QString tmpSize = locale.toString(filesize * (1.0 / (1024.0 * 1024.0 * 1024.0)), 'f', 2);
01459     progMap["filesize_str"] = QObject::tr("%1 GB", "GigaBytes").arg(tmpSize);
01460 
01461     progMap["filesize"] = locale.toString((quint64)filesize);
01462 
01463     seconds = recstartts.secsTo(recendts);
01464     minutes = seconds / 60;
01465 
01466     QString min_str = QObject::tr("%n minute(s)","",minutes);
01467 
01468     progMap["lenmins"] = min_str;
01469     hours   = minutes / 60;
01470     minutes = minutes % 60;
01471 
01472     progMap["lentime"] = min_str;
01473     if (hours > 0 && minutes > 0)
01474     {
01475         min_str = QObject::tr("%n minute(s)","",minutes);
01476         progMap["lentime"] = QString("%1 %2")
01477             .arg(QObject::tr("%n hour(s)","", hours))
01478             .arg(min_str);
01479     }
01480     else if (hours > 0)
01481     {
01482         progMap["lentime"] = QObject::tr("%n hour(s)","", hours);
01483     }
01484 
01485     progMap["rectypechar"] = toQChar(GetRecordingRuleType());
01486     progMap["rectype"] = ::toString(GetRecordingRuleType());
01487     QString tmp_rec = progMap["rectype"];
01488     if (GetRecordingRuleType() != kNotRecording)
01489     {
01490         if (((recendts > timeNow) && (recstatus <= rsWillRecord)) ||
01491             (recstatus == rsConflict) || (recstatus == rsLaterShowing))
01492         {
01493             tmp_rec += QString().sprintf(" %+d", recpriority);
01494             if (recpriority2)
01495                 tmp_rec += QString().sprintf("/%+d", recpriority2);
01496             tmp_rec += " ";
01497         }
01498         else
01499         {
01500             tmp_rec += " -- ";
01501         }
01502         if (showrerecord && (GetRecordingStatus() == rsRecorded) &&
01503             !IsDuplicate())
01504         {
01505             tmp_rec += QObject::tr("Re-Record");
01506         }
01507         else
01508         {
01509             tmp_rec += ::toString(GetRecordingStatus(), GetRecordingRuleType());
01510         }
01511     }
01512     progMap["rectypestatus"] = tmp_rec;
01513 
01514     progMap["card"] = ::toString(GetRecordingStatus(), cardid);
01515     progMap["input"] = ::toString(GetRecordingStatus(), inputid);
01516     progMap["inputname"] = QueryInputDisplayName();
01517 
01518     progMap["recpriority"] = recpriority;
01519     progMap["recpriority2"] = recpriority2;
01520     progMap["recordinggroup"] = (recgroup == "Default")
01521         ? QObject::tr("Default") : recgroup;
01522     progMap["playgroup"] = playgroup;
01523 
01524     if (storagegroup == "Default")
01525         progMap["storagegroup"] = QObject::tr("Default");
01526     else if (StorageGroup::kSpecialGroups.contains(storagegroup))
01527         progMap["storagegroup"] = QObject::tr(storagegroup.toUtf8().constData());
01528     else
01529         progMap["storagegroup"] = storagegroup;
01530 
01531     progMap["programflags"] = programflags;
01532 
01533     progMap["audioproperties"] = GetAudioProperties();
01534     progMap["videoproperties"] = GetVideoProperties();
01535     progMap["subtitleType"] = GetSubtitleType();
01536 
01537     progMap["recstatus"] = ::toString(GetRecordingStatus(),
01538                                       GetRecordingRuleType());
01539     progMap["recstatuslong"] = ::toDescription(GetRecordingStatus(),
01540                                                GetRecordingRuleType(),
01541                                                GetRecordingStartTime());
01542 
01543     if (IsRepeat())
01544     {
01545         progMap["repeat"] = QString("(%1) ").arg(QObject::tr("Repeat"));
01546         progMap["longrepeat"] = progMap["repeat"];
01547         if (originalAirDate.isValid())
01548         {
01549             progMap["longrepeat"] = QString("(%1 %2) ")
01550                 .arg(QObject::tr("Repeat"))
01551                 .arg(MythDateToString(originalAirDate, kDateFull | kAddYear));
01552         }
01553     }
01554     else
01555     {
01556         progMap["repeat"] = "";
01557         progMap["longrepeat"] = "";
01558     }
01559 
01560     progMap["seriesid"] = seriesid;
01561     progMap["programid"] = programid;
01562     progMap["inetref"] = inetref;
01563     progMap["catType"] = catType;
01564 
01565     progMap["year"] = year ? QString::number(year) : "";
01566 
01567     QString star_str = (stars != 0.0f) ?
01568         QObject::tr("%n star(s)", "", GetStars(star_range)) : "";
01569     progMap["stars"] = star_str;
01570     progMap["numstars"] = QString().number(GetStars(star_range));
01571 
01572     if (stars != 0.0f && year)
01573         progMap["yearstars"] = QString("(%1, %2)").arg(year).arg(star_str);
01574     else if (stars != 0.0f)
01575         progMap["yearstars"] = QString("(%1)").arg(star_str);
01576     else if (year)
01577         progMap["yearstars"] = QString("(%1)").arg(year);
01578     else
01579         progMap["yearstars"] = "";
01580 
01581     if (!originalAirDate.isValid() ||
01582         (!programid.isEmpty() && (programid.left(2) == "MV")))
01583     {
01584         progMap["originalairdate"] = "";
01585         progMap["shortoriginalairdate"] = "";
01586     }
01587     else
01588     {
01589         progMap["originalairdate"] = MythDateToString(originalAirDate, kDateFull);
01590         progMap["shortoriginalairdate"] = MythDateToString(originalAirDate, kDateShort);
01591     }
01592 
01593     // 'mediatype' for a statetype, so untranslated
01594     // 'mediatypestring' for textarea, so translated
01595     // TODO Move to a dedicated ToState() method?
01596     QString mediaType;
01597     QString mediaTypeString;
01598     switch (GetProgramInfoType())
01599     {
01600         case kProgramInfoTypeVideoFile :
01601             mediaType = "video";
01602             mediaTypeString = QObject::tr("Video");
01603             break;
01604         case kProgramInfoTypeVideoDVD :
01605             mediaType = "dvd";
01606             mediaTypeString = QObject::tr("DVD");
01607             break;
01608         case kProgramInfoTypeVideoStreamingHTML :
01609             mediaType = "httpstream";
01610             mediaTypeString = QObject::tr("HTTP Streaming");
01611             break;
01612         case kProgramInfoTypeVideoStreamingRTSP :
01613             mediaType = "rtspstream";
01614             mediaTypeString = QObject::tr("RTSP Streaming");
01615             break;
01616         case kProgramInfoTypeVideoBD :
01617             mediaType = "bluraydisc";
01618             mediaTypeString = QObject::tr("Blu-ray Disc");
01619             break;
01620         case kProgramInfoTypeRecording : // Fall through
01621         default :
01622             mediaType = "recording";
01623             mediaTypeString = QObject::tr("Recording",
01624                                           "Recorded file, object not action");
01625     }
01626     progMap["mediatype"] = mediaType;
01627     progMap["mediatypestring"] = mediaTypeString;
01628 }
01629 
01631 uint ProgramInfo::GetSecondsInRecording(void) const
01632 {
01633     int recsecs = recstartts.secsTo(endts);
01634     return (uint) ((recsecs>0) ? recsecs : max(startts.secsTo(endts),0));
01635 }
01636 
01638 uint64_t ProgramInfo::QueryLastFrameInPosMap(void) const
01639 {
01640     uint64_t last_frame = 0;
01641     frm_pos_map_t posMap;
01642     QueryPositionMap(posMap, MARK_GOP_BYFRAME);
01643     if (posMap.empty())
01644     {
01645         QueryPositionMap(posMap, MARK_GOP_START);
01646         if (posMap.empty())
01647             QueryPositionMap(posMap, MARK_KEYFRAME);
01648     }
01649     if (!posMap.empty())
01650     {
01651         frm_pos_map_t::const_iterator it = posMap.constEnd();
01652         --it;
01653         last_frame = it.key();
01654     }
01655     return last_frame;
01656 }
01657 
01658 QString ProgramInfo::toString(const Verbosity v, QString sep, QString grp)
01659     const
01660 {
01661     QString str;
01662     switch (v)
01663     {
01664         case kLongDescription:
01665             str = LOC + "channame(" + channame + ") startts(" +
01666                 startts.toString() + ") endts(" + endts.toString() + ")\n";
01667             str += "             recstartts(" + recstartts.toString() +
01668                 ") recendts(" + recendts.toString() + ")\n";
01669             str += "             title(" + title + ")";
01670             break;
01671         case kTitleSubtitle:
01672             str = title.contains(' ') ?
01673                 QString("%1%2%3").arg(grp).arg(title).arg(grp) : title;
01674             if (!subtitle.isEmpty())
01675             {
01676                 str += subtitle.contains(' ') ?
01677                     QString("%1%2%3%4").arg(sep)
01678                         .arg(grp).arg(subtitle).arg(grp) :
01679                     QString("%1%2").arg(sep).arg(subtitle);
01680             }
01681             break;
01682         case kRecordingKey:
01683             str = QString("%1 at %2")
01684                 .arg(GetChanID()).arg(GetRecordingStartTime(ISODate));
01685             break;
01686         case kSchedulingKey:
01687             str = QString("%1 @ %2")
01688                 .arg(GetChanID()).arg(GetScheduledStartTime(ISODate));
01689             break;
01690     }
01691 
01692     return str;
01693 }
01694 
01695 bool ProgramInfo::Reload(void)
01696 {
01697     ProgramInfo test(chanid, recstartts);
01698     if (test.GetChanID())
01699     {
01700         clone(test, true);
01701         return true;
01702     }
01703     return false;
01704 }
01705 
01709 bool ProgramInfo::LoadProgramFromRecorded(
01710     const uint _chanid, const QDateTime &_recstartts)
01711 {
01712     if (!_chanid || !_recstartts.isValid())
01713     {
01714         clear();
01715         return false;
01716     }
01717 
01718     MSqlQuery query(MSqlQuery::InitCon());
01719     query.prepare(
01720         kFromRecordedQuery +
01721         "WHERE r.chanid    = :CHANID AND "
01722         "      r.starttime = :RECSTARTTS");
01723     query.bindValue(":CHANID",     _chanid);
01724     query.bindValue(":RECSTARTTS", _recstartts);
01725 
01726     if (!query.exec())
01727     {
01728         MythDB::DBError("LoadProgramFromRecorded", query);
01729         clear();
01730         return false;
01731     }
01732 
01733     if (!query.next())
01734     {
01735         clear();
01736         return false;
01737     }
01738 
01739     bool is_reload = (chanid == _chanid) && (recstartts == _recstartts);
01740     if (!is_reload)
01741     {
01742         // These items are not initialized below so they need to be cleared
01743         // if we're loading in a different program into this ProgramInfo
01744         catType.clear();
01745         lastInUseTime = QDateTime::currentDateTime().addSecs(-4 * 60 * 60);
01746         rectype = kNotRecording;
01747         oldrecstatus = rsUnknown;
01748         prefinput = 0;
01749         recpriority2 = 0;
01750         parentid = 0;
01751         sourceid = 0;
01752         inputid = 0;
01753         cardid = 0;
01754 
01755         // everything below this line (in context) is not serialized
01756         spread = startCol = -1;
01757         sortTitle.clear();
01758         availableStatus = asAvailable;
01759         inUseForWhat.clear();
01760         positionMapDBReplacement = NULL;
01761     }
01762 
01763     title        = query.value(0).toString();
01764     subtitle     = query.value(1).toString();
01765     description  = query.value(2).toString();
01766     season       = query.value(3).toUInt();
01767     episode      = query.value(4).toUInt();
01768     category     = query.value(5).toString();
01769 
01770     chanid       = _chanid;
01771     chanstr      = QString("#%1").arg(chanid);
01772     chansign     = chanstr;
01773     channame     = chanstr;
01774     chanplaybackfilters.clear();
01775     if (!query.value(7).toString().isEmpty())
01776     {
01777         chanstr  = query.value(7).toString();
01778         chansign = query.value(8).toString();
01779         channame = query.value(9).toString();
01780         chanplaybackfilters = query.value(10).toString();
01781     }
01782 
01783     recgroup     = query.value(11).toString();
01784     playgroup    = query.value(12).toString();
01785 
01786     // We don't want to update the pathname if the basename is
01787     // the same as we may have already expanded pathname from
01788     // a simple basename to a localized path.
01789     QString new_basename = query.value(14).toString();
01790     if ((GetBasename() != new_basename) || !is_reload)
01791     {
01792         if (is_reload)
01793         {
01794             LOG(VB_FILE, LOG_INFO, LOC +
01795                 QString("Updated pathname '%1':'%2' -> '%3'")
01796                     .arg(pathname).arg(GetBasename()).arg(new_basename));
01797         }
01798         SetPathname(new_basename);
01799     }
01800 
01801     hostname     = query.value(15).toString();
01802     storagegroup = query.value(13).toString();
01803 
01804     seriesid     = query.value(17).toString();
01805     programid    = query.value(18).toString();
01806     inetref      = query.value(19).toString();
01807     //catType;
01808 
01809     recpriority  = query.value(16).toInt();
01810 
01811     filesize     = query.value(20).toULongLong();
01812 
01813     startts      = query.value(21).toDateTime();
01814     endts        = query.value(22).toDateTime();
01815     recstartts   = query.value(24).toDateTime();
01816     recendts     = query.value(25).toDateTime();
01817 
01818     stars        = clamp((float)query.value(23).toDouble(), 0.0f, 1.0f);
01819 
01820     year         = query.value(26).toUInt();
01821     originalAirDate = query.value(27).toDate();
01822     lastmodified = query.value(28).toDateTime();
01823     //lastInUseTime;
01824 
01825     recstatus    = rsRecorded;
01826     //oldrecstatus;
01827 
01828     //prefinput;
01829     //recpriority2;
01830 
01831     recordid     = query.value(29).toUInt();
01832     //parentid;
01833 
01834     //sourcid;
01835     //inputid;
01836     //cardid;
01837     findid       = query.value(45).toUInt();
01838 
01839     //rectype;
01840     dupin        = RecordingDupInType(query.value(46).toInt());
01841     dupmethod    = RecordingDupMethodType(query.value(47).toInt());
01842 
01843     // ancillary data -- begin
01844     set_flag(programflags, FL_CHANCOMMFREE,
01845              query.value(30).toInt() == COMM_DETECT_COMMFREE);
01846     set_flag(programflags, FL_COMMFLAG,
01847              query.value(31).toInt() == COMM_FLAG_DONE);
01848     set_flag(programflags, FL_COMMPROCESSING ,
01849              query.value(31).toInt() == COMM_FLAG_PROCESSING);
01850     set_flag(programflags, FL_REPEAT,        query.value(29).toBool());
01851     set_flag(programflags, FL_TRANSCODED,
01852              query.value(34).toInt() == TRANSCODING_COMPLETE);
01853     set_flag(programflags, FL_DELETEPENDING, query.value(35).toBool());
01854     set_flag(programflags, FL_PRESERVED,     query.value(36).toBool());
01855     set_flag(programflags, FL_CUTLIST,       query.value(37).toBool());
01856     set_flag(programflags, FL_AUTOEXP,       query.value(38).toBool());
01857     set_flag(programflags, FL_REALLYEDITING, query.value(39).toBool());
01858     set_flag(programflags, FL_BOOKMARK,      query.value(40).toBool());
01859     set_flag(programflags, FL_WATCHED,       query.value(41).toBool());
01860     set_flag(programflags, FL_EDITING,
01861              (programflags & FL_REALLYEDITING) ||
01862              (programflags & FL_COMMPROCESSING));
01863 
01864     properties = ((query.value(44).toUInt() << kSubtitlePropertyOffset) |
01865                   (query.value(43).toUInt() << kVideoPropertyOffset)    |
01866                   (query.value(42).toUInt() << kAudioPropertyOffset));
01867     // ancillary data -- end
01868 
01869     if (originalAirDate.isValid() && originalAirDate < QDate(1940, 1, 1))
01870         originalAirDate = QDate();
01871 
01872     // Extra stuff which is not serialized and may get lost.
01873     // spread
01874     // startCol
01875     // sortTitle
01876     // availableStatus
01877     // inUseForWhat
01878     // postitionMapDBReplacement
01879 
01880     return true;
01881 }
01882 
01886 int ProgramInfo::GetRecordingTypeRecPriority(RecordingType type)
01887 {
01888     switch (type)
01889     {
01890         case kSingleRecord:
01891             return gCoreContext->GetNumSetting("SingleRecordRecPriority", 1);
01892         case kTimeslotRecord:
01893             return gCoreContext->GetNumSetting("TimeslotRecordRecPriority", 0);
01894         case kWeekslotRecord:
01895             return gCoreContext->GetNumSetting("WeekslotRecordRecPriority", 0);
01896         case kChannelRecord:
01897             return gCoreContext->GetNumSetting("ChannelRecordRecPriority", 0);
01898         case kAllRecord:
01899             return gCoreContext->GetNumSetting("AllRecordRecPriority", 0);
01900         case kFindOneRecord:
01901         case kFindDailyRecord:
01902         case kFindWeeklyRecord:
01903             return gCoreContext->GetNumSetting("FindOneRecordRecPriority", -1);
01904         case kOverrideRecord:
01905         case kDontRecord:
01906             return gCoreContext->GetNumSetting("OverrideRecordRecPriority", 0);
01907         default:
01908             return 0;
01909     }
01910 }
01911 
01916 bool ProgramInfo::IsSameProgramWeakCheck(const ProgramInfo &other) const
01917 {
01918     return (title == other.title &&
01919             chanid == other.chanid &&
01920             startts == other.startts);
01921 }
01922 
01927 bool ProgramInfo::IsSameProgram(const ProgramInfo& other) const
01928 {
01929     if (GetRecordingRuleType() == kFindOneRecord)
01930         return recordid == other.recordid;
01931 
01932     if (findid && findid == other.findid &&
01933         (recordid == other.recordid || recordid == other.parentid))
01934            return true;
01935 
01936     if (dupmethod & kDupCheckNone)
01937         return false;
01938 
01939     if (title.compare(other.title, Qt::CaseInsensitive) != 0)
01940         return false;
01941 
01942     if (catType == "series")
01943     {
01944         if (programid.endsWith("0000"))
01945             return false;
01946     }
01947 
01948     if (!programid.isEmpty() && !other.programid.isEmpty())
01949     {
01950         if (usingProgIDAuth)
01951         {
01952             int index = programid.indexOf('/');
01953             int oindex = other.programid.indexOf('/');
01954             if (index == oindex && (index < 0 ||
01955                 programid.leftRef(index) == other.programid.leftRef(oindex)))
01956                 return programid == other.programid;
01957         }
01958         else
01959         {
01960             return programid == other.programid;
01961         }
01962     }
01963 
01964     if ((dupmethod & kDupCheckSub) &&
01965         ((subtitle.isEmpty()) ||
01966          (subtitle.compare(other.subtitle, Qt::CaseInsensitive) != 0)))
01967         return false;
01968 
01969     if ((dupmethod & kDupCheckDesc) &&
01970         ((description.isEmpty()) ||
01971          (description.compare(other.description, Qt::CaseInsensitive) != 0)))
01972         return false;
01973 
01974     if ((dupmethod & kDupCheckSubThenDesc) &&
01975         ((subtitle.isEmpty() &&
01976           ((!other.subtitle.isEmpty() &&
01977             description.compare(other.subtitle, Qt::CaseInsensitive) != 0) ||
01978            (other.subtitle.isEmpty() &&
01979             description.compare(other.description, Qt::CaseInsensitive) != 0))) ||
01980          (!subtitle.isEmpty() &&
01981           ((other.subtitle.isEmpty() &&
01982             subtitle.compare(other.description, Qt::CaseInsensitive) != 0) ||
01983            (!other.subtitle.isEmpty() &&
01984             subtitle.compare(other.subtitle, Qt::CaseInsensitive) != 0)))))
01985         return false;
01986 
01987     return true;
01988 }
01989 
01995 bool ProgramInfo::IsSameTimeslot(const ProgramInfo& other) const
01996 {
01997     if (title.compare(other.title, Qt::CaseInsensitive) != 0)
01998         return false;
01999     if (startts == other.startts &&
02000         (chanid == other.chanid ||
02001          (!chansign.isEmpty() &&
02002           chansign.compare(other.chansign, Qt::CaseInsensitive) == 0)))
02003         return true;
02004 
02005     return false;
02006 }
02007 
02014 bool ProgramInfo::IsSameProgramTimeslot(const ProgramInfo &other) const
02015 {
02016     if (title.compare(other.title, Qt::CaseInsensitive) != 0)
02017         return false;
02018     if ((chanid == other.chanid ||
02019          (!chansign.isEmpty() &&
02020           chansign.compare(other.chansign, Qt::CaseInsensitive) == 0)) &&
02021         startts < other.endts &&
02022         endts > other.startts)
02023         return true;
02024 
02025     return false;
02026 }
02027 
02028 void ProgramInfo::CheckProgramIDAuthorities(void)
02029 {
02030     QMap<QString, int> authMap;
02031     QString tables[] = { "program", "recorded", "oldrecorded", "" };
02032     MSqlQuery query(MSqlQuery::InitCon());
02033 
02034     int tableIndex = 0;
02035     QString table = tables[tableIndex];
02036     while (!table.isEmpty())
02037     {
02038         query.prepare(QString(
02039             "SELECT DISTINCT LEFT(programid, LOCATE('/', programid)) "
02040             "FROM %1 WHERE programid <> ''").arg(table));
02041         if (!query.exec())
02042             MythDB::DBError("CheckProgramIDAuthorities", query);
02043         else
02044         {
02045             while (query.next())
02046                 authMap[query.value(0).toString()] = 1;
02047         }
02048         ++tableIndex;
02049         table = tables[tableIndex];
02050     }
02051 
02052     int numAuths = authMap.count();
02053     LOG(VB_GENERAL, LOG_INFO,
02054         QString("Found %1 distinct programid authorities").arg(numAuths));
02055 
02056     usingProgIDAuth = (numAuths > 1);
02057 }
02058 
02063 QString ProgramInfo::CreateRecordBasename(const QString &ext) const
02064 {
02065     QString starts = recstartts.toString("yyyyMMddhhmmss");
02066 
02067     QString retval = QString("%1_%2.%3").arg(chanid)
02068                              .arg(starts).arg(ext);
02069 
02070     return retval;
02071 }
02072 
02073 static ProgramInfoType discover_program_info_type(
02074     uint chanid, const QString &pathname, bool use_remote)
02075 {
02076     QString fn_lower = pathname.toLower();
02077     ProgramInfoType pit = kProgramInfoTypeVideoFile;
02078     if (chanid)
02079         pit = kProgramInfoTypeRecording;
02080     else if (fn_lower.startsWith("http:"))
02081         pit = kProgramInfoTypeVideoStreamingHTML;
02082     else if (fn_lower.startsWith("rtsp:"))
02083         pit = kProgramInfoTypeVideoStreamingRTSP;
02084     else
02085     {
02086         if (fn_lower.startsWith("dvd:") ||
02087             fn_lower.endsWith(".iso") ||
02088             fn_lower.endsWith(".img") ||
02089             ((pathname.left(1) == "/") &&
02090              QDir(pathname + "/VIDEO_TS").exists()))
02091         {
02092             pit = kProgramInfoTypeVideoDVD;
02093         }
02094         else if (fn_lower.startsWith("bd:") ||
02095                  ((pathname.left(1) == "/") &&
02096                   QDir(pathname + "/BDMV").exists()))
02097         {
02098             pit = kProgramInfoTypeVideoBD;
02099         }
02100         else if (use_remote && fn_lower.startsWith("myth://"))
02101         {
02102             QString tmpFileDVD = pathname + "/VIDEO_TS";
02103             QString tmpFileBD = pathname + "/BDMV";
02104             if (RemoteFile::Exists(tmpFileDVD))
02105                 pit = kProgramInfoTypeVideoDVD;
02106             else if (RemoteFile::Exists(tmpFileBD))
02107                 pit = kProgramInfoTypeVideoBD;
02108         }
02109     }
02110     return pit;
02111 }
02112 
02113 void ProgramInfo::SetPathname(const QString &pn) const
02114 {
02115     pathname = pn;
02116     pathname.detach();
02117 
02118     ProgramInfoType pit = discover_program_info_type(chanid, pathname, false);
02119     const_cast<ProgramInfo*>(this)->SetProgramInfoType(pit);
02120 }
02121 
02122 ProgramInfoType ProgramInfo::DiscoverProgramInfoType(void) const
02123 {
02124     return discover_program_info_type(chanid, pathname, true);
02125 }
02126 
02127 void ProgramInfo::SetAvailableStatus(
02128     AvailableStatusType status, const QString &where)
02129 {
02130     if (status != availableStatus)
02131     {
02132         LOG(VB_GUI, LOG_INFO,
02133                  toString(kTitleSubtitle) + QString(": %1 -> %2")
02134                      .arg(::toString((AvailableStatusType)availableStatus))
02135                      .arg(::toString(status)));
02136     }
02137     availableStatus = status;
02138 }
02139 
02143 bool ProgramInfo::SaveBasename(const QString &basename)
02144 {
02145     MSqlQuery query(MSqlQuery::InitCon());
02146     query.prepare("UPDATE recorded "
02147                   "SET basename = :BASENAME "
02148                   "WHERE chanid = :CHANID AND "
02149                   "      starttime = :STARTTIME;");
02150     query.bindValue(":CHANID", chanid);
02151     query.bindValue(":STARTTIME", recstartts);
02152     query.bindValue(":BASENAME", basename);
02153 
02154     if (!query.exec())
02155     {
02156         MythDB::DBError("SetRecordBasename", query);
02157         return false;
02158     }
02159 
02160     SetPathname(basename);
02161 
02162     SendUpdateEvent();
02163     return true;
02164 }
02165 
02173 QString ProgramInfo::QueryBasename(void) const
02174 {
02175     QString bn = GetBasename();
02176     if (!bn.isEmpty())
02177         return bn;
02178 
02179     MSqlQuery query(MSqlQuery::InitCon());
02180     query.prepare(
02181         "SELECT basename "
02182         "FROM recorded "
02183         "WHERE chanid    = :CHANID AND "
02184         "      starttime = :STARTTIME");
02185     query.bindValue(":CHANID",    chanid);
02186     query.bindValue(":STARTTIME", recstartts);
02187 
02188     if (!query.exec())
02189     {
02190         MythDB::DBError("QueryBasename", query);
02191     }
02192     else if (query.next())
02193     {
02194         return query.value(0).toString();
02195     }
02196     else
02197     {
02198         LOG(VB_GENERAL, LOG_INFO,
02199                  QString("QueryBasename found no entry for %1 @ %2")
02200                      .arg(chanid).arg(recstartts.toString(Qt::ISODate)));
02201     }
02202 
02203     return QString();
02204 }
02205 
02213 QString ProgramInfo::GetPlaybackURL(
02214     bool checkMaster, bool forceCheckLocal) const
02215 {
02216         // return the original path if BD or DVD URI
02217     if (IsVideoBD() || IsVideoDVD())
02218         return GetPathname();
02219 
02220     QString basename = QueryBasename();
02221     if (basename.isEmpty())
02222         return "";
02223 
02224     bool checklocal = !gCoreContext->GetNumSetting("AlwaysStreamFiles", 0) ||
02225                       forceCheckLocal;
02226 
02227     if (IsVideo())
02228     {
02229         QString fullpath = GetPathname();
02230         if (!fullpath.startsWith("myth://", Qt::CaseInsensitive) || !checklocal)
02231             return fullpath;
02232 
02233         QUrl    url  = QUrl(fullpath);
02234         QString path = url.path();
02235         QString host = url.toString(QUrl::RemovePath).mid(7);
02236         QStringList list = host.split(":", QString::SkipEmptyParts);
02237         if (list.size())
02238         {
02239             host = list[0];
02240             list = host.split("@", QString::SkipEmptyParts);
02241             QString group;
02242             if (list.size() > 0 && list.size() < 3)
02243             {
02244                 host  = list.size() == 1 ? list[0]   : list[1];
02245                 group = list.size() == 1 ? QString() : list[0];
02246                 StorageGroup sg = StorageGroup(group, host);
02247                 QString local = sg.FindFile(path);
02248                 if (!local.isEmpty() && sg.FileExists(local))
02249                     return local;
02250             }
02251         }
02252         return fullpath;
02253     }
02254 
02255     QString tmpURL;
02256     if (checklocal)
02257     {
02258         // Check to see if the file exists locally
02259         StorageGroup sgroup(storagegroup);
02260 #if 0
02261         LOG(VB_FILE, LOG_DEBUG, LOC +
02262             QString("GetPlaybackURL: CHECKING SG : %1 : ").arg(tmpURL));
02263 #endif
02264         tmpURL = sgroup.FindFile(basename);
02265 
02266         if (!tmpURL.isEmpty())
02267         {
02268             LOG(VB_FILE, LOG_INFO, LOC +
02269                 QString("GetPlaybackURL: File is local: '%1'") .arg(tmpURL));
02270             return tmpURL;
02271         }
02272         else if (hostname == gCoreContext->GetHostName())
02273         {
02274             LOG(VB_GENERAL, LOG_ERR, LOC +
02275                 QString("GetPlaybackURL: '%1' should be local, but it can "
02276                         "not be found.").arg(basename));
02277             // Note do not preceed with "/" that will cause existing code
02278             // to look for a local file with this name...
02279             return QString("GetPlaybackURL/UNABLE/TO/FIND/LOCAL/FILE/ON/%1/%2")
02280                            .arg(hostname).arg(basename);
02281         }
02282     }
02283 
02284     // Check to see if we should stream from the master backend
02285     if ((checkMaster) &&
02286         (gCoreContext->GetNumSetting("MasterBackendOverride", 0)) &&
02287         (RemoteCheckFile(this, false)))
02288     {
02289         tmpURL = gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"),
02290                                           gCoreContext->GetSetting("MasterServerPort").toInt(),
02291                                           basename);
02292 
02293         LOG(VB_FILE, LOG_INFO, LOC +
02294             QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL));
02295         return tmpURL;
02296         }
02297 
02298     // Fallback to streaming from the backend the recording was created on
02299     tmpURL = gCoreContext->GenMythURL(gCoreContext->GetBackendServerIP(hostname),
02300                                       gCoreContext->GetSettingOnHost("BackendServerPort", hostname).toInt(),
02301                                       basename);
02302 
02303     LOG(VB_FILE, LOG_INFO, LOC +
02304         QString("GetPlaybackURL: Using default of: '%1'") .arg(tmpURL));
02305 
02306     return tmpURL;
02307 }
02308 
02312 void ProgramInfo::SaveFilesize(uint64_t fsize)
02313 {
02314     SetFilesize(fsize);
02315 
02316     MSqlQuery query(MSqlQuery::InitCon());
02317     query.prepare(
02318         "UPDATE recorded "
02319         "SET filesize = :FILESIZE "
02320         "WHERE chanid    = :CHANID AND "
02321         "      starttime = :STARTTIME");
02322     query.bindValue(":FILESIZE",  (quint64)fsize);
02323     query.bindValue(":CHANID",    chanid);
02324     query.bindValue(":STARTTIME", recstartts);
02325 
02326     if (!query.exec())
02327         MythDB::DBError("File size update", query);
02328 
02329     updater->insert(chanid, recstartts, kPIUpdateFileSize, fsize);
02330 }
02331 
02333 uint64_t ProgramInfo::QueryFilesize(void) const
02334 {
02335     MSqlQuery query(MSqlQuery::InitCon());
02336 
02337     query.prepare(
02338         "SELECT filesize "
02339         "FROM recorded "
02340         "WHERE chanid    = :CHANID AND "
02341         "      starttime = :STARTTIME");
02342     query.bindValue(":CHANID", chanid);
02343     query.bindValue(":STARTTIME", recstartts);
02344     if (query.exec() && query.next())
02345         return query.value(0).toULongLong();
02346 
02347     return filesize;
02348 }
02349 
02352 uint ProgramInfo::QueryMplexID(void) const
02353 {
02354     uint ret = 0U;
02355     if (chanid)
02356     {
02357         MSqlQuery query(MSqlQuery::InitCon());
02358 
02359         query.prepare("SELECT mplexid FROM channel "
02360                       "WHERE chanid = :CHANID");
02361         query.bindValue(":CHANID", chanid);
02362 
02363         if (!query.exec())
02364             MythDB::DBError("QueryMplexID", query);
02365         else if (query.next())
02366             ret = query.value(0).toUInt();
02367 
02368         // clear out bogus mplexid's
02369         ret = (32767 == ret) ? 0 : ret;
02370     }
02371 
02372     return ret;
02373 }
02374 
02377 void ProgramInfo::SaveBookmark(uint64_t frame)
02378 {
02379     ClearMarkupMap(MARK_BOOKMARK);
02380 
02381     bool is_valid = (frame > 0);
02382     if (is_valid)
02383     {
02384         frm_dir_map_t bookmarkmap;
02385         bookmarkmap[frame] = MARK_BOOKMARK;
02386         SaveMarkupMap(bookmarkmap);
02387     }
02388 
02389     if (IsRecording())
02390     {
02391         MSqlQuery query(MSqlQuery::InitCon());
02392         query.prepare(
02393             "UPDATE recorded "
02394             "SET bookmarkupdate = CURRENT_TIMESTAMP, "
02395             "    bookmark       = :BOOKMARKFLAG "
02396             "WHERE chanid    = :CHANID AND "
02397             "      starttime = :STARTTIME");
02398 
02399         query.bindValue(":BOOKMARKFLAG", is_valid);
02400         query.bindValue(":CHANID",       chanid);
02401         query.bindValue(":STARTTIME",    recstartts);
02402 
02403         if (!query.exec())
02404             MythDB::DBError("bookmark flag update", query);
02405     }
02406 
02407     set_flag(programflags, FL_BOOKMARK, is_valid);
02408 
02409     SendUpdateEvent();
02410 }
02411 
02412 void ProgramInfo::SendUpdateEvent(void)
02413 {
02414     updater->insert(chanid, recstartts, kPIUpdate);
02415 }
02416 
02417 void ProgramInfo::SendAddedEvent(void) const
02418 {
02419     updater->insert(chanid, recstartts, kPIAdd);
02420 }
02421 
02422 void ProgramInfo::SendDeletedEvent(void) const
02423 {
02424     updater->insert(chanid, recstartts, kPIDelete);
02425 }
02426 
02430 QDateTime ProgramInfo::QueryBookmarkTimeStamp(void) const
02431 {
02432     MSqlQuery query(MSqlQuery::InitCon());
02433     query.prepare(
02434         "SELECT bookmarkupdate "
02435         "FROM recorded "
02436         "WHERE chanid    = :CHANID AND"
02437         "      starttime = :STARTTIME");
02438     query.bindValue(":CHANID",    chanid);
02439     query.bindValue(":STARTTIME", recstartts);
02440 
02441     QDateTime ts;
02442 
02443     if (!query.exec())
02444         MythDB::DBError("ProgramInfo::GetBookmarkTimeStamp()", query);
02445     else if (query.next())
02446         ts = query.value(0).toDateTime();
02447 
02448     return ts;
02449 }
02450 
02457 uint64_t ProgramInfo::QueryBookmark(void) const
02458 {
02459     if (programflags & FL_IGNOREBOOKMARK)
02460         return 0;
02461 
02462     frm_dir_map_t bookmarkmap;
02463     QueryMarkupMap(bookmarkmap, MARK_BOOKMARK);
02464 
02465     return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
02466 }
02467 
02468 uint64_t ProgramInfo::QueryBookmark(uint chanid, const QDateTime &recstartts)
02469 {
02470     frm_dir_map_t bookmarkmap;
02471     QueryMarkupMap(
02472         chanid, recstartts,
02473         bookmarkmap, MARK_BOOKMARK);
02474 
02475     return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
02476 }
02477 
02483 QStringList ProgramInfo::QueryDVDBookmark(
02484     const QString &serialid) const
02485 {
02486     QStringList fields = QStringList();
02487     MSqlQuery query(MSqlQuery::InitCon());
02488 
02489     if (!(programflags & FL_IGNOREBOOKMARK))
02490     {
02491         query.prepare(" SELECT title, framenum, audionum, subtitlenum "
02492                         " FROM dvdbookmark "
02493                         " WHERE serialid = :SERIALID ");
02494         query.bindValue(":SERIALID", serialid);
02495 
02496         if (query.exec() && query.next())
02497         {
02498             for(int i = 0; i < 4; i++)
02499                 fields.append(query.value(i).toString());
02500         }
02501     }
02502 
02503     return fields;
02504 }
02505 
02506 void ProgramInfo::SaveDVDBookmark(const QStringList &fields) const
02507 {
02508     QStringList::const_iterator it = fields.begin();
02509     MSqlQuery query(MSqlQuery::InitCon());
02510 
02511     QString serialid    = *(it);
02512     QString name        = *(++it);
02513     QString title       = *(++it);
02514     QString audionum    = *(++it);
02515     QString subtitlenum = *(++it);
02516     QString frame       = *(++it);
02517 
02518     query.prepare("INSERT IGNORE INTO dvdbookmark "
02519                     " (serialid, name)"
02520                     " VALUES ( :SERIALID, :NAME );");
02521     query.bindValue(":SERIALID", serialid);
02522     query.bindValue(":NAME", name);
02523 
02524     if (!query.exec())
02525         MythDB::DBError("SetDVDBookmark inserting", query);
02526 
02527     query.prepare(" UPDATE dvdbookmark "
02528                     " SET title       = :TITLE , "
02529                     "     audionum    = :AUDIONUM , "
02530                     "     subtitlenum = :SUBTITLENUM , "
02531                     "     framenum    = :FRAMENUM , "
02532                     "     timestamp   = NOW() "
02533                     " WHERE serialid = :SERIALID");
02534     query.bindValue(":TITLE",title);
02535     query.bindValue(":AUDIONUM",audionum);
02536     query.bindValue(":SUBTITLENUM",subtitlenum);
02537     query.bindValue(":FRAMENUM",frame);
02538     query.bindValue(":SERIALID",serialid);
02539 
02540     if (!query.exec())
02541         MythDB::DBError("SetDVDBookmark updating", query);
02542 }
02543 
02549 QString ProgramInfo::QueryCategoryType(void) const
02550 {
02551     QString ret;
02552 
02553     MSqlQuery query(MSqlQuery::InitCon());
02554 
02555     query.prepare(" SELECT category_type "
02556                   " FROM recordedprogram "
02557                   " WHERE chanid = :CHANID "
02558                   " AND starttime = :STARTTIME;");
02559 
02560     query.bindValue(":CHANID", chanid);
02561     query.bindValue(":STARTTIME", startts);
02562 
02563     if (query.exec() && query.next())
02564     {
02565         ret = query.value(0).toString();
02566     }
02567 
02568     return ret;
02569 }
02570 
02572 void ProgramInfo::SaveWatched(bool watched)
02573 {
02574     if (IsRecording())
02575     {
02576         MSqlQuery query(MSqlQuery::InitCon());
02577 
02578         query.prepare("UPDATE recorded"
02579                     " SET watched = :WATCHEDFLAG"
02580                     " WHERE chanid = :CHANID"
02581                     " AND starttime = :STARTTIME ;");
02582         query.bindValue(":CHANID", chanid);
02583         query.bindValue(":STARTTIME", recstartts);
02584         query.bindValue(":WATCHEDFLAG", watched);
02585 
02586         if (!query.exec())
02587             MythDB::DBError("Set watched flag", query);
02588         else
02589             UpdateLastDelete(watched);
02590     }
02591     else if (IsVideoFile())
02592     {
02593         QString url = pathname;
02594         if (url.startsWith("myth://"))
02595         {
02596             url = QUrl(url).path();
02597             url.remove(0,1);
02598         }
02599 
02600         MSqlQuery query(MSqlQuery::InitCon());
02601         query.prepare("UPDATE videometadata"
02602                     " SET watched = :WATCHEDFLAG"
02603                     " WHERE title = :TITLE"
02604                     " AND subtitle = :SUBTITLE"
02605                     " AND filename = :FILENAME ;");
02606         query.bindValue(":TITLE", title);
02607         query.bindValue(":SUBTITLE", subtitle);
02608         query.bindValue(":FILENAME", url);
02609         query.bindValue(":WATCHEDFLAG", watched);
02610 
02611         if (!query.exec())
02612             MythDB::DBError("Set watched flag", query);
02613     }
02614 
02615     set_flag(programflags, FL_WATCHED, watched);
02616     SendUpdateEvent();
02617 }
02618 
02623 bool ProgramInfo::QueryIsEditing(void) const
02624 {
02625     bool editing = programflags & FL_REALLYEDITING;
02626 
02627     MSqlQuery query(MSqlQuery::InitCon());
02628 
02629     query.prepare("SELECT editing FROM recorded"
02630                   " WHERE chanid = :CHANID"
02631                   " AND starttime = :STARTTIME ;");
02632     query.bindValue(":CHANID", chanid);
02633     query.bindValue(":STARTTIME", recstartts);
02634 
02635     if (query.exec() && query.next())
02636         editing = query.value(0).toBool();
02637 
02638     /*
02639     set_flag(programflags, FL_REALLYEDITING, editing);
02640     set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
02641                                         (programflags & COMM_FLAG_PROCESSING)));
02642     */
02643     return editing;
02644 }
02645 
02649 void ProgramInfo::SaveEditing(bool edit)
02650 {
02651     MSqlQuery query(MSqlQuery::InitCon());
02652 
02653     query.prepare("UPDATE recorded"
02654                   " SET editing = :EDIT"
02655                   " WHERE chanid = :CHANID"
02656                   " AND starttime = :STARTTIME ;");
02657     query.bindValue(":EDIT", edit);
02658     query.bindValue(":CHANID", chanid);
02659     query.bindValue(":STARTTIME", recstartts);
02660 
02661     if (!query.exec())
02662         MythDB::DBError("Edit status update", query);
02663 
02664     set_flag(programflags, FL_REALLYEDITING, edit);
02665     set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
02666                                         (programflags & COMM_FLAG_PROCESSING)));
02667 
02668     SendUpdateEvent();
02669 }
02670 
02674 void ProgramInfo::SaveDeletePendingFlag(bool deleteFlag)
02675 {
02676     MSqlQuery query(MSqlQuery::InitCon());
02677 
02678     query.prepare("UPDATE recorded"
02679                   " SET deletepending = :DELETEFLAG, "
02680                   "     duplicate = 0 "
02681                   " WHERE chanid = :CHANID"
02682                   " AND starttime = :STARTTIME ;");
02683     query.bindValue(":CHANID", chanid);
02684     query.bindValue(":STARTTIME", recstartts);
02685     query.bindValue(":DELETEFLAG", deleteFlag);
02686 
02687     if (!query.exec())
02688         MythDB::DBError("SaveDeletePendingFlag", query);
02689 
02690     set_flag(programflags, FL_DELETEPENDING, deleteFlag);
02691 
02692     if (!deleteFlag)
02693         SendAddedEvent();
02694 
02695     SendUpdateEvent();
02696 }
02697 
02702 bool ProgramInfo::QueryIsInUse(QStringList &byWho) const
02703 {
02704     if (!IsRecording())
02705         return false;
02706 
02707     QDateTime oneHourAgo = QDateTime::currentDateTime().addSecs(-61 * 60);
02708     MSqlQuery query(MSqlQuery::InitCon());
02709 
02710     query.prepare("SELECT hostname, recusage FROM inuseprograms "
02711                   " WHERE chanid = :CHANID"
02712                   " AND starttime = :STARTTIME "
02713                   " AND lastupdatetime > :ONEHOURAGO ;");
02714     query.bindValue(":CHANID", chanid);
02715     query.bindValue(":STARTTIME", recstartts);
02716     query.bindValue(":ONEHOURAGO", oneHourAgo);
02717 
02718     byWho.clear();
02719     if (query.exec() && query.size() > 0)
02720     {
02721         QString usageStr, recusage;
02722         while (query.next())
02723         {
02724             usageStr = QObject::tr("Unknown");
02725             recusage = query.value(1).toString();
02726 
02727             if (recusage == kPlayerInUseID)
02728                 usageStr = QObject::tr("Playing");
02729             else if (recusage == kPIPPlayerInUseID)
02730                 usageStr = QObject::tr("PIP");
02731             else if (recusage == kPBPPlayerInUseID)
02732                 usageStr = QObject::tr("PBP");
02733             else if ((recusage == kRecorderInUseID) ||
02734                      (recusage == kImportRecorderInUseID))
02735                 usageStr = QObject::tr("Recording");
02736             else if (recusage == kFileTransferInUseID)
02737                 usageStr = QObject::tr("File transfer");
02738             else if (recusage == kTruncatingDeleteInUseID)
02739                 usageStr = QObject::tr("Delete");
02740             else if (recusage == kFlaggerInUseID)
02741                 usageStr = QObject::tr("Commercial Detection");
02742             else if (recusage == kTranscoderInUseID)
02743                 usageStr = QObject::tr("Transcoding");
02744             else if (recusage == kPreviewGeneratorInUseID)
02745                 usageStr = QObject::tr("Preview Generation");
02746             else if (recusage == kJobQueueInUseID)
02747                 usageStr = QObject::tr("User Job");
02748 
02749             byWho.push_back(recusage);
02750             byWho.push_back(query.value(0).toString());
02751             byWho.push_back(query.value(0).toString() + " (" + usageStr + ")");
02752         }
02753 
02754         return true;
02755     }
02756 
02757     return false;
02758 }
02759 
02764 bool ProgramInfo::QueryIsInUse(QString &byWho) const
02765 {
02766     QStringList users;
02767     bool inuse = QueryIsInUse(users);
02768     byWho.clear();
02769     for (uint i = 0; i+2 < (uint)users.size(); i+=3)
02770         byWho += users[i+2] + "\n";
02771     return inuse;
02772 }
02773 
02774 
02781 bool ProgramInfo::QueryIsDeleteCandidate(bool one_playback_allowed) const
02782 {
02783     if (!IsRecording())
02784         return false;
02785 
02786     // gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0) &&
02787     if (GetRecordingGroup() != "Deleted" && GetRecordingGroup() != "LiveTV")
02788         return true;
02789 
02790     bool ok = true;
02791     QStringList byWho;
02792     if (QueryIsInUse(byWho) && !byWho.isEmpty())
02793     {
02794         uint play_cnt = 0, ft_cnt = 0, jq_cnt = 0;
02795         for (uint i = 0; (i+2 < (uint)byWho.size()) && ok; i+=3)
02796         {
02797             play_cnt += byWho[i].contains(kPlayerInUseID) ? 1 : 0;
02798             ft_cnt += (byWho[i].contains(kFlaggerInUseID) ||
02799                        byWho[i].contains(kTranscoderInUseID)) ? 1 : 0;
02800             jq_cnt += (byWho[i].contains(kJobQueueInUseID)) ? 1 : 0;
02801             ok = ok && (byWho[i].contains(kRecorderInUseID)   ||
02802                         byWho[i].contains(kFlaggerInUseID)    ||
02803                         byWho[i].contains(kTranscoderInUseID) ||
02804                         byWho[i].contains(kJobQueueInUseID)   ||
02805                         (one_playback_allowed && (play_cnt <= 1)));
02806         }
02807         ok = ok && (ft_cnt == jq_cnt);
02808     }
02809 
02810     return ok;
02811 }
02812 
02814 TranscodingStatus ProgramInfo::QueryTranscodeStatus(void) const
02815 {
02816     MSqlQuery query(MSqlQuery::InitCon());
02817 
02818     query.prepare("SELECT transcoded FROM recorded"
02819                  " WHERE chanid = :CHANID"
02820                  " AND starttime = :STARTTIME ;");
02821     query.bindValue(":CHANID", chanid);
02822     query.bindValue(":STARTTIME", recstartts);
02823 
02824     if (query.exec() && query.next())
02825         return (TranscodingStatus) query.value(0).toUInt();
02826     return TRANSCODING_NOT_TRANSCODED;
02827 }
02828 
02834 void ProgramInfo::SaveTranscodeStatus(TranscodingStatus trans)
02835 {
02836     MSqlQuery query(MSqlQuery::InitCon());
02837 
02838     query.prepare(
02839         "UPDATE recorded "
02840         "SET transcoded = :VALUE "
02841         "WHERE chanid    = :CHANID AND"
02842         "      starttime = :STARTTIME");
02843     query.bindValue(":VALUE", (uint)trans);
02844     query.bindValue(":CHANID", chanid);
02845     query.bindValue(":STARTTIME", recstartts);
02846 
02847     if (!query.exec())
02848         MythDB::DBError("Transcoded status update", query);
02849 
02850     set_flag(programflags, FL_TRANSCODED, TRANSCODING_COMPLETE == trans);
02851     SendUpdateEvent();
02852 }
02853 
02857 void ProgramInfo::SaveCommFlagged(CommFlagStatus flag)
02858 {
02859     MSqlQuery query(MSqlQuery::InitCon());
02860 
02861     query.prepare("UPDATE recorded"
02862                   " SET commflagged = :FLAG"
02863                   " WHERE chanid = :CHANID"
02864                   " AND starttime = :STARTTIME ;");
02865     query.bindValue(":FLAG", (int)flag);
02866     query.bindValue(":CHANID", chanid);
02867     query.bindValue(":STARTTIME", recstartts);
02868 
02869     if (!query.exec())
02870         MythDB::DBError("Commercial Flagged status update", query);
02871 
02872     set_flag(programflags, FL_COMMFLAG,       COMM_FLAG_DONE == flag);
02873     set_flag(programflags, FL_COMMPROCESSING, COMM_FLAG_PROCESSING == flag);
02874     set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
02875                                         (programflags & COMM_FLAG_PROCESSING)));
02876     SendUpdateEvent();
02877 }
02878 
02879 
02883 void ProgramInfo::SavePreserve(bool preserveEpisode)
02884 {
02885     MSqlQuery query(MSqlQuery::InitCon());
02886 
02887     query.prepare("UPDATE recorded"
02888                   " SET preserve = :PRESERVE"
02889                   " WHERE chanid = :CHANID"
02890                   " AND starttime = :STARTTIME ;");
02891     query.bindValue(":PRESERVE", preserveEpisode);
02892     query.bindValue(":CHANID", chanid);
02893     query.bindValue(":STARTTIME", recstartts);
02894 
02895     if (!query.exec())
02896         MythDB::DBError("PreserveEpisode update", query);
02897     else
02898         UpdateLastDelete(false);
02899 
02900     set_flag(programflags, FL_PRESERVED, preserveEpisode);
02901 
02902     SendUpdateEvent();
02903 }
02904 
02910 void ProgramInfo::SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete)
02911 {
02912     MSqlQuery query(MSqlQuery::InitCon());
02913 
02914     query.prepare("UPDATE recorded"
02915                   " SET autoexpire = :AUTOEXPIRE"
02916                   " WHERE chanid = :CHANID"
02917                   " AND starttime = :STARTTIME ;");
02918     query.bindValue(":AUTOEXPIRE", (uint)autoExpire);
02919     query.bindValue(":CHANID", chanid);
02920     query.bindValue(":STARTTIME", recstartts);
02921 
02922     if (!query.exec())
02923         MythDB::DBError("AutoExpire update", query);
02924     else if (updateDelete)
02925         UpdateLastDelete(true);
02926 
02927     set_flag(programflags, FL_AUTOEXP, (uint)autoExpire);
02928 
02929     SendUpdateEvent();
02930 }
02931 
02936 void ProgramInfo::UpdateLastDelete(bool setTime) const
02937 {
02938     MSqlQuery query(MSqlQuery::InitCon());
02939 
02940     if (setTime)
02941     {
02942         QDateTime timeNow = QDateTime::currentDateTime();
02943         int delay = recstartts.secsTo(timeNow) / 3600;
02944 
02945         if (delay > 200)
02946             delay = 200;
02947         else if (delay < 1)
02948             delay = 1;
02949 
02950         query.prepare("UPDATE record SET last_delete = :TIME, "
02951                       "avg_delay = (avg_delay * 3 + :DELAY) / 4 "
02952                       "WHERE recordid = :RECORDID");
02953         query.bindValue(":TIME", timeNow);
02954         query.bindValue(":DELAY", delay);
02955     }
02956     else
02957     {
02958         query.prepare("UPDATE record SET last_delete = '0000-00-00 00:00:00' "
02959                       "WHERE recordid = :RECORDID");
02960     }
02961     query.bindValue(":RECORDID", recordid);
02962 
02963     if (!query.exec())
02964         MythDB::DBError("Update last_delete", query);
02965 }
02966 
02968 AutoExpireType ProgramInfo::QueryAutoExpire(void) const
02969 {
02970     MSqlQuery query(MSqlQuery::InitCon());
02971 
02972     query.prepare("SELECT autoexpire FROM recorded"
02973                   " WHERE chanid = :CHANID"
02974                   " AND starttime = :STARTTIME ;");
02975     query.bindValue(":CHANID", chanid);
02976     query.bindValue(":STARTTIME", recstartts);
02977 
02978     if (query.exec() && query.next())
02979         return (AutoExpireType) query.value(0).toInt();
02980 
02981     return kDisableAutoExpire;
02982 }
02983 
02984 bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
02985 {
02986     frm_dir_map_t autosaveMap;
02987     QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
02988     QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
02989     QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
02990     bool result = !autosaveMap.isEmpty();
02991 
02992     if (loadAutoSave)
02993     {
02994         // Convert the temporary marks into regular marks.
02995         delMap.clear();
02996         frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
02997         for (; i != autosaveMap.constEnd(); ++i)
02998         {
02999             uint64_t frame = i.key();
03000             MarkTypes mark = i.value();
03001             if (mark == MARK_TMP_CUT_START)
03002                 mark = MARK_CUT_START;
03003             else if (mark == MARK_TMP_CUT_END)
03004                 mark = MARK_CUT_END;
03005             delMap[frame] = mark;
03006         }
03007     }
03008     else
03009     {
03010         QueryMarkupMap(delMap, MARK_CUT_START);
03011         QueryMarkupMap(delMap, MARK_CUT_END, true);
03012         QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
03013     }
03014 
03015     return result;
03016 }
03017 
03018 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
03019 {
03020     if (!isAutoSave)
03021     {
03022         ClearMarkupMap(MARK_CUT_START);
03023         ClearMarkupMap(MARK_CUT_END);
03024     }
03025     ClearMarkupMap(MARK_PLACEHOLDER);
03026     ClearMarkupMap(MARK_TMP_CUT_START);
03027     ClearMarkupMap(MARK_TMP_CUT_END);
03028 
03029     frm_dir_map_t tmpDelMap;
03030     frm_dir_map_t::const_iterator i = delMap.constBegin();
03031     for (; i != delMap.constEnd(); ++i)
03032     {
03033         uint64_t frame = i.key();
03034         MarkTypes mark = i.value();
03035         if (isAutoSave)
03036         {
03037             if (mark == MARK_CUT_START)
03038                 mark = MARK_TMP_CUT_START;
03039             else if (mark == MARK_CUT_END)
03040                 mark = MARK_TMP_CUT_END;
03041         }
03042         tmpDelMap[frame] = mark;
03043     }
03044     SaveMarkupMap(tmpDelMap);
03045 
03046     if (IsRecording())
03047     {
03048         MSqlQuery query(MSqlQuery::InitCon());
03049 
03050         // Flag the existence of a cutlist
03051         query.prepare("UPDATE recorded"
03052                       " SET cutlist = :CUTLIST"
03053                       " WHERE chanid = :CHANID"
03054                       " AND starttime = :STARTTIME ;");
03055 
03056         query.bindValue(":CUTLIST", delMap.isEmpty() ? 0 : 1);
03057         query.bindValue(":CHANID", chanid);
03058         query.bindValue(":STARTTIME", recstartts);
03059 
03060         if (!query.exec())
03061             MythDB::DBError("cutlist flag update", query);
03062     }
03063 }
03064 
03065 void ProgramInfo::SaveCommBreakList(frm_dir_map_t &frames) const
03066 {
03067     ClearMarkupMap(MARK_COMM_START);
03068     ClearMarkupMap(MARK_COMM_END);
03069     SaveMarkupMap(frames);
03070 }
03071 
03072 void ProgramInfo::QueryCommBreakList(frm_dir_map_t &frames) const
03073 {
03074     QueryMarkupMap(frames, MARK_COMM_START);
03075     QueryMarkupMap(frames, MARK_COMM_END, true);
03076 }
03077 
03078 void ProgramInfo::ClearMarkupMap(
03079     MarkTypes type, int64_t min_frame, int64_t max_frame) const
03080 {
03081     MSqlQuery query(MSqlQuery::InitCon());
03082     QString comp;
03083 
03084     if (min_frame >= 0)
03085         comp += QString(" AND mark >= %1 ").arg(min_frame);
03086 
03087     if (max_frame >= 0)
03088         comp += QString(" AND mark <= %1 ").arg(max_frame);
03089 
03090     if (type != MARK_ALL)
03091         comp += QString(" AND type = :TYPE ");
03092 
03093     if (IsVideo())
03094     {
03095         query.prepare("DELETE FROM filemarkup"
03096                       " WHERE filename = :PATH "
03097                       + comp + ";");
03098         query.bindValue(":PATH", StorageGroup::GetRelativePathname(pathname));
03099     }
03100     else if (IsRecording())
03101     {
03102         query.prepare("DELETE FROM recordedmarkup"
03103                       " WHERE chanid = :CHANID"
03104                       " AND STARTTIME = :STARTTIME"
03105                       + comp + ';');
03106         query.bindValue(":CHANID", chanid);
03107         query.bindValue(":STARTTIME", recstartts);
03108     }
03109     else
03110     {
03111         return;
03112     }
03113     query.bindValue(":TYPE", type);
03114 
03115     if (!query.exec())
03116         MythDB::DBError("ClearMarkupMap deleting", query);
03117 }
03118 
03119 void ProgramInfo::SaveMarkupMap(
03120     const frm_dir_map_t &marks, MarkTypes type,
03121     int64_t min_frame, int64_t max_frame) const
03122 {
03123     MSqlQuery query(MSqlQuery::InitCon());
03124     QString videoPath;
03125 
03126     if (IsVideo())
03127     {
03128        videoPath = StorageGroup::GetRelativePathname(pathname);
03129     }
03130     else if (IsRecording())
03131     {
03132         // check to make sure the show still exists before saving markups
03133         query.prepare("SELECT starttime FROM recorded"
03134                       " WHERE chanid = :CHANID"
03135                       " AND starttime = :STARTTIME ;");
03136         query.bindValue(":CHANID", chanid);
03137         query.bindValue(":STARTTIME", recstartts);
03138 
03139         if (!query.exec())
03140             MythDB::DBError("SaveMarkupMap checking record table", query);
03141 
03142         if (!query.next())
03143             return;
03144     }
03145     else
03146     {
03147         return;
03148     }
03149 
03150     frm_dir_map_t::const_iterator it;
03151     for (it = marks.begin(); it != marks.end(); ++it)
03152     {
03153         uint64_t frame = it.key();
03154         int mark_type;
03155         QString querystr;
03156 
03157         if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
03158             continue;
03159 
03160         if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
03161             continue;
03162 
03163         mark_type = (type != MARK_ALL) ? type : *it;
03164 
03165         if (IsVideo())
03166         {
03167             query.prepare("INSERT INTO filemarkup (filename, mark, type)"
03168                           " VALUES ( :PATH , :MARK , :TYPE );");
03169             query.bindValue(":PATH", videoPath);
03170         }
03171         else // if (IsRecording())
03172         {
03173             query.prepare("INSERT INTO recordedmarkup"
03174                           " (chanid, starttime, mark, type)"
03175                           " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE );");
03176             query.bindValue(":CHANID", chanid);
03177             query.bindValue(":STARTTIME", recstartts);
03178         }
03179         query.bindValue(":MARK", (quint64)frame);
03180         query.bindValue(":TYPE", mark_type);
03181 
03182         if (!query.exec())
03183             MythDB::DBError("SaveMarkupMap inserting", query);
03184     }
03185 }
03186 
03187 void ProgramInfo::QueryMarkupMap(
03188     frm_dir_map_t &marks, MarkTypes type, bool merge) const
03189 {
03190     if (!merge)
03191         marks.clear();
03192 
03193     if (IsVideo())
03194     {
03195         QueryMarkupMap(StorageGroup::GetRelativePathname(pathname),
03196                        marks, type, merge);
03197     }
03198     else if (IsRecording())
03199     {
03200         QueryMarkupMap(chanid, recstartts, marks, type, merge);
03201     }
03202 }
03203 
03204 void ProgramInfo::QueryMarkupMap(
03205     const QString &video_pathname,
03206     frm_dir_map_t &marks,
03207     MarkTypes type, bool mergeIntoMap)
03208 {
03209     if (!mergeIntoMap)
03210         marks.clear();
03211 
03212     MSqlQuery query(MSqlQuery::InitCon());
03213 
03214     query.prepare("SELECT mark, type "
03215                   "FROM filemarkup "
03216                   "WHERE filename = :PATH AND "
03217                   "      type     = :TYPE "
03218                   "ORDER BY mark");
03219     query.bindValue(":PATH", video_pathname);
03220     query.bindValue(":TYPE", type);
03221 
03222     if (!query.exec())
03223     {
03224         MythDB::DBError("QueryMarkupMap", query);
03225         return;
03226     }
03227 
03228     while (query.next())
03229     {
03230         marks[query.value(0).toLongLong()] =
03231             (MarkTypes) query.value(1).toInt();
03232     }
03233 }
03234 
03235 void ProgramInfo::QueryMarkupMap(
03236     uint chanid, const QDateTime &recstartts,
03237     frm_dir_map_t &marks,
03238     MarkTypes type, bool mergeIntoMap)
03239 {
03240     if (!mergeIntoMap)
03241         marks.clear();
03242 
03243     MSqlQuery query(MSqlQuery::InitCon());
03244     query.prepare("SELECT mark, type "
03245                   "FROM recordedmarkup "
03246                   "WHERE chanid    = :CHANID AND "
03247                   "      starttime = :STARTTIME AND"
03248                   "      type      = :TYPE "
03249                   "ORDER BY mark");
03250     query.bindValue(":CHANID", chanid);
03251     query.bindValue(":STARTTIME", recstartts);
03252     query.bindValue(":TYPE", type);
03253 
03254     if (!query.exec())
03255     {
03256         MythDB::DBError("QueryMarkupMap", query);
03257         return;
03258     }
03259 
03260     while (query.next())
03261     {
03262         marks[query.value(0).toULongLong()] =
03263             (MarkTypes) query.value(1).toInt();
03264     }
03265 }
03266 
03268 bool ProgramInfo::QueryMarkupFlag(MarkTypes type) const
03269 {
03270     frm_dir_map_t flagMap;
03271 
03272     QueryMarkupMap(flagMap, type);
03273 
03274     return flagMap.contains(0);
03275 }
03276 
03278 void ProgramInfo::SaveMarkupFlag(MarkTypes type) const
03279 {
03280     ClearMarkupMap(type);
03281     frm_dir_map_t flagMap;
03282     flagMap[0] = type;
03283     SaveMarkupMap(flagMap, type);
03284 }
03285 
03286 void ProgramInfo::QueryPositionMap(
03287     frm_pos_map_t &posMap, MarkTypes type) const
03288 {
03289     if (positionMapDBReplacement)
03290     {
03291         QMutexLocker locker(positionMapDBReplacement->lock);
03292         posMap = positionMapDBReplacement->map[(MarkTypes)type];
03293 
03294         return;
03295     }
03296 
03297     posMap.clear();
03298     MSqlQuery query(MSqlQuery::InitCon());
03299 
03300     if (IsVideo())
03301     {
03302         query.prepare("SELECT mark, offset FROM filemarkup"
03303                       " WHERE filename = :PATH"
03304                       " AND type = :TYPE ;");
03305         query.bindValue(":PATH", StorageGroup::GetRelativePathname(pathname));
03306     }
03307     else if (IsRecording())
03308     {
03309         query.prepare("SELECT mark, offset FROM recordedseek"
03310                       " WHERE chanid = :CHANID"
03311                       " AND starttime = :STARTTIME"
03312                       " AND type = :TYPE ;");
03313         query.bindValue(":CHANID", chanid);
03314         query.bindValue(":STARTTIME", recstartts);
03315     }
03316     else
03317     {
03318         return;
03319     }
03320     query.bindValue(":TYPE", type);
03321 
03322     if (!query.exec())
03323     {
03324         MythDB::DBError("QueryPositionMap", query);
03325         return;
03326     }
03327 
03328     while (query.next())
03329         posMap[query.value(0).toULongLong()] = query.value(1).toULongLong();
03330 }
03331 
03332 void ProgramInfo::ClearPositionMap(MarkTypes type) const
03333 {
03334     if (positionMapDBReplacement)
03335     {
03336         QMutexLocker locker(positionMapDBReplacement->lock);
03337         positionMapDBReplacement->map.clear();
03338         return;
03339     }
03340 
03341     MSqlQuery query(MSqlQuery::InitCon());
03342 
03343     if (IsVideo())
03344     {
03345         query.prepare("DELETE FROM filemarkup"
03346                       " WHERE filename = :PATH"
03347                       " AND type = :TYPE ;");
03348         query.bindValue(":PATH", StorageGroup::GetRelativePathname(pathname));
03349     }
03350     else if (IsRecording())
03351     {
03352         query.prepare("DELETE FROM recordedseek"
03353                       " WHERE chanid = :CHANID"
03354                       " AND starttime = :STARTTIME"
03355                       " AND type = :TYPE ;");
03356         query.bindValue(":CHANID", chanid);
03357         query.bindValue(":STARTTIME", recstartts);
03358     }
03359     else
03360     {
03361         return;
03362     }
03363 
03364     query.bindValue(":TYPE", type);
03365 
03366     if (!query.exec())
03367         MythDB::DBError("clear position map", query);
03368 }
03369 
03370 void ProgramInfo::SavePositionMap(
03371     frm_pos_map_t &posMap, MarkTypes type,
03372     int64_t min_frame, int64_t max_frame) const
03373 {
03374     if (positionMapDBReplacement)
03375     {
03376         QMutexLocker locker(positionMapDBReplacement->lock);
03377 
03378         if ((min_frame >= 0) || (max_frame >= 0))
03379         {
03380             frm_pos_map_t::const_iterator it, it_end;
03381             it     = positionMapDBReplacement->map[(MarkTypes)type].begin();
03382             it_end = positionMapDBReplacement->map[(MarkTypes)type].end();
03383 
03384             frm_pos_map_t new_map;
03385             for (; it != it_end; ++it)
03386             {
03387                 uint64_t frame = it.key();
03388                 if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
03389                     continue;
03390                 if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
03391                     continue;
03392                 new_map.insert(it.key(), *it);
03393             }
03394             positionMapDBReplacement->map[(MarkTypes)type] = new_map;
03395         }
03396         else
03397         {
03398             positionMapDBReplacement->map[(MarkTypes)type].clear();
03399         }
03400 
03401         frm_pos_map_t::const_iterator it     = posMap.begin();
03402         frm_pos_map_t::const_iterator it_end = posMap.end();
03403         for (; it != it_end; ++it)
03404         {
03405             uint64_t frame = it.key();
03406             if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
03407                 continue;
03408             if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
03409                 continue;
03410 
03411             positionMapDBReplacement->map[(MarkTypes)type]
03412                 .insert(frame, *it);
03413         }
03414 
03415         return;
03416     }
03417 
03418     MSqlQuery query(MSqlQuery::InitCon());
03419     QString comp;
03420 
03421     if (min_frame >= 0)
03422         comp += " AND mark >= :MIN_FRAME ";
03423     if (max_frame >= 0)
03424         comp += " AND mark <= :MAX_FRAME ";
03425 
03426     QString videoPath;
03427     if (IsVideo())
03428     {
03429         videoPath = StorageGroup::GetRelativePathname(pathname);
03430 
03431         query.prepare("DELETE FROM filemarkup"
03432                       " WHERE filename = :PATH"
03433                       " AND type = :TYPE"
03434                       + comp + ';');
03435         query.bindValue(":PATH", videoPath);
03436     }
03437     else if (IsRecording())
03438     {
03439         query.prepare("DELETE FROM recordedseek"
03440                       " WHERE chanid = :CHANID"
03441                       " AND starttime = :STARTTIME"
03442                       " AND type = :TYPE"
03443                       + comp + ';');
03444         query.bindValue(":CHANID", chanid);
03445         query.bindValue(":STARTTIME", recstartts);
03446     }
03447     else
03448     {
03449         return;
03450     }
03451 
03452     query.bindValue(":TYPE", type);
03453     if (min_frame >= 0)
03454         query.bindValue(":MIN_FRAME", (quint64)min_frame);
03455     if (max_frame >= 0)
03456         query.bindValue(":MAX_FRAME", (quint64)max_frame);
03457 
03458     if (!query.exec())
03459         MythDB::DBError("position map clear", query);
03460 
03461     if (IsVideo())
03462     {
03463         query.prepare(
03464             "INSERT INTO "
03465             "filemarkup (filename, mark, type, offset) "
03466             "VALUES ( :PATH , :MARK , :TYPE , :OFFSET )");
03467         query.bindValue(":PATH", videoPath);
03468     }
03469     else // if (IsRecording())
03470     {
03471         query.prepare(
03472             "INSERT INTO "
03473             "recordedseek (chanid, starttime, mark, type, offset) "
03474             " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE , :OFFSET )");
03475         query.bindValue(":CHANID",    chanid);
03476         query.bindValue(":STARTTIME", recstartts);
03477     }
03478     query.bindValue(":TYPE", type);
03479 
03480     frm_pos_map_t::iterator it;
03481     for (it = posMap.begin(); it != posMap.end(); ++it)
03482     {
03483         uint64_t frame = it.key();
03484 
03485         if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
03486             continue;
03487 
03488         if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
03489             continue;
03490 
03491         uint64_t offset = *it;
03492 
03493         query.bindValue(":MARK",   (quint64)frame);
03494         query.bindValue(":OFFSET", (quint64)offset);
03495 
03496         if (!query.exec())
03497         {
03498             MythDB::DBError("position map insert", query);
03499             break;
03500         }
03501     }
03502 }
03503 
03504 void ProgramInfo::SavePositionMapDelta(
03505     frm_pos_map_t &posMap, MarkTypes type) const
03506 {
03507     if (positionMapDBReplacement)
03508     {
03509         QMutexLocker locker(positionMapDBReplacement->lock);
03510 
03511         frm_pos_map_t::const_iterator it     = posMap.begin();
03512         frm_pos_map_t::const_iterator it_end = posMap.end();
03513         for (; it != it_end; ++it)
03514             positionMapDBReplacement->map[type].insert(it.key(), *it);
03515 
03516         return;
03517     }
03518 
03519     MSqlQuery query(MSqlQuery::InitCon());
03520 
03521     if (IsVideo())
03522     {
03523         query.prepare(
03524             "INSERT INTO "
03525             "filemarkup (filename, mark, type, offset) "
03526             "VALUES ( :PATH , :MARK , :TYPE , :OFFSET )");
03527         query.bindValue(":PATH", StorageGroup::GetRelativePathname(pathname));
03528     }
03529     else if (IsRecording())
03530     {
03531         query.prepare(
03532             "INSERT INTO "
03533             "recordedseek (chanid, starttime, mark, type, offset) "
03534             " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE , :OFFSET )");
03535         query.bindValue(":CHANID",    chanid);
03536         query.bindValue(":STARTTIME", recstartts);
03537     }
03538     else
03539     {
03540         return;
03541     }
03542     query.bindValue(":TYPE", type);
03543 
03544     frm_pos_map_t::iterator it;
03545     for (it = posMap.begin(); it != posMap.end(); ++it)
03546     {
03547         uint64_t frame  = it.key();
03548         uint64_t offset = *it;
03549 
03550         query.bindValue(":MARK",   (quint64)frame);
03551         query.bindValue(":OFFSET", (quint64)offset);
03552 
03553         if (!query.exec())
03554         {
03555             MythDB::DBError("delta position map insert", query);
03556             break;
03557         }
03558     }
03559 }
03560 
03564 void ProgramInfo::SaveAspect(
03565     uint64_t frame, MarkTypes type, uint customAspect)
03566 {
03567     if (!IsRecording())
03568         return;
03569 
03570     MSqlQuery query(MSqlQuery::InitCon());
03571 
03572     query.prepare("INSERT INTO recordedmarkup"
03573                     " (chanid, starttime, mark, type, data)"
03574                     " VALUES"
03575                     " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
03576     query.bindValue(":CHANID", chanid);
03577     query.bindValue(":STARTTIME", recstartts);
03578 
03579     query.bindValue(":MARK", (quint64)frame);
03580     query.bindValue(":TYPE", type);
03581 
03582     if (type == MARK_ASPECT_CUSTOM)
03583         query.bindValue(":DATA", customAspect);
03584     else
03585         query.bindValue(":DATA", QVariant::UInt);
03586 
03587     if (!query.exec())
03588         MythDB::DBError("aspect ratio change insert", query);
03589 }
03590 
03594 void ProgramInfo::SaveFrameRate(uint64_t frame, uint framerate)
03595 {
03596     if (!IsRecording())
03597         return;
03598 
03599     MSqlQuery query(MSqlQuery::InitCon());
03600 
03601     query.prepare("INSERT INTO recordedmarkup"
03602                   "    (chanid, starttime, mark, type, data)"
03603                   "    VALUES"
03604                   " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
03605     query.bindValue(":CHANID", chanid);
03606     query.bindValue(":STARTTIME", recstartts);
03607     query.bindValue(":MARK", (quint64)frame);
03608     query.bindValue(":TYPE", MARK_VIDEO_RATE);
03609     query.bindValue(":DATA", framerate);
03610 
03611     if (!query.exec())
03612         MythDB::DBError("Frame rate insert", query);
03613 }
03614 
03615 
03617 void ProgramInfo::SaveTotalDuration(int64_t duration)
03618 {
03619     if (!IsRecording())
03620         return;
03621 
03622     MSqlQuery query(MSqlQuery::InitCon());
03623 
03624     query.prepare("DELETE FROM recordedmarkup "
03625                   " WHERE chanid=:CHANID "
03626                   " AND starttime=:STARTTIME "
03627                   " AND type=:TYPE");
03628     query.bindValue(":CHANID", chanid);
03629     query.bindValue(":STARTTIME", recstartts);
03630     query.bindValue(":TYPE", MARK_DURATION_MS);
03631 
03632     if (!query.exec())
03633         MythDB::DBError("Duration delete", query);
03634 
03635     query.prepare("INSERT INTO recordedmarkup"
03636                   "    (chanid, starttime, mark, type, data)"
03637                   "    VALUES"
03638                   " ( :CHANID, :STARTTIME, 0, :TYPE, :DATA);");
03639     query.bindValue(":CHANID", chanid);
03640     query.bindValue(":STARTTIME", recstartts);
03641     query.bindValue(":TYPE", MARK_DURATION_MS);
03642     query.bindValue(":DATA", (uint)(duration / 1000));
03643 
03644     if (!query.exec())
03645         MythDB::DBError("Duration insert", query);
03646 }
03647 
03649 void ProgramInfo::SaveTotalFrames(int64_t frames)
03650 {
03651     if (!IsRecording())
03652         return;
03653 
03654     MSqlQuery query(MSqlQuery::InitCon());
03655 
03656     query.prepare("DELETE FROM recordedmarkup "
03657                   " WHERE chanid=:CHANID "
03658                   " AND starttime=:STARTTIME "
03659                   " AND type=:TYPE");
03660     query.bindValue(":CHANID", chanid);
03661     query.bindValue(":STARTTIME", recstartts);
03662     query.bindValue(":TYPE", MARK_TOTAL_FRAMES);
03663 
03664     if (!query.exec())
03665         MythDB::DBError("Frames delete", query);
03666 
03667     query.prepare("INSERT INTO recordedmarkup"
03668                   "    (chanid, starttime, mark, type, data)"
03669                   "    VALUES"
03670                   " ( :CHANID, :STARTTIME, 0, :TYPE, :DATA);");
03671     query.bindValue(":CHANID", chanid);
03672     query.bindValue(":STARTTIME", recstartts);
03673     query.bindValue(":TYPE", MARK_TOTAL_FRAMES);
03674     query.bindValue(":DATA", (uint)(frames));
03675 
03676     if (!query.exec())
03677         MythDB::DBError("Total Frames insert", query);
03678 }
03679 
03683 void ProgramInfo::SaveResolution(uint64_t frame, uint width, uint height)
03684 {
03685     if (!IsRecording())
03686         return;
03687 
03688     MSqlQuery query(MSqlQuery::InitCon());
03689 
03690     query.prepare("INSERT INTO recordedmarkup"
03691                   "    (chanid, starttime, mark, type, data)"
03692                   "    VALUES"
03693                   " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
03694     query.bindValue(":CHANID", chanid);
03695     query.bindValue(":STARTTIME", recstartts);
03696     query.bindValue(":MARK", (quint64)frame);
03697     query.bindValue(":TYPE", MARK_VIDEO_WIDTH);
03698     query.bindValue(":DATA", width);
03699 
03700     if (!query.exec())
03701         MythDB::DBError("Resolution insert", query);
03702 
03703     query.prepare("INSERT INTO recordedmarkup"
03704                   "    (chanid, starttime, mark, type, data)"
03705                   "    VALUES"
03706                   " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
03707     query.bindValue(":CHANID", chanid);
03708     query.bindValue(":STARTTIME", recstartts);
03709     query.bindValue(":MARK", (quint64)frame);
03710     query.bindValue(":TYPE", MARK_VIDEO_HEIGHT);
03711     query.bindValue(":DATA", height);
03712 
03713     if (!query.exec())
03714         MythDB::DBError("Resolution insert", query);
03715 }
03716 
03717 static uint load_markup_datum(
03718     MarkTypes type, uint chanid, const QDateTime &recstartts)
03719 {
03720     QString qstr = QString(
03721         "SELECT recordedmarkup.data "
03722         "FROM recordedmarkup "
03723         "WHERE recordedmarkup.chanid    = :CHANID    AND "
03724         "      recordedmarkup.starttime = :STARTTIME AND "
03725         "      recordedmarkup.type      = %1 "
03726         "GROUP BY recordedmarkup.data "
03727         "ORDER BY SUM( ( SELECT IFNULL(rm.mark, recordedmarkup.mark)"
03728         "                FROM recordedmarkup AS rm "
03729         "                WHERE rm.chanid    = recordedmarkup.chanid    AND "
03730         "                      rm.starttime = recordedmarkup.starttime AND "
03731         "                      rm.type      = recordedmarkup.type      AND "
03732         "                      rm.mark      > recordedmarkup.mark "
03733         "                ORDER BY rm.mark ASC LIMIT 1 "
03734         "              ) - recordedmarkup.mark "
03735         "            ) DESC "
03736         "LIMIT 1").arg((int)type);
03737 
03738     MSqlQuery query(MSqlQuery::InitCon());
03739     query.prepare(qstr);
03740     query.bindValue(":CHANID", chanid);
03741     query.bindValue(":STARTTIME", recstartts);
03742 
03743     if (!query.exec())
03744     {
03745         MythDB::DBError("load_markup_datum", query);
03746         return 0;
03747     }
03748 
03749     return (query.next()) ? query.value(0).toUInt() : 0;
03750 }
03751 
03756 uint ProgramInfo::QueryAverageHeight(void) const
03757 {
03758     return load_markup_datum(MARK_VIDEO_HEIGHT, chanid, recstartts);
03759 }
03760 
03765 uint ProgramInfo::QueryAverageWidth(void) const
03766 {
03767     return load_markup_datum(MARK_VIDEO_WIDTH, chanid, recstartts);
03768 }
03769 
03774 uint ProgramInfo::QueryAverageFrameRate(void) const
03775 {
03776     return load_markup_datum(MARK_VIDEO_RATE, chanid, recstartts);
03777 }
03778 
03782 int64_t ProgramInfo::QueryTotalDuration(void) const
03783 {
03784     if (gCoreContext->IsDatabaseIgnored())
03785         return 0LL;
03786     int64_t msec = load_markup_datum(MARK_DURATION_MS, chanid, recstartts);
03787     return msec * 1000;
03788 }
03789 
03793 int64_t ProgramInfo::QueryTotalFrames(void) const
03794 {
03795     int64_t frames = load_markup_datum(MARK_TOTAL_FRAMES, chanid, recstartts);
03796     return frames;
03797 }
03798 
03799 void ProgramInfo::SaveVideoProperties(uint mask, uint vid_flags)
03800 {
03801     MSqlQuery query(MSqlQuery::InitCon());
03802 
03803     LOG(VB_RECORD, LOG_INFO,
03804         QString("SaveVideoProperties(0x%1, 0x%2)")
03805         .arg(mask,2,16,QChar('0')).arg(vid_flags,2,16,QChar('0')));
03806 
03807     query.prepare(
03808         "UPDATE recordedprogram "
03809         "SET videoprop = ((videoprop+0) & :OTHERFLAGS) | :FLAGS "
03810         "WHERE chanid = :CHANID AND starttime = :STARTTIME");
03811 
03812     query.bindValue(":OTHERFLAGS", ~mask);
03813     query.bindValue(":FLAGS",      vid_flags);
03814     query.bindValue(":CHANID",     chanid);
03815     query.bindValue(":STARTTIME",  startts);
03816     query.exec();
03817 
03818     uint videoproperties = GetVideoProperties();
03819     videoproperties &= ~mask;
03820     videoproperties |= vid_flags;
03821     properties &= ~kVideoPropertyMask;
03822     properties |= videoproperties << kVideoPropertyOffset;
03823 
03824     SendUpdateEvent();
03825 }
03826 
03837 QString ProgramInfo::ChannelText(const QString &format) const
03838 {
03839     QString chan(format);
03840     chan.replace("<num>", chanstr)
03841         .replace("<sign>", chansign)
03842         .replace("<name>", channame);
03843     return chan;
03844 }
03845 
03846 void ProgramInfo::UpdateInUseMark(bool force)
03847 {
03848 #ifdef DEBUG_IN_USE
03849     LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UpdateInUseMark(%1) '%2'")
03850              .arg(force?"force":"no force").arg(inUseForWhat));
03851 #endif
03852 
03853     if (!IsRecording())
03854         return;
03855 
03856     if (inUseForWhat.isEmpty())
03857         return;
03858 
03859     if (force || lastInUseTime.secsTo(QDateTime::currentDateTime()) > 15 * 60)
03860         MarkAsInUse(true);
03861 }
03862 
03863 void ProgramInfo::SaveSeasonEpisode(uint seas, uint ep)
03864 {
03865     MSqlQuery query(MSqlQuery::InitCon());
03866 
03867     query.prepare(
03868         "UPDATE recorded "
03869         "SET season = :SEASON, episode = :EPISODE "
03870         "WHERE chanid = :CHANID AND starttime = :STARTTIME "
03871         "AND recordid = :RECORDID");
03872 
03873     query.bindValue(":SEASON",     seas);
03874     query.bindValue(":EPISODE",    ep);
03875     query.bindValue(":CHANID",     chanid);
03876     query.bindValue(":STARTTIME",  recstartts);
03877     query.bindValue(":RECORDID",   recordid);
03878     query.exec();
03879 
03880     SendUpdateEvent();
03881 }
03882 
03883 void ProgramInfo::SaveInetRef(const QString &inet)
03884 {
03885     MSqlQuery query(MSqlQuery::InitCon());
03886 
03887     query.prepare(
03888         "UPDATE recorded "
03889         "SET inetref = :INETREF "
03890         "WHERE chanid = :CHANID AND starttime = :STARTTIME "
03891         "AND recordid = :RECORDID");
03892 
03893     query.bindValue(":INETREF",    inet);
03894     query.bindValue(":CHANID",     chanid);
03895     query.bindValue(":STARTTIME",  recstartts);
03896     query.bindValue(":RECORDID",   recordid);
03897     query.exec();
03898 
03899     SendUpdateEvent();
03900 }
03901 
03908 bool ProgramInfo::IsFileReadable(void) const
03909 {
03910     if (IsLocal() && QFileInfo(pathname).isReadable())
03911         return true;
03912 
03913     if (!IsMythStream())
03914         pathname = GetPlaybackURL(true, false);
03915 
03916     if (IsMythStream())
03917         return RemoteCheckFile(this);
03918 
03919     if (IsLocal())
03920         return QFileInfo(pathname).isReadable();
03921 
03922     return false;
03923 }
03924 
03925 QString ProgramInfo::QueryRecordingGroupPassword(const QString &group)
03926 {
03927     QString result;
03928 
03929     MSqlQuery query(MSqlQuery::InitCon());
03930     query.prepare("SELECT password FROM recgrouppassword "
03931                     "WHERE recgroup = :GROUP");
03932     query.bindValue(":GROUP", group);
03933 
03934     if (query.exec() && query.next())
03935         result = query.value(0).toString();
03936 
03937     return(result);
03938 }
03939 
03942 QString ProgramInfo::QueryRecordingGroup(void) const
03943 {
03944     MSqlQuery query(MSqlQuery::InitCon());
03945     query.prepare("SELECT recgroup FROM recorded "
03946                   "WHERE chanid    = :CHANID AND "
03947                   "      starttime = :START");
03948     query.bindValue(":START", recstartts);
03949     query.bindValue(":CHANID", chanid);
03950 
03951     QString grp = recgroup;
03952     if (query.exec() && query.next())
03953         grp = query.value(0).toString();
03954 
03955     return grp;
03956 }
03957 
03958 uint ProgramInfo::QueryTranscoderID(void) const
03959 {
03960     MSqlQuery query(MSqlQuery::InitCon());
03961     query.prepare("SELECT transcoder FROM recorded "
03962                   "WHERE chanid    = :CHANID AND "
03963                   "      starttime = :START");
03964     query.bindValue(":CHANID", chanid);
03965     query.bindValue(":START",  recstartts);
03966 
03967     if (query.exec() && query.next())
03968         return query.value(0).toUInt();
03969 
03970     return 0;
03971 }
03972 
03978 QString ProgramInfo::DiscoverRecordingDirectory(void) const
03979 {
03980     if (!IsLocal())
03981     {
03982         if (!gCoreContext->IsBackend())
03983             return "";
03984 
03985         QString path = GetPlaybackURL(false, true);
03986         if (path.left(1) == "/")
03987         {
03988             QFileInfo testFile(path);
03989             return testFile.path();
03990         }
03991 
03992         return "";
03993     }
03994 
03995     QFileInfo testFile(pathname);
03996     if (testFile.exists() || (gCoreContext->GetHostName() == hostname))
03997     {
03998         // we may be recording this file and it may not exist yet so we need
03999         // to do some checking to see what is in pathname
04000         if (testFile.exists())
04001         {
04002             if (testFile.isSymLink())
04003                 testFile.setFile(getSymlinkTarget(pathname));
04004 
04005             if (testFile.isFile())
04006                 return testFile.path();
04007             else if (testFile.isDir())
04008                 return testFile.filePath();
04009         }
04010         else
04011         {
04012             testFile.setFile(testFile.absolutePath());
04013             if (testFile.exists())
04014             {
04015                 if (testFile.isSymLink())
04016                     testFile.setFile(getSymlinkTarget(testFile.path()));
04017 
04018                 if (testFile.isDir())
04019                     return testFile.filePath();
04020             }
04021         }
04022     }
04023 
04024     return "";
04025 }
04026 
04027 #include <cassert>
04034 void ProgramInfo::MarkAsInUse(bool inuse, QString usedFor)
04035 {
04036     if (!IsRecording())
04037         return;
04038 
04039     bool notifyOfChange = false;
04040 
04041     if (inuse &&
04042         (inUseForWhat.isEmpty() ||
04043          (!usedFor.isEmpty() && usedFor != inUseForWhat)))
04044     {
04045         if (!usedFor.isEmpty())
04046         {
04047 
04048 #ifdef DEBUG_IN_USE
04049             if (!inUseForWhat.isEmpty())
04050             {
04051                 LOG(VB_GENERAL, LOG_INFO, LOC +
04052                     QString("MarkAsInUse(true, '%1'->'%2')")
04053                         .arg(inUseForWhat).arg(usedFor) +
04054                     " -- use has changed");
04055             }
04056             else
04057             {
04058                 LOG(VB_GENERAL, LOG_INFO, LOC +
04059                     QString("MarkAsInUse(true, ''->'%1')").arg(usedFor));
04060             }
04061 #endif // DEBUG_IN_USE
04062 
04063             inUseForWhat = usedFor;
04064         }
04065         else if (inUseForWhat.isEmpty())
04066         {
04067             QString oldInUseForWhat = inUseForWhat;
04068             inUseForWhat = QString("%1 [%2]")
04069                 .arg(QObject::tr("Unknown")).arg(getpid());
04070             LOG(VB_GENERAL, LOG_WARNING, LOC +
04071                 QString("MarkAsInUse(true, ''->'%1')").arg(inUseForWhat) +
04072                 " -- use was not explicitly set");
04073         }
04074 
04075         notifyOfChange = true;
04076     }
04077 
04078     if (!inuse && !inUseForWhat.isEmpty() && usedFor != inUseForWhat)
04079     {
04080         LOG(VB_GENERAL, LOG_WARNING, LOC +
04081             QString("MarkAsInUse(false, '%1'->'%2')")
04082                 .arg(inUseForWhat).arg(usedFor) +
04083             " -- use has changed since first setting as in use.");
04084     }
04085 #ifdef DEBUG_IN_USE
04086     else if (!inuse)
04087     {
04088         LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("MarkAsInUse(false, '%1')")
04089                  .arg(inUseForWhat));
04090     }
04091 #endif // DEBUG_IN_USE
04092 
04093     if (!inuse && inUseForWhat.isEmpty())
04094         inUseForWhat = usedFor;
04095 
04096     if (!inuse && inUseForWhat.isEmpty())
04097     {
04098         LOG(VB_GENERAL, LOG_WARNING, LOC +
04099             "MarkAsInUse requires a key to delete in use mark");
04100         return; // can't delete if we don't have a key
04101     }
04102 
04103     if (!inuse)
04104     {
04105         MSqlQuery query(MSqlQuery::InitCon());
04106         query.prepare(
04107             "DELETE FROM inuseprograms "
04108             "WHERE chanid   = :CHANID   AND starttime = :STARTTIME AND "
04109             "      hostname = :HOSTNAME AND recusage  = :RECUSAGE");
04110         query.bindValue(":CHANID",    chanid);
04111         query.bindValue(":STARTTIME", recstartts);
04112         query.bindValue(":HOSTNAME",  gCoreContext->GetHostName());
04113         query.bindValue(":RECUSAGE",  inUseForWhat);
04114 
04115         if (!query.exec())
04116             MythDB::DBError("MarkAsInUse -- delete", query);
04117 
04118         inUseForWhat.clear();
04119         lastInUseTime = mythCurrentDateTime().addSecs(-4 * 60 * 60);
04120         SendUpdateEvent();
04121         return;
04122     }
04123 
04124     if (pathname == GetBasename())
04125         pathname = GetPlaybackURL(false, true);
04126 
04127     QString recDir = DiscoverRecordingDirectory();
04128 
04129     QDateTime inUseTime = mythCurrentDateTime();
04130 
04131     MSqlQuery query(MSqlQuery::InitCon());
04132     query.prepare(
04133         "SELECT count(*) "
04134         "FROM inuseprograms "
04135         "WHERE chanid   = :CHANID   AND starttime = :STARTTIME AND "
04136         "      hostname = :HOSTNAME AND recusage  = :RECUSAGE");
04137     query.bindValue(":CHANID",    chanid);
04138     query.bindValue(":STARTTIME", recstartts);
04139     query.bindValue(":HOSTNAME",  gCoreContext->GetHostName());
04140     query.bindValue(":RECUSAGE",  inUseForWhat);
04141 
04142     if (!query.exec())
04143     {
04144         MythDB::DBError("MarkAsInUse -- select", query);
04145     }
04146     else if (!query.next())
04147     {
04148         LOG(VB_GENERAL, LOG_ERR, LOC + "MarkAsInUse -- select query failed");
04149     }
04150     else if (query.value(0).toUInt())
04151     {
04152         query.prepare(
04153             "UPDATE inuseprograms "
04154             "SET lastupdatetime = :UPDATETIME "
04155             "WHERE chanid   = :CHANID   AND starttime = :STARTTIME AND "
04156             "      hostname = :HOSTNAME AND recusage  = :RECUSAGE");
04157         query.bindValue(":CHANID",     chanid);
04158         query.bindValue(":STARTTIME",  recstartts);
04159         query.bindValue(":HOSTNAME",   gCoreContext->GetHostName());
04160         query.bindValue(":RECUSAGE",   inUseForWhat);
04161         query.bindValue(":UPDATETIME", inUseTime);
04162 
04163         if (!query.exec())
04164             MythDB::DBError("MarkAsInUse -- update failed", query);
04165         else
04166             lastInUseTime = inUseTime;
04167     }
04168     else // if (!query.value(0).toUInt())
04169     {
04170         query.prepare(
04171             "INSERT INTO inuseprograms "
04172             " (chanid,         starttime,  recusage,  hostname, "
04173             "  lastupdatetime, rechost,    recdir) "
04174             "VALUES "
04175             " (:CHANID,       :STARTTIME, :RECUSAGE, :HOSTNAME, "
04176             "  :UPDATETIME,   :RECHOST,   :RECDIR)");
04177         query.bindValue(":CHANID",     chanid);
04178         query.bindValue(":STARTTIME",  recstartts);
04179         query.bindValue(":HOSTNAME",   gCoreContext->GetHostName());
04180         query.bindValue(":RECUSAGE",   inUseForWhat);
04181         query.bindValue(":UPDATETIME", inUseTime);
04182         query.bindValue(":RECHOST",    hostname);
04183         query.bindValue(":RECDIR",     recDir);
04184 
04185         if (!query.exec())
04186             MythDB::DBError("MarkAsInUse -- insert failed", query);
04187         else
04188             lastInUseTime = inUseTime;
04189     }
04190 
04191     if (!notifyOfChange)
04192         return;
04193 
04194     // Let others know we changed status
04195     QDateTime oneHourAgo = QDateTime::currentDateTime().addSecs(-61 * 60);
04196     query.prepare("SELECT DISTINCT recusage "
04197                   "FROM inuseprograms "
04198                   "WHERE lastupdatetime >= :ONEHOURAGO AND "
04199                   "      chanid          = :CHANID     AND "
04200                   "      starttime       = :STARTTIME");
04201     query.bindValue(":CHANID",     chanid);
04202     query.bindValue(":STARTTIME",  recstartts);
04203     query.bindValue(":ONEHOURAGO", oneHourAgo);
04204     if (!query.exec())
04205         return; // not safe to send update event...
04206 
04207     programflags &= ~(FL_INUSEPLAYING | FL_INUSERECORDING | FL_INUSEOTHER);
04208     while (query.next())
04209     {
04210         QString inUseForWhat = query.value(0).toString();
04211         if (inUseForWhat.contains(kPlayerInUseID))
04212             programflags |= FL_INUSEPLAYING;
04213         else if (inUseForWhat == kRecorderInUseID)
04214             programflags |= FL_INUSERECORDING;
04215         else
04216             programflags |= FL_INUSEOTHER;
04217     }
04218     SendUpdateEvent();
04219 }
04220 
04226 bool ProgramInfo::QueryTuningInfo(QString &channum, QString &input) const
04227 {
04228     channum.clear();
04229     input.clear();
04230     MSqlQuery query(MSqlQuery::InitCon());
04231 
04232     query.prepare("SELECT channel.channum, cardinput.inputname "
04233                   "FROM channel, capturecard, cardinput "
04234                   "WHERE channel.chanid     = :CHANID            AND "
04235                   "      cardinput.cardid   = capturecard.cardid AND "
04236                   "      cardinput.sourceid = :SOURCEID          AND "
04237                   "      capturecard.cardid = :CARDID");
04238     query.bindValue(":CHANID",   chanid);
04239     query.bindValue(":SOURCEID", sourceid);
04240     query.bindValue(":CARDID",   cardid);
04241 
04242     if (query.exec() && query.next())
04243     {
04244         channum = query.value(0).toString();
04245         input   = query.value(1).toString();
04246         return true;
04247     }
04248     else
04249     {
04250         MythDB::DBError("GetChannel(ProgInfo...)", query);
04251         return false;
04252     }
04253 }
04254 
04260 QString ProgramInfo::QueryInputDisplayName(void) const
04261 {
04262     if (!inputid)
04263         return QString::null;
04264 
04265     MSqlQuery query(MSqlQuery::InitCon());
04266     query.prepare("SELECT displayname, cardid, inputname "
04267                   "FROM cardinput "
04268                   "WHERE cardinputid = :INPUTID");
04269     query.bindValue(":INPUTID", inputid);
04270 
04271     if (!query.exec())
04272         MythDB::DBError("ProgramInfo::GetInputDisplayName(uint)", query);
04273     else if (query.next())
04274     {
04275         QString result = query.value(0).toString();
04276         if (result.isEmpty())
04277             result = QString("%1: %2").arg(query.value(1).toInt())
04278                                       .arg(query.value(2).toString());
04279         return result;
04280     }
04281 
04282     return QString::null;
04283 }
04284 
04285 static int init_tr(void)
04286 {
04287     static bool done = false;
04288     static QMutex init_tr_lock;
04289     QMutexLocker locker(&init_tr_lock);
04290     if (done)
04291         return 1;
04292 
04293     QString rec_profile_names =
04294         QObject::tr("Default",        "Recording Profile Default") +
04295         QObject::tr("High Quality",   "Recording Profile High Quality") +
04296         QObject::tr("Live TV",        "Recording Profile Live TV") +
04297         QObject::tr("Low Quality",    "Recording Profile Low Quality") +
04298         QObject::tr("Medium Quality", "Recording Profile Medium Quality") +
04299         QObject::tr("MPEG-2",         "Recording Profile MPEG-2") +
04300         QObject::tr("RTjpeg/MPEG-4",  "Recording Profile RTjpeg/MPEG-4");
04301 
04302 
04303     QString rec_profile_groups =
04304         QObject::tr("CRC IP Recorders",
04305                     "Recording Profile Group Name") +
04306         QObject::tr("FireWire Input",
04307                     "Recording Profile Group Name") +
04308         QObject::tr("Freebox Input",
04309                     "Recording Profile Group Name") +
04310         QObject::tr("Hardware DVB Encoders",
04311                     "Recording Profile Group Name") +
04312         QObject::tr("Hardware HDTV",
04313                     "Recording Profile Group Name") +
04314         QObject::tr("Hardware MJPEG Encoders (Matrox G200-TV, Miro DC10, etc)",
04315                     "Recording Profile Group Name") +
04316         QObject::tr("HD-PVR Recorders",
04317                     "Recording Profile Group Name") +
04318         QObject::tr("HDHomeRun Recorders",
04319                     "Recording Profile Group Name") +
04320         QObject::tr("MPEG-2 Encoders (PVR-x50, PVR-500)",
04321                     "Recording Profile Group Name") +
04322         QObject::tr("Software Encoders (V4L based)",
04323                     "Recording Profile Group Name") +
04324         QObject::tr("Transcoders",
04325                     "Recording Profile Group Name") +
04326         QObject::tr("USB MPEG-4 Encoder (Plextor ConvertX, etc)",
04327                     "Recording Profile Group Name");
04328 
04329     QString display_rec_groups =
04330         QObject::tr("All Programs",   "Recording Group All Programs") +
04331         QObject::tr("All", "Recording Group All Programs -- short form") +
04332         QObject::tr("Live TV",        "Recording Group Live TV") +
04333         QObject::tr("Default",        "Recording Group Default") +
04334         QObject::tr("Deleted",        "Recording Group Deleted");
04335 
04336     QString storage_groups =
04337         QObject::tr("Default",        "Storage Group Name") +
04338         QObject::tr("Live TV",        "Storage Group Name") +
04339         QObject::tr("Thumbnails",     "Storage Group Name") +
04340         QObject::tr("DB Backups",     "Storage Group Name");
04341 
04342     QString play_groups =
04343         QObject::tr("Default",        "Playback Group Name");
04344 
04345     done = true;
04346     return (rec_profile_names.length() +
04347             rec_profile_groups.length() +
04348             display_rec_groups.length() +
04349             storage_groups.length() +
04350             play_groups.length());
04351 }
04352 
04353 int ProgramInfo::InitStatics(void)
04354 {
04355     QMutexLocker locker(&staticDataLock);
04356     if (!updater)
04357         updater = new ProgramInfoUpdater();
04358     return 1;
04359 }
04360 
04362 QString ProgramInfo::i18n(const QString &msg)
04363 {
04364     init_tr();
04365     QByteArray msg_arr = msg.toLatin1();
04366     QString msg_i18n = QObject::tr(msg_arr.constData());
04367     QByteArray msg_i18n_arr = msg_i18n.toLatin1();
04368     return (msg_arr == msg_i18n_arr) ? msg : msg_i18n;
04369 }
04370 
04377 void ProgramInfo::SubstituteMatches(QString &str)
04378 {
04379     QString pburl = GetPlaybackURL(false, true);
04380     if (pburl.left(7) == "myth://")
04381     {
04382         str.replace(QString("%DIR%"), pburl);
04383     }
04384     else
04385     {
04386         QFileInfo dirInfo(pburl);
04387         str.replace(QString("%DIR%"), dirInfo.path());
04388     }
04389 
04390     str.replace(QString("%FILE%"), GetBasename());
04391     str.replace(QString("%TITLE%"), title);
04392     str.replace(QString("%SUBTITLE%"), subtitle);
04393     str.replace(QString("%SEASON%"), QString::number(season));
04394     str.replace(QString("%EPISODE%"), QString::number(episode));
04395     str.replace(QString("%DESCRIPTION%"), description);
04396     str.replace(QString("%HOSTNAME%"), hostname);
04397     str.replace(QString("%CATEGORY%"), category);
04398     str.replace(QString("%RECGROUP%"), recgroup);
04399     str.replace(QString("%PLAYGROUP%"), playgroup);
04400     str.replace(QString("%CHANID%"), QString::number(chanid));
04401     str.replace(QString("%INETREF%"), inetref);
04402     str.replace(QString("%ORIGINALAIRDATE%"), originalAirDate.toString(Qt::ISODate));
04403     static const char *time_str[] =
04404         { "STARTTIME", "ENDTIME", "PROGSTART", "PROGEND", };
04405     const QDateTime *time_dtr[] =
04406         { &recstartts, &recendts, &startts,    &endts,    };
04407     for (uint i = 0; i < sizeof(time_str)/sizeof(char*); i++)
04408     {
04409         str.replace(QString("%%1%").arg(time_str[i]),
04410                     time_dtr[i]->toString("yyyyMMddhhmmss"));
04411         str.replace(QString("%%1ISO%").arg(time_str[i]),
04412                     time_dtr[i]->toString(Qt::ISODate));
04413         str.replace(QString("%%1UTC%").arg(time_str[i]),
04414                     (time_dtr[i]->toUTC()).toString("yyyyMMddhhmmss"));
04415         str.replace(QString("%%1ISOUTC%").arg(time_str[i]),
04416                     (time_dtr[i]->toUTC()).toString(Qt::ISODate));
04417     }
04418 }
04419 
04420 QMap<QString,uint32_t> ProgramInfo::QueryInUseMap(void)
04421 {
04422     QMap<QString, uint32_t> inUseMap;
04423     QDateTime oneHourAgo = QDateTime::currentDateTime().addSecs(-61 * 60);
04424 
04425     MSqlQuery query(MSqlQuery::InitCon());
04426 
04427     query.prepare("SELECT DISTINCT chanid, starttime, recusage "
04428                   "FROM inuseprograms WHERE lastupdatetime >= :ONEHOURAGO");
04429     query.bindValue(":ONEHOURAGO", oneHourAgo);
04430 
04431     if (!query.exec())
04432         return inUseMap;
04433 
04434     while (query.next())
04435     {
04436         QString inUseKey = ProgramInfo::MakeUniqueKey(
04437             query.value(0).toUInt(), query.value(1).toDateTime());
04438 
04439         QString inUseForWhat = query.value(2).toString();
04440 
04441         if (!inUseMap.contains(inUseKey))
04442             inUseMap[inUseKey] = 0;
04443 
04444         if (inUseForWhat.contains(kPlayerInUseID))
04445             inUseMap[inUseKey] |= FL_INUSEPLAYING;
04446         else if (inUseForWhat == kRecorderInUseID)
04447             inUseMap[inUseKey] |= FL_INUSERECORDING;
04448         else
04449             inUseMap[inUseKey] |= FL_INUSEOTHER;
04450     }
04451 
04452     return inUseMap;
04453 }
04454 
04455 QMap<QString,bool> ProgramInfo::QueryJobsRunning(int type)
04456 {
04457     QMap<QString,bool> is_job_running;
04458 
04459     MSqlQuery query(MSqlQuery::InitCon());
04460     query.prepare("SELECT chanid, starttime, status FROM jobqueue "
04461                   "WHERE type = :TYPE");
04462     query.bindValue(":TYPE", type);
04463     if (!query.exec())
04464         return is_job_running;
04465 
04466     while (query.next())
04467     {
04468         uint      chanid     = query.value(0).toUInt();
04469         QDateTime recstartts = query.value(1).toDateTime();
04470         int       tmpStatus  = query.value(2).toInt();
04471         if ((tmpStatus != /*JOB_UNKNOWN*/ 0x0000) &&
04472             (tmpStatus != /*JOB_QUEUED*/ 0x0001) &&
04473             (!(tmpStatus & /*JOB_DONE*/ 0x0100)))
04474         {
04475             is_job_running[
04476                 ProgramInfo::MakeUniqueKey(chanid,recstartts)] = true;
04477         }
04478     }
04479 
04480     return is_job_running;
04481 }
04482 
04483 QStringList ProgramInfo::LoadFromScheduler(
04484     const QString &tmptable, int recordid)
04485 {
04486     QStringList slist;
04487 
04488     MythScheduler *sched = gCoreContext->GetScheduler();
04489     if (sched && tmptable.isEmpty())
04490     {
04491         sched->GetAllPending(slist);
04492         return slist;
04493     }
04494 
04495     if (sched)
04496     {
04497         LOG(VB_GENERAL, LOG_ERR,
04498             "Called from master backend\n\t\t\t"
04499             "with recordid or tmptable, this is not currently supported");
04500         return slist;
04501     }
04502 
04503     slist.push_back(
04504         (tmptable.isEmpty()) ?
04505         QString("QUERY_GETALLPENDING") :
04506         QString("QUERY_GETALLPENDING %1 %2").arg(tmptable).arg(recordid));
04507 
04508     if (!gCoreContext->SendReceiveStringList(slist) || slist.size() < 2)
04509     {
04510         LOG(VB_GENERAL, LOG_ALERT,
04511                  "LoadFromScheduler(): Error querying master.");
04512         slist.clear();
04513     }
04514 
04515     return slist;
04516 }
04517 
04518 static bool FromProgramQuery(
04519     const QString &sql, const MSqlBindings &bindings, MSqlQuery &query)
04520 {
04521     QString querystr = QString(
04522         "SELECT DISTINCT program.chanid, program.starttime, program.endtime, "
04523         "    program.title, program.subtitle, program.description, "
04524         "    program.category, channel.channum, channel.callsign, "
04525         "    channel.name, program.previouslyshown, channel.commmethod, "
04526         "    channel.outputfilters, program.seriesid, program.programid, "
04527         "    program.airdate, program.stars, program.originalairdate, "
04528         "    program.category_type, oldrecstatus.recordid, "
04529         "    oldrecstatus.rectype, oldrecstatus.recstatus, "
04530         "    oldrecstatus.findid, program.videoprop+0, program.audioprop+0, "
04531         "    program.subtitletypes+0 "
04532         "FROM program "
04533         "LEFT JOIN channel ON program.chanid = channel.chanid "
04534         "LEFT JOIN oldrecorded AS oldrecstatus ON "
04535         "    oldrecstatus.future = 0 AND "
04536         "    program.title = oldrecstatus.title AND "
04537         "    channel.callsign = oldrecstatus.station AND "
04538         "    program.starttime = oldrecstatus.starttime "
04539         ) + sql;
04540 
04541     if (!sql.contains(" GROUP BY "))
04542         querystr += " GROUP BY program.starttime, channel.channum, "
04543             "  channel.callsign, program.title ";
04544     if (!sql.contains(" ORDER BY "))
04545     {
04546         querystr += " ORDER BY program.starttime, ";
04547         QString chanorder =
04548             gCoreContext->GetSetting("ChannelOrdering", "channum");
04549         if (chanorder != "channum")
04550             querystr += chanorder + " ";
04551         else // approximation which the DB can handle
04552             querystr += "atsc_major_chan,atsc_minor_chan,channum,callsign ";
04553     }
04554     if (!sql.contains(" LIMIT "))
04555         querystr += " LIMIT 20000 ";
04556 
04557     query.prepare(querystr);
04558     MSqlBindings::const_iterator it;
04559     for (it = bindings.begin(); it != bindings.end(); ++it)
04560     {
04561         if (querystr.contains(it.key()))
04562             query.bindValue(it.key(), it.value());
04563     }
04564 
04565     if (!query.exec())
04566     {
04567         MythDB::DBError("LoadFromProgramQuery", query);
04568         return false;
04569     }
04570 
04571     return true;
04572 }
04573 
04574 bool LoadFromProgram(
04575     ProgramList &destination,
04576     const QString &sql, const MSqlBindings &bindings,
04577     const ProgramList &schedList)
04578 {
04579     destination.clear();
04580 
04581     MSqlQuery query(MSqlQuery::InitCon());
04582     if (!FromProgramQuery(sql, bindings, query))
04583         return false;
04584 
04585     while (query.next())
04586     {
04587         destination.push_back(
04588             new ProgramInfo(
04589                 query.value(3).toString(), // title
04590                 query.value(4).toString(), // subtitle
04591                 query.value(5).toString(), // description
04592                 query.value(6).toString(), // category
04593 
04594                 query.value(0).toUInt(), // chanid
04595                 query.value(7).toString(), // channum
04596                 query.value(8).toString(), // chansign
04597                 query.value(9).toString(), // channame
04598                 query.value(12).toString(), // chanplaybackfilters
04599 
04600                 query.value(1).toDateTime(), // startts
04601                 query.value(2).toDateTime(), // endts
04602                 query.value(1).toDateTime(), // recstartts
04603                 query.value(2).toDateTime(), // recendts
04604 
04605                 query.value(13).toString(), // seriesid
04606                 query.value(14).toString(), // programid
04607                 query.value(18).toString(), // catType
04608 
04609                 query.value(16).toDouble(), // stars
04610                 query.value(15).toUInt(), // year
04611                 query.value(17).toDate(), // originalAirDate
04612                 RecStatusType(query.value(21).toInt()), // recstatus
04613                 query.value(19).toUInt(), // recordid
04614                 RecordingType(query.value(20).toInt()), // rectype
04615                 query.value(22).toUInt(), // findid
04616 
04617                 query.value(11).toInt() == COMM_DETECT_COMMFREE, // commfree
04618                 query.value(10).toInt(), // repeat
04619                 query.value(23).toInt(), // videoprop
04620                 query.value(24).toInt(), // audioprop
04621                 query.value(25).toInt(), // subtitletypes
04622 
04623                 schedList));
04624     }
04625 
04626     return true;
04627 }
04628 
04629 bool LoadFromOldRecorded(
04630     ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
04631 {
04632     destination.clear();
04633 
04634     MSqlQuery query(MSqlQuery::InitCon());
04635 
04636     QString querystr =
04637         "SELECT oldrecorded.chanid, starttime, endtime, "
04638         "       title, subtitle, description, season, episode, category, seriesid, "
04639         "       programid, inetref, channel.channum, channel.callsign, "
04640         "       channel.name, findid, rectype, recstatus, recordid, "
04641         "       duplicate "
04642         " FROM oldrecorded "
04643         " LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
04644         " WHERE oldrecorded.future = 0 "
04645         + sql;
04646 
04647     query.prepare(querystr);
04648     MSqlBindings::const_iterator it;
04649     for (it = bindings.begin(); it != bindings.end(); ++it)
04650     {
04651         if (querystr.contains(it.key()))
04652             query.bindValue(it.key(), it.value());
04653     }
04654 
04655     if (!query.exec())
04656     {
04657         MythDB::DBError("LoadFromOldRecorded", query);
04658         return false;
04659     }
04660 
04661     while (query.next())
04662     {
04663         uint chanid = query.value(0).toUInt();
04664         QString channum  = QString("#%1").arg(chanid);
04665         QString chansign = channum;
04666         QString channame = channum;
04667         if (!query.value(12).toString().isEmpty())
04668         {
04669             channum  = query.value(12).toString();
04670             chansign = query.value(13).toString();
04671             channame = query.value(14).toString();
04672         }
04673 
04674         destination.push_back(new ProgramInfo(
04675             query.value(3).toString(),
04676             query.value(4).toString(),
04677             query.value(5).toString(),
04678             query.value(6).toUInt(),
04679             query.value(7).toUInt(),
04680             query.value(8).toString(),
04681 
04682             chanid, channum, chansign, channame,
04683 
04684             query.value(9).toString(), query.value(10).toString(),
04685             query.value(11).toString(),
04686 
04687             query.value(1).toDateTime(), query.value(2).toDateTime(),
04688             query.value(1).toDateTime(), query.value(2).toDateTime(),
04689 
04690             RecStatusType(query.value(17).toInt()),
04691             query.value(18).toUInt(),
04692             RecordingType(query.value(16).toInt()),
04693             query.value(15).toUInt(),
04694 
04695             query.value(19).toInt()));
04696     }
04697 
04698     return true;
04699 }
04700 
04716 bool LoadFromRecorded(
04717     ProgramList &destination,
04718     bool possiblyInProgressRecordingsOnly,
04719     const QMap<QString,uint32_t> &inUseMap,
04720     const QMap<QString,bool> &isJobRunning,
04721     const QMap<QString, ProgramInfo*> &recMap,
04722     int sort)
04723 {
04724     destination.clear();
04725 
04726     QString     fs_db_name = "";
04727     QDateTime   rectime    = QDateTime::currentDateTime().addSecs(
04728         -gCoreContext->GetNumSetting("RecordOverTime"));
04729 
04730     // ----------------------------------------------------------------------
04731 
04732     QString thequery = ProgramInfo::kFromRecordedQuery;
04733     if (possiblyInProgressRecordingsOnly)
04734         thequery += "WHERE r.endtime >= NOW() AND r.starttime <= NOW() ";
04735 
04736     if (sort)
04737         thequery += "ORDER BY r.starttime ";
04738     if (sort < 0)
04739         thequery += "DESC ";
04740 
04741     MSqlQuery query(MSqlQuery::InitCon());
04742     query.prepare(thequery);
04743 
04744     if (!query.exec())
04745     {
04746         MythDB::DBError("ProgramList::FromRecorded", query);
04747         return true;
04748     }
04749 
04750     while (query.next())
04751     {
04752         const uint chanid = query.value(6).toUInt();
04753         QString channum  = QString("#%1").arg(chanid);
04754         QString chansign = channum;
04755         QString channame = channum;
04756         QString chanfilt;
04757         if (!query.value(7).toString().isEmpty())
04758         {
04759             channum  = query.value(7).toString();
04760             chansign = query.value(8).toString();
04761             channame = query.value(9).toString();
04762             chanfilt = query.value(10).toString();
04763         }
04764 
04765         QString hostname = query.value(15).toString();
04766         if (hostname.isEmpty())
04767             hostname = gCoreContext->GetHostName();
04768 
04769         RecStatusType recstatus = rsRecorded;
04770         QDateTime recstartts = query.value(24).toDateTime();
04771 
04772         QString key = ProgramInfo::MakeUniqueKey(chanid, recstartts);
04773         if (query.value(25).toDateTime() > rectime && recMap.contains(key))
04774             recstatus = rsRecording;
04775 
04776         bool save_not_commflagged = false;
04777         uint flags = 0;
04778 
04779         set_flag(flags, FL_CHANCOMMFREE,
04780                  query.value(30).toInt() == COMM_DETECT_COMMFREE);
04781         set_flag(flags, FL_COMMFLAG,
04782                  query.value(31).toInt() == COMM_FLAG_DONE);
04783         set_flag(flags, FL_COMMPROCESSING ,
04784                  query.value(31).toInt() == COMM_FLAG_PROCESSING);
04785         set_flag(flags, FL_REPEAT,        query.value(32).toBool());
04786         set_flag(flags, FL_TRANSCODED,
04787                  query.value(34).toInt() == TRANSCODING_COMPLETE);
04788         set_flag(flags, FL_DELETEPENDING, query.value(35).toBool());
04789         set_flag(flags, FL_PRESERVED,     query.value(36).toBool());
04790         set_flag(flags, FL_CUTLIST,       query.value(37).toBool());
04791         set_flag(flags, FL_AUTOEXP,       query.value(38).toBool());
04792         set_flag(flags, FL_REALLYEDITING, query.value(39).toBool());
04793         set_flag(flags, FL_BOOKMARK,      query.value(40).toBool());
04794         set_flag(flags, FL_WATCHED,       query.value(41).toBool());
04795 
04796         if (inUseMap.contains(key))
04797             flags |= inUseMap[key];
04798 
04799         if (flags & FL_COMMPROCESSING &&
04800             (isJobRunning.find(key) == isJobRunning.end()))
04801         {
04802             flags &= ~FL_COMMPROCESSING;
04803             save_not_commflagged = true;
04804         }
04805 
04806         set_flag(flags, FL_EDITING,
04807                  (flags & FL_REALLYEDITING) ||
04808                  (flags & COMM_FLAG_PROCESSING));
04809 
04810         destination.push_back(
04811             new ProgramInfo(
04812                 query.value(0).toString(),
04813                 query.value(1).toString(),
04814                 query.value(2).toString(),
04815                 query.value(3).toUInt(),
04816                 query.value(4).toUInt(),
04817                 query.value(5).toString(),
04818 
04819                 chanid, channum, chansign, channame, chanfilt,
04820 
04821                 query.value(11).toString(), query.value(12).toString(),
04822 
04823                 query.value(14).toString(),
04824 
04825                 hostname, query.value(13).toString(),
04826 
04827                 query.value(17).toString(), query.value(18).toString(),
04828                 query.value(19).toString(),
04829 
04830                 query.value(16).toInt(),
04831 
04832                 query.value(20).toULongLong(),
04833 
04834                 query.value(21).toDateTime(), query.value(22).toDateTime(),
04835                 query.value(24).toDateTime(), query.value(25).toDateTime(),
04836 
04837                 query.value(23).toDouble(),
04838 
04839                 query.value(26).toUInt(),
04840                 query.value(27).toDate(),
04841                 query.value(28).toDateTime(),
04842 
04843                 recstatus,
04844 
04845                 query.value(29).toUInt(),
04846 
04847                 RecordingDupInType(query.value(46).toInt()),
04848                 RecordingDupMethodType(query.value(47).toInt()),
04849 
04850                 query.value(45).toUInt(),
04851 
04852                 flags,
04853                 query.value(42).toUInt(),
04854                 query.value(43).toUInt(),
04855                 query.value(44).toUInt()));
04856 
04857         if (save_not_commflagged)
04858             destination.back()->SaveCommFlagged(COMM_FLAG_NOT_FLAGGED);
04859     }
04860 
04861     return true;
04862 }
04863 
04864 QString SkipTypeToString(int flags)
04865 {
04866     if (COMM_DETECT_COMMFREE == flags)
04867         return QObject::tr("Commercial Free");
04868     if (COMM_DETECT_UNINIT == flags)
04869         return QObject::tr("Use Global Setting");
04870 
04871     QChar chr = '0';
04872     QString ret = QString("0x%1").arg(flags,3,16,chr);
04873     bool blank = COMM_DETECT_BLANK & flags;
04874     bool scene = COMM_DETECT_SCENE & flags;
04875     bool logo  = COMM_DETECT_LOGO  & flags;
04876     bool exp   = COMM_DETECT_2     & flags;
04877     bool prePst= COMM_DETECT_PREPOSTROLL & flags;
04878 
04879     if (blank && scene && logo)
04880         ret = QObject::tr("All Available Methods");
04881     else if (blank && scene && !logo)
04882         ret = QObject::tr("Blank Frame + Scene Change");
04883     else if (blank && !scene && logo)
04884         ret = QObject::tr("Blank Frame + Logo Detection");
04885     else if (!blank && scene && logo)
04886         ret = QObject::tr("Scene Change + Logo Detection");
04887     else if (blank && !scene && !logo)
04888         ret = QObject::tr("Blank Frame Detection");
04889     else if (!blank && scene && !logo)
04890         ret = QObject::tr("Scene Change Detection");
04891     else if (!blank && !scene && logo)
04892         ret = QObject::tr("Logo Detection");
04893 
04894     if (exp)
04895         ret = QObject::tr("Experimental") + ": " + ret;
04896     else if(prePst)
04897         ret = QObject::tr("Pre & Post Roll") + ": " + ret;
04898 
04899     return ret;
04900 }
04901 
04902 deque<int> GetPreferredSkipTypeCombinations(void)
04903 {
04904     deque<int> tmp;
04905     tmp.push_back(COMM_DETECT_BLANK | COMM_DETECT_SCENE | COMM_DETECT_LOGO);
04906     tmp.push_back(COMM_DETECT_BLANK);
04907     tmp.push_back(COMM_DETECT_BLANK | COMM_DETECT_SCENE);
04908     tmp.push_back(COMM_DETECT_SCENE);
04909     tmp.push_back(COMM_DETECT_LOGO);
04910     tmp.push_back(COMM_DETECT_2 | COMM_DETECT_BLANK | COMM_DETECT_LOGO);
04911     tmp.push_back(COMM_DETECT_PREPOSTROLL | COMM_DETECT_BLANK |
04912                   COMM_DETECT_SCENE);
04913     return tmp;
04914 }
04915 
04916 bool GetNextRecordingList(QDateTime &nextRecordingStart,
04917                           bool *hasConflicts,
04918                           vector<ProgramInfo> *list)
04919 {
04920     nextRecordingStart = QDateTime();
04921 
04922     bool dummy;
04923     bool *conflicts = (hasConflicts) ? hasConflicts : &dummy;
04924 
04925     ProgramList progList;
04926     if (!LoadFromScheduler(progList, *conflicts))
04927         return false;
04928 
04929     // find the earliest scheduled recording
04930     ProgramList::const_iterator it = progList.begin();
04931     for (; it != progList.end(); ++it)
04932     {
04933         if (((*it)->GetRecordingStatus() == rsWillRecord) &&
04934             (nextRecordingStart.isNull() ||
04935              nextRecordingStart > (*it)->GetRecordingStartTime()))
04936         {
04937             nextRecordingStart = (*it)->GetRecordingStartTime();
04938         }
04939     }
04940 
04941     if (!list)
04942         return true;
04943 
04944     // save the details of the earliest recording(s)
04945     for (it = progList.begin(); it != progList.end(); ++it)
04946     {
04947         if (((*it)->GetRecordingStatus()    == rsWillRecord) &&
04948             ((*it)->GetRecordingStartTime() == nextRecordingStart))
04949         {
04950             list->push_back(ProgramInfo(**it));
04951         }
04952     }
04953 
04954     return true;
04955 }
04956 
04957 PMapDBReplacement::PMapDBReplacement() : lock(new QMutex())
04958 {
04959 }
04960 
04961 PMapDBReplacement::~PMapDBReplacement()
04962 {
04963     delete lock;
04964 }
04965 
04966 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends