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