mirror of
https://projects.vdr-developer.org/git/vdr-plugin-streamdev.git
synced 2023-10-10 19:16:51 +02:00
Restructured menuHTTP classes
This commit is contained in:
parent
9bbb74b7fd
commit
d3dd72072c
1
HISTORY
1
HISTORY
@ -1,6 +1,7 @@
|
|||||||
VDR Plugin 'streamdev' Revision History
|
VDR Plugin 'streamdev' Revision History
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
- Restructured menuHTTP classes
|
||||||
- Added RSS format for HTTP menus
|
- Added RSS format for HTTP menus
|
||||||
- Recordings can now also be selected by struct stat "st_dev:st_ino.rec"
|
- Recordings can now also be selected by struct stat "st_dev:st_ino.rec"
|
||||||
- Implemented multi-device support for streamdev client (suggested by johns)
|
- Implemented multi-device support for streamdev client (suggested by johns)
|
||||||
|
@ -25,7 +25,7 @@ cConnectionHTTP::cConnectionHTTP(void):
|
|||||||
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
||||||
m_Channel(NULL),
|
m_Channel(NULL),
|
||||||
m_Recording(NULL),
|
m_Recording(NULL),
|
||||||
m_ChannelList(NULL)
|
m_MenuList(NULL)
|
||||||
{
|
{
|
||||||
Dprintf("constructor hsRequest\n");
|
Dprintf("constructor hsRequest\n");
|
||||||
m_Apid[0] = m_Apid[1] = 0;
|
m_Apid[0] = m_Apid[1] = 0;
|
||||||
@ -169,8 +169,8 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
// should never happen
|
// should never happen
|
||||||
esyslog("streamdev-server connectionHTTP: Missing method or pathinfo");
|
esyslog("streamdev-server connectionHTTP: Missing method or pathinfo");
|
||||||
} else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) {
|
} else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) {
|
||||||
if (m_ChannelList)
|
if (m_MenuList)
|
||||||
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
|
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
|
||||||
else if (m_Channel != NULL) {
|
else if (m_Channel != NULL) {
|
||||||
cDevice *device = NULL;
|
cDevice *device = NULL;
|
||||||
if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority))
|
if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority))
|
||||||
@ -217,9 +217,9 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
return HttpResponse(404, true);
|
return HttpResponse(404, true);
|
||||||
}
|
}
|
||||||
} else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
|
} else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
|
||||||
if (m_ChannelList) {
|
if (m_MenuList) {
|
||||||
DeferClose();
|
DeferClose();
|
||||||
return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
|
return Respond("%s", true, m_MenuList->HttpHeader().c_str());
|
||||||
}
|
}
|
||||||
else if (m_Channel != NULL) {
|
else if (m_Channel != NULL) {
|
||||||
if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
|
if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) {
|
||||||
@ -363,13 +363,13 @@ void cConnectionHTTP::Flushed(void)
|
|||||||
if (m_Status != hsBody)
|
if (m_Status != hsBody)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_ChannelList) {
|
if (m_MenuList) {
|
||||||
if (m_ChannelList->HasNext()) {
|
if (m_MenuList->HasNext()) {
|
||||||
if (!Respond("%s", true, m_ChannelList->Next().c_str()))
|
if (!Respond("%s", true, m_MenuList->Next().c_str()))
|
||||||
DeferClose();
|
DeferClose();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DELETENULL(m_ChannelList);
|
DELETENULL(m_MenuList);
|
||||||
m_Status = hsFinished;
|
m_Status = hsFinished;
|
||||||
DeferClose();
|
DeferClose();
|
||||||
}
|
}
|
||||||
@ -387,28 +387,22 @@ void cConnectionHTTP::Flushed(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
|
cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
|
||||||
{
|
{
|
||||||
std::string groupTarget;
|
std::string groupTarget;
|
||||||
cChannelIterator *iterator = NULL;
|
cItemIterator *iterator = NULL;
|
||||||
|
|
||||||
const static std::string GROUP("group");
|
const static std::string GROUP("group");
|
||||||
if (Filebase.compare("tree") == 0) {
|
if (Filebase.compare("tree") == 0) {
|
||||||
const cChannel* c = NULL;
|
|
||||||
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
||||||
if (it != m_Params.end())
|
iterator = new cListTree(it == m_Params.end() ? NULL : it->second.c_str());
|
||||||
c = cChannelList::GetGroup(atoi(it->second.c_str()));
|
|
||||||
iterator = new cListTree(c);
|
|
||||||
groupTarget = Filebase + Fileext;
|
groupTarget = Filebase + Fileext;
|
||||||
} else if (Filebase.compare("groups") == 0) {
|
} else if (Filebase.compare("groups") == 0) {
|
||||||
iterator = new cListGroups();
|
iterator = new cListGroups();
|
||||||
groupTarget = (std::string) "group" + Fileext;
|
groupTarget = (std::string) "group" + Fileext;
|
||||||
} else if (Filebase.compare("group") == 0) {
|
} else if (Filebase.compare("group") == 0) {
|
||||||
const cChannel* c = NULL;
|
|
||||||
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
tStrStrMap::const_iterator it = m_Params.find(GROUP);
|
||||||
if (it != m_Params.end())
|
iterator = new cListGroup(it == m_Params.end() ? NULL : it->second.c_str());
|
||||||
c = cChannelList::GetGroup(atoi(it->second.c_str()));
|
|
||||||
iterator = new cListGroup(c);
|
|
||||||
} else if (Filebase.compare("channels") == 0) {
|
} else if (Filebase.compare("channels") == 0) {
|
||||||
iterator = new cListChannels();
|
iterator = new cListChannels();
|
||||||
} else if (Filebase.compare("all") == 0 ||
|
} else if (Filebase.compare("all") == 0 ||
|
||||||
@ -436,16 +430,16 @@ cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, co
|
|||||||
self += '?' + it->second;
|
self += '?' + it->second;
|
||||||
rss += '?' + it->second;
|
rss += '?' + it->second;
|
||||||
}
|
}
|
||||||
return new cHtmlChannelList(iterator, m_StreamType, self.c_str(), rss.c_str(), groupTarget.c_str());
|
return new cHtmlMenuList(iterator, m_StreamType, self.c_str(), rss.c_str(), groupTarget.c_str());
|
||||||
} else if (Fileext.compare(".m3u") == 0) {
|
} else if (Fileext.compare(".m3u") == 0) {
|
||||||
return new cM3uChannelList(iterator, base.c_str());
|
return new cM3uMenuList(iterator, base.c_str());
|
||||||
} else if (Fileext.compare(".rss") == 0) {
|
} else if (Fileext.compare(".rss") == 0) {
|
||||||
std::string html = Filebase + ".html";
|
std::string html = Filebase + ".html";
|
||||||
tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
|
tStrStrMap::const_iterator it = Headers().find("QUERY_STRING");
|
||||||
if (it != Headers().end() && !it->second.empty()) {
|
if (it != Headers().end() && !it->second.empty()) {
|
||||||
html += '?' + it->second;
|
html += '?' + it->second;
|
||||||
}
|
}
|
||||||
return new cRssChannelList(iterator, base.c_str(), html.c_str());
|
return new cRssMenuList(iterator, base.c_str(), html.c_str());
|
||||||
} else {
|
} else {
|
||||||
delete iterator;
|
delete iterator;
|
||||||
}
|
}
|
||||||
@ -520,7 +514,7 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
|
|||||||
|
|
||||||
Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());
|
Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());
|
||||||
|
|
||||||
if ((m_ChannelList = ChannelListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
|
if ((m_MenuList = MenuListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
|
||||||
Dprintf("Channel list requested\n");
|
Dprintf("Channel list requested\n");
|
||||||
return true;
|
return true;
|
||||||
} else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) {
|
} else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <tools/select.h>
|
#include <tools/select.h>
|
||||||
|
|
||||||
class cChannel;
|
class cChannel;
|
||||||
class cChannelList;
|
class cMenuList;
|
||||||
|
|
||||||
class cConnectionHTTP: public cServerConnection {
|
class cConnectionHTTP: public cServerConnection {
|
||||||
private:
|
private:
|
||||||
@ -36,9 +36,9 @@ private:
|
|||||||
// job: replay
|
// job: replay
|
||||||
cRecording *m_Recording;
|
cRecording *m_Recording;
|
||||||
// job: listing
|
// job: listing
|
||||||
cChannelList *m_ChannelList;
|
cMenuList *m_MenuList;
|
||||||
|
|
||||||
cChannelList* ChannelListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
|
cMenuList* MenuListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const;
|
||||||
cRecording* RecordingFromString(const char* FileBase, const char* FileExt) const;
|
cRecording* RecordingFromString(const char* FileBase, const char* FileExt) const;
|
||||||
|
|
||||||
bool ProcessURI(const std::string &PathInfo);
|
bool ProcessURI(const std::string &PathInfo);
|
||||||
|
@ -2,16 +2,60 @@
|
|||||||
#include "server/menuHTTP.h"
|
#include "server/menuHTTP.h"
|
||||||
|
|
||||||
//**************************** cChannelIterator **************
|
//**************************** cChannelIterator **************
|
||||||
cChannelIterator::cChannelIterator(const cChannel *First): channel(First)
|
cChannelIterator::cChannelIterator(const cChannel *First)
|
||||||
{}
|
{
|
||||||
|
first = First;
|
||||||
|
current = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
const cChannel* cChannelIterator::Next()
|
bool cChannelIterator::Next()
|
||||||
{
|
{
|
||||||
const cChannel *current = channel;
|
if (first)
|
||||||
channel = NextChannel(channel);
|
{
|
||||||
|
current = first;
|
||||||
|
first = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
current = NextChannel(current);
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cString cChannelIterator::ItemId() const
|
||||||
|
{
|
||||||
|
if (current)
|
||||||
|
{
|
||||||
|
if (current->GroupSep())
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
|
||||||
|
{
|
||||||
|
if (Channels.Get(curr) == current)
|
||||||
|
return itoa(index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return itoa(current->Number());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cString("-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cChannel* cChannelIterator::GetGroup(const char* GroupId)
|
||||||
|
{
|
||||||
|
int group = -1;
|
||||||
|
if (GroupId)
|
||||||
|
{
|
||||||
|
int Index = atoi(GroupId);
|
||||||
|
group = Channels.GetNextGroup(-1);
|
||||||
|
while (Index-- && group >= 0)
|
||||||
|
group = Channels.GetNextGroup(group);
|
||||||
|
}
|
||||||
|
return group >= 0 ? Channels.Get(group) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//**************************** cListAll **************
|
//**************************** cListAll **************
|
||||||
cListAll::cListAll(): cChannelIterator(Channels.First())
|
cListAll::cListAll(): cChannelIterator(Channels.First())
|
||||||
{}
|
{}
|
||||||
@ -46,7 +90,7 @@ const cChannel* cListGroups::NextChannel(const cChannel *Channel)
|
|||||||
}
|
}
|
||||||
//
|
//
|
||||||
// ********************* cListGroup ****************
|
// ********************* cListGroup ****************
|
||||||
cListGroup::cListGroup(const cChannel *Group): cChannelIterator(GetNextChannelInGroup(Group))
|
cListGroup::cListGroup(const char *GroupId): cChannelIterator(GetNextChannelInGroup(GetGroup(GroupId)))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel)
|
const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel)
|
||||||
@ -62,9 +106,9 @@ const cChannel* cListGroup::NextChannel(const cChannel *Channel)
|
|||||||
}
|
}
|
||||||
//
|
//
|
||||||
// ********************* cListTree ****************
|
// ********************* cListTree ****************
|
||||||
cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
cListTree::cListTree(const char *SelectedGroupId): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1)))
|
||||||
{
|
{
|
||||||
selectedGroup = SelectedGroup;
|
selectedGroup = GetGroup(SelectedGroupId);
|
||||||
currentGroup = Channels.Get(Channels.GetNextGroup(-1));
|
currentGroup = Channels.Get(Channels.GetNextGroup(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,43 +130,23 @@ const cChannel* cListTree::NextChannel(const cChannel *Channel)
|
|||||||
return Channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************** cChannelList ******************
|
// ******************** cMenuList ******************
|
||||||
cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator)
|
cMenuList::cMenuList(cItemIterator *Iterator) : iterator(Iterator)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
cChannelList::~cChannelList()
|
cMenuList::~cMenuList()
|
||||||
{
|
{
|
||||||
delete iterator;
|
delete iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannelList::GetGroupIndex(const cChannel *Group)
|
// ******************** cHtmlMenuList ******************
|
||||||
{
|
const char* cHtmlMenuList::menu =
|
||||||
int index = 0;
|
|
||||||
for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr))
|
|
||||||
{
|
|
||||||
if (Channels.Get(curr) == Group)
|
|
||||||
return index;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cChannel* cChannelList::GetGroup(int Index)
|
|
||||||
{
|
|
||||||
int group = Channels.GetNextGroup(-1);
|
|
||||||
while (Index-- && group >= 0)
|
|
||||||
group = Channels.GetNextGroup(group);
|
|
||||||
return group >= 0 ? Channels.Get(group) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ******************** cHtmlChannelList ******************
|
|
||||||
const char* cHtmlChannelList::menu =
|
|
||||||
"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
|
"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
|
||||||
"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
|
"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
|
||||||
"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a> | <a href=\"groups.rss\">RSS</a>)] "
|
"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a> | <a href=\"groups.rss\">RSS</a>)] "
|
||||||
"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a> | <a href=\"channels.rss\">RSS</a>)] ";
|
"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a> | <a href=\"channels.rss\">RSS</a>)] ";
|
||||||
|
|
||||||
const char* cHtmlChannelList::css =
|
const char* cHtmlMenuList::css =
|
||||||
"<style type=\"text/css\">\n"
|
"<style type=\"text/css\">\n"
|
||||||
"<!--\n"
|
"<!--\n"
|
||||||
"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
|
"a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n"
|
||||||
@ -138,7 +162,7 @@ const char* cHtmlChannelList::css =
|
|||||||
"-->\n"
|
"-->\n"
|
||||||
"</style>";
|
"</style>";
|
||||||
|
|
||||||
const char* cHtmlChannelList::js =
|
const char* cHtmlMenuList::js =
|
||||||
"<script language=\"JavaScript\">\n"
|
"<script language=\"JavaScript\">\n"
|
||||||
"<!--\n"
|
"<!--\n"
|
||||||
|
|
||||||
@ -199,7 +223,7 @@ const char* cHtmlChannelList::js =
|
|||||||
"</script>";
|
"</script>";
|
||||||
|
|
||||||
|
|
||||||
std::string cHtmlChannelList::StreamTypeMenu()
|
std::string cHtmlMenuList::StreamTypeMenu()
|
||||||
{
|
{
|
||||||
std::string typeMenu;
|
std::string typeMenu;
|
||||||
typeMenu += (streamType == stTS ? (std::string) "[TS] " :
|
typeMenu += (streamType == stTS ? (std::string) "[TS] " :
|
||||||
@ -215,29 +239,29 @@ std::string cHtmlChannelList::StreamTypeMenu()
|
|||||||
return typeMenu;
|
return typeMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget): cChannelList(Iterator)
|
cHtmlMenuList::cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget): cMenuList(Iterator)
|
||||||
{
|
{
|
||||||
streamType = StreamType;
|
streamType = StreamType;
|
||||||
self = strdup(Self);
|
self = strdup(Self);
|
||||||
rss = strdup(Rss);
|
rss = strdup(Rss);
|
||||||
groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
|
groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL;
|
||||||
htmlState = hsRoot;
|
htmlState = hsRoot;
|
||||||
current = NULL;
|
onItem = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cHtmlChannelList::~cHtmlChannelList()
|
cHtmlMenuList::~cHtmlMenuList()
|
||||||
{
|
{
|
||||||
free((void *) self);
|
free((void *) self);
|
||||||
free((void *) rss);
|
free((void *) rss);
|
||||||
free((void *) groupTarget);
|
free((void *) groupTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cHtmlChannelList::HasNext()
|
bool cHtmlMenuList::HasNext()
|
||||||
{
|
{
|
||||||
return htmlState != hsPageBottom;
|
return htmlState != hsPageBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::Next()
|
std::string cHtmlMenuList::Next()
|
||||||
{
|
{
|
||||||
switch (htmlState)
|
switch (htmlState)
|
||||||
{
|
{
|
||||||
@ -254,39 +278,39 @@ std::string cHtmlChannelList::Next()
|
|||||||
htmlState = hsPageTop;
|
htmlState = hsPageTop;
|
||||||
break;
|
break;
|
||||||
case hsPageTop:
|
case hsPageTop:
|
||||||
current = NextChannel();
|
onItem = NextItem();
|
||||||
htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom;
|
htmlState = onItem ? (IsGroup() ? hsGroupTop : hsPlainTop) : hsPageBottom;
|
||||||
break;
|
break;
|
||||||
case hsPlainTop:
|
case hsPlainTop:
|
||||||
htmlState = hsPlainItem;
|
htmlState = hsPlainItem;
|
||||||
break;
|
break;
|
||||||
case hsPlainItem:
|
case hsPlainItem:
|
||||||
current = NextChannel();
|
onItem = NextItem();
|
||||||
htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom;
|
htmlState = onItem && !IsGroup() ? hsPlainItem : hsPlainBottom;
|
||||||
break;
|
break;
|
||||||
case hsPlainBottom:
|
case hsPlainBottom:
|
||||||
htmlState = current ? hsGroupTop : hsPageBottom;
|
htmlState = onItem ? hsGroupTop : hsPageBottom;
|
||||||
break;
|
break;
|
||||||
case hsGroupTop:
|
case hsGroupTop:
|
||||||
current = NextChannel();
|
onItem = NextItem();
|
||||||
htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom;
|
htmlState = onItem && !IsGroup() ? hsItemsTop : hsGroupBottom;
|
||||||
break;
|
break;
|
||||||
case hsItemsTop:
|
case hsItemsTop:
|
||||||
htmlState = hsItem;
|
htmlState = hsItem;
|
||||||
break;
|
break;
|
||||||
case hsItem:
|
case hsItem:
|
||||||
current = NextChannel();
|
onItem = NextItem();
|
||||||
htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom;
|
htmlState = onItem && !IsGroup() ? hsItem : hsItemsBottom;
|
||||||
break;
|
break;
|
||||||
case hsItemsBottom:
|
case hsItemsBottom:
|
||||||
htmlState = hsGroupBottom;
|
htmlState = hsGroupBottom;
|
||||||
break;
|
break;
|
||||||
case hsGroupBottom:
|
case hsGroupBottom:
|
||||||
htmlState = current ? hsGroupTop : hsPageBottom;
|
htmlState = onItem ? hsGroupTop : hsPageBottom;
|
||||||
break;
|
break;
|
||||||
case hsPageBottom:
|
case hsPageBottom:
|
||||||
default:
|
default:
|
||||||
esyslog("streamdev-server cHtmlChannelList: invalid call to Next()");
|
esyslog("streamdev-server cHtmlMenuList: invalid call to Next()");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
switch (htmlState)
|
switch (htmlState)
|
||||||
@ -311,36 +335,35 @@ std::string cHtmlChannelList::Next()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::HtmlHead()
|
std::string cHtmlMenuList::HtmlHead()
|
||||||
{
|
{
|
||||||
return (std::string) "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"" + rss + "\"/>";
|
return (std::string) "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"" + rss + "\"/>";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::PageTop()
|
std::string cHtmlMenuList::PageTop()
|
||||||
{
|
{
|
||||||
return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
|
return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::PageBottom()
|
std::string cHtmlMenuList::PageBottom()
|
||||||
{
|
{
|
||||||
return (std::string) "";
|
return (std::string) "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::GroupTitle()
|
std::string cHtmlMenuList::GroupTitle()
|
||||||
{
|
{
|
||||||
if (groupTarget)
|
if (groupTarget)
|
||||||
{
|
{
|
||||||
return (std::string) "<a href=\"" + groupTarget + "?group=" +
|
return (std::string) "<a href=\"" + groupTarget + "?group=" + (const char*) ItemId() + "\">" +
|
||||||
(const char*) itoa(cChannelList::GetGroupIndex(current)) +
|
ItemTitle() + "</a>";
|
||||||
"\">" + current->Name() + "</a>";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (std::string) current->Name();
|
return (std::string) ItemTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cHtmlChannelList::ItemText()
|
std::string cHtmlMenuList::ItemText()
|
||||||
{
|
{
|
||||||
std::string line;
|
std::string line;
|
||||||
std::string suffix;
|
std::string suffix;
|
||||||
@ -352,58 +375,58 @@ std::string cHtmlChannelList::ItemText()
|
|||||||
case stPES: suffix = (std::string) ".vdr"; break;
|
case stPES: suffix = (std::string) ".vdr"; break;
|
||||||
default: suffix = "";
|
default: suffix = "";
|
||||||
}
|
}
|
||||||
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
|
line += (std::string) "<li value=\"" + (const char*) ItemId() + "\">";
|
||||||
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\"";
|
line += (std::string) "<a href=\"" + (const char*) ItemRessource() + suffix + "\"";
|
||||||
|
|
||||||
// for Network Media Tank
|
// for Network Media Tank
|
||||||
line += (std::string) " vod ";
|
line += (std::string) " vod ";
|
||||||
if (current->Number() < 1000)
|
if (strlen(ItemId()) < 4)
|
||||||
line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\"";
|
line += (std::string) " tvid=\"" + (const char*) ItemId() + "\"";
|
||||||
|
|
||||||
line += (std::string) ">" + current->Name() + "</a>";
|
line += (std::string) ">" + ItemTitle() + "</a>";
|
||||||
|
|
||||||
int count = 0;
|
// TS always streams all PIDs
|
||||||
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
|
if (streamType != stTS)
|
||||||
;
|
|
||||||
for (int i = 0; current->Dpid(i) != 0; ++i, ++count)
|
|
||||||
;
|
|
||||||
|
|
||||||
if (count > 1)
|
|
||||||
{
|
{
|
||||||
int index = 1;
|
int index = 1;
|
||||||
for (int i = 0; current->Apid(i) != 0; ++i, ++index) {
|
const char* lang;
|
||||||
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
std::string pids;
|
||||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + current->Alang(i) + "</a>";
|
for (int i = 0; (lang = Alang(i)) != NULL; ++i, ++index) {
|
||||||
}
|
pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
|
||||||
for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
|
"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + (const char*) lang + "</a>";
|
||||||
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
}
|
||||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + current->Dlang(i) + "</a>";
|
for (int i = 0; (lang = Dlang(i)) != NULL; ++i, ++index) {
|
||||||
}
|
pids += (std::string) " <a href=\"" + (const char*) ItemRessource() +
|
||||||
|
"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + (const char*) lang + "</a>";
|
||||||
|
}
|
||||||
|
// always show audio PIDs for stES to select audio only
|
||||||
|
if (index > 2 || streamType == stES)
|
||||||
|
line += pids;
|
||||||
}
|
}
|
||||||
line += "</li>";
|
line += "</li>";
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************** cM3uChannelList ******************
|
// ******************** cM3uMenuList ******************
|
||||||
cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base)
|
cM3uMenuList::cM3uMenuList(cItemIterator *Iterator, const char* Base)
|
||||||
: cChannelList(Iterator),
|
: cMenuList(Iterator),
|
||||||
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||||
{
|
{
|
||||||
base = strdup(Base);
|
base = strdup(Base);
|
||||||
m3uState = msFirst;
|
m3uState = msFirst;
|
||||||
}
|
}
|
||||||
|
|
||||||
cM3uChannelList::~cM3uChannelList()
|
cM3uMenuList::~cM3uMenuList()
|
||||||
{
|
{
|
||||||
free(base);
|
free(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cM3uChannelList::HasNext()
|
bool cM3uMenuList::HasNext()
|
||||||
{
|
{
|
||||||
return m3uState != msLast;
|
return m3uState != msLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cM3uChannelList::Next()
|
std::string cM3uMenuList::Next()
|
||||||
{
|
{
|
||||||
if (m3uState == msFirst)
|
if (m3uState == msFirst)
|
||||||
{
|
{
|
||||||
@ -411,32 +434,30 @@ std::string cM3uChannelList::Next()
|
|||||||
return "#EXTM3U";
|
return "#EXTM3U";
|
||||||
}
|
}
|
||||||
|
|
||||||
const cChannel *channel = NextChannel();
|
if (!NextItem())
|
||||||
if (!channel)
|
|
||||||
{
|
{
|
||||||
m3uState = msLast;
|
m3uState = msLast;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name = (std::string) m_IConv.Convert(channel->Name());
|
std::string name = (std::string) m_IConv.Convert(ItemTitle());
|
||||||
|
|
||||||
if (channel->GroupSep())
|
if (IsGroup())
|
||||||
{
|
{
|
||||||
return (std::string) "#EXTINF:-1," + name + "\r\n" +
|
return (std::string) "#EXTINF:-1," + name + "\r\n" +
|
||||||
base + "group.m3u?group=" +
|
base + "group.m3u?group=" + (const char*) ItemId();
|
||||||
(const char*) itoa(cChannelList::GetGroupIndex(channel));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (std::string) "#EXTINF:-1," +
|
return (std::string) "#EXTINF:-1," +
|
||||||
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
|
(const char*) ItemId() + " " + name + "\r\n" +
|
||||||
base + (std::string) channel->GetChannelID().ToString();
|
base + (const char*) ItemRessource();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************** cRssChannelList ******************
|
// ******************** cRssMenuList ******************
|
||||||
cRssChannelList::cRssChannelList(cChannelIterator *Iterator, const char *Base, const char *Html)
|
cRssMenuList::cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html)
|
||||||
: cChannelList(Iterator),
|
: cMenuList(Iterator),
|
||||||
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||||
{
|
{
|
||||||
base = strdup(Base);
|
base = strdup(Base);
|
||||||
@ -444,18 +465,18 @@ cRssChannelList::cRssChannelList(cChannelIterator *Iterator, const char *Base, c
|
|||||||
rssState = msFirst;
|
rssState = msFirst;
|
||||||
}
|
}
|
||||||
|
|
||||||
cRssChannelList::~cRssChannelList()
|
cRssMenuList::~cRssMenuList()
|
||||||
{
|
{
|
||||||
free(base);
|
free(base);
|
||||||
free(html);
|
free(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRssChannelList::HasNext()
|
bool cRssMenuList::HasNext()
|
||||||
{
|
{
|
||||||
return rssState != msLast;
|
return rssState != msLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cRssChannelList::Next()
|
std::string cRssMenuList::Next()
|
||||||
{
|
{
|
||||||
std::string type_ext;
|
std::string type_ext;
|
||||||
|
|
||||||
@ -469,27 +490,26 @@ std::string cRssChannelList::Next()
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cChannel *channel = NextChannel();
|
if (!NextItem())
|
||||||
if (!channel)
|
|
||||||
{
|
{
|
||||||
rssState = msLast;
|
rssState = msLast;
|
||||||
return "\t</channel>\n</rss>\n";
|
return "\t</channel>\n</rss>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name = (std::string) m_IConv.Convert(channel->Name());
|
std::string name = (std::string) m_IConv.Convert(ItemTitle());
|
||||||
|
|
||||||
if (channel->GroupSep())
|
if (IsGroup())
|
||||||
{
|
{
|
||||||
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
||||||
name + "</title>\n\t\t\t<link>" +
|
name + "</title>\n\t\t\t<link>" +
|
||||||
base + "group.rss?group=" + (const char*) itoa(cChannelList::GetGroupIndex(channel)) + "</link>\n\t\t</item>\n";
|
base + "group.rss?group=" + (const char*) ItemId() + "</link>\n\t\t</item>\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
return (std::string) "\t\t<item>\n\t\t\t<title>" +
|
||||||
(const char*) itoa(channel->Number()) + " " + name + "</title>\n\t\t\t<link>" +
|
(const char*) ItemId() + " " + name + "</title>\n\t\t\t<link>" +
|
||||||
base + (std::string) channel->GetChannelID().ToString() + "</link>\n\t\t\t<enclosure url=\"" +
|
base + (const char*) ItemRessource() + "</link>\n\t\t\t<enclosure url=\"" +
|
||||||
base + (std::string) channel->GetChannelID().ToString() + "\" type=\"video/mpeg\" />\n\t\t</item>\n";
|
base + (const char*) ItemRessource() + "\" type=\"video/mpeg\" />\n\t\t</item>\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,16 +6,38 @@
|
|||||||
|
|
||||||
class cChannel;
|
class cChannel;
|
||||||
|
|
||||||
// ******************** cChannelIterator ******************
|
// ******************** cItemIterator ******************
|
||||||
class cChannelIterator
|
class cItemIterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool Next() = 0;
|
||||||
|
virtual bool IsGroup() const = 0;
|
||||||
|
virtual const cString ItemId() const = 0;
|
||||||
|
virtual const char* ItemTitle() const = 0;
|
||||||
|
virtual const cString ItemRessource() const = 0;
|
||||||
|
virtual const char* Alang(int i) const = 0;
|
||||||
|
virtual const char* Dlang(int i) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class cChannelIterator: public cItemIterator
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
const cChannel *channel;
|
const cChannel *first;
|
||||||
|
const cChannel *current;
|
||||||
protected:
|
protected:
|
||||||
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
|
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
|
||||||
static inline const cChannel* SkipFakeGroups(const cChannel *Channel);
|
static inline const cChannel* SkipFakeGroups(const cChannel *Channel);
|
||||||
|
// Helper which returns the group by its index
|
||||||
|
static const cChannel* GetGroup(const char* GroupId);
|
||||||
public:
|
public:
|
||||||
const cChannel* Next();
|
|
||||||
|
virtual bool Next();
|
||||||
|
virtual bool IsGroup() const { return current && current->GroupSep(); }
|
||||||
|
virtual const cString ItemId() const;
|
||||||
|
virtual const char* ItemTitle() const { return current ? current->Name() : ""; }
|
||||||
|
virtual const cString ItemRessource() const { return (current ? current->GetChannelID() : tChannelID::InvalidID).ToString(); }
|
||||||
|
virtual const char* Alang(int i) const { return current && current->Apid(i) ? current->Alang(i) : NULL; }
|
||||||
|
virtual const char* Dlang(int i) const { return current && current->Dpid(i) ? current->Dlang(i) : NULL; }
|
||||||
cChannelIterator(const cChannel *First);
|
cChannelIterator(const cChannel *First);
|
||||||
virtual ~cChannelIterator() {};
|
virtual ~cChannelIterator() {};
|
||||||
};
|
};
|
||||||
@ -54,7 +76,7 @@ class cListGroup: public cChannelIterator
|
|||||||
protected:
|
protected:
|
||||||
virtual const cChannel* NextChannel(const cChannel *Channel);
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
public:
|
public:
|
||||||
cListGroup(const cChannel *Group);
|
cListGroup(const char *GroupId);
|
||||||
virtual ~cListGroup() {};
|
virtual ~cListGroup() {};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,31 +88,32 @@ class cListTree: public cChannelIterator
|
|||||||
protected:
|
protected:
|
||||||
virtual const cChannel* NextChannel(const cChannel *Channel);
|
virtual const cChannel* NextChannel(const cChannel *Channel);
|
||||||
public:
|
public:
|
||||||
cListTree(const cChannel *SelectedGroup);
|
cListTree(const char *SelectedGroupId);
|
||||||
virtual ~cListTree() {};
|
virtual ~cListTree() {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ******************** cChannelList ******************
|
// ******************** cMenuList ******************
|
||||||
class cChannelList
|
class cMenuList
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
cChannelIterator *iterator;
|
cItemIterator *iterator;
|
||||||
protected:
|
protected:
|
||||||
const cChannel* NextChannel() { return iterator->Next(); }
|
bool NextItem() { return iterator->Next(); }
|
||||||
|
bool IsGroup() { return iterator->IsGroup(); }
|
||||||
|
const cString ItemId() { return iterator->ItemId(); }
|
||||||
|
const char* ItemTitle() { return iterator->ItemTitle(); }
|
||||||
|
const cString ItemRessource() { return iterator->ItemRessource(); }
|
||||||
|
const char* Alang(int i) { return iterator->Alang(i); }
|
||||||
|
const char* Dlang(int i) { return iterator->Dlang(i); }
|
||||||
public:
|
public:
|
||||||
// Helper which returns the group index
|
|
||||||
static int GetGroupIndex(const cChannel* Group);
|
|
||||||
// Helper which returns the group by its index
|
|
||||||
static const cChannel* GetGroup(int Index);
|
|
||||||
|
|
||||||
virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; };
|
virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; };
|
||||||
virtual bool HasNext() = 0;
|
virtual bool HasNext() = 0;
|
||||||
virtual std::string Next() = 0;
|
virtual std::string Next() = 0;
|
||||||
cChannelList(cChannelIterator *Iterator);
|
cMenuList(cItemIterator *Iterator);
|
||||||
virtual ~cChannelList();
|
virtual ~cMenuList();
|
||||||
};
|
};
|
||||||
|
|
||||||
class cHtmlChannelList: public cChannelList
|
class cHtmlMenuList: public cMenuList
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
static const char* menu;
|
static const char* menu;
|
||||||
@ -104,7 +127,7 @@ class cHtmlChannelList: public cChannelList
|
|||||||
hsItemsTop, hsItem, hsItemsBottom
|
hsItemsTop, hsItem, hsItemsBottom
|
||||||
};
|
};
|
||||||
eHtmlState htmlState;
|
eHtmlState htmlState;
|
||||||
const cChannel *current;
|
bool onItem;
|
||||||
eStreamType streamType;
|
eStreamType streamType;
|
||||||
const char* self;
|
const char* self;
|
||||||
const char* rss;
|
const char* rss;
|
||||||
@ -118,18 +141,18 @@ class cHtmlChannelList: public cChannelList
|
|||||||
std::string PageBottom();
|
std::string PageBottom();
|
||||||
public:
|
public:
|
||||||
virtual std::string HttpHeader() {
|
virtual std::string HttpHeader() {
|
||||||
return cChannelList::HttpHeader()
|
return cMenuList::HttpHeader()
|
||||||
+ "Content-type: text/html; charset="
|
+ "Content-type: text/html; charset="
|
||||||
+ (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8")
|
+ (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8")
|
||||||
+ "\r\n";
|
+ "\r\n";
|
||||||
}
|
}
|
||||||
virtual bool HasNext();
|
virtual bool HasNext();
|
||||||
virtual std::string Next();
|
virtual std::string Next();
|
||||||
cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget);
|
cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget);
|
||||||
virtual ~cHtmlChannelList();
|
virtual ~cHtmlMenuList();
|
||||||
};
|
};
|
||||||
|
|
||||||
class cM3uChannelList: public cChannelList
|
class cM3uMenuList: public cMenuList
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
char *base;
|
char *base;
|
||||||
@ -137,14 +160,14 @@ class cM3uChannelList: public cChannelList
|
|||||||
eM3uState m3uState;
|
eM3uState m3uState;
|
||||||
cCharSetConv m_IConv;
|
cCharSetConv m_IConv;
|
||||||
public:
|
public:
|
||||||
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; };
|
virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; };
|
||||||
virtual bool HasNext();
|
virtual bool HasNext();
|
||||||
virtual std::string Next();
|
virtual std::string Next();
|
||||||
cM3uChannelList(cChannelIterator *Iterator, const char* Base);
|
cM3uMenuList(cItemIterator *Iterator, const char* Base);
|
||||||
virtual ~cM3uChannelList();
|
virtual ~cM3uMenuList();
|
||||||
};
|
};
|
||||||
|
|
||||||
class cRssChannelList: public cChannelList
|
class cRssMenuList: public cMenuList
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
char *base;
|
char *base;
|
||||||
@ -153,12 +176,12 @@ class cRssChannelList: public cChannelList
|
|||||||
eRssState rssState;
|
eRssState rssState;
|
||||||
cCharSetConv m_IConv;
|
cCharSetConv m_IConv;
|
||||||
public:
|
public:
|
||||||
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: application/rss+xml\r\n"; };
|
virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: application/rss+xml\r\n"; };
|
||||||
virtual bool HasNext();
|
virtual bool HasNext();
|
||||||
virtual std::string Next();
|
virtual std::string Next();
|
||||||
|
|
||||||
cRssChannelList(cChannelIterator *Iterator, const char *Base, const char *Html);
|
cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html);
|
||||||
virtual ~cRssChannelList();
|
virtual ~cRssMenuList();
|
||||||
};
|
};
|
||||||
|
|
||||||
inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group)
|
inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group)
|
||||||
|
Loading…
Reference in New Issue
Block a user