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"