1 /*
2  * dvbepg.cpp
3  *
4  * Copyright (C) 2009-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 <QLoggingCategory>
26 #include <QStandardPaths>
27 
28 #include "../ensurenopendingoperation.h"
29 #include "../iso-codes.h"
30 #include "dvbdevice.h"
31 #include "dvbepg.h"
32 #include "dvbepg_p.h"
33 #include "dvbmanager.h"
34 #include "dvbsi.h"
35 
validate() const36 bool DvbEpgEntry::validate() const
37 {
38 	if (channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) &&
39 	    duration.isValid()) {
40 		return true;
41 	}
42 
43 	return false;
44 }
45 
operator <(const DvbEpgEntryId & other) const46 bool DvbEpgEntryId::operator<(const DvbEpgEntryId &other) const
47 {
48 	if (entry->channel != other.entry->channel) {
49 		return (entry->channel < other.entry->channel);
50 	}
51 
52 	if (entry->begin != other.entry->begin) {
53 		return (entry->begin < other.entry->begin);
54 	}
55 	return false;
56 }
57 
DvbEpgModel(DvbManager * manager_,QObject * parent)58 DvbEpgModel::DvbEpgModel(DvbManager *manager_, QObject *parent) : QObject(parent),
59 	manager(manager_), hasPendingOperation(false)
60 {
61 	currentDateTimeUtc = QDateTime::currentDateTime().toUTC();
62 	startTimer(54000);
63 
64 	DvbChannelModel *channelModel = manager->getChannelModel();
65 	connect(channelModel, SIGNAL(channelAboutToBeUpdated(DvbSharedChannel)),
66 		this, SLOT(channelAboutToBeUpdated(DvbSharedChannel)));
67 	connect(channelModel, SIGNAL(channelUpdated(DvbSharedChannel)),
68 		this, SLOT(channelUpdated(DvbSharedChannel)));
69 	connect(channelModel, SIGNAL(channelRemoved(DvbSharedChannel)),
70 		this, SLOT(channelRemoved(DvbSharedChannel)));
71 	connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)),
72 		this, SLOT(recordingRemoved(DvbSharedRecording)));
73 
74 	// TODO use SQL to store epg data
75 
76 	QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb"));
77 
78 	if (!file.open(QIODevice::ReadOnly)) {
79 		qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName()));
80 		return;
81 	}
82 
83 	QDataStream stream(&file);
84 	stream.setVersion(QDataStream::Qt_4_4);
85 	DvbRecordingModel *recordingModel = manager->getRecordingModel();
86 	bool hasRecordingKey = true, hasParental = true, hasMultilang = true;
87 	int version;
88 	stream >> version;
89 
90 	if (version == 0x1ce0eca7) {
91 		hasRecordingKey = false;
92 	} else if (version == 0x79cffd36) {
93 		hasParental = false;
94 	} else if (version == 0x140c37b5) {
95 		hasMultilang = false;
96 	} else if (version != 0x20171112) {
97 		qCWarning(logEpg, "Wrong DB version for: %s", qPrintable(file.fileName()));
98 		return;
99 	}
100 
101 	while (!stream.atEnd()) {
102 		DvbEpgEntry entry;
103 		QString channelName;
104 		stream >> channelName;
105 		entry.channel = channelModel->findChannelByName(channelName);
106 		stream >> entry.begin;
107 		entry.begin = entry.begin.toUTC();
108 		stream >> entry.duration;
109 
110 		if (hasMultilang) {
111 			int i, count;
112 
113 			stream >> count;
114 
115 			for (i = 0; i < count; i++) {
116 				QString code;
117 
118 				DvbEpgLangEntry langEntry;
119 				stream >> code;
120 				stream >> langEntry.title;
121 				stream >> langEntry.subheading;
122 				stream >> langEntry.details;
123 
124 				entry.langEntry[code] = langEntry;
125 
126 				if (!langEntry.title.isEmpty() && !manager->languageCodes.contains(code))
127 					manager->languageCodes[code] = true;
128 			}
129 
130 
131 		} else {
132 			DvbEpgLangEntry langEntry;
133 
134 			stream >> langEntry.title;
135 			stream >> langEntry.subheading;
136 			stream >> langEntry.details;
137 
138 			entry.langEntry[FIRST_LANG] = langEntry;
139 		}
140 
141 		if (hasRecordingKey) {
142 			SqlKey recordingKey;
143 			stream >> recordingKey.sqlKey;
144 
145 			if (recordingKey.isSqlKeyValid()) {
146 				entry.recording = recordingModel->findRecordingByKey(recordingKey);
147 			}
148 		}
149 
150 		if (hasParental) {
151 			unsigned type;
152 
153 			stream >> type;
154 			stream >> entry.content;
155 			stream >> entry.parental;
156 
157 			if (type <= DvbEpgEntry::EitLast)
158 				entry.type = DvbEpgEntry::EitType(type);
159 			else
160 				entry.type = DvbEpgEntry::EitActualTsSchedule;
161 		}
162 
163 		if (stream.status() != QDataStream::Ok) {
164 			qCWarning(logEpg, "Corrupt data %s", qPrintable(file.fileName()));
165 			break;
166 		}
167 
168 		addEntry(entry);
169 	}
170 }
171 
~DvbEpgModel()172 DvbEpgModel::~DvbEpgModel()
173 {
174 	if (hasPendingOperation) {
175 		qCWarning(logEpg, "Illegal recursive call");
176 	}
177 
178 	if (!dvbEpgFilters.isEmpty() || !atscEpgFilters.isEmpty()) {
179 		qCWarning(logEpg, "filter list not empty");
180 	}
181 
182 	QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb"));
183 
184 	if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
185 		qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName()));
186 		return;
187 	}
188 
189 	QDataStream stream(&file);
190 	stream.setVersion(QDataStream::Qt_4_4);
191 	int version = 0x20171112;
192 	stream << version;
193 
194 	foreach (const DvbSharedEpgEntry &entry, entries) {
195 		SqlKey recordingKey;
196 
197 		if (entry->recording.isValid()) {
198 			recordingKey = *entry->recording;
199 		}
200 
201 		stream << entry->channel->name;
202 		stream << entry->begin;
203 		stream << entry->duration;
204 
205 		stream << entry->langEntry.size();
206 
207 		QHashIterator<QString, DvbEpgLangEntry> i(entry->langEntry);
208 
209 		while (i.hasNext()) {
210 			i.next();
211 
212 			stream << i.key();
213 
214 			DvbEpgLangEntry langEntry = i.value();
215 
216 			stream << langEntry.title;
217 			stream << langEntry.subheading;
218 			stream << langEntry.details;
219 		}
220 
221 		stream << recordingKey.sqlKey;
222 		stream << int(entry->type);
223 		stream << entry->content;
224 		stream << entry->parental;
225 	}
226 }
227 
getRecordings() const228 QMap<DvbSharedRecording, DvbSharedEpgEntry> DvbEpgModel::getRecordings() const
229 {
230 	return recordings;
231 }
232 
setRecordings(const QMap<DvbSharedRecording,DvbSharedEpgEntry> map)233 void DvbEpgModel::setRecordings(const QMap<DvbSharedRecording, DvbSharedEpgEntry> map)
234 {
235 	recordings = map;
236 }
237 
getEntries() const238 QMap<DvbEpgEntryId, DvbSharedEpgEntry> DvbEpgModel::getEntries() const
239 {
240 	return entries;
241 }
242 
getEpgChannels() const243 QHash<DvbSharedChannel, int> DvbEpgModel::getEpgChannels() const
244 {
245 	return epgChannels;
246 }
247 
getCurrentNext(const DvbSharedChannel & channel) const248 QList<DvbSharedEpgEntry> DvbEpgModel::getCurrentNext(const DvbSharedChannel &channel) const
249 {
250 	QList<DvbSharedEpgEntry> result;
251 	DvbEpgEntry fakeEntry(channel);
252 
253 	for (ConstIterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
254 	     it != entries.constEnd(); ++it) {
255 		const DvbSharedEpgEntry &entry = *it;
256 
257 		if (entry->channel != channel) {
258 			break;
259 		}
260 
261 		result.append(entry);
262 
263 		if (result.size() == 2) {
264 			break;
265 		}
266 	}
267 
268 	return result;
269 }
270 
Debug(QString text,const DvbSharedEpgEntry & entry)271 void DvbEpgModel::Debug(QString text, const DvbSharedEpgEntry &entry)
272 {
273 	if (!QLoggingCategory::defaultCategory()->isEnabled(QtDebugMsg))
274 		return;
275 
276 	QDateTime begin = entry->begin.toLocalTime();
277 	QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time();
278 
279 	qCDebug(logEpg, "event %s: type %d, from %s to %s: %s: %s: %s : %s",
280 		qPrintable(text), entry->type, qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)),
281 		qPrintable(entry->title()), qPrintable(entry->subheading()), qPrintable(entry->details()), qPrintable(entry->content));
282 }
283 
addEntry(const DvbEpgEntry & entry)284 DvbSharedEpgEntry DvbEpgModel::addEntry(const DvbEpgEntry &entry)
285 {
286 	if (!entry.validate()) {
287 		qCWarning(logEpg, "Invalid entry: channel is %s, begin is %s, duration is %s", entry.channel.isValid() ? "valid" : "invalid", entry.begin.isValid() ? "valid" : "invalid", entry.duration.isValid() ? "valid" : "invalid");
288 		return DvbSharedEpgEntry();
289 	}
290 
291 	if (hasPendingOperation) {
292 		qCWarning(logEpg, "Illegal recursive call");
293 		return DvbSharedEpgEntry();
294 	}
295 
296 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
297 
298 	// Check if the event was already recorded
299 	const QDateTime end = entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration));
300 
301 	// Optimize duplicated register logic by using find, with is O(log n)
302 	Iterator it = entries.find(DvbEpgEntryId(&entry));
303 	while (it != entries.end()) {
304 		const DvbSharedEpgEntry &existingEntry = *it;
305 
306 		// Don't do anything if the event already exists
307 		if (*existingEntry == entry)
308 			return DvbSharedEpgEntry();
309 
310 		const QDateTime enEnd = existingEntry->begin.addSecs(QTime(0, 0, 0).secsTo(existingEntry->duration));
311 
312 		// The logic here was simplified due to performance.
313 		// It won't check anymore if an event has its start time
314 		// switched, as that would require a O(n) loop, with is
315 		// too slow, specially on DVB-S/S2. So, we're letting the QMap
316 		// to use a key with just channel/begin time, identifying
317 		// obsolete entries only if the end time doesn't match.
318 
319 		// A new event conflicts with an existing one
320 		if (end != enEnd) {
321 			Debug("removed", existingEntry);
322 			it = removeEntry(it);
323 			break;
324 		}
325 		// New event data for the same event
326 		if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) {
327 			emit entryAboutToBeUpdated(existingEntry);
328 
329 			QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry);
330 
331 			while (i.hasNext()) {
332 				i.next();
333 
334 				DvbEpgLangEntry langEntry = i.value();
335 
336 				const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details;
337 			}
338 			emit entryUpdated(existingEntry);
339 			Debug("updated", existingEntry);
340 		}
341 		return existingEntry;
342 	}
343 
344 	if (entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)) > currentDateTimeUtc) {
345 		DvbSharedEpgEntry existingEntry = entries.value(DvbEpgEntryId(&entry));
346 
347 		if (existingEntry.isValid()) {
348 			if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) {
349 				// needed for atsc
350 				emit entryAboutToBeUpdated(existingEntry);
351 
352 				QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry);
353 
354 				while (i.hasNext()) {
355 					i.next();
356 
357 					DvbEpgLangEntry langEntry = i.value();
358 
359 					const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details;
360 				}
361 				emit entryUpdated(existingEntry);
362 				Debug("updated2", existingEntry);
363 			}
364 
365 			return existingEntry;
366 		}
367 
368 		DvbSharedEpgEntry newEntry(new DvbEpgEntry(entry));
369 		entries.insert(DvbEpgEntryId(newEntry), newEntry);
370 
371 		if (newEntry->recording.isValid()) {
372 			recordings.insert(newEntry->recording, newEntry);
373 		}
374 
375 		if (++epgChannels[newEntry->channel] == 1) {
376 			emit epgChannelAdded(newEntry->channel);
377 		}
378 
379 		emit entryAdded(newEntry);
380 		Debug("new", newEntry);
381 		return newEntry;
382 	}
383 
384 	return DvbSharedEpgEntry();
385 }
386 
scheduleProgram(const DvbSharedEpgEntry & entry,int extraSecondsBefore,int extraSecondsAfter,bool checkForRecursion,int priority)387 void DvbEpgModel::scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore,
388 	int extraSecondsAfter, bool checkForRecursion, int priority)
389 {
390 	if (!entry.isValid() || (entries.value(DvbEpgEntryId(entry)) != entry)) {
391 		qCWarning(logEpg, "Can't schedule program: invalid entry");
392 		return;
393 	}
394 
395 	if (hasPendingOperation) {
396 		qCWarning(logEpg, "Illegal recursive call");
397 		return;
398 	}
399 
400 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
401 	emit entryAboutToBeUpdated(entry);
402 	DvbSharedRecording oldRecording;
403 
404 	if (!entry->recording.isValid()) {
405 		DvbRecording recording;
406 		recording.priority = priority;
407 		recording.name = entry->title(manager->currentEpgLanguage);
408 		recording.channel = entry->channel;
409 		recording.begin = entry->begin.addSecs(-extraSecondsBefore);
410 		recording.beginEPG = entry->begin;
411 		recording.duration =
412 			entry->duration.addSecs(extraSecondsBefore + extraSecondsAfter);
413 		recording.durationEPG =
414 			entry->duration;
415 		recording.subheading =
416 			entry->subheading(manager->currentEpgLanguage);
417 		recording.details =
418 			entry->details(manager->currentEpgLanguage);
419 		recording.disabled = false;
420 		const_cast<DvbEpgEntry *>(entry.constData())->recording =
421 			manager->getRecordingModel()->addRecording(recording, checkForRecursion);
422 		recordings.insert(entry->recording, entry);
423 	} else {
424 		oldRecording = entry->recording;
425 		recordings.remove(entry->recording);
426 		const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording();
427 	}
428 
429 	emit entryUpdated(entry);
430 
431 	if (oldRecording.isValid()) {
432 		// recordingRemoved() will be called
433 		hasPendingOperation = false;
434 		manager->getRecordingModel()->removeRecording(oldRecording);
435 	}
436 }
437 
startEventFilter(DvbDevice * device,const DvbSharedChannel & channel)438 void DvbEpgModel::startEventFilter(DvbDevice *device, const DvbSharedChannel &channel)
439 {
440 	if (manager->disableEpg())
441 		return;
442 
443 	switch (channel->transponder.getTransmissionType()) {
444 	case DvbTransponderBase::Invalid:
445 		break;
446 	case DvbTransponderBase::DvbC:
447 	case DvbTransponderBase::DvbS:
448 	case DvbTransponderBase::DvbS2:
449 	case DvbTransponderBase::DvbT:
450 	case DvbTransponderBase::DvbT2:
451 	case DvbTransponderBase::IsdbT:
452 		dvbEpgFilters.append(QExplicitlySharedDataPointer<DvbEpgFilter>(
453 			new DvbEpgFilter(manager, device, channel)));
454 		break;
455 	case DvbTransponderBase::Atsc:
456 		atscEpgFilters.append(QExplicitlySharedDataPointer<AtscEpgFilter>(
457 			new AtscEpgFilter(manager, device, channel)));
458 		break;
459 	}
460 }
461 
stopEventFilter(DvbDevice * device,const DvbSharedChannel & channel)462 void DvbEpgModel::stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel)
463 {
464 	switch (channel->transponder.getTransmissionType()) {
465 	case DvbTransponderBase::Invalid:
466 		break;
467 	case DvbTransponderBase::DvbC:
468 	case DvbTransponderBase::DvbS:
469 	case DvbTransponderBase::DvbS2:
470 	case DvbTransponderBase::DvbT:
471 	case DvbTransponderBase::DvbT2:
472 	case DvbTransponderBase::IsdbT:
473 		for (int i = 0; i < dvbEpgFilters.size(); ++i) {
474 			const DvbEpgFilter *epgFilter = dvbEpgFilters.at(i).constData();
475 
476 			if ((epgFilter->device == device) &&
477 			    (epgFilter->source == channel->source) &&
478 			    (epgFilter->transponder.corresponds(channel->transponder))) {
479 				dvbEpgFilters.removeAt(i);
480 				break;
481 			}
482 		}
483 
484 		break;
485 	case DvbTransponderBase::Atsc:
486 		for (int i = 0; i < atscEpgFilters.size(); ++i) {
487 			const AtscEpgFilter *epgFilter = atscEpgFilters.at(i).constData();
488 
489 			if ((epgFilter->device == device) &&
490 			    (epgFilter->source == channel->source) &&
491 			    (epgFilter->transponder.corresponds(channel->transponder))) {
492 				atscEpgFilters.removeAt(i);
493 				break;
494 			}
495 		}
496 
497 		break;
498 	}
499 }
500 
channelAboutToBeUpdated(const DvbSharedChannel & channel)501 void DvbEpgModel::channelAboutToBeUpdated(const DvbSharedChannel &channel)
502 {
503 	updatingChannel = *channel;
504 }
505 
channelUpdated(const DvbSharedChannel & channel)506 void DvbEpgModel::channelUpdated(const DvbSharedChannel &channel)
507 {
508 	if (hasPendingOperation) {
509 		qCWarning(logEpg, "Illegal recursive call");
510 		return;
511 	}
512 
513 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
514 
515 	if (DvbChannelId(channel) != DvbChannelId(&updatingChannel)) {
516 		DvbEpgEntry fakeEntry(channel);
517 		Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
518 
519 		while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) {
520 			it = removeEntry(it);
521 		}
522 	}
523 }
524 
channelRemoved(const DvbSharedChannel & channel)525 void DvbEpgModel::channelRemoved(const DvbSharedChannel &channel)
526 {
527 	if (hasPendingOperation) {
528 		qCWarning(logEpg, "Illegal recursive call");
529 		return;
530 	}
531 
532 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
533 	DvbEpgEntry fakeEntry(channel);
534 	Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
535 
536 	while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) {
537 		it = removeEntry(it);
538 	}
539 }
540 
recordingRemoved(const DvbSharedRecording & recording)541 void DvbEpgModel::recordingRemoved(const DvbSharedRecording &recording)
542 {
543 	if (hasPendingOperation) {
544 		qCWarning(logEpg, "Illegal recursive call");
545 		return;
546 	}
547 
548 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
549 	DvbSharedEpgEntry entry = recordings.take(recording);
550 
551 	if (entry.isValid()) {
552 		emit entryAboutToBeUpdated(entry);
553 		const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording();
554 		emit entryUpdated(entry);
555 	}
556 }
557 
timerEvent(QTimerEvent * event)558 void DvbEpgModel::timerEvent(QTimerEvent *event)
559 {
560 	Q_UNUSED(event)
561 
562 	if (hasPendingOperation) {
563 		qCWarning(logEpg, "Illegal recursive call");
564 		return;
565 	}
566 
567 	EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
568 	currentDateTimeUtc = QDateTime::currentDateTime().toUTC();
569 	Iterator it = entries.begin();
570 
571 	while (ConstIterator(it) != entries.constEnd()) {
572 		const DvbSharedEpgEntry &entry = *it;
573 
574 		if (entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)) > currentDateTimeUtc) {
575 			++it;
576 		} else {
577 			it = removeEntry(it);
578 		}
579 	}
580 }
581 
removeEntry(Iterator it)582 DvbEpgModel::Iterator DvbEpgModel::removeEntry(Iterator it)
583 {
584 	const DvbSharedEpgEntry &entry = *it;
585 
586 	if (entry->recording.isValid()) {
587 		recordings.remove(entry->recording);
588 	}
589 
590 	if (--epgChannels[entry->channel] == 0) {
591 		epgChannels.remove(entry->channel);
592 		emit epgChannelRemoved(entry->channel);
593 	}
594 
595 	emit entryRemoved(entry);
596 	return entries.erase(it);
597 }
598 
DvbEpgFilter(DvbManager * manager_,DvbDevice * device_,const DvbSharedChannel & channel)599 DvbEpgFilter::DvbEpgFilter(DvbManager *manager_, DvbDevice *device_,
600 	const DvbSharedChannel &channel) : device(device_)
601 {
602 	manager = manager_;
603 	source = channel->source;
604 	transponder = channel->transponder;
605 	device->addSectionFilter(0x12, this);
606 	channelModel = manager->getChannelModel();
607 	epgModel = manager->getEpgModel();
608 }
609 
~DvbEpgFilter()610 DvbEpgFilter::~DvbEpgFilter()
611 {
612 	device->removeSectionFilter(0x12, this);
613 }
614 
bcdToTime(int bcd)615 QTime DvbEpgFilter::bcdToTime(int bcd)
616 {
617 	return QTime(((bcd >> 20) & 0x0f) * 10 + ((bcd >> 16) & 0x0f),
618 		((bcd >> 12) & 0x0f) * 10 + ((bcd >> 8) & 0x0f),
619 		((bcd >> 4) & 0x0f) * 10 + (bcd & 0x0f));
620 }
621 
622 static const QByteArray contentStr[16][16] = {
623 	[0] = {},
624 	[1] = {
625 			/* Movie/Drama */
626 		{},
627 		{I18N_NOOP("Detective")},
628 		{I18N_NOOP("Adventure")},
629 		{I18N_NOOP("Science Fiction")},
630 		{I18N_NOOP("Comedy")},
631 		{I18N_NOOP("Soap")},
632 		{I18N_NOOP("Romance")},
633 		{I18N_NOOP("Classical")},
634 		{I18N_NOOP("Adult")},
635 		{I18N_NOOP("User defined")},
636 	},
637 	[2] = {
638 			/* News/Current affairs */
639 		{},
640 		{I18N_NOOP("Weather")},
641 		{I18N_NOOP("Magazine")},
642 		{I18N_NOOP("Documentary")},
643 		{I18N_NOOP("Discussion")},
644 		{I18N_NOOP("User Defined")},
645 	},
646 	[3] = {
647 			/* Show/Game show */
648 		{},
649 		{I18N_NOOP("Quiz")},
650 		{I18N_NOOP("Variety")},
651 		{I18N_NOOP("Talk")},
652 		{I18N_NOOP("User Defined")},
653 	},
654 	[4] = {
655 			/* Sports */
656 		{},
657 		{I18N_NOOP("Events")},
658 		{I18N_NOOP("Magazine")},
659 		{I18N_NOOP("Football")},
660 		{I18N_NOOP("Tennis")},
661 		{I18N_NOOP("Team")},
662 		{I18N_NOOP("Athletics")},
663 		{I18N_NOOP("Motor")},
664 		{I18N_NOOP("Water")},
665 		{I18N_NOOP("Winter")},
666 		{I18N_NOOP("Equestrian")},
667 		{I18N_NOOP("Martial")},
668 		{I18N_NOOP("User Defined")},
669 	},
670 	[5] = {
671 			/* Children's/Youth */
672 		{},
673 		{I18N_NOOP("Preschool")},
674 		{I18N_NOOP("06 to 14")},
675 		{I18N_NOOP("10 to 16")},
676 		{I18N_NOOP("Educational")},
677 		{I18N_NOOP("Cartoons")},
678 		{I18N_NOOP("User Defined")},
679 	},
680 	[6] = {
681 			/* Music/Ballet/Dance */
682 		{},
683 		{I18N_NOOP("Poprock")},
684 		{I18N_NOOP("Classical")},
685 		{I18N_NOOP("Folk")},
686 		{I18N_NOOP("Jazz")},
687 		{I18N_NOOP("Opera")},
688 		{I18N_NOOP("Ballet")},
689 		{I18N_NOOP("User Defined")},
690 	},
691 	[7] = {
692 			/* Arts/Culture */
693 		{},
694 		{I18N_NOOP("Performance")},
695 		{I18N_NOOP("Fine Arts")},
696 		{I18N_NOOP("Religion")},
697 		{I18N_NOOP("Traditional")},
698 		{I18N_NOOP("Literature")},
699 		{I18N_NOOP("Cinema")},
700 		{I18N_NOOP("Experimental")},
701 		{I18N_NOOP("Press")},
702 		{I18N_NOOP("New Media")},
703 		{I18N_NOOP("Magazine")},
704 		{I18N_NOOP("Fashion")},
705 		{I18N_NOOP("User Defined")},
706 	},
707 	[8] = {
708 			/* Social/Political/Economics */
709 		{},
710 		{I18N_NOOP("Magazine")},
711 		{I18N_NOOP("Advisory")},
712 		{I18N_NOOP("People")},
713 		{I18N_NOOP("User Defined")},
714 	},
715 	[9] = {
716 			/* Education/Science/Factual */
717 		{},
718 		{I18N_NOOP("Nature")},
719 		{I18N_NOOP("Technology")},
720 		{I18N_NOOP("Medicine")},
721 		{I18N_NOOP("Foreign")},
722 		{I18N_NOOP("Social")},
723 		{I18N_NOOP("Further")},
724 		{I18N_NOOP("Language")},
725 		{I18N_NOOP("User Defined")},
726 	},
727 	[10] = {
728 			/* Leisure/Hobbies */
729 		{},
730 		{I18N_NOOP("Travel")},
731 		{I18N_NOOP("Handicraft")},
732 		{I18N_NOOP("Motoring")},
733 		{I18N_NOOP("Fitness")},
734 		{I18N_NOOP("Cooking")},
735 		{I18N_NOOP("Shopping")},
736 		{I18N_NOOP("Gardening")},
737 		{I18N_NOOP("User Defined")},
738 	},
739 	[11] = {
740 			/* Special characteristics */
741 		{I18N_NOOP("Original Language")},
742 		{I18N_NOOP("Black and White ")},
743 		{I18N_NOOP("Unpublished")},
744 		{I18N_NOOP("Live")},
745 		{I18N_NOOP("Planostereoscopic")},
746 		{I18N_NOOP("User Defined")},
747 		{I18N_NOOP("User Defined 1")},
748 		{I18N_NOOP("User Defined 2")},
749 		{I18N_NOOP("User Defined 3")},
750 		{I18N_NOOP("User Defined 4")}
751 	}
752 };
753 
754 static const QByteArray nibble1Str[16] = {
755 	[0]  = {I18N_NOOP("Undefined")},
756 	[1]  = {I18N_NOOP("Movie")},
757 	[2]  = {I18N_NOOP("News")},
758 	[3]  = {I18N_NOOP("Show")},
759 	[4]  = {I18N_NOOP("Sports")},
760 	[5]  = {I18N_NOOP("Children")},
761 	[6]  = {I18N_NOOP("Music")},
762 	[7]  = {I18N_NOOP("Culture")},
763 	[8]  = {I18N_NOOP("Social")},
764 	[9]  = {I18N_NOOP("Education")},
765 	[10] = {I18N_NOOP("Leisure")},
766 	[11] = {I18N_NOOP("Special")},
767 	[12] = {I18N_NOOP("Reserved")},
768 	[13] = {I18N_NOOP("Reserved")},
769 	[14] = {I18N_NOOP("Reserved")},
770 	[15] = {I18N_NOOP("User defined")},
771 };
772 
773 static const QByteArray braNibble1Str[16] = {
774 	[0]  = {I18N_NOOP("News")},
775 	[1]  = {I18N_NOOP("Sports")},
776 	[2]  = {I18N_NOOP("Education")},
777 	[3]  = {I18N_NOOP("Soap opera")},
778 	[4]  = {I18N_NOOP("Mini-series")},
779 	[5]  = {I18N_NOOP("Series")},
780 	[6]  = {I18N_NOOP("Variety")},
781 	[7]  = {I18N_NOOP("Reality show")},
782 	[8]  = {I18N_NOOP("Information")},
783 	[9]  = {I18N_NOOP("Comical")},
784 	[10] = {I18N_NOOP("Children")},
785 	[11] = {I18N_NOOP("Erotic")},
786 	[12] = {I18N_NOOP("Movie")},
787 	[13] = {I18N_NOOP("Raffle, television sales, prizing")},
788 	[14] = {I18N_NOOP("Debate/interview")},
789 	[15] = {I18N_NOOP("Other")},
790 };
791 
792 // Using the terms from the English version of NBR 15603-2:2007
793 // The table omits nibble2="Other", as it is better to show nibble 1
794 // definition instead.
795 // when nibble2[x][0] == nibble1[x] and it has no other definition,
796 // except for "Other", the field will be kept in blank, as the logic
797 // will fall back to the definition at nibble 1.
798 static QByteArray braNibble2Str[16][16] = {
799 	[0] = {
800 		{I18N_NOOP("News")},
801 		{I18N_NOOP("Report")},
802 		{I18N_NOOP("Documentary")},
803 		{I18N_NOOP("Biography")},
804 	},
805 	[1] = {},
806 	[2] = {
807 		{I18N_NOOP("Educative")},
808 	},
809 	[3] = {},
810 	[4] = {},
811 	[5] = {},
812 	[6] = {
813 		{I18N_NOOP("Auditorium")},
814 		{I18N_NOOP("Show")},
815 		{I18N_NOOP("Musical")},
816 		{I18N_NOOP("Making of")},
817 		{I18N_NOOP("Feminine")},
818 		{I18N_NOOP("Game show")},
819 	},
820 	[7] = {},
821 	[8] = {
822 		{I18N_NOOP("Cooking")},
823 		{I18N_NOOP("Fashion")},
824 		{I18N_NOOP("Country")},
825 		{I18N_NOOP("Health")},
826 		{I18N_NOOP("Travel")},
827 	},
828 	[9] = {},
829 	[10] = {},
830 	[11] = {},
831 	[12] = {},
832 	[13] = {
833 		{I18N_NOOP("Raffle")},
834 		{I18N_NOOP("Television sales")},
835 		{I18N_NOOP("Prizing")},
836 	},
837 	[14] = {
838 		{I18N_NOOP("Discussion")},
839 		{I18N_NOOP("Interview")},
840 	},
841 	[15] = {
842 		{I18N_NOOP("Adult cartoon")},
843 		{I18N_NOOP("Interactive")},
844 		{I18N_NOOP("Policy")},
845 		{I18N_NOOP("Religion")},
846 	},
847 };
848 
getContent(DvbContentDescriptor & descriptor)849 QString DvbEpgFilter::getContent(DvbContentDescriptor &descriptor)
850 {
851 	QString content;
852 
853 	for (DvbEitContentEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) {
854 		const int nibble1 = entry.contentNibbleLevel1();
855 		const int nibble2 = entry.contentNibbleLevel2();
856 		QByteArray s;
857 
858 		// FIXME: should do it only for ISDB-Tb (Brazilian variation),
859 		// as the Japanese variation uses the same codes as DVB
860 		if (transponder.getTransmissionType() == DvbTransponderBase::IsdbT) {
861 			s = braNibble2Str[nibble1][nibble2];
862 			if (s == "")
863 				s = braNibble1Str[nibble1];
864 			if (s != "")
865 				content += i18n(s) + '\n';
866 		} else {
867 			s = contentStr[nibble1][nibble2];
868 			if (s == "")
869 				s = nibble1Str[nibble1];
870 			if (s != "")
871 				content += i18n(s) + '\n';
872 		}
873 	}
874 
875 	if (content != "") {
876 		// xgettext:no-c-format
877 		return (i18n("Genre: %1", content));
878 	}
879 	return content;
880 }
881 
882 /* As defined at ABNT NBR 15603-2 */
883 static const QByteArray braRating[] = {
884 	[0] = {I18N_NOOP("reserved")},
885 	[1] = {I18N_NOOP("all audiences")},
886 	[2] = {I18N_NOOP("10 years")},
887 	[3] = {I18N_NOOP("12 years")},
888 	[4] = {I18N_NOOP("14 years")},
889 	[5] = {I18N_NOOP("16 years")},
890 	[6] = {I18N_NOOP("18 years")},
891 };
892 
893 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
894 
getParental(DvbParentalRatingDescriptor & descriptor)895 QString DvbEpgFilter::getParental(DvbParentalRatingDescriptor &descriptor)
896 {
897 	QString parental;
898 
899 	for (DvbParentalRatingEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) {
900 		QString code;
901 		code.append(QChar(entry.languageCode1()));
902 		code.append(QChar(entry.languageCode2()));
903 		code.append(QChar(entry.languageCode3()));
904 
905 		QString country;
906 		IsoCodes::getCountry(code, &country);
907 		if (country.isEmpty())
908 			country = code;
909 
910 		// Rating from 0x10 to 0xff are broadcaster's specific
911 		if (entry.rating() == 0) {
912 			// xgettext:no-c-format
913 			parental += i18n("Country %1: not rated\n", country);
914 		} else if (entry.rating() < 0x10) {
915 			if (code == "BRA" && transponder.getTransmissionType() == DvbTransponderBase::IsdbT) {
916 				unsigned int rating = entry.rating();
917 
918 				if (rating >= ARRAY_SIZE(braRating))
919 					rating = 0;	// Reserved
920 
921 				QString GenStr;
922 				int genre = entry.rating() >> 4;
923 
924 				if (genre & 0x2)
925 					GenStr = i18n("violence / ");
926 				if (genre & 0x4)
927 					GenStr = i18n("sex / ");
928 				if (genre & 0x1)
929 					GenStr = i18n("drugs / ");
930 				if (genre) {
931 					GenStr.truncate(GenStr.size() - 2);
932 					GenStr = " (" + GenStr + ')';
933 				}
934 
935 				QString ratingStr = i18n(braRating[entry.rating()]);
936 				// xgettext:no-c-format
937 				parental += i18n("Country %1: rating: %2%3\n", country, ratingStr, GenStr);
938 			} else {
939 				// xgettext:no-c-format
940 				parental += i18n("Country %1: rating: %2 years.\n", country, entry.rating() + 3);
941 			}
942 		}
943 	}
944 	return parental;
945 }
946 
getLangEntry(DvbEpgEntry & epgEntry,int code1,int code2,int code3,bool add_code,QString * code_)947 DvbEpgLangEntry *DvbEpgFilter::getLangEntry(DvbEpgEntry &epgEntry,
948 					    int code1, int code2, int code3,
949 					    bool add_code,
950 					    QString *code_)
951 {
952 	DvbEpgLangEntry *langEntry;
953 	QString code;
954 
955 	if (!code1 || code1 == 0x20)
956 		code = FIRST_LANG;
957 	else {
958 		code.append(QChar(code1));
959 		code.append(QChar(code2));
960 		code.append(QChar(code3));
961 		code = code.toUpper();
962 	}
963 	if (code_)
964 		code_ = new QString(code);
965 
966 	if (!epgEntry.langEntry.contains(code)) {
967 		DvbEpgLangEntry e;
968 		epgEntry.langEntry.insert(code, e);
969 		if (add_code) {
970 			if (!manager->languageCodes.contains(code)) {
971 				manager->languageCodes[code] = true;
972 				emit epgModel->languageAdded(code);
973 			}
974 		}
975 	}
976 	langEntry = &epgEntry.langEntry[code];
977 
978 	return langEntry;
979 }
980 
981 
processSection(const char * data,int size)982 void DvbEpgFilter::processSection(const char *data, int size)
983 {
984 	unsigned char tableId = data[0];
985 
986 	if ((tableId < 0x4e) || (tableId > 0x6f)) {
987 		return;
988 	}
989 
990 	DvbEitSection eitSection(data, size);
991 
992 	if (!eitSection.isValid()) {
993 		qCDebug(logEpg, "section is invalid");
994 		return;
995 	}
996 
997 	DvbChannel fakeChannel;
998 	fakeChannel.source = source;
999 	fakeChannel.transponder = transponder;
1000 	fakeChannel.networkId = eitSection.originalNetworkId();
1001 	fakeChannel.transportStreamId = eitSection.transportStreamId();
1002 	fakeChannel.serviceId = eitSection.serviceId();
1003 	DvbSharedChannel channel = channelModel->findChannelById(fakeChannel);
1004 
1005 	if (!channel.isValid()) {
1006 		fakeChannel.networkId = -1;
1007 		channel = channelModel->findChannelById(fakeChannel);
1008 	}
1009 
1010 	if (!channel.isValid()) {
1011 		qCDebug(logEpg, "channel invalid");
1012 		return;
1013 	}
1014 
1015 	if (eitSection.entries().getLength())
1016 		qCDebug(logEpg, "table 0x%02x, extension 0x%04x, session %d/%d, size %d", eitSection.tableId(), eitSection.tableIdExtension(), eitSection.sectionNumber(), eitSection.lastSectionNumber(), eitSection.entries().getLength());
1017 
1018 	for (DvbEitSectionEntry entry = eitSection.entries(); entry.isValid(); entry.advance()) {
1019 		DvbEpgEntry epgEntry;
1020 		DvbEpgLangEntry *langEntry;
1021 
1022 		if (tableId == 0x4e)
1023 			epgEntry.type = DvbEpgEntry::EitActualTsPresentFollowing;
1024 		else if (tableId == 0x4f)
1025 			epgEntry.type = DvbEpgEntry::EitOtherTsPresentFollowing;
1026 		else if (tableId < 0x60)
1027 			epgEntry.type = DvbEpgEntry::EitActualTsSchedule;
1028 		else
1029 			epgEntry.type = DvbEpgEntry::EitOtherTsSchedule;
1030 
1031 		epgEntry.channel = channel;
1032 
1033 		/*
1034 		 * ISDB-T Brazil uses time in UTC-3,
1035 		 * as defined by ABNT NBR 15603-2:2007.
1036 		 */
1037 		if (channel->transponder.getTransmissionType() == DvbTransponderBase::IsdbT)
1038 			epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001),
1039 						   bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC();
1040 		else
1041 			epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001),
1042 						   bcdToTime(entry.startTime()), Qt::UTC);
1043 		epgEntry.duration = bcdToTime(entry.duration());
1044 
1045 		for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid();
1046 		     descriptor.advance()) {
1047 			switch (descriptor.descriptorTag()) {
1048 			case 0x4d: {
1049 				DvbShortEventDescriptor eventDescriptor(descriptor);
1050 
1051 				if (!eventDescriptor.isValid()) {
1052 					break;
1053 				}
1054 
1055 				langEntry = getLangEntry(epgEntry,
1056 					     eventDescriptor.languageCode1(),
1057 					     eventDescriptor.languageCode2(),
1058 					     eventDescriptor.languageCode3());
1059 
1060 				langEntry->title += eventDescriptor.eventName();
1061 				langEntry->subheading += eventDescriptor.text();
1062 
1063 				break;
1064 			    }
1065 			case 0x4e: {
1066 				DvbExtendedEventDescriptor eventDescriptor(descriptor);
1067 
1068 				if (!eventDescriptor.isValid()) {
1069 					break;
1070 				}
1071 
1072 				langEntry = getLangEntry(epgEntry,
1073 					     eventDescriptor.languageCode1(),
1074 					     eventDescriptor.languageCode2(),
1075 					     eventDescriptor.languageCode3());
1076 				langEntry->details += eventDescriptor.text();
1077 				break;
1078 			    }
1079 			case 0x54: {
1080 				DvbContentDescriptor eventDescriptor(descriptor);
1081 
1082 				if (!eventDescriptor.isValid()) {
1083 					break;
1084 				}
1085 
1086 				epgEntry.content += getContent(eventDescriptor);
1087 				break;
1088 			    }
1089 			case 0x55: {
1090 				DvbParentalRatingDescriptor eventDescriptor(descriptor);
1091 
1092 				if (!eventDescriptor.isValid()) {
1093 					break;
1094 				}
1095 
1096 				epgEntry.parental += getParental(eventDescriptor);
1097 				break;
1098 			    }
1099 			}
1100 		}
1101 
1102 		epgModel->addEntry(epgEntry);
1103 	}
1104 }
1105 
processSection(const char * data,int size)1106 void AtscEpgMgtFilter::processSection(const char *data, int size)
1107 {
1108 	epgFilter->processMgtSection(data, size);
1109 }
1110 
processSection(const char * data,int size)1111 void AtscEpgEitFilter::processSection(const char *data, int size)
1112 {
1113 	epgFilter->processEitSection(data, size);
1114 }
1115 
processSection(const char * data,int size)1116 void AtscEpgEttFilter::processSection(const char *data, int size)
1117 {
1118 	epgFilter->processEttSection(data, size);
1119 }
1120 
AtscEpgFilter(DvbManager * manager,DvbDevice * device_,const DvbSharedChannel & channel)1121 AtscEpgFilter::AtscEpgFilter(DvbManager *manager, DvbDevice *device_,
1122 	const DvbSharedChannel &channel) : device(device_), mgtFilter(this), eitFilter(this),
1123 	ettFilter(this)
1124 {
1125 	source = channel->source;
1126 	transponder = channel->transponder;
1127 	device->addSectionFilter(0x1ffb, &mgtFilter);
1128 	channelModel = manager->getChannelModel();
1129 	epgModel = manager->getEpgModel();
1130 }
1131 
~AtscEpgFilter()1132 AtscEpgFilter::~AtscEpgFilter()
1133 {
1134 	foreach (int pid, eitPids) {
1135 		device->removeSectionFilter(pid, &eitFilter);
1136 	}
1137 
1138 	foreach (int pid, ettPids) {
1139 		device->removeSectionFilter(pid, &ettFilter);
1140 	}
1141 
1142 	device->removeSectionFilter(0x1ffb, &mgtFilter);
1143 }
1144 
processMgtSection(const char * data,int size)1145 void AtscEpgFilter::processMgtSection(const char *data, int size)
1146 {
1147 	unsigned char tableId = data[0];
1148 
1149 	if (tableId != 0xc7) {
1150 		return;
1151 	}
1152 
1153 	AtscMgtSection mgtSection(data, size);
1154 
1155 	if (!mgtSection.isValid()) {
1156 		return;
1157 	}
1158 
1159 	int entryCount = mgtSection.entryCount();
1160 	QList<int> newEitPids;
1161 	QList<int> newEttPids;
1162 
1163 	AtscMgtSectionEntry entry = mgtSection.entries();
1164 	for (int i = 0; i < entryCount; i++) {
1165 		if (!entry.isValid())
1166 			break;
1167 
1168 		int tableType = entry.tableType();
1169 
1170 		if ((tableType >= 0x0100) && (tableType <= 0x017f)) {
1171 			int pid = entry.pid();
1172 			int index = (qLowerBound(newEitPids, pid) - newEitPids.constBegin());
1173 
1174 			if ((index >= newEitPids.size()) || (newEitPids.at(index) != pid)) {
1175 				newEitPids.insert(index, pid);
1176 			}
1177 		}
1178 
1179 		if ((tableType >= 0x0200) && (tableType <= 0x027f)) {
1180 			int pid = entry.pid();
1181 			int index = (qLowerBound(newEttPids, pid) - newEttPids.constBegin());
1182 
1183 			if ((index >= newEttPids.size()) || (newEttPids.at(index) != pid)) {
1184 				newEttPids.insert(index, pid);
1185 			}
1186 		}
1187 		if (i < entryCount - 1)
1188 			entry.advance();
1189 	}
1190 
1191 	for (int i = 0; i < eitPids.size(); ++i) {
1192 		int pid = eitPids.at(i);
1193 		int index = (qBinaryFind(newEitPids, pid) - newEitPids.constBegin());
1194 
1195 		if (index < newEitPids.size()) {
1196 			newEitPids.removeAt(index);
1197 		} else {
1198 			device->removeSectionFilter(pid, &eitFilter);
1199 			eitPids.removeAt(i);
1200 			--i;
1201 		}
1202 	}
1203 
1204 	for (int i = 0; i < ettPids.size(); ++i) {
1205 		int pid = ettPids.at(i);
1206 		int index = (qBinaryFind(newEttPids, pid) - newEttPids.constBegin());
1207 
1208 		if (index < newEttPids.size()) {
1209 			newEttPids.removeAt(index);
1210 		} else {
1211 			device->removeSectionFilter(pid, &ettFilter);
1212 			ettPids.removeAt(i);
1213 			--i;
1214 		}
1215 	}
1216 
1217 	for (int i = 0; i < newEitPids.size(); ++i) {
1218 		int pid = newEitPids.at(i);
1219 		eitPids.append(pid);
1220 		device->addSectionFilter(pid, &eitFilter);
1221 	}
1222 
1223 	for (int i = 0; i < newEttPids.size(); ++i) {
1224 		int pid = newEttPids.at(i);
1225 		ettPids.append(pid);
1226 		device->addSectionFilter(pid, &ettFilter);
1227 	}
1228 }
1229 
processEitSection(const char * data,int size)1230 void AtscEpgFilter::processEitSection(const char *data, int size)
1231 {
1232 	unsigned char tableId = data[0];
1233 
1234 	if (tableId != 0xcb) {
1235 		return;
1236 	}
1237 
1238 	AtscEitSection eitSection(data, size);
1239 
1240 	if (!eitSection.isValid()) {
1241 		qCDebug(logEpg, "section is invalid");
1242 		return;
1243 	}
1244 
1245 	DvbChannel fakeChannel;
1246 	fakeChannel.source = source;
1247 	fakeChannel.transponder = transponder;
1248 	fakeChannel.networkId = eitSection.sourceId();
1249 	DvbSharedChannel channel = channelModel->findChannelById(fakeChannel);
1250 
1251 	if (!channel.isValid()) {
1252 		qCDebug(logEpg, "channel is invalid");
1253 		return;
1254 	}
1255 
1256 	qCDebug(logEpg, "Processing EIT section with size %d", size);
1257 
1258 	int entryCount = eitSection.entryCount();
1259 	// 1980-01-06T000000 minus 15 secs (= UTC - GPS in 2011)
1260 	QDateTime baseDateTime = QDateTime(QDate(1980, 1, 5), QTime(23, 59, 45), Qt::UTC);
1261 
1262 	AtscEitSectionEntry eitEntry = eitSection.entries();
1263 	for (int i = 0; i < entryCount; i++) {
1264 		if (!eitEntry.isValid())
1265 			break;
1266 		DvbEpgEntry epgEntry;
1267 		epgEntry.channel = channel;
1268 		epgEntry.begin = baseDateTime.addSecs(eitEntry.startTime());
1269 		epgEntry.duration = QTime(0, 0, 0).addSecs(eitEntry.duration());
1270 
1271 
1272 		DvbEpgLangEntry *langEntry;
1273 
1274 		/* Should be similar to DvbEpgFilter::getLangEntry */
1275 		if (!epgEntry.langEntry.contains(FIRST_LANG)) {
1276 			DvbEpgLangEntry e;
1277 			epgEntry.langEntry.insert(FIRST_LANG, e);
1278 		}
1279 		langEntry = &epgEntry.langEntry[FIRST_LANG];
1280 
1281 		langEntry->title = eitEntry.title();
1282 
1283 		quint32 id = ((quint32(fakeChannel.networkId) << 16) | quint32(eitEntry.eventId()));
1284 		DvbSharedEpgEntry entry = epgEntries.value(id);
1285 
1286 		entry = epgModel->addEntry(epgEntry);
1287 		epgEntries.insert(id, entry);
1288 		if ( i < entryCount -1)
1289 			eitEntry.advance();
1290 	}
1291 }
1292 
processEttSection(const char * data,int size)1293 void AtscEpgFilter::processEttSection(const char *data, int size)
1294 {
1295 	unsigned char tableId = data[0];
1296 
1297 	if (tableId != 0xcc) {
1298 		return;
1299 	}
1300 
1301 	AtscEttSection ettSection(data, size);
1302 
1303 	if (!ettSection.isValid() || (ettSection.messageType() != 0x02)) {
1304 		return;
1305 	}
1306 
1307 	quint32 id = ((quint32(ettSection.sourceId()) << 16) | quint32(ettSection.eventId()));
1308 	DvbSharedEpgEntry entry = epgEntries.value(id);
1309 
1310 	if (entry.isValid()) {
1311 		QString details = ettSection.text();
1312 
1313 		if (entry->details() != details) {
1314 			DvbEpgEntry modifiedEntry = *entry;
1315 
1316 			DvbEpgLangEntry *langEntry;
1317 
1318 			if (modifiedEntry.langEntry.contains(FIRST_LANG))
1319 				langEntry = &modifiedEntry.langEntry[FIRST_LANG];
1320 			else
1321 				langEntry = new(DvbEpgLangEntry);
1322 
1323 			langEntry->details = details;
1324 			entry = epgModel->addEntry(modifiedEntry);
1325 			epgEntries.insert(id, entry);
1326 		}
1327 	}
1328 }
1329