diff --git a/pvr.plutotv/addon.xml.in b/pvr.plutotv/addon.xml.in index 18bd14c..c6c180e 100644 --- a/pvr.plutotv/addon.xml.in +++ b/pvr.plutotv/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.plutotv/changelog.txt b/pvr.plutotv/changelog.txt index 31d3f22..a8f1f92 100644 --- a/pvr.plutotv/changelog.txt +++ b/pvr.plutotv/changelog.txt @@ -1,3 +1,10 @@ +v19.0.2 +- Fix memory leak when redirecting HTTP requests +- Load channel data on demand, not on add-on startup +- Do not report API call success when channels can't be loaded +- Fix crash on HTTP request error +- Code cleanup + v19.0.1 - Speedup and fix EPG data caching diff --git a/src/Curl.cpp b/src/Curl.cpp index d894789..1ea8114 100644 --- a/src/Curl.cpp +++ b/src/Curl.cpp @@ -10,8 +10,50 @@ #include "Curl.h" #include "Utils.h" +#include "kodi/tools/StringUtils.h" -#include +namespace +{ +std::string Base64Encode(const std::string& str, bool urlEncode) +{ + std::string ret; + int i = 3; + unsigned char c_3[3]; + unsigned char c_4[4]; + + static const char* to_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + int len = str.size(); + int curr = 0; + while (len) + { + i = len > 2 ? 3 : len; + len -= i; + c_3[0] = str[curr++]; + c_3[1] = i > 1 ? str[curr++] : 0; + c_3[2] = i > 2 ? str[curr++] : 0; + + c_4[0] = (c_3[0] & 0xfc) >> 2; + c_4[1] = ((c_3[0] & 0x03) << 4) + ((c_3[1] & 0xf0) >> 4); + c_4[2] = ((c_3[1] & 0x0f) << 2) + ((c_3[2] & 0xc0) >> 6); + c_4[3] = c_3[2] & 0x3f; + + for (int j = 0; j < i + 1; ++j) + { + if (urlEncode && to_base64[c_4[j]] == '+') + ret += "%2B"; + else if (urlEncode && to_base64[c_4[j]] == '/') + ret += "%2F"; + else + ret += to_base64[c_4[j]]; + } + } + while ((i++ < 3)) + ret += urlEncode ? "%3D" : "="; + + return ret; +} +} // namespace Curl::Curl() = default; @@ -19,7 +61,7 @@ Curl::~Curl() = default; std::string Curl::GetCookie(const std::string& name) { - for (const auto& cookie : cookies) + for (const auto& cookie : m_cookies) { if (cookie.name == name) return cookie.value; @@ -29,34 +71,35 @@ std::string Curl::GetCookie(const std::string& name) void Curl::SetCookie(const std::string& host, const std::string& name, const std::string& value) { - for (std::list::iterator i = cookies.begin(); i != cookies.end(); ++i) + for (auto& cookie : m_cookies) { - if (i->host == host && i->name == name) + if (cookie.host == host && cookie.name == name) { - i->value = value; + cookie.value = value; return; } } + Cookie cookie; cookie.host = host; cookie.name = name; cookie.value = value; - cookies.push_back(cookie); + m_cookies.emplace_back(cookie); } void Curl::AddHeader(const std::string& name, const std::string& value) { - headers[name] = value; + m_headers[name] = value; } void Curl::AddOption(const std::string& name, const std::string& value) { - options[name] = value; + m_options[name] = value; } void Curl::ResetHeaders() { - headers.clear(); + m_headers.clear(); } std::string Curl::Delete(const std::string& url, const std::string& postData, int& statusCode) @@ -76,19 +119,18 @@ std::string Curl::Post(const std::string& url, const std::string& postData, int& void Curl::ParseCookies(kodi::vfs::CFile* file, const std::string& host) { - int numValues; const std::vector cookies = file->GetPropertyValues(ADDON_FILE_PROPERTY_RESPONSE_HEADER, "set-cookie"); for (auto cookie : cookies) { - std::string::size_type paramPos = cookie.find(';'); + const std::string::size_type paramPos = cookie.find(';'); if (paramPos != std::string::npos) cookie.resize(paramPos); - std::vector parts = Utils::SplitString(cookie, '=', 2); + + const std::vector parts = kodi::tools::StringUtils::Split(cookie, "=", 2); if (parts.size() != 2) - { continue; - } + SetCookie(host, parts[0], parts[1]); kodi::Log(ADDON_LOG_DEBUG, "Got cookie: %s.", parts[0].c_str()); } @@ -96,14 +138,16 @@ void Curl::ParseCookies(kodi::vfs::CFile* file, const std::string& host) std::string Curl::ParseHostname(const std::string& url) { - size_t pos = url.find_first_of(":"); + const size_t pos = url.find_first_of(':'); if (pos == std::string::npos) return ""; + std::string host = url.substr(pos + 3); - size_t pos_end = host.find_first_of("://"); + const size_t pos_end = host.find_first_of("://"); if (pos_end == std::string::npos) return host; + host = host.substr(0, pos_end); return host; } @@ -118,39 +162,40 @@ kodi::vfs::CFile* Curl::PrepareRequest(const std::string& action, delete file; return nullptr; } + file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "redirect-limit", "0"); - file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "customrequest", action.c_str()); + file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "customrequest", action); file->CURLAddOption(ADDON_CURL_OPTION_HEADER, "acceptencoding", "gzip"); if (!postData.empty()) { - std::string base64 = - Base64Encode((const unsigned char*)postData.c_str(), postData.size(), false); - file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "postdata", base64.c_str()); + const std::string base64 = Base64Encode(postData, false); + file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "postdata", base64); } - for (auto const& entry : headers) + for (auto const& entry : m_headers) { - file->CURLAddOption(ADDON_CURL_OPTION_HEADER, entry.first.c_str(), entry.second.c_str()); + file->CURLAddOption(ADDON_CURL_OPTION_HEADER, entry.first, entry.second); } - for (auto const& entry : options) + for (auto const& entry : m_options) { - file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, entry.first.c_str(), entry.second.c_str()); + file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, entry.first, entry.second); } - std::string host = ParseHostname(url); + const std::string host = ParseHostname(url); kodi::Log(ADDON_LOG_DEBUG, "Add cookies for host: %s.", host.c_str()); - std::string cookie_s = ""; - for (auto& cookie : cookies) + std::string cookie_s; + for (auto& cookie : m_cookies) { if (cookie.host != host) continue; - cookie_s = cookie_s + cookie.name.c_str() + "=" + cookie.value.c_str() + "; "; + + cookie_s = cookie_s + cookie.name + "=" + cookie.value + "; "; } if (cookie_s.size() > 0) - file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "cookie", cookie_s.c_str()); + file->CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "cookie", cookie_s); // we have to set "failonerror" to get error results file->CURLAddOption(ADDON_CURL_OPTION_HEADER, "failonerror", "false"); @@ -163,8 +208,8 @@ std::string Curl::Request(const std::string& action, const std::string& postData, int& statusCode) { - int remaining_redirects = redirectLimit; - location = url; + int remaining_redirects = m_redirectLimit; + m_location = url; bool redirect; kodi::vfs::CFile* file = PrepareRequest(action, url, postData); @@ -186,18 +231,21 @@ std::string Curl::Request(const std::string& action, statusCode = 200; // get the real statusCode - std::string tmpRespLine = file->GetPropertyValue(ADDON_FILE_PROPERTY_RESPONSE_PROTOCOL, ""); - std::vector resp_protocol_parts = Utils::SplitString(tmpRespLine, ' ', 3); + const std::string tmpRespLine = + file->GetPropertyValue(ADDON_FILE_PROPERTY_RESPONSE_PROTOCOL, ""); + const std::vector resp_protocol_parts = + kodi::tools::StringUtils::Split(tmpRespLine, " ", 3); + if (resp_protocol_parts.size() >= 2) { - statusCode = Utils::stoiDefault(resp_protocol_parts[1].c_str(), -1); + statusCode = Utils::StringToInt(resp_protocol_parts[1], -1); kodi::Log(ADDON_LOG_DEBUG, "HTTP response code: %i.", statusCode); } - ParseCookies(file, ParseHostname(location)); + ParseCookies(file, ParseHostname(m_location)); - location = file->GetPropertyValue(ADDON_FILE_PROPERTY_RESPONSE_HEADER, "Location"); - kodi::Log(ADDON_LOG_DEBUG, "Location: %s.", location.c_str()); + m_location = file->GetPropertyValue(ADDON_FILE_PROPERTY_RESPONSE_HEADER, "Location"); + kodi::Log(ADDON_LOG_DEBUG, "Location: %s.", m_location.c_str()); if (statusCode >= 301 && statusCode <= 303) { @@ -205,7 +253,8 @@ std::string Curl::Request(const std::string& action, redirect = true; kodi::Log(ADDON_LOG_DEBUG, "redirects remaining: %i", remaining_redirects); remaining_redirects--; - file = PrepareRequest("GET", location.c_str(), ""); + delete file; + file = PrepareRequest("GET", m_location, ""); } } while (redirect && remaining_redirects >= 0); @@ -223,41 +272,3 @@ std::string Curl::Request(const std::string& action, delete file; return body; } - - -std::string Curl::Base64Encode(unsigned char const* in, unsigned int in_len, bool urlEncode) -{ - std::string ret; - int i(3); - unsigned char c_3[3]; - unsigned char c_4[4]; - - const char* to_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - while (in_len) - { - i = in_len > 2 ? 3 : in_len; - in_len -= i; - c_3[0] = *(in++); - c_3[1] = i > 1 ? *(in++) : 0; - c_3[2] = i > 2 ? *(in++) : 0; - - c_4[0] = (c_3[0] & 0xfc) >> 2; - c_4[1] = ((c_3[0] & 0x03) << 4) + ((c_3[1] & 0xf0) >> 4); - c_4[2] = ((c_3[1] & 0x0f) << 2) + ((c_3[2] & 0xc0) >> 6); - c_4[3] = c_3[2] & 0x3f; - - for (int j = 0; (j < i + 1); ++j) - { - if (urlEncode && to_base64[c_4[j]] == '+') - ret += "%2B"; - else if (urlEncode && to_base64[c_4[j]] == '/') - ret += "%2F"; - else - ret += to_base64[c_4[j]]; - } - } - while ((i++ < 3)) - ret += urlEncode ? "%3D" : "="; - return ret; -} diff --git a/src/Curl.h b/src/Curl.h index b599aec..70a7b61 100644 --- a/src/Curl.h +++ b/src/Curl.h @@ -9,9 +9,9 @@ #include "kodi/Filesystem.h" -#include #include #include +#include struct Cookie { @@ -25,33 +25,32 @@ class Curl public: Curl(); virtual ~Curl(); - virtual std::string Delete(const std::string& url, const std::string& postData, int& statusCode); - virtual std::string Get(const std::string& url, int& statusCode); - virtual std::string Post(const std::string& url, const std::string& postData, int& statusCode); - virtual void AddHeader(const std::string& name, const std::string& value); - virtual void AddOption(const std::string& name, const std::string& value); - virtual void ResetHeaders(); - virtual std::string GetCookie(const std::string& name); - virtual void SetCookie(const std::string& host, - const std::string& name, - const std::string& value); - virtual std::string GetLocation() { return location; } - virtual void SetRedirectLimit(int limit) { redirectLimit = limit; } + + std::string Delete(const std::string& url, const std::string& postData, int& statusCode); + std::string Get(const std::string& url, int& statusCode); + std::string Post(const std::string& url, const std::string& postData, int& statusCode); + void AddHeader(const std::string& name, const std::string& value); + void AddOption(const std::string& name, const std::string& value); + void ResetHeaders(); + std::string GetCookie(const std::string& name); + void SetCookie(const std::string& host, const std::string& name, const std::string& value); + std::string GetLocation() const { return m_location; } + void SetRedirectLimit(int limit) { m_redirectLimit = limit; } private: - virtual kodi::vfs::CFile* PrepareRequest(const std::string& action, - const std::string& url, - const std::string& postData); - virtual void ParseCookies(kodi::vfs::CFile* file, const std::string& host); - virtual std::string Request(const std::string& action, - const std::string& url, - const std::string& postData, - int& statusCode); - virtual std::string ParseHostname(const std::string& url); - std::string Base64Encode(unsigned char const* in, unsigned int in_len, bool urlEncode); - std::map headers; - std::map options; - std::list cookies; - std::string location; - int redirectLimit = 8; + kodi::vfs::CFile* PrepareRequest(const std::string& action, + const std::string& url, + const std::string& postData); + void ParseCookies(kodi::vfs::CFile* file, const std::string& host); + std::string Request(const std::string& action, + const std::string& url, + const std::string& postData, + int& statusCode); + std::string ParseHostname(const std::string& url); + + std::map m_headers; + std::map m_options; + std::vector m_cookies; + std::string m_location; + int m_redirectLimit = 8; }; diff --git a/src/PlutotvData.cpp b/src/PlutotvData.cpp index 37d5fcd..c7fc98e 100644 --- a/src/PlutotvData.cpp +++ b/src/PlutotvData.cpp @@ -8,79 +8,36 @@ #include "PlutotvData.h" +#include "Curl.h" #include "Utils.h" -#include "kodi/General.h" -#include "rapidjson/document.h" +#include "kodi/tools/StringUtils.h" -#include #include -#include -// BEGIN CURL helpers from zattoo addon: -std::string PlutotvData::HttpGet(const std::string& url) +namespace { - return HttpRequest("GET", url, ""); -} - -std::string PlutotvData::HttpDelete(const std::string& url, const std::string& postData) +std::string HttpGet(const std::string& url) { - return HttpRequest("DELETE", url, postData); -} + kodi::Log(ADDON_LOG_DEBUG, "Http-GET-Request: %s.", url.c_str()); -std::string PlutotvData::HttpPost(const std::string& url, const std::string& postData) -{ - return HttpRequest("POST", url, postData); -} - -std::string PlutotvData::HttpRequest(const std::string& action, - const std::string& url, - const std::string& postData) -{ Curl curl; - int statusCode; - curl.AddHeader("User-Agent", PLUTOTV_USER_AGENT); - return HttpRequestToCurl(curl, action, url, postData, statusCode); -} -std::string PlutotvData::HttpRequestToCurl(Curl& curl, - const std::string& action, - const std::string& url, - const std::string& postData, - int& statusCode) -{ - kodi::Log(ADDON_LOG_DEBUG, "Http-Request: %s %s.", action.c_str(), url.c_str()); - std::string content; - if (action == "POST") - { - content = curl.Post(url, postData, statusCode); - } - else if (action == "DELETE") - { - content = curl.Delete(url, postData, statusCode); - } - else - { - content = curl.Get(url, statusCode); - } - return content; -} -// END CURL helpers from zattoo addon + int statusCode; + std::string content = curl.Get(url, statusCode); + if (statusCode == 200) + return content; + kodi::Log(ADDON_LOG_ERROR, "[Http-GET-Request] error. status: %i, body: %s", statusCode, + content.c_str()); + return ""; +} +} // namespace ADDON_STATUS PlutotvData::Create() { kodi::Log(ADDON_LOG_DEBUG, "%s - Creating the pluto.tv PVR add-on", __FUNCTION__); - - LoadChannelData(); - m_curStatus = ADDON_STATUS_OK; - return m_curStatus; -} - -ADDON_STATUS PlutotvData::GetStatus() -{ - kodi::Log(ADDON_LOG_DEBUG, "pluto.tv function call: [%s]", __FUNCTION__); - return m_curStatus; + return ADDON_STATUS_OK; } ADDON_STATUS PlutotvData::SetSetting(const std::string& settingName, @@ -109,12 +66,6 @@ PVR_ERROR PlutotvData::GetBackendVersion(std::string& version) return PVR_ERROR_NO_ERROR; } -PVR_ERROR PlutotvData::GetConnectionString(std::string& connection) -{ - connection = "connected"; - return PVR_ERROR_NO_ERROR; -} - void PlutotvData::SetStreamProperties(std::vector& properties, const std::string& url, bool realtime) @@ -131,18 +82,21 @@ void PlutotvData::SetStreamProperties(std::vector GET CHANNELS"); + if (m_bChannelsLoaded) + return true; + + kodi::Log(ADDON_LOG_DEBUG, "[load data] GET CHANNELS"); std::string jsonChannels = HttpGet("https://api.pluto.tv/v2/channels.json"); - if (jsonChannels.size() == 0) + if (jsonChannels.empty()) { kodi::Log(ADDON_LOG_ERROR, "[channels] ERROR - empty response"); - return PVR_ERROR_SERVER_ERROR; + return false; } jsonChannels = "{\"result\": " + jsonChannels + "}"; - kodi::Log(ADDON_LOG_DEBUG, "[channels] length: %i;", jsonChannels.length()); + kodi::Log(ADDON_LOG_DEBUG, "[channels] length: %i;", jsonChannels.size()); kodi::Log(ADDON_LOG_DEBUG, "[channels] %s;", jsonChannels.c_str()); kodi::Log(ADDON_LOG_DEBUG, "[channels] %s;", jsonChannels.substr(jsonChannels.size() - 40).c_str()); @@ -154,7 +108,7 @@ bool PlutotvData::LoadChannelData(void) if (channelsDoc.GetParseError()) { kodi::Log(ADDON_LOG_ERROR, "[LoadChannelData] ERROR: error while parsing json"); - return PVR_ERROR_SERVER_ERROR; + return false; } kodi::Log(ADDON_LOG_DEBUG, "[channels] iterate channels"); kodi::Log(ADDON_LOG_DEBUG, "[channels] size: %i;", channelsDoc["result"].Size()); @@ -198,7 +152,7 @@ bool PlutotvData::LoadChannelData(void) "sessionURL":"https://service-stitcher.clusters.pluto.tv/session/.json" }}, */ - std::string plutotvid = channel["_id"].GetString(); + const std::string plutotvid = channel["_id"].GetString(); ++i; PlutotvChannel plutotv_channel; plutotv_channel.iChannelNumber = i; // position @@ -207,15 +161,15 @@ bool PlutotvData::LoadChannelData(void) plutotv_channel.plutotvID = plutotvid; kodi::Log(ADDON_LOG_DEBUG, "[channel] pluto.tv ID: %s;", plutotv_channel.plutotvID.c_str()); - int uniqueId = Utils::GetChannelId(plutotvid.c_str()); + const int uniqueId = Utils::Hash(plutotvid); plutotv_channel.iUniqueId = uniqueId; kodi::Log(ADDON_LOG_DEBUG, "[channel] id: %i;", uniqueId); - std::string displayName = channel["name"].GetString(); + const std::string displayName = channel["name"].GetString(); plutotv_channel.strChannelName = displayName; kodi::Log(ADDON_LOG_DEBUG, "[channel] name: %s;", plutotv_channel.strChannelName.c_str()); - std::string logo = ""; + std::string logo; if (channel.HasMember("logo")) { logo = channel["logo"]["path"].GetString(); @@ -228,18 +182,18 @@ bool PlutotvData::LoadChannelData(void) plutotv_channel.strIconPath = logo; kodi::Log(ADDON_LOG_DEBUG, "[channel] logo: %s;", plutotv_channel.strIconPath.c_str()); - // plutotv_channel.strStreamURL if (channel.HasMember("stitched") && channel["stitched"].HasMember("urls") && channel["stitched"]["urls"].Size() > 0) { - std::string streamURL = channel["stitched"]["urls"][0]["url"].GetString(); + const std::string streamURL = channel["stitched"]["urls"][0]["url"].GetString(); plutotv_channel.strStreamURL = streamURL; kodi::Log(ADDON_LOG_DEBUG, "[channel] streamURL: %s;", streamURL.c_str()); } - m_channels.push_back(plutotv_channel); + m_channels.emplace_back(plutotv_channel); } + m_bChannelsLoaded = true; return true; } @@ -247,7 +201,11 @@ PVR_ERROR PlutotvData::GetChannelsAmount(int& amount) { kodi::Log(ADDON_LOG_DEBUG, "pluto.tv function call: [%s]", __FUNCTION__); - amount = m_channels.size(); + LoadChannelsData(); + if (!m_bChannelsLoaded) + return PVR_ERROR_SERVER_ERROR; + + amount = static_cast(m_channels.size()); return PVR_ERROR_NO_ERROR; } @@ -255,9 +213,13 @@ PVR_ERROR PlutotvData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet { kodi::Log(ADDON_LOG_DEBUG, "pluto.tv function call: [%s]", __FUNCTION__); - for (const auto& channel : m_channels) + if (!radio) { - if (!radio) + LoadChannelsData(); + if (!m_bChannelsLoaded) + return PVR_ERROR_SERVER_ERROR; + + for (const auto& channel : m_channels) { kodi::addon::PVRChannel kodiChannel; @@ -277,7 +239,7 @@ PVR_ERROR PlutotvData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet PVR_ERROR PlutotvData::GetChannelStreamProperties( const kodi::addon::PVRChannel& channel, std::vector& properties) { - std::string strUrl = GetChannelStreamUrl(channel.GetUniqueId()); + const std::string strUrl = GetChannelStreamURL(channel.GetUniqueId()); kodi::Log(ADDON_LOG_DEBUG, "Stream URL -> %s", strUrl.c_str()); PVR_ERROR ret = PVR_ERROR_FAILED; if (!strUrl.empty()) @@ -288,34 +250,37 @@ PVR_ERROR PlutotvData::GetChannelStreamProperties( return ret; } -std::string PlutotvData::GetSettingsUUID(std::string setting) +std::string PlutotvData::GetSettingsUUID(const std::string& setting) { std::string uuid = kodi::GetSettingString(setting); if (uuid.empty()) { - uuid = Utils::get_uuid(); + uuid = Utils::CreateUUID(); kodi::Log(ADDON_LOG_DEBUG, "uuid (generated): %s", uuid.c_str()); kodi::SetSettingString(setting, uuid); } return uuid; } -std::string PlutotvData::GetChannelStreamUrl(int uniqueId) +std::string PlutotvData::GetChannelStreamURL(int uniqueId) { - for (const auto& thisChannel : m_channels) + LoadChannelsData(); + if (!m_bChannelsLoaded) + return {}; + + for (const auto& channel : m_channels) { - if (thisChannel.iUniqueId == (int)uniqueId) + if (channel.iUniqueId == uniqueId) { - kodi::Log(ADDON_LOG_DEBUG, "Get live url for channel %s", thisChannel.strChannelName.c_str()); + kodi::Log(ADDON_LOG_DEBUG, "Get live url for channel %s", channel.strChannelName.c_str()); - std::string streamURL = thisChannel.strStreamURL; + std::string streamURL = channel.strStreamURL; kodi::Log(ADDON_LOG_DEBUG, "URL source: %s", streamURL.c_str()); - - if (Utils::ends_with(streamURL, "?deviceType=")) + if (kodi::tools::StringUtils::EndsWith(streamURL, "?deviceType=")) { // lazy approach by plugin.video.plutotv - streamURL = Utils::ReplaceAll( + kodi::tools::StringUtils::Replace( streamURL, "deviceType=", "deviceType=&deviceMake=&deviceModel=&&deviceVersion=unknown&appVersion=unknown&" "deviceDNT=0&userId=&advertisingId=&app_name=&appName=&buildVersion=&appStoreUrl=&" @@ -323,28 +288,27 @@ std::string PlutotvData::GetChannelStreamUrl(int uniqueId) } //if 'sid' not in streamURL - //streamURL = Utils::ReplaceAll(streamURL,"deviceModel=&","deviceModel=&sid="+PLUTOTV_SID+"&deviceId="+PLUTOTV_DEVICEID+"&"); - streamURL = Utils::ReplaceAll(streamURL, "deviceId=&", - "deviceId=" + GetSettingsUUID("internal_deviceid") + "&"); - streamURL = - Utils::ReplaceAll(streamURL, "sid=&", "sid=" + GetSettingsUUID("internal_sid") + "&"); + //kodi::tools::StringUtils::Replace(streamURL,"deviceModel=&","deviceModel=&sid="+PLUTOTV_SID+"&deviceId="+PLUTOTV_DEVICEID+"&"); + kodi::tools::StringUtils::Replace(streamURL, "deviceId=&", + "deviceId=" + GetSettingsUUID("internal_deviceid") + "&"); + kodi::tools::StringUtils::Replace(streamURL, "sid=&", + "sid=" + GetSettingsUUID("internal_sid") + "&"); // generic - streamURL = Utils::ReplaceAll(streamURL, "deviceType=&", "deviceType=web&"); - streamURL = Utils::ReplaceAll(streamURL, "deviceMake=&", "deviceMake=Chrome&"); - streamURL = Utils::ReplaceAll(streamURL, "deviceModel=&", "deviceModel=Chrome&"); - streamURL = Utils::ReplaceAll(streamURL, "appName=&", "appName=web&"); + kodi::tools::StringUtils::Replace(streamURL, "deviceType=&", "deviceType=web&"); + kodi::tools::StringUtils::Replace(streamURL, "deviceMake=&", "deviceMake=Chrome&"); + kodi::tools::StringUtils::Replace(streamURL, "deviceModel=&", "deviceModel=Chrome&"); + kodi::tools::StringUtils::Replace(streamURL, "appName=&", "appName=web&"); return streamURL; } } - return ""; + return {}; } PVR_ERROR PlutotvData::GetChannelGroupsAmount(int& amount) { - amount = static_cast(0); - return PVR_ERROR_NO_ERROR; + return PVR_ERROR_NOT_IMPLEMENTED; } PVR_ERROR PlutotvData::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) @@ -363,20 +327,19 @@ PVR_ERROR PlutotvData::GetEPGForChannel(int channelUid, time_t end, kodi::addon::PVREPGTagsResultSet& results) { + LoadChannelsData(); + if (!m_bChannelsLoaded) + return PVR_ERROR_SERVER_ERROR; + // Find channel data - for (unsigned int iChannelPtr = 0; iChannelPtr < m_channels.size(); iChannelPtr++) + for (const auto& channel : m_channels) { - PlutotvChannel& myChannel = m_channels.at(iChannelPtr); - if (myChannel.iUniqueId != channelUid) + if (channel.iUniqueId != channelUid) continue; // Channel data found - rapidjson::Document epgDoc; - if (start == m_epg_cache_start && end == m_epg_cache_end) - { - epgDoc.CopyFrom(m_epg_cache_document, epgDoc.GetAllocator()); - } - else + if (!m_epg_cache_document || m_epg_cache_start == 0 || m_epg_cache_end == 0 || + start < m_epg_cache_start || end > m_epg_cache_end) { const time_t orig_start = start; const time_t now = std::time(nullptr); @@ -389,45 +352,46 @@ PVR_ERROR PlutotvData::GetEPGForChannel(int channelUid, const std::tm* pstm = std::localtime(&start); // 2020-05-27T15:04:05Z char startTime[21] = ""; - std::strftime(startTime, 20, "%Y-%m-%dT%H:%M:%SZ", pstm); + std::strftime(startTime, sizeof(startTime), "%Y-%m-%dT%H:%M:%SZ", pstm); const std::tm* petm = std::localtime(&end); // 2020-05-27T15:04:05Z char endTime[21] = ""; - std::strftime(endTime, 20, "%Y-%m-%dT%H:%M:%SZ", petm); + std::strftime(endTime, sizeof(endTime), "%Y-%m-%dT%H:%M:%SZ", petm); const std::string url = "http://api.pluto.tv/v2/channels?start=" + std::string(startTime) + "&stop=" + std::string(endTime); std::string jsonEpg = HttpGet(url); kodi::Log(ADDON_LOG_DEBUG, "[epg-all] %s", jsonEpg.c_str()); - if (jsonEpg.size() == 0) + if (jsonEpg.empty()) { kodi::Log(ADDON_LOG_ERROR, "[epg] empty server response"); return PVR_ERROR_SERVER_ERROR; } jsonEpg = "{\"result\": " + jsonEpg + "}"; - epgDoc.Parse(jsonEpg.c_str()); - if (epgDoc.GetParseError()) + const std::shared_ptr epgDoc(new rapidjson::Document); + epgDoc->Parse(jsonEpg.c_str()); + if (epgDoc->GetParseError()) { kodi::Log(ADDON_LOG_ERROR, "[GetEPG] ERROR: error while parsing json"); return PVR_ERROR_SERVER_ERROR; } - m_epg_cache_document.CopyFrom(epgDoc, m_epg_cache_document.GetAllocator()); + m_epg_cache_document = epgDoc; m_epg_cache_start = orig_start; m_epg_cache_end = end; } kodi::Log(ADDON_LOG_DEBUG, "[epg] iterate entries"); - kodi::Log(ADDON_LOG_DEBUG, "[epg] size: %i;", epgDoc["result"].Size()); + kodi::Log(ADDON_LOG_DEBUG, "[epg] size: %i;", (*m_epg_cache_document)["result"].Size()); // Find EPG data - for (const auto& epgChannel : epgDoc["result"].GetArray()) + for (const auto& epgChannel : (*m_epg_cache_document)["result"].GetArray()) { - if (epgChannel["_id"].GetString() != myChannel.plutotvID) + if (epgChannel["_id"].GetString() != channel.plutotvID) continue; // EPG data found @@ -467,14 +431,14 @@ PVR_ERROR PlutotvData::GetEPGForChannel(int channelUid, // } } } }, // generate a unique boadcast id - std::string epg_bid = epgData["_id"].GetString(); - kodi::Log(ADDON_LOG_DEBUG, "[epg] epg_bid: %s;", epg_bid.c_str()); - int dirtyID = Utils::GetIDDirty(epg_bid); - kodi::Log(ADDON_LOG_DEBUG, "[epg] epg_bid dirty: %i;", dirtyID); - tag.SetUniqueBroadcastId(dirtyID); + const std::string epg_bsid = epgData["_id"].GetString(); + kodi::Log(ADDON_LOG_DEBUG, "[epg] epg_bsid: %s;", epg_bsid.c_str()); + const int epg_bid = Utils::Hash(epg_bsid); + kodi::Log(ADDON_LOG_DEBUG, "[epg] epg_bid: %i;", epg_bid); + tag.SetUniqueBroadcastId(epg_bid); // channel ID - tag.SetUniqueChannelId(myChannel.iUniqueId); + tag.SetUniqueChannelId(channel.iUniqueId); // set title tag.SetTitle(epgData["title"].GetString()); @@ -490,7 +454,6 @@ PVR_ERROR PlutotvData::GetEPGForChannel(int channelUid, if (epgData.HasMember("episode")) { - // set description if (epgData["episode"].HasMember("description") && epgData["episode"]["description"].IsString()) diff --git a/src/PlutotvData.h b/src/PlutotvData.h index a679427..806fc44 100644 --- a/src/PlutotvData.h +++ b/src/PlutotvData.h @@ -8,10 +8,10 @@ #pragma once -#include "Curl.h" #include "kodi/addon-instance/PVR.h" #include "rapidjson/document.h" +#include #include /** @@ -31,14 +31,12 @@ class ATTRIBUTE_HIDDEN PlutotvData : public kodi::addon::CAddonBase, PlutotvData& operator=(PlutotvData&&) = delete; ADDON_STATUS Create() override; - ADDON_STATUS GetStatus() override; ADDON_STATUS SetSetting(const std::string& settingName, const kodi::CSettingValue& settingValue) override; PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; PVR_ERROR GetBackendName(std::string& name) override; PVR_ERROR GetBackendVersion(std::string& version) override; - PVR_ERROR GetConnectionString(std::string& connection) override; PVR_ERROR GetChannelsAmount(int& amount) override; PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results) override; @@ -56,7 +54,6 @@ class ATTRIBUTE_HIDDEN PlutotvData : public kodi::addon::CAddonBase, time_t end, kodi::addon::PVREPGTagsResultSet& results) override; - private: struct PlutotvChannel { @@ -68,33 +65,17 @@ class ATTRIBUTE_HIDDEN PlutotvData : public kodi::addon::CAddonBase, std::string strStreamURL; }; - rapidjson::Document m_epg_cache_document; + std::shared_ptr m_epg_cache_document; time_t m_epg_cache_start = time_t(0); time_t m_epg_cache_end = time_t(0);; - ADDON_STATUS m_curStatus = ADDON_STATUS_OK; - std::vector m_channels; + bool m_bChannelsLoaded = false; - void AddTimerType(std::vector& types, int idx, int attributes); - - std::string GetChannelStreamUrl(int uniqueId); - std::string GetLicense(void); - std::string GetSettingsUUID(std::string setting); + std::string GetChannelStreamURL(int uniqueId); + std::string GetSettingsUUID(const std::string& setting); void SetStreamProperties(std::vector& properties, const std::string& url, bool realtime); - - std::string HttpGet(const std::string& url); - std::string HttpDelete(const std::string& url, const std::string& postData); - std::string HttpPost(const std::string& url, const std::string& postData); - std::string HttpRequest(const std::string& action, - const std::string& url, - const std::string& postData); - std::string HttpRequestToCurl(Curl& curl, - const std::string& action, - const std::string& url, - const std::string& postData, - int& statusCode); - bool LoadChannelData(void); + bool LoadChannelsData(); }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 869ab15..d1c6b56 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -9,115 +9,24 @@ #include "Utils.h" -#include "kodi/Filesystem.h" -#include "kodi/General.h" - -#include -#include -#include -#include +#include +#include +#include #include -#include +#include -std::string Utils::GetFilePath(std::string strPath, bool bUserPath) -{ - return (bUserPath ? kodi::GetBaseUserPath(strPath) : kodi::GetAddonPath(strPath)); -} +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define timegm _mkgmtime +#endif -// http://stackoverflow.com/a/17708801 -std::string Utils::UrlEncode(const std::string& value) -{ - std::ostringstream escaped; - escaped.fill('0'); - escaped << std::hex; - - for (char c : value) - { - // Keep alphanumeric and other accepted characters intact - if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') - { - escaped << c; - continue; - } - - // Any other characters are percent-encoded - escaped << '%' << std::setw(2) << int((unsigned char)c); - } - - return escaped.str(); -} - -double Utils::StringToDouble(const std::string& value) -{ - std::istringstream iss(value); - double result; - - iss >> result; - - return result; -} - -int Utils::StringToInt(const std::string& value) -{ - return (int)StringToDouble(value); -} - -std::vector Utils::SplitString(const std::string& str, const char& delim, int maxParts) -{ - typedef std::string::const_iterator iter; - iter beg = str.begin(); - std::vector tokens; - - while (beg != str.end()) - { - if (maxParts == 1) - { - tokens.emplace_back(beg, str.end()); - break; - } - maxParts--; - iter temp = find(beg, str.end(), delim); - if (beg != str.end()) - tokens.emplace_back(beg, temp); - beg = temp; - while ((beg != str.end()) && (*beg == delim)) - beg++; - } - - return tokens; -} - -std::string Utils::ReadFile(const std::string& path) -{ - kodi::vfs::CFile file; - file.CURLCreate(path); - if (!file.CURLCreate(path) || !file.CURLOpen(0)) - { - kodi::Log(ADDON_LOG_ERROR, "Failed to open file [%s].", path.c_str()); - return ""; - } - - char buf[1025]; - ssize_t nbRead; - std::string content; - while ((nbRead = file.Read(buf, 1024)) > 0) - { - buf[nbRead] = 0; - content.append(buf); - } - - return content; -} - -time_t Utils::StringToTime(std::string timeString) +time_t Utils::StringToTime(const std::string& timeString) { // expected timeString "2019-01-20T15:40:00+0100" - struct tm tm - { - }; + struct tm tm = {}; int year, month, day, h, m, s, tzh, tzm; - if (sscanf(timeString.c_str(), "%d-%d-%dT%d:%d:%d%d", &year, &month, &day, &h, &m, &s, &tzh) < 7) + if (std::sscanf(timeString.c_str(), "%d-%d-%dT%d:%d:%d%d", &year, &month, &day, &h, &m, &s, + &tzh) < 7) { tzh = 0; } @@ -135,74 +44,29 @@ time_t Utils::StringToTime(std::string timeString) return ret; } -std::string Utils::ltrim(std::string str, const std::string chars) -{ - str.erase(0, str.find_first_not_of(chars)); - return str; -} - -int Utils::GetIDDirty(std::string str) -{ - // str= "_1035245078" or = "misc-rand-int-whatever" - if (str.rfind("_", 0) == 0) - { - // str starts with _ - return stoi(ltrim(str)); - } - // dirty shit begins here: - return rand() % 99999 + 1; -} - -int Utils::GetChannelId(const char* strChannelName) -{ - int iId = 0; - int c; - while ((c = *strChannelName++)) - iId = ((iId << 5) + iId) + c; /* iId * 33 + c */ - return abs(iId); -} - -int Utils::stoiDefault(std::string str, int i) +int Utils::StringToInt(const std::string& str, int defaultValue) { try { - return stoi(str); + return std::stoi(str); } catch (std::exception& e) { - return i; + return defaultValue; } } -bool Utils::ends_with(std::string const& haystack, std::string const& end) +int Utils::Hash(const std::string& str) { - if (haystack.length() >= end.length()) - { - return (0 == haystack.compare(haystack.length() - end.length(), end.length(), end)); - } - else - { - return false; - } -} + const char* s = str.c_str(); - -std::string Utils::ReplaceAll(std::string str, - const std::string& search, - const std::string& replace) -{ - // taken from: https://stackoverflow.com/questions/2896600/how-to-replace-all-occurrences-of-a-character-in-string - size_t start_pos = 0; - while ((start_pos = str.find(search, start_pos)) != std::string::npos) - { - str.replace(start_pos, search.length(), replace); - start_pos += replace.length(); - } - return str; + int hash = 0; + while (*s) + hash = hash << 1 ^ *s++; + return hash; } - -std::string Utils::get_uuid() +std::string Utils::CreateUUID() { // https://stackoverflow.com/questions/24365331/how-can-i-generate-uuid-in-c-without-using-boost-library static std::random_device dev; diff --git a/src/Utils.h b/src/Utils.h index 7ef68b0..4f28995 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -9,33 +9,13 @@ #pragma once -#include #include -#include - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) -#define timegm _mkgmtime -#endif class Utils { public: - static std::string GetFilePath(std::string strPath, bool bUserPath = true); - static std::string UrlEncode(const std::string& string); - static double StringToDouble(const std::string& value); - static int StringToInt(const std::string& value); - static std::string ReadFile(const std::string& path); - static std::vector SplitString(const std::string& str, - const char& delim, - int maxParts = 0); - static time_t StringToTime(std::string timeString); - static std::string ltrim(std::string str, const std::string chars = "\t\n\v\f\r _"); - static int GetIDDirty(std::string str); - static int GetChannelId(const char* strChannelName); - static int stoiDefault(std::string str, int i); - static bool ends_with(std::string const& haystack, std::string const& end); - static std::string ReplaceAll(std::string str, - const std::string& search, - const std::string& replace); - static std::string get_uuid(); + static time_t StringToTime(const std::string& timeString); + static int StringToInt(const std::string& str, int defaultValue); + static int Hash(const std::string& str); + static std::string CreateUUID(); };