MythTV master
channeldata.cpp
Go to the documentation of this file.
1// C++ headers
2#include <cstdlib>
3#include <iostream>
4
5// Qt headers
6#include <QDir>
7#include <QFile>
8#include <QRegularExpression>
9
10// MythTV headers
11#include "libmythbase/mythdb.h"
15#include "libmythtv/cardutil.h"
19
20// filldata headers
21#include "channeldata.h"
22#include "fillutil.h"
23
24static const QRegularExpression parseMajorMinor { R"((\d+)\D(\d+))" };
25
26static void get_atsc_stuff(const QString& channum, int sourceid, int freqid,
27 int &major, int &minor, long long &freq)
28{
29 major = freqid;
30 minor = 0;
31
32 auto match = parseMajorMinor.match(channum);
33 if (!match.hasMatch())
34 return;
35
36 major = match.capturedView(1).toInt();
37 minor = match.capturedView(2).toInt();
38
39 freq = get_center_frequency("atsc", "vsb8", "us", freqid);
40
41 // Check if this is connected to an HDTV card.
43 query.prepare(
44 "SELECT cardtype "
45 "FROM capturecard "
46 "WHERE sourceid = :SOURCEID");
47 query.bindValue(":SOURCEID", sourceid);
48
49 if (query.exec() && query.isActive() && query.next() &&
50 query.value(0).toString() == "HDTV")
51 {
52 freq -= 1750000; // convert to visual carrier freq.
53 }
54}
55
56bool ChannelData::insert_chan(uint sourceid) const
57{
58 bool insert_channels = m_channelUpdates;
59 if (!insert_channels)
60 {
61 bool isEncoder = false;
62 bool isUnscanable = false;
63 bool isCableCard = SourceUtil::IsCableCardPresent(sourceid);
64 if (m_cardType.isEmpty())
65 {
66 isEncoder = SourceUtil::IsEncoder(sourceid);
67 isUnscanable = SourceUtil::IsUnscanable(sourceid);
68 }
69 else
70 {
72 isUnscanable = CardUtil::IsUnscanable(m_cardType);
73 }
74 insert_channels = (isCableCard || isEncoder || isUnscanable);
75 }
76
77 return insert_channels;
78}
79
80
82 ChannelInfoList::iterator chaninfo, unsigned int chanid) const
83{
84 (*chaninfo).m_name = getResponse(QObject::tr("Choose a channel name (any string, "
85 "long version) "),(*chaninfo).m_name);
86 (*chaninfo).m_callSign = getResponse(QObject::tr("Choose a channel callsign (any string, "
87 "short version) "),(*chaninfo).m_callSign);
88
90 {
91 (*chaninfo).m_chanNum = getResponse(QObject::tr("Choose a channel preset (0..999) "),
92 (*chaninfo).m_chanNum);
93 (*chaninfo).m_freqId = getResponse(QObject::tr("Choose a frequency id "),
94 (*chaninfo).m_freqId);
95 }
96 else
97 {
98 (*chaninfo).m_chanNum = getResponse(QObject::tr("Choose a channel number "),
99 (*chaninfo).m_chanNum);
100 (*chaninfo).m_freqId = (*chaninfo).m_chanNum;
101 }
102
103 (*chaninfo).m_fineTune = getResponse(QObject::tr("Choose a channel fine tune offset "),
104 QString::number((*chaninfo).m_fineTune)).toInt();
105
106 (*chaninfo).m_tvFormat = getResponse(QObject::tr("Choose a TV format "
107 "(PAL/SECAM/NTSC/ATSC/Default) "),
108 (*chaninfo).m_tvFormat);
109
110 (*chaninfo).m_icon = getResponse(QObject::tr("Choose a channel icon image "
111 "(relative path to icon storage group) "),
112 (*chaninfo).m_icon);
113
114 return(chanid);
115}
116
117QString ChannelData::normalizeChannelKey(const QString &chanName)
118{
119 QString result = chanName;
120
121 // Lowercase
122 result = result.toLower();
123 // Strip all whitespace
124 result = result.replace(" ", "");
125
126 return result;
127}
128
130{
131 ChannelList retList;
132
133 uint avail = 0;
137 sourceId, 0,
138 false, "", "",
139 false);
140
141 for (auto & channel : channelList)
142 {
143 QString chanName = channel.m_name;
144 QString key = normalizeChannelKey(chanName);
145 retList.insert(key, channel);
146 }
147
148 return retList;
149}
150
152 ChannelList existingChannels)
153{
154 ChannelList::iterator it;
155 for (it = existingChannels.begin(); it != existingChannels.end(); ++it)
156 {
157 if ((*it).m_xmltvId == chanInfo.m_xmltvId)
158 return (*it);
159 }
160
161 QString searchKey = normalizeChannelKey(chanInfo.m_name);
162 ChannelInfo existChan = existingChannels.value(searchKey);
163
164 if (existChan.m_chanId < 1)
165 {
166 // Check if it is ATSC
167 auto match = parseMajorMinor.match(chanInfo.m_chanNum);
168 if (match.hasMatch())
169 {
170 // Populate xmltvid for scanned ATSC channels
171 uint major = match.capturedView(1).toUInt();
172 uint minor = match.capturedView(2).toUInt();
173
174 for (it = existingChannels.begin();
175 it != existingChannels.end(); ++it)
176 {
177 if ((*it).m_atscMajorChan == major &&
178 (*it).m_atscMinorChan == minor)
179 return (*it);
180 }
181 }
182 }
183
184 return existChan;
185}
186
187void ChannelData::handleChannels(int id, ChannelInfoList *chanlist) const
188{
189 if (m_guideDataOnly)
190 {
191 LOG(VB_GENERAL, LOG_NOTICE, "Skipping Channel Updates");
192 return;
193 }
194
195 ChannelList existingChannels = channelList(id);
196 QString fileprefix = SetupIconCacheDirectory();
197
198 QDir::setCurrent(fileprefix);
199
200 fileprefix += "/";
201
202 bool insertChan = insert_chan(id); // unscannable source
203
204 auto i = chanlist->begin();
205 for (; i != chanlist->end(); ++i)
206 {
207 if ((*i).m_xmltvId.isEmpty())
208 continue;
209
210 QString localfile;
211
212 if (!(*i).m_icon.isEmpty())
213 {
214 QDir remotefile = QDir((*i).m_icon);
215 QString filename = remotefile.dirName();
216
217 localfile = fileprefix + filename;
218 QFile actualfile(localfile);
219 if (!actualfile.exists() &&
220 !GetMythDownloadManager()->download((*i).m_icon, localfile))
221 {
222 LOG(VB_GENERAL, LOG_ERR,
223 QString("Failed to fetch icon from '%1'")
224 .arg((*i).m_icon));
225 }
226
227 localfile = filename;
228 }
229
231
232 if (!(*i).m_oldXmltvId.isEmpty())
233 {
234 query.prepare(
235 "SELECT xmltvid "
236 "FROM channel "
237 "WHERE xmltvid = :XMLTVID");
238 query.bindValue(":XMLTVID", (*i).m_oldXmltvId);
239
240 if (!query.exec())
241 {
242 MythDB::DBError("xmltvid conversion 1", query);
243 }
244 else if (query.next())
245 {
246 LOG(VB_GENERAL, LOG_INFO,
247 QString("Converting old xmltvid (%1) to new (%2)")
248 .arg((*i).m_oldXmltvId, (*i).m_xmltvId));
249
250 query.prepare("UPDATE channel "
251 "SET xmltvid = :NEWXMLTVID"
252 "WHERE xmltvid = :OLDXMLTVID");
253 query.bindValue(":NEWXMLTVID", (*i).m_xmltvId);
254 query.bindValue(":OLDXMLTVID", (*i).m_oldXmltvId);
255
256 if (!query.exec())
257 {
258 MythDB::DBError("xmltvid conversion 2", query);
259 }
260 }
261 }
262
263 ChannelInfo dbChan = FindMatchingChannel(*i, existingChannels);
264 if (dbChan.m_chanId > 0) // Channel exists, updating
265 {
266 LOG(VB_XMLTV, LOG_DEBUG,
267 QString("Match found for xmltvid %1 to channel %2 (%3)")
268 .arg((*i).m_xmltvId, dbChan.m_name, QString::number(dbChan.m_chanId)));
269 if (m_interactive)
270 {
271
272 std::cout << "### " << std::endl;
273 std::cout << "### Existing channel found" << std::endl;
274 std::cout << "### " << std::endl;
275 std::cout << "### xmltvid = "
276 << (*i).m_xmltvId.toLocal8Bit().constData() << std::endl;
277 std::cout << "### chanid = "
278 << dbChan.m_chanId << std::endl;
279 std::cout << "### name = "
280 << dbChan.m_name.toLocal8Bit().constData() << std::endl;
281 std::cout << "### callsign = "
282 << dbChan.m_callSign.toLocal8Bit().constData() << std::endl;
283 std::cout << "### channum = "
284 << dbChan.m_chanNum.toLocal8Bit().constData() << std::endl;
285 if (m_channelPreset)
286 {
287 std::cout << "### freqid = "
288 << dbChan.m_freqId.toLocal8Bit().constData() << std::endl;
289 }
290 std::cout << "### finetune = "
291 << dbChan.m_fineTune << std::endl;
292 std::cout << "### tvformat = "
293 << dbChan.m_tvFormat.toLocal8Bit().constData() << std::endl;
294 std::cout << "### icon = "
295 << dbChan.m_icon.toLocal8Bit().constData() << std::endl;
296 std::cout << "### " << std::endl;
297
298 // The only thing the xmltv data supplies here is the icon
299 (*i).m_name = dbChan.m_name;
300 (*i).m_callSign = dbChan.m_callSign;
301 (*i).m_chanNum = dbChan.m_chanNum;
302 (*i).m_fineTune = dbChan.m_fineTune;
303 (*i).m_freqId = dbChan.m_freqId;
304 (*i).m_tvFormat = dbChan.m_tvFormat;
305
307
308 if ((*i).m_callSign.isEmpty())
309 (*i).m_callSign = dbChan.m_name;
310
311 if (dbChan.m_name != (*i).m_name ||
312 dbChan.m_callSign != (*i).m_callSign ||
313 dbChan.m_chanNum != (*i).m_chanNum ||
314 dbChan.m_fineTune != (*i).m_fineTune ||
315 dbChan.m_freqId != (*i).m_freqId ||
316 dbChan.m_icon != localfile ||
317 dbChan.m_tvFormat != (*i).m_tvFormat)
318 {
319 MSqlQuery subquery(MSqlQuery::InitCon());
320 subquery.prepare("UPDATE channel SET chanid = :CHANID, "
321 "name = :NAME, callsign = :CALLSIGN, "
322 "channum = :CHANNUM, finetune = :FINE, "
323 "icon = :ICON, freqid = :FREQID, "
324 "tvformat = :TVFORMAT "
325 " WHERE xmltvid = :XMLTVID "
326 "AND sourceid = :SOURCEID;");
327 subquery.bindValue(":CHANID", dbChan.m_chanId);
328 subquery.bindValue(":NAME", (*i).m_name);
329 subquery.bindValue(":CALLSIGN", (*i).m_callSign);
330 subquery.bindValue(":CHANNUM", (*i).m_chanNum);
331 subquery.bindValue(":FINE", (*i).m_fineTune);
332 subquery.bindValue(":ICON", localfile);
333 subquery.bindValue(":FREQID", (*i).m_freqId);
334 subquery.bindValue(":TVFORMAT", (*i).m_tvFormat);
335 subquery.bindValue(":XMLTVID", (*i).m_xmltvId);
336 subquery.bindValue(":SOURCEID", id);
337
338 if (!subquery.exec())
339 {
340 MythDB::DBError("update failed", subquery);
341 }
342 else
343 {
344 std::cout << "### " << std::endl;
345 std::cout << "### Change performed" << std::endl;
346 std::cout << "### " << std::endl;
347 }
348 }
349 else
350 {
351 std::cout << "### " << std::endl;
352 std::cout << "### Nothing changed" << std::endl;
353 std::cout << "### " << std::endl;
354 }
355 }
356 else if ((dbChan.m_icon != localfile) ||
357 (dbChan.m_xmltvId != (*i).m_xmltvId))
358 {
359 LOG(VB_XMLTV, LOG_NOTICE, QString("Updating channel %1 (%2)")
360 .arg(dbChan.m_name).arg(dbChan.m_chanId));
361
362 if (localfile.isEmpty())
363 localfile = dbChan.m_icon;
364
365 if (dbChan.m_xmltvId != (*i).m_xmltvId)
366 {
367 MSqlQuery subquery(MSqlQuery::InitCon());
368
369 subquery.prepare("UPDATE channel SET icon = :ICON "
370 ", xmltvid:= :XMLTVID WHERE "
371 "chanid = :CHANID;");
372 subquery.bindValue(":ICON", localfile);
373 subquery.bindValue(":XMLTVID", (*i).m_xmltvId);
374 subquery.bindValue(":CHANID", dbChan.m_chanId);
375
376 if (!subquery.exec())
377 MythDB::DBError("Channel icon change", subquery);
378 }
379 else
380 {
381 MSqlQuery subquery(MSqlQuery::InitCon());
382 subquery.prepare("UPDATE channel SET icon = :ICON WHERE "
383 "chanid = :CHANID;");
384 subquery.bindValue(":ICON", localfile);
385 subquery.bindValue(":CHANID", dbChan.m_chanId);
386
387 if (!subquery.exec())
388 MythDB::DBError("Channel icon change", subquery);
389 }
390
391 }
392 }
393 else if (insertChan) // Only insert channels for non-scannable sources
394 {
395 int major = 0;
396 int minor = 0;
397 long long freq = 0;
398 get_atsc_stuff((*i).m_chanNum, id, (*i).m_freqId.toInt(), major, minor, freq);
399
400 if (m_interactive && ((minor == 0) || (freq > 0)))
401 {
402 std::cout << "### " << std::endl;
403 std::cout << "### New channel found" << std::endl;
404 std::cout << "### " << std::endl;
405 std::cout << "### name = "
406 << (*i).m_name.toLocal8Bit().constData() << std::endl;
407 std::cout << "### callsign = "
408 << (*i).m_callSign.toLocal8Bit().constData() << std::endl;
409 std::cout << "### channum = "
410 << (*i).m_chanNum.toLocal8Bit().constData() << std::endl;
411 if (m_channelPreset)
412 {
413 std::cout << "### freqid = "
414 << (*i).m_freqId.toLocal8Bit().constData() << std::endl;
415 }
416 std::cout << "### finetune = "
417 << (*i).m_fineTune << std::endl;
418 std::cout << "### tvformat = "
419 << (*i).m_tvFormat.toLocal8Bit().constData() << std::endl;
420 std::cout << "### icon = "
421 << localfile.toLocal8Bit().constData() << std::endl;
422 std::cout << "### " << std::endl;
423
424 uint chanid = promptForChannelUpdates(i,0);
425
426 if ((*i).m_callSign.isEmpty())
427 (*i).m_callSign = QString::number(chanid);
428
429 int mplexid = 0;
430 if ((chanid > 0) && (minor > 0))
431 mplexid = ChannelUtil::CreateMultiplex(id, "atsc",
432 freq, "8vsb");
433
434 if (((mplexid > 0) || ((minor == 0) && (chanid > 0))) &&
436 mplexid, id, chanid,
437 (*i).m_callSign, (*i).m_name, (*i).m_chanNum,
438 0 /*service id*/, major, minor,
439 false /*use on air guide*/, kChannelVisible,
440 (*i).m_freqId, localfile, (*i).m_tvFormat,
441 (*i).m_xmltvId))
442 {
443 std::cout << "### " << std::endl;
444 std::cout << "### Channel inserted" << std::endl;
445 std::cout << "### " << std::endl;
446 }
447 else
448 {
449 std::cout << "### " << std::endl;
450 std::cout << "### Channel skipped" << std::endl;
451 std::cout << "### " << std::endl;
452 }
453 }
454 else if ((minor == 0) || (freq > 0))
455 {
456 // We only do this if we are not asked to skip it with the
457 // --update-guide-only (formerly --update) flag.
458 int mplexid = 0;
459 int chanid = 0;
460 if (minor > 0)
461 {
463 id, "atsc", freq, "8vsb");
464 }
465
466 if ((mplexid > 0) || (minor == 0))
467 chanid = ChannelUtil::CreateChanID(id, (*i).m_chanNum);
468
469 if ((*i).m_callSign.isEmpty())
470 {
471 QStringList words = (*i).m_name.simplified().toUpper()
472 .split(" ");
473 QString callsign = "";
474 QString w1 = !words.empty() ? words[0] : QString();
475 QString w2 = words.size() > 1 ? words[1] : QString();
476 if (w1.isEmpty())
477 callsign = QString::number(chanid);
478 else if (w2.isEmpty())
479 callsign = words[0].left(5);
480 else
481 {
482 callsign = w1.left(w2.length() == 1 ? 4:3);
483 callsign += w2.left(5 - callsign.length());
484 }
485 (*i).m_callSign = callsign;
486 }
487
488 if (chanid > 0)
489 {
490 QString cstr = (*i).m_chanNum;
491 if(m_channelPreset && cstr.isEmpty())
492 cstr = QString::number(chanid % 1000);
493
494 bool retval = ChannelUtil::CreateChannel(
495 mplexid, id,
496 chanid,
497 (*i).m_callSign,
498 (*i).m_name, cstr,
499 0 /*service id*/,
500 major, minor,
501 false /*use on air guide*/,
503 (*i).m_freqId,
504 localfile,
505 (*i).m_tvFormat,
506 (*i).m_xmltvId
507 );
508 if (!retval)
509 std::cout << "Channel " << chanid << " creation failed"
510 << std::endl;
511 }
512 }
513 }
514 }
515}
static void get_atsc_stuff(const QString &channum, int sourceid, int freqid, int &major, int &minor, long long &freq)
Definition: channeldata.cpp:26
static const QRegularExpression parseMajorMinor
Definition: channeldata.cpp:24
@ kChannelVisible
Definition: channelinfo.h:23
std::vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:131
QList< ChannelListItem > ChannelList
static bool IsUnscanable(const QString &rawtype)
Definition: cardutil.h:158
static bool IsEncoder(const QString &rawtype)
Definition: cardutil.h:135
bool m_interactive
Definition: channeldata.h:28
void handleChannels(int id, ChannelInfoList *chanlist) const
bool insert_chan(uint sourceid) const
Definition: channeldata.cpp:56
static ChannelList channelList(int sourceId)
static QString normalizeChannelKey(const QString &chanName)
bool m_guideDataOnly
Definition: channeldata.h:29
unsigned int promptForChannelUpdates(ChannelInfoList::iterator chaninfo, unsigned int chanid) const
Definition: channeldata.cpp:81
bool m_channelPreset
Definition: channeldata.h:30
QString m_cardType
Definition: channeldata.h:33
bool m_channelUpdates
Definition: channeldata.h:31
static ChannelInfo FindMatchingChannel(const ChannelInfo &chanInfo, ChannelList existingChannels)
QString m_chanNum
Definition: channelinfo.h:86
uint m_chanId
Definition: channelinfo.h:85
int m_fineTune
Definition: channelinfo.h:95
QString m_tvFormat
Definition: channelinfo.h:105
QString m_name
Definition: channelinfo.h:92
QString m_icon
Definition: channelinfo.h:93
QString m_freqId
Definition: channelinfo.h:87
QString m_callSign
Definition: channelinfo.h:91
QString m_xmltvId
Definition: channelinfo.h:97
static uint CreateMultiplex(int sourceid, const QString &sistandard, uint64_t frequency, const QString &modulation, int transport_id=-1, int network_id=-1)
@ kChanGroupByChanid
Definition: channelutil.h:217
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
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, ChannelVisibleType visible, const QString &freqid, const QString &icon=QString(), QString format="Default", const QString &xmltvid=QString(), const QString &default_authority=QString(), uint service_type=0, int recpriority=0, int tmOffset=0, int commMethod=-1)
static ChannelInfoList LoadChannels(uint startIndex, uint count, uint &totalAvailable, bool ignoreHidden=true, OrderBy orderBy=kChanOrderByChanNum, GroupBy groupBy=kChanGroupByChanid, uint sourceID=0, uint channelGroupID=0, bool liveTVOnly=false, const QString &callsign="", const QString &channum="", bool ignoreUntunable=true)
Load channels from database into a list of ChannelInfo objects.
@ kChanOrderByChanNum
Definition: channelutil.h:208
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
QVariant value(int i) const
Definition: mythdbcon.h:204
bool isActive(void) const
Definition: mythdbcon.h:215
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
static MSqlQueryInfo ChannelCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:599
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
static bool IsEncoder(uint sourceid, bool strict=false)
Definition: sourceutil.cpp:308
static bool IsCableCardPresent(uint sourceid)
Definition: sourceutil.cpp:351
static bool IsUnscanable(uint sourceid)
Definition: sourceutil.cpp:342
#define minor(X)
Definition: compat.h:74
static const std::array< const uint32_t, 4 > freq
Definition: element.cpp:45
QString SetupIconCacheDirectory(void)
Definition: fillutil.cpp:35
unsigned int uint
Definition: freesurround.h:24
long long get_center_frequency(const QString &format, const QString &modulation, const QString &country, int freqid)
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString getResponse(const QString &query, const QString &def)
In an interactive shell, prompt the user to input a string.