• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Classes
  • Files

programinfo.cpp

Go to the documentation of this file.
00001 #include <iostream>
00002 #include <qsocket.h>
00003 #include <qregexp.h>
00004 #include <qmap.h>
00005 #include <qlayout.h>
00006 #include <qlabel.h>
00007 #include <qapplication.h>
00008 #include <qfile.h>
00009 #include <qfileinfo.h>
00010 
00011 #include <sys/types.h>
00012 #include <unistd.h>
00013 #include <stdlib.h>
00014 
00015 #include "programinfo.h"
00016 #include "progdetails.h"
00017 #include "scheduledrecording.h"
00018 #include "util.h"
00019 #include "mythcontext.h"
00020 #include "dialogbox.h"
00021 #include "remoteutil.h"
00022 #include "jobqueue.h"
00023 #include "mythdbcon.h"
00024 #include "storagegroup.h"
00025 #include "previewgenerator.h"
00026 
00027 #define LOC QString("ProgramInfo: ")
00028 #define LOC_ERR QString("ProgramInfo, Error: ")
00029 
00030 using namespace std;
00031 
00032 // works only for integer divisors of 60
00033 static const uint kUnknownProgramLength = 30;
00034 
00035 static bool insert_program(const ProgramInfo*,
00036                            const ScheduledRecording*);
00037 
00046 static QString StripHTMLTags(const QString& src)
00047 {
00048     QString dst(src);
00049 
00050     // First replace some tags with some ASCII formatting
00051     dst.replace( QRegExp("<br[^>]*>"), "\n" );
00052     dst.replace( QRegExp("<p[^>]*>"),  "\n" );
00053     dst.replace( QRegExp("<li[^>]*>"), "\n- " );
00054     // And finally remve any remaining tags
00055     dst.replace( QRegExp("<[^>]*>"), "" );
00056 
00057     return dst;
00058 }
00059 
00073 ProgramInfo::ProgramInfo(void) :
00074     regExpLock(false), regExpSeries("0000$")
00075 {
00076     spread = -1;
00077     startCol = -1;
00078     isVideo = false;
00079     lenMins = 0;
00080 
00081     title = "";
00082     subtitle = "";
00083     description = "";
00084     category = "";
00085     chanstr = "";
00086     chansign = "";
00087     channame = "";
00088     chancommfree = 0;
00089     chanOutputFilters = "";
00090     year = "";
00091     stars = 0;
00092     availableStatus = asAvailable;    
00093 
00094     pathname = "";
00095     storagegroup = QString("Default");
00096     filesize = 0;
00097     hostname = "";
00098     programflags = 0;
00099     transcoder = 0;
00100     audioproperties = 0;
00101     videoproperties = 0;
00102     subtitleType = 0;
00103 
00104     startts = mythCurrentDateTime();
00105     endts = startts;
00106     recstartts = startts;
00107     recendts = startts;
00108     originalAirDate = QDate::QDate (0, 1, 1);
00109     lastmodified = startts;
00110     lastInUseTime = startts.addSecs(-4 * 60 * 60);
00111 
00112     recstatus = rsUnknown;
00113     oldrecstatus = rsUnknown;
00114     savedrecstatus = rsUnknown;
00115     recpriority2 = 0;
00116     reactivate = false;
00117     recordid = 0;
00118     parentid = 0;
00119     rectype = kNotRecording;
00120     dupin = kDupsInAll;
00121     dupmethod = kDupCheckSubDesc;
00122 
00123     sourceid = 0;
00124     inputid = 0;
00125     cardid = 0;
00126     shareable = false;
00127     duplicate = false;
00128     schedulerid = "";
00129     findid = 0;
00130     recpriority = 0;
00131     recgroup = QString("Default");
00132     playgroup = QString("Default");
00133 
00134     hasAirDate = false;
00135     repeat = false;
00136 
00137     seriesid = "";
00138     programid = "";
00139     ignoreBookmark = false;
00140     catType = "";
00141 
00142     sortTitle = "";
00143 
00144     inUseForWhat = "";
00145 
00146     record = NULL;
00147 }   
00148 
00152 ProgramInfo::ProgramInfo(const ProgramInfo &other) :
00153     record(NULL), regExpLock(false), regExpSeries("0000$")
00154 {
00155     clone(other);
00156 }
00157 
00161 ProgramInfo &ProgramInfo::operator=(const ProgramInfo &other) 
00162 { 
00163     return clone(other); 
00164 }
00165 
00169 ProgramInfo &ProgramInfo::clone(const ProgramInfo &other)
00170 {
00171     if (record)
00172     {
00173         record->deleteLater();
00174         record = NULL;
00175     }
00176     
00177     isVideo = other.isVideo;
00178     lenMins = other.lenMins;
00179     
00180     title = QDeepCopy<QString>(other.title);
00181     subtitle = QDeepCopy<QString>(other.subtitle);
00182     description = QDeepCopy<QString>(other.description);
00183     category = QDeepCopy<QString>(other.category);
00184     chanid = QDeepCopy<QString>(other.chanid);
00185     chanstr = QDeepCopy<QString>(other.chanstr);
00186     chansign = QDeepCopy<QString>(other.chansign);
00187     channame = QDeepCopy<QString>(other.channame);
00188     chancommfree = other.chancommfree;
00189     chanOutputFilters = QDeepCopy<QString>(other.chanOutputFilters);
00190     
00191     pathname = QDeepCopy<QString>(other.pathname);
00192     storagegroup = QDeepCopy<QString>(other.storagegroup);
00193     filesize = other.filesize;
00194     hostname = QDeepCopy<QString>(other.hostname);
00195 
00196     startts = other.startts;
00197     endts = other.endts;
00198     recstartts = other.recstartts;
00199     recendts = other.recendts;
00200     lastmodified = other.lastmodified;
00201     spread = other.spread;
00202     startCol = other.startCol;
00203 
00204     availableStatus = other.availableStatus;
00205 
00206     recstatus = other.recstatus;
00207     oldrecstatus = other.oldrecstatus;
00208     savedrecstatus = other.savedrecstatus;
00209     recpriority2 = other.recpriority2;
00210     reactivate = other.reactivate;
00211     recordid = other.recordid;
00212     parentid = other.parentid;
00213     rectype = other.rectype;
00214     dupin = other.dupin;
00215     dupmethod = other.dupmethod;
00216 
00217     sourceid = other.sourceid;
00218     inputid = other.inputid;
00219     cardid = other.cardid;
00220     shareable = other.shareable;
00221     duplicate = other.duplicate;
00222     schedulerid = QDeepCopy<QString>(other.schedulerid);
00223     findid = other.findid;
00224     recpriority = other.recpriority;
00225     recgroup = QDeepCopy<QString>(other.recgroup);
00226     playgroup = QDeepCopy<QString>(other.playgroup);
00227     programflags = other.programflags;
00228     transcoder = other.transcoder;
00229     audioproperties = other.audioproperties;
00230     videoproperties = other.videoproperties;
00231     subtitleType = other.subtitleType;
00232 
00233     hasAirDate = other.hasAirDate;
00234     repeat = other.repeat;
00235 
00236     seriesid = QDeepCopy<QString>(other.seriesid);
00237     programid = QDeepCopy<QString>(other.programid);
00238     catType = QDeepCopy<QString>(other.catType);
00239 
00240     sortTitle = QDeepCopy<QString>(other.sortTitle);
00241 
00242     originalAirDate = other.originalAirDate;
00243     stars = other.stars;
00244     year = QDeepCopy<QString>(other.year);
00245     ignoreBookmark = other.ignoreBookmark; 
00246    
00247     inUseForWhat = QDeepCopy<QString>(other.inUseForWhat);
00248     lastInUseTime = other.lastInUseTime;
00249     record = NULL;
00250 
00251     return *this;
00252 }
00253 
00257 ProgramInfo::~ProgramInfo() 
00258 {
00259     if (record)
00260     {
00261         record->deleteLater();
00262         record = NULL;
00263     }
00264 }
00265 
00269 QString ProgramInfo::MakeUniqueKey(void) const
00270 {
00271     return chanid + "_" + recstartts.toString(Qt::ISODate);
00272 }
00273 
00274 #define INT_TO_LIST(x)       sprintf(tmp, "%i", (x)); list << tmp;
00275 
00276 #define DATETIME_TO_LIST(x)  INT_TO_LIST((x).toTime_t())
00277 
00278 #define LONGLONG_TO_LIST(x)  INT_TO_LIST((int)((x) >> 32))  \
00279                              INT_TO_LIST((int)((x) & 0xffffffffLL))
00280 
00281 #define STR_TO_LIST(x)       if ((x).isNull()) list << ""; else list << (x);
00282 #define DATE_TO_LIST(x)      STR_TO_LIST((x).toString(Qt::ISODate))
00283 
00284 #define FLOAT_TO_LIST(x)     sprintf(tmp, "%f", (x)); list << tmp;
00285 
00292 void ProgramInfo::ToStringList(QStringList &list) const
00293 {
00294     char tmp[64];
00295 
00296     STR_TO_LIST(title)
00297     STR_TO_LIST(subtitle)
00298     STR_TO_LIST(description)
00299     STR_TO_LIST(category)
00300     STR_TO_LIST(chanid)
00301     STR_TO_LIST(chanstr)
00302     STR_TO_LIST(chansign)
00303     STR_TO_LIST(channame)
00304     STR_TO_LIST(pathname)
00305     LONGLONG_TO_LIST(filesize)
00306 
00307     DATETIME_TO_LIST(startts)
00308     DATETIME_TO_LIST(endts)
00309     INT_TO_LIST(duplicate)
00310     INT_TO_LIST(shareable)
00311     INT_TO_LIST(findid);
00312     STR_TO_LIST(hostname)
00313     INT_TO_LIST(sourceid)
00314     INT_TO_LIST(cardid)
00315     INT_TO_LIST(inputid)
00316     INT_TO_LIST(recpriority)
00317     INT_TO_LIST(recstatus)
00318     INT_TO_LIST(recordid)
00319     INT_TO_LIST(rectype)
00320     INT_TO_LIST(dupin)
00321     INT_TO_LIST(dupmethod)
00322     DATETIME_TO_LIST(recstartts)
00323     DATETIME_TO_LIST(recendts)
00324     INT_TO_LIST(repeat)
00325     INT_TO_LIST(programflags)
00326     STR_TO_LIST((recgroup != "") ? recgroup : "Default")
00327     INT_TO_LIST(chancommfree)
00328     STR_TO_LIST(chanOutputFilters)
00329     STR_TO_LIST(seriesid)
00330     STR_TO_LIST(programid)
00331     DATETIME_TO_LIST(lastmodified)
00332     FLOAT_TO_LIST(stars)
00333     DATE_TO_LIST(originalAirDate)
00334     INT_TO_LIST(hasAirDate)
00335     STR_TO_LIST((playgroup != "") ? playgroup : "Default")
00336     INT_TO_LIST(recpriority2)
00337     INT_TO_LIST(parentid)
00338     STR_TO_LIST((storagegroup != "") ? storagegroup : "Default")
00339     INT_TO_LIST(audioproperties)
00340     INT_TO_LIST(videoproperties)
00341     INT_TO_LIST(subtitleType)
00342 /* do not forget to update the NUMPROGRAMLINES defines! */
00343 }
00344 
00360 bool ProgramInfo::FromStringList(const QStringList &list, uint offset)
00361 {
00362     QStringList::const_iterator it = list.at(offset);
00363     return FromStringList(it, list.end());
00364 }
00365 
00366 #define NEXT_STR()             if (it == listend)     \
00367                                {                      \
00368                                    VERBOSE(VB_IMPORTANT, listerror); \
00369                                    return false;      \
00370                                }                      \
00371                                ts = *it++;            \
00372                                if (ts.isNull())       \
00373                                    ts = "";           
00374                                
00375 #define INT_FROM_LIST(x)       NEXT_STR() (x) = atoi(ts.ascii());
00376 #define ENUM_FROM_LIST(x, y)   NEXT_STR() (x) = (y)atoi(ts.ascii());
00377 
00378 #define DATETIME_FROM_LIST(x)  NEXT_STR() (x).setTime_t((uint)atoi(ts.ascii()));
00379 #define DATE_FROM_LIST(x)      NEXT_STR() (x) = \
00380                                    ((ts.isEmpty()) || (ts == "0000-00-00")) ?\
00381                                    QDate() : QDate::fromString(ts, Qt::ISODate)
00382 
00383 #define LONGLONG_FROM_LIST(x)  INT_FROM_LIST(ti); NEXT_STR() \
00384                                (x) = ((long long)(ti) << 32) | \
00385                                ((long long)(atoi(ts.ascii())) & 0xffffffffLL);
00386 
00387 #define STR_FROM_LIST(x)       NEXT_STR() (x) = ts;
00388 
00389 #define FLOAT_FROM_LIST(x)     NEXT_STR() (x) = atof(ts.ascii());
00390 
00402 bool ProgramInfo::FromStringList(QStringList::const_iterator &it,
00403                                  QStringList::const_iterator  listend)
00404 {
00405     QString listerror = LOC + "FromStringList, not enough items in list."; 
00406     QString ts;
00407     int ti;
00408 
00409     STR_FROM_LIST(title)
00410     STR_FROM_LIST(subtitle)
00411     STR_FROM_LIST(description)
00412     STR_FROM_LIST(category)
00413     STR_FROM_LIST(chanid)
00414     STR_FROM_LIST(chanstr)
00415     STR_FROM_LIST(chansign)
00416     STR_FROM_LIST(channame)
00417     STR_FROM_LIST(pathname)
00418     LONGLONG_FROM_LIST(filesize)
00419 
00420     DATETIME_FROM_LIST(startts)
00421     DATETIME_FROM_LIST(endts)
00422     NEXT_STR() // dummy place holder
00423     INT_FROM_LIST(shareable)
00424     INT_FROM_LIST(findid)
00425     STR_FROM_LIST(hostname)
00426     INT_FROM_LIST(sourceid)
00427     INT_FROM_LIST(cardid)
00428     INT_FROM_LIST(inputid)
00429     INT_FROM_LIST(recpriority)
00430     ENUM_FROM_LIST(recstatus, RecStatusType)
00431     INT_FROM_LIST(recordid)
00432     ENUM_FROM_LIST(rectype, RecordingType)
00433     ENUM_FROM_LIST(dupin, RecordingDupInType)
00434     ENUM_FROM_LIST(dupmethod, RecordingDupMethodType)
00435     DATETIME_FROM_LIST(recstartts)
00436     DATETIME_FROM_LIST(recendts)
00437     INT_FROM_LIST(repeat)
00438     INT_FROM_LIST(programflags)
00439     STR_FROM_LIST(recgroup)
00440     INT_FROM_LIST(chancommfree)
00441     STR_FROM_LIST(chanOutputFilters)
00442     STR_FROM_LIST(seriesid)
00443     STR_FROM_LIST(programid)
00444     DATETIME_FROM_LIST(lastmodified)
00445     FLOAT_FROM_LIST(stars)
00446     DATE_FROM_LIST(originalAirDate);
00447     INT_FROM_LIST(hasAirDate);
00448     STR_FROM_LIST(playgroup)
00449     INT_FROM_LIST(recpriority2)
00450     INT_FROM_LIST(parentid)
00451     STR_FROM_LIST(storagegroup)
00452     INT_FROM_LIST(audioproperties)
00453     INT_FROM_LIST(videoproperties)
00454     INT_FROM_LIST(subtitleType)
00455 
00456     return true;
00457 }
00458 
00463 void ProgramInfo::ToMap(QMap<QString, QString> &progMap, 
00464                         bool showrerecord) const
00465 {
00466     QString timeFormat = gContext->GetSetting("TimeFormat", "h:mm AP");
00467     QString dateFormat = gContext->GetSetting("DateFormat", "ddd MMMM d");
00468     QString fullDateFormat = dateFormat;
00469     if (fullDateFormat.find(QRegExp("yyyy")) < 0)
00470         fullDateFormat += " yyyy";
00471     QString shortDateFormat = gContext->GetSetting("ShortDateFormat", "M/d");
00472     QString channelFormat = 
00473         gContext->GetSetting("ChannelFormat", "<num> <sign>");
00474     QString longChannelFormat = 
00475         gContext->GetSetting("LongChannelFormat", "<num> <name>");
00476 
00477     QDateTime timeNow = QDateTime::currentDateTime();
00478 
00479     QString length;
00480     int hours, minutes, seconds;
00481     
00482     progMap["title"] = title;
00483     progMap["subtitle"] = subtitle;
00484     progMap["description"] = StripHTMLTags(description);
00485     progMap["category"] = category;
00486     progMap["callsign"] = chansign;
00487     progMap["commfree"] = chancommfree;
00488     progMap["outputfilters"] = chanOutputFilters;
00489     if (isVideo)
00490     {
00491         progMap["starttime"] = "";
00492         progMap["startdate"] = "";
00493         progMap["endtime"] = "";
00494         progMap["enddate"] = "";
00495         progMap["recstarttime"] = "";
00496         progMap["recstartdate"] = "";
00497         progMap["recendtime"] = "";
00498         progMap["recenddate"] = "";
00499         
00500         if (startts.date().year() == 1895)
00501         {
00502            progMap["startdate"] = "?";
00503            progMap["recstartdate"] = "?";
00504         }
00505         else
00506         {
00507             progMap["startdate"] = startts.toString("yyyy");
00508             progMap["recstartdate"] = startts.toString("yyyy");
00509         }
00510     }
00511     else
00512     {
00513         progMap["starttime"] = startts.toString(timeFormat);
00514         progMap["startdate"] = startts.toString(shortDateFormat);
00515         progMap["endtime"] = endts.toString(timeFormat);
00516         progMap["enddate"] = endts.toString(shortDateFormat);
00517         progMap["recstarttime"] = recstartts.toString(timeFormat);
00518         progMap["recstartdate"] = recstartts.toString(shortDateFormat);
00519         progMap["recendtime"] = recendts.toString(timeFormat);
00520         progMap["recenddate"] = recendts.toString(shortDateFormat);
00521     }
00522     
00523     progMap["lastmodifiedtime"] = lastmodified.toString(timeFormat);
00524     progMap["lastmodifieddate"] = lastmodified.toString(dateFormat);
00525     progMap["lastmodified"] = lastmodified.toString(dateFormat) + " " +
00526                               lastmodified.toString(timeFormat);
00527 
00528     progMap["channum"] = chanstr;
00529     progMap["chanid"] = chanid;
00530     progMap["channel"] = ChannelText(channelFormat);
00531     progMap["longchannel"] = ChannelText(longChannelFormat);
00532     progMap["iconpath"] = "";
00533 
00534     QString tmpSize;
00535 
00536     tmpSize.sprintf("%0.2f ", filesize / 1024.0 / 1024.0 / 1024.0);
00537     tmpSize += QObject::tr("GB", "GigaBytes");
00538     progMap["filesize_str"] = tmpSize;
00539 
00540     progMap["filesize"] = longLongToString(filesize);
00541 
00542     if (isVideo)
00543     {
00544         minutes = lenMins;
00545         seconds = lenMins * 60;
00546     }
00547     else
00548     {
00549         seconds = recstartts.secsTo(recendts);
00550         minutes = seconds / 60;
00551     }
00552     
00553     progMap["lenmins"] = QString("%1 %2").
00554         arg(minutes).arg(QObject::tr("minutes"));
00555     hours   = minutes / 60;
00556     minutes = minutes % 60;
00557     length.sprintf("%d:%02d", hours, minutes);
00558     progMap["lentime"] = length;
00559 
00560     progMap["rec_type"] = RecTypeChar();
00561     progMap["rec_str"] = RecTypeText();
00562     if (rectype != kNotRecording)
00563     {
00564         QString tmp_rec;
00565         if (recendts > timeNow && recstatus <= rsWillRecord || 
00566             recstatus == rsConflict || recstatus == rsLaterShowing)
00567         {
00568             tmp_rec += QString().sprintf(" %+d", recpriority);
00569             if (recpriority2)
00570                 tmp_rec += QString().sprintf("/%+d", recpriority2);
00571             tmp_rec += " ";
00572         }
00573         else
00574         {
00575             tmp_rec += " -- ";
00576         }
00577         if (showrerecord && recstatus == rsRecorded && !duplicate)
00578             tmp_rec += QObject::tr("Re-Record");
00579         else
00580             tmp_rec += RecStatusText();
00581         progMap["rec_str"] += tmp_rec;
00582     }
00583     progMap["recordingstatus"] = progMap["rec_str"];
00584     progMap["type"] = progMap["rec_str"];
00585 
00586     progMap["recpriority"] = recpriority;
00587     progMap["recpriority2"] = recpriority2;
00588     progMap["recgroup"] = recgroup;
00589     progMap["playgroup"] = playgroup;
00590     progMap["programflags"] = programflags;
00591 
00592     progMap["audioproperties"] = audioproperties;
00593     progMap["videoproperties"] = videoproperties;
00594     progMap["subtitleType"] = subtitleType;
00595 
00596     progMap["timedate"] = recstartts.date().toString(dateFormat) + ", " +
00597                           recstartts.time().toString(timeFormat) + " - " +
00598                           recendts.time().toString(timeFormat);
00599 
00600     progMap["shorttimedate"] =
00601                           recstartts.date().toString(shortDateFormat) + ", " +
00602                           recstartts.time().toString(timeFormat) + " - " +
00603                           recendts.time().toString(timeFormat);
00604 
00605     progMap["time"] = timeNow.time().toString(timeFormat);
00606 
00607     MSqlQuery query(MSqlQuery::InitCon());
00608 
00609     query.prepare("SELECT icon FROM channel WHERE chanid = :CHANID ;");
00610     query.bindValue(":CHANID", chanid);
00611         
00612     if (query.exec() && query.isActive() && query.size() > 0)
00613         if (query.next())
00614             progMap["iconpath"] = query.value(0).toString();
00615 
00616     progMap["RECSTATUS"] = RecStatusText();
00617 
00618     if (repeat)
00619     {
00620         progMap["REPEAT"] = QString("(%1) ").arg(QObject::tr("Repeat"));
00621         progMap["LONGREPEAT"] = progMap["REPEAT"];
00622         if (hasAirDate)
00623             progMap["LONGREPEAT"] = QString("(%1 %2) ")
00624                                 .arg(QObject::tr("Repeat"))
00625                                 .arg(originalAirDate.toString(fullDateFormat));
00626     }
00627     else
00628     {
00629         progMap["REPEAT"] = "";
00630         progMap["LONGREPEAT"] = "";
00631     }
00632 
00633     progMap["seriesid"] = seriesid;
00634     progMap["programid"] = programid;
00635     progMap["catType"] = catType;
00636 
00637     progMap["year"] = year;
00638     
00639     if (stars)
00640     {
00641         QString str = QObject::tr("stars");
00642         if (stars > 0 && stars <= 0.25)
00643             str = QObject::tr("star");
00644 
00645         if (year != "")
00646             progMap["stars"] = QString("(%1, %2 %3) ")
00647                                        .arg(year).arg(4.0 * stars).arg(str);
00648         else
00649             progMap["stars"] = QString("(%1 %2) ").arg(4.0 * stars).arg(str);
00650     }
00651     else
00652         progMap["stars"] = "";
00653 
00654     if (hasAirDate)
00655     {
00656         progMap["originalairdate"] = originalAirDate.toString(dateFormat);
00657         progMap["shortoriginalairdate"] = 
00658                                 originalAirDate.toString(shortDateFormat);
00659     }
00660     else
00661     {
00662         progMap["originalairdate"] = "";
00663         progMap["shortoriginalairdate"] = "";
00664     }
00665 }
00666 
00670 int ProgramInfo::CalculateLength(void) const
00671 {
00672     if (isVideo)
00673         return lenMins * 60;
00674     else
00675         return startts.secsTo(endts);
00676 }
00677 
00682 int ProgramInfo::SecsTillStart(void) const
00683 {
00684     return QDateTime::currentDateTime().secsTo(startts);
00685 }
00686 
00698 ProgramInfo *ProgramInfo::GetProgramAtDateTime(const QString &channel, 
00699                                                const QDateTime &dtime,
00700                                                bool genUnknown,
00701                                                int clampHoursMax)
00702 {
00703     ProgramList schedList;
00704     ProgramList progList;
00705 
00706     MSqlBindings bindings;
00707     QString querystr = "WHERE program.chanid = :CHANID "
00708                        "  AND program.starttime < :STARTTS "
00709                        "  AND program.endtime > :STARTTS ";
00710     bindings[":CHANID"] = channel;
00711     bindings[":STARTTS"] = dtime.toString("yyyy-MM-ddThh:mm:50");
00712 
00713     schedList.FromScheduler();
00714     progList.FromProgram(querystr, bindings, schedList);
00715 
00716     if (!progList.isEmpty())
00717     {
00718         ProgramInfo *pginfo = progList.take(0);
00719 
00720         if (clampHoursMax > 0)
00721         {
00722             if (dtime.secsTo(pginfo->endts) > clampHoursMax * 3600)
00723             {
00724                 pginfo->endts = dtime.addSecs(clampHoursMax * 3600);
00725                 pginfo->recendts = pginfo->endts;
00726             }
00727         }
00728     
00729         return pginfo;
00730     }
00731     ProgramInfo *p = new ProgramInfo;
00732 
00733     MSqlQuery query(MSqlQuery::InitCon());
00734     query.prepare("SELECT chanid, channum, callsign, name, "
00735                   "commmethod, outputfilters "
00736                   "FROM channel "
00737                   "WHERE chanid = :CHANID ;");
00738     query.bindValue(":CHANID", channel);
00739 
00740     if (!query.exec() || !query.isActive())
00741     {
00742         MythContext::DBError(LOC + "GetProgramAtDateTime", 
00743                              query);
00744         return p;
00745     }
00746 
00747     if (!query.next())
00748         return p;
00749 
00750     p->chanid             = query.value(0).toString();
00751     p->startts            = dtime;
00752     p->endts              = dtime;
00753     p->recstartts         = p->startts;
00754     p->recendts           = p->endts;
00755     p->lastmodified       = p->startts;
00756     p->title              = gContext->GetSetting("UnknownTitle");
00757     p->subtitle           = "";
00758     p->description        = "";
00759     p->category           = "";
00760     p->chanstr            = query.value(1).toString();
00761     p->chansign           = QString::fromUtf8(query.value(2).toString());
00762     p->channame           = QString::fromUtf8(query.value(3).toString());
00763     p->repeat             = 0;
00764     p->chancommfree       = (query.value(4).toInt() == -2);
00765     p->chanOutputFilters  = query.value(5).toString();
00766     p->seriesid           = "";
00767     p->programid          = "";
00768     p->year               = "";
00769     p->stars              = 0.0f;
00770 
00771     if (!genUnknown)
00772         return p;
00773 
00774     // Round endtime up to the next half-hour.
00775     p->endts.setTime(QTime(p->endts.time().hour(),
00776                            p->endts.time().minute() / kUnknownProgramLength
00777                            * kUnknownProgramLength));
00778     p->endts = p->endts.addSecs(kUnknownProgramLength * 60);
00779 
00780     // if under a minute, bump it up to the next half hour
00781     if (p->startts.secsTo(p->endts) < 60)
00782         p->endts = p->endts.addSecs(kUnknownProgramLength * 60);
00783 
00784     p->recendts = p->endts;
00785 
00786     // Find next program starttime
00787     QDateTime nextstart = p->startts;
00788     querystr = "WHERE program.chanid    = :CHANID  AND "
00789                "      program.starttime > :STARTTS "
00790                "GROUP BY program.starttime ORDER BY program.starttime LIMIT 1 ";
00791     bindings[":CHANID"]  = channel;
00792     bindings[":STARTTS"] = dtime.toString("yyyy-MM-ddThh:mm:50");
00793 
00794     progList.FromProgram(querystr, bindings, schedList);
00795 
00796     if (!progList.isEmpty())
00797         nextstart = progList.at(0)->startts;
00798 
00799     if (nextstart > p->startts && nextstart < p->recendts)
00800     {
00801         p->recendts = p->endts = nextstart;
00802     }
00803 
00804     return p;
00805 }
00806 
00807 QString ProgramInfo::toString(void) const
00808 {
00809     QString str("");
00810     str += LOC + "channame(" + channame + ") startts(" +
00811         startts.toString() + ") endts(" + endts.toString() + ")\n";
00812     str += "             recstartts(" + recstartts.toString() +
00813         ") recendts(" + recendts.toString() + ")\n";
00814     str += "             title(" + title + ")";
00815     return str;
00816 }
00817 
00822 ProgramInfo *ProgramInfo::GetProgramFromBasename(const QString filename)
00823 {
00824     QFileInfo inf(filename);
00825 
00826     MSqlQuery query(MSqlQuery::InitCon());
00827 
00828     query.prepare("SELECT chanid, starttime FROM recorded "
00829                   "WHERE basename = :BASENAME;");
00830     query.bindValue(":BASENAME", inf.fileName());
00831 
00832     if (query.exec() && query.isActive() && query.size() > 0)
00833     {
00834         query.next();
00835 
00836         return GetProgramFromRecorded(query.value(0).toString(),
00837                                       query.value(1).toDateTime());
00838     }
00839     
00840     return NULL;
00841 }
00842 
00847 ProgramInfo *ProgramInfo::GetProgramFromRecorded(const QString &channel, 
00848                                                  const QDateTime &dtime)
00849 {
00850     return GetProgramFromRecorded(channel, dtime.toString(Qt::ISODate));
00851 }
00852 
00857 ProgramInfo *ProgramInfo::GetProgramFromRecorded(const QString &channel, 
00858                                                  const QString &starttime)
00859 {
00860     MSqlQuery query(MSqlQuery::InitCon());
00861     query.prepare("SELECT recorded.chanid,starttime,endtime,title, "
00862                   "subtitle,description,channel.channum, "
00863                   "channel.callsign,channel.name,channel.commmethod, "
00864                   "channel.outputfilters,seriesid,programid,filesize, "
00865                   "lastmodified,stars,previouslyshown,originalairdate, "
00866                   "hostname,recordid,transcoder,playgroup, "
00867                   "recorded.recpriority,progstart,progend,basename,recgroup, "
00868                   "storagegroup "
00869                   "FROM recorded "
00870                   "LEFT JOIN channel "
00871                   "ON recorded.chanid = channel.chanid "
00872                   "WHERE recorded.chanid = :CHANNEL "
00873                   "AND starttime = :STARTTIME ;");
00874     query.bindValue(":CHANNEL", channel);
00875     query.bindValue(":STARTTIME", starttime);
00876     
00877     if (query.exec() && query.isActive() && query.size() > 0)
00878     {
00879         query.next();
00880 
00881         ProgramInfo *proginfo = new ProgramInfo;
00882         proginfo->chanid = query.value(0).toString();
00883         proginfo->startts = query.value(23).toDateTime();
00884         proginfo->endts = query.value(24).toDateTime();
00885         proginfo->recstartts = query.value(1).toDateTime();
00886         proginfo->recendts = query.value(2).toDateTime();
00887         proginfo->title = QString::fromUtf8(query.value(3).toString());
00888         proginfo->subtitle = QString::fromUtf8(query.value(4).toString());
00889         proginfo->description = QString::fromUtf8(query.value(5).toString());
00890 
00891         proginfo->chanstr = query.value(6).toString();
00892         proginfo->chansign = QString::fromUtf8(query.value(7).toString());
00893         proginfo->channame = QString::fromUtf8(query.value(8).toString());
00894         proginfo->chancommfree = (query.value(9).toInt() == -2);
00895         proginfo->chanOutputFilters = query.value(10).toString();
00896         proginfo->seriesid = query.value(11).toString();
00897         proginfo->programid = query.value(12).toString();
00898         proginfo->filesize = stringToLongLong(query.value(13).toString());
00899 
00900         proginfo->lastmodified =
00901                   QDateTime::fromString(query.value(14).toString(),
00902                                         Qt::ISODate);
00903         
00904         proginfo->stars = query.value(15).toDouble();
00905         proginfo->repeat = query.value(16).toInt();
00906         
00907         if (query.value(17).isNull() || query.value(17).toString().isEmpty())
00908         {
00909             proginfo->originalAirDate = QDate::QDate (0, 1, 1);
00910             proginfo->hasAirDate = false;
00911         }
00912         else
00913         {
00914             proginfo->originalAirDate = 
00915                 QDate::fromString(query.value(17).toString(),Qt::ISODate);
00916 
00917             if (proginfo->originalAirDate > QDate(1940, 1, 1))
00918                 proginfo->hasAirDate = true;
00919             else
00920                 proginfo->hasAirDate = false;
00921         }
00922         proginfo->hostname = query.value(18).toString();
00923         proginfo->recstatus = rsRecorded;
00924         proginfo->recordid = query.value(19).toInt();
00925         proginfo->transcoder = query.value(20).toInt();
00926 
00927         proginfo->spread = -1;
00928 
00929         proginfo->programflags = proginfo->getProgramFlags();
00930 
00931         proginfo->getProgramProperties();
00932 
00933         proginfo->recgroup = QString::fromUtf8(query.value(26).toString());
00934         proginfo->storagegroup = QString::fromUtf8(query.value(27).toString());
00935         proginfo->playgroup = QString::fromUtf8(query.value(21).toString());
00936         proginfo->recpriority = query.value(22).toInt();
00937 
00938         proginfo->pathname = QString::fromUtf8(query.value(25).toString());
00939 
00940         return proginfo;
00941     }
00942 
00943     return NULL;
00944 }
00945 
00949 bool ProgramInfo::IsFindApplicable(void) const
00950 {
00951     return rectype == kFindDailyRecord ||
00952            rectype == kFindWeeklyRecord;
00953 }
00954 
00959 int ProgramInfo::IsProgramRecurring(void) const
00960 {
00961     QDateTime dtime = startts;
00962 
00963     int weekday = dtime.date().dayOfWeek();
00964     if (weekday < 6)
00965     {
00966         // week day    
00967         int daysadd = 1;
00968         if (weekday == 5)
00969             daysadd = 3;
00970 
00971         QDateTime checktime = dtime.addDays(daysadd);
00972 
00973         ProgramInfo *nextday = GetProgramAtDateTime(chanid, checktime);
00974 
00975         if (NULL == nextday)
00976             return -1;
00977 
00978         if (nextday && nextday->title == title)
00979         {
00980             delete nextday;
00981             return 1;
00982         }
00983         if (nextday)
00984             delete nextday;
00985     }
00986 
00987     QDateTime checktime = dtime.addDays(7);
00988     ProgramInfo *nextweek = GetProgramAtDateTime(chanid, checktime);
00989 
00990     if (NULL == nextweek)
00991         return -1;
00992 
00993     if (nextweek && nextweek->title == title)
00994     {
00995         delete nextweek;
00996         return 2;
00997     }
00998 
00999     if (nextweek)
01000         delete nextweek;
01001     return 0;
01002 }
01003 
01009 RecordingType ProgramInfo::GetProgramRecordingStatus(void)
01010 {
01011     if (record == NULL) 
01012     {
01013         record = new ScheduledRecording();
01014         record->loadByProgram(this);
01015     }
01016 
01017     return record->getRecordingType();
01018 }
01019 
01025 QString ProgramInfo::GetProgramRecordingProfile(void)
01026 {
01027     if (record == NULL)
01028     {
01029         record = new ScheduledRecording();
01030         record->loadByProgram(this);
01031     }
01032 
01033     return record->getProfileName();
01034 }
01035 
01040 int ProgramInfo::GetAutoRunJobs(void) const
01041 {
01042     if (record == NULL) 
01043     {
01044         record = new ScheduledRecording();
01045         record->loadByProgram(this);
01046     }
01047 
01048     return record->GetAutoRunJobs();
01049 }
01050 
01055 int ProgramInfo::GetChannelRecPriority(const QString &channel)
01056 {
01057     MSqlQuery query(MSqlQuery::InitCon());
01058     query.prepare("SELECT recpriority FROM channel WHERE chanid = :CHANID ;");
01059     query.bindValue(":CHANID", channel);
01060     
01061     if (query.exec() && query.isActive() && query.size() > 0)
01062     {
01063         query.next();
01064         return query.value(0).toInt();
01065     }
01066 
01067     return 0;
01068 }
01069 
01073 int ProgramInfo::GetRecordingTypeRecPriority(RecordingType type)
01074 {
01075     switch (type)
01076     {
01077         case kSingleRecord:
01078             return gContext->GetNumSetting("SingleRecordRecPriority", 1);
01079         case kTimeslotRecord:
01080             return gContext->GetNumSetting("TimeslotRecordRecPriority", 0);
01081         case kWeekslotRecord:
01082             return gContext->GetNumSetting("WeekslotRecordRecPriority", 0);
01083         case kChannelRecord:
01084             return gContext->GetNumSetting("ChannelRecordRecPriority", 0);
01085         case kAllRecord:
01086             return gContext->GetNumSetting("AllRecordRecPriority", 0);
01087         case kFindOneRecord:
01088         case kFindDailyRecord:
01089         case kFindWeeklyRecord:
01090             return gContext->GetNumSetting("FindOneRecordRecPriority", -1);
01091         case kOverrideRecord:
01092         case kDontRecord:
01093             return gContext->GetNumSetting("OverrideRecordRecPriority", 0);
01094         default:
01095             return 0;
01096     }
01097 }
01098 
01102 void ProgramInfo::ApplyRecordRecID(void)
01103 {
01104     MSqlQuery query(MSqlQuery::InitCon());
01105 
01106     if (getRecordID() < 0)
01107     {
01108         VERBOSE(VB_IMPORTANT,
01109                 "ProgInfo Error: ApplyRecordRecID(void) needs recordid");
01110         return;
01111     }
01112 
01113     query.prepare("UPDATE recorded "
01114                   "SET recordid = :RECID "
01115                   "WHERE chanid = :CHANID AND starttime = :START");
01116 
01117     if (rectype == kOverrideRecord && parentid > 0)
01118         query.bindValue(":RECID", parentid);
01119     else
01120         query.bindValue(":RECID",  getRecordID());
01121     query.bindValue(":CHANID", chanid);
01122     query.bindValue(":START",  recstartts);
01123 
01124     if (!query.exec())
01125         MythContext::DBError(LOC + "RecordID update", query);
01126 }
01127 
01133 // newstate uses same values as return of GetProgramRecordingState
01134 void ProgramInfo::ApplyRecordStateChange(RecordingType newstate)
01135 {
01136     GetProgramRecordingStatus();
01137     if (newstate == kOverrideRecord || newstate == kDontRecord)
01138         record->makeOverride();
01139     record->setRecordingType(newstate);
01140     record->save();
01141 }
01142 
01148 void ProgramInfo::ApplyRecordRecPriorityChange(int newrecpriority)
01149 {
01150     GetProgramRecordingStatus();
01151     record->setRecPriority(newrecpriority);
01152     record->save();
01153 }
01154 
01160 void ProgramInfo::ApplyRecordRecGroupChange(const QString &newrecgroup)
01161 {
01162     MSqlQuery query(MSqlQuery::InitCon());
01163 
01164     query.prepare("UPDATE recorded"
01165                   " SET recgroup = :RECGROUP"
01166                   " WHERE chanid = :CHANID"
01167                   " AND starttime = :START ;");
01168     query.bindValue(":RECGROUP", newrecgroup.utf8());
01169     query.bindValue(":START", recstartts);
01170     query.bindValue(":CHANID", chanid);
01171 
01172     if (!query.exec())
01173         MythContext::DBError("RecGroup update", query);
01174 
01175     recgroup = newrecgroup;
01176 }
01177 
01183 void ProgramInfo::ApplyRecordPlayGroupChange(const QString &newplaygroup)
01184 {
01185     MSqlQuery query(MSqlQuery::InitCon());
01186 
01187     query.prepare("UPDATE recorded"
01188                   " SET playgroup = :PLAYGROUP"
01189                   " WHERE chanid = :CHANID"
01190                   " AND starttime = :START ;");
01191     query.bindValue(":PLAYGROUP", newplaygroup.utf8());
01192     query.bindValue(":START", recstartts);
01193     query.bindValue(":CHANID", chanid);
01194 
01195     if (!query.exec())
01196         MythContext::DBError("PlayGroup update", query);
01197 
01198     playgroup = newplaygroup;
01199 }
01200 
01207 void ProgramInfo::ApplyRecordRecTitleChange(const QString &newTitle, const QString &newSubtitle)
01208 {
01209     MSqlQuery query(MSqlQuery::InitCon());
01210 
01211     query.prepare("UPDATE recorded"
01212                   " SET title = :TITLE, subtitle = :SUBTITLE"
01213                   " WHERE chanid = :CHANID"
01214                   " AND starttime = :START ;");
01215     query.bindValue(":TITLE", newTitle.utf8());
01216     query.bindValue(":SUBTITLE", newSubtitle.utf8());
01217     query.bindValue(":CHANID", chanid);
01218     query.bindValue(":START", recstartts.toString("yyyyMMddhhmmss"));
01219 
01220     if (!query.exec())
01221         MythContext::DBError("RecTitle update", query);
01222 
01223     title = newTitle;
01224     subtitle = newSubtitle;
01225 }
01226 
01227 /* \fn ProgramInfo::ApplyTranscoderProfileChange(QString profile) 
01228  * \brief Sets the transcoder profile for a recording
01229  * \param profile Descriptive name of the profile. ie: Autodetect
01230  */
01231 void ProgramInfo::ApplyTranscoderProfileChange(QString profile)
01232 {
01233     if(profile == "Default") // use whatever is already in the transcoder
01234         return;
01235 
01236     MSqlQuery query(MSqlQuery::InitCon());
01237     
01238     if(profile == "Autodetect")
01239     {
01240         query.prepare("UPDATE recorded "
01241                       "SET transcoder = 0 "
01242                       "WHERE chanid = :CHANID "
01243                       "AND starttime = :START");
01244         query.bindValue(":CHANID",  chanid);
01245         query.bindValue(":START",  recstartts);
01246     
01247         if (!query.exec())
01248             MythContext::DBError(LOC + "unable to update transcoder "
01249                                  "in recorded table", query);
01250     }
01251     else
01252     {
01253         MSqlQuery pidquery(MSqlQuery::InitCon());
01254         pidquery.prepare("SELECT r.id "
01255                          "FROM recordingprofiles r, profilegroups p "
01256                          "WHERE r.profilegroup = p.id "
01257                              "AND p.name = 'Transcoders' "
01258                              "AND r.name = :PROFILE ");
01259         pidquery.bindValue(":PROFILE",  profile);
01260 
01261         if (pidquery.exec() && pidquery.isActive() && pidquery.next())
01262         {
01263             query.prepare("UPDATE recorded "
01264                           "SET transcoder = :TRANSCODER "
01265                           "WHERE chanid = :CHANID "
01266                               "AND starttime = :START");
01267             query.bindValue(":TRANSCODER", pidquery.value(0).toInt());
01268             query.bindValue(":CHANID",  chanid);
01269             query.bindValue(":START",  recstartts);
01270     
01271             if (!query.exec())
01272                 MythContext::DBError(LOC + "unable to update transcoder "
01273                                      "in recorded table", query);
01274         }
01275         else
01276             MythContext::DBError("PlaybackBox: unable to query transcoder "
01277                                  "profile ID", query);
01278     }
01279 }
01280 
01299 void ProgramInfo::ToggleRecord(void)
01300 {
01301     RecordingType curType = GetProgramRecordingStatus();
01302 
01303     switch (curType) 
01304     {
01305         case kNotRecording:
01306             ApplyRecordStateChange(kSingleRecord);
01307             break;
01308         case kSingleRecord:
01309             ApplyRecordStateChange(kFindOneRecord);
01310             break;
01311         case kFindOneRecord:
01312             ApplyRecordStateChange(kAllRecord);
01313             break;
01314         case kAllRecord:
01315             ApplyRecordStateChange(kSingleRecord);
01316             break;
01317 
01318         case kOverrideRecord:
01319             ApplyRecordStateChange(kDontRecord);
01320             break;
01321         case kDontRecord:
01322             ApplyRecordStateChange(kOverrideRecord);
01323             break;
01324 
01325         default:
01326             ApplyRecordStateChange(kAllRecord);
01327             break;
01328 /*
01329         case kNotRecording:
01330             ApplyRecordStateChange(kSingleRecord);
01331             break;
01332         case kSingleRecord:
01333             ApplyRecordStateChange(kFindOneRecord);
01334             break;
01335         case kFindOneRecord:
01336             ApplyRecordStateChange(kWeekslotRecord);
01337             break;
01338         case kWeekslotRecord:
01339             ApplyRecordStateChange(kFindWeeklyRecord);
01340             break;
01341         case kFindWeeklyRecord:
01342             ApplyRecordStateChange(kTimeslotRecord);
01343             break;
01344         case kTimeslotRecord:
01345             ApplyRecordStateChange(kFindDailyRecord);
01346             break;
01347         case kFindDailyRecord:
01348             ApplyRecordStateChange(kChannelRecord);
01349             break;
01350         case kChannelRecord:
01351             ApplyRecordStateChange(kAllRecord);
01352             break;
01353         case kAllRecord:
01354         default:
01355             ApplyRecordStateChange(kNotRecording);
01356             break;
01357         case kOverrideRecord:
01358             ApplyRecordStateChange(kDontRecord);
01359             break;
01360         case kDontRecord:
01361             ApplyRecordStateChange(kOverrideRecord);
01362             break;
01363 */
01364     }
01365 }
01366 
01370 ScheduledRecording* ProgramInfo::GetScheduledRecording(void)
01371 {
01372     GetProgramRecordingStatus();
01373     return record;
01374 }
01375 
01379 int ProgramInfo::getRecordID(void)
01380 {
01381     GetProgramRecordingStatus();
01382     recordid = record->getRecordID();
01383     return recordid;
01384 }
01385 
01390 bool ProgramInfo::IsSameProgram(const ProgramInfo& other) const
01391 {
01392     if (rectype == kFindOneRecord)
01393         return recordid == other.recordid;
01394 
01395     if (findid && findid == other.findid &&
01396         (recordid == other.recordid || recordid == other.parentid))
01397            return true;
01398 
01399     if (title.lower() != other.title.lower())
01400         return false;
01401 
01402     if (findid && findid == other.findid)
01403         return true;
01404 
01405     if (dupmethod & kDupCheckNone)
01406         return false;
01407 
01408     if (catType == "series")
01409     {
01410         QMutexLocker locker(&regExpLock);
01411         if (programid.contains(regExpSeries))
01412             return false;
01413     }
01414 
01415     if (!programid.isEmpty() && !other.programid.isEmpty())
01416         return programid == other.programid;
01417 
01418     if ((dupmethod & kDupCheckSub) &&
01419         ((subtitle.isEmpty()) ||
01420          (subtitle.lower() != other.subtitle.lower())))
01421         return false;
01422 
01423     if ((dupmethod & kDupCheckDesc) &&
01424         ((description.isEmpty()) ||
01425          (description.lower() != other.description.lower())))
01426         return false;
01427 
01428     if ((dupmethod & kDupCheckSubThenDesc) &&
01429         ((subtitle.isEmpty() && other.subtitle.isEmpty() &&
01430           description.lower() != other.description.lower()) ||
01431          (subtitle.lower() != other.subtitle.lower()) ||
01432          (description.isEmpty() && subtitle.isEmpty())))
01433         return false;
01434 
01435     return true;
01436 }
01437 
01443 bool ProgramInfo::IsSameTimeslot(const ProgramInfo& other) const
01444 {
01445     if (title != other.title)
01446         return false;
01447     if (startts == other.startts && endts == other.endts &&
01448         (chanid == other.chanid || 
01449          (chansign != "" && chansign == other.chansign)))
01450         return true;
01451 
01452     return false;
01453 }
01454 
01461 bool ProgramInfo::IsSameProgramTimeslot(const ProgramInfo &other) const
01462 {
01463     if (title != other.title)
01464         return false;
01465     if ((chanid == other.chanid ||
01466          (chansign != "" && chansign == other.chansign)) &&
01467         startts < other.endts &&
01468         endts > other.startts)
01469         return true;
01470 
01471     return false;
01472 }
01473 
01478 QString ProgramInfo::CreateRecordBasename(const QString &ext) const
01479 {
01480     QString starts = recstartts.toString("yyyyMMddhhmmss");
01481 
01482     QString retval = QString("%1_%2.%3").arg(chanid)
01483                              .arg(starts).arg(ext);
01484     
01485     return retval;
01486 }               
01487 
01491 bool ProgramInfo::SetRecordBasename(QString basename)
01492 {
01493     MSqlQuery query(MSqlQuery::InitCon());
01494     query.prepare("UPDATE recorded "
01495                   "SET basename = :BASENAME "
01496                   "WHERE chanid = :CHANID AND "
01497                   "      starttime = :STARTTIME;");
01498     query.bindValue(":CHANID", chanid);
01499     query.bindValue(":STARTTIME", recstartts);
01500     query.bindValue(":BASENAME", basename);
01501 
01502     if (!query.exec() || !query.isActive())
01503     {
01504         MythContext::DBError("SetRecordBasename", query);
01505         return false;
01506     }
01507     
01508     return true;
01509 }               
01510 
01515 QString ProgramInfo::GetRecordBasename(bool fromDB) const
01516 {
01517     QString retval = "";
01518 
01519     if (!fromDB && !pathname.isEmpty())
01520         retval = pathname.section('/', -1);
01521     else
01522     {
01523         MSqlQuery query(MSqlQuery::InitCon());
01524         query.prepare("SELECT basename FROM recorded "
01525                       "WHERE chanid = :CHANID AND "
01526                       "      starttime = :STARTTIME;");
01527         query.bindValue(":CHANID", chanid);
01528         query.bindValue(":STARTTIME", recstartts);
01529 
01530         if (!query.exec() || !query.isActive())
01531             MythContext::DBError("GetRecordBasename", query);
01532         else if (query.size() < 1)
01533             VERBOSE(VB_IMPORTANT, QString("GetRecordBasename found no entry "
01534                     "for %1 @ %2")
01535                     .arg(chanid).arg(recstartts.toString(Qt::ISODate)));
01536         else
01537         {
01538             query.next();
01539             retval = query.value(0).toString();
01540         }
01541     }
01542 
01543     return retval;
01544 }               
01545 
01551 QString ProgramInfo::GetPlaybackURL(bool checkMaster, bool forceCheckLocal)
01552 {
01553     QString tmpURL;
01554     QString basename = GetRecordBasename(true);
01555 
01556     if (basename == "")
01557         return "";
01558 
01559     bool alwaysStream = gContext->GetNumSetting("AlwaysStreamFiles", 0);
01560 
01561     if ((!alwaysStream) ||
01562         (forceCheckLocal) ||
01563         (hostname == gContext->GetHostName()))
01564     {
01565         // Check to see if the file exists locally
01566         StorageGroup sgroup(storagegroup);
01567         tmpURL = sgroup.FindRecordingFile(basename);
01568 
01569         if (tmpURL != "")
01570         {
01571             VERBOSE(VB_FILE, LOC +
01572                     QString("GetPlaybackURL: File is local: '%1'").arg(tmpURL));
01573             return tmpURL;
01574         }
01575         else if (hostname == gContext->GetHostName())
01576         {
01577             VERBOSE(VB_IMPORTANT, LOC_ERR +
01578                     QString("GetPlaybackURL: '%1' should be local, but it can "
01579                             "not be found.").arg(basename));
01580             return QString("/GetPlaybackURL/UNABLE/TO/FIND/LOCAL/FILE/ON/%1/%2")
01581                            .arg(hostname).arg(basename);
01582         }
01583     }
01584 
01585     // Check to see if we should stream from the master backend
01586     if ((checkMaster) &&
01587         (gContext->GetNumSetting("MasterBackendOverride", 0)) &&
01588         (RemoteCheckFile(this, false)))
01589     {
01590         tmpURL = QString("myth://") +
01591                  gContext->GetSetting("MasterServerIP") + ":" +
01592                  gContext->GetSetting("MasterServerPort") + "/" + basename;
01593         VERBOSE(VB_FILE, LOC +
01594                 QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL));
01595         return tmpURL;
01596     }
01597 
01598     // Fallback to streaming from the backend the recording was created on
01599     tmpURL = QString("myth://") +
01600              gContext->GetSettingOnHost("BackendServerIP", hostname) + ":" +
01601              gContext->GetSettingOnHost("BackendServerPort", hostname) + "/" +
01602              basename;
01603 
01604     VERBOSE(VB_FILE, LOC + QString("GetPlaybackURL: Using default of: '%1'")
01605                                    .arg(tmpURL));
01606 
01607     return tmpURL;
01608 }
01609 
01618 void ProgramInfo::StartedRecording(QString ext)
01619 {
01620     QString dirname = pathname;
01621 
01622     if (!record)
01623     {
01624         record = new ScheduledRecording();
01625         record->loadByProgram(this);
01626     }
01627 
01628     hostname = gContext->GetHostName();
01629     pathname = CreateRecordBasename(ext);
01630 
01631     int count = 0;
01632     while (!insert_program(this, record) && count < 50)
01633     {
01634         recstartts = recstartts.addSecs(1);
01635         pathname = CreateRecordBasename(ext);
01636         count++;
01637     }
01638 
01639     if (count >= 50)
01640     {
01641         VERBOSE(VB_IMPORTANT, "Couldn't insert program");
01642         return;
01643     }
01644 
01645     pathname = dirname + "/" + pathname;
01646 
01647     VERBOSE(VB_FILE, QString(LOC + "StartedRecording: Recording to '%1'")
01648                              .arg(pathname));
01649 
01650 
01651     MSqlQuery query(MSqlQuery::InitCon());
01652 
01653     query.prepare("DELETE FROM recordedseek WHERE chanid = :CHANID"
01654                   " AND starttime = :START;");
01655     query.bindValue(":CHANID", chanid);
01656     query.bindValue(":START", recstartts);
01657 
01658     if (!query.exec() || !query.isActive())
01659         MythContext::DBError("Clear seek info on record", query);
01660 
01661     query.prepare("DELETE FROM recordedmarkup WHERE chanid = :CHANID"
01662                   " AND starttime = :START;");
01663     query.bindValue(":CHANID", chanid);
01664     query.bindValue(":START", recstartts);
01665 
01666     if (!query.exec() || !query.isActive())
01667         MythContext::DBError("Clear markup on record", query);
01668 
01669     query.prepare("REPLACE INTO recordedcredits"
01670                  " SELECT * FROM credits"
01671                  " WHERE chanid = :CHANID AND starttime = :START;");
01672     query.bindValue(":CHANID", chanid);
01673     query.bindValue(":START", startts);
01674     if (!query.exec() || !query.isActive())
01675         MythContext::DBError("Copy program credits on record", query);
01676 
01677     query.prepare("REPLACE INTO recordedprogram"
01678                  " SELECT * from program"
01679                  " WHERE chanid = :CHANID AND starttime = :START"
01680                  " AND title = :TITLE;");
01681     query.bindValue(":CHANID", chanid);
01682     query.bindValue(":START", startts);
01683     query.bindValue(":TITLE", title.utf8());
01684     if (!query.exec() || !query.isActive())
01685         MythContext::DBError("Copy program data on record", query);
01686 
01687     query.prepare("REPLACE INTO recordedrating"
01688                  " SELECT * from programrating"
01689                  " WHERE chanid = :CHANID AND starttime = :START;");
01690     query.bindValue(":CHANID", chanid);
01691     query.bindValue(":START", startts);
01692     if (!query.exec() || !query.isActive())
01693         MythContext::DBError("Copy program ratings on record", query);    
01694 }
01695 
01696 static bool insert_program(const ProgramInfo        *pg,
01697                            const ScheduledRecording *schd)
01698 {
01699     MSqlQuery query(MSqlQuery::InitCon());
01700 
01701     query.prepare("LOCK TABLES recorded WRITE");
01702     if (!query.exec())
01703     {
01704         MythContext::DBError("insert_program -- lock", query);
01705         return false;
01706     }
01707 
01708     query.prepare(
01709         "SELECT recordid "
01710         "    FROM recorded "
01711         "    WHERE chanid    = :CHANID AND "
01712         "          starttime = :STARTS");
01713     query.bindValue(":CHANID", pg->chanid);
01714     query.bindValue(":STARTS", pg->recstartts);
01715 
01716     if (!query.exec() || query.size())
01717     {
01718         if (!query.isActive())
01719             MythContext::DBError("insert_program -- select", query);
01720         else
01721             VERBOSE(VB_IMPORTANT, "recording already exists...");
01722 
01723         query.prepare("UNLOCK TABLES");
01724         query.exec();
01725         return false;
01726     }
01727 
01728     query.prepare(    
01729         "INSERT INTO recorded "
01730         "   (chanid,    starttime,   endtime,         title,            "
01731         "    subtitle,  description, hostname,        category,         "
01732         "    recgroup,  autoexpire,  recordid,        seriesid,         "
01733         "    programid, stars,       previouslyshown, originalairdate,  "
01734         "    findid,    transcoder,  playgroup,       recpriority,      "
01735         "    basename,  progstart,   progend,         profile,          "
01736         "    duplicate, storagegroup) "
01737         "VALUES"
01738         "  (:CHANID,   :STARTS,     :ENDS,           :TITLE,            "
01739         "   :SUBTITLE, :DESC,       :HOSTNAME,       :CATEGORY,         "
01740         "   :RECGROUP, :AUTOEXP,    :RECORDID,       :SERIESID,         "
01741         "   :PROGRAMID,:STARS,      :REPEAT,         :ORIGAIRDATE,      "
01742         "   :FINDID,   :TRANSCODER, :PLAYGROUP,      :RECPRIORITY,      "
01743         "   :BASENAME, :PROGSTART,  :PROGEND,        :PROFILE,          "
01744         "   0,         :STORGROUP) "
01745         );
01746 
01747     if (pg->rectype == kOverrideRecord)
01748         query.bindValue(":RECORDID",    pg->parentid);
01749     else
01750         query.bindValue(":RECORDID",    pg->recordid);
01751 
01752     if (pg->hasAirDate)
01753         query.bindValue(":ORIGAIRDATE", pg->originalAirDate);
01754     else
01755         query.bindValue(":ORIGAIRDATE", "0000-00-00");
01756 
01757     query.bindValue(":CHANID",      pg->chanid);
01758     query.bindValue(":STARTS",      pg->recstartts);
01759     query.bindValue(":ENDS",        pg->recendts);
01760     query.bindValue(":TITLE",       pg->title.utf8());
01761     query.bindValue(":SUBTITLE",    pg->subtitle.utf8());
01762     query.bindValue(":DESC",        pg->description.utf8());
01763     query.bindValue(":HOSTNAME",    pg->hostname);
01764     query.bindValue(":CATEGORY",    pg->category.utf8());
01765     query.bindValue(":RECGROUP",    pg->recgroup.utf8());
01766     query.bindValue(":AUTOEXP",     schd->GetAutoExpire());
01767     query.bindValue(":SERIESID",    pg->seriesid.utf8());
01768     query.bindValue(":PROGRAMID",   pg->programid.utf8());
01769     query.bindValue(":FINDID",      pg->findid);
01770     query.bindValue(":STARS",       pg->stars);
01771     query.bindValue(":REPEAT",      pg->repeat);
01772     query.bindValue(":TRANSCODER",  schd->GetTranscoder());
01773     query.bindValue(":PLAYGROUP",   pg->playgroup.utf8());
01774     query.bindValue(":RECPRIORITY", schd->getRecPriority());
01775     query.bindValue(":BASENAME",    pg->pathname);
01776     query.bindValue(":STORGROUP",   pg->storagegroup.utf8());
01777     query.bindValue(":PROGSTART",   pg->startts);
01778     query.bindValue(":PROGEND",     pg->endts);
01779     query.bindValue(":PROFILE",     schd->getProfileName());
01780 
01781     bool ok = query.exec() && (query.numRowsAffected() > 0);
01782     bool active = query.isActive();
01783 
01784     query.prepare("UNLOCK TABLES");
01785     query.exec();
01786 
01787     if (!ok && !active)
01788         MythContext::DBError("insert_program -- insert", query);
01789 
01790     else if (pg->recordid > 0)
01791     {
01792         query.prepare("UPDATE channel SET last_record = NOW() "
01793                       "WHERE chanid = :CHANID");
01794         query.bindValue(":CHANID", pg->chanid);
01795         query.exec();
01796 
01797         query.prepare("UPDATE record SET last_record = NOW() "
01798                       "WHERE recordid = :RECORDID");
01799         query.bindValue(":RECORDID", pg->recordid);
01800         query.exec();
01801 
01802         if (pg->rectype == kOverrideRecord && pg->parentid > 0)
01803         {
01804             query.prepare("UPDATE record SET last_record = NOW() "
01805                           "WHERE recordid = :PARENTID");
01806             query.bindValue(":PARENTID", pg->parentid);
01807             query.exec();
01808         }
01809     }
01810 
01811     return ok;
01812 }
01813 
01819 void ProgramInfo::FinishedRecording(bool prematurestop)
01820 {
01821     MSqlQuery query(MSqlQuery::InitCon());
01822     query.prepare("UPDATE recorded SET endtime = :ENDTIME, "
01823                   "       duplicate = 1 "
01824                   "WHERE chanid = :CHANID AND "
01825                   "    starttime = :STARTTIME ");
01826     query.bindValue(":ENDTIME", recendts);
01827     query.bindValue(":CHANID", chanid);
01828     query.bindValue(":STARTTIME", recstartts);
01829 
01830     query.exec();
01831 
01832     if (!query.isActive())
01833         MythContext::DBError("FinishedRecording update", query);
01834 
01835     GetProgramRecordingStatus();
01836     if (!prematurestop)
01837         record->doneRecording(*this);
01838 }
01839 
01844 void ProgramInfo::UpdateRecordingEnd(void)
01845 {
01846     MSqlQuery query(MSqlQuery::InitCon());
01847     query.prepare("UPDATE recorded SET endtime = :ENDTIME "
01848                   "WHERE chanid = :CHANID AND "
01849                   "    starttime = :STARTTIME ");
01850     query.bindValue(":ENDTIME", recendts);
01851 
01852     query.bindValue(":CHANID", chanid);
01853     query.bindValue(":STARTTIME", recstartts);
01854 
01855     query.exec();
01856 
01857     if (!query.isActive())
01858         MythContext::DBError("FinishedRecording update", query);
01859 }
01860 
01861 
01865 void ProgramInfo::SetFilesize(long long fsize)
01866 {
01867     filesize = fsize;
01868 
01869     MSqlQuery query(MSqlQuery::InitCon());
01870     query.prepare("UPDATE recorded SET filesize = :FILESIZE"
01871                   " WHERE chanid = :CHANID"
01872                   " AND starttime = :STARTTIME ;");
01873     query.bindValue(":FILESIZE", longLongToString(fsize));
01874     query.bindValue(":CHANID", chanid);
01875     query.bindValue(":STARTTIME", recstartts);
01876     
01877     if (!query.exec() || !query.isActive())
01878         MythContext::DBError("File size update", 
01879                              query);
01880 }
01881 
01885 long long ProgramInfo::GetFilesize(void)
01886 {
01887     MSqlQuery query(MSqlQuery::InitCon());
01888     
01889     query.prepare("SELECT filesize FROM recorded"
01890                   " WHERE chanid = :CHANID"
01891                   " AND starttime = :STARTTIME ;");
01892     query.bindValue(":CHANID", chanid);
01893     query.bindValue(":STARTTIME", recstartts);
01894     
01895     if (query.exec() && query.isActive() && query.size() > 0)
01896     {
01897         query.next();
01898         filesize = stringToLongLong(query.value(0).toString());
01899     }
01900     else
01901         filesize = 0;
01902 
01903     return filesize;
01904 }
01905 
01909 int ProgramInfo::GetMplexID(void) const
01910 {
01911     int ret = 0;
01912     if (chanid)
01913     {
01914         MSqlQuery query(MSqlQuery::InitCon());
01915 
01916         query.prepare("SELECT mplexid FROM channel "
01917                       "WHERE chanid = :CHANID");
01918         query.bindValue(":CHANID", chanid);
01919 
01920         if (!query.exec())
01921             MythContext::DBError("GetMplexID", query);
01922         else if (query.next())
01923             ret = query.value(0).toUInt();
01924 
01925         // clear out bogus mplexid's
01926         ret = (32767 == ret) ? 0 : ret;
01927     }
01928 
01929     return ret;
01930 }
01931 
01936 void ProgramInfo::SetBookmark(long long pos) const
01937 {
01938     ClearMarkupMap(MARK_BOOKMARK);
01939     frm_dir_map_t bookmarkmap;
01940     bookmarkmap[pos] = MARK_BOOKMARK;
01941     SetMarkupMap(bookmarkmap);
01942 
01943     if (!isVideo)
01944     {
01945         MSqlQuery query(MSqlQuery::InitCon());
01946     
01947         // For the time being, note whether a bookmark
01948         // exists in the recorded table
01949         query.prepare("UPDATE recorded"
01950                       " SET bookmark = :BOOKMARKFLAG"
01951                       " WHERE chanid = :CHANID"
01952                       " AND starttime = :STARTTIME ;");
01953 
01954         query.bindValue(":BOOKMARKFLAG", pos == 0 ? 0 : 1);
01955         query.bindValue(":CHANID", chanid);
01956         query.bindValue(":STARTTIME", recstartts);
01957 
01958         if (!query.exec() || !query.isActive())
01959             MythContext::DBError("bookmark flag update", query);
01960     }
01961 }
01962 
01970 long long ProgramInfo::GetBookmark(void) const
01971 {
01972     QMap<long long, int>::Iterator i;
01973     long long pos = 0;
01974 
01975     if (ignoreBookmark)
01976         return pos;
01977 
01978     frm_dir_map_t bookmarkmap;
01979     GetMarkupMap(bookmarkmap, MARK_BOOKMARK);
01980     
01981     if (bookmarkmap.isEmpty())
01982         return pos;
01983 
01984     i = bookmarkmap.begin();
01985     pos = i.key();
01986     
01987     return pos;
01988 }
01989 
01995 QStringList ProgramInfo::GetDVDBookmark(QString serialid, bool delbookmark) const
01996 {
01997     QStringList fields = QStringList();
01998     MSqlQuery query(MSqlQuery::InitCon());
01999 
02000     if (!ignoreBookmark)
02001     {
02002         query.prepare(" SELECT title, framenum, audionum, subtitlenum "
02003                         " FROM dvdbookmark "
02004                         " WHERE serialid = ? ");
02005         query.addBindValue(serialid.utf8());
02006 
02007         if (query.exec() && query.isActive() && query.size() > 0)
02008         {
02009             query.next();
02010             for(int i = 0; i < 4; i++)
02011                 fields.append(query.value(i).toString());
02012         }
02013     }
02014 
02015     if (delbookmark)
02016     {
02017         int days = -(gContext->GetNumSetting("DVDBookmarkDays", 10));
02018         QDateTime removedate = mythCurrentDateTime().addDays(days);
02019         query.prepare(" DELETE from dvdbookmark "
02020                         " WHERE timestamp < ? ");
02021         query.addBindValue(removedate.toString(Qt::ISODate));
02022 
02023         if (!query.exec() || !query.isActive())
02024             MythContext::DBError("GetDVDBookmark deleting old entries", query);
02025     }
02026 
02027     return fields;
02028 }
02029 
02030 void ProgramInfo::SetDVDBookmark(QStringList fields) const
02031 {
02032     QStringList::Iterator it = fields.begin();
02033     MSqlQuery query(MSqlQuery::InitCon());
02034 
02035     QString serialid    = *(it);
02036     QString name        = *(++it);
02037     QString title       = *(++it);
02038     QString audionum    = *(++it);
02039     QString subtitlenum = *(++it);
02040     QString frame       = *(++it);
02041 
02042     query.prepare("INSERT IGNORE INTO dvdbookmark "
02043                     " (serialid, name)"
02044                     " VALUES ( :SERIALID, :NAME );");
02045     query.bindValue(":SERIALID", serialid.utf8());
02046     query.bindValue(":NAME", name.utf8());
02047 
02048     if (!query.exec() || !query.isActive())
02049         MythContext::DBError("SetDVDBookmark inserting", query);
02050 
02051     query.prepare(" UPDATE dvdbookmark "
02052                     " SET title       = ? , "
02053                     "     audionum    = ? , "
02054                     "     subtitlenum = ? , "
02055                     "     framenum    = ? , "
02056                     "     timestamp   = NOW() "
02057                     " WHERE serialid = ? ;");
02058     query.addBindValue(title.utf8());
02059     query.addBindValue(audionum.utf8());
02060     query.addBindValue(subtitlenum.utf8());
02061     query.addBindValue(frame.utf8());
02062     query.addBindValue(serialid.utf8());
02063 
02064     if (!query.exec() || !query.isActive())
02065         MythContext::DBError("SetDVDBookmark updating", query);
02066 }
02071 void ProgramInfo::SetWatchedFlag(bool watchedFlag) const
02072 {
02073 
02074     if (!isVideo)
02075     {
02076         MSqlQuery query(MSqlQuery::InitCon());
02077 
02078         query.prepare("UPDATE recorded"
02079                     " SET watched = :WATCHEDFLAG"
02080                     " WHERE chanid = :CHANID"
02081                     " AND starttime = :STARTTIME ;");
02082         query.bindValue(":CHANID", chanid);
02083         query.bindValue(":STARTTIME", recstartts);
02084 
02085         if (watchedFlag)
02086             query.bindValue(":WATCHEDFLAG", 1);
02087         else
02088             query.bindValue(":WATCHEDFLAG", 0);
02089 
02090         if (!query.exec() || !query.isActive())
02091             MythContext::DBError("Set watched flag", query);
02092         else
02093             UpdateLastDelete(watchedFlag);
02094     }
02095 }
02096 
02102 bool ProgramInfo::IsEditing(void) const
02103 {
02104     MSqlQuery query(MSqlQuery::InitCon());
02105 
02106     query.prepare("SELECT editing FROM recorded"
02107                   " WHERE chanid = :CHANID"
02108                   " AND starttime = :STARTTIME ;");
02109     query.bindValue(":CHANID", chanid);
02110     query.bindValue(":STARTTIME", recstartts);
02111 
02112     if (query.exec() && query.isActive() && query.size() > 0)
02113     {
02114         query.next();
02115         return query.value(0).toBool();
02116     }
02117 
02118     return false;
02119 }
02120 
02125 void ProgramInfo::SetEditing(bool edit) const
02126 {
02127     MSqlQuery query(MSqlQuery::InitCon());
02128     
02129     query.prepare("UPDATE recorded"
02130                   " SET editing = :EDIT"
02131                   " WHERE chanid = :CHANID"
02132                   " AND starttime = :STARTTIME ;");
02133     query.bindValue(":EDIT", edit);
02134     query.bindValue(":CHANID", chanid);
02135     query.bindValue(":STARTTIME", recstartts);
02136    
02137     if (!query.exec() || !query.isActive())
02138         MythContext::DBError("Edit status update", 
02139                              query);
02140 }
02141 
02146 void ProgramInfo::SetDeleteFlag(bool deleteFlag) const
02147 {
02148     MSqlQuery query(MSqlQuery::InitCon());
02149 
02150     query.prepare("UPDATE recorded"
02151                   " SET deletepending = :DELETEFLAG"
02152                   " WHERE chanid = :CHANID"
02153                   " AND starttime = :STARTTIME ;");
02154     query.bindValue(":CHANID", chanid);
02155     query.bindValue(":STARTTIME", recstartts);
02156 
02157     if (deleteFlag)
02158         query.bindValue(":DELETEFLAG", 1);
02159     else
02160         query.bindValue(":DELETEFLAG", 0);
02161     
02162     if (!query.exec() || !query.isActive())
02163         MythContext::DBError("Set delete flag", query);
02164 }
02165 
02170 bool ProgramInfo::IsCommFlagged(void) const
02171 {
02172     MSqlQuery query(MSqlQuery::InitCon());
02173     
02174     query.prepare("SELECT commflagged FROM recorded"
02175                   " WHERE chanid = :CHANID"
02176                   " AND starttime = :STARTTIME ;");
02177     query.bindValue(":CHANID", chanid);
02178     query.bindValue(":STARTTIME", recstartts);
02179 
02180     if (query.exec() && query.isActive() && query.size() > 0)
02181     {
02182         query.next();
02183         return query.value(0).toBool();
02184     }
02185 
02186     return false;
02187 }
02188 
02194 bool ProgramInfo::IsInUse(QString &byWho) const
02195 {
02196     if (isVideo)
02197         return false;
02198 
02199     QDateTime oneHourAgo = QDateTime::currentDateTime().addSecs(-61 * 60);
02200     MSqlQuery query(MSqlQuery::InitCon());
02201     
02202     query.prepare("SELECT hostname, recusage FROM inuseprograms "
02203                   " WHERE chanid = :CHANID"
02204                   " AND starttime = :STARTTIME "
02205                   " AND lastupdatetime > :ONEHOURAGO ;");
02206     query.bindValue(":CHANID", chanid);
02207     query.bindValue(":STARTTIME", recstartts);
02208     query.bindValue(":ONEHOURAGO", oneHourAgo);
02209 
02210     byWho = "";
02211     if (query.exec() && query.isActive() && query.size() > 0)
02212     {
02213         QString usageStr, recusage;
02214         while(query.next())
02215         {
02216             usageStr = QObject::tr("Unknown");
02217             recusage = query.value(1).toString();
02218 
02219             if (recusage == "player")
02220                 usageStr = QObject::tr("Playing");
02221             else if (recusage == "recorder")
02222                 usageStr = QObject::tr("Recording");
02223             else if (recusage == "flagger")
02224                 usageStr = QObject::tr("Commercial Flagging");
02225             else if (recusage == "transcoder")
02226                 usageStr = QObject::tr("Transcoding");
02227             else if (recusage == "PIP player")
02228                 usageStr = QObject::tr("PIP");
02229 
02230             byWho += query.value(0).toString() + " (" + usageStr + ")\n";
02231         }
02232 
02233         return true;
02234     }
02235 
02236     return false;
02237 }
02238 
02242 int ProgramInfo::GetTranscodedStatus(void) const
02243 {
02244     MSqlQuery query(MSqlQuery::InitCon());
02245 
02246     query.prepare("SELECT transcoded FROM recorded"
02247                  " WHERE chanid = :CHANID"
02248                  " AND starttime = :STARTTIME ;");
02249     query.bindValue(":CHANID", chanid);
02250     query.bindValue(":STARTTIME", recstartts);
02251 
02252     if (query.exec() && query.isActive() && query.size() > 0)
02253     {
02254         query.next();
02255         return query.value(0).toInt();
02256     }
02257 
02258     return false;
02259 }
02260 
02265 void ProgramInfo::SetTranscoded(int transFlag) const
02266 {
02267     MSqlQuery query(MSqlQuery::InitCon());
02268 
02269     query.prepare("UPDATE recorded"
02270                  " SET transcoded = :FLAG"
02271                  " WHERE chanid = :CHANID"
02272                  " AND starttime = :STARTTIME ;");
02273     query.bindValue(":FLAG", transFlag);
02274     query.bindValue(":CHANID", chanid);
02275     query.bindValue(":STARTTIME", recstartts);
02276 
02277     if(!query.exec() || !query.isActive())
02278         MythContext::DBError("Transcoded status update",
02279                              query);
02280 }
02281 
02286 void ProgramInfo::SetCommFlagged(int flag) const
02287 {
02288     MSqlQuery query(MSqlQuery::InitCon());
02289 
02290     query.prepare("UPDATE recorded"
02291                   " SET commflagged = :FLAG"
02292                   " WHERE chanid = :CHANID"
02293                   " AND starttime = :STARTTIME ;");
02294     query.bindValue(":FLAG", flag);
02295     query.bindValue(":CHANID", chanid);
02296     query.bindValue(":STARTTIME", recstartts);
02297     
02298     if (!query.exec() || !query.isActive())
02299         MythContext::DBError("Commercial Flagged status update",
02300                              query);
02301 }
02302 
02307 void ProgramInfo::SetPreserveEpisode(bool preserveEpisode) const
02308 {
02309     MSqlQuery query(MSqlQuery::InitCon());
02310 
02311     query.prepare("UPDATE recorded"
02312                   " SET preserve = :PRESERVE"
02313                   " WHERE chanid = :CHANID"
02314                   " AND starttime = :STARTTIME ;");
02315     query.bindValue(":PRESERVE", preserveEpisode);
02316     query.bindValue(":CHANID", chanid);
02317     query.bindValue(":STARTTIME", recstartts);
02318 
02319     if (!query.exec() || !query.isActive())
02320         MythContext::DBError("PreserveEpisode update", query);
02321     else
02322         UpdateLastDelete(false);
02323 }
02324 
02329 void ProgramInfo::SetAutoExpire(int autoExpire, bool updateDelete) const
02330 {
02331     MSqlQuery query(MSqlQuery::InitCon());
02332 
02333     query.prepare("UPDATE recorded"
02334                   " SET autoexpire = :AUTOEXPIRE"
02335                   " WHERE chanid = :CHANID"
02336                   " AND starttime = :STARTTIME ;");
02337     query.bindValue(":AUTOEXPIRE", autoExpire);
02338     query.bindValue(":CHANID", chanid);
02339     query.bindValue(":STARTTIME", recstartts);
02340 
02341     if (!query.exec() || !query.isActive())
02342         MythContext::DBError("AutoExpire update", query);
02343     else if (updateDelete)
02344         UpdateLastDelete(true);
02345 }
02346 
02351 void ProgramInfo::UpdateLastDelete(bool setTime) const
02352 {
02353     MSqlQuery query(MSqlQuery::InitCon());
02354 
02355     if (setTime)
02356     {
02357         QDateTime timeNow = QDateTime::currentDateTime();
02358         int delay = recstartts.secsTo(timeNow) / 3600;
02359 
02360         if (delay > 200)
02361             delay = 200;
02362         else if (delay < 1)
02363             delay = 1;
02364 
02365         query.prepare("UPDATE record SET last_delete = :TIME, "
02366                       "avg_delay = (avg_delay * 3 + :DELAY) / 4 "
02367                       "WHERE recordid = :RECORDID");
02368         query.bindValue(":TIME", timeNow);
02369         query.bindValue(":DELAY", delay);
02370         query.bindValue(":RECORDID", recordid);
02371     }
02372     else
02373         query.prepare("UPDATE record SET last_delete = '0000-00-00T00:00:00' "
02374                       "WHERE recordid = :RECORDID");
02375         query.bindValue(":RECORDID", recordid);
02376 
02377     if (!query.exec() || !query.isActive())
02378         MythContext::DBError("Update last_delete", query);
02379 }
02380 
02384 int ProgramInfo::GetAutoExpireFromRecorded(void) const
02385 {
02386     MSqlQuery query(MSqlQuery::InitCon());
02387 
02388     query.prepare("SELECT autoexpire FROM recorded"
02389                   " WHERE chanid = :CHANID"
02390                   " AND starttime = :STARTTIME ;");
02391     query.bindValue(":CHANID", chanid);
02392     query.bindValue(":STARTTIME", recstartts);
02393 
02394     if (query.exec() && query.isActive() && query.size() > 0)
02395     {
02396         query.next();
02397         return query.value(0).toInt();
02398     }
02399 
02400     return false;
02401 }
02402 
02406 bool ProgramInfo::GetPreserveEpisodeFromRecorded(void) const
02407 {
02408     MSqlQuery query(MSqlQuery::InitCon());
02409 
02410     query.prepare("SELECT preserve FROM recorded"
02411                   " WHERE chanid = :CHANID"
02412                   " AND starttime = :STARTTIME ;");
02413     query.bindValue(":CHANID", chanid);
02414     query.bindValue(":STARTTIME", recstartts);
02415 
02416     if (query.exec() && query.isActive() && query.size() > 0)
02417     {
02418         query.next();
02419         return query.value(0).toBool();
02420     }
02421 
02422     return false;
02423 }
02424 
02428 bool ProgramInfo::UsesMaxEpisodes(void) const
02429 {
02430     MSqlQuery query(MSqlQuery::InitCon());
02431 
02432     query.prepare("SELECT maxepisodes FROM record WHERE "
02433                   "recordid = :RECID ;");
02434     query.bindValue(":RECID", recordid);
02435 
02436     if (query.exec() && query.isActive() && query.size() > 0)
02437     {
02438         query.next();
02439         return query.value(0).toInt();
02440     }
02441 
02442     return false;
02443 }
02444 
02445 void ProgramInfo::GetCutList(frm_dir_map_t &delMap) const
02446 {
02447     GetMarkupMap(delMap, MARK_CUT_START);
02448     GetMarkupMap(delMap, MARK_CUT_END, true);
02449 }
02450 
02451 void ProgramInfo::SetCutList(frm_dir_map_t &delMap) const
02452 {
02453     ClearMarkupMap(MARK_CUT_START);
02454     ClearMarkupMap(MARK_CUT_END);
02455     SetMarkupMap(delMap);
02456 
02457     if (!isVideo)
02458     {
02459         MSqlQuery query(MSqlQuery::InitCon());
02460     
02461         // Flag the existence of a cutlist
02462         query.prepare("UPDATE recorded"
02463                       " SET cutlist = :CUTLIST"
02464                       " WHERE chanid = :CHANID"
02465                       " AND starttime = :STARTTIME ;");
02466     
02467         query.bindValue(":CUTLIST", delMap.isEmpty() ? 0 : 1);
02468         query.bindValue(":CHANID", chanid);
02469         query.bindValue(":STARTTIME", recstartts);
02470 
02471         if (!query.exec() || !query.isActive())
02472             MythContext::DBError("cutlist flag update", query);
02473     }
02474 }
02475 
02476 void ProgramInfo::SetCommBreakList(frm_dir_map_t &frames) const
02477 {
02478     ClearMarkupMap(MARK_COMM_START);
02479     ClearMarkupMap(MARK_COMM_END);
02480     SetMarkupMap(frames);
02481 }
02482 
02483 void ProgramInfo::GetCommBreakList(frm_dir_map_t &frames) const
02484 {
02485     GetMarkupMap(frames, MARK_COMM_START);
02486     GetMarkupMap(frames, MARK_COMM_END, true);
02487 }
02488 
02489 void ProgramInfo::ClearMarkupMap(int type, long long min_frame, 
02490                                            long long max_frame) const
02491 {
02492     MSqlQuery query(MSqlQuery::InitCon());
02493     QString comp = "";
02494 
02495     if (min_frame >= 0)
02496     {
02497         char tempc[128];
02498         sprintf(tempc, " AND mark >= %lld ", min_frame);
02499         comp += tempc;
02500     }
02501 
02502     if (max_frame >= 0)
02503     {
02504         char tempc[128];
02505         sprintf(tempc, " AND mark <= %lld ", max_frame);
02506         comp += tempc;
02507     }
02508 
02509     if (type != -100)
02510         comp += QString(" AND type = :TYPE ");
02511     
02512     if (isVideo)
02513     {
02514         query.prepare("DELETE FROM filemarkup"
02515                       " WHERE filename = :PATH "
02516                       + comp + ";");
02517         query.bindValue(":PATH", pathname);
02518     }
02519     else
02520     {
02521         query.prepare("DELETE FROM recordedmarkup"
02522                       " WHERE chanid = :CHANID"
02523                       " AND STARTTIME = :STARTTIME"
02524                       + comp + ";");
02525         query.bindValue(":CHANID", chanid);
02526         query.bindValue(":STARTTIME", recstartts);
02527     }
02528     query.bindValue(":TYPE", type);
02529     
02530     if (!query.exec() || !query.isActive())
02531         MythContext::DBError("ClearMarkupMap deleting", query);
02532 }
02533 
02534 void ProgramInfo::SetMarkupMap(frm_dir_map_t &marks,
02535                                int type, long long min_frame, 
02536                                long long max_frame) const
02537 {
02538     QMap<long long, int>::Iterator i;
02539     MSqlQuery query(MSqlQuery::InitCon());
02540     
02541     if (!isVideo)
02542     {
02543         // check to make sure the show still exists before saving markups
02544         query.prepare("SELECT starttime FROM recorded"
02545                       " WHERE chanid = :CHANID"
02546                       " AND starttime = :STARTTIME ;");
02547         query.bindValue(":CHANID", chanid);
02548         query.bindValue(":STARTTIME", recstartts);
02549 
02550         if (!query.exec() || !query.isActive())
02551             MythContext::DBError("SetMarkupMap checking record table", query);
02552 
02553         if (query.size() < 1 || !query.next())
02554             return;
02555     }
02556  
02557     for (i = marks.begin(); i != marks.end(); ++i)
02558     {
02559         long long frame = i.key();
02560         int mark_type;
02561         QString querystr;
02562        
02563         if ((min_frame >= 0) && (frame < min_frame))
02564             continue;
02565 
02566         if ((max_frame >= 0) && (frame > max_frame))
02567             continue;
02568 
02569         if (type != -100)
02570             mark_type = type;
02571         else
02572             mark_type = i.data();
02573 
02574         if (isVideo)
02575         {
02576             query.prepare("INSERT INTO filemarkup (filename, mark, type)"
02577                           " VALUES ( :PATH , :MARK , :TYPE );");
02578             query.bindValue(":PATH", pathname);
02579         }
02580         else
02581         {
02582             query.prepare("INSERT INTO recordedmarkup"
02583                           " (chanid, starttime, mark, type)"
02584                           " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE );");
02585             query.bindValue(":CHANID", chanid);
02586             query.bindValue(":STARTTIME", recstartts);
02587         }
02588         query.bindValue(":MARK", frame);
02589         query.bindValue(":TYPE", mark_type);
02590        
02591         if (!query.exec() || !query.isActive())
02592             MythContext::DBError("SetMarkupMap inserting", query);
02593     }
02594 }
02595 
02596 void ProgramInfo::GetMarkupMap(frm_dir_map_t &marks,
02597                                int type, bool mergeIntoMap) const
02598 {
02599     if (!mergeIntoMap)
02600         marks.clear();
02601 
02602     MSqlQuery query(MSqlQuery::InitCon());
02603     
02604     if (isVideo)
02605     {
02606         query.prepare("SELECT mark, type FROM filemarkup"
02607                       " WHERE filename = :PATH"
02608                       " AND type = :TYPE"
02609                       " ORDER BY mark;");
02610         query.bindValue(":PATH", pathname);
02611     }
02612     else
02613     {
02614         query.prepare("SELECT mark, type FROM recordedmarkup"
02615                       " WHERE chanid = :CHANID"
02616                       " AND starttime = :STARTTIME"
02617                       " AND type = :TYPE"
02618                       " ORDER BY mark;");
02619         query.bindValue(":CHANID", chanid);
02620         query.bindValue(":STARTTIME", recstartts);
02621     }
02622     query.bindValue(":TYPE", type);
02623 
02624     if (query.exec() && query.isActive() && query.size() > 0)
02625     {
02626         while(query.next())
02627             marks[query.value(0).toLongLong()] = query.value(1).toInt();
02628     }
02629 }
02630 
02631 bool ProgramInfo::CheckMarkupFlag(int type) const
02632 {
02633     QMap<long long, int> flagMap;
02634 
02635     GetMarkupMap(flagMap, type);
02636 
02637     return(flagMap.contains(0));
02638 }
02639 
02640 void ProgramInfo::SetMarkupFlag(int type, bool flag) const
02641 {
02642     ClearMarkupMap(type);
02643 
02644     if (flag)
02645     {
02646         QMap<long long, int> flagMap;
02647 
02648         flagMap[0] = type;
02649         SetMarkupMap(flagMap, type);
02650     }
02651 }
02652 
02653 void ProgramInfo::GetPositionMap(frm_pos_map_t &posMap,
02654                                  int type) const
02655 {
02656     posMap.clear();
02657     MSqlQuery query(MSqlQuery::InitCon());
02658 
02659     if (isVideo)
02660     {
02661         query.prepare("SELECT mark, offset FROM filemarkup"
02662                       " WHERE filename = :PATH"
02663                       " AND type = :TYPE ;");
02664         query.bindValue(":PATH", pathname);
02665     }
02666     else
02667     {
02668         query.prepare("SELECT mark, offset FROM recordedseek"
02669                       " WHERE chanid = :CHANID"
02670                       " AND starttime = :STARTTIME"
02671                       " AND type = :TYPE ;");
02672         query.bindValue(":CHANID", chanid);
02673         query.bindValue(":STARTTIME", recstartts);
02674     }
02675     query.bindValue(":TYPE", type);
02676 
02677     if (query.exec() && query.isActive() && query.size() > 0)
02678     {
02679         while (query.next())
02680             posMap[query.value(0).toLongLong()] = query.value(1).toLongLong();
02681     }
02682 }
02683 
02684 void ProgramInfo::ClearPositionMap(int type) const
02685 {
02686     MSqlQuery query(MSqlQuery::InitCon());
02687   
02688     if (isVideo)
02689     {
02690         query.prepare("DELETE FROM filemarkup"
02691                       " WHERE filename = :PATH"
02692                       " AND type = :TYPE ;");
02693         query.bindValue(":PATH", pathname);
02694     }
02695     else
02696     {
02697         query.prepare("DELETE FROM recordedseek"
02698                       " WHERE chanid = :CHANID"
02699                       " AND starttime = :STARTTIME"
02700                       " AND type = :TYPE ;");
02701         query.bindValue(":CHANID", chanid);
02702         query.bindValue(":STARTTIME", recstartts);
02703     }
02704     query.bindValue(":TYPE", type);
02705                                
02706     if (!query.exec() || !query.isActive())
02707         MythContext::DBError("clear position map", 
02708                              query);
02709 }
02710 
02711 void ProgramInfo::SetPositionMap(frm_pos_map_t &posMap, int type,
02712                                  long long min_frame, long long max_frame) const
02713 {
02714     QMap<long long, long long>::Iterator i;
02715     MSqlQuery query(MSqlQuery::InitCon());
02716     QString comp = "";
02717 
02718     if (min_frame >= 0)
02719         comp += " AND mark >= :MIN_FRAME ";
02720     if (max_frame >= 0)
02721         comp += " AND mark <= :MAX_FRAME ";
02722 
02723     if (isVideo)
02724     {
02725         query.prepare("DELETE FROM filemarkup"
02726                       " WHERE filename = :PATH"
02727                       " AND type = :TYPE"
02728                       + comp + ";");
02729         query.bindValue(":PATH", pathname);
02730     }
02731     else
02732     {
02733         query.prepare("DELETE FROM recordedseek"
02734                       " WHERE chanid = :CHANID"
02735                       " AND starttime = :STARTTIME"
02736                       " AND type = :TYPE"
02737                       + comp + ";");
02738         query.bindValue(":CHANID", chanid);
02739         query.bindValue(":STARTTIME", recstartts);
02740     }
02741     query.bindValue(":TYPE", type);
02742     if (min_frame >= 0)
02743         query.bindValue(":MIN_FRAME", min_frame);
02744     if (max_frame >= 0)
02745         query.bindValue(":MAX_FRAME", max_frame);
02746     
02747     if (!query.exec() || !query.isActive())
02748         MythContext::DBError("position map clear", 
02749                              query);
02750 
02751     for (i = posMap.begin(); i != posMap.end(); ++i)
02752     {
02753         long long frame = i.key();
02754 
02755         if ((min_frame >= 0) && (frame < min_frame))
02756             continue;
02757 
02758         if ((max_frame >= 0) && (frame > max_frame))
02759             continue;
02760 
02761         long long offset = i.data();
02762        
02763         if (isVideo)
02764         {
02765             query.prepare("INSERT INTO filemarkup"
02766                           " (filename, mark, type, offset)"
02767                           " VALUES"
02768                           " ( :PATH , :MARK , :TYPE , :OFFSET );");
02769             query.bindValue(":PATH", pathname);
02770         }
02771         else
02772         {        
02773             query.prepare("INSERT INTO recordedseek"
02774                           " (chanid, starttime, mark, type, offset)"
02775                           " VALUES"
02776                           " ( :CHANID , :STARTTIME , :MARK , :TYPE , :OFFSET );");
02777             query.bindValue(":CHANID", chanid);
02778             query.bindValue(":STARTTIME", recstartts);
02779         }
02780         query.bindValue(":MARK", frame);
02781         query.bindValue(":TYPE", type);
02782         query.bindValue(":OFFSET", offset);
02783         
02784         if (!query.exec() || !query.isActive())
02785             MythContext::DBError("position map insert", 
02786                                  query);
02787     }
02788 }
02789 
02790 void ProgramInfo::SetPositionMapDelta(frm_pos_map_t &posMap,
02791                                       int type) const
02792 {
02793     QMap<long long, long long>::Iterator i;
02794     MSqlQuery query(MSqlQuery::InitCon());
02795 
02796     for (i = posMap.begin(); i != posMap.end(); ++i)
02797     {
02798         long long frame = i.key();
02799         long long offset = i.data();
02800 
02801         if (isVideo)
02802         {
02803             query.prepare("INSERT INTO filemarkup"
02804                           " (filename, mark, type, offset)"
02805                           " VALUES"
02806                           " ( :PATH , :MARK , :TYPE , :OFFSET );");
02807             query.bindValue(":PATH", pathname);
02808         }
02809         else
02810         {
02811             query.prepare("INSERT INTO recordedseek"
02812                           " (chanid, starttime, mark, type, offset)"
02813                           " VALUES"
02814                           " ( :CHANID , :STARTTIME , :MARK , :TYPE , :OFFSET );");
02815             query.bindValue(":CHANID", chanid);
02816             query.bindValue(":STARTTIME", recstartts);
02817         }
02818         query.bindValue(":MARK", frame);
02819         query.bindValue(":TYPE", type);
02820         query.bindValue(":OFFSET", offset);
02821         
02822         if (!query.exec() || !query.isActive())
02823             MythContext::DBError("delta position map insert", 
02824                                  query);
02825     }
02826 }
02827 
02831 void ProgramInfo::ReactivateRecording(void)
02832 {
02833     MSqlQuery result(MSqlQuery::InitCon());
02834 
02835     result.prepare("UPDATE oldrecorded SET reactivate = 1 "
02836                    "WHERE station = :STATION AND "
02837                    "  starttime = :STARTTIME AND "
02838                    "  title = :TITLE;");
02839     result.bindValue(":STARTTIME", startts);
02840     result.bindValue(":TITLE", title.utf8());
02841     result.bindValue(":STATION", chansign);
02842 
02843     result.exec();
02844     if (!result.isActive())
02845         MythContext::DBError("ReactivateRecording", result);
02846 
02847     ScheduledRecording::signalChange(0);
02848 }
02849 
02853 void ProgramInfo::AddHistory(bool resched, bool forcedup)
02854 {
02855     bool dup = (recstatus == rsRecorded || forcedup);
02856     RecStatusType rs = (recstatus == rsCurrentRecording) ?
02857         rsPreviousRecording : recstatus;
02858     oldrecstatus = recstatus;
02859     if (dup)
02860         reactivate = false;
02861 
02862     MSqlQuery result(MSqlQuery::InitCon());
02863 
02864     result.prepare("REPLACE INTO oldrecorded (chanid,starttime,"
02865                    "endtime,title,subtitle,description,category,"
02866                    "seriesid,programid,findid,recordid,station,"
02867                    "rectype,recstatus,duplicate,reactivate) "
02868                    "VALUES(:CHANID,:START,:END,:TITLE,:SUBTITLE,:DESC,"
02869                    ":CATEGORY,:SERIESID,:PROGRAMID,:FINDID,:RECORDID,"
02870                    ":STATION,:RECTYPE,:RECSTATUS,:DUPLICATE,:REACTIVATE);");
02871     result.bindValue(":CHANID", chanid);
02872     result.bindValue(":START", startts.toString(Qt::ISODate));
02873     result.bindValue(":END", endts.toString(Qt::ISODate));
02874     result.bindValue(":TITLE", title.utf8());
02875     result.bindValue(":SUBTITLE", subtitle.utf8());
02876     result.bindValue(":DESC", description.utf8());
02877     result.bindValue(":CATEGORY", category.utf8());
02878     result.bindValue(":SERIESID", seriesid.utf8());
02879     result.bindValue(":PROGRAMID", programid.utf8());
02880     result.bindValue(":FINDID", findid);
02881     result.bindValue(":RECORDID", recordid);
02882     result.bindValue(":STATION", chansign);
02883     result.bindValue(":RECTYPE", rectype);
02884     result.bindValue(":RECSTATUS", rs);
02885     result.bindValue(":DUPLICATE", dup);
02886     result.bindValue(":REACTIVATE", reactivate);
02887 
02888     result.exec();
02889     if (!result.isActive())
02890         MythContext::DBError("addHistory", result);
02891 
02892     if (dup && findid)
02893     {
02894         result.prepare("REPLACE INTO oldfind (recordid, findid) "
02895                        "VALUES(:RECORDID,:FINDID);");
02896         result.bindValue(":RECORDID", recordid);
02897         result.bindValue(":FINDID", findid);
02898     
02899         result.exec();
02900         if (!result.isActive())
02901             MythContext::DBError("addFindHistory", result);
02902     }
02903 
02904     // The adding of an entry to oldrecorded may affect near-future
02905     // scheduling decisions, so recalculate if told
02906     if (resched)
02907         ScheduledRecording::signalChange(0);
02908 }
02909 
02913 void ProgramInfo::DeleteHistory(void)
02914 {
02915     MSqlQuery result(MSqlQuery::InitCon());
02916 
02917     result.prepare("DELETE FROM oldrecorded WHERE title = :TITLE AND "
02918                    "starttime = :START AND station = :STATION");
02919     result.bindValue(":TITLE", title.utf8());
02920     result.bindValue(":START", recstartts);
02921     result.bindValue(":STATION", chansign);
02922     
02923     result.exec();
02924     if (!result.isActive())
02925         MythContext::DBError("deleteHistory", result);
02926 
02927     if (/*duplicate &&*/ findid)
02928     {
02929         result.prepare("DELETE FROM oldfind WHERE "
02930                        "recordid = :RECORDID AND findid = :FINDID");
02931         result.bindValue(":RECORDID", recordid);
02932         result.bindValue(":FINDID", findid);
02933     
02934         result.exec();
02935         if (!result.isActive())
02936             MythContext::DBError("deleteFindHistory", result);
02937     }
02938 
02939     // The removal of an entry from oldrecorded may affect near-future
02940     // scheduling decisions, so recalculate
02941     ScheduledRecording::signalChange(0);
02942 }
02943 
02952 void ProgramInfo::ForgetHistory(void)
02953 {
02954     MSqlQuery result(MSqlQuery::InitCon());
02955 
02956     result.prepare("UPDATE recorded SET duplicate = 0 "
02957                    "WHERE chanid = :CHANID "
02958                        "AND starttime = :STARTTIME "
02959                        "AND title = :TITLE;");
02960     result.bindValue(":STARTTIME", recstartts);
02961     result.bindValue(":TITLE", title.utf8());
02962     result.bindValue(":CHANID", chanid);
02963 
02964     result.exec();
02965     if (!result.isActive())
02966         MythContext::DBError("forgetRecorded", result);
02967 
02968     result.prepare("UPDATE oldrecorded SET duplicate = 0 "
02969                    "WHERE duplicate = 1 "
02970                    "AND title = :TITLE AND "
02971                    "((programid = '' AND subtitle = :SUBTITLE"
02972                    "  AND description = :DESC) OR "
02973                    " (programid <> '' AND programid = :PROGRAMID) OR "
02974                    " (findid <> 0 AND findid = :FINDID))");
02975     result.bindValue(":TITLE", title.utf8());
02976     result.bindValue(":SUBTITLE", subtitle.utf8());
02977     result.bindValue(":DESC", description.utf8());
02978     result.bindValue(":PROGRAMID", programid);
02979     result.bindValue(":FINDID", findid);
02980     
02981     result.exec();
02982     if (!result.isActive())
02983         MythContext::DBError("forgetHistory", result);
02984 
02985     result.prepare("DELETE FROM oldrecorded "
02986                    "WHERE recstatus = :NEVER AND duplicate = 0");
02987     result.bindValue(":NEVER", rsNeverRecord);
02988     
02989     result.exec();
02990     if (!result.isActive())
02991         MythContext::DBError("forgetNeverHisttory", result);
02992 
02993     if (findid)
02994     {
02995         result.prepare("DELETE FROM oldfind WHERE "
02996                        "recordid = :RECORDID AND findid = :FINDID");
02997         result.bindValue(":RECORDID", recordid);
02998         result.bindValue(":FINDID", findid);
02999     
03000         result.exec();
03001         if (!result.isActive())
03002             MythContext::DBError("forgetFindHistory", result);
03003     }
03004 
03005     // The removal of an entry from oldrecorded may affect near-future
03006     // scheduling decisions, so recalculate
03007     ScheduledRecording::signalChange(0);
03008 }
03009 
03013 void ProgramInfo::SetDupHistory(void)
03014 {
03015     MSqlQuery result(MSqlQuery::InitCon());
03016 
03017     result.prepare("UPDATE oldrecorded SET duplicate = 1 "
03018                    "WHERE duplicate = 0 "
03019                    "AND title = :TITLE AND "
03020                    "((programid = '' AND subtitle = :SUBTITLE"
03021                    "  AND description = :DESC) OR "
03022                    " (programid <> '' AND programid = :PROGRAMID) OR "
03023                    " (findid <> 0 AND findid = :FINDID))");
03024     result.bindValue(":TITLE", title.utf8());
03025     result.bindValue(":SUBTITLE", subtitle.utf8());
03026     result.bindValue(":DESC", description.utf8());
03027     result.bindValue(":PROGRAMID", programid);
03028     result.bindValue(":FINDID", findid);
03029     
03030     result.exec();
03031     if (!result.isActive())
03032         MythContext::DBError("setDupHistory", result);
03033 
03034     ScheduledRecording::signalChange(0);
03035 }
03036 
03040 QString ProgramInfo::RecTypeChar(void) const
03041 {
03042     switch (rectype)
03043     {
03044     case kSingleRecord:
03045         return QObject::tr("S", "RecTypeChar kSingleRecord");
03046     case kTimeslotRecord:
03047         return QObject::tr("T", "RecTypeChar kTimeslotRecord");
03048     case kWeekslotRecord:
03049         return QObject::tr("W", "RecTypeChar kWeekslotRecord");
03050     case kChannelRecord:
03051         return QObject::tr("C", "RecTypeChar kChannelRecord");
03052     case kAllRecord:
03053         return QObject::tr("A", "RecTypeChar kAllRecord");
03054     case kFindOneRecord:
03055         return QObject::tr("F", "RecTypeChar kFindOneRecord");
03056     case kFindDailyRecord:
03057         return QObject::tr("d", "RecTypeChar kFindDailyRecord");
03058     case kFindWeeklyRecord:
03059         return QObject::tr("w", "RecTypeChar kFindWeeklyRecord");
03060     case kOverrideRecord:
03061     case kDontRecord:
03062         return QObject::tr("O", "RecTypeChar kOverrideRecord/kDontRecord");
03063     case kNotRecording:
03064     default:
03065         return " ";
03066     }
03067 }
03068 
03072 QString ProgramInfo::RecTypeText(void) const
03073 {
03074     switch (rectype)
03075     {
03076     case kSingleRecord:
03077         return QObject::tr("Single Record");
03078     case kTimeslotRecord:
03079         return QObject::tr("Record Daily");
03080     case kWeekslotRecord:
03081         return QObject::tr("Record Weekly");
03082     case kChannelRecord:
03083         return QObject::tr("Channel Record");
03084     case kAllRecord:
03085         return QObject::tr("Record All");
03086     case kFindOneRecord:
03087         return QObject::tr("Find One");
03088     case kFindDailyRecord:
03089         return QObject::tr("Find Daily");
03090     case kFindWeeklyRecord:
03091         return QObject::tr("Find Weekly");
03092     case kOverrideRecord:
03093     case kDontRecord:
03094         return QObject::tr("Override Recording");
03095     default:
03096         return QObject::tr("Not Recording");
03097     }
03098 }
03099 
03103 QString ProgramInfo::RecStatusChar(void) const
03104 {
03105     switch (recstatus)
03106     {
03107     case rsAborted:
03108         return QObject::tr("A", "RecStatusChar rsAborted");
03109     case rsRecorded:
03110         return QObject::tr("R", "RecStatusChar rsRecorded");
03111     case rsRecording:
03112         if (cardid > 0)
03113             return QString::number(cardid);
03114         else
03115             return QObject::tr("R", "RecStatusChar rsCurrentRecording");
03116     case rsWillRecord:
03117         return QString::number(cardid);
03118     case rsDontRecord:
03119         return QObject::tr("X", "RecStatusChar rsDontRecord");
03120     case rsPreviousRecording:
03121         return QObject::tr("P", "RecStatusChar rsPreviousRecording");
03122     case rsCurrentRecording:
03123         return QObject::tr("R", "RecStatusChar rsCurrentRecording");
03124     case rsEarlierShowing:
03125         return QObject::tr("E", "RecStatusChar rsEarlierShowing");
03126     case rsTooManyRecordings:
03127         return QObject::tr("T", "RecStatusChar rsTooManyRecordings");
03128     case rsCancelled:
03129         return QObject::tr("c", "RecStatusChar rsCancelled");
03130     case rsMissed:
03131         return QObject::tr("M", "RecStatusChar rsMissed");
03132     case rsConflict:
03133         return QObject::tr("C", "RecStatusChar rsConflict");
03134     case rsLaterShowing:
03135         return QObject::tr("L", "RecStatusChar rsLaterShowing");
03136     case rsRepeat:
03137         return QObject::tr("r", "RecStatusChar rsRepeat");    
03138     case rsInactive:
03139         return QObject::tr("x", "RecStatusChar rsInactive");
03140     case rsLowDiskSpace:
03141         return QObject::tr("K", "RecStatusChar rsLowDiskSpace");
03142     case rsTunerBusy:
03143         return QObject::tr("B", "RecStatusChar rsTunerBusy");
03144     case rsFailed:
03145         return QObject::tr("f", "RecStatusChar rsFailed");
03146     case rsNotListed:
03147         return QObject::tr("N", "RecStatusChar rsNotListed");
03148     case rsNeverRecord:
03149         return QObject::tr("V", "RecStatusChar rsNeverRecord");
03150     case rsOffLine:
03151         return QObject::tr("F", "RecStatusChar rsOffLine");
03152     case rsOtherShowing:
03153         return QObject::tr("O", "RecStatusChar rsOtherShowing");
03154     default:
03155         return "-";
03156     }
03157 }
03158 
03162 QString ProgramInfo::RecStatusText(void) const
03163 {
03164     if (rectype == kNotRecording)
03165         return QObject::tr("Not Recording");
03166     else
03167     {
03168         switch (recstatus)
03169         {
03170         case rsAborted:
03171             return QObject::tr("Aborted");
03172         case rsRecorded:
03173             return QObject::tr("Recorded");
03174         case rsRecording:
03175             return QObject::tr("Recording");
03176         case rsWillRecord:
03177             return QObject::tr("Will Record");
03178         case rsDontRecord:
03179             return QObject::tr("Don't Record");
03180         case rsPreviousRecording:
03181             return QObject::tr("Previously Recorded");
03182         case rsCurrentRecording:
03183             return QObject::tr("Currently Recorded");
03184         case rsEarlierShowing:
03185             return QObject::tr("Earlier Showing");
03186         case rsTooManyRecordings:
03187             return QObject::tr("Max Recordings");
03188         case rsCancelled:
03189             return QObject::tr("Manual Cancel");
03190         case rsMissed:
03191             return QObject::tr("Missed");
03192         case rsConflict:
03193             return QObject::tr("Conflicting");
03194         case rsLaterShowing:
03195             return QObject::tr("Later Showing");
03196         case rsRepeat:
03197             return QObject::tr("Repeat");            
03198         case rsInactive:
03199             return QObject::tr("Inactive");            
03200         case rsLowDiskSpace:
03201             return QObject::tr("Low Disk Space");
03202         case rsTunerBusy:
03203             return QObject::tr("Tuner Busy");
03204         case rsFailed:
03205             return QObject::tr("Recorder Failed");
03206         case rsNotListed:
03207             return QObject::tr("Not Listed");
03208         case rsNeverRecord:
03209             return QObject::tr("Never Record");
03210         case rsOffLine:
03211             return QObject::tr("Recorder Off-Line");
03212         case rsOtherShowing:
03213             return QObject::tr("Other Showing");
03214         default:
03215             return QObject::tr("Unknown");
03216         }
03217     }
03218 
03219     return QObject::tr("Unknown");
03220 }
03221 
03225 QString ProgramInfo::RecStatusDesc(void) const
03226 {
03227     QString message;
03228     QDateTime now = QDateTime::currentDateTime();
03229 
03230     if (recstatus <= rsWillRecord)
03231     {
03232         switch (recstatus)
03233         {
03234         case rsWillRecord:
03235             message = QObject::tr("This showing will be recorded.");
03236             break;
03237         case rsRecording:
03238             message = QObject::tr("This showing is being recorded.");
03239             break;
03240         case rsRecorded:
03241             message = QObject::tr("This showing was recorded.");
03242             break;
03243         case rsAborted:
03244             message = QObject::tr("This showing was recorded but was aborted "
03245                                    "before recording was completed.");
03246             break;
03247         case rsMissed:
03248             message += QObject::tr("This showing was not recorded because it "
03249                                    "was scheduled after it would have ended.");
03250             break;
03251         case rsCancelled:
03252             message += QObject::tr("This showing was not recorded because it "
03253                                    "was manually cancelled.");
03254             break;
03255         case rsLowDiskSpace:
03256             message += QObject::tr("there wasn't enough disk space available.");
03257             break;
03258         case rsTunerBusy:
03259             message += QObject::tr("the tuner card was already being used.");
03260             break;
03261         case rsFailed:
03262             message += QObject::tr("the recorder failed to record.");
03263             break;
03264         default:
03265             message = QObject::tr("The status of this showing is unknown.");
03266             break;
03267         }
03268     }
03269     else
03270     {
03271         if (recstartts > now)
03272             message = QObject::tr("This showing will not be recorded because ");
03273         else
03274             message = QObject::tr("This showing was not recorded because ");
03275 
03276         switch (recstatus)
03277         {
03278         case rsDontRecord:
03279             message += QObject::tr("it was manually set to not record.");
03280             break;
03281         case rsPreviousRecording:
03282             message += QObject::tr("this episode was previously recorded "
03283                                    "according to the duplicate policy chosen "
03284                                    "for this title.");
03285             break;
03286         case rsCurrentRecording:
03287             message += QObject::tr("this episode was previously recorded and "
03288                                    "is still available in the list of "
03289                                    "recordings.");
03290             break;
03291         case rsEarlierShowing:
03292             message += QObject::tr("this episode will be recorded at an "
03293                                    "earlier time instead.");
03294             break;
03295         case rsTooManyRecordings:
03296             message += QObject::tr("too many recordings of this program have "
03297                                    "already been recorded.");
03298             break;
03299         case rsConflict:
03300             message += QObject::tr("another program with a higher priority "
03301                                    "will be recorded.");
03302             break;
03303         case rsLaterShowing:
03304             message += QObject::tr("this episode will be recorded at a "
03305                                    "later time.");
03306             break;
03307         case rsRepeat:
03308             message += QObject::tr("this episode is a repeat.");
03309             break;            
03310         case rsInactive:
03311             message += QObject::tr("this recording rule is inactive.");
03312             break;
03313         case rsNotListed:
03314             message += QObject::tr("this rule does not match any showings in "
03315                                    "the current program listings.");
03316             break;            
03317         case rsNeverRecord:
03318             message += QObject::tr("it was marked to never be recorded.");
03319             break;            
03320         case rsOffLine:
03321             message += QObject::tr("the backend recorder is off-line.");
03322             break;
03323         case rsOtherShowing:
03324             message += QObject::tr("this episode will be recorded on a "
03325                                    "different channel in this time slot.");
03326             break;
03327         default:
03328             message += QObject::tr("you should never see this.");
03329             break;
03330         }
03331     }
03332 
03333     return message;
03334 }
03335 
03346 QString ProgramInfo::ChannelText(const QString &format) const
03347 {
03348     QString chan(format);
03349     chan.replace("<num>", chanstr)
03350         .replace("<sign>", chansign)
03351         .replace("<name>", channame);
03352     return chan;
03353 }
03354 
03360 bool ProgramInfo::FillInRecordInfo(const vector<ProgramInfo *> &reclist)
03361 {
03362     vector<ProgramInfo *>::const_iterator i;
03363     ProgramInfo *found = NULL;
03364     int pfound = 0;
03365 
03366     for (i = reclist.begin(); i != reclist.end(); i++)
03367     {
03368         ProgramInfo *p = *i;
03369         if (IsSameTimeslot(*p))
03370         {
03371             int pp = RecTypePriority(p->rectype);
03372             if (!found || pp < pfound || 
03373                 (pp == pfound && p->recordid < found->recordid))
03374             {
03375                 found = p;
03376                 pfound = pp;
03377             }
03378         }
03379     }
03380                 
03381     if (found)
03382     {
03383         recstatus = found->recstatus;
03384         recordid = found->recordid;
03385         rectype = found->rectype;
03386         dupin = found->dupin;
03387         dupmethod = found->dupmethod;
03388         recstartts = found->recstartts;
03389         recendts = found->recendts;
03390         cardid = found->cardid;
03391         inputid = found->inputid;
03392     }
03393     return found;
03394 }
03395 
03400 void ProgramInfo::Save(void) const
03401 {
03402     MSqlQuery query(MSqlQuery::InitCon());
03403 
03404     // This used to be REPLACE INTO...
03405     // primary key of table program is chanid,starttime
03406     query.prepare("DELETE FROM program"
03407                   " WHERE chanid = :CHANID"
03408                   " AND starttime = :STARTTIME ;");
03409     query.bindValue(":CHANID", chanid.toInt());
03410     query.bindValue(":STARTTIME", startts);
03411     if (!query.exec())
03412         MythContext::DBError("Saving program", 
03413                              query);
03414 
03415     query.prepare("INSERT INTO program (chanid,starttime,endtime,"
03416                   " title,subtitle,description,category,airdate,"
03417                   " stars) VALUES (:CHANID,:STARTTIME,:ENDTIME,:TITLE,"
03418                   " :SUBTITLE,:DESCRIPTION,:CATEGORY,:AIRDATE,:STARS);");
03419     query.bindValue(":CHANID", chanid.toInt());
03420     query.bindValue(":STARTTIME", startts);
03421     query.bindValue(":ENDTIME", endts);
03422     query.bindValue(":TITLE", title.utf8());
03423     query.bindValue(":SUBTITLE", subtitle.utf8());
03424     query.bindValue(":DESCRIPTION", description.utf8());
03425     query.bindValue(":CATEGORY", category.utf8());
03426     query.bindValue(":AIRDATE", "0");
03427     query.bindValue(":STARS", "0");
03428 
03429     if (!query.exec())
03430         MythContext::DBError("Saving program", 
03431                              query);
03432 }
03433 
03434 
03439 void ProgramInfo::EditRecording(void)
03440 {
03441     if (recordid == 0)
03442         EditScheduled();
03443     else if (recstatus <= rsWillRecord)
03444         ShowRecordingDialog();
03445     else
03446         ShowNotRecordingDialog();
03447 }
03448 
03453 void ProgramInfo::EditScheduled(void)
03454 {
03455     GetProgramRecordingStatus();
03456     record->exec();
03457 }
03458 
03459 static QString get_ratings(bool recorded, uint chanid, QDateTime startts)
03460 {
03461     QString table = (recorded) ? "recordedrating" : "programrating";
03462     QString sel = QString(
03463         "SELECT system, rating FROM %1 "
03464         "WHERE chanid  = :CHANID "
03465         "AND starttime = :STARTTIME").arg(table);
03466 
03467     MSqlQuery query(MSqlQuery::InitCon());
03468     query.prepare(sel);
03469     query.bindValue(":CHANID", chanid);
03470     query.bindValue(":STARTTIME", startts);
03471         
03472     if (!query.exec() || !query.isActive())
03473     {
03474         MythContext::DBError("programinfo.cpp: get_ratings", query);
03475         return "";
03476     }
03477 
03478     QMap<QString,QString> main_ratings;
03479     QString advisory = "";
03480     while (query.next())
03481     {
03482         if (query.value(0).toString().lower() == "advisory")
03483         {
03484             advisory += query.value(1).toString() + ", ";
03485             continue;
03486         }
03487         main_ratings[query.value(0).toString()] = query.value(1).toString();
03488     }
03489 
03490     if (!advisory.length() > 2)
03491         advisory.left(advisory.length() - 2);
03492 
03493     if (main_ratings.empty())
03494         return advisory;
03495 
03496     if (!advisory.isEmpty())
03497         advisory = ": " + advisory;
03498 
03499     if (main_ratings.size() == 1)
03500     {
03501         return *main_ratings.begin() + advisory;
03502     }
03503 
03504     QString ratings = "";
03505     QMap<QString,QString>::const_iterator it;
03506     for (it = main_ratings.begin(); it != main_ratings.end(); ++it)
03507     {
03508         ratings += it.key() + ": " + *it + ", ";
03509     }
03510 
03511     return ratings + "Advisory" + advisory;
03512 }
03513 
03514 #define ADD_PAR(title,text,result)                                    \
03515     result += details_dialog->themeText("heading", title + ":  ", 3)  \
03516            +  details_dialog->themeText("body", text, 3) + "<br>";
03517 
03522 void ProgramInfo::showDetails(void) const
03523 {
03524     MSqlQuery query(MSqlQuery::InitCon());
03525     QString fullDateFormat = gContext->GetSetting("DateFormat", "M/d/yyyy");
03526     if (fullDateFormat.find(QRegExp("yyyy")) < 0)
03527         fullDateFormat += " yyyy";
03528     QString category_type, showtype, year, epinum, rating, colorcode,
03529             title_pronounce;
03530     float stars = 0.0;
03531     int partnumber = 0, parttotal = 0;
03532     int audioprop = 0, videoprop = 0, subtype = 0, generic = 0;
03533     bool recorded = false;
03534 
03535     if (record == NULL && recordid)
03536     {
03537         record = new ScheduledRecording();
03538         record->loadByProgram(this);
03539     }
03540 
03541     if (filesize > 0)
03542         recorded = true;
03543 
03544     if (endts != startts)
03545     {
03546         QString ptable = "program";
03547         if (recorded)
03548             ptable = "recordedprogram";
03549 
03550         query.prepare(QString("SELECT category_type, airdate, stars,"
03551                       " partnumber, parttotal, audioprop+0, videoprop+0,"
03552                       " subtitletypes+0, syndicatedepisodenumber, generic,"
03553                       " showtype, colorcode, title_pronounce"
03554                       " FROM %1 WHERE chanid = :CHANID AND"
03555                       " starttime = :STARTTIME ;").arg(ptable));
03556 
03557         query.bindValue(":CHANID", chanid);
03558         query.bindValue(":STARTTIME", startts);
03559 
03560         if (query.exec() && query.isActive() && query.size() > 0)
03561         {
03562             query.next();
03563             category_type = query.value(0).toString();
03564             year = query.value(1).toString();
03565             stars = query.value(2).toDouble();
03566             partnumber = query.value(3).toInt();
03567             parttotal = query.value(4).toInt();
03568             audioprop = query.value(5).toInt();
03569             videoprop = query.value(6).toInt();
03570             subtype = query.value(7).toInt();
03571             epinum = query.value(8).toString();
03572             generic = query.value(9).toInt();
03573             showtype = query.value(10).toString();
03574             colorcode = query.value(11).toString();
03575             title_pronounce = QString::fromUtf8(query.value(12).toString());
03576         }
03577         else if (!query.isActive())
03578             MythContext::DBError(LOC + "showDetails", query);
03579 
03580         rating = get_ratings(recorded, chanid.toUInt(), startts);
03581     }
03582 
03583     if (category_type == "" && programid != "")
03584     {
03585         QString prefix = programid.left(2);
03586 
03587         if (prefix == "MV")
03588            category_type = "movie";
03589         else if (prefix == "EP")
03590            category_type = "series";
03591         else if (prefix == "SP")
03592            category_type = "sports";
03593         else if (prefix == "SH")
03594            category_type = "tvshow";
03595     }
03596 
03597     ProgDetails *details_dialog = new ProgDetails(gContext->GetMainWindow(),
03598             "progdetails");
03599 
03600     QString msg = "";
03601     QString s   = "";
03602 
03603     s = title;
03604     if (subtitle != "")
03605         s += " - \"" + subtitle + "\"";
03606     ADD_PAR(QObject::tr("Title"), s, msg)
03607 
03608     if (title_pronounce != "")
03609         ADD_PAR(QObject::tr("Title Pronounce"), title_pronounce, msg)
03610 
03611     s = description; 
03612 
03613     QString attr = "";
03614 
03615     if (partnumber > 0)
03616         attr += QString(QObject::tr("Part %1 of %2, ")).arg(partnumber).arg(parttotal);
03617 
03618     if (rating != "" && rating != "NR")
03619         attr += rating + ", ";
03620     if (category_type == "movie")
03621     {
03622         if (year != "")
03623             attr += year + ", ";
03624 
03625         if (stars > 0.0)
03626         {
03627             QString str = QObject::tr("stars");
03628             if (stars > 0 && stars <= 0.25)
03629                 str = QObject::tr("star");
03630 
03631             attr += QString("%1 %2, ").arg(4.0 * stars).arg(str);
03632         }
03633     }
03634     if (colorcode != "")
03635         attr += colorcode + ", ";
03636 
03637     if (audioprop & AUD_MONO)
03638         attr += QObject::tr("Mono") + ", ";
03639     if (audioprop & AUD_STEREO)
03640         attr += QObject::tr("Stereo") + ", ";
03641     if (audioprop & AUD_SURROUND)
03642         attr += QObject::tr("Surround Sound") + ", ";
03643     if (audioprop & AUD_DOLBY)
03644         attr += QObject::tr("Dolby Sound") + ", ";
03645     if (audioprop & AUD_HARDHEAR)
03646         attr += QObject::tr("Audio for Hearing Impaired") + ", ";
03647     if (audioprop & AUD_VISUALIMPAIR)
03648         attr += QObject::tr("Audio for Visually Impaired") + ", ";
03649 
03650     if (videoprop & VID_HDTV)
03651         attr += QObject::tr("HDTV") + ", ";
03652     if  (videoprop & VID_WIDESCREEN)
03653         attr += QObject::tr("Widescreen") + ", ";
03654     if  (videoprop & VID_AVC)
03655         attr += QObject::tr("AVC/H.264") + ", ";
03656 
03657     if (subtype & SUB_HARDHEAR)
03658         attr += QObject::tr("CC","Closed Captioned") + ", ";
03659     if (subtype & SUB_NORMAL)
03660         attr += QObject::tr("Subtitles Available") + ", ";
03661     if (subtype & SUB_ONSCREEN)
03662         attr += QObject::tr("Subtitled") + ", ";
03663     if (subtype & SUB_SIGNED)
03664         attr += QObject::tr("Deaf Signing") + ", ";
03665 
03666     if (generic && category_type == "series")
03667         attr += QObject::tr("Unidentified Episode") + ", ";
03668     else if (repeat)
03669         attr += QObject::tr("Repeat") + ", ";
03670 
03671     if (attr != "")
03672     {
03673         attr.truncate(attr.findRev(','));
03674         s += " (" + attr + ")";
03675     }
03676 
03677     if (s != "")
03678         ADD_PAR(QObject::tr("Description"), s, msg)
03679 
03680     if (category != "")
03681     {
03682         s = category;
03683 
03684         query.prepare("SELECT genre FROM programgenres "
03685                       "WHERE chanid = :CHANID AND starttime = :STARTTIME "
03686                       "AND relevance > 0 ORDER BY relevance;");
03687 
03688         query.bindValue(":CHANID", chanid);
03689         query.bindValue(":STARTTIME", startts);
03690 
03691         if (query.exec() && query.isActive() && query.size() > 0)
03692         {
03693             while (query.next())
03694                 s += ", " + query.value(0).toString();
03695         }
03696         ADD_PAR(QObject::tr("Category"), s, msg)
03697     }
03698 
03699     if (category_type  != "")
03700     {
03701         s = category_type;
03702         if (seriesid != "")
03703             s += "  (" + seriesid + ")";
03704         if (showtype != "")
03705             s += "  " + showtype;
03706         ADD_PAR(QObject::tr("Type","category_type"), s, msg)
03707     }
03708 
03709     if (epinum != "")
03710         ADD_PAR(QObject::tr("Episode Number"), epinum, msg)
03711 
03712     if (hasAirDate && category_type != "movie")
03713     {
03714         ADD_PAR(QObject::tr("Original Airdate"),
03715                 originalAirDate.toString(fullDateFormat), msg)
03716     }
03717     if (programid  != "")
03718         ADD_PAR(QObject::tr("Program ID"), programid, msg)
03719 
03720     QString role = "", pname = "";
03721 
03722     if (endts != startts)
03723     {
03724         if (recorded)
03725             query.prepare("SELECT role,people.name FROM recordedcredits"
03726                           " AS credits"
03727                           " LEFT JOIN people ON credits.person = people.person"
03728                           " WHERE credits.chanid = :CHANID"
03729                           " AND credits.starttime = :STARTTIME"
03730                           " ORDER BY role;");
03731         else
03732             query.prepare("SELECT role,people.name FROM credits"
03733                           " LEFT JOIN people ON credits.person = people.person"
03734                           " WHERE credits.chanid = :CHANID"
03735                           " AND credits.starttime = :STARTTIME"
03736                           " ORDER BY role;");
03737         query.bindValue(":CHANID", chanid);
03738         query.bindValue(":STARTTIME", startts);
03739 
03740         if (query.exec() && query.isActive() && query.size() > 0)
03741         {
03742             QString rstr = "", plist = "";
03743 
03744             while(query.next())
03745             {
03746                 role = QString::fromUtf8(query.value(0).toString());
03747                 pname = QString::fromUtf8(query.value(1).toString());
03748 
03749                 if (rstr == role)
03750                     plist += ", " + pname;
03751                 else
03752                 {
03753                     if (rstr == "actor")
03754                         ADD_PAR(QObject::tr("Actors"), plist, msg)
03755                     else if (rstr == "director")
03756                         ADD_PAR(QObject::tr("Director"), plist, msg)
03757                     else if (rstr == "producer")
03758                         ADD_PAR(QObject::tr("Producer"), plist, msg)
03759                     else if (rstr == "executive_producer")
03760                         ADD_PAR(QObject::tr("Executive Producer"), plist, msg)
03761                     else if (rstr == "writer")
03762                         ADD_PAR(QObject::tr("Writer"), plist, msg)
03763                     else if (rstr == "guest_star")
03764                         ADD_PAR(QObject::tr("Guest Star"), plist, msg)
03765                     else if (rstr == "host")
03766                         ADD_PAR(QObject::tr("Host"), plist, msg)
03767                     else if (rstr == "adapter")
03768                         ADD_PAR(QObject::tr("Adapter"), plist, msg)
03769                     else if (rstr == "presenter")
03770                         ADD_PAR(QObject::tr("Presenter"), plist, msg)
03771                     else if (rstr == "commentator")
03772                         ADD_PAR(QObject::tr("Commentator"), plist, msg)
03773                     else if (rstr == "guest")
03774                         ADD_PAR(QObject::tr("Guest"), plist, msg)
03775 
03776                     rstr = role;
03777                     plist = pname;
03778                 }
03779             }
03780             if (rstr == "actor")
03781                 ADD_PAR(QObject::tr("Actors"), plist, msg)
03782             else if (rstr == "director")
03783                 ADD_PAR(QObject::tr("Director"), plist, msg)
03784             else if (rstr == "producer")
03785                 ADD_PAR(QObject::tr("Producer"), plist, msg)
03786             else if (rstr == "executive_producer")
03787                 ADD_PAR(QObject::tr("Executive Producer"), plist, msg)
03788             else if (rstr == "writer")
03789                 ADD_PAR(QObject::tr("Writer"), plist, msg)
03790             else if (rstr == "guest_star")
03791                 ADD_PAR(QObject::tr("Guest Star"), plist, msg)
03792             else if (rstr == "host")
03793                 ADD_PAR(QObject::tr("Host"), plist, msg)
03794             else if (rstr == "adapter")
03795                 ADD_PAR(QObject::tr("Adapter"), plist, msg)
03796             else if (rstr == "presenter")
03797                 ADD_PAR(QObject::tr("Presenter"), plist, msg)
03798             else if (rstr == "commentator")
03799                 ADD_PAR(QObject::tr("Commentator"), plist, msg)
03800             else if (rstr == "guest")
03801                 ADD_PAR(QObject::tr("Guest"), plist, msg)
03802         }
03803     }
03804 
03805     // Begin MythTV information not found in the listings info
03806     msg += "<p>";
03807     QDateTime statusDate;
03808     if (recstatus == rsWillRecord)
03809         statusDate = startts;
03810 
03811     ProgramInfo *p = new ProgramInfo;
03812     p->rectype = kSingleRecord; // must be != kNotRecording
03813     p->recstatus = recstatus;
03814 
03815     if (p->recstatus == rsPreviousRecording ||
03816         p->recstatus == rsNeverRecord || p->recstatus == rsUnknown)
03817     {
03818         query.prepare("SELECT recstatus, starttime "
03819                       "FROM oldrecorded WHERE duplicate > 0 AND "
03820                       "((programid <> '' AND programid = :PROGRAMID) OR "
03821                       " (title <> '' AND title = :TITLE AND "
03822                       "  subtitle <> '' AND subtitle = :SUBTITLE AND "
03823                       "  description <> '' AND description = :DECRIPTION));");
03824 
03825         query.bindValue(":PROGRAMID", programid);
03826         query.bindValue(":TITLE", title);
03827         query.bindValue(":SUBTITLE", subtitle);
03828         query.bindValue(":DECRIPTION", description);
03829 
03830         if (!query.exec() || !query.isActive())
03831             MythContext::DBError("showDetails", query);
03832 
03833         if (query.isActive() && query.size() > 0)
03834         {
03835             query.next();
03836             if (p->recstatus == rsUnknown)
03837                 p->recstatus = RecStatusType(query.value(0).toInt());
03838             if (p->recstatus == rsPreviousRecording || 
03839                 p->recstatus == rsNeverRecord || p->recstatus == rsRecorded)
03840                 statusDate = QDateTime::fromString(query.value(1).toString(),
03841                                                   Qt::ISODate);
03842         }
03843     }
03844     if (p->recstatus == rsUnknown)
03845     {
03846         if (recorded)
03847         {
03848             p->recstatus = rsRecorded;
03849             statusDate = startts;
03850         }
03851         else
03852         {
03853             p->rectype = rectype; // re-enable "Not Recording" status text.
03854         }
03855     }
03856     s = p->RecStatusText();
03857     if (statusDate.isValid())
03858         s += " " + statusDate.toString(fullDateFormat);
03859     ADD_PAR(QString("MythTV " + QObject::tr("Status")), s, msg)
03860     delete p;
03861 
03862     if (recordid)
03863     {
03864         s = QString("%1, ").arg(recordid);
03865         if (rectype != kNotRecording)
03866             s += RecTypeText();
03867         if (record->getRecordTitle())
03868             s += QString(" \"%2\"").arg(record->getRecordTitle());
03869         ADD_PAR(QObject::tr("Recording Rule"), s, msg)
03870 
03871         query.prepare("SELECT last_record, next_record, avg_delay "
03872                       "FROM record WHERE recordid = :RECORDID");
03873         query.bindValue(":RECORDID", recordid);
03874         
03875         if (query.exec() && query.isActive() && query.size() > 0)
03876         {
03877             query.next();
03878             if (query.value(0).toDateTime().isValid())
03879                 ADD_PAR(QObject::tr("Last Recorded"),
03880                         QObject::tr(query.value(0).toDateTime()
03881                                     .toString(fullDateFormat)), msg)
03882             if (query.value(1).toDateTime().isValid())
03883                 ADD_PAR(QObject::tr("Next Recording"),
03884                         QObject::tr(query.value(1).toDateTime()
03885                                     .toString(fullDateFormat)), msg)
03886             if (query.value(2).toInt() > 0)
03887                 ADD_PAR(QObject::tr("Average Time Shift"),
03888                         QString("%1 %2").arg(query.value(2).toInt())
03889                                         .arg(QObject::tr("hours")), msg)
03890         }
03891         if (recorded)
03892         {
03893             if (recpriority2 > 0)
03894                 ADD_PAR(QObject::tr("Watch List Score"),
03895                         QString("%1").arg(recpriority2), msg)
03896     
03897             if (recpriority2 < 0)
03898             {
03899                 QString st = "";
03900     
03901                 switch(recpriority2)
03902                 {
03903                 case wlExpireOff:
03904                     st = QObject::tr("Auto-expire off");
03905                     break;
03906                 case wlWatched:
03907                     st = QObject::tr("Marked as 'watched'");
03908                     break;
03909                 case wlEarlier:
03910                     st = QObject::tr("Not the earliest episode");
03911                     break;
03912                 case wlDeleted:
03913                     st = QObject::tr("Recently deleted episode");
03914                     break;
03915                 }
03916                 ADD_PAR(QObject::tr("Watch List Status"), st, msg)
03917             }
03918         }
03919         if (record->getSearchType() &&
03920             record->getSearchType() != kManualSearch &&
03921             record->getRecordDescription() != description)
03922             ADD_PAR(QObject::tr("Search Phrase"),
03923                     record->getRecordDescription().replace("<", "&lt;")
03924                             .replace(">", "&gt;").replace("\n", " "), msg)
03925     }
03926     if (findid > 0)
03927     {
03928         QDate fdate = QDate::QDate (1970, 1, 1);
03929         fdate = fdate.addDays(findid - 719528);
03930         ADD_PAR(QObject::tr("Find ID"), QString("%1 (%2)").arg(findid)
03931                 .arg(fdate.toString(fullDateFormat)), msg)
03932     }
03933     if (recorded)
03934     {
03935         ADD_PAR(QObject::tr("Recording Host"), hostname, msg)
03936         ADD_PAR(QObject::tr("Recorded File Name"), GetRecordBasename(), msg)
03937 
03938         QString tmpSize;
03939         tmpSize.sprintf("%0.2f ", filesize / 1024.0 / 1024.0 / 1024.0);
03940         tmpSize += QObject::tr("GB", "GigaBytes");
03941         ADD_PAR(QObject::tr("Recorded File Size"), tmpSize, msg)
03942 
03943         query.prepare("SELECT profile FROM recorded"
03944                       " WHERE chanid = :CHANID"
03945                       " AND starttime = :STARTTIME;");
03946         query.bindValue(":CHANID", chanid);
03947         query.bindValue(":STARTTIME", recstartts);
03948         
03949         if (query.exec() && query.isActive() && query.size() > 0)
03950         {
03951             query.next();
03952             if (query.value(0).toString() > "")
03953                 ADD_PAR(QObject::tr("Recording Profile"),
03954                         QObject::tr(query.value(0).toString()), msg)
03955         }
03956         ADD_PAR(QObject::tr("Recording Group"), QObject::tr(recgroup), msg)
03957         ADD_PAR(QObject::tr("Storage Group"), QObject::tr(storagegroup), msg)
03958         ADD_PAR(QObject::tr("Playback Group"), QObject::tr(playgroup), msg)
03959     }
03960     else if (recordid)
03961     {
03962         ADD_PAR(QObject::tr("Recording Profile"), record->getProfileName(),msg)
03963     }
03964     msg.remove(QRegExp("<br>$"));
03965     details_dialog->setDetails(msg);
03966     details_dialog->exec();
03967 
03968     delete details_dialog;
03969 }
03970 
03975 int ProgramInfo::getProgramFlags(void) const
03976 {
03977     int flags = 0;
03978     MSqlQuery query(MSqlQuery::InitCon());
03979 
03980     query.prepare("SELECT commflagged, cutlist, autoexpire, "
03981                   "editing, bookmark, watched, preserve "
03982                   "FROM recorded LEFT JOIN recordedprogram ON "
03983                   "(recorded.chanid = recordedprogram.chanid AND "
03984                   "recorded.progstart = recordedprogram.starttime) "
03985                   "WHERE recorded.chanid = :CHANID AND recorded.starttime = :STARTTIME ;");
03986     query.bindValue(":CHANID", chanid);
03987     query.bindValue(":STARTTIME", recstartts);
03988 
03989     if (query.exec() && query.isActive() && query.size() > 0)
03990     {
03991         query.next();
03992 
03993         flags |= (query.value(0).toInt() == COMM_FLAG_DONE) ? FL_COMMFLAG : 0;
03994         flags |= (query.value(1).toInt() == 1) ? FL_CUTLIST : 0;
03995         flags |= query.value(2).toInt() ? FL_AUTOEXP : 0;
03996         if ((query.value(3).toInt()) ||
03997             (query.value(0).toInt() == COMM_FLAG_PROCESSING))
03998             flags |= FL_EDITING;
03999         flags |= (query.value(4).toInt() == 1) ? FL_BOOKMARK : 0;
04000         flags |= (query.value(5).toInt() == 1) ? FL_WATCHED : 0;
04001         flags |= (query.value(6).toInt() == 1) ? FL_PRESERVED : 0;
04002     }
04003 
04004     return flags;
04005 }
04006 
04007 void ProgramInfo::getProgramProperties(void)
04008 {
04009 
04010     MSqlQuery query(MSqlQuery::InitCon());
04011 
04012     query.prepare("SELECT audioprop+0, videoprop+0, subtitletypes+0 "
04013                   "FROM recorded LEFT JOIN recordedprogram ON "
04014                   "(recorded.chanid = recordedprogram.chanid AND "
04015                   "recorded.progstart = recordedprogram.starttime) "
04016                   "WHERE recorded.chanid = :CHANID AND recorded.starttime = :STARTTIME ;");
04017     query.bindValue(":CHANID", chanid);
04018     query.bindValue(":STARTTIME", recstartts);
04019 
04020     if (query.exec() && query.isActive() && query.size() > 0)
04021     {
04022         query.next();
04023 
04024         audioproperties = query.value(0).toInt();
04025         videoproperties = query.value(1).toInt();
04026         subtitleType = query.value(2).toInt();
04027     }
04028 
04029 }
04030 
04031 void ProgramInfo::UpdateInUseMark(bool force)
04032 {
04033     if (isVideo)
04034         return;
04035 
04036     if (inUseForWhat == "")
04037         return;
04038 
04039     if (force || lastInUseTime.secsTo(QDateTime::currentDateTime()) > 15 * 60)
04040         MarkAsInUse(true);
04041 }
04042 
04043 bool ProgramInfo::PathnameExists(void)
04044 {
04045     if (pathname.left(7) == "myth://")
04046        return RemoteCheckFile(this);
04047     
04048     QFile checkFile(pathname);
04049 
04050     return checkFile.exists();
04051 }
04052 
04053 QString ProgramInfo::GetRecGroupPassword(QString group)
04054 {
04055     QString result = QString("");
04056 
04057     if (group == "All Programs")
04058     {
04059         result = gContext->GetSetting("AllRecGroupPassword");
04060     }
04061     else
04062     {
04063         MSqlQuery query(MSqlQuery::InitCon());
04064         query.prepare("SELECT password FROM recgrouppassword "
04065                         "WHERE recgroup = :GROUP ;");
04066         query.bindValue(":GROUP", group.utf8());
04067 
04068         if (query.exec() && query.isActive() && query.size() > 0)
04069             if (query.next())
04070                 result = query.value(0).toString();
04071     }
04072 
04073     if (result == QString::null)
04074         result = QString("");
04075 
04076     return(result);
04077 }
04078 
04081 void ProgramInfo::UpdateRecGroup(void)
04082 {
04083     MSqlQuery query(MSqlQuery::InitCon());
04084     query.prepare("SELECT recgroup FROM recorded"
04085                     "WHERE chanid = :CHANID"
04086                     "AND starttime = :START ;");
04087     query.bindValue(":START", recstartts);
04088     query.bindValue(":CHANID", chanid);
04089     if (query.exec() && query.next())
04090     {
04091         recgroup = QString::fromUtf8(query.value(0).toString());
04092     }
04093 }
04094 void ProgramInfo::MarkAsInUse(bool inuse, QString usedFor)
04095 {
04096     if (isVideo)
04097         return;
04098 
04099     bool notifyOfChange = false;
04100 
04101     if (inuse && inUseForWhat.length() < 2)
04102     {
04103         if (usedFor != "")
04104             inUseForWhat = usedFor;
04105         else
04106             inUseForWhat = QObject::tr("Unknown") + " [" +
04107                            QString::number(getpid()) + "]";
04108 
04109         notifyOfChange = true;
04110     }
04111 
04112     if (!inuse && inUseForWhat.length() < 2)
04113         return; // can't delete if we don't have a key
04114 
04115     MSqlQuery query(MSqlQuery::InitCon());
04116 
04117     query.prepare("DELETE FROM inuseprograms WHERE "
04118                   "chanid = :CHANID AND starttime = :STARTTIME AND "
04119                   "hostname = :HOSTNAME AND recusage = :RECUSAGE ;");
04120     query.bindValue(":CHANID", chanid);
04121     query.bindValue(":STARTTIME", recstartts);
04122     query.bindValue(":HOSTNAME", gContext->GetHostName());
04123     query.bindValue(":RECUSAGE", inUseForWhat);
04124 
04125     query.exec();
04126 
04127     if (!inuse)
04128     {
04129         if (!gContext->IsBackend())
04130             RemoteSendMessage("RECORDING_LIST_CHANGE");
04131         inUseForWhat = "";
04132         return;
04133     }
04134 
04135     if (pathname.left(7) == "myth://")
04136         pathname = GetPlaybackURL();
04137 
04138     if (pathname.right(1) == "/")
04139         pathname.remove(pathname.length() - 1, 1);
04140 
04141     QString recDir = "";
04142     if (hostname == gContext->GetHostName())
04143     {
04144         // we may be recording this file and it may not exist yet so we need
04145         // to do some checking to see what is in pathname
04146         QFileInfo testFile(pathname);
04147         if (testFile.exists())
04148         {
04149             while (testFile.isSymLink()) 
04150                 testFile.setFile(testFile.readLink()); 
04151 
04152             if (testFile.isFile())
04153                 recDir = testFile.dirPath();
04154             else if (testFile.isDir())
04155                 recDir = testFile.filePath();
04156         }
04157         else
04158         {
04159             testFile.setFile(testFile.dirPath());
04160             if (testFile.exists())
04161             {
04162                 while(testFile.isSymLink())
04163                     testFile.setFile(testFile.readLink()); 
04164 
04165                 if (testFile.isDir())
04166                     recDir = testFile.filePath();
04167             }
04168         }
04169     }
04170     else if (inUseForWhat == PreviewGenerator::kInUseID)
04171     {
04172         recDir = "";
04173     }
04174     else if (!gContext->IsBackend() && RemoteCheckFile(this))
04175     {
04176         recDir = pathname.section("/", 0, -2);
04177     }
04178 
04179     lastInUseTime = mythCurrentDateTime();
04180 
04181     query.prepare("INSERT INTO inuseprograms "
04182                   " (chanid, starttime, recusage, hostname, lastupdatetime, "
04183                       " rechost, recdir ) "
04184                   " VALUES "
04185                   " (:CHANID, :STARTTIME, :RECUSAGE, :HOSTNAME, :UPDATETIME, "
04186                       " :RECHOST, :RECDIR);");
04187     query.bindValue(":CHANID", chanid);
04188     query.bindValue(":STARTTIME", recstartts);
04189     query.bindValue(":HOSTNAME", gContext->GetHostName());
04190     query.bindValue(":RECUSAGE", inUseForWhat);
04191     query.bindValue(":UPDATETIME", lastInUseTime);
04192     query.bindValue(":RECHOST", hostname);
04193     query.bindValue(":RECDIR", recDir);
04194 
04195     if (!query.exec() || !query.isActive())
04196         MythContext::DBError("SetInUse", query);
04197 
04198     // Let others know