MythTV  0.26-pre
playbackboxhelper.cpp
Go to the documentation of this file.
00001 #include <algorithm>
00002 using namespace std;
00003 
00004 #include <QCoreApplication>
00005 #include <QStringList>
00006 #include <QDateTime>
00007 #include <QFileInfo>
00008 #include <QDir>
00009 
00010 #include "previewgeneratorqueue.h"
00011 #include "metadataimagehelper.h"
00012 #include "playbackboxhelper.h"
00013 #include "mythcorecontext.h"
00014 #include "filesysteminfo.h"
00015 #include "tvremoteutil.h"
00016 #include "storagegroup.h"
00017 #include "mythlogging.h"
00018 #include "programinfo.h"
00019 #include "remoteutil.h"
00020 #include "mythevent.h"
00021 #include "mythdirs.h"
00022 
00023 #define LOC      QString("PlaybackBoxHelper: ")
00024 #define LOC_WARN QString("PlaybackBoxHelper Warning: ")
00025 #define LOC_ERR  QString("PlaybackBoxHelper Error: ")
00026 
00027 class PBHEventHandler : public QObject
00028 {
00029   public:
00030     PBHEventHandler(PlaybackBoxHelper &pbh) :
00031         m_pbh(pbh), m_freeSpaceTimerId(0), m_checkAvailabilityTimerId(0)
00032     {
00033         StorageGroup::ClearGroupToUseCache();
00034     }
00035     virtual bool event(QEvent*); // QObject
00036     void UpdateFreeSpaceEvent(void);
00037     AvailableStatusType CheckAvailability(const QStringList &slist);
00038     PlaybackBoxHelper &m_pbh;
00039     int m_freeSpaceTimerId;
00040     int m_checkAvailabilityTimerId;
00041     static const uint kUpdateFreeSpaceInterval;
00042     QMap<QString, QStringList> m_fileListCache;
00043     QHash<QString, QStringList> m_checkAvailability;
00044 };
00045 
00046 const uint PBHEventHandler::kUpdateFreeSpaceInterval = 15000; // 15 seconds
00047 
00048 AvailableStatusType PBHEventHandler::CheckAvailability(const QStringList &slist)
00049 {
00050     QTime tm = QTime::currentTime();
00051 
00052     QStringList::const_iterator it = slist.begin();
00053     ProgramInfo evinfo(it, slist.end());
00054     QSet<CheckAvailabilityType> cats;
00055     for (; it != slist.end(); ++it)
00056         cats.insert((CheckAvailabilityType)(*it).toUInt());
00057 
00058     {
00059         QMutexLocker locker(&m_pbh.m_lock);
00060         QHash<QString, QStringList>::iterator it =
00061             m_checkAvailability.find(evinfo.MakeUniqueKey());
00062         if (it != m_checkAvailability.end())
00063             m_checkAvailability.erase(it);
00064         if (m_checkAvailability.empty() && m_checkAvailabilityTimerId)
00065         {
00066             killTimer(m_checkAvailabilityTimerId);
00067             m_checkAvailabilityTimerId = 0;
00068         }
00069     }
00070 
00071     if (cats.empty())
00072         return asFileNotFound;
00073 
00074     AvailableStatusType availableStatus = asAvailable;
00075     if (!evinfo.HasPathname() && !evinfo.GetChanID())
00076         availableStatus = asFileNotFound;
00077     else
00078     {
00079         // Note IsFileReadable() implicitly calls GetPlaybackURL
00080         // when necessary, we rely on this.
00081         if (!evinfo.IsFileReadable())
00082         {
00083             LOG(VB_GENERAL, LOG_ERR, LOC +
00084                 QString("CHECK_AVAILABILITY '%1' file not found")
00085                     .arg(evinfo.GetPathname()));
00086             availableStatus = asFileNotFound;
00087         }
00088         else if (!evinfo.GetFilesize())
00089         {
00090             evinfo.SetFilesize(evinfo.QueryFilesize());
00091             if (!evinfo.GetFilesize())
00092             {
00093                 availableStatus =
00094                     (evinfo.GetRecordingStatus() == rsRecording) ?
00095                     asNotYetAvailable : asZeroByte;
00096             }
00097         }
00098     }
00099 
00100     QStringList list;
00101     list.push_back(evinfo.MakeUniqueKey());
00102     list.push_back(evinfo.GetPathname());
00103     MythEvent *e0 = new MythEvent("SET_PLAYBACK_URL", list);
00104     QCoreApplication::postEvent(m_pbh.m_listener, e0);
00105 
00106     list.clear();
00107     list.push_back(evinfo.MakeUniqueKey());
00108     list.push_back(QString::number((int)*cats.begin()));
00109     list.push_back(QString::number((int)availableStatus));
00110     list.push_back(QString::number(evinfo.GetFilesize()));
00111     list.push_back(QString::number(tm.hour()));
00112     list.push_back(QString::number(tm.minute()));
00113     list.push_back(QString::number(tm.second()));
00114     list.push_back(QString::number(tm.msec()));
00115 
00116     QSet<CheckAvailabilityType>::iterator cit = cats.begin();
00117     for (; cit != cats.end(); ++cit)
00118     {
00119         if (*cit == kCheckForCache && cats.size() > 1)
00120             continue;
00121         list[1] = QString::number((int)*cit);
00122         MythEvent *e = new MythEvent("AVAILABILITY", list);
00123         QCoreApplication::postEvent(m_pbh.m_listener, e);
00124     }
00125 
00126     return availableStatus;
00127 }
00128 
00129 bool PBHEventHandler::event(QEvent *e)
00130 {
00131     if (e->type() == QEvent::Timer)
00132     {
00133         QTimerEvent *te = (QTimerEvent*)e;
00134         const int timer_id = te->timerId();
00135         if (timer_id == m_freeSpaceTimerId)
00136             UpdateFreeSpaceEvent();
00137         if (timer_id == m_checkAvailabilityTimerId)
00138         {
00139             QStringList slist;
00140             {
00141                 QMutexLocker locker(&m_pbh.m_lock);
00142                 QHash<QString, QStringList>::iterator it =
00143                     m_checkAvailability.begin();
00144                 if (it != m_checkAvailability.end())
00145                     slist = *it;
00146             }
00147 
00148             if (slist.size() >= 1 + NUMPROGRAMLINES)
00149                 CheckAvailability(slist);
00150         }
00151         return true;
00152     }
00153     else if (e->type() == (QEvent::Type) MythEvent::MythEventMessage)
00154     {
00155         MythEvent *me = (MythEvent*)e;
00156         if (me->Message() == "UPDATE_FREE_SPACE")
00157         {
00158             UpdateFreeSpaceEvent();
00159             return true;
00160         }
00161         else if (me->Message() == "STOP_RECORDING")
00162         {
00163             ProgramInfo pginfo(me->ExtraDataList());
00164             if (pginfo.GetChanID())
00165                 RemoteStopRecording(&pginfo);
00166             return true;
00167         }
00168         else if (me->Message() == "DELETE_RECORDINGS")
00169         {
00170             QStringList successes;
00171             QStringList failures;
00172             QStringList list = me->ExtraDataList();
00173             while (list.size() >= 4)
00174             {
00175                 uint      chanid        = list[0].toUInt();
00176                 QDateTime recstartts    = QDateTime::fromString(
00177                     list[1], Qt::ISODate);
00178                 bool      forceDelete   = list[2].toUInt();
00179                 bool      forgetHistory = list[3].toUInt();
00180 
00181                 bool ok = RemoteDeleteRecording(
00182                     chanid, recstartts, forceDelete, forgetHistory);
00183 
00184                 QStringList &res = (ok) ? successes : failures;
00185                 for (uint i = 0; i < 4; i++)
00186                 {
00187                     res.push_back(list.front());
00188                     list.pop_front();
00189                 }
00190             }
00191             if (!successes.empty())
00192             {
00193                 MythEvent *e = new MythEvent("DELETE_SUCCESSES", successes);
00194                 QCoreApplication::postEvent(m_pbh.m_listener, e);
00195             }
00196             if (!failures.empty())
00197             {
00198                 MythEvent *e = new MythEvent("DELETE_FAILURES", failures);
00199                 QCoreApplication::postEvent(m_pbh.m_listener, e);
00200             }
00201 
00202             return true;
00203         }
00204         else if (me->Message() == "UNDELETE_RECORDINGS")
00205         {
00206             QStringList successes;
00207             QStringList failures;
00208             QStringList list = me->ExtraDataList();
00209             while (list.size() >= 2)
00210             {
00211                 uint      chanid        = list[0].toUInt();
00212                 QDateTime recstartts    = QDateTime::fromString(
00213                     list[1], Qt::ISODate);
00214 
00215                 bool ok = RemoteUndeleteRecording(chanid, recstartts);
00216 
00217                 QStringList &res = (ok) ? successes : failures;
00218                 for (uint i = 0; i < 2; i++)
00219                 {
00220                     res.push_back(list.front());
00221                     list.pop_front();
00222                 }
00223             }
00224             if (!successes.empty())
00225             {
00226                 MythEvent *e = new MythEvent("UNDELETE_SUCCESSES", successes);
00227                 QCoreApplication::postEvent(m_pbh.m_listener, e);
00228             }
00229             if (!failures.empty())
00230             {
00231                 MythEvent *e = new MythEvent("UNDELETE_FAILURES", failures);
00232                 QCoreApplication::postEvent(m_pbh.m_listener, e);
00233             }
00234 
00235             return true;
00236         }
00237         else if (me->Message() == "GET_PREVIEW")
00238         {
00239             QString token = me->ExtraData(0);
00240             bool check_avail = (bool) me->ExtraData(1).toInt();
00241             QStringList list = me->ExtraDataList();
00242             QStringList::const_iterator it = list.begin()+2;
00243             ProgramInfo evinfo(it, list.end());
00244             if (!evinfo.HasPathname())
00245                 return true;
00246 
00247             list.clear();
00248             evinfo.ToStringList(list);
00249             list += QString::number(kCheckForCache);
00250             if (check_avail && (asAvailable != CheckAvailability(list)))
00251                 return true;
00252             else if (asAvailable != evinfo.GetAvailableStatus())
00253                 return true;
00254 
00255             // Now we can actually request the preview...
00256             PreviewGeneratorQueue::GetPreviewImage(evinfo, token);
00257 
00258             return true;
00259         }
00260         else if (me->Message() == "CHECK_AVAILABILITY")
00261         {
00262             if (me->ExtraData(0) != QString::number(kCheckForCache))
00263             {
00264                 if (m_checkAvailabilityTimerId)
00265                     killTimer(m_checkAvailabilityTimerId);
00266                 m_checkAvailabilityTimerId = startTimer(0);
00267             }
00268             else if (!m_checkAvailabilityTimerId)
00269                 m_checkAvailabilityTimerId = startTimer(50);
00270         }
00271         else if (me->Message() == "LOCATE_ARTWORK")
00272         {
00273             QString                inetref   = me->ExtraData(0);
00274             uint                   season    = me->ExtraData(1).toUInt();
00275             const VideoArtworkType type      = (VideoArtworkType)me->ExtraData(2).toInt();
00276             const QString          pikey     = me->ExtraData(3);
00277             const QString          group     = me->ExtraData(4);
00278             const QString          cacheKey = QString("%1:%2:%3")
00279                 .arg((int)type).arg(inetref).arg(season);
00280 
00281             ArtworkMap map = GetArtwork(inetref, season);
00282 
00283             ArtworkInfo info = map.value(type);
00284 
00285             QString foundFile;
00286 
00287             if (!info.url.isEmpty())
00288             {
00289                 foundFile = info.url;
00290                 QMutexLocker locker(&m_pbh.m_lock);
00291                 m_pbh.m_artworkFilenameCache[cacheKey] = foundFile;
00292             }
00293 
00294             if (!foundFile.isEmpty())
00295             {
00296                 QStringList list = me->ExtraDataList();
00297                 list.push_back(foundFile);
00298                 MythEvent *e = new MythEvent("FOUND_ARTWORK", list);
00299                 QCoreApplication::postEvent(m_pbh.m_listener, e);
00300             }
00301 
00302             return true;
00303         }
00304     }
00305 
00306     return QObject::event(e);
00307 }
00308 
00309 void PBHEventHandler::UpdateFreeSpaceEvent(void)
00310 {
00311     if (m_freeSpaceTimerId)
00312         killTimer(m_freeSpaceTimerId);
00313     m_pbh.UpdateFreeSpace();
00314     m_freeSpaceTimerId = startTimer(kUpdateFreeSpaceInterval);
00315 }
00316 
00318 
00319 PlaybackBoxHelper::PlaybackBoxHelper(QObject *listener) :
00320     MThread("PlaybackBoxHelper"),
00321     m_listener(listener), m_eventHandler(new PBHEventHandler(*this)),
00322     // Free Space Tracking Variables
00323     m_freeSpaceTotalMB(0ULL), m_freeSpaceUsedMB(0ULL)
00324 {
00325     start();
00326     m_eventHandler->moveToThread(qthread());
00327     // Prime the pump so the disk free display starts updating
00328     ForceFreeSpaceUpdate();
00329 }
00330 
00331 PlaybackBoxHelper::~PlaybackBoxHelper()
00332 {
00333     exit();
00334     wait();
00335 
00336     // delete the event handler
00337     delete m_eventHandler;
00338     m_eventHandler = NULL;
00339 }
00340 
00341 void PlaybackBoxHelper::ForceFreeSpaceUpdate(void)
00342 {
00343     QCoreApplication::postEvent(
00344         m_eventHandler, new MythEvent("UPDATE_FREE_SPACE"));
00345 }
00346 
00347 void PlaybackBoxHelper::StopRecording(const ProgramInfo &pginfo)
00348 {
00349     QStringList list;
00350     pginfo.ToStringList(list);
00351     MythEvent *e = new MythEvent("STOP_RECORDING", list);
00352     QCoreApplication::postEvent(m_eventHandler, e);
00353 }
00354 
00355 void PlaybackBoxHelper::DeleteRecording(
00356     uint chanid, const QDateTime &recstartts, bool forceDelete,
00357     bool forgetHistory)
00358 {
00359     QStringList list;
00360     list.push_back(QString::number(chanid));
00361     list.push_back(recstartts.toString(Qt::ISODate));
00362     list.push_back((forceDelete)    ? "1" : "0");
00363     list.push_back((forgetHistory)  ? "1" : "0");
00364     DeleteRecordings(list);
00365 }
00366 
00367 void PlaybackBoxHelper::DeleteRecordings(const QStringList &list)
00368 {
00369     MythEvent *e = new MythEvent("DELETE_RECORDINGS", list);
00370     QCoreApplication::postEvent(m_eventHandler, e);
00371 }
00372 
00373 void PlaybackBoxHelper::UndeleteRecording(
00374     uint chanid, const QDateTime &recstartts)
00375 {
00376     QStringList list;
00377     list.push_back(QString::number(chanid));
00378     list.push_back(recstartts.toString(Qt::ISODate));
00379     MythEvent *e = new MythEvent("UNDELETE_RECORDINGS", list);
00380     QCoreApplication::postEvent(m_eventHandler, e);
00381 }
00382 
00383 void PlaybackBoxHelper::UpdateFreeSpace(void)
00384 {
00385     QList<FileSystemInfo> fsInfos = FileSystemInfo::RemoteGetInfo();
00386 
00387     QMutexLocker locker(&m_lock);
00388     for (int i = 0; i < fsInfos.size(); i++)
00389     {
00390         if (fsInfos[i].getPath() == "TotalDiskSpace")
00391         {
00392             m_freeSpaceTotalMB = (uint64_t) (fsInfos[i].getTotalSpace() >> 10);
00393             m_freeSpaceUsedMB  = (uint64_t) (fsInfos[i].getUsedSpace()  >> 10);
00394         }
00395     }
00396     MythEvent *e = new MythEvent("UPDATE_USAGE_UI");
00397     QCoreApplication::postEvent(m_listener, e);
00398 }
00399 
00400 uint64_t PlaybackBoxHelper::GetFreeSpaceTotalMB(void) const
00401 {
00402     QMutexLocker locker(&m_lock);
00403     return m_freeSpaceTotalMB;
00404 }
00405 
00406 uint64_t PlaybackBoxHelper::GetFreeSpaceUsedMB(void) const
00407 {
00408     QMutexLocker locker(&m_lock);
00409     return m_freeSpaceUsedMB;
00410 }
00411 
00412 void PlaybackBoxHelper::CheckAvailability(
00413     const ProgramInfo &pginfo, CheckAvailabilityType cat)
00414 {
00415     QString catstr = QString::number((int)cat);
00416     QMutexLocker locker(&m_lock);
00417     QHash<QString, QStringList>::iterator it =
00418         m_eventHandler->m_checkAvailability.find(pginfo.MakeUniqueKey());
00419     if (it == m_eventHandler->m_checkAvailability.end())
00420     {
00421         QStringList list;
00422         pginfo.ToStringList(list);
00423         list += catstr;
00424         m_eventHandler->m_checkAvailability[pginfo.MakeUniqueKey()] = list;
00425     }
00426     else
00427     {
00428         (*it).push_back(catstr);
00429     }
00430     MythEvent *e = new MythEvent("CHECK_AVAILABILITY", QStringList(catstr));
00431     QCoreApplication::postEvent(m_eventHandler, e);
00432 }
00433 
00434 QString PlaybackBoxHelper::LocateArtwork(
00435     const QString &inetref, uint season,
00436     const VideoArtworkType type,
00437     const ProgramInfo *pginfo,
00438     const QString &groupname)
00439 {
00440     QString cacheKey = QString("%1:%2:%3")
00441         .arg((int)type).arg(inetref).arg(season);
00442 
00443     QMutexLocker locker(&m_lock);
00444 
00445     QHash<QString,QString>::const_iterator it =
00446         m_artworkFilenameCache.find(cacheKey);
00447 
00448     if (it != m_artworkFilenameCache.end())
00449         return *it;
00450 
00451     QStringList list(inetref);
00452     list.push_back(QString::number(season));
00453     list.push_back(QString::number(type));
00454     list.push_back((pginfo)?pginfo->MakeUniqueKey():"");
00455     list.push_back(groupname);
00456     MythEvent *e = new MythEvent("LOCATE_ARTWORK", list);
00457     QCoreApplication::postEvent(m_eventHandler, e);
00458 
00459     return QString();
00460 }
00461 
00462 QString PlaybackBoxHelper::GetPreviewImage(
00463     const ProgramInfo &pginfo, bool check_availability)
00464 {
00465     if (!check_availability && pginfo.GetAvailableStatus() != asAvailable)
00466         return QString();
00467 
00468     if (pginfo.GetAvailableStatus() == asPendingDelete)
00469         return QString();
00470 
00471     QString token = QString("%1:%2")
00472         .arg(pginfo.MakeUniqueKey()).arg(random());
00473 
00474     QStringList extra(token);
00475     extra.push_back(check_availability?"1":"0");
00476     pginfo.ToStringList(extra);
00477     MythEvent *e = new MythEvent("GET_PREVIEW", extra);
00478     QCoreApplication::postEvent(m_eventHandler, e);
00479 
00480     return token;
00481 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends