diff --git a/src/OctonetData.cpp b/src/OctonetData.cpp index 2ef2515..b79fd88 100644 --- a/src/OctonetData.cpp +++ b/src/OctonetData.cpp @@ -35,6 +35,7 @@ OctonetData::OctonetData() serverAddress = octonetAddress; channels.clear(); groups.clear(); + lastEpgLoad = 0; if (loadChannelList()) kodi->QueueNotification(QUEUE_INFO, "%d channels loaded.", channels.size()); @@ -106,6 +107,97 @@ bool OctonetData::loadChannelList() return true; } +OctonetChannel* OctonetData::findChannel(int64_t nativeId) +{ + std::vector::iterator it; + for (it = channels.begin(); it < channels.end(); ++it) { + if (it->nativeId == nativeId) + return &*it; + } + + return NULL; +} + +time_t OctonetData::parseDateTime(std::string date) +{ + struct tm timeinfo; + time_t time; + + memset(&timeinfo, 0, sizeof(timeinfo)); + + if (date.length() > 8) { + sscanf(date.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", + &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, + &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec); + timeinfo.tm_mon -= 1; + timeinfo.tm_year -= 1900; + } else { + sscanf(date.c_str(), "%02d:%02d:%02d", + &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec); + timeinfo.tm_year = 70; // unix timestamps start 1970 + timeinfo.tm_mday = 1; + } + + timeinfo.tm_isdst = -1; + + return timegm(&timeinfo); +} + +bool OctonetData::loadEPG(void) +{ + /* Reload at most every 30 seconds */ + if (lastEpgLoad + 30 > time(NULL)) + return false; + + std::string jsonContent; + void *f = kodi->OpenFile(("http://" + serverAddress + "/epg.lua?;#|encoding=gzip").c_str(), 0); + if (!f) + return false; + + char buf[1024]; + while (int read = kodi->ReadFile(f, buf, 1024)) + jsonContent.append(buf, read); + + kodi->CloseFile(f); + + Json::Value root; + Json::Reader reader; + + if (!reader.parse(jsonContent, root, false)) + return false; + + const Json::Value eventList = root["EventList"]; + OctonetChannel *channel = NULL; + for (unsigned int i = 0; i < eventList.size(); i++) { + const Json::Value event = eventList[i]; + OctonetEpgEntry entry; + + entry.start = parseDateTime(event["Time"].asString()); + entry.end = entry.start + parseDateTime(event["Duration"].asString()); + entry.title = event["Name"].asString(); + entry.subtitle = event["Text"].asString(); + std::string channelId = event["ID"].asString(); + std::string epgId = channelId.substr(channelId.rfind(":") + 1); + channelId = channelId.substr(0, channelId.rfind(":")); + + entry.channelId = parseID(channelId); + entry.id = atoi(epgId.c_str()); + + if (channel == NULL || channel->nativeId != entry.channelId) + channel = findChannel(entry.channelId); + + if (channel == NULL) { + kodi->Log(LOG_ERROR, "EPG for unknown channel."); + continue; + } + + channel->epg.push_back(entry); + } + + lastEpgLoad = time(NULL); + return true; +} + void *OctonetData::Process(void) { return NULL; @@ -140,6 +232,47 @@ PVR_ERROR OctonetData::getChannels(ADDON_HANDLE handle, bool bRadio) return PVR_ERROR_NO_ERROR; } +PVR_ERROR OctonetData::getEPG(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + bool needs_reload = false; + for (unsigned int i = 0; i < channels.size(); i++) + { + OctonetChannel &chan = channels.at(i); + if (channel.iUniqueId != chan.id) + continue; + + // FIXME: Check if reload is needed!? + + std::vector::iterator it; + time_t last_end = 0; + for (it = chan.epg.begin(); it < chan.epg.end(); ++it) { + if (end > last_end) + last_end = end; + + if (it->end < start || it->start > end) { + continue; + } + + EPG_TAG entry; + memset(&entry, 0, sizeof(EPG_TAG)); + + entry.iChannelNumber = i; + entry.iUniqueBroadcastId = it->id; + entry.strTitle = it->title.c_str(); + entry.strPlotOutline = it->subtitle.c_str(); + entry.startTime = it->start; + entry.endTime = it->end; + + pvr->TransferEpgEntry(handle, &entry); + } + + if (last_end < end) + loadEPG(); + } + + return PVR_ERROR_NO_ERROR; +} + int OctonetData::getGroupCount(void) { return groups.size(); diff --git a/src/OctonetData.h b/src/OctonetData.h index 1d20717..e457696 100644 --- a/src/OctonetData.h +++ b/src/OctonetData.h @@ -28,6 +28,16 @@ #include "platform/util/StdString.h" #include "client.h" +struct OctonetEpgEntry +{ + int64_t channelId; + time_t start; + time_t end; + int id; + std::string title; + std::string subtitle; +}; + struct OctonetChannel { int64_t nativeId; @@ -35,6 +45,8 @@ struct OctonetChannel std::string url; bool radio; int id; + + std::vector epg; }; struct OctonetGroup @@ -57,14 +69,23 @@ class OctonetData : public PLATFORM::CThread virtual PVR_ERROR getGroups(ADDON_HANDLE handle, bool bRadio); virtual PVR_ERROR getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group); + virtual PVR_ERROR getEPG(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); + protected: virtual bool loadChannelList(void); + virtual bool loadEPG(void); virtual OctonetGroup* findGroup(const std::string &name); virtual void *Process(void); + OctonetChannel* findChannel(int64_t nativeId); + time_t parseDateTime(std::string date); + int64_t parseID(std::string id); + private: std::string serverAddress; std::vector channels; std::vector groups; + + time_t lastEpgLoad; }; diff --git a/src/client.cpp b/src/client.cpp index c8dd99c..b427f88 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -149,6 +149,7 @@ PVR_ERROR GetAddonCapabilities(PVR_ADDON_CAPABILITIES *pCapabilities) pCapabilities->bSupportsTV = true; pCapabilities->bSupportsRadio = true; pCapabilities->bSupportsChannelGroups = true; + pCapabilities->bSupportsEPG = true; return PVR_ERROR_NO_ERROR; } @@ -172,7 +173,10 @@ PVR_ERROR GetDriveSpace(long long* iTotal, long long* iUsed) { return PVR_ERROR_ PVR_ERROR CallMenuHook(const PVR_MENUHOOK& menuhook, const PVR_MENUHOOK_DATA &item) { return PVR_ERROR_NOT_IMPLEMENTED; } /* EPG */ -PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL& channel, time_t iStart, time_t iEnd) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL& channel, time_t iStart, time_t iEnd) +{ + return data->getEPG(handle, channel, iStart, iEnd); +} /* Channel groups */ int GetChannelGroupsAmount(void)