MythTV  master
channelimporter.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 /*
3  * Copyright (C) Daniel Kristjansson 2007
4  *
5  * This file is licensed under GPL v2 or (at your option) any later version.
6  *
7  */
8 
9 // C++ includes
10 #include <iostream>
11 #include <utility>
12 #include <algorithm>
13 
14 // Qt includes
15 #include <QTextStream>
16 #include <QElapsedTimer>
17 
18 using namespace std;
19 
20 // MythTV headers
21 #include "channelimporter.h"
22 #include "mythdialogbox.h"
23 #include "mythdb.h"
24 #include "mpegstreamdata.h" // for kEncDecrypted
25 #include "channelutil.h"
26 
27 #define LOC QString("ChanImport: ")
28 
29 static const QString kATSCChannelFormat = "%1_%2";
30 
31 static QString map_str(QString str)
32 {
33  if (str.isEmpty())
34  return "";
35  return str;
36 }
37 
39  int sourceid)
40 {
41  if (_transports.empty())
42  {
43  if (m_useGui)
44  {
45  int channels = ChannelUtil::GetChannelCount(sourceid);
46 
47  LOG(VB_GENERAL, LOG_INFO, LOC + (channels ?
48  (m_success ?
49  QString("Found %1 channels")
50  .arg(channels) :
51  "No new channels to process") :
52  "No channels to process.."));
53 
55  channels ?
56  (m_success ? tr("Found %n channel(s)", "", channels) :
57  tr("Failed to find any new channels!"))
58  : tr("Failed to find any channels."));
59  }
60  else
61  {
62  cout << (ChannelUtil::GetChannelCount() ?
63  "No new channels to process" :
64  "No channels to process..");
65  }
66 
67  return;
68  }
69 
70  ScanDTVTransportList transports = _transports;
71  QString msg;
72  QTextStream ssMsg(&msg);
73 
74  // Scan parameters
75  {
76  bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
77  bool require_a = (m_serviceRequirements & kRequireAudio) != 0;
78  ssMsg << endl << endl;
79  ssMsg << "Scan parameters:" << endl;
80  ssMsg << "Desired Services : " << (require_av ? "tv" : require_a ? "tv+radio" : "all") << endl;
81  ssMsg << "Unencrypted Only : " << (m_ftaOnly ? "yes" : "no") << endl;
82  ssMsg << "Logical Channel Numbers only: " << (m_lcnOnly ? "yes" : "no") << endl;
83  ssMsg << "Complete scan data required : " << (m_completeOnly ? "yes" : "no") << endl;
84  ssMsg << "Full search for old channels: " << (m_fullChannelSearch ? "yes" : "no") << endl;
85  ssMsg << "Remove duplicates : " << (m_removeDuplicates ? "yes" : "no") << endl;
86  }
87 
88  // Transports and channels before processing
89  if (!transports.empty())
90  {
91  ssMsg << endl;
92  ssMsg << "Transport list before processing (" << transports.size() << "):" << endl;
93  ssMsg << FormatTransports(transports).toLatin1().constData();
94 
95  ChannelImporterBasicStats info = CollectStats(transports);
96  ssMsg << endl;
97  ssMsg << "Channel list before processing (";
98  ssMsg << SimpleCountChannels(transports) << "):" << endl;
99  ssMsg << FormatChannels(transports, &info).toLatin1().constData();
100  }
101  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
102 
103  uint saved_scan = 0;
104  if (m_doSave)
105  saved_scan = SaveScan(transports);
106 
107  // Merge transports with the same frequency into one
108  MergeSameFrequency(transports);
109 
110  // Remove duplicate transports with a lower signal strength.
111  if (m_removeDuplicates)
112  {
113  ScanDTVTransportList duplicates;
114  RemoveDuplicates(transports, duplicates);
115  if (!duplicates.empty())
116  {
117  msg = "";
118  ssMsg << endl;
119  ssMsg << "Discarded duplicate transports (" << duplicates.size() << "):" << endl;
120  ssMsg << FormatTransports(duplicates).toLatin1().constData() << endl;
121  ssMsg << "Discarded duplicate channels (" << SimpleCountChannels(duplicates) << "):" << endl;
122  ssMsg << FormatChannels(duplicates).toLatin1().constData() << endl;
123  LOG(VB_CHANSCAN, LOG_INFO, LOC + msg);
124  }
125  }
126 
127  // Remove the channels that do not pass various criteria.
128  FilterServices(transports);
129 
130  // Remove the channels that have been relocated.
131  if (m_removeDuplicates)
132  {
133  FilterRelocatedServices(transports);
134  }
135 
136  // Pull in DB info in transports
137  // Channels not found in scan but only in DB are returned in db_trans
138  sourceid = transports[0].m_channels[0].m_sourceId;
139  ScanDTVTransportList db_trans = GetDBTransports(sourceid, transports);
140  msg = "";
141  ssMsg << endl;
142  if (!db_trans.empty())
143  {
144  ssMsg << endl;
145  ssMsg << "Transports with channels in DB but not in scan (";
146  ssMsg << db_trans.size() << "):" << endl;
147  ssMsg << FormatTransports(db_trans).toLatin1().constData();
148  }
149 
150  // Make sure "Open Cable" channels are marked that way.
151  FixUpOpenCable(transports);
152 
153  // All channels in the scan after comparing with the database
154  {
155  ChannelImporterBasicStats info = CollectStats(transports);
156  ssMsg << endl;
157  ssMsg << "Channel list after compare with database (";
158  ssMsg << SimpleCountChannels(transports) << "):" << endl;
159  ssMsg << FormatChannels(transports, &info).toLatin1().constData();
160  }
161 
162  // Add channels from the DB to the channels from the scan
163  // and possibly delete one or more of the off-air channels
164  if (m_doDelete)
165  {
166  ScanDTVTransportList trans = transports;
167  for (const auto & tran : db_trans)
168  trans.push_back(tran);
169  uint deleted_count = DeleteChannels(trans);
170  if (deleted_count)
171  transports = trans;
172  }
173 
174  // Determine System Info standards..
175  ChannelImporterBasicStats info = CollectStats(transports);
176 
177  // Determine uniqueness of various naming schemes
179  CollectUniquenessStats(transports, info);
180 
181  // Final channel list
182  ssMsg << endl;
183  ssMsg << "Channel list (" << SimpleCountChannels(transports) << "):" << endl;
184  ssMsg << FormatChannels(transports).toLatin1().constData();
185 
186  // Create summary
187  ssMsg << endl;
188  ssMsg << GetSummary(info, stats) << endl;
189 
190  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
191 
192  if (m_doInsert)
193  InsertChannels(transports, info);
194 
195  if (m_doDelete && sourceid)
196  DeleteUnusedTransports(sourceid);
197 
198  if (m_doDelete || m_doInsert)
199  ScanInfo::MarkProcessed(saved_scan);
200 }
201 
203 {
204  switch (type)
205  {
206  // non-conflicting
207  case kATSCNonConflicting: return "ATSC";
208  case kDVBNonConflicting: return "DVB";
209  case kSCTENonConflicting: return "SCTE";
210  case kMPEGNonConflicting: return "MPEG";
211  case kNTSCNonConflicting: return "NTSC";
212  // conflicting
213  case kATSCConflicting: return "ATSC";
214  case kDVBConflicting: return "DVB";
215  case kSCTEConflicting: return "SCTE";
216  case kMPEGConflicting: return "MPEG";
217  case kNTSCConflicting: return "NTSC";
218  }
219  return "Unknown";
220 }
221 
222 // Ask user what to do with the off-air channels
223 //
225  ScanDTVTransportList &transports)
226 {
227  vector<uint> off_air_list;
228  QMap<uint,bool> deleted;
229  ScanDTVTransportList off_air_transports;
230 
231  for (size_t i = 0; i < transports.size(); ++i)
232  {
233  ScanDTVTransport transport_copy;
234  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
235  {
236  ChannelInsertInfo chan = transports[i].m_channels[j];
237  bool was_in_db = (chan.m_dbMplexId != 0U) && (chan.m_channelId != 0U);
238  if (!was_in_db)
239  continue;
240 
241  if (!chan.m_inPmt)
242  {
243  off_air_list.push_back(i<<16|j);
244  AddChanToCopy(transport_copy, transports[i], chan);
245  }
246  }
247  if (!transport_copy.m_channels.empty())
248  off_air_transports.push_back(transport_copy);
249  }
250 
251  if (off_air_list.empty())
252  return 0;
253 
254  // List of off-air channels (in database but not in the scan)
255  cout << endl << "Off-air channels (" << SimpleCountChannels(off_air_transports) << "):" << endl;
256  ChannelImporterBasicStats infoA = CollectStats(off_air_transports);
257  cout << FormatChannels(off_air_transports, &infoA).toLatin1().constData() << endl;
258 
259  // Ask user whether to delete all or some of these stale channels
260  // if some is selected ask about each individually
261  //: %n is the number of channels
262  QString msg = tr("Found %n off-air channel(s).", "", off_air_list.size());
263  DeleteAction action = QueryUserDelete(msg);
264  if (kDeleteIgnoreAll == action)
265  return 0;
266 
267  if (kDeleteAll == action)
268  {
269  for (uint item : off_air_list)
270  {
271  int i = item >> 16;
272  int j = item & 0xFFFF;
274  transports[i].m_channels[j].m_channelId);
275  deleted[item] = true;
276  }
277  }
278  else if (kDeleteInvisibleAll == action)
279  {
280  for (uint item : off_air_list)
281  {
282  int i = item >> 16;
283  int j = item & 0xFFFF;
284  int chanid = transports[i].m_channels[j].m_channelId;
285  QString channum = ChannelUtil::GetChanNum(chanid);
287  ChannelUtil::SetChannelValue("channum", QString("_%1").arg(channum),
288  chanid);
289  }
290  }
291  else
292  {
293  // TODO manual delete
294  }
295 
296  // TODO delete encrypted channels when m_ftaOnly set
297 
298  if (deleted.empty())
299  return 0;
300 
301  // Create a new transports list without the deleted channels
302  ScanDTVTransportList newlist;
303  for (size_t i = 0; i < transports.size(); ++i)
304  {
305  newlist.push_back(transports[i]);
306  newlist.back().m_channels.clear();
307  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
308  {
309  if (!deleted.contains(i<<16|j))
310  {
311  newlist.back().m_channels.push_back(
312  transports[i].m_channels[j]);
313  }
314  }
315  }
316 
317  transports = newlist;
318  return deleted.size();
319 }
320 
322 {
324  query.prepare(
325  "SELECT mplexid FROM dtv_multiplex "
326  "WHERE sourceid = :SOURCEID1 AND "
327  " mplexid NOT IN "
328  " (SELECT mplexid "
329  " FROM channel "
330  " WHERE sourceid = :SOURCEID2)");
331  query.bindValue(":SOURCEID1", sourceid);
332  query.bindValue(":SOURCEID2", sourceid);
333  if (!query.exec())
334  {
335  MythDB::DBError("DeleteUnusedTransports() -- select", query);
336  return 0;
337  }
338 
339  QString msg = tr("Found %n unused transport(s).", "", query.size());
340 
341  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
342 
343  if (query.size() == 0)
344  return 0;
345 
346  DeleteAction action = QueryUserDelete(msg);
347  if (kDeleteIgnoreAll == action)
348  return 0;
349 
350  if (kDeleteAll == action)
351  {
352  query.prepare(
353  "DELETE FROM dtv_multiplex "
354  "WHERE sourceid = :SOURCEID1 AND "
355  " mplexid NOT IN "
356  " (SELECT mplexid "
357  " FROM channel "
358  " WHERE sourceid = :SOURCEID2)");
359  query.bindValue(":SOURCEID1", sourceid);
360  query.bindValue(":SOURCEID2", sourceid);
361  if (!query.exec())
362  {
363  MythDB::DBError("DeleteUnusedTransports() -- delete", query);
364  return 0;
365  }
366  }
367  else
368  {
369  // TODO manual delete
370  LOG(VB_GENERAL, LOG_INFO, LOC + "Manual delete of transport not implemented");
371  }
372  return 0;
373 }
374 
376  const ScanDTVTransportList &transports,
377  const ChannelImporterBasicStats &info)
378 {
379  ScanDTVTransportList list = transports;
380  ScanDTVTransportList inserted;
381  ScanDTVTransportList updated;
382  ScanDTVTransportList skipped_inserts;
383  ScanDTVTransportList skipped_updates;
384 
385  // Insert or update all channels with non-conflicting channum
386  // and complete tuning information.
387  uint chantype = (uint) kChannelTypeNonConflictingFirst;
388  for (; chantype <= (uint) kChannelTypeNonConflictingLast; ++chantype)
389  {
390  auto type = (ChannelType) chantype;
391  uint new_chan = 0;
392  uint old_chan = 0;
393  CountChannels(list, info, type, new_chan, old_chan);
394 
395  if (kNTSCNonConflicting == type)
396  continue;
397 
398  if (old_chan)
399  {
400  //: %n is the number of channels, %1 is the type of channel
401  QString msg = tr("Found %n old %1 channel(s).", "", old_chan)
402  .arg(toString(type));
403 
404  UpdateAction action = QueryUserUpdate(msg);
405  list = UpdateChannels(list, info, action, type, updated, skipped_updates);
406  }
407  if (new_chan)
408  {
409  //: %n is the number of channels, %1 is the type of channel
410  QString msg = tr("Found %n new %1 channel(s).", "", new_chan)
411  .arg(toString(type));
412 
413  InsertAction action = QueryUserInsert(msg);
414  list = InsertChannels(list, info, action, type, inserted, skipped_inserts);
415  }
416  }
417 
418  if (!m_isInteractive)
419  return;
420 
421  // If any of the potential uniques is high and inserting
422  // with those as the channum would result in few conflicts
423  // ask user if it is ok to to proceed using it as the channum
424 
425  // For remaining channels with complete tuning information
426  // insert channels with contiguous list of numbers as the channums
427  chantype = (uint) kChannelTypeConflictingFirst;
428  for (; chantype <= (uint) kChannelTypeConflictingLast; ++chantype)
429  {
430  auto type = (ChannelType) chantype;
431  uint new_chan = 0;
432  uint old_chan = 0;
433  CountChannels(list, info, type, new_chan, old_chan);
434 
435  if (old_chan)
436  {
437  //: %n is the number of channels, %1 is the type of channel
438  QString msg = tr("Found %n conflicting old %1 channel(s).",
439  "", old_chan).arg(toString(type));
440 
441  UpdateAction action = QueryUserUpdate(msg);
442  list = UpdateChannels(list, info, action, type, updated, skipped_updates);
443  }
444  if (new_chan)
445  {
446  //: %n is the number of channels, %1 is the type of channel
447  QString msg = tr("Found %n new conflicting %1 channel(s).",
448  "", new_chan).arg(toString(type));
449 
450  InsertAction action = QueryUserInsert(msg);
451  list = InsertChannels(list, info, action, type, inserted, skipped_inserts);
452  }
453  }
454 
455  QString msg;
456  QTextStream ssMsg(&msg);
457 
458  if (!updated.empty())
459  {
460  ssMsg << endl << endl;
461  ssMsg << "Updated old transports (" << updated.size() << "):" << endl;
462  ssMsg << FormatTransports(updated).toLatin1().constData();
463 
464  ssMsg << endl;
465  ssMsg << "Updated old channels (" << SimpleCountChannels(updated) << "):" << endl;
466  ssMsg << FormatChannels(updated).toLatin1().constData();
467  }
468  if (!skipped_updates.empty())
469  {
470  ssMsg << endl;
471  ssMsg << "Skipped old channels (" << SimpleCountChannels(skipped_updates) << "):" << endl;
472  ssMsg << FormatChannels(skipped_updates).toLatin1().constData();
473  }
474  if (!inserted.empty())
475  {
476  ssMsg << endl;
477  ssMsg << "Inserted new channels (" << SimpleCountChannels(inserted) << "):" << endl;
478  ssMsg << FormatChannels(inserted).toLatin1().constData();
479  }
480  if (!skipped_inserts.empty())
481  {
482  ssMsg << endl;
483  ssMsg << "Skipped new channels (" << SimpleCountChannels(skipped_inserts) << "):" << endl;
484  ssMsg << FormatChannels(skipped_inserts).toLatin1().constData();
485  }
486 
487  // Remaining channels and sum uniques again
488  if (!list.empty())
489  {
490  ChannelImporterBasicStats ninfo = CollectStats(list);
491  ChannelImporterUniquenessStats nstats = CollectUniquenessStats(list, ninfo);
492  ssMsg << endl;
493  ssMsg << "Remaining channels (" << SimpleCountChannels(list) << "):" << endl;
494  ssMsg << FormatChannels(list).toLatin1().constData() << endl;
495  ssMsg << GetSummary(ninfo, nstats).toLatin1().constData();
496  }
497  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
498 }
499 
500 // ChannelImporter::InsertChannels
501 //
502 // transports List of channels to insert
503 // info Channel statistics
504 // action Insert all, Insert manually, Ignore all
505 // type Channel type such as dvb or atsc
506 // inserted_list List of inserted channels
507 // skipped_list List of skipped channels
508 //
509 // return: List of channels that have not been inserted
510 //
512  const ScanDTVTransportList &transports,
513  const ChannelImporterBasicStats &info,
516  ScanDTVTransportList &inserted_list,
517  ScanDTVTransportList &skipped_list)
518 {
519  ScanDTVTransportList next_list;
520 
521  bool cancel_all = false;
522  bool ok_all = false;
523 
524  // Insert all channels with non-conflicting channum
525  // and complete tuning information.
526  for (const auto & transport : transports)
527  {
528  ScanDTVTransport new_transport;
529  ScanDTVTransport inserted_transport;
530  ScanDTVTransport skipped_transport;
531 
532  for (size_t j = 0; j < transport.m_channels.size(); ++j)
533  {
534  ChannelInsertInfo chan = transport.m_channels[j];
535 
536  bool asked = false;
537  bool filter = false;
538  bool handle = false;
539  if (!chan.m_channelId && (kInsertIgnoreAll == action) &&
540  IsType(info, chan, type))
541  {
542  filter = true;
543  }
544  else if (!chan.m_channelId && IsType(info, chan, type))
545  {
546  handle = true;
547  }
548 
549  if (cancel_all)
550  {
551  handle = false;
552  }
553 
554  if (handle && kInsertManual == action)
555  {
556  OkCancelType rc = QueryUserInsert(transport, chan);
557  if (kOCTCancelAll == rc)
558  {
559  cancel_all = true;
560  handle = false;
561  }
562  else if (kOCTCancel == rc)
563  {
564  handle = false;
565  }
566  else if (kOCTOk == rc)
567  {
568  asked = true;
569  }
570  }
571 
572  if (handle)
573  {
574  bool conflicting = false;
575 
576  if (chan.m_chanNum.isEmpty())
577  {
578  if ((kATSCNonConflicting == type) ||
579  (kATSCConflicting == type))
580  {
582  .arg(chan.m_atscMajorChannel)
583  .arg(chan.m_atscMinorChannel);
584  }
585  else
586  {
587  chan.m_chanNum = QString("%1").arg(chan.m_serviceId);
588  }
589  }
590  conflicting = ChannelUtil::IsConflicting(
591  chan.m_chanNum, chan.m_sourceId);
592 
593  // Only ask if not already asked before with kInsertManual
594  if (m_isInteractive && !asked &&
595  (conflicting || (kChannelTypeConflictingFirst <= type)))
596  {
597  bool ok_done = false;
598  if (ok_all)
599  {
600  QString val = ComputeSuggestedChannelNum(chan);
601  bool ok = CheckChannelNumber(val, chan);
602  if (ok)
603  {
604  chan.m_chanNum = val;
605  conflicting = false;
606  ok_done = true;
607  }
608  }
609  if (!ok_done)
610  {
611  OkCancelType rc =
612  QueryUserResolve(transport, chan);
613 
614  conflicting = true;
615  if (kOCTCancelAll == rc)
616  {
617  cancel_all = true;
618  }
619  else if (kOCTOk == rc)
620  {
621  conflicting = false;
622  }
623  else if (kOCTOkAll == rc)
624  {
625  conflicting = false;
626  ok_all = true;
627  }
628  }
629  }
630 
631  if (conflicting)
632  {
633  handle = false;
634  }
635  }
636 
637  bool inserted = false;
638  if (handle)
639  {
640  int chanid = ChannelUtil::CreateChanID(
641  chan.m_sourceId, chan.m_chanNum);
642 
643  chan.m_channelId = (chanid > 0) ? chanid : 0;
644 
645  if (chan.m_channelId)
646  {
647  uint tsid = chan.m_vctTsId;
648  tsid = (tsid) ? tsid : chan.m_sdtTsId;
649  tsid = (tsid) ? tsid : chan.m_patTsId;
650  tsid = (tsid) ? tsid : chan.m_vctChanTsId;
651 
653  chan.m_sourceId, transport, tsid, chan.m_origNetId);
654  }
655 
656  if (chan.m_channelId && chan.m_dbMplexId)
657  {
658  chan.m_channelId = chanid;
659 
660  inserted = ChannelUtil::CreateChannel(
661  chan.m_dbMplexId,
662  chan.m_sourceId,
663  chan.m_channelId,
664  chan.m_callSign,
665  chan.m_serviceName,
666  chan.m_chanNum,
667  chan.m_serviceId,
668  chan.m_atscMajorChannel,
669  chan.m_atscMinorChannel,
670  chan.m_useOnAirGuide,
672  chan.m_freqId,
673  QString(),
674  chan.m_format,
675  QString(),
676  chan.m_defaultAuthority,
677  chan.m_serviceType);
678 
679  if (!transport.m_iptvTuning.GetDataURL().isEmpty())
681  transport.m_iptvTuning);
682  }
683  }
684 
685  if (inserted)
686  {
687  // Update list of inserted channels
688  AddChanToCopy(inserted_transport, transport, chan);
689  }
690 
691  if (filter)
692  {
693  // Update list of skipped channels
694  AddChanToCopy(skipped_transport, transport, chan);
695  }
696  else if (!inserted)
697  {
698  // Update list of remaining channels
699  AddChanToCopy(new_transport, transport, chan);
700  }
701  }
702 
703  if (!new_transport.m_channels.empty())
704  next_list.push_back(new_transport);
705 
706  if (!skipped_transport.m_channels.empty())
707  skipped_list.push_back(skipped_transport);
708 
709  if (!inserted_transport.m_channels.empty())
710  inserted_list.push_back(inserted_transport);
711  }
712 
713  return next_list;
714 }
715 
716 // ChannelImporter::UpdateChannels
717 //
718 // transports List of channels to update
719 // info Channel statistics
720 // action Update All, Ignore All
721 // type Channel type such as dvb or atsc
722 // updated_list List of updated channels
723 // skipped_list List of skipped channels
724 //
725 // return: List of channels that have not been updated
726 //
728  const ScanDTVTransportList &transports,
729  const ChannelImporterBasicStats &info,
732  ScanDTVTransportList &updated_list,
733  ScanDTVTransportList &skipped_list) const
734 {
735  ScanDTVTransportList next_list;
736 
737  // update all channels with non-conflicting channum
738  // and complete tuning information.
739  for (const auto & transport : transports)
740  {
741  ScanDTVTransport new_transport;
742  ScanDTVTransport updated_transport;
743  ScanDTVTransport skipped_transport;
744 
745  for (size_t j = 0; j < transport.m_channels.size(); ++j)
746  {
747  ChannelInsertInfo chan = transport.m_channels[j];
748 
749  bool filter = false;
750  bool handle = false;
751  if (chan.m_channelId && (kUpdateIgnoreAll == action) &&
752  IsType(info, chan, type))
753  {
754  filter = true;
755  }
756  else if (chan.m_channelId && IsType(info, chan, type))
757  {
758  handle = true;
759  }
760 
761  if (handle)
762  {
763  bool conflicting = false;
764 
765  if (m_keepChannelNumbers)
766  {
768  }
769  if (chan.m_chanNum.isEmpty())
770  {
771  if ((kATSCNonConflicting == type) ||
772  (kATSCConflicting == type))
773  {
775  .arg(chan.m_atscMajorChannel)
776  .arg(chan.m_atscMinorChannel);
777  }
778  else
779  {
780  chan.m_chanNum = QString("%1").arg(chan.m_serviceId);
781  }
782  }
783  conflicting = ChannelUtil::IsConflicting(
784  chan.m_chanNum, chan.m_sourceId, chan.m_channelId);
785 
786  if (conflicting)
787  {
788  handle = false;
789 
790  // Update list of skipped channels
791  AddChanToCopy(skipped_transport, transport, chan);
792  }
793  }
794 
795  bool updated = false;
796  if (handle)
797  {
799 
800  // Find the matching multiplex. This updates the
801  // transport and network ID's in case the transport
802  // was created manually
803  uint tsid = chan.m_vctTsId;
804  tsid = (tsid) ? tsid : chan.m_sdtTsId;
805  tsid = (tsid) ? tsid : chan.m_patTsId;
806  tsid = (tsid) ? tsid : chan.m_vctChanTsId;
807 
809  chan.m_sourceId, transport, tsid, chan.m_origNetId);
810 
811  updated = ChannelUtil::UpdateChannel(
812  chan.m_dbMplexId,
813  chan.m_sourceId,
814  chan.m_channelId,
815  chan.m_callSign,
816  chan.m_serviceName,
817  chan.m_chanNum,
818  chan.m_serviceId,
819  chan.m_atscMajorChannel,
820  chan.m_atscMinorChannel,
821  chan.m_useOnAirGuide,
822  ((chan.m_visible == kChannelAlwaysVisible ||
824  chan.m_visible :
826  chan.m_freqId,
827  QString(),
828  chan.m_format,
829  QString(),
830  chan.m_defaultAuthority,
831  chan.m_serviceType);
832  }
833 
834  if (updated)
835  {
836  // Update list of updated channels
837  AddChanToCopy(updated_transport, transport, chan);
838  }
839 
840  if (filter)
841  {
842  // Update list of skipped channels
843  AddChanToCopy(skipped_transport, transport, chan);
844  }
845  else if (!updated)
846  {
847  // Update list of remaining channels
848  AddChanToCopy(new_transport, transport, chan);
849  }
850  }
851 
852  if (!new_transport.m_channels.empty())
853  next_list.push_back(new_transport);
854 
855  if (!skipped_transport.m_channels.empty())
856  skipped_list.push_back(skipped_transport);
857 
858  if (!updated_transport.m_channels.empty())
859  updated_list.push_back(updated_transport);
860  }
861 
862  return next_list;
863 }
864 
865 // ChannelImporter::AddChanToCopy
866 //
867 // Add channel to copy of transport.
868 // This is used to keep track of what is done with each channel
869 //
870 // transport_copy with zero to all channels of transport
871 // transport transport with channel info as scanned
872 // chan one channel of transport, to be copied
873 //
875  ScanDTVTransport &transport_copy,
876  const ScanDTVTransport &transport,
877  const ChannelInsertInfo &chan
878 )
879 {
880  if (transport_copy.m_channels.empty())
881  {
882  transport_copy = transport;
883  transport_copy.m_channels.clear();
884  }
885  transport_copy.m_channels.push_back(chan);
886 }
887 
888 // ChannelImporter::MergeSameFrequency
889 //
890 // Merge transports that are on the same frequency by
891 // combining all channels of both transports into one transport
892 //
894 {
895  ScanDTVTransportList no_dups;
896 
898  if (!transports.empty())
899  tuner_type = transports[0].m_tuner_type;
900 
901  bool is_dvbs = ((DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
902  (DTVTunerType::kTunerTypeDVBS2 == tuner_type));
903 
904  uint freq_mult = (is_dvbs) ? 1 : 1000;
905 
906  vector<bool> ignore;
907  ignore.resize(transports.size());
908  for (size_t i = 0; i < transports.size(); ++i)
909  {
910  if (ignore[i])
911  continue;
912 
913  for (size_t j = i+1; j < transports.size(); ++j)
914  {
915  if (!transports[i].IsEqual(
916  tuner_type, transports[j], 500 * freq_mult))
917  {
918  continue;
919  }
920 
921  for (size_t k = 0; k < transports[j].m_channels.size(); ++k)
922  {
923  bool found_same = false;
924  for (size_t l = 0; l < transports[i].m_channels.size(); ++l)
925  {
926  if (transports[j].m_channels[k].IsSameChannel(
927  transports[i].m_channels[l]))
928  {
929  found_same = true;
930  transports[i].m_channels[l].ImportExtraInfo(
931  transports[j].m_channels[k]);
932  }
933  }
934  if (!found_same)
935  transports[i].m_channels.push_back(transports[j].m_channels[k]);
936  }
937  LOG(VB_CHANSCAN, LOG_INFO, LOC +
938  QString("Transport on same frequency:") + FormatTransport(transports[j]));
939  ignore[j] = true;
940  }
941  no_dups.push_back(transports[i]);
942  }
943  transports = no_dups;
944 }
945 
946 // ChannelImporter::RemoveDuplicates
947 //
948 // When there are two transports that have the same list of channels
949 // but that are received on different frequencies then remove
950 // the transport with the weakest signal.
951 //
952 // In DVB two transports are duplicates when the original network ID and the
953 // transport ID are the same. This is possibly different in ATSC.
954 // Here all channels of both transports are compared.
955 //
957 {
958  LOG(VB_CHANSCAN, LOG_INFO, LOC +
959  QString("Number of transports:%1").arg(transports.size()));
960 
961  ScanDTVTransportList no_dups;
962  vector<bool> ignore;
963  ignore.resize(transports.size());
964  for (size_t i = 0; i < transports.size(); ++i)
965  {
966  ScanDTVTransport &ta = transports[i];
967  LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport " +
968  FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()));
969 
970  if (!ignore[i])
971  {
972  for (size_t j = i+1; j < transports.size(); ++j)
973  {
974  ScanDTVTransport &tb = transports[j];
975  bool found_same = true;
976  bool found_diff = true;
977  if (ta.m_channels.size() == tb.m_channels.size())
978  {
979  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transport A " +
980  FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()));
981  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transport B " +
982  FormatTransport(tb) + QString(" size(%1)").arg(tb.m_channels.size()));
983 
984  for (size_t k = 0; found_same && k < tb.m_channels.size(); ++k)
985  {
986  if (tb.m_channels[k].IsSameChannel(ta.m_channels[k], 0))
987  {
988  found_diff = false;
989  }
990  else
991  {
992  found_same = false;
993  }
994  }
995  }
996 
997  // Transport with the lowest signal strength is duplicate
998  if (found_same && !found_diff)
999  {
1000  size_t lowss = transports[i].m_signalStrength < transports[j].m_signalStrength ? i : j;
1001  ignore[lowss] = true;
1002  duplicates.push_back(transports[lowss]);
1003 
1004  LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate transports found:");
1005  LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport A " + FormatTransport(transports[i]));
1006  LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport B " + FormatTransport(transports[j]));
1007  LOG(VB_CHANSCAN, LOG_INFO, LOC + "Discarding " + FormatTransport(transports[lowss]));
1008  }
1009  }
1010  }
1011  if (!ignore[i])
1012  {
1013  no_dups.push_back(transports[i]);
1014  }
1015  }
1016 
1017  transports = no_dups;
1018 }
1019 
1021 {
1022  bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
1023  bool require_a = (m_serviceRequirements & kRequireAudio) != 0;
1024 
1025  for (auto & transport : transports)
1026  {
1027  ChannelInsertInfoList filtered;
1028  for (auto & channel : transport.m_channels)
1029  {
1030  if (m_ftaOnly && channel.m_isEncrypted &&
1031  channel.m_decryptionStatus != kEncDecrypted)
1032  continue;
1033 
1034  if (require_a && channel.m_isDataService)
1035  continue;
1036 
1037  if (require_av && channel.m_isAudioService)
1038  continue;
1039 
1040  // Filter channels out that do not have a logical channel number
1041  if (m_lcnOnly && channel.m_chanNum.isEmpty())
1042  {
1043  QString msg = FormatChannel(transport, channel);
1044  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("No LCN: %1").arg(msg));
1045  continue;
1046  }
1047 
1048  // Filter channels out that are not present in PAT and PMT.
1049  if (m_completeOnly &&
1050  !(channel.m_inPat &&
1051  channel.m_inPmt ))
1052  {
1053  QString msg = FormatChannel(transport, channel);
1054  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Not in PAT/PMT: %1").arg(msg));
1055  continue;
1056  }
1057 
1058  // Filter channels out that are not present in SDT and that are not ATSC
1059  if (m_completeOnly &&
1060  channel.m_atscMajorChannel == 0 &&
1061  channel.m_atscMinorChannel == 0 &&
1062  !(channel.m_inPat &&
1063  channel.m_inPmt &&
1064  channel.m_inSdt &&
1065  (channel.m_patTsId ==
1066  channel.m_sdtTsId)))
1067  {
1068  QString msg = FormatChannel(transport, channel);
1069  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Not in PAT/PMT/SDT: %1").arg(msg));
1070  continue;
1071  }
1072 
1073  // Filter channels out that do not have a name
1074  if (m_completeOnly && channel.m_serviceName.isEmpty())
1075  {
1076  QString msg = FormatChannel(transport, channel);
1077  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("No name: %1").arg(msg));
1078  continue;
1079  }
1080 
1081  // Filter channels out only in channels.conf, i.e. not found
1082  if (channel.m_inChannelsConf &&
1083  !(channel.m_inPat ||
1084  channel.m_inPmt ||
1085  channel.m_inVct ||
1086  channel.m_inNit ||
1087  channel.m_inSdt))
1088  continue;
1089 
1090  filtered.push_back(channel);
1091  }
1092  transport.m_channels = filtered;
1093  }
1094 }
1095 
1097 {
1098  QMap<uint64_t, bool> rs;
1099 
1100  // Search all channels to find relocated services
1101  for (auto & transport : transports)
1102  {
1103  for (auto & channel : transport.m_channels)
1104  {
1105  if (channel.m_oldOrigNetId > 0)
1106  {
1107  uint64_t key = ((uint64_t)channel.m_oldOrigNetId << 32) | (channel.m_oldTsId << 16) | channel.m_oldServiceId;
1108  rs[key] = true;
1109  }
1110  }
1111  }
1112 
1113  // Remove all relocated services
1114  for (auto & transport : transports)
1115  {
1116  ChannelInsertInfoList filtered;
1117  for (auto & channel : transport.m_channels)
1118  {
1119  uint64_t key = ((uint64_t)channel.m_origNetId << 32) | (channel.m_sdtTsId << 16) | channel.m_serviceId;
1120  if (rs.value(key, false))
1121  {
1122  QString msg = FormatChannel(transport, channel);
1123  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Relocated: %1").arg(msg));
1124  continue;
1125  }
1126  filtered.push_back(channel);
1127  }
1128  transport.m_channels = filtered;
1129  }
1130 }
1131 
1140  uint sourceid, ScanDTVTransportList &transports) const
1141 {
1142  ScanDTVTransportList not_in_scan;
1143  int found_in_same_transport = 0;
1144  int found_in_other_transport = 0;
1145  int found_nowhere = 0;
1146 
1148  if (!transports.empty())
1149  tuner_type = transports[0].m_tuner_type;
1150 
1151  bool is_dvbs =
1152  (DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
1153  (DTVTunerType::kTunerTypeDVBS2 == tuner_type);
1154 
1155  uint freq_mult = (is_dvbs) ? 1 : 1000;
1156 
1158  query.prepare(
1159  "SELECT mplexid "
1160  "FROM dtv_multiplex "
1161  "WHERE sourceid = :SOURCEID "
1162  "GROUP BY mplexid "
1163  "ORDER BY mplexid");
1164  query.bindValue(":SOURCEID", sourceid);
1165 
1166  if (!query.exec())
1167  {
1168  MythDB::DBError("GetDBTransports()", query);
1169  return not_in_scan;
1170  }
1171 
1172  QMap<uint,bool> found_in_scan;
1173  while (query.next())
1174  {
1175  ScanDTVTransport db_transport;
1176  uint mplexid = query.value(0).toUInt();
1177  if (db_transport.FillFromDB(tuner_type, mplexid))
1178  {
1179  if (db_transport.m_channels.empty())
1180  {
1181  continue;
1182  }
1183  }
1184 
1185  bool found_transport = false;
1186  QMap<uint,bool> found_in_database;
1187 
1188  // Search for old channels in the same transport of the scan.
1189  for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1190  {
1191  ScanDTVTransport &scan_transport = transports[ist]; // Transport from the scan
1192  if (scan_transport.IsEqual(tuner_type, db_transport, 500 * freq_mult, true)) // Same transport?
1193  {
1194  found_transport = true; // Yes
1195  scan_transport.m_mplex = db_transport.m_mplex; // Found multiplex
1196  for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1197  {
1198  if (!found_in_database[jdc]) // Channel not found yet?
1199  {
1200  ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1201  for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1202  { // Channel in scanned transport
1203  if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1204  {
1205  ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1206  if (db_chan.IsSameChannel(scan_chan, 2)) // Same transport, relaxed check
1207  {
1208  found_in_same_transport++;
1209  found_in_database[jdc] = true; // Channel from db found in scan
1210  found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1211  scan_chan.m_dbMplexId = db_transport.m_mplex; // Found multiplex
1212  scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1213  break; // Ready with scanned transport
1214  }
1215  }
1216  }
1217  }
1218  }
1219  }
1220  }
1221 
1222  // Search for old channels in all transports of the scan.
1223  // This is done for all channels that have not yet been found.
1224  // This can identify the channels that have moved to another transport.
1225  if (m_fullChannelSearch)
1226  {
1227  for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1228  {
1229  ScanDTVTransport &scan_transport = transports[ist]; // Scanned transport
1230  for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1231  {
1232  if (!found_in_database[jdc]) // Channel not found yet?
1233  {
1234  ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1235  for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1236  {
1237  if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1238  {
1239  ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1240  if (db_chan.IsSameChannel(scan_chan, 1)) // Other transport, check
1241  { // network id and service id
1242  found_in_other_transport++;
1243  found_in_database[jdc] = true; // Channel from db found in scan
1244  found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1245  scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1246  break; // Ready with scanned transport
1247  }
1248  }
1249  }
1250  }
1251  }
1252  }
1253  }
1254 
1255  // If the transport in the database is found in the scan
1256  // then all channels in that transport that are not found
1257  // in the scan are copied to the "not_in_scan" list.
1258  if (found_transport)
1259  {
1260  ScanDTVTransport tmp = db_transport;
1261  tmp.m_channels.clear();
1262 
1263  for (size_t idc = 0; idc < db_transport.m_channels.size(); ++idc)
1264  {
1265  if (!found_in_database[idc])
1266  {
1267  tmp.m_channels.push_back(db_transport.m_channels[idc]);
1268  found_nowhere++;
1269  }
1270  }
1271 
1272  if (!tmp.m_channels.empty())
1273  not_in_scan.push_back(tmp);
1274  }
1275  }
1276  LOG(VB_GENERAL, LOG_INFO, LOC +
1277  QString("Old channels found in same transport: %1")
1278  .arg(found_in_same_transport));
1279  LOG(VB_GENERAL, LOG_INFO, LOC +
1280  QString("Old channels found in other transport: %1")
1281  .arg(found_in_other_transport));
1282  LOG(VB_GENERAL, LOG_INFO, LOC +
1283  QString("Old channels not found (off-air): %1")
1284  .arg(found_nowhere));
1285 
1286  return not_in_scan;
1287 }
1288 
1290 {
1292  for (auto & transport : transports)
1293  {
1294  for (auto & chan : transport.m_channels)
1295  {
1296  if (((chan.m_couldBeOpencable && (chan.m_siStandard == "mpeg")) ||
1297  chan.m_isOpencable) && !chan.m_inVct)
1298  {
1299  chan.m_siStandard = "opencable";
1300  }
1301  }
1302  }
1303 }
1304 
1306  const ScanDTVTransportList &transports)
1307 {
1309  for (const auto & transport : transports)
1310  {
1311  for (const auto & chan : transport.m_channels)
1312  {
1313  int enc = (chan.m_isEncrypted) ?
1314  ((chan.m_decryptionStatus == kEncDecrypted) ? 2 : 1) : 0;
1315  if (chan.m_siStandard == "atsc") info.m_atscChannels[enc] += 1;
1316  if (chan.m_siStandard == "dvb") info.m_dvbChannels[enc] += 1;
1317  if (chan.m_siStandard == "mpeg") info.m_mpegChannels[enc] += 1;
1318  if (chan.m_siStandard == "opencable") info.m_scteChannels[enc] += 1;
1319  if (chan.m_siStandard == "ntsc") info.m_ntscChannels[enc] += 1;
1320  if (chan.m_siStandard != "ntsc")
1321  {
1322  ++info.m_progNumCnt[chan.m_serviceId];
1323  ++info.m_chanNumCnt[map_str(chan.m_chanNum)];
1324  }
1325  if (chan.m_siStandard == "atsc")
1326  {
1327  ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1328  (chan.m_atscMinorChannel)];
1329  ++info.m_atscMinCnt[chan.m_atscMinorChannel];
1330  ++info.m_atscMajCnt[chan.m_atscMajorChannel];
1331  }
1332  if (chan.m_siStandard == "ntsc")
1333  {
1334  ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1335  (chan.m_atscMinorChannel)];
1336  }
1337  }
1338  }
1339 
1340  return info;
1341 }
1342 
1344  const ScanDTVTransportList &transports,
1345  const ChannelImporterBasicStats &info)
1346 {
1348 
1349  for (const auto & transport : transports)
1350  {
1351  for (const auto & chan : transport.m_channels)
1352  {
1353  stats.m_uniqueProgNum +=
1354  (info.m_progNumCnt[chan.m_serviceId] == 1) ? 1 : 0;
1355  stats.m_uniqueChanNum +=
1356  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1) ? 1 : 0;
1357 
1358  if (chan.m_siStandard == "atsc")
1359  {
1360  stats.m_uniqueAtscNum +=
1361  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1362  (chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1363  stats.m_uniqueAtscMin +=
1364  (info.m_atscMinCnt[(chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1365  stats.m_maxAtscMajCnt = max(
1366  stats.m_maxAtscMajCnt,
1367  info.m_atscMajCnt[chan.m_atscMajorChannel]);
1368  }
1369  }
1370  }
1371 
1372  stats.m_uniqueTotal = (stats.m_uniqueProgNum + stats.m_uniqueAtscNum +
1373  stats.m_uniqueAtscMin + stats.m_uniqueChanNum);
1374 
1375  return stats;
1376 }
1377 
1378 
1380  const ScanDTVTransport &transport,
1381  const ChannelInsertInfo &chan,
1382  const ChannelImporterBasicStats *info)
1383 {
1384  QString msg;
1385  QTextStream ssMsg(&msg);
1386 
1387  ssMsg << transport.m_frequency << ":";
1388 
1389  QString si_standard = (chan.m_siStandard=="opencable") ?
1390  QString("scte") : chan.m_siStandard;
1391 
1392  if (si_standard == "atsc" || si_standard == "scte")
1393  {
1394  ssMsg << (QString("%1:%2:%3-%4:%5:%6=%7=%8:%9")
1395  .arg(chan.m_callSign).arg(chan.m_chanNum)
1396  .arg(chan.m_atscMajorChannel)
1397  .arg(chan.m_atscMinorChannel)
1398  .arg(chan.m_serviceId)
1399  .arg(chan.m_vctTsId)
1400  .arg(chan.m_vctChanTsId)
1401  .arg(chan.m_patTsId)
1402  .arg(si_standard)).toLatin1().constData();
1403  }
1404  else if (si_standard == "dvb")
1405  {
1406  ssMsg << (QString("%1:%2:%3:%4:%5:%6=%7:%8")
1407  .arg(chan.m_serviceName).arg(chan.m_chanNum)
1408  .arg(chan.m_netId).arg(chan.m_origNetId)
1409  .arg(chan.m_serviceId)
1410  .arg(chan.m_sdtTsId)
1411  .arg(chan.m_patTsId)
1412  .arg(si_standard)).toLatin1().constData();
1413  }
1414  else
1415  {
1416  ssMsg << (QString("%1:%2:%3:%4:%5")
1417  .arg(chan.m_callSign).arg(chan.m_chanNum)
1418  .arg(chan.m_serviceId)
1419  .arg(chan.m_patTsId)
1420  .arg(si_standard)).toLatin1().constData();
1421  }
1422 
1423  if (info)
1424  {
1425  ssMsg << ' ';
1426  msg = msg.leftJustified(72);
1427 
1428  ssMsg << chan.m_channelId;
1429 
1430  ssMsg << ":"
1431  << (QString("cnt(pnum:%1,channum:%2)")
1432  .arg(info->m_progNumCnt[chan.m_serviceId])
1433  .arg(info->m_chanNumCnt[map_str(chan.m_chanNum)])
1434  ).toLatin1().constData();
1435 
1436  if (chan.m_siStandard == "atsc")
1437  {
1438  ssMsg <<
1439  (QString(":atsc_cnt(tot:%1,minor:%2)")
1440  .arg(info->m_atscNumCnt[
1441  (chan.m_atscMajorChannel << 16) |
1442  (chan.m_atscMinorChannel)])
1443  .arg(info->m_atscMinCnt[chan.m_atscMinorChannel])
1444  ).toLatin1().constData();
1445  }
1446  }
1447 
1448  return msg;
1449 }
1450 
1463  const ScanDTVTransport &/*transport*/,
1464  const ChannelInsertInfo &chan)
1465 {
1466  QString msg;
1467  QTextStream ssMsg(&msg);
1468 
1469  QString si_standard = (chan.m_siStandard=="opencable") ?
1470  QString("scte") : chan.m_siStandard;
1471 
1472  if (si_standard == "atsc" || si_standard == "scte")
1473  {
1474 
1475  if (si_standard == "atsc")
1476  {
1477  ssMsg << (kATSCChannelFormat
1478  .arg(chan.m_atscMajorChannel)
1479  .arg(chan.m_atscMinorChannel)).toLatin1().constData();
1480  }
1481  else if (chan.m_freqId.isEmpty())
1482  {
1483  ssMsg << (QString("%1-%2")
1484  .arg(chan.m_sourceId)
1485  .arg(chan.m_serviceId)).toLatin1().constData();
1486  }
1487  else
1488  {
1489  ssMsg << (QString("%1-%2")
1490  .arg(chan.m_freqId)
1491  .arg(chan.m_serviceId)).toLatin1().constData();
1492  }
1493 
1494  if (!chan.m_callSign.isEmpty())
1495  ssMsg << (QString(" (%1)")
1496  .arg(chan.m_callSign)).toLatin1().constData();
1497  }
1498  else if (si_standard == "dvb")
1499  {
1500  ssMsg << (QString("%1 (%2 %3)")
1501  .arg(chan.m_serviceName).arg(chan.m_serviceId)
1502  .arg(chan.m_netId)).toLatin1().constData();
1503  }
1504  else if (chan.m_freqId.isEmpty())
1505  {
1506  ssMsg << (QString("%1-%2")
1507  .arg(chan.m_sourceId).arg(chan.m_serviceId))
1508  .toLatin1().constData();
1509  }
1510  else
1511  {
1512  ssMsg << (QString("%1-%2")
1513  .arg(chan.m_freqId).arg(chan.m_serviceId))
1514  .toLatin1().constData();
1515  }
1516 
1517  return msg;
1518 }
1519 
1521  const ScanDTVTransportList &transports_in,
1522  const ChannelImporterBasicStats *info)
1523 {
1524  // Sort transports in order of increasing frequency
1525  struct less_than_key
1526  {
1527  inline bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1528  {
1529  return t1.m_frequency < t2.m_frequency;
1530  }
1531  };
1532  ScanDTVTransportList transports(transports_in);
1533  std::sort(transports.begin(), transports.end(), less_than_key());
1534 
1535  QString msg;
1536 
1537  for (auto & transport : transports)
1538  {
1539  for (auto & channel : transport.m_channels)
1540  {
1541  msg += FormatChannel(transport, channel, info) + "\n";
1542  }
1543  }
1544 
1545  return msg;
1546 }
1547 
1549  const ScanDTVTransport &transport)
1550 {
1551  QString msg;
1552  QTextStream ssMsg(&msg);
1553  ssMsg << transport.toString();
1554  ssMsg << QString(" onid:%1").arg(transport.m_networkID);
1555  ssMsg << QString(" tsid:%1").arg(transport.m_transportID);
1556  ssMsg << QString(" ss:%1").arg(transport.m_signalStrength);
1557  return msg;
1558 }
1559 
1561  const ScanDTVTransportList &transports_in)
1562 {
1563  // Sort transports in order of increasing frequency
1564  struct less_than_key
1565  {
1566  inline bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1567  {
1568  return t1.m_frequency < t2.m_frequency;
1569  }
1570  };
1571  ScanDTVTransportList transports(transports_in);
1572  std::sort(transports.begin(), transports.end(), less_than_key());
1573 
1574  QString msg;
1575 
1576  for (const auto & transport : transports)
1577  msg += FormatTransport(transport) + "\n";
1578 
1579  return msg;
1580 }
1581 
1583  const ChannelImporterBasicStats &info,
1584  const ChannelImporterUniquenessStats &stats)
1585 {
1586  QString msg = tr("Channels: FTA Enc Dec\n") +
1587  QString("ATSC %1 %2 %3\n")
1588  .arg(info.m_atscChannels[0],3)
1589  .arg(info.m_atscChannels[1],3)
1590  .arg(info.m_atscChannels[2],3) +
1591  QString("DVB %1 %2 %3\n")
1592  .arg(info.m_dvbChannels [0],3)
1593  .arg(info.m_dvbChannels [1],3)
1594  .arg(info.m_dvbChannels [2],3) +
1595  QString("SCTE %1 %2 %3\n")
1596  .arg(info.m_scteChannels[0],3)
1597  .arg(info.m_scteChannels[1],3)
1598  .arg(info.m_scteChannels[2],3) +
1599  QString("MPEG %1 %2 %3\n")
1600  .arg(info.m_mpegChannels[0],3)
1601  .arg(info.m_mpegChannels[1],3)
1602  .arg(info.m_mpegChannels[2],3) +
1603  QString("NTSC %1\n")
1604  .arg(info.m_ntscChannels[0],3) +
1605  tr("Unique: prog %1 atsc %2 atsc minor %3 channum %4\n")
1606  .arg(stats.m_uniqueProgNum).arg(stats.m_uniqueAtscNum)
1607  .arg(stats.m_uniqueAtscMin).arg(stats.m_uniqueChanNum) +
1608  tr("Max atsc major count: %1")
1609  .arg(stats.m_maxAtscMajCnt);
1610 
1611  return msg;
1612 }
1613 
1615  const ChannelImporterBasicStats &info,
1616  const ChannelInsertInfo &chan, ChannelType type)
1617 {
1618  switch (type)
1619  {
1620  case kATSCNonConflicting:
1621  return ((chan.m_siStandard == "atsc") /* &&
1622  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1623  (chan.m_atscMinorChannel)] == 1) */);
1624 
1625  case kDVBNonConflicting:
1626  return ((chan.m_siStandard == "dvb") /* &&
1627  (info.m_progNumCnt[chan.m_serviceId] == 1) */);
1628 
1629  case kMPEGNonConflicting:
1630  return ((chan.m_siStandard == "mpeg") &&
1631  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1632 
1633  case kSCTENonConflicting:
1634  return (((chan.m_siStandard == "scte") ||
1635  (chan.m_siStandard == "opencable")) &&
1636  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1637 
1638  case kNTSCNonConflicting:
1639  return ((chan.m_siStandard == "ntsc") &&
1640  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1641  (chan.m_atscMinorChannel)] == 1));
1642 
1643  case kATSCConflicting:
1644  return ((chan.m_siStandard == "atsc") &&
1645  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1646  (chan.m_atscMinorChannel)] != 1));
1647 
1648  case kDVBConflicting:
1649  return ((chan.m_siStandard == "dvb") &&
1650  (info.m_progNumCnt[chan.m_serviceId] != 1));
1651 
1652  case kMPEGConflicting:
1653  return ((chan.m_siStandard == "mpeg") &&
1654  (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1655 
1656  case kSCTEConflicting:
1657  return (((chan.m_siStandard == "scte") ||
1658  (chan.m_siStandard == "opencable")) &&
1659  (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1660 
1661  case kNTSCConflicting:
1662  return ((chan.m_siStandard == "ntsc") &&
1663  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1664  (chan.m_atscMinorChannel)] != 1));
1665  }
1666  return false;
1667 }
1668 
1670  const ScanDTVTransportList &transports,
1671  const ChannelImporterBasicStats &info,
1672  ChannelType type, uint &new_chan, uint &old_chan)
1673 {
1674  new_chan = old_chan = 0;
1675  for (const auto & transport : transports)
1676  {
1677  for (const auto& chan : transport.m_channels)
1678  {
1679  if (IsType(info, chan, type))
1680  {
1681  if (chan.m_channelId)
1682  ++old_chan;
1683  else
1684  ++new_chan;
1685  }
1686  }
1687  }
1688 }
1689 
1691  const ScanDTVTransportList &transports)
1692 {
1693  int count = 0;
1694  for (const auto & transport : transports)
1695  count += transport.m_channels.size();
1696  return count;
1697 }
1698 
1714  const ChannelInsertInfo &chan)
1715 {
1716  static QMutex s_lastFreeLock;
1717  static QMap<uint,uint> s_lastFreeChanNumMap;
1718  QString chanNum;
1719 
1720  // Suggest existing channel number if non-conflicting
1722  return chan.m_chanNum;
1723 
1724  // Add a suffix to make it unique
1725  for (char suffix = 'A'; suffix <= 'Z'; ++suffix)
1726  {
1727  chanNum = chan.m_chanNum + suffix;
1728  if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1729  return chanNum;
1730  }
1731 
1732  // Find unused channel number
1733  QMutexLocker locker(&s_lastFreeLock);
1734  uint last_free_chan_num = s_lastFreeChanNumMap[chan.m_sourceId];
1735  for (last_free_chan_num++; ; ++last_free_chan_num)
1736  {
1737  chanNum = QString::number(last_free_chan_num);
1738  if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1739  break;
1740  }
1741  // cppcheck-suppress unreadVariable
1742  s_lastFreeChanNumMap[chan.m_sourceId] = last_free_chan_num;
1743 
1744  return chanNum;
1745 }
1746 
1749 {
1750  DeleteAction action = kDeleteAll;
1751  if (m_useGui)
1752  {
1753  int ret = -1;
1754  do
1755  {
1756  MythScreenStack *popupStack =
1757  GetMythMainWindow()->GetStack("popup stack");
1758  auto *deleteDialog =
1759  new MythDialogBox(msg, popupStack, "deletechannels");
1760 
1761  if (deleteDialog->Create())
1762  {
1763  deleteDialog->AddButton(tr("Delete All"));
1764  deleteDialog->AddButton(tr("Set all invisible"));
1765 // deleteDialog->AddButton(tr("Handle manually"));
1766  deleteDialog->AddButton(tr("Ignore All"));
1767  QObject::connect(deleteDialog, &MythDialogBox::Closed,
1768  [&](const QString & /*resultId*/, int result)
1769  {
1770  ret = result;
1771  m_eventLoop.quit();
1772  });
1773  popupStack->AddScreen(deleteDialog);
1774 
1775  m_eventLoop.exec();
1776  }
1777  } while (ret < 0);
1778 
1779  action = (0 == ret) ? kDeleteAll : action;
1780  action = (1 == ret) ? kDeleteInvisibleAll : action;
1781  action = (2 == ret) ? kDeleteIgnoreAll : action;
1782 // action = (2 == m_deleteChannelResult) ? kDeleteManual : action;
1783 // action = (3 == m_deleteChannelResult) ? kDeleteIgnoreAll : action;
1784  }
1785  else if (m_isInteractive)
1786  {
1787  cout << msg.toLatin1().constData()
1788  << endl
1789  << tr("Do you want to:").toLatin1().constData()
1790  << endl
1791  << tr("1. Delete All").toLatin1().constData()
1792  << endl
1793  << tr("2. Set all invisible").toLatin1().constData()
1794  << endl
1795 // cout << "3. Handle manually" << endl;
1796  << tr("4. Ignore All").toLatin1().constData()
1797  << endl;
1798  while (true)
1799  {
1800  string ret;
1801  cin >> ret;
1802  bool ok = false;
1803  uint val = QString(ret.c_str()).toUInt(&ok);
1804  if (ok && (val == 1 || val == 2 || val == 4))
1805  {
1806  action = (1 == val) ? kDeleteAll : action;
1807  action = (2 == val) ? kDeleteInvisibleAll : action;
1808  //action = (3 == val) ? kDeleteManual : action;
1809  action = (4 == val) ? kDeleteIgnoreAll : action;
1810  break;
1811  }
1812 
1813  //cout << "Please enter either 1, 2, 3 or 4:" << endl;
1814  cout << tr("Please enter either 1, 2 or 4:")
1815  .toLatin1().constData() << endl;//
1816  }
1817  }
1818 
1819  return action;
1820 }
1821 
1824 {
1825  InsertAction action = kInsertAll;
1826  if (m_useGui)
1827  {
1828  int ret = -1;
1829  do
1830  {
1831  MythScreenStack *popupStack =
1832  GetMythMainWindow()->GetStack("popup stack");
1833  auto *insertDialog =
1834  new MythDialogBox(msg, popupStack, "insertchannels");
1835 
1836  if (insertDialog->Create())
1837  {
1838  insertDialog->AddButton(tr("Insert All"));
1839  insertDialog->AddButton(tr("Insert Manually"));
1840  insertDialog->AddButton(tr("Ignore All"));
1841  QObject::connect(insertDialog, &MythDialogBox::Closed,
1842  [&](const QString & /*resultId*/, int result)
1843  {
1844  ret = result;
1845  m_eventLoop.quit();
1846  });
1847 
1848  popupStack->AddScreen(insertDialog);
1849  m_eventLoop.exec();
1850  }
1851  } while (ret < 0);
1852 
1853  action = (0 == ret) ? kInsertAll : action;
1854  action = (1 == ret) ? kInsertManual : action;
1855  action = (2 == ret) ? kInsertIgnoreAll : action;
1856  }
1857  else if (m_isInteractive)
1858  {
1859  cout << msg.toLatin1().constData()
1860  << endl
1861  << tr("Do you want to:").toLatin1().constData()
1862  << endl
1863  << tr("1. Insert All").toLatin1().constData()
1864  << endl
1865  << tr("2. Insert Manually").toLatin1().constData()
1866  << endl
1867  << tr("3. Ignore All").toLatin1().constData()
1868  << endl;
1869  while (true)
1870  {
1871  string ret;
1872  cin >> ret;
1873  bool ok = false;
1874  uint val = QString(ret.c_str()).toUInt(&ok);
1875  if (ok && (1 <= val) && (val <= 3))
1876  {
1877  action = (1 == val) ? kInsertAll : action;
1878  action = (2 == val) ? kInsertManual : action;
1879  action = (3 == val) ? kInsertIgnoreAll : action;
1880  break;
1881  }
1882 
1883  cout << tr("Please enter either 1, 2, or 3:")
1884  .toLatin1().constData() << endl;
1885  }
1886  }
1887 
1888  return action;
1889 }
1890 
1893 {
1894  UpdateAction action = kUpdateAll;
1895 
1896  if (m_useGui)
1897  {
1898  int ret = -1;
1899  do
1900  {
1901  MythScreenStack *popupStack =
1902  GetMythMainWindow()->GetStack("popup stack");
1903  auto *updateDialog =
1904  new MythDialogBox(msg, popupStack, "updatechannels");
1905 
1906  if (updateDialog->Create())
1907  {
1908  updateDialog->AddButton(tr("Update All"));
1909  updateDialog->AddButton(tr("Ignore All"));
1910  QObject::connect(updateDialog, &MythDialogBox::Closed,
1911  [&](const QString& /*resultId*/, int result)
1912  {
1913  ret = result;
1914  m_eventLoop.quit();
1915  });
1916 
1917  popupStack->AddScreen(updateDialog);
1918  m_eventLoop.exec();
1919  }
1920  } while (ret < 0);
1921 
1922  action = (0 == ret) ? kUpdateAll : action;
1923  action = (1 == ret) ? kUpdateIgnoreAll : action;
1924  }
1925  else if (m_isInteractive)
1926  {
1927  cout << msg.toLatin1().constData()
1928  << endl
1929  << tr("Do you want to:").toLatin1().constData()
1930  << endl
1931  << tr("1. Update All").toLatin1().constData()
1932  << endl
1933  << tr("2. Update Manually").toLatin1().constData()
1934  << endl
1935  << tr("3. Ignore All").toLatin1().constData()
1936  << endl;
1937  while (true)
1938  {
1939  string ret;
1940  cin >> ret;
1941  bool ok = false;
1942  uint val = QString(ret.c_str()).toUInt(&ok);
1943  if (ok && (1 <= val) && (val <= 3))
1944  {
1945  action = (1 == val) ? kUpdateAll : action;
1946  action = (2 == val) ? kUpdateManual : action;
1947  action = (3 == val) ? kUpdateIgnoreAll : action;
1948  break;
1949  }
1950 
1951  cout << tr("Please enter either 1, 2, or 3:")
1952  .toLatin1().constData() << endl;
1953  }
1954  }
1955 
1956  return action;
1957 }
1958 
1960  MythMainWindow *parent, const QString& title,
1961  const QString& message, QString &text)
1962 {
1963  int dc = -1;
1964  MythScreenStack *popupStack = parent->GetStack("popup stack");
1965  auto *popup = new MythDialogBox(title, message, popupStack,
1966  "manualchannelpopup");
1967 
1968  if (popup->Create())
1969  {
1970  popup->AddButton(QCoreApplication::translate("(Common)", "OK"));
1971  popup->AddButton(tr("Edit"));
1972  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel"));
1973  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel All"));
1974  QObject::connect(popup, &MythDialogBox::Closed,
1975  [&](const QString & /*resultId*/, int result)
1976  {
1977  dc = result;
1978  m_eventLoop.quit();
1979  });
1980  popupStack->AddScreen(popup);
1981  m_eventLoop.exec();
1982  }
1983  else
1984  {
1985  delete popup;
1986  popup = nullptr;
1987  }
1988 
1989  // Choice "Edit"
1990  if (1 == dc)
1991  {
1992  auto *textEdit =
1993  new MythTextInputDialog(popupStack,
1994  tr("Please enter a unique channel number."),
1995  FilterNone, false, text);
1996  if (textEdit->Create())
1997  {
1998  QObject::connect(textEdit, &MythTextInputDialog::haveResult,
1999  [&](QString result)
2000  {
2001  dc = 0;
2002  text = std::move(result);
2003  });
2004  QObject::connect(textEdit, &MythTextInputDialog::Exiting,
2005  [&]()
2006  {
2007  m_eventLoop.quit();
2008  });
2009 
2010  popupStack->AddScreen(textEdit);
2011  m_eventLoop.exec();
2012  }
2013  else
2014  delete textEdit;
2015  }
2016 
2017  OkCancelType rval = kOCTCancel;
2018  switch (dc) {
2019  case 0: rval = kOCTOk; break;
2020  // NOLINTNEXTLINE(bugprone-branch-clone)
2021  case 1: rval = kOCTCancel; break; // "Edit" is done already
2022  case 2: rval = kOCTCancel; break;
2023  case 3: rval = kOCTCancelAll; break;
2024  }
2025  return rval;
2026 }
2027 
2029  MythMainWindow *parent, const QString& title,
2030  const QString& message, QString &text)
2031 {
2032  int dc = -1;
2033  MythScreenStack *popupStack = parent->GetStack("popup stack");
2034  auto *popup = new MythDialogBox(title, message, popupStack,
2035  "resolvechannelpopup");
2036 
2037  if (popup->Create())
2038  {
2039  popup->AddButton(QCoreApplication::translate("(Common)", "OK"));
2040  popup->AddButton(QCoreApplication::translate("(Common)", "OK All"));
2041  popup->AddButton(tr("Edit"));
2042  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel"));
2043  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel All"));
2044  QObject::connect(popup, &MythDialogBox::Closed,
2045  [&](const QString & /*resultId*/, int result)
2046  {
2047  dc = result;
2048  m_eventLoop.quit();
2049  });
2050  popupStack->AddScreen(popup);
2051  m_eventLoop.exec();
2052  }
2053  else
2054  {
2055  delete popup;
2056  popup = nullptr;
2057  }
2058 
2059  // Choice "Edit"
2060  if (2 == dc)
2061  {
2062  auto *textEdit =
2063  new MythTextInputDialog(popupStack,
2064  tr("Please enter a unique channel number."),
2065  FilterNone, false, text);
2066  if (textEdit->Create())
2067  {
2068  QObject::connect(textEdit, &MythTextInputDialog::haveResult,
2069  [&](QString result)
2070  {
2071  dc = 0;
2072  text = std::move(result);
2073  });
2074  QObject::connect(textEdit, &MythTextInputDialog::Exiting,
2075  [&]()
2076  {
2077  m_eventLoop.quit();
2078  });
2079 
2080  popupStack->AddScreen(textEdit);
2081  m_eventLoop.exec();
2082  }
2083  else
2084  delete textEdit;
2085  }
2086 
2087  OkCancelType rval = kOCTCancel;
2088  switch (dc) {
2089  case 0: rval = kOCTOk; break;
2090  case 1: rval = kOCTOkAll; break;
2091  // NOLINTNEXTLINE(bugprone-branch-clone)
2092  case 2: rval = kOCTCancel; break; // "Edit" is done already
2093  case 3: rval = kOCTCancel; break;
2094  case 4: rval = kOCTCancelAll; break;
2095  }
2096  return rval;
2097 }
2098 
2100  const ScanDTVTransport &transport,
2101  ChannelInsertInfo &chan)
2102 {
2103  QString msg = tr("Channel %1 has channel number %2 but that is already in use.")
2104  .arg(SimpleFormatChannel(transport, chan))
2105  .arg(chan.m_chanNum);
2106 
2107  OkCancelType ret = kOCTCancel;
2108 
2109  if (m_useGui)
2110  {
2111  while (true)
2112  {
2113  QString msg2 = msg;
2114  msg2 += "\n";
2115  msg2 += tr("Please enter a unique channel number.");
2116 
2117  QString val = ComputeSuggestedChannelNum(chan);
2118  msg2 += "\n";
2119  msg2 += tr("Default value is %1.").arg(val);
2120  ret = ShowResolveChannelPopup(
2121  GetMythMainWindow(), tr("Channel Importer"),
2122  msg2, val);
2123 
2124  if (kOCTOk != ret && kOCTOkAll != ret)
2125  break; // user canceled..
2126 
2127  bool ok = CheckChannelNumber(val, chan);
2128  if (ok)
2129  {
2130  chan.m_chanNum = val;
2131  break;
2132  }
2133  }
2134  }
2135  else if (m_isInteractive)
2136  {
2137  cout << msg.toLatin1().constData() << endl;
2138 
2139  QString cancelStr = QCoreApplication::translate("(Common)",
2140  "Cancel").toLower();
2141  QString cancelAllStr = QCoreApplication::translate("(Common)",
2142  "Cancel All").toLower();
2143  QString msg2 = tr("Please enter a non-conflicting channel number "
2144  "(or type '%1' to skip, '%2' to skip all):")
2145  .arg(cancelStr).arg(cancelAllStr);
2146 
2147  while (true)
2148  {
2149  cout << msg2.toLatin1().constData() << endl;
2150  string sret;
2151  cin >> sret;
2152  QString val = QString(sret.c_str());
2153  if (val.toLower() == cancelStr)
2154  {
2155  ret = kOCTCancel;
2156  break; // user canceled..
2157  }
2158  if (val.toLower() == cancelAllStr)
2159  {
2160  ret = kOCTCancelAll;
2161  break; // user canceled..
2162  }
2163 
2164  bool ok = CheckChannelNumber(val, chan);
2165  if (ok)
2166  {
2167  chan.m_chanNum = val;
2168  ret = kOCTOk;
2169  break;
2170  }
2171  }
2172  }
2173 
2174  return ret;
2175 }
2176 
2178  const ScanDTVTransport &transport,
2179  ChannelInsertInfo &chan)
2180 {
2181  QString msg = tr("You chose to manually insert channel %1.")
2182  .arg(SimpleFormatChannel(transport, chan));
2183 
2184  OkCancelType ret = kOCTCancel;
2185 
2186  if (m_useGui)
2187  {
2188  while (true)
2189  {
2190  QString msg2 = msg;
2191  msg2 += " ";
2192  msg2 += tr("Please enter a unique channel number.");
2193 
2194  QString val = ComputeSuggestedChannelNum(chan);
2195  msg2 += " ";
2196  msg2 += tr("Default value is %1").arg(val);
2197  ret = ShowManualChannelPopup(
2198  GetMythMainWindow(), tr("Channel Importer"),
2199  msg2, val);
2200 
2201  if (kOCTOk != ret)
2202  break; // user canceled..
2203 
2204  bool ok = CheckChannelNumber(val, chan);
2205  if (ok)
2206  {
2207  chan.m_chanNum = val;
2208  ret = kOCTOk;
2209  break;
2210  }
2211  }
2212  }
2213  else if (m_isInteractive)
2214  {
2215  cout << msg.toLatin1().constData() << endl;
2216 
2217  QString cancelStr = QCoreApplication::translate("(Common)", "Cancel").toLower();
2218  QString cancelAllStr = QCoreApplication::translate("(Common)", "Cancel All").toLower();
2219 
2220  //: %1 is the translation of "Cancel", %2 of "Cancel All"
2221  QString msg2 = tr("Please enter a non-conflicting channel number "
2222  "(or type '%1' to skip, '%2' to skip all): ")
2223  .arg(cancelStr).arg(cancelAllStr);
2224 
2225  while (true)
2226  {
2227  cout << msg2.toLatin1().constData() << endl;
2228  string sret;
2229  cin >> sret;
2230  QString val = QString(sret.c_str());
2231  if (val.toLower() == cancelStr)
2232  {
2233  ret = kOCTCancel;
2234  break; // user canceled..
2235  }
2236  if (val.toLower() == cancelAllStr)
2237  {
2238  ret = kOCTCancelAll;
2239  break; // user canceled..
2240  }
2241 
2242  bool ok = CheckChannelNumber(val, chan);
2243  if (ok)
2244  {
2245  chan.m_chanNum = val;
2246  ret = kOCTOk;
2247  break;
2248  }
2249  }
2250  }
2251 
2252  return ret;
2253 }
2254 
2255 // ChannelImporter::CheckChannelNumber
2256 //
2257 // Check validity of a new channel number.
2258 // The channel number is not a number but it is a string that starts with a digit.
2259 // The channel number should not yet exist in this video source.
2260 //
2262  const QString &num,
2263  const ChannelInsertInfo &chan)
2264 {
2265  bool ok = (num.length() >= 1);
2266  ok = ok && ((num[0] >= '0') && (num[0] <= '9'));
2267  ok = ok && !ChannelUtil::IsConflicting(
2268  num, chan.m_sourceId, chan.m_channelId);
2269  return ok;
2270 }
DTVMultiplex::m_frequency
uint64_t m_frequency
Definition: dtvmultiplex.h:94
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
ChannelImporterBasicStats::m_chanNumCnt
QMap< QString, uint > m_chanNumCnt
Definition: channelimporter.h:52
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
channel
QDomElement channel
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:498
ChannelImporter::FixUpOpenCable
static void FixUpOpenCable(ScanDTVTransportList &transports)
Definition: channelimporter.cpp:1289
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:203
RemoveDuplicates
Definition: channelscanmiscsettings.h:175
OkCancelType
OkCancelType
Definition: channelimporter.h:26
ChannelInsertInfo::m_useOnAirGuide
bool m_useOnAirGuide
Definition: channelinfo.h:225
kRequireAV
@ kRequireAV
Definition: channelscantypes.h:9
FilterNone
@ FilterNone
Definition: mythuitextedit.h:19
ChannelImporter::QueryUserUpdate
UpdateAction QueryUserUpdate(const QString &msg)
For multiple channels.
Definition: channelimporter.cpp:1892
ChannelImporter::FormatTransports
static QString FormatTransports(const ScanDTVTransportList &transports_in)
Definition: channelimporter.cpp:1560
ChannelInsertInfo::m_chanNum
QString m_chanNum
Definition: channelinfo.h:220
ChannelInsertInfo::m_atscMajorChannel
uint m_atscMajorChannel
Definition: channelinfo.h:223
ShowOkPopup
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
Definition: mythdialogbox.cpp:551
SaveScan
uint SaveScan(const ScanDTVTransportList &scan)
Definition: scaninfo.cpp:22
mythdb.h
ChannelImporter::CountChannels
static void CountChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, ChannelType type, uint &new_chan, uint &old_chan)
Definition: channelimporter.cpp:1669
ScanDTVTransport::m_transportID
uint m_transportID
Definition: dtvmultiplex.h:140
ChannelInsertInfo::m_freqId
QString m_freqId
Definition: channelinfo.h:229
channelimporter.h
kChannelNeverVisible
@ kChannelNeverVisible
Definition: channelinfo.h:27
ChannelImporter::FilterServices
void FilterServices(ScanDTVTransportList &transports) const
Definition: channelimporter.cpp:1020
ChannelImporter::GetDBTransports
ScanDTVTransportList GetDBTransports(uint sourceid, ScanDTVTransportList &transports) const
Adds found channel info to transports list, returns channels in DB which were not found in scan in an...
Definition: channelimporter.cpp:1139
title
QString title
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:633
ChannelUtil::DeleteChannel
static bool DeleteChannel(uint channel_id)
Definition: channelutil.cpp:1766
DTVTunerType::kTunerTypeDVBS1
static const int kTunerTypeDVBS1
Definition: dtvconfparserhelpers.h:93
ChannelInsertInfo::m_origNetId
uint m_origNetId
Definition: channelinfo.h:240
ChannelInsertInfo::m_inPmt
bool m_inPmt
Definition: channelinfo.h:245
kOCTCancel
@ kOCTCancel
Definition: channelimporter.h:28
ChannelUtil::SetChannelValue
static bool SetChannelValue(const QString &field_name, const QString &value, uint sourceid, const QString &channum)
Definition: channelutil.cpp:1138
mythdialogbox.h
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
MythScreenStack
Definition: mythscreenstack.h:16
arg
arg(title).arg(filename).arg(doDelete))
ScanDTVTransportList
vector< ScanDTVTransport > ScanDTVTransportList
Definition: dtvmultiplex.h:143
ChannelInsertInfo::m_defaultAuthority
QString m_defaultAuthority
Definition: channelinfo.h:233
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
ChannelImporter::ComputeSuggestedChannelNum
static QString ComputeSuggestedChannelNum(const ChannelInsertInfo &chan)
Compute a suggested channel number that is unique in the video source.
Definition: channelimporter.cpp:1713
ChannelUtil::SetVisible
static bool SetVisible(uint channel_id, ChannelVisibleType visible)
Definition: channelutil.cpp:1795
ChannelInsertInfo::m_hidden
bool m_hidden
Definition: channelinfo.h:226
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
ChannelInsertInfo::m_dbMplexId
uint m_dbMplexId
Definition: channelinfo.h:215
MythDialogBox::Closed
void Closed(QString, int)
ScanDTVTransport::m_networkID
uint m_networkID
Definition: dtvmultiplex.h:139
ChannelImporterUniquenessStats::m_uniqueProgNum
uint m_uniqueProgNum
Definition: channelimporter.h:60
ChannelImporter::UpdateAction
UpdateAction
Definition: channelimporter.h:110
ChannelImporterBasicStats::m_atscChannels
ChanStats m_atscChannels
Definition: channelimporter.h:41
kChannelAlwaysVisible
@ kChannelAlwaysVisible
Definition: channelinfo.h:24
ChannelImporter::QueryUserDelete
DeleteAction QueryUserDelete(const QString &msg)
For multiple channels.
Definition: channelimporter.cpp:1748
ChannelUtil::CreateChannel
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)
Definition: channelutil.cpp:1482
MythTextInputDialog::haveResult
void haveResult(QString)
ChannelImporterBasicStats::m_atscNumCnt
QMap< uint, uint > m_atscNumCnt
Definition: channelimporter.h:49
MythMainWindow::GetStack
MythScreenStack * GetStack(const QString &stackname)
Definition: mythmainwindow.cpp:309
ChannelImporter::SimpleFormatChannel
static QString SimpleFormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Format channel information into a simple string.
Definition: channelimporter.cpp:1462
kOCTOk
@ kOCTOk
Definition: channelimporter.h:29
ChannelImporter::Process
void Process(const ScanDTVTransportList &_transports, int sourceid=-1)
Definition: channelimporter.cpp:38
kRequireAudio
@ kRequireAudio
Definition: channelscantypes.h:8
ChannelImporter::SimpleCountChannels
static int SimpleCountChannels(const ScanDTVTransportList &transports)
Definition: channelimporter.cpp:1690
ChannelImporter::QueryUserInsert
InsertAction QueryUserInsert(const QString &msg)
For multiple channels.
Definition: channelimporter.cpp:1823
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
ChannelImporter::DeleteUnusedTransports
uint DeleteUnusedTransports(uint sourceid)
Definition: channelimporter.cpp:321
ChannelInsertInfo::m_atscMinorChannel
uint m_atscMinorChannel
Definition: channelinfo.h:224
ChannelImporter::toString
static QString toString(ChannelType type)
Definition: channelimporter.cpp:202
ChannelInsertInfo
Definition: channelinfo.h:136
DTVTunerType
Definition: dtvconfparserhelpers.h:77
ChannelImporter::DeleteAction
DeleteAction
Definition: channelimporter.h:97
ChannelImporter::AddChanToCopy
static void AddChanToCopy(ScanDTVTransport &transport_copy, const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Definition: channelimporter.cpp:874
toString
QString toString(MarkTypes type)
Definition: programtypes.cpp:26
if
if(query.exec() &&query.next())
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:458
ChannelImporter::CollectUniquenessStats
static ChannelImporterUniquenessStats CollectUniquenessStats(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
Definition: channelimporter.cpp:1343
DTVMultiplex::m_mplex
uint m_mplex
Definition: dtvmultiplex.h:110
ChannelImporter::ShowManualChannelPopup
OkCancelType ShowManualChannelPopup(MythMainWindow *parent, const QString &title, const QString &message, QString &text)
Definition: channelimporter.cpp:1959
ChannelImporterBasicStats::m_atscMajCnt
QMap< uint, uint > m_atscMajCnt
Definition: channelimporter.h:51
MythDialogBox
Basic menu dialog, message and a list of options.
Definition: mythdialogbox.h:138
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
ChannelImporter::RemoveDuplicates
static void RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
Definition: channelimporter.cpp:956
ChannelInsertInfo::m_channelId
uint m_channelId
Definition: channelinfo.h:217
ScanDTVTransport::FillFromDB
bool FillFromDB(DTVTunerType type, uint mplexid) override
Definition: dtvmultiplex.cpp:563
ChannelImporter::FormatChannel
static QString FormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan, const ChannelImporterBasicStats *info=nullptr)
Definition: channelimporter.cpp:1379
ChannelUtil::CreateChanID
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
Definition: channelutil.cpp:1446
ChannelImporter::ShowResolveChannelPopup
OkCancelType ShowResolveChannelPopup(MythMainWindow *parent, const QString &title, const QString &message, QString &text)
Definition: channelimporter.cpp:2028
ScanInfo::MarkProcessed
static bool MarkProcessed(uint scanid)
Definition: scaninfo.cpp:200
DTVMultiplex::toString
QString toString() const
Definition: dtvmultiplex.cpp:34
ChannelInsertInfo::IsSameChannel
bool IsSameChannel(const ChannelInsertInfo &other, int relaxed=0) const
Definition: channelinfo.cpp:454
ChannelInsertInfo::m_vctTsId
uint m_vctTsId
Definition: channelinfo.h:237
uint
unsigned int uint
Definition: compat.h:140
ChannelImporter::FormatChannels
static QString FormatChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats *info=nullptr)
Definition: channelimporter.cpp:1520
kEncDecrypted
@ kEncDecrypted
Definition: mpegstreamdata.h:58
ChannelImporter::FilterRelocatedServices
static void FilterRelocatedServices(ScanDTVTransportList &transports)
Definition: channelimporter.cpp:1096
map_str
static QString map_str(QString str)
Definition: channelimporter.cpp:31
ScanDTVTransport::m_signalStrength
int m_signalStrength
Definition: dtvmultiplex.h:141
ChannelInsertInfo::m_callSign
QString m_callSign
Definition: channelinfo.h:218
ChannelInsertInfo::m_format
QString m_format
Definition: channelinfo.h:231
ChannelUtil::UpdateInsertInfoFromDB
static void UpdateInsertInfoFromDB(ChannelInsertInfo &chan)
Definition: channelutil.cpp:1669
channelutil.h
mpegstreamdata.h
ChannelUtil::IsConflicting
static bool IsConflicting(const QString &channum, uint sourceid=0, uint excluded_chanid=0)
Definition: channelutil.h:287
ChannelImporter::CheckChannelNumber
static bool CheckChannelNumber(const QString &num, const ChannelInsertInfo &chan)
Definition: channelimporter.cpp:2261
DTVTunerType::kTunerTypeDVBS2
static const int kTunerTypeDVBS2
Definition: dtvconfparserhelpers.h:94
ChannelImporter::CollectStats
static ChannelImporterBasicStats CollectStats(const ScanDTVTransportList &transports)
Definition: channelimporter.cpp:1305
ChannelUtil::UpdateChannel
static bool UpdateChannel(uint db_mplexid, uint source_id, uint 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=QString(), const QString &icon=QString(), QString format=QString(), const QString &xmltvid=QString(), const QString &default_authority=QString(), uint service_type=0)
Definition: channelutil.cpp:1562
LOC
#define LOC
Definition: channelimporter.cpp:27
ChannelImporterUniquenessStats::m_uniqueChanNum
uint m_uniqueChanNum
Definition: channelimporter.h:63
ChannelInsertInfo::m_serviceName
QString m_serviceName
Definition: channelinfo.h:219
ChannelImporterBasicStats::m_ntscChannels
ChanStats m_ntscChannels
Definition: channelimporter.h:45
ChannelImporterBasicStats::m_progNumCnt
QMap< uint, uint > m_progNumCnt
Definition: channelimporter.h:48
ChannelInsertInfoList
vector< ChannelInsertInfo > ChannelInsertInfoList
Definition: channelinfo.h:261
kOCTCancelAll
@ kOCTCancelAll
Definition: channelimporter.h:27
kChannelVisible
@ kChannelVisible
Definition: channelinfo.h:25
ChannelInsertInfo::m_sdtTsId
uint m_sdtTsId
Definition: channelinfo.h:239
ChannelInsertInfo::m_netId
uint m_netId
Definition: channelinfo.h:241
ChannelUtil::CreateIPTVTuningData
static bool CreateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
Definition: channelutil.h:145
ChannelUtil::CreateMultiplex
static uint CreateMultiplex(int sourceid, const QString &sistandard, uint64_t frequency, const QString &modulation, int transport_id=-1, int network_id=-1)
Definition: channelutil.cpp:371
ChannelImporter::DeleteChannels
uint DeleteChannels(ScanDTVTransportList &transports)
Definition: channelimporter.cpp:224
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
ChannelUtil::UpdateChannelNumberFromDB
static void UpdateChannelNumberFromDB(ChannelInsertInfo &chan)
Definition: channelutil.cpp:1643
ChannelImporterUniquenessStats::m_uniqueTotal
uint m_uniqueTotal
Definition: channelimporter.h:64
ChannelInsertInfo::m_serviceId
uint m_serviceId
Definition: channelinfo.h:221
ChannelImporter::UpdateChannels
ScanDTVTransportList UpdateChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, UpdateAction action, ChannelType type, ScanDTVTransportList &updated, ScanDTVTransportList &skipped) const
Definition: channelimporter.cpp:727
ChannelImporter::MergeSameFrequency
static void MergeSameFrequency(ScanDTVTransportList &transports)
Definition: channelimporter.cpp:893
ChannelInsertInfo::m_sourceId
uint m_sourceId
Definition: channelinfo.h:216
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:107
ChannelImporterUniquenessStats::m_uniqueAtscMin
uint m_uniqueAtscMin
Definition: channelimporter.h:62
ChannelImporterUniquenessStats::m_maxAtscMajCnt
uint m_maxAtscMajCnt
Definition: channelimporter.h:65
build_compdb.action
action
Definition: build_compdb.py:9
ChannelImporter::InsertAction
InsertAction
Definition: channelimporter.h:104
ChannelImporter::InsertChannels
void InsertChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
Definition: channelimporter.cpp:375
MythScreenType::Exiting
void Exiting()
ChannelImporterBasicStats
Definition: channelimporter.h:36
ChannelImporter::FormatTransport
static QString FormatTransport(const ScanDTVTransport &transport)
Definition: channelimporter.cpp:1548
DTVTunerType::kTunerTypeATSC
static const int kTunerTypeATSC
Definition: dtvconfparserhelpers.h:98
ChannelImporterBasicStats::m_scteChannels
ChanStats m_scteChannels
Definition: channelimporter.h:43
ChannelInsertInfo::m_serviceType
uint m_serviceType
Definition: channelinfo.h:222
ChannelImporter::IsType
static bool IsType(const ChannelImporterBasicStats &info, const ChannelInsertInfo &chan, ChannelType type)
Definition: channelimporter.cpp:1614
ChannelUtil::GetChannelCount
static uint GetChannelCount(int sourceid=-1)
Definition: channelutil.cpp:2261
MythTextInputDialog
Dialog prompting the user to enter a text string.
Definition: mythdialogbox.h:264
DTVMultiplex::IsEqual
bool IsEqual(DTVTunerType type, const DTVMultiplex &other, uint freq_range=0, bool fuzzy=false) const
Definition: dtvmultiplex.cpp:50
ChannelUtil::GetChanNum
static QString GetChanNum(int chan_id)
Returns the channel-number string of the given channel.
Definition: channelutil.cpp:776
ChannelImporter::QueryUserResolve
OkCancelType QueryUserResolve(const ScanDTVTransport &transport, ChannelInsertInfo &chan)
For a single channel.
Definition: channelimporter.cpp:2099
ChannelImporterUniquenessStats::m_uniqueAtscNum
uint m_uniqueAtscNum
Definition: channelimporter.h:61
ChannelInsertInfo::m_vctChanTsId
uint m_vctChanTsId
Definition: channelinfo.h:238
ChannelImporterBasicStats::m_dvbChannels
ChanStats m_dvbChannels
Definition: channelimporter.h:42
ChannelImporterBasicStats::m_mpegChannels
ChanStats m_mpegChannels
Definition: channelimporter.h:44
MythScreenStack::AddScreen
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Definition: mythscreenstack.cpp:49
ChannelInsertInfo::m_patTsId
uint m_patTsId
Definition: channelinfo.h:236
query
MSqlQuery query(MSqlQuery::InitCon())
ChannelImporterBasicStats::m_atscMinCnt
QMap< uint, uint > m_atscMinCnt
Definition: channelimporter.h:50
ChannelImporter::ChannelType
ChannelType
Definition: channelimporter.h:117
MythMainWindow
Definition: mythmainwindow.h:33
ChannelInsertInfo::m_siStandard
QString m_siStandard
Definition: channelinfo.h:242
kATSCChannelFormat
static const QString kATSCChannelFormat
Definition: channelimporter.cpp:29
ChannelImporterUniquenessStats
Definition: channelimporter.h:56
kChannelNotVisible
@ kChannelNotVisible
Definition: channelinfo.h:26
ChannelInsertInfo::m_visible
ChannelVisibleType m_visible
Definition: channelinfo.h:228
ChannelImporter::GetSummary
static QString GetSummary(const ChannelImporterBasicStats &info, const ChannelImporterUniquenessStats &stats)
Definition: channelimporter.cpp:1582
ScanDTVTransport
Definition: dtvmultiplex.h:116
ScanDTVTransport::m_channels
ChannelInsertInfoList m_channels
Definition: dtvmultiplex.h:138
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
kOCTOkAll
@ kOCTOkAll
Definition: channelimporter.h:30