1 /*
2 * dvbchannel.cpp
3 *
4 * Copyright (C) 2007-2011 Christoph Pfister <christophpfister@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "../log.h"
22
23 #include <QDataStream>
24 #include <QFile>
25 #include <QStandardPaths>
26 #include <QVariant>
27
28 #include "../ensurenopendingoperation.h"
29 #include "dvbchannel.h"
30 #include "dvbsi.h"
31
validate()32 bool DvbChannel::validate()
33 {
34 if (!name.isEmpty() && (number >= 1) && !source.isEmpty() && transponder.isValid() &&
35 (networkId >= -1) && (networkId <= 0xffff) && (transportStreamId >= 0) &&
36 (transportStreamId <= 0xffff) && (pmtPid >= 0) && (pmtPid <= 0x1fff) &&
37 (audioPid >= -1) && (audioPid <= 0x1fff)) {
38 if ((serviceId >= 0) && (serviceId <= 0xffff)) {
39 if (pmtSectionData.size() < 5) {
40 pmtSectionData = QByteArray(5, 0);
41 }
42
43 pmtSectionData[3] = ((serviceId >> 8) & 0xff);
44 pmtSectionData[4] = (serviceId & 0xff);
45 return true;
46 } else if (pmtSectionData.size() >= 5) {
47 serviceId = ((static_cast<unsigned char>(pmtSectionData.at(3)) << 8) |
48 static_cast<unsigned char>(pmtSectionData.at(4)));
49 return true;
50 }
51 }
52
53 return false;
54 }
55
operator ==(const DvbChannelId & other) const56 bool DvbChannelId::operator==(const DvbChannelId &other) const
57 {
58 if ((channel->source != other.channel->source) ||
59 (channel->transponder.getTransmissionType() !=
60 other.channel->transponder.getTransmissionType()) ||
61 (channel->networkId != other.channel->networkId)) {
62 return false;
63 }
64
65 switch (channel->transponder.getTransmissionType()) {
66 case DvbTransponderBase::Invalid:
67 break;
68 case DvbTransponderBase::DvbC:
69 case DvbTransponderBase::DvbS:
70 case DvbTransponderBase::DvbS2:
71 case DvbTransponderBase::DvbT:
72 case DvbTransponderBase::DvbT2:
73 case DvbTransponderBase::IsdbT:
74 return ((channel->transportStreamId == other.channel->transportStreamId) &&
75 (channel->serviceId == other.channel->serviceId));
76 case DvbTransponderBase::Atsc:
77 // source id has to be unique only within a transport stream
78 // --> we need to check transponder as well
79 return channel->transponder.corresponds(other.channel->transponder);
80 }
81
82 return false;
83 }
84
qHash(const DvbChannelId & channel)85 uint qHash(const DvbChannelId &channel)
86 {
87 uint hash = (qHash(channel.channel->source) ^ qHash(channel.channel->networkId));
88
89 switch (channel.channel->transponder.getTransmissionType()) {
90 case DvbTransponderBase::Invalid:
91 break;
92 case DvbTransponderBase::DvbC:
93 case DvbTransponderBase::DvbS:
94 case DvbTransponderBase::DvbS2:
95 case DvbTransponderBase::DvbT:
96 case DvbTransponderBase::DvbT2:
97 case DvbTransponderBase::IsdbT:
98 hash ^= (qHash(channel.channel->transportStreamId) << 8);
99 hash ^= (qHash(channel.channel->serviceId) << 16);
100 break;
101 case DvbTransponderBase::Atsc:
102 break;
103 }
104
105 return hash;
106 }
107
DvbChannelModel(QObject * parent)108 DvbChannelModel::DvbChannelModel(QObject *parent) : QObject(parent), hasPendingOperation(false),
109 isSqlModel(false)
110 {
111 }
112
~DvbChannelModel()113 DvbChannelModel::~DvbChannelModel()
114 {
115 if (hasPendingOperation) {
116 qCWarning(logDvb, "Illegal recursive call");
117 }
118
119 if (isSqlModel) {
120 sqlFlush();
121 }
122 }
123
channelFlush()124 void DvbChannelModel::channelFlush()
125 {
126 if (isSqlModel)
127 sqlFlush();
128 }
129
createSqlModel(QObject * parent)130 DvbChannelModel *DvbChannelModel::createSqlModel(QObject *parent)
131 {
132 DvbChannelModel *channelModel = new DvbChannelModel(parent);
133 channelModel->isSqlModel = true;
134 channelModel->sqlInit(QLatin1String("Channels"),
135 QStringList() << QLatin1String("Name") << QLatin1String("Number") << QLatin1String("Source") <<
136 QLatin1String("Transponder") << QLatin1String("NetworkId") << QLatin1String("TransportStreamId") <<
137 QLatin1String("PmtPid") << QLatin1String("PmtSection") << QLatin1String("AudioPid") <<
138 QLatin1String("Flags"));
139
140 // compatibility code
141
142 QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/channels.dtv"));
143
144 if (!file.exists()) {
145 return channelModel;
146 }
147
148 if (!file.open(QIODevice::ReadOnly)) {
149 qCWarning(logDvb, "Cannot open %s", qPrintable(file.fileName()));
150 return channelModel;
151 }
152
153 QDataStream stream(&file);
154 stream.setVersion(QDataStream::Qt_4_4);
155
156 while (!stream.atEnd()) {
157 DvbChannel channel;
158 int type;
159 stream >> type;
160 stream >> channel.name;
161 stream >> channel.number;
162 stream >> channel.source;
163
164 switch (type) {
165 case DvbTransponderBase::DvbC:
166 channel.transponder = DvbTransponder(DvbTransponderBase::DvbC);
167 channel.transponder.as<DvbCTransponder>()->readTransponder(stream);
168 break;
169 case DvbTransponderBase::DvbS:
170 channel.transponder = DvbTransponder(DvbTransponderBase::DvbS);
171 channel.transponder.as<DvbSTransponder>()->readTransponder(stream);
172 break;
173 case DvbTransponderBase::DvbS2:
174 channel.transponder = DvbTransponder(DvbTransponderBase::DvbS2);
175 channel.transponder.as<DvbS2Transponder>()->readTransponder(stream);
176 break;
177 case DvbTransponderBase::DvbT:
178 channel.transponder = DvbTransponder(DvbTransponderBase::DvbT);
179 channel.transponder.as<DvbTTransponder>()->readTransponder(stream);
180 break;
181 case DvbTransponderBase::DvbT2:
182 channel.transponder = DvbTransponder(DvbTransponderBase::DvbT2);
183 channel.transponder.as<DvbT2Transponder>()->readTransponder(stream);
184 break;
185 case DvbTransponderBase::Atsc:
186 channel.transponder = DvbTransponder(DvbTransponderBase::Atsc);
187 channel.transponder.as<AtscTransponder>()->readTransponder(stream);
188 break;
189 case DvbTransponderBase::IsdbT:
190 channel.transponder = DvbTransponder(DvbTransponderBase::IsdbT);
191 channel.transponder.as<IsdbTTransponder>()->readTransponder(stream);
192 break;
193 default:
194 stream.setStatus(QDataStream::ReadCorruptData);
195 break;
196 }
197
198 stream >> channel.networkId;
199 stream >> channel.transportStreamId;
200 int serviceId;
201 stream >> serviceId;
202 stream >> channel.pmtPid;
203
204 stream >> channel.pmtSectionData;
205 int videoPid;
206 stream >> videoPid;
207 stream >> channel.audioPid;
208
209 int flags;
210 stream >> flags;
211 channel.hasVideo = (videoPid >= 0);
212 channel.isScrambled = (flags & 0x1) != 0;
213
214 if (stream.status() != QDataStream::Ok) {
215 qCWarning(logDvb, "Invalid channels in file %s", qPrintable(file.fileName()));
216 break;
217 }
218
219 channelModel->addChannel(channel);
220 }
221
222 // As we'll remove the old channel file, flush the DB content
223 channelModel->channelFlush();
224
225 if (!file.remove()) {
226 qCWarning(logDvb, "Cannot remove '%s' from DB", qPrintable(file.fileName()));
227 }
228
229 return channelModel;
230 }
231
getChannels() const232 QMap<int, DvbSharedChannel> DvbChannelModel::getChannels() const
233 {
234 return channelNumbers;
235 }
236
findChannelByName(const QString & channelName) const237 DvbSharedChannel DvbChannelModel::findChannelByName(const QString &channelName) const
238 {
239 return channelNames.value(channelName);
240 }
241
hasChannelByName(const QString & channelName)242 bool DvbChannelModel::hasChannelByName(const QString &channelName)
243 {
244 if (channelNames.contains(channelName))
245 return true;
246 return false;
247 }
248
findChannelByNumber(int channelNumber) const249 DvbSharedChannel DvbChannelModel::findChannelByNumber(int channelNumber) const
250 {
251 return channelNumbers.value(channelNumber);
252 }
253
findChannelById(const DvbChannel & channel) const254 DvbSharedChannel DvbChannelModel::findChannelById(const DvbChannel &channel) const
255 {
256 return channelIds.value(DvbChannelId(&channel));
257 }
258
cloneFrom(DvbChannelModel * other)259 void DvbChannelModel::cloneFrom(DvbChannelModel *other)
260 {
261 if (!isSqlModel && other->isSqlModel && channelNumbers.isEmpty()) {
262 if (hasPendingOperation) {
263 qCWarning(logDvb, "Illegal recursive call");
264 return;
265 }
266
267 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
268 channelNames = other->channelNames;
269 channelNumbers = other->channelNumbers;
270 channelIds = other->channelIds;
271
272 foreach (const DvbSharedChannel &channel, channelNumbers) {
273 emit channelAdded(channel);
274 }
275 } else if (isSqlModel && !other->isSqlModel) {
276 QMultiMap<SqlKey, DvbSharedChannel> otherChannelKeys;
277
278 foreach (const DvbSharedChannel &channel, other->getChannels()) {
279 otherChannelKeys.insert(*channel, channel);
280 }
281
282 for (QMap<SqlKey, DvbSharedChannel>::ConstIterator it = channels.constBegin();
283 it != channels.constEnd();) {
284 const DvbSharedChannel &channel = *it;
285 ++it;
286 DvbSharedChannel otherChannel = otherChannelKeys.take(*channel);
287
288 if (otherChannel.isValid()) {
289 if (otherChannel != channel) {
290 DvbChannel modifiedChannel(*otherChannel);
291 updateChannel(channel, modifiedChannel);
292 }
293 } else {
294 removeChannel(channel);
295 }
296 }
297
298 foreach (const DvbSharedChannel &channel, otherChannelKeys) {
299 DvbChannel newChannel(*channel);
300 addChannel(newChannel);
301 }
302 } else {
303 qCWarning(logDvb, "Illegal type of clone");
304 }
305 }
306
addChannel(DvbChannel & channel)307 void DvbChannelModel::addChannel(DvbChannel &channel)
308 {
309 bool forceAdd;
310
311 if (channel.number < 1) {
312 channel.number = 1;
313 forceAdd = false;
314 } else {
315 forceAdd = true;
316 }
317
318 if (!channel.validate()) {
319 qCWarning(logDvb, "Invalid channel");
320 return;
321 }
322
323 if (forceAdd) {
324 DvbSharedChannel existingChannel = channelNames.value(channel.name);
325
326 if (existingChannel.isValid()) {
327 DvbChannel updatedChannel = *existingChannel;
328 updatedChannel.name = findNextFreeChannelName(updatedChannel.name);
329 updateChannel(existingChannel, updatedChannel);
330 }
331
332 existingChannel = channelNumbers.value(channel.number);
333
334 if (existingChannel.isValid()) {
335 DvbChannel updatedChannel = *existingChannel;
336 updatedChannel.number = findNextFreeChannelNumber(updatedChannel.number);
337 updateChannel(existingChannel, updatedChannel);
338 }
339 } else {
340 DvbSharedChannel existingChannel = channelIds.value(DvbChannelId(&channel));
341
342 if (existingChannel.isValid()) {
343 if (channel.name == extractBaseName(existingChannel->name)) {
344 channel.name = existingChannel->name;
345 } else {
346 channel.name = findNextFreeChannelName(channel.name);
347 }
348
349 channel.number = existingChannel->number;
350 DvbPmtSection pmtSection(channel.pmtSectionData);
351 DvbPmtParser pmtParser(pmtSection);
352
353 for (int i = 0; i < pmtParser.audioPids.size(); ++i) {
354 if (pmtParser.audioPids.at(i).first == existingChannel->audioPid) {
355 channel.audioPid = existingChannel->audioPid;
356 break;
357 }
358 }
359
360 updateChannel(existingChannel, channel);
361 return;
362 }
363
364 channel.name = findNextFreeChannelName(channel.name);
365 channel.number = findNextFreeChannelNumber(channel.number);
366 }
367
368 if (hasPendingOperation) {
369 qCWarning(logDvb, "Illegal recursive call");
370 return;
371 }
372
373 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
374
375 if (isSqlModel) {
376 channel.setSqlKey(sqlFindFreeKey(channels));
377 } else {
378 channel.setSqlKey(SqlKey());
379 }
380
381 DvbSharedChannel newChannel(new DvbChannel(channel));
382 channelNames.insert(newChannel->name, newChannel);
383 channelNumbers.insert(newChannel->number, newChannel);
384 channelIds.insert(DvbChannelId(newChannel), newChannel);
385
386 if (isSqlModel) {
387 channels.insert(*newChannel, newChannel);
388 sqlInsert(*newChannel);
389 }
390
391 emit channelAdded(newChannel);
392 }
393
updateChannel(DvbSharedChannel channel,DvbChannel & modifiedChannel)394 void DvbChannelModel::updateChannel(DvbSharedChannel channel, DvbChannel &modifiedChannel)
395 {
396 if (!channel.isValid() || (channelNumbers.value(channel->number) != channel) ||
397 !modifiedChannel.validate()) {
398 qCWarning(logDvb, "Invalid channel");
399 return;
400 }
401
402 if (channel->name != modifiedChannel.name) {
403 DvbSharedChannel existingChannel = channelNames.value(modifiedChannel.name);
404
405 if (existingChannel.isValid()) {
406 DvbChannel updatedChannel = *existingChannel;
407 updatedChannel.name = findNextFreeChannelName(updatedChannel.name);
408 updateChannel(existingChannel, updatedChannel);
409 }
410 }
411
412 if (channel->number != modifiedChannel.number) {
413 DvbSharedChannel existingChannel = channelNumbers.value(modifiedChannel.number);
414
415 if (existingChannel.isValid()) {
416 DvbChannel updatedChannel = *existingChannel;
417 updatedChannel.number = findNextFreeChannelNumber(updatedChannel.number);
418 updateChannel(existingChannel, updatedChannel);
419 }
420 }
421
422 if (hasPendingOperation) {
423 qCWarning(logDvb, "Illegal recursive call");
424 return;
425 }
426
427 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
428 modifiedChannel.setSqlKey(*channel);
429 bool channelNameChanged = (channel->name != modifiedChannel.name);
430 bool channelNumberChanged = (channel->number != modifiedChannel.number);
431 bool channelIdChanged = (DvbChannelId(channel) != DvbChannelId(&modifiedChannel));
432 emit channelAboutToBeUpdated(channel);
433
434 if (channelNameChanged) {
435 channelNames.remove(channel->name);
436 }
437
438 if (channelNumberChanged) {
439 channelNumbers.remove(channel->number);
440 }
441
442 if (channelIdChanged) {
443 channelIds.remove(DvbChannelId(channel), channel);
444 }
445
446 if (!isSqlModel && channel->isSqlKeyValid()) {
447 DvbSharedChannel detachedChannel(new DvbChannel(modifiedChannel));
448 channelNames.insert(detachedChannel->name, detachedChannel);
449 channelNumbers.insert(detachedChannel->number, detachedChannel);
450 channelIds.insert(DvbChannelId(detachedChannel), detachedChannel);
451 emit channelUpdated(detachedChannel);
452 } else {
453 *const_cast<DvbChannel *>(channel.constData()) = modifiedChannel;
454
455 if (channelNameChanged) {
456 channelNames.insert(channel->name, channel);
457 }
458
459 if (channelNumberChanged) {
460 channelNumbers.insert(channel->number, channel);
461 }
462
463 if (channelIdChanged) {
464 channelIds.insert(DvbChannelId(channel), channel);
465 }
466
467 if (isSqlModel) {
468 sqlUpdate(*channel);
469 }
470
471 emit channelUpdated(channel);
472 }
473 }
474
removeChannel(DvbSharedChannel channel)475 void DvbChannelModel::removeChannel(DvbSharedChannel channel)
476 {
477 if (!channel.isValid() || (channelNumbers.value(channel->number) != channel)) {
478 qCWarning(logDvb, "Invalid channel");
479 return;
480 }
481
482 if (hasPendingOperation) {
483 qCWarning(logDvb, "Illegal recursive call");
484 return;
485 }
486
487 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
488 channelNames.remove(channel->name);
489 channelNumbers.remove(channel->number);
490 channelIds.remove(DvbChannelId(channel), channel);
491
492 if (isSqlModel) {
493 channels.remove(*channel);
494 sqlRemove(*channel);
495 }
496
497 emit channelRemoved(channel);
498 }
499
dndMoveChannels(const QList<DvbSharedChannel> & selectedChannels,int insertBeforeNumber)500 void DvbChannelModel::dndMoveChannels(const QList<DvbSharedChannel> &selectedChannels,
501 int insertBeforeNumber)
502 {
503 if (hasPendingOperation) {
504 qCWarning(logDvb, "Illegal recursive call");
505 return;
506 }
507
508 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
509 typedef QMap<int, DvbSharedChannel>::ConstIterator ConstIterator;
510 QList<DvbSharedChannel> channelQueue;
511
512 foreach (const DvbSharedChannel &channel, selectedChannels) {
513 if (channel.isValid()) {
514 ConstIterator it = channelNumbers.constFind(channel->number);
515
516 if ((it != channelNumbers.constEnd()) && (*it == channel)) {
517 channelNumbers.remove(channel->number);
518 channelQueue.append(channel);
519 }
520 }
521 }
522
523 ConstIterator it = channelNumbers.constFind(insertBeforeNumber);
524 int currentNumber = 1;
525
526 if (it != channelNumbers.constBegin()) {
527 currentNumber = ((it - 1).key() + 1);
528 }
529
530 while (!channelQueue.isEmpty()) {
531 DvbSharedChannel channel = channelQueue.takeFirst();
532
533 if (channel->number != currentNumber) {
534 emit channelAboutToBeUpdated(channel);
535 DvbSharedChannel existingChannel = channelNumbers.take(currentNumber);
536
537 if (existingChannel.isValid()) {
538 channelQueue.append(existingChannel);
539 }
540
541 if (!isSqlModel && channel->isSqlKeyValid()) {
542 DvbChannel *newChannel = new DvbChannel(*channel);
543 newChannel->number = currentNumber;
544 DvbSharedChannel detachedChannel(newChannel);
545 channelNames.insert(detachedChannel->name, detachedChannel);
546 channelNumbers.insert(detachedChannel->number, detachedChannel);
547 channelIds.insert(DvbChannelId(detachedChannel), detachedChannel);
548 emit channelUpdated(detachedChannel);
549 } else {
550 const_cast<DvbChannel *>(channel.constData())->number =
551 currentNumber;
552 channelNumbers.insert(channel->number, channel);
553
554 if (isSqlModel) {
555 sqlUpdate(*channel);
556 }
557
558 emit channelUpdated(channel);
559 }
560 } else {
561 channelNumbers.insert(channel->number, channel);
562 }
563
564 ++currentNumber;
565 }
566 }
567
bindToSqlQuery(SqlKey sqlKey,QSqlQuery & query,int index) const568 void DvbChannelModel::bindToSqlQuery(SqlKey sqlKey, QSqlQuery &query, int index) const
569 {
570 DvbSharedChannel channel = channels.value(sqlKey);
571
572 if (!channel.isValid()) {
573 qCWarning(logDvb, "Invalid channel");
574 return;
575 }
576
577 query.bindValue(index++, channel->name);
578 query.bindValue(index++, channel->number);
579 query.bindValue(index++, channel->source);
580 query.bindValue(index++, channel->transponder.toString());
581 query.bindValue(index++, channel->networkId);
582 query.bindValue(index++, channel->transportStreamId);
583 query.bindValue(index++, channel->pmtPid);
584 query.bindValue(index++, channel->pmtSectionData);
585 query.bindValue(index++, channel->audioPid);
586 query.bindValue(index++, (channel->hasVideo ? 0x01 : 0) |
587 (channel->isScrambled ? 0x02 : 0));
588 }
589
insertFromSqlQuery(SqlKey sqlKey,const QSqlQuery & query,int index)590 bool DvbChannelModel::insertFromSqlQuery(SqlKey sqlKey, const QSqlQuery &query, int index)
591 {
592 DvbChannel *channel = new DvbChannel();
593 DvbSharedChannel sharedChannel(channel);
594 channel->setSqlKey(sqlKey);
595 channel->name = query.value(index++).toString();
596 channel->number = query.value(index++).toInt();
597 channel->source = query.value(index++).toString();
598 channel->transponder = DvbTransponder::fromString(query.value(index++).toString());
599 channel->networkId = query.value(index++).toInt();
600 channel->transportStreamId = query.value(index++).toInt();
601 channel->pmtPid = query.value(index++).toInt();
602 channel->pmtSectionData = query.value(index++).toByteArray();
603 channel->audioPid = query.value(index++).toInt();
604 int flags = query.value(index++).toInt();
605 channel->hasVideo = ((flags & 0x01) != 0);
606 channel->isScrambled = ((flags & 0x02) != 0);
607
608 if (channel->validate() && !channelNames.contains(channel->name) &&
609 !channelNumbers.contains(channel->number)) {
610 channelNames.insert(sharedChannel->name, sharedChannel);
611 channelNumbers.insert(sharedChannel->number, sharedChannel);
612 channelIds.insert(DvbChannelId(sharedChannel), sharedChannel);
613 channels.insert(*sharedChannel, sharedChannel);
614 return true;
615 }
616
617 return false;
618 }
619
areInTheSameBunch(DvbSharedChannel channel1,DvbSharedChannel channel2)620 bool DvbChannelModel::areInTheSameBunch(DvbSharedChannel channel1, DvbSharedChannel channel2)
621 {
622 if (channel1->transportStreamId == channel2->transportStreamId) {
623 return true;
624 }
625
626 return false;
627 }
628
extractBaseName(const QString & name) const629 QString DvbChannelModel::extractBaseName(const QString &name) const
630 {
631 QString baseName = name;
632 int position = baseName.lastIndexOf(QLatin1Char('-'));
633
634 if (position > 0) {
635 QString suffix = baseName.mid(position + 1);
636
637 if (suffix == QString::number(suffix.toInt())) {
638 baseName.truncate(position);
639 }
640 }
641
642 return baseName;
643 }
644
findNextFreeChannelName(const QString & name) const645 QString DvbChannelModel::findNextFreeChannelName(const QString &name) const
646 {
647 if (!channelNames.contains(name)) {
648 return name;
649 }
650
651 QString baseName = extractBaseName(name);
652 int suffix = 0;
653 QString newName = baseName;
654
655 while (channelNames.contains(newName)) {
656 ++suffix;
657 newName = baseName + QLatin1Char('-') + QString::number(suffix);
658 }
659
660 return newName;
661 }
662
findNextFreeChannelNumber(int number) const663 int DvbChannelModel::findNextFreeChannelNumber(int number) const
664 {
665 while (channelNumbers.contains(number)) {
666 ++number;
667 }
668
669 return number;
670 }
671