#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "recorder.h" using namespace std; RecorderManager::RecorderManager(QObject */*parent*/, StreamStorage *storage) { streamStorage = storage; recList.setAutoDelete(false); kill = false; connect ( streamStorage, SIGNAL(storageEvent(int, int, bool)), this, SLOT(slotStorageEvent(int, int, bool)) ); connect( streamStorage, SIGNAL(recordInserted(ChangedRecord*)), this, SLOT(slotRecordInserted(ChangedRecord*)) ); connect( streamStorage, SIGNAL(recordUpdated(ChangedRecord*)), this, SLOT(slotRecordUpdated(ChangedRecord*)) ); connect( streamStorage, SIGNAL(recordRemoved(ChangedRecord*)), this, SLOT(slotRecordRemoved(ChangedRecord*)) ); QTimer *timer = new QTimer( this ); connect( timer, SIGNAL(timeout()), this, SLOT(timerEvent()) ); timer->start(10000, false); } RecorderManager::~RecorderManager() { kill = true; recList.setAutoDelete(true); recList.clear(); } void RecorderManager::timerEvent() { Recorder *recorder; QString errorMessage; Q3DictIterator i(recList); for(; i.current(); ++i) { recorder = i.current(); if (recorder) { if ( !recorder->checkSchedule(errorMessage) ) emit scheduleEvent(recorder->recName, errorMessage, false); } } } void RecorderManager::slotStorageEvent(int ident, int eventType, bool error) { if (ident == 105 || error) return; if (recList.count() != 0) // no storage event should occur during recording cerr << "TARGET warning: storage manipulation during recording" << endl; if (eventType == MStorage::loaded) { if (recList.count() != 0) // no storage event should occur during recording stopAllRecordings(); streamStorage->resetRecordList(); ValueList values(5); while ( streamStorage->getNextRecord(values) ) { if ( values.count() == 5 && values[0] == "recordings" ) handleNewRecord(values, false); // do not allow overwrite } } } void RecorderManager::handleNewRecord(ValueList &values, bool allowOverwrite) { QString errorMsg; bool result = scheduleRecording( values[1], values[2], values[3], values[4], errorMsg, allowOverwrite ); emit scheduleEvent(values[1], errorMsg, result); } bool RecorderManager::scheduleRecording(QString name, QString url, QString descr, QString /*handler*/, QString& errorMessage, bool allowOverwrite) { QDateTime startDt, stopDt; bool overwriting = false; // swap naming QString fileName = url; QString recName = name; url = descr; errorMessage = ""; // get scheduling info if ( !getUTime(recName, startDt, stopDt) ) { errorMessage = "no schedule info"; return false; } // ignore old recordings (empty message) if (QDateTime::currentDateTime() > stopDt) return false; // abort if record file exists and contains data QFile file(fileName); if (file.exists() && file.size() > 0) // existing recording { if (!allowOverwrite) // do not overwrite { errorMessage = "record file exists"; return false; } else overwriting = true; } // claim file, abort on fail if ( !file.exists() ) { bool isOpen = file.open(QIODevice::WriteOnly); if (isOpen) file.close(); else { errorMessage = "file access problem"; return false; } } assignRecorder(recName, url, fileName, startDt, stopDt); errorMessage = "scheduled"; if (overwriting) errorMessage += " (to overwrite!)"; return true; } void RecorderManager::slotRecordInserted(ChangedRecord* rec) { if (rec->ident == 105 || rec->error) return; if (rec->values[0] == "recordings") handleNewRecord(rec->values, false); // do not allow overwrite } void RecorderManager::slotRecordUpdated(ChangedRecord* rec) { QDateTime startDt, stopDt; QString errorMsg, recName; if (rec->ident == 105 || rec->error) return; if ( rec->values[0] == "recordings" && !getUTime(rec->values[1], startDt, stopDt) ) { emit scheduleEvent(rec->values[1], "no schedule info", false); return; } // no recording OR recording with parsed startDt, stopDt from here Recorder *recorder = recList.find(rec->oldValues[1]); if (recorder) // update values { recList.remove(recorder->recName); recorder->startDt = startDt; recorder->stopDt = stopDt; recorder->recName = rec->values[1]; recorder->fileName = rec->values[2]; recorder->url = rec->values[3]; recorder->handler = rec->values[4]; recList.insert(recorder->recName, recorder); emit scheduleEvent(rec->values[1], "rescheduled", true); } else if (rec->values[0] == "recordings" && QDateTime::currentDateTime() < stopDt ) // try schedule if pending recording handleNewRecord(rec->values, true); // allow overwrite } void RecorderManager::slotRecordRemoved(ChangedRecord* rec) { if (rec->ident == 105 || rec->error) return; // delete file if: folder=recording if ( rec->oldValues[0] == "recordings" ) { stopRecording(rec->values[1]); QFile(rec->oldValues[2]).remove(); } } // returns empty string on fail bool createRecordFile(QString& fileName, QString prefix, uint &index) { fileName = prefix + "_" + QString::number(index); QFile file(fileName); while ( file.exists() ) { index++; fileName = prefix + "_" + QString::number(index); file.setName(fileName); } bool isOpen = file.open(QIODevice::WriteOnly); if (isOpen) { file.close(); return true; } return false; } bool deleteRecordFile(QString fileName) { return QFile(fileName).remove(); } bool RecorderManager::createStreamItem(QString name, QString url, QString descr, QString handler) { QString errorMsg; ValueList values(5); values[s_folder] = "recordings"; values[s_name] = name; values[s_url] = url; values[s_descr] = descr; values[s_handler] = handler; return streamStorage->insertRecord(105, values, errorMsg); } bool RecorderManager::deleteStreamItem(QString name, QString url, QString descr, QString handler) { QString errorMsg; ValueList values(5); values[s_folder] = "recordings"; values[s_name] = name; values[s_url] = url; values[s_descr] = descr; values[s_handler] = handler; return streamStorage->removeRecord(105, values, errorMsg); } bool RecorderManager::getUTime(QString name, QDateTime& start, QDateTime& stop) { QRegExp expr; int i; bool test; int year, month, day, sHour, sMin, eHour, eMin; expr.setPattern("^REC.*(\\d{4})[/-]?(\\d{2})[/-]?(\\d{2}).*(\\d{2}):?(\\d{2}).*(\\d{2}):?(\\d{2})"); i = expr.search( name, 0 ); if ( i > -1 ) { year = expr.cap(1).toInt(&test); month = expr.cap(2).toInt(&test); day = expr.cap(3).toInt(&test); sHour = expr.cap(4).toInt(&test); sMin = expr.cap(5).toInt(&test); eHour = expr.cap(6).toInt(&test); eMin = expr.cap(7).toInt(&test); //cout << name << endl; //cout << " " << year << month << day << " " << sHour << sMin << " " << eHour << eMin << endl; start = QDateTime( QDate(year, month, day), QTime(sHour, sMin) ); stop = QDateTime( QDate(year, month, day), QTime(eHour, eMin) ); if ( stop < start ) stop = stop.addDays(1); return true; } else return false; } Recorder* RecorderManager::assignRecorder(QString recName, QString url, QString fileName, QDateTime startDt, QDateTime stopDt) { Recorder *recorder = new Recorder(this, recName, url, fileName, startDt, stopDt); connect ( recorder, SIGNAL( recordingStopped(Recorder*) ), this, SLOT( slotRecorderStopped(Recorder*) ) ); connect ( recorder, SIGNAL( recordingStarted(Recorder*) ), this, SLOT( slotRecorderStarted(Recorder*) ) ); recList.insert( recName, recorder ); return recorder; } // returns recordName (name of stream item entry in storage) // returns empty string on error QString RecorderManager::recordNow(QString url, QString name, uint seconds, QString& errorMessage) { errorMessage = ""; QDateTime startDt = QDateTime::currentDateTime(); QDateTime stopDt = startDt.addSecs(seconds); QString date = startDt.toString("yyyyMMdd"); QString sTime = startDt.toString("hhmm"); QString eTime = stopDt.toString("hhmm"); QString path = QString(getenv("HOME")) + "/."SUBPATH"/recordings/"; QString prefix = path + "REC_" + date + "_" + sTime + "_" + eTime; QString fileName = ""; QString recName = ""; QString handler = ""; uint index = 0; QDir dir(path); if (!dir.exists()) dir.mkdir(path); bool ready = false; while (!ready) { if ( createRecordFile(fileName, prefix, index) ) { recName = "REC" + QString::number(index) + " " + date + " " + sTime + " " + eTime + " " + name; ready = createStreamItem(recName, fileName, url, handler); if (!ready) QFile(fileName).remove(); if (index > 20) { errorMessage = "more than 20 REC files with prefix " + prefix + " OR stream repository problem"; fileName = ""; ready = true; } } else { errorMessage = "cannot create file " + fileName; fileName = ""; ready = true; } } if (fileName != "") { Recorder *recorder = assignRecorder(recName, url, fileName, startDt, stopDt); if ( !recorder->startRecording(errorMessage) ) { recList.remove(recName); QFile(fileName).remove(); deleteStreamItem(recName, url, "", ""); delete recorder; recName = ""; } } else recName = ""; return recName; } ItemStatus RecorderManager::getItemStatus(QString recName) { Recorder *recorder = recList.find(recName); if (recorder) if (recorder->isRecording) return recording; else return scheduled; else return recorded; } void RecorderManager::stopRecording(QString recName) { Recorder *recorder = recList.find(recName); if (recorder) { if ( !recList.remove(recName) ) cerr << TARGET": recorder instance not found in list"; recorder->stopRecording(); } } void RecorderManager::stopAllRecordings() { Recorder *recorder; Q3DictIterator i(recList); for(; i.current(); ++i) { recorder = i.current(); if (recorder) { recorder->stopRecording(); } } recList.clear(); } void RecorderManager::slotRecorderStarted(Recorder* recorder) { emit recordingStarted(recorder->recName); emit recorderActive(true); } void RecorderManager::slotRecorderStopped(Recorder* recorder) { // delete empty file on fail QString fileName = recorder->fileName; QFile file(fileName); if ( file.size() == 0 && file.remove() ) { deleteStreamItem( recorder->recName, fileName, "", "" ); emit scheduleEvent(recorder->recName, "Recording removed because it was empty", false); } recList.remove( recorder->recName ); // if not stopped by stopRecording call emit recordingStopped( recorder->recName, recorder->getStopReason() ); if (!kill) recorder->deleteLater(); // check recordings and report state Recorder *p_rec; bool oneActive = false; Q3DictIterator i(recList); for(; i.current(); ++i) { p_rec = i.current(); if (p_rec && p_rec->isRecording) oneActive = true; } if (!oneActive) emit recorderActive(false); } //----------------------------------------------------------------- Recorder::Recorder(QObject *parent, QString recName, QString url, QString fileName, QDateTime startDt, QDateTime stopDt) { myParent = parent; this->recName = recName; this->url = url; this->fileName = fileName; this->startDt = startDt; this->stopDt = stopDt; isRecording = false; reason = process; sawPlayerOutput = false; proc = NULL; } Recorder::~Recorder() { if (proc) { if ( proc->isRunning() ) proc->tryTerminate(); //QTimer::singleShot( 1000, proc, SLOT( kill() ) ); } } bool Recorder::checkSchedule(QString& errorMsg) { QDateTime now = QDateTime::currentDateTime(); if ( now >= startDt && now < stopDt && !isRecording ) return startRecording(errorMsg); if ( isRecording && now >= stopDt ) stopRecording(); return true; } bool Recorder::startRecording(QString& errorMsg) { if (proc != NULL) { errorMsg = "Already recording. Should not happen (bug)."; return false; } this->url = url; Q3Url qurl = Q3Url(url); if ( !qurl.isValid() || qurl.protocol() == "file" || qurl.isLocalFile() ) { errorMsg = "invalid URL: " + url; return false; } startStream(); return true; } void Recorder::stopRecording() { stopStream(); } void Recorder::parsePlayerOutput(const QString /*msg*/) { sawPlayerOutput = true; // assume player is responsible for unwanted abort } void Recorder::startStream() { if ( proc ) return; proc = new Q3Process( this ); proc->setCommunication( Q3Process::Stdin | Q3Process::Stdout| Q3Process::Stderr| Q3Process::DupStderr ); // proc->addArgument( "sh" ); // proc->addArgument( "-c" ); // proc->addArgument( "tail -c 100000000000000 -f /data/linuxhome/files/lessig | mplayer -" ); proc->addArgument( "mplayer"); QString file = Q3Url(url).fileName(); // file == "" matches find below if ( file != "" && QString("PLAYLIST").find(Q3Url(url).fileName().right(4), 0, false) != -1 ) { proc->addArgument( "-playlist" ); } proc->addArgument( url ); proc->addArgument( "-dumpstream"); proc->addArgument( "-dumpfile"); proc->addArgument( fileName); connect( proc, SIGNAL(readyReadStdout()), this, SLOT(readFromStdout()) ); connect( proc, SIGNAL(readyReadStderr()), this, SLOT(readFromStderr()) ); connect( proc, SIGNAL(processExited()), this, SLOT(streamExited()) ); if ( !proc->start() ) { fprintf( stderr, "error starting player\n" ); reason = process; streamExited(); } else { isRecording = true; recordingStarted(this); } } void Recorder::stopStream() { if ( proc && proc->isRunning() ) { reason = command; proc->tryTerminate(); } } void Recorder::readFromStdout() { QString val = ""; QString temp = " "; while ( temp != "" ) { temp = QString(proc->readStdout()); val += temp; } QStringList lines = QStringList::split( QRegExp("[\r\n|\r]"), val ); for ( QStringList::iterator line = lines.begin(); line != lines.end(); ++line ) parsePlayerOutput(*line); } void Recorder::readFromStderr() { // is rerouted to stdout } void Recorder::streamExited() { delete proc; proc = NULL; //cout << "recorder exited" << endl; // if the player failed blame it on the recorder if (sawPlayerOutput && reason == process) reason = recorder; isRecording = false; emit recordingStopped(this); }