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 
13 // Qt includes
14 #include <QTextStream>
15 
16 using namespace std;
17 
18 // MythTV headers
19 #include "channelimporter.h"
20 #include "mythdialogbox.h"
21 #include "mythdb.h"
22 #include "mpegstreamdata.h" // for kEncDecrypted
23 #include "channelutil.h"
24 
25 #define LOC QString("ChanImport: ")
26 
27 static QString map_str(QString str)
28 {
29  if (str.isEmpty())
30  return "";
31  return str;
32 }
33 
35  int sourceid)
36 {
37  if (_transports.empty())
38  {
39  if (m_use_gui)
40  {
41  int channels = ChannelUtil::GetChannelCount(sourceid);
42 
43  LOG(VB_GENERAL, LOG_INFO, LOC + (channels ?
44  (m_success ?
45  QString("Found %1 channels")
46  .arg(channels) :
47  "No new channels to process") :
48  "No channels to process.."));
49 
51  channels ?
52  (m_success ? tr("Found %n channel(s)", "", channels) :
53  tr("Failed to find any new channels!"))
54  : tr("Failed to find any channels."));
55  }
56  else
57  {
58  cout << (ChannelUtil::GetChannelCount() ?
59  "No new channels to process" :
60  "No channels to process..");
61  }
62 
63  return;
64  }
65 
66  ScanDTVTransportList transports = _transports;
67 
68  // Print out each channel
69  if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
70  {
71  cout << "Before processing: " << endl;
72  ChannelImporterBasicStats infoA = CollectStats(transports);
73  cout << FormatChannels(transports, infoA).toLatin1().constData() << endl;
74  cout << endl << endl;
75  }
76 
77  uint saved_scan = 0;
78  if (m_do_save)
79  saved_scan = SaveScan(transports);
80 
81  CleanupDuplicates(transports);
82 
83  FilterServices(transports);
84 
85  // Pull in DB info
86  sourceid = transports[0].m_channels[0].m_source_id;
87  ScanDTVTransportList db_trans = GetDBTransports(sourceid, transports);
88 
89  // Make sure "Open Cable" channels are marked that way.
90  FixUpOpenCable(transports);
91 
92  // if scan was not aborted prematurely..
93  if (m_do_delete)
94  {
95  ScanDTVTransportList trans = transports;
96  for (size_t i = 0; i < db_trans.size(); ++i)
97  trans.push_back(db_trans[i]);
98  uint deleted_count = DeleteChannels(trans);
99  if (deleted_count)
100  transports = trans;
101  }
102 
103  // Determine System Info standards..
104  ChannelImporterBasicStats info = CollectStats(transports);
105 
106  // Determine uniqueness of various naming schemes
108  CollectUniquenessStats(transports, info);
109 
110  // Print out each channel
111  cout << FormatChannels(transports, info).toLatin1().constData() << endl;
112 
113  // Create summary
114  QString msg = GetSummary(transports.size(), info, stats);
115  cout << msg.toLatin1().constData() << endl << endl;
116 
117  if (m_do_insert)
118  InsertChannels(transports, info);
119 
120  if (m_do_delete && sourceid)
121  DeleteUnusedTransports(sourceid);
122 
123  if (m_do_delete || m_do_insert)
124  ScanInfo::MarkProcessed(saved_scan);
125 }
126 
128 {
129  switch (type)
130  {
131  // non-conflicting
132  case kATSCNonConflicting: return "ATSC";
133  case kDVBNonConflicting: return "DVB";
134  case kSCTENonConflicting: return "SCTE";
135  case kMPEGNonConflicting: return "MPEG";
136  case kNTSCNonConflicting: return "NTSC";
137  // conflicting
138  case kATSCConflicting: return "ATSC";
139  case kDVBConflicting: return "DVB";
140  case kSCTEConflicting: return "SCTE";
141  case kMPEGConflicting: return "MPEG";
142  case kNTSCConflicting: return "NTSC";
143  }
144  return "Unknown";
145 }
146 
148  ScanDTVTransportList &transports)
149 {
150  vector<uint> off_air_list;
151  QMap<uint,bool> deleted;
152 
153  for (size_t i = 0; i < transports.size(); ++i)
154  {
155  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
156  {
157  ChannelInsertInfo chan = transports[i].m_channels[j];
158  bool was_in_db = chan.m_db_mplexid && chan.m_channel_id;
159  if (!was_in_db)
160  continue;
161 
162  if (!chan.m_in_pmt)
163  off_air_list.push_back(i<<16|j);
164  }
165  }
166 
167  ScanDTVTransportList newlist;
168  if (off_air_list.empty())
169  {
170  return 0;
171  }
172 
173  // ask user whether to delete all or some of these stale channels
174  // if some is selected ask about each individually
175  //: %n is the number of channels
176  QString msg = tr("Found %n off-air channel(s).", "", off_air_list.size());
177  DeleteAction action = QueryUserDelete(msg);
178  if (kDeleteIgnoreAll == action)
179  return 0;
180 
181  if (kDeleteAll == action)
182  {
183  for (size_t k = 0; k < off_air_list.size(); ++k)
184  {
185  int i = off_air_list[k] >> 16, j = off_air_list[k] & 0xFFFF;
187  transports[i].m_channels[j].m_channel_id);
188  deleted[off_air_list[k]] = true;
189  }
190  }
191  else if (kDeleteInvisibleAll == action)
192  {
193  for (size_t k = 0; k < off_air_list.size(); ++k)
194  {
195  int i = off_air_list[k] >> 16, j = off_air_list[k] & 0xFFFF;
196  int chanid = transports[i].m_channels[j].m_channel_id;
197  QString channum = ChannelUtil::GetChanNum(chanid);
198  ChannelUtil::SetVisible(chanid, false);
199  ChannelUtil::SetChannelValue("channum", QString("_%1").arg(channum),
200  chanid);
201  }
202  }
203  else
204  {
205  // TODO manual delete
206  }
207 
208  // TODO delete encrypted channels when m_fta_only set
209 
210  if (deleted.empty())
211  return 0;
212 
213  // Create a new transports list without the deleted channels
214  for (size_t i = 0; i < transports.size(); ++i)
215  {
216  newlist.push_back(transports[i]);
217  newlist.back().m_channels.clear();
218  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
219  {
220  if (!deleted.contains(i<<16|j))
221  {
222  newlist.back().m_channels.push_back(
223  transports[i].m_channels[j]);
224  }
225  }
226  }
227 
228  // TODO print list of stale channels (as deleted if action approved).
229 
230  transports = newlist;
231  return deleted.size();
232 }
233 
235 {
236  MSqlQuery query(MSqlQuery::InitCon());
237  query.prepare(
238  "SELECT mplexid FROM dtv_multiplex "
239  "WHERE sourceid = :SOURCEID1 AND "
240  " mplexid NOT IN "
241  " (SELECT mplexid "
242  " FROM channel "
243  " WHERE sourceid = :SOURCEID2)");
244  query.bindValue(":SOURCEID1", sourceid);
245  query.bindValue(":SOURCEID2", sourceid);
246  if (!query.exec())
247  {
248  MythDB::DBError("DeleteUnusedTransports() -- select", query);
249  return 0;
250  }
251 
252  QString msg = tr("Found %n unused transport(s).", "", query.size());
253 
254  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
255 
256  if (query.size() == 0)
257  return 0;
258 
259  DeleteAction action = QueryUserDelete(msg);
260  if (kDeleteIgnoreAll == action)
261  return 0;
262 
263  if (kDeleteAll == action)
264  {
265  query.prepare(
266  "DELETE FROM dtv_multiplex "
267  "WHERE sourceid = :SOURCEID1 AND "
268  " mplexid NOT IN "
269  " (SELECT mplexid "
270  " FROM channel "
271  " WHERE sourceid = :SOURCEID2)");
272  query.bindValue(":SOURCEID1", sourceid);
273  query.bindValue(":SOURCEID2", sourceid);
274  if (!query.exec())
275  {
276  MythDB::DBError("DeleteUnusedTransports() -- delete", query);
277  return 0;
278  }
279  }
280  else
281  {
282  // TODO manual delete
283  }
284  return 0;
285 }
286 
288  const ScanDTVTransportList &transports,
289  const ChannelImporterBasicStats &info)
290 {
291  ScanDTVTransportList list = transports;
292  ScanDTVTransportList filtered;
293 
294  // insert/update all channels with non-conflicting channum
295  // and complete tuning information.
296 
297  uint chantype = (uint) kChannelTypeNonConflictingFirst;
298  for (; chantype <= (uint) kChannelTypeNonConflictingLast; ++chantype)
299  {
300  ChannelType type = (ChannelType) chantype;
301  uint new_chan, old_chan;
302  CountChannels(list, info, type, new_chan, old_chan);
303 
304  if (kNTSCNonConflicting == type)
305  continue;
306 
307  if (old_chan)
308  {
309  //: %n is the number of channels, %1 is the type of channel
310  QString msg = tr("Found %n old %1 channel(s).", "", old_chan)
311  .arg(toString(type));
312 
313  UpdateAction action = QueryUserUpdate(msg);
314  list = UpdateChannels(list, info, action, type, filtered);
315  }
316  if (new_chan)
317  {
318  //: %n is the number of channels, %1 is the type of channel
319  QString msg = tr("Found %n new non-conflicting %1 channel(s).",
320  "", new_chan).arg(toString(type));
321 
322  InsertAction action = QueryUserInsert(msg);
323  list = InsertChannels(list, info, action, type, filtered);
324  }
325  }
326 
327  if (!m_is_interactive)
328  return;
329 
330  // sum uniques again
331  ChannelImporterBasicStats ninfo = CollectStats(list);
332  ChannelImporterUniquenessStats nstats = CollectUniquenessStats(list, ninfo);
333  cout << endl << endl << "Printing remaining channels" << endl;
334  cout << FormatChannels(list, ninfo).toLatin1().constData() << endl;
335  cout << GetSummary(list.size(), ninfo, nstats).toLatin1().constData()
336  << endl << endl;
337 
338  // if any of the potential uniques is high and inserting
339  // with those as the channum would result in few conflicts
340  // ask user if it is ok to to proceed using it as the channum
341 
342  // for remaining channels with complete tuning information
343  // insert channels with contiguous list of numbers as the channums
344  chantype = (uint) kChannelTypeConflictingFirst;
345  for (; chantype <= (uint) kChannelTypeConflictingLast; ++chantype)
346  {
347 
348  ChannelType type = (ChannelType) chantype;
349  uint new_chan, old_chan;
350  CountChannels(list, info, type, new_chan, old_chan);
351  if (new_chan)
352  {
353  //: %n is the number of channels, %1 is the type of channel
354  QString msg = tr("Found %n new conflicting %1 channel(s).",
355  "", new_chan).arg(toString(type));
356 
357  InsertAction action = QueryUserInsert(msg);
358  list = InsertChannels(list, info, action, type, filtered);
359  }
360  if (old_chan)
361  {
362  //: %n is the number of channels, %1 is the type of channel
363  QString msg = tr("Found %n conflicting old %1 channel(s).",
364  "", old_chan).arg(toString(type));
365 
366  UpdateAction action = QueryUserUpdate(msg);
367  list = UpdateChannels(list, info, action, type, filtered);
368  }
369  }
370 
371  // print list of inserted channels
372  // print list of ignored channels (by ignored reason category)
373  // print list of invalid channels
374 }
375 
377  const ScanDTVTransportList &transports,
378  const ChannelImporterBasicStats &info,
380  ScanDTVTransportList &filtered)
381 {
382  QString channelFormat = "%1_%2";
383 
384  ScanDTVTransportList next_list;
385 
386  bool ignore_rest = false;
387 
388  // insert all channels with non-conflicting channum
389  // and complete tuning information.
390  for (size_t i = 0; i < transports.size(); ++i)
391  {
392  bool created_new_transport = false;
393  ScanDTVTransport new_transport;
394  bool created_filter_transport = false;
395  ScanDTVTransport filter_transport;
396 
397  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
398  {
399  ChannelInsertInfo chan = transports[i].m_channels[j];
400 
401  bool asked = false;
402  bool filter = false;
403  bool handle = false;
404  if (!chan.m_channel_id && (kInsertIgnoreAll == action) &&
405  IsType(info, chan, type))
406  {
407  filter = true;
408  }
409  else if (!chan.m_channel_id && IsType(info, chan, type))
410  {
411  handle = true;
412  }
413 
414  if (ignore_rest)
415  {
416  cout<<QString("Skipping Insert: %1")
417  .arg(FormatChannel(transports[i], chan))
418  .toLatin1().constData()<<endl;
419  handle = false;
420  }
421 
422  if (handle && kInsertManual == action)
423  {
424  OkCancelType rc = QueryUserInsert(info, transports[i], chan);
425  if (kOCTCancelAll == rc)
426  {
427  ignore_rest = true;
428  handle = false;
429  }
430  else if (kOCTCancel == rc)
431  {
432  handle = false;
433  }
434  else if (kOCTOk == rc)
435  {
436  asked = true;
437  }
438  }
439 
440  if (handle)
441  {
442  bool conflicting = false;
443 
444  if (chan.m_chan_num.isEmpty() ||
446  {
447  if ((kATSCNonConflicting == type) ||
448  (kATSCConflicting == type))
449  {
450  chan.m_chan_num = channelFormat
451  .arg(chan.m_atsc_major_channel)
452  .arg(chan.m_atsc_minor_channel);
453  }
454  else if (chan.m_si_standard == "dvb")
455  {
456  chan.m_chan_num = QString("%1")
457  .arg(chan.m_service_id);
458  }
459  else if (chan.m_freqid.isEmpty())
460  {
461  chan.m_chan_num = QString("%1-%2")
462  .arg(chan.m_source_id)
463  .arg(chan.m_service_id);
464  }
465  else
466  {
467  chan.m_chan_num = QString("%1-%2")
468  .arg(chan.m_freqid)
469  .arg(chan.m_service_id);
470  }
471 
472  conflicting = ChannelUtil::IsConflicting(
473  chan.m_chan_num, chan.m_source_id);
474  }
475 
476  // Only ask if not already asked before with kInsertManual
477  if (m_is_interactive && !asked &&
478  (conflicting || (kChannelTypeConflictingFirst <= type)))
479  {
480  OkCancelType rc =
481  QueryUserResolve(info, transports[i], chan);
482 
483  conflicting = true;
484  if (kOCTCancelAll == rc)
485  ignore_rest = true;
486  else if (kOCTOk == rc)
487  conflicting = false;
488  }
489 
490  if (conflicting)
491  {
492  cout<<QString("Skipping Insert: %1")
493  .arg(FormatChannel(transports[i], chan))
494  .toLatin1().constData()<<endl;
495  handle = false;
496  }
497  }
498 
499  bool inserted = false;
500  if (handle)
501  {
502  int chanid = ChannelUtil::CreateChanID(
503  chan.m_source_id, chan.m_chan_num);
504 
505  chan.m_channel_id = (chanid > 0) ? chanid : chan.m_channel_id;
506 
507  if (chan.m_channel_id)
508  {
509  uint tsid = chan.m_vct_tsid;
510  tsid = (tsid) ? tsid : chan.m_sdt_tsid;
511  tsid = (tsid) ? tsid : chan.m_pat_tsid;
512  tsid = (tsid) ? tsid : chan.m_vct_chan_tsid;
513 
514  if (!chan.m_db_mplexid)
515  {
517  chan.m_source_id, transports[i], tsid, chan.m_orig_netid);
518  }
519  else
520  {
521  // Find the matching multiplex. This updates the
522  // transport and network ID's in case the transport
523  // was created manually
525  tsid, chan.m_orig_netid);
526  if (id >= 0)
527  chan.m_db_mplexid = id;
528  }
529  }
530 
531  if (chan.m_channel_id && chan.m_db_mplexid)
532  {
533  chan.m_channel_id = chanid;
534 
535  cout<<"Insert("<<chan.m_si_standard.toLatin1().constData()
536  <<"): "<<chan.m_chan_num.toLatin1().constData()<<endl;
537 
538  inserted = ChannelUtil::CreateChannel(
539  chan.m_db_mplexid,
540  chan.m_source_id,
541  chan.m_channel_id,
542  chan.m_callsign,
543  chan.m_service_name,
544  chan.m_chan_num,
545  chan.m_service_id,
548  chan.m_use_on_air_guide,
549  chan.m_hidden, chan.m_hidden_in_guide,
550  chan.m_freqid,
551  QString(),
552  chan.m_format,
553  QString(),
554  chan.m_default_authority);
555 
556  if (!transports[i].m_iptv_tuning.GetDataURL().isEmpty())
558  transports[i].m_iptv_tuning);
559  }
560  }
561 
562  if (filter)
563  {
564  if (!created_filter_transport)
565  {
566  filter_transport = transports[i];
567  filter_transport.m_channels.clear();
568  created_filter_transport = true;
569  }
570  filter_transport.m_channels.push_back(transports[i].m_channels[j]);
571  }
572  else if (!inserted)
573  {
574  if (!created_new_transport)
575  {
576  new_transport = transports[i];
577  new_transport.m_channels.clear();
578  created_new_transport = true;
579  }
580  new_transport.m_channels.push_back(transports[i].m_channels[j]);
581  }
582  }
583 
584  if (created_filter_transport)
585  filtered.push_back(filter_transport);
586 
587  if (created_new_transport)
588  next_list.push_back(new_transport);
589  }
590 
591  return next_list;
592 }
593 
595  const ScanDTVTransportList &transports,
596  const ChannelImporterBasicStats &info,
598  ScanDTVTransportList &filtered)
599 {
600  QString channelFormat = "%1_%2";
601  bool renameChannels = false;
602 
603  ScanDTVTransportList next_list;
604 
605  // update all channels with non-conflicting channum
606  // and complete tuning information.
607  for (size_t i = 0; i < transports.size(); ++i)
608  {
609  bool created_transport = false;
610  ScanDTVTransport new_transport;
611  bool created_filter_transport = false;
612  ScanDTVTransport filter_transport;
613 
614  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
615  {
616  ChannelInsertInfo chan = transports[i].m_channels[j];
617 
618  bool filter = false, handle = false;
619  if (chan.m_channel_id && (kUpdateIgnoreAll == action) &&
620  IsType(info, chan, type))
621  {
622  filter = true;
623  }
624  else if (chan.m_channel_id && IsType(info, chan, type))
625  {
626  handle = true;
627  }
628 
629  if (handle)
630  {
631  bool conflicting = false;
632 
633  if (chan.m_chan_num.isEmpty() || renameChannels ||
635  chan.m_chan_num, chan.m_source_id, chan.m_channel_id))
636  {
637  if (kATSCNonConflicting == type)
638  {
639  chan.m_chan_num = channelFormat
640  .arg(chan.m_atsc_major_channel)
641  .arg(chan.m_atsc_minor_channel);
642  }
643  else if (chan.m_si_standard == "dvb")
644  {
645  chan.m_chan_num = QString("%1")
646  .arg(chan.m_service_id);
647  }
648  else if (chan.m_freqid.isEmpty())
649  {
650  chan.m_chan_num = QString("%1-%2")
651  .arg(chan.m_source_id)
652  .arg(chan.m_service_id);
653  }
654  else
655  {
656  chan.m_chan_num = QString("%1-%2")
657  .arg(chan.m_freqid)
658  .arg(chan.m_service_id);
659  }
660 
661  conflicting = ChannelUtil::IsConflicting(
662  chan.m_chan_num, chan.m_source_id, chan.m_channel_id);
663  }
664 
665  if (conflicting)
666  {
667  cout<<"Skipping Update("
668  <<chan.m_si_standard.toLatin1().constData()<<"): "
669  <<chan.m_chan_num.toLatin1().constData()<<endl;
670  handle = false;
671  }
672  }
673 
674  if (m_is_interactive && (kUpdateManual == action))
675  {
676  // TODO Ask user how to update this channel..
677  }
678 
679  bool updated = false;
680  if (handle)
681  {
682  cout<<"Update("<<chan.m_si_standard.toLatin1().constData()<<"): "
683  <<chan.m_chan_num.toLatin1().constData()<<endl;
684 
686 
687  // Find the matching multiplex. This updates the
688  // transport and network ID's in case the transport
689  // was created manually
690  uint tsid = chan.m_vct_tsid;
691  tsid = (tsid) ? tsid : chan.m_sdt_tsid;
692  tsid = (tsid) ? tsid : chan.m_pat_tsid;
693  tsid = (tsid) ? tsid : chan.m_vct_chan_tsid;
695  tsid, chan.m_orig_netid);
696  if (id >= 0)
697  chan.m_db_mplexid = id;
698 
699  updated = ChannelUtil::UpdateChannel(
700  chan.m_db_mplexid,
701  chan.m_source_id,
702  chan.m_channel_id,
703  chan.m_callsign,
704  chan.m_service_name,
705  chan.m_chan_num,
706  chan.m_service_id,
709  chan.m_use_on_air_guide,
710  chan.m_hidden, chan.m_hidden_in_guide,
711  chan.m_freqid,
712  QString(),
713  chan.m_format,
714  QString(),
715  chan.m_default_authority);
716  }
717 
718  if (filter)
719  {
720  if (!created_filter_transport)
721  {
722  filter_transport = transports[i];
723  filter_transport.m_channels.clear();
724  created_filter_transport = true;
725  }
726  filter_transport.m_channels.push_back(transports[i].m_channels[j]);
727  }
728  else if (!updated)
729  {
730  if (!created_transport)
731  {
732  new_transport = transports[i];
733  new_transport.m_channels.clear();
734  created_transport = true;
735  }
736  new_transport.m_channels.push_back(transports[i].m_channels[j]);
737  }
738  }
739 
740  if (created_filter_transport)
741  filtered.push_back(filter_transport);
742 
743  if (created_transport)
744  next_list.push_back(new_transport);
745  }
746 
747  return next_list;
748 }
749 
751 {
752  ScanDTVTransportList no_dups;
753 
755  if (!transports.empty())
756  tuner_type = transports[0].m_tuner_type;
757 
758  bool is_dvbs = ((DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
759  (DTVTunerType::kTunerTypeDVBS2 == tuner_type));
760 
761  uint freq_mult = (is_dvbs) ? 1 : 1000;
762 
763  vector<bool> ignore;
764  ignore.resize(transports.size());
765  for (size_t i = 0; i < transports.size(); ++i)
766  {
767  if (ignore[i])
768  continue;
769 
770  for (size_t j = i+1; j < transports.size(); ++j)
771  {
772  if (!transports[i].IsEqual(
773  tuner_type, transports[j], 500 * freq_mult))
774  {
775  continue;
776  }
777 
778  for (size_t k = 0; k < transports[j].m_channels.size(); ++k)
779  {
780  bool found_same = false;
781  for (size_t l = 0; l < transports[i].m_channels.size(); ++l)
782  {
783  if (transports[j].m_channels[k].IsSameChannel(
784  transports[i].m_channels[l]))
785  {
786  found_same = true;
787  transports[i].m_channels[l].ImportExtraInfo(
788  transports[j].m_channels[k]);
789  }
790  }
791  if (!found_same)
792  transports[i].m_channels.push_back(transports[j].m_channels[k]);
793  }
794  ignore[j] = true;
795  }
796  no_dups.push_back(transports[i]);
797  }
798 
799  transports = no_dups;
800 }
801 
803 {
804  bool require_av = (m_service_requirements & kRequireAV) == kRequireAV;
805  bool require_a = (m_service_requirements & kRequireAudio) != 0;
806 
807  for (size_t i = 0; i < transports.size(); ++i)
808  {
809  ChannelInsertInfoList filtered;
810  for (size_t k = 0; k < transports[i].m_channels.size(); ++k)
811  {
812  if (m_fta_only && transports[i].m_channels[k].m_is_encrypted &&
813  transports[i].m_channels[k].m_decryption_status != kEncDecrypted)
814  continue;
815 
816  if (require_a && transports[i].m_channels[k].m_is_data_service)
817  continue;
818 
819  if (require_av && transports[i].m_channels[k].m_is_audio_service)
820  continue;
821 
822  // Filter channels out that do not have a logical channel number
823  if (m_lcn_only && transports[i].m_channels[k].m_chan_num.isEmpty())
824  {
825  QString msg = FormatChannel(transports[i], transports[i].m_channels[k]);
826  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + QString("No LCN: %1").arg(msg));
827  continue;
828  }
829 
830  // Filter channels out that are not present in PAT, PMT and SDT.
831  if (m_complete_only &&
832  !(transports[i].m_channels[k].m_in_pat &&
833  transports[i].m_channels[k].m_in_pmt &&
834  transports[i].m_channels[k].m_in_sdt &&
835  (transports[i].m_channels[k].m_pat_tsid ==
836  transports[i].m_channels[k].m_sdt_tsid)))
837  {
838  QString msg = FormatChannel(transports[i], transports[i].m_channels[k]);
839  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + QString("Not in PAT/PMT/SDT: %1").arg(msg));
840  continue;
841  }
842 
843  // Filter channels out that do not have a name
844  if (m_complete_only && transports[i].m_channels[k].m_service_name.isEmpty())
845  {
846  QString msg = FormatChannel(transports[i], transports[i].m_channels[k]);
847  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + QString("No name: %1").arg(msg));
848  continue;
849  }
850 
851  // Filter channels out only in channels.conf, i.e. not found
852  if (transports[i].m_channels[k].m_in_channels_conf &&
853  !(transports[i].m_channels[k].m_in_pat ||
854  transports[i].m_channels[k].m_in_pmt ||
855  transports[i].m_channels[k].m_in_vct ||
856  transports[i].m_channels[k].m_in_nit ||
857  transports[i].m_channels[k].m_in_sdt))
858  continue;
859 
860  filtered.push_back(transports[i].m_channels[k]);
861  }
862  transports[i].m_channels = filtered;
863  }
864 }
865 
871  uint sourceid, ScanDTVTransportList &transports) const
872 {
873  ScanDTVTransportList not_in_scan;
874 
876  if (!transports.empty())
877  tuner_type = transports[0].m_tuner_type;
878 
879  bool is_dvbs =
880  (DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
881  (DTVTunerType::kTunerTypeDVBS2 == tuner_type);
882 
883  uint freq_mult = (is_dvbs) ? 1 : 1000;
884 
885  MSqlQuery query(MSqlQuery::InitCon());
886  query.prepare(
887  "SELECT mplexid "
888  "FROM dtv_multiplex "
889  "WHERE sourceid = :SOURCEID "
890  "GROUP BY mplexid "
891  "ORDER BY mplexid");
892  query.bindValue(":SOURCEID", sourceid);
893 
894  if (!query.exec())
895  {
896  MythDB::DBError("GetDBTransports()", query);
897  return not_in_scan;
898  }
899 
900  while (query.next())
901  {
902  uint mplexid = query.value(0).toUInt();
903 
904  ScanDTVTransport newt;
905  if (!newt.FillFromDB(tuner_type, mplexid))
906  continue;
907 
908  bool newt_found = false;
909  QMap<uint,bool> found_chan;
910 
911  for (size_t i = 0; i < transports.size(); ++i)
912  {
913  if (!transports[i].IsEqual(tuner_type, newt, 500 * freq_mult, true))
914  continue;
915 
916  transports[i].m_mplex = mplexid;
917  newt_found = true;
918  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
919  {
920  ChannelInsertInfo &chan = transports[i].m_channels[j];
921  for (size_t k = 0; k < newt.m_channels.size(); ++k)
922  {
923  if (newt.m_channels[k].IsSameChannel(chan, true))
924  {
925  found_chan[k] = true;
926  chan.m_db_mplexid = mplexid;
927  chan.m_channel_id = newt.m_channels[k].m_channel_id;
928  }
929  }
930  }
931  break;
932  }
933 
934  if (!newt_found)
935  {
936  /* XXX HACK -- begin
937  * disabling adding transponders not found in the scan list
938  * to the db list to avoid deleting many channels as off air
939  * for a single transponder scan
940  not_in_scan.push_back(newt);
941  * XXX HACK -- end */
942  }
943  else
944  {
945  ScanDTVTransport tmp = newt;
946  tmp.m_channels.clear();
947 
948  for (size_t k = 0; k < newt.m_channels.size(); ++k)
949  {
950  if (!found_chan[k])
951  tmp.m_channels.push_back(newt.m_channels[k]);
952  }
953 
954  if (!tmp.m_channels.empty())
955  not_in_scan.push_back(tmp);
956  }
957  }
958 
959  return not_in_scan;
960 }
961 
963 {
965  for (size_t i = 0; i < transports.size(); ++i)
966  {
967  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
968  {
969  ChannelInsertInfo &chan = transports[i].m_channels[j];
970  if (((chan.m_could_be_opencable && (chan.m_si_standard == "mpeg")) ||
971  chan.m_is_opencable) && !chan.m_in_vct)
972  {
973  chan.m_si_standard = "opencable";
974  }
975  }
976  }
977 }
978 
980  const ScanDTVTransportList &transports)
981 {
983  for (size_t i = 0; i < transports.size(); ++i)
984  {
985  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
986  {
987  const ChannelInsertInfo &chan = transports[i].m_channels[j];
988  int enc = (chan.m_is_encrypted) ?
989  ((chan.m_decryption_status == kEncDecrypted) ? 2 : 1) : 0;
990  info.m_atsc_channels[enc] += (chan.m_si_standard == "atsc");
991  info.m_dvb_channels [enc] += (chan.m_si_standard == "dvb");
992  info.m_mpeg_channels[enc] += (chan.m_si_standard == "mpeg");
993  info.m_scte_channels[enc] += (chan.m_si_standard == "opencable");
994  info.m_ntsc_channels[enc] += (chan.m_si_standard == "ntsc");
995  if (chan.m_si_standard != "ntsc")
996  {
997  ++info.m_prognum_cnt[chan.m_service_id];
998  ++info.m_channum_cnt[map_str(chan.m_chan_num)];
999  }
1000  if (chan.m_si_standard == "atsc")
1001  {
1002  ++info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1003  (chan.m_atsc_minor_channel)];
1004  ++info.m_atscmin_cnt[chan.m_atsc_minor_channel];
1005  ++info.m_atscmaj_cnt[chan.m_atsc_major_channel];
1006  }
1007  if (chan.m_si_standard == "ntsc")
1008  {
1009  ++info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1010  (chan.m_atsc_minor_channel)];
1011  }
1012  }
1013  }
1014 
1015  return info;
1016 }
1017 
1019  const ScanDTVTransportList &transports,
1020  const ChannelImporterBasicStats &info)
1021 {
1023 
1024  for (size_t i = 0; i < transports.size(); ++i)
1025  {
1026  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
1027  {
1028  const ChannelInsertInfo &chan = transports[i].m_channels[j];
1029  stats.m_unique_prognum +=
1030  (info.m_prognum_cnt[chan.m_service_id] == 1) ? 1 : 0;
1031  stats.m_unique_channum +=
1032  (info.m_channum_cnt[map_str(chan.m_chan_num)] == 1) ? 1 : 0;
1033 
1034  if (chan.m_si_standard == "atsc")
1035  {
1036  stats.m_unique_atscnum +=
1037  (info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1038  (chan.m_atsc_minor_channel)] == 1) ? 1 : 0;
1039  stats.m_unique_atscmin +=
1040  (info.m_atscmin_cnt[(chan.m_atsc_minor_channel)] == 1) ? 1 : 0;
1041  stats.m_max_atscmajcnt = max(
1042  stats.m_max_atscmajcnt,
1043  info.m_atscmaj_cnt[chan.m_atsc_major_channel]);
1044  }
1045  }
1046  }
1047 
1048  stats.m_unique_total = (stats.m_unique_prognum + stats.m_unique_atscnum +
1049  stats.m_unique_atscmin + stats.m_unique_channum);
1050 
1051  return stats;
1052 }
1053 
1054 
1056  const ScanDTVTransport &transport,
1057  const ChannelInsertInfo &chan,
1058  const ChannelImporterBasicStats *info)
1059 {
1060  QString msg;
1061  QTextStream ssMsg(&msg);
1062 
1063  ssMsg << transport.m_modulation.toString().toLatin1().constData()
1064  << ":";
1065  ssMsg << transport.m_frequency << ":";
1066 
1067  QString si_standard = (chan.m_si_standard=="opencable") ?
1068  QString("scte") : chan.m_si_standard;
1069 
1070  if (si_standard == "atsc" || si_standard == "scte")
1071  ssMsg << (QString("%1:%2:%3-%4:%5:%6=%7=%8:%9")
1072  .arg(chan.m_callsign).arg(chan.m_chan_num)
1073  .arg(chan.m_atsc_major_channel)
1074  .arg(chan.m_atsc_minor_channel)
1075  .arg(chan.m_service_id)
1076  .arg(chan.m_vct_tsid)
1077  .arg(chan.m_vct_chan_tsid)
1078  .arg(chan.m_pat_tsid)
1079  .arg(si_standard)).toLatin1().constData();
1080  else if (si_standard == "dvb")
1081  ssMsg << (QString("%1:%2:%3:%4:%5:%6=%7:%8")
1082  .arg(chan.m_service_name).arg(chan.m_chan_num)
1083  .arg(chan.m_netid).arg(chan.m_orig_netid)
1084  .arg(chan.m_service_id)
1085  .arg(chan.m_sdt_tsid)
1086  .arg(chan.m_pat_tsid)
1087  .arg(si_standard)).toLatin1().constData();
1088  else
1089  ssMsg << (QString("%1:%2:%3:%4:%5")
1090  .arg(chan.m_callsign).arg(chan.m_chan_num)
1091  .arg(chan.m_service_id)
1092  .arg(chan.m_pat_tsid)
1093  .arg(si_standard)).toLatin1().constData();
1094 
1095  if (info)
1096  {
1097  ssMsg <<"\t"
1098  << chan.m_channel_id;
1099  }
1100 
1101  if (info)
1102  {
1103  ssMsg << ":"
1104  << (QString("cnt(pnum:%1,channum:%2)")
1105  .arg(info->m_prognum_cnt[chan.m_service_id])
1106  .arg(info->m_channum_cnt[map_str(chan.m_chan_num)])
1107  ).toLatin1().constData();
1108  if (chan.m_si_standard == "atsc")
1109  {
1110  ssMsg <<
1111  (QString(":atsc_cnt(tot:%1,minor:%2)")
1112  .arg(info->m_atscnum_cnt[
1113  (chan.m_atsc_major_channel << 16) |
1114  (chan.m_atsc_minor_channel)])
1115  .arg(info->m_atscmin_cnt[
1116  chan.m_atsc_minor_channel])
1117  ).toLatin1().constData();
1118  }
1119  }
1120 
1121  return msg;
1122 }
1123 
1136  const ScanDTVTransport &/*transport*/,
1137  const ChannelInsertInfo &chan)
1138 {
1139  QString msg;
1140  QTextStream ssMsg(&msg);
1141 
1142  QString si_standard = (chan.m_si_standard=="opencable") ?
1143  QString("scte") : chan.m_si_standard;
1144 
1145  if (si_standard == "atsc" || si_standard == "scte")
1146  {
1147 
1148  if (si_standard == "atsc")
1149  ssMsg << (QString("%1-%2")
1150  .arg(chan.m_atsc_major_channel)
1151  .arg(chan.m_atsc_minor_channel)).toLatin1().constData();
1152  else if (chan.m_freqid.isEmpty())
1153  ssMsg << (QString("%1-%2")
1154  .arg(chan.m_source_id)
1155  .arg(chan.m_service_id)).toLatin1().constData();
1156  else
1157  ssMsg << (QString("%1-%2")
1158  .arg(chan.m_freqid)
1159  .arg(chan.m_service_id)).toLatin1().constData();
1160 
1161  if (!chan.m_callsign.isEmpty())
1162  ssMsg << (QString(" (%1)")
1163  .arg(chan.m_callsign)).toLatin1().constData();
1164  }
1165  else if (si_standard == "dvb")
1166  ssMsg << (QString("%1 (%2 %3)")
1167  .arg(chan.m_service_name).arg(chan.m_service_id)
1168  .arg(chan.m_netid)).toLatin1().constData();
1169  else if (chan.m_freqid.isEmpty())
1170  ssMsg << (QString("%1-%2")
1171  .arg(chan.m_source_id).arg(chan.m_service_id))
1172  .toLatin1().constData();
1173  else
1174  ssMsg << (QString("%1-%2")
1175  .arg(chan.m_freqid).arg(chan.m_service_id))
1176  .toLatin1().constData();
1177 
1178  return msg;
1179 }
1180 
1182  const ScanDTVTransportList &transports_in,
1183  const ChannelImporterBasicStats &info)
1184 {
1185  // Sort transports in order of increasing frequency
1186  struct less_than_key
1187  {
1188  inline bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1189  {
1190  return t1.m_frequency < t2.m_frequency;
1191  }
1192  };
1193  ScanDTVTransportList transports(transports_in);
1194  std::sort(transports.begin(), transports.end(), less_than_key());
1195 
1196  QString msg;
1197 
1198  for (size_t i = 0; i < transports.size(); ++i)
1199  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
1200  msg += FormatChannel(transports[i], transports[i].m_channels[j],
1201  &info) + "\n";
1202 
1203  return msg;
1204 }
1205 
1207  uint transport_count,
1208  const ChannelImporterBasicStats &info,
1209  const ChannelImporterUniquenessStats &stats)
1210 {
1211  //: %n is the number of transports
1212  QString msg = tr("Found %n transport(s):\n", "", transport_count);
1213  msg += tr("Channels: FTA Enc Dec\n") +
1214  QString("ATSC %1 %2 %3\n")
1215  .arg(info.m_atsc_channels[0],3).arg(info.m_atsc_channels[1],3)
1216  .arg(info.m_atsc_channels[2],3) +
1217  QString("DVB %1 %2 %3\n")
1218  .arg(info.m_dvb_channels [0],3).arg(info.m_dvb_channels [1],3)
1219  .arg(info.m_dvb_channels [2],3) +
1220  QString("SCTE %1 %2 %3\n")
1221  .arg(info.m_scte_channels[0],3).arg(info.m_scte_channels[1],3)
1222  .arg(info.m_scte_channels[2],3) +
1223  QString("MPEG %1 %2 %3\n")
1224  .arg(info.m_mpeg_channels[0],3).arg(info.m_mpeg_channels[1],3)
1225  .arg(info.m_mpeg_channels[2],3) +
1226  QString("NTSC %1\n").arg(info.m_ntsc_channels[0],3) +
1227  tr("Unique: prog %1 atsc %2 atsc minor %3 channum %4\n")
1228  .arg(stats.m_unique_prognum).arg(stats.m_unique_atscnum)
1229  .arg(stats.m_unique_atscmin).arg(stats.m_unique_channum) +
1230  tr("Max atsc major count: %1")
1231  .arg(stats.m_max_atscmajcnt);
1232 
1233  return msg;
1234 }
1235 
1237  const ChannelImporterBasicStats &info,
1238  const ChannelInsertInfo &chan, ChannelType type)
1239 {
1240  switch (type)
1241  {
1242  case kATSCNonConflicting:
1243  return ((chan.m_si_standard == "atsc") &&
1244  (info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1245  (chan.m_atsc_minor_channel)] == 1));
1246 
1247  case kDVBNonConflicting:
1248  return ((chan.m_si_standard == "dvb") &&
1249  (info.m_prognum_cnt[chan.m_service_id] == 1));
1250 
1251  case kMPEGNonConflicting:
1252  return ((chan.m_si_standard == "mpeg") &&
1253  (info.m_channum_cnt[map_str(chan.m_chan_num)] == 1));
1254 
1255  case kSCTENonConflicting:
1256  return (((chan.m_si_standard == "scte") ||
1257  (chan.m_si_standard == "opencable")) &&
1258  (info.m_channum_cnt[map_str(chan.m_chan_num)] == 1));
1259 
1260  case kNTSCNonConflicting:
1261  return ((chan.m_si_standard == "ntsc") &&
1262  (info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1263  (chan.m_atsc_minor_channel)] == 1));
1264 
1265  case kATSCConflicting:
1266  return ((chan.m_si_standard == "atsc") &&
1267  (info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1268  (chan.m_atsc_minor_channel)] != 1));
1269 
1270  case kDVBConflicting:
1271  return ((chan.m_si_standard == "dvb") &&
1272  (info.m_prognum_cnt[chan.m_service_id] != 1));
1273 
1274  case kMPEGConflicting:
1275  return ((chan.m_si_standard == "mpeg") &&
1276  (info.m_channum_cnt[map_str(chan.m_chan_num)] != 1));
1277 
1278  case kSCTEConflicting:
1279  return (((chan.m_si_standard == "scte") ||
1280  (chan.m_si_standard == "opencable")) &&
1281  (info.m_channum_cnt[map_str(chan.m_chan_num)] != 1));
1282 
1283  case kNTSCConflicting:
1284  return ((chan.m_si_standard == "ntsc") &&
1285  (info.m_atscnum_cnt[(chan.m_atsc_major_channel << 16) |
1286  (chan.m_atsc_minor_channel)] != 1));
1287  }
1288  return false;
1289 }
1290 
1292  const ScanDTVTransportList &transports,
1293  const ChannelImporterBasicStats &info,
1294  ChannelType type, uint &new_chan, uint &old_chan)
1295 {
1296  new_chan = old_chan = 0;
1297  for (size_t i = 0; i < transports.size(); ++i)
1298  {
1299  for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
1300  {
1301  ChannelInsertInfo chan = transports[i].m_channels[j];
1302  if (IsType(info, chan, type))
1303  {
1304  if (chan.m_channel_id)
1305  ++old_chan;
1306  else
1307  ++new_chan;
1308  }
1309  }
1310  }
1311 }
1312 
1327  const ChannelImporterBasicStats &/*info*/,
1328  const ScanDTVTransport &/*transport*/,
1329  const ChannelInsertInfo &chan)
1330 {
1331  static QMutex last_free_lock;
1332  static QMap<uint,uint> last_free_chan_num_map;
1333 
1334  QString channelFormat = "%1_%2";
1335  QString chan_num = channelFormat
1336  .arg(chan.m_atsc_major_channel)
1337  .arg(chan.m_atsc_minor_channel);
1338 
1339  if (!chan.m_atsc_minor_channel)
1340  {
1341  if (chan.m_si_standard == "dvb")
1342  {
1343  chan_num = QString("%1")
1344  .arg(chan.m_service_id);
1345  }
1346  else if (chan.m_freqid.isEmpty())
1347  {
1348  chan_num = QString("%1-%2")
1349  .arg(chan.m_source_id)
1350  .arg(chan.m_service_id);
1351  }
1352  else
1353  {
1354  chan_num = QString("%1-%2")
1355  .arg(chan.m_freqid)
1356  .arg(chan.m_service_id);
1357  }
1358  }
1359 
1360  if (!ChannelUtil::IsConflicting(chan_num, chan.m_source_id))
1361  return chan_num;
1362 
1363  QMutexLocker locker(&last_free_lock);
1364  uint last_free_chan_num = last_free_chan_num_map[chan.m_source_id];
1365  for (last_free_chan_num++; ; ++last_free_chan_num)
1366  {
1367  chan_num = QString::number(last_free_chan_num);
1368  if (!ChannelUtil::IsConflicting(chan_num, chan.m_source_id))
1369  break;
1370  }
1371  // cppcheck-suppress unreadVariable
1372  last_free_chan_num_map[chan.m_source_id] = last_free_chan_num;
1373 
1374  return chan_num;
1375 }
1376 
1379 {
1380  DeleteAction action = kDeleteAll;
1381  if (m_use_gui)
1382  {
1383  int ret = -1;
1384  do
1385  {
1386  MythScreenStack *popupStack =
1387  GetMythMainWindow()->GetStack("popup stack");
1388  MythDialogBox *deleteDialog =
1389  new MythDialogBox(msg, popupStack, "deletechannels");
1390 
1391  if (deleteDialog->Create())
1392  {
1393  deleteDialog->AddButton(tr("Delete all"));
1394  deleteDialog->AddButton(tr("Set all invisible"));
1395 // deleteDialog->AddButton(tr("Handle manually"));
1396  deleteDialog->AddButton(tr("Ignore all"));
1397  QObject::connect(deleteDialog, &MythDialogBox::Closed,
1398  [&](const QString & /*resultId*/, int result)
1399  {
1400  ret = result;
1401  m_eventLoop.quit();
1402  });
1403  popupStack->AddScreen(deleteDialog);
1404 
1405  m_eventLoop.exec();
1406  }
1407  } while (ret < 0);
1408 
1409  action = (0 == ret) ? kDeleteAll : action;
1410  action = (1 == ret) ? kDeleteInvisibleAll : action;
1411  action = (2 == ret) ? kDeleteIgnoreAll : action;
1412 // action = (2 == m_deleteChannelResult) ? kDeleteManual : action;
1413 // action = (3 == m_deleteChannelResult) ? kDeleteIgnoreAll : action;
1414  }
1415  else if (m_is_interactive)
1416  {
1417  cout << msg.toLatin1().constData()
1418  << endl
1419  << tr("Do you want to:").toLatin1().constData()
1420  << endl
1421  << tr("1. Delete all").toLatin1().constData()
1422  << endl
1423  << tr("2. Set all invisible").toLatin1().constData()
1424  << endl
1425 // cout << "3. Handle manually" << endl;
1426  << tr("4. Ignore all").toLatin1().constData()
1427  << endl;
1428  while (true)
1429  {
1430  string ret;
1431  cin >> ret;
1432  bool ok;
1433  uint val = QString(ret.c_str()).toUInt(&ok);
1434  if (ok && (val == 1 || val == 2 || val == 4))
1435  {
1436  action = (1 == val) ? kDeleteAll : action;
1437  action = (2 == val) ? kDeleteInvisibleAll : action;
1438  //action = (3 == val) ? kDeleteManual : action;
1439  action = (4 == val) ? kDeleteIgnoreAll : action;
1440  break;
1441  }
1442 
1443  //cout << "Please enter either 1, 2, 3 or 4:" << endl;
1444  cout << tr("Please enter either 1, 2 or 4:")
1445  .toLatin1().constData() << endl;//
1446  }
1447  }
1448 
1449  return action;
1450 }
1451 
1454 {
1455  InsertAction action = kInsertAll;
1456  if (m_use_gui)
1457  {
1458  int ret = -1;
1459  do
1460  {
1461  MythScreenStack *popupStack =
1462  GetMythMainWindow()->GetStack("popup stack");
1463  MythDialogBox *insertDialog =
1464  new MythDialogBox(msg, popupStack, "insertchannels");
1465 
1466  if (insertDialog->Create())
1467  {
1468  insertDialog->AddButton(tr("Insert all"));
1469  insertDialog->AddButton(tr("Insert manually"));
1470  insertDialog->AddButton(tr("Ignore all"));
1471  QObject::connect(insertDialog, &MythDialogBox::Closed,
1472  [&](const QString & /*resultId*/, int result)
1473  {
1474  ret = result;
1475  m_eventLoop.quit();
1476  });
1477 
1478  popupStack->AddScreen(insertDialog);
1479  m_eventLoop.exec();
1480  }
1481  } while (ret < 0);
1482 
1483  action = (0 == ret) ? kInsertAll : action;
1484  action = (1 == ret) ? kInsertManual : action;
1485  action = (2 == ret) ? kInsertIgnoreAll : action;
1486  }
1487  else if (m_is_interactive)
1488  {
1489  cout << msg.toLatin1().constData()
1490  << endl
1491  << tr("Do you want to:").toLatin1().constData()
1492  << endl
1493  << tr("1. Insert all").toLatin1().constData()
1494  << endl
1495  << tr("2. Insert manually").toLatin1().constData()
1496  << endl
1497  << tr("3. Ignore all").toLatin1().constData()
1498  << endl;
1499  while (true)
1500  {
1501  string ret;
1502  cin >> ret;
1503  bool ok;
1504  uint val = QString(ret.c_str()).toUInt(&ok);
1505  if (ok && (1 <= val) && (val <= 3))
1506  {
1507  action = (1 == val) ? kInsertAll : action;
1508  action = (2 == val) ? kInsertManual : action;
1509  action = (3 == val) ? kInsertIgnoreAll : action;
1510  break;
1511  }
1512 
1513  cout << tr("Please enter either 1, 2, or 3:")
1514  .toLatin1().constData() << endl;
1515  }
1516  }
1517 
1518  return action;
1519 }
1520 
1523 {
1524  UpdateAction action = kUpdateAll;
1525 
1526  if (m_use_gui)
1527  {
1528  int ret = -1;
1529  do
1530  {
1531  MythScreenStack *popupStack =
1532  GetMythMainWindow()->GetStack("popup stack");
1533  MythDialogBox *updateDialog =
1534  new MythDialogBox(msg, popupStack, "updatechannels");
1535 
1536  if (updateDialog->Create())
1537  {
1538  updateDialog->AddButton(tr("Update all"));
1539  updateDialog->AddButton(tr("Update manually"));
1540  updateDialog->AddButton(tr("Ignore all"));
1541  QObject::connect(updateDialog, &MythDialogBox::Closed,
1542  [&](const QString& /*resultId*/, int result)
1543  {
1544  ret = result;
1545  m_eventLoop.quit();
1546  });
1547 
1548  popupStack->AddScreen(updateDialog);
1549  m_eventLoop.exec();
1550  }
1551  } while (ret < 0);
1552 
1553  action = (0 == ret) ? kUpdateAll : action;
1554  action = (1 == ret) ? kUpdateManual : action;
1555  action = (2 == ret) ? kUpdateIgnoreAll : action;
1556  }
1557  else if (m_is_interactive)
1558  {
1559  cout << msg.toLatin1().constData()
1560  << endl
1561  << tr("Do you want to:").toLatin1().constData()
1562  << endl
1563  << tr("1. Update all").toLatin1().constData()
1564  << endl
1565  << tr("2. Update manually").toLatin1().constData()
1566  << endl
1567  << tr("3. Ignore all").toLatin1().constData()
1568  << endl;
1569  while (true)
1570  {
1571  string ret;
1572  cin >> ret;
1573  bool ok;
1574  uint val = QString(ret.c_str()).toUInt(&ok);
1575  if (ok && (1 <= val) && (val <= 3))
1576  {
1577  action = (1 == val) ? kUpdateAll : action;
1578  action = (2 == val) ? kUpdateManual : action;
1579  action = (3 == val) ? kUpdateIgnoreAll : action;
1580  break;
1581  }
1582 
1583  cout << tr("Please enter either 1, 2, or 3:")
1584  .toLatin1().constData() << endl;
1585  }
1586  }
1587 
1588  return action;
1589 }
1590 
1592  MythMainWindow *parent, const QString& title,
1593  const QString& message, QString &text)
1594 {
1595  int dc = -1;
1596  MythScreenStack *popupStack = parent->GetStack("popup stack");
1597  MythDialogBox *popup = new MythDialogBox(title, message, popupStack,
1598  "manualchannelpopup");
1599 
1600  if (popup->Create())
1601  {
1602  popup->AddButton(QCoreApplication::translate("(Common)", "OK"));
1603  popup->AddButton(tr("Edit"));
1604  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel"));
1605  popup->AddButton(QCoreApplication::translate("(Common)", "Cancel All"));
1606  QObject::connect(popup, &MythDialogBox::Closed,
1607  [&](const QString & /*resultId*/, int result)
1608  {
1609  dc = result;
1610  m_eventLoop.quit();
1611  });
1612  popupStack->AddScreen(popup);
1613  m_eventLoop.exec();
1614  }
1615  else
1616  {
1617  delete popup;
1618  popup = nullptr;
1619  }
1620 
1621  if (1 == dc)
1622  {
1623  MythTextInputDialog *textEdit =
1624  new MythTextInputDialog(popupStack,
1625  tr("Please enter a unique channel number."),
1626  FilterNone, false, text);
1627  if (textEdit->Create())
1628  {
1629  QObject::connect(textEdit, &MythTextInputDialog::haveResult,
1630  [&](QString result)
1631  {
1632  dc = 0;
1633  text = std::move(result);
1634  });
1635  QObject::connect(textEdit, &MythTextInputDialog::Exiting,
1636  [&]()
1637  {
1638  m_eventLoop.quit();
1639  });
1640 
1641  popupStack->AddScreen(textEdit);
1642  m_eventLoop.exec();
1643  }
1644  else
1645  delete textEdit;
1646  }
1647 
1648  bool ok = (0 == dc);
1649 
1650  return (ok) ? kOCTOk :
1651  ((1 == dc) ? kOCTCancel : kOCTCancelAll);
1652 }
1653 
1655  const ChannelImporterBasicStats &info,
1656  const ScanDTVTransport &transport,
1657  ChannelInsertInfo &chan)
1658 {
1659  QString msg = tr("Channel %1 was found to be in conflict with other "
1660  "channels.").arg(SimpleFormatChannel(transport, chan));
1661 
1662  OkCancelType ret = kOCTCancel;
1663 
1664  if (m_use_gui)
1665  {
1666  while (true)
1667  {
1668  QString msg2 = msg;
1669  msg2 += " ";
1670  msg2 += tr("Please enter a unique channel number.");
1671 
1672  QString val = ComputeSuggestedChannelNum(info, transport, chan);
1673  msg2 += " ";
1674  msg2 += tr("Default value is %1").arg(val);
1675  ret = ShowManualChannelPopup(
1676  GetMythMainWindow(), tr("Channel Importer"),
1677  msg2, val);
1678 
1679  if (kOCTOk != ret)
1680  break; // user canceled..
1681 
1682  bool ok = (val.length() >= 1);
1683  ok = ok && ((val[0] >= '0') && (val[0] <= '9'));
1684  ok = ok && !ChannelUtil::IsConflicting(
1685  val, chan.m_source_id, chan.m_channel_id);
1686 
1687  chan.m_chan_num = (ok) ? val : chan.m_chan_num;
1688  if (ok)
1689  break;
1690  }
1691  }
1692  else if (m_is_interactive)
1693  {
1694  cout << msg.toLatin1().constData() << endl;
1695 
1696  QString cancelStr = QCoreApplication::translate("(Common)",
1697  "Cancel").toLower();
1698  QString cancelAllStr = QCoreApplication::translate("(Common)",
1699  "Cancel All").toLower();
1700  QString msg2 = tr("Please enter a non-conflicting channel number "
1701  "(or type '%1' to skip, '%2' to skip all):")
1702  .arg(cancelStr).arg(cancelAllStr);
1703 
1704  while (true)
1705  {
1706  cout << msg2.toLatin1().constData() << endl;
1707  string sret;
1708  cin >> sret;
1709  QString val = QString(sret.c_str());
1710  if (val.toLower() == cancelStr)
1711  {
1712  ret = kOCTCancel;
1713  break; // user canceled..
1714  }
1715  if (val.toLower() == cancelAllStr)
1716  {
1717  ret = kOCTCancelAll;
1718  break; // user canceled..
1719  }
1720 
1721  bool ok = (val.length() >= 1);
1722  ok = ok && ((val[0] >= '0') && (val[0] <= '9'));
1723  ok = ok && !ChannelUtil::IsConflicting(
1724  val, chan.m_source_id, chan.m_channel_id);
1725 
1726  chan.m_chan_num = (ok) ? val : chan.m_chan_num;
1727  if (ok)
1728  {
1729  ret = kOCTOk;
1730  break;
1731  }
1732  }
1733  }
1734 
1735  return ret;
1736 }
1737 
1739  const ChannelImporterBasicStats &info,
1740  const ScanDTVTransport &transport,
1741  ChannelInsertInfo &chan)
1742 {
1743  QString msg = tr("You chose to manually insert channel %1.")
1744  .arg(SimpleFormatChannel(transport, chan));
1745 
1746  OkCancelType ret = kOCTCancel;
1747 
1748  if (m_use_gui)
1749  {
1750  while (true)
1751  {
1752  QString msg2 = msg;
1753  msg2 += " ";
1754  msg2 += tr("Please enter a unique channel number.");
1755 
1756  QString val = ComputeSuggestedChannelNum(info, transport, chan);
1757  msg2 += " ";
1758  msg2 += tr("Default value is %1").arg(val);
1759  ret = ShowManualChannelPopup(
1760  GetMythMainWindow(), tr("Channel Importer"),
1761  msg2, val);
1762 
1763  if (kOCTOk != ret)
1764  break; // user canceled..
1765 
1766  bool ok = (val.length() >= 1);
1767  ok = ok && ((val[0] >= '0') && (val[0] <= '9'));
1768  ok = ok && !ChannelUtil::IsConflicting(
1769  val, chan.m_source_id, chan.m_channel_id);
1770 
1771  chan.m_chan_num = (ok) ? val : chan.m_chan_num;
1772  if (ok)
1773  {
1774  ret = kOCTOk;
1775  break;
1776  }
1777  }
1778  }
1779  else if (m_is_interactive)
1780  {
1781  cout << msg.toLatin1().constData() << endl;
1782 
1783  QString cancelStr = QCoreApplication::translate("(Common)",
1784  "Cancel").toLower();
1785  QString cancelAllStr = QCoreApplication::translate("(Common)",
1786  "Cancel All").toLower();
1787 
1788  //: %1 is the translation of "cancel", %2 of "cancel all"
1789  QString msg2 = tr("Please enter a non-conflicting channel number "
1790  "(or type '%1' to skip, '%2' to skip all): ")
1791  .arg(cancelStr).arg(cancelAllStr);
1792 
1793  while (true)
1794  {
1795  cout << msg2.toLatin1().constData() << endl;
1796  string sret;
1797  cin >> sret;
1798  QString val = QString(sret.c_str());
1799  if (val.toLower() == cancelStr)
1800  {
1801  ret = kOCTCancel;
1802  break; // user canceled..
1803  }
1804  if (val.toLower() == cancelAllStr)
1805  {
1806  ret = kOCTCancelAll;
1807  break; // user canceled..
1808  }
1809 
1810  bool ok = (val.length() >= 1);
1811  ok = ok && ((val[0] >= '0') && (val[0] <= '9'));
1812  ok = ok && !ChannelUtil::IsConflicting(
1813  val, chan.m_source_id, chan.m_channel_id);
1814 
1815  chan.m_chan_num = (ok) ? val : chan.m_chan_num;
1816  if (ok)
1817  {
1818  ret = kOCTOk;
1819  break;
1820  }
1821  }
1822  }
1823 
1824  return ret;
1825 }
QString toString(ChannelType type)
static QString GetSummary(uint transport_count, const ChannelImporterBasicStats &info, const ChannelImporterUniquenessStats &stats)
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
static bool SetVisible(uint channel_id, bool visible)
static bool CreateChannel(uint db_mplexid, uint db_sourceid, uint new_channel_id, const QString &callsign, const QString &service_name, const QString &chan_num, uint service_id, uint atsc_major_channel, uint atsc_minor_channel, bool use_on_air_guide, bool hidden, bool hidden_in_guide, const QString &freqid, QString icon=QString(), QString format="Default", QString xmltvid=QString(), QString default_authority=QString())
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
uint SaveScan(const ScanDTVTransportList &scan)
Definition: scaninfo.cpp:21
QString m_service_name
Definition: channelinfo.h:205
QMap< QString, uint > m_channum_cnt
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
static bool IsType(const ChannelImporterBasicStats &info, const ChannelInsertInfo &chan, ChannelType type)
DeleteAction QueryUserDelete(const QString &msg)
For multiple channels.
QMap< uint, uint > m_prognum_cnt
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.
vector< ScanDTVTransport > ScanDTVTransportList
Definition: dtvmultiplex.h:141
int size(void) const
Definition: mythdbcon.h:203
static int GetBetterMplexID(int current_mplexid, int transport_id, int network_id)
Returns best match multiplex ID, creating one if needed.
MythScreenStack * GetStack(const QString &stackname)
void FilterServices(ScanDTVTransportList &transports) const
unsigned int uint
Definition: compat.h:140
QMap< uint, uint > m_atscmaj_cnt
QMap< uint, uint > m_atscmin_cnt
static guint32 * tmp
Definition: goom_core.c:35
static const int kTunerTypeDVBS1
static void FixUpOpenCable(ScanDTVTransportList &transports)
static bool SetChannelValue(const QString &field_name, const QString &value, uint sourceid, const QString &channum)
void InsertChannels(const ScanDTVTransportList &, const ChannelImporterBasicStats &)
#define LOC
ChannelInsertInfoList m_channels
Definition: dtvmultiplex.h:139
static QString SimpleFormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Format channel information into a simple string.
QVariant value(int i) const
Definition: mythdbcon.h:198
void AddButton(const QString &title, QVariant data=0, bool newMenu=false, bool setCurrent=false)
OkCancelType ShowManualChannelPopup(MythMainWindow *parent, const QString &title, const QString &message, QString &text)
bool Create(void) override
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
static const int kTunerTypeDVBS2
DTVModulation m_modulation
Definition: dtvmultiplex.h:100
void haveResult(QString)
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
static bool IsConflicting(const QString &channum, uint sourceid=0, uint excluded_chanid=0)
Definition: channelutil.h:280
ScanDTVTransportList GetDBTransports(uint sourceid, ScanDTVTransportList &) const
Adds found channel info to transports list, returns channels in DB which were not found in scan.
QString toString() 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)
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
OkCancelType QueryUserResolve(const ChannelImporterBasicStats &info, const ScanDTVTransport &transport, ChannelInsertInfo &chan)
For a single channel.
static bool CreateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
Definition: channelutil.h:140
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
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, bool hidden, bool hidden_in_guide, const QString &freqid=QString(), const QString &icon=QString(), QString format=QString(), const QString &xmltvid=QString(), const QString &default_authority=QString())
vector< ChannelInsertInfo > ChannelInsertInfoList
Definition: channelinfo.h:240
MythMainWindow * GetMythMainWindow(void)
static bool MarkProcessed(uint scanid)
Definition: scaninfo.cpp:192
void CleanupDuplicates(ScanDTVTransportList &transports) const
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:807
static ChannelImporterBasicStats CollectStats(const ScanDTVTransportList &transports)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString m_si_standard
Definition: channelinfo.h:226
uint DeleteChannels(ScanDTVTransportList &)
OkCancelType
static ChannelImporterUniquenessStats CollectUniquenessStats(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
bool FillFromDB(DTVTunerType type, uint mplexid) override
static QString FormatChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
static uint CreateMultiplex(int sourceid, QString sistandard, uint64_t frequency, QString modulation, int transport_id=-1, int network_id=-1)
QString m_default_authority
Definition: channelinfo.h:217
static QString ComputeSuggestedChannelNum(const ChannelImporterBasicStats &info, const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Compute a suggested channel number based on various aspects of the channel information.
ScanDTVTransportList UpdateChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, UpdateAction action, ChannelType type, ScanDTVTransportList &filtered)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
QMap< uint, uint > m_atscnum_cnt
static uint GetChannelCount(int sourceid=-1)
static QString GetChanNum(int chan_id)
Returns the channel-number string of the given channel.
void Closed(QString, int)
uint DeleteUnusedTransports(uint sourceid)
bool Create(void) override
static bool DeleteChannel(uint channel_id)
uint64_t m_frequency
Definition: dtvmultiplex.h:94
void Process(const ScanDTVTransportList &, int sourceid=-1)