diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/CMakeLists.txt kodi-pvr-sledovanitv-cz-1.6.0/CMakeLists.txt --- kodi-pvr-sledovanitv-cz-1.5.1/CMakeLists.txt 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/CMakeLists.txt 2020-03-13 06:39:40.000000000 +0000 @@ -18,18 +18,20 @@ set(SLEDOVANITV_SOURCES src/client.cpp - src/apimanager.cpp - src/PVRIptvData.cpp) + src/ApiManager.cpp + src/Data.cpp) set(SLEDOVANITV_HEADERS src/client.h - src/apimanager.h + src/ApiManager.h src/CallLimiter.hh - src/PVRIptvData.h) + src/Data.h) if(WIN32) add_definitions("/wd4996") endif() build_addon(pvr.sledovanitv.cz SLEDOVANITV DEPLIBS) +set_property(TARGET pvr.sledovanitv.cz PROPERTY CXX_STANDARD 11) +set_property(TARGET pvr.sledovanitv.cz PROPERTY CXX_STANDARD_REQUIRED ON) include(CPack) diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/debian/changelog kodi-pvr-sledovanitv-cz-1.6.0/debian/changelog --- kodi-pvr-sledovanitv-cz-1.5.1/debian/changelog 2013-05-31 22:59:22.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/debian/changelog 2013-05-31 22:59:22.000000000 +0000 @@ -1,4 +1,4 @@ -kodi-pvr-sledovanitv-cz (1.5.1-1~disco) disco; urgency=low +kodi-pvr-sledovanitv-cz (1.6.0-1~disco) disco; urgency=low [ kodi ] * autogenerated dummy changelog diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/.gitignore kodi-pvr-sledovanitv-cz-1.6.0/.gitignore --- kodi-pvr-sledovanitv-cz-1.5.1/.gitignore 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -# build artifacts -build/ -pvr.*/addon.xml - -# Debian build files -debian/changelog -debian/files -debian/*.log -debian/*.substvars -debian/.debhelper/ -debian/tmp/ -debian/kodi-pvr-*/ -obj-x86_64-linux-gnu/ - -# commonly used editors -# vim -*.swp - -# Eclipse -*.project -*.cproject -.classpath -*.sublime-* -.settings/ - -# KDevelop 4 -*.kdev4 - -# Visual Studio -.vs/ - -# gedit -*~ - -# CLion -/.idea - -# clion -.idea/ diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/addon.xml.in kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/addon.xml.in --- kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/addon.xml.in 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/addon.xml.in 2020-03-13 06:39:40.000000000 +0000 @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.cs_cz/strings.po kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.cs_cz/strings.po --- kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.cs_cz/strings.po 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.cs_cz/strings.po 2020-03-13 06:39:40.000000000 +0000 @@ -30,6 +30,14 @@ msgid "Password" msgstr "Heslo" +msgctxt "#30008" +msgid "Device ID (empty - use MAC address)" +msgstr "ID zařízení (prázdné - použít MAC adresu)" + +msgctxt "#30012" +msgid "Product ID (empty - use hostname)" +msgstr "ID produktu (prázdné - použít hostname)" + msgctxt "#30002" msgid "Stream quality" msgstr "Kvalita streamu" @@ -54,6 +62,10 @@ msgid "Show unavailable channels" msgstr "Zobrazovat nedostupné kanály" +msgctxt "#30009" +msgid "Show only PIN locked channels" +msgstr "Zobrazovat jenom kanály blokované PIN" + msgctxt "#30100" msgid "Refresh times" msgstr "Časy aktualizace" @@ -77,3 +89,7 @@ msgctxt "#30201" msgid "unavailable" msgstr "nedostupné" + +msgctxt "#30202" +msgid "Enter PIN for unlock" +msgstr "Zadejte PIN pro odemknutí" diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.en_gb/strings.po kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.en_gb/strings.po --- kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.en_gb/strings.po 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.en_gb/strings.po 2020-03-13 06:39:40.000000000 +0000 @@ -30,6 +30,14 @@ msgid "Password" msgstr "Password" +msgctxt "#30008" +msgid "Device ID (empty - use MAC address)" +msgstr "Device ID (empty - use MAC address)" + +msgctxt "#30012" +msgid "Product ID (empty - use hostname)" +msgstr "Product ID (empty - use hostname)" + msgctxt "#30002" msgid "Stream quality" msgstr "Stream quality" @@ -54,6 +62,10 @@ msgid "Show unavailable channels" msgstr "Show unavailable channels" +msgctxt "#30009" +msgid "Show only PIN locked channels" +msgstr "Show only PIN locked channels" + msgctxt "#30100" msgid "Refresh times" msgstr "Refresh times" @@ -77,3 +89,7 @@ msgctxt "#30201" msgid "unavailable" msgstr "unavailable" + +msgctxt "#30202" +msgid "Enter PIN for unlock" +msgstr "Enter PIN for unlock" diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.sk_sk/strings.po kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.sk_sk/strings.po --- kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/language/resource.language.sk_sk/strings.po 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/language/resource.language.sk_sk/strings.po 2020-03-13 06:39:40.000000000 +0000 @@ -30,6 +30,14 @@ msgid "Password" msgstr "Heslo" +msgctxt "#30008" +msgid "Device ID (empty - use MAC address)" +msgstr "ID zariadenia (prázdne - použiť MAC adresu)" + +msgctxt "#30012" +msgid "Product ID (empty - use hostname)" +msgstr "ID produktu (prázdne - použiť hostname)" + msgctxt "#30002" msgid "Stream quality" msgstr "Kvalita streamu" @@ -54,6 +62,10 @@ msgid "Show unavailable channels" msgstr "Zobrazovať nedostupné kanály" +msgctxt "#30009" +msgid "Show only PIN locked channels" +msgstr "Zobrazovať len kanály blokované PIN" + msgctxt "#30100" msgid "Refresh times" msgstr "Časy aktualizácie" @@ -77,3 +89,7 @@ msgctxt "#30201" msgid "unavailable" msgstr "nedostupné" + +msgctxt "#30202" +msgid "Enter PIN for unlock" +msgstr "Zadajte PIN pre odmknutie" diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/settings.xml kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/settings.xml --- kodi-pvr-sledovanitv-cz-1.5.1/pvr.sledovanitv.cz/resources/settings.xml 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/pvr.sledovanitv.cz/resources/settings.xml 2020-03-13 06:39:40.000000000 +0000 @@ -22,6 +22,22 @@ true + + 1 + + + true + + + + + 3 + + + true + + + 1 0 @@ -48,6 +64,14 @@ true + + 1 + false + + true + + + diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/README.md kodi-pvr-sledovanitv-cz-1.6.0/README.md --- kodi-pvr-sledovanitv-cz-1.5.1/README.md 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/README.md 2020-03-13 06:39:40.000000000 +0000 @@ -10,7 +10,7 @@ ### Linux 1. `git clone --branch Leia https://github.com/xbmc/xbmc.git` -2. `git clone https://github.com/palinek/pvr.sledovanitv.cz.git` +2. `git clone --branch Leia https://github.com/palinek/pvr.sledovanitv.cz.git` 3. `cd pvr.sledovanitv.cz && mkdir build && cd build` 4. `cmake -DADDONS_TO_BUILD=pvr.sledovanitv.cz -DADDON_SRC_PREFIX=../.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=xbmc/addons -DPACKAGE_ZIP=1 -DADDONS_DEFINITION_DIR="$(pwd)/../xbmc/cmake/addons/addons" ../../xbmc/cmake/addons` 5. `make` diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/apimanager.cpp kodi-pvr-sledovanitv-cz-1.6.0/src/apimanager.cpp --- kodi-pvr-sledovanitv-cz-1.5.1/src/apimanager.cpp 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/apimanager.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,537 +0,0 @@ -/* - * Copyright (c) 2018~now Palo Kisa - * - * Copyright (C) 2014 Josef Rokos - * http://github.com/PepaRokos/xbmc-pvr-addons/ - * - * Copyright (C) 2011 Pulse-Eight - * http://www.pulse-eight.com/ - * - * This Program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This Program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this addon; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * http://www.gnu.org/copyleft/gpl.html - * - */ - -#include -#if defined(TARGET_POSIX) -#include -#endif -#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) -#include -#include -# if defined(TARGET_LINUX) -#include -# else //defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) -#include -#include -# endif -# if defined(TARGET_ANDROID) -#include -# endif -#elif defined(TARGET_WINDOWS) -#include -#include -#include -#pragma comment(lib, "IPHLPAPI.lib") -#pragma comment(lib, "WSOCK32.lib") -#endif - -#include -#include - -#include "client.h" -#include "apimanager.h" -#include "picosha2.h" -#include -#include -#include -#include -#include - -using namespace ADDON; - -const std::string ApiManager::API_URL = "https://sledovanitv.cz/api/"; -const std::string ApiManager::TIMESHIFTINFO_URL = "https://sledovanitv.cz/playback/timeshiftInfo"; -const std::string ApiManager::PAIR_FILE = "pairinfo"; - -/* Converts a hex character to its integer value */ -char from_hex(char ch) -{ - return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; -} - -/* Converts an integer value to its hex character*/ -char to_hex(char code) -{ - static char hex[] = "0123456789abcdef"; - return hex[code & 15]; -} - -char *url_encode(const char *str) -{ - char *pstr = (char*) str, *buf = (char *)malloc(strlen(str) * 3 + 1), *pbuf = buf; - while (*pstr) -{ - if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') - *pbuf++ = *pstr; - else if (*pstr == ' ') - *pbuf++ = '+'; - else - *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); - pstr++; - } - *pbuf = '\0'; - return buf; -} - -#if defined(TARGET_ANDROID) && __ANDROID_API__ < 24 -// Note: declare dummy "ifaddr" functions to make this compile on pre API-24 versions -#warning "Compiling for ANDROID with API version < 24, getting MAC addres will not be available." -static int getifaddrs(struct ifaddrs ** /*ifap*/) { errno = ENOTSUP; return -1; } -static void freeifaddrs(struct ifaddrs * /*ifa*/) {} -#endif -static std::string get_mac_address() -{ - std::string mac_addr; -#if defined(TARGET_ANDROID) && __ANDROID_API__ < 24 - XBMC->Log(LOG_NOTICE, "Can't get MAC address with target Android API < 24 (no getifaddrs() support)"); -#endif -#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) - struct ifaddrs * addrs; - if (0 != getifaddrs(&addrs)) - { - XBMC->Log(LOG_NOTICE, "While getting MAC address getifaddrs() failed, %s", strerror(errno)); - return mac_addr; - } - std::unique_ptr if_addrs{addrs, &freeifaddrs}; - for (struct ifaddrs * p = if_addrs.get(); p; p = p->ifa_next) - { -#if defined(TARGET_LINUX) - if (nullptr != p->ifa_addr && p->ifa_addr->sa_family == AF_PACKET && 0 == (p->ifa_flags & IFF_LOOPBACK)) - { - struct sockaddr_ll * address = reinterpret_cast(p->ifa_addr); - std::ostringstream addr; - for (int i = 0; i < address->sll_halen; ++i) - addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(address->sll_addr[i]); - mac_addr = addr.str(); - break; - } -#else - if (nullptr != p->ifa_addr && p->ifa_addr->sa_family == AF_LINK) - { - struct sockaddr_dl * address = reinterpret_cast(p->ifa_addr); - if (address->sdl_type == IFT_LOOP) - continue; - std::ostringstream addr; - for (int i = 0; i < address->sdl_alen; ++i) - addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(*(address->sdl_data + address->sdl_nlen + i)); - mac_addr = addr.str(); - break; - } -#endif - } -#elif defined(TARGET_WINDOWS) - std::unique_ptr pAddresses{static_cast(malloc(15 * 1024)), &free}; - ULONG outBufLen = 0; - - if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses.get(), &outBufLen) == ERROR_BUFFER_OVERFLOW) - { - pAddresses.reset(static_cast(malloc(outBufLen))); - } - - if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses.get(), &outBufLen) == NO_ERROR) - { - IP_ADAPTER_ADDRESSES * p_addr = pAddresses.get(); - while (p_addr) - { - if (p_addr->PhysicalAddressLength > 0) - { - std::ostringstream addr; - for (int i = 0; i < p_addr->PhysicalAddressLength; ++i) - addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(p_addr->PhysicalAddress[i]); - mac_addr = addr.str(); - break; - } - p_addr = p_addr->Next; - } - } else - { - XBMC->Log(LOG_NOTICE, "GetAdaptersAddresses failed..."); - } -#endif - return mac_addr; -} - -std::string ApiManager::formatTime(time_t t) -{ - std::string buf(17, ' '); - std::strftime(const_cast(buf.data()), buf.size(), "%Y-%m-%d %H:%M", std::localtime(&t)); - return buf; -} - -ApiManager::ApiManager(const std::string & userName, const std::string & userPassword) - : m_userName{userName} - , m_userPassword{userPassword} - , m_sessionId{std::make_shared()} -{ - XBMC->Log(LOG_NOTICE, "Loading ApiManager"); -} - -std::string ApiManager::call(const std::string & urlPath, const ApiParamMap & paramsMap, bool putSessionVar) const -{ - if (putSessionVar) - { - auto session_id = std::atomic_load(&m_sessionId); - // if we need to put the sessionVar, but not logged in... do nothing - if (session_id->empty()) - return std::string(); - } - std::string url = urlPath; - url += '?'; - url += buildQueryString(paramsMap, putSessionVar); - std::string response; - - void *fh = XBMC->OpenFile(url.c_str(), XFILE::READ_NO_CACHE); - if (fh) - { - char buffer[1024]; - while (int bytesRead = XBMC->ReadFile(fh, buffer, 1024)) - response.append(buffer, bytesRead); - XBMC->CloseFile(fh); - } - else - { - XBMC->Log(LOG_ERROR, "Cannot open url"); - } - - return response; -} - -std::string ApiManager::apiCall(const std::string &function, const ApiParamMap & paramsMap, bool putSessionVar /*= true*/) const -{ - std::string url = API_URL; - url += function; - return call(url, paramsMap, putSessionVar); -} - -bool ApiManager::isSuccess(const std::string &response, Json::Value & root) -{ - std::string jsonReaderError; - Json::CharReaderBuilder jsonReaderBuilder; - std::unique_ptr const reader(jsonReaderBuilder.newCharReader()); - - if (reader->parse(response.c_str(), response.c_str() + response.size(), &root, &jsonReaderError)) - { - bool success = root.get("status", 0).asInt() == 1; - if (!success) - XBMC->Log(LOG_ERROR, "Error indicated in response. status: %d, error: %s", root.get("status", 0).asInt(), root.get("error", "").asString().c_str()); - return success; - } - - XBMC->Log(LOG_ERROR, "Error parsing response. Response is: %*s, reader error: %s", std::min(response.size(), static_cast(1024)), response.c_str(), jsonReaderError.c_str()); - return false; -} - -bool ApiManager::isSuccess(const std::string &response) -{ - Json::Value root; - return isSuccess(response, root); -} - -bool ApiManager::pairDevice() -{ - bool new_pairing = false; - std::string pairJson = readPairFile(); - - Json::Value root; - if (pairJson.empty() || !isSuccess(pairJson, root) || root.get("userName", "").asString() != m_userName) - { - new_pairing = true; - ApiParamMap params; - - char hostName[256]; - gethostname(hostName, 256); - - std::string macAddr = get_mac_address(); - if (macAddr.empty()) - { - XBMC->Log(LOG_NOTICE, "Unable to get MAC address, using a dummy for serial"); - macAddr = "11223344"; - } - - params["username"] = m_userName; - params["password"] = m_userPassword; - params["type"] = "androidportable"; - params["product"] = hostName; - // compute SHA256 of string representation of MAC address - params["serial"] = picosha2::hash256_hex_string(macAddr); - params["unit"] = "default"; - //params["checkLimit"] = "1"; - - pairJson = apiCall("create-pairing", params, false); - } - - if (isSuccess(pairJson, root)) - { - int devId = root.get("deviceId", 0).asInt(); - std::string passwd = root.get("password", "").asString(); - - char buf[256]; - sprintf(buf, "%d", devId); - m_deviceId = buf; - m_password = passwd; - - XBMC->Log(LOG_DEBUG, "Device ID: %d, Password: %s", devId, passwd.c_str()); - - const bool paired = !m_deviceId.empty() && !m_password.empty(); - - if (paired && new_pairing) - { - // add the userName to written json - root["userName"] = m_userName; - std::ostringstream os; - os << root; - createPairFile(os.str()); - } - return paired; - } - else - { - XBMC->Log(LOG_ERROR, "Error in pairing response."); - } - - return false; -} - -bool ApiManager::login() -{ - if (m_deviceId.empty() && m_password.empty()) - { - if (!pairDevice()) - { - XBMC->Log(LOG_ERROR, "Cannot pair device"); - return false; - } - } - - ApiParamMap param; - param["deviceId"] = m_deviceId; - param["password"] = m_password; - param["unit"] = "default"; - - Json::Value root; - - std::string new_session_id; - if (isSuccess(apiCall("device-login", param, false), root)) - { - new_session_id = root.get("PHPSESSID", "").asString(); - - if (new_session_id.empty()) - { - XBMC->Log(LOG_ERROR, "Cannot perform device login"); - } - else - { - XBMC->Log(LOG_INFO, "Device logged in. Session ID: %s", new_session_id.c_str()); - } - } - - const bool success = !new_session_id.empty(); - if (!success) - { - m_deviceId.clear(); - m_password.clear(); - createPairFile(std::string{}); // truncate any "old" pairing response - } - - std::atomic_store(&m_sessionId, std::make_shared(std::move(new_session_id))); - - return success; -} - -bool ApiManager::getPlaylist(StreamQuality_t quality, bool useH265, bool useAdaptive, Json::Value & root) -{ - ApiParamMap params; - params["format"] = "m3u8"; - params["quality"] = std::to_string(quality); - std::string caps = useAdaptive ? "adaptive" : ""; - if (useH265) - { - if (!caps.empty()) - caps += ','; - caps += "h265"; - } - params["capabilities"] = std::move(caps); - return isSuccess(apiCall("playlist", params), root); -} - -bool ApiManager::getStreamQualities(Json::Value & root) -{ - return isSuccess(apiCall("get-stream-qualities", ApiParamMap()), root); -} - -bool ApiManager::getEpg(time_t start, bool smallDuration, const std::string & channels, Json::Value & root) -{ - ApiParamMap params; - - params["time"] = formatTime(start); - params["duration"] = smallDuration ? "60" : "1439"; - params["detail"] = "1"; - if (!channels.empty()) - params["channels"] = std::move(channels); - - return isSuccess(apiCall("epg", params), root); -} - -bool ApiManager::getPvr(Json::Value & root) -{ - return isSuccess(apiCall("get-pvr", ApiParamMap()), root); -} - -std::string ApiManager::getRecordingUrl(const std::string &recId, std::string & channel) -{ - ApiParamMap param; - param["recordId"] = recId; - param["format"] = "m3u8"; - - Json::Value root; - - if (isSuccess(apiCall("record-timeshift", param), root)) - { - channel = root.get("channel", "").asString(); - return root.get("url", "").asString(); - } - - return ""; -} - -bool ApiManager::getTimeShiftInfo(const std::string &eventId - , std::string & streamUrl - , std::string & channel - , int & duration) const -{ - ApiParamMap param; - param["eventId"] = eventId; - param["format"] = "m3u8"; - - Json::Value root; - - if (isSuccess(apiCall("event-timeshift", param), root)) - { - streamUrl = root.get("url", "").asString(); - channel = root.get("channel", "").asString(); - duration = root.get("duration", 0).asInt(); - return true; - } - - return false; -} - -bool ApiManager::addTimer(const std::string &eventId, std::string & recordId) -{ - ApiParamMap param; - param["eventId"] = eventId; - - Json::Value root; - - if (isSuccess(apiCall("record-event", param), root)) - { - recordId = root.get("recordId", "").asString(); - return true; - } - return false; -} - -bool ApiManager::deleteRecord(const std::string &recId) -{ - ApiParamMap param; - param["recordId"] = recId; - - return isSuccess(apiCall("delete-record", param)); -} - -bool ApiManager::keepAlive() -{ - ApiParamMap param; - return isSuccess(apiCall("keepalive", param)); -} - -bool ApiManager::loggedIn() const -{ - auto session_id = std::atomic_load(&m_sessionId); - return !session_id->empty(); -} - -std::string ApiManager::urlEncode(const std::string &str) -{ - std::string strOut; - strOut.append(url_encode(str.c_str())); - - return strOut; -} - -std::string ApiManager::buildQueryString(const ApiParamMap & paramMap, bool putSessionVar) const -{ - XBMC->Log(LOG_DEBUG, "%s - size %d", __FUNCTION__, paramMap.size()); - std::string strOut; - for (const auto & param : paramMap) - { - if (!strOut.empty()) - { - strOut += "&"; - } - - strOut += param.first + "=" + urlEncode(param.second); - } - - std::shared_ptr session_id = std::atomic_load(&m_sessionId); - - if (putSessionVar) - strOut += "&PHPSESSID="; - strOut += *session_id; - - return strOut; -} - -std::string ApiManager::readPairFile() -{ - std::string url = GetUserFilePath(PAIR_FILE); - std::string strContent; - - XBMC->Log(LOG_DEBUG, "Openning file %s", url.c_str()); - - void* fileHandle = XBMC->OpenFile(url.c_str(), 0); - if (fileHandle) - { - char buffer[1024]; - while (int bytesRead = XBMC->ReadFile(fileHandle, buffer, 1024)) - strContent.append(buffer, bytesRead); - XBMC->CloseFile(fileHandle); - } - - return strContent; -} - -void ApiManager::createPairFile(const std::string &content) -{ - std::string url = GetUserFilePath(PAIR_FILE); - - void *fileHandle = XBMC->OpenFileForWrite(url.c_str(), true); - if (fileHandle) - { - XBMC->WriteFile(fileHandle, content.c_str(), content.length()); - XBMC->CloseFile(fileHandle); - } -} diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/ApiManager.cpp kodi-pvr-sledovanitv-cz-1.6.0/src/ApiManager.cpp --- kodi-pvr-sledovanitv-cz-1.5.1/src/ApiManager.cpp 1970-01-01 00:00:00.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/ApiManager.cpp 2020-03-13 06:39:40.000000000 +0000 @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2018~now Palo Kisa + * + * Copyright (C) 2014 Josef Rokos + * http://github.com/PepaRokos/xbmc-pvr-addons/ + * + * Copyright (C) 2011 Pulse-Eight + * http://www.pulse-eight.com/ + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this addon; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#if defined(TARGET_POSIX) +#include +#endif +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) +#include +#include +# if defined(TARGET_LINUX) +#include +# else //defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) +#include +#include +# endif +# if defined(TARGET_ANDROID) +#include +# endif +#elif defined(TARGET_WINDOWS) +#include +#include +#include +#pragma comment(lib, "IPHLPAPI.lib") +#pragma comment(lib, "WSOCK32.lib") +#endif + +#include +#include + +#include "client.h" +#include "ApiManager.h" +#include "picosha2.h" +#include +#include +#include +#include +#include + +namespace sledovanitvcz +{ + +const std::string ApiManager::API_URL = "https://sledovanitv.cz/api/"; +const std::string ApiManager::TIMESHIFTINFO_URL = "https://sledovanitv.cz/playback/timeshiftInfo"; +const std::string ApiManager::PAIR_FILE = "pairinfo"; + +/* Converts a hex character to its integer value */ +char from_hex(char ch) +{ + return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; +} + +/* Converts an integer value to its hex character*/ +char to_hex(char code) +{ + static char hex[] = "0123456789abcdef"; + return hex[code & 15]; +} + +char *url_encode(const char *str) +{ + char *pstr = (char*) str, *buf = (char *)malloc(strlen(str) * 3 + 1), *pbuf = buf; + while (*pstr) +{ + if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') + *pbuf++ = *pstr; + else if (*pstr == ' ') + *pbuf++ = '+'; + else + *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); + pstr++; + } + *pbuf = '\0'; + return buf; +} + +#if defined(TARGET_ANDROID) && __ANDROID_API__ < 24 +// Note: declare dummy "ifaddr" functions to make this compile on pre API-24 versions +#warning "Compiling for ANDROID with API version < 24, getting MAC addres will not be available." +static int getifaddrs(struct ifaddrs ** /*ifap*/) { errno = ENOTSUP; return -1; } +static void freeifaddrs(struct ifaddrs * /*ifa*/) {} +#endif +static std::string get_mac_address() +{ + std::string mac_addr; +#if defined(TARGET_ANDROID) && __ANDROID_API__ < 24 + XBMC->Log(ADDON::LOG_NOTICE, "Can't get MAC address with target Android API < 24 (no getifaddrs() support)"); +#endif +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_DARWIN) + struct ifaddrs * addrs; + if (0 != getifaddrs(&addrs)) + { + XBMC->Log(ADDON::LOG_NOTICE, "While getting MAC address getifaddrs() failed, %s", strerror(errno)); + return mac_addr; + } + std::unique_ptr if_addrs{addrs, &freeifaddrs}; + for (struct ifaddrs * p = if_addrs.get(); p; p = p->ifa_next) + { +#if defined(TARGET_LINUX) + if (nullptr != p->ifa_addr && p->ifa_addr->sa_family == AF_PACKET && 0 == (p->ifa_flags & IFF_LOOPBACK)) + { + struct sockaddr_ll * address = reinterpret_cast(p->ifa_addr); + std::ostringstream addr; + for (int i = 0; i < address->sll_halen; ++i) + addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(address->sll_addr[i]); + mac_addr = addr.str(); + break; + } +#else + if (nullptr != p->ifa_addr && p->ifa_addr->sa_family == AF_LINK) + { + struct sockaddr_dl * address = reinterpret_cast(p->ifa_addr); + if (address->sdl_type == IFT_LOOP) + continue; + std::ostringstream addr; + for (int i = 0; i < address->sdl_alen; ++i) + addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(*(address->sdl_data + address->sdl_nlen + i)); + mac_addr = addr.str(); + break; + } +#endif + } +#elif defined(TARGET_WINDOWS) + std::unique_ptr pAddresses{static_cast(malloc(15 * 1024)), &free}; + ULONG outBufLen = 0; + + if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses.get(), &outBufLen) == ERROR_BUFFER_OVERFLOW) + { + pAddresses.reset(static_cast(malloc(outBufLen))); + } + + if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses.get(), &outBufLen) == NO_ERROR) + { + IP_ADAPTER_ADDRESSES * p_addr = pAddresses.get(); + while (p_addr) + { + if (p_addr->PhysicalAddressLength > 0) + { + std::ostringstream addr; + for (int i = 0; i < p_addr->PhysicalAddressLength; ++i) + addr << std::hex << std::setw(2) << std::setfill('0') << static_cast(p_addr->PhysicalAddress[i]); + mac_addr = addr.str(); + break; + } + p_addr = p_addr->Next; + } + } else + { + XBMC->Log(ADDON::LOG_NOTICE, "GetAdaptersAddresses failed..."); + } +#endif + return mac_addr; +} + +std::string ApiManager::formatTime(time_t t) +{ + std::string buf(17, ' '); + std::strftime(const_cast(buf.data()), buf.size(), "%Y-%m-%d %H:%M", std::localtime(&t)); + return buf; +} + +ApiManager::ApiManager(const std::string & userName + , const std::string & userPassword + , const std::string & overridenMac + , const std::string & product) + : m_userName{userName} + , m_userPassword{userPassword} + , m_overridenMac{overridenMac} + , m_product{product} + , m_pinUnlocked{false} + , m_sessionId{std::make_shared()} +{ + XBMC->Log(ADDON::LOG_NOTICE, "Loading ApiManager"); +} + +std::string ApiManager::call(const std::string & urlPath, const ApiParamMap & paramsMap, bool putSessionVar) const +{ + if (putSessionVar) + { + auto session_id = std::atomic_load(&m_sessionId); + // if we need to put the sessionVar, but not logged in... do nothing + if (session_id->empty()) + return std::string(); + } + std::string url = urlPath; + url += '?'; + url += buildQueryString(paramsMap, putSessionVar); + // add User-Agent header... TODO: make it configurable + url += "|User-Agent=okhttp%2F3.12.0"; + std::string response; + + void *fh = XBMC->OpenFile(url.c_str(), XFILE::READ_NO_CACHE); + if (fh) + { + char buffer[1024]; + while (int bytesRead = XBMC->ReadFile(fh, buffer, 1024)) + response.append(buffer, bytesRead); + XBMC->CloseFile(fh); + } + else + { + XBMC->Log(ADDON::LOG_ERROR, "Cannot open url"); + } + + return response; +} + +std::string ApiManager::apiCall(const std::string &function, const ApiParamMap & paramsMap, bool putSessionVar /*= true*/) const +{ + std::string url = API_URL; + url += function; + return call(url, paramsMap, putSessionVar); +} + +bool ApiManager::isSuccess(const std::string &response, Json::Value & root) +{ + std::string jsonReaderError; + Json::CharReaderBuilder jsonReaderBuilder; + std::unique_ptr const reader(jsonReaderBuilder.newCharReader()); + + if (reader->parse(response.c_str(), response.c_str() + response.size(), &root, &jsonReaderError)) + { + bool success = root.get("status", 0).asInt() == 1; + if (!success) + XBMC->Log(ADDON::LOG_ERROR, "Error indicated in response. status: %d, error: %s", root.get("status", 0).asInt(), root.get("error", "").asString().c_str()); + return success; + } + + XBMC->Log(ADDON::LOG_ERROR, "Error parsing response. Response is: %*s, reader error: %s", std::min(response.size(), static_cast(1024)), response.c_str(), jsonReaderError.c_str()); + return false; +} + +bool ApiManager::isSuccess(const std::string &response) +{ + Json::Value root; + return isSuccess(response, root); +} + +bool ApiManager::pairDevice() +{ + bool new_pairing = false; + std::string pairJson = readPairFile(); + + Json::Value root; + if (pairJson.empty() || !isSuccess(pairJson, root) || root.get("userName", "").asString() != m_userName) + { + // remove pairing if any exising + const std::string old_dev_id = root.get("deviceId", "").asString(); + const std::string old_password = root.get("password", "").asString(); + if (!old_dev_id.empty()) + { + ApiParamMap params_del; + params_del["deviceId"] = old_dev_id; + params_del["password"] = old_password; + isSuccess(apiCall("delete-pairing", params_del, false)); + } + + new_pairing = true; + ApiParamMap params; + + std::string product = m_product; + if (product.empty()) + { + char host_name[256]; + gethostname(host_name, 256); + product = host_name; + } + + std::string macAddr = m_overridenMac.empty() ? get_mac_address() : m_overridenMac; + if (macAddr.empty()) + { + XBMC->Log(ADDON::LOG_NOTICE, "Unable to get MAC address, using a dummy for serial"); + macAddr = "11223344"; + } + + params["username"] = m_userName; + params["password"] = m_userPassword; + params["type"] = "androidportable"; + params["product"] = product; + // compute SHA256 of string representation of MAC address + params["serial"] = picosha2::hash256_hex_string(macAddr); + params["unit"] = "default"; + params["checkLimit"] = "1"; + + pairJson = apiCall("create-pairing", params, false); + } + + if (isSuccess(pairJson, root)) + { + int devId = root.get("deviceId", 0).asInt(); + std::string passwd = root.get("password", "").asString(); + + char buf[256]; + sprintf(buf, "%d", devId); + m_deviceId = buf; + m_password = passwd; + + XBMC->Log(ADDON::LOG_DEBUG, "Device ID: %d, Password: %s", devId, passwd.c_str()); + + const bool paired = !m_deviceId.empty() && !m_password.empty(); + + if (paired && new_pairing) + { + // add the userName to written json + root["userName"] = m_userName; + std::ostringstream os; + os << root; + createPairFile(os.str()); + } + return paired; + } + else + { + XBMC->Log(ADDON::LOG_ERROR, "Error in pairing response."); + } + + return false; +} + +bool ApiManager::login() +{ + m_pinUnlocked = false; + if (m_deviceId.empty() && m_password.empty()) + { + if (!pairDevice()) + { + XBMC->Log(ADDON::LOG_ERROR, "Cannot pair device"); + return false; + } + } + + ApiParamMap param; + param["deviceId"] = m_deviceId; + param["password"] = m_password; + param["version"] = "2.6.21"; + param["lang"] = "en"; + param["unit"] = "default"; + + Json::Value root; + + std::string new_session_id; + if (isSuccess(apiCall("device-login", param, false), root)) + { + new_session_id = root.get("PHPSESSID", "").asString(); + + if (new_session_id.empty()) + { + XBMC->Log(ADDON::LOG_ERROR, "Cannot perform device login"); + } + else + { + XBMC->Log(ADDON::LOG_NOTICE, "Device logged in. Session ID: %s", new_session_id.c_str()); + } + } + + const bool success = !new_session_id.empty(); + if (!success) + { + m_deviceId.clear(); + m_password.clear(); + createPairFile(std::string{}); // truncate any "old" pairing response + } + + std::atomic_store(&m_sessionId, std::make_shared(std::move(new_session_id))); + + return success; +} + +bool ApiManager::pinUnlock(const std::string & pin) +{ + ApiParamMap params; + params["pin"] = pin; + + bool result = isSuccess(apiCall("pin-unlock", params)); + if (result) + m_pinUnlocked = true; + return result; +} + +bool ApiManager::pinUnlocked() const +{ + return m_pinUnlocked; +} + +bool ApiManager::getPlaylist(StreamQuality_t quality, bool useH265, bool useAdaptive, Json::Value & root) +{ + ApiParamMap params; + params["format"] = "m3u8"; + params["quality"] = std::to_string(quality); + std::string caps = useH265 ? "h265" : ""; + if (useAdaptive) + { + if (!caps.empty()) + caps += ','; + caps += "adaptive2"; + } + params["capabilities"] = std::move(caps); + return isSuccess(apiCall("playlist", params), root); +} + +bool ApiManager::getStreamQualities(Json::Value & root) +{ + return isSuccess(apiCall("get-stream-qualities", ApiParamMap()), root); +} + +bool ApiManager::getEpg(time_t start, bool smallDuration, const std::string & channels, Json::Value & root) +{ + ApiParamMap params; + + params["time"] = formatTime(start); + params["duration"] = smallDuration ? "60" : "1439"; + params["detail"] = "1"; + if (!channels.empty()) + params["channels"] = std::move(channels); + + return isSuccess(apiCall("epg", params), root); +} + +bool ApiManager::getPvr(Json::Value & root) +{ + return isSuccess(apiCall("get-pvr", ApiParamMap()), root); +} + +std::string ApiManager::getRecordingUrl(const std::string &recId, std::string & channel) +{ + ApiParamMap param; + param["recordId"] = recId; + param["format"] = "m3u8"; + + Json::Value root; + + if (isSuccess(apiCall("record-timeshift", param), root)) + { + channel = root.get("channel", "").asString(); + return root.get("url", "").asString(); + } + + return ""; +} + +bool ApiManager::getTimeShiftInfo(const std::string &eventId + , std::string & streamUrl + , std::string & channel + , int & duration) const +{ + ApiParamMap param; + param["eventId"] = eventId; + param["format"] = "m3u8"; + + Json::Value root; + + if (isSuccess(apiCall("event-timeshift", param), root)) + { + streamUrl = root.get("url", "").asString(); + channel = root.get("channel", "").asString(); + duration = root.get("duration", 0).asInt(); + return true; + } + + return false; +} + +bool ApiManager::addTimer(const std::string &eventId, std::string & recordId) +{ + ApiParamMap param; + param["eventId"] = eventId; + + Json::Value root; + + if (isSuccess(apiCall("record-event", param), root)) + { + recordId = root.get("recordId", "").asString(); + return true; + } + return false; +} + +bool ApiManager::deleteRecord(const std::string &recId) +{ + ApiParamMap param; + param["recordId"] = recId; + + return isSuccess(apiCall("delete-record", param)); +} + +bool ApiManager::keepAlive() +{ + ApiParamMap param; + return isSuccess(apiCall("keepalive", param)); +} + +bool ApiManager::loggedIn() const +{ + auto session_id = std::atomic_load(&m_sessionId); + return !session_id->empty(); +} + +std::string ApiManager::urlEncode(const std::string &str) +{ + std::string strOut; + strOut.append(url_encode(str.c_str())); + + return strOut; +} + +std::string ApiManager::buildQueryString(const ApiParamMap & paramMap, bool putSessionVar) const +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s - size %d", __FUNCTION__, paramMap.size()); + std::string strOut; + for (const auto & param : paramMap) + { + if (!strOut.empty()) + { + strOut += "&"; + } + + strOut += param.first + "=" + urlEncode(param.second); + } + + if (putSessionVar) + { + auto session_id = std::atomic_load(&m_sessionId); + strOut += "&PHPSESSID="; + strOut += *session_id; + } + + return strOut; +} + +std::string ApiManager::readPairFile() +{ + std::string url = GetUserFilePath(PAIR_FILE); + std::string strContent; + + XBMC->Log(ADDON::LOG_DEBUG, "Openning file %s", url.c_str()); + + void* fileHandle = XBMC->OpenFile(url.c_str(), 0); + if (fileHandle) + { + char buffer[1024]; + while (int bytesRead = XBMC->ReadFile(fileHandle, buffer, 1024)) + strContent.append(buffer, bytesRead); + XBMC->CloseFile(fileHandle); + } + + return strContent; +} + +void ApiManager::createPairFile(const std::string &content) +{ + std::string url = GetUserFilePath(PAIR_FILE); + + void *fileHandle = XBMC->OpenFileForWrite(url.c_str(), true); + if (fileHandle) + { + XBMC->WriteFile(fileHandle, content.c_str(), content.length()); + XBMC->CloseFile(fileHandle); + } +} + +} // namespace sledovanitvcz diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/apimanager.h kodi-pvr-sledovanitv-cz-1.6.0/src/apimanager.h --- kodi-pvr-sledovanitv-cz-1.5.1/src/apimanager.h 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/apimanager.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2018~now Palo Kisa - * - * Copyright (C) 2014 Josef Rokos - * http://github.com/PepaRokos/xbmc-pvr-addons/ - * - * Copyright (C) 2011 Pulse-Eight - * http://www.pulse-eight.com/ - * - * This Program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This Program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this addon; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * http://www.gnu.org/copyleft/gpl.html - * - */ - -#ifndef APIMANAGER_H -#define APIMANAGER_H - -#include -#include -#include - -typedef std::map ApiParamMap; - -namespace Json -{ - class Value; -} - -class ApiManager -{ -public: - enum StreamQuality_t - { - SQ_DEFAULT = 0 - , SQ_SD = 20 - , SQ_HD = 40 - }; -public: - static std::string formatTime(time_t t); - -public: - ApiManager(const std::string & userName, const std::string & userPassword); - - bool pairDevice(); - bool login(); - bool getPlaylist(StreamQuality_t quality, bool useH265, bool useAdaptive, Json::Value & root); - bool getStreamQualities(Json::Value & root); - bool getEpg(time_t start, bool smallDuration, const std::string & channels, Json::Value & root); - bool getPvr(Json::Value & root); - std::string getRecordingUrl(const std::string &recId, std::string & channel); - bool getTimeShiftInfo(const std::string &eventId - , std::string & streamUrl - , std::string & channel - , int & duration) const; - bool addTimer(const std::string & eventId, std::string & recordId); - bool deleteRecord(const std::string &recId); - bool keepAlive(); - bool loggedIn() const; - -private: - static std::string urlEncode(const std::string &str); - static std::string readPairFile(); - static void createPairFile(const std::string &content); - static bool isSuccess(const std::string &response, Json::Value & root); - static bool isSuccess(const std::string &response); - - std::string buildQueryString(const ApiParamMap & paramMap, bool putSessionVar) const; - std::string call(const std::string & urlPath, const ApiParamMap & paramsMap, bool putSessionVar) const; - std::string apiCall(const std::string &function, const ApiParamMap & paramsMap, bool putSessionVar = true) const; - - static const std::string API_URL; - static const std::string TIMESHIFTINFO_URL; - static const std::string PAIR_FILE; - const std::string m_userName; - const std::string m_userPassword; - std::string m_deviceId; - std::string m_password; - std::shared_ptr m_sessionId; -}; - -#endif // APIMANAGER_H diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/ApiManager.h kodi-pvr-sledovanitv-cz-1.6.0/src/ApiManager.h --- kodi-pvr-sledovanitv-cz-1.5.1/src/ApiManager.h 1970-01-01 00:00:00.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/ApiManager.h 2020-03-13 06:39:40.000000000 +0000 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018~now Palo Kisa + * + * Copyright (C) 2014 Josef Rokos + * http://github.com/PepaRokos/xbmc-pvr-addons/ + * + * Copyright (C) 2011 Pulse-Eight + * http://www.pulse-eight.com/ + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this addon; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef sledovanitcz_ApiManager_h +#define sledovanitcz_ApiManager_h + +#include +#include +#include + +namespace Json +{ + class Value; +} + +namespace sledovanitvcz +{ + +typedef std::map ApiParamMap; + +class ApiManager +{ +public: + enum StreamQuality_t + { + SQ_DEFAULT = 0 + , SQ_SD = 20 + , SQ_HD = 40 + }; +public: + static std::string formatTime(time_t t); + +public: + ApiManager(const std::string & userName + , const std::string & userPassword + , const std::string & overridenMac + , const std::string & product); + + bool pairDevice(); + bool login(); + bool pinUnlock(const std::string & pin); + bool getPlaylist(StreamQuality_t quality, bool useH265, bool useAdaptive, Json::Value & root); + bool getStreamQualities(Json::Value & root); + bool getEpg(time_t start, bool smallDuration, const std::string & channels, Json::Value & root); + bool getPvr(Json::Value & root); + std::string getRecordingUrl(const std::string &recId, std::string & channel); + bool getTimeShiftInfo(const std::string &eventId + , std::string & streamUrl + , std::string & channel + , int & duration) const; + bool addTimer(const std::string & eventId, std::string & recordId); + bool deleteRecord(const std::string &recId); + bool keepAlive(); + bool loggedIn() const; + bool pinUnlocked() const; + +private: + static std::string urlEncode(const std::string &str); + static std::string readPairFile(); + static void createPairFile(const std::string &content); + static bool isSuccess(const std::string &response, Json::Value & root); + static bool isSuccess(const std::string &response); + + std::string buildQueryString(const ApiParamMap & paramMap, bool putSessionVar) const; + std::string call(const std::string & urlPath, const ApiParamMap & paramsMap, bool putSessionVar) const; + std::string apiCall(const std::string &function, const ApiParamMap & paramsMap, bool putSessionVar = true) const; + + static const std::string API_URL; + static const std::string TIMESHIFTINFO_URL; + static const std::string PAIR_FILE; + const std::string m_userName; + const std::string m_userPassword; + const std::string m_overridenMac; + const std::string m_product; + std::string m_deviceId; + std::string m_password; + bool m_pinUnlocked; + std::shared_ptr m_sessionId; +}; + +} // namespace sledovanitvcz +#endif // sledovanitcz_ApiManager_h diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/client.cpp kodi-pvr-sledovanitv-cz-1.6.0/src/client.cpp --- kodi-pvr-sledovanitv-cz-1.5.1/src/client.cpp 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/client.cpp 2020-03-13 06:39:40.000000000 +0000 @@ -26,7 +26,7 @@ #include "client.h" -#include "PVRIptvData.h" +#include "Data.h" #include "kodi/xbmc_pvr_dll.h" #include @@ -46,7 +46,7 @@ ADDON_STATUS m_CurStatus = ADDON_STATUS_UNKNOWN; -static std::shared_ptr m_data; +static std::shared_ptr m_data; /* User adjustable settings are saved here. * Default values are defined inside client.h @@ -58,6 +58,7 @@ std::unique_ptr XBMC; std::unique_ptr PVR; +std::unique_ptr GUI; std::string PathCombine(const std::string &strPath, const std::string &strFileName) { @@ -86,7 +87,7 @@ return PathCombine(g_strUserPath, strFileName); } -static void ReadSettings(PVRIptvConfiguration & cfg) +static void ReadSettings(sledovanitvcz::Configuration & cfg) { char buffer[1024]; @@ -100,6 +101,16 @@ cfg.password = buffer; } + if (XBMC->GetSetting("deviceId", &buffer)) + { + cfg.deviceId = buffer; + } + + if (XBMC->GetSetting("productId", &buffer)) + { + cfg.productId = buffer; + } + if (!XBMC->GetSetting("streamQuality", &cfg.streamQuality)) { cfg.streamQuality = 0; @@ -143,9 +154,14 @@ { cfg.showLockedChannels = true; } + + if (!XBMC->GetSetting("showLockedOnlyPin", &cfg.showLockedOnlyPin)) + { + cfg.showLockedOnlyPin = true; + } } -static PVR_ERROR FillStreamProperties(const properties_t & props, PVR_NAMED_VALUE* properties, unsigned int* iPropertiesCount) +static PVR_ERROR FillStreamProperties(const sledovanitvcz::properties_t & props, PVR_NAMED_VALUE* properties, unsigned int* iPropertiesCount) { if (*iPropertiesCount < props.size()) return PVR_ERROR_INVALID_PARAMETERS; @@ -189,6 +205,13 @@ return ADDON_STATUS_PERMANENT_FAILURE; } + GUI.reset(new CHelper_libKODI_guilib); + if (!GUI->RegisterMe(hdl)) + { + GUI.reset(nullptr); + return ADDON_STATUS_PERMANENT_FAILURE; + } + XBMC->Log(LOG_DEBUG, "%s - Creating the %s", __FUNCTION__, GetBackendName()); m_CurStatus = ADDON_STATUS_UNKNOWN; @@ -200,12 +223,12 @@ XBMC->CreateDirectory(g_strUserPath.c_str()); } - PVRIptvConfiguration cfg; + sledovanitvcz::Configuration cfg; ReadSettings(cfg); cfg.epgMaxDays = pvrprops->iEpgMaxDays; - std::atomic_store(&m_data, std::shared_ptr{nullptr}); // be sure that the previous one is deleted before new is constructed - std::atomic_store(&m_data, std::make_shared(std::move(cfg))); + std::atomic_store(&m_data, std::shared_ptr{nullptr}); // be sure that the previous one is deleted before new is constructed + std::atomic_store(&m_data, std::make_shared(std::move(cfg))); m_CurStatus = ADDON_STATUS_OK; return m_CurStatus; @@ -218,7 +241,7 @@ void ADDON_Destroy() { - std::atomic_store(&m_data, std::shared_ptr{nullptr}); + std::atomic_store(&m_data, std::shared_ptr{nullptr}); m_CurStatus = ADDON_STATUS_UNKNOWN; } diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/client.h kodi-pvr-sledovanitv-cz-1.6.0/src/client.h --- kodi-pvr-sledovanitv-cz-1.5.1/src/client.h 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/client.h 2020-03-13 06:39:40.000000000 +0000 @@ -27,10 +27,12 @@ #include "kodi/libXBMC_addon.h" #include "kodi/libXBMC_pvr.h" +#include "kodi/libKODI_guilib.h" #include extern std::unique_ptr XBMC; extern std::unique_ptr PVR; +extern std::unique_ptr GUI; extern std::string PathCombine(const std::string &strPath, const std::string &strFileName); extern std::string GetClientFilePath(const std::string &strFileName); diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/Data.cpp kodi-pvr-sledovanitv-cz-1.6.0/src/Data.cpp --- kodi-pvr-sledovanitv-cz-1.5.1/src/Data.cpp 1970-01-01 00:00:00.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/Data.cpp 2020-03-13 06:39:40.000000000 +0000 @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 2018~now Palo Kisa + * + * Copyright (C) 2014 Josef Rokos + * http://github.com/PepaRokos/xbmc-pvr-addons/ + * + * Copyright (C) 2011 Pulse-Eight + * http://www.pulse-eight.com/ + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this addon; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Data.h" +#include "CallLimiter.hh" + +#if defined(TARGET_WINDOWS) +# define LOCALTIME_R(src, dst) localtime_s(dst, src) +# define GMTIME_R(src, dst) gmtime_s(dst, src) +#else +# define LOCALTIME_R(src, dst) localtime_r(src, dst) +# define GMTIME_R(src, dst) gmtime_r(src, dst) +#endif + +namespace sledovanitvcz +{ + +template void strAssign(char (&dst)[N], const std::string & src) +{ + strncpy(dst, src.c_str(), N - 1); + dst[N - 1] = '\0'; // just to be sure +} + +static void xbmcStrFree(char * str) +{ + XBMC->FreeString(str); +} + +static unsigned DiffBetweenPragueAndLocalTime(const time_t * when = nullptr) +{ + time_t tloc; + if (0 == when) + time(&tloc); + else + tloc = *when; + + struct tm tm1; + LOCALTIME_R(&tloc, &tm1); + auto isdst = tm1.tm_isdst; + GMTIME_R(&tloc, &tm1); + tm1.tm_isdst = isdst; + time_t t2 = mktime(&tm1); + + // Note: Prague(Czech) is in Central Europe Time -> CET or CEST == UTC+1 or UTC+2 == +3600 or +7200 + return tloc - t2 - (isdst > 0 ? 7200 : 3600); +} + +Data::Data(Configuration cfg) + : m_bKeepAlive{true} + , m_bLoadRecordings{true} + , m_bChannelsLoaded{false} + , m_groups{std::make_shared()} + , m_channels{std::make_shared()} + , m_epg{std::make_shared()} + , m_recordings{std::make_shared()} + , m_timers{std::make_shared()} + , m_recordingAvailableDuration{0} + , m_recordingRecordedDuration{0} + , m_epgMinTime{time(nullptr)} + , m_epgMaxTime{time(nullptr) + 3600} + , m_epgMaxDays{cfg.epgMaxDays} + , m_bEGPLoaded{false} + , m_iLastStart{0} + , m_iLastEnd{0} + , m_streamQuality{static_cast(cfg.streamQuality)} + , m_fullChannelEpgRefresh{cfg.fullChannelEpgRefresh} + , m_loadingsRefresh{cfg.loadingsRefresh} + , m_keepAliveDelay{cfg.keepAliveDelay} + , m_epgCheckDelay{cfg.epgCheckDelay} + , m_useH265{cfg.useH265} + , m_useAdaptive{cfg.useAdaptive} + , m_showLockedChannels{cfg.showLockedChannels} + , m_showLockedOnlyPin{cfg.showLockedOnlyPin} + , m_manager{std::move(cfg.userName), std::move(cfg.password), std::move(cfg.deviceId), std::move(cfg.productId)} +{ + + SetEPGTimeFrame(m_epgMaxDays); + + m_thread = std::thread{[this] { Process(); }}; +} + +bool Data::LoadRecordingsJob() +{ + if (!KeepAlive()) + return false; + + bool load = false; + { + std::lock_guard critical(m_mutex); + if (m_bLoadRecordings) + { + load = true; + m_bLoadRecordings = false; + } + } + if (load) + { + LoadRecordings(); + } + return load; +} + +void Data::SetLoadRecordings() +{ + std::lock_guard critical(m_mutex); + m_bLoadRecordings = true; +} + +void Data::TriggerFullRefresh() +{ + XBMC->Log(ADDON::LOG_NOTICE, "%s triggering channels/EGP full refresh", __FUNCTION__); + m_iLastEnd = 0; + m_iLastStart = 0; + + int epg_max_days = 0; + { + std::lock_guard critical(m_mutex); + epg_max_days = m_epgMaxDays; + } + SetEPGTimeFrame(epg_max_days); + LoadPlayList(); +} + +bool Data::LoadEPGJob() +{ + XBMC->Log(ADDON::LOG_INFO, "%s will check if EGP loading needed", __FUNCTION__); + time_t min_epg, max_epg; + { + std::lock_guard critical(m_mutex); + min_epg = m_epgMinTime; + max_epg = m_epgMaxTime; + } + bool updated = false; + if (KeepAlive() && 0 == m_iLastEnd) + { + // the first run...load just needed data as soon as posible + LoadEPG(time(nullptr), true); + updated = true; + } else + { + if (KeepAlive() && max_epg > m_iLastEnd) + { + LoadEPG(m_iLastEnd, max_epg - m_iLastEnd <= 3600); + updated = true; + } + if (KeepAlive() && min_epg < m_iLastStart) + { + LoadEPG(m_iLastStart - 86400, false); + updated = true; + } + } + if (KeepAlive()) + ReleaseUnneededEPG(); + return updated; +} + +void Data::ReleaseUnneededEPG() +{ + decltype (m_epg) epg; + time_t min_epg, max_epg; + { + std::lock_guard critical(m_mutex); + min_epg = m_epgMinTime; + max_epg = m_epgMaxTime; + epg = m_epg; + } + auto epg_copy = std::make_shared(); + XBMC->Log(ADDON::LOG_DEBUG, "%s min_epg=%s max_epg=%s", __FUNCTION__, ApiManager::formatTime(min_epg).c_str(), ApiManager::formatTime(max_epg).c_str()); + + for (const auto & epg_channel : *epg) + { + auto & epg_data = epg_channel.second.epg; + std::vector to_delete; + for (auto entry_i = epg_data.cbegin(); entry_i != epg_data.cend(); ++entry_i) + { + const EpgEntry & entry = entry_i->second; + if (entry_i->second.startTime > max_epg || entry_i->second.endTime < min_epg) + { + XBMC->Log(ADDON::LOG_DEBUG, "Removing TV show: %s - %s, start=%s end=%s", epg_channel.second.strName.c_str(), entry.strTitle.c_str() + , ApiManager::formatTime(entry.startTime).c_str(), ApiManager::formatTime(entry.endTime).c_str()); + // notify about the epg change...and delete it + EPG_TAG tag; + memset(&tag, 0, sizeof(EPG_TAG)); + tag.iUniqueBroadcastId = entry.iBroadcastId; + tag.iUniqueChannelId = entry.iChannelId; + PVR->EpgEventStateChange(&tag, EPG_EVENT_DELETED); + + to_delete.push_back(entry_i->first); + } + } + if (!to_delete.empty()) + { + auto & epg_copy_channel = (*epg_copy)[epg_channel.first]; + epg_copy_channel = epg_channel.second; + for (const auto key_delete : to_delete) + { + epg_copy_channel.epg.erase(key_delete); + } + } + } + + // check if something deleted, if so make copy and atomically reassign + if (!epg_copy->empty()) + { + for (const auto & epg_channel : *epg) + { + if (epg_copy->count(epg_channel.first) <= 0) + (*epg_copy)[epg_channel.first] = epg_channel.second; + } + + { + std::lock_guard critical(m_mutex); + m_epg = std::move(epg_copy); + } + } + + // narrow the loaded time info (if needed) + m_iLastStart = std::max(m_iLastStart, min_epg); + m_iLastEnd = std::min(m_iLastEnd, max_epg); +} + +void Data::KeepAliveJob() +{ + if (!KeepAlive()) + return; + + XBMC->Log(ADDON::LOG_DEBUG, "keepAlive:: trigger"); + if (!m_manager.keepAlive()) + { + LoginLoop(); + } +} + +void Data::LoginLoop() +{ + unsigned login_delay = 0; + for (bool should_try = true; KeepAlive() && should_try; --login_delay) + { + if (0 >= login_delay) + { + if (m_manager.login()) + should_try = false; + else + login_delay = 30; // try in 30 seconds + } + std::this_thread::sleep_for(std::chrono::seconds{1}); + } +} + +bool Data::WaitForChannels() const +{ + std::unique_lock critical(m_mutex); + return m_waitCond.wait_for(critical, std::chrono::seconds{5}, [this] { return m_bChannelsLoaded; }); +} + +void Data::Process(void) +{ + XBMC->Log(ADDON::LOG_DEBUG, "keepAlive:: thread started"); + + LoginLoop(); + + LoadPlayList(); + + bool epg_updated = false; + + auto keep_alive_job = getCallLimiter(std::bind(&Data::KeepAliveJob, this), std::chrono::seconds{m_keepAliveDelay}, true); + auto trigger_full_refresh = getCallLimiter(std::bind(&Data::TriggerFullRefresh, this), std::chrono::seconds{m_fullChannelEpgRefresh}, true); + auto trigger_load_recordings = getCallLimiter(std::bind(&Data::SetLoadRecordings, this), std::chrono::seconds{m_loadingsRefresh}, true); + auto epg_dummy_trigger = getCallLimiter([] {}, std::chrono::seconds{m_epgCheckDelay}, false); // using the CallLimiter just to test if the epg should be done + + bool work_done = true; + while (KeepAlive()) + { + if (!work_done) + std::this_thread::sleep_for(std::chrono::seconds{1}); + + work_done = false; + + work_done |= LoadRecordingsJob(); + + // trigger full refresh once a time + work_done |= trigger_full_refresh.Call(); + // trigger loading of recordings once a time + work_done |= trigger_load_recordings.Call(); + + if (epg_dummy_trigger.Call() || epg_updated) + { + // perform epg loading in next cycle if something updated in this one + epg_updated = LoadEPGJob(); + work_done = true; + } else + { + epg_updated = false; + } + + // do keep alive call once a time + work_done |= keep_alive_job.Call(); + } + XBMC->Log(ADDON::LOG_DEBUG, "keepAlive:: thread stopped"); +} + +Data::~Data(void) +{ + { + std::lock_guard critical(m_mutex); + m_bKeepAlive = false; + } + m_thread.join(); + XBMC->Log(ADDON::LOG_DEBUG, "%s destructed", __FUNCTION__); +} + +bool Data::KeepAlive() +{ + std::lock_guard critical(m_mutex); + return m_bKeepAlive; +} + +bool Data::LoadEPG(time_t iStart, bool bSmallStep) +{ + const int step = bSmallStep ? 3600 : 86400; + XBMC->Log(ADDON::LOG_DEBUG, "%s last start %s, start %s, last end %s, end %s", __FUNCTION__, ApiManager::formatTime(m_iLastStart).c_str() + , ApiManager::formatTime(iStart).c_str(), ApiManager::formatTime(m_iLastEnd).c_str(), ApiManager::formatTime(iStart + step).c_str()); + if (m_bEGPLoaded && m_iLastStart != 0 && iStart >= m_iLastStart && iStart + step <= m_iLastEnd) + return false; + + Json::Value root; + + if (!m_manager.getEpg(iStart, bSmallStep, ChannelsList(), root)) + { + XBMC->Log(ADDON::LOG_NOTICE, "Cannot parse EPG data. EPG not loaded."); + m_bEGPLoaded = true; + return false; + } + + if (m_iLastStart == 0 || m_iLastStart > iStart) + m_iLastStart = iStart; + if (iStart + step > m_iLastEnd) + m_iLastEnd = iStart + step; + + decltype (m_channels) channels; + decltype (m_epg) epg; + time_t min_epg, max_epg; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + epg = m_epg; + min_epg = m_epgMinTime; + max_epg = m_epgMaxTime; + } + // narrow the loaded time info (if needed) + m_iLastStart = std::max(m_iLastStart, min_epg); + m_iLastEnd = std::min(m_iLastEnd, max_epg); + + auto epg_copy = std::make_shared(*epg); + + Json::Value json_channels = root["channels"]; + Json::Value::Members chIds = json_channels.getMemberNames(); + for (Json::Value::Members::iterator i = chIds.begin(); i != chIds.end(); i++) + { + std::string strChId = *i; + + const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&strChId] (const Channel & ch) { return ch.strId == strChId; }); + if (channel_i != channels->cend()) + { + EpgChannel & epgChannel = (*epg_copy)[strChId]; + epgChannel.strId = strChId; + + Json::Value epgData = json_channels[strChId]; + for (unsigned int j = 0; j < epgData.size(); j++) + { + Json::Value epgEntry = epgData[j]; + + const time_t start_time = ParseDateTime(epgEntry.get("startTime", "").asString()); + const time_t end_time = ParseDateTime(epgEntry.get("endTime", "").asString()); + // skip unneeded EPGs + if (start_time > max_epg || end_time < min_epg) + continue; + EpgEntry iptventry; + iptventry.iBroadcastId = start_time; // unique id for channel (even if time_t is wider, int should be enough for short period of time) + iptventry.iGenreType = 0; + iptventry.iGenreSubType = 0; + iptventry.iChannelId = channel_i->iUniqueId; + iptventry.strTitle = epgEntry.get("title", "").asString(); + iptventry.strPlot = epgEntry.get("description", "").asString(); + iptventry.startTime = start_time; + iptventry.endTime = end_time; + iptventry.strEventId = epgEntry.get("eventId", "").asString(); + std::string availability = epgEntry.get("availability", "none").asString(); + iptventry.availableTimeshift = availability == "timeshift" || availability == "pvr"; + iptventry.strRecordId = epgEntry["recordId"].asString(); + + XBMC->Log(ADDON::LOG_DEBUG, "Loading TV show: %s - %s, start=%s(epoch=%llu)", strChId.c_str(), iptventry.strTitle.c_str() + , epgEntry.get("startTime", "").asString().c_str(), static_cast(start_time)); + + // notify about the epg change...and store it + EPG_TAG tag; + memset(&tag, 0, sizeof(EPG_TAG)); + + tag.iUniqueBroadcastId = iptventry.iBroadcastId; + tag.iUniqueChannelId = iptventry.iChannelId; + tag.strTitle = strdup(iptventry.strTitle.c_str()); + tag.startTime = iptventry.startTime; + tag.endTime = iptventry.endTime; + tag.strPlotOutline = strdup(iptventry.strPlotOutline.c_str()); + tag.strPlot = strdup(iptventry.strPlot.c_str()); + tag.strIconPath = strdup(iptventry.strIconPath.c_str()); + tag.iGenreType = EPG_GENRE_USE_STRING; //iptventry.iGenreType; + tag.iGenreSubType = 0; //iptventry.iGenreSubType; + tag.strGenreDescription = strdup(iptventry.strGenreString.c_str()); + + auto result = epgChannel.epg.emplace(iptventry.startTime, iptventry); + bool value_changed = !result.second; + if (value_changed) + { + epgChannel.epg[iptventry.startTime] = std::move(iptventry); + } + + PVR->EpgEventStateChange(&tag, value_changed ? EPG_EVENT_UPDATED : EPG_EVENT_CREATED); + + free(const_cast(tag.strTitle)); + free(const_cast(tag.strPlotOutline)); + free(const_cast(tag.strPlot)); + free(const_cast(tag.strIconPath)); + free(const_cast(tag.strGenreDescription)); + + } + } + } + + // atomic assign new version of the epg all epgs + { + std::lock_guard critical(m_mutex); + m_epg = epg_copy; + } + + m_bEGPLoaded = true; + XBMC->Log(ADDON::LOG_NOTICE, "EPG Loaded."); + + return true; +} + +bool Data::LoadRecordings() +{ + decltype (m_channels) channels; + decltype (m_recordings) recordings; + decltype (m_timers) timers; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + recordings = m_recordings; + timers = m_timers; + } + + auto new_recordings = std::make_shared(); + auto new_timers = std::make_shared(); + long long available_duration = 0; + long long recorded_duration = 0; + + Json::Value root; + + if (!m_manager.getPvr(root)) + { + XBMC->Log(ADDON::LOG_NOTICE, "Cannot parse recordings."); + return false; + } + + available_duration = root["summary"].get("availableDuration", 0).asInt() / 60 * 1024; //report minutes as MB + recorded_duration = root["summary"].get("recordedDuration", 0).asInt() / 60 * 1024; + + Json::Value records = root["records"]; + for (unsigned int i = 0; i < records.size(); i++) + { + Json::Value record = records[i]; + const std::string title = record.get("title", "").asString(); + const std::string locked = record.get("channelLocked", "none").asString(); + std::string directory; + if (locked != "none") + { + //Note: std::make_unique is available from c++14 + std::unique_ptr loc{XBMC->GetLocalizedString(30201), &xbmcStrFree}; + directory = loc.get(); + directory += " - "; + directory += locked; + XBMC->Log(ADDON::LOG_NOTICE, "Timer/recording '%s' is locked(%s)", title.c_str(), locked.c_str()); + } + std::string str_ch_id = record.get("channel", "").asString(); + const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&str_ch_id] (const Channel & ch) { return ch.strId == str_ch_id; }); + Recording iptvrecording; + Timer iptvtimer; + time_t startTime = ParseDateTime(record.get("startTime", "").asString()); + int duration = record.get("duration", 0).asInt(); + time_t now; + time(&now); + + if ((startTime + duration) < now) + { + char buf[256]; + sprintf(buf, "%d", record.get("id", 0).asInt()); + iptvrecording.strRecordId = buf; + iptvrecording.strTitle = std::move(title); + + if (channel_i != channels->cend()) + { + iptvrecording.strChannelName = channel_i->strChannelName; + iptvrecording.iChannelUid = channel_i->iUniqueId; + } else + { + iptvrecording.iChannelUid = PVR_CHANNEL_INVALID_UID; + } + iptvrecording.startTime = startTime; + iptvrecording.strPlotOutline = record.get("event", "").get("description", "").asString(); + iptvrecording.duration = duration; + iptvrecording.bRadio = channel_i->bIsRadio; + iptvrecording.iLifeTime = (ParseDateTime(record.get("expires", "").asString() + "00:00") - now) / 86400; + iptvrecording.strDirectory = std::move(directory); + iptvrecording.bIsPinLocked = locked == "pin"; + + XBMC->Log(ADDON::LOG_DEBUG, "Loading recording '%s'", iptvrecording.strTitle.c_str()); + + new_recordings->push_back(iptvrecording); + } + else + { + iptvtimer.iClientIndex = record.get("id", 0).asInt(); + if (channel_i != channels->cend()) + { + iptvtimer.iClientChannelUid = channel_i->iUniqueId; + } + iptvtimer.startTime = ParseDateTime(record.get("startTime", "").asString()); + iptvtimer.endTime = iptvtimer.startTime + record.get("duration", 0).asInt(); + + if (startTime < now && (startTime + duration) >= now) + { + iptvtimer.state = PVR_TIMER_STATE_RECORDING; + } + else + { + iptvtimer.state = PVR_TIMER_STATE_SCHEDULED; + } + iptvtimer.strTitle = std::move(title); + iptvtimer.iLifeTime = (ParseDateTime(record.get("expires", "").asString() + "00:00") - now) / 86400; + iptvtimer.strDirectory = std::move(directory); + + XBMC->Log(ADDON::LOG_DEBUG, "Loading timer '%s'", iptvtimer.strTitle.c_str()); + + new_timers->push_back(iptvtimer); + } + + } + + bool changed_r = new_recordings->size() != recordings->size(); + for (size_t i = 0; !changed_r && i < new_recordings->size(); ++i) + { + const auto & old_rec = (*recordings)[i]; + const auto & new_rec = (*new_recordings)[i]; + if (new_rec.strRecordId != old_rec.strRecordId || new_rec.strStreamUrl != old_rec.strStreamUrl) + { + changed_r = true; + break; + } + } + if (changed_r) + { + for (auto & recording : *new_recordings) + { + std::string channel_id; + recording.strStreamUrl = m_manager.getRecordingUrl(recording.strRecordId, channel_id); + // get the stream type based on channel + recording.strStreamType = ChannelStreamType(channel_id); + } + } + bool changed_t = new_timers->size() != timers->size(); + for (size_t i = 0; !changed_t && i < new_timers->size(); ++i) + { + const auto & old_timer = (*timers)[i]; + const auto & new_timer = (*new_timers)[i]; + if (new_timer.iClientIndex != old_timer.iClientIndex) + { + changed_t = true; + break; + } + } + { + std::lock_guard critical(m_mutex); + if (changed_r) + { + m_recordings = std::move(new_recordings); + PVR->TriggerRecordingUpdate(); + } + + if (changed_t) + { + m_timers = std::move(new_timers); + PVR->TriggerTimerUpdate(); + } + m_recordingAvailableDuration = available_duration; + m_recordingRecordedDuration = recorded_duration; + } + + return true; +} + +bool Data::LoadPlayList(void) +{ + if (!KeepAlive()) + return false; + + Json::Value root; + + if (!m_manager.getPlaylist(m_streamQuality, m_useH265, m_useAdaptive, root)) + { + XBMC->Log(ADDON::LOG_NOTICE, "Cannot get/parse playlist."); + return false; + } + + /* + std::string qualities = m_manager.getStreamQualities(); + XBMC->Log(ADDON::LOG_DEBUG, "Stream qualities: %s", qualities.c_str()); + */ + + //channels + auto new_channels = std::make_shared(); + Json::Value channels = root["channels"]; + for (unsigned int i = 0; i < channels.size(); i++) + { + Json::Value channel = channels[i]; + const std::string locked = channel.get("locked", "none").asString(); + if (locked != "none") + { + if (!m_showLockedChannels || (m_showLockedOnlyPin && locked != "pin")) + { + XBMC->Log(ADDON::LOG_NOTICE, "Skipping locked(%s) channel#%u %s", locked.c_str(), i + 1, channel.get("name", "").asString().c_str()); + continue; + } + } + + Channel iptvchan; + + iptvchan.strId = channel.get("id", "").asString(); + iptvchan.strChannelName = channel.get("name", "").asString(); + iptvchan.strGroupId = channel.get("group", "").asString(); + iptvchan.strStreamURL = channel.get("url", "").asString(); + iptvchan.strStreamType = channel.get("streamType", "").asString(); + iptvchan.iUniqueId = i + 1; + iptvchan.iChannelNumber = i + 1; + XBMC->Log(ADDON::LOG_DEBUG, "Channel#%d %s, URL: %s", iptvchan.iUniqueId, iptvchan.strChannelName.c_str(), iptvchan.strStreamURL.c_str()); + iptvchan.strIconPath = channel.get("logoUrl", "").asString(); + iptvchan.bIsRadio = channel.get("type", "").asString() != "tv"; + iptvchan.bIsPinLocked = locked == "pin"; + + new_channels->push_back(iptvchan); + } + + auto new_groups = std::make_shared(); + Json::Value groups = root["groups"]; + for (const auto & group_id : groups.getMemberNames()) + { + ChannelGroup group; + group.bRadio = false; // currently there is no way to distinguish group types in the returned json + group.strGroupId = group_id; + group.strGroupName = groups[group_id].asString(); + for (const auto & channel : *new_channels) + { + if (channel.strGroupId == group_id && !channel.bIsRadio) + group.members.push_back(channel.iUniqueId); + } + new_groups->push_back(std::move(group)); + } + + XBMC->Log(ADDON::LOG_NOTICE, "Loaded %d channels.", new_channels->size()); + XBMC->QueueNotification(ADDON::QUEUE_INFO, "%d channels loaded.", new_channels->size()); + + + bool channels_loaded; + { + std::lock_guard critical(m_mutex); + m_channels = std::move(new_channels); + m_groups = std::move(new_groups); + channels_loaded = m_bChannelsLoaded; + m_bChannelsLoaded = true; + } + m_waitCond.notify_all(); + if (channels_loaded) + { + PVR->TriggerChannelUpdate(); + PVR->TriggerChannelGroupsUpdate(); + } + + return true; +} + +int Data::GetChannelsAmount(void) +{ + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + } + + return channels->size(); +} + +PVR_ERROR Data::GetChannels(ADDON_HANDLE handle, bool bRadio) +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s %s", __FUNCTION__, bRadio ? "radio" : "tv"); + WaitForChannels(); + + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + } + + std::vector xbmc_channels; + for (const auto & channel : *channels) + { + if (channel.bIsRadio == bRadio) + { + PVR_CHANNEL xbmcChannel; + memset(&xbmcChannel, 0, sizeof(PVR_CHANNEL)); + + xbmcChannel.iUniqueId = channel.iUniqueId; + xbmcChannel.bIsRadio = channel.bIsRadio; + xbmcChannel.iChannelNumber = channel.iChannelNumber; + strAssign(xbmcChannel.strChannelName, channel.strChannelName); + xbmcChannel.iEncryptionSystem = channel.iEncryptionSystem; + strAssign(xbmcChannel.strIconPath, channel.strIconPath); + xbmcChannel.bIsHidden = false; + + xbmc_channels.push_back(std::move(xbmcChannel)); + } + } + + for (const auto & xbmcChannel : xbmc_channels) + { + PVR->TransferChannelEntry(handle, &xbmcChannel); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::GetChannelStreamUrl(const PVR_CHANNEL* channel, std::string & streamUrl, std::string & streamType) +{ + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + } + + auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [channel] (const Channel & c) { return c.iUniqueId == channel->iUniqueId; }); + if (channels->cend() == channel_i) + { + XBMC->Log(ADDON::LOG_NOTICE, "%s can't find channel %d", __FUNCTION__, channel->iUniqueId); + return PVR_ERROR_INVALID_PARAMETERS; + } + + if (!PinCheckUnlock(channel_i->bIsPinLocked)) + return PVR_ERROR_REJECTED; + + streamUrl = channel_i->strStreamURL; + streamType = channel_i->strStreamType; + return PVR_ERROR_NO_ERROR; + +} + +int Data::GetChannelGroupsAmount(void) +{ + decltype (m_groups) groups; + { + std::lock_guard critical(m_mutex); + groups = m_groups; + } + return groups->size(); +} + +PVR_ERROR Data::GetChannelGroups(ADDON_HANDLE handle, bool bRadio) +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s %s", __FUNCTION__, bRadio ? "radio" : "tv"); + WaitForChannels(); + + decltype (m_groups) groups; + { + std::lock_guard critical(m_mutex); + groups = m_groups; + } + + std::vector xbmc_groups; + for (const auto & group : *groups) + { + if (group.bRadio == bRadio) + { + PVR_CHANNEL_GROUP xbmcGroup; + memset(&xbmcGroup, 0, sizeof(PVR_CHANNEL_GROUP)); + + xbmcGroup.bIsRadio = bRadio; + strAssign(xbmcGroup.strGroupName, group.strGroupName); + + xbmc_groups.push_back(std::move(xbmcGroup)); + } + } + + for (const auto & xbmcGroup : xbmc_groups) + { + PVR->TransferChannelGroup(handle, &xbmcGroup); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group) +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s %s", __FUNCTION__, group.strGroupName); + WaitForChannels(); + + decltype (m_groups) groups; + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + groups = m_groups; + channels = m_channels; + } + + std::vector xbmc_group_members; + auto group_i = std::find_if(groups->cbegin(), groups->cend(), [&group] (ChannelGroup const & g) { return g.strGroupName == group.strGroupName; }); + if (group_i != groups->cend()) + { + int order = 0; + for (const auto & member : group_i->members) + { + if (member < 0 || member >= channels->size()) + continue; + + const Channel &channel = (*channels)[member]; + PVR_CHANNEL_GROUP_MEMBER xbmcGroupMember; + memset(&xbmcGroupMember, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); + + strncpy(xbmcGroupMember.strGroupName, group.strGroupName, sizeof(xbmcGroupMember.strGroupName) - 1); + xbmcGroupMember.iChannelUniqueId = channel.iUniqueId; + xbmcGroupMember.iChannelNumber = ++order; + + xbmc_group_members.push_back(std::move(xbmcGroupMember)); + } + } + for (const auto & xbmcGroupMember : xbmc_group_members) + { + PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t iStart, time_t iEnd) +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s %s, from=%s to=%s", __FUNCTION__, channel.strChannelName, ApiManager::formatTime(iStart).c_str(), ApiManager::formatTime(iEnd).c_str()); + std::lock_guard critical(m_mutex); + // Note: For future scheduled timers Kodi requests EPG (this function) with + // iStart & iEnd as given by the timer timespan. But we don't want to narrow + // our EPG interval in such cases. + m_epgMinTime = iStart < m_epgMinTime ? iStart : m_epgMinTime; + m_epgMaxTime = iEnd > m_epgMaxTime ? iEnd : m_epgMaxTime; + return PVR_ERROR_NO_ERROR; +} + +static PVR_ERROR GetEPGData(const EPG_TAG* tag + , const channel_container_t * channels + , const epg_container_t * epg + , epg_entry_container_t::const_iterator & epg_i + , bool * isChannelPinLocked = nullptr + ) +{ + auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [tag] (const Channel & c) { return c.iUniqueId == tag->iUniqueChannelId; }); + if (channels->cend() == channel_i) + { + XBMC->Log(ADDON::LOG_NOTICE, "%s can't find channel %d", __FUNCTION__, tag->iUniqueChannelId); + return PVR_ERROR_INVALID_PARAMETERS; + } + if (isChannelPinLocked) + *isChannelPinLocked = channel_i->bIsPinLocked; + + auto ch_epg_i = epg->find(channel_i->strId); + + if (epg->cend() == ch_epg_i || (epg_i = ch_epg_i->second.epg.find(tag->iUniqueBroadcastId)) == ch_epg_i->second.epg.cend()) + { + XBMC->Log(ADDON::LOG_NOTICE, "%s can't find EPG data for channel %s, time %d", __FUNCTION__, channel_i->strId.c_str(), tag->iUniqueBroadcastId); + return PVR_ERROR_INVALID_PARAMETERS; + } + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::IsEPGTagPlayable(const EPG_TAG* tag, bool* bIsPlayable) const +{ + decltype (m_channels) channels; + decltype (m_epg) epg; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + epg = m_epg; + } + + epg_entry_container_t::const_iterator epg_i; + PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i); + if (PVR_ERROR_NO_ERROR != ret) + return ret; + + *bIsPlayable = epg_i->second.availableTimeshift && tag->startTime < time(nullptr); + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::IsEPGTagRecordable(const EPG_TAG* tag, bool* bIsRecordable) const +{ + decltype (m_channels) channels; + decltype (m_epg) epg; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + epg = m_epg; + } + + epg_entry_container_t::const_iterator epg_i; + PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i); + if (PVR_ERROR_NO_ERROR != ret) + return ret; + + *bIsRecordable = epg_i->second.availableTimeshift && !RecordingExists(epg_i->second.strRecordId) && tag->startTime < time(nullptr); + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::GetEPGStreamUrl(const EPG_TAG* tag, std::string & streamUrl, std::string & streamType) +{ + decltype (m_channels) channels; + decltype (m_epg) epg; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + epg = m_epg; + } + + bool isPinLocked; + epg_entry_container_t::const_iterator epg_i; + PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i, &isPinLocked); + if (PVR_ERROR_NO_ERROR != ret) + return ret; + + if (!PinCheckUnlock(isPinLocked)) + return PVR_ERROR_REJECTED; + + if (RecordingExists(epg_i->second.strRecordId)) + return GetRecordingStreamUrl(epg_i->second.strRecordId, streamUrl, streamType); + + std::string channel_id; + int duration; + if (!m_manager.getTimeShiftInfo(epg_i->second.strEventId, streamUrl, channel_id, duration)) + return PVR_ERROR_INVALID_PARAMETERS; + // get the stream type based on channel + streamType = ChannelStreamType(channel_id); + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::SetEPGTimeFrame(int iDays) +{ + XBMC->Log(ADDON::LOG_DEBUG, "%s iDays=%d", __FUNCTION__, iDays); + time_t now = time(nullptr); + std::lock_guard critical(m_mutex); + m_epgMinTime = now; + m_epgMaxTime = now + iDays * 86400; + m_epgMaxDays = iDays; + + return PVR_ERROR_NO_ERROR; +} + +int Data::ParseDateTime(std::string strDate) +{ + struct tm timeinfo; + memset(&timeinfo, 0, sizeof(tm)); + + sscanf(strDate.c_str(), "%04d-%02d-%02d %02d:%02d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min); + timeinfo.tm_sec = 0; + + timeinfo.tm_mon -= 1; + timeinfo.tm_year -= 1900; + timeinfo.tm_isdst = -1; + + time_t t = mktime(&timeinfo); + return t - DiffBetweenPragueAndLocalTime(&t); +} + +int Data::GetRecordingsAmount() +{ + decltype (m_recordings) recordings; + { + std::lock_guard critical(m_mutex); + recordings = m_recordings; + } + return recordings->size(); +} + +PVR_ERROR Data::GetRecordings(ADDON_HANDLE handle) +{ + decltype (m_recordings) recordings; + { + std::lock_guard critical(m_mutex); + recordings = m_recordings; + } + std::vector xbmc_records; + auto insert_lambda = [&xbmc_records] (const Recording & rec) + { + PVR_RECORDING xbmcRecord; + memset(&xbmcRecord, 0, sizeof(PVR_RECORDING)); + + strAssign(xbmcRecord.strRecordingId, rec.strRecordId); + strAssign(xbmcRecord.strTitle, rec.strTitle); + strAssign(xbmcRecord.strDirectory, rec.strDirectory); + strAssign(xbmcRecord.strChannelName, rec.strChannelName); + xbmcRecord.recordingTime = rec.startTime; + strAssign(xbmcRecord.strPlotOutline, rec.strPlotOutline); + strAssign(xbmcRecord.strPlot, rec.strPlotOutline); + xbmcRecord.iDuration = rec.duration; + xbmcRecord.iLifetime = rec.iLifeTime; + xbmcRecord.iChannelUid = rec.iChannelUid; + xbmcRecord.channelType = rec.bRadio ? PVR_RECORDING_CHANNEL_TYPE_RADIO : PVR_RECORDING_CHANNEL_TYPE_TV; + + xbmc_records.push_back(std::move(xbmcRecord)); + }; + + std::for_each(recordings->cbegin(), recordings->cend(), insert_lambda); + + for (const auto & xbmcRecord : xbmc_records) + { + PVR->TransferRecordingEntry(handle, &xbmcRecord); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType) +{ + decltype (m_recordings) recordings; + { + std::lock_guard critical(m_mutex); + recordings = m_recordings; + } + auto rec_i = std::find_if(recordings->cbegin(), recordings->cend(), [recording] (const Recording & r) { return recording == r.strRecordId; }); + if (recordings->cend() == rec_i) + return PVR_ERROR_INVALID_PARAMETERS; + + if (!PinCheckUnlock(rec_i->bIsPinLocked)) + return PVR_ERROR_REJECTED; + + streamUrl = rec_i->strStreamUrl; + streamType = rec_i->strStreamType; + return PVR_ERROR_NO_ERROR; +} + +bool Data::RecordingExists(const std::string & recordId) const +{ + decltype (m_recordings) recordings; + { + std::lock_guard critical(m_mutex); + recordings = m_recordings; + } + return recordings->cend() != std::find_if(recordings->cbegin(), recordings->cend(), [&recordId] (const Recording & r) { return recordId == r.strRecordId; }); +} + +int Data::GetTimersAmount() +{ + decltype (m_timers) timers; + { + std::lock_guard critical(m_mutex); + timers = m_timers; + } + return timers->size(); +} + + +PVR_ERROR Data::GetTimers(ADDON_HANDLE handle) +{ + decltype (m_timers) timers; + { + std::lock_guard critical(m_mutex); + timers = m_timers; + } + + std::vector xbmc_timers; + for (const auto & timer : *timers) + { + PVR_TIMER xbmcTimer; + memset(&xbmcTimer, 0, sizeof(PVR_TIMER)); + + xbmcTimer.iClientIndex = timer.iClientIndex; + xbmcTimer.iClientChannelUid = timer.iClientChannelUid; + xbmcTimer.startTime = timer.startTime; + xbmcTimer.endTime = timer.endTime; + xbmcTimer.state = timer.state; + xbmcTimer.iTimerType = 1; // Note: this must match some type from GetTimerTypes() + xbmcTimer.iLifetime = timer.iLifeTime; + strAssign(xbmcTimer.strTitle, timer.strTitle); + strAssign(xbmcTimer.strSummary, timer.strSummary); + strAssign(xbmcTimer.strDirectory, timer.strDirectory); + + xbmc_timers.push_back(std::move(xbmcTimer)); + } + for (const auto & xbmcTimer : xbmc_timers) + { + PVR->TransferTimerEntry(handle, &xbmcTimer); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR Data::AddTimer(const PVR_TIMER &timer) +{ + decltype (m_channels) channels; + decltype (m_epg) epg; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + epg = m_epg; + } + + const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&timer] (const Channel & ch) { return ch.iUniqueId == timer.iClientChannelUid; }); + if (channel_i == channels->cend()) + { + XBMC->Log(ADDON::LOG_ERROR, "%s - channel not found", __FUNCTION__); + return PVR_ERROR_SERVER_ERROR; + } + const auto epg_channel_i = epg->find(channel_i->strId); + if (epg_channel_i == epg->cend()) + { + XBMC->Log(ADDON::LOG_ERROR, "%s - epg channel not found", __FUNCTION__); + return PVR_ERROR_SERVER_ERROR; + } + + const auto epg_i = epg_channel_i->second.epg.find(timer.startTime); + if (epg_i == epg_channel_i->second.epg.cend()) + { + XBMC->Log(ADDON::LOG_ERROR, "%s - event not found", __FUNCTION__); + return PVR_ERROR_SERVER_ERROR; + } + + const EpgEntry & epg_entry = epg_i->second; + std::string record_id; + if (m_manager.addTimer(epg_entry.strEventId, record_id)) + { + // update the record_id into EPG + // Note: the m_epg/epg is read-only, so the keys must exist + auto epg_copy = std::make_shared(*epg); + (*epg_copy)[channel_i->strId].epg[timer.startTime].strRecordId = record_id; + { + std::lock_guard critical(m_mutex); + m_epg = epg_copy; + } + SetLoadRecordings(); + return PVR_ERROR_NO_ERROR; + } + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR Data::DeleteRecord(const std::string &strRecordId) +{ + if (m_manager.deleteRecord(strRecordId)) + { + SetLoadRecordings(); + return PVR_ERROR_NO_ERROR; + } + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR Data::DeleteRecord(int iRecordId) +{ + std::ostringstream os; + os << iRecordId; + + return DeleteRecord(os.str()); +} + +PVR_ERROR Data::GetDriveSpace(long long *iTotal, long long *iUsed) +{ + { + std::lock_guard critical(m_mutex); + *iTotal = m_recordingAvailableDuration; + *iUsed = m_recordingRecordedDuration; + } + return PVR_ERROR_NO_ERROR; +} + +bool Data::LoggedIn() const +{ + return m_manager.loggedIn(); +} + +properties_t Data::GetStreamProperties(const std::string & url, const std::string & streamType, bool isLive) const +{ + static const std::set ADAPTIVE_TYPES = {"mpd", "ism", "hls"}; + properties_t props; + props[PVR_STREAM_PROPERTY_STREAMURL] = url; + if (m_useAdaptive && 0 < ADAPTIVE_TYPES.count(streamType)) + { + props[PVR_STREAM_PROPERTY_INPUTSTREAMADDON] = "inputstream.adaptive"; + props["inputstream.adaptive.manifest_type"] = streamType; + } + if (isLive) + props[PVR_STREAM_PROPERTY_ISREALTIMESTREAM] = "true"; + return props; +} + +std::string Data::ChannelsList() const +{ + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + } + std::ostringstream os; + bool first = true; + std::for_each(channels->cbegin(), channels->cend(), [&os, &first] (channel_container_t::const_reference chan) + { + if (first) + first = false; + else + os << ","; + os << chan.strId; + }); + return os.str(); +} + +std::string Data::ChannelStreamType(const std::string & channelId) const +{ + decltype (m_channels) channels; + { + std::lock_guard critical(m_mutex); + channels = m_channels; + } + + std::string stream_type = "unknown"; + auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&channelId] (const Channel & c) { return c.strId == channelId; }); + if (channels->cend() == channel_i) + XBMC->Log(ADDON::LOG_NOTICE, "%s can't find channel %s", __FUNCTION__, channelId.c_str()); + else + stream_type = channel_i->strStreamType; + return stream_type; +} + +bool Data::PinCheckUnlock(bool isPinLocked) +{ + if (!isPinLocked) + return true; + + if (!m_manager.pinUnlocked()) + { + //Note: std::make_unique is available from c++14 + std::unique_ptr loc{XBMC->GetLocalizedString(30202), &xbmcStrFree}; + char pin[32]; + pin[0] = 0; + if (GUI->Dialog_Numeric_ShowAndGetNumber(*pin, sizeof (pin), loc.get())) + { + if (!m_manager.pinUnlock(pin)) + { + XBMC->Log(ADDON::LOG_ERROR, "PIN-unlocking failed"); + return false; + } + } else + { + XBMC->Log(ADDON::LOG_ERROR, "PIN-entering cancelled"); + return false; + } + } + // unlocking can lead to unlock of recordings + SetLoadRecordings(); + return true; +} + +} // namespace sledovanitvcz diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/Data.h kodi-pvr-sledovanitv-cz-1.6.0/src/Data.h --- kodi-pvr-sledovanitv-cz-1.5.1/src/Data.h 1970-01-01 00:00:00.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/Data.h 2020-03-13 06:39:40.000000000 +0000 @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2018~now Palo Kisa + * + * Copyright (C) 2014 Josef Rokos + * http://github.com/PepaRokos/xbmc-pvr-addons/ + * + * Copyright (C) 2011 Pulse-Eight + * http://www.pulse-eight.com/ + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this addon; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef sledovanitvcz_Data_h +#define sledovanitvcz_Data_h + +#include +#include "client.h" +#include +#include "ApiManager.h" +#include +#include +#include +#include + +namespace sledovanitvcz +{ + +struct EpgEntry +{ + unsigned iBroadcastId; + int iChannelId; + int iGenreType; + int iGenreSubType; + time_t startTime; + time_t endTime; + std::string strTitle; + std::string strPlotOutline; + std::string strPlot; + std::string strIconPath; + std::string strGenreString; + std::string strEventId; + bool availableTimeshift; + std::string strRecordId; // optionally recorded +}; + +typedef std::map epg_entry_container_t; +struct EpgChannel +{ + std::string strId; + std::string strName; + epg_entry_container_t epg; +}; + +struct Channel +{ + bool bIsRadio; + int iUniqueId; + int iChannelNumber; + int iEncryptionSystem; + int iTvgShift; + std::string strChannelName; + std::string strIconPath; + std::string strStreamURL; + std::string strId; + std::string strGroupId; + std::string strStreamType; + bool bIsPinLocked; +}; + +struct ChannelGroup +{ + bool bRadio; + std::string strGroupId; + std::string strGroupName; + std::vector members; +}; + +struct Recording +{ + std::string strRecordId; + std::string strTitle; + std::string strStreamUrl; + std::string strPlotOutline; + std::string strPlot; + std::string strChannelName; + time_t startTime; + int duration; + std::string strDirectory; + bool bRadio; + int iLifeTime; + std::string strStreamType; + int iChannelUid; + bool bIsPinLocked; +}; + +struct Timer +{ + unsigned int iClientIndex; + int iClientChannelUid; + time_t startTime; + time_t endTime; + PVR_TIMER_STATE state; /*!< @brief (required) the state of this timer */ + std::string strTitle; + std::string strSummary; + int iLifetime; + bool bIsRepeating; + time_t firstDay; + int iWeekdays; + int iEpgUid; + unsigned int iMarginStart; + unsigned int iMarginEnd; + int iGenreType; + int iGenreSubType; + int iLifeTime; + std::string strDirectory; +}; + +typedef std::vector group_container_t; +typedef std::vector channel_container_t; +typedef std::map epg_container_t; +typedef std::vector recording_container_t; +typedef std::vector timer_container_t; +typedef std::map properties_t; + +struct Configuration +{ + std::string userName; + std::string password; + std::string deviceId; //!< device identifier (value for overriding the MAC address detection) + std::string productId; //!< product identifier (value for overriding the hostname detection) + int streamQuality; + int epgMaxDays; + unsigned fullChannelEpgRefresh; //!< delay (seconds) between full channel/EPG refresh + unsigned loadingsRefresh; //!< delay (seconds) between loadings refresh + unsigned keepAliveDelay; //!< delay (seconds) between keepalive calls + unsigned epgCheckDelay; //!< delay (seconds) between checking if EPG load is needed + bool useH265; //!< flag, if h265 codec should be requested + bool useAdaptive; //!< flag, if inpustream.adaptive (aka adaptive bitrate streaming) should be used/requested + bool showLockedChannels; //!< flag, if unavailable/locked channels should be presented + bool showLockedOnlyPin; //!< flag, if PIN-locked only channels should be presented +}; + +class Data +{ +public: + Data(Configuration cfg); + virtual ~Data(void); + + int GetChannelsAmount(void); + PVR_ERROR GetChannels(ADDON_HANDLE handle, bool bRadio); + PVR_ERROR GetChannelStreamUrl(const PVR_CHANNEL* channel, std::string & streamUrl, std::string & streamType); + PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t iStart, time_t iEnd); + PVR_ERROR IsEPGTagPlayable(const EPG_TAG* tag, bool* bIsPlayable) const; + PVR_ERROR IsEPGTagRecordable(const EPG_TAG* tag, bool* bIsRecordable) const; + PVR_ERROR GetEPGStreamUrl(const EPG_TAG* tag, std::string & streamUrl, std::string & streamType); + PVR_ERROR SetEPGTimeFrame(int iDays); + int GetChannelGroupsAmount(void); + PVR_ERROR GetChannelGroups(ADDON_HANDLE handle, bool bRadio); + PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group); + int GetRecordingsAmount(); + PVR_ERROR GetRecordings(ADDON_HANDLE handle); + PVR_ERROR GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType); + void GetRecordingsUrls(); + int GetTimersAmount(); + PVR_ERROR GetTimers(ADDON_HANDLE handle); + PVR_ERROR AddTimer(const PVR_TIMER &timer); + PVR_ERROR DeleteRecord(const std::string &strRecordId); + PVR_ERROR DeleteRecord(int iRecordId); + PVR_ERROR GetDriveSpace(long long *iTotal, long long *iUsed); + bool LoggedIn() const; + properties_t GetStreamProperties(const std::string & url, const std::string & streamType, bool isLive) const; + +protected: + static int ParseDateTime(std::string strDate); + +protected: + bool KeepAlive(); + void KeepAliveJob(); + bool LoadPlayList(void); + bool LoadEPG(time_t iStart, bool bSmallStep); + void ReleaseUnneededEPG(); + //! \return true if actual update was performed + bool LoadEPGJob(); + bool LoadRecordings(); + bool LoadRecordingsJob(); + void SetLoadRecordings(); + void LoginLoop(); + bool WaitForChannels() const; + void TriggerFullRefresh(); + bool RecordingExists(const std::string & recordId) const; + std::string ChannelsList() const; + std::string ChannelStreamType(const std::string & channelId) const; + bool PinCheckUnlock(bool isPinLocked); + +protected: + void Process(void); + +private: + bool m_bKeepAlive; + bool m_bLoadRecordings; + mutable std::mutex m_mutex; + bool m_bChannelsLoaded; + mutable std::condition_variable m_waitCond; + std::thread m_thread; + + // stored data from backend (used by multiple threads...) + std::shared_ptr m_groups; + std::shared_ptr m_channels; + std::shared_ptr m_epg; + std::shared_ptr m_recordings; + std::shared_ptr m_timers; + long long m_recordingAvailableDuration; + long long m_recordingRecordedDuration; + time_t m_epgMinTime; + time_t m_epgMaxTime; + int m_epgMaxDays; + + // data used only by "job" thread + bool m_bEGPLoaded; + time_t m_iLastStart; + time_t m_iLastEnd; + ApiManager::StreamQuality_t m_streamQuality; + unsigned m_fullChannelEpgRefresh; + unsigned m_loadingsRefresh; + unsigned m_keepAliveDelay; + unsigned m_epgCheckDelay; + bool m_useH265; + bool m_useAdaptive; + bool m_showLockedChannels; + bool m_showLockedOnlyPin; + + ApiManager m_manager; +}; + +} //namespace sledovanitvcz +#endif // sledovanitvcz_Data_h diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/PVRIptvData.cpp kodi-pvr-sledovanitv-cz-1.6.0/src/PVRIptvData.cpp --- kodi-pvr-sledovanitv-cz-1.5.1/src/PVRIptvData.cpp 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/PVRIptvData.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1251 +0,0 @@ -/* - * Copyright (c) 2018~now Palo Kisa - * - * Copyright (C) 2014 Josef Rokos - * http://github.com/PepaRokos/xbmc-pvr-addons/ - * - * Copyright (C) 2011 Pulse-Eight - * http://www.pulse-eight.com/ - * - * This Program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This Program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this addon; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * http://www.gnu.org/copyleft/gpl.html - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "PVRIptvData.h" -#include "apimanager.h" -#include "CallLimiter.hh" - -#if defined(TARGET_WINDOWS) -# define LOCALTIME_R(src, dst) localtime_s(dst, src) -# define GMTIME_R(src, dst) gmtime_s(dst, src) -#else -# define LOCALTIME_R(src, dst) localtime_r(src, dst) -# define GMTIME_R(src, dst) gmtime_r(src, dst) -#endif -using namespace std; -using namespace ADDON; - -template void strAssign(char (&dst)[N], const std::string & src) -{ - strncpy(dst, src.c_str(), N - 1); - dst[N - 1] = '\0'; // just to be sure -} - -static void xbmcStrFree(char * str) -{ - XBMC->FreeString(str); -} - -static unsigned DiffBetweenPragueAndLocalTime(const time_t * when = nullptr) -{ - time_t tloc; - if (0 == when) - time(&tloc); - else - tloc = *when; - - struct tm tm1; - LOCALTIME_R(&tloc, &tm1); - auto isdst = tm1.tm_isdst; - GMTIME_R(&tloc, &tm1); - tm1.tm_isdst = isdst; - time_t t2 = mktime(&tm1); - - // Note: Prague(Czech) is in Central Europe Time -> CET or CEST == UTC+1 or UTC+2 == +3600 or +7200 - return tloc - t2 - (isdst > 0 ? 7200 : 3600); -} - -PVRIptvData::PVRIptvData(PVRIptvConfiguration cfg) - : m_bKeepAlive{true} - , m_bLoadRecordings{true} - , m_bChannelsLoaded{false} - , m_groups{std::make_shared()} - , m_channels{std::make_shared()} - , m_epg{std::make_shared()} - , m_recordings{std::make_shared()} - , m_timers{std::make_shared()} - , m_recordingAvailableDuration{0} - , m_recordingRecordedDuration{0} - , m_epgMinTime{time(nullptr)} - , m_epgMaxTime{time(nullptr) + 3600} - , m_epgMaxDays{cfg.epgMaxDays} - , m_bEGPLoaded{false} - , m_iLastStart{0} - , m_iLastEnd{0} - , m_streamQuality{static_cast(cfg.streamQuality)} - , m_fullChannelEpgRefresh{cfg.fullChannelEpgRefresh} - , m_loadingsRefresh{cfg.loadingsRefresh} - , m_keepAliveDelay{cfg.keepAliveDelay} - , m_epgCheckDelay{cfg.epgCheckDelay} - , m_useH265{cfg.useH265} - , m_useAdaptive{cfg.useAdaptive} - , m_showLockedChannels{cfg.showLockedChannels} - , m_manager{std::move(cfg.userName), std::move(cfg.password)} -{ - - SetEPGTimeFrame(m_epgMaxDays); - - m_thread = std::thread{[this] { Process(); }}; -} - -bool PVRIptvData::LoadRecordingsJob() -{ - if (!KeepAlive()) - return false; - - bool load = false; - { - std::lock_guard critical(m_mutex); - if (m_bLoadRecordings) - { - load = true; - m_bLoadRecordings = false; - } - } - if (load) - { - LoadRecordings(); - } - return load; -} - -void PVRIptvData::SetLoadRecordings() -{ - std::lock_guard critical(m_mutex); - m_bLoadRecordings = true; -} - -void PVRIptvData::TriggerFullRefresh() -{ - XBMC->Log(LOG_INFO, "%s triggering channels/EGP full refresh", __FUNCTION__); - m_iLastEnd = 0; - m_iLastStart = 0; - - int epg_max_days = 0; - { - std::lock_guard critical(m_mutex); - epg_max_days = m_epgMaxDays; - } - SetEPGTimeFrame(epg_max_days); - LoadPlayList(); -} - -bool PVRIptvData::LoadEPGJob() -{ - XBMC->Log(LOG_INFO, "%s will check if EGP loading needed", __FUNCTION__); - time_t min_epg, max_epg; - { - std::lock_guard critical(m_mutex); - min_epg = m_epgMinTime; - max_epg = m_epgMaxTime; - } - bool updated = false; - if (KeepAlive() && 0 == m_iLastEnd) - { - // the first run...load just needed data as soon as posible - LoadEPG(time(nullptr), true); - updated = true; - } else - { - if (KeepAlive() && max_epg > m_iLastEnd) - { - LoadEPG(m_iLastEnd, max_epg - m_iLastEnd <= 3600); - updated = true; - } - if (KeepAlive() && min_epg < m_iLastStart) - { - LoadEPG(m_iLastStart - 86400, false); - updated = true; - } - } - if (KeepAlive()) - ReleaseUnneededEPG(); - return updated; -} - -void PVRIptvData::ReleaseUnneededEPG() -{ - decltype (m_epg) epg; - time_t min_epg, max_epg; - { - std::lock_guard critical(m_mutex); - min_epg = m_epgMinTime; - max_epg = m_epgMaxTime; - epg = m_epg; - } - auto epg_copy = std::make_shared(); - XBMC->Log(LOG_DEBUG, "%s min_epg=%s max_epg=%s", __FUNCTION__, ApiManager::formatTime(min_epg).c_str(), ApiManager::formatTime(max_epg).c_str()); - - for (const auto & epg_channel : *epg) - { - auto & epg_data = epg_channel.second.epg; - std::vector to_delete; - for (auto entry_i = epg_data.cbegin(); entry_i != epg_data.cend(); ++entry_i) - { - const PVRIptvEpgEntry & entry = entry_i->second; - if (entry_i->second.startTime > max_epg || entry_i->second.endTime < min_epg) - { - XBMC->Log(LOG_DEBUG, "Removing TV show: %s - %s, start=%s end=%s", epg_channel.second.strName.c_str(), entry.strTitle.c_str() - , ApiManager::formatTime(entry.startTime).c_str(), ApiManager::formatTime(entry.endTime).c_str()); - // notify about the epg change...and delete it - EPG_TAG tag; - memset(&tag, 0, sizeof(EPG_TAG)); - tag.iUniqueBroadcastId = entry.iBroadcastId; - tag.iUniqueChannelId = entry.iChannelId; - PVR->EpgEventStateChange(&tag, EPG_EVENT_DELETED); - - to_delete.push_back(entry_i->first); - } - } - if (!to_delete.empty()) - { - auto & epg_copy_channel = (*epg_copy)[epg_channel.first]; - epg_copy_channel = epg_channel.second; - for (const auto key_delete : to_delete) - { - epg_copy_channel.epg.erase(key_delete); - } - } - } - - // check if something deleted, if so make copy and atomically reassign - if (!epg_copy->empty()) - { - for (const auto & epg_channel : *epg) - { - if (epg_copy->count(epg_channel.first) <= 0) - (*epg_copy)[epg_channel.first] = epg_channel.second; - } - - { - std::lock_guard critical(m_mutex); - m_epg = std::move(epg_copy); - } - } - - // narrow the loaded time info (if needed) - m_iLastStart = std::max(m_iLastStart, min_epg); - m_iLastEnd = std::min(m_iLastEnd, max_epg); -} - -void PVRIptvData::KeepAliveJob() -{ - if (!KeepAlive()) - return; - - XBMC->Log(LOG_DEBUG, "keepAlive:: trigger"); - if (!m_manager.keepAlive()) - { - LoginLoop(); - } -} - -void PVRIptvData::LoginLoop() -{ - unsigned login_delay = 0; - for (bool should_try = true; KeepAlive() && should_try; --login_delay) - { - if (0 >= login_delay) - { - if (m_manager.login()) - should_try = false; - else - login_delay = 30; // try in 30 seconds - } - std::this_thread::sleep_for(std::chrono::seconds{1}); - } -} - -bool PVRIptvData::WaitForChannels() const -{ - std::unique_lock critical(m_mutex); - return m_waitCond.wait_for(critical, std::chrono::seconds{5}, [this] { return m_bChannelsLoaded; }); -} - -void PVRIptvData::Process(void) -{ - XBMC->Log(LOG_DEBUG, "keepAlive:: thread started"); - - LoginLoop(); - - LoadPlayList(); - - bool epg_updated = false; - - auto keep_alive_job = getCallLimiter(std::bind(&PVRIptvData::KeepAliveJob, this), std::chrono::seconds{m_keepAliveDelay}, true); - auto trigger_full_refresh = getCallLimiter(std::bind(&PVRIptvData::TriggerFullRefresh, this), std::chrono::seconds{m_fullChannelEpgRefresh}, true); - auto trigger_load_recordings = getCallLimiter(std::bind(&PVRIptvData::SetLoadRecordings, this), std::chrono::seconds{m_loadingsRefresh}, true); - auto epg_dummy_trigger = getCallLimiter([] {}, std::chrono::seconds{m_epgCheckDelay}, false); // using the CallLimiter just to test if the epg should be done - - bool work_done = true; - while (KeepAlive()) - { - if (!work_done) - std::this_thread::sleep_for(std::chrono::seconds{1}); - - work_done = false; - - work_done |= LoadRecordingsJob(); - - // trigger full refresh once a time - work_done |= trigger_full_refresh.Call(); - // trigger loading of recordings once a time - work_done |= trigger_load_recordings.Call(); - - if (epg_dummy_trigger.Call() || epg_updated) - { - // perform epg loading in next cycle if something updated in this one - epg_updated = LoadEPGJob(); - work_done = true; - } else - { - epg_updated = false; - } - - // do keep alive call once a time - work_done |= keep_alive_job.Call(); - } - XBMC->Log(LOG_DEBUG, "keepAlive:: thread stopped"); -} - -PVRIptvData::~PVRIptvData(void) -{ - { - std::lock_guard critical(m_mutex); - m_bKeepAlive = false; - } - m_thread.join(); - XBMC->Log(LOG_DEBUG, "%s destructed", __FUNCTION__); -} - -bool PVRIptvData::KeepAlive() -{ - std::lock_guard critical(m_mutex); - return m_bKeepAlive; -} - -bool PVRIptvData::LoadEPG(time_t iStart, bool bSmallStep) -{ - const int step = bSmallStep ? 3600 : 86400; - XBMC->Log(LOG_DEBUG, "%s last start %s, start %s, last end %s, end %s", __FUNCTION__, ApiManager::formatTime(m_iLastStart).c_str() - , ApiManager::formatTime(iStart).c_str(), ApiManager::formatTime(m_iLastEnd).c_str(), ApiManager::formatTime(iStart + step).c_str()); - if (m_bEGPLoaded && m_iLastStart != 0 && iStart >= m_iLastStart && iStart + step <= m_iLastEnd) - return false; - - Json::Value root; - - if (!m_manager.getEpg(iStart, bSmallStep, ChannelsList(), root)) - { - XBMC->Log(LOG_NOTICE, "Cannot parse EPG data. EPG not loaded."); - m_bEGPLoaded = true; - return false; - } - - if (m_iLastStart == 0 || m_iLastStart > iStart) - m_iLastStart = iStart; - if (iStart + step > m_iLastEnd) - m_iLastEnd = iStart + step; - - decltype (m_channels) channels; - decltype (m_epg) epg; - time_t min_epg, max_epg; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - epg = m_epg; - min_epg = m_epgMinTime; - max_epg = m_epgMaxTime; - } - // narrow the loaded time info (if needed) - m_iLastStart = std::max(m_iLastStart, min_epg); - m_iLastEnd = std::min(m_iLastEnd, max_epg); - - auto epg_copy = std::make_shared(*epg); - - Json::Value json_channels = root["channels"]; - Json::Value::Members chIds = json_channels.getMemberNames(); - for (Json::Value::Members::iterator i = chIds.begin(); i != chIds.end(); i++) - { - std::string strChId = *i; - - const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&strChId] (const PVRIptvChannel & ch) { return ch.strId == strChId; }); - if (channel_i != channels->cend()) - { - PVRIptvEpgChannel & epgChannel = (*epg_copy)[strChId]; - epgChannel.strId = strChId; - - Json::Value epgData = json_channels[strChId]; - for (unsigned int j = 0; j < epgData.size(); j++) - { - Json::Value epgEntry = epgData[j]; - - const time_t start_time = ParseDateTime(epgEntry.get("startTime", "").asString()); - const time_t end_time = ParseDateTime(epgEntry.get("endTime", "").asString()); - // skip unneeded EPGs - if (start_time > max_epg || end_time < min_epg) - continue; - PVRIptvEpgEntry iptventry; - iptventry.iBroadcastId = start_time; // unique id for channel (even if time_t is wider, int should be enough for short period of time) - iptventry.iGenreType = 0; - iptventry.iGenreSubType = 0; - iptventry.iChannelId = channel_i->iUniqueId; - iptventry.strTitle = epgEntry.get("title", "").asString(); - iptventry.strPlot = epgEntry.get("description", "").asString(); - iptventry.startTime = start_time; - iptventry.endTime = end_time; - iptventry.strEventId = epgEntry.get("eventId", "").asString(); - std::string availability = epgEntry.get("availability", "none").asString(); - iptventry.availableTimeshift = availability == "timeshift" || availability == "pvr"; - iptventry.strRecordId = epgEntry["recordId"].asString(); - - XBMC->Log(LOG_DEBUG, "Loading TV show: %s - %s, start=%s(epoch=%llu)", strChId.c_str(), iptventry.strTitle.c_str() - , epgEntry.get("startTime", "").asString().c_str(), static_cast(start_time)); - - // notify about the epg change...and store it - EPG_TAG tag; - memset(&tag, 0, sizeof(EPG_TAG)); - - tag.iUniqueBroadcastId = iptventry.iBroadcastId; - tag.iUniqueChannelId = iptventry.iChannelId; - tag.strTitle = strdup(iptventry.strTitle.c_str()); - tag.startTime = iptventry.startTime; - tag.endTime = iptventry.endTime; - tag.strPlotOutline = strdup(iptventry.strPlotOutline.c_str()); - tag.strPlot = strdup(iptventry.strPlot.c_str()); - tag.strIconPath = strdup(iptventry.strIconPath.c_str()); - tag.iGenreType = EPG_GENRE_USE_STRING; //iptventry.iGenreType; - tag.iGenreSubType = 0; //iptventry.iGenreSubType; - tag.strGenreDescription = strdup(iptventry.strGenreString.c_str()); - - auto result = epgChannel.epg.emplace(iptventry.startTime, iptventry); - bool value_changed = !result.second; - if (value_changed) - { - epgChannel.epg[iptventry.startTime] = std::move(iptventry); - } - - PVR->EpgEventStateChange(&tag, value_changed ? EPG_EVENT_UPDATED : EPG_EVENT_CREATED); - - free(const_cast(tag.strTitle)); - free(const_cast(tag.strPlotOutline)); - free(const_cast(tag.strPlot)); - free(const_cast(tag.strIconPath)); - free(const_cast(tag.strGenreDescription)); - - } - } - } - - // atomic assign new version of the epg all epgs - { - std::lock_guard critical(m_mutex); - m_epg = epg_copy; - } - - m_bEGPLoaded = true; - XBMC->Log(LOG_NOTICE, "EPG Loaded."); - - return true; -} - -bool PVRIptvData::LoadRecordings() -{ - decltype (m_channels) channels; - decltype (m_recordings) recordings; - decltype (m_timers) timers; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - recordings = m_recordings; - timers = m_timers; - } - - auto new_recordings = std::make_shared(); - auto new_timers = std::make_shared(); - long long available_duration = 0; - long long recorded_duration = 0; - - Json::Value root; - - if (!m_manager.getPvr(root)) - { - XBMC->Log(LOG_NOTICE, "Cannot parse recordings."); - return false; - } - - available_duration = root["summary"].get("availableDuration", 0).asInt() / 60 * 1024; //report minutes as MB - recorded_duration = root["summary"].get("recordedDuration", 0).asInt() / 60 * 1024; - - Json::Value records = root["records"]; - for (unsigned int i = 0; i < records.size(); i++) - { - Json::Value record = records[i]; - const std::string title = record.get("title", "").asString(); - const std::string locked = record.get("channelLocked", "none").asString(); - std::string directory; - if (locked != "none") - { - //Note: std::make_unique is available from c++14 - std::unique_ptr loc{XBMC->GetLocalizedString(30201), &xbmcStrFree}; - directory = loc.get(); - directory += " - "; - directory += locked; - XBMC->Log(LOG_INFO, "Timer/recording '%s' is locked(%s)", title.c_str(), locked.c_str()); - } - std::string str_ch_id = record.get("channel", "").asString(); - const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&str_ch_id] (const PVRIptvChannel & ch) { return ch.strId == str_ch_id; }); - PVRIptvRecording iptvrecording; - PVRIptvTimer iptvtimer; - time_t startTime = ParseDateTime(record.get("startTime", "").asString()); - int duration = record.get("duration", 0).asInt(); - time_t now; - time(&now); - - if ((startTime + duration) < now) - { - char buf[256]; - sprintf(buf, "%d", record.get("id", 0).asInt()); - iptvrecording.strRecordId = buf; - iptvrecording.strTitle = std::move(title); - - if (channel_i != channels->cend()) - { - iptvrecording.strChannelName = channel_i->strChannelName; - iptvrecording.iChannelUid = channel_i->iUniqueId; - } else - { - iptvrecording.iChannelUid = PVR_CHANNEL_INVALID_UID; - } - iptvrecording.startTime = startTime; - iptvrecording.strPlotOutline = record.get("event", "").get("description", "").asString(); - iptvrecording.duration = duration; - iptvrecording.bRadio = channel_i->bIsRadio; - iptvrecording.iLifeTime = (ParseDateTime(record.get("expires", "").asString() + "00:00") - now) / 86400; - iptvrecording.strDirectory = std::move(directory); - - XBMC->Log(LOG_DEBUG, "Loading recording '%s'", iptvrecording.strTitle.c_str()); - - new_recordings->push_back(iptvrecording); - } - else - { - iptvtimer.iClientIndex = record.get("id", 0).asInt(); - if (channel_i != channels->cend()) - { - iptvtimer.iClientChannelUid = channel_i->iUniqueId; - } - iptvtimer.startTime = ParseDateTime(record.get("startTime", "").asString()); - iptvtimer.endTime = iptvtimer.startTime + record.get("duration", 0).asInt(); - - if (startTime < now && (startTime + duration) >= now) - { - iptvtimer.state = PVR_TIMER_STATE_RECORDING; - } - else - { - iptvtimer.state = PVR_TIMER_STATE_SCHEDULED; - } - iptvtimer.strTitle = std::move(title); - iptvtimer.iLifeTime = (ParseDateTime(record.get("expires", "").asString() + "00:00") - now) / 86400; - iptvtimer.strDirectory = std::move(directory); - - XBMC->Log(LOG_DEBUG, "Loading timer '%s'", iptvtimer.strTitle.c_str()); - - new_timers->push_back(iptvtimer); - } - - } - - bool changed_r = new_recordings->size() != recordings->size(); - for (size_t i = 0; !changed_r && i < new_recordings->size(); ++i) - { - const auto & old_rec = (*recordings)[i]; - const auto & new_rec = (*new_recordings)[i]; - if (new_rec.strRecordId != old_rec.strRecordId) - { - changed_r = true; - break; - } - } - if (changed_r) - { - for (auto & recording : *new_recordings) - { - std::string channel_id; - recording.strStreamUrl = m_manager.getRecordingUrl(recording.strRecordId, channel_id); - // get the stream type based on channel - recording.strStreamType = ChannelStreamType(channel_id); - } - } - bool changed_t = new_timers->size() != timers->size(); - for (size_t i = 0; !changed_t && i < new_timers->size(); ++i) - { - const auto & old_timer = (*timers)[i]; - const auto & new_timer = (*new_timers)[i]; - if (new_timer.iClientIndex != old_timer.iClientIndex) - { - changed_t = true; - break; - } - } - { - std::lock_guard critical(m_mutex); - if (changed_r) - { - m_recordings = std::move(new_recordings); - PVR->TriggerRecordingUpdate(); - } - - if (changed_t) - { - m_timers = std::move(new_timers); - PVR->TriggerTimerUpdate(); - } - m_recordingAvailableDuration = available_duration; - m_recordingRecordedDuration = recorded_duration; - } - - return true; -} - -bool PVRIptvData::LoadPlayList(void) -{ - if (!KeepAlive()) - return false; - - Json::Value root; - - if (!m_manager.getPlaylist(m_streamQuality, m_useH265, m_useAdaptive, root)) - { - XBMC->Log(LOG_NOTICE, "Cannot get/parse playlist."); - return false; - } - - /* - std::string qualities = m_manager.getStreamQualities(); - XBMC->Log(LOG_DEBUG, "Stream qualities: %s", qualities.c_str()); - */ - - //channels - auto new_channels = std::make_shared(); - Json::Value channels = root["channels"]; - for (unsigned int i = 0; i < channels.size(); i++) - { - Json::Value channel = channels[i]; - if (!m_showLockedChannels) - { - const std::string locked = channel.get("locked", "none").asString(); - if (locked != "none") - { - XBMC->Log(LOG_INFO, "Skipping locked(%s) channel %s", locked.c_str(), channel.get("name", "").asString().c_str()); - continue; - } - } - - PVRIptvChannel iptvchan; - - iptvchan.strId = channel.get("id", "").asString(); - iptvchan.strChannelName = channel.get("name", "").asString(); - iptvchan.strGroupId = channel.get("group", "").asString(); - iptvchan.strStreamURL = channel.get("url", "").asString(); - iptvchan.strStreamType = channel.get("streamType", "").asString(); - XBMC->Log(LOG_DEBUG, "Channel %s, URL: %s", iptvchan.strChannelName.c_str(), iptvchan.strStreamURL.c_str()); - iptvchan.iUniqueId = i + 1; - iptvchan.iChannelNumber = i + 1; - iptvchan.strIconPath = channel.get("logoUrl", "").asString(); - iptvchan.bIsRadio = channel.get("type", "").asString() != "tv"; - - new_channels->push_back(iptvchan); - } - - auto new_groups = std::make_shared(); - Json::Value groups = root["groups"]; - for (const auto & group_id : groups.getMemberNames()) - { - PVRIptvChannelGroup group; - group.bRadio = false; // currently there is no way to distinguish group types in the returned json - group.strGroupId = group_id; - group.strGroupName = groups[group_id].asString(); - for (const auto & channel : *new_channels) - { - if (channel.strGroupId == group_id) - group.members.push_back(channel.iUniqueId); - } - new_groups->push_back(std::move(group)); - } - - XBMC->Log(LOG_NOTICE, "Loaded %d channels.", new_channels->size()); - XBMC->QueueNotification(QUEUE_INFO, "%d channels loaded.", new_channels->size()); - - - { - std::lock_guard critical(m_mutex); - m_channels = std::move(new_channels); - m_groups = std::move(new_groups); - m_bChannelsLoaded = true; - } - m_waitCond.notify_all(); - PVR->TriggerChannelUpdate(); - PVR->TriggerChannelGroupsUpdate(); - - return true; -} - -int PVRIptvData::GetChannelsAmount(void) -{ - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - } - - return channels->size(); -} - -PVR_ERROR PVRIptvData::GetChannels(ADDON_HANDLE handle, bool bRadio) -{ - XBMC->Log(LOG_DEBUG, "%s %s", __FUNCTION__, bRadio ? "radio" : "tv"); - WaitForChannels(); - - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - } - - std::vector xbmc_channels; - for (const auto & channel : *channels) - { - if (channel.bIsRadio == bRadio) - { - PVR_CHANNEL xbmcChannel; - memset(&xbmcChannel, 0, sizeof(PVR_CHANNEL)); - - xbmcChannel.iUniqueId = channel.iUniqueId; - xbmcChannel.bIsRadio = channel.bIsRadio; - xbmcChannel.iChannelNumber = channel.iChannelNumber; - strAssign(xbmcChannel.strChannelName, channel.strChannelName); - xbmcChannel.iEncryptionSystem = channel.iEncryptionSystem; - strAssign(xbmcChannel.strIconPath, channel.strIconPath); - xbmcChannel.bIsHidden = false; - - xbmc_channels.push_back(std::move(xbmcChannel)); - } - } - - for (const auto & xbmcChannel : xbmc_channels) - { - PVR->TransferChannelEntry(handle, &xbmcChannel); - } - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::GetChannelStreamUrl(const PVR_CHANNEL* channel, std::string & streamUrl, std::string & streamType) const -{ - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - } - - auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [channel] (const PVRIptvChannel & c) { return c.iChannelNumber == channel->iUniqueId; }); - if (channels->cend() == channel_i) - { - XBMC->Log(LOG_NOTICE, "%s can't find channel %d", __FUNCTION__, channel->iUniqueId); - return PVR_ERROR_INVALID_PARAMETERS; - } - - streamUrl = channel_i->strStreamURL; - streamType = channel_i->strStreamType; - return PVR_ERROR_NO_ERROR; - -} - -int PVRIptvData::GetChannelGroupsAmount(void) -{ - decltype (m_groups) groups; - { - std::lock_guard critical(m_mutex); - groups = m_groups; - } - return groups->size(); -} - -PVR_ERROR PVRIptvData::GetChannelGroups(ADDON_HANDLE handle, bool bRadio) -{ - WaitForChannels(); - - decltype (m_groups) groups; - { - std::lock_guard critical(m_mutex); - groups = m_groups; - } - - std::vector xbmc_groups; - for (const auto & group : *groups) - { - if (group.bRadio == bRadio) - { - PVR_CHANNEL_GROUP xbmcGroup; - memset(&xbmcGroup, 0, sizeof(PVR_CHANNEL_GROUP)); - - xbmcGroup.bIsRadio = bRadio; - strAssign(xbmcGroup.strGroupName, group.strGroupName); - - xbmc_groups.push_back(std::move(xbmcGroup)); - } - } - - for (const auto & xbmcGroup : xbmc_groups) - { - PVR->TransferChannelGroup(handle, &xbmcGroup); - } - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group) -{ - WaitForChannels(); - - decltype (m_groups) groups; - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - groups = m_groups; - channels = m_channels; - } - - std::vector xbmc_group_members; - auto group_i = std::find_if(groups->cbegin(), groups->cend(), [&group] (PVRIptvChannelGroup const & g) { return g.strGroupName == group.strGroupName; }); - if (group_i != groups->cend()) - { - for (const auto & member : group_i->members) - { - if (member < 0 || member >= channels->size()) - continue; - - const PVRIptvChannel &channel = (*channels)[member]; - PVR_CHANNEL_GROUP_MEMBER xbmcGroupMember; - memset(&xbmcGroupMember, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); - - strncpy(xbmcGroupMember.strGroupName, group.strGroupName, sizeof(xbmcGroupMember.strGroupName) - 1); - xbmcGroupMember.iChannelUniqueId = channel.iUniqueId; - xbmcGroupMember.iChannelNumber = channel.iChannelNumber; - - xbmc_group_members.push_back(std::move(xbmcGroupMember)); - } - } - for (const auto & xbmcGroupMember : xbmc_group_members) - { - PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); - } - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t iStart, time_t iEnd) -{ - XBMC->Log(LOG_DEBUG, "%s %s, from=%s to=%s", __FUNCTION__, channel.strChannelName, ApiManager::formatTime(iStart).c_str(), ApiManager::formatTime(iEnd).c_str()); - std::lock_guard critical(m_mutex); - // Note: For future scheduled timers Kodi requests EPG (this function) with - // iStart & iEnd as given by the timer timespan. But we don't want to narrow - // our EPG interval in such cases. - m_epgMinTime = iStart < m_epgMinTime ? iStart : m_epgMinTime; - m_epgMaxTime = iEnd > m_epgMaxTime ? iEnd : m_epgMaxTime; - return PVR_ERROR_NO_ERROR; -} - -static PVR_ERROR GetEPGData(const EPG_TAG* tag - , const channel_container_t * channels - , const epg_container_t * epg - , epg_entry_container_t::const_iterator & epg_i - ) -{ - auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [tag] (const PVRIptvChannel & c) { return c.iChannelNumber == tag->iUniqueChannelId; }); - if (channels->cend() == channel_i) - { - XBMC->Log(LOG_NOTICE, "%s can't find channel %d", __FUNCTION__, tag->iUniqueChannelId); - return PVR_ERROR_INVALID_PARAMETERS; - } - - auto ch_epg_i = epg->find(channel_i->strId); - - if (epg->cend() == ch_epg_i || (epg_i = ch_epg_i->second.epg.find(tag->iUniqueBroadcastId)) == ch_epg_i->second.epg.cend()) - { - XBMC->Log(LOG_NOTICE, "%s can't EPG data for find channel %s, time %d", __FUNCTION__, channel_i->strId.c_str(), tag->iUniqueBroadcastId); - return PVR_ERROR_INVALID_PARAMETERS; - } - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::IsEPGTagPlayable(const EPG_TAG* tag, bool* bIsPlayable) const -{ - decltype (m_channels) channels; - decltype (m_epg) epg; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - epg = m_epg; - } - - epg_entry_container_t::const_iterator epg_i; - PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i); - if (PVR_ERROR_NO_ERROR != ret) - return ret; - - *bIsPlayable = epg_i->second.availableTimeshift && tag->startTime < time(nullptr); - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::IsEPGTagRecordable(const EPG_TAG* tag, bool* bIsRecordable) const -{ - decltype (m_channels) channels; - decltype (m_epg) epg; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - epg = m_epg; - } - - epg_entry_container_t::const_iterator epg_i; - PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i); - if (PVR_ERROR_NO_ERROR != ret) - return ret; - - *bIsRecordable = epg_i->second.availableTimeshift && !RecordingExists(epg_i->second.strRecordId) && tag->startTime < time(nullptr); - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::GetEPGStreamUrl(const EPG_TAG* tag, std::string & streamUrl, std::string & streamType) const -{ - decltype (m_channels) channels; - decltype (m_epg) epg; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - epg = m_epg; - } - - epg_entry_container_t::const_iterator epg_i; - PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i); - if (PVR_ERROR_NO_ERROR != ret) - return ret; - - if (RecordingExists(epg_i->second.strRecordId)) - return GetRecordingStreamUrl(epg_i->second.strRecordId, streamUrl, streamType); - - std::string channel_id; - int duration; - if (!m_manager.getTimeShiftInfo(epg_i->second.strEventId, streamUrl, channel_id, duration)) - return PVR_ERROR_INVALID_PARAMETERS; - // get the stream type based on channel - streamType = ChannelStreamType(channel_id); - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::SetEPGTimeFrame(int iDays) -{ - XBMC->Log(LOG_DEBUG, "%s iDays=%d", __FUNCTION__, iDays); - time_t now = time(nullptr); - std::lock_guard critical(m_mutex); - m_epgMinTime = now; - m_epgMaxTime = now + iDays * 86400; - m_epgMaxDays = iDays; - - return PVR_ERROR_NO_ERROR; -} - -int PVRIptvData::ParseDateTime(std::string strDate) -{ - struct tm timeinfo; - memset(&timeinfo, 0, sizeof(tm)); - - sscanf(strDate.c_str(), "%04d-%02d-%02d %02d:%02d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min); - timeinfo.tm_sec = 0; - - timeinfo.tm_mon -= 1; - timeinfo.tm_year -= 1900; - timeinfo.tm_isdst = -1; - - time_t t = mktime(&timeinfo); - return t - DiffBetweenPragueAndLocalTime(&t); -} - -int PVRIptvData::GetRecordingsAmount() -{ - decltype (m_recordings) recordings; - { - std::lock_guard critical(m_mutex); - recordings = m_recordings; - } - return recordings->size(); -} - -PVR_ERROR PVRIptvData::GetRecordings(ADDON_HANDLE handle) -{ - decltype (m_recordings) recordings; - { - std::lock_guard critical(m_mutex); - recordings = m_recordings; - } - std::vector xbmc_records; - auto insert_lambda = [&xbmc_records] (const PVRIptvRecording & rec) - { - PVR_RECORDING xbmcRecord; - memset(&xbmcRecord, 0, sizeof(PVR_RECORDING)); - - strAssign(xbmcRecord.strRecordingId, rec.strRecordId); - strAssign(xbmcRecord.strTitle, rec.strTitle); - strAssign(xbmcRecord.strDirectory, rec.strDirectory); - strAssign(xbmcRecord.strChannelName, rec.strChannelName); - xbmcRecord.recordingTime = rec.startTime; - strAssign(xbmcRecord.strPlotOutline, rec.strPlotOutline); - strAssign(xbmcRecord.strPlot, rec.strPlotOutline); - xbmcRecord.iDuration = rec.duration; - xbmcRecord.iLifetime = rec.iLifeTime; - xbmcRecord.iChannelUid = rec.iChannelUid; - xbmcRecord.channelType = rec.bRadio ? PVR_RECORDING_CHANNEL_TYPE_RADIO : PVR_RECORDING_CHANNEL_TYPE_TV; - - xbmc_records.push_back(std::move(xbmcRecord)); - }; - - std::for_each(recordings->cbegin(), recordings->cend(), insert_lambda); - - for (const auto & xbmcRecord : xbmc_records) - { - PVR->TransferRecordingEntry(handle, &xbmcRecord); - } - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType) const -{ - decltype (m_recordings) recordings; - { - std::lock_guard critical(m_mutex); - recordings = m_recordings; - } - auto rec_i = std::find_if(recordings->cbegin(), recordings->cend(), [recording] (const PVRIptvRecording & r) { return recording == r.strRecordId; }); - if (recordings->cend() == rec_i) - return PVR_ERROR_INVALID_PARAMETERS; - - streamUrl = rec_i->strStreamUrl; - streamType = rec_i->strStreamType; - return PVR_ERROR_NO_ERROR; -} - -bool PVRIptvData::RecordingExists(const std::string & recordId) const -{ - decltype (m_recordings) recordings; - { - std::lock_guard critical(m_mutex); - recordings = m_recordings; - } - return recordings->cend() != std::find_if(recordings->cbegin(), recordings->cend(), [&recordId] (const PVRIptvRecording & r) { return recordId == r.strRecordId; }); -} - -int PVRIptvData::GetTimersAmount() -{ - decltype (m_timers) timers; - { - std::lock_guard critical(m_mutex); - timers = m_timers; - } - return timers->size(); -} - - -PVR_ERROR PVRIptvData::GetTimers(ADDON_HANDLE handle) -{ - decltype (m_timers) timers; - { - std::lock_guard critical(m_mutex); - timers = m_timers; - } - - std::vector xbmc_timers; - for (const auto & timer : *timers) - { - PVR_TIMER xbmcTimer; - memset(&xbmcTimer, 0, sizeof(PVR_TIMER)); - - xbmcTimer.iClientIndex = timer.iClientIndex; - xbmcTimer.iClientChannelUid = timer.iClientChannelUid; - xbmcTimer.startTime = timer.startTime; - xbmcTimer.endTime = timer.endTime; - xbmcTimer.state = timer.state; - xbmcTimer.iTimerType = 1; // Note: this must match some type from GetTimerTypes() - xbmcTimer.iLifetime = timer.iLifeTime; - strAssign(xbmcTimer.strTitle, timer.strTitle); - strAssign(xbmcTimer.strSummary, timer.strSummary); - strAssign(xbmcTimer.strDirectory, timer.strDirectory); - - xbmc_timers.push_back(std::move(xbmcTimer)); - } - for (const auto & xbmcTimer : xbmc_timers) - { - PVR->TransferTimerEntry(handle, &xbmcTimer); - } - - return PVR_ERROR_NO_ERROR; -} - -PVR_ERROR PVRIptvData::AddTimer(const PVR_TIMER &timer) -{ - decltype (m_channels) channels; - decltype (m_epg) epg; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - epg = m_epg; - } - - const auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&timer] (const PVRIptvChannel & ch) { return ch.iUniqueId == timer.iClientChannelUid; }); - if (channel_i == channels->cend()) - { - XBMC->Log(LOG_ERROR, "%s - channel not found", __FUNCTION__); - return PVR_ERROR_SERVER_ERROR; - } - const auto epg_channel_i = epg->find(channel_i->strId); - if (epg_channel_i == epg->cend()) - { - XBMC->Log(LOG_ERROR, "%s - epg channel not found", __FUNCTION__); - return PVR_ERROR_SERVER_ERROR; - } - - const auto epg_i = epg_channel_i->second.epg.find(timer.startTime); - if (epg_i == epg_channel_i->second.epg.cend()) - { - XBMC->Log(LOG_ERROR, "%s - event not found", __FUNCTION__); - return PVR_ERROR_SERVER_ERROR; - } - - const PVRIptvEpgEntry & epg_entry = epg_i->second; - std::string record_id; - if (m_manager.addTimer(epg_entry.strEventId, record_id)) - { - // update the record_id into EPG - // Note: the m_epg/epg is read-only, so the keys must exist - auto epg_copy = std::make_shared(*epg); - (*epg_copy)[channel_i->strId].epg[timer.startTime].strRecordId = record_id; - { - std::lock_guard critical(m_mutex); - m_epg = epg_copy; - } - SetLoadRecordings(); - return PVR_ERROR_NO_ERROR; - } - return PVR_ERROR_SERVER_ERROR; -} - -PVR_ERROR PVRIptvData::DeleteRecord(const string &strRecordId) -{ - if (m_manager.deleteRecord(strRecordId)) - { - SetLoadRecordings(); - return PVR_ERROR_NO_ERROR; - } - return PVR_ERROR_SERVER_ERROR; -} - -PVR_ERROR PVRIptvData::DeleteRecord(int iRecordId) -{ - std::ostringstream os; - os << iRecordId; - - return DeleteRecord(os.str()); -} - -PVR_ERROR PVRIptvData::GetDriveSpace(long long *iTotal, long long *iUsed) -{ - { - std::lock_guard critical(m_mutex); - *iTotal = m_recordingAvailableDuration; - *iUsed = m_recordingRecordedDuration; - } - return PVR_ERROR_NO_ERROR; -} - -bool PVRIptvData::LoggedIn() const -{ - return m_manager.loggedIn(); -} - -properties_t PVRIptvData::GetStreamProperties(const std::string & url, const std::string & streamType, bool isLive) const -{ - static const std::set ADAPTIVE_TYPES = {"mpd", "ism", "hls"}; - properties_t props; - props[PVR_STREAM_PROPERTY_STREAMURL] = url; - if (m_useAdaptive && 0 < ADAPTIVE_TYPES.count(streamType)) - { - props[PVR_STREAM_PROPERTY_INPUTSTREAMADDON] = "inputstream.adaptive"; - props["inputstream.adaptive.manifest_type"] = streamType; - } - if (isLive) - props[PVR_STREAM_PROPERTY_ISREALTIMESTREAM] = "true"; - return props; -} - -std::string PVRIptvData::ChannelsList() const -{ - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - } - std::ostringstream os; - bool first = true; - std::for_each(channels->cbegin(), channels->cend(), [&os, &first] (channel_container_t::const_reference chan) - { - if (first) - first = false; - else - os << ","; - os << chan.strId; - }); - return os.str(); -} - -std::string PVRIptvData::ChannelStreamType(const std::string & channelId) const -{ - decltype (m_channels) channels; - { - std::lock_guard critical(m_mutex); - channels = m_channels; - } - - std::string stream_type = "unknown"; - auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [&channelId] (const PVRIptvChannel & c) { return c.strId == channelId; }); - if (channels->cend() == channel_i) - XBMC->Log(LOG_NOTICE, "%s can't find channel %s", __FUNCTION__, channelId.c_str()); - else - stream_type = channel_i->strStreamType; - return stream_type; -} diff -Nru kodi-pvr-sledovanitv-cz-1.5.1/src/PVRIptvData.h kodi-pvr-sledovanitv-cz-1.6.0/src/PVRIptvData.h --- kodi-pvr-sledovanitv-cz-1.5.1/src/PVRIptvData.h 2019-09-23 10:23:44.000000000 +0000 +++ kodi-pvr-sledovanitv-cz-1.6.0/src/PVRIptvData.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,235 +0,0 @@ -#pragma once -/* - * Copyright (c) 2018~now Palo Kisa - * - * Copyright (C) 2014 Josef Rokos - * http://github.com/PepaRokos/xbmc-pvr-addons/ - * - * Copyright (C) 2011 Pulse-Eight - * http://www.pulse-eight.com/ - * - * This Program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This Program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this addon; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * http://www.gnu.org/copyleft/gpl.html - * - */ - -#include -#include "client.h" -#include -#include "apimanager.h" -#include -#include -#include -#include - -struct PVRIptvEpgEntry -{ - unsigned iBroadcastId; - int iChannelId; - int iGenreType; - int iGenreSubType; - time_t startTime; - time_t endTime; - std::string strTitle; - std::string strPlotOutline; - std::string strPlot; - std::string strIconPath; - std::string strGenreString; - std::string strEventId; - bool availableTimeshift; - std::string strRecordId; // optionally recorded -}; - -typedef std::map epg_entry_container_t; -struct PVRIptvEpgChannel -{ - std::string strId; - std::string strName; - epg_entry_container_t epg; -}; - -struct PVRIptvChannel -{ - bool bIsRadio; - int iUniqueId; - int iChannelNumber; - int iEncryptionSystem; - int iTvgShift; - std::string strChannelName; - std::string strIconPath; - std::string strStreamURL; - std::string strId; - std::string strGroupId; - std::string strStreamType; -}; - -struct PVRIptvChannelGroup -{ - bool bRadio; - std::string strGroupId; - std::string strGroupName; - std::vector members; -}; - -struct PVRIptvRecording -{ - std::string strRecordId; - std::string strTitle; - std::string strStreamUrl; - std::string strPlotOutline; - std::string strPlot; - std::string strChannelName; - time_t startTime; - int duration; - std::string strDirectory; - bool bRadio; - int iLifeTime; - std::string strStreamType; - int iChannelUid; -}; - -struct PVRIptvTimer -{ - unsigned int iClientIndex; - int iClientChannelUid; - time_t startTime; - time_t endTime; - PVR_TIMER_STATE state; /*!< @brief (required) the state of this timer */ - std::string strTitle; - std::string strSummary; - int iLifetime; - bool bIsRepeating; - time_t firstDay; - int iWeekdays; - int iEpgUid; - unsigned int iMarginStart; - unsigned int iMarginEnd; - int iGenreType; - int iGenreSubType; - int iLifeTime; - std::string strDirectory; -}; - -typedef std::vector group_container_t; -typedef std::vector channel_container_t; -typedef std::map epg_container_t; -typedef std::vector recording_container_t; -typedef std::vector timer_container_t; -typedef std::map properties_t; - -struct PVRIptvConfiguration -{ - std::string userName; - std::string password; - int streamQuality; - int epgMaxDays; - unsigned fullChannelEpgRefresh; //!< delay (seconds) between full channel/EPG refresh - unsigned loadingsRefresh; //!< delay (seconds) between loadings refresh - unsigned keepAliveDelay; //!< delay (seconds) between keepalive calls - unsigned epgCheckDelay; //!< delay (seconds) between checking if EPG load is needed - bool useH265; //!< flag, if h265 codec should be requested - bool useAdaptive; //!< flag, if inpustream.adaptive (aka adaptive bitrate streaming) should be used/requested - bool showLockedChannels; //!< flag, if unavailable/locked channels should be presented -}; - -class PVRIptvData -{ -public: - PVRIptvData(PVRIptvConfiguration cfg); - virtual ~PVRIptvData(void); - - int GetChannelsAmount(void); - PVR_ERROR GetChannels(ADDON_HANDLE handle, bool bRadio); - PVR_ERROR GetChannelStreamUrl(const PVR_CHANNEL* channel, std::string & streamUrl, std::string & streamType) const; - PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANNEL &channel, time_t iStart, time_t iEnd); - PVR_ERROR IsEPGTagPlayable(const EPG_TAG* tag, bool* bIsPlayable) const; - PVR_ERROR IsEPGTagRecordable(const EPG_TAG* tag, bool* bIsRecordable) const; - PVR_ERROR GetEPGStreamUrl(const EPG_TAG* tag, std::string & streamUrl, std::string & streamType) const; - PVR_ERROR SetEPGTimeFrame(int iDays); - int GetChannelGroupsAmount(void); - PVR_ERROR GetChannelGroups(ADDON_HANDLE handle, bool bRadio); - PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group); - int GetRecordingsAmount(); - PVR_ERROR GetRecordings(ADDON_HANDLE handle); - PVR_ERROR GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType) const; - void GetRecordingsUrls(); - int GetTimersAmount(); - PVR_ERROR GetTimers(ADDON_HANDLE handle); - PVR_ERROR AddTimer(const PVR_TIMER &timer); - PVR_ERROR DeleteRecord(const std::string &strRecordId); - PVR_ERROR DeleteRecord(int iRecordId); - PVR_ERROR GetDriveSpace(long long *iTotal, long long *iUsed); - bool LoggedIn() const; - properties_t GetStreamProperties(const std::string & url, const std::string & streamType, bool isLive) const; - -protected: - static int ParseDateTime(std::string strDate); - -protected: - bool KeepAlive(); - void KeepAliveJob(); - bool LoadPlayList(void); - bool LoadEPG(time_t iStart, bool bSmallStep); - void ReleaseUnneededEPG(); - //! \return true if actual update was performed - bool LoadEPGJob(); - bool LoadRecordings(); - bool LoadRecordingsJob(); - void SetLoadRecordings(); - void LoginLoop(); - bool WaitForChannels() const; - void TriggerFullRefresh(); - bool RecordingExists(const std::string & recordId) const; - std::string ChannelsList() const; - std::string ChannelStreamType(const std::string & channelId) const; - -protected: - void Process(void); - -private: - bool m_bKeepAlive; - bool m_bLoadRecordings; - mutable std::mutex m_mutex; - bool m_bChannelsLoaded; - mutable std::condition_variable m_waitCond; - std::thread m_thread; - - // stored data from backend (used by multiple threads...) - std::shared_ptr m_groups; - std::shared_ptr m_channels; - std::shared_ptr m_epg; - std::shared_ptr m_recordings; - std::shared_ptr m_timers; - long long m_recordingAvailableDuration; - long long m_recordingRecordedDuration; - time_t m_epgMinTime; - time_t m_epgMaxTime; - int m_epgMaxDays; - - // data used only by "job" thread - bool m_bEGPLoaded; - time_t m_iLastStart; - time_t m_iLastEnd; - ApiManager::StreamQuality_t m_streamQuality; - unsigned m_fullChannelEpgRefresh; - unsigned m_loadingsRefresh; - unsigned m_keepAliveDelay; - unsigned m_epgCheckDelay; - bool m_useH265; - bool m_useAdaptive; - bool m_showLockedChannels; - - ApiManager m_manager; -};