summaryrefslogtreecommitdiffstats
path: root/xbmc/network/WakeOnAccess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/network/WakeOnAccess.cpp')
-rw-r--r--xbmc/network/WakeOnAccess.cpp956
1 files changed, 956 insertions, 0 deletions
diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp
new file mode 100644
index 0000000..929c68b
--- /dev/null
+++ b/xbmc/network/WakeOnAccess.cpp
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WakeOnAccess.h"
+
+#include "DNSNameCache.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "messaging/ApplicationMessenger.h"
+#include "network/Network.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/MediaSourceSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/JobManager.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/XMLUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <limits.h>
+#include <mutex>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#ifdef HAS_UPNP
+#include "network/upnp/UPnP.h"
+#include <Platinum/Source/Platinum/Platinum.h>
+#endif
+
+#define DEFAULT_NETWORK_INIT_SEC (20) // wait 20 sec for network after startup or resume
+#define DEFAULT_NETWORK_SETTLE_MS (500) // require 500ms of consistent network availability before trusting it
+
+#define DEFAULT_TIMEOUT_SEC (5*60) // at least 5 minutes between each magic packets
+#define DEFAULT_WAIT_FOR_ONLINE_SEC_1 (40) // wait at 40 seconds after sending magic packet
+#define DEFAULT_WAIT_FOR_ONLINE_SEC_2 (40) // same for extended wait
+#define DEFAULT_WAIT_FOR_SERVICES_SEC (5) // wait 5 seconds after host go online to launch file sharing daemons
+
+using namespace std::chrono_literals;
+
+static CDateTime upnpInitReady;
+
+static int GetTotalSeconds(const CDateTimeSpan& ts)
+{
+ int hours = ts.GetHours() + ts.GetDays() * 24;
+ int minutes = ts.GetMinutes() + hours * 60;
+ return ts.GetSeconds() + minutes * 60;
+}
+
+static unsigned long HostToIP(const std::string& host)
+{
+ std::string ip;
+ CDNSNameCache::Lookup(host, ip);
+ return inet_addr(ip.c_str());
+}
+
+#define LOCALIZED(id) g_localizeStrings.Get(id)
+
+static void ShowDiscoveryMessage(const char* function, const char* server_name, bool new_entry)
+{
+ std::string message;
+
+ if (new_entry)
+ {
+ CLog::Log(LOGINFO, "{} - Create new entry for host '{}'", function, server_name);
+ message = StringUtils::Format(LOCALIZED(13035), server_name);
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "{} - Update existing entry for host '{}'", function, server_name);
+ message = StringUtils::Format(LOCALIZED(13034), server_name);
+ }
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, LOCALIZED(13033), message, 4000, true, 3000);
+}
+
+struct UPnPServer
+{
+ UPnPServer() : m_nextWake(CDateTime::GetCurrentDateTime()) {}
+ bool operator == (const UPnPServer& server) const { return server.m_uuid == m_uuid; }
+ bool operator != (const UPnPServer& server) const { return !(*this == server); }
+ bool operator == (const std::string& server_uuid) const { return server_uuid == m_uuid; }
+ bool operator != (const std::string& server_uuid) const { return !(*this == server_uuid); }
+ std::string m_name;
+ std::string m_uuid;
+ std::string m_mac;
+ CDateTime m_nextWake;
+};
+
+static UPnPServer* LookupUPnPServer(std::vector<UPnPServer>& list, const std::string& uuid)
+{
+ auto serverIt = find(list.begin(), list.end(), uuid);
+
+ return serverIt != list.end() ? &(*serverIt) : nullptr;
+}
+
+static void AddOrUpdateUPnPServer(std::vector<UPnPServer>& list, const UPnPServer& server)
+{
+ auto serverIt = find(list.begin(), list.end(), server);
+
+ bool addNewEntry = serverIt == list.end();
+
+ if (addNewEntry)
+ list.push_back(server); // add server
+ else
+ *serverIt = server; // update existing server
+
+ ShowDiscoveryMessage(__FUNCTION__, server.m_name.c_str(), addNewEntry);
+}
+
+static void AddMatchingUPnPServers(std::vector<UPnPServer>& list, const std::string& host, const std::string& mac, const CDateTimeSpan& wakeupDelay)
+{
+#ifdef HAS_UPNP
+ while (CDateTime::GetCurrentDateTime() < upnpInitReady)
+ KODI::TIME::Sleep(1s);
+
+ PLT_SyncMediaBrowser* browser = UPNP::CUPnP::GetInstance()->m_MediaBrowser;
+
+ if (browser)
+ {
+ UPnPServer server;
+ server.m_nextWake += wakeupDelay;
+
+ for (NPT_List<PLT_DeviceDataReference>::Iterator device = browser->GetMediaServers().GetFirstItem(); device; ++device)
+ {
+ if (host == (const char*) (*device)->GetURLBase().GetHost())
+ {
+ server.m_name = (*device)->GetFriendlyName();
+ server.m_uuid = (*device)->GetUUID();
+ server.m_mac = mac;
+
+ AddOrUpdateUPnPServer(list, server);
+ }
+ }
+ }
+#endif
+}
+
+static std::string LookupUPnPHost(const std::string& uuid)
+{
+#ifdef HAS_UPNP
+ UPNP::CUPnP* upnp = UPNP::CUPnP::GetInstance();
+
+ if (!upnp->IsClientStarted())
+ {
+ upnp->StartClient();
+
+ upnpInitReady = CDateTime::GetCurrentDateTime() + CDateTimeSpan(0, 0, 0, 10);
+ }
+
+ PLT_SyncMediaBrowser* browser = upnp->m_MediaBrowser;
+
+ PLT_DeviceDataReference device;
+
+ if (browser && NPT_SUCCEEDED(browser->FindServer(uuid.c_str(), device)) && !device.IsNull())
+ return (const char*)device->GetURLBase().GetHost();
+#endif
+
+ return "";
+}
+
+CWakeOnAccess::WakeUpEntry::WakeUpEntry (bool isAwake)
+ : timeout (0, 0, 0, DEFAULT_TIMEOUT_SEC)
+ , wait_online1_sec(DEFAULT_WAIT_FOR_ONLINE_SEC_1)
+ , wait_online2_sec(DEFAULT_WAIT_FOR_ONLINE_SEC_2)
+ , wait_services_sec(DEFAULT_WAIT_FOR_SERVICES_SEC)
+{
+ nextWake = CDateTime::GetCurrentDateTime();
+
+ if (isAwake)
+ nextWake += timeout;
+}
+
+//**
+
+class CMACDiscoveryJob : public CJob
+{
+public:
+ explicit CMACDiscoveryJob(const std::string& host) : m_host(host) {}
+
+ bool DoWork() override;
+
+ const std::string& GetMAC() const { return m_macAddress; }
+ const std::string& GetHost() const { return m_host; }
+
+private:
+ std::string m_macAddress;
+ std::string m_host;
+};
+
+bool CMACDiscoveryJob::DoWork()
+{
+ unsigned long ipAddress = HostToIP(m_host);
+
+ if (ipAddress == INADDR_NONE)
+ {
+ CLog::Log(LOGERROR, "{} - can't determine ip of '{}'", __FUNCTION__, m_host);
+ return false;
+ }
+
+ const std::vector<CNetworkInterface*>& ifaces = CServiceBroker::GetNetwork().GetInterfaceList();
+ for (const auto& it : ifaces)
+ {
+ if (it->GetHostMacAddress(ipAddress, m_macAddress))
+ return true;
+ }
+
+ return false;
+}
+
+//**
+
+class WaitCondition
+{
+public:
+ virtual ~WaitCondition() = default;
+ virtual bool SuccessWaiting () const { return false; }
+};
+
+//
+
+class NestDetect
+{
+public:
+ NestDetect() : m_gui_thread(CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ if (m_gui_thread)
+ ++m_nest;
+ }
+ ~NestDetect()
+ {
+ if (m_gui_thread)
+ m_nest--;
+ }
+ static int Level()
+ {
+ return m_nest;
+ }
+ bool IsNested() const
+ {
+ return m_gui_thread && m_nest > 1;
+ }
+
+private:
+ static int m_nest;
+ const bool m_gui_thread;
+};
+int NestDetect::m_nest = 0;
+
+//
+
+class ProgressDialogHelper
+{
+public:
+ explicit ProgressDialogHelper (const std::string& heading) : m_dialog(0)
+ {
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ m_dialog = gui->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ }
+
+ if (m_dialog)
+ {
+ m_dialog->SetHeading(CVariant{heading});
+ m_dialog->SetLine(0, CVariant{""});
+ m_dialog->SetLine(1, CVariant{""});
+ m_dialog->SetLine(2, CVariant{""});
+ }
+ }
+ ~ProgressDialogHelper ()
+ {
+ if (m_dialog)
+ m_dialog->Close();
+ }
+
+ bool HasDialog() const { return m_dialog != 0; }
+
+ enum wait_result { TimedOut, Canceled, Success };
+
+ wait_result ShowAndWait (const WaitCondition& waitObj, unsigned timeOutSec, const std::string& line1)
+ {
+ auto timeOutMs = std::chrono::milliseconds(timeOutSec * 1000);
+
+ if (m_dialog)
+ {
+ m_dialog->SetLine(0, CVariant{line1});
+
+ m_dialog->SetPercentage(1); // avoid flickering by starting at 1% ..
+ }
+
+ XbmcThreads::EndTime<> end_time(timeOutMs);
+
+ while (!end_time.IsTimePast())
+ {
+ if (waitObj.SuccessWaiting())
+ return Success;
+
+ if (m_dialog)
+ {
+ if (!m_dialog->IsActive())
+ m_dialog->Open();
+
+ if (m_dialog->IsCanceled())
+ return Canceled;
+
+ m_dialog->Progress();
+
+ auto ms_passed = timeOutMs - end_time.GetTimeLeft();
+
+ int percentage = (ms_passed.count() * 100) / timeOutMs.count();
+ m_dialog->SetPercentage(std::max(percentage, 1)); // avoid flickering , keep minimum 1%
+ }
+
+ KODI::TIME::Sleep(m_dialog ? 20ms : 200ms);
+ }
+
+ return TimedOut;
+ }
+
+private:
+ CGUIDialogProgress* m_dialog;
+};
+
+class NetworkStartWaiter : public WaitCondition
+{
+public:
+ NetworkStartWaiter (unsigned settle_time_ms, const std::string& host) : m_settle_time_ms (settle_time_ms), m_host(host)
+ {
+ }
+ bool SuccessWaiting () const override
+ {
+ unsigned long address = ntohl(HostToIP(m_host));
+ bool online = CServiceBroker::GetNetwork().HasInterfaceForIP(address);
+
+ if (!online) // setup endtime so we dont return true until network is consistently connected
+ m_end.Set(std::chrono::milliseconds(m_settle_time_ms));
+
+ return online && m_end.IsTimePast();
+ }
+private:
+ mutable XbmcThreads::EndTime<> m_end;
+ unsigned m_settle_time_ms;
+ const std::string m_host;
+};
+
+class PingResponseWaiter : public WaitCondition, private IJobCallback
+{
+public:
+ PingResponseWaiter (bool async, const CWakeOnAccess::WakeUpEntry& server)
+ : m_server(server), m_jobId(0), m_hostOnline(false)
+ {
+ if (async)
+ {
+ CJob* job = new CHostProberJob(server);
+ m_jobId = CServiceBroker::GetJobManager()->AddJob(job, this);
+ }
+ }
+ ~PingResponseWaiter() override { CServiceBroker::GetJobManager()->CancelJob(m_jobId); }
+ bool SuccessWaiting () const override
+ {
+ return m_jobId ? m_hostOnline : Ping(m_server);
+ }
+
+ void OnJobComplete(unsigned int jobID, bool success, CJob *job) override
+ {
+ m_hostOnline = success;
+ }
+
+ static bool Ping(const CWakeOnAccess::WakeUpEntry& server, unsigned timeOutMs = 2000)
+ {
+ if (server.upnpUuid.empty())
+ {
+ unsigned long dst_ip = HostToIP(server.host);
+
+ return CServiceBroker::GetNetwork().PingHost(dst_ip, server.ping_port, timeOutMs, server.ping_mode & 1);
+ }
+ else // upnp mode
+ {
+ std::string host = LookupUPnPHost(server.upnpUuid);
+
+ if (host.empty())
+ {
+ KODI::TIME::Sleep(std::chrono::milliseconds(timeOutMs));
+
+ host = LookupUPnPHost(server.upnpUuid);
+ }
+
+ return !host.empty();
+ }
+ }
+
+private:
+ class CHostProberJob : public CJob
+ {
+ public:
+ explicit CHostProberJob(const CWakeOnAccess::WakeUpEntry& server) : m_server (server) {}
+
+ bool DoWork() override
+ {
+ while (!ShouldCancel(0,0))
+ {
+ if (PingResponseWaiter::Ping(m_server))
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ const CWakeOnAccess::WakeUpEntry& m_server;
+ };
+
+ const CWakeOnAccess::WakeUpEntry& m_server;
+ unsigned int m_jobId;
+ bool m_hostOnline;
+};
+
+//
+
+CWakeOnAccess::CWakeOnAccess()
+ : m_netinit_sec(DEFAULT_NETWORK_INIT_SEC) // wait for network to connect
+ , m_netsettle_ms(DEFAULT_NETWORK_SETTLE_MS) // wait for network to settle
+{
+}
+
+CWakeOnAccess &CWakeOnAccess::GetInstance()
+{
+ static CWakeOnAccess sWakeOnAccess;
+ return sWakeOnAccess;
+}
+
+bool CWakeOnAccess::WakeUpHost(const CURL& url)
+{
+ const std::string& hostName = url.GetHostName();
+
+ if (!hostName.empty())
+ return WakeUpHost(hostName, url.Get(), url.IsProtocol("upnp"));
+
+ return true;
+}
+
+bool CWakeOnAccess::WakeUpHost(const std::string& hostName, const std::string& customMessage)
+{
+ return WakeUpHost(hostName, customMessage, false);
+}
+
+bool CWakeOnAccess::WakeUpHost(const std::string& hostName, const std::string& customMessage, bool upnpMode)
+{
+ if (!IsEnabled())
+ return true; // bail if feature is turned off
+
+ WakeUpEntry server;
+
+ if (FindOrTouchHostEntry(hostName, upnpMode, server))
+ {
+ CLog::Log(LOGINFO, "WakeOnAccess [{}] triggered by accessing : {}", server.friendlyName,
+ customMessage);
+
+ NestDetect nesting ; // detect recursive calls on gui thread..
+
+ if (nesting.IsNested()) // we might get in trouble if it gets called back in loop
+ CLog::Log(LOGWARNING, "WakeOnAccess recursively called on gui-thread [{}]",
+ NestDetect::Level());
+
+ bool ret = WakeUpHost(server);
+
+ if (!ret) // extra log if we fail for some reason
+ CLog::Log(LOGWARNING, "WakeOnAccess failed to bring up [{}] - there may be trouble ahead !",
+ server.friendlyName);
+
+ TouchHostEntry(hostName, upnpMode);
+
+ return ret;
+ }
+ return true;
+}
+
+bool CWakeOnAccess::WakeUpHost(const WakeUpEntry& server)
+{
+ std::string heading = StringUtils::Format(LOCALIZED(13027), server.friendlyName);
+
+ ProgressDialogHelper dlg (heading);
+
+ {
+ NetworkStartWaiter waitObj (m_netsettle_ms, server.host); // wait until network connected before sending wake-on-lan
+
+ if (dlg.ShowAndWait (waitObj, m_netinit_sec, LOCALIZED(13028)) != ProgressDialogHelper::Success)
+ {
+ if (CServiceBroker::GetNetwork().IsConnected() && HostToIP(server.host) == INADDR_NONE)
+ {
+ // network connected (at least one interface) but dns-lookup failed (host by name, not ip-address), so dont abort yet
+ CLog::Log(LOGWARNING, "WakeOnAccess timeout/cancel while waiting for network (proceeding anyway)");
+ }
+ else
+ {
+ CLog::Log(LOGINFO, "WakeOnAccess timeout/cancel while waiting for network");
+ return false; // timedout or canceled ; give up
+ }
+ }
+ }
+
+ if (PingResponseWaiter::Ping(server, 500)) // quick ping with short timeout to not block too long
+ {
+ CLog::Log(LOGINFO, "WakeOnAccess success exit, server already running");
+ return true;
+ }
+
+ if (!CServiceBroker::GetNetwork().WakeOnLan(server.mac.c_str()))
+ {
+ CLog::Log(LOGERROR,"WakeOnAccess failed to send. (Is it blocked by firewall?)");
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread() || !appPlayer->IsPlaying())
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, LOCALIZED(13029));
+ return false;
+ }
+
+ {
+ PingResponseWaiter waitObj (dlg.HasDialog(), server); // wait for ping response ..
+
+ ProgressDialogHelper::wait_result
+ result = dlg.ShowAndWait (waitObj, server.wait_online1_sec, LOCALIZED(13030));
+
+ if (result == ProgressDialogHelper::TimedOut)
+ result = dlg.ShowAndWait (waitObj, server.wait_online2_sec, LOCALIZED(13031));
+
+ if (result != ProgressDialogHelper::Success)
+ {
+ CLog::Log(LOGINFO, "WakeOnAccess timeout/cancel while waiting for response");
+ return false; // timedout or canceled
+ }
+ }
+
+ // we have ping response ; just add extra wait-for-services before returning if requested
+
+ {
+ WaitCondition waitObj ; // wait uninterruptible fixed time for services ..
+
+ dlg.ShowAndWait (waitObj, server.wait_services_sec, LOCALIZED(13032));
+
+ CLog::Log(LOGINFO, "WakeOnAccess sequence completed, server started");
+ }
+ return true;
+}
+
+bool CWakeOnAccess::FindOrTouchHostEntry(const std::string& hostName, bool upnpMode, WakeUpEntry& result)
+{
+ std::unique_lock<CCriticalSection> lock(m_entrylist_protect);
+
+ bool need_wakeup = false;
+
+ UPnPServer* upnp = upnpMode ? LookupUPnPServer(m_UPnPServers, hostName) : nullptr;
+
+ for (auto& server : m_entries)
+ {
+ if (upnp ? StringUtils::EqualsNoCase(upnp->m_mac, server.mac) : StringUtils::EqualsNoCase(hostName, server.host))
+ {
+ CDateTime now = CDateTime::GetCurrentDateTime();
+
+ if (now >= (upnp ? upnp->m_nextWake : server.nextWake))
+ {
+ result = server;
+
+ result.friendlyName = upnp ? upnp->m_name : server.host;
+
+ if (upnp)
+ result.upnpUuid = upnp->m_uuid;
+
+ need_wakeup = true;
+ }
+ else // 'touch' next wakeup time
+ {
+ server.nextWake = now + server.timeout;
+
+ if (upnp)
+ upnp->m_nextWake = server.nextWake;
+ }
+
+ break;
+ }
+ }
+
+ return need_wakeup;
+}
+
+void CWakeOnAccess::TouchHostEntry(const std::string& hostName, bool upnpMode)
+{
+ std::unique_lock<CCriticalSection> lock(m_entrylist_protect);
+
+ UPnPServer* upnp = upnpMode ? LookupUPnPServer(m_UPnPServers, hostName) : nullptr;
+
+ for (auto& server : m_entries)
+ {
+ if (upnp ? StringUtils::EqualsNoCase(upnp->m_mac, server.mac) : StringUtils::EqualsNoCase(hostName, server.host))
+ {
+ server.nextWake = CDateTime::GetCurrentDateTime() + server.timeout;
+
+ if (upnp)
+ upnp->m_nextWake = server.nextWake;
+
+ return;
+ }
+ }
+}
+
+static void AddHost (const std::string& host, std::vector<std::string>& hosts)
+{
+ for (const auto& it : hosts)
+ if (StringUtils::EqualsNoCase(host, it))
+ return; // already there ..
+
+ if (!host.empty())
+ hosts.push_back(host);
+}
+
+static void AddHostFromDatabase(const DatabaseSettings& setting, std::vector<std::string>& hosts)
+{
+ if (StringUtils::EqualsNoCase(setting.type, "mysql"))
+ AddHost(setting.host, hosts);
+}
+
+void CWakeOnAccess::QueueMACDiscoveryForHost(const std::string& host)
+{
+ if (IsEnabled())
+ {
+ if (URIUtils::IsHostOnLAN(host, true))
+ CServiceBroker::GetJobManager()->AddJob(new CMACDiscoveryJob(host), this);
+ else
+ CLog::Log(LOGINFO, "{} - skip Mac discovery for non-local host '{}'", __FUNCTION__, host);
+ }
+}
+
+static void AddHostsFromMediaSource(const CMediaSource& source, std::vector<std::string>& hosts)
+{
+ for (const auto& it : source.vecPaths)
+ {
+ CURL url(it);
+
+ std::string host_name = url.GetHostName();
+
+ if (url.IsProtocol("upnp"))
+ host_name = LookupUPnPHost(host_name);
+
+ AddHost(host_name, hosts);
+ }
+}
+
+static void AddHostsFromVecSource(const VECSOURCES& sources, std::vector<std::string>& hosts)
+{
+ for (const auto& it : sources)
+ AddHostsFromMediaSource(it, hosts);
+}
+
+static void AddHostsFromVecSource(const VECSOURCES* sources, std::vector<std::string>& hosts)
+{
+ if (sources)
+ AddHostsFromVecSource(*sources, hosts);
+}
+
+void CWakeOnAccess::QueueMACDiscoveryForAllRemotes()
+{
+ std::vector<std::string> hosts;
+
+ // add media sources
+ CMediaSourceSettings& ms = CMediaSourceSettings::GetInstance();
+
+ AddHostsFromVecSource(ms.GetSources("video"), hosts);
+ AddHostsFromVecSource(ms.GetSources("music"), hosts);
+ AddHostsFromVecSource(ms.GetSources("files"), hosts);
+ AddHostsFromVecSource(ms.GetSources("pictures"), hosts);
+ AddHostsFromVecSource(ms.GetSources("programs"), hosts);
+
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // add mysql servers
+ AddHostFromDatabase(advancedSettings->m_databaseVideo, hosts);
+ AddHostFromDatabase(advancedSettings->m_databaseMusic, hosts);
+ AddHostFromDatabase(advancedSettings->m_databaseEpg, hosts);
+ AddHostFromDatabase(advancedSettings->m_databaseTV, hosts);
+
+ // add from path substitutions ..
+ for (const auto& pathPair : advancedSettings->m_pathSubstitutions)
+ {
+ CURL url(pathPair.second);
+ AddHost (url.GetHostName(), hosts);
+ }
+
+ for (const std::string& host : hosts)
+ QueueMACDiscoveryForHost(host);
+}
+
+void CWakeOnAccess::SaveMACDiscoveryResult(const std::string& host, const std::string& mac)
+{
+ CLog::Log(LOGINFO, "{} - Mac discovered for host '{}' -> '{}'", __FUNCTION__, host, mac);
+
+ for (auto& i : m_entries)
+ {
+ if (StringUtils::EqualsNoCase(host, i.host))
+ {
+ i.mac = mac;
+ ShowDiscoveryMessage(__FUNCTION__, host.c_str(), false);
+
+ AddMatchingUPnPServers(m_UPnPServers, host, mac, i.timeout);
+ SaveToXML();
+ return;
+ }
+ }
+
+ // not found entry to update - create using default values
+ WakeUpEntry entry (true);
+ entry.host = host;
+ entry.mac = mac;
+ m_entries.push_back(entry);
+ ShowDiscoveryMessage(__FUNCTION__, host.c_str(), true);
+
+ AddMatchingUPnPServers(m_UPnPServers, host, mac, entry.timeout);
+ SaveToXML();
+}
+
+void CWakeOnAccess::OnJobComplete(unsigned int jobID, bool success, CJob *job)
+{
+ CMACDiscoveryJob* discoverJob = static_cast<CMACDiscoveryJob*>(job);
+
+ const std::string& host = discoverJob->GetHost();
+ const std::string& mac = discoverJob->GetMAC();
+
+ if (success)
+ {
+ std::unique_lock<CCriticalSection> lock(m_entrylist_protect);
+
+ SaveMACDiscoveryResult(host, mac);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "{} - Mac discovery failed for host '{}'", __FUNCTION__, host);
+
+ if (IsEnabled())
+ {
+ const std::string& heading = LOCALIZED(13033);
+ std::string message = StringUtils::Format(LOCALIZED(13036), host);
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, message, 4000, true, 3000);
+ }
+ }
+}
+
+void CWakeOnAccess::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_POWERMANAGEMENT_WAKEONACCESS)
+ {
+ bool enabled = std::static_pointer_cast<const CSettingBool>(setting)->GetValue();
+
+ SetEnabled(enabled);
+
+ if (enabled)
+ QueueMACDiscoveryForAllRemotes();
+ }
+}
+
+std::string CWakeOnAccess::GetSettingFile()
+{
+ return CSpecialProtocol::TranslatePath("special://profile/wakeonlan.xml");
+}
+
+void CWakeOnAccess::OnSettingsLoaded()
+{
+ std::unique_lock<CCriticalSection> lock(m_entrylist_protect);
+
+ LoadFromXML();
+}
+
+void CWakeOnAccess::SetEnabled(bool enabled)
+{
+ m_enabled = enabled;
+
+ CLog::Log(LOGINFO, "WakeOnAccess - Enabled:{}", m_enabled ? "TRUE" : "FALSE");
+}
+
+void CWakeOnAccess::LoadFromXML()
+{
+ bool enabled = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_POWERMANAGEMENT_WAKEONACCESS);
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(GetSettingFile()))
+ {
+ if (enabled)
+ CLog::Log(LOGINFO, "{} - unable to load:{}", __FUNCTION__, GetSettingFile());
+ return;
+ }
+
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (StringUtils::CompareNoCase(pRootElement->Value(), "onaccesswakeup"))
+ {
+ CLog::Log(LOGERROR, "{} - XML file {} doesn't contain <onaccesswakeup>", __FUNCTION__,
+ GetSettingFile());
+ return;
+ }
+
+ m_entries.clear();
+
+ CLog::Log(LOGINFO, "WakeOnAccess - Load settings :");
+
+ SetEnabled(enabled);
+
+ int tmp;
+ if (XMLUtils::GetInt(pRootElement, "netinittimeout", tmp, 0, 5 * 60))
+ m_netinit_sec = tmp;
+ CLog::Log(LOGINFO, " -Network init timeout : [{}] sec", m_netinit_sec);
+
+ if (XMLUtils::GetInt(pRootElement, "netsettletime", tmp, 0, 5 * 1000))
+ m_netsettle_ms = tmp;
+ CLog::Log(LOGINFO, " -Network settle time : [{}] ms", m_netsettle_ms);
+
+ const TiXmlNode* pWakeUp = pRootElement->FirstChildElement("wakeup");
+ while (pWakeUp)
+ {
+ WakeUpEntry entry;
+
+ std::string strtmp;
+ if (XMLUtils::GetString(pWakeUp, "host", strtmp))
+ entry.host = strtmp;
+
+ if (XMLUtils::GetString(pWakeUp, "mac", strtmp))
+ entry.mac = strtmp;
+
+ if (entry.host.empty())
+ CLog::Log(LOGERROR, "{} - Missing <host> tag or it's empty", __FUNCTION__);
+ else if (entry.mac.empty())
+ CLog::Log(LOGERROR, "{} - Missing <mac> tag or it's empty", __FUNCTION__);
+ else
+ {
+ if (XMLUtils::GetInt(pWakeUp, "pingport", tmp, 0, USHRT_MAX))
+ entry.ping_port = (unsigned short) tmp;
+
+ if (XMLUtils::GetInt(pWakeUp, "pingmode", tmp, 0, USHRT_MAX))
+ entry.ping_mode = (unsigned short) tmp;
+
+ if (XMLUtils::GetInt(pWakeUp, "timeout", tmp, 10, 12 * 60 * 60))
+ entry.timeout.SetDateTimeSpan (0, 0, 0, tmp);
+
+ if (XMLUtils::GetInt(pWakeUp, "waitonline", tmp, 0, 10 * 60)) // max 10 minutes
+ entry.wait_online1_sec = tmp;
+
+ if (XMLUtils::GetInt(pWakeUp, "waitonline2", tmp, 0, 10 * 60)) // max 10 minutes
+ entry.wait_online2_sec = tmp;
+
+ if (XMLUtils::GetInt(pWakeUp, "waitservices", tmp, 0, 5 * 60)) // max 5 minutes
+ entry.wait_services_sec = tmp;
+
+ CLog::Log(LOGINFO, " Registering wakeup entry:");
+ CLog::Log(LOGINFO, " HostName : {}", entry.host);
+ CLog::Log(LOGINFO, " MacAddress : {}", entry.mac);
+ CLog::Log(LOGINFO, " PingPort : {}", entry.ping_port);
+ CLog::Log(LOGINFO, " PingMode : {}", entry.ping_mode);
+ CLog::Log(LOGINFO, " Timeout : {} (sec)", GetTotalSeconds(entry.timeout));
+ CLog::Log(LOGINFO, " WaitForOnline : {} (sec)", entry.wait_online1_sec);
+ CLog::Log(LOGINFO, " WaitForOnlineEx : {} (sec)", entry.wait_online2_sec);
+ CLog::Log(LOGINFO, " WaitForServices : {} (sec)", entry.wait_services_sec);
+
+ m_entries.push_back(entry);
+ }
+
+ pWakeUp = pWakeUp->NextSiblingElement("wakeup"); // get next one
+ }
+
+ // load upnp server map
+ m_UPnPServers.clear();
+
+ const TiXmlNode* pUPnPNode = pRootElement->FirstChildElement("upnp_map");
+ while (pUPnPNode)
+ {
+ UPnPServer server;
+
+ XMLUtils::GetString(pUPnPNode, "name", server.m_name);
+ XMLUtils::GetString(pUPnPNode, "uuid", server.m_uuid);
+ XMLUtils::GetString(pUPnPNode, "mac", server.m_mac);
+
+ if (server.m_name.empty())
+ server.m_name = server.m_uuid;
+
+ if (server.m_uuid.empty() || server.m_mac.empty())
+ CLog::Log(LOGERROR, "{} - Missing or empty <upnp_map> entry", __FUNCTION__);
+ else
+ {
+ CLog::Log(LOGINFO, " Registering upnp_map entry [{} : {}] -> [{}]", server.m_name,
+ server.m_uuid, server.m_mac);
+
+ m_UPnPServers.push_back(server);
+ }
+
+ pUPnPNode = pUPnPNode->NextSiblingElement("upnp_map"); // get next one
+ }
+}
+
+void CWakeOnAccess::SaveToXML()
+{
+ CXBMCTinyXML xmlDoc;
+ TiXmlElement xmlRootElement("onaccesswakeup");
+ TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement);
+ if (!pRoot) return;
+
+ XMLUtils::SetInt(pRoot, "netinittimeout", m_netinit_sec);
+ XMLUtils::SetInt(pRoot, "netsettletime", m_netsettle_ms);
+
+ for (const auto& i : m_entries)
+ {
+ TiXmlElement xmlSetting("wakeup");
+ TiXmlNode* pWakeUpNode = pRoot->InsertEndChild(xmlSetting);
+ if (pWakeUpNode)
+ {
+ XMLUtils::SetString(pWakeUpNode, "host", i.host);
+ XMLUtils::SetString(pWakeUpNode, "mac", i.mac);
+ XMLUtils::SetInt(pWakeUpNode, "pingport", i.ping_port);
+ XMLUtils::SetInt(pWakeUpNode, "pingmode", i.ping_mode);
+ XMLUtils::SetInt(pWakeUpNode, "timeout", GetTotalSeconds(i.timeout));
+ XMLUtils::SetInt(pWakeUpNode, "waitonline", i.wait_online1_sec);
+ XMLUtils::SetInt(pWakeUpNode, "waitonline2", i.wait_online2_sec);
+ XMLUtils::SetInt(pWakeUpNode, "waitservices", i.wait_services_sec);
+ }
+ }
+
+ for (const auto& upnp : m_UPnPServers)
+ {
+ TiXmlElement xmlSetting("upnp_map");
+ TiXmlNode* pUPnPNode = pRoot->InsertEndChild(xmlSetting);
+ if (pUPnPNode)
+ {
+ XMLUtils::SetString(pUPnPNode, "name", upnp.m_name);
+ XMLUtils::SetString(pUPnPNode, "uuid", upnp.m_uuid);
+ XMLUtils::SetString(pUPnPNode, "mac", upnp.m_mac);
+ }
+ }
+
+ xmlDoc.SaveFile(GetSettingFile());
+}