MythTV  master
guide.cpp
Go to the documentation of this file.
1 // Program Name: guide.cpp
3 // Created : Mar. 7, 2011
4 //
5 // Copyright (c) 2011 David Blain <dblain@mythtv.org>
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 //
25 
26 #include <cmath>
27 
28 #include "guide.h"
29 
30 #include "compat.h"
31 #include "mythversion.h"
32 #include "mythcorecontext.h"
33 #include "scheduler.h"
34 #include "autoexpire.h"
35 #include "channelutil.h"
36 #include "channelgroup.h"
37 #include "storagegroup.h"
38 
39 #include "mythlogging.h"
40 
41 extern AutoExpire *expirer;
42 extern Scheduler *sched;
43 
45 //
47 
48 DTC::ProgramGuide *Guide::GetProgramGuide( const QDateTime &rawStartTime,
49  const QDateTime &rawEndTime,
50  bool bDetails,
51  int nChannelGroupId,
52  int nStartIndex,
53  int nCount,
54  bool bWithInvisible)
55 {
56  if (!rawStartTime.isValid())
57  throw QString( "StartTime is invalid" );
58 
59  if (!rawEndTime.isValid())
60  throw QString( "EndTime is invalid" );
61 
62  QDateTime dtStartTime = rawStartTime.toUTC();
63  QDateTime dtEndTime = rawEndTime.toUTC();
64 
65  if (dtEndTime < dtStartTime)
66  throw QString( "EndTime is before StartTime");
67 
68  if (nStartIndex <= 0)
69  nStartIndex = 0;
70 
71  if (nCount <= 0)
72  nCount = 20000;
73 
74  // ----------------------------------------------------------------------
75  // Load the channel list
76  // ----------------------------------------------------------------------
77 
78  uint nTotalAvailable = 0;
79  ChannelInfoList chanList = ChannelUtil::LoadChannels(nStartIndex, nCount,
80  nTotalAvailable,
81  !bWithInvisible,
84  0,
85  nChannelGroupId);
86 
87  // ----------------------------------------------------------------------
88  // Build SQL statement for Program Listing
89  // ----------------------------------------------------------------------
90 
91  ProgramList schedList;
92  MSqlBindings bindings;
93 
94  QString sWhere = "program.chanid = :CHANID "
95  "AND program.endtime >= :STARTDATE "
96  "AND program.starttime < :ENDDATE "
97  "AND program.starttime >= :STARTDATELIMIT "
98  "AND program.manualid = 0"; // Omit 'manual' recordings scheds
99 
100 #if 0
101  QString sGroupBy = "program.starttime, channel.channum,"
102  "channel.callsign, program.title";
103 #endif
104 
105  QString sOrderBy = "program.starttime";
106 
107  bindings[":STARTDATE" ] = dtStartTime;
108  bindings[":STARTDATELIMIT"] = dtStartTime.addDays(-1);
109  bindings[":ENDDATE" ] = dtEndTime;
110 
111  // ----------------------------------------------------------------------
112  // Get all Pending Scheduled Programs
113  // ----------------------------------------------------------------------
114 
115  // NOTE: Fetching this information directly from the schedule is
116  // significantly faster than using ProgramInfo::LoadFromScheduler()
117  Scheduler *scheduler = dynamic_cast<Scheduler*>(gCoreContext->GetScheduler());
118  if (scheduler)
119  scheduler->GetAllPending(schedList);
120 
121  // ----------------------------------------------------------------------
122  // Build Response
123  // ----------------------------------------------------------------------
124 
125  DTC::ProgramGuide *pGuide = new DTC::ProgramGuide();
126 
127  ChannelInfoList::iterator chan_it;
128  for (chan_it = chanList.begin(); chan_it != chanList.end(); ++chan_it)
129  {
130  // Create ChannelInfo Object
131  DTC::ChannelInfo *pChannel = pGuide->AddNewChannel();
132  FillChannelInfo( pChannel, (*chan_it), bDetails );
133 
134  // Load the list of programmes for this channel
135  ProgramList progList;
136  bindings[":CHANID"] = (*chan_it).m_chanid;
137  LoadFromProgram( progList, sWhere, sOrderBy, sOrderBy, bindings,
138  schedList );
139 
140  // Create Program objects and add them to the channel object
141  ProgramList::iterator progIt;
142  for( progIt = progList.begin(); progIt != progList.end(); ++progIt)
143  {
144  DTC::Program *pProgram = pChannel->AddNewProgram();
145  FillProgramInfo( pProgram, *progIt, false, bDetails, false ); // No cast info
146  }
147  }
148 
149  // ----------------------------------------------------------------------
150 
151  pGuide->setStartTime ( dtStartTime );
152  pGuide->setEndTime ( dtEndTime );
153  pGuide->setDetails ( bDetails );
154 
155  pGuide->setStartIndex ( nStartIndex );
156  pGuide->setCount ( chanList.size() );
157  pGuide->setTotalAvailable( nTotalAvailable );
158  pGuide->setAsOf ( MythDate::current() );
159 
160  pGuide->setVersion ( MYTH_BINARY_VERSION );
161  pGuide->setProtoVer ( MYTH_PROTO_VERSION );
162 
163  return pGuide;
164 }
165 
167 //
169 
171  int nCount,
172  const QDateTime& rawStartTime,
173  const QDateTime& rawEndTime,
174  int nChanId,
175  const QString& sTitleFilter,
176  const QString& sCategoryFilter,
177  const QString& sPersonFilter,
178  const QString& sKeywordFilter,
179  bool bOnlyNew,
180  bool bDetails,
181  const QString &sSort,
182  bool bDescending,
183  bool bWithInvisible)
184 {
185  if (!rawStartTime.isNull() && !rawStartTime.isValid())
186  throw QString( "StartTime is invalid" );
187 
188  if (!rawEndTime.isNull() && !rawEndTime.isValid())
189  throw QString( "EndTime is invalid" );
190 
191  QDateTime dtStartTime = rawStartTime;
192  const QDateTime& dtEndTime = rawEndTime;
193 
194  if (!rawEndTime.isNull() && dtEndTime < dtStartTime)
195  throw QString( "EndTime is before StartTime");
196 
197  MSqlQuery query(MSqlQuery::InitCon());
198 
199 
200  // ----------------------------------------------------------------------
201  // Build SQL statement for Program Listing
202  // ----------------------------------------------------------------------
203 
204  ProgramList progList;
205  ProgramList schedList;
206  MSqlBindings bindings;
207 
208  QString sSQL;
209 
210  if (!sPersonFilter.isEmpty())
211  {
212  sSQL = ", people, credits " // LEFT JOIN
213  "WHERE people.name LIKE :PersonFilter "
214  "AND credits.person = people.person "
215  "AND program.chanid = credits.chanid "
216  "AND program.starttime = credits.starttime AND ";
217  bindings[":PersonFilter"] = QString("%%1%").arg(sPersonFilter);
218  }
219  else
220  sSQL = "WHERE ";
221 
222  if (bOnlyNew)
223  sSQL = "LEFT JOIN oldprogram ON oldprogram.oldtitle = program.title "
224  + sSQL
225  + "oldprogram.oldtitle IS NULL AND ";
226 
227  if (!bWithInvisible)
228  sSQL += "visible != 0 AND ";
229 
230  sSQL += "program.manualid = 0 "; // Exclude programmes created purely for 'manual' recording schedules
231 
232  if (nChanId < 0)
233  nChanId = 0;
234 
235  if (nChanId > 0)
236  {
237  sSQL += "AND program.chanid = :ChanId ";
238  bindings[":ChanId"] = nChanId;
239  }
240 
241  if (dtStartTime.isNull())
242  dtStartTime = QDateTime::currentDateTimeUtc();
243 
244  sSQL += " AND program.endtime >= :StartDate ";
245  bindings[":StartDate"] = dtStartTime;
246 
247  if (!dtEndTime.isNull())
248  {
249  sSQL += "AND program.starttime <= :EndDate ";
250  bindings[":EndDate"] = dtEndTime;
251  }
252 
253  if (!sTitleFilter.isEmpty())
254  {
255  sSQL += "AND program.title LIKE :Title ";
256  bindings[":Title"] = QString("%%1%").arg(sTitleFilter);
257  }
258 
259  if (!sCategoryFilter.isEmpty())
260  {
261  sSQL += "AND program.category LIKE :Category ";
262  bindings[":Category"] = sCategoryFilter;
263  }
264 
265  if (!sKeywordFilter.isEmpty())
266  {
267  sSQL += "AND (program.title LIKE :Keyword1 "
268  "OR program.subtitle LIKE :Keyword2 "
269  "OR program.description LIKE :Keyword3) ";
270 
271  QString filter = QString("%%1%").arg(sKeywordFilter);
272  bindings[":Keyword1"] = filter;
273  bindings[":Keyword2"] = filter;
274  bindings[":Keyword3"] = filter;
275  }
276 
277  if (sSort == "starttime")
278  sSQL += "ORDER BY program.starttime ";
279  else if (sSort == "title")
280  sSQL += "ORDER BY program.title ";
281  else if (sSort == "channel")
282  sSQL += "ORDER BY channel.channum ";
283  else if (sSort == "duration")
284  sSQL += "ORDER BY (program.endtime - program.starttime) ";
285  else
286  sSQL += "ORDER BY program.starttime ";
287 
288  if (bDescending)
289  sSQL += "DESC ";
290  else
291  sSQL += "ASC ";
292 
293  // ----------------------------------------------------------------------
294  // Get all Pending Scheduled Programs
295  // ----------------------------------------------------------------------
296 
297  // NOTE: Fetching this information directly from the schedule is
298  // significantly faster than using ProgramInfo::LoadFromScheduler()
299  Scheduler *scheduler = dynamic_cast<Scheduler*>(gCoreContext->GetScheduler());
300  if (scheduler)
301  scheduler->GetAllPending(schedList);
302 
303  // ----------------------------------------------------------------------
304 
305  uint nTotalAvailable = 0;
306  LoadFromProgram( progList, sSQL, bindings, schedList,
307  (uint)nStartIndex, (uint)nCount, nTotalAvailable);
308 
309  // ----------------------------------------------------------------------
310  // Build Response
311  // ----------------------------------------------------------------------
312 
313  DTC::ProgramList *pPrograms = new DTC::ProgramList();
314 
315  nCount = (int)progList.size();
316  int nEndIndex = (int)progList.size();
317 
318  for( int n = 0; n < nEndIndex; n++)
319  {
320  ProgramInfo *pInfo = progList[ n ];
321 
322  DTC::Program *pProgram = pPrograms->AddNewProgram();
323 
324  FillProgramInfo( pProgram, pInfo, true, bDetails, false ); // No cast info, loading this takes far too long
325  }
326 
327  // ----------------------------------------------------------------------
328 
329  pPrograms->setStartIndex ( nStartIndex );
330  pPrograms->setCount ( nCount );
331  pPrograms->setTotalAvailable( nTotalAvailable );
332  pPrograms->setAsOf ( MythDate::current() );
333  pPrograms->setVersion ( MYTH_BINARY_VERSION );
334  pPrograms->setProtoVer ( MYTH_PROTO_VERSION );
335 
336  return pPrograms;
337 }
338 
340 //
342 
344  const QDateTime &rawStartTime )
345 
346 {
347  if (!(nChanId > 0))
348  throw QString( "Channel ID is invalid" );
349  if (!rawStartTime.isValid())
350  throw QString( "StartTime is invalid" );
351 
352  QDateTime dtStartTime = rawStartTime.toUTC();
353 
354  // ----------------------------------------------------------------------
355  // -=>TODO: Add support for getting Recorded Program Info
356  // ----------------------------------------------------------------------
357 
358  // Build Response
359 
360  DTC::Program *pProgram = new DTC::Program();
361  ProgramInfo *pInfo = LoadProgramFromProgram(nChanId, dtStartTime);
362 
363  FillProgramInfo( pProgram, pInfo, true, true, true );
364 
365  delete pInfo;
366 
367  return pProgram;
368 }
369 
371 //
373 
374 QFileInfo Guide::GetChannelIcon( int nChanId,
375  int nWidth /* = 0 */,
376  int nHeight /* = 0 */ )
377 {
378  // Get Icon file path
379 
380  QString sFileName = ChannelUtil::GetIcon( nChanId );
381 
382  if (sFileName.isEmpty())
383  {
384  LOG(VB_UPNP, LOG_ERR,
385  QString("GetImageFile - ChanId %1 doesn't exist or isn't visible")
386  .arg(nChanId));
387  return QFileInfo();
388  }
389 
390  // ------------------------------------------------------------------
391  // Search for the filename
392  // ------------------------------------------------------------------
393 
394  StorageGroup storage( "ChannelIcons" );
395  QString sFullFileName = storage.FindFile( sFileName );
396 
397  if (sFullFileName.isEmpty())
398  {
399  LOG(VB_UPNP, LOG_ERR,
400  QString("GetImageFile - Unable to find %1.").arg(sFileName));
401 
402  return QFileInfo();
403  }
404 
405  // ----------------------------------------------------------------------
406  // check to see if the file (still) exists
407  // ----------------------------------------------------------------------
408 
409  if ((nWidth == 0) && (nHeight == 0))
410  {
411  if (QFile::exists( sFullFileName ))
412  {
413  return QFileInfo( sFullFileName );
414  }
415 
416  LOG(VB_UPNP, LOG_ERR,
417  QString("GetImageFile - File Does not exist %1.").arg(sFullFileName));
418 
419  return QFileInfo();
420  }
421  // -------------------------------------------------------------------
422 
423  QString sNewFileName = QString( "%1.%2x%3.png" )
424  .arg( sFullFileName )
425  .arg( nWidth )
426  .arg( nHeight );
427 
428  // ----------------------------------------------------------------------
429  // check to see if image is already created.
430  // ----------------------------------------------------------------------
431 
432  if (QFile::exists( sNewFileName ))
433  return QFileInfo( sNewFileName );
434 
435  // ----------------------------------------------------------------------
436  // We need to create it...
437  // ----------------------------------------------------------------------
438 
439  QString sChannelsDirectory = QFileInfo( sNewFileName ).absolutePath();
440 
441  if (!QFileInfo( sChannelsDirectory ).isWritable())
442  {
443  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - no write access to: %1")
444  .arg( sChannelsDirectory ));
445  return QFileInfo();
446  }
447 
448  QImage *pImage = new QImage( sFullFileName );
449 
450  if (!pImage)
451  {
452  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - can't create image: %1")
453  .arg( sFullFileName ));
454  return QFileInfo();
455  }
456 
457  float fAspect = (float)(pImage->width()) / pImage->height();
458  if (fAspect == 0)
459  {
460  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - zero aspect"));
461  delete pImage;
462  return QFileInfo();
463  }
464 
465  if ( nWidth == 0 )
466  nWidth = (int)rint(nHeight * fAspect);
467 
468  if ( nHeight == 0 )
469  nHeight = (int)rint(nWidth / fAspect);
470 
471  QImage img = pImage->scaled( nWidth, nHeight, Qt::IgnoreAspectRatio,
472  Qt::SmoothTransformation);
473 
474  if (img.isNull())
475  {
476  LOG(VB_UPNP, LOG_ERR, QString("SaveImageFile - unable to scale. "
477  "See if %1 is really an image.").arg( sFullFileName ));
478  delete pImage;
479  return QFileInfo();
480  }
481 
482  if (!img.save( sNewFileName, "PNG" ))
483  {
484  LOG(VB_UPNP, LOG_ERR, QString("SaveImageFile - failed, %1")
485  .arg( sNewFileName ));
486  delete pImage;
487  return QFileInfo();
488  }
489 
490  delete pImage;
491 
492  return QFileInfo( sNewFileName );
493 }
494 
496 //
498 
500 {
501  ChannelGroupList list = ChannelGroup::GetChannelGroups(bIncludeEmpty);
502  DTC::ChannelGroupList *pGroupList = new DTC::ChannelGroupList();
503 
504  ChannelGroupList::iterator it;
505  for (it = list.begin(); it < list.end(); ++it)
506  {
507  DTC::ChannelGroup *pGroup = pGroupList->AddNewChannelGroup();
508  FillChannelGroup(pGroup, (*it));
509  }
510 
511  return pGroupList;
512 }
513 
515 //
517 
518 QStringList Guide::GetCategoryList( ) //int nStartIndex, int nCount)
519 {
520  QStringList catList;
521  MSqlQuery query(MSqlQuery::InitCon());
522 
523  query.prepare("SELECT DISTINCT category FROM program WHERE category != '' "
524  "ORDER BY category");
525 
526  if (!query.exec())
527  return catList;
528 
529  while (query.next())
530  {
531  catList << query.value(0).toString();
532  }
533 
534  return catList;
535 }
536 
538 //
540 
541 QStringList Guide::GetStoredSearches( const QString& sType )
542 {
543  QStringList keywordList;
544  MSqlQuery query(MSqlQuery::InitCon());
545 
546  RecSearchType iType = searchTypeFromString(sType);
547 
548  if (iType == kNoSearch)
549  {
550  //throw QString( "Invalid Type" );
551  return keywordList;
552  }
553 
554  query.prepare("SELECT DISTINCT phrase FROM keyword "
555  "WHERE searchtype = :TYPE "
556  "ORDER BY phrase");
557  query.bindValue(":TYPE", static_cast<int>(iType));
558 
559  if (!query.exec())
560  return keywordList;
561 
562  while (query.next())
563  {
564  keywordList << query.value(0).toString();
565  }
566 
567  return keywordList;
568 }
569 
571 //
573 
574 bool Guide::AddToChannelGroup ( int nChannelGroupId,
575  int nChanId )
576 {
577  bool bResult = false;
578 
579  if (!(nChanId > 0))
580  throw QString( "Channel ID is invalid" );
581 
582  bResult = ChannelGroup::AddChannel(nChanId, nChannelGroupId);
583 
584  return bResult;
585 }
586 
588 //
590 
591 bool Guide::RemoveFromChannelGroup ( int nChannelGroupId,
592  int nChanId )
593 {
594  bool bResult = false;
595 
596  if (!(nChanId > 0))
597  throw QString( "Channel ID is invalid" );
598 
599  bResult = ChannelGroup::DeleteChannel(nChanId, nChannelGroupId);
600 
601  return bResult;
602 }
enum RecSearchTypes RecSearchType
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
MythScheduler * GetScheduler(void)
void FillChannelGroup(DTC::ChannelGroup *pGroup, const ChannelGroupItem &pGroupItem)
Program * AddNewProgram()
Definition: programList.h:79
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
ProgramInfo * LoadProgramFromProgram(const uint chanid, const QDateTime &starttime)
DTC::ChannelGroupList * GetChannelGroupList(bool IncludeEmpty) override
Definition: guide.cpp:499
void FillProgramInfo(DTC::Program *pProgram, ProgramInfo *pInfo, bool bIncChannel, bool bDetails, bool bIncCast)
Definition: serviceUtil.cpp:44
static ChannelInfoList LoadChannels(uint startIndex, uint count, uint &totalAvailable, bool ignoreHidden=true, OrderBy orderBy=kChanOrderByChanNum, GroupBy groupBy=kChanGroupByChanid, uint sourceID=0, uint channelGroupID=0, bool liveTVOnly=false, const QString &callsign="", const QString &channum="")
Load channels from database into a list of ChannelInfo objects.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
AutoExpire * expirer
bool RemoveFromChannelGroup(int ChannelGroupId, int ChanId) override
Definition: guide.cpp:591
RecSearchType searchTypeFromString(const QString &type)
vector< ChannelGroupItem > ChannelGroupList
Definition: channelgroup.h:32
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
AutoDeleteDeque< ProgramInfo * > ProgramList
Definition: programinfo.h:29
size_t size(void) const
bool AddToChannelGroup(int ChannelGroupId, int ChanId) override
Definition: guide.cpp:574
iterator begin(void)
QVariant value(int i) const
Definition: mythdbcon.h:198
bool FillChannelInfo(DTC::ChannelInfo *pChannel, uint nChanID, bool bDetails)
Holds information on recordings and videos.
Definition: programinfo.h:66
DTC::ProgramList * GetProgramList(int StartIndex, int Count, const QDateTime &StartTime, const QDateTime &EndTime, int ChanId, const QString &TitleFilter, const QString &CategoryFilter, const QString &PersonFilter, const QString &KeywordFilter, bool OnlyNew, bool Details, const QString &Sort, bool Descending, bool WithInvisible) override
Definition: guide.cpp:170
static bool DeleteChannel(uint chanid, int changrpid)
ChannelGroup * AddNewChannelGroup()
static ChannelGroupList GetChannelGroups(bool includeEmpty=true)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QStringList GetCategoryList() override
Definition: guide.cpp:518
#define MYTH_PROTO_VERSION
Increment this whenever the MythTV network protocol changes.
Definition: mythversion.h:48
static QString GetIcon(uint chanid)
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:120
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
DTC::ProgramGuide * GetProgramGuide(const QDateTime &StartTime, const QDateTime &EndTime, bool Details, int ChannelGroupId, int StartIndex, int Count, bool WithInvisible) override
Definition: guide.cpp:48
bool LoadFromProgram(ProgramList &destination, const QString &where, const QString &groupBy, const QString &orderBy, const MSqlBindings &bindings, const ProgramList &schedList)
QString FindFile(const QString &filename)
Used to expire recordings to make space for new recordings.
Definition: autoexpire.h:61
iterator end(void)
static bool AddChannel(uint chanid, int changrpid)
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:16
DTC::Program * GetProgramDetails(int ChanId, const QDateTime &StartTime) override
Definition: guide.cpp:343
Scheduler * sched
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:98
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
ChannelInfo * AddNewChannel()
Definition: programGuide.h:100
Program * AddNewProgram()
bool GetAllPending(RecList &retList, int recRuleId=0) const
Definition: scheduler.cpp:1752
QStringList GetStoredSearches(const QString &Type) override
Definition: guide.cpp:541
QFileInfo GetChannelIcon(int ChanId, int Width, int Height) override
Definition: guide.cpp:374