summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xresponsememblk.c180
-rwxr-xr-xsmarttvfactory.c357
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();