MythTV  master
programinfo.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // POSIX headers
4 // FIXME What are these used for?
5 // #include <sys/types.h>
6 // #include <unistd.h>
7 
8 // C++ headers
9 #include <algorithm>
10 using std::max;
11 using std::min;
12 
13 // Qt headers
14 #include <QRegExp>
15 #include <QMap>
16 #include <QUrl>
17 #include <QFile>
18 #include <QFileInfo>
19 #include <QDir>
20 
21 // MythTV headers
22 #include "programinfoupdater.h"
23 #include "mythcorecontext.h"
24 #include "mythscheduler.h"
25 #include "mythmiscutil.h"
26 #include "storagegroup.h"
27 #include "mythlogging.h"
28 #include "programinfo.h"
29 #include "remotefile.h"
30 #include "remoteutil.h"
31 #include "mythdb.h"
32 #include "compat.h"
33 #include "mythcdrom.h"
34 #include "mythsorthelper.h"
35 
36 #include <unistd.h> // for getpid()
37 
38 #define LOC QString("ProgramInfo(%1): ").arg(GetBasename())
39 
40 //#define DEBUG_IN_USE
41 
42 static int init_tr(void);
43 
49 
50 const static uint kInvalidDateTime = (uint)-1;
51 
52 
53 const QString ProgramInfo::kFromRecordedQuery =
54  "SELECT r.title, r.subtitle, r.description, "// 0-2
55  " r.season, r.episode, r.category, "// 3-5
56  " r.chanid, c.channum, c.callsign, "// 6-8
57  " c.name, c.outputfilters,r.recgroup, "// 9-11
58  " r.playgroup, r.storagegroup, r.basename, "//12-14
59  " r.hostname, r.recpriority, r.seriesid, "//15-17
60  " r.programid, r.inetref, r.filesize, "//18-20
61  " r.progstart, r.progend, r.stars, "//21-23
62  " r.starttime, r.endtime, p.airdate+0, "//24-26
63  " r.originalairdate, r.lastmodified, r.recordid, "//27-29
64  " c.commmethod, r.commflagged, r.previouslyshown, "//30-32
65  " r.transcoder, r.transcoded, r.deletepending, "//33-35
66  " r.preserve, r.cutlist, r.autoexpire, "//36-38
67  " r.editing, r.bookmark, r.watched, "//39-41
68  " p.audioprop+0, p.videoprop+0, p.subtitletypes+0, "//42-44
69  " r.findid, rec.dupin, rec.dupmethod, "//45-47
70  " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "//48-50
71  " p.season, p.episode, p.totalepisodes, "//51-53
72  " p.category_type, r.recordedid, r.inputname, "//54-56
73  " r.bookmarkupdate "//57-57
74  "FROM recorded AS r "
75  "LEFT JOIN channel AS c "
76  "ON (r.chanid = c.chanid) "
77  "LEFT JOIN recordedprogram AS p "
78  "ON (r.chanid = p.chanid AND "
79  " r.progstart = p.starttime) "
80  "LEFT JOIN record AS rec "
81  "ON (r.recordid = rec.recordid) ";
82 
83 static void set_flag(uint32_t &flags, int flag_to_set, bool is_set)
84 {
85  flags &= ~flag_to_set;
86  if (is_set)
87  flags |= flag_to_set;
88 }
89 
90 static QString determineURLType(const QString& url)
91 {
92  QString result = url;
93 
94  if (!url.startsWith("dvd:") && !url.startsWith("bd:"))
95  {
96  if(url.endsWith(".img", Qt::CaseInsensitive) ||
97  url.endsWith(".iso", Qt::CaseInsensitive))
98  {
99  switch (MythCDROM::inspectImage(url))
100  {
101  case MythCDROM::kBluray:
102  result = "bd:" + url;
103  break;
104 
105  case MythCDROM::kDVD:
106  result = "dvd:" + url;
107  break;
108 
109  case MythCDROM::kUnknown:
110  // Quiet compiler warning.
111  break;
112  }
113  }
114  else
115  {
116  if (QDir(url + "/BDMV").exists())
117  result = "bd:" + url;
118  else if (QDir(url + "/VIDEO_TS").exists())
119  result = "dvd:" + url;
120  }
121  }
122 
123  return result;
124 }
125 
127 {
128  static int NUM_CAT_TYPES = 5;
129  static const char *cattype[] =
130  { "", "movie", "series", "sports", "tvshow", };
131 
132  if ((category_type > ProgramInfo::kCategoryNone) &&
133  ((int)category_type < NUM_CAT_TYPES))
134  return QString(cattype[category_type]);
135 
136  return "";
137 }
138 
140 {
141  static int NUM_CAT_TYPES = 5;
142  static const char *cattype[] =
143  { "", "movie", "series", "sports", "tvshow", };
144 
145  for (int i = 1; i < NUM_CAT_TYPES; i++)
146  if (category_type == cattype[i])
147  return (ProgramInfo::CategoryType) i;
149 }
150 
155  m_title(other.m_title),
156  m_sortTitle(other.m_sortTitle),
157  m_subtitle(other.m_subtitle),
158  m_sortSubtitle(other.m_sortSubtitle),
159  m_description(other.m_description),
160  m_season(other.m_season),
161  m_episode(other.m_episode),
162  m_totalepisodes(other.m_totalepisodes),
163  m_syndicatedepisode(other.m_syndicatedepisode),
164  m_category(other.m_category),
165  m_director(other.m_director),
166 
167  m_recpriority(other.m_recpriority),
168 
169  m_chanid(other.m_chanid),
170  m_chanstr(other.m_chanstr),
171  m_chansign(other.m_chansign),
172  m_channame(other.m_channame),
173  m_chanplaybackfilters(other.m_chanplaybackfilters),
174 
175  m_recgroup(other.m_recgroup),
176  m_playgroup(other.m_playgroup),
177 
178  m_pathname(other.m_pathname),
179 
180  m_hostname(other.m_hostname),
181  m_storagegroup(other.m_storagegroup),
182 
183  m_seriesid(other.m_seriesid),
184  m_programid(other.m_programid),
185  m_inetref(other.m_inetref),
186  m_catType(other.m_catType),
187 
188  m_filesize(other.m_filesize),
189 
190  m_startts(other.m_startts),
191  m_endts(other.m_endts),
192  m_recstartts(other.m_recstartts),
193  m_recendts(other.m_recendts),
194 
195  m_stars(other.m_stars),
196 
197  m_originalAirDate(other.m_originalAirDate),
198  m_lastmodified(other.m_lastmodified),
199  m_lastInUseTime(MythDate::current().addSecs(-4 * 60 * 60)),
200 
201  m_recpriority2(other.m_recpriority2),
202  m_recordid(other.m_recordid),
203  m_parentid(other.m_parentid),
204 
205  m_sourceid(other.m_sourceid),
206  m_inputid(other.m_inputid),
207 
208  m_findid(other.m_findid),
209  m_programflags(other.m_programflags),
210  m_properties(other.m_properties),
211  m_year(other.m_year),
212  m_partnumber(other.m_partnumber),
213  m_parttotal(other.m_parttotal),
214 
215  m_recstatus(other.m_recstatus),
216  m_rectype(other.m_rectype),
217  m_dupin(other.m_dupin),
218  m_dupmethod(other.m_dupmethod),
219 
220  m_recordedid(other.m_recordedid),
221  m_inputname(other.m_inputname),
222  m_bookmarkupdate(other.m_bookmarkupdate),
223 
224  // everything below this line is not serialized
225  m_availableStatus(other.m_availableStatus),
226  m_spread(other.m_spread),
227  m_startCol(other.m_startCol),
228 
229  // Private
230  m_positionMapDBReplacement(other.m_positionMapDBReplacement)
231 {
233 }
234 
239 {
241 
242  MSqlQuery query(MSqlQuery::InitCon());
243  query.prepare(
244  "SELECT chanid, starttime "
245  "FROM recorded "
246  "WHERE recordedid = :RECORDEDID");
247  query.bindValue(":RECORDEDID", _recordedid);
248 
249  if (query.exec() && query.next())
250  {
251  uint _chanid = query.value(0).toUInt();
252  QDateTime _recstartts = MythDate::as_utc(query.value(1).toDateTime());
253  LoadProgramFromRecorded(_chanid, _recstartts);
254  }
255  else
256  {
257  LOG(VB_GENERAL, LOG_CRIT, LOC +
258  QString("Failed to find recorded entry for %1.")
259  .arg(_recordedid));
261  }
262 
264 }
265 
269 ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts)
270 {
272 
273  LoadProgramFromRecorded(_chanid, _recstartts);
275 }
276 
281  uint _recordedid,
282  const QString &_title,
283  const QString &_sortTitle,
284  const QString &_subtitle,
285  const QString &_sortSubtitle,
286  const QString &_description,
287  uint _season,
288  uint _episode,
289  uint _totalepisodes,
290  const QString &_syndicatedepisode,
291  const QString &_category,
292 
293  uint _chanid,
294  const QString &_channum,
295  const QString &_chansign,
296  const QString &_channame,
297  const QString &_chanplaybackfilters,
298 
299  const QString &_recgroup,
300  const QString &_playgroup,
301 
302  const QString &_pathname,
303 
304  const QString &_hostname,
305  const QString &_storagegroup,
306 
307  const QString &_seriesid,
308  const QString &_programid,
309  const QString &_inetref,
310  CategoryType _catType,
311 
312  int _recpriority,
313 
314  uint64_t _filesize,
315 
316  const QDateTime &_startts,
317  const QDateTime &_endts,
318  const QDateTime &_recstartts,
319  const QDateTime &_recendts,
320 
321  float _stars,
322 
323  uint _year,
324  uint _partnumber,
325  uint _parttotal,
326  const QDate &_originalAirDate,
327  const QDateTime &_lastmodified,
328 
329  RecStatus::Type _recstatus,
330 
331  uint _recordid,
332 
333  RecordingDupInType _dupin,
334  RecordingDupMethodType _dupmethod,
335 
336  uint _findid,
337 
338  uint _programflags,
339  uint _audioproperties,
340  uint _videoproperties,
341  uint _subtitleType,
342  const QString &_inputname,
343  const QDateTime &_bookmarkupdate) :
344  m_title(_title),
345  m_sortTitle(_sortTitle),
346  m_subtitle(_subtitle),
347  m_sortSubtitle(_sortSubtitle),
348  m_description(_description),
349  m_season(_season),
350  m_episode(_episode),
351  m_totalepisodes(_totalepisodes),
352  m_syndicatedepisode(_syndicatedepisode),
353  m_category(_category),
354 
355  m_recpriority(_recpriority),
356 
357  m_chanid(_chanid),
358  m_chanstr(_channum),
359  m_chansign(_chansign),
360  m_channame(_channame),
361  m_chanplaybackfilters(_chanplaybackfilters),
362 
363  m_recgroup(_recgroup),
364  m_playgroup(_playgroup),
365 
366  m_pathname(_pathname),
367 
368  m_hostname(_hostname),
369  m_storagegroup(_storagegroup),
370 
371  m_seriesid(_seriesid),
372  m_programid(_programid),
373  m_inetref(_inetref),
374  m_catType(_catType),
375 
376  m_filesize(_filesize),
377 
378  m_startts(_startts),
379  m_endts(_endts),
380  m_recstartts(_recstartts),
381  m_recendts(_recendts),
382 
383  m_stars(clamp(_stars, 0.0F, 1.0F)),
384 
385  m_originalAirDate(_originalAirDate),
386  m_lastmodified(_lastmodified),
387  m_lastInUseTime(MythDate::current().addSecs(-4 * 60 * 60)),
388 
389  m_recordid(_recordid),
390  m_findid(_findid),
391 
392  m_programflags(_programflags),
393  m_properties((_subtitleType << kSubtitlePropertyOffset) |
394  (_videoproperties << kVideoPropertyOffset) |
395  (_audioproperties << kAudioPropertyOffset)),
396  m_year(_year),
397  m_partnumber(_partnumber),
398  m_parttotal(_parttotal),
399 
400  m_recstatus(_recstatus),
401  m_dupin(_dupin),
402  m_dupmethod(_dupmethod),
403 
404  m_recordedid(_recordedid),
405  m_inputname(_inputname),
406  m_bookmarkupdate(_bookmarkupdate)
407 {
408  if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
409  m_originalAirDate = QDate();
410 
411  SetPathname(_pathname);
413 }
414 
419  const QString &_title,
420  const QString &_sortTitle,
421  const QString &_subtitle,
422  const QString &_sortSubtitle,
423  const QString &_description,
424  uint _season,
425  uint _episode,
426  const QString &_category,
427 
428  uint _chanid,
429  const QString &_channum,
430  const QString &_chansign,
431  const QString &_channame,
432 
433  const QString &_seriesid,
434  const QString &_programid,
435  const QString &_inetref,
436 
437  const QDateTime &_startts,
438  const QDateTime &_endts,
439  const QDateTime &_recstartts,
440  const QDateTime &_recendts,
441 
442  RecStatus::Type _recstatus,
443 
444  uint _recordid,
445 
446  RecordingType _rectype,
447 
448  uint _findid,
449 
450  bool duplicate) :
451  m_title(_title),
452  m_sortTitle(_sortTitle),
453  m_subtitle(_subtitle),
454  m_sortSubtitle(_sortSubtitle),
455  m_description(_description),
456  m_season(_season),
457  m_episode(_episode),
458  m_category(_category),
459 
460  m_chanid(_chanid),
461  m_chanstr(_channum),
462  m_chansign(_chansign),
463  m_channame(_channame),
464 
465  m_seriesid(_seriesid),
466  m_programid(_programid),
467  m_inetref(_inetref),
468 
469  m_startts(_startts),
470  m_endts(_endts),
471  m_recstartts(_recstartts),
472  m_recendts(_recendts),
473 
474  m_lastmodified(m_startts),
475  m_lastInUseTime(MythDate::current().addSecs(-4 * 60 * 60)),
476 
477  m_recordid(_recordid),
478  m_findid(_findid),
479 
480  m_programflags((duplicate) ? FL_DUPLICATE : 0),
481 
482  m_recstatus(_recstatus),
483  m_rectype(_rectype),
484  m_dupin(kDupsUnset),
485  m_dupmethod(kDupCheckUnset)
486 {
488 }
489 
494  const QString &_title,
495  const QString &_sortTitle,
496  const QString &_subtitle,
497  const QString &_sortSubtitle,
498  const QString &_description,
499  const QString &_syndicatedepisode,
500  const QString &_category,
501 
502  uint _chanid,
503  const QString &_channum,
504  const QString &_chansign,
505  const QString &_channame,
506  const QString &_chanplaybackfilters,
507 
508  const QDateTime &_startts,
509  const QDateTime &_endts,
510  const QDateTime &_recstartts,
511  const QDateTime &_recendts,
512 
513  const QString &_seriesid,
514  const QString &_programid,
515  const CategoryType _catType,
516 
517  float _stars,
518  uint _year,
519  uint _partnumber,
520  uint _parttotal,
521 
522  const QDate &_originalAirDate,
523  RecStatus::Type _recstatus,
524  uint _recordid,
525  RecordingType _rectype,
526  uint _findid,
527 
528  bool commfree,
529  bool repeat,
530 
531  uint _videoproperties,
532  uint _audioproperties,
533  uint _subtitleType,
534 
535  uint _season,
536  uint _episode,
537  uint _totalepisodes,
538 
539  const ProgramList &schedList) :
540  m_title(_title),
541  m_sortTitle(_sortTitle),
542  m_subtitle(_subtitle),
543  m_sortSubtitle(_sortSubtitle),
544  m_description(_description),
545  m_season(_season),
546  m_episode(_episode),
547  m_totalepisodes(_totalepisodes),
548  m_syndicatedepisode(_syndicatedepisode),
549  m_category(_category),
550 
551  m_chanid(_chanid),
552  m_chanstr(_channum),
553  m_chansign(_chansign),
554  m_channame(_channame),
555  m_chanplaybackfilters(_chanplaybackfilters),
556 
557  m_seriesid(_seriesid),
558  m_programid(_programid),
559  m_catType(_catType),
560 
561  m_startts(_startts),
562  m_endts(_endts),
563  m_recstartts(_recstartts),
564  m_recendts(_recendts),
565 
566  m_stars(clamp(_stars, 0.0F, 1.0F)),
567 
568  m_originalAirDate(_originalAirDate),
569  m_lastmodified(m_startts),
570  m_lastInUseTime(m_startts.addSecs(-4 * 60 * 60)),
571 
572  m_recordid(_recordid),
573  m_findid(_findid),
574 
575  m_programflags(FL_NONE),
576  m_properties((_subtitleType << kSubtitlePropertyOffset) |
577  (_videoproperties << kVideoPropertyOffset) |
578  (_audioproperties << kAudioPropertyOffset)),
579  m_year(_year),
580  m_partnumber(_partnumber),
581  m_parttotal(_parttotal),
582 
583  m_recstatus(_recstatus),
584  m_rectype(_rectype)
585 {
586  m_programflags |= (commfree) ? FL_CHANCOMMFREE : 0;
587  m_programflags |= (repeat) ? FL_REPEAT : 0;
588 
589  if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
590  m_originalAirDate = QDate();
591 
592  ProgramList::const_iterator it = schedList.begin();
593  for (; it != schedList.end(); ++it)
594  {
595  // If this showing is scheduled to be recorded, then we need to copy
596  // some of the information from the scheduler
597  //
598  // This applies even if the showing may be on a different channel
599  // to the one which is actually being recorded e.g. A regional or HD
600  // variant of the same channel
601  if (!IsSameProgramAndStartTime(**it))
602  continue;
603 
604  const ProgramInfo &s = **it;
606  m_rectype = s.m_rectype;
610  m_inputid = s.m_inputid;
611  m_dupin = s.m_dupin;
613  m_findid = s.m_findid;
617 
618  // This is the exact showing (same chanid or callsign)
619  // which will be recorded
620  if (IsSameChannel(s))
621  {
623  break;
624  }
625 
632  }
634 }
635 
640  const QString &_title,
641  const QString &_sortTitle,
642  const QString &_subtitle,
643  const QString &_sortSubtitle,
644  const QString &_description,
645  uint _season,
646  uint _episode,
647  uint _totalepisodes,
648  const QString &_category,
649 
650  uint _chanid,
651  const QString &_channum,
652  const QString &_chansign,
653  const QString &_channame,
654  const QString &_chanplaybackfilters,
655 
656  const QString &_recgroup,
657  const QString &_playgroup,
658 
659  const QDateTime &_startts,
660  const QDateTime &_endts,
661  const QDateTime &_recstartts,
662  const QDateTime &_recendts,
663 
664  const QString &_seriesid,
665  const QString &_programid,
666  const QString &_inetref,
667  const QString &_inputname) :
668  m_title(_title),
669  m_sortTitle(_sortTitle),
670  m_subtitle(_subtitle),
671  m_sortSubtitle(_sortSubtitle),
672  m_description(_description),
673  m_season(_season),
674  m_episode(_episode),
675  m_totalepisodes(_totalepisodes),
676  m_category(_category),
677 
678  m_chanid(_chanid),
679  m_chanstr(_channum),
680  m_chansign(_chansign),
681  m_channame(_channame),
682  m_chanplaybackfilters(_chanplaybackfilters),
683 
684  m_recgroup(_recgroup),
685  m_playgroup(_playgroup),
686 
687  m_seriesid(_seriesid),
688  m_programid(_programid),
689  m_inetref(_inetref),
690 
691  m_startts(_startts),
692  m_endts(_endts),
693  m_recstartts(_recstartts),
694  m_recendts(_recendts),
695 
696  m_lastmodified(MythDate::current()),
697  m_lastInUseTime(m_lastmodified.addSecs(-4 * 60 * 60)),
698 
699  m_inputname(_inputname)
700 {
702 }
703 
707 ProgramInfo::ProgramInfo(const QString &_pathname)
708 {
710  if (_pathname.isEmpty())
711  {
712  return;
713  }
714 
715  uint _chanid;
716  QDateTime _recstartts;
718  QueryKeyFromPathname(_pathname, _chanid, _recstartts) &&
719  LoadProgramFromRecorded(_chanid, _recstartts))
720  {
721  return;
722  }
723 
725 
726  QDateTime cur = MythDate::current();
727  m_recstartts = m_startts = cur.addSecs(-4 * 60 * 60 - 1);
728  m_recendts = m_endts = cur.addSecs(-1);
729 
730  QString basename = _pathname.section('/', -1);
731  if (_pathname == basename)
732  SetPathname(QDir::currentPath() + '/' + _pathname);
733  else if (_pathname.contains("./") && !_pathname.contains(":"))
734  SetPathname(QFileInfo(_pathname).absoluteFilePath());
735  else
736  SetPathname(_pathname);
738 }
739 
743 ProgramInfo::ProgramInfo(const QString &_pathname,
744  const QString &_plot,
745  const QString &_title,
746  const QString &_sortTitle,
747  const QString &_subtitle,
748  const QString &_sortSubtitle,
749  const QString &_director,
750  int _season, int _episode,
751  const QString &_inetref,
752  uint _length_in_minutes,
753  uint _year,
754  const QString &_programid)
755 {
757 
758  m_title = _title;
759  m_sortTitle = _sortTitle;
760  m_subtitle = _subtitle;
761  m_sortSubtitle = _sortSubtitle;
762  m_description = _plot;
763  m_season = _season;
764  m_episode = _episode;
765  m_director = _director;
766  m_programid = _programid;
767  m_inetref = _inetref;
768  m_year = _year;
769 
770  QDateTime cur = MythDate::current();
771  m_recstartts = cur.addSecs(((int)_length_in_minutes + 1) * -60);
772  m_recendts = m_recstartts.addSecs(_length_in_minutes * 60);
773  m_startts = QDateTime(QDate(m_year,1,1),QTime(0,0,0), Qt::UTC);
774  m_endts = m_startts.addSecs(_length_in_minutes * 60);
775 
776  QString pn = _pathname;
777  if (!_pathname.startsWith("myth://"))
778  pn = determineURLType(_pathname);
779 
780  SetPathname(pn);
782 }
783 
787 ProgramInfo::ProgramInfo(const QString &_title, uint _chanid,
788  const QDateTime &_startts,
789  const QDateTime &_endts)
790 {
792 
793  MSqlQuery query(MSqlQuery::InitCon());
794  query.prepare(
795  "SELECT chanid, channum, callsign, name, outputfilters, commmethod "
796  "FROM channel "
797  "WHERE chanid=:CHANID");
798  query.bindValue(":CHANID", _chanid);
799  if (query.exec() && query.next())
800  {
801  m_chanstr = query.value(1).toString();
802  m_chansign = query.value(2).toString();
803  m_channame = query.value(3).toString();
804  m_chanplaybackfilters = query.value(4).toString();
806  query.value(5).toInt() == COMM_DETECT_COMMFREE);
807  }
808 
809  m_chanid = _chanid;
810  m_startts = _startts;
811  m_endts = _endts;
812 
813  m_title = _title;
814  if (m_title.isEmpty())
815  {
816  QString channelFormat =
817  gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
818 
819  m_title = QString("%1 - %2").arg(ChannelText(channelFormat))
821  }
822 
824  QString("%1 (%2)").arg(m_title).arg(QObject::tr("Manual Record"));
826 }
827 
832 {
833  clone(other);
834  return *this;
835 }
836 
838 void ProgramInfo::clone(const ProgramInfo &other,
839  bool ignore_non_serialized_data)
840 {
841  bool is_same =
842  (m_chanid && m_recstartts.isValid() && m_startts.isValid() &&
843  m_chanid == other.m_chanid && m_recstartts == other.m_recstartts &&
844  m_startts == other.m_startts);
845 
846  m_title = other.m_title;
847  m_sortTitle = other.m_sortTitle;
848  m_subtitle = other.m_subtitle;
851  m_season = other.m_season;
852  m_episode = other.m_episode;
855  m_category = other.m_category;
856  m_director = other.m_director;
857 
858  m_chanid = other.m_chanid;
859  m_chanstr = other.m_chanstr;
860  m_chansign = other.m_chansign;
861  m_channame = other.m_channame;
863 
864  m_recgroup = other.m_recgroup;
865  m_playgroup = other.m_playgroup;
866 
867  if (!ignore_non_serialized_data || !is_same ||
868  (GetBasename() != other.GetBasename()))
869  {
870  m_pathname = other.m_pathname;
871  }
872 
873  m_hostname = other.m_hostname;
875 
876  m_seriesid = other.m_seriesid;
877  m_programid = other.m_programid;
878  m_inetref = other.m_inetref;
879  m_catType = other.m_catType;
880 
882 
883  m_filesize = other.m_filesize;
884 
885  m_startts = other.m_startts;
886  m_endts = other.m_endts;
887  m_recstartts = other.m_recstartts;
888  m_recendts = other.m_recendts;
889 
890  m_stars = other.m_stars;
891 
892  m_year = other.m_year;
893  m_partnumber = other.m_partnumber;
894  m_parttotal = other.m_parttotal;
895 
898  m_lastInUseTime = MythDate::current().addSecs(-4 * 60 * 60);
899 
900  m_recstatus = other.m_recstatus;
901 
903  m_recordid = other.m_recordid;
904  m_parentid = other.m_parentid;
905 
906  m_rectype = other.m_rectype;
907  m_dupin = other.m_dupin;
908  m_dupmethod = other.m_dupmethod;
909 
910  m_recordedid = other.m_recordedid;
911  m_inputname = other.m_inputname;
913 
914  m_sourceid = other.m_sourceid;
915  m_inputid = other.m_inputid;
916 
917  m_findid = other.m_findid;
919  m_properties = other.m_properties;
920 
921  if (!ignore_non_serialized_data)
922  {
923  m_spread = other.m_spread;
924  m_startCol = other.m_startCol;
926 
929  }
930 }
931 
933 {
934  m_title.clear();
935  m_sortTitle.clear();
936  m_subtitle.clear();
937  m_sortSubtitle.clear();
938  m_description.clear();
939  m_season = 0;
940  m_episode = 0;
941  m_totalepisodes = 0;
942  m_syndicatedepisode.clear();
943  m_category.clear();
944  m_director.clear();
945 
946  m_chanid = 0;
947  m_chanstr.clear();
948  m_chansign.clear();
949  m_channame.clear();
950  m_chanplaybackfilters.clear();
951 
952  m_recgroup = "Default";
953  m_playgroup = "Default";
954 
955  m_pathname.clear();
956 
957  m_hostname.clear();
958  m_storagegroup = "Default";
959 
960  m_year = 0;
961  m_partnumber = 0;
962  m_parttotal = 0;
963 
964  m_seriesid.clear();
965  m_programid.clear();
966  m_inetref.clear();
968 
969  m_recpriority = 0;
970 
971  m_filesize = 0ULL;
972 
974  m_endts = m_startts;
977 
978  m_stars = 0.0F;
979 
980  m_originalAirDate = QDate();
982  m_lastInUseTime = m_startts.addSecs(-4 * 60 * 60);
983 
985 
986  m_recpriority2 = 0;
987  m_recordid = 0;
988  m_parentid = 0;
989 
993 
994  m_recordedid = 0;
995  m_inputname.clear();
996  m_bookmarkupdate = QDateTime();
997 
998  m_sourceid = 0;
999  m_inputid = 0;
1000 
1001  m_findid = 0;
1002 
1004  m_properties = 0;
1005 
1006  // everything below this line is not serialized
1007  m_spread = -1;
1008  m_startCol = -1;
1010 
1011  // Private
1012  m_inUseForWhat.clear();
1013  m_positionMapDBReplacement = nullptr;
1014 }
1015 
1020 bool qstringEqualOrDefault(const QString& a, const QString& b);
1021 bool qstringEqualOrDefault(const QString& a, const QString& b)
1022 {
1023  if (a == b)
1024  return true;
1025  if (a.isEmpty() and (b == "Default"))
1026  return true;
1027  if ((a == "Default") and b.isEmpty())
1028  return true;
1029  return false;
1030 }
1031 
1043 {
1044  if ((m_title != rhs.m_title) ||
1045  (m_subtitle != rhs.m_subtitle) ||
1046  (m_description != rhs.m_description) ||
1047  (m_season != rhs.m_season) ||
1048  (m_episode != rhs.m_episode) ||
1049  (m_totalepisodes != rhs.m_totalepisodes) ||
1051  (m_category != rhs.m_category)
1052 #if 0
1053  || (m_director != rhs.m_director)
1054 #endif
1055  )
1056  return false;
1057 
1058  if (m_recpriority != rhs.m_recpriority)
1059  return false;
1060 
1061  if ((m_chanid != rhs.m_chanid) ||
1062  (m_chanstr != rhs.m_chanstr) ||
1063  (m_chansign != rhs.m_chansign) ||
1064  (m_channame != rhs.m_channame) ||
1066  return false;
1067 
1070  return false;
1071 
1072  if (m_pathname != rhs.m_pathname)
1073  return false;
1074 
1075  if ((m_hostname != rhs.m_hostname) ||
1077  return false;
1078 
1079  if ((m_seriesid != rhs.m_seriesid) ||
1080  (m_programid != rhs.m_programid) ||
1081  (m_inetref != rhs.m_inetref) ||
1082  (m_catType != rhs.m_catType))
1083  return false;
1084 
1085  if (m_filesize != rhs.m_filesize)
1086  return false;
1087 
1088  if ((m_startts != rhs.m_startts) ||
1089  (m_endts != rhs.m_endts) ||
1090  (m_recstartts != rhs.m_recstartts) ||
1091  (m_recendts != rhs.m_recendts))
1092  return false;
1093 
1094  if ((m_stars != rhs.m_stars) ||
1097 #if 0
1098  || (m_lastInUseTime != rhs.m_lastInUseTime)
1099 #endif
1100  )
1101  return false;
1102 
1103  if (m_recpriority2 != rhs.m_recpriority2)
1104  return false;
1105 
1106  if ((m_recordid != rhs.m_recordid) ||
1107  (m_parentid != rhs.m_parentid))
1108  return false;
1109 
1110  if ((m_sourceid != rhs.m_sourceid) ||
1111  (m_inputid != rhs.m_inputid) ||
1112  (m_findid != rhs.m_findid))
1113  return false;
1114 
1115  if ((m_programflags != rhs.m_programflags) ||
1116  (m_properties != rhs.m_properties) ||
1117  (m_year != rhs.m_year) ||
1118  (m_partnumber != rhs.m_partnumber) ||
1119  (m_parttotal != rhs.m_parttotal))
1120  return false;
1121 
1122  if ((m_recstatus != rhs.m_recstatus) ||
1123  (m_rectype != rhs.m_rectype) ||
1124  (m_dupin != rhs.m_dupin) ||
1125  (m_dupmethod != rhs.m_dupmethod))
1126  return false;
1127 
1128  if ((m_recordedid != rhs.m_recordedid) ||
1129  (m_inputname != rhs.m_inputname) ||
1131  return false;
1132 
1133  return true;
1134 }
1135 
1140 {
1141  std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
1142 
1143  if (m_sortTitle.isEmpty() and not m_title.isEmpty())
1144  m_sortTitle = sh->doTitle(m_title);
1145  if (m_sortSubtitle.isEmpty() and not m_subtitle.isEmpty())
1146  m_sortSubtitle = sh->doTitle(m_subtitle);
1147 }
1148 
1149 void ProgramInfo::SetTitle(const QString &t, const QString &st)
1150 {
1151  m_title = t;
1152  m_sortTitle = st;
1153  ensureSortFields();
1154 }
1155 
1156 void ProgramInfo::SetSubtitle(const QString &st, const QString &sst)
1157 {
1158  m_subtitle = st;
1159  m_sortSubtitle = sst;
1160  ensureSortFields();
1161 }
1162 
1165  uint chanid, const QDateTime &recstartts)
1166 {
1167  return QString("%1_%2").arg(chanid).arg(recstartts.toString(Qt::ISODate));
1168 }
1169 
1173 bool ProgramInfo::ExtractKey(const QString &uniquekey,
1174  uint &chanid, QDateTime &recstartts)
1175 {
1176  QStringList keyParts = uniquekey.split('_');
1177  if (keyParts.size() != 2)
1178  return false;
1179  chanid = keyParts[0].toUInt();
1180  recstartts = MythDate::fromString(keyParts[1]);
1181  return chanid && recstartts.isValid();
1182 }
1183 
1185  const QString &pathname, uint &chanid, QDateTime &recstartts)
1186 {
1187  QString basename = pathname.section('/', -1);
1188  if (basename.isEmpty())
1189  return false;
1190 
1191  QStringList lr = basename.split("_");
1192  if (lr.size() == 2)
1193  {
1194  chanid = lr[0].toUInt();
1195  QStringList ts = lr[1].split(".");
1196  if (chanid && !ts.empty())
1197  {
1198  recstartts = MythDate::fromString(ts[0]);
1199  return recstartts.isValid();
1200  }
1201  }
1202 
1203  return false;
1204 }
1205 
1207  const QString &pathname, uint &chanid, QDateTime &recstartts)
1208 {
1209  QString basename = pathname.section('/', -1);
1210  if (basename.isEmpty())
1211  return false;
1212 
1213  MSqlQuery query(MSqlQuery::InitCon());
1214  query.prepare(
1215  "SELECT chanid, starttime "
1216  "FROM recorded "
1217  "WHERE basename = :BASENAME");
1218  query.bindValue(":BASENAME", basename);
1219  if (query.exec() && query.next())
1220  {
1221  chanid = query.value(0).toUInt();
1222  recstartts = MythDate::as_utc(query.value(1).toDateTime());
1223  return true;
1224  }
1225 
1226  return ExtractKeyFromPathname(pathname, chanid, recstartts);
1227 }
1228 
1229 bool ProgramInfo::QueryRecordedIdFromPathname(const QString &pathname,
1230  uint &recordedid)
1231 {
1232  QString basename = pathname.section('/', -1);
1233  if (basename.isEmpty())
1234  return false;
1235 
1236  MSqlQuery query(MSqlQuery::InitCon());
1237  query.prepare(
1238  "SELECT recordedid "
1239  "FROM recorded "
1240  "WHERE basename = :BASENAME");
1241  query.bindValue(":BASENAME", basename);
1242  if (query.exec() && query.next())
1243  {
1244  recordedid = query.value(0).toUInt();
1245  return true;
1246  }
1247 
1248  return false;
1249 }
1250 
1251 #define INT_TO_LIST(x) do { list << QString::number(x); } while (false)
1252 
1253 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
1254 #define DATETIME_TO_LIST(x) INT_TO_LIST((x).toTime_t())
1255 #else
1256 #define DATETIME_TO_LIST(x) do { \
1257  if ((x).isValid()) { \
1258  INT_TO_LIST((x).toSecsSinceEpoch()); \
1259  } else { \
1260  INT_TO_LIST(kInvalidDateTime); \
1261  } \
1262  } while (false)
1263 #endif
1264 
1265 #define LONGLONG_TO_LIST(x) do { list << QString::number(x); } while (false)
1266 
1267 #define STR_TO_LIST(x) do { list << (x); } while (false)
1268 #define DATE_TO_LIST(x) do { list << (x).toString(Qt::ISODate); } while (false)
1269 
1270 #define FLOAT_TO_LIST(x) do { list << QString("%1").arg(x); } while (false)
1271 
1278 void ProgramInfo::ToStringList(QStringList &list) const
1279 {
1280  STR_TO_LIST(m_title); // 0
1281  STR_TO_LIST(m_subtitle); // 1
1282  STR_TO_LIST(m_description); // 2
1283  INT_TO_LIST(m_season); // 3
1284  INT_TO_LIST(m_episode); // 4
1287  STR_TO_LIST(m_category); // 7
1288  INT_TO_LIST(m_chanid); // 8
1289  STR_TO_LIST(m_chanstr); // 9
1290  STR_TO_LIST(m_chansign); // 10
1291  STR_TO_LIST(m_channame); // 11
1292  STR_TO_LIST(m_pathname); // 12
1293  INT_TO_LIST(m_filesize); // 13
1294 
1295  DATETIME_TO_LIST(m_startts); // 14
1296  DATETIME_TO_LIST(m_endts); // 15
1297  INT_TO_LIST(m_findid); // 16
1298  STR_TO_LIST(m_hostname); // 17
1299  INT_TO_LIST(m_sourceid); // 18
1300  INT_TO_LIST(m_inputid); // 19 (m_formerly cardid)
1301  INT_TO_LIST(m_inputid); // 20
1302  INT_TO_LIST(m_recpriority); // 21
1303  INT_TO_LIST(m_recstatus); // 22
1304  INT_TO_LIST(m_recordid); // 23
1305 
1306  INT_TO_LIST(m_rectype); // 24
1307  INT_TO_LIST(m_dupin); // 25
1308  INT_TO_LIST(m_dupmethod); // 26
1311  INT_TO_LIST(m_programflags); // 29
1312  STR_TO_LIST(!m_recgroup.isEmpty() ? m_recgroup : "Default"); // 30
1314  STR_TO_LIST(m_seriesid); // 32
1315  STR_TO_LIST(m_programid); // 33
1316  STR_TO_LIST(m_inetref); // 34
1317 
1319  FLOAT_TO_LIST(m_stars); // 36
1321  STR_TO_LIST((!m_playgroup.isEmpty()) ? m_playgroup : "Default"); // 38
1322  INT_TO_LIST(m_recpriority2); // 39
1323  INT_TO_LIST(m_parentid); // 40
1324  STR_TO_LIST((!m_storagegroup.isEmpty()) ? m_storagegroup : "Default"); // 41
1327  INT_TO_LIST(GetSubtitleType()); // 44
1328 
1329  INT_TO_LIST(m_year); // 45
1330  INT_TO_LIST(m_partnumber); // 46
1331  INT_TO_LIST(m_parttotal); // 47
1332  INT_TO_LIST(m_catType); // 48
1333 
1334  INT_TO_LIST(m_recordedid); // 49
1335  STR_TO_LIST(m_inputname); // 50
1337 /* do not forget to update the NUMPROGRAMLINES defines! */
1338 }
1339 
1340 // QStringList::const_iterator it = list.begin()+offset;
1341 
1342 #define NEXT_STR() do { if (it == listend) \
1343  { \
1344  LOG(VB_GENERAL, LOG_ERR, listerror); \
1345  clear(); \
1346  return false; \
1347  } \
1348  ts = *it++; } while (false)
1349 
1350 #define INT_FROM_LIST(x) do { NEXT_STR(); (x) = ts.toLongLong(); } while (false)
1351 #define ENUM_FROM_LIST(x, y) do { NEXT_STR(); (x) = ((y)ts.toInt()); } while (false)
1352 
1353 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
1354 #define DATETIME_FROM_LIST(x) \
1355  do { NEXT_STR(); \
1356  x = (ts.toUInt() == kInvalidDateTime ? \
1357  QDateTime() : MythDate::fromTime_t(ts.toUInt())); \
1358  } while (false)
1359 #else
1360 #define DATETIME_FROM_LIST(x) \
1361  do { NEXT_STR(); \
1362  if (ts.isEmpty() or (ts.toUInt() == kInvalidDateTime)) { \
1363  (x) = QDateTime(); \
1364  } else { \
1365  (x) = MythDate::fromSecsSinceEpoch(ts.toLongLong()); \
1366  } \
1367  } while (false)
1368 #endif
1369 #define DATE_FROM_LIST(x) \
1370  do { NEXT_STR(); (x) = ((ts.isEmpty()) || (ts == "0000-00-00")) ? \
1371  QDate() : QDate::fromString(ts, Qt::ISODate); \
1372  } while (false)
1373 
1374 #define STR_FROM_LIST(x) do { NEXT_STR(); (x) = ts; } while (false)
1375 
1376 #define FLOAT_FROM_LIST(x) do { NEXT_STR(); (x) = ts.toFloat(); } while (false)
1377 
1389 bool ProgramInfo::FromStringList(QStringList::const_iterator &it,
1390  const QStringList::const_iterator& listend)
1391 {
1392  QString listerror = LOC + "FromStringList, not enough items in list.";
1393  QString ts;
1394 
1395  uint origChanid = m_chanid;
1396  QDateTime origRecstartts = m_recstartts;
1397 
1398  STR_FROM_LIST(m_title); // 0
1399  STR_FROM_LIST(m_subtitle); // 1
1401  INT_FROM_LIST(m_season); // 3
1402  INT_FROM_LIST(m_episode); // 4
1405  STR_FROM_LIST(m_category); // 7
1406  INT_FROM_LIST(m_chanid); // 8
1407  STR_FROM_LIST(m_chanstr); // 9
1408  STR_FROM_LIST(m_chansign); // 10
1409  STR_FROM_LIST(m_channame); // 11
1410  STR_FROM_LIST(m_pathname); // 12
1411  INT_FROM_LIST(m_filesize); // 13
1412 
1414  DATETIME_FROM_LIST(m_endts); // 15
1415  INT_FROM_LIST(m_findid); // 16
1416  STR_FROM_LIST(m_hostname); // 17
1417  INT_FROM_LIST(m_sourceid); // 18
1418  NEXT_STR(); // 19 (formerly cardid)
1419  INT_FROM_LIST(m_inputid); // 20
1422  INT_FROM_LIST(m_recordid); // 23
1423 
1430  STR_FROM_LIST(m_recgroup); // 30
1432  STR_FROM_LIST(m_seriesid); // 32
1433  STR_FROM_LIST(m_programid); // 33
1434  STR_FROM_LIST(m_inetref); // 34
1435 
1437  FLOAT_FROM_LIST(m_stars); // 36
1439  STR_FROM_LIST(m_playgroup); // 38
1441  INT_FROM_LIST(m_parentid); // 40
1443  uint audioproperties, videoproperties, subtitleType;
1444  INT_FROM_LIST(audioproperties); // 42
1445  INT_FROM_LIST(videoproperties); // 43
1446  INT_FROM_LIST(subtitleType); // 44
1447  m_properties = ((subtitleType << kSubtitlePropertyOffset) |
1448  (videoproperties << kVideoPropertyOffset) |
1449  (audioproperties << kAudioPropertyOffset));
1450 
1451  INT_FROM_LIST(m_year); // 45
1452  INT_FROM_LIST(m_partnumber); // 46
1453  INT_FROM_LIST(m_parttotal); // 47
1455 
1456  INT_FROM_LIST(m_recordedid); // 49
1457  STR_FROM_LIST(m_inputname); // 50
1459 
1460  if (!origChanid || !origRecstartts.isValid() ||
1461  (origChanid != m_chanid) || (origRecstartts != m_recstartts))
1462  {
1464  m_spread = -1;
1465  m_startCol = -1;
1466  m_inUseForWhat = QString();
1467  m_positionMapDBReplacement = nullptr;
1468  }
1469 
1470  ensureSortFields();
1471 
1472  return true;
1473 }
1474 
1479  bool showrerecord,
1480  uint star_range) const
1481 {
1482  QLocale locale = gCoreContext->GetQLocale();
1483  // NOTE: Format changes and relevant additions made here should be
1484  // reflected in RecordingRule
1485  QString channelFormat =
1486  gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
1487  QString longChannelFormat =
1488  gCoreContext->GetSetting("LongChannelFormat", "<num> <name>");
1489 
1490  QDateTime timeNow = MythDate::current();
1491 
1492  int hours, minutes, seconds;
1493 
1494  progMap["title"] = m_title;
1495  progMap["subtitle"] = m_subtitle;
1496  progMap["sorttitle"] = m_sortTitle;
1497  progMap["sortsubtitle"] = m_sortSubtitle;
1498 
1499  QString tempSubTitle = m_title;
1500  QString tempSortSubtitle = m_sortTitle;
1501  if (!m_subtitle.trimmed().isEmpty())
1502  {
1503  tempSubTitle = QString("%1 - \"%2\"")
1504  .arg(tempSubTitle).arg(m_subtitle);
1505  tempSortSubtitle = QString("%1 - \"%2\"")
1506  .arg(tempSortSubtitle).arg(m_sortSubtitle);
1507  }
1508 
1509  progMap["titlesubtitle"] = tempSubTitle;
1510  progMap["sorttitlesubtitle"] = tempSortSubtitle;
1511 
1512  progMap["description"] = progMap["description0"] = m_description;
1513 
1514  if (m_season > 0 || m_episode > 0)
1515  {
1516  progMap["season"] = format_season_and_episode(m_season, 1);
1517  progMap["episode"] = format_season_and_episode(m_episode, 1);
1518  progMap["totalepisodes"] = format_season_and_episode(m_totalepisodes, 1);
1519  progMap["s00e00"] = QString("s%1e%2")
1522  progMap["00x00"] = QString("%1x%2")
1525  }
1526  else
1527  {
1528  progMap["season"] = progMap["episode"] = "";
1529  progMap["totalepisodes"] = "";
1530  progMap["s00e00"] = progMap["00x00"] = "";
1531  }
1532  progMap["syndicatedepisode"] = m_syndicatedepisode;
1533 
1534  progMap["category"] = m_category;
1535  progMap["director"] = m_director;
1536 
1537  progMap["callsign"] = m_chansign;
1538  progMap["commfree"] = (m_programflags & FL_CHANCOMMFREE) ? 1 : 0;
1539  progMap["outputfilters"] = m_chanplaybackfilters;
1540  if (IsVideo())
1541  {
1542  progMap["starttime"] = "";
1543  progMap["startdate"] = "";
1544  progMap["endtime"] = "";
1545  progMap["enddate"] = "";
1546  progMap["recstarttime"] = "";
1547  progMap["recstartdate"] = "";
1548  progMap["recendtime"] = "";
1549  progMap["recenddate"] = "";
1550 
1551  if (m_startts.date().year() == 1895)
1552  {
1553  progMap["startdate"] = "";
1554  progMap["recstartdate"] = "";
1555  }
1556  else
1557  {
1558  progMap["startdate"] = m_startts.toLocalTime().toString("yyyy");
1559  progMap["recstartdate"] = m_startts.toLocalTime().toString("yyyy");
1560  }
1561  }
1562  else // if (IsRecording())
1563  {
1564  using namespace MythDate;
1565  progMap["starttime"] = MythDate::toString(m_startts, kTime);
1566  progMap["startdate"] =
1568  progMap["shortstartdate"] = MythDate::toString(m_startts, kDateShort);
1569  progMap["endtime"] = MythDate::toString(m_endts, kTime);
1570  progMap["enddate"] = MythDate::toString(m_endts, kDateFull | kSimplify);
1571  progMap["shortenddate"] = MythDate::toString(m_endts, kDateShort);
1572  progMap["recstarttime"] = MythDate::toString(m_recstartts, kTime);
1573  progMap["recstartdate"] = MythDate::toString(m_recstartts, kDateShort);
1574  progMap["recendtime"] = MythDate::toString(m_recendts, kTime);
1575  progMap["recenddate"] = MythDate::toString(m_recendts, kDateShort);
1576 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
1577  progMap["startts"] = QString::number(m_startts.toTime_t());
1578  progMap["endts"] = QString::number(m_endts.toTime_t());
1579 #else
1580  progMap["startts"] = QString::number(m_startts.toSecsSinceEpoch());
1581  progMap["endts"] = QString::number(m_endts.toSecsSinceEpoch());
1582 #endif
1583  if (timeNow.toLocalTime().date().year() !=
1584  m_startts.toLocalTime().date().year())
1585  progMap["startyear"] = m_startts.toLocalTime().toString("yyyy");
1586  if (timeNow.toLocalTime().date().year() !=
1587  m_endts.toLocalTime().date().year())
1588  progMap["endyear"] = m_endts.toLocalTime().toString("yyyy");
1589  }
1590 
1591  using namespace MythDate;
1592  progMap["timedate"] =
1595 
1596  progMap["shorttimedate"] =
1599 
1600  progMap["starttimedate"] =
1602 
1603  progMap["shortstarttimedate"] =
1605 
1606  progMap["lastmodifiedtime"] = MythDate::toString(m_lastmodified, kTime);
1607  progMap["lastmodifieddate"] =
1609  progMap["lastmodified"] =
1611 
1612  if (m_recordedid)
1613  progMap["recordedid"] = m_recordedid;
1614 
1615  progMap["channum"] = m_chanstr;
1616  progMap["chanid"] = m_chanid;
1617  progMap["channame"] = m_channame;
1618  progMap["channel"] = ChannelText(channelFormat);
1619  progMap["longchannel"] = ChannelText(longChannelFormat);
1620 
1621  QString tmpSize = locale.toString(m_filesize * (1.0 / (1024.0 * 1024.0 * 1024.0)), 'f', 2);
1622  progMap["filesize_str"] = QObject::tr("%1 GB", "GigaBytes").arg(tmpSize);
1623 
1624  progMap["filesize"] = locale.toString((quint64)m_filesize);
1625 
1626  seconds = m_recstartts.secsTo(m_recendts);
1627  minutes = seconds / 60;
1628 
1629  QString min_str = QObject::tr("%n minute(s)","",minutes);
1630 
1631  progMap["lenmins"] = min_str;
1632  hours = minutes / 60;
1633  minutes = minutes % 60;
1634 
1635  progMap["lentime"] = min_str;
1636  if (hours > 0 && minutes > 0)
1637  {
1638  min_str = QObject::tr("%n minute(s)","",minutes);
1639  progMap["lentime"] = QString("%1 %2")
1640  .arg(QObject::tr("%n hour(s)","", hours))
1641  .arg(min_str);
1642  }
1643  else if (hours > 0)
1644  {
1645  progMap["lentime"] = QObject::tr("%n hour(s)","", hours);
1646  }
1647 
1648  progMap["rectypechar"] = toQChar(GetRecordingRuleType());
1649  progMap["rectype"] = ::toString(GetRecordingRuleType());
1650  QString tmp_rec = progMap["rectype"];
1652  {
1653  if (((m_recendts > timeNow) && (m_recstatus <= RecStatus::WillRecord)) ||
1655  {
1656  tmp_rec += QString().sprintf(" %+d", m_recpriority);
1657  if (m_recpriority2)
1658  tmp_rec += QString().sprintf("/%+d", m_recpriority2);
1659  tmp_rec += " ";
1660  }
1661  else
1662  {
1663  tmp_rec += " -- ";
1664  }
1665  if (showrerecord && (GetRecordingStatus() == RecStatus::Recorded) &&
1666  !IsDuplicate())
1667  {
1668  tmp_rec += QObject::tr("Re-Record");
1669  }
1670  else
1671  {
1673  }
1674  }
1675  progMap["rectypestatus"] = tmp_rec;
1676 
1677  progMap["card"] = RecStatus::toString(GetRecordingStatus(), m_inputid);
1678  progMap["input"] = RecStatus::toString(GetRecordingStatus(),
1679  GetShortInputName());
1680  progMap["inputname"] = m_inputname;
1681  // Don't add bookmarkupdate to progMap, for now.
1682 
1683  progMap["recpriority"] = m_recpriority;
1684  progMap["recpriority2"] = m_recpriority2;
1685  progMap["recordinggroup"] = (m_recgroup == "Default")
1686  ? QObject::tr("Default") : m_recgroup;
1687  progMap["playgroup"] = m_playgroup;
1688 
1689  if (m_storagegroup == "Default")
1690  progMap["storagegroup"] = QObject::tr("Default");
1691  else if (StorageGroup::kSpecialGroups.contains(m_storagegroup))
1692  progMap["storagegroup"] = QObject::tr(m_storagegroup.toUtf8().constData());
1693  else
1694  progMap["storagegroup"] = m_storagegroup;
1695 
1696  progMap["programflags"] = m_programflags;
1697 
1698  progMap["audioproperties"] = GetAudioProperties();
1699  progMap["videoproperties"] = GetVideoProperties();
1700  progMap["subtitleType"] = GetSubtitleType();
1701 
1702  progMap["recstatus"] = RecStatus::toString(GetRecordingStatus(),
1704  progMap["recstatuslong"] = RecStatus::toDescription(GetRecordingStatus(),
1707 
1708  if (IsRepeat())
1709  {
1710  progMap["repeat"] = QString("(%1) ").arg(QObject::tr("Repeat"));
1711  progMap["longrepeat"] = progMap["repeat"];
1712  if (m_originalAirDate.isValid())
1713  {
1714  progMap["longrepeat"] = QString("(%1 %2) ")
1715  .arg(QObject::tr("Repeat"))
1716  .arg(MythDate::toString(
1719  }
1720  }
1721  else
1722  {
1723  progMap["repeat"] = "";
1724  progMap["longrepeat"] = "";
1725  }
1726 
1727  progMap["seriesid"] = m_seriesid;
1728  progMap["programid"] = m_programid;
1729  progMap["inetref"] = m_inetref;
1730  progMap["catType"] = myth_category_type_to_string(m_catType);
1731 
1732  progMap["year"] = m_year > 1895 ? QString::number(m_year) : "";
1733 
1734  progMap["partnumber"] = m_partnumber ? QString::number(m_partnumber) : "";
1735  progMap["parttotal"] = m_parttotal ? QString::number(m_parttotal) : "";
1736 
1737  QString star_str = (m_stars != 0.0F) ?
1738  QObject::tr("%n star(s)", "", GetStars(star_range)) : "";
1739  progMap["stars"] = star_str;
1740  progMap["numstars"] = QString::number(GetStars(star_range));
1741 
1742  if (m_stars != 0.0F && m_year)
1743  progMap["yearstars"] = QString("(%1, %2)").arg(m_year).arg(star_str);
1744  else if (m_stars != 0.0F)
1745  progMap["yearstars"] = QString("(%1)").arg(star_str);
1746  else if (m_year)
1747  progMap["yearstars"] = QString("(%1)").arg(m_year);
1748  else
1749  progMap["yearstars"] = "";
1750 
1751  if (!m_originalAirDate.isValid() ||
1752  (!m_programid.isEmpty() && m_programid.startsWith("MV")))
1753  {
1754  progMap["originalairdate"] = "";
1755  progMap["shortoriginalairdate"] = "";
1756  }
1757  else
1758  {
1759  progMap["originalairdate"] = MythDate::toString(
1761  progMap["shortoriginalairdate"] = MythDate::toString(
1763  }
1764 
1765  // 'mediatype' for a statetype, so untranslated
1766  // 'mediatypestring' for textarea, so translated
1767  // TODO Move to a dedicated ToState() method?
1768  QString mediaType;
1769  QString mediaTypeString;
1770  switch (GetProgramInfoType())
1771  {
1773  mediaType = "video";
1774  mediaTypeString = QObject::tr("Video");
1775  break;
1777  mediaType = "dvd";
1778  mediaTypeString = QObject::tr("DVD");
1779  break;
1781  mediaType = "httpstream";
1782  mediaTypeString = QObject::tr("HTTP Streaming");
1783  break;
1785  mediaType = "rtspstream";
1786  mediaTypeString = QObject::tr("RTSP Streaming");
1787  break;
1789  mediaType = "bluraydisc";
1790  mediaTypeString = QObject::tr("Blu-ray Disc");
1791  break;
1792  case kProgramInfoTypeRecording : // Fall through
1793  default :
1794  mediaType = "recording";
1795  mediaTypeString = QObject::tr("Recording",
1796  "Recorded file, object not action");
1797  }
1798  progMap["mediatype"] = mediaType;
1799  progMap["mediatypestring"] = mediaTypeString;
1800 }
1801 
1804 {
1805  int64_t recsecs = m_recstartts.secsTo(m_endts);
1806  int64_t duration = m_startts.secsTo(m_endts);
1807  return (uint) ((recsecs>0) ? recsecs : max(duration,int64_t(0)));
1808 }
1809 
1812 {
1814 }
1815 
1818 {
1819  uint64_t last_frame = 0;
1820  frm_pos_map_t posMap;
1822  if (posMap.empty())
1823  {
1825  if (posMap.empty())
1827  }
1828  if (!posMap.empty())
1829  {
1830  frm_pos_map_t::const_iterator it = posMap.constEnd();
1831  --it;
1832  last_frame = it.key();
1833  }
1834  return last_frame;
1835 }
1836 
1837 bool ProgramInfo::IsGeneric(void) const
1838 {
1839  return
1840  (m_programid.isEmpty() && m_subtitle.isEmpty() &&
1841  m_description.isEmpty()) ||
1842  (!m_programid.isEmpty() && m_programid.endsWith("0000")
1843  && m_catType == kCategorySeries);
1844 }
1845 
1846 QString ProgramInfo::toString(const Verbosity v, const QString& sep, const QString& grp)
1847  const
1848 {
1849  QString str;
1850  switch (v)
1851  {
1852  case kLongDescription:
1853  str = LOC + "channame(" + m_channame + ") startts(" +
1854  m_startts.toString() + ") endts(" + m_endts.toString() + ")\n";
1855  str += " recstartts(" + m_recstartts.toString() +
1856  ") recendts(" + m_recendts.toString() + ")\n";
1857  str += " title(" + m_title + ")";
1858  break;
1859  case kTitleSubtitle:
1860  str = m_title.contains(' ') ?
1861  QString("%1%2%3").arg(grp).arg(m_title).arg(grp) : m_title;
1862  if (!m_subtitle.isEmpty())
1863  {
1864  str += m_subtitle.contains(' ') ?
1865  QString("%1%2%3%4").arg(sep)
1866  .arg(grp).arg(m_subtitle).arg(grp) :
1867  QString("%1%2").arg(sep).arg(m_subtitle);
1868  }
1869  break;
1870  case kRecordingKey:
1871  str = QString("%1 at %2")
1873  break;
1874  case kSchedulingKey:
1875  str = QString("%1 @ %2")
1877  break;
1878  }
1879 
1880  return str;
1881 }
1882 
1884 {
1886  if (test.GetChanID())
1887  {
1888  clone(test, true);
1889  return true;
1890  }
1891  return false;
1892 }
1893 
1898  const uint _chanid, const QDateTime &_recstartts)
1899 {
1900  if (!_chanid || !_recstartts.isValid())
1901  {
1903  return false;
1904  }
1905 
1906  MSqlQuery query(MSqlQuery::InitCon());
1907  query.prepare(
1909  "WHERE r.chanid = :CHANID AND "
1910  " r.starttime = :RECSTARTTS");
1911  query.bindValue(":CHANID", _chanid);
1912  query.bindValue(":RECSTARTTS", _recstartts);
1913 
1914  if (!query.exec())
1915  {
1916  MythDB::DBError("LoadProgramFromRecorded", query);
1918  return false;
1919  }
1920 
1921  if (!query.next())
1922  {
1924  return false;
1925  }
1926 
1927  bool is_reload = (m_chanid == _chanid) && (m_recstartts == _recstartts);
1928  if (!is_reload)
1929  {
1930  // These items are not initialized below so they need to be cleared
1931  // if we're loading in a different program into this ProgramInfo
1933  m_lastInUseTime = MythDate::current().addSecs(-4 * 60 * 60);
1935  m_recpriority2 = 0;
1936  m_parentid = 0;
1937  m_sourceid = 0;
1938  m_inputid = 0;
1939 
1940  // everything below this line (in context) is not serialized
1941  m_spread = m_startCol = -1;
1943  m_inUseForWhat.clear();
1944  m_positionMapDBReplacement = nullptr;
1945  }
1946 
1947  m_title = query.value(0).toString();
1948  m_subtitle = query.value(1).toString();
1949  m_description = query.value(2).toString();
1950  m_season = query.value(3).toUInt();
1951  if (m_season == 0)
1952  m_season = query.value(51).toUInt();
1953  m_episode = query.value(4).toUInt();
1954  if (m_episode == 0)
1955  m_episode = query.value(52).toUInt();
1956  m_totalepisodes = query.value(53).toUInt();
1957  m_syndicatedepisode = query.value(48).toString();
1958  m_category = query.value(5).toString();
1959 
1960  m_chanid = _chanid;
1961  m_chanstr = QString("#%1").arg(m_chanid);
1964  m_chanplaybackfilters.clear();
1965  if (!query.value(7).toString().isEmpty())
1966  {
1967  m_chanstr = query.value(7).toString();
1968  m_chansign = query.value(8).toString();
1969  m_channame = query.value(9).toString();
1970  m_chanplaybackfilters = query.value(10).toString();
1971  }
1972 
1973  m_recgroup = query.value(11).toString();
1974  m_playgroup = query.value(12).toString();
1975 
1976  // We don't want to update the pathname if the basename is
1977  // the same as we may have already expanded pathname from
1978  // a simple basename to a localized path.
1979  QString new_basename = query.value(14).toString();
1980  if ((GetBasename() != new_basename) || !is_reload)
1981  {
1982  if (is_reload)
1983  {
1984  LOG(VB_FILE, LOG_INFO, LOC +
1985  QString("Updated pathname '%1':'%2' -> '%3'")
1986  .arg(m_pathname).arg(GetBasename()).arg(new_basename));
1987  }
1988  SetPathname(new_basename);
1989  }
1990 
1991  m_hostname = query.value(15).toString();
1992  m_storagegroup = query.value(13).toString();
1993 
1994  m_seriesid = query.value(17).toString();
1995  m_programid = query.value(18).toString();
1996  m_inetref = query.value(19).toString();
1997  m_catType = string_to_myth_category_type(query.value(54).toString());
1998 
1999  m_recpriority = query.value(16).toInt();
2000 
2001  m_filesize = query.value(20).toULongLong();
2002 
2003  m_startts = MythDate::as_utc(query.value(21).toDateTime());
2004  m_endts = MythDate::as_utc(query.value(22).toDateTime());
2005  m_recstartts = MythDate::as_utc(query.value(24).toDateTime());
2006  m_recendts = MythDate::as_utc(query.value(25).toDateTime());
2007 
2008  m_stars = clamp((float)query.value(23).toDouble(), 0.0F, 1.0F);
2009 
2010  m_year = query.value(26).toUInt();
2011  m_partnumber = query.value(49).toUInt();
2012  m_parttotal = query.value(50).toUInt();
2013 
2014  m_originalAirDate = query.value(27).toDate();
2015  m_lastmodified = MythDate::as_utc(query.value(28).toDateTime());
2016  //m_lastInUseTime;
2017 
2019 
2020  //m_recpriority2;
2021 
2022  m_recordid = query.value(29).toUInt();
2023  //m_parentid;
2024 
2025  //m_sourcid;
2026  //m_inputid;
2027  //m_cardid;
2028  m_findid = query.value(45).toUInt();
2029 
2030  //m_rectype;
2031  m_dupin = RecordingDupInType(query.value(46).toInt());
2032  m_dupmethod = RecordingDupMethodType(query.value(47).toInt());
2033 
2034  m_recordedid = query.value(55).toUInt();
2035  m_inputname = query.value(56).toString();
2036  m_bookmarkupdate = MythDate::as_utc(query.value(57).toDateTime());
2037 
2038  // ancillary data -- begin
2041  query.value(30).toInt() == COMM_DETECT_COMMFREE);
2043  query.value(31).toInt() == COMM_FLAG_DONE);
2045  query.value(31).toInt() == COMM_FLAG_PROCESSING);
2046  set_flag(m_programflags, FL_REPEAT, query.value(32).toBool());
2048  query.value(34).toInt() == TRANSCODING_COMPLETE);
2049  set_flag(m_programflags, FL_DELETEPENDING, query.value(35).toBool());
2050  set_flag(m_programflags, FL_PRESERVED, query.value(36).toBool());
2051  set_flag(m_programflags, FL_CUTLIST, query.value(37).toBool());
2052  set_flag(m_programflags, FL_AUTOEXP, query.value(38).toBool());
2053  set_flag(m_programflags, FL_REALLYEDITING, query.value(39).toBool());
2054  set_flag(m_programflags, FL_BOOKMARK, query.value(40).toBool());
2055  set_flag(m_programflags, FL_WATCHED, query.value(41).toBool());
2057  ((m_programflags & FL_REALLYEDITING) != 0U) ||
2058  ((m_programflags & FL_COMMPROCESSING) != 0U));
2059 
2060  m_properties = ((query.value(44).toUInt() << kSubtitlePropertyOffset) |
2061  (query.value(43).toUInt() << kVideoPropertyOffset) |
2062  (query.value(42).toUInt() << kAudioPropertyOffset));
2063  // ancillary data -- end
2064 
2065  if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
2066  m_originalAirDate = QDate();
2067 
2068  // Extra stuff which is not serialized and may get lost.
2069  // m_spread
2070  // m_startCol
2071  // m_availableStatus
2072  // m_inUseForWhat
2073  // m_postitionMapDBReplacement
2074 
2075  return true;
2076 }
2077 
2083 {
2084  return (m_title == other.m_title &&
2085  m_chanid == other.m_chanid &&
2086  m_startts == other.m_startts);
2087 }
2088 
2094 {
2096  return m_recordid == other.m_recordid;
2097 
2098  if (m_findid && m_findid == other.m_findid &&
2099  (m_recordid == other.m_recordid || m_recordid == other.m_parentid))
2100  return true;
2101 
2102  if (m_dupmethod & kDupCheckNone)
2103  return false;
2104 
2105  if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2106  return false;
2107 
2108  if (m_catType == kCategorySeries)
2109  {
2110  if (m_programid.endsWith("0000"))
2111  return false;
2112  }
2113 
2114  if (!m_programid.isEmpty() && !other.m_programid.isEmpty())
2115  {
2116  if (s_usingProgIDAuth)
2117  {
2118  int index = m_programid.indexOf('/');
2119  int oindex = other.m_programid.indexOf('/');
2120  if (index == oindex && (index < 0 ||
2121  m_programid.leftRef(index) == other.m_programid.leftRef(oindex)))
2122  return m_programid == other.m_programid;
2123  }
2124  else
2125  {
2126  return m_programid == other.m_programid;
2127  }
2128  }
2129 
2130  if ((m_dupmethod & kDupCheckSub) &&
2131  ((m_subtitle.isEmpty()) ||
2132  (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2133  return false;
2134 
2135  if ((m_dupmethod & kDupCheckDesc) &&
2136  ((m_description.isEmpty()) ||
2137  (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2138  return false;
2139 
2141  ((m_subtitle.isEmpty() &&
2142  ((!other.m_subtitle.isEmpty() &&
2143  m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2144  (other.m_subtitle.isEmpty() &&
2145  m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2146  (!m_subtitle.isEmpty() &&
2147  ((other.m_subtitle.isEmpty() &&
2148  m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2149  (!other.m_subtitle.isEmpty() &&
2150  m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2151  return false;
2152 
2153  return true;
2154 }
2155 
2162 bool ProgramInfo::IsSameProgram(const ProgramInfo& other) const
2163 {
2164  if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2165  return false;
2166 
2167  if (!m_programid.isEmpty() && !other.m_programid.isEmpty())
2168  {
2169  if (m_catType == kCategorySeries)
2170  {
2171  if (m_programid.endsWith("0000"))
2172  return false;
2173  }
2174 
2175  if (s_usingProgIDAuth)
2176  {
2177  int index = m_programid.indexOf('/');
2178  int oindex = other.m_programid.indexOf('/');
2179  if (index == oindex && (index < 0 ||
2180  m_programid.leftRef(index) == other.m_programid.leftRef(oindex)))
2181  return m_programid == other.m_programid;
2182  }
2183  else
2184  {
2185  return m_programid == other.m_programid;
2186  }
2187  }
2188 
2189  if ((m_dupmethod & kDupCheckSub) &&
2190  ((m_subtitle.isEmpty()) ||
2191  (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2192  return false;
2193 
2194  if ((m_dupmethod & kDupCheckDesc) &&
2195  ((m_description.isEmpty()) ||
2196  (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2197  return false;
2198 
2200  ((m_subtitle.isEmpty() &&
2201  ((!other.m_subtitle.isEmpty() &&
2202  m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2203  (other.m_subtitle.isEmpty() &&
2204  m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2205  (!m_subtitle.isEmpty() &&
2206  ((other.m_subtitle.isEmpty() &&
2207  m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2208  (!other.m_subtitle.isEmpty() &&
2209  m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2210  return false;
2211 
2212  return true;
2213 }
2214 
2221 {
2222  if (m_startts != other.m_startts)
2223  return false;
2224  if (IsSameChannel(other))
2225  return true;
2226  if (!IsSameProgram(other))
2227  return false;
2228  return true;
2229 }
2230 
2237 {
2238  if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2239  return false;
2240  return m_startts == other.m_startts &&
2241  IsSameChannel(other);
2242 }
2243 
2251 {
2252  if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2253  return false;
2254  return IsSameChannel(other) &&
2255  m_startts < other.m_endts &&
2256  m_endts > other.m_startts;
2257 }
2258 
2265 bool ProgramInfo::IsSameChannel(const ProgramInfo& other) const
2266 {
2267  return m_chanid == other.m_chanid ||
2268  (!m_chansign.isEmpty() &&
2269  m_chansign.compare(other.m_chansign, Qt::CaseInsensitive) == 0);
2270 }
2271 
2273 {
2274  QMap<QString, int> authMap;
2275  QString tables[] = { "program", "recorded", "oldrecorded", "" };
2276  MSqlQuery query(MSqlQuery::InitCon());
2277 
2278  int tableIndex = 0;
2279  QString table = tables[tableIndex];
2280  while (!table.isEmpty())
2281  {
2282  query.prepare(QString(
2283  "SELECT DISTINCT LEFT(programid, LOCATE('/', programid)) "
2284  "FROM %1 WHERE programid <> ''").arg(table));
2285  if (!query.exec())
2286  MythDB::DBError("CheckProgramIDAuthorities", query);
2287  else
2288  {
2289  while (query.next())
2290  authMap[query.value(0).toString()] = 1;
2291  }
2292  ++tableIndex;
2293  table = tables[tableIndex];
2294  }
2295 
2296  int numAuths = authMap.count();
2297  LOG(VB_GENERAL, LOG_INFO,
2298  QString("Found %1 distinct programid authorities").arg(numAuths));
2299 
2300  s_usingProgIDAuth = (numAuths > 1);
2301 }
2302 
2307 QString ProgramInfo::CreateRecordBasename(const QString &ext) const
2308 {
2310 
2311  QString retval = QString("%1_%2.%3").arg(m_chanid)
2312  .arg(starts).arg(ext);
2313 
2314  return retval;
2315 }
2316 
2318  uint chanid, const QString &pathname, bool use_remote)
2319 {
2320  QString fn_lower = pathname.toLower();
2322  if (chanid)
2324  else if (fn_lower.startsWith("http:"))
2326  else if (fn_lower.startsWith("rtsp:"))
2328  else
2329  {
2330  fn_lower = determineURLType(pathname);
2331 
2332  if (fn_lower.startsWith("dvd:"))
2333  {
2335  }
2336  else if (fn_lower.startsWith("bd:"))
2337  {
2339  }
2340  else if (use_remote && fn_lower.startsWith("myth://"))
2341  {
2342  QString tmpFileDVD = pathname + "/VIDEO_TS";
2343  QString tmpFileBD = pathname + "/BDMV";
2344  if (RemoteFile::Exists(tmpFileDVD))
2346  else if (RemoteFile::Exists(tmpFileBD))
2348  }
2349  }
2350  return pit;
2351 }
2352 
2353 void ProgramInfo::SetPathname(const QString &pn) const
2354 {
2355  m_pathname = pn;
2356 
2358  const_cast<ProgramInfo*>(this)->SetProgramInfoType(pit);
2359 }
2360 
2362 {
2364 }
2365 
2367  AvailableStatusType status, const QString &where)
2368 {
2369  if (status != m_availableStatus)
2370  {
2371  LOG(VB_GUI, LOG_INFO,
2372  toString(kTitleSubtitle) + QString(": %1 -> %2 in %3")
2374  .arg(::toString(status))
2375  .arg(where));
2376  }
2377  m_availableStatus = status;
2378 }
2379 
2383 bool ProgramInfo::SaveBasename(const QString &basename)
2384 {
2385  MSqlQuery query(MSqlQuery::InitCon());
2386  query.prepare("UPDATE recorded "
2387  "SET basename = :BASENAME "
2388  "WHERE recordedid = :RECORDEDID;");
2389  query.bindValue(":RECORDEDID", m_recordedid);
2390  query.bindValue(":BASENAME", basename);
2391 
2392  if (!query.exec())
2393  {
2394  MythDB::DBError("SetRecordBasename", query);
2395  return false;
2396  }
2397 
2398  query.prepare("UPDATE recordedfile "
2399  "SET basename = :BASENAME "
2400  "WHERE recordedid = :RECORDEDID;");
2401  query.bindValue(":RECORDEDID", m_recordedid);
2402  query.bindValue(":BASENAME", basename);
2403 
2404  if (!query.exec())
2405  {
2406  MythDB::DBError("SetRecordBasename", query);
2407  return false;
2408  }
2409 
2410  SetPathname(basename);
2411 
2412  SendUpdateEvent();
2413  return true;
2414 }
2415 
2423 QString ProgramInfo::QueryBasename(void) const
2424 {
2425  QString bn = GetBasename();
2426  if (!bn.isEmpty())
2427  return bn;
2428 
2429  MSqlQuery query(MSqlQuery::InitCon());
2430  query.prepare(
2431  "SELECT basename "
2432  "FROM recordedfile "
2433  "WHERE recordedid = :RECORDEDID;");
2434  query.bindValue(":RECORDEDID", m_recordedid);
2435 
2436  if (!query.exec())
2437  {
2438  MythDB::DBError("QueryBasename", query);
2439  }
2440  else if (query.next())
2441  {
2442  return query.value(0).toString();
2443  }
2444  else
2445  {
2446  LOG(VB_GENERAL, LOG_INFO,
2447  QString("QueryBasename found no entry for recording ID %1")
2448  .arg(m_recordedid));
2449  }
2450 
2451  return QString();
2452 }
2453 
2462  bool checkMaster, bool forceCheckLocal) const
2463 {
2464  // return the original path if BD or DVD URI
2465  if (IsVideoBD() || IsVideoDVD())
2466  return GetPathname();
2467 
2468  QString basename = QueryBasename();
2469  if (basename.isEmpty())
2470  return "";
2471 
2472  bool checklocal = !gCoreContext->GetBoolSetting("AlwaysStreamFiles", false) ||
2473  forceCheckLocal;
2474 
2475  if (IsVideo())
2476  {
2477  QString fullpath = GetPathname();
2478  if (!fullpath.startsWith("myth://", Qt::CaseInsensitive) || !checklocal)
2479  return fullpath;
2480 
2481  QUrl url = QUrl(fullpath);
2482  QString path = url.path();
2483  QString host = url.toString(QUrl::RemovePath).mid(7);
2484  QStringList list = host.split(":", QString::SkipEmptyParts);
2485  if (!list.empty())
2486  {
2487  host = list[0];
2488  list = host.split("@", QString::SkipEmptyParts);
2489  QString group;
2490  if (!list.empty() && list.size() < 3)
2491  {
2492  host = list.size() == 1 ? list[0] : list[1];
2493  group = list.size() == 1 ? QString() : list[0];
2494  StorageGroup sg = StorageGroup(group, host);
2495  QString local = sg.FindFile(path);
2496  if (!local.isEmpty() && sg.FileExists(local))
2497  return local;
2498  }
2499  }
2500  return fullpath;
2501  }
2502 
2503  QString tmpURL;
2504  if (checklocal)
2505  {
2506  // Check to see if the file exists locally
2507  StorageGroup sgroup(m_storagegroup);
2508 #if 0
2509  LOG(VB_FILE, LOG_DEBUG, LOC +
2510  QString("GetPlaybackURL: CHECKING SG : %1 : ").arg(tmpURL));
2511 #endif
2512  tmpURL = sgroup.FindFile(basename);
2513 
2514  if (!tmpURL.isEmpty())
2515  {
2516  LOG(VB_FILE, LOG_INFO, LOC +
2517  QString("GetPlaybackURL: File is local: '%1'") .arg(tmpURL));
2518  return tmpURL;
2519  }
2521  {
2522  LOG(VB_GENERAL, LOG_ERR, LOC +
2523  QString("GetPlaybackURL: '%1' should be local, but it can "
2524  "not be found.").arg(basename));
2525  // Note do not preceed with "/" that will cause existing code
2526  // to look for a local file with this name...
2527  return QString("GetPlaybackURL/UNABLE/TO/FIND/LOCAL/FILE/ON/%1/%2")
2528  .arg(m_hostname).arg(basename);
2529  }
2530  }
2531 
2532  // Check to see if we should stream from the master backend
2533  if ((checkMaster) &&
2534  (gCoreContext->GetBoolSetting("MasterBackendOverride", false)) &&
2535  (RemoteCheckFile(this, false)))
2536  {
2539  basename);
2540 
2541  LOG(VB_FILE, LOG_INFO, LOC +
2542  QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL));
2543  return tmpURL;
2544  }
2545 
2546  // Fallback to streaming from the backend the recording was created on
2549  basename);
2550 
2551  LOG(VB_FILE, LOG_INFO, LOC +
2552  QString("GetPlaybackURL: Using default of: '%1'") .arg(tmpURL));
2553 
2554  return tmpURL;
2555 }
2556 
2560 {
2561  uint ret = 0U;
2562  if (m_chanid)
2563  {
2564  MSqlQuery query(MSqlQuery::InitCon());
2565 
2566  query.prepare("SELECT mplexid FROM channel "
2567  "WHERE chanid = :CHANID");
2568  query.bindValue(":CHANID", m_chanid);
2569 
2570  if (!query.exec())
2571  MythDB::DBError("QueryMplexID", query);
2572  else if (query.next())
2573  ret = query.value(0).toUInt();
2574 
2575  // clear out bogus mplexid's
2576  ret = (32767 == ret) ? 0 : ret;
2577  }
2578 
2579  return ret;
2580 }
2581 
2584 void ProgramInfo::SaveBookmark(uint64_t frame)
2585 {
2587 
2588  bool is_valid = (frame > 0);
2589  if (is_valid)
2590  {
2591  frm_dir_map_t bookmarkmap;
2592  bookmarkmap[frame] = MARK_BOOKMARK;
2593  SaveMarkupMap(bookmarkmap);
2594  }
2595 
2596  set_flag(m_programflags, FL_BOOKMARK, is_valid);
2597 
2598  if (IsRecording())
2599  {
2600  MSqlQuery query(MSqlQuery::InitCon());
2601  query.prepare(
2602  "UPDATE recorded "
2603  "SET bookmarkupdate = CURRENT_TIMESTAMP, "
2604  " bookmark = :BOOKMARKFLAG "
2605  "WHERE chanid = :CHANID AND "
2606  " starttime = :STARTTIME");
2607 
2608  query.bindValue(":BOOKMARKFLAG", is_valid);
2609  query.bindValue(":CHANID", m_chanid);
2610  query.bindValue(":STARTTIME", m_recstartts);
2611 
2612  if (!query.exec())
2613  MythDB::DBError("bookmark flag update", query);
2614 
2615  SendUpdateEvent();
2616  }
2617 }
2618 
2620 {
2622 }
2623 
2625 {
2627 }
2628 
2630 {
2632 }
2633 
2638 {
2639  MSqlQuery query(MSqlQuery::InitCon());
2640  query.prepare(
2641  "SELECT bookmarkupdate "
2642  "FROM recorded "
2643  "WHERE chanid = :CHANID AND"
2644  " starttime = :STARTTIME");
2645  query.bindValue(":CHANID", m_chanid);
2646  query.bindValue(":STARTTIME", m_recstartts);
2647 
2648  QDateTime ts;
2649 
2650  if (!query.exec())
2651  MythDB::DBError("ProgramInfo::GetBookmarkTimeStamp()", query);
2652  else if (query.next())
2653  ts = MythDate::as_utc(query.value(0).toDateTime());
2654 
2655  return ts;
2656 }
2657 
2664 uint64_t ProgramInfo::QueryBookmark(void) const
2665 {
2667  return 0;
2668 
2669  frm_dir_map_t bookmarkmap;
2670  QueryMarkupMap(bookmarkmap, MARK_BOOKMARK);
2671 
2672  return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2673 }
2674 
2675 uint64_t ProgramInfo::QueryBookmark(uint chanid, const QDateTime &recstartts)
2676 {
2677  frm_dir_map_t bookmarkmap;
2679  chanid, recstartts,
2680  bookmarkmap, MARK_BOOKMARK);
2681 
2682  return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2683 }
2684 
2691 uint64_t ProgramInfo::QueryProgStart(void) const
2692 {
2694  return 0;
2695 
2696  frm_dir_map_t bookmarkmap;
2697  QueryMarkupMap(bookmarkmap, MARK_UTIL_PROGSTART);
2698 
2699  return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2700 }
2701 
2708 uint64_t ProgramInfo::QueryLastPlayPos(void) const
2709 {
2711  return 0;
2712 
2713  frm_dir_map_t bookmarkmap;
2714  QueryMarkupMap(bookmarkmap, MARK_UTIL_LASTPLAYPOS);
2715 
2716  return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2717 }
2718 
2725  const QString &serialid) const
2726 {
2727  QStringList fields = QStringList();
2728  MSqlQuery query(MSqlQuery::InitCon());
2729 
2731  {
2732  query.prepare(" SELECT dvdstate, title, framenum, audionum, subtitlenum "
2733  " FROM dvdbookmark "
2734  " WHERE serialid = :SERIALID ");
2735  query.bindValue(":SERIALID", serialid);
2736 
2737  if (query.exec() && query.next())
2738  {
2739  QString dvdstate = query.value(0).toString();
2740 
2741  if (!dvdstate.isEmpty())
2742  {
2743  fields.append(dvdstate);
2744  }
2745  else
2746  {
2747  // Legacy bookmark
2748  for(int i = 1; i < 5; i++)
2749  fields.append(query.value(i).toString());
2750  }
2751  }
2752  }
2753 
2754  return fields;
2755 }
2756 
2757 void ProgramInfo::SaveDVDBookmark(const QStringList &fields) const
2758 {
2759  QStringList::const_iterator it = fields.begin();
2760  MSqlQuery query(MSqlQuery::InitCon());
2761 
2762  QString serialid = *(it);
2763  QString name = *(++it);
2764 
2765  if( fields.count() == 3 )
2766  {
2767  // We have a state field, so update/create the bookmark
2768  QString state = *(++it);
2769 
2770  query.prepare("INSERT IGNORE INTO dvdbookmark "
2771  " (serialid, name)"
2772  " VALUES ( :SERIALID, :NAME );");
2773  query.bindValue(":SERIALID", serialid);
2774  query.bindValue(":NAME", name);
2775 
2776  if (!query.exec())
2777  MythDB::DBError("SetDVDBookmark inserting", query);
2778 
2779  query.prepare(" UPDATE dvdbookmark "
2780  " SET dvdstate = :STATE , "
2781  " timestamp = NOW() "
2782  " WHERE serialid = :SERIALID");
2783  query.bindValue(":STATE",state);
2784  query.bindValue(":SERIALID",serialid);
2785  }
2786  else
2787  {
2788  // No state field, delete the bookmark
2789  query.prepare("DELETE FROM dvdbookmark "
2790  "WHERE serialid = :SERIALID");
2791  query.bindValue(":SERIALID",serialid);
2792  }
2793 
2794  if (!query.exec())
2795  MythDB::DBError("SetDVDBookmark updating", query);
2796 }
2797 
2801 QStringList ProgramInfo::QueryBDBookmark(const QString &serialid) const
2802 {
2803  QStringList fields = QStringList();
2804  MSqlQuery query(MSqlQuery::InitCon());
2805 
2807  {
2808  query.prepare(" SELECT bdstate FROM bdbookmark "
2809  " WHERE serialid = :SERIALID ");
2810  query.bindValue(":SERIALID", serialid);
2811 
2812  if (query.exec() && query.next())
2813  fields.append(query.value(0).toString());
2814  }
2815 
2816  return fields;
2817 }
2818 
2819 void ProgramInfo::SaveBDBookmark(const QStringList &fields) const
2820 {
2821  QStringList::const_iterator it = fields.begin();
2822  MSqlQuery query(MSqlQuery::InitCon());
2823 
2824  QString serialid = *(it);
2825  QString name = *(++it);
2826 
2827  if( fields.count() == 3 )
2828  {
2829  // We have a state field, so update/create the bookmark
2830  QString state = *(++it);
2831 
2832  query.prepare("INSERT IGNORE INTO bdbookmark "
2833  " (serialid, name)"
2834  " VALUES ( :SERIALID, :NAME );");
2835  query.bindValue(":SERIALID", serialid);
2836  query.bindValue(":NAME", name);
2837 
2838  if (!query.exec())
2839  MythDB::DBError("SetBDBookmark inserting", query);
2840 
2841  query.prepare(" UPDATE bdbookmark "
2842  " SET bdstate = :STATE , "
2843  " timestamp = NOW() "
2844  " WHERE serialid = :SERIALID");
2845  query.bindValue(":STATE",state);
2846  query.bindValue(":SERIALID",serialid);
2847  }
2848  else
2849  {
2850  // No state field, delete the bookmark
2851  query.prepare("DELETE FROM bdbookmark "
2852  "WHERE serialid = :SERIALID");
2853  query.bindValue(":SERIALID",serialid);
2854  }
2855 
2856  if (!query.exec())
2857  MythDB::DBError("SetBDBookmark updating", query);
2858 }
2859 
2866 {
2868 
2869  MSqlQuery query(MSqlQuery::InitCon());
2870 
2871  query.prepare(" SELECT category_type "
2872  " FROM recordedprogram "
2873  " WHERE chanid = :CHANID "
2874  " AND starttime = :STARTTIME;");
2875 
2876  query.bindValue(":CHANID", m_chanid);
2877  query.bindValue(":STARTTIME", m_startts);
2878 
2879  if (query.exec() && query.next())
2880  {
2881  ret = string_to_myth_category_type(query.value(0).toString());
2882  }
2883 
2884  return ret;
2885 }
2886 
2888 void ProgramInfo::SaveWatched(bool watched)
2889 {
2890  if (IsRecording())
2891  {
2892  MSqlQuery query(MSqlQuery::InitCon());
2893 
2894  query.prepare("UPDATE recorded"
2895  " SET watched = :WATCHEDFLAG"
2896  " WHERE chanid = :CHANID"
2897  " AND starttime = :STARTTIME ;");
2898  query.bindValue(":CHANID", m_chanid);
2899  query.bindValue(":STARTTIME", m_recstartts);
2900  query.bindValue(":WATCHEDFLAG", watched);
2901 
2902  if (!query.exec())
2903  MythDB::DBError("Set watched flag", query);
2904  else
2905  UpdateLastDelete(watched);
2906 
2907  SendUpdateEvent();
2908  }
2909  else if (IsVideoFile())
2910  {
2911  QString url = m_pathname;
2912  if (url.startsWith("myth://"))
2913  {
2914  url = QUrl(url).path();
2915  url.remove(0,1);
2916  }
2917 
2918  MSqlQuery query(MSqlQuery::InitCon());
2919  query.prepare("UPDATE videometadata"
2920  " SET watched = :WATCHEDFLAG"
2921  " WHERE title = :TITLE"
2922  " AND subtitle = :SUBTITLE"
2923  " AND filename = :FILENAME ;");
2924  query.bindValue(":TITLE", m_title);
2925  query.bindValue(":SUBTITLE", m_subtitle);
2926  query.bindValue(":FILENAME", url);
2927  query.bindValue(":WATCHEDFLAG", watched);
2928 
2929  if (!query.exec())
2930  MythDB::DBError("Set watched flag", query);
2931  }
2932 
2933  set_flag(m_programflags, FL_WATCHED, watched);
2934 }
2935 
2941 {
2942  bool editing = (m_programflags & FL_REALLYEDITING) != 0U;
2943 
2944  MSqlQuery query(MSqlQuery::InitCon());
2945 
2946  query.prepare("SELECT editing FROM recorded"
2947  " WHERE chanid = :CHANID"
2948  " AND starttime = :STARTTIME ;");
2949  query.bindValue(":CHANID", m_chanid);
2950  query.bindValue(":STARTTIME", m_recstartts);
2951 
2952  if (query.exec() && query.next())
2953  editing = query.value(0).toBool();
2954 
2955  /*
2956  set_flag(programflags, FL_REALLYEDITING, editing);
2957  set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
2958  (programflags & COMM_FLAG_PROCESSING)));
2959  */
2960  return editing;
2961 }
2962 
2967 {
2968  MSqlQuery query(MSqlQuery::InitCon());
2969 
2970  query.prepare("UPDATE recorded"
2971  " SET editing = :EDIT"
2972  " WHERE chanid = :CHANID"
2973  " AND starttime = :STARTTIME ;");
2974  query.bindValue(":EDIT", edit);
2975  query.bindValue(":CHANID", m_chanid);
2976  query.bindValue(":STARTTIME", m_recstartts);
2977 
2978  if (!query.exec())
2979  MythDB::DBError("Edit status update", query);
2980 
2983  ((m_programflags & COMM_FLAG_PROCESSING) != 0U)));
2984 
2985  SendUpdateEvent();
2986 }
2987 
2992 {
2993  MSqlQuery query(MSqlQuery::InitCon());
2994 
2995  query.prepare("UPDATE recorded"
2996  " SET deletepending = :DELETEFLAG, "
2997  " duplicate = 0 "
2998  " WHERE chanid = :CHANID"
2999  " AND starttime = :STARTTIME ;");
3000  query.bindValue(":CHANID", m_chanid);
3001  query.bindValue(":STARTTIME", m_recstartts);
3002  query.bindValue(":DELETEFLAG", deleteFlag);
3003 
3004  if (!query.exec())
3005  MythDB::DBError("SaveDeletePendingFlag", query);
3006 
3008 
3009  if (!deleteFlag)
3010  SendAddedEvent();
3011 
3012  SendUpdateEvent();
3013 }
3014 
3019 bool ProgramInfo::QueryIsInUse(QStringList &byWho) const
3020 {
3021  if (!IsRecording())
3022  return false;
3023 
3024  QDateTime oneHourAgo = MythDate::current().addSecs(-61 * 60);
3025  MSqlQuery query(MSqlQuery::InitCon());
3026 
3027  query.prepare("SELECT hostname, recusage FROM inuseprograms "
3028  " WHERE chanid = :CHANID"
3029  " AND starttime = :STARTTIME "
3030  " AND lastupdatetime > :ONEHOURAGO ;");
3031  query.bindValue(":CHANID", m_chanid);
3032  query.bindValue(":STARTTIME", m_recstartts);
3033  query.bindValue(":ONEHOURAGO", oneHourAgo);
3034 
3035  byWho.clear();
3036  if (query.exec() && query.size() > 0)
3037  {
3038  QString usageStr, recusage;
3039  while (query.next())
3040  {
3041  usageStr = QObject::tr("Unknown");
3042  recusage = query.value(1).toString();
3043 
3044  if (recusage == kPlayerInUseID)
3045  usageStr = QObject::tr("Playing");
3046  else if (recusage == kPIPPlayerInUseID)
3047  usageStr = QObject::tr("PIP");
3048  else if (recusage == kPBPPlayerInUseID)
3049  usageStr = QObject::tr("PBP");
3050  else if ((recusage == kRecorderInUseID) ||
3051  (recusage == kImportRecorderInUseID))
3052  usageStr = QObject::tr("Recording");
3053  else if (recusage == kFileTransferInUseID)
3054  usageStr = QObject::tr("File transfer");
3055  else if (recusage == kTruncatingDeleteInUseID)
3056  usageStr = QObject::tr("Delete");
3057  else if (recusage == kFlaggerInUseID)
3058  usageStr = QObject::tr("Commercial Detection");
3059  else if (recusage == kTranscoderInUseID)
3060  usageStr = QObject::tr("Transcoding");
3061  else if (recusage == kPreviewGeneratorInUseID)
3062  usageStr = QObject::tr("Preview Generation");
3063  else if (recusage == kJobQueueInUseID)
3064  usageStr = QObject::tr("User Job");
3065 
3066  byWho.push_back(recusage);
3067  byWho.push_back(query.value(0).toString());
3068  byWho.push_back(query.value(0).toString() + " (" + usageStr + ")");
3069  }
3070 
3071  return true;
3072  }
3073 
3074  return false;
3075 }
3076 
3081 bool ProgramInfo::QueryIsInUse(QString &byWho) const
3082 {
3083  QStringList users;
3084  bool inuse = QueryIsInUse(users);
3085  byWho.clear();
3086  for (uint i = 0; i+2 < (uint)users.size(); i+=3)
3087  byWho += users[i+2] + "\n";
3088  return inuse;
3089 }
3090 
3091 
3098 bool ProgramInfo::QueryIsDeleteCandidate(bool one_playback_allowed) const
3099 {
3100  if (!IsRecording())
3101  return false;
3102 
3103  // gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0) &&
3104  if (GetRecordingGroup() != "Deleted" && GetRecordingGroup() != "LiveTV")
3105  return true;
3106 
3107  bool ok = true;
3108  QStringList byWho;
3109  if (QueryIsInUse(byWho) && !byWho.isEmpty())
3110  {
3111  uint play_cnt = 0, ft_cnt = 0, jq_cnt = 0;
3112  for (uint i = 0; (i+2 < (uint)byWho.size()) && ok; i+=3)
3113  {
3114  play_cnt += byWho[i].contains(kPlayerInUseID) ? 1 : 0;
3115  ft_cnt += (byWho[i].contains(kFlaggerInUseID) ||
3116  byWho[i].contains(kTranscoderInUseID)) ? 1 : 0;
3117  jq_cnt += (byWho[i].contains(kJobQueueInUseID)) ? 1 : 0;
3118  ok = ok && (byWho[i].contains(kRecorderInUseID) ||
3119  byWho[i].contains(kFlaggerInUseID) ||
3120  byWho[i].contains(kTranscoderInUseID) ||
3121  byWho[i].contains(kJobQueueInUseID) ||
3122  (one_playback_allowed && (play_cnt <= 1)));
3123  }
3124  ok = ok && (ft_cnt == jq_cnt);
3125  }
3126 
3127  return ok;
3128 }
3129 
3132 {
3133  MSqlQuery query(MSqlQuery::InitCon());
3134 
3135  query.prepare("SELECT transcoded FROM recorded"
3136  " WHERE chanid = :CHANID"
3137  " AND starttime = :STARTTIME ;");
3138  query.bindValue(":CHANID", m_chanid);
3139  query.bindValue(":STARTTIME", m_recstartts);
3140 
3141  if (query.exec() && query.next())
3142  return (TranscodingStatus) query.value(0).toUInt();
3144 }
3145 
3152 {
3153  MSqlQuery query(MSqlQuery::InitCon());
3154 
3155  query.prepare(
3156  "UPDATE recorded "
3157  "SET transcoded = :VALUE "
3158  "WHERE chanid = :CHANID AND"
3159  " starttime = :STARTTIME");
3160  query.bindValue(":VALUE", (uint)trans);
3161  query.bindValue(":CHANID", m_chanid);
3162  query.bindValue(":STARTTIME", m_recstartts);
3163 
3164  if (!query.exec())
3165  MythDB::DBError("Transcoded status update", query);
3166 
3168  SendUpdateEvent();
3169 }
3170 
3175 {
3176  MSqlQuery query(MSqlQuery::InitCon());
3177 
3178  query.prepare("UPDATE recorded"
3179  " SET commflagged = :FLAG"
3180  " WHERE chanid = :CHANID"
3181  " AND starttime = :STARTTIME ;");
3182  query.bindValue(":FLAG", (int)flag);
3183  query.bindValue(":CHANID", m_chanid);
3184  query.bindValue(":STARTTIME", m_recstartts);
3185 
3186  if (!query.exec())
3187  MythDB::DBError("Commercial Flagged status update", query);
3188 
3192  ((m_programflags & COMM_FLAG_PROCESSING) != 0U)));
3193  SendUpdateEvent();
3194 }
3195 
3196 
3200 void ProgramInfo::SavePreserve(bool preserveEpisode)
3201 {
3202  MSqlQuery query(MSqlQuery::InitCon());
3203 
3204  query.prepare("UPDATE recorded"
3205  " SET preserve = :PRESERVE"
3206  " WHERE chanid = :CHANID"
3207  " AND starttime = :STARTTIME ;");
3208  query.bindValue(":PRESERVE", preserveEpisode);
3209  query.bindValue(":CHANID", m_chanid);
3210  query.bindValue(":STARTTIME", m_recstartts);
3211 
3212  if (!query.exec())
3213  MythDB::DBError("PreserveEpisode update", query);
3214  else
3215  UpdateLastDelete(false);
3216 
3217  set_flag(m_programflags, FL_PRESERVED, preserveEpisode);
3218 
3219  SendUpdateEvent();
3220 }
3221 
3227 void ProgramInfo::SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete)
3228 {
3229  MSqlQuery query(MSqlQuery::InitCon());
3230 
3231  query.prepare("UPDATE recorded"
3232  " SET autoexpire = :AUTOEXPIRE"
3233  " WHERE chanid = :CHANID"
3234  " AND starttime = :STARTTIME ;");
3235  query.bindValue(":AUTOEXPIRE", (uint)autoExpire);
3236  query.bindValue(":CHANID", m_chanid);
3237  query.bindValue(":STARTTIME", m_recstartts);
3238 
3239  if (!query.exec())
3240  MythDB::DBError("AutoExpire update", query);
3241  else if (updateDelete)
3242  UpdateLastDelete(true);
3243 
3245 
3246  SendUpdateEvent();
3247 }
3248 
3253 void ProgramInfo::UpdateLastDelete(bool setTime) const
3254 {
3255  MSqlQuery query(MSqlQuery::InitCon());
3256 
3257  if (setTime)
3258  {
3259  QDateTime timeNow = MythDate::current();
3260  int delay = m_recstartts.secsTo(timeNow) / 3600;
3261 
3262  if (delay > 200)
3263  delay = 200;
3264  else if (delay < 1)
3265  delay = 1;
3266 
3267  query.prepare("UPDATE record SET last_delete = :TIME, "
3268  "avg_delay = (avg_delay * 3 + :DELAY) / 4 "
3269  "WHERE recordid = :RECORDID");
3270  query.bindValue(":TIME", timeNow);
3271  query.bindValue(":DELAY", delay);
3272  }
3273  else
3274  {
3275  query.prepare("UPDATE record SET last_delete = NULL "
3276  "WHERE recordid = :RECORDID");
3277  }
3278  query.bindValue(":RECORDID", m_recordid);
3279 
3280  if (!query.exec())
3281  MythDB::DBError("Update last_delete", query);
3282 }
3283 
3286 {
3287  MSqlQuery query(MSqlQuery::InitCon());
3288 
3289  query.prepare("SELECT autoexpire FROM recorded"
3290  " WHERE chanid = :CHANID"
3291  " AND starttime = :STARTTIME ;");
3292  query.bindValue(":CHANID", m_chanid);
3293  query.bindValue(":STARTTIME", m_recstartts);
3294 
3295  if (query.exec() && query.next())
3296  return (AutoExpireType) query.value(0).toInt();
3297 
3298  return kDisableAutoExpire;
3299 }
3300 
3301 bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
3302 {
3303  if (loadAutoSave)
3304  {
3305  frm_dir_map_t autosaveMap;
3306  QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
3307  QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
3308  QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
3309  // Convert the temporary marks into regular marks.
3310  delMap.clear();
3311  frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
3312  for (; i != autosaveMap.constEnd(); ++i)
3313  {
3314  uint64_t frame = i.key();
3315  MarkTypes mark = i.value();
3316  if (mark == MARK_TMP_CUT_START)
3317  mark = MARK_CUT_START;
3318  else if (mark == MARK_TMP_CUT_END)
3319  mark = MARK_CUT_END;
3320  delMap[frame] = mark;
3321  }
3322  }
3323  else
3324  {
3325  QueryMarkupMap(delMap, MARK_CUT_START);
3326  QueryMarkupMap(delMap, MARK_CUT_END, true);
3327  QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
3328  }
3329 
3330  return !delMap.isEmpty();
3331 }
3332 
3333 void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
3334 {
3335  if (!isAutoSave)
3336  {
3339  }
3343 
3344  frm_dir_map_t tmpDelMap;
3345  frm_dir_map_t::const_iterator i = delMap.constBegin();
3346  for (; i != delMap.constEnd(); ++i)
3347  {
3348  uint64_t frame = i.key();
3349  MarkTypes mark = i.value();
3350  if (isAutoSave)
3351  {
3352  if (mark == MARK_CUT_START)
3354  else if (mark == MARK_CUT_END)
3356  }
3357  tmpDelMap[frame] = mark;
3358  }
3359  SaveMarkupMap(tmpDelMap);
3360 
3361  if (IsRecording())
3362  {
3363  MSqlQuery query(MSqlQuery::InitCon());
3364 
3365  // Flag the existence of a cutlist
3366  query.prepare("UPDATE recorded"
3367  " SET cutlist = :CUTLIST"
3368  " WHERE chanid = :CHANID"
3369  " AND starttime = :STARTTIME ;");
3370 
3371  query.bindValue(":CUTLIST", delMap.isEmpty() ? 0 : 1);
3372  query.bindValue(":CHANID", m_chanid);
3373  query.bindValue(":STARTTIME", m_recstartts);
3374 
3375  if (!query.exec())
3376  MythDB::DBError("cutlist flag update", query);
3377  }
3378 }
3379 
3381 {
3385 }
3386 
3388 {
3391 }
3392 
3394  MarkTypes type, int64_t min_frame, int64_t max_frame) const
3395 {
3396  MSqlQuery query(MSqlQuery::InitCon());
3397  QString comp;
3398 
3399  if (min_frame >= 0)
3400  comp += QString(" AND mark >= %1 ").arg(min_frame);
3401 
3402  if (max_frame >= 0)
3403  comp += QString(" AND mark <= %1 ").arg(max_frame);
3404 
3405  if (type != MARK_ALL)
3406  comp += QString(" AND type = :TYPE ");
3407 
3408  if (IsVideo())
3409  {
3410  query.prepare("DELETE FROM filemarkup"
3411  " WHERE filename = :PATH "
3412  + comp + ";");
3414  }
3415  else if (IsRecording())
3416  {
3417  query.prepare("DELETE FROM recordedmarkup"
3418  " WHERE chanid = :CHANID"
3419  " AND STARTTIME = :STARTTIME"
3420  + comp + ';');
3421  query.bindValue(":CHANID", m_chanid);
3422  query.bindValue(":STARTTIME", m_recstartts);
3423  }
3424  else
3425  {
3426  return;
3427  }
3428  query.bindValue(":TYPE", type);
3429 
3430  if (!query.exec())
3431  MythDB::DBError("ClearMarkupMap deleting", query);
3432 }
3433 
3436  int64_t min_frame, int64_t max_frame) const
3437 {
3438  MSqlQuery query(MSqlQuery::InitCon());
3439  QString videoPath;
3440 
3441  if (IsVideo())
3442  {
3444  }
3445  else if (IsRecording())
3446  {
3447  // check to make sure the show still exists before saving markups
3448  query.prepare("SELECT starttime FROM recorded"
3449  " WHERE chanid = :CHANID"
3450  " AND starttime = :STARTTIME ;");
3451  query.bindValue(":CHANID", m_chanid);
3452  query.bindValue(":STARTTIME", m_recstartts);
3453 
3454  if (!query.exec())
3455  MythDB::DBError("SaveMarkupMap checking record table", query);
3456 
3457  if (!query.next())
3458  return;
3459  }
3460  else
3461  {
3462  return;
3463  }
3464 
3465  frm_dir_map_t::const_iterator it;
3466  for (it = marks.begin(); it != marks.end(); ++it)
3467  {
3468  uint64_t frame = it.key();
3469  int mark_type;
3470  QString querystr;
3471 
3472  if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
3473  continue;
3474 
3475  if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
3476  continue;
3477 
3478  mark_type = (type != MARK_ALL) ? type : *it;
3479 
3480  if (IsVideo())
3481  {
3482  query.prepare("INSERT INTO filemarkup (filename, mark, type)"
3483  " VALUES ( :PATH , :MARK , :TYPE );");
3484  query.bindValue(":PATH", videoPath);
3485  }
3486  else // if (IsRecording())
3487  {
3488  query.prepare("INSERT INTO recordedmarkup"
3489  " (chanid, starttime, mark, type)"
3490  " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE );");
3491  query.bindValue(":CHANID", m_chanid);
3492  query.bindValue(":STARTTIME", m_recstartts);
3493  }
3494  query.bindValue(":MARK", (quint64)frame);
3495  query.bindValue(":TYPE", mark_type);
3496 
3497  if (!query.exec())
3498  MythDB::DBError("SaveMarkupMap inserting", query);
3499  }
3500 }
3501 
3503  frm_dir_map_t &marks, MarkTypes type, bool merge) const
3504 {
3505  if (!merge)
3506  marks.clear();
3507 
3508  if (IsVideo())
3509  {
3511  marks, type, merge);
3512  }
3513  else if (IsRecording())
3514  {
3516  }
3517 }
3518 
3520  const QString &video_pathname,
3522  MarkTypes type, bool mergeIntoMap)
3523 {
3524  if (!mergeIntoMap)
3525  marks.clear();
3526 
3527  MSqlQuery query(MSqlQuery::InitCon());
3528 
3529  query.prepare("SELECT mark, type "
3530  "FROM filemarkup "
3531  "WHERE filename = :PATH AND "
3532  " type = :TYPE "
3533  "ORDER BY mark");
3534  query.bindValue(":PATH", video_pathname);
3535  query.bindValue(":TYPE", type);
3536 
3537  if (!query.exec())
3538  {
3539  MythDB::DBError("QueryMarkupMap", query);
3540  return;
3541  }
3542 
3543  while (query.next())
3544  {
3545  marks[query.value(0).toLongLong()] =
3546  (MarkTypes) query.value(1).toInt();
3547  }
3548 }
3549 
3551  uint chanid, const QDateTime &recstartts,
3553  MarkTypes type, bool mergeIntoMap)
3554 {
3555  if (!mergeIntoMap)
3556  marks.clear();
3557 
3558  MSqlQuery query(MSqlQuery::InitCon());
3559  query.prepare("SELECT mark, type "
3560  "FROM recordedmarkup "
3561  "WHERE chanid = :CHANID AND "
3562  " starttime = :STARTTIME AND"
3563  " type = :TYPE "
3564  "ORDER BY mark");
3565  query.bindValue(":CHANID", chanid);
3566  query.bindValue(":STARTTIME", recstartts);
3567  query.bindValue(":TYPE", type);
3568 
3569  if (!query.exec())
3570  {
3571  MythDB::DBError("QueryMarkupMap", query);
3572  return;
3573  }
3574 
3575  while (query.next())
3576  {
3577  marks[query.value(0).toULongLong()] =
3578  (MarkTypes) query.value(1).toInt();
3579  }
3580 }
3581 
3584 {
3585  frm_dir_map_t flagMap;
3586 
3587  QueryMarkupMap(flagMap, type);
3588 
3589  return flagMap.contains(0);
3590 }
3591 
3594 {
3596  frm_dir_map_t flagMap;
3597  flagMap[0] = type;
3598  SaveMarkupMap(flagMap, type);
3599 }
3600 
3602  frm_pos_map_t &posMap, MarkTypes type) const
3603 {
3605  {
3606  QMutexLocker locker(m_positionMapDBReplacement->lock);
3607  posMap = m_positionMapDBReplacement->map[type];
3608 
3609  return;
3610  }
3611 
3612  posMap.clear();
3613  MSqlQuery query(MSqlQuery::InitCon());
3614 
3615  if (IsVideo())
3616  {
3617  query.prepare("SELECT mark, offset FROM filemarkup"
3618  " WHERE filename = :PATH"
3619  " AND type = :TYPE ;");
3621  }
3622  else if (IsRecording())
3623  {
3624  query.prepare("SELECT mark, offset FROM recordedseek"
3625  " WHERE chanid = :CHANID"
3626  " AND starttime = :STARTTIME"
3627  " AND type = :TYPE ;");
3628  query.bindValue(":CHANID", m_chanid);
3629  query.bindValue(":STARTTIME", m_recstartts);
3630  }
3631  else
3632  {
3633  return;
3634  }
3635  query.bindValue(":TYPE", type);
3636 
3637  if (!query.exec())
3638  {
3639  MythDB::DBError("QueryPositionMap", query);
3640  return;
3641  }
3642 
3643  while (query.next())
3644  posMap[query.value(0).toULongLong()] = query.value(1).toULongLong();
3645 }
3646 
3648 {
3650  {
3651  QMutexLocker locker(m_positionMapDBReplacement->lock);
3653  return;
3654  }
3655 
3656  MSqlQuery query(MSqlQuery::InitCon());
3657 
3658  if (IsVideo())
3659  {
3660  query.prepare("DELETE FROM filemarkup"
3661  " WHERE filename = :PATH"
3662  " AND type = :TYPE ;");
3664  }
3665  else if (IsRecording())
3666  {
3667  query.prepare("DELETE FROM recordedseek"
3668  " WHERE chanid = :CHANID"
3669  " AND starttime = :STARTTIME"
3670  " AND type = :TYPE ;");
3671  query.bindValue(":CHANID", m_chanid);
3672  query.bindValue(":STARTTIME", m_recstartts);
3673  }
3674  else
3675  {
3676  return;
3677  }
3678 
3679  query.bindValue(":TYPE", type);
3680 
3681  if (!query.exec())
3682  MythDB::DBError("clear position map", query);
3683 }
3684 
3686  frm_pos_map_t &posMap, MarkTypes type,
3687  int64_t min_frame, int64_t max_frame) const
3688 {
3690  {
3691  QMutexLocker locker(m_positionMapDBReplacement->lock);
3692 
3693  if ((min_frame >= 0) || (max_frame >= 0))
3694  {
3695  frm_pos_map_t::const_iterator it, it_end;
3696  it = m_positionMapDBReplacement->map[type].begin();
3697  it_end = m_positionMapDBReplacement->map[type].end();
3698 
3699  frm_pos_map_t new_map;
3700  for (; it != it_end; ++it)
3701  {
3702  uint64_t frame = it.key();
3703  if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3704  continue;
3705  if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3706  continue;
3707  new_map.insert(it.key(), *it);
3708  }
3709  m_positionMapDBReplacement->map[type] = new_map;
3710  }
3711  else
3712  {
3714  }
3715 
3716  frm_pos_map_t::const_iterator it = posMap.begin();
3717  frm_pos_map_t::const_iterator it_end = posMap.end();
3718  for (; it != it_end; ++it)
3719  {
3720  uint64_t frame = it.key();
3721  if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3722  continue;
3723  if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3724  continue;
3725 
3727  .insert(frame, *it);
3728  }
3729 
3730  return;
3731  }
3732 
3733  MSqlQuery query(MSqlQuery::InitCon());
3734  QString comp;
3735 
3736  if (min_frame >= 0)
3737  comp += " AND mark >= :MIN_FRAME ";
3738  if (max_frame >= 0)
3739  comp += " AND mark <= :MAX_FRAME ";
3740 
3741  QString videoPath;
3742  if (IsVideo())
3743  {
3745 
3746  query.prepare("DELETE FROM filemarkup"
3747  " WHERE filename = :PATH"
3748  " AND type = :TYPE"
3749  + comp + ';');
3750  query.bindValue(":PATH", videoPath);
3751  }
3752  else if (IsRecording())
3753  {
3754  query.prepare("DELETE FROM recordedseek"
3755  " WHERE chanid = :CHANID"
3756  " AND starttime = :STARTTIME"
3757  " AND type = :TYPE"
3758  + comp + ';');
3759  query.bindValue(":CHANID", m_chanid);
3760  query.bindValue(":STARTTIME", m_recstartts);
3761  }
3762  else
3763  {
3764  return;
3765  }
3766 
3767  query.bindValue(":TYPE", type);
3768  if (min_frame >= 0)
3769  query.bindValue(":MIN_FRAME", (quint64)min_frame);
3770  if (max_frame >= 0)
3771  query.bindValue(":MAX_FRAME", (quint64)max_frame);
3772 
3773  if (!query.exec())
3774  MythDB::DBError("position map clear", query);
3775 
3776  if (posMap.isEmpty())
3777  return;
3778 
3779  // Use the multi-value insert syntax to reduce database I/O
3780  QStringList q("INSERT INTO ");
3781  QString qfields;
3782  if (IsVideo())
3783  {
3784  q << "filemarkup (filename, type, mark, offset)";
3785  qfields = QString("('%1',%2,") .
3786  // ideally, this should be escaped
3787  arg(videoPath) .
3788  arg(type);
3789  }
3790  else // if (IsRecording())
3791  {
3792  q << "recordedseek (chanid, starttime, type, mark, offset)";
3793  qfields = QString("(%1,'%2',%3,") .
3794  arg(m_chanid) .
3795  arg(m_recstartts.toString(Qt::ISODate)) .
3796  arg(type);
3797  }
3798  q << " VALUES ";
3799 
3800  bool add_comma = false;
3801  frm_pos_map_t::iterator it;
3802  for (it = posMap.begin(); it != posMap.end(); ++it)
3803  {
3804  uint64_t frame = it.key();
3805 
3806  if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
3807  continue;
3808 
3809  if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
3810  continue;
3811 
3812  uint64_t offset = *it;
3813 
3814  if (add_comma)
3815  {
3816  q << ",";
3817  }
3818  else
3819  {
3820  add_comma = true;
3821  }
3822  q << qfields << QString("%1,%2)").arg(frame).arg(offset);
3823  }
3824  query.prepare(q.join(""));
3825  if (!query.exec())
3826  {
3827  MythDB::DBError("position map insert", query);
3828  }
3829 }
3830 
3832  frm_pos_map_t &posMap, MarkTypes type) const
3833 {
3834  if (posMap.isEmpty())
3835  return;
3836 
3838  {
3839  QMutexLocker locker(m_positionMapDBReplacement->lock);
3840 
3841  frm_pos_map_t::const_iterator it = posMap.begin();
3842  frm_pos_map_t::const_iterator it_end = posMap.end();
3843  for (; it != it_end; ++it)
3844  m_positionMapDBReplacement->map[type].insert(it.key(), *it);
3845 
3846  return;
3847  }
3848 
3849  // Use the multi-value insert syntax to reduce database I/O
3850  QStringList q("INSERT INTO ");
3851  QString qfields;
3852  if (IsVideo())
3853  {
3854  q << "filemarkup (filename, type, mark, offset)";
3855  qfields = QString("('%1',%2,") .
3856  // ideally, this should be escaped
3858  arg(type);
3859  }
3860  else if (IsRecording())
3861  {
3862  q << "recordedseek (chanid, starttime, type, mark, offset)";
3863  qfields = QString("(%1,'%2',%3,") .
3864  arg(m_chanid) .
3865  arg(m_recstartts.toString(Qt::ISODate)) .
3866  arg(type);
3867  }
3868  else
3869  {
3870  return;
3871  }
3872  q << " VALUES ";
3873 
3874  bool add_comma = false;
3875  frm_pos_map_t::iterator it;
3876  for (it = posMap.begin(); it != posMap.end(); ++it)
3877  {
3878  uint64_t frame = it.key();
3879  uint64_t offset = *it;
3880 
3881  if (add_comma)
3882  {
3883  q << ",";
3884  }
3885  else
3886  {
3887  add_comma = true;
3888  }
3889  q << qfields << QString("%1,%2)").arg(frame).arg(offset);
3890  }
3891 
3892  MSqlQuery query(MSqlQuery::InitCon());
3893  query.prepare(q.join(""));
3894  if (!query.exec())
3895  {
3896  MythDB::DBError("delta position map insert", query);
3897  }
3898 }
3899 
3900 static const char *from_filemarkup_offset_asc =
3901  "SELECT mark, offset FROM filemarkup"
3902  " WHERE filename = :PATH"
3903  " AND type = :TYPE"
3904  " AND mark >= :QUERY_ARG"
3905  " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
3906 static const char *from_filemarkup_offset_desc =
3907  "SELECT mark, offset FROM filemarkup"
3908  " WHERE filename = :PATH"
3909  " AND type = :TYPE"
3910  " AND mark <= :QUERY_ARG"
3911  " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
3912 static const char *from_recordedseek_offset_asc =
3913  "SELECT mark, offset FROM recordedseek"
3914  " WHERE chanid = :CHANID"
3915  " AND starttime = :STARTTIME"
3916  " AND type = :TYPE"
3917  " AND mark >= :QUERY_ARG"
3918  " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
3919 static const char *from_recordedseek_offset_desc =
3920  "SELECT mark, offset FROM recordedseek"
3921  " WHERE chanid = :CHANID"
3922  " AND starttime = :STARTTIME"
3923  " AND type = :TYPE"
3924  " AND mark <= :QUERY_ARG"
3925  " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
3926 static const char *from_filemarkup_mark_asc =
3927  "SELECT offset,mark FROM filemarkup"
3928  " WHERE filename = :PATH"
3929  " AND type = :TYPE"
3930  " AND offset >= :QUERY_ARG"
3931  " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
3932 static const char *from_filemarkup_mark_desc =
3933  "SELECT offset,mark FROM filemarkup"
3934  " WHERE filename = :PATH"
3935  " AND type = :TYPE"
3936  " AND offset <= :QUERY_ARG"
3937  " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
3938 static const char *from_recordedseek_mark_asc =
3939  "SELECT offset,mark FROM recordedseek"
3940  " WHERE chanid = :CHANID"
3941  " AND starttime = :STARTTIME"
3942  " AND type = :TYPE"
3943  " AND offset >= :QUERY_ARG"
3944  " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
3945 static const char *from_recordedseek_mark_desc =
3946  "SELECT offset,mark FROM recordedseek"
3947  " WHERE chanid = :CHANID"
3948  " AND starttime = :STARTTIME"
3949  " AND type = :TYPE"
3950  " AND offset <= :QUERY_ARG"
3951  " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
3952 
3953 bool ProgramInfo::QueryKeyFrameInfo(uint64_t * result,
3954  uint64_t position_or_keyframe,
3955  bool backwards,
3956  MarkTypes type,
3957  const char *from_filemarkup_asc,
3958  const char *from_filemarkup_desc,
3959  const char *from_recordedseek_asc,
3960  const char *from_recordedseek_desc) const
3961 {
3962  MSqlQuery query(MSqlQuery::InitCon());
3963 
3964  if (IsVideo())
3965  {
3966  if (backwards)
3967  query.prepare(from_filemarkup_desc);
3968  else
3969  query.prepare(from_filemarkup_asc);
3971  }
3972  else if (IsRecording())
3973  {
3974  if (backwards)
3975  query.prepare(from_recordedseek_desc);
3976  else
3977  query.prepare(from_recordedseek_asc);
3978  query.bindValue(":CHANID", m_chanid);
3979  query.bindValue(":STARTTIME", m_recstartts);
3980  }
3981  query.bindValue(":TYPE", type);
3982  query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
3983 
3984  if (!query.exec())
3985  {
3986  MythDB::DBError("QueryKeyFrameInfo", query);
3987  return false;
3988  }
3989 
3990  if (query.next())
3991  {
3992  *result = query.value(1).toULongLong();
3993  return true;
3994  }
3995 
3996  if (IsVideo())
3997  {
3998  if (backwards)
3999  query.prepare(from_filemarkup_asc);
4000  else
4001  query.prepare(from_filemarkup_desc);
4003  }
4004  else if (IsRecording())
4005  {
4006  if (backwards)
4007  query.prepare(from_recordedseek_asc);
4008  else
4009  query.prepare(from_recordedseek_desc);
4010  query.bindValue(":CHANID", m_chanid);
4011  query.bindValue(":STARTTIME", m_recstartts);
4012  }
4013  query.bindValue(":TYPE", type);
4014  query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
4015 
4016  if (!query.exec())
4017  {
4018  MythDB::DBError("QueryKeyFrameInfo", query);
4019  return false;
4020  }
4021 
4022  if (query.next())
4023  {
4024  *result = query.value(1).toULongLong();
4025  return true;
4026  }
4027 
4028  return false;
4029 
4030 }
4031 
4032 bool ProgramInfo::QueryPositionKeyFrame(uint64_t *keyframe, uint64_t position,
4033  bool backwards) const
4034 {
4035  return QueryKeyFrameInfo(keyframe, position, backwards, MARK_GOP_BYFRAME,
4040 }
4041 bool ProgramInfo::QueryKeyFramePosition(uint64_t *position, uint64_t keyframe,
4042  bool backwards) const
4043 {
4044  return QueryKeyFrameInfo(position, keyframe, backwards, MARK_GOP_BYFRAME,
4049 }
4050 bool ProgramInfo::QueryDurationKeyFrame(uint64_t *keyframe, uint64_t duration,
4051  bool backwards) const
4052 {
4053  return QueryKeyFrameInfo(keyframe, duration, backwards, MARK_DURATION_MS,
4058 }
4059 bool ProgramInfo::QueryKeyFrameDuration(uint64_t *duration, uint64_t keyframe,
4060  bool backwards) const
4061 {
4062  return QueryKeyFrameInfo(duration, keyframe, backwards, MARK_DURATION_MS,
4067 }
4068 
4073  uint64_t frame, MarkTypes type, uint customAspect)
4074 {
4075  if (!IsRecording())
4076  return;
4077 
4078  MSqlQuery query(MSqlQuery::InitCon());
4079 
4080  query.prepare("INSERT INTO recordedmarkup"
4081  " (chanid, starttime, mark, type, data)"
4082  " VALUES"
4083  " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4084  query.bindValue(":CHANID", m_chanid);
4085  query.bindValue(":STARTTIME", m_recstartts);
4086 
4087  query.bindValue(":MARK", (quint64)frame);
4088  query.bindValue(":TYPE", type);
4089 
4090  if (type == MARK_ASPECT_CUSTOM)
4091  query.bindValue(":DATA", customAspect);
4092  else
4093  query.bindValue(":DATA", QVariant::UInt);
4094 
4095  if (!query.exec())
4096  MythDB::DBError("aspect ratio change insert", query);
4097 }
4098 
4102 void ProgramInfo::SaveFrameRate(uint64_t frame, uint framerate)
4103 {
4104  if (!IsRecording())
4105  return;
4106 
4107  MSqlQuery query(MSqlQuery::InitCon());
4108 
4109  query.prepare("INSERT INTO recordedmarkup"
4110  " (chanid, starttime, mark, type, data)"
4111  " VALUES"
4112  " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4113  query.bindValue(":CHANID", m_chanid);
4114  query.bindValue(":STARTTIME", m_recstartts);
4115  query.bindValue(":MARK", (quint64)frame);
4116  query.bindValue(":TYPE", MARK_VIDEO_RATE);
4117  query.bindValue(":DATA", framerate);
4118 
4119  if (!query.exec())
4120  MythDB::DBError("Frame rate insert", query);
4121 }
4122 
4123 
4125 void ProgramInfo::SaveTotalDuration(int64_t duration)
4126 {
4127  if (!IsRecording())
4128  return;
4129 
4130  MSqlQuery query(MSqlQuery::InitCon());
4131 
4132  query.prepare("DELETE FROM recordedmarkup "
4133  " WHERE chanid=:CHANID "
4134  " AND starttime=:STARTTIME "
4135  " AND type=:TYPE");
4136  query.bindValue(":CHANID", m_chanid);
4137  query.bindValue(":STARTTIME", m_recstartts);
4138  query.bindValue(":TYPE", MARK_DURATION_MS);
4139 
4140  if (!query.exec())
4141  MythDB::DBError("Duration delete", query);
4142 
4143  query.prepare("INSERT INTO recordedmarkup"
4144  " (chanid, starttime, mark, type, data)"
4145  " VALUES"
4146  " ( :CHANID, :STARTTIME, 0, :TYPE, :DATA);");
4147  query.bindValue(":CHANID", m_chanid);
4148  query.bindValue(":STARTTIME", m_recstartts);
4149  query.bindValue(":TYPE", MARK_DURATION_MS);
4150  query.bindValue(":DATA", (uint)(duration / 1000));
4151 
4152  if (!query.exec())
4153  MythDB::DBError("Duration insert", query);
4154 }
4155 
4158 {
4159  if (!IsRecording())
4160  return;
4161 
4162  MSqlQuery query(MSqlQuery::InitCon());
4163 
4164  query.prepare("DELETE FROM recordedmarkup "
4165  " WHERE chanid=:CHANID "
4166  " AND starttime=:STARTTIME "
4167  " AND type=:TYPE");
4168  query.bindValue(":CHANID", m_chanid);
4169  query.bindValue(":STARTTIME", m_recstartts);
4170  query.bindValue(":TYPE", MARK_TOTAL_FRAMES);
4171 
4172  if (!query.exec())
4173  MythDB::DBError("Frames delete", query);
4174 
4175  query.prepare("INSERT INTO recordedmarkup"
4176  " (chanid, starttime, mark, type, data)"
4177  " VALUES"
4178  " ( :CHANID, :STARTTIME, 0, :TYPE, :DATA);");
4179  query.bindValue(":CHANID", m_chanid);
4180  query.bindValue(":STARTTIME", m_recstartts);
4181  query.bindValue(":TYPE", MARK_TOTAL_FRAMES);
4182  query.bindValue(":DATA", (uint)(frames));
4183 
4184  if (!query.exec())
4185  MythDB::DBError("Total Frames insert", query);
4186 }
4187 
4191 void ProgramInfo::SaveResolution(uint64_t frame, uint width, uint height)
4192 {
4193  if (!IsRecording())
4194  return;
4195 
4196  MSqlQuery query(MSqlQuery::InitCon());
4197 
4198  query.prepare("INSERT INTO recordedmarkup"
4199  " (chanid, starttime, mark, type, data)"
4200  " VALUES"
4201  " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4202  query.bindValue(":CHANID", m_chanid);
4203  query.bindValue(":STARTTIME", m_recstartts);
4204  query.bindValue(":MARK", (quint64)frame);
4205  query.bindValue(":TYPE", MARK_VIDEO_WIDTH);
4206  query.bindValue(":DATA", width);
4207 
4208  if (!query.exec())
4209  MythDB::DBError("Resolution insert", query);
4210 
4211  query.prepare("INSERT INTO recordedmarkup"
4212  " (chanid, starttime, mark, type, data)"
4213  " VALUES"
4214  " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4215  query.bindValue(":CHANID", m_chanid);
4216  query.bindValue(":STARTTIME", m_recstartts);
4217  query.bindValue(":MARK", (quint64)frame);
4218  query.bindValue(":TYPE", MARK_VIDEO_HEIGHT);
4219  query.bindValue(":DATA", height);
4220 
4221  if (!query.exec())
4222  MythDB::DBError("Resolution insert", query);
4223 }
4224 
4226  MarkTypes type, uint chanid, const QDateTime &recstartts)
4227 {
4228  QString qstr = QString(
4229  "SELECT recordedmarkup.data "
4230  "FROM recordedmarkup "
4231  "WHERE recordedmarkup.chanid = :CHANID AND "
4232  " recordedmarkup.starttime = :STARTTIME AND "
4233  " recordedmarkup.type = :TYPE "
4234  "GROUP BY recordedmarkup.data "
4235  "ORDER BY SUM( ( SELECT IFNULL(rm.mark, recordedmarkup.mark)"
4236  " FROM recordedmarkup AS rm "
4237  " WHERE rm.chanid = recordedmarkup.chanid AND "
4238  " rm.starttime = recordedmarkup.starttime AND "
4239  " rm.type = recordedmarkup.type AND "
4240  " rm.mark > recordedmarkup.mark "
4241  " ORDER BY rm.mark ASC LIMIT 1 "
4242  " ) - recordedmarkup.mark "
4243  " ) DESC "
4244  "LIMIT 1");
4245 
4246  MSqlQuery query(MSqlQuery::InitCon());
4247  query.prepare(qstr);
4248  query.bindValue(":TYPE", (int)type);
4249  query.bindValue(":CHANID", chanid);
4250  query.bindValue(":STARTTIME", recstartts);
4251 
4252  if (!query.exec())
4253  {
4254  MythDB::DBError("load_markup_datum", query);
4255  return 0;
4256  }
4257 
4258  return (query.next()) ? query.value(0).toUInt() : 0;
4259 }
4260 
4266 {
4268 }
4269 
4275 {
4277 }
4278 
4284 {
4286 }
4287 
4289 {
4290  MSqlQuery query(MSqlQuery::InitCon());
4291  query.prepare("SELECT recordedmarkup.type "
4292  "FROM recordedmarkup "
4293  "WHERE recordedmarkup.chanid = :CHANID AND "
4294  " recordedmarkup.starttime = :STARTTIME AND "
4295  " recordedmarkup.type >= :ASPECTSTART AND "
4296  " recordedmarkup.type <= :ASPECTEND "
4297  "GROUP BY recordedmarkup.type "
4298  "ORDER BY SUM( ( SELECT IFNULL(rm.mark, ( "
4299  " SELECT MAX(rmmax.mark) "
4300  " FROM recordedmarkup AS rmmax "
4301  " WHERE rmmax.chanid = recordedmarkup.chanid "
4302  " AND rmmax.starttime = recordedmarkup.starttime "
4303  " ) ) "
4304  " FROM recordedmarkup AS rm "
4305  " WHERE rm.chanid = recordedmarkup.chanid AND "
4306  " rm.starttime = recordedmarkup.starttime AND "
4307  " rm.type >= :ASPECTSTART2 AND "
4308  " rm.type <= :ASPECTEND2 AND "
4309  " rm.mark > recordedmarkup.mark "
4310  " ORDER BY rm.mark ASC LIMIT 1 "
4311  " ) - recordedmarkup.mark "
4312  " ) DESC "
4313  "LIMIT 1");
4314  query.bindValue(":CHANID", m_chanid);
4315  query.bindValue(":STARTTIME", m_recstartts);
4316  query.bindValue(":ASPECTSTART", MARK_ASPECT_4_3); // 11
4317  query.bindValue(":ASPECTEND", MARK_ASPECT_CUSTOM); // 14
4318  query.bindValue(":ASPECTSTART2", MARK_ASPECT_4_3); // 11
4319  query.bindValue(":ASPECTEND2", MARK_ASPECT_CUSTOM); // 14
4320 
4321  if (!query.exec())
4322  {
4323  MythDB::DBError("QueryAverageAspectRatio", query);
4324  return MARK_UNSET;
4325  }
4326 
4327  if (!query.next())
4328  return MARK_UNSET;
4329 
4330  return static_cast<MarkTypes>(query.value(0).toInt());
4331 }
4332 
4339 {
4341  return 0;
4342 
4343  // 32Bits is more than sufficient. A recording would need to be almost a
4344  // month long to wrap and this is impossible since we cap the maximum
4345  // recording length to 6 hours.
4347 
4348 // Impossible condition, load_markup_datum returns an unsigned int
4349 // if (msec < 0)
4350 // return 0;
4351 
4352  return msec;
4353 }
4354 
4359 {
4361  return frames;
4362 }
4363 
4364 void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
4365  QVector<MarkupEntry> &mapSeek) const
4366 {
4367  MSqlQuery query(MSqlQuery::InitCon());
4368  // Get the markup
4369  if (IsVideo())
4370  {
4371  query.prepare("SELECT type, mark, offset FROM filemarkup"
4372  " WHERE filename = :PATH"
4373  " AND type NOT IN (:KEYFRAME,:DURATION)"
4374  " ORDER BY mark, type;");
4376  query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4377  query.bindValue(":DURATION", MARK_DURATION_MS);
4378  }
4379  else if (IsRecording())
4380  {
4381  query.prepare("SELECT type, mark, data FROM recordedmarkup"
4382  " WHERE chanid = :CHANID"
4383  " AND STARTTIME = :STARTTIME"
4384  " ORDER BY mark, type");
4385  query.bindValue(":CHANID", m_chanid);
4386  query.bindValue(":STARTTIME", m_recstartts);
4387  }
4388  else
4389  {
4390  return;
4391  }
4392  if (!query.exec())
4393  {
4394  MythDB::DBError("QueryMarkup markup data", query);
4395  return;
4396  }
4397  while (query.next())
4398  {
4399  int type = query.value(0).toInt();
4400  uint64_t frame = query.value(1).toLongLong();
4401  uint64_t data = 0;
4402  bool isDataNull = query.value(2).isNull();
4403  if (!isDataNull)
4404  data = query.value(2).toLongLong();
4405  mapMark.append(MarkupEntry(type, frame, data, isDataNull));
4406  }
4407 
4408  // Get the seektable
4409  if (IsVideo())
4410  {
4411  query.prepare("SELECT type, mark, offset FROM filemarkup"
4412  " WHERE filename = :PATH"
4413  " AND type IN (:KEYFRAME,:DURATION)"
4414  " ORDER BY mark, type;");
4416  query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4417  query.bindValue(":DURATION", MARK_DURATION_MS);
4418  }
4419  else if (IsRecording())
4420  {
4421  query.prepare("SELECT type, mark, offset FROM recordedseek"
4422  " WHERE chanid = :CHANID"
4423  " AND STARTTIME = :STARTTIME"
4424  " ORDER BY mark, type");
4425  query.bindValue(":CHANID", m_chanid);
4426  query.bindValue(":STARTTIME", m_recstartts);
4427  }
4428  if (!query.exec())
4429  {
4430  MythDB::DBError("QueryMarkup seektable data", query);
4431  return;
4432  }
4433  while (query.next())
4434  {
4435  int type = query.value(0).toInt();
4436  uint64_t frame = query.value(1).toLongLong();
4437  uint64_t data = 0;
4438  bool isDataNull = query.value(2).isNull();
4439  if (!isDataNull)
4440  data = query.value(2).toLongLong();
4441  mapSeek.append(MarkupEntry(type, frame, data, isDataNull));
4442  }
4443 }
4444 
4445 void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
4446  const QVector<MarkupEntry> &mapSeek) const
4447 {
4448  MSqlQuery query(MSqlQuery::InitCon());
4449  if (IsVideo())
4450  {
4452  if (mapMark.isEmpty())
4453  {
4454  LOG(VB_GENERAL, LOG_INFO,
4455  QString("No mark entries in input, "
4456  "not removing marks from DB"));
4457  }
4458  else
4459  {
4460  query.prepare("DELETE FROM filemarkup"
4461  " WHERE filename = :PATH"
4462  " AND type NOT IN (:KEYFRAME,:DURATION)");
4463  query.bindValue(":PATH", path);
4464  query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4465  query.bindValue(":DURATION", MARK_DURATION_MS);
4466  if (!query.exec())
4467  {
4468  MythDB::DBError("SaveMarkup seektable data", query);
4469  return;
4470  }
4471  for (int i = 0; i < mapMark.size(); ++i)
4472  {
4473  const MarkupEntry &entry = mapMark[i];
4474  if (entry.type == MARK_DURATION_MS)
4475  continue;
4476  if (entry.isDataNull)
4477  {
4478  query.prepare("INSERT INTO filemarkup"
4479  " (filename,type,mark)"
4480  " VALUES (:PATH,:TYPE,:MARK)");
4481  }
4482  else
4483  {
4484  query.prepare("INSERT INTO filemarkup"
4485  " (filename,type,mark,offset)"
4486  " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4487  query.bindValue(":OFFSET", (quint64)entry.data);
4488  }
4489  query.bindValue(":PATH", path);
4490  query.bindValue(":TYPE", entry.type);
4491  query.bindValue(":MARK", (quint64)entry.frame);
4492  if (!query.exec())
4493  {
4494  MythDB::DBError("SaveMarkup seektable data", query);
4495  return;
4496  }
4497  }
4498  }
4499  if (mapSeek.isEmpty())
4500  {
4501  LOG(VB_GENERAL, LOG_INFO,
4502  QString("No seek entries in input, "
4503  "not removing marks from DB"));
4504  }
4505  else
4506  {
4507  query.prepare("DELETE FROM filemarkup"
4508  " WHERE filename = :PATH"
4509  " AND type IN (:KEYFRAME,:DURATION)");
4510  query.bindValue(":PATH", path);
4511  query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4512  query.bindValue(":DURATION", MARK_DURATION_MS);
4513  if (!query.exec())
4514  {
4515  MythDB::DBError("SaveMarkup seektable data", query);
4516  return;
4517  }
4518  for (int i = 0; i < mapSeek.size(); ++i)
4519  {
4520  if (i > 0 && (i % 1000 == 0))
4521  LOG(VB_GENERAL, LOG_INFO,
4522  QString("Inserted %1 of %2 records")
4523  .arg(i).arg(mapSeek.size()));
4524  const MarkupEntry &entry = mapSeek[i];
4525  query.prepare("INSERT INTO filemarkup"
4526  " (filename,type,mark,offset)"
4527  " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4528  query.bindValue(":PATH", path);
4529  query.bindValue(":TYPE", entry.type);
4530  query.bindValue(":MARK", (quint64)entry.frame);
4531  query.bindValue(":OFFSET", (quint64)entry.data);
4532  if (!query.exec())
4533  {
4534  MythDB::DBError("SaveMarkup seektable data", query);
4535  return;
4536  }
4537  }
4538  }
4539  }
4540  else if (IsRecording())
4541  {
4542  if (mapMark.isEmpty())
4543  {
4544  LOG(VB_GENERAL, LOG_INFO,
4545  QString("No mark entries in input, "
4546  "not removing marks from DB"));
4547  }
4548  else
4549  {
4550  query.prepare("DELETE FROM recordedmarkup"
4551  " WHERE chanid = :CHANID"
4552  " AND starttime = :STARTTIME");
4553  query.bindValue(":CHANID", m_chanid);
4554  query.bindValue(":STARTTIME", m_recstartts);
4555  if (!query.exec())
4556  {
4557  MythDB::DBError("SaveMarkup seektable data", query);
4558  return;
4559  }
4560  for (int i = 0; i < mapMark.size(); ++i)
4561  {
4562  const MarkupEntry &entry = mapMark[i];
4563  if (entry.isDataNull)
4564  {
4565  query.prepare("INSERT INTO recordedmarkup"
4566  " (chanid,starttime,type,mark)"
4567  " VALUES (:CHANID,:STARTTIME,:TYPE,:MARK)");
4568  }
4569  else
4570  {
4571  query.prepare("INSERT INTO recordedmarkup"
4572  " (chanid,starttime,type,mark,data)"
4573  " VALUES (:CHANID,:STARTTIME,"
4574  " :TYPE,:MARK,:OFFSET)");
4575  query.bindValue(":OFFSET", (quint64)entry.data);
4576  }
4577  query.bindValue(":CHANID", m_chanid);
4578  query.bindValue(":STARTTIME", m_recstartts);
4579  query.bindValue(":TYPE", entry.type);
4580  query.bindValue(":MARK", (quint64)entry.frame);
4581  if (!query.exec())
4582  {
4583  MythDB::DBError("SaveMarkup seektable data", query);
4584  return;
4585  }
4586  }
4587  }
4588  if (mapSeek.isEmpty())
4589  {
4590  LOG(VB_GENERAL, LOG_INFO,
4591  QString("No seek entries in input, "
4592  "not removing marks from DB"));
4593  }
4594  else
4595  {
4596  query.prepare("DELETE FROM recordedseek"
4597  " WHERE chanid = :CHANID"
4598  " AND starttime = :STARTTIME");
4599  query.bindValue(":CHANID", m_chanid);
4600  query.bindValue(":STARTTIME", m_recstartts);
4601  if (!query.exec())
4602  {
4603  MythDB::DBError("SaveMarkup seektable data", query);
4604  return;
4605  }
4606  for (int i = 0; i < mapSeek.size(); ++i)
4607  {
4608  if (i > 0 && (i % 1000 == 0))
4609  LOG(VB_GENERAL, LOG_INFO,
4610  QString("Inserted %1 of %2 records")
4611  .arg(i).arg(mapSeek.size()));
4612  const MarkupEntry &entry = mapSeek[i];
4613  query.prepare("INSERT INTO recordedseek"
4614  " (chanid,starttime,type,mark,offset)"
4615  " VALUES (:CHANID,:STARTTIME,"
4616  " :TYPE,:MARK,:OFFSET)");
4617  query.bindValue(":CHANID", m_chanid);
4618  query.bindValue(":STARTTIME", m_recstartts);
4619  query.bindValue(":TYPE", entry.type);
4620  query.bindValue(":MARK", (quint64)entry.frame);
4621  query.bindValue(":OFFSET", (quint64)entry.data);
4622  if (!query.exec())
4623  {
4624  MythDB::DBError("SaveMarkup seektable data", query);
4625  return;
4626  }
4627  }
4628  }
4629  }
4630 }
4631 
4632 void ProgramInfo::SaveVideoProperties(uint mask, uint video_property_flags)
4633 {
4634  MSqlQuery query(MSqlQuery::InitCon());
4635 
4636  LOG(VB_RECORD, LOG_INFO,
4637  QString("SaveVideoProperties(0x%1, 0x%2)")
4638  .arg(mask,2,16,QChar('0')).arg(video_property_flags,2,16,QChar('0')));
4639 
4640  query.prepare(
4641  "UPDATE recordedprogram "
4642  "SET videoprop = ((videoprop+0) & :OTHERFLAGS) | :FLAGS "
4643  "WHERE chanid = :CHANID AND starttime = :STARTTIME");
4644 
4645  query.bindValue(":OTHERFLAGS", ~mask);
4646  query.bindValue(":FLAGS", video_property_flags);
4647  query.bindValue(":CHANID", m_chanid);
4648  query.bindValue(":STARTTIME", m_startts);
4649  if (!query.exec())
4650  {
4651  MythDB::DBError("SaveVideoProperties", query);
4652  return;
4653  }
4654 
4655  uint videoproperties = GetVideoProperties();
4656  videoproperties &= ~mask;
4657  videoproperties |= video_property_flags;
4659  m_properties |= videoproperties << kVideoPropertyOffset;
4660 
4661  SendUpdateEvent();
4662 }
4663 
4674 QString ProgramInfo::ChannelText(const QString &format) const
4675 {
4676  QString chan(format);
4677  chan.replace("<num>", m_chanstr)
4678  .replace("<sign>", m_chansign)
4679  .replace("<name>", m_channame);
4680  return chan;
4681 }
4682 
4684 {
4685 #ifdef DEBUG_IN_USE
4686  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UpdateInUseMark(%1) '%2'")
4687  .arg(force?"force":"no force").arg(m_inUseForWhat));
4688 #endif
4689 
4690  if (!IsRecording())
4691  return;
4692 
4693  if (m_inUseForWhat.isEmpty())
4694  return;
4695 
4696  if (force || m_lastInUseTime.secsTo(MythDate::current()) > 15 * 60)
4697  MarkAsInUse(true);
4698 }
4699 
4701 {
4702  MSqlQuery query(MSqlQuery::InitCon());
4703 
4704  query.prepare(
4705  "UPDATE recorded "
4706  "SET season = :SEASON, episode = :EPISODE "
4707  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
4708  "AND recordid = :RECORDID");
4709 
4710  query.bindValue(":SEASON", seas);
4711  query.bindValue(":EPISODE", ep);
4712  query.bindValue(":CHANID", m_chanid);
4713  query.bindValue(":STARTTIME", m_recstartts);
4714  query.bindValue(":RECORDID", m_recordid);
4715  if (!query.exec())
4716  {
4717  MythDB::DBError("SaveSeasonEpisode", query);
4718  return;
4719  }
4720 
4721  SendUpdateEvent();
4722 }
4723 
4724 void ProgramInfo::SaveInetRef(const QString &inet)
4725 {
4726  MSqlQuery query(MSqlQuery::InitCon());
4727 
4728  query.prepare(
4729  "UPDATE recorded "
4730  "SET inetref = :INETREF "
4731  "WHERE chanid = :CHANID AND starttime = :STARTTIME "
4732  "AND recordid = :RECORDID");
4733 
4734  query.bindValue(":INETREF", inet);
4735  query.bindValue(":CHANID", m_chanid);
4736  query.bindValue(":STARTTIME", m_recstartts);
4737  query.bindValue(":RECORDID", m_recordid);
4738  query.exec();
4739 
4740  SendUpdateEvent();
4741 }
4742 
4750 {
4751  if (IsLocal() && QFileInfo(m_pathname).isReadable())
4752  return true;
4753 
4754  if (!IsMythStream())
4755  m_pathname = GetPlaybackURL(true, false);
4756 
4757  if (IsMythStream())
4758  return RemoteCheckFile(this);
4759 
4760  if (IsLocal())
4761  return QFileInfo(m_pathname).isReadable();
4762 
4763  return false;
4764 }
4765 
4766 QString ProgramInfo::QueryRecordingGroupPassword(const QString &group)
4767 {
4768  QString result;
4769 
4770  MSqlQuery query(MSqlQuery::InitCon());
4771  query.prepare("SELECT password FROM recgroups "
4772  "WHERE recgroup = :GROUP");
4773  query.bindValue(":GROUP", group);
4774 
4775  if (query.exec() && query.next())
4776  result = query.value(0).toString();
4777 
4778  return(result);
4779 }
4780 
4784 {
4785  MSqlQuery query(MSqlQuery::InitCon());
4786  query.prepare("SELECT recgroup FROM recorded "
4787  "WHERE chanid = :CHANID AND "
4788  " starttime = :START");
4789  query.bindValue(":START", m_recstartts);
4790  query.bindValue(":CHANID", m_chanid);
4791 
4792  QString grp = m_recgroup;
4793  if (query.exec() && query.next())
4794  grp = query.value(0).toString();
4795 
4796  return grp;
4797 }
4798 
4800 {
4801  MSqlQuery query(MSqlQuery::InitCon());
4802  query.prepare("SELECT transcoder FROM recorded "
4803  "WHERE chanid = :CHANID AND "
4804  " starttime = :START");
4805  query.bindValue(":CHANID", m_chanid);
4806  query.bindValue(":START", m_recstartts);
4807 
4808  if (query.exec() && query.next())
4809  return query.value(0).toUInt();
4810 
4811  return 0;
4812 }
4813 
4820 {
4821  if (!IsLocal())
4822  {
4823  if (!gCoreContext->IsBackend())
4824  return "";
4825 
4826  QString path = GetPlaybackURL(false, true);
4827  if (path.startsWith("/"))
4828  {
4829  QFileInfo testFile(path);
4830  return testFile.path();
4831  }
4832 
4833  return "";
4834  }
4835 
4836  QFileInfo testFile(m_pathname);
4837  if (testFile.exists() || (gCoreContext->GetHostName() == m_hostname))
4838  {
4839  // we may be recording this file and it may not exist yet so we need
4840  // to do some checking to see what is in pathname
4841  if (testFile.exists())
4842  {
4843  if (testFile.isSymLink())
4844  testFile.setFile(getSymlinkTarget(m_pathname));
4845 
4846  if (testFile.isFile())
4847  return testFile.path();
4848  if (testFile.isDir())
4849  return testFile.filePath();
4850  }
4851  else
4852  {
4853  testFile.setFile(testFile.absolutePath());
4854  if (testFile.exists())
4855  {
4856  if (testFile.isSymLink())
4857  testFile.setFile(getSymlinkTarget(testFile.path()));
4858 
4859  if (testFile.isDir())
4860  return testFile.filePath();
4861  }
4862  }
4863  }
4864 
4865  return "";
4866 }
4867 
4868 #include <cassert>
4875 void ProgramInfo::MarkAsInUse(bool inuse, const QString& usedFor)
4876 {
4877  if (!IsRecording())
4878  return;
4879 
4880  bool notifyOfChange = false;
4881 
4882  if (inuse &&
4883  (m_inUseForWhat.isEmpty() ||
4884  (!usedFor.isEmpty() && usedFor != m_inUseForWhat)))
4885  {
4886  if (!usedFor.isEmpty())
4887  {
4888 
4889 #ifdef DEBUG_IN_USE
4890  if (!m_inUseForWhat.isEmpty())
4891  {
4892  LOG(VB_GENERAL, LOG_INFO, LOC +
4893  QString("MarkAsInUse(true, '%1'->'%2')")
4894  .arg(m_inUseForWhat).arg(usedFor) +
4895  " -- use has changed");
4896  }
4897  else
4898  {
4899  LOG(VB_GENERAL, LOG_INFO, LOC +
4900  QString("MarkAsInUse(true, ''->'%1')").arg(usedFor));
4901  }
4902 #endif // DEBUG_IN_USE
4903 
4904  m_inUseForWhat = usedFor;
4905  }
4906  else if (m_inUseForWhat.isEmpty())
4907  {
4908  QString oldInUseForWhat = m_inUseForWhat;
4909  m_inUseForWhat = QString("%1 [%2]")
4910  .arg(QObject::tr("Unknown")).arg(getpid());
4911  LOG(VB_GENERAL, LOG_WARNING, LOC +
4912  QString("MarkAsInUse(true, ''->'%1')").arg(m_inUseForWhat) +
4913  " -- use was not explicitly set");
4914  }
4915 
4916  notifyOfChange = true;
4917  }
4918 
4919  if (!inuse && !m_inUseForWhat.isEmpty() && usedFor != m_inUseForWhat)
4920  {
4921  LOG(VB_GENERAL, LOG_WARNING, LOC +
4922  QString("MarkAsInUse(false, '%1'->'%2')")
4923  .arg(m_inUseForWhat).arg(usedFor) +
4924  " -- use has changed since first setting as in use.");
4925  }
4926 #ifdef DEBUG_IN_USE
4927  else if (!inuse)
4928  {
4929  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("MarkAsInUse(false, '%1')")
4930  .arg(m_inUseForWhat));
4931  }
4932 #endif // DEBUG_IN_USE
4933 
4934  if (!inuse && m_inUseForWhat.isEmpty())
4935  m_inUseForWhat = usedFor;
4936 
4937  if (!inuse && m_inUseForWhat.isEmpty())
4938  {
4939  LOG(VB_GENERAL, LOG_WARNING, LOC +
4940  "MarkAsInUse requires a key to delete in use mark");
4941  return; // can't delete if we don't have a key
4942  }
4943 
4944  if (!inuse)
4945  {
4946  MSqlQuery query(MSqlQuery::InitCon());
4947  query.prepare(
4948  "DELETE FROM inuseprograms "
4949  "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
4950  " hostname = :HOSTNAME AND recusage = :RECUSAGE");
4951  query.bindValue(":CHANID", m_chanid);
4952  query.bindValue(":STARTTIME", m_recstartts);
4953  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
4954  query.bindValue(":RECUSAGE", m_inUseForWhat);
4955 
4956  if (!query.exec())
4957  MythDB::DBError("MarkAsInUse -- delete", query);
4958 
4959  m_inUseForWhat.clear();
4960  m_lastInUseTime = MythDate::current(true).addSecs(-4 * 60 * 60);
4961  SendUpdateEvent();
4962  return;
4963  }
4964 
4965  if (m_pathname == GetBasename())
4966  m_pathname = GetPlaybackURL(false, true);
4967 
4968  QString recDir = DiscoverRecordingDirectory();
4969 
4970  QDateTime inUseTime = MythDate::current(true);
4971 
4972  MSqlQuery query(MSqlQuery::InitCon());
4973  query.prepare(
4974  "SELECT count(*) "
4975  "FROM inuseprograms "
4976  "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
4977  " hostname = :HOSTNAME AND recusage = :RECUSAGE");
4978  query.bindValue(":CHANID", m_chanid);
4979  query.bindValue(":STARTTIME", m_recstartts);
4980  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
4981  query.bindValue(":RECUSAGE", m_inUseForWhat);
4982 
4983  if (!query.exec())
4984  {
4985  MythDB::DBError("MarkAsInUse -- select", query);
4986  }
4987  else if (!query.next())
4988  {
4989  LOG(VB_GENERAL, LOG_ERR, LOC + "MarkAsInUse -- select query failed");
4990  }
4991  else if (query.value(0).toBool())
4992  {
4993  query.prepare(
4994  "UPDATE inuseprograms "
4995  "SET lastupdatetime = :UPDATETIME "
4996  "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
4997  " hostname = :HOSTNAME AND recusage = :RECUSAGE");
4998  query.bindValue(":CHANID", m_chanid);
4999  query.bindValue(":STARTTIME", m_recstartts);
5000  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5001  query.bindValue(":RECUSAGE", m_inUseForWhat);
5002  query.bindValue(":UPDATETIME", inUseTime);
5003 
5004  if (!query.exec())
5005  MythDB::DBError("MarkAsInUse -- update failed", query);
5006  else
5007  m_lastInUseTime = inUseTime;
5008  }
5009  else // if (!query.value(0).toUInt())
5010  {
5011  query.prepare(
5012  "INSERT INTO inuseprograms "
5013  " (chanid, starttime, recusage, hostname, "
5014  " lastupdatetime, rechost, recdir) "
5015  "VALUES "
5016  " (:CHANID, :STARTTIME, :RECUSAGE, :HOSTNAME, "
5017  " :UPDATETIME, :RECHOST, :RECDIR)");
5018  query.bindValue(":CHANID", m_chanid);
5019  query.bindValue(":STARTTIME", m_recstartts);
5020  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5021  query.bindValue(":RECUSAGE", m_inUseForWhat);
5022  query.bindValue(":UPDATETIME", inUseTime);
5023  query.bindValue(":RECHOST",
5024  m_hostname.isEmpty() ? gCoreContext->GetHostName()
5025  : m_hostname);
5026  query.bindValue(":RECDIR", recDir);
5027 
5028  if (!query.exec())
5029  MythDB::DBError("MarkAsInUse -- insert failed", query);
5030  else
5031  m_lastInUseTime = inUseTime;
5032  }
5033 
5034  if (!notifyOfChange)
5035  return;
5036 
5037  // Let others know we changed status
5038  QDateTime oneHourAgo = MythDate::current().addSecs(-61 * 60);
5039  query.prepare("SELECT DISTINCT recusage "
5040  "FROM inuseprograms "
5041  "WHERE lastupdatetime >= :ONEHOURAGO AND "
5042  " chanid = :CHANID AND "
5043  " starttime = :STARTTIME");
5044  query.bindValue(":CHANID", m_chanid);
5045  query.bindValue(":STARTTIME", m_recstartts);
5046  query.bindValue(":ONEHOURAGO", oneHourAgo);
5047  if (!query.exec())
5048  return; // not safe to send update event...
5049 
5051  while (query.next())
5052  {
5053  QString inUseForWhat = query.value(0).toString();
5054  if (inUseForWhat.contains(kPlayerInUseID))
5056  else if (inUseForWhat == kRecorderInUseID)
5058  else
5060  }
5061  SendUpdateEvent();
5062 }
5063 
5069 bool ProgramInfo::QueryTuningInfo(QString &channum, QString &input) const
5070 {
5071  channum.clear();
5072  input.clear();
5073  MSqlQuery query(MSqlQuery::InitCon());
5074 
5075  query.prepare("SELECT channel.channum, capturecard.inputname "
5076  "FROM channel, capturecard "
5077  "WHERE channel.chanid = :CHANID AND "
5078  " capturecard.sourceid = :SOURCEID AND "
5079  " capturecard.cardid = :INPUTID");
5080  query.bindValue(":CHANID", m_chanid);
5081  query.bindValue(":SOURCEID", m_sourceid);
5082  query.bindValue(":INPUTID", m_inputid);
5083 
5084  if (query.exec() && query.next())
5085  {
5086  channum = query.value(0).toString();
5087  input = query.value(1).toString();
5088  return true;
5089  }
5090  MythDB::DBError("GetChannel(ProgInfo...)", query);
5091  return false;
5092 }
5093 
5094 static int init_tr(void)
5095 {
5096  static bool done = false;
5097  static QMutex init_tr_lock;
5098  QMutexLocker locker(&init_tr_lock);
5099  if (done)
5100  return 1;
5101 
5102  QString rec_profile_names =
5103  QObject::tr("Default", "Recording Profile Default") +
5104  QObject::tr("High Quality", "Recording Profile High Quality") +
5105  QObject::tr("Live TV", "Recording Profile Live TV") +
5106  QObject::tr("Low Quality", "Recording Profile Low Quality") +
5107  QObject::tr("Medium Quality", "Recording Profile Medium Quality") +
5108  QObject::tr("MPEG-2", "Recording Profile MPEG-2") +
5109  QObject::tr("RTjpeg/MPEG-4", "Recording Profile RTjpeg/MPEG-4");
5110 
5111 
5112  QString rec_profile_groups =
5113  QObject::tr("CRC IP Recorders",
5114  "Recording Profile Group Name") +
5115  QObject::tr("FireWire Input",
5116  "Recording Profile Group Name") +
5117  QObject::tr("Freebox Input",
5118  "Recording Profile Group Name") +
5119  QObject::tr("Hardware DVB Encoders",
5120  "Recording Profile Group Name") +
5121  QObject::tr("Hardware HDTV",
5122  "Recording Profile Group Name") +
5123  QObject::tr("Hardware MJPEG Encoders (Matrox G200-TV, Miro DC10, etc)",
5124  "Recording Profile Group Name") +
5125  QObject::tr("HD-PVR Recorders",
5126  "Recording Profile Group Name") +
5127  QObject::tr("HDHomeRun Recorders",
5128  "Recording Profile Group Name") +
5129  QObject::tr("MPEG-2 Encoders (PVR-x50, PVR-500)",
5130  "Recording Profile Group Name") +
5131  QObject::tr("Software Encoders (V4L based)",
5132  "Recording Profile Group Name") +
5133  QObject::tr("Transcoders",
5134  "Recording Profile Group Name") +
5135  QObject::tr("USB MPEG-4 Encoder (Plextor ConvertX, etc)",
5136  "Recording Profile Group Name") +
5137  QObject::tr("V4L2 Encoders",
5138  "Recording Profile Group Name");
5139 
5140  QString display_rec_groups =
5141  QObject::tr("All Programs", "Recording Group All Programs") +
5142  QObject::tr("All", "Recording Group All Programs -- short form") +
5143  QObject::tr("Live TV", "Recording Group Live TV") +
5144  QObject::tr("Default", "Recording Group Default") +
5145  QObject::tr("Deleted", "Recording Group Deleted");
5146 
5147  QString special_program_groups =
5148  QObject::tr("All Programs - %1",
5149  "Show all programs from a specific recording group");
5150 
5151  QString storage_groups =
5152  QObject::tr("Default", "Storage Group Name") +
5153  QObject::tr("Live TV", "Storage Group Name") +
5154  QObject::tr("Thumbnails", "Storage Group Name") +
5155  QObject::tr("DB Backups", "Storage Group Name");
5156 
5157  QString play_groups =
5158  QObject::tr("Default", "Playback Group Name");
5159 
5160  done = true;
5161  return (rec_profile_names.length() +
5162  rec_profile_groups.length() +
5163  display_rec_groups.length() +
5164  special_program_groups.length() +
5165  storage_groups.length() +
5166  play_groups.length());
5167 }
5168 
5170 {
5171  QMutexLocker locker(&s_staticDataLock);
5172  if (!s_updater)
5173  s_updater = new ProgramInfoUpdater();
5174  return 1;
5175 }
5176 
5178 QString ProgramInfo::i18n(const QString &msg)
5179 {
5180  init_tr();
5181  QByteArray msg_arr = msg.toLatin1();
5182  QString msg_i18n = QObject::tr(msg_arr.constData());
5183  QByteArray msg_i18n_arr = msg_i18n.toLatin1();
5184  return (msg_arr == msg_i18n_arr) ? msg : msg_i18n;
5185 }
5186 
5194 {
5195  QString pburl = GetPlaybackURL(false, true);
5196  if (pburl.startsWith("myth://"))
5197  {
5198  str.replace(QString("%DIR%"), pburl);
5199  }
5200  else
5201  {
5202  QFileInfo dirInfo(pburl);
5203  str.replace(QString("%DIR%"), dirInfo.path());
5204  }
5205 
5206  str.replace(QString("%FILE%"), GetBasename());
5207  str.replace(QString("%TITLE%"), m_title);
5208  str.replace(QString("%SUBTITLE%"), m_subtitle);
5209  str.replace(QString("%SEASON%"), QString::number(m_season));
5210  str.replace(QString("%EPISODE%"), QString::number(m_episode));
5211  str.replace(QString("%TOTALEPISODES%"), QString::number(m_totalepisodes));
5212  str.replace(QString("%SYNDICATEDEPISODE%"), m_syndicatedepisode);
5213  str.replace(QString("%DESCRIPTION%"), m_description);
5214  str.replace(QString("%HOSTNAME%"), m_hostname);
5215  str.replace(QString("%CATEGORY%"), m_category);
5216  str.replace(QString("%RECGROUP%"), m_recgroup);
5217  str.replace(QString("%PLAYGROUP%"), m_playgroup);
5218  str.replace(QString("%CHANID%"), QString::number(m_chanid));
5219  str.replace(QString("%INETREF%"), m_inetref);
5220  str.replace(QString("%PARTNUMBER%"), QString::number(m_partnumber));
5221  str.replace(QString("%PARTTOTAL%"), QString::number(m_parttotal));
5222  str.replace(QString("%ORIGINALAIRDATE%"),
5223  m_originalAirDate.toString(Qt::ISODate));
5224  static const char *time_str[] =
5225  { "STARTTIME", "ENDTIME", "PROGSTART", "PROGEND", };
5226  const QDateTime *time_dtr[] =
5228  for (size_t i = 0; i < sizeof(time_str)/sizeof(char*); i++)
5229  {
5230  str.replace(QString("%%1%").arg(time_str[i]),
5231  (time_dtr[i]->toLocalTime()).toString("yyyyMMddhhmmss"));
5232  str.replace(QString("%%1ISO%").arg(time_str[i]),
5233  (time_dtr[i]->toLocalTime()).toString(Qt::ISODate));
5234  str.replace(QString("%%1UTC%").arg(time_str[i]),
5235  time_dtr[i]->toString("yyyyMMddhhmmss"));
5236  str.replace(QString("%%1ISOUTC%").arg(time_str[i]),
5237  time_dtr[i]->toString(Qt::ISODate));
5238  }
5239  str.replace(QString("%RECORDEDID%"), QString::number(m_recordedid));
5240 }
5241 
5242 QMap<QString,uint32_t> ProgramInfo::QueryInUseMap(void)
5243 {
5244  QMap<QString, uint32_t> inUseMap;
5245  QDateTime oneHourAgo = MythDate::current().addSecs(-61 * 60);
5246 
5247  MSqlQuery query(MSqlQuery::InitCon());
5248 
5249  query.prepare("SELECT DISTINCT chanid, starttime, recusage "
5250  "FROM inuseprograms WHERE lastupdatetime >= :ONEHOURAGO");
5251  query.bindValue(":ONEHOURAGO", oneHourAgo);
5252 
5253  if (!query.exec())
5254  return inUseMap;
5255 
5256  while (query.next())
5257  {
5258  QString inUseKey = ProgramInfo::MakeUniqueKey(
5259  query.value(0).toUInt(),
5260  MythDate::as_utc(query.value(1).toDateTime()));
5261 
5262  QString inUseForWhat = query.value(2).toString();
5263 
5264  if (!inUseMap.contains(inUseKey))
5265  inUseMap[inUseKey] = 0;
5266 
5267  if (inUseForWhat.contains(kPlayerInUseID))
5268  inUseMap[inUseKey] |= FL_INUSEPLAYING;
5269  else if (inUseForWhat == kRecorderInUseID)
5270  inUseMap[inUseKey] |= FL_INUSERECORDING;
5271  else
5272  inUseMap[inUseKey] |= FL_INUSEOTHER;
5273  }
5274 
5275  return inUseMap;
5276 }
5277 
5278 QMap<QString,bool> ProgramInfo::QueryJobsRunning(int type)
5279 {
5280  QMap<QString,bool> is_job_running;
5281 
5282  MSqlQuery query(MSqlQuery::InitCon());
5283  query.prepare("SELECT chanid, starttime, status FROM jobqueue "
5284  "WHERE type = :TYPE");
5285  query.bindValue(":TYPE", type);
5286  if (!query.exec())
5287  return is_job_running;
5288 
5289  while (query.next())
5290  {
5291  uint chanid = query.value(0).toUInt();
5292  QDateTime recstartts = MythDate::as_utc(query.value(1).toDateTime());
5293  int tmpStatus = query.value(2).toInt();
5294  if ((tmpStatus != /*JOB_UNKNOWN*/ 0x0000) &&
5295  (tmpStatus != /*JOB_QUEUED*/ 0x0001) &&
5296  (!(tmpStatus & /*JOB_DONE*/ 0x0100)))
5297  {
5298  is_job_running[
5299  ProgramInfo::MakeUniqueKey(chanid,recstartts)] = true;
5300  }
5301  }
5302 
5303  return is_job_running;
5304 }
5305 
5307  const QString &tmptable, int recordid)
5308 {
5309  QStringList slist;
5310 
5312  if (sched && tmptable.isEmpty())
5313  {
5314  sched->GetAllPending(slist);
5315  return slist;
5316  }
5317 
5318  if (sched)
5319  {
5320  LOG(VB_GENERAL, LOG_ERR,
5321  "Called from master backend\n\t\t\t"
5322  "with recordid or tmptable, this is not currently supported");
5323  return slist;
5324  }
5325 
5326  slist.push_back(
5327  (tmptable.isEmpty()) ?
5328  QString("QUERY_GETALLPENDING") :
5329  QString("QUERY_GETALLPENDING %1 %2").arg(tmptable).arg(recordid));
5330 
5331  if (!gCoreContext->SendReceiveStringList(slist) || slist.size() < 2)
5332  {
5333  LOG(VB_GENERAL, LOG_ALERT,
5334  "LoadFromScheduler(): Error querying master.");
5335  slist.clear();
5336  }
5337 
5338  return slist;
5339 }
5340 
5341 // NOTE: This may look ugly, and in some ways it is, but it's designed this
5342 // way for with two purposes in mind - Consistent behaviour and speed
5343 // So if you do make changes then carefully test that it doesn't result
5344 // in any regressions in total speed of execution or adversely affect the
5345 // results returned for any of it's users.
5346 static bool FromProgramQuery(const QString &sql, const MSqlBindings &bindings,
5347  MSqlQuery &query, const uint &start,
5348  const uint &limit, uint &count)
5349 {
5350  count = 0;
5351 
5352  QString columns = QString(
5353  "program.chanid, program.starttime, program.endtime, " // 0-2
5354  "program.title, program.subtitle, program.description, " // 3-5
5355  "program.category, channel.channum, channel.callsign, " // 6-8
5356  "channel.name, program.previouslyshown, channel.commmethod, " // 9-11
5357  "channel.outputfilters, program.seriesid, program.programid, " // 12-14
5358  "program.airdate, program.stars, program.originalairdate, " // 15-17
5359  "program.category_type, oldrecstatus.recordid, " // 18-19
5360  "oldrecstatus.rectype, oldrecstatus.recstatus, " // 20-21
5361  "oldrecstatus.findid, program.videoprop+0, program.audioprop+0, " // 22-24
5362  "program.subtitletypes+0, program.syndicatedepisodenumber, " // 25-26
5363  "program.partnumber, program.parttotal, " // 27-28
5364  "program.season, program.episode, program.totalepisodes "); // 29-31
5365 
5366  QString querystr = QString(
5367  "SELECT %1 "
5368  "FROM program "
5369  "LEFT JOIN channel ON program.chanid = channel.chanid "
5370  "LEFT JOIN oldrecorded AS oldrecstatus ON "
5371  " oldrecstatus.future = 0 AND "
5372  " program.title = oldrecstatus.title AND "
5373  " channel.callsign = oldrecstatus.station AND "
5374  " program.starttime = oldrecstatus.starttime "
5375  ) + sql;
5376 
5377  // If a limit arg was given then append the LIMIT, otherwise set a hard
5378  // limit of 20000.
5379  if (limit > 0)
5380  querystr += QString("LIMIT %1 ").arg(limit);
5381  else if (!querystr.contains(" LIMIT "))
5382  querystr += " LIMIT 20000 "; // For performance reasons we have to have an upper limit
5383 
5384  MSqlBindings::const_iterator it;
5385  // Some end users need to know the total number of matching records,
5386  // irrespective of any LIMIT clause
5387  //
5388  // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
5389  // with any GROUP BY clauses. COUNT is marginally faster but not enough to
5390  // matter
5391  //
5392  // It's considerably faster in my tests to do a separate query which returns
5393  // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
5394  // with the full query. Unfortunate but true.
5395  //
5396  // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
5397  // Same query with SQL_CALC_FOUND_ROWS - 1920ms
5398  // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
5399  // Total to fetch both the count and the data = 590ms vs 1920ms
5400  // Therefore two queries is 1.4 seconds faster than one query.
5401  if (start > 0 || limit > 0)
5402  {
5403  QString countStr = querystr.arg("SQL_CALC_FOUND_ROWS program.chanid");
5404  query.prepare(countStr);
5405  for (it = bindings.begin(); it != bindings.end(); ++it)
5406  {
5407  if (countStr.contains(it.key()))
5408  query.bindValue(it.key(), it.value());
5409  }
5410 
5411  if (!query.exec())
5412  {
5413  MythDB::DBError("LoadFromProgramQuery", query);
5414  return false;
5415  }
5416 
5417  if (query.exec("SELECT FOUND_ROWS()") && query.next())
5418  count = query.value(0).toUInt();
5419  }
5420 
5421  if (start > 0)
5422  querystr += QString("OFFSET %1 ").arg(start);
5423 
5424  querystr = querystr.arg(columns);
5425  query.prepare(querystr);
5426  for (it = bindings.begin(); it != bindings.end(); ++it)
5427  {
5428  if (querystr.contains(it.key()))
5429  query.bindValue(it.key(), it.value());
5430  }
5431 
5432  if (!query.exec())
5433  {
5434  MythDB::DBError("LoadFromProgramQuery", query);
5435  return false;
5436  }
5437 
5438  return true;
5439 }
5440 
5441 bool LoadFromProgram(ProgramList &destination, const QString &where,
5442  const QString &groupBy, const QString &orderBy,
5443  const MSqlBindings &bindings, const ProgramList &schedList)
5444 {
5445  uint count = 0;
5446 
5447  QString queryStr = "";
5448 
5449  if (!where.isEmpty())
5450  queryStr.append(QString("WHERE %1 ").arg(where));
5451 
5452  if (!groupBy.isEmpty())
5453  queryStr.append(QString("GROUP BY %1 ").arg(groupBy));
5454 
5455  if (!orderBy.isEmpty())
5456  queryStr.append(QString("ORDER BY %1 ").arg(orderBy));
5457 
5458  // ------------------------------------------------------------------------
5459 
5460  return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0, count);
5461 }
5462 
5463 bool LoadFromProgram(ProgramList &destination,
5464  const QString &sql, const MSqlBindings &bindings,
5465  const ProgramList &schedList)
5466 {
5467  uint count;
5468 
5469  QString queryStr = sql;
5470  // ------------------------------------------------------------------------
5471  // FIXME: Remove the following. These all make assumptions about the content
5472  // of the sql passed in, they can end up breaking that query by
5473  // inserting a WHERE clause after a GROUP BY, or a GROUP BY after
5474  // an ORDER BY. These should be part of the sql passed in otherwise
5475  // the caller isn't getting what they asked for and to fix that they
5476  // are forced to include a GROUP BY, ORDER BY or WHERE that they
5477  // do not want
5478  //
5479  // See the new version of this method above. The plan is to convert existing
5480  // users of this method and have them supply all of their own data for the
5481  // queries (no defaults.)
5482 
5483  if (!queryStr.contains("WHERE"))
5484  queryStr += " WHERE visible != 0 ";
5485 
5486  // NOTE: Any GROUP BY clause with a LIMIT is slow, adding at least
5487  // a couple of seconds to the query execution time
5488 
5489  // TODO: This one seems to be dealing with eliminating duplicate channels (same
5490  // programming, different source), but using GROUP BY for that isn't very
5491  // efficient so another approach is required
5492  if (!queryStr.contains("GROUP BY"))
5493  queryStr += " GROUP BY program.starttime, channel.channum, "
5494  " channel.callsign, program.title ";
5495 
5496  if (!queryStr.contains("ORDER BY"))
5497  {
5498  queryStr += " ORDER BY program.starttime, ";
5499  QString chanorder =
5500  gCoreContext->GetSetting("ChannelOrdering", "channum");
5501  if (chanorder != "channum")
5502  queryStr += chanorder + " ";
5503  else // approximation which the DB can handle
5504  queryStr += "atsc_major_chan,atsc_minor_chan,channum,callsign ";
5505  }
5506 
5507  // ------------------------------------------------------------------------
5508 
5509  return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0, count);
5510 }
5511 
5512 bool LoadFromProgram( ProgramList &destination,
5513  const QString &sql, const MSqlBindings &bindings,
5514  const ProgramList &schedList,
5515  const uint &start, const uint &limit, uint &count)
5516 {
5517  destination.clear();
5518 
5519  if (sql.contains(" OFFSET", Qt::CaseInsensitive))
5520  LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains OFFSET "
5521  "clause, caller should be updated to use "
5522  "start parameter instead");
5523 
5524  if (sql.contains(" LIMIT", Qt::CaseInsensitive))
5525  LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains LIMIT "
5526  "clause, caller should be updated to use "
5527  "limit parameter instead");
5528 
5529  MSqlQuery query(MSqlQuery::InitCon());
5530  query.setForwardOnly(true);
5531  if (!FromProgramQuery(sql, bindings, query, start, limit, count))
5532  return false;
5533 
5534  if (count == 0)
5535  count = query.size();
5536 
5537  while (query.next())
5538  {
5539  destination.push_back(
5540  new ProgramInfo(
5541  query.value(3).toString(), // title
5542  QString(), // sortTitle
5543  query.value(4).toString(), // subtitle
5544  QString(), // sortSubtitle
5545  query.value(5).toString(), // description
5546  query.value(26).toString(), // syndicatedepisodenumber
5547  query.value(6).toString(), // category
5548 
5549  query.value(0).toUInt(), // chanid
5550  query.value(7).toString(), // channum
5551  query.value(8).toString(), // chansign
5552  query.value(9).toString(), // channame
5553  query.value(12).toString(), // chanplaybackfilters
5554 
5555  MythDate::as_utc(query.value(1).toDateTime()), // startts
5556  MythDate::as_utc(query.value(2).toDateTime()), // endts
5557  MythDate::as_utc(query.value(1).toDateTime()), // recstartts
5558  MythDate::as_utc(query.value(2).toDateTime()), // recendts
5559 
5560  query.value(13).toString(), // seriesid
5561  query.value(14).toString(), // programid
5562  string_to_myth_category_type(query.value(18).toString()), // catType
5563 
5564  query.value(16).toDouble(), // stars
5565  query.value(15).toUInt(), // year
5566  query.value(27).toUInt(), // partnumber
5567  query.value(28).toUInt(), // parttotal
5568  query.value(17).toDate(), // originalAirDate
5569  RecStatus::Type(query.value(21).toInt()), // recstatus
5570  query.value(19).toUInt(), // recordid
5571  RecordingType(query.value(20).toInt()), // rectype
5572  query.value(22).toUInt(), // findid
5573 
5574  query.value(11).toInt() == COMM_DETECT_COMMFREE, // commfree
5575  query.value(10).toBool(), // repeat
5576  query.value(23).toInt(), // videoprop
5577  query.value(24).toInt(), // audioprop
5578  query.value(25).toInt(), // subtitletypes
5579  query.value(29).toUInt(), // season
5580  query.value(30).toUInt(), // episode
5581  query.value(31).toUInt(), // totalepisodes
5582 
5583  schedList));
5584  }
5585 
5586  return true;
5587 }
5588 
5590  const QDateTime& starttime)
5591 {
5592  ProgramInfo *progInfo = nullptr;
5593 
5594  // Build add'l SQL statement for Program Listing
5595 
5596  MSqlBindings bindings;
5597  QString sSQL = "WHERE program.chanid = :ChanId "
5598  "AND program.starttime = :StartTime ";
5599 
5600  bindings[":ChanId" ] = chanid;
5601  bindings[":StartTime"] = starttime;
5602 
5603  // Get all Pending Scheduled Programs
5604 
5605  ProgramList schedList;
5606  bool hasConflicts;
5607  LoadFromScheduler(schedList, hasConflicts);
5608 
5609  // ----------------------------------------------------------------------
5610 
5611  ProgramList progList;
5612 
5613  LoadFromProgram( progList, sSQL, bindings, schedList );
5614 
5615  if (progList.empty())
5616  return progInfo;
5617 
5618  // progList is an Auto-delete deque, the object will be deleted with the
5619  // list, so we need to make a copy
5620  progInfo = new ProgramInfo(*(progList[0]));
5621 
5622  return progInfo;
5623 }
5624 
5626  ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
5627 {
5628  uint count = 0;
5629  return LoadFromOldRecorded(destination, sql, bindings, 0, 0, count);
5630 }
5631 
5632 bool LoadFromOldRecorded(ProgramList &destination, const QString &sql,
5633  const MSqlBindings &bindings,
5634  const uint &start, const uint &limit, uint &count)
5635 {
5636  destination.clear();
5637 
5638  MSqlQuery query(MSqlQuery::InitCon());
5639  query.setForwardOnly(true);
5640 
5641  QString columns =
5642  "oldrecorded.chanid, starttime, endtime, "
5643  "title, subtitle, description, season, episode, category, seriesid, "
5644  "programid, inetref, channel.channum, channel.callsign, "
5645  "channel.name, findid, rectype, recstatus, recordid, duplicate ";
5646 
5647  QString querystr =
5648  "SELECT %1 "
5649  "FROM oldrecorded "
5650  "LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
5651  "WHERE oldrecorded.future = 0 "
5652  + sql;
5653 
5654  bool hasLimit = querystr.contains(" LIMIT ",Qt::CaseInsensitive);
5655 
5656  // make sure the most recent rows are retrieved first in case
5657  // there are more than the limit to be set below
5658  if (!hasLimit && !querystr.contains(" ORDER ",Qt::CaseInsensitive))
5659  querystr += " ORDER BY starttime DESC ";
5660 
5661  // If a limit arg was given then append the LIMIT, otherwise set a hard
5662  // limit of 20000, which can be overridden by a setting
5663  if (limit > 0)
5664  querystr += QString("LIMIT %1 ").arg(limit);
5665  else if (!hasLimit)
5666  {
5667  // For performance reasons we have to have an upper limit
5668  int nLimit = gCoreContext->GetNumSetting("PrevRecLimit", 20000);
5669  // For sanity sake at least 100
5670  if (nLimit < 100)
5671  nLimit = 100;
5672  querystr += QString("LIMIT %1 ").arg(nLimit);
5673  }
5674 
5675  MSqlBindings::const_iterator it;
5676  // If count is non-zero then also return total number of matching records,
5677  // irrespective of any LIMIT clause
5678  //
5679  // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
5680  // with any GROUP BY clauses. COUNT is marginally faster but not enough to
5681  // matter
5682  //
5683  // It's considerably faster in my tests to do a separate query which returns
5684  // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
5685  // with the full query. Unfortunate but true.
5686  //
5687  // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
5688  // Same query with SQL_CALC_FOUND_ROWS - 1920ms
5689  // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
5690  // Total to fetch both the count and the data = 590ms vs 1920ms
5691  // Therefore two queries is 1.4 seconds faster than one query.
5692  if (count > 0 && (start > 0 || limit > 0))
5693  {
5694  QString countStr = querystr.arg("SQL_CALC_FOUND_ROWS oldrecorded.chanid");
5695  query.prepare(countStr);
5696  for (it = bindings.begin(); it != bindings.end(); ++it)
5697  {
5698  if (countStr.contains(it.key()))
5699  query.bindValue(it.key(), it.value());
5700  }
5701 
5702  if (!query.exec())
5703  {
5704  MythDB::DBError("LoadFromOldRecorded", query);
5705  return false;
5706  }
5707 
5708  if (query.exec("SELECT FOUND_ROWS()") && query.next())
5709  count = query.value(0).toUInt();
5710  }
5711 
5712  if (start > 0)
5713  querystr += QString("OFFSET %1 ").arg(start);
5714 
5715  querystr = querystr.arg(columns);
5716  query.prepare(querystr);
5717  for (it = bindings.begin(); it != bindings.end(); ++it)
5718  {
5719  if (querystr.contains(it.key()))
5720  query.bindValue(it.key(), it.value());
5721  }
5722 
5723  if (!query.exec())
5724  {
5725  MythDB::DBError("LoadFromOldRecorded", query);
5726  return false;
5727  }
5728 
5729  while (query.next())
5730  {
5731  uint chanid = query.value(0).toUInt();
5732  QString channum = QString("#%1").arg(chanid);
5733  QString chansign = channum;
5734  QString channame = channum;
5735  if (!query.value(12).toString().isEmpty())
5736  {
5737  channum = query.value(12).toString();
5738  chansign = query.value(13).toString();
5739  channame = query.value(14).toString();
5740  }
5741 
5742  destination.push_back(new ProgramInfo(
5743  query.value(3).toString(),
5744  QString(),
5745  query.value(4).toString(),
5746  QString(),
5747  query.value(5).toString(),
5748  query.value(6).toUInt(),
5749  query.value(7).toUInt(),
5750  query.value(8).toString(),
5751 
5752  chanid, channum, chansign, channame,
5753 
5754  query.value(9).toString(), query.value(10).toString(),
5755  query.value(11).toString(),
5756 
5757  MythDate::as_utc(query.value(1).toDateTime()),
5758  MythDate::as_utc(query.value(2).toDateTime()),
5759  MythDate::as_utc(query.value(1).toDateTime()),
5760  MythDate::as_utc(query.value(2).toDateTime()),
5761 
5762  RecStatus::Type(query.value(17).toInt()),
5763  query.value(18).toUInt(),
5764  RecordingType(query.value(16).toInt()),
5765  query.value(15).toUInt(),
5766 
5767  query.value(19).toBool()));
5768  }
5769 
5770  return true;
5771 }
5772 
5790  ProgramList &destination,
5791  bool possiblyInProgressRecordingsOnly,
5792  const QMap<QString,uint32_t> &inUseMap,
5793  const QMap<QString,bool> &isJobRunning,
5794  const QMap<QString, ProgramInfo*> &recMap,
5795  int sort,
5796  const QString &sortBy)
5797 {
5798  destination.clear();