File indexing completed on 2024-05-12 04:52:16
0001 /* 0002 * dvbrecording.cpp 0003 * 0004 * Copyright (C) 2009-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 <errno.h> 0024 0025 #include <QCoreApplication> 0026 #include <QDataStream> 0027 #include <QDir> 0028 #include <QEventLoop> 0029 #include <QMap> 0030 #include <QProcess> 0031 #include <QSet> 0032 #include <QStandardPaths> 0033 #include <QVariant> 0034 0035 #include "../ensurenopendingoperation.h" 0036 #include "dvbdevice.h" 0037 #include "dvbepg.h" 0038 #include "dvbliveview.h" 0039 #include "dvbmanager.h" 0040 #include "dvbrecording.h" 0041 #include "dvbrecording_p.h" 0042 #include "dvbtab.h" 0043 0044 bool DvbRecording::validate() 0045 { 0046 if (!name.isEmpty() && channel.isValid() && begin.isValid() && 0047 (begin.timeSpec() == Qt::UTC) && duration.isValid()) { 0048 // the seconds and milliseconds aren't visible --> set them to zero 0049 begin = begin.addMSecs(-(QTime(0, 0, 0).msecsTo(begin.time()) % 60000)); 0050 end = begin.addSecs(QTime(0, 0, 0).secsTo(duration)); 0051 beginEPG = beginEPG.addMSecs(-(QTime(0, 0, 0).msecsTo(beginEPG.time()) % 60000)); 0052 endEPG = beginEPG.addSecs(QTime(0, 0, 0).secsTo(durationEPG)); 0053 repeat &= ((1 << 7) - 1); 0054 return true; 0055 } 0056 0057 return false; 0058 } 0059 0060 DvbRecordingModel::DvbRecordingModel(DvbManager *manager_, QObject *parent) : QObject(parent), 0061 manager(manager_), hasPendingOperation(false) 0062 { 0063 sqlInit(QLatin1String("RecordingSchedule"), 0064 QStringList() << QLatin1String("Name") << QLatin1String("Channel") << QLatin1String("Begin") << 0065 QLatin1String("Duration") << QLatin1String("Repeat") << QLatin1String("Subheading") << QLatin1String("Details") 0066 << QLatin1String("beginEPG") << QLatin1String("endEPG") << QLatin1String("durationEPG") << QLatin1String("Priority") << QLatin1String("Disabled")); 0067 0068 // we regularly recheck the status of the recordings 0069 // this way we can keep retrying if the device was busy / tuning failed 0070 0071 startTimer(5000); 0072 0073 // compatibility code 0074 0075 QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/recordings.dvb")); 0076 0077 if (!file.exists()) { 0078 return; 0079 } 0080 0081 if (!file.open(QIODevice::ReadOnly)) { 0082 qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); 0083 return; 0084 } 0085 0086 DvbChannelModel *channelModel = manager->getChannelModel(); 0087 QDataStream stream(&file); 0088 stream.setVersion(QDataStream::Qt_4_4); 0089 int version; 0090 stream >> version; 0091 0092 if (version != 0x4d848730) { 0093 // the older version didn't store a magic number ... 0094 file.seek(0); 0095 version = 0; 0096 } 0097 0098 while (!stream.atEnd()) { 0099 DvbRecording recording; 0100 recording.disabled = false; 0101 QString channelName; 0102 stream >> channelName; 0103 recording.channel = channelModel->findChannelByName(channelName); 0104 stream >> recording.beginEPG; 0105 recording.begin = recording.beginEPG.toUTC(); 0106 recording.beginEPG = recording.beginEPG.toLocalTime(); 0107 stream >> recording.duration; 0108 recording.durationEPG = recording.duration; 0109 QDateTime end; 0110 stream >> end; 0111 0112 if (version != 0) { 0113 stream >> recording.repeat; 0114 } 0115 0116 stream >> recording.subheading; 0117 stream >> recording.details; 0118 0119 if (stream.status() != QDataStream::Ok) { 0120 qCWarning(logDvb, "Invalid recordings in file %s", qPrintable(file.fileName())); 0121 break; 0122 } 0123 0124 addRecording(recording); 0125 } 0126 0127 if (!file.remove()) { 0128 qCWarning(logDvb, "Cannot remove file %s", qPrintable(file.fileName())); 0129 } 0130 } 0131 0132 DvbRecordingModel::~DvbRecordingModel() 0133 { 0134 if (hasPendingOperation) { 0135 qCWarning(logDvb, "Illegal recursive call"); 0136 } 0137 0138 sqlFlush(); 0139 } 0140 0141 bool DvbRecordingModel::hasRecordings() const 0142 { 0143 return !recordings.isEmpty(); 0144 } 0145 0146 bool DvbRecordingModel::hasActiveRecordings() const 0147 { 0148 return !recordingFiles.isEmpty(); 0149 } 0150 0151 DvbSharedRecording DvbRecordingModel::findRecordingByKey(const SqlKey &sqlKey) const 0152 { 0153 return recordings.value(sqlKey); 0154 } 0155 0156 DvbRecording DvbRecordingModel::getCurrentRecording() 0157 { 0158 return currentRecording; 0159 } 0160 0161 void DvbRecordingModel::setCurrentRecording(DvbRecording _currentRecording) 0162 { 0163 currentRecording = _currentRecording; 0164 } 0165 0166 QMap<SqlKey, DvbSharedRecording> DvbRecordingModel::getRecordings() const 0167 { 0168 return recordings; 0169 } 0170 0171 QList<DvbSharedRecording> DvbRecordingModel::getUnwantedRecordings() const 0172 { 0173 return unwantedRecordings; 0174 } 0175 0176 DvbSharedRecording DvbRecordingModel::addRecording(DvbRecording &recording, bool checkForRecursion) 0177 { 0178 if (checkForRecursion) { 0179 if (hasPendingOperation) { 0180 qCWarning(logDvb, "Illegal recursive call"); 0181 return DvbSharedRecording(); 0182 } 0183 0184 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0185 } 0186 0187 if (!recording.validate()) { 0188 qCWarning(logDvb, "Invalid recording"); 0189 return DvbSharedRecording(); 0190 } 0191 0192 recording.setSqlKey(sqlFindFreeKey(recordings)); 0193 0194 if (!updateStatus(recording)) { 0195 return DvbSharedRecording(); 0196 } 0197 0198 DvbSharedRecording newRecording(new DvbRecording(recording)); 0199 recordings.insert(*newRecording, newRecording); 0200 sqlInsert(*newRecording); 0201 emit recordingAdded(newRecording); 0202 return newRecording; 0203 } 0204 0205 void DvbRecordingModel::updateRecording(DvbSharedRecording recording, 0206 DvbRecording &modifiedRecording) 0207 { 0208 if (hasPendingOperation) { 0209 qCWarning(logDvb, "Illegal recursive call"); 0210 return; 0211 } 0212 0213 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0214 0215 if (!recording.isValid() || (recordings.value(*recording) != recording) || 0216 !modifiedRecording.validate()) { 0217 qCWarning(logDvb, "Invalid recording"); 0218 return; 0219 } 0220 0221 modifiedRecording.setSqlKey(*recording); 0222 0223 if (!updateStatus(modifiedRecording)) { 0224 recordings.remove(*recording); 0225 recordingFiles.remove(*recording); 0226 sqlRemove(*recording); 0227 emit recordingRemoved(recording); 0228 return; 0229 } 0230 0231 emit recordingAboutToBeUpdated(recording); 0232 *const_cast<DvbRecording *>(recording.constData()) = modifiedRecording; 0233 sqlUpdate(*recording); 0234 emit recordingUpdated(recording); 0235 } 0236 0237 void DvbRecordingModel::removeRecording(DvbSharedRecording recording) 0238 { 0239 if (hasPendingOperation) { 0240 qCWarning(logDvb, "Illegal recursive call"); 0241 return; 0242 } 0243 0244 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0245 0246 if (!recording.isValid() || (recordings.value(*recording) != recording)) { 0247 qCWarning(logDvb, "Invalid recording"); 0248 return; 0249 } 0250 0251 recordings.remove(*recording); 0252 recordingFiles.remove(*recording); 0253 sqlRemove(*recording); 0254 emit recordingRemoved(recording); 0255 executeActionAfterRecording(*recording); 0256 findNewRecordings(); 0257 removeDuplicates(); 0258 disableConflicts(); 0259 } 0260 0261 0262 void DvbRecordingModel::disableLessImportant(DvbSharedRecording &recording1, DvbSharedRecording &recording2) 0263 { 0264 if (recording1->priority < recording2->priority) { 0265 DvbRecording rec1 = *(recording1.constData()); 0266 rec1.disabled = true; 0267 qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording1->name), qPrintable(recording2->name)); 0268 } 0269 if (recording2->priority < recording1->priority) { 0270 DvbRecording rec2 = *(recording1.constData()); 0271 rec2.disabled = true; 0272 qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording2->name), qPrintable(recording1->name)); 0273 } 0274 } 0275 0276 void DvbRecordingModel::addToUnwantedRecordings(DvbSharedRecording recording) 0277 { 0278 unwantedRecordings.append(recording); 0279 qCDebug(logDvb, "executed %s", qPrintable(recording->name)); 0280 } 0281 0282 void DvbRecordingModel::executeActionAfterRecording(DvbRecording recording) 0283 { 0284 QString stopCommand = manager->getActionAfterRecording(); 0285 0286 stopCommand.replace("%filename", recording.filename); 0287 if (!stopCommand.isEmpty()) 0288 { 0289 QProcess* child = new QProcess(); 0290 child->setProgram(stopCommand); 0291 child->start(); 0292 qCWarning(logDvb, "Not executing command after recording"); 0293 } 0294 qCDebug(logDvb, "executed."); 0295 0296 0297 } 0298 0299 void DvbRecordingModel::removeDuplicates() 0300 { 0301 QList<DvbSharedRecording> recordingList = QList<DvbSharedRecording>(); 0302 DvbEpgModel *epgModel = manager->getEpgModel(); 0303 0304 if (!epgModel) 0305 return; 0306 0307 QMap<DvbSharedRecording, DvbSharedEpgEntry> recordingMap = epgModel->getRecordings(); 0308 foreach(DvbSharedRecording key, recordings.values()) 0309 { 0310 recordingList.append(key); 0311 } 0312 int i = 0; 0313 foreach(DvbSharedRecording rec1, recordingList) 0314 { 0315 int j = 0; 0316 DvbRecording loopEntry1 = *rec1; 0317 foreach(DvbSharedRecording rec2, recordingList) 0318 { 0319 DvbRecording loopEntry2 = *rec2; 0320 if (i < j) { 0321 if (loopEntry1.begin == loopEntry2.begin 0322 && loopEntry1.duration == loopEntry2.duration 0323 && loopEntry1.channel->name == loopEntry2.channel->name 0324 && loopEntry1.name == loopEntry2.name) { 0325 recordings.remove(recordings.key(rec1)); 0326 recordingMap.remove(rec1); 0327 qCDebug(logDvb, "Removed. %s", qPrintable(loopEntry1.name)); 0328 } 0329 } 0330 j = j + 1; 0331 } 0332 i = i + 1; 0333 } 0334 epgModel->setRecordings(recordingMap); 0335 0336 qCDebug(logDvb, "executed."); 0337 0338 } 0339 0340 bool DvbRecordingModel::existsSimilarRecording(DvbEpgEntry recording) 0341 { 0342 bool found = false; 0343 0344 DvbEpgEntry entry = recording; 0345 DvbEpgModel *epgModel = manager->getEpgModel(); 0346 0347 if (!epgModel) 0348 return found; 0349 0350 QMap<DvbSharedRecording, DvbSharedEpgEntry> recordingMap = epgModel->getRecordings(); 0351 foreach(DvbSharedRecording key, recordingMap.keys()) 0352 { 0353 DvbEpgEntry loopEntry = *(recordingMap.value(key)); 0354 int loopLength = 60 * 60 * loopEntry.duration.hour() + 60 * loopEntry.duration.minute() + loopEntry.duration.second(); 0355 int length = 60 * 60 * entry.duration.hour() + 60 * entry.duration.minute() + entry.duration.second(); 0356 QDateTime end = entry.begin.addSecs(length); 0357 QDateTime loopEnd = loopEntry.begin.addSecs(loopLength); 0358 0359 if (QString::compare(entry.channel->name, loopEntry.channel->name) == 0) { 0360 // Is included in an existing recording 0361 if (entry.begin <= loopEntry.begin && end >= loopEnd) { 0362 found = true; 0363 break; 0364 // Includes an existing recording 0365 } else if (entry.begin >= loopEntry.begin && end <= loopEnd) { 0366 found = true; 0367 break; 0368 } 0369 } 0370 } 0371 0372 QList<DvbSharedRecording> unwantedRecordingsList = manager->getRecordingModel()->getUnwantedRecordings(); 0373 0374 foreach(DvbSharedRecording unwanted, unwantedRecordingsList) 0375 { 0376 DvbRecording loopEntry = *unwanted; 0377 if (QString::compare(entry.begin.toString(), ((loopEntry.begin.addSecs(manager->getBeginMargin()))).toString()) == 0 0378 && QString::compare(entry.channel->name, loopEntry.channel->name) == 0 0379 && QString::compare((entry.duration).toString(), 0380 loopEntry.duration.addSecs(- manager->getBeginMargin() - manager->getEndMargin()).toString()) == 0) { 0381 qCDebug(logDvb, "Found from unwanteds %s", qPrintable(loopEntry.name)); 0382 found = true; 0383 break; 0384 } 0385 } 0386 0387 return found; 0388 } 0389 0390 void DvbRecordingModel::disableConflicts() 0391 { 0392 int maxSize = 1; // manager->getDeviceConfigs().size(); 0393 // foreach(DvbDeviceConfig config, manager->getDeviceConfigs()) 0394 // { 0395 // maxSize = maxSize + config.numberOfTuners; 0396 // } 0397 0398 QList<DvbSharedRecording> recordingList = QList<DvbSharedRecording>(); 0399 foreach(DvbSharedRecording rec, recordings.values()) 0400 { 0401 if (!(rec->disabled)) { 0402 recordingList.append(rec); 0403 } 0404 } 0405 0406 foreach(DvbSharedRecording rec1, recordingList) 0407 { 0408 QList<DvbSharedRecording> conflictList = QList<DvbSharedRecording>(); 0409 conflictList.append(rec1); 0410 0411 foreach(DvbSharedRecording rec2, recordingList) 0412 { 0413 if (isInConflictWithAll(rec2, conflictList)) { 0414 conflictList.append(rec2); 0415 qCDebug(logDvb, "conflict: '%s' '%s' and '%s' '%s'", qPrintable(rec1->name), qPrintable(rec1->begin.toString()), qPrintable(rec2->name), qPrintable(rec2->begin.toString())); 0416 0417 } 0418 0419 } 0420 if (conflictList.size() > maxSize) { 0421 disableLeastImportants(conflictList); 0422 } 0423 } 0424 0425 } 0426 0427 0428 bool DvbRecordingModel::areInConflict(DvbSharedRecording recording1, DvbSharedRecording recording2) 0429 { 0430 int length1 = 60 * 60 * recording1->duration.hour() + 60 * recording1->duration.minute() + recording1->duration.second(); 0431 QDateTime end1 = recording1->begin.addSecs(length1); 0432 int length2 = 60 * 60 * recording2->duration.hour() + 60 * recording2->duration.minute() + recording2->duration.second(); 0433 QDateTime end2 = recording2->begin.addSecs(length2); 0434 if (!recording1->disabled && !recording1->disabled) { 0435 if (recording1->channel->transportStreamId != recording2->channel->transportStreamId) { 0436 if (recording2->begin > recording1->begin && recording2->begin < end1) { 0437 return true; 0438 } 0439 if (recording1->begin > recording2->begin && recording1->begin < end2) { 0440 return true; 0441 } 0442 } 0443 } 0444 return false; 0445 } 0446 bool DvbRecordingModel::isInConflictWithAll(DvbSharedRecording rec, QList<DvbSharedRecording> recList) 0447 { 0448 0449 foreach(DvbSharedRecording listRec, recList) 0450 { 0451 if (!areInConflict(rec, listRec)) { 0452 return false; 0453 } 0454 } 0455 0456 return true; 0457 } 0458 0459 int DvbRecordingModel::getNumberOfLeastImportants(QList<DvbSharedRecording> recList) 0460 { 0461 DvbSharedRecording leastImportantShared = getLeastImportant(recList); 0462 int leastImportance = leastImportantShared->priority; 0463 int count = 0; 0464 foreach(DvbSharedRecording listRecShared, recList) 0465 { 0466 if (listRecShared->priority == leastImportance) { 0467 count = count + 1; 0468 } 0469 } 0470 0471 return count; 0472 } 0473 0474 DvbSharedRecording DvbRecordingModel::getLeastImportant(QList<DvbSharedRecording> recList) 0475 { 0476 DvbSharedRecording leastImportant = recList.value(0); 0477 foreach(DvbSharedRecording listRec, recList) 0478 { 0479 qCDebug(logDvb, "name and priority %s %d", qPrintable(listRec->name), listRec->priority); 0480 if (listRec->priority < leastImportant->priority) { 0481 leastImportant = listRec; 0482 } 0483 } 0484 0485 qCDebug(logDvb, "least important: %s", qPrintable(leastImportant->name)); 0486 return leastImportant; 0487 } 0488 0489 void DvbRecordingModel::disableLeastImportants(QList<DvbSharedRecording> recList) 0490 { 0491 int numberOfLeastImportants = getNumberOfLeastImportants(recList); 0492 if (numberOfLeastImportants < recList.size()) { 0493 DvbSharedRecording leastImportantShared = getLeastImportant(recList); 0494 int leastImportance = leastImportantShared->priority; 0495 0496 foreach(DvbSharedRecording listRecShared, recList) 0497 { 0498 DvbRecording listRec = *(listRecShared.constData()); 0499 if (listRecShared->priority == leastImportance) { 0500 listRec.disabled = true; 0501 updateRecording(listRecShared, listRec); 0502 qCDebug(logDvb, "disabled: %s %s", qPrintable(listRec.name), qPrintable(listRec.begin.toString())); 0503 } 0504 } 0505 } 0506 0507 } 0508 0509 void DvbRecordingModel::findNewRecordings() 0510 { 0511 DvbEpgModel *epgModel = manager->getEpgModel(); 0512 0513 if (!epgModel) 0514 return; 0515 0516 QMap<DvbEpgEntryId, DvbSharedEpgEntry> epgMap = epgModel->getEntries(); 0517 foreach(DvbEpgEntryId key, epgMap.keys()) 0518 { 0519 DvbEpgEntry entry = *(epgMap.value(key)); 0520 QString title = entry.title(FIRST_LANG); 0521 QStringList regexList = manager->getRecordingRegexList(); 0522 int i = 0; 0523 foreach(QString regex, regexList) { 0524 QRegExp recordingRegex = QRegExp(regex); 0525 if (!recordingRegex.isEmpty()) 0526 { 0527 if (recordingRegex.indexIn(title) != -1) 0528 { 0529 if (!DvbRecordingModel::existsSimilarRecording(*epgMap.value(key))) 0530 { 0531 int priority = manager->getRecordingRegexPriorityList().value(i); 0532 epgModel->scheduleProgram(epgMap.value(key), manager->getBeginMargin(), 0533 manager->getEndMargin(), false, priority); 0534 qCDebug(logDvb, "scheduled %s", qPrintable(title)); 0535 } 0536 } 0537 } 0538 i = i + 1; 0539 } 0540 } 0541 0542 qCDebug(logDvb, "executed."); 0543 } 0544 0545 void DvbRecordingModel::timerEvent(QTimerEvent *event) 0546 { 0547 Q_UNUSED(event) 0548 QDateTime currentDateTime = QDateTime::currentDateTime().toUTC(); 0549 0550 foreach (const DvbSharedRecording &recording, recordings) { 0551 if (recording->end <= currentDateTime) { 0552 DvbRecording modifiedRecording = *recording; 0553 updateRecording(recording, modifiedRecording); 0554 } 0555 } 0556 0557 foreach (const DvbSharedRecording &recording, recordings) { 0558 if ((recording->status != DvbRecording::Recording) && 0559 (recording->begin <= currentDateTime)) { 0560 DvbRecording modifiedRecording = *recording; 0561 updateRecording(recording, modifiedRecording); 0562 } 0563 } 0564 } 0565 0566 void DvbRecordingModel::bindToSqlQuery(SqlKey sqlKey, QSqlQuery &query, int index) const 0567 { 0568 DvbSharedRecording recording = recordings.value(sqlKey); 0569 0570 if (!recording.isValid()) { 0571 qCWarning(logDvb, "Invalid recording"); 0572 return; 0573 } 0574 0575 query.bindValue(index++, recording->name); 0576 query.bindValue(index++, recording->channel->name); 0577 query.bindValue(index++, recording->begin.toString(Qt::ISODate) + QLatin1Char('Z')); 0578 query.bindValue(index++, recording->duration.toString(Qt::ISODate)); 0579 query.bindValue(index++, recording->repeat); 0580 query.bindValue(index++, recording->subheading); 0581 query.bindValue(index++, recording->details); 0582 query.bindValue(index++, recording->beginEPG.toString(Qt::ISODate)); 0583 query.bindValue(index++, recording->endEPG.toString(Qt::ISODate)); 0584 query.bindValue(index++, recording->durationEPG.toString(Qt::ISODate)); 0585 query.bindValue(index++, recording->priority); 0586 query.bindValue(index++, recording->disabled); 0587 } 0588 0589 bool DvbRecordingModel::insertFromSqlQuery(SqlKey sqlKey, const QSqlQuery &query, int index) 0590 { 0591 DvbRecording *recording = new DvbRecording(); 0592 DvbSharedRecording newRecording(recording); 0593 recording->name = query.value(index++).toString(); 0594 recording->channel = 0595 manager->getChannelModel()->findChannelByName(query.value(index++).toString()); 0596 recording->begin = 0597 QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toUTC(); 0598 recording->duration = QTime::fromString(query.value(index++).toString(), Qt::ISODate); 0599 recording->repeat = query.value(index++).toInt(); 0600 recording->subheading = query.value(index++).toString(); 0601 recording->details = query.value(index++).toString(); 0602 recording->beginEPG = 0603 QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); 0604 recording->endEPG = 0605 QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); 0606 recording->durationEPG = QTime::fromString(query.value(index++).toString(), Qt::ISODate); 0607 recording->priority = query.value(index++).toInt(); 0608 recording->disabled = query.value(index++).toBool(); 0609 0610 if (recording->validate()) { 0611 recording->setSqlKey(sqlKey); 0612 recordings.insert(*newRecording, newRecording); 0613 return true; 0614 } 0615 0616 return false; 0617 } 0618 0619 /* 0620 * Returns -1 if no upcoming recordings. 0621 */ 0622 int DvbRecordingModel::getSecondsUntilNextRecording() const 0623 { 0624 signed long timeUntil = -1; 0625 foreach (const DvbSharedRecording &recording, recordings) { 0626 DvbRecording rec = *recording; 0627 int length = 60 * 60 * rec.duration.hour() + 60 * rec.duration.minute() + rec.duration.second(); 0628 QDateTime end = rec.begin.addSecs(length); 0629 if (rec.disabled || end < QDateTime::currentDateTime().toUTC()) { 0630 continue; 0631 } 0632 if (end > QDateTime::currentDateTime().toUTC() && rec.begin <= QDateTime::currentDateTime().toUTC()) { 0633 timeUntil = 0; 0634 qCDebug(logDvb, "Rec ongoing %s", qPrintable(rec.name)); 0635 break; 0636 } 0637 if (rec.begin > QDateTime::currentDateTime().toUTC()) { 0638 if (timeUntil == -1 || timeUntil > rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t()) { 0639 timeUntil = rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t(); 0640 } 0641 } 0642 0643 } 0644 0645 qCDebug(logDvb, "returned TRUE %ld", timeUntil); 0646 return timeUntil; 0647 } 0648 0649 bool DvbRecordingModel::isScanWhenIdle() const 0650 { 0651 return manager->isScanWhenIdle(); 0652 } 0653 0654 bool DvbRecordingModel::shouldWeScanChannels() const 0655 { 0656 int numberOfChannels = manager->getChannelModel()->getChannels().size(); 0657 int idleTime = 1000 * 3600 + 1; // TODO 0658 if (idleTime > 1000 * 3600) { 0659 if (DvbRecordingModel::getSecondsUntilNextRecording() > numberOfChannels * 10) { 0660 if (DvbRecordingModel::isScanWhenIdle()) { 0661 qCDebug(logDvb, "Scan on Idle enabled"); 0662 return true; 0663 } 0664 } 0665 } 0666 0667 return false; 0668 } 0669 0670 void delay(int seconds) 0671 { 0672 QTime dieTime= QTime::currentTime().addSecs(seconds); 0673 while (QTime::currentTime() < dieTime) 0674 QCoreApplication::processEvents(QEventLoop::AllEvents, 100); 0675 0676 qCInfo(logDvb, "Delayed for %d seconds", seconds); 0677 } 0678 0679 void DvbRecordingModel::scanChannels() 0680 { 0681 qCDebug(logDvb, "auto-scan channels"); 0682 0683 if (shouldWeScanChannels()) { 0684 DvbChannelModel *channelModel = manager->getChannelModel(); 0685 QMap<int, DvbSharedChannel> channelMap = channelModel->getChannels(); 0686 foreach (int channelInt, channelMap.keys()) { 0687 DvbSharedChannel channel; 0688 0689 if (channelInt > 0) { 0690 channel = channelModel->findChannelByNumber(channelInt); 0691 } 0692 if (channel.isValid()) { 0693 // TODO update tab 0694 qCDebug(logDvb, "Executed %s", qPrintable(channel->name)); 0695 manager->getLiveView()->playChannel(channel); 0696 delay(5); 0697 } 0698 } 0699 } 0700 } 0701 0702 bool DvbRecordingModel::updateStatus(DvbRecording &recording) 0703 { 0704 QDateTime currentDateTimeLocal = QDateTime::currentDateTime(); 0705 QDateTime currentDateTime = currentDateTimeLocal.toUTC(); 0706 0707 if (recording.end <= currentDateTime) { 0708 if (recording.repeat == 0) { 0709 return false; 0710 } 0711 0712 recordingFiles.remove(recording); 0713 0714 // take care of DST switches 0715 QDateTime beginLocal = recording.begin.toLocalTime(); 0716 QDateTime endLocal = recording.end.toLocalTime(); 0717 int days = endLocal.daysTo(currentDateTimeLocal); 0718 0719 if (endLocal.addDays(days) <= currentDateTimeLocal) { 0720 ++days; 0721 } 0722 0723 // QDate::dayOfWeek() and our dayOfWeek differ by one 0724 int dayOfWeek = (beginLocal.date().dayOfWeek() - 1 + days); 0725 0726 for (int j = 0; j < 7; ++j) { 0727 if ((recording.repeat & (1 << (dayOfWeek % 7))) != 0) { 0728 break; 0729 } 0730 0731 ++days; 0732 ++dayOfWeek; 0733 } 0734 0735 recording.begin = beginLocal.addDays(days).toUTC(); 0736 recording.end = recording.begin.addSecs(QTime().secsTo(recording.duration)); 0737 } 0738 0739 if (recording.begin <= currentDateTime) { 0740 QExplicitlySharedDataPointer<DvbRecordingFile> recordingFile = 0741 recordingFiles.value(recording); 0742 0743 if (recordingFile.constData() == NULL) { 0744 recordingFile = new DvbRecordingFile(manager); 0745 recordingFiles.insert(recording, recordingFile); 0746 } 0747 0748 if (recordingFile->start(recording)) { 0749 recording.status = DvbRecording::Recording; 0750 } else { 0751 recording.status = DvbRecording::Error; 0752 } 0753 } else { 0754 recording.status = DvbRecording::Inactive; 0755 recordingFiles.remove(recording); 0756 } 0757 0758 return true; 0759 } 0760 0761 DvbRecordingFile::DvbRecordingFile(DvbManager *manager_) : manager(manager_), device(NULL), 0762 pmtValid(false) 0763 { 0764 connect(&pmtFilter, SIGNAL(pmtSectionChanged(QByteArray)), 0765 this, SLOT(pmtSectionChanged(QByteArray))); 0766 connect(&patPmtTimer, SIGNAL(timeout()), this, SLOT(insertPatPmt())); 0767 } 0768 0769 DvbRecordingFile::~DvbRecordingFile() 0770 { 0771 stop(); 0772 } 0773 0774 bool DvbRecordingFile::start(DvbRecording &recording) 0775 { 0776 if (recording.disabled) { 0777 return false; 0778 } 0779 0780 if (!file.isOpen()) { 0781 QString folder = manager->getRecordingFolder(); 0782 QDate currentDate = QDate::currentDate(); 0783 QTime currentTime = QTime::currentTime(); 0784 0785 QString filename = manager->getNamingFormat(); 0786 filename = filename.replace("%year", currentDate.toString("yyyy")); 0787 filename = filename.replace("%month", currentDate.toString("MM")); 0788 filename = filename.replace("%day", currentDate.toString("dd")); 0789 filename = filename.replace("%hour", currentTime.toString("hh")); 0790 filename = filename.replace("%min", currentTime.toString("mm")); 0791 filename = filename.replace("%sec", currentTime.toString("ss")); 0792 filename = filename.replace("%channel", recording.channel->name); 0793 filename = filename.replace("%title", QString(recording.name)); 0794 filename = filename.replace(QLatin1Char('/'), QLatin1Char('_')); 0795 if (filename.isEmpty()) { 0796 filename = QString(recording.name); 0797 } 0798 0799 QString path = folder + QLatin1Char('/') + filename; 0800 0801 0802 for (int attempt = 0; attempt < 100; ++attempt) { 0803 if (attempt == 0) { 0804 file.setFileName(path + QLatin1String(".m2t")); 0805 recording.filename = filename + QLatin1String(".m2t"); 0806 } else { 0807 file.setFileName(path + QLatin1Char('-') + QString::number(attempt) + 0808 QLatin1String(".m2t")); 0809 recording.filename = filename + QLatin1Char('-') + QString::number(attempt) + 0810 QLatin1String(".m2t"); 0811 } 0812 0813 if (file.exists()) { 0814 continue; 0815 } 0816 0817 if (file.open(QIODevice::WriteOnly)) { 0818 break; 0819 } else { 0820 qCWarning(logDvb, "Cannot open file %s. Error: %d", qPrintable(file.fileName()), errno); 0821 } 0822 0823 if ((attempt == 0) && !QDir(folder).exists()) { 0824 if (QDir().mkpath(folder)) { 0825 attempt = -1; 0826 continue; 0827 } else { 0828 qCWarning(logDvb, "Cannot create folder %s", qPrintable(folder)); 0829 } 0830 } 0831 0832 if (folder != QDir::homePath()) { 0833 folder = QDir::homePath(); 0834 path = folder + QLatin1Char('/') + 0835 QString(recording.name).replace(QLatin1Char('/'), QLatin1Char('_')); 0836 attempt = -1; 0837 continue; 0838 } 0839 0840 break; 0841 } 0842 0843 if (manager->createInfoFile()) { 0844 QString infoFile = path + ".txt"; 0845 QFile file(infoFile); 0846 if (file.open(QIODevice::ReadWrite)) 0847 { 0848 QTextStream stream(&file); 0849 #if QT_VERSION < 0x050e00 0850 stream << "EPG info" << endl; 0851 stream << "Title: " + QString(recording.name) << endl; 0852 stream << "Description: " + QString(recording.subheading) << endl; 0853 //stream << "Details: " + QString(recording.details) << endl; // Details is almost always empty 0854 stream << "Channel: " + QString(recording.channel->name) << endl; 0855 stream << "Date: " + recording.beginEPG.toLocalTime().toString("yyyy-MM-dd") << endl; 0856 stream << "Start time: " + recording.beginEPG.toLocalTime().toString("hh:mm:ss") << endl; 0857 stream << "Duration: " + recording.durationEPG.toString("HH:mm:ss") << endl; 0858 #else 0859 stream << "EPG info" << Qt::endl; 0860 stream << "Title: " + QString(recording.name) << Qt::endl; 0861 stream << "Description: " + QString(recording.subheading) << Qt::endl; 0862 //stream << "Details: " + QString(recording.details) << Qt::endl; // Details is almost always empty 0863 stream << "Channel: " + QString(recording.channel->name) << Qt::endl; 0864 stream << "Date: " + recording.beginEPG.toLocalTime().toString("yyyy-MM-dd") << Qt::endl; 0865 stream << "Start time: " + recording.beginEPG.toLocalTime().toString("hh:mm:ss") << Qt::endl; 0866 stream << "Duration: " + recording.durationEPG.toString("HH:mm:ss") << Qt::endl; 0867 #endif 0868 } 0869 } 0870 0871 if (!file.isOpen()) { 0872 qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); 0873 return false; 0874 } 0875 } 0876 0877 if (device == NULL) { 0878 channel = recording.channel; 0879 device = manager->requestDevice(channel->source, channel->transponder, 0880 DvbManager::Prioritized); 0881 0882 if (device == NULL) { 0883 qCWarning(logDvb, "Cannot find a suitable device"); 0884 return false; 0885 } 0886 0887 /* 0888 * When there's not enough devices to record while 0889 * watching, switch to the channel that will be recorded 0890 */ 0891 if (manager->hasReacquired()) 0892 manager->getLiveView()->playChannel(channel); 0893 0894 connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); 0895 pmtFilter.setProgramNumber(channel->serviceId); 0896 device->addSectionFilter(channel->pmtPid, &pmtFilter); 0897 pmtSectionData = channel->pmtSectionData; 0898 patGenerator.initPat(channel->transportStreamId, channel->serviceId, 0899 channel->pmtPid); 0900 0901 if (channel->isScrambled && !pmtSectionData.isEmpty()) { 0902 device->startDescrambling(pmtSectionData, this); 0903 } 0904 } 0905 0906 manager->getRecordingModel()->setCurrentRecording(recording); 0907 0908 return true; 0909 } 0910 0911 void DvbRecordingFile::stop() 0912 { 0913 if (device != NULL) { 0914 if (channel->isScrambled && !pmtSectionData.isEmpty()) { 0915 device->stopDescrambling(pmtSectionData, this); 0916 } 0917 0918 foreach (int pid, pids) { 0919 device->removePidFilter(pid, this); 0920 } 0921 0922 device->removeSectionFilter(channel->pmtPid, &pmtFilter); 0923 disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); 0924 manager->releaseDevice(device, DvbManager::Prioritized); 0925 device = NULL; 0926 } 0927 0928 pmtValid = false; 0929 patPmtTimer.stop(); 0930 patGenerator.reset(); 0931 pmtGenerator.reset(); 0932 pmtSectionData.clear(); 0933 pids.clear(); 0934 buffers.clear(); 0935 file.close(); 0936 channel = DvbSharedChannel(); 0937 0938 manager->getRecordingModel()->executeActionAfterRecording(manager->getRecordingModel()->getCurrentRecording()); 0939 manager->getRecordingModel()->findNewRecordings(); 0940 manager->getRecordingModel()->removeDuplicates(); 0941 manager->getRecordingModel()->disableConflicts(); 0942 } 0943 0944 void DvbRecordingFile::deviceStateChanged() 0945 { 0946 if (device->getDeviceState() == DvbDevice::DeviceReleased) { 0947 foreach (int pid, pids) { 0948 device->removePidFilter(pid, this); 0949 } 0950 0951 device->removeSectionFilter(channel->pmtPid, &pmtFilter); 0952 disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); 0953 0954 if (channel->isScrambled && !pmtSectionData.isEmpty()) { 0955 device->stopDescrambling(pmtSectionData, this); 0956 } 0957 0958 device = manager->requestDevice(channel->source, channel->transponder, 0959 DvbManager::Prioritized); 0960 0961 if (device != NULL) { 0962 connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); 0963 device->addSectionFilter(channel->pmtPid, &pmtFilter); 0964 0965 foreach (int pid, pids) { 0966 device->addPidFilter(pid, this); 0967 } 0968 0969 if (channel->isScrambled && !pmtSectionData.isEmpty()) { 0970 device->startDescrambling(pmtSectionData, this); 0971 } 0972 } else { 0973 // TODO 0974 0975 stop(); 0976 } 0977 } 0978 } 0979 0980 void DvbRecordingFile::pmtSectionChanged(const QByteArray &pmtSectionData_) 0981 { 0982 pmtSectionData = pmtSectionData_; 0983 DvbPmtSection pmtSection(pmtSectionData); 0984 DvbPmtParser pmtParser(pmtSection); 0985 int pcrPid = pmtSection.pcrPid(); 0986 QSet<int> newPids; 0987 0988 if (pmtParser.videoPid != -1) { 0989 newPids.insert(pmtParser.videoPid); 0990 } 0991 0992 for (int i = 0; i < pmtParser.audioPids.size(); ++i) { 0993 newPids.insert(pmtParser.audioPids.at(i).first); 0994 } 0995 0996 for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { 0997 newPids.insert(pmtParser.subtitlePids.at(i).first); 0998 } 0999 1000 if (pmtParser.teletextPid != -1) { 1001 newPids.insert(pmtParser.teletextPid); 1002 } 1003 1004 /* check PCR PID is set */ 1005 if (pcrPid != 0x1fff) { 1006 /* Check not already in list */ 1007 if (!newPids.contains(pcrPid)) 1008 newPids.insert(pcrPid); 1009 } 1010 1011 for (int i = 0; i < pids.size(); ++i) { 1012 int pid = pids.at(i); 1013 1014 if (!newPids.remove(pid)) { 1015 device->removePidFilter(pid, this); 1016 pids.removeAt(i); 1017 --i; 1018 } 1019 } 1020 1021 foreach (int pid, newPids) { 1022 device->addPidFilter(pid, this); 1023 pids.append(pid); 1024 } 1025 1026 pmtGenerator.initPmt(channel->pmtPid, pmtSection, pids); 1027 1028 if (!pmtValid) { 1029 pmtValid = true; 1030 file.write(patGenerator.generatePackets()); 1031 file.write(pmtGenerator.generatePackets()); 1032 1033 foreach (const QByteArray &buffer, buffers) { 1034 file.write(buffer); 1035 } 1036 1037 buffers.clear(); 1038 patPmtTimer.start(500); 1039 } 1040 1041 insertPatPmt(); 1042 1043 if (channel->isScrambled) { 1044 device->startDescrambling(pmtSectionData, this); 1045 } 1046 } 1047 1048 void DvbRecordingFile::insertPatPmt() 1049 { 1050 if (!pmtValid) { 1051 pmtSectionChanged(channel->pmtSectionData); 1052 return; 1053 } 1054 1055 file.write(patGenerator.generatePackets()); 1056 file.write(pmtGenerator.generatePackets()); 1057 } 1058 1059 void DvbRecordingFile::processData(const char data[188]) 1060 { 1061 if (!pmtValid) { 1062 if (!patPmtTimer.isActive()) { 1063 patPmtTimer.start(1000); 1064 QByteArray nextBuffer; 1065 nextBuffer.reserve(348 * 188); 1066 buffers.append(nextBuffer); 1067 } 1068 1069 QByteArray &buffer = buffers.last(); 1070 buffer.append(data, 188); 1071 1072 if (buffer.size() >= (348 * 188)) { 1073 QByteArray nextBuffer; 1074 nextBuffer.reserve(348 * 188); 1075 buffers.append(nextBuffer); 1076 } 1077 1078 return; 1079 } 1080 1081 file.write(data, 188); 1082 } 1083 1084 #include "moc_dvbrecording_p.cpp" 1085 #include "moc_dvbrecording.cpp"