diff options
-rwxr-xr-x | responsememblk.c | 180 | ||||
-rwxr-xr-x | smarttvfactory.c | 357 |
2 files changed, 528 insertions, 9 deletions
diff --git a/responsememblk.c b/responsememblk.c index 3cda494..203f706 100755 --- a/responsememblk.c +++ b/responsememblk.c @@ -1593,7 +1593,9 @@ uint64_t cResponseMemBlk::getVdrFileSize() { // common for all create xml file modules -int cResponseMemBlk::writeXmlItem(string name, string link, string programme, string desc, string guid, int no, time_t start, int dur, double fps, int is_pes, int is_new, string mime) { +int cResponseMemBlk::writeXmlItem(string name, string link, string programme, bool add_desc, string desc, + string guid, int no, time_t start, int dur, double fps, int is_pes, + int is_new, string mime) { string hdr = ""; char f[400]; @@ -1878,13 +1880,13 @@ int cResponseMemBlk::sendMediaXml (struct stat *statbuf) { cUrlEncode::doUrlSaveEncode(entries[i].sPath).c_str()); if (entries[i].sHaveMeta) { if (writeXmlItem(cUrlEncode::doXmlSaveEncode(entries[i].sTitle), pathbuf, - "NA", cUrlEncode::doXmlSaveEncode(entries[i].sLongDesc), + "NA", true, cUrlEncode::doXmlSaveEncode(entries[i].sLongDesc), cUrlEncode::doUrlSaveEncode(entries[i].sPath).c_str(), -1, entries[i].sStart, entries[i].sDuration, -1, -1, -1, entries[i].sMime) == ERROR) return ERROR; } else - if (writeXmlItem(cUrlEncode::doXmlSaveEncode(entries[i].sName), pathbuf, "NA", "NA", + if (writeXmlItem(cUrlEncode::doXmlSaveEncode(entries[i].sName), pathbuf, "NA", false, "NA", cUrlEncode::doUrlSaveEncode(entries[i].sPath).c_str(), -1, entries[i].sStart, -1, -1, -1, -1, entries[i].sMime) == ERROR) return ERROR; @@ -2237,7 +2239,8 @@ int cResponseMemBlk::sendChannelsXml (struct stat *statbuf) { string c_name = (group_sep != "") ? (group_sep + "~" + cUrlEncode::doXmlSaveEncode(channel->Name())) : cUrlEncode::doXmlSaveEncode(channel->Name()); // if (writeXmlItem(channel->Name(), link, title, desc, *(channel->GetChannelID()).ToString(), start_time, duration) == ERROR) - if (writeXmlItem(c_name, link, title, desc, *(channel->GetChannelID()).ToString(), channel->Number(), start_time, duration, -1, -1, -1, "video/mpeg") == ERROR) + if (writeXmlItem(c_name, link, title, add_desc, desc, *(channel->GetChannelID()).ToString(), + channel->Number(), start_time, duration, -1, -1, -1, "video/mpeg") == ERROR) return ERROR; } @@ -2431,6 +2434,173 @@ int cResponseMemBlk::sendEpgXml (struct stat *statbuf) { } +int cResponseMemBlk::GetRecordings( ) { + if (isHeadRequest()) + return OKAY; +#ifndef STANDALONE + + mResponseMessage = new string(); + *mResponseMessage = ""; + mResponseMessagePos = 0; + + mRequest->mConnState = SERVING; + + string own_ip = mRequest->getOwnIp(); + *(mLog->log()) << " OwnIP= " << own_ip << endl; + + vector<sQueryAVP> avps; + mRequest->parseQueryLine(&avps); + string guid = ""; + string dir = ""; + bool single_item = false; + string link_ext = ""; + string type = ""; + bool add_desc = false; + + char f[600]; + int item_count = 0; + int rec_dur = 0; + + list<string> dir_list; + + if (mRequest->getQueryAttributeValue(&avps, "dir", dir) == OKAY){ + dir = cUrlEncode::doUrlSaveDecode(dir); + *(mLog->log())<< DEBUGPREFIX + << " Found a dir Parameter: " << dir + << endl; + + size_t pos = 0; + size_t l_pos = 0; + // for (int i = 0; i <= l; i++) { + for (;;) { + pos = dir.find('~', l_pos ); + string d = dir.substr(l_pos, (pos -l_pos)); + + *(mLog->log()) << " (p= " << pos + << " l= " << l_pos + << " d= " << d + << ")" + << endl; + + dir_list.push_back(d); + + if (pos == string::npos) { + *(mLog->log()) << mLog->getTimeString() + << " Done " + << endl; + break; + } + + l_pos = pos +1; + } + *(mLog->log()) << endl; + } + + + cRecFolder* rec_db = mRequest->mFactory->GetRecDb(); + cRecFolder* rec_dir; + if (dir_list.size() == 0) + rec_dir = rec_db; + else + rec_dir = rec_db->GetFolder(&dir_list); + + // find folder + if (rec_dir != NULL) { + int res = rec_dir->writeXmlFolder(mResponseMessage, own_ip, mRequest->mServerPort); + sendHeaders(200, "OK", NULL, "application/xml", mResponseMessage->size(), -1); + return res; + } + else { + *mResponseMessage = ""; + sendError(400, "Bad Request", NULL, "00x Folder not found"); + return OKAY; + } + + // ------------------------------ + // allows requesting the recordings information for a single file only + if (mRequest->getQueryAttributeValue(&avps, "guid", guid) == OKAY){ + guid = cUrlEncode::doUrlSaveDecode(guid); + *(mLog->log())<< DEBUGPREFIX + << " Found a guid Parameter: " << guid + << endl; + + single_item = true; + } + + // allows requesting Urls with HLS or HAS type of manifest + if (mRequest->getQueryAttributeValue(&avps, "type", type) == OKAY){ + *(mLog->log())<< DEBUGPREFIX + << " Found a Type Parameter: " << type + << endl; + if (type == "hls") { + link_ext = "/manifest-seg.m3u8"; + } + if (type == "has") { + link_ext = "/manifest-seg.mpd"; + } + } + + + *(mLog->log())<< DEBUGPREFIX + << " generating /GetRecordings" + << endl; + + string hdr = ""; + hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + hdr += "<rss version=\"2.0\">\n"; + hdr+= "<channel>\n"; + hdr+= "<title>VDR Recordings List</title>\n"; + + *mResponseMessage += hdr; + cRecording *recording = NULL; + recording = Recordings.First(); + + while (recording != NULL) { + hdr = ""; + + if (recording->IsPesRecording() ) + snprintf(f, sizeof(f), "http://%s:%d%s", own_ip.c_str(), mRequest->mServerPort, + cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str()); + else + snprintf(f, sizeof(f), "http://%s:%d%s%s", own_ip.c_str(), mRequest->mServerPort, + cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(), link_ext.c_str()); + + string link = f; + string desc = "No description available"; + rec_dur = recording->LengthInSeconds(); + + string name = recording->Name(); + + if (recording->Info() != NULL) { + if ((recording->Info()->Description() != NULL) && add_desc) { + desc = cUrlEncode::doXmlSaveEncode(recording->Info()->Description()); + } + } + + if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", add_desc, desc, + cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(), + -1, + recording->Start(), rec_dur, recording->FramesPerSecond(), + (recording->IsPesRecording() ? 0: 1), (recording->IsNew() ? 0: 1), "video/mpeg") == ERROR) { + *mResponseMessage = ""; + sendError(500, "Internal Server Error", NULL, "005 writeXMLItem returned an error"); + return OKAY; + } + item_count ++; + recording = (!single_item) ? Recordings.Next(recording) : NULL; + } + + hdr = "</channel>\n"; + hdr += "</rss>\n"; + + *mResponseMessage += hdr; + + *(mLog->log())<< DEBUGPREFIX << " Recording Count= " <<item_count<< endl; + +#endif + sendHeaders(200, "OK", NULL, "application/xml", mResponseMessage->size(), -1); + return OKAY; +} int cResponseMemBlk::sendRecordingsXml(struct stat *statbuf) { if (isHeadRequest()) @@ -2612,7 +2782,7 @@ int cResponseMemBlk::sendRecordingsXml(struct stat *statbuf) { } } - if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", desc, + if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", add_desc, desc, cUrlEncode::doUrlSaveEncode(recording->FileName()).c_str(), -1, recording->Start(), rec_dur, recording->FramesPerSecond(), diff --git a/smarttvfactory.c b/smarttvfactory.c index 5265132..f0cb3d9 100755 --- a/smarttvfactory.c +++ b/smarttvfactory.c @@ -1,4 +1,4 @@ -/* + /* * smarttvfactory.c: VDR on Smart TV plugin * * Copyright (C) 2012 - 2014 T. Lohmar @@ -110,18 +110,270 @@ void cCmd::trim(string &t) { } +void cRecEntryBase::print(string pref) { + *(mLog->log()) << pref + << ": l= " << mLevel + << " B= " << mName + << endl; +}; + +void cRecEntry::print(string pref) { + *(mLog->log()) << pref + << ": l= " << mLevel + << " sfs= " << mSubfolders.size() + << " E= " << mTitle + << endl; +}; + +void cRecFolder::print(string pref) { + *(mLog->log()) << pref + << ": l= " << mLevel + << " es= " << mEntries.size() + << " F= " << mName + << endl; + for (list<cRecEntryBase*>::iterator iter = mEntries.begin(); iter != mEntries.end(); ++iter) + (*iter)->print(pref + "+" + mName); + +}; + +cRecEntry::cRecEntry(string n, int l, Log* lg, cRecording* r) : cRecEntryBase(n, l, false, lg), mRec(r), mSubfolders(), + mError(false), mTitle(n) { + + size_t pos = 0; + size_t l_pos = 0; + for (int i = 0; i <= l; i++) { + if (l_pos == string::npos) { + *(mLog->log()) << mLog->getTimeString() + << " ERROR: " + << " Name= " << n + << endl; + mError = true; + break; + } + pos = n.find('~', l_pos); + string dir = n.substr(l_pos, (pos -l_pos)); + + /* + *(mLog->log()) << " (p= " << pos + << " l= " << l_pos + << " d= " << dir + << ")"; +*/ + mSubfolders.push_back(dir); + + l_pos = pos +1; + } + // *(mLog->log()) << endl; + + *(mLog->log()) << mLog->getTimeString() + << " L= " << mLevel << " " << l + << " SFs= " << mSubfolders.size() + << " FName= " << mName + << endl; + + int idx = mSubfolders.size() -1; + if (idx != l) { + *(mLog->log()) << mLog->getTimeString() + << " ERROR: mName= " << mName + << " Level (" << l + << ") missmatches subfolders (" << mSubfolders.size() + << ")" + << endl; + } + else + mName = mSubfolders[idx]; +} + +int cRecEntry::writeXmlItem(string * msg, string own_ip, int own_port) { + string hdr = ""; + char f[400]; + + hdr += "<item>\n"; + hdr += "<title>" + mTitle +"</title>\n"; + hdr += "<isfolder>false</isfolder>\n"; + // mRec->IsPesRecording(); + + snprintf(f, sizeof(f), "http://%s:%d%s", own_ip.c_str(), own_port, + cUrlEncode::doUrlSaveEncode(mRec->FileName()).c_str()); + + string mime = "video/mpeg"; + string programme = "NA"; + string desc = "NA"; + int no = -1; + + hdr += "<enclosure url=\""; + hdr += f; + hdr += "\" type=\""+mime+"\" />\n"; + + hdr += "<guid>" + cUrlEncode::doUrlSaveEncode(mRec->FileName()) + "</guid>\n"; + + snprintf(f, sizeof(f), "%d", no); + hdr += "<number>"; + hdr += f; + hdr += "</number>\n"; + hdr += "<programme>" + programme +"</programme>\n"; + hdr += "<description>" + desc + "</description>\n"; + + snprintf(f, sizeof(f), "%ld", mRec->Start()); + hdr += "<start>"; + hdr += f; + hdr += "</start>\n"; + + snprintf(f, sizeof(f), "%d", mRec->LengthInSeconds()); + hdr += "<duration>"; + + hdr += f; + hdr += "</duration>\n"; + + snprintf(f, sizeof(f), "<fps>%.2f</fps>\n", mRec->FramesPerSecond()); + hdr += f; + + if (mRec->IsPesRecording()) + hdr += "<ispes>true</ispes>\n"; + else + hdr += "<ispes>false</ispes>\n"; + + if (mRec->IsNew()) + hdr += "<isnew>true</isnew>\n"; + else + hdr += "<isnew>false</isnew>\n"; + + hdr += "</item>\n"; + + *msg += hdr; + return 0; +} + +int cRecFolder::writeXmlItem(string * msg, string own_ip, int own_port) { + string hdr = ""; + + hdr += "<item>\n"; + hdr += "<title>" + mName +"</title>\n"; + hdr += "<guid>" + cUrlEncode::doUrlSaveEncode(mPath) + "</guid>\n"; + + hdr += "<isfolder>true</isfolder>\n"; + hdr += "</item>\n"; + + *msg += hdr; + return 0; +} + +int cRecFolder::writeXmlFolder(string* msg, string own_ip, int own_port) { + string hdr = ""; + hdr += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + hdr += "<rss version=\"2.0\">\n"; + hdr+= "<channel>\n"; + hdr+= "<title>VDR Recordings List</title>\n"; + + *msg += hdr; + + //iter over all items + for (list<cRecEntryBase*>::iterator iter = mEntries.begin(); iter != mEntries.end(); ++iter) { + (*iter)->writeXmlItem(msg, own_ip, own_port); + } + + hdr = "</channel>\n"; + hdr += "</rss>\n"; + + *msg += hdr; + + // *(mLog->log())<< DEBUGPREFIX << " Recording Count= " <<item_count<< endl; + + return OKAY; + +} + +void cRecFolder::appendEntry(cRecFolder* entry) { + *(mLog->log()) << mLog->getTimeString() << " appendEntry " + << " mName= " << mName + << " ol= " << mLevel + << " Folder" + << " l= " << entry->mLevel + << " Name= " << entry->mName + << endl; + mEntries.push_back(entry); +} + + +void cRecFolder::appendEntry(cRecEntry* entry) { + + // root: append to mEntries + if (entry->mLevel == mLevel) { + *(mLog->log()) << mLog->getTimeString() << " appendEntry " + << " mName= " << mName + << " ol= " << mLevel + << " Entry" + << " l= " << entry->mLevel + << " Name= " << entry->mName + << endl; + + mEntries.push_back(entry); + return; + } + + *(mLog->log()) << mLog->getTimeString() << " - appendEntry " + << " mName= " << mName + << " ol= " << mLevel + << " Entry" + << " l= " << entry->mLevel + << " sf= " << (entry->mSubfolders).size() + << " f= " << entry->mSubfolders[mLevel] + << " Name= " << entry->mName + << endl; + + // find needed subfolder + bool found = false; + for (list<cRecEntryBase*>::iterator iter= mEntries.begin(); iter != mEntries.end(); ++iter) { + if (((*iter)->mName == entry->mSubfolders[mLevel]) && ((*iter)->mIsFolder)) { + ((cRecFolder*)(*iter))->appendEntry(entry); + found = true; + break; + } + } + if (!found) { + string p = ((mPath == "") ? entry->mSubfolders[mLevel] : mPath + "~" + entry->mSubfolders[mLevel]); + cRecFolder* folder = new cRecFolder(entry->mSubfolders[mLevel], p, mLevel +1, mLog); + appendEntry(folder); + folder->appendEntry(entry); + } + // check, if a subfolder is needed +} + +cRecFolder* cRecFolder::GetFolder(list<string> *folder_list) { + string name = folder_list->front(); + cRecFolder* dir = NULL; + *(mLog->log()) << mLog->getTimeString() + << " GetFolder name= " << name << endl; + for (list<cRecEntryBase*>::iterator iter= mEntries.begin(); iter != mEntries.end(); ++iter) { + if (((*iter)->mName == name) && ((*iter)->mIsFolder)) { + dir = (cRecFolder*)(*iter); + break; + } + } + if (dir != NULL) { + folder_list->pop_front(); + if (folder_list->size() != 0) + return dir->GetFolder(folder_list); + else + return dir; + } + return NULL; +} SmartTvServer::SmartTvServer(): cStatus(), mRequestCount(0), isInited(false), serverPort(PORT), mServerFd(-1), mSegmentDuration(10), mHasMinBufferTime(40), mLiveChannels(20), clientList(), mConTvClients(), mRecCmds(), mCmdCmds(), mRecMsg(), mCmdMsg(), mActiveSessions(0), mHttpClientId(0), mConfig(NULL), mMaxFd(0), - mManagedUrls(NULL) { + mManagedUrls(NULL), mActRecordings(), mRecordings(NULL), mRecState(0) { + } SmartTvServer::~SmartTvServer() { + delete mRecordings; + if (mConfig != NULL) delete mConfig; @@ -158,6 +410,15 @@ void SmartTvServer::Recording(const cDevice *Device, const char *Name, const cha name = Name; string method = (On) ? "RECSTART" : "RECSTOP"; + + // keep track of active recordings + if (FileName != NULL) { + if (On) + AddActRecording(name, FileName); + else + DelActRecording(name, FileName); + } + string guid = (FileName == NULL) ? "" : cUrlEncode::doUrlSaveEncode(FileName); msg << "{\"type\":\""+method+"\",\"name\":\"" << name << "\",\"guid\":\""+guid+"\"}"; @@ -180,6 +441,46 @@ void SmartTvServer::Recording(const cDevice *Device, const char *Name, const cha } }; + //TODO: Store active recordings in a Database + // The database should contain only active recordings + // database for active recordings: entry is FileName (i.e. guid) + // store only, when FileName is not NULL + +void SmartTvServer::AddActRecording(string n, string fn) { + mActRecordings.push_back(new cActiveRecording(n, fn)); + *(mLog.log()) << mLog.getTimeString() + << " AddActRecording fn= " << fn + << " CurListSize= " << mActRecordings.size() + << endl; +} + +void SmartTvServer::DelActRecording(string n, string fn) { + int del_count =0; + for (list<cActiveRecording*>::iterator itr = mActRecordings.begin(); itr != mActRecordings.end(); /*nothing*/) { + if ((*itr)->mFilename == fn) { + itr = mActRecordings.erase(itr); + del_count ++; + } + else + ++itr; + } + + *(mLog.log()) << mLog.getTimeString() + << " DelActRecording Deleted " << n + << " Occurances= " << del_count + << " CurListSize= " << mActRecordings.size() + << endl; +} + +bool SmartTvServer::IsActRecording(string fn) { + for (list<cActiveRecording*>::iterator itr = mActRecordings.begin(); itr != mActRecordings.end(); ++itr) { + if ((*itr)->mFilename == fn) { + return true; + } + } + return false; +} + //thlo: Try to clean up void SmartTvServer::pushToClients(cHttpResourceBase* resource) { for (uint i = 0; i < mConTvClients.size(); i ++) { @@ -527,6 +828,53 @@ void SmartTvServer::acceptHttpResource(int &req_id) { } + +cRecFolder* SmartTvServer::GetRecDb() { + bool changed = Recordings.StateChanged(mRecState); + *(mLog.log()) << mLog.getTimeString() + << " GetRecDb Changed= " << ((changed) ? "Yes" : "No") + << endl; + if (changed) { + + delete mRecordings; + mRecordings = new cRecFolder(".", "", 0, &mLog); + CreateRecDb(); + } + return mRecordings; +} + +void SmartTvServer::CreateRecDb() { + cRecording *recording = Recordings.First(); + *(mLog.log()) << mLog.getTimeString() << ": CreateRecDb " + << " NewState= " << mRecState + << endl; + + while (recording != NULL) { + string name = recording->Name(); + + // (mRecordings->mEntries).push_back(new cRecEntry(recording->Name(), recording->HierarchyLevels())); + cRecEntry* entry = new cRecEntry(recording->Name(), recording->HierarchyLevels(), &mLog, recording); + mRecordings->appendEntry(entry); + // (mRecordings->mEntries).appendEntry(entry, 0); + + /* + *(mLog.log()) << mLog.getTimeString() + << " L= " << recording->HierarchyLevels() + << " FName= " << recording->Name() + << endl; +*/ + recording = Recordings.Next(recording); + } + + + *(mLog.log()) << " Summary " << endl; + mRecordings->print(""); + + + *(mLog.log()) << " Summary -done " << endl; + +} + void SmartTvServer::loop() { int req_id = 0; int ret = 0; @@ -730,9 +1078,10 @@ void SmartTvServer::initServer(string dir) { cout << "SmartTvWeb: Listening on port= " << PORT << endl; #endif - - mConfig->printConfig(); + mRecordings = new cRecFolder(".", "", 0, &mLog); + + mConfig->printConfig(); mSegmentDuration= mConfig->getSegmentDuration(); mHasMinBufferTime= mConfig->getHasMinBufferTime(); |