From 7a157b682a621a67786c0b1dc50adb8df9ded636 Mon Sep 17 00:00:00 2001 From: lotodore Date: Sat, 19 Apr 2008 11:48:09 +0000 Subject: [PATCH] The client now downloads a zipped server list file from pokerth.net. This is parsed and the first server entry is used for now. Error handling still missing. boost::iostreams now required. --- pokerth_game.pro | 3 +- pokerth_lib.pro | 1 + src/net/clientcontext.h | 5 ++ src/net/clientstate.h | 45 ++++++++++ src/net/clientthread.h | 5 +- src/net/common/clientstate.cpp | 139 +++++++++++++++++++++++++++++- src/net/common/clientthread.cpp | 4 +- src/net/common/downloadhelper.cpp | 127 +++++++++++++++++++++++++++ src/net/downloadhelper.h | 53 ++++++++++++ src/session.cpp | 9 +- 10 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 src/net/common/downloadhelper.cpp create mode 100644 src/net/downloadhelper.h diff --git a/pokerth_game.pro b/pokerth_game.pro index 3e22921c..93b7d4a1 100644 --- a/pokerth_game.pro +++ b/pokerth_game.pro @@ -236,8 +236,7 @@ SOURCES += \ src/gui/qt/gamelobbydialog/lobbychat/lobbychat.cpp \ src/gui/qt/timeoutmsgbox/timeoutmsgboximpl.cpp \ src/net/common/net_helper_client.cpp \ - src/core/common/loghelper_client.cpp \ - src/third_party/zlib/gun.c + src/core/common/loghelper_client.cpp TRANSLATIONS = \ ts/pokerth_bg.ts \ diff --git a/pokerth_lib.pro b/pokerth_lib.pro index 03f47987..cfd7b3c4 100644 --- a/pokerth_lib.pro +++ b/pokerth_lib.pro @@ -150,6 +150,7 @@ SOURCES += \ src/net/common/clientcontext.cpp \ src/net/common/clientstate.cpp \ src/net/common/clientthread.cpp \ + src/net/common/downloadhelper.cpp \ src/net/common/netpacket.cpp \ src/net/common/resolverthread.cpp \ src/net/common/senderthread.cpp \ diff --git a/src/net/clientcontext.h b/src/net/clientcontext.h index ea5c8ccd..9649f2aa 100644 --- a/src/net/clientcontext.h +++ b/src/net/clientcontext.h @@ -71,6 +71,10 @@ public: {return m_avatarFile;} void SetAvatarFile(const std::string &avatarFile) {m_avatarFile = avatarFile;} + const std::string &GetCacheDir() const + {return m_cacheDir;} + void SetCacheDir(const std::string &cacheDir) + {m_cacheDir = cacheDir;} int GetClientSockaddrSize() const {return m_addrFamily == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);} @@ -89,6 +93,7 @@ private: sockaddr_storage m_clientSockaddr; std::string m_playerName; std::string m_avatarFile; + std::string m_cacheDir; ReceiveBuffer m_receiveBuffer; }; diff --git a/src/net/clientstate.h b/src/net/clientstate.h index d3b9faf4..e1b17b97 100644 --- a/src/net/clientstate.h +++ b/src/net/clientstate.h @@ -33,6 +33,7 @@ class ClientCallback; class ResolverThread; class Game; class NetPacket; +class DownloadHelper; class ClientState { @@ -105,6 +106,50 @@ private: ResolverThread *m_resolver; }; +// State: Start download of the server list. +class ClientStateStartServerListDownload : public ClientState +{ +public: + // Access the state singleton. + static ClientStateStartServerListDownload &Instance(); + + virtual ~ClientStateStartServerListDownload(); + + // Initiate the name resolution. + virtual int Process(ClientThread &client); + +protected: + + // Protected constructor - this is a singleton. + ClientStateStartServerListDownload(); +}; + +// State: Downloading the server list. +class ClientStateDownloadingServerList : public ClientState +{ +public: + // Access the state singleton. + static ClientStateDownloadingServerList &Instance(); + + virtual ~ClientStateDownloadingServerList(); + + void SetDownloadHelper(DownloadHelper *helper); + + // Poll for the completion of the download. + virtual int Process(ClientThread &client); + +protected: + + // Protected constructor - this is a singleton. + ClientStateDownloadingServerList(); + + void Cleanup(); + +private: + + DownloadHelper *m_downloadHelper; +}; + // State: Initiate server connection. class ClientStateStartConnect : public ClientState { diff --git a/src/net/clientthread.h b/src/net/clientthread.h index 69b3c485..1b5cae3f 100644 --- a/src/net/clientthread.h +++ b/src/net/clientthread.h @@ -55,7 +55,8 @@ public: bool sctp, const std::string &pwd, const std::string &playerName, - const std::string &avatarFile); + const std::string &avatarFile, + const std::string &cacheDir); void SendKickPlayer(unsigned playerId); void SendLeaveCurrentGame(); @@ -196,6 +197,8 @@ friend class AbstractClientStateReceiving; friend class ClientStateInit; friend class ClientStateStartResolve; friend class ClientStateResolving; +friend class ClientStateStartServerListDownload; +friend class ClientStateDownloadingServerList; friend class ClientStateStartConnect; friend class ClientStateConnecting; friend class ClientStateStartSession; diff --git a/src/net/common/clientstate.cpp b/src/net/common/clientstate.cpp index d4f80fc6..0880d0dc 100644 --- a/src/net/common/clientstate.cpp +++ b/src/net/common/clientstate.cpp @@ -27,17 +27,27 @@ #include #include #include +#include #include #include #include #include +#include "tinyxml.h" +#include "zlib.h" #include +#include +#include +#include +#include +#include +#include #include using namespace std; +using namespace boost::filesystem; #define CLIENT_WAIT_TIMEOUT_MSEC 50 #define CLIENT_CONNECT_TIMEOUT_SEC 10 @@ -92,7 +102,9 @@ ClientStateInit::Process(ClientThread &client) setsockopt(context.GetSocket(), SOL_SOCKET, SO_NOSIGPIPE, (char *)&nosigpipe, sizeof(nosigpipe)); #endif - client.SetState(ClientStateStartResolve::Instance()); + // TODO + //client.SetState(ClientStateStartResolve::Instance()); + client.SetState(ClientStateStartServerListDownload::Instance()); return MSG_SOCK_INIT_DONE; } @@ -117,7 +129,7 @@ ClientStateStartResolve::~ClientStateStartResolve() int ClientStateStartResolve::Process(ClientThread &client) { - int retVal; + int retVal = MSG_SOCK_INTERNAL_PENDING; ClientContext &context = client.GetContext(); @@ -148,8 +160,6 @@ ClientStateStartResolve::Process(ClientThread &client) ClientStateResolving::Instance().SetResolver(resolver.release()); client.SetState(ClientStateResolving::Instance()); - - retVal = MSG_SOCK_INTERNAL_PENDING; } return retVal; @@ -223,6 +233,127 @@ ClientStateResolving::Cleanup() //----------------------------------------------------------------------------- +ClientStateStartServerListDownload & +ClientStateStartServerListDownload::Instance() +{ + static ClientStateStartServerListDownload state; + return state; +} + +ClientStateStartServerListDownload::ClientStateStartServerListDownload() +{ +} + +ClientStateStartServerListDownload::~ClientStateStartServerListDownload() +{ +} + +int +ClientStateStartServerListDownload::Process(ClientThread &client) +{ + int retVal = MSG_SOCK_INTERNAL_PENDING; + + const ClientContext &context = client.GetContext(); + path tmpServerListPath(context.GetCacheDir()); + tmpServerListPath /= "serverlist.xml.z"; + + std::auto_ptr downloader(new DownloadHelper); + downloader->Init("http://pokerth.net/serverlist.xml.z", tmpServerListPath.directory_string()); + ClientStateDownloadingServerList::Instance().SetDownloadHelper(downloader.release()); + client.SetState(ClientStateDownloadingServerList::Instance()); + + return retVal; +} + +//----------------------------------------------------------------------------- + +ClientStateDownloadingServerList & +ClientStateDownloadingServerList::Instance() +{ + static ClientStateDownloadingServerList state; + return state; +} + +ClientStateDownloadingServerList::ClientStateDownloadingServerList() +: m_downloadHelper(NULL) +{ +} + +ClientStateDownloadingServerList::~ClientStateDownloadingServerList() +{ + Cleanup(); +} + +void +ClientStateDownloadingServerList::SetDownloadHelper(DownloadHelper *helper) +{ + Cleanup(); + m_downloadHelper = helper; +} + +int +ClientStateDownloadingServerList::Process(ClientThread &client) +{ + int retVal = MSG_SOCK_INTERNAL_PENDING; + + // TODO +// if (!m_resolver) +// throw ClientException(__FILE__, __LINE__, ERR_SOCK_RESOLVE_FAILED, 0); + + if (m_downloadHelper->Process()) + { + ClientContext &context = client.GetContext(); + path zippedServerListPath(context.GetCacheDir()); + zippedServerListPath /= "serverlist.xml.z"; + path xmlServerListPath = change_extension(zippedServerListPath, ""); + // Unzip the file. + { + ifstream inFile(zippedServerListPath.directory_string().c_str(), ios_base::in | ios_base::binary); + ofstream outFile(xmlServerListPath.directory_string().c_str(), ios_base::out); + boost::iostreams::filtering_streambuf in; + in.push(boost::iostreams::zlib_decompressor()); + in.push(inFile); + boost::iostreams::copy(in, outFile); + } + // Parse the server address. + TiXmlDocument doc(xmlServerListPath.directory_string()); + + if (doc.LoadFile()) + { + TiXmlHandle docHandle(&doc); + const TiXmlElement *firstServer = docHandle.FirstChild("ServerList" ).FirstChild("Server").ToElement(); + if (firstServer) + { + ClientContext &context = client.GetContext(); + const TiXmlNode *addrNode = firstServer->FirstChild("IPv4Address"); + if (addrNode && addrNode->ToElement()) + context.SetServerAddr(addrNode->ToElement()->Attribute("value")); + const TiXmlNode *portNode = firstServer->FirstChild("Port"); + if (portNode && portNode->ToElement()) + { + int tmpPort = 0; + portNode->ToElement()->QueryIntAttribute("value", &tmpPort); + context.SetServerPort((unsigned)tmpPort); + } + } + } + + client.SetState(ClientStateStartResolve::Instance()); + } + + return retVal; +} + + +void +ClientStateDownloadingServerList::Cleanup() +{ + delete m_downloadHelper; + m_downloadHelper = NULL; +} + +//----------------------------------------------------------------------------- + ClientStateStartConnect & ClientStateStartConnect::Instance() { diff --git a/src/net/common/clientthread.cpp b/src/net/common/clientthread.cpp index 6fe096cb..bfce15b9 100644 --- a/src/net/common/clientthread.cpp +++ b/src/net/common/clientthread.cpp @@ -76,7 +76,8 @@ void ClientThread::Init( const string &serverAddress, const string &alternateServerAddress, unsigned serverPort, bool ipv6, bool sctp, - const string &pwd, const string &playerName, const string &avatarFile) + const string &pwd, const string &playerName, const string &avatarFile, + const string &cacheDir) { if (IsRunning()) { @@ -94,6 +95,7 @@ ClientThread::Init( context.SetPassword(pwd); context.SetPlayerName(playerName); context.SetAvatarFile(avatarFile); + context.SetCacheDir(cacheDir); } void diff --git a/src/net/common/downloadhelper.cpp b/src/net/common/downloadhelper.cpp new file mode 100644 index 00000000..b1e41d80 --- /dev/null +++ b/src/net/common/downloadhelper.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2008 by Lothar May * + * * + * 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 of the License, 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include + +#include + +using namespace std; + +struct DownloadData +{ + CURL *curlHandle; + CURLM *curlMultiHandle; + FILE *targetFile; +}; + + +DownloadHelper::DownloadHelper() +{ + m_data.reset(new DownloadData); + m_data->curlHandle = NULL; + m_data->curlMultiHandle = NULL; + m_data->targetFile = NULL; +} + +DownloadHelper::~DownloadHelper() +{ + Cleanup(); +} + +static size_t curlWriter(void *ptr, size_t size, size_t nmemb, void *stream) +{ + int written = fwrite(ptr, size, nmemb, (FILE *)stream); + return written; +} + +void +DownloadHelper::Init(const string &url, const string &targetFileName) +{ + m_data->targetFile = fopen(targetFileName.c_str(), "wb"); + m_data->curlHandle = curl_easy_init(); + m_data->curlMultiHandle = curl_multi_init(); + + // TODO throw exception on error + + curl_easy_setopt(m_data->curlHandle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_data->curlHandle, CURLOPT_WRITEFUNCTION, curlWriter); + curl_easy_setopt(m_data->curlHandle, CURLOPT_WRITEDATA, m_data->targetFile); + + curl_multi_add_handle(m_data->curlMultiHandle, m_data->curlHandle); +} + +bool +DownloadHelper::Process() +{ + bool retVal = false; + int runningHandles; + while(CURLM_CALL_MULTI_PERFORM == + curl_multi_perform(m_data->curlMultiHandle, &runningHandles)); + + if (runningHandles) + { + struct timeval timeout; + fd_set readSet; + fd_set writeSet; + fd_set exceptSet; + int maxfd; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&exceptSet); + + timeout.tv_sec = 0; + timeout.tv_usec = RECV_TIMEOUT_MSEC * 1000; + + curl_multi_fdset(m_data->curlMultiHandle, &readSet, &writeSet, &exceptSet, &maxfd); + + if (maxfd >= 0) + int selectResult = select(maxfd+1, &readSet, &writeSet, &exceptSet, &timeout); + // TODO throw exception on error + } + else + { + Cleanup(); + retVal = true; + } + return retVal; +} + +void +DownloadHelper::Cleanup() +{ + if (m_data->curlMultiHandle) + { + curl_multi_cleanup(m_data->curlMultiHandle); + m_data->curlMultiHandle = NULL; + } + if (m_data->curlHandle) + { + curl_easy_cleanup(m_data->curlHandle); + m_data->curlHandle = NULL; + } + if (m_data->targetFile) + { + fclose(m_data->targetFile); + m_data->targetFile = NULL; + } +} + diff --git a/src/net/downloadhelper.h b/src/net/downloadhelper.h new file mode 100644 index 00000000..452fad3b --- /dev/null +++ b/src/net/downloadhelper.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2008 by Lothar May * + * * + * 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 of the License, 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +/* libcurl download helper. */ + +#ifndef _DOWNLOADHELPER_H_ +#define _DOWNLOADHELPER_H_ + +#include +#include + +struct DownloadData; + + +class DownloadHelper +{ +public: + DownloadHelper(); + virtual ~DownloadHelper(); + + // Set the parameters. Does not do any error checking. + // Throws an exception on failure. + void Init(const std::string &url, const std::string &targetFileName); + + // Returns true when done, false should call again. + // Throws an exception on error. + bool Process(); + + // Close all handles. + void Cleanup(); + +private: + + std::auto_ptr m_data; +}; + +#endif + diff --git a/src/session.cpp b/src/session.cpp index d4f2b8ea..0aa87549 100755 --- a/src/session.cpp +++ b/src/session.cpp @@ -181,7 +181,8 @@ void Session::startInternetClient() myConfig->readConfigInt("InternetServerUseSctp") == 1, myConfig->readConfigString("InternetServerPassword"), myConfig->readConfigString("MyName"), - myConfig->readConfigString("MyAvatar")); + myConfig->readConfigString("MyAvatar"), + myConfig->readConfigString("CacheDir")); myNetClient->Run(); } @@ -203,7 +204,8 @@ void Session::startNetworkClient(const string &serverAddress, unsigned serverPor sctp, pwd, myConfig->readConfigString("MyName"), - myConfig->readConfigString("MyAvatar")); + myConfig->readConfigString("MyAvatar"), + myConfig->readConfigString("CacheDir")); myNetClient->Run(); myNetClient->SendJoinFirstGame(""); } @@ -228,7 +230,8 @@ void Session::startNetworkClientForLocalServer(const GameData &gameData) myConfig->readConfigInt("ServerUseSctp") == 1, myConfig->readConfigString("ServerPassword"), myConfig->readConfigString("MyName"), - myConfig->readConfigString("MyAvatar")); + myConfig->readConfigString("MyAvatar"), + myConfig->readConfigString("CacheDir")); myNetClient->Run(); myNetClient->SendCreateGame(gameData, NET_DEFAULT_GAME, ""); }