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  QElapsedTimer timer;
1100  timer.start();
1101 
1102  // Search all channels to find relocated services
1103  for (auto & transport : transports)
1104  {
1105  for (auto & channel : transport.m_channels)
1106  {
1107  if (channel.m_oldOrigNetId > 0)
1108  {
1109  uint64_t key = ((uint64_t)channel.m_oldOrigNetId << 32) | (channel.m_oldTsId << 16) | channel.m_oldServiceId;
1110  rs[key] = true;
1111  }
1112  }
1113  }
1114 
1115  // Remove all relocated services
1116  for (auto & transport : transports)
1117  {
1118  ChannelInsertInfoList filtered;
1119  for (auto & channel : transport.m_channels)
1120  {
1121  uint64_t key = ((uint64_t)channel.m_origNetId << 32) | (channel.m_sdtTsId << 16) | channel.m_serviceId;
1122  if (rs.value(key, false))
1123  {
1124  QString msg = FormatChannel(transport, channel);
1125  LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Relocated: %1").arg(msg));
1126  continue;
1127  }
1128  filtered.push_back(channel);
1129  }
1130  transport.m_channels = filtered;
1131  }
1132  LOG(VB_CHANSCAN, LOG_DEBUG, QString("%1 processing %2 milliseconds").arg(__func__).arg(timer.elapsed()));
1133 }
1134 
1143  uint sourceid, ScanDTVTransportList &transports) const
1144 {
1145  ScanDTVTransportList not_in_scan;
1146  int found_in_same_transport = 0;
1147  int found_in_other_transport = 0;
1148  int found_nowhere = 0;
1149 
1151  if (!transports.empty())
1152  tuner_type = transports[0].m_tuner_type;
1153 
1154  bool is_dvbs =
1155  (DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
1156  (DTVTunerType::kTunerTypeDVBS2 == tuner_type);
1157 
1158  uint freq_mult = (is_dvbs) ? 1 : 1000;
1159 
1161  query.prepare(
1162  "SELECT mplexid "
1163  "FROM dtv_multiplex "
1164  "WHERE sourceid = :SOURCEID "
1165  "GROUP BY mplexid "
1166  "ORDER BY mplexid");
1167  query.bindValue(":SOURCEID", sourceid);
1168 
1169  if (!query.exec())
1170  {
1171  MythDB::DBError("GetDBTransports()", query);
1172  return not_in_scan;
1173  }
1174 
1175  QMap<uint,bool> found_in_scan;
1176  while (query.next())
1177  {
1178  ScanDTVTransport db_transport;
1179  uint mplexid = query.value(0).toUInt();
1180  if (db_transport.FillFromDB(tuner_type, mplexid))
1181  {
1182  if (db_transport.m_channels.empty())
1183  {
1184  continue;
1185  }
1186  }
1187 
1188  bool found_transport = false;
1189  QMap<uint,bool> found_in_database;
1190 
1191  // Search for old channels in the same transport of the scan.
1192  for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1193  {
1194  ScanDTVTransport &scan_transport = transports[ist]; // Transport from the scan
1195  if (scan_transport.IsEqual(tuner_type, db_transport, 500 * freq_mult, true)) // Same transport?
1196  {
1197  found_transport = true; // Yes
1198  scan_transport.m_mplex = db_transport.m_mplex; // Found multiplex
1199  for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1200  {
1201  if (!found_in_database[jdc]) // Channel not found yet?
1202  {
1203  ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1204  for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1205  { // Channel in scanned transport
1206  if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1207  {
1208  ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1209  if (db_chan.IsSameChannel(scan_chan, 2)) // Same transport, relaxed check
1210  {
1211  found_in_same_transport++;
1212  found_in_database[jdc] = true; // Channel from db found in scan
1213  found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1214  scan_chan.m_dbMplexId = db_transport.m_mplex; // Found multiplex
1215  scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1216  break; // Ready with scanned transport
1217  }
1218  }
1219  }
1220  }
1221  }
1222  }
1223  }
1224 
1225  // Search for old channels in all transports of the scan.
1226  // This is done for all channels that have not yet been found.
1227  // This can identify the channels that have moved to another transport.
1228  if (m_fullChannelSearch)
1229  {
1230  for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1231  {
1232  ScanDTVTransport &scan_transport = transports[ist]; // Scanned transport
1233  for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1234  {
1235  if (!found_in_database[jdc]) // Channel not found yet?
1236  {
1237  ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1238  for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1239  {
1240  if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1241  {
1242  ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1243  if (db_chan.IsSameChannel(scan_chan, 1)) // Other transport, check
1244  { // network id and service id
1245  found_in_other_transport++;
1246  found_in_database[jdc] = true; // Channel from db found in scan
1247  found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1248  scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1249  break; // Ready with scanned transport
1250  }
1251  }
1252  }
1253  }
1254  }
1255  }
1256  }
1257 
1258  // If the transport in the database is found in the scan
1259  // then all channels in that transport that are not found
1260  // in the scan are copied to the "not_in_scan" list.
1261  if (found_transport)
1262  {
1263  ScanDTVTransport tmp = db_transport;
1264  tmp.m_channels.clear();
1265 
1266  for (size_t idc = 0; idc < db_transport.m_channels.size(); ++idc)
1267  {
1268  if (!found_in_database[idc])
1269  {
1270  tmp.m_channels.push_back(db_transport.m_channels[idc]);
1271  found_nowhere++;
1272  }
1273  }
1274 
1275  if (!tmp.m_channels.empty())
1276  not_in_scan.push_back(tmp);
1277  }
1278  }
1279  LOG(VB_GENERAL, LOG_INFO, LOC +
1280  QString("Old channels found in same transport: %1")
1281  .arg(found_in_same_transport));
1282  LOG(VB_GENERAL, LOG_INFO, LOC +
1283  QString("Old channels found in other transport: %1")
1284  .arg(found_in_other_transport));
1285  LOG(VB_GENERAL, LOG_INFO, LOC +
1286  QString("Old channels not found (off-air): %1")
1287  .arg(found_nowhere));
1288 
1289  return not_in_scan;
1290 }
1291 
1293 {
1295  for (auto & transport : transports)
1296  {
1297  for (auto & chan : transport.m_channels)
1298  {
1299  if (((chan.m_couldBeOpencable && (chan.m_siStandard == "mpeg")) ||
1300  chan.m_isOpencable) && !chan.m_inVct)
1301  {
1302  chan.m_siStandard = "opencable";
1303  }
1304  }
1305  }
1306 }
1307 
1309  const ScanDTVTransportList &transports)
1310 {
1312  for (const auto & transport : transports)
1313  {
1314  for (const auto & chan : transport.m_channels)
1315  {
1316  int enc = (chan.m_isEncrypted) ?
1317  ((chan.m_decryptionStatus == kEncDecrypted) ? 2 : 1) : 0;
1318  if (chan.m_siStandard == "atsc") info.m_atscChannels[enc] += 1;
1319  if (chan.m_siStandard == "dvb") info.m_dvbChannels[enc] += 1;
1320  if (chan.m_siStandard == "mpeg") info.m_mpegChannels[enc] += 1;
1321  if (chan.m_siStandard == "opencable") info.m_scteChannels[enc] += 1;
1322  if (chan.m_siStandard == "ntsc") info.m_ntscChannels[enc] += 1;
1323  if (chan.m_siStandard != "ntsc")
1324  {
1325  ++info.m_progNumCnt[chan.m_serviceId];
1326  ++info.m_chanNumCnt[map_str(chan.m_chanNum)];
1327  }
1328  if (chan.m_siStandard == "atsc")
1329  {
1330  ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1331  (chan.m_atscMinorChannel)];
1332  ++info.m_atscMinCnt[chan.m_atscMinorChannel];
1333  ++info.m_atscMajCnt[chan.m_atscMajorChannel];
1334  }
1335  if (chan.m_siStandard == "ntsc")
1336  {
1337  ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1338  (chan.m_atscMinorChannel)];
1339  }
1340  }
1341  }
1342 
1343  return info;
1344 }
1345 
1347  const ScanDTVTransportList &transports,
1348  const ChannelImporterBasicStats &info)
1349 {
1351 
1352  for (const auto & transport : transports)
1353  {
1354  for (const auto & chan : transport.m_channels)
1355  {
1356  stats.m_uniqueProgNum +=
1357  (info.m_progNumCnt[chan.m_serviceId] == 1) ? 1 : 0;
1358  stats.m_uniqueChanNum +=
1359  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1) ? 1 : 0;
1360 
1361  if (chan.m_siStandard == "atsc")
1362  {
1363  stats.m_uniqueAtscNum +=
1364  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1365  (chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1366  stats.m_uniqueAtscMin +=
1367  (info.m_atscMinCnt[(chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1368  stats.m_maxAtscMajCnt = max(
1369  stats.m_maxAtscMajCnt,
1370  info.m_atscMajCnt[chan.m_atscMajorChannel]);
1371  }
1372  }
1373  }
1374 
1375  stats.m_uniqueTotal = (stats.m_uniqueProgNum + stats.m_uniqueAtscNum +
1376  stats.m_uniqueAtscMin + stats.m_uniqueChanNum);
1377 
1378  return stats;
1379 }
1380 
1381 
1383  const ScanDTVTransport &transport,
1384  const ChannelInsertInfo &chan,
1385  const ChannelImporterBasicStats *info)
1386 {
1387  QString msg;
1388  QTextStream ssMsg(&msg);
1389 
1390  ssMsg << transport.m_frequency << ":";
1391 
1392  QString si_standard = (chan.m_siStandard=="opencable") ?
1393  QString("scte") : chan.m_siStandard;
1394 
1395  if (si_standard == "atsc" || si_standard == "scte")
1396  {
1397  ssMsg << (QString("%1:%2:%3-%4:%5:%6=%7=%8:%9")
1398  .arg(chan.m_callSign).arg(chan.m_chanNum)
1399  .arg(chan.m_atscMajorChannel)
1400  .arg(chan.m_atscMinorChannel)
1401  .arg(chan.m_serviceId)
1402  .arg(chan.m_vctTsId)
1403  .arg(chan.m_vctChanTsId)
1404  .arg(chan.m_patTsId)
1405  .arg(si_standard)).toLatin1().constData();
1406  }
1407  else if (si_standard == "dvb")
1408  {
1409  ssMsg << (QString("%1:%2:%3:%4:%5:%6=%7:%8")
1410  .arg(chan.m_serviceName).arg(chan.m_chanNum)
1411  .arg(chan.m_netId).arg(chan.m_origNetId)
1412  .arg(chan.m_serviceId)
1413  .arg(chan.m_sdtTsId)
1414  .arg(chan.m_patTsId)
1415  .arg(si_standard)).toLatin1().constData();
1416  }
1417  else
1418  {
1419  ssMsg << (QString("%1:%2:%3:%4:%5")
1420  .arg(chan.m_callSign).arg(chan.m_chanNum)
1421  .arg(chan.m_serviceId)
1422  .arg(chan.m_patTsId)
1423  .arg(si_standard)).toLatin1().constData();
1424  }
1425 
1426  if (info)
1427  {
1428  ssMsg << ' ';
1429  msg = msg.leftJustified(72);
1430 
1431  ssMsg << chan.m_channelId;
1432 
1433  ssMsg << ":"
1434  << (QString("cnt(pnum:%1,channum:%2)")
1435  .arg(info->m_progNumCnt[chan.m_serviceId])
1436  .arg(info->m_chanNumCnt[map_str(chan.m_chanNum)])
1437  ).toLatin1().constData();
1438 
1439  if (chan.m_siStandard == "atsc")
1440  {
1441  ssMsg <<
1442  (QString(":atsc_cnt(tot:%1,minor:%2)")
1443  .arg(info->m_atscNumCnt[
1444  (chan.m_atscMajorChannel << 16) |
1445  (chan.m_atscMinorChannel)])
1446  .arg(info->m_atscMinCnt[chan.m_atscMinorChannel])
1447  ).toLatin1().constData();
1448  }
1449  }
1450 
1451  return msg;
1452 }
1453 
1466  const ScanDTVTransport &/*transport*/,
1467  const ChannelInsertInfo &chan)
1468 {
1469  QString msg;
1470  QTextStream ssMsg(&msg);
1471 
1472  QString si_standard = (chan.m_siStandard=="opencable") ?
1473  QString("scte") : chan.m_siStandard;
1474 
1475  if (si_standard == "atsc" || si_standard == "scte")
1476  {
1477 
1478  if (si_standard == "atsc")
1479  {
1480  ssMsg << (kATSCChannelFormat
1481  .arg(chan.m_atscMajorChannel)
1482  .arg(chan.m_atscMinorChannel)).toLatin1().constData();
1483  }
1484  else if (chan.m_freqId.isEmpty())
1485  {
1486  ssMsg << (QString("%1-%2")
1487  .arg(chan.m_sourceId)
1488  .arg(chan.m_serviceId)).toLatin1().constData();
1489  }
1490  else
1491  {
1492  ssMsg << (QString("%1-%2")
1493  .arg(chan.m_freqId)
1494  .arg(chan.m_serviceId)).toLatin1().constData();
1495  }
1496 
1497  if (!chan.m_callSign.isEmpty())
1498  ssMsg << (QString(" (%1)")
1499  .arg(chan.m_callSign)).toLatin1().constData();
1500  }
1501  else if (si_standard == "dvb")
1502  {
1503  ssMsg << (QString("%1 (%2 %3)")
1504  .arg(chan.m_serviceName).arg(chan.m_serviceId)
1505  .arg(chan.m_netId)).toLatin1().constData();
1506  }
1507  else if (chan.m_freqId.isEmpty())
1508  {
1509  ssMsg << (QString("%1-%2")
1510  .arg(chan.m_sourceId).arg(chan.m_serviceId))
1511  .toLatin1().constData();
1512  }
1513  else
1514  {
1515  ssMsg << (QString("%1-%2")
1516  .arg(chan.m_freqId).arg(chan.m_serviceId))
1517  .toLatin1().constData();
1518  }
1519 
1520  return msg;
1521 }
1522 
1524  const ScanDTVTransportList &transports_in,
1525  const ChannelImporterBasicStats *info)
1526 {
1527  // Sort transports in order of increasing frequency
1528  struct less_than_key
1529  {
1530  inline bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1531  {
1532  return t1.m_frequency < t2.m_frequency;
1533  }
1534  };
1535  ScanDTVTransportList transports(transports_in);
1536  std::sort(transports.begin(), transports.end(), less_than_key());
1537 
1538  QString msg;
1539 
1540  for (auto & transport : transports)
1541  {
1542  for (auto & channel : transport.m_channels)
1543  {
1544  msg += FormatChannel(transport, channel, info) + "\n";
1545  }
1546  }
1547 
1548  return msg;
1549 }
1550 
1552  const ScanDTVTransport &transport)
1553 {
1554  QString msg;
1555  QTextStream ssMsg(&msg);
1556  ssMsg << transport.toString();
1557  ssMsg << QString(" onid:%1").arg(transport.m_networkID);
1558  ssMsg << QString(" tsid:%1").arg(transport.m_transportID);
1559  ssMsg << QString(" ss:%1").arg(transport.m_signalStrength);
1560  return msg;
1561 }
1562 
1564  const ScanDTVTransportList &transports_in)
1565 {
1566  // Sort transports in order of increasing frequency
1567  struct less_than_key
1568  {
1569  inline bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1570  {
1571  return t1.m_frequency < t2.m_frequency;
1572  }
1573  };
1574  ScanDTVTransportList transports(transports_in);
1575  std::sort(transports.begin(), transports.end(), less_than_key());
1576 
1577  QString msg;
1578 
1579  for (const auto & transport : transports)
1580  msg += FormatTransport(transport) + "\n";
1581 
1582  return msg;
1583 }
1584 
1586  const ChannelImporterBasicStats &info,
1587  const ChannelImporterUniquenessStats &stats)
1588 {
1589  QString msg = tr("Channels: FTA Enc Dec\n") +
1590  QString("ATSC %1 %2 %3\n")
1591  .arg(info.m_atscChannels[0],3)
1592  .arg(info.m_atscChannels[1],3)
1593  .arg(info.m_atscChannels[2],3) +
1594  QString("DVB %1 %2 %3\n")
1595  .arg(info.m_dvbChannels [0],3)
1596  .arg(info.m_dvbChannels [1],3)
1597  .arg(info.m_dvbChannels [2],3) +
1598  QString("SCTE %1 %2 %3\n")
1599  .arg(info.m_scteChannels[0],3)
1600  .arg(info.m_scteChannels[1],3)
1601  .arg(info.m_scteChannels[2],3) +
1602  QString("MPEG %1 %2 %3\n")
1603  .arg(info.m_mpegChannels[0],3)
1604  .arg(info.m_mpegChannels[1],3)
1605  .arg(info.m_mpegChannels[2],3) +
1606  QString("NTSC %1\n")
1607  .arg(info.m_ntscChannels[0],3) +
1608  tr("Unique: prog %1 atsc %2 atsc minor %3 channum %4\n")
1609  .arg(stats.m_uniqueProgNum).arg(stats.m_uniqueAtscNum)
1610  .arg(stats.m_uniqueAtscMin).arg(stats.m_uniqueChanNum) +
1611  tr("Max atsc major count: %1")
1612  .arg(stats.m_maxAtscMajCnt);
1613 
1614  return msg;
1615 }
1616 
1618  const ChannelImporterBasicStats &info,
1619  const ChannelInsertInfo &chan, ChannelType type)
1620 {
1621  switch (type)
1622  {
1623  case kATSCNonConflicting:
1624  return ((chan.m_siStandard == "atsc") /* &&
1625  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1626  (chan.m_atscMinorChannel)] == 1) */);
1627 
1628  case kDVBNonConflicting:
1629  return ((chan.m_siStandard == "dvb") /* &&
1630  (info.m_progNumCnt[chan.m_serviceId] == 1) */);
1631 
1632  case kMPEGNonConflicting:
1633  return ((chan.m_siStandard == "mpeg") &&
1634  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1635 
1636  case kSCTENonConflicting:
1637  return (((chan.m_siStandard == "scte") ||
1638  (chan.m_siStandard == "opencable")) &&
1639  (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1640 
1641  case kNTSCNonConflicting:
1642  return ((chan.m_siStandard == "ntsc") &&
1643  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1644  (chan.m_atscMinorChannel)] == 1));
1645 
1646  case kATSCConflicting:
1647  return ((chan.m_siStandard == "atsc") &&
1648  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1649  (chan.m_atscMinorChannel)] != 1));
1650 
1651  case kDVBConflicting:
1652  return ((chan.m_siStandard == "dvb") &&
1653  (info.m_progNumCnt[chan.m_serviceId] != 1));
1654 
1655  case kMPEGConflicting:
1656  return ((chan.m_siStandard == "mpeg") &&
1657  (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1658 
1659  case kSCTEConflicting:
1660  return (((chan.m_siStandard == "scte") ||
1661  (chan.m_siStandard == "opencable")) &&
1662  (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1663 
1664  case kNTSCConflicting:
1665  return ((chan.m_siStandard == "ntsc") &&
1666  (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1667  (chan.m_atscMinorChannel)] != 1));
1668  }
1669  return false;
1670 }
1671 
1673  const ScanDTVTransportList &transports,
1674  const ChannelImporterBasicStats &info,
1675  ChannelType type, uint &new_chan, uint &old_chan)
1676 {
1677  new_chan = old_chan = 0;
1678  for (const auto & transport : transports)
1679  {
1680  for (const auto& chan : transport.m_channels)
1681  {
1682  if (IsType(info, chan, type))
1683  {
1684  if (chan.m_channelId)
1685  ++old_chan;
1686  else
1687  ++new_chan;
1688  }
1689  }
1690  }
1691 }
1692 
1694  const ScanDTVTransportList &transports)
1695 {
1696  int count = 0;
1697  for (const auto & transport : transports)
1698  count += transport.m_channels.size();
1699  return count;
1700 }
1701 
1717  const ChannelInsertInfo &chan)
1718 {
1719  static QMutex s_lastFreeLock;
1720  static QMap<uint,uint> s_lastFreeChanNumMap;
1721  QString chanNum;
1722 
1723  // Suggest existing channel number if non-conflicting
1725  return chan.m_chanNum;
1726 
1727  // Add a suffix to make it unique
1728  for (char suffix = 'A'; suffix <= 'Z'; ++suffix)
1729  {
1730  chanNum = chan.m_chanNum + suffix;
1731  if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1732  return chanNum;
1733  }
1734 
1735  // Find unused channel number
1736  QMutexLocker locker(&s_lastFreeLock);
1737  uint last_free_chan_num = s_lastFreeChanNumMap[chan.m_sourceId];
1738  for (last_free_chan_num++; ; ++last_free_chan_num)
1739  {
1740  chanNum = QString::number(last_free_chan_num);
1741  if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1742  break;
1743  }
1744  // cppcheck-suppress unreadVariable
1745  s_lastFreeChanNumMap[chan.m_sourceId] = last_free_chan_num;
1746 
1747  return chanNum;
1748 }
1749 
1752 {
1753  DeleteAction action = kDeleteAll;
1754  if (m_useGui)
1755  {
1756  int ret = -1;
1757  do
1758  {
1759  MythScreenStack *popupStack =
1760  GetMythMainWindow()->GetStack("popup stack");
1761  auto *deleteDialog =
1762  new MythDialogBox(msg, popupStack, "deletechannels");
1763 
1764  if (deleteDialog->Create())
1765  {
1766  deleteDialog->AddButton(tr("Delete All"));
1767  deleteDialog->AddButton(tr("Set all invisible"));
1768 // deleteDialog->AddButton(tr("Handle manually"));
1769  deleteDialog->AddButton(tr("Ignore All"));
1770  QObject::connect(deleteDialog, &MythDialogBox::Closed,
1771  [&](const QString & /*resultId*/, int result)
1772  {
1773  ret = result;
1774  m_eventLoop.quit();
1775  });
1776  popupStack->AddScreen(deleteDialog);
1777 
1778  m_eventLoop.exec();
1779  }
1780  } while (ret < 0);
1781 
1782  action = (0 == ret) ? kDeleteAll : action;
1783  action = (1 == ret) ? kDeleteInvisibleAll : action;
1784  action = (2 == ret) ? kDeleteIgnoreAll : action;
1785 // action = (2 == m_deleteChannelResult) ? kDeleteManual : action;
1786 // action = (3 == m_deleteChannelResult) ? kDeleteIgnoreAll : action;
1787  }
1788  else if (m_isInteractive)
1789  {
1790  cout << msg.toLatin1().constData()
1791  << endl
1792  << tr("Do you want to:").toLatin1().constData()
1793  << endl
1794  << tr("1. Delete All").toLatin1().constData()
1795  << endl
1796  << tr("2. Set all invisible").toLatin1().constData()
1797  << endl
1798 // cout << "3. Handle manually" << endl;
1799  << tr("4. Ignore All").toLatin1().constData()
1800  << endl;
1801  while (true)
1802  {
1803  string ret;
1804  cin >> ret;
1805  bool ok = false;
1806  uint val = QString(ret.c_str()).toUInt(&ok);
1807  if (ok && (val == 1 || val == 2 || val == 4))
1808  {
1809  action = (1 == val) ? kDeleteAll : action;
1810  action = (2 == val) ? kDeleteInvisibleAll : action;
1811  //action = (3 == val) ? kDeleteManual : action;
1812  action = (4 == val) ? kDeleteIgnoreAll : action;
1813  break;
1814  }
1815 
1816  //cout << "Please enter either 1, 2, 3 or 4:" << endl;
1817  cout << tr("Please enter either 1, 2 or 4:")
1818  .toLatin1().constData() << endl;//
1819  }
1820  }
1821 
1822  return action;
1823 }
1824 
1827 {
1828  InsertAction action = kInsertAll;
1829  if (m_useGui)
1830  {
1831  int ret = -1;
1832  do
1833  {
1834  MythScreenStack *popupStack =
1835  GetMythMainWindow()->GetStack("popup stack");
1836  auto *insertDialog =
1837  new MythDialogBox(msg, popupStack, "insertchannels");
1838 
1839  if (insertDialog->Create())
1840  {
1841  insertDialog->AddButton(tr("Insert All"));
1842  insertDialog->AddButton(tr("Insert Manually"));
1843  insertDialog->AddButton(tr("Ignore All"));
1844  QObject::connect(insertDialog, &MythDialogBox::Closed,
1845  [&](const QString & /*resultId*/, int result)
1846  {
1847  ret = result;
1848  m_eventLoop.quit();
1849  });
1850 
1851  popupStack->AddScreen(insertDialog);
1852  m_eventLoop.exec();
1853  }
1854  } while (ret < 0);
1855 
1856  action = (0 == ret) ? kInsertAll : action;
1857  action = (1 == ret) ? kInsertManual : action;
1858  action = (2 == ret) ? kInsertIgnoreAll : action;
1859  }
1860  else if (m_isInteractive)
1861  {
1862  cout << msg.toLatin1().constData()
1863  << endl
1864  << tr("Do you want to:").toLatin1().constData()
1865  << endl
1866  << tr("1. Insert All").toLatin1().constData()
1867  << endl
1868  << tr("2. Insert Manually").toLatin1().constData()
1869  << endl
1870  << tr("3. Ignore All").toLatin1().constData()
1871  << endl;
1872  while (true)
1873  {
1874  string ret;
1875  cin >> ret;
1876  bool ok = false;
1877  uint val = QString(ret.c_str()).toUInt(&ok);
1878  if (ok && (1 <= val) && (val <= 3))
1879  {
1880  action = (1 == val) ? kInsertAll : action;
1881  action = (2 == val) ? kInsertManual : action;
1882  action = (3 == val) ? kInsertIgnoreAll : action;
1883  break;
1884  }
1885 
1886  cout << tr("Please enter either 1, 2, or 3:")
1887  .toLatin1().constData() << endl;
1888  }
1889  }
1890 
1891  return action;
1892 }
1893 
1896 {
1897  UpdateAction action = kUpdateAll;
1898 
1899  if (m_useGui)
1900  {
1901  int ret = -1;
1902  do
1903  {
1904  MythScreenStack *popupStack =
1905  GetMythMainWindow()->GetStack("popup stack");
1906  auto *updateDialog =
1907  new MythDialogBox(msg, popupStack, "updatechannels");
1908 
1909  if (updateDialog->Create())
1910  {
1911  updateDialog->AddButton(tr("Update All"));
1912  updateDialog->AddButton(tr("Ignore All"));
1913  QObject::connect(updateDialog, &MythDialogBox::Closed,
1914  [&](const QString& /*resultId*/, int result)
1915  {
1916  ret = result;
1917  m_eventLoop.quit();
1918  });
1919 
1920  popupStack->AddScreen(updateDialog);
1921  m_eventLoop.exec();
1922  }
1923  } while (ret < 0);
1924 
1925  action = (0 == ret) ? kUpdateAll : action;
1926  action = (1 == ret) ? kUpdateIgnoreAll : action;
1927  }
1928  else if (m_isInteractive)
1929  {
1930  cout << msg.toLatin1().constData()
1931  << endl
1932  << tr("Do you want to:").toLatin1().constData()
1933  << endl
1934  << tr("1. Update All").toLatin1().constData()
1935  << endl
1936  << tr("2. Update Manually").toLatin1().constData()
1937  << endl
1938  << tr("3. Ignore All").toLatin1().constData()
1939  << endl;
1940  while (true)
1941  {
1942  string ret;
1943  cin >> ret;
1944  bool ok = false;
1945  uint val = QString(ret.c_str()).toUInt(&ok);
1946  if (ok && (1 <= val) && (val <= 3))
1947  {
1948  action = (1 == val) ? kUpdateAll : action;
1949  action = (2 == val) ? kUpdateManual : action;
1950  action = (3 == val) ? kUpdateIgnoreAll : action;
1951  break;
1952  }
1953 
1954  cout << tr("Please enter either 1, 2, or 3:")
1955  .toLatin1().constData() << endl;
1956  }
1957  }
1958 
1959  return action;
1960 }
1961 
1963  MythMainWindow *parent, const QString& title,
1964  const QString& message, QString &text)
1965 {
1966  int dc = -1;
1967  MythScreenStack *popupStack = parent->GetStack("popup stack");
1968  auto *popup = new MythDialogBox(title, message, popupStack,
1969  "manualchannelpopup");
1970 
1971  if (popup->Create())
1972  {
1973  popup->AddButton(QCoreApplication::translate("(Common)", "OK"));
1974  popup->AddButton(tr("Edit"));
1975  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel"));
1976  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel All"));
1977  QObject::connect(popup, &MythDialogBox::Closed,
1978  [&](const QString & /*resultId*/, int result)
1979  {
1980  dc = result;
1981  m_eventLoop.quit();
1982  });
1983  popupStack->AddScreen(popup);
1984  m_eventLoop.exec();
1985  }
1986  else
1987  {
1988  delete popup;
1989  popup = nullptr;
1990  }
1991 
1992  // Choice "Edit"
1993  if (1 == dc)
1994  {
1995  auto *textEdit =
1996  new MythTextInputDialog(popupStack,
1997  tr("Please enter a unique channel number."),
1998  FilterNone, false, text);
1999  if (textEdit->Create())
2000  {
2001  QObject::connect(textEdit, &MythTextInputDialog::haveResult,
2002  [&](QString result)
2003  {
2004  dc = 0;
2005  text = std::move(result);
2006  });
2007  QObject::connect(textEdit, &MythTextInputDialog::Exiting,
2008  [&]()
2009  {
2010  m_eventLoop.quit();
2011  });
2012 
2013  popupStack->AddScreen(textEdit);
2014  m_eventLoop.exec();
2015  }
2016  else
2017  delete textEdit;
2018  }
2019 
2020  OkCancelType rval = kOCTCancel;
2021  switch (dc) {
2022  case 0: rval = kOCTOk; break;
2023  // NOLINTNEXTLINE(bugprone-branch-clone)
2024  case 1: rval = kOCTCancel; break; // "Edit" is done already
2025  case 2: rval = kOCTCancel; break;
2026  case 3: rval = kOCTCancelAll; break;
2027  }
2028  return rval;
2029 }
2030 
2032  MythMainWindow *parent, const QString& title,
2033  const QString& message, QString &text)
2034 {
2035  int dc = -1;
2036  MythScreenStack *popupStack = parent->GetStack("popup stack");
2037  auto *popup = new MythDialogBox(title, message, popupStack,
2038  "resolvechannelpopup");
2039 
2040  if (popup->Create())
2041  {
2042  popup->AddButton(QCoreApplication::translate("(Common)", "OK"));
2043  popup->AddButton(QCoreApplication::translate("(Common)", "OK All"));
2044  popup->AddButton(tr("Edit"));
2045  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel"));
2046  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel All"));
2047  QObject::connect(popup, &MythDialogBox::Closed,
2048  [&](const QString & /*resultId*/, int result)
2049  {
2050  dc = result;
2051  m_eventLoop.quit();
2052  });
2053  popupStack->AddScreen(popup);
2054  m_eventLoop.exec();
2055  }
2056  else
2057  {
2058  delete popup;
2059  popup = nullptr;
2060  }
2061 
2062  // Choice "Edit"
2063  if (2 == dc)
2064  {
2065  auto *textEdit =
2066  new MythTextInputDialog(popupStack,
2067  tr("Please enter a unique channel number."),
2068  FilterNone, false, text);
2069  if (textEdit->Create())
2070  {
2071  QObject::connect(textEdit, &MythTextInputDialog::haveResult,
2072  [&](QString result)
2073  {
2074  dc = 0;
2075  text = std::move(result);
2076  });
2077  QObject::connect(textEdit, &MythTextInputDialog::Exiting,
2078  [&]()
2079  {
2080  m_eventLoop.quit();
2081  });
2082 
2083  popupStack->AddScreen(textEdit);
2084  m_eventLoop.exec();
2085  }
2086  else
2087  delete textEdit;
2088  }
2089 
2090  OkCancelType rval = kOCTCancel;
2091  switch (dc) {
2092  case 0: rval = kOCTOk; break;
2093  case 1: rval = kOCTOkAll; break;
2094  // NOLINTNEXTLINE(bugprone-branch-clone)
2095  case 2: rval = kOCTCancel; break; // "Edit" is done already
2096  case 3: rval = kOCTCancel; break;
2097  case 4: rval = kOCTCancelAll; break;
2098  }
2099  return rval;
2100 }
2101 
2103  const ScanDTVTransport &transport,
2104  ChannelInsertInfo &chan)
2105 {
2106  QString msg = tr("Channel %1 has channel number %2 but that is already in use.")
2107  .arg(SimpleFormatChannel(transport, chan))
2108  .arg(chan.m_chanNum);
2109 
2110  OkCancelType ret = kOCTCancel;
2111 
2112  if (m_useGui)
2113  {
2114  while (true)
2115  {
2116  QString msg2 = msg;
2117  msg2 += "\n";
2118  msg2 += tr("Please enter a unique channel number.");
2119 
2120  QString val = ComputeSuggestedChannelNum(chan);
2121  msg2 += "\n";
2122  msg2 += tr("Default value is %1.").arg(val);
2123  ret = ShowResolveChannelPopup(
2124  GetMythMainWindow(), tr("Channel Importer"),
2125  msg2, val);
2126 
2127  if (kOCTOk != ret && kOCTOkAll != ret)
2128  break; // user canceled..
2129 
2130  bool ok = CheckChannelNumber(val, chan);
2131  if (ok)
2132  {
2133  chan.m_chanNum = val;
2134  break;
2135  }
2136  }
2137  }
2138  else if (m_isInteractive)
2139  {
2140  cout << msg.toLatin1().constData() << endl;
2141 
2142  QString cancelStr = QCoreApplication::translate("(Common)",
2143  "Cancel").toLower();
2144  QString cancelAllStr = QCoreApplication::translate("(Common)",
2145  "Cancel All").toLower();
2146  QString msg2 = tr("Please enter a non-conflicting channel number "
2147  "(or type '%1' to skip, '%2' to skip all):")
2148  .arg(cancelStr).arg(cancelAllStr);
2149 
2150  while (true)
2151  {
2152  cout << msg2.toLatin1().constData() << endl;
2153  string sret;
2154  cin >> sret;
2155  QString val = QString(sret.c_str());
2156  if (val.toLower() == cancelStr)
2157  {
2158  ret = kOCTCancel;
2159  break; // user canceled..
2160  }
2161  if (val.toLower() == cancelAllStr)
2162  {
2163  ret = kOCTCancelAll;
2164  break; // user canceled..
2165  }
2166 
2167  bool ok = CheckChannelNumber(val, chan);
2168  if (ok)
2169  {
2170  chan.m_chanNum = val;
2171  ret = kOCTOk;
2172  break;
2173  }
2174  }
2175  }
2176 
2177  return ret;
2178 }
2179 
2181  const ScanDTVTransport &transport,
2182  ChannelInsertInfo &chan)
2183 {
2184  QString msg = tr("You chose to manually insert channel %1.")
2185  .arg(SimpleFormatChannel(transport, chan));
2186 
2187  OkCancelType ret = kOCTCancel;
2188 
2189  if (m_useGui)
2190  {
2191  while (true)
2192  {
2193  QString msg2 = msg;
2194  msg2 += " ";
2195  msg2 += tr("Please enter a unique channel number.");
2196 
2197  QString val = ComputeSuggestedChannelNum(chan);
2198  msg2 += " ";
2199  msg2 += tr("Default value is %1").arg(val);
2200  ret = ShowManualChannelPopup(
2201  GetMythMainWindow(), tr("Channel Importer"),
2202  msg2, val);
2203 
2204  if (kOCTOk != ret)
2205  break; // user canceled..
2206 
2207  bool ok = CheckChannelNumber(val, chan);
2208  if (ok)
2209  {
2210  chan.m_chanNum = val;
2211  ret = kOCTOk;
2212  break;
2213  }
2214  }
2215  }
2216  else if (m_isInteractive)
2217  {
2218  cout << msg.toLatin1().constData() << endl;
2219 
2220  QString cancelStr = QCoreApplication::translate("(Common)", "Cancel").toLower();
2221  QString cancelAllStr = QCoreApplication::translate("(Common)", "Cancel All").toLower();
2222 
2223  //: %1 is the translation of "Cancel", %2 of "Cancel All"
2224  QString msg2 = tr("Please enter a non-conflicting channel number "
2225  "(or type '%1' to skip, '%2' to skip all): ")
2226  .arg(cancelStr).arg(cancelAllStr);
2227 
2228  while (true)
2229  {
2230  cout << msg2.toLatin1().constData() << endl;
2231  string sret;
2232  cin >> sret;
2233  QString val = QString(sret.c_str());
2234  if (val.toLower() == cancelStr)
2235  {
2236  ret = kOCTCancel;
2237  break; // user canceled..
2238  }
2239  if (val.toLower() == cancelAllStr)
2240  {
2241  ret = kOCTCancelAll;
2242  break; // user canceled..
2243  }
2244 
2245  bool ok = CheckChannelNumber(val, chan);
2246  if (ok)
2247  {
2248  chan.m_chanNum = val;
2249  ret = kOCTOk;
2250  break;
2251  }
2252  }
2253  }
2254 
2255  return ret;
2256 }
2257 
2258 // ChannelImporter::CheckChannelNumber
2259 //
2260 // Check validity of a new channel number.
2261 // The channel number is not a number but it is a string that starts with a digit.
2262 // The channel number should not yet exist in this video source.
2263 //
2265  const QString &num,
2266  const ChannelInsertInfo &chan)
2267 {
2268  bool ok = (num.length() >= 1);
2269  ok = ok && ((num[0] >= '0') && (num[0] <= '9'));
2270  ok = ok && !ChannelUtil::IsConflicting(
2271  num, chan.m_sourceId, chan.m_channelId);
2272  return ok;
2273 }
static QString toString(ChannelType type)
static QString FormatTransport(const ScanDTVTransport &transport)
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
QMap< uint, uint > m_atscNumCnt
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
QString m_defaultAuthority
Definition: channelinfo.h:233
static QString ComputeSuggestedChannelNum(const ChannelInsertInfo &chan)
Compute a suggested channel number that is unique in the video source.
uint SaveScan(const ScanDTVTransportList &scan)
Definition: scaninfo.cpp:22
QMap< QString, uint > m_chanNumCnt
static QString map_str(QString str)
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
QString toString(MarkTypes type)
static const int kTunerTypeATSC
ChannelVisibleType m_visible
Definition: channelinfo.h:228
static bool IsType(const ChannelImporterBasicStats &info, const ChannelInsertInfo &chan, ChannelType type)
static bool CheckChannelNumber(const QString &num, const ChannelInsertInfo &chan)
OkCancelType ShowResolveChannelPopup(MythMainWindow *parent, const QString &title, const QString &message, QString &text)
DeleteAction QueryUserDelete(const QString &msg)
For multiple channels.
static QString FormatChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats *info=nullptr)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
static QString FormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan, const ChannelImporterBasicStats *info=nullptr)
Basic menu dialog, message and a list of options.
UpdateAction QueryUserUpdate(const QString &msg)
For multiple channels.
int size(void) const
Definition: mythdbcon.h:203
MythScreenStack * GetStack(const QString &stackname)
void FilterServices(ScanDTVTransportList &transports) const
static bool SetVisible(uint channel_id, ChannelVisibleType visible)
static guint32 * tmp
Definition: goom_core.cpp:30
static void MergeSameFrequency(ScanDTVTransportList &transports)
static const int kTunerTypeDVBS1
QString toString() const
static void FixUpOpenCable(ScanDTVTransportList &transports)
static bool SetChannelValue(const QString &field_name, const QString &value, uint sourceid, const QString &channum)
#define LOC
ChannelInsertInfoList m_channels
Definition: dtvmultiplex.h:138
static QString SimpleFormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Format channel information into a simple string.
static void RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
QVariant value(int i) const
Definition: mythdbcon.h:198
if(query.exec() &&query.next())
QMap< uint, uint > m_progNumCnt
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...
OkCancelType ShowManualChannelPopup(MythMainWindow *parent, const QString &title, const QString &message, QString &text)
static int SimpleCountChannels(const ScanDTVTransportList &transports)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
static const int kTunerTypeDVBS2
void haveResult(QString)
vector< ChannelInsertInfo > ChannelInsertInfoList
Definition: channelinfo.h:261
bool IsEqual(DTVTunerType type, const DTVMultiplex &other, uint freq_range=0, bool fuzzy=false) const
MSqlQuery query(MSqlQuery::InitCon())
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)
static void AddChanToCopy(ScanDTVTransport &transport_copy, const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
QMap< uint, uint > m_atscMinCnt
static bool IsConflicting(const QString &channum, uint sourceid=0, uint excluded_chanid=0)
Definition: channelutil.h:287
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)
bool IsSameChannel(const ChannelInsertInfo &other, int relaxed=0) const
InsertAction QueryUserInsert(const QString &msg)
For multiple channels.
static void CountChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, ChannelType type, uint &new_chan, uint &old_chan)
unsigned int uint
Definition: compat.h:140
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
static bool CreateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
Definition: channelutil.h:145
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
MythMainWindow * GetMythMainWindow(void)
uint DeleteChannels(ScanDTVTransportList &transports)
static bool MarkProcessed(uint scanid)
Definition: scaninfo.cpp:204
static void UpdateInsertInfoFromDB(ChannelInsertInfo &chan)
Dialog prompting the user to enter a text string.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
static ChannelImporterBasicStats CollectStats(const ScanDTVTransportList &transports)
QString m_serviceName
Definition: channelinfo.h:219
OkCancelType
static ChannelImporterUniquenessStats CollectUniquenessStats(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
void InsertChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
bool FillFromDB(DTVTunerType type, uint mplexid) override
void Process(const ScanDTVTransportList &_transports, int sourceid=-1)
QString m_siStandard
Definition: channelinfo.h:242
static void UpdateChannelNumberFromDB(ChannelInsertInfo &chan)
static QString FormatTransports(const ScanDTVTransportList &transports_in)
QMap< uint, uint > m_atscMajCnt
ScanDTVTransportList UpdateChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, UpdateAction action, ChannelType type, ScanDTVTransportList &updated, ScanDTVTransportList &skipped) const
OkCancelType QueryUserResolve(const ScanDTVTransport &transport, ChannelInsertInfo &chan)
For a single channel.
vector< ScanDTVTransport > ScanDTVTransportList
Definition: dtvmultiplex.h:143
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static void FilterRelocatedServices(ScanDTVTransportList &transports)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
static uint CreateMultiplex(int sourceid, const QString &sistandard, uint64_t frequency, const QString &modulation, int transport_id=-1, int network_id=-1)
static const QString kATSCChannelFormat
static uint GetChannelCount(int sourceid=-1)
static QString GetChanNum(int chan_id)
Returns the channel-number string of the given channel.
static QString GetSummary(const ChannelImporterBasicStats &info, const ChannelImporterUniquenessStats &stats)
void Closed(QString, int)
uint DeleteUnusedTransports(uint sourceid)
static bool DeleteChannel(uint channel_id)
uint64_t m_frequency
Definition: dtvmultiplex.h:94
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23