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