diff --git a/CMakeLists.txt b/CMakeLists.txt index 619af2ba7..9de063241 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,35 @@ set(DEPLIBS ${p8-platform_LIBRARIES} message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}") set(IPTV_SOURCES src/client.cpp - src/PVRIptvData.cpp) + src/PVRIptvData.cpp + src/iptvsimple/Settings.cpp + src/iptvsimple/Channels.cpp + src/iptvsimple/ChannelGroups.cpp + src/iptvsimple/Epg.cpp + src/iptvsimple/PlaylistLoader.cpp + src/iptvsimple/data/Channel.cpp + src/iptvsimple/data/ChannelEpg.cpp + src/iptvsimple/data/ChannelGroup.cpp + src/iptvsimple/data/EpgEntry.cpp + src/iptvsimple/data/EpgGenre.cpp + src/iptvsimple/utilities/FileUtils.cpp + src/iptvsimple/utilities/Logger.cpp) set(IPTV_HEADERS src/client.h - src/PVRIptvData.h) + src/PVRIptvData.h + src/iptvsimple/Settings.h + src/iptvsimple/Channels.h + src/iptvsimple/ChannelGroups.h + src/iptvsimple/Epg.h + src/iptvsimple/PlaylistLoader.h + src/iptvsimple/data/Channel.h + src/iptvsimple/data/ChannelEpg.h + src/iptvsimple/data/ChannelGroup.h + src/iptvsimple/data/EpgEntry.h + src/iptvsimple/data/EpgGenre.h + src/iptvsimple/utilities/FileUtils.h + src/iptvsimple/utilities/Logger.h + src/iptvsimple/utilities/XMLUtils.h) addon_version(pvr.iptvsimple IPTV) add_definitions(-DIPTV_VERSION=${IPTV_VERSION}) diff --git a/README.md b/README.md index 688d16435..c99e04f12 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # IPTV Simple PVR -IPTV Live TV and Radio PVR client addon for [Kodi](http://kodi.tv) +IPTV Live TV and Radio PVR client addon for [Kodi](https://kodi.tv) ## Build instructions @@ -42,7 +42,69 @@ Note that the steps in the following section need to be performed before the add 1. `cd $HOME/pvr.iptvsimple` 2. `./build-install-mac.sh ../xbmc-addon` +If you would prefer to run the rebuild steps manually instead of using the above helper script check the appendix [here](#manual-steps-to-rebuild-the-addon-on-macosx) + ##### Useful links -* [Kodi's PVR user support](http://forum.kodi.tv/forumdisplay.php?fid=167) -* [Kodi's PVR development support](http://forum.kodi.tv/forumdisplay.php?fid=136) +* [Kodi's PVR user support](https://forum.kodi.tv/forumdisplay.php?fid=167) +* [Kodi's PVR development support](https://forum.kodi.tv/forumdisplay.php?fid=136) + +### Settings Levels +In Kodi 18.2 the level of settings shown will correspond to the level set in the main kodi settings UI: `Basic`, `Standard`, `Advanced` and `Expert`. From Kodi 19 it will be possible to change the settingds level from within the addon settings itself. + +### General +General settings required for the addon to function. + +* **Location**: Select where to find the M3U resource. The options are: + - `Local path` - A path to an M3U file whether it be on the device or the local network. + - `Remote path` - A URL specifying the location of the M3U file. +* **M3U play list path**: If location is `Local path` this setting must contain a valid path for the addon to function. +* **M3U play list URL**: If location is `Remote path` this setting must contain a valid URL for the addon to function. +* **Cache M3U at local storage**: If location is `Remote path` select whether or not the the M3U file should be cached locally. +* **Start channel number**: The number to start numbering channels from. + +### EPG Settings +Settings related to the EPG. + +* **Location**: Select where to find the XMLTV resource. The options are: + - `Local path` - A path to an XMLTV file whether it be on the device or the local network. + - `Remote path` - A URL specifying the location of the XMLTV file. +* **XMLTV path**: If location is `Local Path` this setting should contain a valid path. +* **XMLTV URL**: If location is `Remote Path` this setting should contain a valid URL. +* **Cache XMLTV at local storage**: If location is `Remote path` select whether or not the the XMLTV file should be cached locally. +* **EPG time shift**: Adjust the EPG times by this value in minutes, range is from -720 mins to +720 mins (+/- 12 hours). +* **Apply time shift to all channels**: Whether or not to override the time shift for all channels with `EPG time shift`. If not enabled `EPG time shift` plus the individual time shift per channel (if available) will be used. + +### Channel Logos +Settings realted to Channel Logos. + +* **Location**: Select where to find the channel logos. The options are: + - `Local path` - A path to a folder whether it be on the device or the local network. + - `Remote path ` - A base URL specifying the location of the logos. +* **Channel logos folder**: If location is `Local Path` this setting should contain a valid folder. +* **Channel logos base URL**: If location is `Remote Path` this setting should contain a valid base URL. +* **Channel logos from XMLTV**: Preference on how to handle channel logos. The options are: + - `Ignore` - Don't use channel logos from an XMLTV file. + - `Prefer M3U` - Use the channel logo from the M3U if available otherwise use the XMLTV logo. + - `Prefer XMLTV` - Use the channel logo from the XMLTV file if available otherwise use the M3U logo. + +## Appendix + +### Manual Steps to rebuild the addon on MacOSX + +The following steps can be followed manually instead of using the `build-install-mac.sh` in the root of the addon repo after the [initial addon build](#build-tools-and-initial-addon-build) has been completed. + +**To rebuild the addon after changes** + +1. `rm tools/depends/target/binary-addons/.installed-macosx*` +2. `make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons ADDONS="pvr.vuplus" ADDON_SRC_PREFIX=$HOME` + +or + +1. `cd tools/depends/target/binary-addons/macosx*` +2. `make` + +**Copy the addon to the Kodi addon directory on Mac** + +1. `rm -rf "$HOME/Library/Application Support/Kodi/addons/pvr.vuplus"` +2. `cp -rf $HOME/xbmc-addon/addons/pvr.vuplus "$HOME/Library/Application Support/Kodi/addons"` \ No newline at end of file diff --git a/build-install-mac.sh b/build-install-mac.sh index 805e3d9ee..079aa89bd 100755 --- a/build-install-mac.sh +++ b/build-install-mac.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then echo "Usage: $0 " >&2 exit 1 @@ -10,6 +12,16 @@ if [[ "$OSTYPE" != "darwin"* ]]; then exit 1 fi +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +if [[ "$1" = /* ]] +then + #absolute path + SCRIPT_DIR="" +else + #relative + SCRIPT_DIR="$SCRIPT_DIR/" +fi + BINARY_ADDONS_TARGET_DIR="$1/tools/depends/target/binary-addons" MACOSX_BINARY_ADDONS_TARGET_DIR="" KODI_ADDONS_DIR="$HOME/Library/Application Support/Kodi/addons" @@ -20,16 +32,15 @@ if [ ! -d "$BINARY_ADDONS_TARGET_DIR" ]; then exit 1 fi -XBMC_BUILD_ADDON_INSTALL_DIR=$(cd $1/addons/$ADDON_NAME 2> /dev/null && pwd -P) - for DIR in "$BINARY_ADDONS_TARGET_DIR/"macosx*; do if [ -d "${DIR}" ]; then - MACOSX_BINARY_ADDONS_TARGET_DIR="${DIR}" + MACOSX_BINARY_ADDONS_TARGET_DIR="${DIR}" + break fi done if [ -z "$MACOSX_BINARY_ADDONS_TARGET_DIR" ]; then - echo "Error: Could not find binary addons build directory at: $BINARY_ADDONS_DIR/macosx*" >&2 + echo "Error: Could not find binary addons build directory at: $BINARY_ADDONS_TARGET_DIR/macosx*" >&2 exit 1 fi @@ -40,5 +51,7 @@ fi cd "$MACOSX_BINARY_ADDONS_TARGET_DIR" make + +XBMC_BUILD_ADDON_INSTALL_DIR=$(cd "$SCRIPT_DIR$1/addons/$ADDON_NAME" 2> /dev/null && pwd -P) rm -rf "$KODI_ADDONS_DIR/$ADDON_NAME" -cp -rf "$XBMC_BUILD_ADDON_INSTALL_DIR" "$KODI_ADDONS_DIR" \ No newline at end of file +cp -rf "$XBMC_BUILD_ADDON_INSTALL_DIR" "$KODI_ADDONS_DIR" diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index dc8126507..3821b2424 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index be39b4812..eb076bf54 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -1,3 +1,9 @@ +v4.3.0 +- Added: Auto reload channels, groups and EPG on settings change +- Added: Support for #EXTGRP tag in M3U file +- Fixed: Channel with no groups inherit previous channels groups +- Added: update new file kodi headers to start with kodi/ + v4.2.2 - Update build system version - Change header include way diff --git a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po index 11064619f..583940706 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -18,98 +18,235 @@ msgstr "" #settings labels +#label: General - m3uPathType +#label: EPG Settings - epgPathType +#label: Channel Logos - logoPathType msgctxt "#30000" msgid "Location" msgstr "" +#label-option: General - m3uPathType +#label-option: General - epgPathType +#label-option: General - logoPathType msgctxt "#30001" -msgid "Local Path (include Local Network)" +msgid "Local path (include local network)" msgstr "" +#label-option: General - m3uPathType +#label-option: General - epgPathType +#label-option: General - logoPathType msgctxt "#30002" -msgid "Remote Path (Internet address)" +msgid "Remote path (Internet address)" msgstr "" #empty strings from id 30003 to 30009 +#label-category: general +#label-group: General - General msgctxt "#30010" msgid "General" msgstr "" +#label: General - m3uPath msgctxt "#30011" -msgid "M3U Play List Path" +msgid "M3U playlist path" msgstr "" +#label: General - m3uUrl msgctxt "#30012" -msgid "M3U Play List URL" +msgid "M3U playlist URL" msgstr "" msgctxt "#30013" -msgid "Numbering channels starts at" +msgid "Start channel number" msgstr "" #empty strings from id 30014 to 30019 +#label-category: epgsettings +#label-group: EPG Settings - EPG Settings msgctxt "#30020" msgid "EPG Settings" msgstr "" +#label: EPG Settings - epgPath msgctxt "#30021" -msgid "XMLTV Path" +msgid "XMLTV path" msgstr "" +#label: EPG Settings - epgUrl msgctxt "#30022" msgid "XMLTV URL" msgstr "" +#label: EPG Settings - epgTSOverride msgctxt "#30023" -msgid "Apply Time Shift To All Channels" +msgid "Apply time shift to all channels" msgstr "" +#label: EPG Settings - epgTimeShift msgctxt "#30024" -msgid "EPG Time Shift (hours)" +msgid "EPG time shift" msgstr "" +#label: General - m3uCache msgctxt "#30025" msgid "Cache m3u at local storage" msgstr "" +#label: EPG Settings - epgCache msgctxt "#30026" msgid "Cache XMLTV at local storage" msgstr "" #empty strings from id 30027 to 30029 +#label-category: channellogos +#label-group: Channel Logos - Channel Logos msgctxt "#30030" -msgid "Channels Logos" +msgid "Channel Logos" msgstr "" +#label: Channel Logos - logoPath msgctxt "#30031" -msgid "Channels Logos Folder" +msgid "Channel logos folder" msgstr "" +#label: Channel Logos - logoBaseUrl msgctxt "#30032" -msgid "Channels Logos Base URL" +msgid "Channel logos base URL" msgstr "" #empty strings from id 30033 to 30039 +#label-group: Channel Logos - XMLTV Logos Options msgctxt "#30040" msgid "XMLTV Logos Options" msgstr "" +#label: Channel Logos - logoFromEpg msgctxt "#30041" -msgid "Channels Logos from XMLTV" +msgid "Channels logos from XMLTV" msgstr "" +#label-option: Channel Logos - logoFromEpg msgctxt "#30042" msgid "Ignore" msgstr "" +#label-option: Channel Logos - logoFromEpg msgctxt "#30043" msgid "Prefer M3U" msgstr "" +#label-option: Channel Logos - logoFromEpg msgctxt "#30044" msgid "Prefer XMLTV" msgstr "" + +#empty strings from id 30045 to 30599 + +############# +# help info # +############# + +#help info - General + +#help-category: General +msgctxt "#30600" +msgid "General settings required for the addon to function." +msgstr "" + +#help: General - m3uPathType +msgctxt "#30601" +msgid "Select where to find the M3U resource. The options are: [Local path] - A path to an M3U file whether it be on the device or the local network; [Remote path] - A URL specifying the location of the M3U file." +msgstr "" + +#help: General - m3uPath +msgctxt "#30602" +msgid "If location is [Local path] this setting must contain a valid path for the addon to function." +msgstr "" + +#help: General - m3uUrl +msgctxt "#30603" +msgid "If location is [Remote path] this setting must contain a valid URL for the addon to function." +msgstr "" + +#help: General - m3uCache +msgctxt "#30604" +msgid "If location is [Remote path] select whether or not the the M3U file should be cached locally." +msgstr "" + +#help: General - startNum +msgctxt "#30605" +msgid "The number to start numbering channels from." +msgstr "" + +#empty strings from id 30006 to 30619 + + +#help info - EPG Settings + +#help-category: EPG Settings +msgctxt "#30620" +msgid "Settings related to the EPG." +msgstr "" + +#help: EPG Settings - epgPathType +msgctxt "#30621" +msgid "Select where to find the XMLTV resource. The options are: [Local path] - A path to an XMLTV file whether it be on the device or the local network; [Remote path] - A URL specifying the location of the XMLTV file." +msgstr "" + +#help: EPG Settings - epgPath +msgctxt "#30622" +msgid "If location is [Local Path] this setting should contain a valid path." +msgstr "" + +#help: EPG Settings - epgUrl +msgctxt "#30623" +msgid "If location is [Remote Path] this setting should contain a valid URL." +msgstr "" + +#help: EPG Settings - epgCache +msgctxt "#30624" +msgid "If location is [Remote path] select whether or not the the XMLTV file should be cached locally." +msgstr "" + +#help: EPG Settings - epgTimeShift +msgctxt "#30625" +msgid "Adjust the EPG times by this value in minutes, range is from -720 mins to +720 mins (+/- 12 hours)." +msgstr "" + +#help: EPG Settings - epgTSOverside +msgctxt "#30626" +msgid "Whether or not to override the time shift for all channels with `EPG time shift`. If not enabled `EPG time shift` plus the individual time shift per channel (if available) will be used." +msgstr "" + +#empty strings from id 30027 to 30639 + +#help info - Channel Logos + +#help-category: Channel Logos +msgctxt "#30640" +msgid "Settings realted to Channel Logos." +msgstr "" + +#help: Channel Logos - logoPathType +msgctxt "#30641" +msgid "Select where to find the channel logos. The options are: [Local path] - A path to a folder whether it be on the device or the local network; [Remote path] - A base URL specifying the location of the logos." +msgstr "" + +#help: Channel Logos - logo Path +msgctxt "#30642" +msgid "If location is [Local Path] this setting should contain a valid folder." +msgstr "" + +#help: Channel Logos - logoBaseUrl +msgctxt "#30643" +msgid "If location is [Remote Path] this setting should contain a valid base URL." +msgstr "" + +#help: Channel Logos - logoFromEpg +msgctxt "#30644" +msgid "Preference on how to handle channel logos. The options are: [Ignore] - Don't use channel logos from an XMLTV file; [Prefer M3U] - Use the channel logo from the M3U if available otherwise use the XMLTV logo; [Prefer XMLTV] - Use the channel logo from the XMLTV file if available otherwise use the M3U logo." +msgstr "" \ No newline at end of file diff --git a/pvr.iptvsimple/resources/settings.xml b/pvr.iptvsimple/resources/settings.xml index f6bc3654e..ca628dbcb 100644 --- a/pvr.iptvsimple/resources/settings.xml +++ b/pvr.iptvsimple/resources/settings.xml @@ -1,33 +1,184 @@ - - - - - - - - - - - + + +
- - - - - - - - - - + + + + + 0 + 1 + + + + + + + + + + 0 + + + true + false + + + 0 + + + 1033 + + + + 0 + + + true + + + 1 + + + + + 0 + true + + 1 + + + + + 0 + 1 + + + + - - - - - - - - - + + + + + 0 + 1 + + + + + + + + + + 0 + + + true + false + + + 0 + + + 1033 + + + + 0 + + + true + + + 1 + + + + + 0 + true + + 1 + + + + + 0 + 0 + + -720 + 30 + 720 + + + 14044 + + + + 0 + false + + + + + + + + + + 0 + 1 + + + + + + + + + + 0 + + + true + true + + + 0 + + + 657 + + + + 0 + + + true + + + 1 + + + + + + + 0 + 1 + + + + + + + + + + + + +
diff --git a/src/PVRIptvData.cpp b/src/PVRIptvData.cpp index 637121da9..989e023d2 100644 --- a/src/PVRIptvData.cpp +++ b/src/PVRIptvData.cpp @@ -25,726 +25,112 @@ #include "PVRIptvData.h" #include "client.h" -#include "p8-platform/util/StringUtils.h" -#include "rapidxml/rapidxml.hpp" -#include "zlib.h" - -#include -#include -#include -#include -#include -#include -#include - -#define M3U_START_MARKER "#EXTM3U" -#define M3U_INFO_MARKER "#EXTINF" -#define TVG_INFO_ID_MARKER "tvg-id=" -#define TVG_INFO_NAME_MARKER "tvg-name=" -#define TVG_INFO_LOGO_MARKER "tvg-logo=" -#define TVG_INFO_SHIFT_MARKER "tvg-shift=" -#define TVG_INFO_CHNO_MARKER "tvg-chno=" -#define GROUP_NAME_MARKER "group-title=" -#define KODIPROP_MARKER "#KODIPROP:" -#define EXTVLCOPT_MARKER "#EXTVLCOPT:" -#define RADIO_MARKER "radio=" -#define PLAYLIST_TYPE_MARKER "#EXT-X-PLAYLIST-TYPE:" -#define CHANNEL_LOGO_EXTENSION ".png" -#define SECONDS_IN_DAY 86400 -#define GENRES_MAP_FILENAME "genres.xml" +#include "iptvsimple/Settings.h" +#include "iptvsimple/utilities/Logger.h" using namespace ADDON; -using namespace rapidxml; +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; -template -inline bool GetNodeValue(const xml_node* pRootNode, const char* strTag, std::string& strStringValue) +PVRIptvData::PVRIptvData() { - xml_node* pChildNode = pRootNode->first_node(strTag); - if (!pChildNode) - { - return false; - } - strStringValue = pChildNode->value(); - return true; -} + m_channels.Clear(); + m_channelGroups.Clear(); + m_epg.Clear(); -template -inline bool GetAttributeValue(const xml_node* pNode, const char* strAttributeName, std::string& strStringValue) -{ - xml_attribute* pAttribute = pNode->first_attribute(strAttributeName); - if (!pAttribute) - { - return false; - } - strStringValue = pAttribute->value(); - return true; + m_playlistLoader.LoadPlayList(); } -namespace -{ - -// Adapted from https://stackoverflow.com/a/31533119 - -// Conversion from UTC date to second, signed 64-bit adjustable epoch version. -// Written by François Grieu, 2015-07-21; public domain. - -long long MakeTime(int year, int month, int day) +bool PVRIptvData::Start() { - return static_cast(year) * 365 + year / 4 - year / 100 * 3 / 4 + (month + 2) * 153 / 5 + day; -} - -long long GetUTCTime(int year, int mon, int mday, int hour, int min, int sec) -{ - int m = mon - 1; - int y = year + 100; - - if (m < 2) - { - m += 12; - --y; - } - - return (((MakeTime(y, m, mday) - MakeTime(1970 + 99, 12, 1)) * 24 + hour) * 60 + min) * 60 + sec; -} - -long long ParseDateTime(const std::string& strDate) -{ - int year = 2000; - int mon = 1; - int mday = 1; - int hour = 0; - int min = 0; - int sec = 0; - char offset_sign = '+'; - int offset_hours = 0; - int offset_minutes = 0; - - sscanf(strDate.c_str(), "%04d%02d%02d%02d%02d%02d %c%02d%02d", &year, &mon, &mday, &hour, &min, &sec, &offset_sign, &offset_hours, &offset_minutes); - - long offset_of_date = (offset_hours * 60 + offset_minutes) * 60; - if (offset_sign == '-') - offset_of_date = -offset_of_date; - - return GetUTCTime(year, mon, mday, hour, min, sec) - offset_of_date; -} - -} // unnamed namespace - - -PVRIptvData::PVRIptvData(void) -{ - m_strXMLTVUrl = g_strTvgPath; - m_strM3uUrl = g_strM3UPath; - m_strLogoPath = g_strLogoPath; - m_iEPGTimeShift = g_iEPGTimeShift; - m_bTSOverride = g_bTSOverride; - m_iLastStart = 0; - m_iLastEnd = 0; - - m_channels.clear(); - m_groups.clear(); - m_epg.clear(); - m_genres.clear(); - - LoadPlayList(); -} - -void* PVRIptvData::Process(void) -{ - return nullptr; -} - -PVRIptvData::~PVRIptvData(void) -{ - m_channels.clear(); - m_groups.clear(); - m_epg.clear(); - m_genres.clear(); -} - -bool PVRIptvData::LoadEPG(time_t iStart, time_t iEnd) -{ - if (m_strXMLTVUrl.empty()) - { - XBMC->Log(LOG_NOTICE, "EPG file path is not configured. EPG not loaded."); - return false; - } - - std::string data; - std::string decompressed; - int iReaded = 0; - - int iCount = 0; - while (iCount < 3) // max 3 tries - { - if ((iReaded = GetCachedFileContents(TVG_FILE_NAME, m_strXMLTVUrl, data, g_bCacheEPG)) != 0) - { - break; - } - XBMC->Log(LOG_ERROR, "Unable to load EPG file '%s': file is missing or empty. :%dth try.", m_strXMLTVUrl.c_str(), ++iCount); - if (iCount < 3) - { - usleep(2 * 1000 * 1000); // sleep 2 sec before next try. - } - } - - if (iReaded == 0) - { - XBMC->Log(LOG_ERROR, "Unable to load EPG file '%s': file is missing or empty. After %d tries.", m_strXMLTVUrl.c_str(), iCount); - return false; - } - - char* buffer; - - // gzip packed - if (data[0] == '\x1F' && data[1] == '\x8B' && data[2] == '\x08') - { - if (!GzipInflate(data, decompressed)) - { - XBMC->Log(LOG_ERROR, "Invalid EPG file '%s': unable to decompress file.", m_strXMLTVUrl.c_str()); - return false; - } - buffer = &(decompressed[0]); - } - else - buffer = &(data[0]); - - // xml should starts with 'Log(LOG_ERROR, "Invalid EPG file '%s': unable to parse file.", m_strXMLTVUrl.c_str()); - return false; - } - } - } - - xml_document<> xmlDoc; - try - { - xmlDoc.parse<0>(buffer); - } - catch (parse_error p) - { - XBMC->Log(LOG_ERROR, "Unable parse EPG XML: %s", p.what()); - return false; - } - - xml_node<>* pRootElement = xmlDoc.first_node("tv"); - if (!pRootElement) - { - XBMC->Log(LOG_ERROR, "Invalid EPG XML: no tag found"); - return false; - } - - // clear previously loaded epg - if (m_epg.size() > 0) - m_epg.clear(); - - int iBroadCastId = 0; - xml_node<>* pChannelNode = nullptr; - for (pChannelNode = pRootElement->first_node("channel"); pChannelNode; pChannelNode = pChannelNode->next_sibling("channel")) - { - std::string strName; - std::string strId; - if (!GetAttributeValue(pChannelNode, "id", strId)) - continue; - - GetNodeValue(pChannelNode, "display-name", strName); - if (!FindChannel(strId, strName)) - continue; - - PVRIptvEpgChannel epgChannel; - epgChannel.strId = strId; - epgChannel.strName = strName; - - // get icon if available - xml_node<>* pIconNode = pChannelNode->first_node("icon"); - if (!pIconNode || !GetAttributeValue(pIconNode, "src", epgChannel.strIcon)) - epgChannel.strIcon = ""; - - m_epg.push_back(epgChannel); - } - - if (m_epg.size() == 0) - { - XBMC->Log(LOG_ERROR, "EPG channels not found."); - return false; - } - - int iMinShiftTime = m_iEPGTimeShift; - int iMaxShiftTime = m_iEPGTimeShift; - if (!m_bTSOverride) - { - iMinShiftTime = SECONDS_IN_DAY; - iMaxShiftTime = -SECONDS_IN_DAY; - - for (const auto& channel : m_channels) - { - if (channel.iTvgShift + m_iEPGTimeShift < iMinShiftTime) - iMinShiftTime = channel.iTvgShift + m_iEPGTimeShift; - if (channel.iTvgShift + m_iEPGTimeShift > iMaxShiftTime) - iMaxShiftTime = channel.iTvgShift + m_iEPGTimeShift; - } - } - - PVRIptvEpgChannel* epg = nullptr; - for (pChannelNode = pRootElement->first_node("programme"); pChannelNode; pChannelNode = pChannelNode->next_sibling("programme")) - { - std::string strId; - if (!GetAttributeValue(pChannelNode, "channel", strId)) - continue; - - if (!epg || StringUtils::CompareNoCase(epg->strId, strId) != 0) - { - if (!(epg = FindEpg(strId))) - continue; - } - - std::string strStart, strStop; - if (!GetAttributeValue(pChannelNode, "start", strStart) || - !GetAttributeValue(pChannelNode, "stop", strStop)) - continue; - - long long iTmpStart = ParseDateTime(strStart); - long long iTmpEnd = ParseDateTime(strStop); - - if ((iTmpEnd + iMaxShiftTime < iStart) || - (iTmpStart + iMinShiftTime > iEnd)) - continue; - - PVRIptvEpgEntry entry; - entry.iBroadcastId = ++iBroadCastId; - entry.iChannelId = atoi(strId.c_str()); - entry.iGenreType = 0; - entry.iGenreSubType = 0; - entry.strPlotOutline = ""; - entry.startTime = static_cast(iTmpStart); - entry.endTime = static_cast(iTmpEnd); - - GetNodeValue(pChannelNode, "title", entry.strTitle); - GetNodeValue(pChannelNode, "desc", entry.strPlot); - GetNodeValue(pChannelNode, "category", entry.strGenreString); - GetNodeValue(pChannelNode, "sub-title", entry.strEpisodeName); - - xml_node<> *pCreditsNode = pChannelNode->first_node("credits"); - if (pCreditsNode != NULL) { - GetNodeValue(pCreditsNode, "actor", entry.strCast); - GetNodeValue(pCreditsNode, "director", entry.strDirector); - GetNodeValue(pCreditsNode, "writer", entry.strWriter); - } - - xml_node<>* pIconNode = pChannelNode->first_node("icon"); - if (!pIconNode || !GetAttributeValue(pIconNode, "src", entry.strIconPath)) - entry.strIconPath = ""; - - epg->epg.push_back(entry); - } - - xmlDoc.clear(); - LoadGenres(); - - XBMC->Log(LOG_NOTICE, "EPG Loaded."); + P8PLATFORM::CLockObject lock(m_mutex); - if (g_iEPGLogos > 0) - ApplyChannelsLogosFromEPG(); + XBMC->Log(LOG_INFO, "%s Starting separate client update thread...", __FUNCTION__); + CreateThread(); - return true; + return IsRunning(); } -bool PVRIptvData::LoadPlayList(void) +void* PVRIptvData::Process() { - if (m_strM3uUrl.empty()) + while (!IsStopped()) { - XBMC->Log(LOG_NOTICE, "Playlist file path is not configured. Channels not loaded."); - return false; - } - - std::string strPlaylistContent; - if (!GetCachedFileContents(M3U_FILE_NAME, m_strM3uUrl, strPlaylistContent, g_bCacheM3U)) - { - XBMC->Log(LOG_ERROR, "Unable to load playlist file '%s': file is missing or empty.", m_strM3uUrl.c_str()); - return false; - } - - std::stringstream stream(strPlaylistContent); - - /* load channels */ - bool bFirst = true; - bool bIsRealTime = true; - int iChannelIndex = 0; - int iUniqueGroupId = 0; - int iChannelNum = g_iStartNumber; - int iEPGTimeShift = 0; - std::vector iCurrentGroupId; - - PVRIptvChannel tmpChannel; - tmpChannel.strTvgId = ""; - tmpChannel.strChannelName = ""; - tmpChannel.strTvgName = ""; - tmpChannel.strTvgLogo = ""; - tmpChannel.iTvgShift = 0; - - std::string strLine; - while (std::getline(stream, strLine)) - { - strLine = StringUtils::TrimRight(strLine, " \t\r\n"); - strLine = StringUtils::TrimLeft(strLine, " \t"); - - XBMC->Log(LOG_DEBUG, "Read line: '%s'", strLine.c_str()); - - if (strLine.empty()) - { - continue; - } - - if (bFirst) - { - bFirst = false; - if (StringUtils::Left(strLine, 3) == "\xEF\xBB\xBF") - { - strLine.erase(0, 3); - } - if (StringUtils::Left(strLine, strlen(M3U_START_MARKER)) == M3U_START_MARKER) - { - double fTvgShift = atof(ReadMarkerValue(strLine, TVG_INFO_SHIFT_MARKER).c_str()); - iEPGTimeShift = (int)(fTvgShift * 3600.0); - continue; - } - else - { - XBMC->Log(LOG_ERROR, - "URL '%s' missing %s descriptor on line 1, attempting to " - "parse it anyway.", - m_strM3uUrl.c_str(), M3U_START_MARKER); - } - } - - if (StringUtils::Left(strLine, strlen(M3U_INFO_MARKER)) == M3U_INFO_MARKER) - { - bool bRadio = false; - double fTvgShift = 0; - std::string strChnlNo = ""; - std::string strChnlName = ""; - std::string strTvgId = ""; - std::string strTvgName = ""; - std::string strTvgLogo = ""; - std::string strTvgShift = ""; - std::string strGroupName = ""; - std::string strRadio = ""; - - // parse line - int iColon = static_cast(strLine.find(':')); - int iComma = static_cast(strLine.rfind(',')); - if (iColon >= 0 && iComma >= 0 && iComma > iColon) - { - // parse name - iComma++; - strChnlName = StringUtils::Right(strLine, static_cast(strLine.size() - iComma)); - strChnlName = StringUtils::Trim(strChnlName); - tmpChannel.strChannelName = XBMC->UnknownToUTF8(strChnlName.c_str()); - - // parse info - iColon++; - iComma--; - const std::string strInfoLine = StringUtils::Mid(strLine, iColon, iComma - iColon); - - strTvgId = ReadMarkerValue(strInfoLine, TVG_INFO_ID_MARKER); - strTvgName = ReadMarkerValue(strInfoLine, TVG_INFO_NAME_MARKER); - strTvgLogo = ReadMarkerValue(strInfoLine, TVG_INFO_LOGO_MARKER); - strChnlNo = ReadMarkerValue(strInfoLine, TVG_INFO_CHNO_MARKER); - strGroupName = ReadMarkerValue(strInfoLine, GROUP_NAME_MARKER); - strRadio = ReadMarkerValue(strInfoLine, RADIO_MARKER); - strTvgShift = ReadMarkerValue(strInfoLine, TVG_INFO_SHIFT_MARKER); - - if (strTvgId.empty()) - { - char buff[255]; - sprintf(buff, "%d", atoi(strInfoLine.c_str())); - strTvgId.append(buff); - } - if (strTvgLogo.empty()) - { - strTvgLogo = strChnlName; - } - if (!strChnlNo.empty()) - { - iChannelNum = atoi(strChnlNo.c_str()); - } - fTvgShift = atof(strTvgShift.c_str()); - - bRadio = !StringUtils::CompareNoCase(strRadio, "true"); - tmpChannel.strTvgId = strTvgId; - tmpChannel.strTvgName = XBMC->UnknownToUTF8(strTvgName.c_str()); - tmpChannel.strTvgLogo = XBMC->UnknownToUTF8(strTvgLogo.c_str()); - tmpChannel.iTvgShift = (int)(fTvgShift * 3600.0); - tmpChannel.bRadio = bRadio; - - if (strTvgShift.empty()) - { - tmpChannel.iTvgShift = iEPGTimeShift; - } - - if (!strGroupName.empty()) - { - std::stringstream streamGroups(strGroupName); - iCurrentGroupId.clear(); - - while (std::getline(streamGroups, strGroupName, ';')) - { - strGroupName = XBMC->UnknownToUTF8(strGroupName.c_str()); - const PVRIptvChannelGroup* pGroup = FindGroup(strGroupName); + Sleep(PROCESS_LOOP_WAIT_SECS * 1000); - if (!pGroup) - { - PVRIptvChannelGroup group; - group.strGroupName = strGroupName; - group.iGroupId = ++iUniqueGroupId; - group.bRadio = bRadio; - - m_groups.push_back(group); - iCurrentGroupId.push_back(iUniqueGroupId); - } - else - { - iCurrentGroupId.push_back(pGroup->iGroupId); - } - } - } - } - } - else if (StringUtils::Left(strLine, strlen(KODIPROP_MARKER)) == KODIPROP_MARKER) - { - const std::string value = ReadMarkerValue(strLine, KODIPROP_MARKER); - auto pos = value.find('='); - if (pos != std::string::npos) - { - const std::string prop = value.substr(0, pos); - const std::string propValue = value.substr(pos + 1); - tmpChannel.properties.insert({prop, propValue}); - } - } - else if (StringUtils::Left(strLine, strlen(EXTVLCOPT_MARKER)) == EXTVLCOPT_MARKER) - { - const std::string value = ReadMarkerValue(strLine, EXTVLCOPT_MARKER); - auto pos = value.find('='); - if (pos != std::string::npos) - { - const std::string prop = value.substr(0, pos); - const std::string propValue = value.substr(pos + 1); - tmpChannel.properties.insert({prop, propValue}); - - XBMC->Log(LOG_DEBUG, "Found #EXTVLCOPT property: '%s' value: '%s'", prop.c_str(), propValue.c_str()); - } - } - else if (StringUtils::Left(strLine, strlen(PLAYLIST_TYPE_MARKER)) == PLAYLIST_TYPE_MARKER) - { - if (ReadMarkerValue(strLine, PLAYLIST_TYPE_MARKER) == "VOD") - bIsRealTime = false; - } - else if (strLine[0] != '#') + P8PLATFORM::CLockObject lock(m_mutex); + if (m_reloadChannelsGroupsAndEPG) { - XBMC->Log(LOG_DEBUG, "Found URL: '%s' (current channel name: '%s')", strLine.c_str(), tmpChannel.strChannelName.c_str()); - - if (bIsRealTime) - tmpChannel.properties.insert({PVR_STREAM_PROPERTY_ISREALTIMESTREAM, "true"}); - - PVRIptvChannel channel; - channel.iUniqueId = GetChannelId(tmpChannel.strChannelName.c_str(), strLine.c_str()); - channel.iChannelNumber = iChannelNum; - channel.strTvgId = tmpChannel.strTvgId; - channel.strChannelName = tmpChannel.strChannelName; - channel.strTvgName = tmpChannel.strTvgName; - channel.strTvgLogo = tmpChannel.strTvgLogo; - channel.iTvgShift = tmpChannel.iTvgShift; - channel.bRadio = tmpChannel.bRadio; - channel.properties = tmpChannel.properties; - channel.strStreamURL = strLine; - channel.iEncryptionSystem = 0; - - iChannelNum++; + Sleep(1000); - for (int myGroupId : iCurrentGroupId) - { - channel.bRadio = m_groups.at(myGroupId - 1).bRadio; - m_groups.at(myGroupId - 1).members.push_back(iChannelIndex); - } + m_playlistLoader.ReloadPlayList(); + m_epg.ReloadEPG(); - m_channels.push_back(channel); - iChannelIndex++; - - tmpChannel.strTvgId = ""; - tmpChannel.strChannelName = ""; - tmpChannel.strTvgName = ""; - tmpChannel.strTvgLogo = ""; - tmpChannel.iTvgShift = 0; - tmpChannel.bRadio = false; - tmpChannel.properties.clear(); - bIsRealTime = true; + m_reloadChannelsGroupsAndEPG = false; } } - stream.clear(); - - if (m_channels.size() == 0) - { - XBMC->Log(LOG_ERROR, "Unable to load channels from file '%s': file is corrupted.", m_strM3uUrl.c_str()); - return false; - } - - ApplyChannelsLogos(); - - XBMC->Log(LOG_NOTICE, "Loaded %d channels.", m_channels.size()); - return true; + return nullptr; } -bool PVRIptvData::LoadGenres(void) +PVRIptvData::~PVRIptvData() { - std::string data; - - // try to load genres from userdata folder - std::string strFilePath = GetUserFilePath(GENRES_MAP_FILENAME); - if (!XBMC->FileExists(strFilePath.c_str(), false)) - { - // try to load file from addom folder - strFilePath = GetClientFilePath(GENRES_MAP_FILENAME); - if (!XBMC->FileExists(strFilePath.c_str(), false)) - return false; - } - - GetFileContents(strFilePath, data); - - if (data.empty()) - return false; - - m_genres.clear(); - - char* buffer = &(data[0]); - xml_document<> xmlDoc; - try - { - xmlDoc.parse<0>(buffer); - } - catch (parse_error p) - { - return false; - } - - xml_node<>* pRootElement = xmlDoc.first_node("genres"); - if (!pRootElement) - return false; - - for (xml_node<>* pGenreNode = pRootElement->first_node("genre"); pGenreNode; pGenreNode = pGenreNode->next_sibling("genre")) - { - std::string buff; - if (!GetAttributeValue(pGenreNode, "type", buff)) - continue; - - if (!StringUtils::IsNaturalNumber(buff)) - continue; - - PVRIptvEpgGenre genre; - genre.strGenre = pGenreNode->value(); - genre.iGenreType = atoi(buff.c_str()); - genre.iGenreSubType = 0; - - if (GetAttributeValue(pGenreNode, "subtype", buff) && StringUtils::IsNaturalNumber(buff)) - genre.iGenreSubType = atoi(buff.c_str()); - - m_genres.push_back(genre); - } + P8PLATFORM::CLockObject lock(m_mutex); + Logger::Log(LEVEL_DEBUG, "%s Stopping update thread...", __FUNCTION__); + StopThread(); - xmlDoc.clear(); - return true; + m_channels.Clear(); + m_channelGroups.Clear(); + m_epg.Clear(); } -int PVRIptvData::GetChannelsAmount(void) +int PVRIptvData::GetChannelsAmount() { P8PLATFORM::CLockObject lock(m_mutex); - return m_channels.size(); + return m_channels.GetChannelsAmount(); } PVR_ERROR PVRIptvData::GetChannels(ADDON_HANDLE handle, bool bRadio) { - P8PLATFORM::CLockObject lock(m_mutex); - for (unsigned int iChannelPtr = 0; iChannelPtr < m_channels.size(); iChannelPtr++) + std::vector channels; { - PVRIptvChannel& channel = m_channels.at(iChannelPtr); - if (channel.bRadio == bRadio) - { - PVR_CHANNEL xbmcChannel; - memset(&xbmcChannel, 0, sizeof(PVR_CHANNEL)); + P8PLATFORM::CLockObject lock(m_mutex); + m_channels.GetChannels(channels, bRadio); + } - xbmcChannel.iUniqueId = channel.iUniqueId; - xbmcChannel.bIsRadio = channel.bRadio; - xbmcChannel.iChannelNumber = channel.iChannelNumber; - strncpy(xbmcChannel.strChannelName, channel.strChannelName.c_str(), sizeof(xbmcChannel.strChannelName) - 1); - xbmcChannel.iEncryptionSystem = channel.iEncryptionSystem; - strncpy(xbmcChannel.strIconPath, channel.strLogoPath.c_str(), sizeof(xbmcChannel.strIconPath) - 1); - xbmcChannel.bIsHidden = false; + Logger::Log(LEVEL_DEBUG, "%s - channels available '%d', radio = %d", __FUNCTION__, channels.size(), bRadio); - PVR->TransferChannelEntry(handle, &xbmcChannel); - } - } + for (auto& channel : channels) + PVR->TransferChannelEntry(handle, &channel); return PVR_ERROR_NO_ERROR; } -bool PVRIptvData::GetChannel(const PVR_CHANNEL& channel, PVRIptvChannel& myChannel) +bool PVRIptvData::GetChannel(const PVR_CHANNEL& channel, Channel& myChannel) { P8PLATFORM::CLockObject lock(m_mutex); - for (unsigned int iChannelPtr = 0; iChannelPtr < m_channels.size(); iChannelPtr++) - { - PVRIptvChannel& thisChannel = m_channels.at(iChannelPtr); - if (thisChannel.iUniqueId == static_cast(channel.iUniqueId)) - { - myChannel.iUniqueId = thisChannel.iUniqueId; - myChannel.bRadio = thisChannel.bRadio; - myChannel.iChannelNumber = thisChannel.iChannelNumber; - myChannel.iEncryptionSystem = thisChannel.iEncryptionSystem; - myChannel.strChannelName = thisChannel.strChannelName; - myChannel.strLogoPath = thisChannel.strLogoPath; - myChannel.strStreamURL = thisChannel.strStreamURL; - myChannel.properties = thisChannel.properties; - - return true; - } - } - return false; + return m_channels.GetChannel(channel, myChannel); } -int PVRIptvData::GetChannelGroupsAmount(void) +int PVRIptvData::GetChannelGroupsAmount() { P8PLATFORM::CLockObject lock(m_mutex); - return m_groups.size(); + return m_channelGroups.GetChannelGroupsAmount(); } PVR_ERROR PVRIptvData::GetChannelGroups(ADDON_HANDLE handle, bool bRadio) { - P8PLATFORM::CLockObject lock(m_mutex); - for (const auto& group : m_groups) + std::vector channelGroups; { - if (group.bRadio == bRadio) - { - PVR_CHANNEL_GROUP xbmcGroup; - memset(&xbmcGroup, 0, sizeof(PVR_CHANNEL_GROUP)); + P8PLATFORM::CLockObject lock(m_mutex); + m_channelGroups.GetChannelGroups(channelGroups, bRadio); + } - xbmcGroup.iPosition = 0; /* not supported */ - xbmcGroup.bIsRadio = bRadio; /* is radio group */ - strncpy(xbmcGroup.strGroupName, group.strGroupName.c_str(), sizeof(xbmcGroup.strGroupName) - 1); + Logger::Log(LEVEL_DEBUG, "%s - channel groups available '%d'", __FUNCTION__, channelGroups.size()); - PVR->TransferChannelGroup(handle, &xbmcGroup); - } - } + for (const auto& channelGroup : channelGroups) + PVR->TransferChannelGroup(handle, &channelGroup); return PVR_ERROR_NO_ERROR; } @@ -752,449 +138,25 @@ PVR_ERROR PVRIptvData::GetChannelGroups(ADDON_HANDLE handle, bool bRadio) PVR_ERROR PVRIptvData::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group) { P8PLATFORM::CLockObject lock(m_mutex); - const PVRIptvChannelGroup* myGroup = FindGroup(group.strGroupName); - if (myGroup) - { - for (const auto& memberId : myGroup->members) - { - if ((memberId) < 0 || (memberId) >= static_cast(m_channels.size())) - continue; - - PVRIptvChannel& channel = m_channels.at(memberId); - 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; - - PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); - } - } - - return PVR_ERROR_NO_ERROR; + return m_channelGroups.GetChannelGroupMembers(handle, group); } PVR_ERROR PVRIptvData::GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t iStart, time_t iEnd) { P8PLATFORM::CLockObject lock(m_mutex); - for (const auto& myChannel : m_channels) - { - if (myChannel.iUniqueId != iChannelUid) - continue; - - if (iStart > m_iLastStart || iEnd > m_iLastEnd) - { - // reload EPG for new time interval only - LoadEPG(iStart, iEnd); - { - // doesn't matter is epg loaded or not we shouldn't try to load it for same interval - m_iLastStart = iStart; - m_iLastEnd = iEnd; - } - } - - const PVRIptvEpgChannel* epg = FindEpgForChannel(myChannel); - if (!epg || epg->epg.size() == 0) - return PVR_ERROR_NO_ERROR; - - int iShift = m_bTSOverride ? m_iEPGTimeShift : myChannel.iTvgShift + m_iEPGTimeShift; - - for (const auto& myTag : epg->epg) - { - if ((myTag.endTime + iShift) < iStart) - continue; - - int iGenreType, iGenreSubType; - - EPG_TAG tag; - memset(&tag, 0, sizeof(EPG_TAG)); - - tag.iUniqueBroadcastId = myTag.iBroadcastId; - tag.strTitle = myTag.strTitle.c_str(); - tag.iUniqueChannelId = iChannelUid; - tag.startTime = myTag.startTime + iShift; - tag.endTime = myTag.endTime + iShift; - tag.strPlotOutline = myTag.strPlotOutline.c_str(); - tag.strPlot = myTag.strPlot.c_str(); - tag.strOriginalTitle = nullptr; /* not supported */ - tag.strCast = myTag.strCast.c_str(); - tag.strDirector = myTag.strDirector.c_str(); - tag.strWriter = myTag.strWriter.c_str(); - tag.iYear = 0; /* not supported */ - tag.strIMDBNumber = nullptr; /* not supported */ - tag.strIconPath = myTag.strIconPath.c_str(); - if (FindEpgGenre(myTag.strGenreString, iGenreType, iGenreSubType)) - { - tag.iGenreType = iGenreType; - tag.iGenreSubType = iGenreSubType; - tag.strGenreDescription = nullptr; - } - else - { - tag.iGenreType = EPG_GENRE_USE_STRING; - tag.iGenreSubType = 0; /* not supported */ - tag.strGenreDescription = myTag.strGenreString.c_str(); - } - tag.iParentalRating = 0; /* not supported */ - tag.iStarRating = 0; /* not supported */ - tag.iSeriesNumber = 0; /* not supported */ - tag.iEpisodeNumber = 0; /* not supported */ - tag.iEpisodePartNumber = 0; /* not supported */ - tag.strEpisodeName = myTag.strEpisodeName.c_str(); - tag.iFlags = EPG_TAG_FLAG_UNDEFINED; - - PVR->TransferEpgEntry(handle, &tag); - - if ((myTag.startTime + iShift) > iEnd) - break; - } - - return PVR_ERROR_NO_ERROR; - } - - return PVR_ERROR_NO_ERROR; -} - -int PVRIptvData::GetFileContents(const std::string& url, std::string& strContent) -{ - strContent.clear(); - 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.length(); -} - -const PVRIptvChannel* PVRIptvData::FindChannel(const std::string& strId, const std::string& strName) const -{ - const std::string strTvgName = std::regex_replace(strName, std::regex(" "), "_"); - - for (const auto& myChannel : m_channels) - { - if (myChannel.strTvgId == strId) - return &myChannel; - - if (strTvgName == "") - continue; - - if (myChannel.strTvgName == strTvgName) - return &myChannel; - if (myChannel.strChannelName == strName) - return &myChannel; - } - - return nullptr; + return m_epg.GetEPGForChannel(handle, iChannelUid, iStart, iEnd); } -const PVRIptvChannelGroup* PVRIptvData::FindGroup(const std::string& strName) const -{ - for (const auto& myGroup : m_groups) - { - if (myGroup.strGroupName == strName) - return &myGroup; - } - - return nullptr; -} - -PVRIptvEpgChannel* PVRIptvData::FindEpg(const std::string& strId) -{ - for (auto& myEpgChannel : m_epg) - { - if (StringUtils::CompareNoCase(myEpgChannel.strId, strId) == 0) - return &myEpgChannel; - } - - return nullptr; -} - -const PVRIptvEpgChannel* PVRIptvData::FindEpgForChannel(const PVRIptvChannel& channel) const -{ - for (const auto& myEpgChannel : m_epg) - { - if (myEpgChannel.strId == channel.strTvgId) - return &myEpgChannel; - - const std::string strName = std::regex_replace(myEpgChannel.strName, std::regex(" "), "_"); - if (strName == channel.strTvgName || myEpgChannel.strName == channel.strTvgName) - return &myEpgChannel; - - if (myEpgChannel.strName == channel.strChannelName) - return &myEpgChannel; - } - - return nullptr; -} - -bool PVRIptvData::FindEpgGenre(const std::string& strGenre, int& iType, int& iSubType) -{ - if (m_genres.empty()) - return false; - - for (const auto& myGenre : m_genres) - { - if (StringUtils::CompareNoCase(myGenre.strGenre, strGenre) == 0) - { - iType = myGenre.iGenreType; - iSubType = myGenre.iGenreSubType; - return true; - } - } - - return false; -} - -/* - * This method uses zlib to decompress a gzipped file in memory. - * Author: Andrew Lim Chong Liang - * http://windrealm.org - */ -bool PVRIptvData::GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes) -{ - -#define HANDLE_CALL_ZLIB(status) { \ - if(status != Z_OK) { \ - free(uncomp); \ - return false; \ - } \ -} - - if (compressedBytes.size() == 0) - { - uncompressedBytes = compressedBytes; - return true; - } - - uncompressedBytes.clear(); - - unsigned full_length = compressedBytes.size(); - unsigned half_length = compressedBytes.size() / 2; - - unsigned uncompLength = full_length; - char* uncomp = static_cast(calloc(sizeof(char), uncompLength)); - - z_stream strm; - strm.next_in = (Bytef*)compressedBytes.c_str(); - strm.avail_in = compressedBytes.size(); - strm.total_out = 0; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - - bool done = false; - - HANDLE_CALL_ZLIB(inflateInit2(&strm, (16 + MAX_WBITS))); - - while (!done) - { - // If our output buffer is too small - if (strm.total_out >= uncompLength) - { - // Increase size of output buffer - uncomp = static_cast(realloc(uncomp, uncompLength + half_length)); - if (!uncomp) - return false; - uncompLength += half_length; - } - - strm.next_out = (Bytef*)(uncomp + strm.total_out); - strm.avail_out = uncompLength - strm.total_out; - - // Inflate another chunk. - int err = inflate(&strm, Z_SYNC_FLUSH); - if (err == Z_STREAM_END) - done = true; - else if (err != Z_OK) - { - break; - } - } - - HANDLE_CALL_ZLIB(inflateEnd(&strm)); - - for (size_t i = 0; i < strm.total_out; ++i) - { - uncompressedBytes += uncomp[i]; - } - - free(uncomp); - return true; -} - -int PVRIptvData::GetCachedFileContents(const std::string& strCachedName, const std::string& filePath, - std::string& strContents, const bool bUseCache /* false */) -{ - bool bNeedReload = false; - const std::string strCachedPath = GetUserFilePath(strCachedName); - const std::string strFilePath = filePath; - - // check cached file is exists - if (bUseCache && XBMC->FileExists(strCachedPath.c_str(), false)) - { - struct __stat64 statCached; - struct __stat64 statOrig; - - XBMC->StatFile(strCachedPath.c_str(), &statCached); - XBMC->StatFile(strFilePath.c_str(), &statOrig); - - bNeedReload = statCached.st_mtime < statOrig.st_mtime || statOrig.st_mtime == 0; - } - else - bNeedReload = true; - - if (bNeedReload) - { - GetFileContents(strFilePath, strContents); - - // write to cache - if (bUseCache && strContents.length() > 0) - { - void* fileHandle = XBMC->OpenFileForWrite(strCachedPath.c_str(), true); - if (fileHandle) - { - XBMC->WriteFile(fileHandle, strContents.c_str(), strContents.length()); - XBMC->CloseFile(fileHandle); - } - } - return strContents.length(); - } - - return GetFileContents(strCachedPath, strContents); -} - -void PVRIptvData::ApplyChannelsLogos() -{ - for (auto& channel : m_channels) - { - if (!channel.strTvgLogo.empty()) - { - if (!m_strLogoPath.empty() - // special proto - && channel.strTvgLogo.find("://") == std::string::npos) - channel.strLogoPath = PathCombine(m_strLogoPath, channel.strTvgLogo); - else - channel.strLogoPath = channel.strTvgLogo; - } - } -} - -void PVRIptvData::ApplyChannelsLogosFromEPG() -{ - bool bUpdated = false; - - for (auto& channel : m_channels) - { - const PVRIptvEpgChannel* epg = FindEpgForChannel(channel); - if (!epg || epg->strIcon.empty()) - continue; - - // 1 - prefer logo from playlist - if (!channel.strLogoPath.empty() && g_iEPGLogos == 1) - continue; - - // 2 - prefer logo from epg - if (!epg->strIcon.empty() && g_iEPGLogos == 2) - { - channel.strLogoPath = epg->strIcon; - bUpdated = true; - } - } - - if (bUpdated) - PVR->TriggerChannelUpdate(); -} - -void PVRIptvData::ReaplyChannelsLogos(const char* strNewPath) -{ - P8PLATFORM::CLockObject lock(m_mutex); - if (strlen(strNewPath) > 0) - { - m_strLogoPath = strNewPath; - ApplyChannelsLogos(); - - PVR->TriggerChannelUpdate(); - PVR->TriggerChannelGroupsUpdate(); - } -} - -void PVRIptvData::ReloadEPG(const char* strNewPath) -{ - P8PLATFORM::CLockObject lock(m_mutex); - if (strNewPath != m_strXMLTVUrl) - { - m_strXMLTVUrl = strNewPath; - // TODO clear epg for all channels - - if (LoadEPG(m_iLastStart, m_iLastEnd)) - { - for (unsigned int iChannelPtr = 0, max = m_channels.size(); iChannelPtr < max; iChannelPtr++) - { - PVRIptvChannel& myChannel = m_channels.at(iChannelPtr); - PVR->TriggerEpgUpdate(myChannel.iUniqueId); - } - } - } -} - -void PVRIptvData::ReloadPlayList(const char* strNewPath) +ADDON_STATUS PVRIptvData::SetSetting(const char* settingName, const void* settingValue) { P8PLATFORM::CLockObject lock(m_mutex); - if (strNewPath != m_strM3uUrl) - { - m_strM3uUrl = strNewPath; - m_channels.clear(); - - if (LoadPlayList()) - { - PVR->TriggerChannelUpdate(); - PVR->TriggerChannelGroupsUpdate(); - } - } -} -std::string PVRIptvData::ReadMarkerValue(const std::string& strLine, const char* strMarkerName) -{ - int iMarkerStart = static_cast(strLine.find(strMarkerName)); - if (iMarkerStart >= 0) - { - const std::string strMarker = strMarkerName; - iMarkerStart += strMarker.length(); - if (iMarkerStart < static_cast(strLine.length())) - { - char cFind = ' '; - if (strLine[iMarkerStart] == '"') - { - cFind = '"'; - iMarkerStart++; - } - int iMarkerEnd = static_cast(strLine.find(cFind, iMarkerStart)); - if (iMarkerEnd < 0) - { - iMarkerEnd = strLine.length(); - } - return strLine.substr(iMarkerStart, iMarkerEnd - iMarkerStart); - } - } + // When a number of settings change set this on the first one so it can be picked up + // in the process call for a reload of channels, groups and EPG. + if (!m_reloadChannelsGroupsAndEPG) + m_reloadChannelsGroupsAndEPG = true; - return std::string(""); -} - -int PVRIptvData::GetChannelId(const char* strChannelName, const char* strStreamUrl) -{ - std::string concat(strChannelName); - concat.append(strStreamUrl); - - const char* strString = concat.c_str(); - int iId = 0; - int c; - while ((c = *strString++)) - iId = ((iId << 5) + iId) + c; /* iId * 33 + c */ - - return abs(iId); -} + return Settings::GetInstance().SetValue(settingName, settingValue); +} \ No newline at end of file diff --git a/src/PVRIptvData.h b/src/PVRIptvData.h index c2c3ee6ae..238564d16 100644 --- a/src/PVRIptvData.h +++ b/src/PVRIptvData.h @@ -23,120 +23,44 @@ * */ -#include "p8-platform/os.h" #include "kodi/libXBMC_pvr.h" #include "p8-platform/threads/threads.h" -#include -#include +#include "iptvsimple/Channels.h" +#include "iptvsimple/ChannelGroups.h" +#include "iptvsimple/Epg.h" +#include "iptvsimple/PlaylistLoader.h" +#include "iptvsimple/data/Channel.h" -struct PVRIptvEpgEntry -{ - int iBroadcastId; - int iChannelId; - int iGenreType; - int iGenreSubType; - time_t startTime; - time_t endTime; - std::string strTitle; - std::string strEpisodeName; - std::string strPlotOutline; - std::string strPlot; - std::string strIconPath; - std::string strGenreString; - std::string strCast; - std::string strDirector; - std::string strWriter; -}; - -struct PVRIptvEpgChannel -{ - std::string strId; - std::string strName; - std::string strIcon; - std::vector epg; -}; - -struct PVRIptvChannel -{ - bool bRadio; - int iUniqueId; - int iChannelNumber; - int iEncryptionSystem; - int iTvgShift; - std::string strChannelName; - std::string strLogoPath; - std::string strStreamURL; - std::string strTvgId; - std::string strTvgName; - std::string strTvgLogo; - std::map properties; -}; - -struct PVRIptvChannelGroup -{ - bool bRadio; - int iGroupId; - std::string strGroupName; - std::vector members; -}; - -struct PVRIptvEpgGenre -{ - int iGenreType; - int iGenreSubType; - std::string strGenre; -}; +#include class PVRIptvData : public P8PLATFORM::CThread { public: - PVRIptvData(void); - ~PVRIptvData(void); + PVRIptvData(); + ~PVRIptvData(); - int GetChannelsAmount(void); + bool Start(); + int GetChannelsAmount(); PVR_ERROR GetChannels(ADDON_HANDLE handle, bool bRadio); - bool GetChannel(const PVR_CHANNEL& channel, PVRIptvChannel& myChannel); - int GetChannelGroupsAmount(void); + bool GetChannel(const PVR_CHANNEL& channel, iptvsimple::data::Channel& myChannel); + int GetChannelGroupsAmount(); PVR_ERROR GetChannelGroups(ADDON_HANDLE handle, bool bRadio); PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group); PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t iStart, time_t iEnd); - void ReaplyChannelsLogos(const char* strNewPath); - void ReloadPlayList(const char* strNewPath); - void ReloadEPG(const char* strNewPath); - -protected: - bool LoadPlayList(void); - bool LoadEPG(time_t iStart, time_t iEnd); - bool LoadGenres(void); - int GetFileContents(const std::string& url, std::string& strContent); - const PVRIptvChannel* FindChannel(const std::string& strId, const std::string& strName) const; - const PVRIptvChannelGroup* FindGroup(const std::string& strName) const; - PVRIptvEpgChannel* FindEpg(const std::string& strId); - const PVRIptvEpgChannel* FindEpgForChannel(const PVRIptvChannel& channel) const; - bool FindEpgGenre(const std::string& strGenre, int& iType, int& iSubType); - bool GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes); - int GetCachedFileContents(const std::string& strCachedName, const std::string& strFilePath, - std::string& strContent, const bool bUseCache = false); - void ApplyChannelsLogos(); - void ApplyChannelsLogosFromEPG(); - std::string ReadMarkerValue(const std::string& strLine, const char* strMarkerName); - int GetChannelId(const char* strChannelName, const char* strStreamUrl); + ADDON_STATUS SetSetting(const char* settingName, const void* settingValue); protected: - void* Process(void) override; + void* Process() override; private: - bool m_bTSOverride; - int m_iEPGTimeShift; - int m_iLastStart; - int m_iLastEnd; - std::string m_strXMLTVUrl; - std::string m_strM3uUrl; - std::string m_strLogoPath; - std::vector m_groups; - std::vector m_channels; - std::vector m_epg; - std::vector m_genres; + static const int PROCESS_LOOP_WAIT_SECS = 2; + + iptvsimple::Channels m_channels; + iptvsimple::ChannelGroups m_channelGroups{m_channels}; + iptvsimple::PlaylistLoader m_playlistLoader{m_channels, m_channelGroups}; + iptvsimple::Epg m_epg{m_channels}; + P8PLATFORM::CMutex m_mutex; -}; + std::atomic_bool m_reloadChannelsGroupsAndEPG{false}; +}; \ No newline at end of file diff --git a/src/client.cpp b/src/client.cpp index 539f253c9..26ed2ca26 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -25,10 +25,16 @@ #include "client.h" #include "PVRIptvData.h" -#include "p8-platform/util/util.h" +#include "iptvsimple/Settings.h" +#include "iptvsimple/data/Channel.h" +#include "iptvsimple/utilities/Logger.h" #include "kodi/xbmc_pvr_dll.h" +#include "p8-platform/util/util.h" using namespace ADDON; +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; #ifdef TARGET_WINDOWS #define snprintf _snprintf @@ -40,137 +46,22 @@ using namespace ADDON; #endif #endif -bool m_bCreated = false; -ADDON_STATUS m_CurStatus = ADDON_STATUS_UNKNOWN; +bool m_created = false; +ADDON_STATUS m_currentStatus = ADDON_STATUS_UNKNOWN; PVRIptvData* m_data = nullptr; -PVRIptvChannel m_currentChannel; +Channel m_currentChannel; +Settings& settings = Settings::GetInstance(); /* User adjustable settings are saved here. * Default values are defined inside client.h * and exported to the other source files. */ -std::string g_strUserPath = ""; -std::string g_strClientPath = ""; CHelper_libXBMC_addon* XBMC = nullptr; CHelper_libXBMC_pvr* PVR = nullptr; -std::string g_strTvgPath = ""; -std::string g_strM3UPath = ""; -std::string g_strLogoPath = ""; -int g_iEPGTimeShift = 0; -int g_iStartNumber = 1; -bool g_bTSOverride = true; -bool g_bCacheM3U = false; -bool g_bCacheEPG = false; -int g_iEPGLogos = 0; - -extern std::string PathCombine(const std::string& strPath, const std::string& strFileName) -{ - std::string strResult = strPath; - if (strResult.at(strResult.size() - 1) == '\\' || - strResult.at(strResult.size() - 1) == '/') - { - strResult.append(strFileName); - } - else - { - strResult.append("/"); - strResult.append(strFileName); - } - - return strResult; -} - -extern std::string GetClientFilePath(const std::string& strFileName) -{ - return PathCombine(g_strClientPath, strFileName); -} - -extern std::string GetUserFilePath(const std::string& strFileName) -{ - return PathCombine(g_strUserPath, strFileName); -} - extern "C" { -void ADDON_ReadSettings(void) -{ - char buffer[1024]; - int iPathType = 0; - if (!XBMC->GetSetting("m3uPathType", &iPathType)) - { - iPathType = 1; - } - if (iPathType) - { - if (XBMC->GetSetting("m3uUrl", &buffer)) - { - g_strM3UPath = buffer; - } - if (!XBMC->GetSetting("m3uCache", &g_bCacheM3U)) - { - g_bCacheM3U = true; - } - } - else - { - if (XBMC->GetSetting("m3uPath", &buffer)) - { - g_strM3UPath = buffer; - } - g_bCacheM3U = false; - } - if (!XBMC->GetSetting("startNum", &g_iStartNumber)) - { - g_iStartNumber = 1; - } - if (!XBMC->GetSetting("epgPathType", &iPathType)) - { - iPathType = 1; - } - if (iPathType) - { - if (XBMC->GetSetting("epgUrl", &buffer)) - { - g_strTvgPath = buffer; - } - if (!XBMC->GetSetting("epgCache", &g_bCacheEPG)) - { - g_bCacheEPG = true; - } - } - else - { - if (XBMC->GetSetting("epgPath", &buffer)) - { - g_strTvgPath = buffer; - } - g_bCacheEPG = false; - } - float fShift; - if (XBMC->GetSetting("epgTimeShift", &fShift)) - { - g_iEPGTimeShift = (int)(fShift * 3600.0); // hours to seconds - } - if (!XBMC->GetSetting("epgTSOverride", &g_bTSOverride)) - { - g_bTSOverride = true; - } - if (!XBMC->GetSetting("logoPathType", &iPathType)) - { - iPathType = 1; - } - if (XBMC->GetSetting(iPathType ? "logoBaseUrl" : "logoPath", &buffer)) - { - g_strLogoPath = buffer; - } - - // Logos from EPG - if (!XBMC->GetSetting("logoFromEpg", &g_iEPGLogos)) - g_iEPGLogos = 0; -} - ADDON_STATUS ADDON_Create(void* hdl, void* props) { if (!hdl || !props) @@ -195,55 +86,77 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) return ADDON_STATUS_PERMANENT_FAILURE; } - XBMC->Log(LOG_DEBUG, "%s - Creating the PVR IPTV Simple add-on", __FUNCTION__); + /* Configure the logger */ + Logger::GetInstance().SetImplementation([](LogLevel level, const char* message) + { + /* Convert the log level */ + addon_log_t addonLevel; - m_CurStatus = ADDON_STATUS_UNKNOWN; - g_strUserPath = pvrprops->strUserPath; - g_strClientPath = pvrprops->strClientPath; + switch (level) + { + case LogLevel::LEVEL_ERROR: + addonLevel = addon_log_t::LOG_ERROR; + break; + case LogLevel::LEVEL_INFO: + addonLevel = addon_log_t::LOG_INFO; + break; + case LogLevel::LEVEL_NOTICE: + addonLevel = addon_log_t::LOG_NOTICE; + break; + default: + addonLevel = addon_log_t::LOG_DEBUG; + } - if (!XBMC->DirectoryExists(g_strUserPath.c_str())) - { - XBMC->CreateDirectory(g_strUserPath.c_str()); - } + XBMC->Log(addonLevel, "%s", message); + }); + + Logger::GetInstance().SetPrefix("pvr.iptvsimple"); + + Logger::Log(LogLevel::LEVEL_INFO, "%s Creating the PVR IPTV Simple add-on", __FUNCTION__); - ADDON_ReadSettings(); + m_currentStatus = ADDON_STATUS_UNKNOWN; + const std::string userPath = pvrprops->strUserPath; + const std::string clientPath = pvrprops->strClientPath; + + if (!XBMC->DirectoryExists(settings.GetUserPath().c_str())) + XBMC->CreateDirectory(settings.GetUserPath().c_str()); + + settings.ReadFromAddon(userPath, clientPath); m_data = new PVRIptvData; - m_CurStatus = ADDON_STATUS_OK; - m_bCreated = true; + if (!m_data->Start()) + { + SAFE_DELETE(m_data); + SAFE_DELETE(PVR); + SAFE_DELETE(XBMC); + m_currentStatus = ADDON_STATUS_LOST_CONNECTION; + return m_currentStatus; + } + + m_currentStatus = ADDON_STATUS_OK; + m_created = true; - return m_CurStatus; + return m_currentStatus; } ADDON_STATUS ADDON_GetStatus() { - return m_CurStatus; + return m_currentStatus; } void ADDON_Destroy() { delete m_data; - m_bCreated = false; - m_CurStatus = ADDON_STATUS_UNKNOWN; + m_created = false; + m_currentStatus = ADDON_STATUS_UNKNOWN; } ADDON_STATUS ADDON_SetSetting(const char* settingName, const void* settingValue) { - // reset cache and restart addon - - std::string strFile = GetUserFilePath(M3U_FILE_NAME); - if (XBMC->FileExists(strFile.c_str(), false)) - { - XBMC->DeleteFile(strFile.c_str()); - } - - strFile = GetUserFilePath(TVG_FILE_NAME); - if (XBMC->FileExists(strFile.c_str(), false)) - { - XBMC->DeleteFile(strFile.c_str()); - } + if (!XBMC || !m_data) + return ADDON_STATUS_OK; - return ADDON_STATUS_NEED_RESTART; + return m_data->SetSetting(settingName, settingValue); } /*********************************************************** @@ -274,20 +187,20 @@ PVR_ERROR GetAddonCapabilities(PVR_ADDON_CAPABILITIES* pCapabilities) const char* GetBackendName(void) { - static const char* strBackendName = "IPTV Simple PVR Add-on"; - return strBackendName; + static const char* backendName = "IPTV Simple PVR Add-on"; + return backendName; } const char* GetBackendVersion(void) { - static std::string strBackendVersion = STR(IPTV_VERSION); - return strBackendVersion.c_str(); + static std::string backendVersion = STR(IPTV_VERSION); + return backendVersion.c_str(); } const char* GetConnectionString(void) { - static std::string strConnectionString = "connected"; - return strConnectionString.c_str(); + static std::string connectionString = "connected"; + return connectionString.c_str(); } const char* GetBackendHostname(void) @@ -337,11 +250,11 @@ PVR_ERROR GetChannelStreamProperties(const PVR_CHANNEL* channel, PVR_NAMED_VALUE if (m_data && m_data->GetChannel(*channel, m_currentChannel)) { strncpy(properties[0].strName, PVR_STREAM_PROPERTY_STREAMURL, sizeof(properties[0].strName) - 1); - strncpy(properties[0].strValue, m_currentChannel.strStreamURL.c_str(), sizeof(properties[0].strValue) - 1); + strncpy(properties[0].strValue, m_currentChannel.GetStreamURL().c_str(), sizeof(properties[0].strValue) - 1); *iPropertiesCount = 1; - if (!m_currentChannel.properties.empty()) + if (!m_currentChannel.GetProperties().empty()) { - for (auto& prop : m_currentChannel.properties) + for (auto& prop : m_currentChannel.GetProperties()) { strncpy(properties[*iPropertiesCount].strName, prop.first.c_str(), sizeof(properties[*iPropertiesCount].strName) - 1); strncpy(properties[*iPropertiesCount].strValue, prop.second.c_str(), sizeof(properties[*iPropertiesCount].strName) - 1); diff --git a/src/client.h b/src/client.h index ea2fdf770..fcd65dd61 100644 --- a/src/client.h +++ b/src/client.h @@ -26,31 +26,5 @@ #include "kodi/libXBMC_addon.h" #include "kodi/libXBMC_pvr.h" -#define M3U_FILE_NAME "iptv.m3u.cache" -#define TVG_FILE_NAME "xmltv.xml.cache" - -/*! - * @brief PVR macros for string exchange - */ -#define PVR_STRCPY(dest, source) do { strncpy(dest, source, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0'; } while (0) -#define PVR_STRCLR(dest) memset(dest, 0, sizeof(dest)) - -extern bool m_bCreated; -extern std::string g_strUserPath; -extern std::string g_strClientPath; extern ADDON::CHelper_libXBMC_addon* XBMC; -extern CHelper_libXBMC_pvr* PVR; - -extern std::string g_strM3UPath; -extern std::string g_strTvgPath; -extern std::string g_strLogoPath; -extern int g_iEPGTimeShift; -extern int g_iStartNumber; -extern bool g_bTSOverride; -extern bool g_bCacheM3U; -extern bool g_bCacheEPG; -extern int g_iEPGLogos; - -extern std::string PathCombine(const std::string& strPath, const std::string& strFileName); -extern std::string GetClientFilePath(const std::string& strFileName); -extern std::string GetUserFilePath(const std::string& strFileName); +extern CHelper_libXBMC_pvr* PVR; \ No newline at end of file diff --git a/src/iptvsimple/ChannelGroups.cpp b/src/iptvsimple/ChannelGroups.cpp new file mode 100644 index 000000000..32355c894 --- /dev/null +++ b/src/iptvsimple/ChannelGroups.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "ChannelGroups.h" + +#include "../client.h" +#include "utilities/Logger.h" + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; + +ChannelGroups::ChannelGroups(const Channels& channels) : m_channels(channels) {} + +void ChannelGroups::Clear() +{ + m_channelGroups.clear(); +} + +int ChannelGroups::GetChannelGroupsAmount() const +{ + return m_channelGroups.size(); +} + +void ChannelGroups::GetChannelGroups(std::vector& kodiChannelGroups, bool radio) const +{ + Logger::Log(LEVEL_DEBUG, "%s - Starting to get ChannelGroups for PVR", __FUNCTION__); + + for (const auto& channelGroup : m_channelGroups) + { + Logger::Log(LEVEL_DEBUG, "%s - Transfer channelGroup '%s', ChannelGroupIndex '%d'", __FUNCTION__, channelGroup.GetGroupName().c_str(), channelGroup.GetUniqueId()); + + if (channelGroup.IsRadio() == radio) + { + PVR_CHANNEL_GROUP kodiChannelGroup = {0}; + + channelGroup.UpdateTo(kodiChannelGroup); + + kodiChannelGroups.emplace_back(kodiChannelGroup); + } + } + + Logger::Log(LEVEL_DEBUG, "%s - Finished getting ChannelGroups for PVR", __FUNCTION__); +} + +PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group) +{ + const ChannelGroup* myGroup = FindChannelGroup(group.strGroupName); + if (myGroup) + { + for (int memberId : myGroup->GetMemberChannelIndexes()) + { + if (memberId < 0 || memberId >= static_cast(m_channels.GetChannelsAmount())) + continue; + + const Channel& channel = m_channels.GetChannelsList().at(memberId); + PVR_CHANNEL_GROUP_MEMBER xbmcGroupMember = {0}; + + strncpy(xbmcGroupMember.strGroupName, group.strGroupName, sizeof(xbmcGroupMember.strGroupName) - 1); + xbmcGroupMember.iChannelUniqueId = channel.GetUniqueId(); + xbmcGroupMember.iChannelNumber = channel.GetChannelNumber(); + + PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); + } + } + + return PVR_ERROR_NO_ERROR; +} + +int ChannelGroups::AddChannelGroup(iptvsimple::data::ChannelGroup& channelGroup) +{ + const ChannelGroup* existingChannelGroup = FindChannelGroup(channelGroup.GetGroupName()); + + if (!existingChannelGroup) + { + channelGroup.SetUniqueId(m_channelGroups.size() + 1); + + m_channelGroups.emplace_back(channelGroup); + + Logger::Log(LEVEL_DEBUG, "%s - Added group: %s, with uniqueId: %d", __FUNCTION__, channelGroup.GetGroupName().c_str(), channelGroup.GetUniqueId()); + + return channelGroup.GetUniqueId(); + } + + Logger::Log(LEVEL_DEBUG, "%s - Did not add group: %s, as it already exists with uniqueId: %d", __FUNCTION__, existingChannelGroup->GetGroupName().c_str(), existingChannelGroup->GetUniqueId()); + + return existingChannelGroup->GetUniqueId(); +} + +ChannelGroup* ChannelGroups::GetChannelGroup(int uniqueId) +{ + for (auto& myChannelGroup : m_channelGroups) + { + if (myChannelGroup.GetUniqueId() == uniqueId) + return &myChannelGroup; + } + + return nullptr; +} + +ChannelGroup* ChannelGroups::FindChannelGroup(const std::string& name) +{ + for (auto& myGroup : m_channelGroups) + { + if (myGroup.GetGroupName() == name) + return &myGroup; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/iptvsimple/ChannelGroups.h b/src/iptvsimple/ChannelGroups.h new file mode 100644 index 000000000..365b9b62d --- /dev/null +++ b/src/iptvsimple/ChannelGroups.h @@ -0,0 +1,53 @@ +#pragma once +/* + * Copyright (C) 2005-2015 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "Channels.h" +#include "data/ChannelGroup.h" + +#include +#include + +namespace iptvsimple +{ + class ChannelGroups + { + public: + ChannelGroups(const iptvsimple::Channels& channels); + + int GetChannelGroupsAmount() const; + void GetChannelGroups(std::vector& kodiChannelGroups, bool radio) const; + PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group); + + int AddChannelGroup(iptvsimple::data::ChannelGroup& channelGroup); + iptvsimple::data::ChannelGroup* GetChannelGroup(int uniqueId); + iptvsimple::data::ChannelGroup* FindChannelGroup(const std::string& name); + const std::vector& GetChannelGroupsList() const { return m_channelGroups; } + void Clear(); + + private: + const iptvsimple::Channels& m_channels; + std::vector m_channelGroups; + }; +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/Channels.cpp b/src/iptvsimple/Channels.cpp new file mode 100644 index 000000000..0c9ad0044 --- /dev/null +++ b/src/iptvsimple/Channels.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Channels.h" + +#include "../client.h" +#include "ChannelGroups.h" +#include "Settings.h" +#include "utilities/FileUtils.h" +#include "utilities/Logger.h" + +#include + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; + +Channels::Channels() + : m_logoLocation(Settings::GetInstance().GetLogoLocation()), + m_currentChannelNumber(Settings::GetInstance().GetStartChannelNumber()) {} + +void Channels::Clear() +{ + m_channels.clear(); + m_logoLocation = Settings::GetInstance().GetLogoLocation(); + m_currentChannelNumber = Settings::GetInstance().GetStartChannelNumber(); +} + +int Channels::GetChannelsAmount() const +{ + return m_channels.size(); +} + +void Channels::GetChannels(std::vector& kodiChannels, bool radio) const +{ + for (const auto& channel : m_channels) + { + if (channel.IsRadio() == radio) + { + Logger::Log(LEVEL_DEBUG, "%s - Transfer channel '%s', ChannelIndex '%d'", __FUNCTION__, channel.GetChannelName().c_str(), + channel.GetUniqueId()); + PVR_CHANNEL kodiChannel = {0}; + + channel.UpdateTo(kodiChannel); + + kodiChannels.emplace_back(kodiChannel); + } + } +} + +bool Channels::GetChannel(const PVR_CHANNEL& channel, Channel& myChannel) +{ + for (const auto& thisChannel : m_channels) + { + if (thisChannel.GetUniqueId() == static_cast(channel.iUniqueId)) + { + thisChannel.UpdateTo(myChannel); + + return true; + } + } + + return false; +} + +void Channels::AddChannel(Channel& channel, std::vector& groupIdList, ChannelGroups& channelGroups) +{ + m_currentChannelNumber = channel.GetChannelNumber(); + channel.SetUniqueId(GenerateChannelId(channel.GetChannelName().c_str(), channel.GetStreamURL().c_str())); + + for (int myGroupId : groupIdList) + { + channel.SetRadio(channelGroups.GetChannelGroup(myGroupId)->IsRadio()); + channelGroups.GetChannelGroup(myGroupId)->AddMemberChannelIndex(m_channels.size()); + } + + m_channels.emplace_back(channel); + + m_currentChannelNumber++; +} + +Channel* Channels::GetChannel(int uniqueId) +{ + for (auto& myChannel : m_channels) + { + if (myChannel.GetUniqueId() == uniqueId) + return &myChannel; + } + + return nullptr; +} + +const Channel* Channels::FindChannel(const std::string& id, const std::string& name) const +{ + const std::string tvgName = std::regex_replace(name, std::regex(" "), "_"); + + for (const auto& myChannel : m_channels) + { + if (myChannel.GetTvgId() == id) + return &myChannel; + + if (tvgName.empty()) + continue; + + if (myChannel.GetTvgName() == tvgName) + return &myChannel; + + if (myChannel.GetChannelName() == name) + return &myChannel; + } + + return nullptr; +} + +void Channels::ApplyChannelLogos() +{ + for (auto& channel : m_channels) + { + if (!channel.GetTvgLogo().empty()) + { + if (!m_logoLocation.empty() && channel.GetTvgLogo().find("://") == std::string::npos) // special proto + channel.SetLogoPath(FileUtils::PathCombine(m_logoLocation, channel.GetTvgLogo())); + else + channel.SetLogoPath(channel.GetTvgLogo()); + } + } +} + +int Channels::GenerateChannelId(const char* channelName, const char* streamUrl) +{ + std::string concat(channelName); + concat.append(streamUrl); + + const char* calcString = concat.c_str(); + int iId = 0; + int c; + while ((c = *calcString++)) + iId = ((iId << 5) + iId) + c; /* iId * 33 + c */ + + return abs(iId); +} \ No newline at end of file diff --git a/src/iptvsimple/Channels.h b/src/iptvsimple/Channels.h new file mode 100644 index 000000000..fb8dd5d9e --- /dev/null +++ b/src/iptvsimple/Channels.h @@ -0,0 +1,66 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "data/Channel.h" + +#include +#include + +namespace iptvsimple +{ + class ChannelGroups; + + namespace data + { + class ChannelGroup; + } + + class Channels + { + public: + Channels(); + + int GetChannelsAmount() const; + void GetChannels(std::vector& kodiChannels, bool radio) const; + bool GetChannel(const PVR_CHANNEL& channel, iptvsimple::data::Channel& myChannel); + + void AddChannel(iptvsimple::data::Channel& channel, std::vector& groupIdList, iptvsimple::ChannelGroups& channelGroups); + iptvsimple::data::Channel* GetChannel(int uniqueId); + const iptvsimple::data::Channel* FindChannel(const std::string& id, const std::string& name) const; + const std::vector& GetChannelsList() const { return m_channels; } + void Clear(); + void ApplyChannelLogos(); + + int GetCurrentChannelNumber() const { return m_currentChannelNumber; } + + private: + int GenerateChannelId(const char* channelName, const char* streamUrl); + + std::string m_logoLocation; + int m_currentChannelNumber; + + std::vector m_channels; + }; +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp new file mode 100644 index 000000000..7a92a7458 --- /dev/null +++ b/src/iptvsimple/Epg.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Epg.h" + +#include "Settings.h" +#include "../client.h" +#include "utilities/FileUtils.h" +#include "utilities/Logger.h" +#include "utilities/XMLUtils.h" + +#include "p8-platform/util/StringUtils.h" +#include "rapidxml/rapidxml.hpp" + +#include +#include +#include + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; +using namespace rapidxml; + +Epg::Epg(Channels& channels) + : m_channels(channels), m_xmltvLocation(Settings::GetInstance().GetEpgLocation()), m_epgTimeShift(Settings::GetInstance().GetEpgTimeshiftSecs()), + m_tsOverride(Settings::GetInstance().GetTsOverride()), m_lastStart(0), m_lastEnd(0) {} + +void Epg::Clear() +{ + m_channelEpgs.clear(); + m_genres.clear(); +} + +bool Epg::LoadEPG(time_t start, time_t end) +{ + if (m_xmltvLocation.empty()) + { + Logger::Log(LEVEL_NOTICE, "EPG file path is not configured. EPG not loaded."); + return false; + } + + std::string data; + + if (GetXMLTVFileWithRetries(data)) + { + char* buffer = FillBufferFromXMLTVData(data); + + if (!buffer) + return false; + + xml_document<> xmlDoc; + try + { + xmlDoc.parse<0>(buffer); + } + catch (parse_error p) + { + Logger::Log(LEVEL_ERROR, "Unable parse EPG XML: %s", p.what()); + return false; + } + + xml_node<>* rootElement = xmlDoc.first_node("tv"); + if (!rootElement) + { + Logger::Log(LEVEL_ERROR, "Invalid EPG XML: no tag found"); + return false; + } + + if (!LoadChannelEpgs(rootElement)) + return false; + + LoadEpgEntries(rootElement, start, end); + + xmlDoc.clear(); + } + else + { + return false; + } + + LoadGenres(); + + Logger::Log(LEVEL_NOTICE, "EPG Loaded."); + + if (Settings::GetInstance().GetEpgLogosMode() != EpgLogosMode::IGNORE_XMLTV) + ApplyChannelsLogosFromEPG(); + + return true; +} + +bool Epg::GetXMLTVFileWithRetries(std::string& data) +{ + int bytesRead = 0; + int count = 0; + + while (count < 3) // max 3 tries + { + if ((bytesRead = FileUtils::GetCachedFileContents(TVG_FILE_NAME, m_xmltvLocation, data, Settings::GetInstance().UseEPGCache())) != 0) + break; + + Logger::Log(LEVEL_ERROR, "Unable to load EPG file '%s': file is missing or empty. :%dth try.", m_xmltvLocation.c_str(), ++count); + + if (count < 3) + std::this_thread::sleep_for(std::chrono::microseconds(2 * 1000 * 1000)); // sleep 2 sec before next try. + } + + if (bytesRead == 0) + { + Logger::Log(LEVEL_ERROR, "Unable to load EPG file '%s': file is missing or empty. After %d tries.", m_xmltvLocation.c_str(), count); + return false; + } + + return true; +} + +char* Epg::FillBufferFromXMLTVData(std::string& data) +{ + std::string decompressed; + char* buffer = nullptr; + + // gzip packed + if (data[0] == '\x1F' && data[1] == '\x8B' && data[2] == '\x08') + { + if (!FileUtils::GzipInflate(data, decompressed)) + { + Logger::Log(LEVEL_ERROR, "Invalid EPG file '%s': unable to decompress file.", m_xmltvLocation.c_str()); + return nullptr; + } + buffer = &(decompressed[0]); + } + else + { + buffer = &(data[0]); + } + + XmltvFileFormat fileFormat = GetXMLTVFileFormat(buffer); + + if (fileFormat == XmltvFileFormat::INVALID) + { + Logger::Log(LEVEL_ERROR, "Invalid EPG file '%s': unable to parse file.", m_xmltvLocation.c_str()); + return nullptr; + } + + if (fileFormat == XmltvFileFormat::TAR_ARCHIVE) + buffer += 0x200; // RECORDSIZE = 512 + + return buffer; +} + +const XmltvFileFormat Epg::GetXMLTVFileFormat(const char* buffer) +{ + if (!buffer) + return XmltvFileFormat::INVALID; + + // xml should starts with '* rootElement) +{ + if (!rootElement) + return false; + + m_channelEpgs.clear(); + + xml_node<>* channelNode = nullptr; + for (channelNode = rootElement->first_node("channel"); channelNode; channelNode = channelNode->next_sibling("channel")) + { + ChannelEpg channelEpg; + + if (channelEpg.UpdateFrom(channelNode, m_channels)) + m_channelEpgs.emplace_back(channelEpg); + } + + if (m_channelEpgs.size() == 0) + { + Logger::Log(LEVEL_ERROR, "EPG channels not found."); + return false; + } + + return true; +} + +void Epg::LoadEpgEntries(xml_node<>* rootElement, int start, int end) +{ + int minShiftTime = m_epgTimeShift; + int maxShiftTime = m_epgTimeShift; + if (!m_tsOverride) + { + minShiftTime = SECONDS_IN_DAY; + maxShiftTime = -SECONDS_IN_DAY; + + for (const auto& channel : m_channels.GetChannelsList()) + { + if (channel.GetTvgShift() + m_epgTimeShift < minShiftTime) + minShiftTime = channel.GetTvgShift() + m_epgTimeShift; + if (channel.GetTvgShift() + m_epgTimeShift > maxShiftTime) + maxShiftTime = channel.GetTvgShift() + m_epgTimeShift; + } + } + + ChannelEpg* channelEpg = nullptr; + int broadcastId = 0; + + for (xml_node<>* channelNode = rootElement->first_node("programme"); channelNode; channelNode = channelNode->next_sibling("programme")) + { + std::string id; + if (!GetAttributeValue(channelNode, "channel", id)) + continue; + + if (!channelEpg || StringUtils::CompareNoCase(channelEpg->GetId(), id) != 0) + { + if (!(channelEpg = FindEpgForChannel(id))) + continue; + } + + EpgEntry entry; + if (entry.UpdateFrom(channelNode, id, broadcastId + 1, start, end, minShiftTime, maxShiftTime)) + { + broadcastId++; + + channelEpg->AddEpgEntry(entry); + } + } +} + + +void Epg::ReloadEPG() +{ + m_xmltvLocation = Settings::GetInstance().GetEpgLocation(); + m_epgTimeShift = Settings::GetInstance().GetEpgTimeshiftSecs(); + m_tsOverride = Settings::GetInstance().GetTsOverride(); + m_lastStart = 0; + m_lastEnd = 0; + + Clear(); + + if (LoadEPG(m_lastStart, m_lastEnd)) + { + for (const auto& myChannel : m_channels.GetChannelsList()) + PVR->TriggerEpgUpdate(myChannel.GetUniqueId()); + } +} + +PVR_ERROR Epg::GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end) +{ + for (const auto& myChannel : m_channels.GetChannelsList()) + { + if (myChannel.GetUniqueId() != iChannelUid) + continue; + + if (start > m_lastStart || end > m_lastEnd) + { + // reload EPG for new time interval only + LoadEPG(start, end); + { + // doesn't matter is epg loaded or not we shouldn't try to load it for same interval + m_lastStart = static_cast(start); + m_lastEnd = static_cast(end); + } + } + + ChannelEpg* channelEpg = FindEpgForChannel(myChannel); + if (!channelEpg || channelEpg->GetEpgEntries().size() == 0) + return PVR_ERROR_NO_ERROR; + + int shift = m_tsOverride ? m_epgTimeShift : myChannel.GetTvgShift() + m_epgTimeShift; + + for (auto& epgEntry : channelEpg->GetEpgEntries()) + { + if ((epgEntry.GetEndTime() + shift) < start) + continue; + + EPG_TAG tag = {0}; + + epgEntry.UpdateTo(tag, iChannelUid, shift, m_genres); + + PVR->TransferEpgEntry(handle, &tag); + + if ((epgEntry.GetStartTime() + shift) > end) + break; + } + + return PVR_ERROR_NO_ERROR; + } + + return PVR_ERROR_NO_ERROR; +} + +ChannelEpg* Epg::FindEpgForChannel(const std::string& id) +{ + for (auto& myChannelEpg : m_channelEpgs) + { + if (StringUtils::CompareNoCase(myChannelEpg.GetId(), id) == 0) + return &myChannelEpg; + } + + return nullptr; +} + +ChannelEpg* Epg::FindEpgForChannel(const Channel& channel) +{ + for (auto& myChannelEpg : m_channelEpgs) + { + if (myChannelEpg.GetId() == channel.GetTvgId()) + return &myChannelEpg; + + const std::string name = std::regex_replace(myChannelEpg.GetName(), std::regex(" "), "_"); + if (name == channel.GetTvgName() || myChannelEpg.GetName() == channel.GetTvgName()) + return &myChannelEpg; + + if (myChannelEpg.GetName() == channel.GetChannelName()) + return &myChannelEpg; + } + + return nullptr; +} + +void Epg::ApplyChannelsLogosFromEPG() +{ + bool updated = false; + + for (const auto& channel : m_channels.GetChannelsList()) + { + const ChannelEpg* channelEpg = FindEpgForChannel(channel); + if (!channelEpg || channelEpg->GetIcon().empty()) + continue; + + // 1 - prefer logo from playlist + if (!channel.GetLogoPath().empty() && Settings::GetInstance().GetEpgLogosMode() == EpgLogosMode::PREFER_M3U) + continue; + + // 2 - prefer logo from epg + if (!channelEpg->GetIcon().empty() && Settings::GetInstance().GetEpgLogosMode() == EpgLogosMode::PREFER_XMLTV) + { + m_channels.GetChannel(channel.GetUniqueId())->SetLogoPath(channelEpg->GetIcon()); + updated = true; + } + } + + if (updated) + PVR->TriggerChannelUpdate(); +} + +bool Epg::LoadGenres() +{ + // try to load genres from userdata folder + std::string filePath = FileUtils::GetUserFilePath(GENRES_MAP_FILENAME); + if (!XBMC->FileExists(filePath.c_str(), false)) + { + // try to load file from addom folder + filePath = FileUtils::GetClientFilePath(GENRES_MAP_FILENAME); + if (!XBMC->FileExists(filePath.c_str(), false)) + return false; + } + + std::string data; + FileUtils::GetFileContents(filePath, data); + + if (data.empty()) + return false; + + m_genres.clear(); + + char* buffer = &(data[0]); + xml_document<> xmlDoc; + try + { + xmlDoc.parse<0>(buffer); + } + catch (parse_error p) + { + return false; + } + + xml_node<>* pRootElement = xmlDoc.first_node("genres"); + if (!pRootElement) + return false; + + for (xml_node<>* pGenreNode = pRootElement->first_node("genre"); pGenreNode; pGenreNode = pGenreNode->next_sibling("genre")) + { + EpgGenre genre; + + if (genre.UpdateFrom(pGenreNode)) + m_genres.emplace_back(genre); + } + + xmlDoc.clear(); + return true; +} \ No newline at end of file diff --git a/src/iptvsimple/Epg.h b/src/iptvsimple/Epg.h new file mode 100644 index 000000000..df4925845 --- /dev/null +++ b/src/iptvsimple/Epg.h @@ -0,0 +1,78 @@ +#pragma once +/* + * Copyright (C) 2005-2015 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "Channels.h" +#include "data/ChannelEpg.h" +#include "data/EpgGenre.h" + +#include +#include + +namespace iptvsimple +{ + static const int SECONDS_IN_DAY = 86400; + static const std::string GENRES_MAP_FILENAME = "genres.xml"; + + enum class XmltvFileFormat + { + NORMAL, + TAR_ARCHIVE, + INVALID + }; + + class Epg + { + public: + Epg(iptvsimple::Channels& channels); + + PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end); + void Clear(); + void ReloadEPG(); + + private: + static const XmltvFileFormat GetXMLTVFileFormat(const char* buffer); + + bool LoadEPG(time_t iStart, time_t iEnd); + bool GetXMLTVFileWithRetries(std::string& data); + char* FillBufferFromXMLTVData(std::string& data); + bool LoadChannelEpgs(rapidxml::xml_node<>* rootElement); + void LoadEpgEntries(rapidxml::xml_node<>* rootElement, int start, int end); + bool LoadGenres(); + + data::ChannelEpg* FindEpgForChannel(const std::string& id); + data::ChannelEpg* FindEpgForChannel(const data::Channel& channel); + void ApplyChannelsLogosFromEPG(); + + std::string m_xmltvLocation; + int m_epgTimeShift; + bool m_tsOverride; + int m_lastStart; + int m_lastEnd; + + iptvsimple::Channels& m_channels; + std::vector m_channelEpgs; + std::vector m_genres; + }; +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp new file mode 100644 index 000000000..34588f3a0 --- /dev/null +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "PlaylistLoader.h" + +#include "Settings.h" +#include "../client.h" +#include "utilities/FileUtils.h" +#include "utilities/Logger.h" + +#include "p8-platform/util/StringUtils.h" + +#include +#include +#include +#include +#include + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace iptvsimple::utilities; + +PlaylistLoader::PlaylistLoader(Channels& channels, ChannelGroups& channelGroups) + : m_channels(channels), m_channelGroups(channelGroups), m_m3uLocation(Settings::GetInstance().GetM3ULocation()) {} + +bool PlaylistLoader::LoadPlayList() +{ + if (m_m3uLocation.empty()) + { + Logger::Log(LEVEL_NOTICE, "Playlist file path is not configured. Channels not loaded."); + return false; + } + + std::string playlistContent; + if (!FileUtils::GetCachedFileContents(M3U_FILE_NAME, m_m3uLocation, playlistContent, Settings::GetInstance().UseM3UCache())) + { + Logger::Log(LEVEL_ERROR, "Unable to load playlist file '%s': file is missing or empty.", m_m3uLocation.c_str()); + return false; + } + + std::stringstream stream(playlistContent); + + /* load channels */ + bool isFirstLine = true; + bool isRealTime = true; + int epgTimeShift = 0; + std::vector currentChannelGroupIdList; + + Channel tmpChannel; + + std::string line; + while (std::getline(stream, line)) + { + line = StringUtils::TrimRight(line, " \t\r\n"); + line = StringUtils::TrimLeft(line, " \t"); + + Logger::Log(LEVEL_DEBUG, "Read line: '%s'", line.c_str()); + + if (line.empty()) + continue; + + if (isFirstLine) + { + isFirstLine = false; + + if (StringUtils::Left(line, 3) == "\xEF\xBB\xBF") + line.erase(0, 3); + + if (StringUtils::StartsWith(line, M3U_START_MARKER)) //#EXTM3U + { + double tvgShiftDecimal = std::atof(ReadMarkerValue(line, TVG_INFO_SHIFT_MARKER).c_str()); + epgTimeShift = static_cast(tvgShiftDecimal * 3600.0); + continue; + } + else + { + Logger::Log(LEVEL_ERROR, "URL '%s' missing %s descriptor on line 1, attempting to parse it anyway.", + m_m3uLocation.c_str(), M3U_START_MARKER.c_str()); + } + } + + if (StringUtils::StartsWith(line, M3U_INFO_MARKER)) //#EXTINF + { + tmpChannel.SetChannelNumber(m_channels.GetCurrentChannelNumber()); + currentChannelGroupIdList.clear(); + + const std::string groupNamesListString = ParseIntoChannel(line, tmpChannel, currentChannelGroupIdList, epgTimeShift); + + if (!groupNamesListString.empty()) + ParseAndAddChannelGroups(groupNamesListString, currentChannelGroupIdList, tmpChannel.IsRadio()); + } + else if (StringUtils::StartsWith(line, KODIPROP_MARKER)) //#KODIPROP: + { + ParseSinglePropertyIntoChannel(line, tmpChannel, KODIPROP_MARKER); + } + else if (StringUtils::StartsWith(line, EXTVLCOPT_MARKER)) //#EXTVLCOPT: + { + ParseSinglePropertyIntoChannel(line, tmpChannel, EXTVLCOPT_MARKER); + } + else if (StringUtils::StartsWith(line, M3U_GROUP_MARKER)) //#EXTGRP: + { + const std::string groupNamesListString = ReadMarkerValue(line, M3U_GROUP_MARKER); + if (!groupNamesListString.empty()) + ParseAndAddChannelGroups(groupNamesListString, currentChannelGroupIdList, tmpChannel.IsRadio()); + } + else if (StringUtils::StartsWith(line, PLAYLIST_TYPE_MARKER)) //#EXT-X-PLAYLIST-TYPE: + { + if (ReadMarkerValue(line, PLAYLIST_TYPE_MARKER) == "VOD") + isRealTime = false; + } + else if (line[0] != '#') + { + Logger::Log(LEVEL_DEBUG, "Found URL: '%s' (current channel name: '%s')", line.c_str(), tmpChannel.GetChannelName().c_str()); + + if (isRealTime) + tmpChannel.AddProperty(PVR_STREAM_PROPERTY_ISREALTIMESTREAM, "true"); + + Channel channel(tmpChannel); + channel.SetStreamURL(line); + + m_channels.AddChannel(channel, currentChannelGroupIdList, m_channelGroups); + + tmpChannel.Reset(); + isRealTime = true; + } + } + + stream.clear(); + + if (m_channels.GetChannelsAmount() == 0) + { + Logger::Log(LEVEL_ERROR, "Unable to load channels from file '%s': file is corrupted.", m_m3uLocation.c_str()); + return false; + } + + m_channels.ApplyChannelLogos(); + + Logger::Log(LEVEL_NOTICE, "Loaded %d channels.", m_channels.GetChannelsAmount()); + return true; +} + +std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& channel, std::vector& groupIdList, int epgTimeShift) +{ + // parse line + size_t colonIndex = line.find(':'); + size_t commaIndex = line.rfind(','); + if (colonIndex != std::string::npos && commaIndex != std::string::npos && commaIndex > colonIndex) + { + // parse name + std::string channelName = line.substr(commaIndex + 1); + channelName = StringUtils::Trim(channelName); + channel.SetChannelName(XBMC->UnknownToUTF8(channelName.c_str())); + + // parse info line containng the attributes for a channel + const std::string infoLine = line.substr(colonIndex + 1, commaIndex - colonIndex - 1); + + std::string strTvgId = ReadMarkerValue(infoLine, TVG_INFO_ID_MARKER); + std::string strTvgName = ReadMarkerValue(infoLine, TVG_INFO_NAME_MARKER); + std::string strTvgLogo = ReadMarkerValue(infoLine, TVG_INFO_LOGO_MARKER); + std::string strChnlNo = ReadMarkerValue(infoLine, TVG_INFO_CHNO_MARKER); + std::string strRadio = ReadMarkerValue(infoLine, RADIO_MARKER); + std::string strTvgShift = ReadMarkerValue(infoLine, TVG_INFO_SHIFT_MARKER); + + if (strTvgId.empty()) + { + char buff[255]; + sprintf(buff, "%d", std::atoi(infoLine.c_str())); + strTvgId.append(buff); + } + + if (strTvgLogo.empty()) + strTvgLogo = channelName; + + if (!strChnlNo.empty()) + channel.SetChannelNumber(std::atoi(strChnlNo.c_str())); + + double tvgShiftDecimal = std::atof(strTvgShift.c_str()); + + bool isRadio = !StringUtils::CompareNoCase(strRadio, "true"); + channel.SetTvgId(strTvgId); + channel.SetTvgName(XBMC->UnknownToUTF8(strTvgName.c_str())); + channel.SetTvgLogo(XBMC->UnknownToUTF8(strTvgLogo.c_str())); + channel.SetTvgShift(static_cast(tvgShiftDecimal * 3600.0)); + channel.SetRadio(isRadio); + + if (strTvgShift.empty()) + channel.SetTvgShift(epgTimeShift); + + return ReadMarkerValue(infoLine, GROUP_NAME_MARKER); + } + + return ""; +} + +void PlaylistLoader::ParseAndAddChannelGroups(const std::string& groupNamesListString, std::vector& groupIdList, bool isRadio) +{ + //groupNamesListString may have a single or multiple group names seapareted by ';' + + std::stringstream streamGroups(groupNamesListString); + std::string groupName; + + while (std::getline(streamGroups, groupName, ';')) + { + groupName = XBMC->UnknownToUTF8(groupName.c_str()); + + ChannelGroup group; + group.SetGroupName(groupName); + group.SetRadio(isRadio); + + int uniqueGroupId = m_channelGroups.AddChannelGroup(group); + groupIdList.emplace_back(uniqueGroupId); + } +} + +void PlaylistLoader::ParseSinglePropertyIntoChannel(const std::string& line, Channel& channel, const std::string& markerName) +{ + const std::string value = ReadMarkerValue(line, markerName); + auto pos = value.find('='); + if (pos != std::string::npos) + { + const std::string prop = value.substr(0, pos); + const std::string propValue = value.substr(pos + 1); + channel.AddProperty(prop, propValue); + + Logger::Log(LEVEL_DEBUG, "%s - Found %s property: '%s' value: '%s'", __FUNCTION__, markerName.c_str(), prop.c_str(), propValue.c_str()); + } +} + +void PlaylistLoader::ReloadPlayList() +{ + m_m3uLocation = Settings::GetInstance().GetM3ULocation(); + + m_channels.Clear(); + m_channelGroups.Clear(); + + if (LoadPlayList()) + { + PVR->TriggerChannelUpdate(); + PVR->TriggerChannelGroupsUpdate(); + } +} + +std::string PlaylistLoader::ReadMarkerValue(const std::string& line, const std::string& markerName) +{ + size_t markerStart = line.find(markerName); + if (markerStart != std::string::npos) + { + const std::string marker = markerName; + markerStart += marker.length(); + if (markerStart < line.length()) + { + char find = ' '; + if (line[markerStart] == '"') + { + find = '"'; + markerStart++; + } + size_t markerEnd = line.find(find, markerStart); + if (markerEnd == std::string::npos) + { + markerEnd = line.length(); + } + return line.substr(markerStart, markerEnd - markerStart); + } + } + + return ""; +} \ No newline at end of file diff --git a/src/iptvsimple/PlaylistLoader.h b/src/iptvsimple/PlaylistLoader.h new file mode 100644 index 000000000..7e3c825e7 --- /dev/null +++ b/src/iptvsimple/PlaylistLoader.h @@ -0,0 +1,68 @@ +#pragma once +/* + * Copyright (C) 2005-2015 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "Channels.h" +#include "ChannelGroups.h" + +#include + +namespace iptvsimple +{ + static const std::string M3U_START_MARKER = "#EXTM3U"; + static const std::string M3U_INFO_MARKER = "#EXTINF"; + static const std::string M3U_GROUP_MARKER = "#EXTGRP:"; + static const std::string TVG_INFO_ID_MARKER = "tvg-id="; + static const std::string TVG_INFO_NAME_MARKER = "tvg-name="; + static const std::string TVG_INFO_LOGO_MARKER = "tvg-logo="; + static const std::string TVG_INFO_SHIFT_MARKER = "tvg-shift="; + static const std::string TVG_INFO_CHNO_MARKER = "tvg-chno="; + static const std::string GROUP_NAME_MARKER = "group-title="; + static const std::string KODIPROP_MARKER = "#KODIPROP:"; + static const std::string EXTVLCOPT_MARKER = "#EXTVLCOPT:"; + static const std::string RADIO_MARKER = "radio="; + static const std::string PLAYLIST_TYPE_MARKER = "#EXT-X-PLAYLIST-TYPE:"; + static const std::string CHANNEL_LOGO_EXTENSION = ".png"; + + class PlaylistLoader + { + public: + PlaylistLoader(iptvsimple::Channels& channels, iptvsimple::ChannelGroups& channelGroups); + + bool LoadPlayList(); + void ReloadPlayList(); + + private: + static std::string ReadMarkerValue(const std::string& line, const std::string& markerName); + static void ParseSinglePropertyIntoChannel(const std::string& line, iptvsimple::data::Channel& channel, const std::string& markerName); + + std::string ParseIntoChannel(const std::string& line, iptvsimple::data::Channel& channel, std::vector& groupIdList, int epgTimeShift); + void ParseAndAddChannelGroups(const std::string& groupNamesListString, std::vector& groupIdList, bool isRadio); + + std::string m_m3uLocation; + + iptvsimple::ChannelGroups& m_channelGroups; + iptvsimple::Channels& m_channels; + }; +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/Settings.cpp b/src/iptvsimple/Settings.cpp new file mode 100644 index 000000000..81726ce61 --- /dev/null +++ b/src/iptvsimple/Settings.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Settings.h" + +#include "../client.h" +#include "utilities/FileUtils.h" + +using namespace ADDON; +using namespace iptvsimple; +using namespace iptvsimple::utilities; + +#ifdef TARGET_WINDOWS +#ifdef DeleteFile +#undef DeleteFile +#endif +#endif + +/*************************************************************************** + * PVR settings + **************************************************************************/ +void Settings::ReadFromAddon(const std::string& userPath, const std::string clientPath) +{ + m_userPath = userPath; + m_clientPath = clientPath; + + char buffer[1024]; + + // M3U + if (!XBMC->GetSetting("m3uPathType", &m_m3uPathType)) + m_m3uPathType = PathType::REMOTE_PATH; + if (XBMC->GetSetting("m3uPath", &buffer)) + m_m3uPath = buffer; + if (XBMC->GetSetting("m3uUrl", &buffer)) + m_m3uUrl = buffer; + if (!XBMC->GetSetting("m3uCache", &m_cacheM3U)) + m_cacheM3U = true; + if (!XBMC->GetSetting("startNum", &m_startChannelNumber)) + m_startChannelNumber = 1; + + // EPG + if (!XBMC->GetSetting("epgPathType", &m_epgPathType)) + m_epgPathType = PathType::REMOTE_PATH; + if (XBMC->GetSetting("epgPath", &buffer)) + m_epgPath = buffer; + if (XBMC->GetSetting("epgUrl", &buffer)) + m_epgUrl = buffer; + if (!XBMC->GetSetting("epgCache", &m_cacheEPG)) + m_cacheEPG = true; + if (!XBMC->GetSetting("epgTimeShift", &m_epgTimeShiftMins)) + m_epgTimeShiftMins = 0; + if (!XBMC->GetSetting("epgTSOverride", &m_tsOverride)) + m_tsOverride = true; + + // Channel Logos + if (!XBMC->GetSetting("logoPathType", &m_logoPathType)) + m_logoPathType = PathType::REMOTE_PATH; + if (XBMC->GetSetting("logoPath", &buffer)) + m_logoPath = buffer; + if (XBMC->GetSetting("logoBaseUrl", &buffer)) + m_logoBaseUrl = buffer; + if (!XBMC->GetSetting("logoFromEpg", &m_epgLogosMode)) + m_epgLogosMode = EpgLogosMode::IGNORE_XMLTV; +} + +ADDON_STATUS Settings::SetValue(const std::string& settingName, const void* settingValue) +{ + // reset cache and restart addon + + std::string strFile = FileUtils::GetUserFilePath(M3U_FILE_NAME); + if (XBMC->FileExists(strFile.c_str(), false)) + XBMC->DeleteFile(strFile.c_str()); + + strFile = FileUtils::GetUserFilePath(TVG_FILE_NAME); + if (XBMC->FileExists(strFile.c_str(), false)) + XBMC->DeleteFile(strFile.c_str()); + + // M3U + if (settingName == "m3uPathType") + return SetSetting(settingName, settingValue, m_m3uPathType, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "m3uPath") + return SetStringSetting(settingName, settingValue, m_m3uPath, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "m3uUrl") + return SetStringSetting(settingName, settingValue, m_m3uUrl, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "m3uCache") + return SetSetting(settingName, settingValue, m_cacheM3U, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "startNum") + return SetSetting(settingName, settingValue, m_startChannelNumber, ADDON_STATUS_OK, ADDON_STATUS_OK); + + // EPG + if (settingName == "epgPathType") + return SetSetting(settingName, settingValue, m_epgPathType, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "epgPath") + return SetStringSetting(settingName, settingValue, m_epgPath, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "epgUrl") + return SetStringSetting(settingName, settingValue, m_epgUrl, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "epgCache") + return SetSetting(settingName, settingValue, m_cacheEPG, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "epgTimeShift") + return SetSetting(settingName, settingValue, m_epgTimeShiftMins, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "epgTSOverride") + return SetSetting(settingName, settingValue, m_tsOverride, ADDON_STATUS_OK, ADDON_STATUS_OK); + + // Channel Logos + if (settingName == "logoPathType") + return SetSetting(settingName, settingValue, m_logoPathType, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "logoPath") + return SetStringSetting(settingName, settingValue, m_logoPath, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "logoBaseUrl") + return SetStringSetting(settingName, settingValue, m_logoBaseUrl, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "logoFromEpg") + return SetSetting(settingName, settingValue, m_epgLogosMode, ADDON_STATUS_OK, ADDON_STATUS_OK); + + return ADDON_STATUS_OK; +} diff --git a/src/iptvsimple/Settings.h b/src/iptvsimple/Settings.h new file mode 100644 index 000000000..e5b1021fe --- /dev/null +++ b/src/iptvsimple/Settings.h @@ -0,0 +1,146 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/xbmc_addon_types.h" + +#include "utilities/Logger.h" + +#include + +namespace iptvsimple +{ + static const std::string M3U_FILE_NAME = "iptv.m3u.cache"; + static const std::string TVG_FILE_NAME = "xmltv.xml.cache"; + + enum class PathType + : int // same type as addon settings + { + LOCAL_PATH = 0, + REMOTE_PATH + }; + + enum class EpgLogosMode + : int // same type as addon settings + { + IGNORE_XMLTV = 0, + PREFER_M3U, + PREFER_XMLTV + }; + + class Settings + { + public: + /** + * Singleton getter for the instance + */ + static Settings& GetInstance() + { + static Settings settings; + return settings; + } + + void ReadFromAddon(const std::string& userPath, const std::string clientPath); + ADDON_STATUS SetValue(const std::string& settingName, const void* settingValue); + + const std::string& GetUserPath() const { return m_userPath; } + const std::string& GetClientPath() const { return m_clientPath; } + + const std::string& GetM3ULocation() const { return m_m3uPathType == PathType::REMOTE_PATH ? m_m3uUrl : m_m3uPath; } + const PathType& GetM3UPathType() const { return m_m3uPathType; } + const std::string& GetM3UPath() const { return m_m3uPath; } + const std::string& GetM3UUrl() const { return m_m3uUrl; } + bool UseM3UCache() const { return m_m3uPathType == PathType::REMOTE_PATH ? m_cacheM3U : false; } + int GetStartChannelNumber() const { return m_startChannelNumber; } + + const std::string& GetEpgLocation() const { return m_epgPathType == PathType::REMOTE_PATH ? m_epgUrl : m_epgPath; } + const PathType& GetEpgPathType() const { return m_epgPathType; } + const std::string& GetEpgPath() const { return m_epgPath; } + const std::string& GetEpgUrl() const { return m_epgUrl; } + bool UseEPGCache() const { return m_epgPathType == PathType::REMOTE_PATH ? m_cacheEPG : false; } + int GetEpgTimeshiftMins() const { return m_epgTimeShiftMins; } + int GetEpgTimeshiftSecs() const { return m_epgTimeShiftMins * 60; } + bool GetTsOverride() const { return m_tsOverride; } + + const std::string& GetLogoLocation() const { return m_logoPathType == PathType::REMOTE_PATH ? m_logoBaseUrl : m_logoPath; } + const PathType& GetLogoPathType() const { return m_logoPathType; } + const std::string& GetLogoPath() const { return m_logoPath; } + const std::string& GetLogoBaseUrl() const { return m_logoBaseUrl; } + const EpgLogosMode& GetEpgLogosMode() const { return m_epgLogosMode; } + + private: + Settings() = default; + + Settings(Settings const&) = delete; + void operator=(Settings const&) = delete; + + template + V SetSetting(const std::string& settingName, const void* settingValue, T& currentValue, V returnValueIfChanged, V defaultReturnValue) + { + T newValue = *static_cast(settingValue); + if (newValue != currentValue) + { + utilities::Logger::Log(utilities::LogLevel::LEVEL_NOTICE, "%s - Changed Setting '%s' from %d to %d", __FUNCTION__, settingName.c_str(), currentValue, newValue); + currentValue = newValue; + return returnValueIfChanged; + } + + return defaultReturnValue; + }; + + template + V SetStringSetting(const std::string &settingName, const void* settingValue, std::string ¤tValue, V returnValueIfChanged, V defaultReturnValue) + { + const std::string strSettingValue = static_cast(settingValue); + + if (strSettingValue != currentValue) + { + utilities::Logger::Log(utilities::LogLevel::LEVEL_NOTICE, "%s - Changed Setting '%s' from '%s' to '%s'", __FUNCTION__, settingName.c_str(), currentValue.c_str(), strSettingValue.c_str()); + currentValue = strSettingValue; + return returnValueIfChanged; + } + + return defaultReturnValue; + } + + std::string m_userPath = ""; + std::string m_clientPath = ""; + + PathType m_m3uPathType = PathType::REMOTE_PATH; + std::string m_m3uPath = ""; + std::string m_m3uUrl = ""; + bool m_cacheM3U = false; + int m_startChannelNumber = 1; + + PathType m_epgPathType = PathType::REMOTE_PATH; + std::string m_epgPath = ""; + std::string m_epgUrl = ""; + bool m_cacheEPG = false; + int m_epgTimeShiftMins = 0; + bool m_tsOverride = true; + + PathType m_logoPathType = PathType::REMOTE_PATH; + std::string m_logoPath = ""; + std::string m_logoBaseUrl = ""; + EpgLogosMode m_epgLogosMode = EpgLogosMode::IGNORE_XMLTV; + }; +} //namespace iptvsimple diff --git a/src/iptvsimple/data/Channel.cpp b/src/iptvsimple/data/Channel.cpp new file mode 100644 index 000000000..063f783ec --- /dev/null +++ b/src/iptvsimple/data/Channel.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Channel.h" + +using namespace iptvsimple; +using namespace iptvsimple::data; + +void Channel::UpdateTo(Channel& left) const +{ + left.m_uniqueId = m_uniqueId; + left.m_radio = m_radio; + left.m_channelNumber = m_channelNumber; + left.m_encryptionSystem = m_encryptionSystem; + left.m_tvgShift = m_tvgShift; + left.m_channelName = m_channelName; + left.m_logoPath = m_logoPath; + left.m_streamURL = m_streamURL; + left.m_tvgId = m_tvgId; + left.m_tvgName = m_tvgName; + left.m_tvgLogo = m_tvgLogo; + left.m_properties = m_properties; +} + +void Channel::UpdateTo(PVR_CHANNEL& left) const +{ + left.iUniqueId = m_uniqueId; + left.bIsRadio = m_radio; + left.iChannelNumber = m_channelNumber; + strncpy(left.strChannelName, m_channelName.c_str(), sizeof(left.strChannelName) - 1); + left.iEncryptionSystem = m_encryptionSystem; + strncpy(left.strIconPath, m_logoPath.c_str(), sizeof(left.strIconPath) - 1); + left.bIsHidden = false; +} + +void Channel::Reset() +{ + m_uniqueId = 0; + m_radio = false; + m_channelNumber = 0; + m_encryptionSystem = 0; + m_tvgShift = 0; + m_channelName.clear(); + m_logoPath.clear(); + m_streamURL.clear(); + m_tvgId.clear(); + m_tvgName.clear(); + m_tvgLogo.clear(); + m_properties.clear(); +} \ No newline at end of file diff --git a/src/iptvsimple/data/Channel.h b/src/iptvsimple/data/Channel.h new file mode 100644 index 000000000..fbc7c8b8b --- /dev/null +++ b/src/iptvsimple/data/Channel.h @@ -0,0 +1,101 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include +#include + +namespace iptvsimple +{ + namespace data + { + class Channel + { + public: + Channel() = default; + Channel(const Channel &c) : m_radio(c.IsRadio()), m_uniqueId(c.GetUniqueId()), + m_channelNumber(c.GetChannelNumber()), m_encryptionSystem(c.GetEncryptionSystem()), + m_tvgShift(c.GetTvgShift()), m_channelName(c.GetChannelName()), m_logoPath(c.GetLogoPath()), + m_streamURL(c.GetStreamURL()), m_tvgId(c.GetTvgId()), + m_tvgName(c.GetTvgName()), m_tvgLogo(c.GetTvgLogo()), + m_properties(c.GetProperties()) {}; + ~Channel() = default; + + bool IsRadio() const { return m_radio; } + void SetRadio(bool value) { m_radio = value; } + + int GetUniqueId() const { return m_uniqueId; } + void SetUniqueId(int value) { m_uniqueId = value; } + + int GetChannelNumber() const { return m_channelNumber; } + void SetChannelNumber(int value) { m_channelNumber = value; } + + int GetEncryptionSystem() const { return m_encryptionSystem; } + void SetEncryptionSystem(int value) { m_encryptionSystem = value; } + + int GetTvgShift() const { return m_tvgShift; } + void SetTvgShift(int value) { m_tvgShift = value; } + + const std::string& GetChannelName() const { return m_channelName; } + void SetChannelName(const std::string& value) { m_channelName = value; } + + const std::string& GetLogoPath() const { return m_logoPath; } + void SetLogoPath(const std::string& value) { m_logoPath = value; } + + const std::string& GetStreamURL() const { return m_streamURL; } + void SetStreamURL(const std::string& value) { m_streamURL = value; } + + const std::string& GetTvgId() const { return m_tvgId; } + void SetTvgId(const std::string& value) { m_tvgId = value; } + + const std::string& GetTvgName() const { return m_tvgName; } + void SetTvgName(const std::string& value) { m_tvgName = value; } + + const std::string& GetTvgLogo() const { return m_tvgLogo; } + void SetTvgLogo(const std::string& value) { m_tvgLogo = value; } + + const std::map& GetProperties() const { return m_properties; } + void SetProperties(std::map& value) { m_properties = value; } + void AddProperty(const std::string& prop, const std::string& value) { m_properties.insert({prop, value}); } + + void UpdateTo(Channel& left) const; + void UpdateTo(PVR_CHANNEL& left) const; + void Reset(); + + private: + bool m_radio = false; + int m_uniqueId = 0; + int m_channelNumber = 0; + int m_encryptionSystem = 0; + int m_tvgShift = 0; + std::string m_channelName = ""; + std::string m_logoPath = ""; + std::string m_streamURL = ""; + std::string m_tvgId = ""; + std::string m_tvgName = ""; + std::string m_tvgLogo = ""; + std::map m_properties; + }; + } //namespace data +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/data/ChannelEpg.cpp b/src/iptvsimple/data/ChannelEpg.cpp new file mode 100644 index 000000000..f5e52a150 --- /dev/null +++ b/src/iptvsimple/data/ChannelEpg.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "ChannelEpg.h" + +#include "../utilities/XMLUtils.h" + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace rapidxml; + +bool ChannelEpg::UpdateFrom(xml_node<>* channelNode, Channels& channels) +{ + std::string id; + if (!GetAttributeValue(channelNode, "id", id)) + return false; + + const std::string name = GetNodeValue(channelNode, "display-name"); + if (!channels.FindChannel(id, name)) + return false; + + m_id = id; + m_name = name; + + // get icon if available + xml_node<>* iconNode = channelNode->first_node("icon"); + std::string icon = m_icon; + if (!iconNode || !GetAttributeValue(iconNode, "src", icon)) + m_icon.clear(); + else + m_icon = icon; + + return true; +} \ No newline at end of file diff --git a/src/iptvsimple/data/ChannelEpg.h b/src/iptvsimple/data/ChannelEpg.h new file mode 100644 index 000000000..dd2d79ef8 --- /dev/null +++ b/src/iptvsimple/data/ChannelEpg.h @@ -0,0 +1,61 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "EpgEntry.h" +#include "../Channels.h" +#include "rapidxml/rapidxml.hpp" + +#include +#include + +namespace iptvsimple +{ + namespace data + { + class ChannelEpg + { + public: + const std::string& GetId() const { return m_id; } + void SetId(const std::string& value) { m_id = value; } + + const std::string& GetName() const { return m_name; } + void SetName(const std::string& value) { m_name = value; } + + const std::string& GetIcon() const { return m_icon; } + void SetIcon(const std::string& value) { m_icon = value; } + + std::vector& GetEpgEntries() { return m_epgEntries; } + void AddEpgEntry(const EpgEntry& epgEntry) { m_epgEntries.emplace_back(epgEntry); } + + bool UpdateFrom(rapidxml::xml_node<>* channelNode, iptvsimple::Channels& channels); + + private: + std::string m_id; + std::string m_name; + std::string m_icon; + std::vector m_epgEntries; + }; + } //namespace data +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/data/ChannelGroup.cpp b/src/iptvsimple/data/ChannelGroup.cpp new file mode 100644 index 000000000..9ca569da4 --- /dev/null +++ b/src/iptvsimple/data/ChannelGroup.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "ChannelGroup.h" + +using namespace iptvsimple; +using namespace iptvsimple::data; + +void ChannelGroup::UpdateTo(PVR_CHANNEL_GROUP& left) const +{ + left.bIsRadio = m_radio; + left.iPosition = 0; // groups default order, unused + strncpy(left.strGroupName, m_groupName.c_str(), sizeof(left.strGroupName) - 1); +} \ No newline at end of file diff --git a/src/iptvsimple/data/ChannelGroup.h b/src/iptvsimple/data/ChannelGroup.h new file mode 100644 index 000000000..40ed2e27b --- /dev/null +++ b/src/iptvsimple/data/ChannelGroup.h @@ -0,0 +1,57 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include +#include + +namespace iptvsimple +{ + namespace data + { + class ChannelGroup + { + public: + bool IsRadio() const { return m_radio; } + void SetRadio(bool value) { m_radio = value; } + + int GetUniqueId() const { return m_uniqueId; } + void SetUniqueId(int value) { m_uniqueId = value; } + + const std::string& GetGroupName() const { return m_groupName; } + void SetGroupName(const std::string& value) { m_groupName = value; } + + const std::vector& GetMemberChannelIndexes() const { return m_memberChannelIndexes; } + void AddMemberChannelIndex(int channelIndex) { m_memberChannelIndexes.emplace_back(channelIndex); } + + void UpdateTo(PVR_CHANNEL_GROUP& left) const; + + private: + bool m_radio; + int m_uniqueId; + std::string m_groupName; + std::vector m_memberChannelIndexes; + }; + } //namespace data +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp new file mode 100644 index 000000000..d533f26b7 --- /dev/null +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "EpgEntry.h" + +#include "../utilities/XMLUtils.h" + +#include "p8-platform/util/StringUtils.h" +#include "rapidxml/rapidxml.hpp" + +#include + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace rapidxml; + +void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vector& genres) +{ + left.iUniqueBroadcastId = m_broadcastId; + left.strTitle = m_title.c_str(); + left.iUniqueChannelId = iChannelUid; + left.startTime = m_startTime + timeShift; + left.endTime = m_endTime + timeShift; + left.strPlotOutline = m_plotOutline.c_str(); + left.strPlot = m_plot.c_str(); + left.strOriginalTitle = nullptr; /* not supported */ + left.strCast = m_cast.c_str(); + left.strDirector = m_director.c_str(); + left.strWriter = m_writer.c_str(); + left.iYear = 0; /* not supported */ + left.strIMDBNumber = nullptr; /* not supported */ + left.strIconPath = m_iconPath.c_str(); + if (SetEpgGenre(genres, m_genreString)) + { + left.iGenreType = m_genreType; + left.iGenreSubType = m_genreSubType; + left.strGenreDescription = nullptr; + } + else + { + left.iGenreType = EPG_GENRE_USE_STRING; + left.iGenreSubType = 0; /* not supported */ + left.strGenreDescription = m_genreString.c_str(); + } + left.iParentalRating = 0; /* not supported */ + left.iStarRating = 0; /* not supported */ + left.iSeriesNumber = 0; /* not supported */ + left.iEpisodeNumber = 0; /* not supported */ + left.iEpisodePartNumber = 0; /* not supported */ + left.strEpisodeName = m_episodeName.c_str(); + left.iFlags = EPG_TAG_FLAG_UNDEFINED; +} + +bool EpgEntry::SetEpgGenre(std::vector genres, const std::string& genreToFind) +{ + if (genres.empty()) + return false; + + for (const auto& myGenre : genres) + { + if (StringUtils::CompareNoCase(myGenre.GetGenreString(), genreToFind) == 0) + { + m_genreType = myGenre.GetGenreType(); + m_genreSubType = myGenre.GetGenreSubType(); + return true; + } + } + + return false; +} + +namespace +{ + +// Adapted from https://stackoverflow.com/a/31533119 + +// Conversion from UTC date to second, signed 64-bit adjustable epoch version. +// Written by François Grieu, 2015-07-21; public domain. + +long long MakeTime(int year, int month, int day) +{ + return static_cast(year) * 365 + year / 4 - year / 100 * 3 / 4 + (month + 2) * 153 / 5 + day; +} + +long long GetUTCTime(int year, int mon, int mday, int hour, int min, int sec) +{ + int m = mon - 1; + int y = year + 100; + + if (m < 2) + { + m += 12; + --y; + } + + return (((MakeTime(y, m, mday) - MakeTime(1970 + 99, 12, 1)) * 24 + hour) * 60 + min) * 60 + sec; +} + +long long ParseDateTime(const std::string& strDate) +{ + int year = 2000; + int mon = 1; + int mday = 1; + int hour = 0; + int min = 0; + int sec = 0; + char offset_sign = '+'; + int offset_hours = 0; + int offset_minutes = 0; + + sscanf(strDate.c_str(), "%04d%02d%02d%02d%02d%02d %c%02d%02d", &year, &mon, &mday, &hour, &min, &sec, &offset_sign, &offset_hours, &offset_minutes); + + long offset_of_date = (offset_hours * 60 + offset_minutes) * 60; + if (offset_sign == '-') + offset_of_date = -offset_of_date; + + return GetUTCTime(year, mon, mday, hour, min, sec) - offset_of_date; +} + +} // unnamed namespace + +bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& id, int broadcastId, + int start, int end, int minShiftTime, int maxShiftTime) +{ + std::string strStart, strStop; + if (!GetAttributeValue(channelNode, "start", strStart) || !GetAttributeValue(channelNode, "stop", strStop)) + return false; + + long long tmpStart = ParseDateTime(strStart); + long long tmpEnd = ParseDateTime(strStop); + + if ((tmpEnd + maxShiftTime < start) || (tmpStart + minShiftTime > end)) + return false; + + m_broadcastId = broadcastId; + m_channelId = std::atoi(id.c_str()); + m_genreType = 0; + m_genreSubType = 0; + m_plotOutline= ""; + m_startTime = static_cast(tmpStart); + m_endTime = static_cast(tmpEnd); + + m_title = GetNodeValue(channelNode, "title"); + m_plot = GetNodeValue(channelNode, "desc"); + m_genreString = GetNodeValue(channelNode, "category"); + m_episodeName = GetNodeValue(channelNode, "sub-title"); + + xml_node<> *creditsNode = channelNode->first_node("credits"); + if (creditsNode != NULL) + { + m_cast = GetNodeValue(creditsNode, "actor"); + m_director = GetNodeValue(creditsNode, "director"); + m_writer = GetNodeValue(creditsNode, "writer"); + } + + xml_node<>* iconNode = channelNode->first_node("icon"); + std::string iconPath; + if (!iconNode || !GetAttributeValue(iconNode, "src", iconPath)) + m_iconPath = ""; + else + m_iconPath = iconPath; + + return true; +} \ No newline at end of file diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h new file mode 100644 index 000000000..d412be623 --- /dev/null +++ b/src/iptvsimple/data/EpgEntry.h @@ -0,0 +1,109 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "kodi/libXBMC_pvr.h" + +#include "EpgGenre.h" + +#include "rapidxml/rapidxml.hpp" + +#include +#include + +namespace iptvsimple +{ + namespace data + { + class EpgEntry + { + public: + int GetBroadcastId() const { return m_broadcastId; } + void SetBroadcastId(int value) { m_broadcastId = value; } + + int GetChannelId() const { return m_channelId; } + void SetChannelId(int value) { m_channelId = value; } + + int GetGenreType() const { return m_genreType; } + void SetGenreType(int value) { m_genreType = value; } + + int GetGenreSubType() const { return m_genreSubType; } + void SetGenreSubType(int value) { m_genreSubType = value; } + + time_t GetStartTime() const { return m_startTime; } + void SetStartTime(time_t value) { m_startTime = value; } + + time_t GetEndTime() const { return m_endTime; } + void SetEndTime(time_t value) { m_endTime = value; } + + const std::string& GetTitle() const { return m_title; } + void SetTitle(const std::string& value) { m_title = value; } + + const std::string& GetEpisodeName() const { return m_episodeName; } + void SetEpisodeName(const std::string& value) { m_episodeName = value; } + + const std::string& GetPlotOutline() const { return m_plotOutline; } + void SetPlotOutline(const std::string& value) { m_plotOutline = value; } + + const std::string& GetPlot() const { return m_plot; } + void SetPlot(const std::string& value) { m_plot = value; } + + const std::string& GetIconPath() const { return m_iconPath; } + void SetIconPath(const std::string& value) { m_iconPath = value; } + + const std::string& GetGenreString() const { return m_genreString; } + void SetGenreString(const std::string& value) { m_genreString = value; } + + const std::string& GetCast() const { return m_cast; } + void SetCast(const std::string& value) { m_cast = value; } + + const std::string& GetDirector() const { return m_director; } + void SetDirector(const std::string& value) { m_director = value; } + + const std::string& GetWriter() const { return m_writer; } + void SetWriter(const std::string& value) { m_writer = value; } + + void UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vector& genres); + bool UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& id, int broadcastId, + int start, int end, int minShiftTime, int maxShiftTime); + + private: + bool SetEpgGenre(std::vector genres, const std::string& genreToFind); + + int m_broadcastId; + int m_channelId; + int m_genreType; + int m_genreSubType; + time_t m_startTime; + time_t m_endTime; + std::string m_title; + std::string m_episodeName; + std::string m_plotOutline; + std::string m_plot; + std::string m_iconPath; + std::string m_genreString; + std::string m_cast; + std::string m_director; + std::string m_writer; + }; + } //namespace data +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/data/EpgGenre.cpp b/src/iptvsimple/data/EpgGenre.cpp new file mode 100644 index 000000000..d2c02c6b9 --- /dev/null +++ b/src/iptvsimple/data/EpgGenre.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "EpgGenre.h" + +#include "../utilities/XMLUtils.h" +#include "p8-platform/util/StringUtils.h" + +#include + +using namespace iptvsimple; +using namespace iptvsimple::data; +using namespace rapidxml; + +bool EpgGenre::UpdateFrom(rapidxml::xml_node<>* genreNode) +{ + std::string buffer; + if (!GetAttributeValue(genreNode, "type", buffer)) + return false; + + if (!StringUtils::IsNaturalNumber(buffer)) + return false; + + m_genreString = genreNode->value(); + m_genreType = std::atoi(buffer.c_str()); + m_genreSubType = 0; + + if (GetAttributeValue(genreNode, "subtype", buffer) && StringUtils::IsNaturalNumber(buffer)) + m_genreSubType = std::atoi(buffer.c_str()); + + return true; +} diff --git a/src/iptvsimple/data/EpgGenre.h b/src/iptvsimple/data/EpgGenre.h new file mode 100644 index 000000000..a59975553 --- /dev/null +++ b/src/iptvsimple/data/EpgGenre.h @@ -0,0 +1,52 @@ +#pragma once +/* + * Copyright (C) 2005-2019 Team XBMC + * http://xbmc.org + * + * 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 XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1335, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "rapidxml/rapidxml.hpp" + +#include + +namespace iptvsimple +{ + namespace data + { + class EpgGenre + { + public: + int GetGenreType() const { return m_genreType; } + void SetGenreType(int value) { m_genreType = value; } + + int GetGenreSubType() const { return m_genreSubType; } + void SetGenreSubType(int value) { m_genreSubType = value; } + + const std::string& GetGenreString() const { return m_genreString; } + void SetGenreString(const std::string& value) { m_genreString = value; } + + bool UpdateFrom(rapidxml::xml_node<>* genreNode); + + private: + int m_genreType; + int m_genreSubType; + std::string m_genreString; + }; + } //namespace data +} //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/utilities/FileUtils.cpp b/src/iptvsimple/utilities/FileUtils.cpp new file mode 100644 index 000000000..51fb015dc --- /dev/null +++ b/src/iptvsimple/utilities/FileUtils.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * 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 XBMC; 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 "FileUtils.h" + +#include "../Settings.h" +#include "../../client.h" +#include "zlib.h" + +using namespace iptvsimple; +using namespace iptvsimple::utilities; + +std::string FileUtils::PathCombine(const std::string& path, const std::string& fileName) +{ + std::string result = path; + + if (!result.empty()) + { + if (result.at(result.size() - 1) == '\\' || + result.at(result.size() - 1) == '/') + { + result.append(fileName); + } + else + { + result.append("/"); + result.append(fileName); + } + } + else + { + result.append(fileName); + } + + return result; +} + +std::string FileUtils::GetClientFilePath(const std::string& fileName) +{ + return PathCombine(Settings::GetInstance().GetClientPath(), fileName); +} + +std::string FileUtils::GetUserFilePath(const std::string& fileName) +{ + return PathCombine(Settings::GetInstance().GetUserPath(), fileName); +} + +int FileUtils::GetFileContents(const std::string& url, std::string& content) +{ + content.clear(); + void* fileHandle = XBMC->OpenFile(url.c_str(), 0); + if (fileHandle) + { + char buffer[1024]; + while (int bytesRead = XBMC->ReadFile(fileHandle, buffer, 1024)) + content.append(buffer, bytesRead); + XBMC->CloseFile(fileHandle); + } + + return content.length(); +} + +/* + * This method uses zlib to decompress a gzipped file in memory. + * Author: Andrew Lim Chong Liang + * http://windrealm.org + */ + +bool FileUtils::GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes) +{ + if (compressedBytes.size() == 0) + { + uncompressedBytes = compressedBytes; + return true; + } + + uncompressedBytes.clear(); + + unsigned uncompLength = compressedBytes.size(); + const unsigned half_length = compressedBytes.size() / 2; + + char* uncomp = static_cast(calloc(sizeof(char), uncompLength)); + + z_stream strm; + strm.next_in = (Bytef*)compressedBytes.c_str(); + strm.avail_in = compressedBytes.size(); + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + + int status = inflateInit2(&strm, 16 + MAX_WBITS); + if (status != Z_OK) + { + free(uncomp); + return false; + } + + bool done = false; + while (!done) + { + // If our output buffer is too small + if (strm.total_out >= uncompLength) + { + // Increase size of output buffer + uncomp = static_cast(realloc(uncomp, uncompLength + half_length)); + if (!uncomp) + return false; + uncompLength += half_length; + } + + strm.next_out = reinterpret_cast(uncomp + strm.total_out); + strm.avail_out = uncompLength - strm.total_out; + + // Inflate another chunk. + int err = inflate(&strm, Z_SYNC_FLUSH); + if (err == Z_STREAM_END) + done = true; + else if (err != Z_OK) + break; + } + + status = inflateEnd(&strm); + if (status != Z_OK) + { + free(uncomp); + return false; + } + + for (size_t i = 0; i < strm.total_out; ++i) + uncompressedBytes += uncomp[i]; + + free(uncomp); + return true; +} + +int FileUtils::GetCachedFileContents(const std::string& cachedName, const std::string& filePath, + std::string& contents, const bool useCache /* false */) +{ + bool needReload = false; + const std::string cachedPath = FileUtils::GetUserFilePath(cachedName); + + // check cached file is exists + if (useCache && XBMC->FileExists(cachedPath.c_str(), false)) + { + struct __stat64 statCached; + struct __stat64 statOrig; + + XBMC->StatFile(cachedPath.c_str(), &statCached); + XBMC->StatFile(filePath.c_str(), &statOrig); + + needReload = statCached.st_mtime < statOrig.st_mtime || statOrig.st_mtime == 0; + } + else + { + needReload = true; + } + + if (needReload) + { + FileUtils::GetFileContents(filePath, contents); + + // write to cache + if (useCache && contents.length() > 0) + { + void* fileHandle = XBMC->OpenFileForWrite(cachedPath.c_str(), true); + if (fileHandle) + { + XBMC->WriteFile(fileHandle, contents.c_str(), contents.length()); + XBMC->CloseFile(fileHandle); + } + } + return contents.length(); + } + + return FileUtils::GetFileContents(cachedPath, contents); +} \ No newline at end of file diff --git a/src/iptvsimple/utilities/FileUtils.h b/src/iptvsimple/utilities/FileUtils.h new file mode 100644 index 000000000..ed0b7e71c --- /dev/null +++ b/src/iptvsimple/utilities/FileUtils.h @@ -0,0 +1,44 @@ +#pragma once + +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * 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 XBMC; 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 "p8-platform/os.h" + +#include + +namespace iptvsimple +{ + namespace utilities + { + class FileUtils + { + public: + static std::string PathCombine(const std::string& path, const std::string& fileName); + static std::string GetClientFilePath(const std::string& fileName); + static std::string GetUserFilePath(const std::string& fileName); + static int GetFileContents(const std::string& url, std::string& content); + static bool GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes); + static int GetCachedFileContents(const std::string& cachedName, const std::string& filePath, + std::string& content, const bool useCache = false); + }; + } // namespace utilities +} // namespace iptvsimple diff --git a/src/iptvsimple/utilities/Logger.cpp b/src/iptvsimple/utilities/Logger.cpp new file mode 100644 index 000000000..fd5619c74 --- /dev/null +++ b/src/iptvsimple/utilities/Logger.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * 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 XBMC; 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 "Logger.h" + +#include + +using namespace iptvsimple::utilities; + +Logger::Logger() +{ + // Use an empty implementation by default + SetImplementation([](LogLevel level, const char* message) + { + }); +} + +Logger& Logger::GetInstance() +{ + static Logger instance; + return instance; +} + +void Logger::Log(LogLevel level, const char* message, ...) +{ + auto& logger = GetInstance(); + + char buffer[MESSAGE_BUFFER_SIZE]; + std::string logMessage = message; + const std::string prefix = logger.m_prefix; + + // Prepend the prefix when set + if (!prefix.empty()) + logMessage = prefix + " - " + message; + + va_list arguments; + va_start(arguments, message); + vsprintf(buffer, logMessage.c_str(), arguments); + va_end(arguments); + + logger.m_implementation(level, buffer); +} + +void Logger::SetImplementation(LoggerImplementation implementation) +{ + m_implementation = implementation; +} + +void Logger::SetPrefix(const std::string& prefix) +{ + m_prefix = prefix; +} diff --git a/src/iptvsimple/utilities/Logger.h b/src/iptvsimple/utilities/Logger.h new file mode 100644 index 000000000..2dea2c03e --- /dev/null +++ b/src/iptvsimple/utilities/Logger.h @@ -0,0 +1,98 @@ +#pragma once + +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * 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 XBMC; 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 + +namespace iptvsimple +{ + namespace utilities + { + /** + * Represents the log level + */ + enum LogLevel + { + LEVEL_ERROR, + LEVEL_NOTICE, + LEVEL_INFO, + LEVEL_DEBUG, + LEVEL_TRACE + }; + + /** + * Short-hand for a function that acts as the logger implementation + */ + typedef std::function LoggerImplementation; + + /** + * The logger class. It is a singleton that by default comes with no + * underlying implementation. It is up to the user to supply a suitable + * implementation as a lambda using SetImplementation(). + */ + class Logger + { + public: + /** + * Returns the singleton instance + * @return + */ + static Logger& GetInstance(); + + /** + * Logs the specified message using the specified log level + * @param level the log level + * @param message the log message + * @param ... parameters for the log message + */ + static void Log(LogLevel level, const char* message, ...); + + /** + * Configures the logger to use the specified implementation + * @param implementation lambda + */ + void SetImplementation(LoggerImplementation implementation); + + /** + * Sets the prefix to use in log messages + * @param prefix + */ + void SetPrefix(const std::string& prefix); + + private: + static const unsigned int MESSAGE_BUFFER_SIZE = 16384; + + Logger(); + + /** + * The logger implementation + */ + LoggerImplementation m_implementation; + + /** + * The log message prefix + */ + std::string m_prefix; + }; + } // namespace utilities +} // namespace iptvsimple diff --git a/src/iptvsimple/utilities/XMLUtils.h b/src/iptvsimple/utilities/XMLUtils.h new file mode 100644 index 000000000..73fbe1a45 --- /dev/null +++ b/src/iptvsimple/utilities/XMLUtils.h @@ -0,0 +1,47 @@ +#pragma once + +/* + * Copyright (C) 2005-2015 Team Kodi + * http://kodi.tv + * + * 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 XBMC; 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 "rapidxml/rapidxml.hpp" +#include + +template +inline std::string GetNodeValue(const rapidxml::xml_node* rootNode, const char* tag) +{ + rapidxml::xml_node* childNode = rootNode->first_node(tag); + if (!childNode) + return ""; + + return childNode->value(); +} + +template +inline bool GetAttributeValue(const rapidxml::xml_node* node, const char* attributeName, std::string& stringValue) +{ + rapidxml::xml_attribute* attribute = node->first_attribute(attributeName); + if (!attribute) + { + return false; + } + stringValue = attribute->value(); + return true; +}