From 9f624a7d224fffafc529e429b5ace3a16b3b6ead Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Mon, 1 Jan 2024 18:53:14 +0000 Subject: [PATCH] Add connection manager support to wait for a valid M3U file before starting the add-on instance --- CMakeLists.txt | 3 + README.md | 2 + .../resources/instance-settings.xml | 31 ++- .../resource.language.en_gb/strings.po | 29 ++- src/IptvSimple.cpp | 63 ++++-- src/IptvSimple.h | 13 +- src/iptvsimple/ConnectionManager.cpp | 182 ++++++++++++++++++ src/iptvsimple/ConnectionManager.h | 57 ++++++ src/iptvsimple/IConnectionListener.h | 24 +++ src/iptvsimple/InstanceSettings.cpp | 6 + src/iptvsimple/InstanceSettings.h | 7 + src/iptvsimple/utilities/WebUtils.cpp | 22 +++ src/iptvsimple/utilities/WebUtils.h | 1 + 13 files changed, 418 insertions(+), 22 deletions(-) create mode 100644 src/iptvsimple/ConnectionManager.cpp create mode 100644 src/iptvsimple/ConnectionManager.h create mode 100644 src/iptvsimple/IConnectionListener.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 521b1c3f3..10979dbbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ set(IPTV_SOURCES src/addon.cpp src/iptvsimple/CatchupController.cpp src/iptvsimple/Channels.cpp src/iptvsimple/ChannelGroups.cpp + src/iptvsimple/ConnectionManager.cpp src/iptvsimple/Epg.cpp src/iptvsimple/InstanceSettings.cpp src/iptvsimple/Media.cpp @@ -52,7 +53,9 @@ set(IPTV_HEADERS src/addon.h src/iptvsimple/CatchupController.h src/iptvsimple/Channels.h src/iptvsimple/ChannelGroups.h + src/iptvsimple/ConnectionManager.h src/iptvsimple/Epg.h + src/iptvsimple/IConnectionListener.h src/iptvsimple/InstanceSettings.h src/iptvsimple/Media.h src/iptvsimple/PlaylistLoader.h diff --git a/README.md b/README.md index 1dce87cf8..4a64640e2 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ General settings required for the addon to function. - `Once per day` - Refresh the files once per day. * **Refresh interval**: If auto refresh mode is `Repeated refresh` refresh the files every time this number of minutes passes. Max 120 minutes. * **Refresh hour (24h)**: If auto refresh mode is `Once per day` refresh the files every time this hour of the day is reached. +* **M3U Check Interval**: When checking for a valid M3U file, the length of time to wait between attempts. Note that a valid file will only be checked for on startup and once a valid file is found all checks stop. +* **M3U Check Timeout**: When checking for a valid M3U file, the length of time to wait before timing out the attempt. * **Default provider name**: If provided this value will be used as the provider name if one was not provided in the M3U. It can be used in combination with the provider mapping file which can supply type, icon path, country code and language code fields. * **Enable provider mapping**: If enabled any provider name read from the M3U or the default provider name will be used to read further metadata from the mapping file. The metadata includes custom name, type, icon path, country code and language code. * **Provider name mapping file**: The config file to map provider names received from the M3U or the default provider name to custom name, icons etc. The default file is `providerMappings.xml`. diff --git a/pvr.iptvsimple/resources/instance-settings.xml b/pvr.iptvsimple/resources/instance-settings.xml index bb3e29b1d..10f760842 100644 --- a/pvr.iptvsimple/resources/instance-settings.xml +++ b/pvr.iptvsimple/resources/instance-settings.xml @@ -106,7 +106,36 @@ - + + + 1 + 10 + + 1 + 1 + 60 + + + true + 14045 + + + + 2 + 20 + + 10 + 10 + 60 + + + true + 14045 + + + + + 2 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 06bf30859..a19e9c82e 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -434,7 +434,22 @@ msgctxt "#30077" msgid "Ignore Case for EPG Channel IDs" msgstr "" -#empty strings from id 30078 to 30099 +#. label-group: General - M3U Startup Check +msgctxt "#30078" +msgid "M3U Startup Check" +msgstr "" + +#. label: Catchup - connectCheckTimoutSecs +msgctxt "#30079" +msgid "Timeout for check" +msgstr "" + +#. label: Catchup - connectCheckIntervalSecs +msgctxt "#30080" +msgid "Interval for check" +msgstr "" + +#empty strings from id 30081 to 30099 #. label-category: catchup #. label-group: Catchup - Catchup @@ -828,7 +843,17 @@ msgctxt "#30627" msgid "Ignore Case for EPG Channel IDs, also known as tvg-id's, when matching channels to EPG entries. If disabled, only case senitive matching will be used." msgstr "" -#empty strings from id 30628 to 30639 +#. help: EPG Settings - connectionCheckTimeoutSecs +msgctxt "#30628" +msgid "When checking for a valid M3U file, the length of time to wait before timing out the attempt." +msgstr "" + +#. help: EPG Settings - connectionCheckIntervalSecs +msgctxt "#30629" +msgid "When checking for a valid M3U file, the length of time to wait between attempts. Note that a valid file will only be checked for on startup and once a valid file is found all checks stop." +msgstr "" + +#empty strings from id 30630 to 30639 #. help info - Channel Logos diff --git a/src/IptvSimple.cpp b/src/IptvSimple.cpp index c19c0a503..9d51c6867 100644 --- a/src/IptvSimple.cpp +++ b/src/IptvSimple.cpp @@ -22,19 +22,45 @@ using namespace iptvsimple::data; using namespace iptvsimple::utilities; using namespace kodi::tools; -IptvSimple::IptvSimple(const kodi::addon::IInstanceInfo& instance) : kodi::addon::CInstancePVRClient(instance), m_settings(new InstanceSettings(*this, instance)) +IptvSimple::IptvSimple(const kodi::addon::IInstanceInfo& instance) : iptvsimple::IConnectionListener(instance), m_settings(new InstanceSettings(*this, instance)) { m_channels.Clear(); m_channelGroups.Clear(); m_providers.Clear(); m_epg.Clear(); m_media.Clear(); + connectionManager = new ConnectionManager(*this, m_settings); } -bool IptvSimple::Initialise() +IptvSimple::~IptvSimple() { + Logger::Log(LEVEL_DEBUG, "%s Stopping update thread...", __FUNCTION__); + m_running = false; + if (m_thread.joinable()) + m_thread.join(); + std::lock_guard lock(m_mutex); + m_channels.Clear(); + m_channelGroups.Clear(); + m_providers.Clear(); + m_epg.Clear(); + if (connectionManager) + connectionManager->Stop(); + delete connectionManager; +} + +/* ************************************************************************** + * Connection + * *************************************************************************/ + +void IptvSimple::ConnectionLost() +{ + Logger::Log(LEVEL_INFO, "%s Could not validiate M3U after startup, but ignoring as startup is all we care about.", __func__); +} + +void IptvSimple::ConnectionEstablished() +{ m_channels.Init(); m_channelGroups.Init(); m_providers.Init(); @@ -50,10 +76,29 @@ bool IptvSimple::Initialise() m_running = true; m_thread = std::thread([&] { Process(); }); +} + +bool IptvSimple::Initialise() +{ + std::lock_guard lock(m_mutex); + + connectionManager->Start(); return true; } +PVR_ERROR IptvSimple::OnSystemSleep() +{ + connectionManager->OnSleep(); + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR IptvSimple::OnSystemWake() +{ + connectionManager->OnWake(); + return PVR_ERROR_NO_ERROR; +} + PVR_ERROR IptvSimple::GetCapabilities(kodi::addon::PVRCapabilities& capabilities) { capabilities.SetSupportsEPG(true); @@ -126,20 +171,6 @@ void IptvSimple::Process() } } -IptvSimple::~IptvSimple() -{ - Logger::Log(LEVEL_DEBUG, "%s Stopping update thread...", __FUNCTION__); - m_running = false; - if (m_thread.joinable()) - m_thread.join(); - - std::lock_guard lock(m_mutex); - m_channels.Clear(); - m_channelGroups.Clear(); - m_providers.Clear(); - m_epg.Clear(); -} - /*************************************************************************** * Providers **************************************************************************/ diff --git a/src/IptvSimple.h b/src/IptvSimple.h index a6c0707dd..11c315351 100644 --- a/src/IptvSimple.h +++ b/src/IptvSimple.h @@ -10,8 +10,10 @@ #include "iptvsimple/CatchupController.h" #include "iptvsimple/Channels.h" #include "iptvsimple/ChannelGroups.h" +#include "iptvsimple/ConnectionManager.h" #include "iptvsimple/Providers.h" #include "iptvsimple/Epg.h" +#include "iptvsimple/IConnectionListener.h" #include "iptvsimple/Media.h" #include "iptvsimple/PlaylistLoader.h" #include "iptvsimple/data/Channel.h" @@ -22,12 +24,16 @@ #include -class ATTR_DLL_LOCAL IptvSimple : public kodi::addon::CInstancePVRClient +class ATTR_DLL_LOCAL IptvSimple : public iptvsimple::IConnectionListener { public: IptvSimple(const kodi::addon::IInstanceInfo& instance); ~IptvSimple() override; + // IConnectionListener implementation + void ConnectionLost() override; + void ConnectionEstablished() override; + bool Initialise(); // kodi::addon::CInstancePVRClient -> kodi::addon::IAddonInstance overrides @@ -41,8 +47,8 @@ class ATTR_DLL_LOCAL IptvSimple : public kodi::addon::CInstancePVRClient PVR_ERROR GetBackendVersion(std::string& version) override; PVR_ERROR GetConnectionString(std::string& connection) override; - PVR_ERROR OnSystemSleep() override { return PVR_ERROR_NO_ERROR; } - PVR_ERROR OnSystemWake() override { return PVR_ERROR_NO_ERROR; } + PVR_ERROR OnSystemSleep() override; + PVR_ERROR OnSystemWake() override; PVR_ERROR OnPowerSavingActivated() override { return PVR_ERROR_NO_ERROR; } PVR_ERROR OnPowerSavingDeactivated() override { return PVR_ERROR_NO_ERROR; } @@ -96,6 +102,7 @@ class ATTR_DLL_LOCAL IptvSimple : public kodi::addon::CInstancePVRClient iptvsimple::PlaylistLoader m_playlistLoader{this, m_channels, m_channelGroups, m_providers, m_media, m_settings}; iptvsimple::Epg m_epg{this, m_channels, m_media, m_settings}; iptvsimple::CatchupController m_catchupController{m_epg, &m_mutex, m_settings}; + iptvsimple::ConnectionManager* connectionManager; std::atomic m_running{false}; std::thread m_thread; diff --git a/src/iptvsimple/ConnectionManager.cpp b/src/iptvsimple/ConnectionManager.cpp new file mode 100644 index 000000000..fbf749637 --- /dev/null +++ b/src/iptvsimple/ConnectionManager.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "ConnectionManager.h" + +#include "IConnectionListener.h" +#include "InstanceSettings.h" +#include "utilities/Logger.h" +#include "utilities/WebUtils.h" + +#include + +#include +#include + +using namespace iptvsimple; +using namespace iptvsimple::utilities; +using namespace kodi::tools; + +/* + * Iptvsimple Connection handler + */ + +ConnectionManager::ConnectionManager(IConnectionListener& connectionListener, std::shared_ptr settings) + : m_connectionListener(connectionListener), m_settings(settings), m_suspended(false), m_state(PVR_CONNECTION_STATE_UNKNOWN) +{ +} + +ConnectionManager::~ConnectionManager() +{ + Stop(); +} + +void ConnectionManager::Start() +{ + // Note: "connecting" must only be set one time, before the very first connection attempt, not on every reconnect. + SetState(PVR_CONNECTION_STATE_CONNECTING); + m_running = true; + m_thread = std::thread([&] { Process(); }); +} + +void ConnectionManager::Stop() +{ + m_running = false; + if (m_thread.joinable()) + m_thread.join(); + + Disconnect(); +} + +void ConnectionManager::OnSleep() +{ + std::lock_guard lock(m_mutex); + + Logger::Log(LogLevel::LEVEL_DEBUG, "%s going to sleep", __func__); + + m_suspended = true; +} + +void ConnectionManager::OnWake() +{ + std::lock_guard lock(m_mutex); + + Logger::Log(LogLevel::LEVEL_DEBUG, "%s Waking up", __func__); + + m_suspended = false; +} + +void ConnectionManager::SetState(PVR_CONNECTION_STATE state) +{ + PVR_CONNECTION_STATE prevState(PVR_CONNECTION_STATE_UNKNOWN); + PVR_CONNECTION_STATE newState(PVR_CONNECTION_STATE_UNKNOWN); + + { + std::lock_guard lock(m_mutex); + + /* No notification if no state change or while suspended. */ + if (m_state != state && !m_suspended) + { + prevState = m_state; + newState = state; + m_state = newState; + + Logger::Log(LogLevel::LEVEL_DEBUG, "connection state change (%d -> %d)", prevState, newState); + } + } + + if (prevState != newState) + { + static std::string serverString; + + if (newState == PVR_CONNECTION_STATE_SERVER_UNREACHABLE) + { + m_connectionListener.ConnectionLost(); + } + else if (newState == PVR_CONNECTION_STATE_CONNECTED) + { + m_connectionListener.ConnectionEstablished(); + } + + /* Notify connection state change (callback!) */ + if (m_notifyStateChangeToUser) + m_connectionListener.ConnectionStateChange(m_settings->GetM3ULocation(), newState, ""); + } +} + +void ConnectionManager::Disconnect() +{ + std::lock_guard lock(m_mutex); + + m_connectionListener.ConnectionLost(); +} + +void ConnectionManager::Reconnect() +{ + // Setting this state will cause Iptvsimple to receive a connetionLost event + // The connection manager will then connect again causeing a reload of all state + SetState(PVR_CONNECTION_STATE_SERVER_UNREACHABLE); +} + +void ConnectionManager::Process() +{ + static bool log = false; + static unsigned int retryAttempt = 0; + int fastReconnectIntervalMs = (m_settings->GetConnectioncCheckIntervalSecs() * 1000) / 2; + int intervalMs = m_settings->GetConnectioncCheckIntervalSecs() * 1000; + + bool firstRun = true; + + while (m_running) + { + while (m_suspended) + { + Logger::Log(LogLevel::LEVEL_DEBUG, "%s - suspended, waiting for wakeup...", __func__); + + /* Wait for wakeup */ + SteppedSleep(intervalMs); + } + + const std::string url = m_settings->GetM3ULocation(); + + /* Connect */ + if ((firstRun || !m_onStartupOnly) && !WebUtils::Check(url, m_settings->GetConnectioncCheckTimeoutSecs())) + { + /* Unable to connect */ + if (retryAttempt == 0) + Logger::Log(LogLevel::LEVEL_ERROR, "%s - unable to connect to: %s", __func__, url.c_str()); + SetState(PVR_CONNECTION_STATE_SERVER_UNREACHABLE); + + // Retry a few times with a short interval, after that with the default timeout + if (++retryAttempt <= FAST_RECONNECT_ATTEMPTS) + SteppedSleep(fastReconnectIntervalMs); + else + SteppedSleep(intervalMs); + + continue; + } + + SetState(PVR_CONNECTION_STATE_CONNECTED); + retryAttempt = 0; + firstRun = false; + + SteppedSleep(intervalMs); + } +} + +void ConnectionManager::SteppedSleep(int intervalMs) +{ + int sleepCountMs = 0; + + while (sleepCountMs <= intervalMs) + { + if (m_running) + std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_STEP_MS)); + + sleepCountMs += SLEEP_INTERVAL_STEP_MS; + } +} diff --git a/src/iptvsimple/ConnectionManager.h b/src/iptvsimple/ConnectionManager.h new file mode 100644 index 000000000..a96097689 --- /dev/null +++ b/src/iptvsimple/ConnectionManager.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include "InstanceSettings.h" + +#include +#include +#include +#include + +#include + +namespace iptvsimple +{ + static const int FAST_RECONNECT_ATTEMPTS = 5; + static const int SLEEP_INTERVAL_STEP_MS = 500; + + class IConnectionListener; + + class ATTR_DLL_LOCAL ConnectionManager + { + public: + ConnectionManager(IConnectionListener& connectionListener, std::shared_ptr settings); + ~ConnectionManager(); + + void Start(); + void Stop(); + void Disconnect(); + void Reconnect(); + + void OnSleep(); + void OnWake(); + + private: + void Process(); + void SetState(PVR_CONNECTION_STATE state); + void SteppedSleep(int intervalMs); + + IConnectionListener& m_connectionListener; + std::atomic m_running = {false}; + std::thread m_thread; + mutable std::mutex m_mutex; + bool m_suspended; + PVR_CONNECTION_STATE m_state; + + bool m_onStartupOnly = true; + bool m_notifyStateChangeToUser = false; + + std::shared_ptr m_settings; + }; +} // namespace iptvsimple diff --git a/src/iptvsimple/IConnectionListener.h b/src/iptvsimple/IConnectionListener.h new file mode 100644 index 000000000..8fbbc4eb3 --- /dev/null +++ b/src/iptvsimple/IConnectionListener.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include + +namespace iptvsimple +{ + class ATTR_DLL_LOCAL IConnectionListener : public kodi::addon::CInstancePVRClient + { + public: + IConnectionListener(const kodi::addon::IInstanceInfo& instance) + : kodi::addon::CInstancePVRClient(instance) { } + virtual ~IConnectionListener() = default; + + virtual void ConnectionLost() = 0; + virtual void ConnectionEstablished() = 0; + }; +} // namespace iptvsimple diff --git a/src/iptvsimple/InstanceSettings.cpp b/src/iptvsimple/InstanceSettings.cpp index 258354940..612fbebf7 100644 --- a/src/iptvsimple/InstanceSettings.cpp +++ b/src/iptvsimple/InstanceSettings.cpp @@ -156,6 +156,8 @@ void InstanceSettings::ReadSettings() m_instance.CheckInstanceSettingString("defaultUserAgent", m_defaultUserAgent); m_instance.CheckInstanceSettingString("defaultInputstream", m_defaultInputstream); m_instance.CheckInstanceSettingString("defaultMimeType", m_defaultMimeType); + m_instance.CheckInstanceSettingInt("connectionchecktimeout", m_connectioncCheckTimeoutSecs); + m_instance.CheckInstanceSettingInt("connectioncheckinterval", m_connectioncCheckIntervalSecs); } void InstanceSettings::ReloadAddonInstanceSettings() @@ -194,6 +196,10 @@ ADDON_STATUS InstanceSettings::SetSetting(const std::string& settingName, const return SetSetting(settingName, settingValue, m_m3uRefreshIntervalMins, ADDON_STATUS_OK, ADDON_STATUS_OK); else if (settingName == "m3uRefreshHour") return SetSetting(settingName, settingValue, m_m3uRefreshHour, ADDON_STATUS_OK, ADDON_STATUS_OK); + else if (settingName == "connectionchecktimeout") + return SetSetting(settingName, settingValue, m_connectioncCheckTimeoutSecs, ADDON_STATUS_OK, ADDON_STATUS_OK); + else if (settingName == "connectioncheckinterval") + return SetSetting(settingName, settingValue, m_connectioncCheckIntervalSecs, ADDON_STATUS_OK, ADDON_STATUS_OK); else if (settingName == "defaultProviderName") return SetStringSetting(settingName, settingValue, m_defaultProviderName, ADDON_STATUS_OK, ADDON_STATUS_OK); else if (settingName == "enableProviderMappings") diff --git a/src/iptvsimple/InstanceSettings.h b/src/iptvsimple/InstanceSettings.h index 049133ccc..7ed875b87 100644 --- a/src/iptvsimple/InstanceSettings.h +++ b/src/iptvsimple/InstanceSettings.h @@ -28,6 +28,9 @@ namespace iptvsimple static const std::string DEFAULT_CUSTOM_TV_GROUPS_FILE = ADDON_DATA_BASE_DIR + "/channelGroups/customTVGroups-example.xml"; static const std::string DEFAULT_CUSTOM_RADIO_GROUPS_FILE = ADDON_DATA_BASE_DIR + "/channelGroups/customRadioGroups-example.xml"; + static const int DEFAULT_CONNECTION_CHECK_TIMEOUT_SECS = 10; + static const int DEFAULT_CONNECTION_CHECK_INTERVAL_SECS = 5; + enum class PathType : int // same type as addon settings { @@ -175,6 +178,8 @@ namespace iptvsimple const std::string& GetDefaultUserAgent() const { return m_defaultUserAgent; } const std::string& GetDefaultInputstream() const { return m_defaultInputstream; } const std::string& GetDefaultMimeType() const { return m_defaultMimeType; } + int GetConnectioncCheckTimeoutSecs() const { return m_connectioncCheckTimeoutSecs; } + int GetConnectioncCheckIntervalSecs() const { return m_connectioncCheckIntervalSecs; } const std::string& GetTvgUrl() const { return m_tvgUrl; } void SetTvgUrl(const std::string& tvgUrl) { m_tvgUrl = tvgUrl; } @@ -337,6 +342,8 @@ namespace iptvsimple std::string m_defaultUserAgent; std::string m_defaultInputstream; std::string m_defaultMimeType; + int m_connectioncCheckTimeoutSecs = DEFAULT_CONNECTION_CHECK_TIMEOUT_SECS; + int m_connectioncCheckIntervalSecs = DEFAULT_CONNECTION_CHECK_INTERVAL_SECS; std::vector m_customTVChannelGroupNameList; std::vector m_customRadioChannelGroupNameList; diff --git a/src/iptvsimple/utilities/WebUtils.cpp b/src/iptvsimple/utilities/WebUtils.cpp index 39b077733..5061355cc 100644 --- a/src/iptvsimple/utilities/WebUtils.cpp +++ b/src/iptvsimple/utilities/WebUtils.cpp @@ -7,6 +7,7 @@ #include "WebUtils.h" +#include "Logger.h" #include #include @@ -128,3 +129,24 @@ std::string WebUtils::RedactUrl(const std::string& url) return redactedUrl; } + +bool WebUtils::Check(const std::string& strURL, int connectionTimeoutSecs) +{ + kodi::vfs::CFile fileHandle; + if (!fileHandle.CURLCreate(strURL)) + { + Logger::Log(LEVEL_ERROR, "%s Unable to create curl handle for %s", __func__, WebUtils::RedactUrl(strURL).c_str()); + return false; + } + + fileHandle.CURLAddOption(ADDON_CURL_OPTION_PROTOCOL, "connection-timeout", + std::to_string(connectionTimeoutSecs)); + + if (!fileHandle.CURLOpen(ADDON_READ_NO_CACHE)) + { + Logger::Log(LEVEL_DEBUG, "%s Unable to open url: %s", __func__, WebUtils::RedactUrl(strURL).c_str()); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/iptvsimple/utilities/WebUtils.h b/src/iptvsimple/utilities/WebUtils.h index f05dc84f8..0fc1fa944 100644 --- a/src/iptvsimple/utilities/WebUtils.h +++ b/src/iptvsimple/utilities/WebUtils.h @@ -27,6 +27,7 @@ namespace iptvsimple static std::string ReadFileContentsStartOnly(const std::string& url, int* httpCode); static bool IsHttpUrl(const std::string& url); static std::string RedactUrl(const std::string& url); + static bool Check(const std::string& url, int connectionTimeoutSecs); }; } // namespace utilities } // namespace iptvsimple