File indexing completed on 2024-05-12 04:52:09

0001 /*
0002  * dvbchannel.cpp
0003  *
0004  * Copyright (C) 2007-2011 Christoph Pfister <christophpfister@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License along
0017  * with this program; if not, write to the Free Software Foundation, Inc.,
0018  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0019  */
0020 
0021 #include "../log.h"
0022 
0023 #include <QDataStream>
0024 #include <QFile>
0025 #include <QStandardPaths>
0026 #include <QVariant>
0027 
0028 #include "../ensurenopendingoperation.h"
0029 #include "dvbchannel.h"
0030 #include "dvbsi.h"
0031 
0032 bool DvbChannel::validate()
0033 {
0034     if (!name.isEmpty() && (number >= 1) && !source.isEmpty() && transponder.isValid() &&
0035         (networkId >= -1) && (networkId <= 0xffff) && (transportStreamId >= 0) &&
0036         (transportStreamId <= 0xffff) && (pmtPid >= 0) && (pmtPid <= 0x1fff) &&
0037         (audioPid >= -1) && (audioPid <= 0x1fff)) {
0038         if ((serviceId >= 0) && (serviceId <= 0xffff)) {
0039             if (pmtSectionData.size() < 5) {
0040                 pmtSectionData = QByteArray(5, 0);
0041             }
0042 
0043             pmtSectionData[3] = ((serviceId >> 8) & 0xff);
0044             pmtSectionData[4] = (serviceId & 0xff);
0045             return true;
0046         } else if (pmtSectionData.size() >= 5) {
0047             serviceId = ((static_cast<unsigned char>(pmtSectionData.at(3)) << 8) |
0048                 static_cast<unsigned char>(pmtSectionData.at(4)));
0049             return true;
0050         }
0051     }
0052 
0053     return false;
0054 }
0055 
0056 bool DvbChannelId::operator==(const DvbChannelId &other) const
0057 {
0058     if ((channel->source != other.channel->source) ||
0059         (channel->transponder.getTransmissionType() !=
0060          other.channel->transponder.getTransmissionType()) ||
0061         (channel->networkId != other.channel->networkId)) {
0062         return false;
0063     }
0064 
0065     switch (channel->transponder.getTransmissionType()) {
0066     case DvbTransponderBase::Invalid:
0067         break;
0068     case DvbTransponderBase::DvbC:
0069     case DvbTransponderBase::DvbS:
0070     case DvbTransponderBase::DvbS2:
0071     case DvbTransponderBase::DvbT:
0072     case DvbTransponderBase::DvbT2:
0073     case DvbTransponderBase::IsdbT:
0074         return ((channel->transportStreamId == other.channel->transportStreamId) &&
0075             (channel->serviceId == other.channel->serviceId));
0076     case DvbTransponderBase::Atsc:
0077         // source id has to be unique only within a transport stream
0078         // --> we need to check transponder as well
0079         return channel->transponder.corresponds(other.channel->transponder);
0080     }
0081 
0082     return false;
0083 }
0084 
0085 uint qHash(const DvbChannelId &channel)
0086 {
0087     uint hash = (qHash(channel.channel->source) ^ qHash(channel.channel->networkId));
0088 
0089     switch (channel.channel->transponder.getTransmissionType()) {
0090     case DvbTransponderBase::Invalid:
0091         break;
0092     case DvbTransponderBase::DvbC:
0093     case DvbTransponderBase::DvbS:
0094     case DvbTransponderBase::DvbS2:
0095     case DvbTransponderBase::DvbT:
0096     case DvbTransponderBase::DvbT2:
0097     case DvbTransponderBase::IsdbT:
0098         hash ^= (qHash(channel.channel->transportStreamId) << 8);
0099         hash ^= (qHash(channel.channel->serviceId) << 16);
0100         break;
0101     case DvbTransponderBase::Atsc:
0102         break;
0103     }
0104 
0105     return hash;
0106 }
0107 
0108 DvbChannelModel::DvbChannelModel(QObject *parent) : QObject(parent), hasPendingOperation(false),
0109     isSqlModel(false)
0110 {
0111 }
0112 
0113 DvbChannelModel::~DvbChannelModel()
0114 {
0115     if (hasPendingOperation) {
0116         qCWarning(logDvb, "Illegal recursive call");
0117     }
0118 
0119     if (isSqlModel) {
0120         sqlFlush();
0121     }
0122 }
0123 
0124 void DvbChannelModel::channelFlush()
0125 {
0126     if (isSqlModel)
0127         sqlFlush();
0128 }
0129 
0130 DvbChannelModel *DvbChannelModel::createSqlModel(QObject *parent)
0131 {
0132     DvbChannelModel *channelModel = new DvbChannelModel(parent);
0133     channelModel->isSqlModel = true;
0134     channelModel->sqlInit(QLatin1String("Channels"),
0135         QStringList() << QLatin1String("Name") << QLatin1String("Number") << QLatin1String("Source") <<
0136         QLatin1String("Transponder") << QLatin1String("NetworkId") << QLatin1String("TransportStreamId") <<
0137         QLatin1String("PmtPid") << QLatin1String("PmtSection") << QLatin1String("AudioPid") <<
0138         QLatin1String("Flags"));
0139 
0140     // compatibility code
0141 
0142     QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/channels.dtv"));
0143 
0144     if (!file.exists()) {
0145         return channelModel;
0146     }
0147 
0148     if (!file.open(QIODevice::ReadOnly)) {
0149         qCWarning(logDvb, "Cannot open %s", qPrintable(file.fileName()));
0150         return channelModel;
0151     }
0152 
0153     QDataStream stream(&file);
0154     stream.setVersion(QDataStream::Qt_4_4);
0155 
0156     while (!stream.atEnd()) {
0157         DvbChannel channel;
0158         int type;
0159         stream >> type;
0160         stream >> channel.name;
0161         stream >> channel.number;
0162         stream >> channel.source;
0163 
0164         switch (type) {
0165         case DvbTransponderBase::DvbC:
0166             channel.transponder = DvbTransponder(DvbTransponderBase::DvbC);
0167             channel.transponder.as<DvbCTransponder>()->readTransponder(stream);
0168             break;
0169         case DvbTransponderBase::DvbS:
0170             channel.transponder = DvbTransponder(DvbTransponderBase::DvbS);
0171             channel.transponder.as<DvbSTransponder>()->readTransponder(stream);
0172             break;
0173         case DvbTransponderBase::DvbS2:
0174             channel.transponder = DvbTransponder(DvbTransponderBase::DvbS2);
0175             channel.transponder.as<DvbS2Transponder>()->readTransponder(stream);
0176             break;
0177         case DvbTransponderBase::DvbT:
0178             channel.transponder = DvbTransponder(DvbTransponderBase::DvbT);
0179             channel.transponder.as<DvbTTransponder>()->readTransponder(stream);
0180             break;
0181         case DvbTransponderBase::DvbT2:
0182             channel.transponder = DvbTransponder(DvbTransponderBase::DvbT2);
0183             channel.transponder.as<DvbT2Transponder>()->readTransponder(stream);
0184             break;
0185         case DvbTransponderBase::Atsc:
0186             channel.transponder = DvbTransponder(DvbTransponderBase::Atsc);
0187             channel.transponder.as<AtscTransponder>()->readTransponder(stream);
0188             break;
0189         case DvbTransponderBase::IsdbT:
0190             channel.transponder = DvbTransponder(DvbTransponderBase::IsdbT);
0191             channel.transponder.as<IsdbTTransponder>()->readTransponder(stream);
0192             break;
0193         default:
0194             stream.setStatus(QDataStream::ReadCorruptData);
0195             break;
0196         }
0197 
0198         stream >> channel.networkId;
0199         stream >> channel.transportStreamId;
0200         int serviceId;
0201         stream >> serviceId;
0202         stream >> channel.pmtPid;
0203 
0204         stream >> channel.pmtSectionData;
0205         int videoPid;
0206         stream >> videoPid;
0207         stream >> channel.audioPid;
0208 
0209         int flags;
0210         stream >> flags;
0211         channel.hasVideo = (videoPid >= 0);
0212         channel.isScrambled = (flags & 0x1) != 0;
0213 
0214         if (stream.status() != QDataStream::Ok) {
0215             qCWarning(logDvb, "Invalid channels in file %s", qPrintable(file.fileName()));
0216             break;
0217         }
0218 
0219         channelModel->addChannel(channel);
0220     }
0221 
0222     // As we'll remove the old channel file, flush the DB content
0223     channelModel->channelFlush();
0224 
0225     if (!file.remove()) {
0226         qCWarning(logDvb, "Cannot remove '%s' from DB", qPrintable(file.fileName()));
0227     }
0228 
0229     return channelModel;
0230 }
0231 
0232 QMap<int, DvbSharedChannel> DvbChannelModel::getChannels() const
0233 {
0234     return channelNumbers;
0235 }
0236 
0237 DvbSharedChannel DvbChannelModel::findChannelByName(const QString &channelName) const
0238 {
0239     return channelNames.value(channelName);
0240 }
0241 
0242 bool DvbChannelModel::hasChannelByName(const QString &channelName)
0243 {
0244     if (channelNames.contains(channelName))
0245         return true;
0246     return false;
0247 }
0248 
0249 DvbSharedChannel DvbChannelModel::findChannelByNumber(int channelNumber) const
0250 {
0251     return channelNumbers.value(channelNumber);
0252 }
0253 
0254 DvbSharedChannel DvbChannelModel::findChannelById(const DvbChannel &channel) const
0255 {
0256     return channelIds.value(DvbChannelId(&channel));
0257 }
0258 
0259 void DvbChannelModel::cloneFrom(DvbChannelModel *other)
0260 {
0261     if (!isSqlModel && other->isSqlModel && channelNumbers.isEmpty()) {
0262         if (hasPendingOperation) {
0263             qCWarning(logDvb, "Illegal recursive call");
0264             return;
0265         }
0266 
0267         EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
0268         channelNames = other->channelNames;
0269         channelNumbers = other->channelNumbers;
0270         channelIds = other->channelIds;
0271 
0272         foreach (const DvbSharedChannel &channel, channelNumbers) {
0273             emit channelAdded(channel);
0274         }
0275     } else if (isSqlModel && !other->isSqlModel) {
0276         QMultiMap<SqlKey, DvbSharedChannel> otherChannelKeys;
0277 
0278         foreach (const DvbSharedChannel &channel, other->getChannels()) {
0279             otherChannelKeys.insert(*channel, channel);
0280         }
0281 
0282         for (QMap<SqlKey, DvbSharedChannel>::ConstIterator it = channels.constBegin();
0283              it != channels.constEnd();) {
0284             const DvbSharedChannel &channel = *it;
0285             ++it;
0286             DvbSharedChannel otherChannel = otherChannelKeys.take(*channel);
0287 
0288             if (otherChannel.isValid()) {
0289                 if (otherChannel != channel) {
0290                     DvbChannel modifiedChannel(*otherChannel);
0291                     updateChannel(channel, modifiedChannel);
0292                 }
0293             } else {
0294                 removeChannel(channel);
0295             }
0296         }
0297 
0298         foreach (const DvbSharedChannel &channel, otherChannelKeys) {
0299             DvbChannel newChannel(*channel);
0300             addChannel(newChannel);
0301         }
0302     } else {
0303         qCWarning(logDvb, "Illegal type of clone");
0304     }
0305 }
0306 
0307 void DvbChannelModel::addChannel(DvbChannel &channel)
0308 {
0309     bool forceAdd;
0310 
0311     if (channel.number < 1) {
0312         channel.number = 1;
0313         forceAdd = false;
0314     } else {
0315         forceAdd = true;
0316     }
0317 
0318     if (!channel.validate()) {
0319         qCWarning(logDvb, "Invalid channel");
0320         return;
0321     }
0322 
0323     if (forceAdd) {
0324         DvbSharedChannel existingChannel = channelNames.value(channel.name);
0325 
0326         if (existingChannel.isValid()) {
0327             DvbChannel updatedChannel = *existingChannel;
0328             updatedChannel.name = findNextFreeChannelName(updatedChannel.name);
0329             updateChannel(existingChannel, updatedChannel);
0330         }
0331 
0332         existingChannel = channelNumbers.value(channel.number);
0333 
0334         if (existingChannel.isValid()) {
0335             DvbChannel updatedChannel = *existingChannel;
0336             updatedChannel.number = findNextFreeChannelNumber(updatedChannel.number);
0337             updateChannel(existingChannel, updatedChannel);
0338         }
0339     } else {
0340         DvbSharedChannel existingChannel = channelIds.value(DvbChannelId(&channel));
0341 
0342         if (existingChannel.isValid()) {
0343             if (channel.name == extractBaseName(existingChannel->name)) {
0344                 channel.name = existingChannel->name;
0345             } else {
0346                 channel.name = findNextFreeChannelName(channel.name);
0347             }
0348 
0349             channel.number = existingChannel->number;
0350             DvbPmtSection pmtSection(channel.pmtSectionData);
0351             DvbPmtParser pmtParser(pmtSection);
0352 
0353             for (int i = 0; i < pmtParser.audioPids.size(); ++i) {
0354                 if (pmtParser.audioPids.at(i).first == existingChannel->audioPid) {
0355                     channel.audioPid = existingChannel->audioPid;
0356                     break;
0357                 }
0358             }
0359 
0360             updateChannel(existingChannel, channel);
0361             return;
0362         }
0363 
0364         channel.name = findNextFreeChannelName(channel.name);
0365         channel.number = findNextFreeChannelNumber(channel.number);
0366     }
0367 
0368     if (hasPendingOperation) {
0369         qCWarning(logDvb, "Illegal recursive call");
0370         return;
0371     }
0372 
0373     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
0374 
0375     if (isSqlModel) {
0376         channel.setSqlKey(sqlFindFreeKey(channels));
0377     } else {
0378         channel.setSqlKey(SqlKey());
0379     }
0380 
0381     DvbSharedChannel newChannel(new DvbChannel(channel));
0382     channelNames.insert(newChannel->name, newChannel);
0383     channelNumbers.insert(newChannel->number, newChannel);
0384     channelIds.insert(DvbChannelId(newChannel), newChannel);
0385 
0386     if (isSqlModel) {
0387         channels.insert(*newChannel, newChannel);
0388         sqlInsert(*newChannel);
0389     }
0390 
0391     emit channelAdded(newChannel);
0392 }
0393 
0394 void DvbChannelModel::updateChannel(DvbSharedChannel channel, DvbChannel &modifiedChannel)
0395 {
0396     if (!channel.isValid() || (channelNumbers.value(channel->number) != channel) ||
0397         !modifiedChannel.validate()) {
0398         qCWarning(logDvb, "Invalid channel");
0399         return;
0400     }
0401 
0402     if (channel->name != modifiedChannel.name) {
0403         DvbSharedChannel existingChannel = channelNames.value(modifiedChannel.name);
0404 
0405         if (existingChannel.isValid()) {
0406             DvbChannel updatedChannel = *existingChannel;
0407             updatedChannel.name = findNextFreeChannelName(updatedChannel.name);
0408             updateChannel(existingChannel, updatedChannel);
0409         }
0410     }
0411 
0412     if (channel->number != modifiedChannel.number) {
0413         DvbSharedChannel existingChannel = channelNumbers.value(modifiedChannel.number);
0414 
0415         if (existingChannel.isValid()) {
0416             DvbChannel updatedChannel = *existingChannel;
0417             updatedChannel.number = findNextFreeChannelNumber(updatedChannel.number);
0418             updateChannel(existingChannel, updatedChannel);
0419         }
0420     }
0421 
0422     if (hasPendingOperation) {
0423         qCWarning(logDvb, "Illegal recursive call");
0424         return;
0425     }
0426 
0427     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
0428     modifiedChannel.setSqlKey(*channel);
0429     bool channelNameChanged = (channel->name != modifiedChannel.name);
0430     bool channelNumberChanged = (channel->number != modifiedChannel.number);
0431     bool channelIdChanged = (DvbChannelId(channel) != DvbChannelId(&modifiedChannel));
0432     emit channelAboutToBeUpdated(channel);
0433 
0434     if (channelNameChanged) {
0435         channelNames.remove(channel->name);
0436     }
0437 
0438     if (channelNumberChanged) {
0439         channelNumbers.remove(channel->number);
0440     }
0441 
0442     if (channelIdChanged) {
0443         channelIds.remove(DvbChannelId(channel), channel);
0444     }
0445 
0446     if (!isSqlModel && channel->isSqlKeyValid()) {
0447         DvbSharedChannel detachedChannel(new DvbChannel(modifiedChannel));
0448         channelNames.insert(detachedChannel->name, detachedChannel);
0449         channelNumbers.insert(detachedChannel->number, detachedChannel);
0450         channelIds.insert(DvbChannelId(detachedChannel), detachedChannel);
0451         emit channelUpdated(detachedChannel);
0452     } else {
0453         *const_cast<DvbChannel *>(channel.constData()) = modifiedChannel;
0454 
0455         if (channelNameChanged) {
0456             channelNames.insert(channel->name, channel);
0457         }
0458 
0459         if (channelNumberChanged) {
0460             channelNumbers.insert(channel->number, channel);
0461         }
0462 
0463         if (channelIdChanged) {
0464             channelIds.insert(DvbChannelId(channel), channel);
0465         }
0466 
0467         if (isSqlModel) {
0468             sqlUpdate(*channel);
0469         }
0470 
0471         emit channelUpdated(channel);
0472     }
0473 }
0474 
0475 void DvbChannelModel::removeChannel(DvbSharedChannel channel)
0476 {
0477     if (!channel.isValid() || (channelNumbers.value(channel->number) != channel)) {
0478         qCWarning(logDvb, "Invalid channel");
0479         return;
0480     }
0481 
0482     if (hasPendingOperation) {
0483         qCWarning(logDvb, "Illegal recursive call");
0484         return;
0485     }
0486 
0487     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
0488     channelNames.remove(channel->name);
0489     channelNumbers.remove(channel->number);
0490     channelIds.remove(DvbChannelId(channel), channel);
0491 
0492     if (isSqlModel) {
0493         channels.remove(*channel);
0494         sqlRemove(*channel);
0495     }
0496 
0497     emit channelRemoved(channel);
0498 }
0499 
0500 void DvbChannelModel::dndMoveChannels(const QList<DvbSharedChannel> &selectedChannels,
0501     int insertBeforeNumber)
0502 {
0503     if (hasPendingOperation) {
0504         qCWarning(logDvb, "Illegal recursive call");
0505         return;
0506     }
0507 
0508     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
0509     typedef QMap<int, DvbSharedChannel>::ConstIterator ConstIterator;
0510     QList<DvbSharedChannel> channelQueue;
0511 
0512     foreach (const DvbSharedChannel &channel, selectedChannels) {
0513         if (channel.isValid()) {
0514             ConstIterator it = channelNumbers.constFind(channel->number);
0515 
0516             if ((it != channelNumbers.constEnd()) && (*it == channel)) {
0517                 channelNumbers.remove(channel->number);
0518                 channelQueue.append(channel);
0519             }
0520         }
0521     }
0522 
0523     ConstIterator it = channelNumbers.constFind(insertBeforeNumber);
0524     int currentNumber = 1;
0525 
0526     if (it != channelNumbers.constBegin()) {
0527         currentNumber = ((it - 1).key() + 1);
0528     }
0529 
0530     while (!channelQueue.isEmpty()) {
0531         DvbSharedChannel channel = channelQueue.takeFirst();
0532 
0533         if (channel->number != currentNumber) {
0534             emit channelAboutToBeUpdated(channel);
0535             DvbSharedChannel existingChannel = channelNumbers.take(currentNumber);
0536 
0537             if (existingChannel.isValid()) {
0538                 channelQueue.append(existingChannel);
0539             }
0540 
0541             if (!isSqlModel && channel->isSqlKeyValid()) {
0542                 DvbChannel *newChannel = new DvbChannel(*channel);
0543                 newChannel->number = currentNumber;
0544                 DvbSharedChannel detachedChannel(newChannel);
0545                 channelNames.insert(detachedChannel->name, detachedChannel);
0546                 channelNumbers.insert(detachedChannel->number, detachedChannel);
0547                 channelIds.insert(DvbChannelId(detachedChannel), detachedChannel);
0548                 emit channelUpdated(detachedChannel);
0549             } else {
0550                 const_cast<DvbChannel *>(channel.constData())->number =
0551                     currentNumber;
0552                 channelNumbers.insert(channel->number, channel);
0553 
0554                 if (isSqlModel) {
0555                     sqlUpdate(*channel);
0556                 }
0557 
0558                 emit channelUpdated(channel);
0559             }
0560         } else {
0561             channelNumbers.insert(channel->number, channel);
0562         }
0563 
0564         ++currentNumber;
0565     }
0566 }
0567 
0568 void DvbChannelModel::bindToSqlQuery(SqlKey sqlKey, QSqlQuery &query, int index) const
0569 {
0570     DvbSharedChannel channel = channels.value(sqlKey);
0571 
0572     if (!channel.isValid()) {
0573         qCWarning(logDvb, "Invalid channel");
0574         return;
0575     }
0576 
0577     query.bindValue(index++, channel->name);
0578     query.bindValue(index++, channel->number);
0579     query.bindValue(index++, channel->source);
0580     query.bindValue(index++, channel->transponder.toString());
0581     query.bindValue(index++, channel->networkId);
0582     query.bindValue(index++, channel->transportStreamId);
0583     query.bindValue(index++, channel->pmtPid);
0584     query.bindValue(index++, channel->pmtSectionData);
0585     query.bindValue(index++, channel->audioPid);
0586     query.bindValue(index++, (channel->hasVideo ? 0x01 : 0) |
0587         (channel->isScrambled ? 0x02 : 0));
0588 }
0589 
0590 bool DvbChannelModel::insertFromSqlQuery(SqlKey sqlKey, const QSqlQuery &query, int index)
0591 {
0592     DvbChannel *channel = new DvbChannel();
0593     DvbSharedChannel sharedChannel(channel);
0594     channel->setSqlKey(sqlKey);
0595     channel->name = query.value(index++).toString();
0596     channel->number = query.value(index++).toInt();
0597     channel->source = query.value(index++).toString();
0598     channel->transponder = DvbTransponder::fromString(query.value(index++).toString());
0599     channel->networkId = query.value(index++).toInt();
0600     channel->transportStreamId = query.value(index++).toInt();
0601     channel->pmtPid = query.value(index++).toInt();
0602     channel->pmtSectionData = query.value(index++).toByteArray();
0603     channel->audioPid = query.value(index++).toInt();
0604     int flags = query.value(index++).toInt();
0605     channel->hasVideo = ((flags & 0x01) != 0);
0606     channel->isScrambled = ((flags & 0x02) != 0);
0607 
0608     if (channel->validate() && !channelNames.contains(channel->name) &&
0609         !channelNumbers.contains(channel->number)) {
0610         channelNames.insert(sharedChannel->name, sharedChannel);
0611         channelNumbers.insert(sharedChannel->number, sharedChannel);
0612         channelIds.insert(DvbChannelId(sharedChannel), sharedChannel);
0613         channels.insert(*sharedChannel, sharedChannel);
0614         return true;
0615     }
0616 
0617     return false;
0618 }
0619 
0620 bool DvbChannelModel::areInTheSameBunch(DvbSharedChannel channel1, DvbSharedChannel channel2)
0621 {
0622     if (channel1->transportStreamId == channel2->transportStreamId) {
0623         return true;
0624     }
0625 
0626     return false;
0627 }
0628 
0629 QString DvbChannelModel::extractBaseName(const QString &name) const
0630 {
0631     QString baseName = name;
0632     int position = baseName.lastIndexOf(QLatin1Char('-'));
0633 
0634     if (position > 0) {
0635         QString suffix = baseName.mid(position + 1);
0636 
0637         if (suffix == QString::number(suffix.toInt())) {
0638             baseName.truncate(position);
0639         }
0640     }
0641 
0642     return baseName;
0643 }
0644 
0645 QString DvbChannelModel::findNextFreeChannelName(const QString &name) const
0646 {
0647     if (!channelNames.contains(name)) {
0648         return name;
0649     }
0650 
0651     QString baseName = extractBaseName(name);
0652     int suffix = 0;
0653     QString newName = baseName;
0654 
0655     while (channelNames.contains(newName)) {
0656         ++suffix;
0657         newName = baseName + QLatin1Char('-') + QString::number(suffix);
0658     }
0659 
0660     return newName;
0661 }
0662 
0663 int DvbChannelModel::findNextFreeChannelNumber(int number) const
0664 {
0665     while (channelNumbers.contains(number)) {
0666         ++number;
0667     }
0668 
0669     return number;
0670 }
0671 
0672 #include "moc_dvbchannel.cpp"