diff options
24 files changed, 1326 insertions, 9 deletions
diff --git a/XBMC-ATV2.xcodeproj/project.pbxproj b/XBMC-ATV2.xcodeproj/project.pbxproj index 564ced5cef..65162866f8 100644 --- a/XBMC-ATV2.xcodeproj/project.pbxproj +++ b/XBMC-ATV2.xcodeproj/project.pbxproj @@ -181,6 +181,7 @@ DFA0F9551618E1CD00611CBB /* DVDOverlayCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA0F9541618E1CD00611CBB /* DVDOverlayCodec.cpp */; }; DFA6BE8713FED2A10048CC11 /* AirPlayServer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA6BE8513FED2A10048CC11 /* AirPlayServer.cpp */; }; DFA6BE8A13FED2B40048CC11 /* HttpParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA6BE8813FED2B40048CC11 /* HttpParser.cpp */; }; + DFA815A216713BC400E4E597 /* WakeOnAccess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA815A016713BC400E4E597 /* WakeOnAccess.cpp */; }; DFAB04C113F8385F00B70BFB /* InertialScrollingHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB04BF13F8385F00B70BFB /* InertialScrollingHandler.cpp */; }; DFAB4C0F15FCCB4300E1BAF6 /* TagLibVFSStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB4C0B15FCCB4300E1BAF6 /* TagLibVFSStream.cpp */; }; DFAB4C1015FCCB4300E1BAF6 /* TagLoaderTagLib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB4C0D15FCCB4300E1BAF6 /* TagLoaderTagLib.cpp */; }; @@ -1445,6 +1446,8 @@ DFA6BE8613FED2A10048CC11 /* AirPlayServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AirPlayServer.h; sourceTree = "<group>"; }; DFA6BE8813FED2B40048CC11 /* HttpParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HttpParser.cpp; sourceTree = "<group>"; }; DFA6BE8913FED2B40048CC11 /* HttpParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpParser.h; sourceTree = "<group>"; }; + DFA815A016713BC400E4E597 /* WakeOnAccess.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WakeOnAccess.cpp; sourceTree = "<group>"; }; + DFA815A116713BC400E4E597 /* WakeOnAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WakeOnAccess.h; sourceTree = "<group>"; }; DFAB04BF13F8385F00B70BFB /* InertialScrollingHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InertialScrollingHandler.cpp; sourceTree = "<group>"; }; DFAB04C013F8385F00B70BFB /* InertialScrollingHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InertialScrollingHandler.h; sourceTree = "<group>"; }; DFAB4C0B15FCCB4300E1BAF6 /* TagLibVFSStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TagLibVFSStream.cpp; sourceTree = "<group>"; }; @@ -5541,6 +5544,8 @@ F56C7668131EC153000AD0F6 /* TCPServer.h */, F56C766B131EC153000AD0F6 /* UdpClient.cpp */, F56C766C131EC153000AD0F6 /* UdpClient.h */, + DFA815A016713BC400E4E597 /* WakeOnAccess.cpp */, + DFA815A116713BC400E4E597 /* WakeOnAccess.h */, F56C766D131EC153000AD0F6 /* WebServer.cpp */, F56C766E131EC153000AD0F6 /* WebServer.h */, F56C766F131EC153000AD0F6 /* Zeroconf.cpp */, @@ -7767,6 +7772,7 @@ DFECFBFD172DA58800A43CF7 /* NetworkServices.cpp in Sources */, F5DB701317322DF700D4DF21 /* FavouritesOperations.cpp in Sources */, DF52568B1732C4620094A464 /* DVDDemuxCDDA.cpp in Sources */, + DFA815A216713BC400E4E597 /* WakeOnAccess.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XBMC-IOS.xcodeproj/project.pbxproj b/XBMC-IOS.xcodeproj/project.pbxproj index b7781dfb42..bd430b4b4b 100644 --- a/XBMC-IOS.xcodeproj/project.pbxproj +++ b/XBMC-IOS.xcodeproj/project.pbxproj @@ -187,6 +187,7 @@ DFA0F9291618E18A00611CBB /* DVDOverlayCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA0F9281618E18A00611CBB /* DVDOverlayCodec.cpp */; }; DFA6BE4313FECA010048CC11 /* AirPlayServer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA6BE4113FECA010048CC11 /* AirPlayServer.cpp */; }; DFA6BE7713FED09C0048CC11 /* HttpParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA6BE7513FED09C0048CC11 /* HttpParser.cpp */; }; + DFA8158E16713BA100E4E597 /* WakeOnAccess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA8158C16713BA100E4E597 /* WakeOnAccess.cpp */; }; DFAB04B013F8383300B70BFB /* InertialScrollingHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB04AE13F8383300B70BFB /* InertialScrollingHandler.cpp */; }; DFAB4BE615FCCA5000E1BAF6 /* TagLibVFSStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB4BE215FCCA5000E1BAF6 /* TagLibVFSStream.cpp */; }; DFAB4BE715FCCA5000E1BAF6 /* TagLoaderTagLib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB4BE415FCCA5000E1BAF6 /* TagLoaderTagLib.cpp */; }; @@ -1466,6 +1467,8 @@ DFA6BE4213FECA010048CC11 /* AirPlayServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AirPlayServer.h; sourceTree = "<group>"; }; DFA6BE7513FED09C0048CC11 /* HttpParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HttpParser.cpp; sourceTree = "<group>"; }; DFA6BE7613FED09C0048CC11 /* HttpParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpParser.h; sourceTree = "<group>"; }; + DFA8158C16713BA100E4E597 /* WakeOnAccess.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WakeOnAccess.cpp; sourceTree = "<group>"; }; + DFA8158D16713BA100E4E597 /* WakeOnAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WakeOnAccess.h; sourceTree = "<group>"; }; DFAB04AE13F8383300B70BFB /* InertialScrollingHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InertialScrollingHandler.cpp; sourceTree = "<group>"; }; DFAB04AF13F8383300B70BFB /* InertialScrollingHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InertialScrollingHandler.h; sourceTree = "<group>"; }; DFAB4BE215FCCA5000E1BAF6 /* TagLibVFSStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TagLibVFSStream.cpp; sourceTree = "<group>"; }; @@ -5943,6 +5946,8 @@ F56C864B131F42EB000AD0F6 /* TCPServer.h */, F56C864E131F42EB000AD0F6 /* UdpClient.cpp */, F56C864F131F42EB000AD0F6 /* UdpClient.h */, + DFA8158C16713BA100E4E597 /* WakeOnAccess.cpp */, + DFA8158D16713BA100E4E597 /* WakeOnAccess.h */, F56C8650131F42EB000AD0F6 /* WebServer.cpp */, F56C8651131F42EB000AD0F6 /* WebServer.h */, F56C8652131F42EB000AD0F6 /* Zeroconf.cpp */, @@ -7838,6 +7843,7 @@ DFECFB4F172D9D8E00A43CF7 /* NetworkServices.cpp in Sources */, F5DB701A17322E0C00D4DF21 /* FavouritesOperations.cpp in Sources */, DF52567A1732C43E0094A464 /* DVDDemuxCDDA.cpp in Sources */, + DFA8158E16713BA100E4E597 /* WakeOnAccess.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XBMC.xcodeproj/project.pbxproj b/XBMC.xcodeproj/project.pbxproj index 2bff3c31c8..636006a780 100644 --- a/XBMC.xcodeproj/project.pbxproj +++ b/XBMC.xcodeproj/project.pbxproj @@ -443,6 +443,7 @@ DF93D7F61444B568007C6459 /* HDHomeRunDirectory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DF93D7F51444B568007C6459 /* HDHomeRunDirectory.cpp */; }; DF98D98C1434F47D00A6EBE1 /* SkinVariable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DF98D98A1434F47D00A6EBE1 /* SkinVariable.cpp */; }; DF9A71EE1639C8F6005ECB2E /* HTTPFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DF9A71EC1639C8F6005ECB2E /* HTTPFile.cpp */; }; + DFA8157E16713B1200E4E597 /* WakeOnAccess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFA8157C16713B1200E4E597 /* WakeOnAccess.cpp */; }; DFAB049813F8376700B70BFB /* InertialScrollingHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAB049613F8376700B70BFB /* InertialScrollingHandler.cpp */; }; DFAF6A4F16EBAE3800D6AE12 /* RssManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFAF6A4D16EBAE3800D6AE12 /* RssManager.cpp */; }; DFB02DEA16629DBA00F37752 /* PyContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFB02DE816629DBA00F37752 /* PyContext.cpp */; }; @@ -2021,6 +2022,8 @@ DF98D98B1434F47D00A6EBE1 /* SkinVariable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SkinVariable.h; sourceTree = "<group>"; }; DF9A71EC1639C8F6005ECB2E /* HTTPFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPFile.cpp; sourceTree = "<group>"; }; DF9A71ED1639C8F6005ECB2E /* HTTPFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPFile.h; sourceTree = "<group>"; }; + DFA8157C16713B1200E4E597 /* WakeOnAccess.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WakeOnAccess.cpp; sourceTree = "<group>"; }; + DFA8157D16713B1200E4E597 /* WakeOnAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WakeOnAccess.h; sourceTree = "<group>"; }; DFAB049613F8376700B70BFB /* InertialScrollingHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InertialScrollingHandler.cpp; sourceTree = "<group>"; }; DFAB049713F8376700B70BFB /* InertialScrollingHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InertialScrollingHandler.h; sourceTree = "<group>"; }; DFAF6A4C16EBAE3800D6AE12 /* IRssObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IRssObserver.h; sourceTree = "<group>"; }; @@ -4032,6 +4035,8 @@ 432D7CF512D870D600CE4C49 /* TCPServer.h */, E38E1E8B0D25F9FD00618676 /* UdpClient.cpp */, E38E1E8C0D25F9FD00618676 /* UdpClient.h */, + DFA8157C16713B1200E4E597 /* WakeOnAccess.cpp */, + DFA8157D16713B1200E4E597 /* WakeOnAccess.h */, F5A7A859112908F00059D6AA /* WebServer.cpp */, F5A7A85A112908F00059D6AA /* WebServer.h */, E46F7C280F77217400C25D29 /* Zeroconf.cpp */, @@ -7836,6 +7841,7 @@ DFECFB4C172D9D6D00A43CF7 /* NetworkServices.cpp in Sources */, F5DB700217322DBB00D4DF21 /* FavouritesOperations.cpp in Sources */, DF52566D1732C1890094A464 /* DVDDemuxCDDA.cpp in Sources */, + DFA8157E16713B1200E4E597 /* WakeOnAccess.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/language/English/strings.po b/language/English/strings.po index c3df8dca71..2575de8e9b 100644 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -4822,7 +4822,62 @@ msgctxt "#13025" msgid "Joystick unplugged" msgstr "" -#empty strings from id 13026 to 13049 +#: system/settings/settings.xml +msgctxt "#13026" +msgid "Try to wake remote servers on access" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13027" +msgid "Wake on Lan (%s)" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13028" +msgid "Waiting for network to connect..." +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13029" +msgid "Failed to execute Wake on Lan!" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13030" +msgid "Waiting for server to wake up..." +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13031" +msgid "Extended wait for server to wake up..." +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13032" +msgid "Waiting for services to launch..." +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13033" +msgid "MAC Discovery" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13034" +msgid "Updated for %s" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13035" +msgid "Found for %s" +msgstr "" + +#: xbmc/network/WakeOnAccess.cpp +msgctxt "#13036" +msgid "Failed for %s" +msgstr "" + +#empty strings from id 13037 to 13049 msgctxt "#13050" msgid "Running low on battery" diff --git a/project/VS2010Express/XBMC.vcxproj b/project/VS2010Express/XBMC.vcxproj index d0cad18bf6..5a40df6b47 100644 --- a/project/VS2010Express/XBMC.vcxproj +++ b/project/VS2010Express/XBMC.vcxproj @@ -848,6 +848,7 @@ <ClCompile Include="..\..\xbmc\network\upnp\UPnPRenderer.cpp" /> <ClCompile Include="..\..\xbmc\network\upnp\UPnPServer.cpp" /> <ClCompile Include="..\..\xbmc\network\upnp\UPnPSettings.cpp" /> + <ClCompile Include="..\..\xbmc\network\WakeOnAccess.cpp" /> <ClCompile Include="..\..\xbmc\network\WebServer.cpp" /> <ClCompile Include="..\..\xbmc\network\websocket\WebSocket.cpp" /> <ClCompile Include="..\..\xbmc\network\websocket\WebSocketManager.cpp" /> @@ -1175,6 +1176,7 @@ <ClInclude Include="..\..\xbmc\network\upnp\UPnPPlayer.h" /> <ClInclude Include="..\..\xbmc\network\upnp\UPnPRenderer.h" /> <ClInclude Include="..\..\xbmc\network\upnp\UPnPServer.h" /> + <ClInclude Include="..\..\xbmc\network\WakeOnAccess.h" /> <ClInclude Include="..\..\xbmc\network\websocket\WebSocket.h" /> <ClInclude Include="..\..\xbmc\network\websocket\WebSocketManager.h" /> <ClInclude Include="..\..\xbmc\network\websocket\WebSocketV13.h" /> diff --git a/project/VS2010Express/XBMC.vcxproj.filters b/project/VS2010Express/XBMC.vcxproj.filters index 2d42f5507d..556a4f552b 100644 --- a/project/VS2010Express/XBMC.vcxproj.filters +++ b/project/VS2010Express/XBMC.vcxproj.filters @@ -1324,6 +1324,9 @@ <ClCompile Include="..\..\xbmc\network\UdpClient.cpp"> <Filter>network</Filter> </ClCompile> + <ClCompile Include="..\..\xbmc\network\WakeOnAccess.cpp"> + <Filter>network</Filter> + </ClCompile> <ClCompile Include="..\..\xbmc\network\WebServer.cpp"> <Filter>network</Filter> </ClCompile> @@ -4255,6 +4258,9 @@ <ClInclude Include="..\..\xbmc\network\UdpClient.h"> <Filter>network</Filter> </ClInclude> + <ClInclude Include="..\..\xbmc\network\WakeOnAccess.h"> + <Filter>network</Filter> + </ClInclude> <ClInclude Include="..\..\xbmc\network\WebServer.h"> <Filter>network</Filter> </ClInclude> diff --git a/system/settings/settings.xml b/system/settings/settings.xml index f5284e806b..488a3c8d00 100644 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -2230,6 +2230,12 @@ <control type="spinner" format="string" /> </setting> </group> + <group id="2"> + <setting id="powermanagement.wakeonaccess" type="boolean" label="13026" help=""> + <level>1</level> + <default>false</default> + </setting> + </group> </category> <category id="debug" label="14092" help=""> <group id="1"> diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index d4cd15320d..07f387506b 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -24,6 +24,7 @@ #include "utils/log.h" #include "system.h" // for GetLastError() +#include "network/WakeOnAccess.h" #ifdef HAS_MYSQL #include "mysqldataset.h" @@ -119,6 +120,8 @@ int MysqlDatabase::connect(bool create_new) { if (conn == NULL) conn = mysql_init(conn); + CWakeOnAccess::Get().WakeUpHost(host, "MySQL : " + db); + // establish connection with just user credentials if (mysql_real_connect(conn, host.c_str(),login.c_str(),passwd.c_str(), NULL, atoi(port.c_str()),NULL,0) != NULL) { diff --git a/xbmc/filesystem/DirectoryFactory.cpp b/xbmc/filesystem/DirectoryFactory.cpp index 73c3c3edb0..df35e16e61 100644 --- a/xbmc/filesystem/DirectoryFactory.cpp +++ b/xbmc/filesystem/DirectoryFactory.cpp @@ -43,6 +43,7 @@ #include "Application.h" #include "addons/Addon.h" #include "utils/log.h" +#include "network/WakeOnAccess.h" #ifdef HAS_FILESYSTEM_SMB #ifdef _WIN32 @@ -123,6 +124,7 @@ using namespace XFILE; IDirectory* CDirectoryFactory::Create(const CStdString& strPath) { CURL url(strPath); + CWakeOnAccess::Get().WakeUpHost(url); CFileItem item(strPath, false); IFileDirectory* pDir=CFileDirectoryFactory::Create(strPath, &item); diff --git a/xbmc/filesystem/FileFactory.cpp b/xbmc/filesystem/FileFactory.cpp index 40f4180fa9..40331a8964 100644 --- a/xbmc/filesystem/FileFactory.cpp +++ b/xbmc/filesystem/FileFactory.cpp @@ -95,6 +95,7 @@ #include "Application.h" #include "URL.h" #include "utils/log.h" +#include "network/WakeOnAccess.h" using namespace XFILE; @@ -114,6 +115,8 @@ IFile* CFileFactory::CreateLoader(const CStdString& strFileName) IFile* CFileFactory::CreateLoader(const CURL& url) { + CWakeOnAccess::Get().WakeUpHost(url); + CStdString strProtocol = url.GetProtocol(); strProtocol.MakeLower(); diff --git a/xbmc/main/main.cpp b/xbmc/main/main.cpp index 9376a1673e..89af7f0877 100644 --- a/xbmc/main/main.cpp +++ b/xbmc/main/main.cpp @@ -65,13 +65,6 @@ int main(int argc, char* argv[]) if (setrlimit(RLIMIT_CORE, &rlim) == -1) CLog::Log(LOGDEBUG, "Failed to set core size limit (%s)", strerror(errno)); #endif - // Prevent child processes from becoming zombies on exit if not waited upon. See also Util::Command - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - - sa.sa_flags = SA_NOCLDWAIT; - sa.sa_handler = SIG_IGN; - sigaction(SIGCHLD, &sa, NULL); #endif setlocale(LC_NUMERIC, "C"); g_advancedSettings.Initialize(); diff --git a/xbmc/network/Makefile.in b/xbmc/network/Makefile.in index bf9bed9330..c80651c4eb 100644 --- a/xbmc/network/Makefile.in +++ b/xbmc/network/Makefile.in @@ -10,6 +10,7 @@ SRCS = cddb.cpp \ Socket.cpp \ TCPServer.cpp \ UdpClient.cpp \ + WakeOnAccess.cpp \ WebServer.cpp \ ZeroconfBrowser.cpp \ Zeroconf.cpp \ diff --git a/xbmc/network/Network.cpp b/xbmc/network/Network.cpp index 8beca4803b..2a1d6bebd4 100644 --- a/xbmc/network/Network.cpp +++ b/xbmc/network/Network.cpp @@ -268,3 +268,120 @@ bool CNetwork::WakeOnLan(const char* mac) CLog::Log(LOGINFO, "%s - Magic packet send to '%s'", __FUNCTION__, mac); return true; } + +// ping helper +static const char* ConnectHostPort(SOCKET soc, const struct sockaddr_in& addr, struct timeval& timeOut, bool tryRead) +{ + // set non-blocking +#ifdef _MSC_VER + u_long nonblocking = 1; + int result = ioctlsocket(soc, FIONBIO, &nonblocking); +#else + int result = fcntl(soc, F_SETFL, fcntl(soc, F_GETFL) | O_NONBLOCK); +#endif + + if (result != 0) + return "set non-blocking option failed"; + + result = connect(soc, (struct sockaddr *)&addr, sizeof(addr)); // non-blocking connect, will fail .. + + if (result < 0) + { +#ifdef _MSC_VER + if (WSAGetLastError() != WSAEWOULDBLOCK) +#else + if (errno != EINPROGRESS) +#endif + return "unexpected connect fail"; + + { // wait for connect to complete + fd_set wset; + FD_ZERO(&wset); + FD_SET(soc, &wset); + + result = select(FD_SETSIZE, 0, &wset, 0, &timeOut); + } + + if (result < 0) + return "select fail"; + + if (result == 0) // timeout + return ""; // no error + + { // verify socket connection state + int err_code = -1; + socklen_t code_len = sizeof (err_code); + + result = getsockopt(soc, SOL_SOCKET, SO_ERROR, (char*) &err_code, &code_len); + + if (result != 0) + return "getsockopt fail"; + + if (err_code != 0) + return ""; // no error, just not connected + } + } + + if (tryRead) + { + fd_set rset; + FD_ZERO(&rset); + FD_SET(soc, &rset); + + result = select(FD_SETSIZE, &rset, 0, 0, &timeOut); + + if (result > 0) + { + char message [32]; + + result = recv(soc, message, sizeof(message), 0); + } + + if (result == 0) + return ""; // no reply yet + + if (result < 0) + return "recv fail"; + } + + return 0; // success +} + +bool CNetwork::PingHost(unsigned long ipaddr, unsigned short port, unsigned int timeOutMs, bool readability_check) +{ + if (port == 0) // use icmp ping + return PingHost (ipaddr, timeOutMs); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ipaddr; + + SOCKET soc = socket(AF_INET, SOCK_STREAM, 0); + + const char* err_msg = "invalid socket"; + + if (soc != INVALID_SOCKET) + { + struct timeval tmout; + tmout.tv_sec = timeOutMs / 1000; + tmout.tv_usec = (timeOutMs % 1000) * 1000; + + err_msg = ConnectHostPort (soc, addr, tmout, readability_check); + + (void) closesocket (soc); + } + + if (err_msg && *err_msg) + { +#ifdef _MSC_VER + CStdString sock_err = WUSysMsg(WSAGetLastError()); +#else + CStdString sock_err = strerror(errno); +#endif + + CLog::Log(LOGERROR, "%s(%s:%d) - %s (%s)", __FUNCTION__, inet_ntoa(addr.sin_addr), port, err_msg, sock_err.c_str()); + } + + return err_msg == 0; +} diff --git a/xbmc/network/Network.h b/xbmc/network/Network.h index 3500b70aaf..e608407dc8 100644 --- a/xbmc/network/Network.h +++ b/xbmc/network/Network.h @@ -63,6 +63,8 @@ public: virtual CStdString GetMacAddress(void) = 0; virtual void GetMacAddressRaw(char rawMac[6]) = 0; + virtual bool GetHostMacAddress(unsigned long host, CStdString& mac) = 0; + virtual CStdString GetCurrentIPAddress() = 0; virtual CStdString GetCurrentNetmask() = 0; virtual CStdString GetCurrentDefaultGateway(void) = 0; @@ -109,6 +111,10 @@ public: // Return true if the magic packet was send bool WakeOnLan(const char *mac); + // Return true if host replies to ping + bool PingHost(unsigned long host, unsigned short port, unsigned int timeout_ms = 2000, bool readability_check = false); + virtual bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) = 0; + // Get/set the nameserver(s) virtual std::vector<CStdString> GetNameServers(void) = 0; virtual void SetNameServers(std::vector<CStdString> nameServers) = 0; diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp new file mode 100644 index 0000000000..8f39a70b78 --- /dev/null +++ b/xbmc/network/WakeOnAccess.cpp @@ -0,0 +1,759 @@ +/* + * Copyright (C) 2013 Team XBMC + * http://www.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, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include <limits.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include "system.h" +#include "network/Network.h" +#include "Application.h" +#include "DNSNameCache.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/MediaSourceSettings.h" +#include "utils/JobManager.h" +#include "utils/log.h" +#include "utils/XMLUtils.h" + +#include "WakeOnAccess.h" + +using namespace std; + +#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 deamons + +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 CStdString& host) +{ + CStdString ip; + CDNSNameCache::Lookup(host, ip); + return inet_addr(ip.c_str()); +} + +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) + , ping_port(0), ping_mode(0) +{ + nextWake = CDateTime::GetCurrentDateTime(); + + if (isAwake) + nextWake += timeout; +} + +//** + +class CMACDiscoveryJob : public CJob +{ +public: + CMACDiscoveryJob(const CStdString& host) : m_host(host) {} + + virtual bool DoWork(); + + const CStdString& GetMAC() const { return m_macAddres; } + const CStdString& GetHost() const { return m_host; } + +private: + CStdString m_macAddres; + CStdString m_host; +}; + +bool CMACDiscoveryJob::DoWork() +{ + unsigned long ipAddress = HostToIP(m_host); + + if (ipAddress == INADDR_NONE) + { + CLog::Log(LOGERROR, "%s - can't determine ip of '%s'", __FUNCTION__, m_host.c_str()); + return false; + } + + vector<CNetworkInterface*>& ifaces = g_application.getNetwork().GetInterfaceList(); + for (vector<CNetworkInterface*>::const_iterator it = ifaces.begin(); it != ifaces.end(); ++it) + { + if ((*it)->GetHostMacAddress(ipAddress, m_macAddres)) + return true; + } + + return false; +} + +//** + +class WaitCondition +{ +public: + virtual bool SuccessWaiting () const { return false; } +}; + +// + +class NestDetect +{ +public: + NestDetect() : m_gui_thread (g_application.IsCurrentThread()) + { + 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: + ProgressDialogHelper (const CStdString& heading) : m_dialog(0) + { + if (g_application.IsCurrentThread()) + m_dialog = (CGUIDialogProgress*) g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + + if (m_dialog) + { + m_dialog->SetHeading (heading); + m_dialog->SetLine(0, ""); + m_dialog->SetLine(1, ""); + m_dialog->SetLine(2, ""); + + int nest_level = NestDetect::Level(); + if (nest_level > 1) + { + CStdString nest; + nest.Format("Nesting:%d", nest_level); + m_dialog->SetLine(2, nest); + } + } + } + ~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 CStdString& line1) + { + unsigned timeOutMs = timeOutSec * 1000; + + if (m_dialog) + { + m_dialog->SetLine(1, 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->StartModal(); + + if (m_dialog->IsCanceled()) + return Canceled; + + m_dialog->Progress(); + + unsigned ms_passed = timeOutMs - end_time.MillisLeft(); + + int percentage = (ms_passed * 100) / timeOutMs; + m_dialog->SetPercentage(max(percentage, 1)); // avoid flickering , keep minimum 1% + } + + Sleep (m_dialog ? 20 : 200); + } + + return TimedOut; + } + +private: + CGUIDialogProgress* m_dialog; +}; + +class NetworkStartWaiter : public WaitCondition +{ +public: + NetworkStartWaiter (unsigned settle_time_ms) : m_settle_time_ms (settle_time_ms) + { + } + virtual bool SuccessWaiting () const + { + CNetworkInterface* iface = g_application.getNetwork().GetFirstConnectedInterface(); + + bool online = iface && iface->IsEnabled(); + + if (!online) // setup endtime so we dont return true until network is consistently connected + m_end.Set (m_settle_time_ms); + + return online && m_end.IsTimePast(); + } +private: + mutable XbmcThreads::EndTime m_end; + unsigned m_settle_time_ms; +}; + +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 = CJobManager::GetInstance().AddJob(job, this); + } + } + ~PingResponseWaiter() + { + CJobManager::GetInstance().CancelJob(m_jobId); + } + virtual bool SuccessWaiting () const + { + return m_jobId ? m_hostOnline : Ping(m_server); + } + + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job) + { + m_hostOnline = success; + } + + static bool Ping (const CWakeOnAccess::WakeUpEntry& server) + { + ULONG dst_ip = HostToIP(server.host); + + return g_application.getNetwork().PingHost(dst_ip, server.ping_port, 2000, server.ping_mode & 1); + } + +private: + class CHostProberJob : public CJob + { + public: + CHostProberJob(const CWakeOnAccess::WakeUpEntry& server) : m_server (server) {} + + virtual bool DoWork() + { + 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 + , m_enabled(false) +{ +} + +CWakeOnAccess &CWakeOnAccess::Get() +{ + static CWakeOnAccess sWakeOnAccess; + return sWakeOnAccess; +} + +void CWakeOnAccess::WakeUpHost(const CURL& url) +{ + CStdString hostName = url.GetHostName(); + + if (!hostName.IsEmpty()) + WakeUpHost (hostName, url.Get()); +} + +void CWakeOnAccess::WakeUpHost (const CStdString& hostName, const string& customMessage) +{ + if (!IsEnabled()) + return; // bail if feature is turned off + + WakeUpEntry server; + + if (FindOrTouchHostEntry(hostName, server)) + { + CLog::Log(LOGNOTICE,"WakeOnAccess [%s] trigged by accessing : %s", hostName.c_str(), customMessage.c_str()); + + 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 [%d]", NestDetect::Level()); + + WakeUpHost(server); + + TouchHostEntry(hostName); + } +} + +#define LOCALIZED(id) g_localizeStrings.Get(id) + +void CWakeOnAccess::WakeUpHost(const WakeUpEntry& server) +{ + CStdString heading = LOCALIZED(13027); + heading.Format (heading, server.host); + + ProgressDialogHelper dlg (heading); + + { + NetworkStartWaiter waitObj (m_netsettle_ms); // wait until network connected before sending wake-on-lan + + if (dlg.ShowAndWait (waitObj, m_netinit_sec, LOCALIZED(13028)) != ProgressDialogHelper::Success) + { + CLog::Log(LOGNOTICE,"WakeOnAccess timeout/cancel while waiting for network"); + return; // timedout or canceled + } + } + + { + ULONG dst_ip = HostToIP(server.host); + + if (g_application.getNetwork().PingHost(dst_ip, server.ping_port, 500)) // quick ping with short timeout to not block too long + { + CLog::Log(LOGNOTICE,"WakeOnAccess success exit, server already running"); + return; + } + } + + if (!g_application.getNetwork().WakeOnLan(server.mac.c_str())) + { + CLog::Log(LOGERROR,"WakeOnAccess failed to send. (Is it blocked by firewall?)"); + + if (g_application.IsCurrentThread() || !g_application.IsPlaying()) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, LOCALIZED(13029)); + return; + } + + { + 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(LOGNOTICE,"WakeOnAccess timeout/cancel while waiting for response"); + return; // timedout or canceled + } + } + + { + WaitCondition waitObj ; // wait uninteruptable fixed time for services .. + + dlg.ShowAndWait (waitObj, server.wait_services_sec, LOCALIZED(13032)); + + CLog::Log(LOGNOTICE,"WakeOnAccess sequence completed, server started"); + } +} + +bool CWakeOnAccess::FindOrTouchHostEntry (const CStdString& hostName, WakeUpEntry& result) +{ + CSingleLock lock (m_entrylist_protect); + + bool need_wakeup = false; + + for (EntriesVector::iterator i = m_entries.begin();i != m_entries.end(); ++i) + { + WakeUpEntry& server = *i; + + if (hostName.Equals(server.host.c_str())) + { + CDateTime now = CDateTime::GetCurrentDateTime(); + + if (now > server.nextWake) + { + result = server; + need_wakeup = true; + } + else // 'touch' next wakeup time + { + server.nextWake = now + server.timeout; + } + + break; + } + } + + return need_wakeup; +} + +void CWakeOnAccess::TouchHostEntry (const CStdString& hostName) +{ + CSingleLock lock (m_entrylist_protect); + + for (EntriesVector::iterator i = m_entries.begin();i != m_entries.end(); ++i) + { + WakeUpEntry& server = *i; + + if (hostName.Equals(server.host.c_str())) + { + server.nextWake = CDateTime::GetCurrentDateTime() + server.timeout; + return; + } + } +} + +static void AddHost (const CStdString& host, vector<string>& hosts) +{ + for (vector<string>::const_iterator it = hosts.begin(); it != hosts.end(); ++it) + if (host.Equals((*it).c_str())) + return; // allready there .. + + if (!host.IsEmpty()) + hosts.push_back(host); +} + +static void AddHostFromDatabase(const DatabaseSettings& setting, vector<string>& hosts) +{ + if (setting.type.Equals("mysql")) + AddHost(setting.host, hosts); +} + +void CWakeOnAccess::QueueMACDiscoveryForHost(const CStdString& host) +{ + if (IsEnabled()) + CJobManager::GetInstance().AddJob(new CMACDiscoveryJob(host), this); +} + +static void AddHostsFromMediaSource(const CMediaSource& source, std::vector<std::string>& hosts) +{ + for (CStdStringArray::const_iterator it = source.vecPaths.begin() ; it != source.vecPaths.end(); it++) + { + CURL url = *it; + + AddHost (url.GetHostName(), hosts); + } +} + +static void AddHostsFromVecSource(const VECSOURCES& sources, vector<string>& hosts) +{ + for (VECSOURCES::const_iterator it = sources.begin(); it != sources.end(); it++) + AddHostsFromMediaSource(*it, hosts); +} + +static void AddHostsFromVecSource(const VECSOURCES* sources, vector<string>& hosts) +{ + if (sources) + AddHostsFromVecSource(*sources, hosts); +} + +void CWakeOnAccess::QueueMACDiscoveryForAllRemotes() +{ + vector<string> hosts; + + // add media sources + CMediaSourceSettings& ms = CMediaSourceSettings::Get(); + + 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); + + // add mysql servers + AddHostFromDatabase(g_advancedSettings.m_databaseVideo, hosts); + AddHostFromDatabase(g_advancedSettings.m_databaseMusic, hosts); + AddHostFromDatabase(g_advancedSettings.m_databaseEpg, hosts); + AddHostFromDatabase(g_advancedSettings.m_databaseTV, hosts); + + // add from path substitutions .. + for (CAdvancedSettings::StringMapping::iterator i = g_advancedSettings.m_pathSubstitutions.begin(); i != g_advancedSettings.m_pathSubstitutions.end(); ++i) + { + CURL url = i->second; + + AddHost (url.GetHostName(), hosts); + } + + for (vector<string>::const_iterator it = hosts.begin(); it != hosts.end(); it++) + QueueMACDiscoveryForHost(*it); +} + +void CWakeOnAccess::SaveMACDiscoveryResult(const CStdString& host, const CStdString& mac) +{ + CLog::Log(LOGNOTICE, "%s - Mac discovered for host '%s' -> '%s'", __FUNCTION__, host.c_str(), mac.c_str()); + + CStdString heading = LOCALIZED(13033); + + for (EntriesVector::iterator i = m_entries.begin(); i != m_entries.end(); ++i) + { + if (host.Equals(i->host.c_str())) + { + CLog::Log(LOGDEBUG, "%s - Update existing entry for host '%s'", __FUNCTION__, host.c_str()); + if (!mac.Equals(i->mac.c_str())) + { + if (IsEnabled()) // show notification only if we have general feature enabled + { + CStdString message = LOCALIZED(13034); + message.Format(message, host); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, heading, message, 4000, true, 3000); + } + + i->mac = mac; + 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); + + CLog::Log(LOGDEBUG, "%s - Create new entry for host '%s'", __FUNCTION__, host.c_str()); + if (IsEnabled()) // show notification only if we have general feature enabled + { + CStdString message = LOCALIZED(13035); + message.Format(message, host); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, heading, message, 4000, true, 3000); + } + + SaveToXML(); +} + +void CWakeOnAccess::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + CMACDiscoveryJob* discoverJob = (CMACDiscoveryJob*)job; + + const CStdString& host = discoverJob->GetHost(); + const CStdString& mac = discoverJob->GetMAC(); + + if (success) + { + CSingleLock lock (m_entrylist_protect); + + SaveMACDiscoveryResult(host, mac); + } + else + { + CLog::Log(LOGERROR, "%s - Mac discovery failed for host '%s'", __FUNCTION__, host.c_str()); + + if (IsEnabled()) + { + CStdString heading = LOCALIZED(13033); + CStdString message = LOCALIZED(13036); + message.Format(message, host); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, message, 4000, true, 3000); + } + } +} + +CStdString CWakeOnAccess::GetSettingFile() +{ + return CSpecialProtocol::TranslatePath("special://masterprofile/wakeonlan.xml"); +} + +void CWakeOnAccess::OnSettingsLoaded() +{ + CSingleLock lock (m_entrylist_protect); + + LoadFromXML(); +} + +void CWakeOnAccess::OnSettingsSaved() const +{ + bool enabled = CSettings::Get().GetBool("powermanagement.wakeonaccess"); + + if (enabled != IsEnabled()) + { + CWakeOnAccess& woa = CWakeOnAccess::Get(); + + woa.SetEnabled(enabled); + + if (enabled) + woa.QueueMACDiscoveryForAllRemotes(); + } +} + +void CWakeOnAccess::LoadFromXML() +{ + bool enabled = CSettings::Get().GetBool("powermanagement.wakeonaccess"); + SetEnabled(enabled); + + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(GetSettingFile())) + { + CLog::Log(LOGNOTICE, "%s - unable to load:%s", __FUNCTION__, GetSettingFile().c_str()); + return; + } + + TiXmlElement* pRootElement = xmlDoc.RootElement(); + if (strcmpi(pRootElement->Value(), "onaccesswakeup")) + { + CLog::Log(LOGERROR, "%s - XML file %s doesnt contain <onaccesswakeup>", __FUNCTION__, GetSettingFile().c_str()); + return; + } + + m_entries.clear(); + + CLog::Log(LOGNOTICE,"WakeOnAccess - Load settings :"); + + int tmp; + if (XMLUtils::GetInt(pRootElement, "netinittimeout", tmp, 0, 5 * 60)) + m_netinit_sec = tmp; + CLog::Log(LOGNOTICE," -Network init timeout : [%d] sec", m_netinit_sec); + + if (XMLUtils::GetInt(pRootElement, "netsettletime", tmp, 0, 5 * 1000)) + m_netsettle_ms = tmp; + CLog::Log(LOGNOTICE," -Network settle time : [%d] ms", m_netsettle_ms); + + const TiXmlNode* pWakeUp = pRootElement->FirstChildElement("wakeup"); + while (pWakeUp) + { + WakeUpEntry entry; + + CStdString 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, "%s - Missing <host> tag or it's empty", __FUNCTION__); + else if (entry.mac.empty()) + CLog::Log(LOGERROR, "%s - 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(LOGNOTICE," Registering wakeup entry:"); + CLog::Log(LOGNOTICE," HostName : %s", entry.host.c_str()); + CLog::Log(LOGNOTICE," MacAddress : %s", entry.mac.c_str()); + CLog::Log(LOGNOTICE," PingPort : %d", entry.ping_port); + CLog::Log(LOGNOTICE," PingMode : %d", entry.ping_mode); + CLog::Log(LOGNOTICE," Timeout : %d (sec)", GetTotalSeconds(entry.timeout)); + CLog::Log(LOGNOTICE," WaitForOnline : %d (sec)", entry.wait_online1_sec); + CLog::Log(LOGNOTICE," WaitForOnlineEx : %d (sec)", entry.wait_online2_sec); + CLog::Log(LOGNOTICE," WaitForServices : %d (sec)", entry.wait_services_sec); + + m_entries.push_back(entry); + } + + pWakeUp = pWakeUp->NextSiblingElement("wakeup"); // 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 (EntriesVector::const_iterator i = m_entries.begin(); i != m_entries.end(); ++i) + { + 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); + } + } + + xmlDoc.SaveFile(GetSettingFile()); +} diff --git a/xbmc/network/WakeOnAccess.h b/xbmc/network/WakeOnAccess.h new file mode 100644 index 0000000000..7f9cdc5a63 --- /dev/null +++ b/xbmc/network/WakeOnAccess.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 Team XBMC + * http://www.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, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "URL.h" +#include "XBDateTime.h" +#include "utils/Job.h" +#include "settings/ISettingsHandler.h" + +class CWakeOnAccess : private IJobCallback, public ISettingsHandler +{ +public: + static CWakeOnAccess &Get(); + + void WakeUpHost (const CURL& fileUrl); + void WakeUpHost (const CStdString& hostName, const std::string& customMessage); + + void QueueMACDiscoveryForAllRemotes(); + + virtual void OnJobComplete(unsigned int jobID, bool success, CJob *job); + virtual void OnSettingsLoaded(); + virtual void OnSettingsSaved() const; + + // struct to keep per host settings + struct WakeUpEntry + { + WakeUpEntry (bool isAwake = false); + + std::string host; + std::string mac; + CDateTimeSpan timeout; + unsigned int wait_online1_sec; // initial wait + unsigned int wait_online2_sec; // extended wait + unsigned int wait_services_sec; + + unsigned short ping_port; // where to ping + unsigned short ping_mode; // how to ping + + CDateTime nextWake; + }; + +private: + CWakeOnAccess(); + CStdString GetSettingFile(); + void LoadFromXML(); + void SaveToXML(); + + void SetEnabled(bool enabled) { m_enabled = enabled; } + bool IsEnabled() const { return m_enabled; } + + void QueueMACDiscoveryForHost(const CStdString& host); + void SaveMACDiscoveryResult(const CStdString& host, const CStdString& mac); + + typedef std::vector<WakeUpEntry> EntriesVector; + EntriesVector m_entries; + CCriticalSection m_entrylist_protect; + bool FindOrTouchHostEntry (const CStdString& hostName, WakeUpEntry& server); + void TouchHostEntry (const CStdString& hostName); + + unsigned int m_netinit_sec, m_netsettle_ms; //time to wait for network connection + + bool m_enabled; + + void WakeUpHost(const WakeUpEntry& server); +}; diff --git a/xbmc/network/linux/NetworkLinux.cpp b/xbmc/network/linux/NetworkLinux.cpp index 013095798b..8908052f65 100644 --- a/xbmc/network/linux/NetworkLinux.cpp +++ b/xbmc/network/linux/NetworkLinux.cpp @@ -37,6 +37,13 @@ #include <sys/sockio.h> #include <net/if.h> #include <net/if_dl.h> +#if defined(TARGET_DARWIN_OSX) + #include <net/if_types.h> + #include <net/route.h> + #include <netinet/if_ether.h> +#else //IOS + #include "network/osx/ioshacks.h" +#endif #include <ifaddrs.h> #elif defined(TARGET_FREEBSD) #include <sys/sockio.h> @@ -520,6 +527,118 @@ void CNetworkLinux::SetNameServers(std::vector<CStdString> nameServers) #endif } +bool CNetworkLinux::PingHost(unsigned long remote_ip, unsigned int timeout_ms) +{ + char cmd_line [64]; + + struct in_addr host_ip; + host_ip.s_addr = remote_ip; + + sprintf(cmd_line, "ping -c 1 -w %d %s", timeout_ms / 1000 + (timeout_ms % 1000) != 0, inet_ntoa(host_ip)); + + int status = system (cmd_line); + + int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + + // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ; + // 0 reply + // 1 no reply + // else some error + + if (result < 0 || result > 1) + CLog::Log(LOGERROR, "Ping fail : status = %d, errno = %d : '%s'", status, errno, cmd_line); + + return result == 0; +} + +#if defined TARGET_DARWIN +bool CNetworkInterfaceLinux::GetHostMacAddress(unsigned long host_ip, CStdString& mac) +{ + bool ret = false; + size_t needed; + char *buf, *next; + struct rt_msghdr *rtm; + struct sockaddr_inarp *sin; + struct sockaddr_dl *sdl; + int mib[6]; + + mac = ""; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_FLAGS; + mib[5] = RTF_LLINFO; + + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &needed, NULL, 0) == 0) + { + if (buf = (char*)malloc(needed)) + { + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf, &needed, NULL, 0) == 0) + { + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) + { + + rtm = (struct rt_msghdr *)next; + sin = (struct sockaddr_inarp *)(rtm + 1); + sdl = (struct sockaddr_dl *)(sin + 1); + + if (host_ip != sin->sin_addr.s_addr || sdl->sdl_alen < 6) + continue; + + u_char *cp = (u_char*)LLADDR(sdl); + + mac.Format("%02X:%02X:%02X:%02X:%02X:%02X", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]); + ret = true; + break; + } + } + free(buf); + } + } + return ret; +} +#else +bool CNetworkInterfaceLinux::GetHostMacAddress(unsigned long host_ip, CStdString& mac) +{ + struct arpreq areq; + struct sockaddr_in* sin; + + memset(&areq, 0x0, sizeof(areq)); + + sin = (struct sockaddr_in *) &areq.arp_pa; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = host_ip; + + sin = (struct sockaddr_in *) &areq.arp_ha; + sin->sin_family = ARPHRD_ETHER; + + strncpy(areq.arp_dev, m_interfaceName.c_str(), sizeof(areq.arp_dev)); + areq.arp_dev[sizeof(areq.arp_dev)-1] = '\0'; + + int result = ioctl (m_network->GetSocket(), SIOCGARP, (caddr_t) &areq); + + if (result != 0) + { +// CLog::Log(LOGERROR, "%s - GetHostMacAddress/ioctl failed with errno (%d)", __FUNCTION__, errno); + return false; + } + + struct sockaddr* res = &areq.arp_ha; + mac.Format("%02X:%02X:%02X:%02X:%02X:%02X", + (uint8_t) res->sa_data[0], (uint8_t) res->sa_data[1], (uint8_t) res->sa_data[2], + (uint8_t) res->sa_data[3], (uint8_t) res->sa_data[4], (uint8_t) res->sa_data[5]); + + for (int i=0; i<6; ++i) + if (res->sa_data[i]) + return true; + + return false; +} +#endif + std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void) { std::vector<NetworkAccessPoint> result; diff --git a/xbmc/network/linux/NetworkLinux.h b/xbmc/network/linux/NetworkLinux.h index 7031b6ec37..be4218ea41 100644 --- a/xbmc/network/linux/NetworkLinux.h +++ b/xbmc/network/linux/NetworkLinux.h @@ -42,6 +42,8 @@ public: virtual CStdString GetMacAddress(void); virtual void GetMacAddressRaw(char rawMac[6]); + virtual bool GetHostMacAddress(unsigned long host, CStdString& mac); + virtual CStdString GetCurrentIPAddress(); virtual CStdString GetCurrentNetmask(); virtual CStdString GetCurrentDefaultGateway(void); @@ -71,6 +73,9 @@ public: virtual std::vector<CNetworkInterface*>& GetInterfaceList(void); virtual CNetworkInterface* GetFirstConnectedInterface(void); + // Ping remote host + virtual bool PingHost(unsigned long host, unsigned int timeout_ms = 2000); + // Get/set the nameserver(s) virtual std::vector<CStdString> GetNameServers(void); virtual void SetNameServers(std::vector<CStdString> nameServers); diff --git a/xbmc/network/osx/ioshacks.h b/xbmc/network/osx/ioshacks.h new file mode 100644 index 0000000000..d15cab0f86 --- /dev/null +++ b/xbmc/network/osx/ioshacks.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013 Team XBMC + * http://www.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, see + * <http://www.gnu.org/licenses/>. + * + */ + +// needed for CNetworkInterfaceLinux::GetHostMacAddress and taken from osx sdk +// net/if_types.h net/route.h netinet/if_ether.h + +/* + * These numbers are used by reliable protocols for determining + * retransmission behavior and are included in the routing structure. + */ +struct rt_metrics { + u_int32_t rmx_locks; /* Kernel must leave these values alone */ + u_int32_t rmx_mtu; /* MTU for this path */ + u_int32_t rmx_hopcount; /* max hops expected */ + int32_t rmx_expire; /* lifetime for route, e.g. redirect */ + u_int32_t rmx_recvpipe; /* inbound delay-bandwidth product */ + u_int32_t rmx_sendpipe; /* outbound delay-bandwidth product */ + u_int32_t rmx_ssthresh; /* outbound gateway buffer limit */ + u_int32_t rmx_rtt; /* estimated round trip time */ + u_int32_t rmx_rttvar; /* estimated rtt variance */ + u_int32_t rmx_pksent; /* packets sent using this route */ + u_int32_t rmx_filler[4]; /* will be used for T/TCP later */ +}; + +/* + * Structures for routing messages. + */ +struct rt_msghdr { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + pid_t rtm_pid; /* identify sender */ + int rtm_seq; /* for sender to identify action */ + int rtm_errno; /* why failed */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; +struct sockaddr_inarp { + u_char sin_len; + u_char sin_family; + u_short sin_port; + struct in_addr sin_addr; + struct in_addr sin_srcaddr; + u_short sin_tos; + u_short sin_other; +#define SIN_PROXY 1 +}; +#define RTF_LLINFO 0x400 /* generated by link layer (e.g. ARP) */ +// --- END diff --git a/xbmc/network/windows/NetworkWin32.cpp b/xbmc/network/windows/NetworkWin32.cpp index f19a2296a1..82fe5a8edb 100644 --- a/xbmc/network/windows/NetworkWin32.cpp +++ b/xbmc/network/windows/NetworkWin32.cpp @@ -19,6 +19,8 @@ */ #include <errno.h> +#include <iphlpapi.h> +#include <IcmpAPI.h> #include "PlatformDefs.h" #include "NetworkWin32.h" #include "utils/log.h" @@ -261,6 +263,58 @@ void CNetworkWin32::SetNameServers(std::vector<CStdString> nameServers) return; } +bool CNetworkWin32::PingHost(unsigned long host, unsigned int timeout_ms /* = 2000 */) +{ + char SendData[] = "poke"; + HANDLE hIcmpFile = IcmpCreateFile(); + BYTE ReplyBuffer [sizeof(ICMP_ECHO_REPLY) + sizeof(SendData)]; + + SetLastError(ERROR_SUCCESS); + + DWORD dwRetVal = IcmpSendEcho(hIcmpFile, host, SendData, sizeof(SendData), + NULL, ReplyBuffer, sizeof(ReplyBuffer), timeout_ms); + + DWORD lastErr = GetLastError(); + if (lastErr != ERROR_SUCCESS) + CLog::Log(LOGERROR, "%s - IcmpSendEcho failed - %s", __FUNCTION__, WUSysMsg(lastErr).c_str()); + + IcmpCloseHandle (hIcmpFile); + + if (dwRetVal != 0) + { + PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer; + return (pEchoReply->Status == IP_SUCCESS); + } + return false; +} + +bool CNetworkInterfaceWin32::GetHostMacAddress(unsigned long host, CStdString& mac) +{ + IPAddr src_ip = inet_addr(GetCurrentIPAddress().c_str()); + BYTE bPhysAddr[6]; // for 6-byte hardware addresses + ULONG PhysAddrLen = 6; // default to length of six bytes + + memset(&bPhysAddr, 0xff, sizeof (bPhysAddr)); + + DWORD dwRetVal = SendARP(host, src_ip, &bPhysAddr, &PhysAddrLen); + if (dwRetVal == NO_ERROR) + { + if (PhysAddrLen == 6) + { + mac.Format("%02X:%02X:%02X:%02X:%02X:%02X", + bPhysAddr[0], bPhysAddr[1], bPhysAddr[2], + bPhysAddr[3], bPhysAddr[4], bPhysAddr[5]); + return true; + } + else + CLog::Log(LOGERROR, "%s - SendArp completed successfully, but mac address has length != 6 (%d)", __FUNCTION__, PhysAddrLen); + } + else + CLog::Log(LOGERROR, "%s - SendArp failed with error (%d)", __FUNCTION__, dwRetVal); + + return false; +} + std::vector<NetworkAccessPoint> CNetworkInterfaceWin32::GetAccessPoints(void) { std::vector<NetworkAccessPoint> result; diff --git a/xbmc/network/windows/NetworkWin32.h b/xbmc/network/windows/NetworkWin32.h index d163d8f4d4..fd75508b77 100644 --- a/xbmc/network/windows/NetworkWin32.h +++ b/xbmc/network/windows/NetworkWin32.h @@ -45,6 +45,8 @@ public: virtual CStdString GetMacAddress(void); virtual void GetMacAddressRaw(char rawMac[6]); + virtual bool GetHostMacAddress(unsigned long host, CStdString& mac); + virtual CStdString GetCurrentIPAddress(); virtual CStdString GetCurrentNetmask(); virtual CStdString GetCurrentDefaultGateway(void); @@ -72,6 +74,9 @@ public: // Return the list of interfaces virtual std::vector<CNetworkInterface*>& GetInterfaceList(void); + // Ping remote host + virtual bool PingHost(unsigned long host, unsigned int timeout_ms = 2000); + // Get/set the nameserver(s) virtual std::vector<CStdString> GetNameServers(void); virtual void SetNameServers(std::vector<CStdString> nameServers); diff --git a/xbmc/powermanagement/PowerManager.cpp b/xbmc/powermanagement/PowerManager.cpp index f671959111..1063a12bbd 100644 --- a/xbmc/powermanagement/PowerManager.cpp +++ b/xbmc/powermanagement/PowerManager.cpp @@ -216,7 +216,12 @@ int CPowerManager::BatteryLevel() } void CPowerManager::ProcessEvents() { - m_instance->PumpPowerEvents(this); + static int nesting = 0; + + if (++nesting == 1) + m_instance->PumpPowerEvents(this); + + nesting--; } void CPowerManager::OnSleep() diff --git a/xbmc/settings/MediaSourceSettings.cpp b/xbmc/settings/MediaSourceSettings.cpp index 7804ac6605..531e586586 100644 --- a/xbmc/settings/MediaSourceSettings.cpp +++ b/xbmc/settings/MediaSourceSettings.cpp @@ -28,6 +28,7 @@ #include "utils/URIUtils.h" #include "utils/XBMCTinyXML.h" #include "utils/XMLUtils.h" +#include "network/WakeOnAccess.h" #define SOURCES_FILE "sources.xml" #define XML_SOURCES "sources" @@ -129,6 +130,8 @@ bool CMediaSourceSettings::Save(const std::string &file) const SetSources(pRoot, "pictures", m_pictureSources, m_defaultPictureSource); SetSources(pRoot, "files", m_fileSources, m_defaultFileSource); + CWakeOnAccess::Get().QueueMACDiscoveryForAllRemotes(); + return doc.SaveFile(file); } diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index 0f2b1b2f69..7a3fec292d 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -50,6 +50,7 @@ #endif // defined(TARGET_LINUX) #include "network/NetworkServices.h" #include "network/upnp/UPnPSettings.h" +#include "network/WakeOnAccess.h" #if defined(TARGET_DARWIN_OSX) #include "osx/XBMCHelper.h" #include "cores/AudioEngine/Engines/CoreAudio/CoreAudioHardware.h" @@ -411,6 +412,7 @@ void CSettings::Uninitialize() m_settingsManager->UnregisterSubSettings(&CViewStateSettings::Get()); // unregister ISettingsHandler implementations + m_settingsManager->UnregisterSettingsHandler(&CWakeOnAccess::Get()); m_settingsManager->UnregisterSettingsHandler(&g_advancedSettings); m_settingsManager->UnregisterSettingsHandler(&CMediaSourceSettings::Get()); m_settingsManager->UnregisterSettingsHandler(&CPlayerCoreFactory::Get()); @@ -799,6 +801,7 @@ void CSettings::InitializeISettingsHandlers() #ifdef HAS_UPNP m_settingsManager->RegisterSettingsHandler(&CUPnPSettings::Get()); #endif + m_settingsManager->RegisterSettingsHandler(&CWakeOnAccess::Get()); } void CSettings::InitializeISubSettings() |