MythTV  master
datadirect.cpp
Go to the documentation of this file.
1 #include <unistd.h>
2 #include <zlib.h>
3 #undef Z_NULL
4 #define Z_NULL nullptr
5 
6 // Qt headers
7 #include <QDir>
8 #include <QFileInfo>
9 #include <QByteArray>
10 #include <QNetworkReply>
11 #include <QAuthenticator>
12 
13 // MythTV headers
14 #include "datadirect.h"
15 #include "sourceutil.h"
16 #include "channelutil.h"
17 #include "frequencytables.h"
18 #include "listingsources.h"
19 #include "mythmiscutil.h"
20 #include "mythcontext.h"
21 #include "mythdb.h"
22 #include "mythlogging.h"
23 #include "mythversion.h"
24 #include "mythdate.h"
25 #include "dbutil.h"
26 #include "mythsystemlegacy.h"
27 #include "exitcodes.h"
28 #include "mythdownloadmanager.h"
29 #include "mythtvexp.h"
30 #include "mythdate.h"
31 
32 #define LOC QString("DataDirect: ")
33 
34 static QMutex user_agent_lock;
35 static QString user_agent;
36 
37 static QMutex lineup_type_lock;
38 static QMap<QString,uint> lineupid_to_srcid;
39 static QMap<uint,QString> srcid_to_type;
40 
41 static void set_lineup_type(const QString &lineupid, const QString &type);
42 static QString get_lineup_type(uint sourceid);
43 static QString get_setting(QString line, QString key);
44 static bool has_setting(QString line, QString key);
45 static QString html_escape(QString str);
46 static void get_atsc_stuff(QString channum, int freqid,
47  int &major, int &minor, long long &freq);
48 static QString process_dd_station(uint sourceid,
49  QString chan_major, QString chan_minor,
50  QString &tvformat, uint &freqid);
51 static uint update_channel_basic(uint sourceid, bool insert,
52  QString xmltvid, QString callsign,
53  QString name, uint freqid,
54  QString chan_major, QString chan_minor);
55 void authenticationCallback(QNetworkReply *reply, QAuthenticator *auth,
56  void *arg);
57 QByteArray gUncompress(const QByteArray &data);
58 
60  stationid(""), callsign(""),
61  stationname(""), affiliate(""),
62  fccchannelnumber("")
63 {
64 }
65 
67  lineupid(""), name(""), displayname(""), type(""), postal(""), device("")
68 {
69 }
70 
72  lineupid(""), stationid(""), channel(""), channelMinor("")
73 {
74 }
75 
77  programid(""), stationid(""),
78  time(), duration(),
79  repeat(false), isnew(false),
80  stereo(false), dolby(false),
81  subtitled(false),
82  hdtv(false), closecaptioned(false),
83  tvrating(""),
84  partnumber(0), parttotal(0)
85 {
86 }
87 
89  programid(""), seriesid(""), title(""),
90  subtitle(""), description(""), mpaaRating(""),
91  starRating(""), duration(), year(""),
92  showtype(""), colorcode(""), originalAirDate(),
93  syndicatedEpisodeNumber("")
94 {
95 }
96 
98  programid(""), role(""), givenname(""), surname(""), fullname("")
99 {
100 }
101 
103  programid(""), gclass(""), relevance("")
104 {
105 }
106 
107 // XXX Program duration should be stored as seconds, not as a QTime.
108 // limited to 24 hours this way.
109 
110 bool DDStructureParser::startElement(const QString &pnamespaceuri,
111  const QString &plocalname,
112  const QString &pqname,
113  const QXmlAttributes &pxmlatts)
114 {
115  (void)pnamespaceuri;
116  (void)plocalname;
117 
118  currtagname = pqname;
119  if (currtagname == "xtvd")
120  {
121  QString beg = pxmlatts.value("from");
122  QDateTime begts = MythDate::fromString(beg);
124 
125  QString end = pxmlatts.value("to");
126  QDateTime endts = MythDate::fromString(end);
127  parent.SetDDProgramsEndAt(endts);
128  }
129  else if (currtagname == "station")
130  {
132  curr_station.stationid = pxmlatts.value("id");
133  }
134  else if (currtagname == "lineup")
135  {
136  curr_lineup.Reset();
137  curr_lineup.name = pxmlatts.value("name");
138  curr_lineup.type = pxmlatts.value("type");
139  curr_lineup.device = pxmlatts.value("device");
140  curr_lineup.postal = pxmlatts.value("postalCode");
141  curr_lineup.lineupid = pxmlatts.value("id");
143  "-" + curr_lineup.device + "-" +
144  curr_lineup.postal + "-" +
146 
147  if (curr_lineup.lineupid.isEmpty())
148  {
151  }
152  }
153  else if (currtagname == "map")
154  {
155  int tmpindex;
158  curr_lineupmap.stationid = pxmlatts.value("station");
159  curr_lineupmap.channel = pxmlatts.value("channel");
160  tmpindex = pxmlatts.index("channelMinor"); // for ATSC
161  if (tmpindex != -1)
162  curr_lineupmap.channelMinor = pxmlatts.value(tmpindex);
163  }
164  else if (currtagname == "schedule")
165  {
167  curr_schedule.programid = pxmlatts.value("program");
168  curr_schedule.stationid = pxmlatts.value("station");
169 
170  curr_schedule.time = MythDate::fromString(pxmlatts.value("time"));
171  QString durstr = pxmlatts.value("duration");
172  curr_schedule.duration = QTime(durstr.mid(2, 2).toInt(),
173  durstr.mid(5, 2).toInt(), 0, 0);
174 
175  curr_schedule.repeat = (pxmlatts.value("repeat") == "true");
176  curr_schedule.isnew = (pxmlatts.value("new") == "true");
177  curr_schedule.stereo = (pxmlatts.value("stereo") == "true");
178  curr_schedule.dolby = (pxmlatts.value("dolby") == "Dolby" ||
179  pxmlatts.value("dolby") == "Dolby Digital");
180  curr_schedule.subtitled = (pxmlatts.value("subtitled") == "true");
181  curr_schedule.hdtv = (pxmlatts.value("hdtv") == "true");
182  curr_schedule.closecaptioned = (pxmlatts.value("closeCaptioned") ==
183  "true");
184  curr_schedule.tvrating = pxmlatts.value("tvRating");
185  }
186  else if (currtagname == "part")
187  {
188  curr_schedule.partnumber = pxmlatts.value("number").toInt();
189  curr_schedule.parttotal = pxmlatts.value("total").toInt();
190  }
191  else if (currtagname == "program")
192  {
194  curr_program.programid = pxmlatts.value("id");
195  }
196  else if (currtagname == "crew")
197  {
199  lastprogramid = pxmlatts.value("program");
200  }
201  else if (currtagname == "programGenre")
202  {
203  curr_genre.Reset();
204  lastprogramid = pxmlatts.value("program");
205  }
206 
207  return true;
208 }
209 
210 bool DDStructureParser::endElement(const QString &pnamespaceuri,
211  const QString &plocalname,
212  const QString &pqname)
213 {
214  (void)pnamespaceuri;
215  (void)plocalname;
216 
217  MSqlQuery query(MSqlQuery::DDCon());
218 
219  if (pqname == "station")
220  {
222 
223  query.prepare(
224  "INSERT INTO dd_station "
225  " ( stationid, callsign, stationname, "
226  " affiliate, fccchannelnumber) "
227  "VALUES "
228  " (:STATIONID, :CALLSIGN, :STATIONNAME, "
229  " :AFFILIATE, :FCCCHANNUM)");
230 
231  query.bindValue(":STATIONID", curr_station.stationid);
232  query.bindValue(":CALLSIGN", curr_station.callsign);
233  query.bindValue(":STATIONNAME", curr_station.stationname);
234  query.bindValue(":AFFILIATE", curr_station.affiliate);
235  query.bindValue(":FCCCHANNUM", curr_station.fccchannelnumber);
236 
237  if (!query.exec())
238  MythDB::DBError("Inserting into dd_station", query);
239  }
240  else if (pqname == "lineup")
241  {
243 
244  parent.m_lineups.push_back(curr_lineup);
245 
246  query.prepare(
247  "INSERT INTO dd_lineup "
248  " ( lineupid, name, type, device, postal) "
249  "VALUES "
250  " (:LINEUPID, :NAME, :TYPE, :DEVICE, :POSTAL)");
251 
252  query.bindValue(":LINEUPID", curr_lineup.lineupid);
253  query.bindValue(":NAME", curr_lineup.name);
254  query.bindValue(":TYPE", curr_lineup.type);
255  query.bindValue(":DEVICE", curr_lineup.device);
256  query.bindValue(":POSTAL", curr_lineup.postal);
257 
258  if (!query.exec())
259  MythDB::DBError("Inserting into dd_lineup", query);
260  }
261  else if (pqname == "map")
262  {
264 
265  query.prepare(
266  "INSERT INTO dd_lineupmap "
267  " ( lineupid, stationid, channel, channelMinor) "
268  "VALUES "
269  " (:LINEUPID, :STATIONID, :CHANNEL, :CHANNELMINOR)");
270 
271  query.bindValue(":LINEUPID", curr_lineupmap.lineupid);
272  query.bindValue(":STATIONID", curr_lineupmap.stationid);
273  query.bindValue(":CHANNEL", curr_lineupmap.channel);
274  query.bindValue(":CHANNELMINOR",curr_lineupmap.channelMinor);
275  if (!query.exec())
276  MythDB::DBError("Inserting into dd_lineupmap", query);
277  }
278  else if (pqname == "schedule")
279  {
280  QDateTime endtime = curr_schedule.time.addSecs(
282 
283  query.prepare(
284  "INSERT INTO dd_schedule "
285  " ( programid, stationid, scheduletime, "
286  " duration, isrepeat, stereo, "
287  " dolby, subtitled, hdtv, "
288  " closecaptioned, tvrating, partnumber, "
289  " parttotal, endtime, isnew) "
290  "VALUES "
291  " (:PROGRAMID, :STATIONID, :TIME, "
292  " :DURATION, :ISREPEAT, :STEREO, "
293  " :DOLBY, :SUBTITLED, :HDTV, "
294  " :CAPTIONED, :TVRATING, :PARTNUMBER, "
295  " :PARTTOTAL, :ENDTIME, :ISNEW)");
296 
297  query.bindValue(":PROGRAMID", curr_schedule.programid);
298  query.bindValue(":STATIONID", curr_schedule.stationid);
299  query.bindValue(":TIME", curr_schedule.time);
300  query.bindValue(":DURATION", curr_schedule.duration);
301  query.bindValue(":ISREPEAT", curr_schedule.repeat);
302  query.bindValue(":STEREO", curr_schedule.stereo);
303  query.bindValue(":DOLBY", curr_schedule.dolby);
304  query.bindValue(":SUBTITLED", curr_schedule.subtitled);
305  query.bindValue(":HDTV", curr_schedule.hdtv);
306  query.bindValue(":CAPTIONED", curr_schedule.closecaptioned);
307  query.bindValue(":TVRATING", curr_schedule.tvrating);
308  query.bindValue(":PARTNUMBER", curr_schedule.partnumber);
309  query.bindValue(":PARTTOTAL", curr_schedule.parttotal);
310  query.bindValue(":ENDTIME", endtime);
311  query.bindValue(":ISNEW", curr_schedule.isnew);
312 
313  if (!query.exec())
314  MythDB::DBError("Inserting into dd_schedule", query);
315  }
316  else if (pqname == "program")
317  {
318  float staravg = 0.0;
319  if (!curr_program.starRating.isEmpty())
320  {
321  int fullstarcount = curr_program.starRating.count("*");
322  int halfstarcount = curr_program.starRating.count("+");
323  staravg = (fullstarcount + (halfstarcount * .5)) / 4;
324  }
325 
326  QString cat_type = "";
327  QString prefix = curr_program.programid.left(2);
328 
329  if (prefix == "MV")
330  cat_type = "movie";
331  else if (prefix == "SP")
332  cat_type = "sports";
333  else if (prefix == "EP" ||
334  curr_program.showtype.contains("series", Qt::CaseInsensitive))
335  cat_type = "series";
336  else
337  cat_type = "tvshow";
338 
339  query.prepare(
340  "INSERT INTO dd_program "
341  " ( programid, title, subtitle, "
342  " description, showtype, category_type, "
343  " mpaarating, starrating, stars, "
344  " runtime, year, seriesid, "
345  " colorcode, syndicatedepisodenumber, originalairdate) "
346  "VALUES "
347  " (:PROGRAMID, :TITLE, :SUBTITLE, "
348  " :DESCRIPTION, :SHOWTYPE, :CATTYPE, "
349  " :MPAARATING, :STARRATING, :STARS, "
350  " :RUNTIME, :YEAR, :SERIESID, "
351  " :COLORCODE, :SYNDNUM, :ORIGAIRDATE) ");
352 
353  query.bindValue(":PROGRAMID", curr_program.programid);
354  query.bindValue(":TITLE", curr_program.title);
355  query.bindValue(":SUBTITLE", curr_program.subtitle);
356  query.bindValue(":DESCRIPTION", curr_program.description);
357  query.bindValue(":SHOWTYPE", curr_program.showtype);
358  query.bindValue(":CATTYPE", cat_type);
359  query.bindValue(":MPAARATING", curr_program.mpaaRating);
360  query.bindValue(":STARRATING", curr_program.starRating);
361  query.bindValue(":STARS", staravg);
362  query.bindValue(":RUNTIME", curr_program.duration);
363  query.bindValue(":YEAR", curr_program.year);
364  query.bindValue(":SERIESID", curr_program.seriesid);
365  query.bindValue(":COLORCODE", curr_program.colorcode);
367  query.bindValue(":ORIGAIRDATE", curr_program.originalAirDate);
368 
369  if (!query.exec())
370  MythDB::DBError("Inserting into dd_program", query);
371  }
372  else if (pqname == "member")
373  {
374  QString roleunderlines = curr_productioncrew.role.replace(" ", "_");
375 
376  QString fullname = curr_productioncrew.givenname;
377  if (!fullname.isEmpty())
378  fullname += " ";
379  fullname += curr_productioncrew.surname;
380 
381  query.prepare(
382  "INSERT INTO dd_productioncrew "
383  " ( programid, role, givenname, surname, fullname) "
384  "VALUES (:PROGRAMID, :ROLE, :GIVENNAME, :SURNAME, :FULLNAME)");
385 
386  query.bindValue(":PROGRAMID", lastprogramid);
387  query.bindValue(":ROLE", roleunderlines);
388  query.bindValue(":GIVENNAME", curr_productioncrew.givenname);
389  query.bindValue(":SURNAME", curr_productioncrew.surname);
390  query.bindValue(":FULLNAME", fullname);
391 
392  if (!query.exec())
393  MythDB::DBError("Inserting into dd_productioncrew", query);
394 
397  }
398  else if (pqname == "genre")
399  {
400  query.prepare(
401  "INSERT INTO dd_genre "
402  " ( programid, class, relevance) "
403  "VALUES (:PROGRAMID, :CLASS, :RELEVANCE)");
404 
405  query.bindValue(":PROGRAMID", lastprogramid);
406  query.bindValue(":CLASS", curr_genre.gclass);
407  query.bindValue(":RELEVANCE", curr_genre.relevance);
408 
409  if (!query.exec())
410  MythDB::DBError("Inserting into dd_genre", query);
411  }
412 
413  return true;
414 }
415 
417 {
419  return true;
420 }
421 
423 {
424  return true;
425 }
426 
427 bool DDStructureParser::characters(const QString& pchars)
428 {
429 #if 0
430  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Characters : " + pchars);
431 #endif
432  if (pchars.trimmed().isEmpty())
433  return true;
434 
435  if (currtagname == "message")
436  {
437  if (pchars.contains("expire"))
438  {
439  QString ExtractDateFromMessage = pchars.right(20);
440  QDateTime EDFM = MythDate::fromString(ExtractDateFromMessage);
441  QString SDDateFormat = GetMythDB()->GetSetting("DateFormat",
442  "ddd d MMMM");
443  // Ensure we show the year when it's important, regardless of
444  // specified DateFormat
445  if ((!SDDateFormat.contains('y')) &&
446  (EDFM.date().year() != MythDate::current().date().year()))
447  {
448  SDDateFormat.append(" (yyyy)");
449  }
450  QString dateFormat = QString("%1 %2")
451  .arg(SDDateFormat)
452  .arg(GetMythDB()->GetSetting("TimeFormat", "hh:mm"));
453  QString ExpirationDate = EDFM.toString(dateFormat);
454 
455  QString ExpirationDateMessage = "Your subscription expires on " +
456  ExpirationDate;
457 
458  QDateTime curTime = MythDate::current();
459  if (curTime.daysTo(EDFM) <= 5)
460  {
461  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("WARNING: ") +
462  ExpirationDateMessage);
463  }
464  else
465  {
466  LOG(VB_GENERAL, LOG_INFO, LOC + ExpirationDateMessage);
467  }
468 
469  gCoreContext->SaveSettingOnHost("DataDirectMessage",
470  ExpirationDateMessage,
471  nullptr);
472  }
473  }
474  if (currtagname == "callSign")
475  curr_station.callsign = pchars;
476  else if (currtagname == "name")
477  curr_station.stationname = pchars;
478  else if (currtagname == "affiliate")
479  curr_station.affiliate = pchars;
480  else if (currtagname == "fccChannelNumber")
482  else if (currtagname == "title")
483  curr_program.title = pchars;
484  else if (currtagname == "subtitle")
485  curr_program.subtitle = pchars;
486  else if (currtagname == "description")
487  curr_program.description = pchars;
488  else if (currtagname == "showType")
489  curr_program.showtype = pchars;
490  else if (currtagname == "series")
491  curr_program.seriesid = pchars;
492  else if (currtagname == "colorCode")
493  curr_program.colorcode = pchars;
494  else if (currtagname == "mpaaRating")
495  curr_program.mpaaRating = pchars;
496  else if (currtagname == "starRating")
497  curr_program.starRating = pchars;
498  else if (currtagname == "year")
499  curr_program.year = pchars;
500  else if (currtagname == "syndicatedEpisodeNumber")
502  else if (currtagname == "runTime")
503  {
504  QString runtimestr = pchars;
505  QTime runtime = QTime(runtimestr.mid(2,2).toInt(),
506  runtimestr.mid(5,2).toInt(), 0, 0);
507  curr_program.duration = runtime;
508  }
509  else if (currtagname == "originalAirDate")
510  {
511  QDate airdate = QDate::fromString(pchars, Qt::ISODate);
512  curr_program.originalAirDate = airdate;
513  }
514  else if (currtagname == "role")
515  curr_productioncrew.role = pchars;
516  else if (currtagname == "givenname")
518  else if (currtagname == "surname")
519  curr_productioncrew.surname = pchars;
520  else if (currtagname == "class")
521  curr_genre.gclass = pchars;
522  else if (currtagname == "relevance")
523  curr_genre.relevance = pchars;
524 
525  return true;
526 }
527 
529  m_listingsProvider(lp % DD_PROVIDER_COUNT),
530  m_userid(user), m_password(pass),
531  m_tmpDir("/tmp"), m_cacheData(false),
532  m_inputFilename(""), m_cookieFileDT()
533 {
534  {
535  QMutexLocker locker(&user_agent_lock);
536  QString mythVersion = MYTH_SOURCE_VERSION;
537  if (mythVersion.startsWith("v"))
538  mythVersion = mythVersion.right(mythVersion.length() - 1); // Trim off the leading 'v'
539  user_agent = QString("MythTV/%1")
540  .arg(mythVersion);
541  }
542 
543  DataDirectURLs urls0(
544  "Tribune Media Zap2It",
545  "http://datadirect.webservices.zap2it.com/tvlistings/xtvdService",
546  "http://labs.zap2it.com",
547  "/ztvws/ztvws_login/1,1059,TMS01-1,00.html");
548  DataDirectURLs urls1(
549  "Schedules Direct",
550  "http://dd.schedulesdirect.org"
551  "/schedulesdirect/tvlistings/xtvdService",
552  "http://schedulesdirect.org",
553  "/login/index.php");
554  m_providers.push_back(urls0);
555  m_providers.push_back(urls1);
556 }
557 
559 {
560  LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting temporary files");
561 
562  if (!m_tmpPostFile.isEmpty())
563  {
564  QByteArray tmp = m_tmpPostFile.toLatin1();
565  unlink(tmp.constData());
566  }
567 
568  if (!m_tmpResultFile.isEmpty())
569  {
570  QByteArray tmp = m_tmpResultFile.toLatin1();
571  unlink(tmp.constData());
572  }
573 
574  if (!m_tmpDDPFile.isEmpty())
575  {
576  QByteArray tmp = m_tmpDDPFile.toLatin1();
577  unlink(tmp.constData());
578  }
579 
580  if (!m_cookieFile.isEmpty())
581  {
582  QByteArray tmp = m_cookieFile.toLatin1();
583  unlink(tmp.constData());
584  }
585 
586  QDir d(m_tmpDir, "mythtv_dd_cache_*", QDir::Name,
587  QDir::Files | QDir::NoSymLinks);
588 
589  for (uint i = 0; i < d.count(); i++)
590  {
591  QString tmps = m_tmpDir + "/" + d[i];
592  QByteArray tmpa = tmps.toLatin1();
593  unlink(tmpa.constData());
594  }
595 
596  if (m_tmpDir != "/tmp")
597  {
598  QByteArray tmp = m_tmpDir.toLatin1();
599  rmdir(tmp.constData());
600  }
601 }
602 
604 {
605  MSqlQuery query(MSqlQuery::DDCon());
606 
607  if (!query.exec("TRUNCATE TABLE dd_v_station;"))
608  MythDB::DBError("Truncating temporary table dd_v_station", query);
609 
610  query.prepare(
611  "INSERT INTO dd_v_station "
612  " ( stationid, callsign, stationname, "
613  " affiliate, fccchannelnumber, channel, "
614  " channelMinor) "
615  "SELECT dd_station.stationid, callsign, stationname, "
616  " affiliate, fccchannelnumber, channel, "
617  " channelMinor "
618  "FROM dd_station, dd_lineupmap "
619  "WHERE ((dd_station.stationid = dd_lineupmap.stationid) AND "
620  " (dd_lineupmap.lineupid = :LINEUP))");
621 
622  query.bindValue(":LINEUP", lineupid);
623 
624  if (!query.exec())
625  MythDB::DBError("Populating temporary table dd_v_station", query);
626 }
627 
629 {
630  MSqlQuery query(MSqlQuery::DDCon());
631 
632  if (!query.exec("TRUNCATE TABLE dd_v_program;"))
633  MythDB::DBError("Truncating temporary table dd_v_program", query);
634 
635  QString qstr =
636  "INSERT INTO dd_v_program "
637  " ( chanid, starttime, endtime, "
638  " title, subtitle, description, "
639  " airdate, stars, previouslyshown, "
640  " stereo, dolby, subtitled, "
641  " hdtv, closecaptioned, partnumber, "
642  " parttotal, seriesid, originalairdate, "
643  " showtype, category_type, colorcode, "
644  " syndicatedepisodenumber, tvrating, mpaarating, "
645  " programid ) "
646  "SELECT chanid, scheduletime, endtime, "
647  " title, subtitle, description, "
648  " year, stars, isrepeat, "
649  " stereo, dolby, subtitled, "
650  " hdtv, closecaptioned, partnumber, "
651  " parttotal, seriesid, originalairdate, "
652  " showtype, category_type, colorcode, "
653  " syndicatedepisodenumber, tvrating, mpaarating, "
654  " dd_program.programid "
655  "FROM channel, dd_schedule, dd_program "
656  "WHERE ((dd_schedule.programid = dd_program.programid) AND "
657  " (channel.xmltvid = dd_schedule.stationid) AND "
658  " (channel.sourceid = :SOURCEID))";
659 
660  query.prepare(qstr);
661 
662  query.bindValue(":SOURCEID", sourceid);
663 
664  if (!query.exec())
665  MythDB::DBError("Populating temporary table dd_v_program", query);
666 
667  if (!query.exec("ANALYZE TABLE dd_v_program;"))
668  MythDB::DBError("Analyzing table dd_v_program", query);
669 
670  if (!query.exec("ANALYZE TABLE dd_productioncrew;"))
671  MythDB::DBError("Analyzing table dd_productioncrew", query);
672 }
673 
675  uint sourceid,
676  bool insert_channels,
677  bool filter_new_channels)
678 {
679  int new_channels = 0;
680 
681  if (!SourceUtil::GetConnectionCount(sourceid))
682  {
683  LOG(VB_GENERAL, LOG_WARNING, LOC +
684  QString("Not inserting channels into disconnected source %1.")
685  .arg(sourceid));
686  return -1;
687  }
688 
689  if (!SourceUtil::IsProperlyConnected(sourceid, true))
690  return -1;
691 
692  // Find all the channels in the dd_v_station temp table
693  // where there is no channel with the same xmltvid in the
694  // DB using the same source.
695  MSqlQuery query(MSqlQuery::DDCon());
696  query.prepare(
697  "SELECT dd_v_station.stationid, dd_v_station.callsign, "
698  " dd_v_station.stationname, dd_v_station.fccchannelnumber, "
699  " dd_v_station.channel, dd_v_station.channelMinor "
700  "FROM dd_v_station LEFT JOIN channel ON "
701  " dd_v_station.stationid = channel.xmltvid AND "
702  " channel.sourceid = :SOURCEID "
703  "WHERE channel.chanid IS NULL");
704  query.bindValue(":SOURCEID", sourceid);
705 
706  if (!query.exec())
707  {
708  MythDB::DBError("Selecting new channels", query);
709  return -1;
710  }
711 
712  bool is_encoder = (SourceUtil::IsCableCardPresent(sourceid) ||
713  SourceUtil::IsEncoder(sourceid, true) ||
714  SourceUtil::IsUnscanable(sourceid));
715 
716  while (query.next())
717  {
718  QString xmltvid = query.value(0).toString();
719  QString callsign = query.value(1).toString();
720  QString name = query.value(2).toString();
721  uint freqid = query.value(3).toUInt();
722  QString chan_major = query.value(4).toString();
723  QString chan_minor = query.value(5).toString();
724 
725  if (filter_new_channels && is_encoder &&
726  (query.value(5).toUInt() > 0))
727  {
728 #if 0
729  LOG(VB_GENERAL, LOG_INFO, LOC +
730  QString("Not adding channel %1-%2 '%3' (%4),\n\t\t\t"
731  "looks like a digital channel on an analog source.")
732  .arg(chan_major).arg(chan_minor).arg(name).arg(callsign));
733 #endif
734  continue;
735  }
736 
737  uint mods =
738  update_channel_basic(sourceid, insert_channels && is_encoder,
739  xmltvid, callsign, name, freqid,
740  chan_major, chan_minor);
741 
742  (void) mods;
743 #if 0
744  if (!insert_channels && !mods)
745  {
746  LOG(VB_GENERAL, LOG_INFO, LOC +
747  QString("Not adding channel '%1' (%2).")
748  .arg(name).arg(callsign));
749  }
750 #endif
751  new_channels++;
752  }
753 
755 
756  return new_channels;
757 }
758 
760  uint sourceid, bool filter_new_channels)
761 {
762  if (filter_new_channels &&
763  !SourceUtil::IsProperlyConnected(sourceid, false))
764  {
765  return false;
766  }
767 
768  MSqlQuery dd_station_info(MSqlQuery::DDCon());
769  dd_station_info.prepare(
770  "SELECT callsign, stationname, stationid,"
771  " fccchannelnumber, channel, channelMinor "
772  "FROM dd_v_station");
773  if (!dd_station_info.exec())
774  return false;
775 
776  if (dd_station_info.size() == 0)
777  return true;
778 
779  MSqlQuery chan_update_q(MSqlQuery::DDCon());
780  chan_update_q.prepare(
781  "UPDATE channel "
782  "SET callsign = :CALLSIGN, name = :NAME, "
783  " channum = :CHANNUM, freqid = :FREQID, "
784  " atsc_major_chan = :MAJORCHAN, "
785  " atsc_minor_chan = :MINORCHAN "
786  "WHERE xmltvid = :STATIONID AND sourceid = :SOURCEID");
787 
788  bool is_encoder = (SourceUtil::IsCableCardPresent(sourceid) ||
789  SourceUtil::IsEncoder(sourceid, true) ||
790  SourceUtil::IsUnscanable(sourceid));
791 
792  while (dd_station_info.next())
793  {
794  uint freqid = dd_station_info.value(3).toUInt();
795  QString chan_major = dd_station_info.value(4).toString();
796  QString chan_minor = dd_station_info.value(5).toString();
797  QString tvformat;
798  QString channum = process_dd_station(
799  sourceid, chan_major, chan_minor, tvformat, freqid);
800 
801  if (filter_new_channels && is_encoder &&
802  (dd_station_info.value(5).toUInt() > 0))
803  {
804 #if 0
805  LOG(VB_GENERAL, LOG_INFO, LOC +
806  QString("Not adding channel %1-%2 '%3' (%4),\n\t\t\t"
807  "looks like a digital channel on an analog source.")
808  .arg(chan_major).arg(chan_minor)
809  .arg(dd_station_info.value(1).toString())
810  .arg(dd_station_info.value(0).toString()));
811 #endif
812  continue;
813  }
814 
815  chan_update_q.bindValue(":CALLSIGN", dd_station_info.value(0));
816  chan_update_q.bindValue(":NAME", dd_station_info.value(1));
817  chan_update_q.bindValue(":STATIONID", dd_station_info.value(2));
818  chan_update_q.bindValue(":CHANNUM", channum);
819  chan_update_q.bindValue(":SOURCEID", sourceid);
820  chan_update_q.bindValue(":FREQID", freqid);
821  chan_update_q.bindValue(":MAJORCHAN", chan_major.toUInt());
822  chan_update_q.bindValue(":MINORCHAN", chan_minor.toUInt());
823 
824  if (!chan_update_q.exec())
825  {
826  MythDB::DBError("Updating channel table", chan_update_q);
827  }
828  }
829 
830  return true;
831 }
832 
834 {
835  MSqlQuery query(MSqlQuery::DDCon());
836 
837 #if 0
838  LOG(VB_GENERAL, LOG_DEBUG, LOC +
839  "Adding rows to main program table from view table");
840 #endif
841  query.prepare(
842  "INSERT IGNORE INTO program "
843  " ( chanid, starttime, endtime, title, "
844  " subtitle, description, showtype, category, "
845  " category_type, airdate, stars, previouslyshown, "
846  " stereo, subtitled, subtitletypes, videoprop, "
847  " audioprop, hdtv, closecaptioned, partnumber, "
848  " parttotal, seriesid, originalairdate, colorcode, "
849  " syndicatedepisodenumber, "
850  " programid, listingsource) "
851  " SELECT "
852  " dd_v_program.chanid, "
853  " DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
854  " DATE_ADD(endtime, INTERVAL channel.tmoffset MINUTE), "
855  " title, "
856  " subtitle, description, showtype, dd_genre.class, "
857  " category_type, airdate, stars, previouslyshown, "
858  " stereo, subtitled, "
859  " (subtitled << 1 ) | closecaptioned, hdtv, "
860  " (dolby << 3) | stereo, "
861  " hdtv, closecaptioned, partnumber, "
862  " parttotal, seriesid, originalairdate, colorcode, "
863  " syndicatedepisodenumber, "
864  " dd_v_program.programid, "
865  " :LSOURCE "
866  "FROM (dd_v_program, channel) "
867  "LEFT JOIN dd_genre ON "
868  " ( dd_v_program.programid = dd_genre.programid AND "
869  " dd_genre.relevance = '0' ) "
870  "WHERE dd_v_program.chanid = channel.chanid");
871 
872  query.bindValue(":LSOURCE", kListingSourceDDSchedulesDirect);
873 
874  if (!query.exec())
875  MythDB::DBError("Inserting into program table", query);
876 
877 #if 0
878  LOG(VB_GENERAL, LOG_DEBUG, LOC +
879  "Finished adding rows to main program table");
880  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Adding program ratings");
881 #endif
882 
883  if (!query.exec("INSERT IGNORE INTO programrating (chanid, starttime, "
884  "system, rating) SELECT dd_v_program.chanid, "
885  "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
886  " 'MPAA', "
887  "mpaarating FROM dd_v_program, channel WHERE "
888  "mpaarating != '' AND dd_v_program.chanid = "
889  "channel.chanid"))
890  MythDB::DBError("Inserting into programrating table", query);
891 
892  if (!query.exec("INSERT IGNORE INTO programrating (chanid, starttime, "
893  "system, rating) SELECT dd_v_program.chanid, "
894  "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
895  "'VCHIP', "
896  "tvrating FROM dd_v_program, channel WHERE tvrating != ''"
897  " AND dd_v_program.chanid = channel.chanid"))
898  MythDB::DBError("Inserting into programrating table", query);
899 
900 #if 0
901  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Finished adding program ratings");
902  LOG(VB_GENERAL, LOG_DEBUG, LOC +
903  "Populating people table from production crew list");
904 #endif
905 
906  if (!query.exec("INSERT IGNORE INTO people (name) "
907  "SELECT fullname "
908  "FROM dd_productioncrew "
909  "LEFT OUTER JOIN people "
910  "ON people.name = dd_productioncrew.fullname "
911  "WHERE people.name IS NULL;"))
912  MythDB::DBError("Inserting into people table", query);
913 
914 #if 0
915  LOG(VB_GENERAL, LOG_INFO, LOC + "Finished adding people");
916  LOG(VB_GENERAL, LOG_INFO, LOC +
917  "Adding credits entries from production crew list");
918 #endif
919 
920  if (!query.exec("INSERT IGNORE INTO credits (chanid, starttime, person, role)"
921  "SELECT dd_v_program.chanid, "
922  "DATE_ADD(dd_v_program.starttime, INTERVAL channel.tmoffset MINUTE), "
923  "people.person, "
924  "dd_productioncrew.role "
925  "FROM dd_v_program "
926  "JOIN channel "
927  "ON dd_v_program.chanid = channel.chanid "
928  "JOIN dd_productioncrew "
929  "ON dd_productioncrew.programid = dd_v_program.programid "
930  "JOIN people "
931  "ON people.name = dd_productioncrew.fullname "
932  "LEFT OUTER JOIN credits "
933  "ON credits.chanid = dd_v_program.chanid "
934  "AND credits.starttime = DATE_ADD(dd_v_program.starttime, INTERVAL channel.tmoffset MINUTE) "
935  "AND credits.person = people.person "
936  "AND credits.role = dd_productioncrew.role "
937  "WHERE credits.role IS NULL;"))
938  MythDB::DBError("Inserting into credits table", query);
939 
940 #if 0
941  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Finished inserting credits");
942  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Adding genres");
943 #endif
944 
945  if (!query.exec("INSERT IGNORE INTO programgenres (chanid, starttime, "
946  "relevance, genre) SELECT dd_v_program.chanid, "
947  "DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE), "
948  "relevance, class FROM dd_v_program, dd_genre, channel "
949  "WHERE (dd_v_program.programid = dd_genre.programid) "
950  "AND dd_v_program.chanid = channel.chanid"))
951  MythDB::DBError("Inserting into programgenres table",query);
952 
953 #if 0
954  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Done");
955 #endif
956 }
957 
958 void authenticationCallback(QNetworkReply *reply, QAuthenticator *auth,
959  void *arg)
960 {
961  if (!arg)
962  return;
963 
964  DataDirectProcessor *dd = reinterpret_cast<DataDirectProcessor *>(arg);
965  dd->authenticationCallback(reply, auth);
966 }
967 
969  QAuthenticator *auth)
970 {
971  LOG(VB_FILE, LOG_DEBUG, "DataDirect auth callback");
972  (void)reply;
973  auth->setUser(GetUserID());
974  auth->setPassword(GetPassword());
975 }
976 
977 bool DataDirectProcessor::DDPost(QString ddurl, QString &inputFile,
978  QDateTime pstartDate, QDateTime pendDate,
979  QString &err_txt)
980 {
981  if (!inputFile.isEmpty() && QFile(inputFile).exists())
982  {
983  return true;
984  }
985 
986  QString startdatestr = pstartDate.toString(Qt::ISODate) + "Z";
987  QString enddatestr = pendDate.toString(Qt::ISODate) + "Z";
988  QByteArray postdata;
989  postdata = "<?xml version='1.0' encoding='utf-8'?>\n";
990  postdata += "<SOAP-ENV:Envelope\n";
991  postdata += "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'\n";
992  postdata += "xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n";
993  postdata += "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n";
994  postdata += "xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'>\n";
995  postdata += "<SOAP-ENV:Body>\n";
996  postdata += "<ns1:download xmlns:ns1='urn:TMSWebServices'>\n";
997  postdata += "<startTime xsi:type='xsd:dateTime'>";
998  postdata += startdatestr;
999  postdata += "</startTime>\n";
1000  postdata += "<endTime xsi:type='xsd:dateTime'>";
1001  postdata += enddatestr;
1002  postdata += "</endTime>\n";
1003  postdata += "</ns1:download>\n";
1004  postdata += "</SOAP-ENV:Body>\n";
1005  postdata += "</SOAP-ENV:Envelope>\n";
1006 
1007  if (inputFile.isEmpty())
1008  {
1009  bool ok;
1010  inputFile = GetDDPFilename(ok);
1011  if (!ok)
1012  {
1013  LOG(VB_GENERAL, LOG_ERR, LOC +
1014  "Failure creating temp ddp file");
1015  return false;
1016  }
1017  }
1018 
1019  QHash<QByteArray, QByteArray> headers;
1020  headers.insert("Accept-Encoding", "gzip");
1021  headers.insert("Content-Type", "application/soap+xml; charset=utf-8");
1022 
1023  LOG(VB_GENERAL, LOG_INFO, "Downloading DataDirect feed");
1024 
1026 
1027  if (!manager->postAuth(ddurl, &postdata, &::authenticationCallback, this,
1028  &headers))
1029  {
1030  err_txt = QString("Download error");
1031  return false;
1032  }
1033 
1034  LOG(VB_GENERAL, LOG_INFO, QString("Downloaded %1 bytes")
1035  .arg(postdata.size()));
1036 
1037  LOG(VB_GENERAL, LOG_INFO, "Uncompressing DataDirect feed");
1038 
1039  QByteArray uncompressed = gUncompress(postdata);
1040 
1041  LOG(VB_GENERAL, LOG_INFO, QString("Uncompressed to %1 bytes")
1042  .arg(uncompressed.size()));
1043 
1044  if (uncompressed.size() == 0)
1045  uncompressed = postdata;
1046 
1047  LOG(VB_GENERAL, LOG_INFO, QString("Writing to temporary file: [%1]")
1048  .arg( inputFile ));
1049 
1050  QFile file(inputFile);
1051  if (!file.open(QIODevice::WriteOnly))
1052  {
1053  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open temporary file: %1").arg(inputFile));
1054  return false;
1055  }
1056  file.write(uncompressed);
1057  file.close();
1058 
1059  if (uncompressed.size() == 0)
1060  {
1061  err_txt = QString("Error uncompressing data");
1062  return false;
1063  }
1064 
1065  return true;
1066 }
1067 
1069 {
1070  LOG(VB_GENERAL, LOG_INFO, LOC + "Grabbing next suggested grabbing time");
1071 
1072  QString ddurl = m_providers[m_listingsProvider].webServiceURL;
1073 
1074  bool ok;
1075  QString resultFilename = GetResultFilename(ok);
1076  if (!ok)
1077  {
1078  LOG(VB_GENERAL, LOG_ERR, LOC +
1079  "GrabNextSuggestedTime: Creating temp result file");
1080  return false;
1081  }
1082 
1083  QByteArray postdata;
1084  postdata = "<?xml version='1.0' encoding='utf-8'?>\n";
1085  postdata += "<SOAP-ENV:Envelope\n";
1086  postdata += "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'\n";
1087  postdata += "xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n";
1088  postdata += "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n";
1089  postdata += "xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'>\n";
1090  postdata += "<SOAP-ENV:Body>\n";
1091  postdata += "<tms:acknowledge xmlns:tms='urn:TMSWebServices'>\n";
1092  postdata += "</SOAP-ENV:Body>\n";
1093  postdata += "</SOAP-ENV:Envelope>\n";
1094 
1095  QHash<QByteArray, QByteArray> headers;
1096  headers.insert("Content-Type", "application/soap+xml; charset=utf-8");
1097 
1099 
1100  if (!manager->postAuth(ddurl, &postdata, &::authenticationCallback, this,
1101  &headers))
1102  {
1103  LOG(VB_GENERAL, LOG_ERR, LOC +
1104  "GrabNextSuggestedTime: Could not download");
1105  return false;
1106  }
1107 
1108  QDateTime nextSuggestedTime;
1109  QDateTime blockedTime;
1110 
1111  LOG(VB_GENERAL, LOG_INFO, QString("Suggested Time data: %1 bytes")
1112  .arg(postdata.size()));
1113 
1114  QFile file(resultFilename);
1115  if (!file.open(QIODevice::WriteOnly))
1116  {
1117  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open result file: %1").arg(resultFilename));
1118  return false;
1119  }
1120  file.write(postdata);
1121  file.close();
1122 
1123  if (file.open(QIODevice::ReadOnly))
1124  {
1125  QTextStream stream(&file);
1126  QString line;
1127  while (!stream.atEnd())
1128  {
1129  line = stream.readLine();
1130  if (line.contains("<suggestedTime>", Qt::CaseInsensitive))
1131  {
1132  QString tmpStr = line;
1133  tmpStr.replace(
1134  QRegExp(".*<suggestedTime>([^<]*)</suggestedTime>.*"),
1135  "\\1");
1136 
1137  nextSuggestedTime = MythDate::fromString(tmpStr);
1138 
1139  LOG(VB_GENERAL, LOG_INFO, LOC +
1140  QString("nextSuggestedTime is: ") +
1141  nextSuggestedTime.toString(Qt::ISODate));
1142  }
1143 
1144  if (line.contains("<blockedTime>", Qt::CaseInsensitive))
1145  {
1146  QString tmpStr = line;
1147  tmpStr.replace(
1148  QRegExp(".*<blockedTime>([^<]*)</blockedTime>.*"), "\\1");
1149 
1150  blockedTime = MythDate::fromString(tmpStr);
1151  LOG(VB_GENERAL, LOG_INFO, LOC + QString("BlockedTime is: ") +
1152  blockedTime.toString(Qt::ISODate));
1153  }
1154  }
1155  file.close();
1156  }
1157 
1158  if (nextSuggestedTime.isValid())
1159  {
1161  "MythFillSuggestedRunTime",
1162  nextSuggestedTime.toString(Qt::ISODate), nullptr);
1163  }
1164 
1165  return nextSuggestedTime.isValid();
1166 }
1167 
1168 bool DataDirectProcessor::GrabData(const QDateTime &pstartDate,
1169  const QDateTime &pendDate)
1170 {
1171  QString msg = (pstartDate.addSecs(1) == pendDate) ? "channel" : "listing";
1172  LOG(VB_GENERAL, LOG_INFO, LOC + "Grabbing " + msg + " data");
1173 
1174  QString err = "";
1175  QString ddurl = m_providers[m_listingsProvider].webServiceURL;
1176  QString inputfile = m_inputFilename;
1177  QString cache_dd_data;
1178 
1179  if (m_cacheData)
1180  {
1181  cache_dd_data = m_tmpDir +
1182  QString("/mythtv_dd_cache_%1_UTC_%2_to_%3")
1183  .arg(GetListingsProvider())
1184  .arg(MythDate::toString(pstartDate, MythDate::kFilename))
1185  .arg(MythDate::toString(pendDate, MythDate::kFilename));
1186 
1187  if (QFile(cache_dd_data).exists() && m_inputFilename.isEmpty())
1188  {
1189  LOG(VB_GENERAL, LOG_INFO, LOC + "Using DD cache");
1190  }
1191 
1192  if (m_inputFilename.isEmpty())
1193  inputfile = cache_dd_data;
1194  }
1195 
1196  if (!DDPost(ddurl, inputfile, pstartDate, pendDate, err))
1197  {
1198  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to get data: %1")
1199  .arg(err));
1200  return false;
1201  }
1202 
1203  QFile file(inputfile);
1204 
1205  if (!file.open(QIODevice::ReadOnly))
1206  {
1207  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open file: %1").arg(inputfile));
1208  return false;
1209  }
1210 
1211  QByteArray data = file.readAll();
1212  file.close();
1213 
1214  if (data.isEmpty())
1215  {
1216  LOG(VB_GENERAL, LOG_ERR, LOC + "Data is empty");
1217  return false;
1218  }
1219 
1220  bool ok = true;
1221 
1222  DDStructureParser ddhandler(*this);
1223  QXmlInputSource xmlsource;
1224  QXmlSimpleReader xmlsimplereader;
1225 
1226  xmlsource.setData(data);
1227  xmlsimplereader.setContentHandler(&ddhandler);
1228  if (!xmlsimplereader.parse(xmlsource))
1229  {
1230  LOG(VB_GENERAL, LOG_ERR, LOC +
1231  "DataDirect XML failed to properly parse, downloaded listings "
1232  "were probably corrupt.");
1233  ok = false;
1234  }
1235 
1236  return ok;
1237 }
1238 
1240 {
1241  const QDateTime start = QDateTime(MythDate::current().date().addDays(2),
1242  QTime(23, 59, 0), Qt::UTC);
1243  const QDateTime end = start.addSecs(1);
1244 
1245  return GrabData(start, end);
1246 }
1247 
1249 {
1250  return GrabData(MythDate::current().addDays(-2),
1251  MythDate::current().addDays(15));
1252 }
1253 
1254 void DataDirectProcessor::CreateATempTable(const QString &ptablename,
1255  const QString &ptablestruct)
1256 {
1257  MSqlQuery query(MSqlQuery::DDCon());
1258  QString querystr;
1259  querystr = "CREATE TEMPORARY TABLE IF NOT EXISTS " + ptablename + " " +
1260  ptablestruct + " ENGINE=MyISAM;";
1261 
1262  if (!query.exec(querystr))
1263  MythDB::DBError("Creating temporary table", query);
1264 
1265  querystr = "TRUNCATE TABLE " + ptablename + ";";
1266 
1267  if (!query.exec(querystr))
1268  MythDB::DBError("Truncating temporary table", query);
1269 }
1270 
1272 {
1273  QMap<QString,QString> dd_tables;
1274 
1275  dd_tables["dd_station"] =
1276  "( stationid char(12), callsign char(10), "
1277  " stationname varchar(40), affiliate varchar(25), "
1278  " fccchannelnumber char(15) )";
1279 
1280  dd_tables["dd_lineup"] =
1281  "( lineupid char(100), name char(42), "
1282  " type char(20), postal char(6), "
1283  " device char(30) )";
1284 
1285  dd_tables["dd_lineupmap"] =
1286  "( lineupid char(100), stationid char(12), "
1287  " channel char(5), channelMinor char(3) )";
1288 
1289 
1290  dd_tables["dd_v_station"] =
1291  "( stationid char(12), callsign char(10), "
1292  " stationname varchar(40), affiliate varchar(25), "
1293  " fccchannelnumber char(15), channel char(5), "
1294  " channelMinor char(3) )";
1295 
1296  dd_tables["dd_schedule"] =
1297  "( programid char(40), stationid char(12), "
1298  " scheduletime datetime, duration time, "
1299  " isrepeat bool, stereo bool, "
1300  " dolby bool, "
1301  " subtitled bool, hdtv bool, "
1302  " closecaptioned bool, tvrating char(5), "
1303  " partnumber int, parttotal int, "
1304  " endtime datetime, isnew bool, "
1305  "INDEX progidx (programid) )";
1306 
1307  dd_tables["dd_program"] =
1308  "( programid char(40) NOT NULL, seriesid char(12), "
1309  " title varchar(120), subtitle varchar(150), "
1310  " description text, mpaarating char(5), "
1311  " starrating char(5), runtime time, "
1312  " year char(4), showtype char(30), "
1313  " category_type char(64), colorcode char(20), "
1314  " originalairdate date, syndicatedepisodenumber char(20), "
1315  " stars float unsigned, "
1316  "PRIMARY KEY (programid))";
1317 
1318  dd_tables["dd_v_program"] =
1319  "( chanid int unsigned NOT NULL, starttime datetime NOT NULL, "
1320  " endtime datetime, title varchar(128), "
1321  " subtitle varchar(128), description text, "
1322  " category varchar(64), category_type varchar(64), "
1323  " airdate year, stars float unsigned, "
1324  " previouslyshown tinyint, isrepeat bool, "
1325  " stereo bool, dolby bool, "
1326  " subtitled bool, "
1327  " hdtv bool, closecaptioned bool, "
1328  " partnumber int, parttotal int, "
1329  " seriesid char(12), originalairdate date, "
1330  " showtype varchar(30), colorcode varchar(20), "
1331  " syndicatedepisodenumber varchar(20), programid char(40), "
1332  " tvrating char(5), mpaarating char(5), "
1333  "INDEX progidx (programid))";
1334 
1335  dd_tables["dd_productioncrew"] =
1336  "( programid char(40), role char(30), "
1337  " givenname char(20), surname char(20), "
1338  " fullname char(41), "
1339  "INDEX progidx (programid), "
1340  "INDEX nameidx (fullname))";
1341 
1342  dd_tables["dd_genre"] =
1343  "( programid char(40) NOT NULL, class char(30), "
1344  " relevance char(1), "
1345  "INDEX progidx (programid))";
1346 
1347  QMap<QString,QString>::const_iterator it;
1348  for (it = dd_tables.begin(); it != dd_tables.end(); ++it)
1349  CreateATempTable(it.key(), *it);
1350 }
1351 
1353 {
1354  LOG(VB_GENERAL, LOG_INFO, LOC + "Grabbing login cookies and lineups");
1355 
1356  PostList list;
1357  list.push_back(PostItem("username", GetUserID()));
1358  list.push_back(PostItem("password", GetPassword()));
1359  list.push_back(PostItem("action", "Login"));
1360 
1361  QString labsURL = m_providers[m_listingsProvider].webURL;
1362  QString loginPage = m_providers[m_listingsProvider].loginPage;
1363 
1364  bool ok;
1365  QString resultFilename = GetResultFilename(ok);
1366  if (!ok)
1367  {
1368  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLoginCookiesAndLineups: "
1369  "Creating temp result file");
1370  return false;
1371  }
1372  QString cookieFilename = GetCookieFilename(ok);
1373  if (!ok)
1374  {
1375  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLoginCookiesAndLineups: "
1376  "Creating temp cookie file");
1377  return false;
1378  }
1379 
1380  ok = Post(labsURL + loginPage, list, resultFilename, "",
1381  cookieFilename);
1382 
1383  bool got_cookie = QFileInfo(cookieFilename).size() > 100;
1384 
1385  ok &= got_cookie && (!parse_lineups || ParseLineups(resultFilename));
1386  if (ok)
1388 
1389  return ok;
1390 }
1391 
1392 bool DataDirectProcessor::GrabLineupForModify(const QString &lineupid)
1393 {
1394  LOG(VB_GENERAL, LOG_INFO, LOC +
1395  QString("Grabbing lineup %1 for modification").arg(lineupid));
1396 
1397  RawLineupMap::const_iterator it = m_rawLineups.find(lineupid);
1398  if (it == m_rawLineups.end())
1399  return false;
1400 
1401  PostList list;
1402  list.push_back(PostItem("udl_id", GetRawUDLID(lineupid)));
1403  list.push_back(PostItem("zipcode", GetRawZipCode(lineupid)));
1404  list.push_back(PostItem("lineup_id", lineupid));
1405  list.push_back(PostItem("submit", "Modify"));
1406 
1407  bool ok;
1408  QString resultFilename = GetResultFilename(ok);
1409  if (!ok)
1410  {
1411  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLoginCookiesAndLineups: "
1412  "Creating temp result file");
1413  return false;
1414  }
1415  QString cookieFilename = GetCookieFilename(ok);
1416  if (!ok)
1417  {
1418  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLoginCookiesAndLineups: "
1419  "Creating temp cookie file");
1420  return false;
1421  }
1422 
1423  QString labsURL = m_providers[m_listingsProvider].webURL;
1424  ok = Post(labsURL + (*it).get_action, list, resultFilename,
1425  cookieFilename, "");
1426 
1427  return ok && ParseLineup(lineupid, resultFilename);
1428 }
1429 
1430 void DataDirectProcessor::SetAll(const QString &lineupid, bool val)
1431 {
1432  LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 all channels in lineup %2")
1433  .arg((val) ? "Selecting" : "Deselecting").arg(lineupid));
1434 
1435  RawLineupMap::iterator lit = m_rawLineups.find(lineupid);
1436  if (lit == m_rawLineups.end())
1437  return;
1438 
1439  RawLineupChannels &ch = (*lit).channels;
1440  for (RawLineupChannels::iterator it = ch.begin(); it != ch.end(); ++it)
1441  (*it).chk_checked = val;
1442 }
1443 
1444 static QString get_cache_filename(const QString &lineupid)
1445 {
1446  return QString("/tmp/.mythtv_cached_lineup_") + lineupid;
1447 }
1448 
1449 QDateTime DataDirectProcessor::GetLineupCacheAge(const QString &lineupid) const
1450 {
1451  QDateTime cache_dt(QDate(1971, 1, 1), QTime(0,0,0), Qt::UTC);
1452  QFile lfile(get_cache_filename(lineupid));
1453  if (!lfile.exists())
1454  {
1455  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLineupCacheAge("+lineupid+
1456  ") failed -- " +
1457  QString("file '%1' doesn't exist")
1458  .arg(get_cache_filename(lineupid)));
1459  return cache_dt;
1460  }
1461  if (lfile.size() < 8)
1462  {
1463  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLineupCacheAge("+lineupid+
1464  ") failed -- " +
1465  QString("file '%1' size %2 too small")
1466  .arg(get_cache_filename(lineupid)).arg(lfile.size()));
1467  return cache_dt;
1468  }
1469  if (!lfile.open(QIODevice::ReadOnly))
1470  {
1471  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLineupCacheAge("+lineupid+
1472  ") failed -- " +
1473  QString("cannot open file '%1'")
1474  .arg(get_cache_filename(lineupid)));
1475  return cache_dt;
1476  }
1477 
1478  QString tmp;
1479  QTextStream io(&lfile);
1480  io >> tmp;
1481  cache_dt = MythDate::fromString(tmp);
1482 
1483  LOG(VB_GENERAL, LOG_INFO, LOC + "GrabLineupCacheAge("+lineupid+") -> " +
1484  cache_dt.toString(Qt::ISODate));
1485 
1486  return cache_dt;
1487 }
1488 
1489 bool DataDirectProcessor::GrabLineupsFromCache(const QString &lineupid)
1490 {
1491  QFile lfile(get_cache_filename(lineupid));
1492  if (!lfile.exists() || (lfile.size() < 8) ||
1493  !lfile.open(QIODevice::ReadOnly))
1494  {
1495  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLineupFromCache("+lineupid+
1496  ") -- failed");
1497  return false;
1498  }
1499 
1500  QString tmp;
1501  uint size;
1502  QTextStream io(&lfile);
1503  io >> tmp; // read in date
1504  io >> size; // read in number of channels mapped
1505 
1506  for (uint i = 0; i < 14; i++)
1507  io.readLine(); // read extra lines
1508 
1509  DDLineupChannels &channels = m_lineupmaps[lineupid];
1510  channels.clear();
1511 
1512  for (uint i = 0; i < size; i++)
1513  {
1514  io.readLine(); // read "start record" string
1515 
1516  DataDirectLineupMap chan;
1517  chan.lineupid = lineupid;
1518  chan.stationid = io.readLine();
1519  chan.channel = io.readLine();
1520  chan.channelMinor = io.readLine();
1521 
1522  chan.mapFrom = QDate();
1523  tmp = io.readLine();
1524  if (!tmp.isEmpty())
1525  chan.mapFrom.fromString(tmp, Qt::ISODate);
1526 
1527  chan.mapTo = QDate();
1528  tmp = io.readLine();
1529  if (!tmp.isEmpty())
1530  chan.mapTo.fromString(tmp, Qt::ISODate);
1531 
1532  channels.push_back(chan);
1533 
1534  DDStation station;
1535  station.stationid = chan.stationid;
1536  station.callsign = io.readLine();
1537  station.stationname = io.readLine();
1538  station.affiliate = io.readLine();
1539  station.fccchannelnumber = io.readLine();
1540  tmp = io.readLine(); // read "end record" string
1541 
1542  m_stations[station.stationid] = station;
1543  }
1544 
1545  LOG(VB_GENERAL, LOG_INFO, LOC + "GrabLineupFromCache("+lineupid+
1546  ") -- success");
1547 
1548  return true;
1549 }
1550 
1551 bool DataDirectProcessor::SaveLineupToCache(const QString &lineupid) const
1552 {
1553  QString fn = get_cache_filename(lineupid);
1554  QByteArray fna = fn.toLatin1();
1555  QFile lfile(fna.constData());
1556  if (!lfile.open(QIODevice::WriteOnly))
1557  {
1558  LOG(VB_GENERAL, LOG_ERR, LOC + "SaveLineupToCache("+lineupid+
1559  ") -- failed");
1560  return false;
1561  }
1562 
1563  QTextStream io(&lfile);
1564  io << MythDate::current_iso_string() << endl;
1565 
1566  const DDLineupChannels channels = GetDDLineup(lineupid);
1567  io << channels.size() << endl;
1568 
1569  io << endl;
1570  io << "# start record" << endl;
1571  io << "# stationid" << endl;
1572  io << "# channel" << endl;
1573  io << "# channelMinor" << endl;
1574  io << "# mapped from date" << endl;
1575  io << "# mapped to date" << endl;
1576  io << "# callsign" << endl;
1577  io << "# stationname" << endl;
1578  io << "# affiliate" << endl;
1579  io << "# fccchannelnumber" << endl;
1580  io << "# end record" << endl;
1581  io << endl;
1582 
1583  DDLineupChannels::const_iterator it;
1584  for (it = channels.begin(); it != channels.end(); ++it)
1585  {
1586  io << "# start record" << endl;
1587  io << (*it).stationid << endl;
1588  io << (*it).channel << endl;
1589  io << (*it).channelMinor << endl;
1590  io << (*it).mapFrom.toString(Qt::ISODate) << endl;
1591  io << (*it).mapTo.toString(Qt::ISODate) << endl;
1592 
1593  DDStation station = GetDDStation((*it).stationid);
1594  io << station.callsign << endl;
1595  io << station.stationname << endl;
1596  io << station.affiliate << endl;
1597  io << station.fccchannelnumber << endl;
1598  io << "# end record" << endl;
1599  }
1600  io << flush;
1601 
1602  LOG(VB_GENERAL, LOG_INFO, LOC + "SaveLineupToCache("+lineupid+
1603  ") -- success");
1604 
1605  bool ret = makeFileAccessible(fna.constData()); // Let anybody update it
1606  if (!ret)
1607  {
1608  // Nothing, makeFileAccessible will print an error
1609  }
1610 
1611  return true;
1612 }
1613 
1614 bool DataDirectProcessor::GrabFullLineup(const QString &lineupid,
1615  bool restore, bool onlyGrabSelected,
1616  uint cache_age_allowed_in_seconds)
1617 {
1618  if (cache_age_allowed_in_seconds)
1619  {
1620  QDateTime exp_time = GetLineupCacheAge(lineupid)
1621  .addSecs(cache_age_allowed_in_seconds);
1622  bool valid = exp_time > MythDate::current();
1623  if (valid && GrabLineupsFromCache(lineupid))
1624  return true;
1625  }
1626 
1627  bool ok = GrabLoginCookiesAndLineups();
1628  if (!ok)
1629  return false;
1630 
1631  ok = GrabLineupForModify(lineupid);
1632  if (!ok)
1633  return false;
1634 
1635  RawLineupMap::iterator lit = m_rawLineups.find(lineupid);
1636  if (lit == m_rawLineups.end())
1637  return false;
1638 
1639  const RawLineupChannels orig_channels = (*lit).channels;
1640 
1641  if (!onlyGrabSelected)
1642  {
1643  SetAll(lineupid, true);
1644  if (!SaveLineupChanges(lineupid))
1645  return false;
1646  }
1647 
1648  ok = GrabLineupsOnly();
1649 
1650  if (ok)
1651  SaveLineupToCache(lineupid);
1652 
1653  (*lit).channels = orig_channels;
1654  if (restore && !onlyGrabSelected)
1655  ok &= SaveLineupChanges(lineupid);
1656 
1657  return ok;
1658 }
1659 
1660 bool DataDirectProcessor::SaveLineup(const QString &lineupid,
1661  const QMap<QString,bool> &xmltvids)
1662 {
1663  QMap<QString,bool> callsigns;
1664  RawLineupMap::iterator lit = m_rawLineups.find(lineupid);
1665  if (lit == m_rawLineups.end())
1666  return false;
1667 
1668  // Grab login cookies if they are more than 5 minutes old
1669  if ((!m_cookieFileDT.isValid() ||
1670  m_cookieFileDT.addSecs(5*60) < MythDate::current()) &&
1672  {
1673  return false;
1674  }
1675 
1676  // Get callsigns based on xmltv ids (aka stationid)
1677  DDLineupMap::const_iterator ddit = m_lineupmaps.find(lineupid);
1678  DDLineupChannels::const_iterator it;
1679  for (it = (*ddit).begin(); it != (*ddit).end(); ++it)
1680  {
1681  if (xmltvids.find((*it).stationid) != xmltvids.end())
1682  callsigns[GetDDStation((*it).stationid).callsign] = true;
1683  }
1684 
1685  // Set checked mark based on whether the channel is mapped
1686  RawLineupChannels &ch = (*lit).channels;
1687  RawLineupChannels::iterator cit;
1688  for (cit = ch.begin(); cit != ch.end(); ++cit)
1689  {
1690  bool chk = callsigns.find((*cit).lbl_callsign) != callsigns.end();
1691  (*cit).chk_checked = chk;
1692  }
1693 
1694  // Save these changes
1695  return SaveLineupChanges(lineupid);
1696 }
1697 
1698 bool DataDirectProcessor::SaveLineupChanges(const QString &lineupid)
1699 {
1700  RawLineupMap::const_iterator lit = m_rawLineups.find(lineupid);
1701  if (lit == m_rawLineups.end())
1702  return false;
1703 
1704  const RawLineup &lineup = *lit;
1705  const RawLineupChannels &ch = lineup.channels;
1706  RawLineupChannels::const_iterator it;
1707 
1708  PostList list;
1709  for (it = ch.begin(); it != ch.end(); ++it)
1710  {
1711  if ((*it).chk_checked)
1712  list.push_back(PostItem((*it).chk_name, (*it).chk_value));
1713  }
1714  list.push_back(PostItem("action", "Update"));
1715 
1716  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Saving lineup %1 with %2 channels")
1717  .arg(lineupid).arg(list.size() - 1));
1718 
1719  bool ok;
1720  QString cookieFilename = GetCookieFilename(ok);
1721  if (!ok)
1722  {
1723  LOG(VB_GENERAL, LOG_ERR, LOC + "GrabLoginCookiesAndLineups: "
1724  "Creating temp cookie file");
1725  return false;
1726  }
1727 
1728  QString labsURL = m_providers[m_listingsProvider].webURL;
1729  return Post(labsURL + lineup.set_action, list, "",
1730  cookieFilename, "");
1731 }
1732 
1734 {
1735  MSqlQuery query(MSqlQuery::DDCon());
1736  query.prepare(
1737  "SELECT xmltvid "
1738  "FROM channel "
1739  "WHERE sourceid = :SOURCEID");
1740  query.bindValue(":SOURCEID", sourceid);
1741 
1742  if (!query.exec() || !query.isActive())
1743  {
1744  MythDB::DBError("Selecting existing channels", query);
1745  return false;
1746  }
1747 
1748  QString a, b, c, lineupid;
1749  if (!SourceUtil::GetListingsLoginData(sourceid, a, b, c, lineupid))
1750  return false;
1751 
1752  QMap<QString,bool> xmltvids;
1753  while (query.next())
1754  {
1755  if (!query.value(0).toString().isEmpty())
1756  xmltvids[query.value(0).toString()] = true;
1757  }
1758 
1759  LOG(VB_GENERAL, LOG_INFO, LOC + "Saving updated DataDirect listing");
1760  bool ok = SaveLineup(lineupid, xmltvids);
1761 
1762  if (!ok)
1763  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to update DataDirect listings.");
1764 
1765  return ok;
1766 }
1767 
1768 QString DataDirectProcessor::GetRawUDLID(const QString &lineupid) const
1769 {
1770  RawLineupMap::const_iterator it = m_rawLineups.find(lineupid);
1771  if (it == m_rawLineups.end())
1772  return QString();
1773  return (*it).udl_id;
1774 }
1775 
1776 QString DataDirectProcessor::GetRawZipCode(const QString &lineupid) const
1777 {
1778  RawLineupMap::const_iterator it = m_rawLineups.find(lineupid);
1779  if (it == m_rawLineups.end())
1780  return QString();
1781  return (*it).zipcode;
1782 }
1783 
1784 RawLineup DataDirectProcessor::GetRawLineup(const QString &lineupid) const
1785 {
1786  RawLineup tmp;
1787  RawLineupMap::const_iterator it = m_rawLineups.find(lineupid);
1788  if (it == m_rawLineups.end())
1789  return tmp;
1790  return (*it);
1791 }
1792 
1794  const QString &templatefilename,
1795  const QString &errmsg,
1796  bool directory,
1797  QString &filename,
1798  bool &ok) const
1799 {
1800  QString tmp = createTempFile(templatefilename, directory);
1801  if (templatefilename == tmp)
1802  {
1803  m_fatalErrors.push_back(errmsg);
1804  ok = false;
1805  }
1806  else
1807  {
1808  filename = tmp;
1809  ok = true;
1810  }
1811 }
1812 
1814 {
1815  bool ok;
1816  pok = (pok) ? pok : &ok;
1817  if (m_tmpDir == "/tmp")
1818  {
1819  CreateTemp("/tmp/mythtv_ddp_XXXXXX",
1820  "Failed to create temp directory",
1821  true, m_tmpDir, *pok);
1822  }
1823  return m_tmpDir;
1824 }
1825 
1826 
1828 {
1829  ok = true;
1830  if (m_tmpResultFile.isEmpty())
1831  {
1832  CreateTemp(m_tmpDir + "/mythtv_result_XXXXXX",
1833  "Failed to create temp result file",
1834  false, m_tmpResultFile, ok);
1835  }
1836  return m_tmpResultFile;
1837 }
1838 
1840 {
1841  ok = true;
1842  if (m_cookieFile.isEmpty())
1843  {
1844  CreateTemp(m_tmpDir + "/mythtv_cookies_XXXXXX",
1845  "Failed to create temp cookie file",
1846  false, m_cookieFile, ok);
1847  }
1848  return m_cookieFile;
1849 }
1850 
1852 {
1853  ok = true;
1854  if (m_tmpDDPFile.isEmpty())
1855  {
1856  CreateTemp(m_tmpDir + "/mythtv_ddp_XXXXXX",
1857  "Failed to create temp ddp file",
1858  false, m_tmpDDPFile, ok);
1859  }
1860  return m_tmpDDPFile;
1861 }
1862 
1863 bool DataDirectProcessor::Post(QString url, const PostList &list,
1864  QString documentFile,
1865  QString inCookieFile, QString outCookieFile)
1866 {
1868 
1869  if (!inCookieFile.isEmpty())
1870  manager->loadCookieJar(inCookieFile);
1871 
1872  QByteArray postdata;
1873  for (uint i = 0; i < list.size(); i++)
1874  {
1875  postdata += ((i) ? "&" : "") + list[i].key + "=";
1876  postdata += html_escape(list[i].value);
1877  }
1878 
1879  if (!manager->post(url, &postdata))
1880  return false;
1881 
1882  if (!outCookieFile.isEmpty())
1883  manager->saveCookieJar(outCookieFile);
1884 
1885  if (documentFile.isEmpty())
1886  return true;
1887 
1888  QFile file(documentFile);
1889  if (!file.open(QIODevice::WriteOnly))
1890  {
1891  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open document file: %1").arg(documentFile));
1892  return false;
1893  }
1894  file.write(postdata);
1895  file.close();
1896 
1897  QFileInfo fi(documentFile);
1898  return fi.size();
1899 }
1900 
1901 bool DataDirectProcessor::ParseLineups(const QString &documentFile)
1902 {
1903  QFile file(documentFile);
1904  if (!file.open(QIODevice::ReadOnly))
1905  {
1906  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open '%1'")
1907  .arg(documentFile));
1908  return false;
1909  }
1910 
1911  QTextStream stream(&file);
1912  bool in_form = false;
1913  QString get_action;
1914  QMap<QString,QString> name_value;
1915 
1916  m_rawLineups.clear();
1917 
1918  while (!stream.atEnd())
1919  {
1920  QString line = stream.readLine();
1921  QString llow = line.toLower();
1922  int frm = llow.indexOf("<form");
1923  if (frm >= 0)
1924  {
1925  in_form = true;
1926  get_action = get_setting(line.mid(frm + 5), "action");
1927  name_value.clear();
1928 #if 0
1929  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("action: %1").arg(action));
1930 #endif
1931  }
1932 
1933  if (!in_form)
1934  continue;
1935 
1936  int inp = llow.indexOf("<input");
1937  if (inp >= 0)
1938  {
1939  QString input_line = line.mid(inp + 6);
1940 #if 0
1941  LOG(VB_GENERAL, LOG_DEBUG, LOC +
1942  QString("input: %1").arg(input_line));
1943 #endif
1944  QString name = get_setting(input_line, "name");
1945  QString value = get_setting(input_line, "value");
1946 #if 0
1947  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("name: %1").arg(name));
1948  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("value: %1").arg(value));
1949 #endif
1950  if (!name.isEmpty() && !value.isEmpty())
1951  name_value[name] = value;
1952  }
1953 
1954  if (llow.contains("</form>"))
1955  {
1956  in_form = false;
1957  if (!get_action.isEmpty() &&
1958  !name_value["udl_id"].isEmpty() &&
1959  !name_value["zipcode"].isEmpty() &&
1960  !name_value["lineup_id"].isEmpty())
1961  {
1962  RawLineup item(get_action, name_value["udl_id"],
1963  name_value["zipcode"]);
1964 
1965  m_rawLineups[name_value["lineup_id"]] = item;
1966 #if 0
1967  LOG(VB_GENERAL, LOG_DEBUG, LOC +
1968  QString("<%1> \t--> <%2,%3,%4>")
1969  .arg(name_value["lineup_id"])
1970  .arg(item.udl_id).arg(item.zipcode)
1971  .arg(item.get_action));
1972 #endif
1973  }
1974  }
1975  }
1976  return true;
1977 }
1978 
1979 bool DataDirectProcessor::ParseLineup(const QString &lineupid,
1980  const QString &documentFile)
1981 {
1982  QFile file(documentFile);
1983  if (!file.open(QIODevice::ReadOnly))
1984  {
1985  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open '%1'")
1986  .arg(documentFile));
1987 
1988  return false;
1989  }
1990 
1991  QTextStream stream(&file);
1992  bool in_form = false;
1993  int in_label = 0;
1994  QMap<QString,QString> settings;
1995 
1996  RawLineup &lineup = m_rawLineups[lineupid];
1997  RawLineupChannels &ch = lineup.channels;
1998 
1999  while (!stream.atEnd())
2000  {
2001  QString line = stream.readLine();
2002  QString llow = line.toLower();
2003  int frm = llow.indexOf("<form");
2004  if (frm >= 0)
2005  {
2006  in_form = true;
2007  lineup.set_action = get_setting(line.mid(frm + 5), "action");
2008 #if 0
2009  LOG(VB_GENERAL, LOG_DEBUG, LOC + "set_action: " +
2010  lineup.set_action);
2011 #endif
2012  }
2013 
2014  if (!in_form)
2015  continue;
2016 
2017  int inp = llow.indexOf("<input");
2018  if (inp >= 0)
2019  {
2020  QString in_line = line.mid(inp + 6);
2021  settings.clear();
2022  settings["chk_name"] = get_setting(in_line, "name");
2023  settings["chk_id"] = get_setting(in_line, "id");
2024  settings["chk_value"] = get_setting(in_line, "value");
2025  settings["chk_checked"] = has_setting(in_line, "checked")?"1":"0";
2026  }
2027 
2028  int lbl = llow.indexOf("<label");
2029  if (lbl >= 0)
2030  {
2031  QString lbl_line = line.mid(inp + 6);
2032  QString name = get_setting(lbl_line, "for");
2033  in_label = (name == settings["chk_name"]) ? 1 : 0;
2034  }
2035 
2036  if (in_label)
2037  {
2038  int start = (lbl >= 0) ? lbl + 6 : 0;
2039  int beg = llow.indexOf("<td>", start), end = -1;
2040  if (beg)
2041  end = llow.indexOf("</td>", beg + 4);
2042 
2043  if (end >= 0)
2044  {
2045  QString key = (in_label == 1) ? "lbl_ch" : "lbl_callsign";
2046  QString val = line.mid(beg + 4, end - beg - 4);
2047  settings[key] = val.replace("&nbsp;", "", Qt::CaseInsensitive);
2048  in_label++;
2049  }
2050  }
2051 
2052  in_label = (llow.indexOf("</label") >= 0) ? 0 : in_label;
2053 
2054  if (!in_label &&
2055  !settings["chk_name"].isEmpty() &&
2056  !settings["chk_id"].isEmpty() &&
2057  !settings["chk_value"].isEmpty() &&
2058  !settings["chk_checked"].isEmpty() &&
2059  !settings["lbl_ch"].isEmpty() &&
2060  !settings["lbl_callsign"].isEmpty())
2061  {
2062  RawLineupChannel chan(
2063  settings["chk_name"], settings["chk_id"],
2064  settings["chk_value"], settings["chk_checked"] == "1",
2065  settings["lbl_ch"], settings["lbl_callsign"]);
2066 
2067 #if 0
2068  LOG(VB_GENERAL, LOG_DEBUG, LOC +
2069  QString("name: %1 id: %2 value: %3 "
2070  "checked: %4 ch: %5 call: %6")
2071  .arg(settings["chk_name"]).arg(settings["chk_id"])
2072  .arg(settings["chk_value"]).arg(settings["chk_checked"])
2073  .arg(settings["lbl_ch"],4).arg(settings["lbl_callsign"]));
2074 #endif
2075 
2076  ch.push_back(chan);
2077  settings.clear();
2078  }
2079 
2080  if (llow.contains("</form>"))
2081  {
2082  in_form = false;
2083  }
2084  }
2085  return true;
2086 }
2087 
2088 static QString html_escape(QString str)
2089 {
2090  QString new_str = "";
2091  for (int i = 0; i < str.length(); i++)
2092  {
2093  if (str[i].isLetterOrNumber())
2094  new_str += str[i];
2095  else
2096  {
2097  new_str += QString("%%1").arg((int)str[1].toLatin1(), 0, 16);
2098  }
2099  }
2100 
2101  return new_str;
2102 }
2103 
2104 static QString get_setting(QString line, QString key)
2105 {
2106  QString llow = line.toLower();
2107  QString kfind = key + "=\"";
2108  int beg = llow.indexOf(kfind);
2109 
2110  if (beg >= 0)
2111  {
2112  int end = llow.indexOf("\"", beg + kfind.length());
2113  return line.mid(beg + kfind.length(), end - beg - kfind.length());
2114  }
2115 
2116  kfind = key + "=";
2117  beg = llow.indexOf(kfind);
2118  if (beg < 0)
2119  return QString();
2120 
2121  int i = beg + kfind.length();
2122  while (i < line.length() && !line[i].isSpace() && line[i] != '>')
2123  i++;
2124 
2125  if (i < line.length() && (line[i].isSpace() || line[i] == '>'))
2126  return line.mid(beg + kfind.length(), i - beg - kfind.length());
2127 
2128  return QString();
2129 }
2130 
2131 static bool has_setting(QString line, QString key)
2132 {
2133  return (line.toLower().indexOf(key) >= 0);
2134 }
2135 
2136 static void get_atsc_stuff(QString channum, int freqid,
2137  int &major, int &minor, long long &freq)
2138 {
2139  major = freqid;
2140  minor = 0;
2141 
2142  int chansep = channum.indexOf(QRegExp("\\D"));
2143  if (chansep < 0)
2144  return;
2145 
2146  major = channum.left(chansep).toInt();
2147  minor = channum.right(channum.length() - (chansep + 1)).toInt();
2148 
2149  freq = get_center_frequency("atsc", "vsb8", "us", freqid);
2150 }
2151 
2152 static QString process_dd_station(
2153  uint sourceid, QString chan_major, QString chan_minor,
2154  QString &tvformat, uint &freqid)
2155 {
2156  QString channum = chan_major;
2157  bool ok;
2158  uint minor = chan_minor.toUInt(&ok);
2159 
2160  tvformat = "Default";
2161 
2162  if (minor && ok)
2163  {
2164  tvformat = "atsc";
2165  channum += SourceUtil::GetChannelSeparator(sourceid) + chan_minor;
2166  }
2167  else if (!freqid && (get_lineup_type(sourceid) == "LocalBroadcast"))
2168  freqid = chan_major.toInt();
2169  else
2170  freqid = channum.toInt();
2171 
2172  return channum;
2173 }
2174 
2175 static uint update_channel_basic(uint sourceid, bool insert,
2176  QString xmltvid, QString callsign,
2177  QString name, uint freqid,
2178  QString chan_major, QString chan_minor)
2179 {
2180  callsign = (callsign.isEmpty()) ? name : callsign;
2181 
2182  QString tvformat;
2183  QString channum = process_dd_station(
2184  sourceid, chan_major, chan_minor, tvformat, freqid);
2185 
2186  // First check if channel already in DB, but without xmltvid
2187  MSqlQuery query(MSqlQuery::DDCon());
2188  query.prepare("SELECT chanid, callsign, name "
2189  "FROM channel "
2190  "WHERE sourceid = :SOURCEID AND "
2191  " ( xmltvid = '0' OR xmltvid = '') AND "
2192  " ( channum = :CHANNUM OR "
2193  " ( freqid = :FREQID AND "
2194  " freqid != '0' AND "
2195  " freqid != '' AND "
2196  " atsc_minor_chan = '0') OR "
2197  " ( atsc_major_chan = :MAJORCHAN AND "
2198  " atsc_minor_chan = :MINORCHAN ) )");
2199  query.bindValue(":SOURCEID", sourceid);
2200  query.bindValue(":CHANNUM", channum);
2201  query.bindValue(":FREQID", freqid);
2202  query.bindValue(":MAJORCHAN", chan_major.toUInt());
2203  query.bindValue(":MINORCHAN", chan_minor.toUInt());
2204 
2205  if (!query.exec() || !query.isActive())
2206  {
2207  MythDB::DBError("Getting chanid of existing channel", query);
2208  return 0; // go on to next channel without xmltv
2209  }
2210 
2211  if (query.next())
2212  {
2213  // The channel already exists in DB, at least once,
2214  // so set the xmltvid..
2215  MSqlQuery chan_update_q(MSqlQuery::DDCon());
2216  chan_update_q.prepare(
2217  "UPDATE channel "
2218  "SET xmltvid = :XMLTVID, name = :NAME, callsign = :CALLSIGN "
2219  "WHERE chanid = :CHANID AND sourceid = :SOURCEID");
2220 
2221  uint i = 0;
2222  do
2223  {
2224  uint chanid = query.value(0).toInt();
2225 
2226  QString new_callsign = query.value(1).toString();
2227  new_callsign =
2228  (new_callsign.indexOf(ChannelUtil::GetUnknownCallsign()) == 0) ?
2229  callsign : new_callsign;
2230 
2231  QString new_name = query.value(2).toString();
2232  new_name = (new_name.isEmpty()) ? name : new_name;
2233  new_name = (new_name.isEmpty()) ? new_callsign : new_name;
2234 
2235  chan_update_q.bindValue(":CHANID", chanid);
2236  chan_update_q.bindValue(":NAME", new_name);
2237  chan_update_q.bindValue(":CALLSIGN", new_callsign);
2238  chan_update_q.bindValue(":XMLTVID", xmltvid);
2239  chan_update_q.bindValue(":SOURCEID", sourceid);
2240 
2241 #if 0
2242  LOG(VB_GENERAL, LOG_INFO, LOC +
2243  QString("Updating channel %1: '%2' (%3).")
2244  .arg(chanid).arg(name).arg(callsign));
2245 #endif
2246 
2247  if (!chan_update_q.exec() || !chan_update_q.isActive())
2248  {
2250  "Updating XMLTVID of existing channel", chan_update_q);
2251  continue; // go on to next instance of this channel
2252  }
2253  i++;
2254  }
2255  while (query.next());
2256 
2257  return i; // go on to next channel without xmltv
2258  }
2259 
2260  if (!insert)
2261  return 0; // go on to next channel without xmltv
2262 
2263  // The channel doesn't exist in the DB, insert it...
2264  int mplexid = -1, majorC, minorC, chanid = 0;
2265  long long freq = -1;
2266  get_atsc_stuff(channum, freqid, majorC, minorC, freq);
2267 
2268  if (minorC > 0 && freq >= 0)
2269  mplexid = ChannelUtil::CreateMultiplex(sourceid, "atsc", freq, "8vsb");
2270 
2271  if ((mplexid > 0) || (minorC == 0))
2272  chanid = ChannelUtil::CreateChanID(sourceid, channum);
2273 
2274  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Adding channel %1 '%2' (%3).")
2275  .arg(channum).arg(name).arg(callsign));
2276 
2277  if (chanid > 0)
2278  {
2279  QString icon = "";
2280  int serviceid = 0;
2281  bool oag = false; // use on air guide
2282  bool hidden = false;
2283  bool hidden_in_guide = false;
2284  QString freq_id= QString::number(freqid);
2285 
2287  mplexid, sourceid, chanid,
2288  callsign, name, channum,
2289  serviceid, majorC, minorC,
2290  oag, hidden, hidden_in_guide,
2291  freq_id, icon, tvformat,
2292  xmltvid);
2293  }
2294 
2295  return 1;
2296 }
2297 
2298 static void set_lineup_type(const QString &lineupid, const QString &type)
2299 {
2300  QMutexLocker locker(&lineup_type_lock);
2301  if (lineupid_to_srcid[lineupid])
2302  return;
2303 
2304  // get lineup to source mapping
2305  uint srcid = 0;
2306  MSqlQuery query(MSqlQuery::InitCon());
2307  query.prepare(
2308  "SELECT sourceid "
2309  "FROM videosource "
2310  "WHERE lineupid = :LINEUPID");
2311  query.bindValue(":LINEUPID", lineupid);
2312 
2313  if (!query.exec() || !query.isActive())
2314  MythDB::DBError("end_element", query);
2315  else if (query.next())
2316  srcid = query.value(0).toUInt();
2317 
2318  if (srcid)
2319  {
2320  lineupid_to_srcid[lineupid] = srcid;
2321 
2322  // set type for source
2323  srcid_to_type[srcid] = type;
2324 
2325  LOG(VB_GENERAL, LOG_INFO, LOC +
2326  QString("sourceid %1 has lineup type: %2").arg(srcid).arg(type));
2327  }
2328 }
2329 
2330 static QString get_lineup_type(uint sourceid)
2331 {
2332  QMutexLocker locker(&lineup_type_lock);
2333  return srcid_to_type[sourceid];
2334 }
2335 
2336 /*
2337  * This function taken from:
2338  * http://stackoverflow.com/questions/2690328/qt-quncompress-gzip-data
2339  *
2340  * Based on zlib example code.
2341  *
2342  * Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de>
2343  * Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler
2344  *
2345  * Licensed under the terms of the ZLib license which is found at
2346  * http://zlib.net/zlib_license.html and is as follows:
2347  *
2348  * This software is provided 'as-is', without any express or implied
2349  * warranty. In no event will the authors be held liable for any damages
2350  * arising from the use of this software.
2351  *
2352  * Permission is granted to anyone to use this software for any purpose,
2353  * including commercial applications, and to alter it and redistribute it
2354  * freely, subject to the following restrictions:
2355  *
2356  * 1. The origin of this software must not be misrepresented; you must not
2357  * claim that you wrote the original software. If you use this software
2358  * in a product, an acknowledgment in the product documentation would be
2359  * appreciated but is not required.
2360  * 2. Altered source versions must be plainly marked as such, and must not be
2361  * misrepresented as being the original software.
2362  * 3. This notice may not be removed or altered from any source distribution.
2363  *
2364  * NOTE: The Zlib license is listed as being GPL-compatible
2365  * http://www.gnu.org/licenses/license-list.html#ZLib
2366  */
2367 
2368 QByteArray gUncompress(const QByteArray &data)
2369 {
2370  if (data.size() <= 4) {
2371  LOG(VB_GENERAL, LOG_WARNING, "gUncompress: Input data is truncated");
2372  return QByteArray();
2373  }
2374 
2375  QByteArray result;
2376 
2377  int ret;
2378  z_stream strm;
2379  static const int CHUNK_SIZE = 1024;
2380  char out[CHUNK_SIZE];
2381 
2382  /* allocate inflate state */
2383  strm.zalloc = Z_NULL;
2384  strm.zfree = Z_NULL;
2385  strm.opaque = Z_NULL;
2386  strm.avail_in = data.size();
2387  strm.next_in = (Bytef*)(data.data());
2388 
2389  ret = inflateInit2(&strm, 15 + 32); // gzip decoding
2390  if (ret != Z_OK)
2391  return QByteArray();
2392 
2393  // run inflate()
2394  do {
2395  strm.avail_out = CHUNK_SIZE;
2396  strm.next_out = (Bytef*)(out);
2397 
2398  ret = inflate(&strm, Z_NO_FLUSH);
2399  Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered
2400 
2401  switch (ret) {
2402  case Z_NEED_DICT:
2403  ret = Z_DATA_ERROR;
2404  [[clang::fallthrough]];
2405  case Z_DATA_ERROR:
2406  case Z_MEM_ERROR:
2407  (void)inflateEnd(&strm);
2408  return QByteArray();
2409  }
2410 
2411  result.append(out, CHUNK_SIZE - strm.avail_out);
2412  } while (strm.avail_out == 0);
2413 
2414  // clean up and return
2415  inflateEnd(&strm);
2416  return result;
2417 }
2418 
2419 /* vim: set expandtab tabstop=4 shiftwidth=4: */
static int UpdateChannelsSafe(uint sourceid, bool insert_channels, bool filter_new_channels)
Definition: datadirect.cpp:674
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:794
bool GrabLoginCookiesAndLineups(bool parse_lineups=true)
void SetAll(const QString &lineupid, bool val)
static bool CreateChannel(uint db_mplexid, uint db_sourceid, uint new_channel_id, const QString &callsign, const QString &service_name, const QString &chan_num, uint service_id, uint atsc_major_channel, uint atsc_minor_channel, bool use_on_air_guide, bool hidden, bool hidden_in_guide, const QString &freqid, QString icon=QString(), QString format="Default", QString xmltvid=QString(), QString default_authority=QString())
static bool Post(QString url, const PostList &list, QString documentFile, QString inCookieFile, QString outCookieFile)
bool SaveLineup(const QString &lineupid, const QMap< QString, bool > &xmltvids)
void bindValue(const QString &placeholder, const QVariant &val)
Definition: mythdbcon.cpp:875
QString description
Definition: datadirect.h:155
static bool IsCableCardPresent(uint sourceid)
Definition: sourceutil.cpp:328
static QString get_cache_filename(const QString &lineupid)
static QMap< uint, QString > srcid_to_type
Definition: datadirect.cpp:39
bool ParseLineups(const QString &documentFile)
static QMutex user_agent_lock
Definition: datadirect.cpp:34
vector< PostItem > PostList
Definition: datadirect.h:259
static void set_lineup_type(const QString &lineupid, const QString &type)
bool GrabLineupForModify(const QString &lineupid)
static QString get_setting(QString line, QString key)
QString GetRawUDLID(const QString &lineupid) const
QString zipcode
Definition: datadirect.h:242
QByteArray gUncompress(const QByteArray &data)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QString udl_id
Definition: datadirect.h:241
static QMutex lineup_type_lock
Definition: datadirect.cpp:37
static bool UpdateChannelsUnsafe(uint sourceid, bool filter_new_channels)
Definition: datadirect.cpp:759
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:15
DataDirectProductionCrew curr_productioncrew
Definition: datadirect.h:288
int size(void) const
Definition: mythdbcon.h:187
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:18
DDLineupMap m_lineupmaps
Definition: datadirect.h:437
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool has_setting(QString line, QString key)
static QString user_agent
Definition: datadirect.cpp:35
uint32_t freq[4]
Definition: element.c:44
bool GrabNextSuggestedTime(void)
int toSeconds(const QTime &time)
Returns the total number of seconds since midnight of the supplied QTime.
Definition: mythdate.cpp:203
DDStationList m_stations
Definition: datadirect.h:435
static QString html_escape(QString str)
void Reset(void)
Definition: datadirect.h:144
bool teardown_frequency_tables(void)
void Reset(void)
Definition: datadirect.h:93
static bool IsUnscanable(uint sourceid)
Definition: sourceutil.cpp:317
static guint32 * tmp
Definition: goom_core.c:35
static void get_atsc_stuff(QString channum, int freqid, int &major, int &minor, long long &freq)
bool DDPost(QString url, QString &inputFilename, QDateTime pstartDate, QDateTime pendDate, QString &err_txt)
Definition: datadirect.cpp:977
bool GrabData(const QDateTime &startdate, const QDateTime &enddate)
QString syndicatedEpisodeNumber
Definition: datadirect.h:163
DataDirectStation curr_station
Definition: datadirect.h:283
static void UpdateStationViewTable(QString lineupid)
Definition: datadirect.cpp:603
unsigned char b
Definition: ParseText.cpp:340
friend void authenticationCallback(QNetworkReply *, QAuthenticator *, void *)
Definition: datadirect.cpp:958
void SetDDProgramsStartAt(QDateTime begts)
Definition: datadirect.h:408
QVariant value(int i) const
Definition: mythdbcon.h:182
bool SaveLineupChanges(const QString &lineupid)
RawLineupMap m_rawLineups
Definition: datadirect.h:439
vector< DataDirectLineupMap > DDLineupChannels
Definition: datadirect.h:296
QString GetPassword(void) const
Definition: datadirect.h:344
QString postal
Definition: datadirect.h:82
DataDirectProgram curr_program
Definition: datadirect.h:287
QString relevance
Definition: datadirect.h:202
QString GetDDPFilename(bool &ok) const
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
DataDirectGenre curr_genre
Definition: datadirect.h:289
static QString GetUnknownCallsign(void)
void Reset(void)
Definition: datadirect.h:193
void CreateTempTables(void)
QString get_action
Definition: datadirect.h:239
static const uint16_t * d
DataDirectLineupMap curr_lineupmap
Definition: datadirect.h:285
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
bool SaveLineupToCache(const QString &lineupid) const
QString GetResultFilename(bool &ok) const
bool makeFileAccessible(QString filename)
Makes a file accessible to all frontends/backends.
static QString GetChannelSeparator(uint sourceid)
Definition: sourceutil.cpp:67
vector< RawLineupChannel > RawLineupChannels
Definition: datadirect.h:227
QString stationname
Definition: datadirect.h:57
QString displayname
Definition: datadirect.h:80
#define minor(X)
Definition: compat.h:138
RawLineupChannels channels
Definition: datadirect.h:244
bool isActive(void) const
Definition: mythdbcon.h:188
DataDirectProcessor(uint listings_provider=DD_ZAP2IT, QString userid="", QString password="")
Definition: datadirect.cpp:528
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
static MSqlQueryInfo DDCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:596
QString mpaaRating
Definition: datadirect.h:156
RawLineup GetRawLineup(const QString &lineupid) const
bool UpdateListings(uint sourceid)
uint GetListingsProvider(void) const
Definition: datadirect.h:345
bool startDocument(void) override
Definition: datadirect.cpp:416
DataDirectLineup curr_lineup
Definition: datadirect.h:284
QString stationid
Definition: datadirect.h:55
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:547
#define LOC
Definition: datadirect.cpp:32
bool endElement(const QString &pnamespaceuri, const QString &plocalname, const QString &pqname) override
Definition: datadirect.cpp:210
QString m_inputFilename
Definition: datadirect.h:433
const char * name
Definition: ParseText.cpp:339
static uint update_channel_basic(uint sourceid, bool insert, QString xmltvid, QString callsign, QString name, uint freqid, QString chan_major, QString chan_minor)
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
bool GrabFullLineup(const QString &lineupid, bool restore=true, bool onlyGrabSelected=false, uint cache_age_allowed_in_seconds=0)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
static QString process_dd_station(uint sourceid, QString chan_major, QString chan_minor, QString &tvformat, uint &freqid)
static uint GetConnectionCount(uint sourceid)
Definition: sourceutil.cpp:202
static MythSystemLegacyManager * manager
QString set_action
Definition: datadirect.h:240
QString m_tmpResultFile
Definition: datadirect.h:441
void SetDDProgramsEndAt(QDateTime endts)
Definition: datadirect.h:409
QString GetRawZipCode(const QString &lineupid) const
DDLineupChannels GetDDLineup(const QString &lineupid) const
Definition: datadirect.h:354
bool startElement(const QString &pnamespaceuri, const QString &plocalname, const QString &pqname, const QXmlAttributes &pxmlatts) override
Definition: datadirect.cpp:110
void Reset(void)
Definition: datadirect.h:47
QString createTempFile(QString name_template, bool dir)
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:819
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static QMap< QString, uint > lineupid_to_srcid
Definition: datadirect.cpp:38
void Reset(void)
Definition: datadirect.h:115
bool ParseLineup(const QString &lineupid, const QString &documentFile)
#define Z_NULL
Definition: datadirect.cpp:4
QString lineupid
Definition: datadirect.h:78
QString gclass
Definition: datadirect.h:201
QString affiliate
Definition: datadirect.h:58
static void DataDirectProgramUpdate(void)
Definition: datadirect.cpp:833
DataDirectSchedule curr_schedule
Definition: datadirect.h:286
DataDirectProcessor & parent
Definition: datadirect.h:280
void CreateATempTable(const QString &ptablename, const QString &ptablestruct)
bool characters(const QString &pchars) override
Definition: datadirect.cpp:427
bool GrabAllData(void)
void Reset(void)
Definition: datadirect.h:70
bool endDocument(void) override
Definition: datadirect.cpp:422
static uint CreateMultiplex(int sourceid, QString sistandard, uint64_t frequency, QString modulation, int transport_id=-1, int network_id=-1)
QString CreateTempDirectory(bool *ok=nullptr) const
QString GetCookieFilename(bool &ok) const
bool GrabLineupsOnly(void)
void authenticationCallback(QNetworkReply *reply, QAuthenticator *auth, void *arg)
Definition: datadirect.cpp:958
DDProviders m_providers
Definition: datadirect.h:423
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:615
DDStation GetDDStation(const QString &xmltvid) const
Definition: datadirect.h:359
QDateTime m_cookieFileDT
Definition: datadirect.h:444
static bool IsEncoder(uint sourceid, bool strict=false)
Definition: sourceutil.cpp:281
MythDB * GetMythDB(void)
Definition: mythdb.cpp:46
static bool GetListingsLoginData(uint sourceid, QString &grabber, QString &userid, QString &passwd, QString &lineupid)
Definition: sourceutil.cpp:144
long long get_center_frequency(QString format, QString modulation, QString country, int freqid)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
QStringList m_fatalErrors
Definition: datadirect.h:446
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
QString starRating
Definition: datadirect.h:157
static void UpdateProgramViewTable(uint sourceid)
Definition: datadirect.cpp:628
QString GetUserID(void) const
Definition: datadirect.h:343
QString callsign
Definition: datadirect.h:56
QString lastprogramid
Definition: datadirect.h:290
static bool IsProperlyConnected(uint sourceid, bool strict=false)
Definition: sourceutil.cpp:208
void CreateTemp(const QString &templatefilename, const QString &errmsg, bool directory, QString &filename, bool &ok) const
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
DDLineupList m_lineups
Definition: datadirect.h:436
QString fccchannelnumber
Definition: datadirect.h:59
#define MYTH_SOURCE_VERSION
Definition: version.h:2
static QString get_lineup_type(uint sourceid)
QString device
Definition: datadirect.h:83
Default UTC.
Definition: mythdate.h:14
QDateTime GetLineupCacheAge(const QString &lineupid) const
QString currtagname
Definition: datadirect.h:282
bool GrabLineupsFromCache(const QString &lineupid)