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