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 {
55  if (!rawStartTime.isValid())
56  throw QString( "StartTime is invalid" );
57 
58  if (!rawEndTime.isValid())
59  throw QString( "EndTime is invalid" );
60 
61  QDateTime dtStartTime = rawStartTime.toUTC();
62  QDateTime dtEndTime = rawEndTime.toUTC();
63 
64  if (dtEndTime < dtStartTime)
65  throw QString( "EndTime is before StartTime");
66 
67  if (nStartIndex <= 0)
68  nStartIndex = 0;
69 
70  if (nCount <= 0)
71  nCount = 20000;
72 
73  // ----------------------------------------------------------------------
74  // Load the channel list
75  // ----------------------------------------------------------------------
76 
77  uint nTotalAvailable = 0;
78  ChannelInfoList chanList = ChannelUtil::LoadChannels(nStartIndex, nCount,
79  nTotalAvailable, true,
82  0,
83  nChannelGroupId);
84 
85  // ----------------------------------------------------------------------
86  // Build SQL statement for Program Listing
87  // ----------------------------------------------------------------------
88 
89  ProgramList schedList;
90  MSqlBindings bindings;
91 
92  QString sWhere = "program.chanid = :CHANID "
93  "AND program.endtime >= :STARTDATE "
94  "AND program.starttime < :ENDDATE "
95  "AND program.starttime >= :STARTDATELIMIT "
96  "AND program.manualid = 0"; // Omit 'manual' recordings scheds
97 
98  QString sGroupBy = "program.starttime, channel.channum,"
99  "channel.callsign, program.title";
100 
101  QString sOrderBy = "program.starttime";
102 
103  bindings[":STARTDATE" ] = dtStartTime;
104  bindings[":STARTDATELIMIT"] = dtStartTime.addDays(-1);
105  bindings[":ENDDATE" ] = dtEndTime;
106 
107  // ----------------------------------------------------------------------
108  // Get all Pending Scheduled Programs
109  // ----------------------------------------------------------------------
110 
111  // NOTE: Fetching this information directly from the schedule is
112  // significantly faster than using ProgramInfo::LoadFromScheduler()
113  Scheduler *scheduler = dynamic_cast<Scheduler*>(gCoreContext->GetScheduler());
114  if (scheduler)
115  scheduler->GetAllPending(schedList);
116 
117  // ----------------------------------------------------------------------
118  // Build Response
119  // ----------------------------------------------------------------------
120 
121  DTC::ProgramGuide *pGuide = new DTC::ProgramGuide();
122 
123  ChannelInfoList::iterator chan_it;
124  for (chan_it = chanList.begin(); chan_it != chanList.end(); ++chan_it)
125  {
126  // Create ChannelInfo Object
127  DTC::ChannelInfo *pChannel = pGuide->AddNewChannel();
128  FillChannelInfo( pChannel, (*chan_it), bDetails );
129 
130  // Load the list of programmes for this channel
131  ProgramList progList;
132  bindings[":CHANID"] = (*chan_it).chanid;
133  LoadFromProgram( progList, sWhere, sOrderBy, sOrderBy, bindings,
134  schedList );
135 
136  // Create Program objects and add them to the channel object
137  ProgramList::iterator progIt;
138  for( progIt = progList.begin(); progIt != progList.end(); ++progIt)
139  {
140  DTC::Program *pProgram = pChannel->AddNewProgram();
141  FillProgramInfo( pProgram, *progIt, false, bDetails, false ); // No cast info
142  }
143  }
144 
145  // ----------------------------------------------------------------------
146 
147  pGuide->setStartTime ( dtStartTime );
148  pGuide->setEndTime ( dtEndTime );
149  pGuide->setDetails ( bDetails );
150 
151  pGuide->setStartIndex ( nStartIndex );
152  pGuide->setCount ( chanList.size() );
153  pGuide->setTotalAvailable( nTotalAvailable );
154  pGuide->setAsOf ( MythDate::current() );
155 
156  pGuide->setVersion ( MYTH_BINARY_VERSION );
157  pGuide->setProtoVer ( MYTH_PROTO_VERSION );
158 
159  return pGuide;
160 }
161 
163 //
165 
167  int nCount,
168  const QDateTime& rawStartTime,
169  const QDateTime& rawEndTime,
170  int nChanId,
171  const QString& sTitleFilter,
172  const QString& sCategoryFilter,
173  const QString& sPersonFilter,
174  const QString& sKeywordFilter,
175  bool bOnlyNew,
176  bool bDetails,
177  const QString &sSort,
178  bool bDescending)
179 {
180  if (!rawStartTime.isNull() && !rawStartTime.isValid())
181  throw QString( "StartTime is invalid" );
182 
183  if (!rawEndTime.isNull() && !rawEndTime.isValid())
184  throw QString( "EndTime is invalid" );
185 
186  QDateTime dtStartTime = rawStartTime;
187  QDateTime dtEndTime = rawEndTime;
188 
189  if (!rawEndTime.isNull() && dtEndTime < dtStartTime)
190  throw QString( "EndTime is before StartTime");
191 
192  MSqlQuery query(MSqlQuery::InitCon());
193 
194 
195  // ----------------------------------------------------------------------
196  // Build SQL statement for Program Listing
197  // ----------------------------------------------------------------------
198 
199  ProgramList progList;
200  ProgramList schedList;
201  MSqlBindings bindings;
202 
203  QString sSQL;
204 
205  if (!sPersonFilter.isEmpty())
206  {
207  sSQL = ", people, credits " // LEFT JOIN
208  "WHERE people.name LIKE :PersonFilter "
209  "AND credits.person = people.person "
210  "AND program.chanid = credits.chanid "
211  "AND program.starttime = credits.starttime AND ";
212  bindings[":PersonFilter"] = QString("%%1%").arg(sPersonFilter);
213  }
214  else
215  sSQL = "WHERE ";
216 
217  if (bOnlyNew)
218  sSQL = "LEFT JOIN oldprogram ON oldprogram.oldtitle = program.title "
219  + sSQL
220  + "oldprogram.oldtitle IS NULL AND ";
221 
222  sSQL += "visible != 0 AND program.manualid = 0 "; // Exclude programmes created purely for 'manual' recording schedules
223 
224  if (nChanId < 0)
225  nChanId = 0;
226 
227  if (nChanId > 0)
228  {
229  sSQL += "AND program.chanid = :ChanId ";
230  bindings[":ChanId"] = nChanId;
231  }
232 
233  if (dtStartTime.isNull())
234  dtStartTime = QDateTime::currentDateTimeUtc();
235 
236  sSQL += " AND program.endtime >= :StartDate ";
237  bindings[":StartDate"] = dtStartTime;
238 
239  if (!dtEndTime.isNull())
240  {
241  sSQL += "AND program.starttime <= :EndDate ";
242  bindings[":EndDate"] = dtEndTime;
243  }
244 
245  if (!sTitleFilter.isEmpty())
246  {
247  sSQL += "AND program.title LIKE :Title ";
248  bindings[":Title"] = QString("%%1%").arg(sTitleFilter);
249  }
250 
251  if (!sCategoryFilter.isEmpty())
252  {
253  sSQL += "AND program.category LIKE :Category ";
254  bindings[":Category"] = sCategoryFilter;
255  }
256 
257  if (!sKeywordFilter.isEmpty())
258  {
259  sSQL += "AND (program.title LIKE :Keyword1 "
260  "OR program.subtitle LIKE :Keyword2 "
261  "OR program.description LIKE :Keyword3) ";
262 
263  QString filter = QString("%%1%").arg(sKeywordFilter);
264  bindings[":Keyword1"] = filter;
265  bindings[":Keyword2"] = filter;
266  bindings[":Keyword3"] = filter;
267  }
268 
269  if (sSort == "starttime")
270  sSQL += "ORDER BY program.starttime ";
271  else if (sSort == "title")
272  sSQL += "ORDER BY program.title ";
273  else if (sSort == "channel")
274  sSQL += "ORDER BY channel.channum ";
275  else if (sSort == "duration")
276  sSQL += "ORDER BY (program.endtime - program.starttime) ";
277  else
278  sSQL += "ORDER BY program.starttime ";
279 
280  if (bDescending)
281  sSQL += "DESC ";
282  else
283  sSQL += "ASC ";
284 
285  // ----------------------------------------------------------------------
286  // Get all Pending Scheduled Programs
287  // ----------------------------------------------------------------------
288 
289  // NOTE: Fetching this information directly from the schedule is
290  // significantly faster than using ProgramInfo::LoadFromScheduler()
291  Scheduler *scheduler = dynamic_cast<Scheduler*>(gCoreContext->GetScheduler());
292  if (scheduler)
293  scheduler->GetAllPending(schedList);
294 
295  // ----------------------------------------------------------------------
296 
297  uint nTotalAvailable = 0;
298  LoadFromProgram( progList, sSQL, bindings, schedList,
299  (uint)nStartIndex, (uint)nCount, nTotalAvailable);
300 
301  // ----------------------------------------------------------------------
302  // Build Response
303  // ----------------------------------------------------------------------
304 
305  DTC::ProgramList *pPrograms = new DTC::ProgramList();
306 
307  nCount = (int)progList.size();
308  int nEndIndex = (int)progList.size();
309 
310  for( int n = 0; n < nEndIndex; n++)
311  {
312  ProgramInfo *pInfo = progList[ n ];
313 
314  DTC::Program *pProgram = pPrograms->AddNewProgram();
315 
316  FillProgramInfo( pProgram, pInfo, true, bDetails, false ); // No cast info, loading this takes far too long
317  }
318 
319  // ----------------------------------------------------------------------
320 
321  pPrograms->setStartIndex ( nStartIndex );
322  pPrograms->setCount ( nCount );
323  pPrograms->setTotalAvailable( nTotalAvailable );
324  pPrograms->setAsOf ( MythDate::current() );
325  pPrograms->setVersion ( MYTH_BINARY_VERSION );
326  pPrograms->setProtoVer ( MYTH_PROTO_VERSION );
327 
328  return pPrograms;
329 }
330 
332 //
334 
336  const QDateTime &rawStartTime )
337 
338 {
339  if (!(nChanId > 0))
340  throw QString( "Channel ID is invalid" );
341  if (!rawStartTime.isValid())
342  throw QString( "StartTime is invalid" );
343 
344  QDateTime dtStartTime = rawStartTime.toUTC();
345 
346  // ----------------------------------------------------------------------
347  // -=>TODO: Add support for getting Recorded Program Info
348  // ----------------------------------------------------------------------
349 
350  // Build Response
351 
352  DTC::Program *pProgram = new DTC::Program();
353  ProgramInfo *pInfo = LoadProgramFromProgram(nChanId, dtStartTime);
354 
355  FillProgramInfo( pProgram, pInfo, true, true, true );
356 
357  delete pInfo;
358 
359  return pProgram;
360 }
361 
363 //
365 
366 QFileInfo Guide::GetChannelIcon( int nChanId,
367  int nWidth /* = 0 */,
368  int nHeight /* = 0 */ )
369 {
370  // Get Icon file path
371 
372  QString sFileName = ChannelUtil::GetIcon( nChanId );
373 
374  if (sFileName.isEmpty())
375  {
376  LOG(VB_UPNP, LOG_ERR,
377  QString("GetImageFile - ChanId %1 doesn't exist or isn't visible")
378  .arg(nChanId));
379  return QFileInfo();
380  }
381 
382  // ------------------------------------------------------------------
383  // Search for the filename
384  // ------------------------------------------------------------------
385 
386  StorageGroup storage( "ChannelIcons" );
387  QString sFullFileName = storage.FindFile( sFileName );
388 
389  if (sFullFileName.isEmpty())
390  {
391  LOG(VB_UPNP, LOG_ERR,
392  QString("GetImageFile - Unable to find %1.").arg(sFileName));
393 
394  return QFileInfo();
395  }
396 
397  // ----------------------------------------------------------------------
398  // check to see if the file (still) exists
399  // ----------------------------------------------------------------------
400 
401  if ((nWidth == 0) && (nHeight == 0))
402  {
403  if (QFile::exists( sFullFileName ))
404  {
405  return QFileInfo( sFullFileName );
406  }
407 
408  LOG(VB_UPNP, LOG_ERR,
409  QString("GetImageFile - File Does not exist %1.").arg(sFullFileName));
410 
411  return QFileInfo();
412  }
413  // -------------------------------------------------------------------
414 
415  QString sNewFileName = QString( "%1.%2x%3.png" )
416  .arg( sFullFileName )
417  .arg( nWidth )
418  .arg( nHeight );
419 
420  // ----------------------------------------------------------------------
421  // check to see if image is already created.
422  // ----------------------------------------------------------------------
423 
424  if (QFile::exists( sNewFileName ))
425  return QFileInfo( sNewFileName );
426 
427  // ----------------------------------------------------------------------
428  // We need to create it...
429  // ----------------------------------------------------------------------
430 
431  QString sChannelsDirectory = QFileInfo( sNewFileName ).absolutePath();
432 
433  if (!QFileInfo( sChannelsDirectory ).isWritable())
434  {
435  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - no write access to: %1")
436  .arg( sChannelsDirectory ));
437  return QFileInfo();
438  }
439 
440  QImage *pImage = new QImage( sFullFileName );
441 
442  if (!pImage)
443  {
444  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - can't create image: %1")
445  .arg( sFullFileName ));
446  return QFileInfo();
447  }
448 
449  float fAspect = (float)(pImage->width()) / pImage->height();
450  if (fAspect == 0)
451  {
452  LOG(VB_UPNP, LOG_ERR, QString("GetImageFile - zero aspect"));
453  delete pImage;
454  return QFileInfo();
455  }
456 
457  if ( nWidth == 0 )
458  nWidth = (int)rint(nHeight * fAspect);
459 
460  if ( nHeight == 0 )
461  nHeight = (int)rint(nWidth / fAspect);
462 
463  QImage img = pImage->scaled( nWidth, nHeight, Qt::IgnoreAspectRatio,
464  Qt::SmoothTransformation);
465 
466  if (img.isNull())
467  {
468  LOG(VB_UPNP, LOG_ERR, QString("SaveImageFile - unable to scale. "
469  "See if %1 is really an image.").arg( sFullFileName ));
470  delete pImage;
471  return QFileInfo();
472  }
473 
474  if (!img.save( sNewFileName, "PNG" ))
475  {
476  LOG(VB_UPNP, LOG_ERR, QString("SaveImageFile - failed, %1")
477  .arg( sNewFileName ));
478  delete pImage;
479  return QFileInfo();
480  }
481 
482  delete pImage;
483 
484  return QFileInfo( sNewFileName );
485 }
486 
488 //
490 
492 {
493  ChannelGroupList list = ChannelGroup::GetChannelGroups(bIncludeEmpty);
494  DTC::ChannelGroupList *pGroupList = new DTC::ChannelGroupList();
495 
496  ChannelGroupList::iterator it;
497  for (it = list.begin(); it < list.end(); ++it)
498  {
499  DTC::ChannelGroup *pGroup = pGroupList->AddNewChannelGroup();
500  FillChannelGroup(pGroup, (*it));
501  }
502 
503  return pGroupList;
504 }
505 
507 //
509 
510 QStringList Guide::GetCategoryList( ) //int nStartIndex, int nCount)
511 {
512  QStringList catList;
513  MSqlQuery query(MSqlQuery::InitCon());
514 
515  query.prepare("SELECT DISTINCT category FROM program WHERE category != '' "
516  "ORDER BY category");
517 
518  if (!query.exec())
519  return catList;
520 
521  while (query.next())
522  {
523  catList << query.value(0).toString();
524  }
525 
526  return catList;
527 }
528 
530 //
532 
533 QStringList Guide::GetStoredSearches( const QString& sType )
534 {
535  QStringList keywordList;
536  MSqlQuery query(MSqlQuery::InitCon());
537 
538  RecSearchType iType = searchTypeFromString(sType);
539 
540  if (iType == kNoSearch)
541  {
542  //throw QString( "Invalid Type" );
543  return keywordList;
544  }
545 
546  query.prepare("SELECT DISTINCT phrase FROM keyword "
547  "WHERE searchtype = :TYPE "
548  "ORDER BY phrase");
549  query.bindValue(":TYPE", static_cast<int>(iType));
550 
551  if (!query.exec())
552  return keywordList;
553 
554  while (query.next())
555  {
556  keywordList << query.value(0).toString();
557  }
558 
559  return keywordList;
560 }
561 
563 //
565 
566 bool Guide::AddToChannelGroup ( int nChannelGroupId,
567  int nChanId )
568 {
569  bool bResult = false;
570 
571  if (!(nChanId > 0))
572  throw QString( "Channel ID is invalid" );
573 
574  bResult = ChannelGroup::AddChannel(nChanId, nChannelGroupId);
575 
576  return bResult;
577 }
578 
580 //
582 
583 bool Guide::RemoveFromChannelGroup ( int nChannelGroupId,
584  int nChanId )
585 {
586  bool bResult = false;
587 
588  if (!(nChanId > 0))
589  throw QString( "Channel ID is invalid" );
590 
591  bResult = ChannelGroup::DeleteChannel(nChanId, nChannelGroupId);
592 
593  return bResult;
594 }
enum RecSearchTypes RecSearchType
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:794
MythScheduler * GetScheduler(void)
Program * AddNewProgram()
Definition: programList.h:79
void bindValue(const QString &placeholder, const QVariant &val)
Definition: mythdbcon.cpp:875
ProgramInfo * LoadProgramFromProgram(const uint chanid, const QDateTime &starttime)
DTC::ChannelGroupList * GetChannelGroupList(bool IncludeEmpty) override
Definition: guide.cpp:491
void FillProgramInfo(DTC::Program *pProgram, ProgramInfo *pInfo, bool bIncChannel, bool bDetails, bool bIncCast)
Definition: serviceUtil.cpp:44
RecSearchType searchTypeFromString(QString type)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
AutoExpire * expirer
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) override
Definition: guide.cpp:166
bool RemoveFromChannelGroup(int ChannelGroupId, int ChanId) override
Definition: guide.cpp:583
vector< ChannelGroupItem > ChannelGroupList
Definition: channelgroup.h:32
DTC::ProgramGuide * GetProgramGuide(const QDateTime &StartTime, const QDateTime &EndTime, bool Details, int ChannelGroupId, int StartIndex, int Count) override
Definition: guide.cpp:48
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:566
void FillChannelGroup(DTC::ChannelGroup *pGroup, ChannelGroupItem pGroupItem)
iterator begin(void)
QVariant value(int i) const
Definition: mythdbcon.h:182
bool FillChannelInfo(DTC::ChannelInfo *pChannel, uint nChanID, bool bDetails)
Holds information on recordings and videos.
Definition: programinfo.h:66
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:510
#define MYTH_PROTO_VERSION
Increment this whenever the MythTV network protocol changes.
Definition: mythversion.h:48
static QString GetIcon(uint chanid)
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, QString callsign="", QString channum="")
Load channels from database into a list of ChannelInfo objects.
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:547
vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:119
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:819
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
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:335
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:615
ChannelInfo * AddNewChannel()
Definition: programGuide.h:100
Program * AddNewProgram()
bool GetAllPending(RecList &retList, int recRuleId=0) const
Definition: scheduler.cpp:1769
QStringList GetStoredSearches(const QString &Type) override
Definition: guide.cpp:533
QFileInfo GetChannelIcon(int ChanId, int Width, int Height) override
Definition: guide.cpp:366