Merge pull request #4196 from arnova/sub_fallback
[vuplus_xbmc] / xbmc / network / WakeOnAccess.cpp
1 /*
2  *      Copyright (C) 2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include <limits.h>
22 #include <netinet/in.h>
23 #include <sys/socket.h>
24 #include <arpa/inet.h>
25
26 #include "system.h"
27 #include "network/Network.h"
28 #include "Application.h"
29 #include "DNSNameCache.h"
30 #include "dialogs/GUIDialogProgress.h"
31 #include "dialogs/GUIDialogKaiToast.h"
32 #include "filesystem/SpecialProtocol.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "settings/AdvancedSettings.h"
36 #include "settings/Settings.h"
37 #include "settings/MediaSourceSettings.h"
38 #include "utils/JobManager.h"
39 #include "utils/log.h"
40 #include "utils/XMLUtils.h"
41 #include "utils/URIUtils.h"
42 #include "utils/StringUtils.h"
43
44 #include "WakeOnAccess.h"
45
46 using namespace std;
47
48 #define DEFAULT_NETWORK_INIT_SEC      (20)   // wait 20 sec for network after startup or resume
49 #define DEFAULT_NETWORK_SETTLE_MS     (500)  // require 500ms of consistent network availability before trusting it
50
51 #define DEFAULT_TIMEOUT_SEC (5*60)           // at least 5 minutes between each magic packets
52 #define DEFAULT_WAIT_FOR_ONLINE_SEC_1 (40)   // wait at 40 seconds after sending magic packet
53 #define DEFAULT_WAIT_FOR_ONLINE_SEC_2 (40)   // same for extended wait
54 #define DEFAULT_WAIT_FOR_SERVICES_SEC (5)    // wait 5 seconds after host go online to launch file sharing deamons
55
56 static int GetTotalSeconds(const CDateTimeSpan& ts)
57 {
58   int hours = ts.GetHours() + ts.GetDays() * 24;
59   int minutes = ts.GetMinutes() + hours * 60;
60   return ts.GetSeconds() + minutes * 60;
61 }
62
63 static unsigned long HostToIP(const CStdString& host)
64 {
65   CStdString ip;
66   CDNSNameCache::Lookup(host, ip);
67   return inet_addr(ip.c_str());
68 }
69
70 CWakeOnAccess::WakeUpEntry::WakeUpEntry (bool isAwake)
71   : timeout (0, 0, 0, DEFAULT_TIMEOUT_SEC)
72   , wait_online1_sec(DEFAULT_WAIT_FOR_ONLINE_SEC_1)
73   , wait_online2_sec(DEFAULT_WAIT_FOR_ONLINE_SEC_2)
74   , wait_services_sec(DEFAULT_WAIT_FOR_SERVICES_SEC)
75   , ping_port(0), ping_mode(0)
76 {
77   nextWake = CDateTime::GetCurrentDateTime();
78
79   if (isAwake)
80     nextWake += timeout;
81 }
82
83 //**
84
85 class CMACDiscoveryJob : public CJob
86 {
87 public:
88   CMACDiscoveryJob(const CStdString& host) : m_host(host) {}
89
90   virtual bool DoWork();
91
92   const CStdString& GetMAC() const { return m_macAddres; }
93   const CStdString& GetHost() const { return m_host; }
94
95 private:
96   CStdString m_macAddres;
97   CStdString m_host;
98 };
99
100 bool CMACDiscoveryJob::DoWork()
101 {
102   unsigned long ipAddress = HostToIP(m_host);
103
104   if (ipAddress == INADDR_NONE)
105   {
106     CLog::Log(LOGERROR, "%s - can't determine ip of '%s'", __FUNCTION__, m_host.c_str());
107     return false;
108   }
109
110   vector<CNetworkInterface*>& ifaces = g_application.getNetwork().GetInterfaceList();
111   for (vector<CNetworkInterface*>::const_iterator it = ifaces.begin(); it != ifaces.end(); ++it)
112   {
113     if ((*it)->GetHostMacAddress(ipAddress, m_macAddres))
114       return true;
115   }
116
117   return false;
118 }
119
120 //**
121
122 class WaitCondition
123 {
124 public:
125   virtual bool SuccessWaiting () const { return false; }
126 };
127
128 //
129
130 class NestDetect
131 {
132 public:
133   NestDetect() : m_gui_thread (g_application.IsCurrentThread())
134   {
135     if (m_gui_thread)
136       ++m_nest;
137   }
138   ~NestDetect()
139   {
140     if (m_gui_thread)
141       m_nest--;
142   }
143   static int Level()
144   {
145     return m_nest;
146   }
147   bool IsNested() const
148   {
149     return m_gui_thread && m_nest > 1;
150   }
151
152 private:
153   static int m_nest;
154   const bool m_gui_thread;
155 };
156 int NestDetect::m_nest = 0;
157
158 //
159
160 class ProgressDialogHelper
161 {
162 public:
163   ProgressDialogHelper (const CStdString& heading) : m_dialog(0)
164   {
165     if (g_application.IsCurrentThread())
166       m_dialog = (CGUIDialogProgress*) g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
167
168     if (m_dialog)
169     {
170       m_dialog->SetHeading (heading); 
171       m_dialog->SetLine(0, "");
172       m_dialog->SetLine(1, "");
173       m_dialog->SetLine(2, "");
174
175       int nest_level = NestDetect::Level();
176       if (nest_level > 1)
177       {
178         CStdString nest = StringUtils::Format("Nesting:%d", nest_level);
179         m_dialog->SetLine(2, nest);
180       }
181     }
182   }
183   ~ProgressDialogHelper ()
184   {
185     if (m_dialog)
186       m_dialog->Close();
187   }
188
189   bool HasDialog() const { return m_dialog != 0; }
190
191   enum wait_result { TimedOut, Canceled, Success };
192
193   wait_result ShowAndWait (const WaitCondition& waitObj, unsigned timeOutSec, const CStdString& line1)
194   {
195     unsigned timeOutMs = timeOutSec * 1000;
196
197     if (m_dialog)
198     {
199       m_dialog->SetLine(1, line1);
200
201       m_dialog->SetPercentage(1); // avoid flickering by starting at 1% ..
202     }
203
204     XbmcThreads::EndTime end_time (timeOutMs);
205
206     while (!end_time.IsTimePast())
207     {
208       if (waitObj.SuccessWaiting())
209         return Success;
210             
211       if (m_dialog)
212       {
213         if (!m_dialog->IsActive())
214           m_dialog->StartModal();
215
216         if (m_dialog->IsCanceled())
217           return Canceled;
218
219         m_dialog->Progress();
220
221         unsigned ms_passed = timeOutMs - end_time.MillisLeft();
222
223         int percentage = (ms_passed * 100) / timeOutMs;
224         m_dialog->SetPercentage(max(percentage, 1)); // avoid flickering , keep minimum 1%
225       }
226
227       Sleep (m_dialog ? 20 : 200);
228     }
229
230     return TimedOut;
231   }
232
233 private:
234   CGUIDialogProgress* m_dialog;
235 };
236
237 class NetworkStartWaiter : public WaitCondition
238 {
239 public:
240   NetworkStartWaiter (unsigned settle_time_ms, const CStdString& host) : m_settle_time_ms (settle_time_ms), m_host(host)
241   {
242   }
243   virtual bool SuccessWaiting () const
244   {
245     unsigned long address = ntohl(HostToIP(m_host));
246     bool online = g_application.getNetwork().HasInterfaceForIP(address);
247
248     if (!online) // setup endtime so we dont return true until network is consistently connected
249       m_end.Set (m_settle_time_ms);
250
251     return online && m_end.IsTimePast();
252   }
253 private:
254   mutable XbmcThreads::EndTime m_end;
255   unsigned m_settle_time_ms;
256   const CStdString m_host;
257 };
258
259 class PingResponseWaiter : public WaitCondition, private IJobCallback
260 {
261 public:
262   PingResponseWaiter (bool async, const CWakeOnAccess::WakeUpEntry& server) 
263     : m_server(server), m_jobId(0), m_hostOnline(false)
264   {
265     if (async)
266     {
267       CJob* job = new CHostProberJob(server);
268       m_jobId = CJobManager::GetInstance().AddJob(job, this);
269     }
270   }
271   ~PingResponseWaiter()
272   {
273     CJobManager::GetInstance().CancelJob(m_jobId);
274   }
275   virtual bool SuccessWaiting () const
276   {
277     return m_jobId ? m_hostOnline : Ping(m_server);
278   }
279
280   virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job)
281   {
282     m_hostOnline = success;
283   }
284
285   static bool Ping (const CWakeOnAccess::WakeUpEntry& server)
286   {
287     ULONG dst_ip = HostToIP(server.host);
288
289     return g_application.getNetwork().PingHost(dst_ip, server.ping_port, 2000, server.ping_mode & 1);
290   }
291
292 private:
293   class CHostProberJob : public CJob
294   {
295     public:
296       CHostProberJob(const CWakeOnAccess::WakeUpEntry& server) : m_server (server) {}
297
298       virtual bool DoWork()
299       {
300         while (!ShouldCancel(0,0))
301         {
302           if (PingResponseWaiter::Ping(m_server))
303             return true;
304         }
305         return false;
306       }
307
308     private:
309       const CWakeOnAccess::WakeUpEntry& m_server;
310   };
311
312   const CWakeOnAccess::WakeUpEntry& m_server;
313   unsigned int m_jobId;
314   bool m_hostOnline;
315 };
316
317 //
318
319 CWakeOnAccess::CWakeOnAccess()
320   : m_netinit_sec(DEFAULT_NETWORK_INIT_SEC)    // wait for network to connect
321   , m_netsettle_ms(DEFAULT_NETWORK_SETTLE_MS)  // wait for network to settle
322   , m_enabled(false)
323 {
324 }
325
326 CWakeOnAccess &CWakeOnAccess::Get()
327 {
328   static CWakeOnAccess sWakeOnAccess;
329   return sWakeOnAccess;
330 }
331
332 bool CWakeOnAccess::WakeUpHost(const CURL& url)
333 {
334   CStdString hostName = url.GetHostName();
335
336   if (!hostName.empty())
337     return WakeUpHost (hostName, url.Get());
338   return true;
339 }
340
341 bool CWakeOnAccess::WakeUpHost (const CStdString& hostName, const string& customMessage)
342 {
343   if (!IsEnabled())
344     return true; // bail if feature is turned off
345
346   WakeUpEntry server;
347
348   if (FindOrTouchHostEntry(hostName, server))
349   {
350     CLog::Log(LOGNOTICE,"WakeOnAccess [%s] trigged by accessing : %s", hostName.c_str(), customMessage.c_str());
351
352     NestDetect nesting ; // detect recursive calls on gui thread..
353
354     if (nesting.IsNested()) // we might get in trouble if it gets called back in loop
355       CLog::Log(LOGWARNING,"WakeOnAccess recursively called on gui-thread [%d]", NestDetect::Level());
356
357     bool ret = WakeUpHost(server);
358
359     if (!ret) // extra log if we fail for some reason
360       CLog::Log(LOGWARNING,"WakeOnAccess failed to bring up [%s] - there may be trouble ahead !", hostName.c_str());
361
362     TouchHostEntry(hostName);
363
364     return ret;
365   }
366   return true;
367 }
368
369 #define LOCALIZED(id) g_localizeStrings.Get(id)
370
371 bool CWakeOnAccess::WakeUpHost(const WakeUpEntry& server)
372 {
373   CStdString heading = StringUtils::Format(LOCALIZED(13027), server.host.c_str());
374
375   ProgressDialogHelper dlg (heading);
376
377   {
378     NetworkStartWaiter waitObj (m_netsettle_ms, server.host); // wait until network connected before sending wake-on-lan
379
380     if (dlg.ShowAndWait (waitObj, m_netinit_sec, LOCALIZED(13028)) != ProgressDialogHelper::Success)
381     {
382       CLog::Log(LOGNOTICE,"WakeOnAccess timeout/cancel while waiting for network");
383       return false; // timedout or canceled
384     }
385   }
386
387   {
388     ULONG dst_ip = HostToIP(server.host);
389
390     if (g_application.getNetwork().PingHost(dst_ip, server.ping_port, 500)) // quick ping with short timeout to not block too long
391     {
392       CLog::Log(LOGNOTICE,"WakeOnAccess success exit, server already running");
393       return true;
394     }
395   }
396
397   if (!g_application.getNetwork().WakeOnLan(server.mac.c_str()))
398   {
399     CLog::Log(LOGERROR,"WakeOnAccess failed to send. (Is it blocked by firewall?)");
400
401     if (g_application.IsCurrentThread() || !g_application.m_pPlayer->IsPlaying())
402       CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, LOCALIZED(13029));
403     return false;
404   }
405
406   {
407     PingResponseWaiter waitObj (dlg.HasDialog(), server); // wait for ping response ..
408
409     ProgressDialogHelper::wait_result 
410       result = dlg.ShowAndWait (waitObj, server.wait_online1_sec, LOCALIZED(13030));
411
412     if (result == ProgressDialogHelper::TimedOut)
413       result = dlg.ShowAndWait (waitObj, server.wait_online2_sec, LOCALIZED(13031));
414
415     if (result != ProgressDialogHelper::Success)
416     {
417       CLog::Log(LOGNOTICE,"WakeOnAccess timeout/cancel while waiting for response");
418       return false; // timedout or canceled
419     }
420   }
421
422   // we have ping response ; just add extra wait-for-services before returning if requested
423
424   {
425     WaitCondition waitObj ; // wait uninteruptable fixed time for services ..
426
427     dlg.ShowAndWait (waitObj, server.wait_services_sec, LOCALIZED(13032));
428
429     CLog::Log(LOGNOTICE,"WakeOnAccess sequence completed, server started");
430   }
431   return true;
432 }
433
434 bool CWakeOnAccess::FindOrTouchHostEntry (const CStdString& hostName, WakeUpEntry& result)
435 {
436   CSingleLock lock (m_entrylist_protect);
437
438   bool need_wakeup = false;
439
440   for (EntriesVector::iterator i = m_entries.begin();i != m_entries.end(); ++i)
441   {
442     WakeUpEntry& server = *i;
443
444     if (hostName.Equals(server.host.c_str()))
445     {
446       CDateTime now = CDateTime::GetCurrentDateTime();
447
448       if (now > server.nextWake)
449       {
450         result = server;
451         need_wakeup = true;
452       }
453       else // 'touch' next wakeup time
454       {
455         server.nextWake = now + server.timeout;
456       }
457
458       break;
459     }
460   }
461
462   return need_wakeup;
463 }
464
465 void CWakeOnAccess::TouchHostEntry (const CStdString& hostName)
466 {
467   CSingleLock lock (m_entrylist_protect);
468
469   for (EntriesVector::iterator i = m_entries.begin();i != m_entries.end(); ++i)
470   {
471     WakeUpEntry& server = *i;
472
473     if (hostName.Equals(server.host.c_str()))
474     {
475       server.nextWake = CDateTime::GetCurrentDateTime() + server.timeout;
476       return;
477     }
478   }
479 }
480
481 static void AddHost (const CStdString& host, vector<string>& hosts)
482 {
483   for (vector<string>::const_iterator it = hosts.begin(); it != hosts.end(); ++it)
484     if (host.Equals((*it).c_str()))
485       return; // allready there ..
486
487   if (!host.empty())
488     hosts.push_back(host);
489 }
490
491 static void AddHostFromDatabase(const DatabaseSettings& setting, vector<string>& hosts)
492 {
493   if (setting.type.Equals("mysql"))
494     AddHost(setting.host, hosts);
495 }
496
497 void CWakeOnAccess::QueueMACDiscoveryForHost(const CStdString& host)
498 {
499   if (IsEnabled())
500   {
501     if (URIUtils::IsHostOnLAN(host, true))
502       CJobManager::GetInstance().AddJob(new CMACDiscoveryJob(host), this);
503     else
504       CLog::Log(LOGNOTICE, "%s - skip Mac discovery for non-local host '%s'", __FUNCTION__, host.c_str());
505   }
506 }
507
508 static void AddHostsFromMediaSource(const CMediaSource& source, std::vector<std::string>& hosts)
509 {
510   for (CStdStringArray::const_iterator it = source.vecPaths.begin() ; it != source.vecPaths.end(); it++)
511   {
512     CURL url = *it;
513
514     AddHost (url.GetHostName(), hosts);
515   }
516 }
517
518 static void AddHostsFromVecSource(const VECSOURCES& sources, vector<string>& hosts)
519 {
520   for (VECSOURCES::const_iterator it = sources.begin(); it != sources.end(); it++)
521     AddHostsFromMediaSource(*it, hosts);
522 }
523
524 static void AddHostsFromVecSource(const VECSOURCES* sources, vector<string>& hosts)
525 {
526   if (sources)
527     AddHostsFromVecSource(*sources, hosts);
528 }
529
530 void CWakeOnAccess::QueueMACDiscoveryForAllRemotes()
531 {
532   vector<string> hosts;
533
534   // add media sources
535   CMediaSourceSettings& ms = CMediaSourceSettings::Get();
536
537   AddHostsFromVecSource(ms.GetSources("video"), hosts);
538   AddHostsFromVecSource(ms.GetSources("music"), hosts);
539   AddHostsFromVecSource(ms.GetSources("files"), hosts);
540   AddHostsFromVecSource(ms.GetSources("pictures"), hosts);
541   AddHostsFromVecSource(ms.GetSources("programs"), hosts);
542
543   // add mysql servers
544   AddHostFromDatabase(g_advancedSettings.m_databaseVideo, hosts);
545   AddHostFromDatabase(g_advancedSettings.m_databaseMusic, hosts);
546   AddHostFromDatabase(g_advancedSettings.m_databaseEpg, hosts);
547   AddHostFromDatabase(g_advancedSettings.m_databaseTV, hosts);
548
549   // add from path substitutions ..
550   for (CAdvancedSettings::StringMapping::iterator i = g_advancedSettings.m_pathSubstitutions.begin(); i != g_advancedSettings.m_pathSubstitutions.end(); ++i)
551   {
552     CURL url = i->second;
553
554     AddHost (url.GetHostName(), hosts);
555   }
556
557   for (vector<string>::const_iterator it = hosts.begin(); it != hosts.end(); it++)
558     QueueMACDiscoveryForHost(*it);
559 }
560
561 void CWakeOnAccess::SaveMACDiscoveryResult(const CStdString& host, const CStdString& mac)
562 {
563   CLog::Log(LOGNOTICE, "%s - Mac discovered for host '%s' -> '%s'", __FUNCTION__, host.c_str(), mac.c_str());
564
565   CStdString heading = LOCALIZED(13033);
566
567   for (EntriesVector::iterator i = m_entries.begin(); i != m_entries.end(); ++i)
568   {
569     if (host.Equals(i->host.c_str()))
570     {
571       CLog::Log(LOGDEBUG, "%s - Update existing entry for host '%s'", __FUNCTION__, host.c_str());
572       if (!mac.Equals(i->mac.c_str()))
573       {
574         if (IsEnabled()) // show notification only if we have general feature enabled
575         {
576           CStdString message = StringUtils::Format(LOCALIZED(13034), host.c_str());
577           CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, heading, message, 4000, true, 3000);
578         }
579
580         i->mac = mac;
581         SaveToXML();
582       }
583
584       return;
585     }
586   }
587
588   // not found entry to update - create using default values
589   WakeUpEntry entry (true);
590   entry.host = host;
591   entry.mac  = mac;
592   m_entries.push_back(entry);
593
594   CLog::Log(LOGDEBUG, "%s - Create new entry for host '%s'", __FUNCTION__, host.c_str());
595   if (IsEnabled()) // show notification only if we have general feature enabled
596   {
597     CStdString message = StringUtils::Format(LOCALIZED(13035), host.c_str());
598     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, heading, message, 4000, true, 3000);
599   }
600
601   SaveToXML();
602 }
603
604 void CWakeOnAccess::OnJobComplete(unsigned int jobID, bool success, CJob *job)
605 {
606   CMACDiscoveryJob* discoverJob = (CMACDiscoveryJob*)job;
607
608   const CStdString& host = discoverJob->GetHost();
609   const CStdString& mac = discoverJob->GetMAC();
610
611   if (success)
612   {
613     CSingleLock lock (m_entrylist_protect);
614
615     SaveMACDiscoveryResult(host, mac);
616   }
617   else
618   {
619     CLog::Log(LOGERROR, "%s - Mac discovery failed for host '%s'", __FUNCTION__, host.c_str());
620
621     if (IsEnabled())
622     {
623       CStdString heading = LOCALIZED(13033);
624       CStdString message = StringUtils::Format(LOCALIZED(13036), host.c_str());
625       CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, message, 4000, true, 3000);
626     }
627   }
628 }
629
630 CStdString CWakeOnAccess::GetSettingFile()
631 {
632   return CSpecialProtocol::TranslatePath("special://masterprofile/wakeonlan.xml");
633 }
634
635 void CWakeOnAccess::OnSettingsLoaded()
636 {
637   CSingleLock lock (m_entrylist_protect);
638
639   LoadFromXML();
640 }
641
642 void CWakeOnAccess::OnSettingsSaved()
643 {
644   bool enabled = CSettings::Get().GetBool("powermanagement.wakeonaccess");
645
646   if (enabled != IsEnabled())
647   {
648     SetEnabled(enabled);
649
650     if (enabled)
651       QueueMACDiscoveryForAllRemotes();
652   }
653 }
654
655 void CWakeOnAccess::LoadFromXML()
656 {
657   bool enabled = CSettings::Get().GetBool("powermanagement.wakeonaccess");
658   SetEnabled(enabled);
659
660   CXBMCTinyXML xmlDoc;
661   if (!xmlDoc.LoadFile(GetSettingFile()))
662   {
663     CLog::Log(LOGNOTICE, "%s - unable to load:%s", __FUNCTION__, GetSettingFile().c_str());
664     return;
665   }
666
667   TiXmlElement* pRootElement = xmlDoc.RootElement();
668   if (strcmpi(pRootElement->Value(), "onaccesswakeup"))
669   {
670     CLog::Log(LOGERROR, "%s - XML file %s doesnt contain <onaccesswakeup>", __FUNCTION__, GetSettingFile().c_str());
671     return;
672   }
673
674   m_entries.clear();
675
676   CLog::Log(LOGNOTICE,"WakeOnAccess - Load settings :");
677
678   int tmp;
679   if (XMLUtils::GetInt(pRootElement, "netinittimeout", tmp, 0, 5 * 60))
680     m_netinit_sec = tmp;
681   CLog::Log(LOGNOTICE,"  -Network init timeout : [%d] sec", m_netinit_sec);
682   
683   if (XMLUtils::GetInt(pRootElement, "netsettletime", tmp, 0, 5 * 1000))
684     m_netsettle_ms = tmp;
685   CLog::Log(LOGNOTICE,"  -Network settle time  : [%d] ms", m_netsettle_ms);
686
687   const TiXmlNode* pWakeUp = pRootElement->FirstChildElement("wakeup");
688   while (pWakeUp)
689   {
690     WakeUpEntry entry;
691
692     CStdString strtmp;
693     if (XMLUtils::GetString(pWakeUp, "host", strtmp))
694       entry.host = strtmp;
695
696     if (XMLUtils::GetString(pWakeUp, "mac", strtmp))
697       entry.mac = strtmp;
698
699     if (entry.host.empty())
700       CLog::Log(LOGERROR, "%s - Missing <host> tag or it's empty", __FUNCTION__);
701     else if (entry.mac.empty())
702        CLog::Log(LOGERROR, "%s - Missing <mac> tag or it's empty", __FUNCTION__);
703     else
704     {
705       if (XMLUtils::GetInt(pWakeUp, "pingport", tmp, 0, USHRT_MAX))
706         entry.ping_port = (unsigned short) tmp;
707
708       if (XMLUtils::GetInt(pWakeUp, "pingmode", tmp, 0, USHRT_MAX))
709         entry.ping_mode = (unsigned short) tmp;
710
711       if (XMLUtils::GetInt(pWakeUp, "timeout", tmp, 10, 12 * 60 * 60))
712         entry.timeout.SetDateTimeSpan (0, 0, 0, tmp);
713
714       if (XMLUtils::GetInt(pWakeUp, "waitonline", tmp, 0, 10 * 60)) // max 10 minutes
715         entry.wait_online1_sec = tmp;
716
717       if (XMLUtils::GetInt(pWakeUp, "waitonline2", tmp, 0, 10 * 60)) // max 10 minutes
718         entry.wait_online2_sec = tmp;
719
720       if (XMLUtils::GetInt(pWakeUp, "waitservices", tmp, 0, 5 * 60)) // max 5 minutes
721         entry.wait_services_sec = tmp;
722
723       CLog::Log(LOGNOTICE,"  Registering wakeup entry:");
724       CLog::Log(LOGNOTICE,"    HostName        : %s", entry.host.c_str());
725       CLog::Log(LOGNOTICE,"    MacAddress      : %s", entry.mac.c_str());
726       CLog::Log(LOGNOTICE,"    PingPort        : %d", entry.ping_port);
727       CLog::Log(LOGNOTICE,"    PingMode        : %d", entry.ping_mode);
728       CLog::Log(LOGNOTICE,"    Timeout         : %d (sec)", GetTotalSeconds(entry.timeout));
729       CLog::Log(LOGNOTICE,"    WaitForOnline   : %d (sec)", entry.wait_online1_sec);
730       CLog::Log(LOGNOTICE,"    WaitForOnlineEx : %d (sec)", entry.wait_online2_sec);
731       CLog::Log(LOGNOTICE,"    WaitForServices : %d (sec)", entry.wait_services_sec);
732
733       m_entries.push_back(entry);
734     }
735
736     pWakeUp = pWakeUp->NextSiblingElement("wakeup"); // get next one
737   }
738 }
739
740 void CWakeOnAccess::SaveToXML()
741 {
742   CXBMCTinyXML xmlDoc;
743   TiXmlElement xmlRootElement("onaccesswakeup");
744   TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement);
745   if (!pRoot) return;
746
747   XMLUtils::SetInt(pRoot, "netinittimeout", m_netinit_sec);
748   XMLUtils::SetInt(pRoot, "netsettletime", m_netsettle_ms);
749
750   for (EntriesVector::const_iterator i = m_entries.begin(); i != m_entries.end(); ++i)
751   {
752     TiXmlElement xmlSetting("wakeup");
753     TiXmlNode* pWakeUpNode = pRoot->InsertEndChild(xmlSetting);
754     if (pWakeUpNode)
755     {
756       XMLUtils::SetString(pWakeUpNode, "host", i->host);
757       XMLUtils::SetString(pWakeUpNode, "mac", i->mac);
758       XMLUtils::SetInt(pWakeUpNode, "pingport", i->ping_port);
759       XMLUtils::SetInt(pWakeUpNode, "pingmode", i->ping_mode);
760       XMLUtils::SetInt(pWakeUpNode, "timeout", GetTotalSeconds(i->timeout));
761       XMLUtils::SetInt(pWakeUpNode, "waitonline", i->wait_online1_sec);
762       XMLUtils::SetInt(pWakeUpNode, "waitonline2", i->wait_online2_sec);
763       XMLUtils::SetInt(pWakeUpNode, "waitservices", i->wait_services_sec);
764     }
765   }
766
767   xmlDoc.SaveFile(GetSettingFile());
768 }