diff options
author | rubidium <rubidium@openttd.org> | 2009-01-20 16:51:55 +0000 |
---|---|---|
committer | rubidium <rubidium@openttd.org> | 2009-01-20 16:51:55 +0000 |
commit | 7c5a61863975862b7c22441db700ef75c5f8840b (patch) | |
tree | f9dd4fc7b570b06c529e9e2384c58c0dd92f2003 /src/network | |
parent | 593f38046270f90c62f6269d292c1dde9b440147 (diff) | |
download | openttd-7c5a61863975862b7c22441db700ef75c5f8840b.tar.xz |
(svn r15176) -Fix [FS#2554]: querying the content server could free when resolving the hostname or connecting takes long/is timing out.
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/core/tcp_content.h | 4 | ||||
-rw-r--r-- | src/network/network.cpp | 2 | ||||
-rw-r--r-- | src/network/network_content.cpp | 477 | ||||
-rw-r--r-- | src/network/network_content.h | 78 | ||||
-rw-r--r-- | src/network/network_content_gui.cpp | 389 |
5 files changed, 542 insertions, 408 deletions
diff --git a/src/network/core/tcp_content.h b/src/network/core/tcp_content.h index 60f68d933..e084f5fc7 100644 --- a/src/network/core/tcp_content.h +++ b/src/network/core/tcp_content.h @@ -191,9 +191,9 @@ public: * @param sin IP etc. of the client */ NetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin) : - NetworkTCPSocketHandler(s), - client_addr(*sin) + NetworkTCPSocketHandler(s) { + if (sin != NULL) this->client_addr = *sin; } /** On destructing of this class, the socket needs to be closed */ diff --git a/src/network/network.cpp b/src/network/network.cpp index 80f929f6b..89131156a 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -946,7 +946,7 @@ static bool NetworkDoClientLoop() // We have to do some UDP checking void NetworkUDPGameLoop() { - NetworkContentLoop(); + _network_content_client.SendReceive(); TCPConnecter::CheckCallbacks(); if (_network_udp_server) { diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 26a92324f..978f9b9e9 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -15,6 +15,7 @@ #include "../ai/ai.hpp" #include "../window_func.h" #include "../gui.h" +#include "../variables.h" #include "core/host.h" #include "network_content.h" @@ -26,7 +27,7 @@ extern bool TarListAddFile(const char *filename); extern bool HasGraphicsSet(const ContentInfo *ci, bool md5sum); -static ClientNetworkContentSocketHandler *_network_content_client; +ClientNetworkContentSocketHandler _network_content_client; /** Wrapper function for the HasProc */ static bool HasGRFConfig(const ContentInfo *ci, bool md5sum) @@ -108,15 +109,44 @@ DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_INFO) /* Something we don't have and has filesize 0 does not exist in te system */ if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST; - for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { - (*iter)->OnReceiveContentInfo(ci); + /* Do we already have a stub for this? */ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + ContentInfo *ici = *iter; + if (ici->type == ci->type && ici->unique_id == ci->unique_id && + memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) { + /* Preserve the name if possible */ + if (StrEmpty(ci->name)) strecpy(ci->name, ici->name, lastof(ci->name)); + + delete ici; + *iter = ci; + + this->OnReceiveContentInfo(ci); + return true; + } + } + + /* Missing content info? Don't list it */ + if (ci->filesize == 0) { + delete ci; + return true; + } + + *this->infos.Append() = ci; + + /* Incoming data means that we might need to reconsider dependencies */ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + this->CheckDependencyState(*iter); } + this->OnReceiveContentInfo(ci); + return true; } void ClientNetworkContentSocketHandler::RequestContentList(ContentType type) { + this->Connect(); + Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST); p->Send_uint8 ((byte)type); p->Send_uint32(_openttd_newgrf_version); @@ -126,6 +156,8 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentType type) void ClientNetworkContentSocketHandler::RequestContentList(uint count, const ContentID *content_ids) { + this->Connect(); + while (count > 0) { /* We can "only" send a limited number of IDs in a single packet. * A packet begins with the packet size and a byte for the type. @@ -151,6 +183,8 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo { if (cv == NULL) return; + this->Connect(); + /* 20 is sizeof(uint32) + sizeof(md5sum (byte[16])) */ assert(cv->Length() < 255); assert(cv->Length() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) / (send_md5sum ? 20 : sizeof(uint32))); @@ -170,10 +204,45 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo } this->Send_Packet(p); + + for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) { + ContentInfo *ci = *iter; + bool found = false; + for (ContentIterator iter2 = this->infos.Begin(); iter2 != this->infos.End(); iter2++) { + ContentInfo *ci2 = *iter; + if (ci->type == ci2->type && ci->unique_id == ci2->unique_id && + (!send_md5sum || memcmp(ci->md5sum, ci2->md5sum, sizeof(ci->md5sum)) == 0)) { + found = true; + break; + } + } + if (!found) { + *this->infos.Append() = ci; + } else { + delete ci; + } + } } -void ClientNetworkContentSocketHandler::RequestContent(uint count, const uint32 *content_ids) +void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes) { + files = 0; + bytes = 0; + + /** Make the list of items to download */ + ContentID *ids = MallocT<ContentID>(infos.Length()); + for (ContentIterator iter = infos.Begin(); iter != infos.End(); iter++) { + const ContentInfo *ci = *iter; + if (!ci->IsSelected()) continue; + + ids[files++] = ci->id; + bytes += ci->filesize; + } + + uint count = files; + ContentID *content_ids = ids; + this->Connect(); + while (count > 0) { /* We can "only" send a limited number of IDs in a single packet. * A packet begins with the packet size and a byte for the type. @@ -192,6 +261,8 @@ void ClientNetworkContentSocketHandler::RequestContent(uint count, const uint32 count -= p_count; content_ids += count; } + + free(ids); } /** @@ -300,10 +371,7 @@ DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT) return false; } - /* Tell the rest we downloaded some bytes */ - for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { - (*iter)->OnDownloadProgress(this->curInfo, toRead); - } + this->OnDownloadProgress(this->curInfo, toRead); if (toRead == 0) { /* We read nothing; that's our marker for end-of-stream. @@ -316,9 +384,7 @@ DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT) TarListAddFile(GetFullFilename(this->curInfo, false)); - for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { - (*iter)->OnDownloadComplete(this->curInfo->id); - } + this->OnDownloadComplete(this->curInfo->id); } else { ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_EXTRACT, 0, 0); } @@ -339,10 +405,11 @@ DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT) * @param s the socket to communicate over * @param sin the IP/port of the server */ -ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin) : - NetworkContentSocketHandler(s, sin), +ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() : + NetworkContentSocketHandler(INVALID_SOCKET, NULL), curFile(NULL), - curInfo(NULL) + curInfo(NULL), + isConnecting(false) { } @@ -351,76 +418,63 @@ ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler() { delete this->curInfo; if (this->curFile != NULL) fclose(this->curFile); -} - -/** - * Connect to the content server if needed, return the socket handler and - * add the callback to the socket handler. - * @param cb the callback to add - * @return the socket handler or NULL is connecting failed - */ -ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb) -{ - /* If there's no connection or when it's broken, we try to reconnect. - * Otherwise we just add ourselves and return immediatelly */ - if (_network_content_client != NULL && !_network_content_client->has_quit) { - *_network_content_client->callbacks.Append() = cb; - return _network_content_client; - } - SOCKET s = socket(AF_INET, SOCK_STREAM, 0); - if (s == INVALID_SOCKET) return NULL; - - if (!SetNoDelay(s)) DEBUG(net, 1, "Setting TCP_NODELAY failed"); + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter; +} - struct sockaddr_in sin; - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = NetworkResolveHost(NETWORK_CONTENT_SERVER_HOST); - sin.sin_port = htons(NETWORK_CONTENT_SERVER_PORT); +class NetworkContentConnecter : TCPConnecter { +public: + NetworkContentConnecter(const NetworkAddress &address) : TCPConnecter(address) {} - /* We failed to connect for which reason what so ever */ - if (connect(s, (struct sockaddr*)&sin, sizeof(sin)) != 0) return NULL; + virtual void OnFailure() + { + _network_content_client.isConnecting = false; + _network_content_client.OnConnect(false); + } - if (!SetNonBlocking(s)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error? + virtual void OnConnect(SOCKET s) + { + assert(_network_content_client.sock == INVALID_SOCKET); + _network_content_client.isConnecting = false; + _network_content_client.sock = s; + _network_content_client.has_quit = false; + _network_content_client.OnConnect(true); + } +}; - if (_network_content_client != NULL) { - if (_network_content_client->sock != INVALID_SOCKET) closesocket(_network_content_client->sock); - _network_content_client->sock = s; +/** + * Connect with the content server. + */ +void ClientNetworkContentSocketHandler::Connect() +{ + this->lastActivity = _realtime_tick; - /* Clean up the mess that could've been left behind */ - _network_content_client->has_quit = false; - delete _network_content_client->curInfo; - _network_content_client->curInfo = NULL; - if (_network_content_client->curFile != NULL) fclose(_network_content_client->curFile); - _network_content_client->curFile = NULL; - } else { - _network_content_client = new ClientNetworkContentSocketHandler(s, &sin); - } - *_network_content_client->callbacks.Append() = cb; - return _network_content_client; + if (this->sock != INVALID_SOCKET || this->isConnecting) return; + this->isConnecting = true; + new NetworkContentConnecter(NetworkAddress(NETWORK_CONTENT_SERVER_HOST, NETWORK_CONTENT_SERVER_PORT)); } /** - * Remove yourself from the network content callback list and when - * that list is empty the connection will be closed. + * Disconnect from the content server. */ -void NetworkContent_Disconnect(ContentCallback *cb) +void ClientNetworkContentSocketHandler::Disconnect() { - _network_content_client->callbacks.Erase(_network_content_client->callbacks.Find(cb)); - - if (_network_content_client->callbacks.Length() == 0) { - delete _network_content_client; - _network_content_client = NULL; - } + if (this->sock == INVALID_SOCKET) return; + this->Close(); } /** * Check whether we received/can send some data from/to the content server and * when that's the case handle it appropriately */ -void NetworkContentLoop() +void ClientNetworkContentSocketHandler::SendReceive() { - if (_network_content_client == NULL) return; + if (this->sock == INVALID_SOCKET || this->isConnecting) return; + + if (this->lastActivity + IDLE_TIMEOUT < _realtime_tick) { + this->Close(); + return; + } fd_set read_fd, write_fd; struct timeval tv; @@ -428,8 +482,8 @@ void NetworkContentLoop() FD_ZERO(&read_fd); FD_ZERO(&write_fd); - FD_SET(_network_content_client->sock, &read_fd); - FD_SET(_network_content_client->sock, &write_fd); + FD_SET(this->sock, &read_fd); + FD_SET(this->sock, &write_fd); tv.tv_sec = tv.tv_usec = 0; // don't block at all. #if !defined(__MORPHOS__) && !defined(__AMIGA__) @@ -437,9 +491,290 @@ void NetworkContentLoop() #else WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL); #endif - if (FD_ISSET(_network_content_client->sock, &read_fd)) _network_content_client->Recv_Packets(); - _network_content_client->writable = !!FD_ISSET(_network_content_client->sock, &write_fd); - _network_content_client->Send_Packets(); + if (FD_ISSET(this->sock, &read_fd)) this->Recv_Packets(); + this->writable = !!FD_ISSET(this->sock, &write_fd); + this->Send_Packets(); +} + +/** + * Download information of a given Content ID if not already tried + * @param cid the ID to try + */ +void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid) +{ + /* When we tried to download it already, don't try again */ + if (this->requested.Contains(cid)) return; + + *this->requested.Append() = cid; + assert(this->requested.Contains(cid)); + this->RequestContentList(1, &cid); +} + +/** + * Get the content info based on a ContentID + * @param cid the ContentID to search for + * @return the ContentInfo or NULL if not found + */ +ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid) +{ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + ContentInfo *ci = *iter; + if (ci->id == cid) return ci; + } + return NULL; +} + + +/** + * Select a specific content id. + * @param cid the content ID to select + */ +void ClientNetworkContentSocketHandler::Select(ContentID cid) +{ + ContentInfo *ci = this->GetContent(cid); + if (ci->state != ContentInfo::UNSELECTED) return; + + ci->state = ContentInfo::SELECTED; + this->CheckDependencyState(ci); +} + +/** + * Unselect a specific content id. + * @param cid the content ID to deselect + */ +void ClientNetworkContentSocketHandler::Unselect(ContentID cid) +{ + ContentInfo *ci = this->GetContent(cid); + if (!ci->IsSelected()) return; + + ci->state = ContentInfo::UNSELECTED; + this->CheckDependencyState(ci); +} + +/** Select everything we can select */ +void ClientNetworkContentSocketHandler::SelectAll() +{ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + ContentInfo *ci = *iter; + if (ci->state == ContentInfo::UNSELECTED) { + ci->state = ContentInfo::SELECTED; + this->CheckDependencyState(ci); + } + } +} + +/** Select everything that's an update for something we've got */ +void ClientNetworkContentSocketHandler::SelectUpdate() +{ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + ContentInfo *ci = *iter; + if (ci->state == ContentInfo::UNSELECTED && ci->update) { + ci->state = ContentInfo::SELECTED; + this->CheckDependencyState(ci); + } + } +} + +/** Unselect everything that we've not downloaded so far. */ +void ClientNetworkContentSocketHandler::UnselectAll() +{ + for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + ContentInfo *ci = *iter; + if (ci->IsSelected()) ci->state = ContentInfo::UNSELECTED; + } +} + +/** Toggle the state of a content info and check it's dependencies */ +void ClientNetworkContentSocketHandler::ToggleSelectedState(const ContentInfo *ci) +{ + switch (ci->state) { + case ContentInfo::SELECTED: + case ContentInfo::AUTOSELECTED: + this->Unselect(ci->id); + break; + + case ContentInfo::UNSELECTED: + this->Select(ci->id); + break; + + default: + break; + } +} + +/** + * Reverse lookup the dependencies of (direct) parents over a given child. + * @param parents list to store all parents in (is not cleared) + * @param child the child to search the parents' dependencies for + */ +void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const +{ + for (ConstContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + const ContentInfo *ci = *iter; + if (ci == child) continue; + + for (uint i = 0; i < ci->dependency_count; i++) { + if (ci->dependencies[i] == child->id) { + *parents.Append() = ci; + break; + } + } + } +} + +/** + * Reverse lookup the dependencies of all parents over a given child. + * @param tree list to store all parents in (is not cleared) + * @param child the child to search the parents' dependencies for + */ +void ClientNetworkContentSocketHandler::ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const +{ + *tree.Append() = child; + + /* First find all direct parents */ + for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) { + ConstContentVector parents; + this->ReverseLookupDependency(parents, *iter); + + for (ConstContentIterator piter = parents.Begin(); piter != parents.End(); piter++) { + tree.Include(*piter); + } + } +} + +/** + * Check the dependencies (recursively) of this content info + * @param ci the content info to check the dependencies of + */ +void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci) +{ + if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) { + /* Selection is easy; just walk all children and set the + * autoselected state. That way we can see what we automatically + * selected and thus can unselect when a dependency is removed. */ + for (uint i = 0; i < ci->dependency_count; i++) { + ContentInfo *c = this->GetContent(ci->dependencies[i]); + if (c == NULL) { + this->DownloadContentInfo(ci->dependencies[i]); + } else if (c->state == ContentInfo::UNSELECTED) { + c->state = ContentInfo::AUTOSELECTED; + this->CheckDependencyState(c); + } + } + return; + } + + if (ci->state != ContentInfo::UNSELECTED) return; + + /* For unselection we need to find the parents of us. We need to + * unselect them. After that we unselect all children that we + * depend on and are not used as dependency for us, but only when + * we automatically selected them. */ + ConstContentVector parents; + this->ReverseLookupDependency(parents, ci); + for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { + const ContentInfo *c = *iter; + if (!c->IsSelected()) continue; + + this->Unselect(c->id); + } + + for (uint i = 0; i < ci->dependency_count; i++) { + const ContentInfo *c = this->GetContent(ci->dependencies[i]); + if (c == NULL) { + DownloadContentInfo(ci->dependencies[i]); + continue; + } + if (c->state != ContentInfo::AUTOSELECTED) continue; + + /* Only unselect when WE are the only parent. */ + parents.Clear(); + this->ReverseLookupDependency(parents, c); + + /* First check whether anything depends on us */ + int sel_count = 0; + bool force_selection = false; + for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { + if ((*iter)->IsSelected()) sel_count++; + if ((*iter)->state == ContentInfo::SELECTED) force_selection = true; + } + if (sel_count == 0) { + /* Nothing depends on us */ + this->Unselect(c->id); + continue; + } + /* Something manually selected depends directly on us */ + if (force_selection) continue; + + /* "Flood" search to find all items in the dependency graph*/ + parents.Clear(); + this->ReverseLookupTreeDependency(parents, c); + + /* Is there anything that is "force" selected?, if so... we're done. */ + for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { + if ((*iter)->state != ContentInfo::SELECTED) continue; + + force_selection = true; + break; + } + + /* So something depended directly on us */ + if (force_selection) continue; + + /* Nothing depends on us, mark the whole graph as unselected. + * After that's done run over them once again to test their children + * to unselect. Don't do it immediatelly because it'll do exactly what + * we're doing now. */ + for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { + const ContentInfo *c = *iter; + if (c->state == ContentInfo::AUTOSELECTED) this->Unselect(c->id); + } + for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { + this->CheckDependencyState(this->GetContent((*iter)->id)); + } + } +} + +/*** CALLBACK ***/ + +void ClientNetworkContentSocketHandler::OnConnect(bool success) +{ + for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { + (*iter)->OnConnect(success); + } +} + +void ClientNetworkContentSocketHandler::OnDisconnect() +{ + for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { + (*iter)->OnDisconnect(); + } +} + +void ClientNetworkContentSocketHandler::OnReceiveContentInfo(const ContentInfo *ci) +{ + for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { + (*iter)->OnReceiveContentInfo(ci); + } +} + +void ClientNetworkContentSocketHandler::OnDownloadProgress(const ContentInfo *ci, uint bytes) +{ + for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { + (*iter)->OnDownloadProgress(ci, bytes); + } +} + +void ClientNetworkContentSocketHandler::OnDownloadComplete(ContentID cid) +{ + ContentInfo *ci = this->GetContent(cid); + if (ci != NULL) { + ci->state = ContentInfo::ALREADY_HERE; + } + + for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); iter++) { + (*iter)->OnDownloadComplete(cid); + } } #endif /* ENABLE_NETWORK */ diff --git a/src/network/network_content.h b/src/network/network_content.h index bcbb05f8f..a566dafc4 100644 --- a/src/network/network_content.h +++ b/src/network/network_content.h @@ -12,23 +12,37 @@ /** Vector with content info */ typedef SmallVector<ContentInfo *, 16> ContentVector; +typedef SmallVector<const ContentInfo *, 16> ConstContentVector; + /** Iterator for the content vector */ typedef ContentInfo **ContentIterator; +typedef const ContentInfo * const * ConstContentIterator; /** Callbacks for notifying others about incoming data */ struct ContentCallback { /** + * Callback for when the connection has finished + * @param success whether the connection was made or that we failed to make it + */ + virtual void OnConnect(bool success) {} + + /** + * Callback for when the connection got disconnected. + */ + virtual void OnDisconnect() {} + + /** * We received a content info. * @param ci the content info */ - virtual void OnReceiveContentInfo(ContentInfo *ci) {} + virtual void OnReceiveContentInfo(const ContentInfo *ci) {} /** * We have progress in the download of a file * @param ci the content info of the file * @param bytes the number of bytes downloaded since the previous call */ - virtual void OnDownloadProgress(ContentInfo *ci, uint bytes) {} + virtual void OnDownloadProgress(const ContentInfo *ci, uint bytes) {} /** * We have finished downloading a file @@ -43,32 +57,74 @@ struct ContentCallback { /** * Socket handler for the content server connection */ -class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler { +class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler, ContentCallback { protected: SmallVector<ContentCallback *, 2> callbacks; ///< Callbacks to notify "the world" + SmallVector<ContentID, 4> requested; ///< ContentIDs we already requested (so we don't do it again) + ContentVector infos; ///< All content info we received FILE *curFile; ///< Currently downloaded file ContentInfo *curInfo; ///< Information about the currently downloaded file + bool isConnecting; ///< Whether we're connecting + uint32 lastActivity; ///< The last time there was network activity - friend ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb); - friend void NetworkContent_Disconnect(ContentCallback *cb); + friend class NetworkContentConnecter; DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_INFO); DECLARE_CONTENT_RECEIVE_COMMAND(PACKET_CONTENT_SERVER_CONTENT); - ClientNetworkContentSocketHandler(SOCKET s, const struct sockaddr_in *sin); - ~ClientNetworkContentSocketHandler(); + ContentInfo *GetContent(ContentID cid); + void DownloadContentInfo(ContentID cid); + + void OnConnect(bool success); + void OnDisconnect(); + void OnReceiveContentInfo(const ContentInfo *ci); + void OnDownloadProgress(const ContentInfo *ci, uint bytes); + void OnDownloadComplete(ContentID cid); public: + /** The idle timeout; when to close the connection because it's idle. */ + static const int IDLE_TIMEOUT = 60 * 1000; + + ClientNetworkContentSocketHandler(); + ~ClientNetworkContentSocketHandler(); + + void Connect(); + void SendReceive(); + void Disconnect(); + void RequestContentList(ContentType type); void RequestContentList(uint count, const ContentID *content_ids); void RequestContentList(ContentVector *cv, bool send_md5sum = true); - void RequestContent(uint count, const uint32 *content_ids); + void DownloadSelectedContent(uint &files, uint &bytes); + + void Select(ContentID cid); + void Unselect(ContentID cid); + void SelectAll(); + void SelectUpdate(); + void UnselectAll(); + void ToggleSelectedState(const ContentInfo *ci); + + void ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const; + void ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const; + void CheckDependencyState(ContentInfo *ci); + + /** Get the number of content items we know locally. */ + uint Length() const { return this->infos.Length(); } + /** Get the begin of the content inf iterator. */ + ConstContentIterator Begin() const { return this->infos.Begin(); } + /** Get the nth position of the content inf iterator. */ + ConstContentIterator Get(uint32 index) const { return this->infos.Get(index); } + /** Get the end of the content inf iterator. */ + ConstContentIterator End() const { return this->infos.End(); } + + /** Add a callback to this class */ + void AddCallback(ContentCallback *cb) { this->callbacks.Include(cb); } + /** Remove a callback */ + void RemoveCallback(ContentCallback *cb) { this->callbacks.Erase(this->callbacks.Find(cb)); } }; -ClientNetworkContentSocketHandler *NetworkContent_Connect(ContentCallback *cb); -void NetworkContent_Disconnect(ContentCallback *cb); -void NetworkContentLoop(); +extern ClientNetworkContentSocketHandler _network_content_client; void ShowNetworkContentListWindow(ContentVector *cv = NULL, ContentType type = CONTENT_TYPE_END); diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index 90aa3e9af..fa8b80d04 100644 --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -61,33 +61,14 @@ public: * with flags whether to download them or not. * @param infos the list to search in */ - NetworkContentDownloadStatusWindow(ContentVector &infos) : + NetworkContentDownloadStatusWindow() : Window(&_network_content_download_status_window_desc), cur_id(UINT32_MAX) { this->parent = FindWindowById(WC_NETWORK_WINDOW, 1); - this->connection = NetworkContent_Connect(this); - if (this->connection == NULL) { - /* When the connection got broken and we can't rebuild it we can't do much :( */ - ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_CONNECTION_LOST, 0, 0); - delete this; - return; - } - - /** Make the list of items to download */ - uint32 *ids = MallocT<uint32>(infos.Length()); - for (ContentIterator iter = infos.Begin(); iter != infos.End(); iter++) { - const ContentInfo *ci = *iter; - if (!ci->IsSelected()) continue; - - ids[this->total_files++] = ci->id; - this->total_bytes += ci->filesize; - } - - /** And request them for download */ - this->connection->RequestContent(this->total_files, ids); - free(ids); + _network_content_client.AddCallback(this); + _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes); this->FindWindowPlacementAndResize(&_network_content_download_status_window_desc); } @@ -121,7 +102,7 @@ public: } } - NetworkContent_Disconnect(this); + _network_content_client.RemoveCallback(this); } virtual void OnPaint() @@ -158,7 +139,7 @@ public: if (widget == NCDSWW_CANCELOK) delete this; } - virtual void OnDownloadProgress(ContentInfo *ci, uint bytes) + virtual void OnDownloadProgress(const ContentInfo *ci, uint bytes) { if (ci->id != this->cur_id) { strecpy(this->name, ci->filename, lastof(this->name)); @@ -198,11 +179,8 @@ class NetworkContentListWindow : public Window, ContentCallback { NCLWW_RESIZE, ///< Resize button }; - ClientNetworkContentSocketHandler *connection; ///< Connection with the content server - SmallVector<ContentID, 4> requested; ///< ContentIDs we already requested (so we don't do it again) - ContentVector infos; ///< All content info we received - ContentInfo *selected; ///< The selected content info - int list_pos; ///< Our position in the list + const ContentInfo *selected; ///< The selected content info + int list_pos; ///< Our position in the list /** Make sure that the currently selected content info is within the visible part of the matrix */ void ScrollToSelected() @@ -218,232 +196,28 @@ class NetworkContentListWindow : public Window, ContentCallback { } } - /** - * Download information of a given Content ID if not already tried - * @param cid the ID to try - */ - void DownloadContentInfo(ContentID cid) - { - /* When we tried to download it already, don't try again */ - if (this->requested.Contains(cid)) return; - - *this->requested.Append() = cid; - assert(this->requested.Contains(cid)); - this->connection->RequestContentList(1, &cid); - } - - - /** - * Get the content info based on a ContentID - * @param cid the ContentID to search for - * @return the ContentInfo or NULL if not found - */ - ContentInfo *GetContent(ContentID cid) - { - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - ContentInfo *ci = *iter; - if (ci->id == cid) return ci; - } - return NULL; - } - - /** - * Reverse lookup the dependencies of (direct) parents over a given child. - * @param parents list to store all parents in (is not cleared) - * @param child the child to search the parents' dependencies for - */ - void ReverseLookupDependency(ContentVector &parents, ContentInfo *child) - { - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - ContentInfo *ci = *iter; - if (ci == child) continue; - - for (uint i = 0; i < ci->dependency_count; i++) { - if (ci->dependencies[i] == child->id) { - *parents.Append() = ci; - break; - } - } - } - } - - /** - * Reverse lookup the dependencies of all parents over a given child. - * @param tree list to store all parents in (is not cleared) - * @param child the child to search the parents' dependencies for - */ - void ReverseLookupTreeDependency(ContentVector &tree, ContentInfo *child) - { - *tree.Append() = child; - - /* First find all direct parents */ - for (ContentIterator iter = tree.Begin(); iter != tree.End(); iter++) { - ContentVector parents; - ReverseLookupDependency(parents, *iter); - - for (ContentIterator piter = parents.Begin(); piter != parents.End(); piter++) { - tree.Include(*piter); - } - } - } - - /** - * Check the dependencies (recursively) of this content info - * @param ci the content info to check the dependencies of - */ - void CheckDependencyState(ContentInfo *ci) - { - if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) { - /* Selection is easy; just walk all children and set the - * autoselected state. That way we can see what we automatically - * selected and thus can unselect when a dependency is removed. */ - for (uint i = 0; i < ci->dependency_count; i++) { - ContentInfo *c = this->GetContent(ci->dependencies[i]); - if (c == NULL) { - DownloadContentInfo(ci->dependencies[i]); - } else if (c->state == ContentInfo::UNSELECTED) { - c->state = ContentInfo::AUTOSELECTED; - this->CheckDependencyState(c); - } - } - return; - } - - if (ci->state != ContentInfo::UNSELECTED) return; - - /* For unselection we need to find the parents of us. We need to - * unselect them. After that we unselect all children that we - * depend on and are not used as dependency for us, but only when - * we automatically selected them. */ - ContentVector parents; - ReverseLookupDependency(parents, ci); - for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { - ContentInfo *c = *iter; - if (!c->IsSelected()) continue; - - c->state = ContentInfo::UNSELECTED; - CheckDependencyState(c); - } - - for (uint i = 0; i < ci->dependency_count; i++) { - ContentInfo *c = this->GetContent(ci->dependencies[i]); - if (c == NULL) { - DownloadContentInfo(ci->dependencies[i]); - continue; - } - if (c->state != ContentInfo::AUTOSELECTED) continue; - - /* Only unselect when WE are the only parent. */ - parents.Clear(); - ReverseLookupDependency(parents, c); - - /* First check whether anything depends on us */ - int sel_count = 0; - bool force_selection = false; - for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { - if ((*iter)->IsSelected()) sel_count++; - if ((*iter)->state == ContentInfo::SELECTED) force_selection = true; - } - if (sel_count == 0) { - /* Nothing depends on us */ - c->state = ContentInfo::UNSELECTED; - this->CheckDependencyState(c); - continue; - } - /* Something manually selected depends directly on us */ - if (force_selection) continue; - - /* "Flood" search to find all items in the dependency graph*/ - parents.Clear(); - ReverseLookupTreeDependency(parents, c); - - /* Is there anything that is "force" selected?, if so... we're done. */ - for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { - if ((*iter)->state != ContentInfo::SELECTED) continue; - - force_selection = true; - break; - } - - /* So something depended directly on us */ - if (force_selection) continue; - - /* Nothing depends on us, mark the whole graph as unselected. - * After that's done run over them once again to test their children - * to unselect. Don't do it immediatelly because it'll do exactly what - * we're doing now. */ - for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { - ContentInfo *c = *iter; - if (c->state == ContentInfo::AUTOSELECTED) c->state = ContentInfo::UNSELECTED; - } - for (ContentIterator iter = parents.Begin(); iter != parents.End(); iter++) { - this->CheckDependencyState(*iter); - } - } - } - - /** Toggle the state of a content info and check it's dependencies */ - void ToggleSelectedState() - { - switch (this->selected->state) { - case ContentInfo::SELECTED: - case ContentInfo::AUTOSELECTED: - this->selected->state = ContentInfo::UNSELECTED; - break; - - case ContentInfo::UNSELECTED: - this->selected->state = ContentInfo::SELECTED; - break; - - default: - return; - } - - this->CheckDependencyState(this->selected); - } - public: /** * Create the content list window. * @param desc the window description to pass to Window's constructor. - * @param cv the list with content to show; if NULL find content ourselves */ - NetworkContentListWindow(const WindowDesc *desc, ContentVector *cv, ContentType type) : Window(desc, 1), selected(NULL), list_pos(0) + NetworkContentListWindow(const WindowDesc *desc, bool select_all) : Window(desc, 1), selected(NULL), list_pos(0) { - this->connection = NetworkContent_Connect(this); - this->vscroll.cap = 14; this->resize.step_height = 14; this->resize.step_width = 2; - if (cv == NULL) { - if (type == CONTENT_TYPE_END) { - this->connection->RequestContentList(CONTENT_TYPE_BASE_GRAPHICS); - this->connection->RequestContentList(CONTENT_TYPE_NEWGRF); - this->connection->RequestContentList(CONTENT_TYPE_AI); - } else { - this->connection->RequestContentList(type); - } - this->HideWidget(NCLWW_SELECT_ALL); - } else { - this->connection->RequestContentList(cv, true); - - for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) { - *this->infos.Append() = *iter; - } - this->HideWidget(NCLWW_SELECT_UPDATE); - } + _network_content_client.AddCallback(this); + this->HideWidget(select_all ? NCLWW_SELECT_UPDATE : NCLWW_SELECT_ALL); - SetVScrollCount(this, this->infos.Length()); + SetVScrollCount(this, _network_content_client.Length()); this->FindWindowPlacementAndResize(desc); } /** Free everything we allocated */ ~NetworkContentListWindow() { - NetworkContent_Disconnect(this); - - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter; + _network_content_client.RemoveCallback(this); } virtual void OnPaint() @@ -452,7 +226,7 @@ public: uint filesize = 0; bool show_select_all = false; bool show_select_update = false; - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { + for (ConstContentIterator iter = _network_content_client.Begin(); iter != _network_content_client.End(); iter++) { const ContentInfo *ci = *iter; switch (ci->state) { case ContentInfo::SELECTED: @@ -480,7 +254,7 @@ public: /* Fill the matrix with the information */ uint y = this->widget[NCLWW_MATRIX].top + 3; int cnt = 0; - for (ContentIterator iter = this->infos.Get(this->vscroll.pos); iter != this->infos.End() && cnt < this->vscroll.cap; iter++, cnt++) { + for (ConstContentIterator iter = _network_content_client.Get(this->vscroll.pos); iter != _network_content_client.End() && cnt < this->vscroll.cap; iter++, cnt++) { const ContentInfo *ci = *iter; if (ci == this->selected) GfxFillRect(this->widget[NCLWW_CHECKBOX].left + 1, y - 2, this->widget[NCLWW_NAME].right - 1, y + 9, 10); @@ -557,17 +331,14 @@ public: ContentID cid = this->selected->dependencies[i]; /* Try to find the dependency */ - ContentIterator iter = this->infos.Begin(); - for (; iter != this->infos.End(); iter++) { + ConstContentIterator iter = _network_content_client.Begin(); + for (; iter != _network_content_client.End(); iter++) { const ContentInfo *ci = *iter; if (ci->id != cid) continue; p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name); break; } - - /* We miss the dependency, but we'll only request it if not done before. */ - if (iter == this->infos.End()) DownloadContentInfo(cid); } SetDParamStr(0, buf); y += DrawStringMultiLine(this->widget[NCLWW_DETAILS].left + 5, y, STR_CONTENT_DETAIL_DEPENDENCIES, this->widget[NCLWW_DETAILS].right - this->widget[NCLWW_DETAILS].left - 5, max_y - y); @@ -586,13 +357,13 @@ public: if (this->selected->IsSelected()) { /* When selected show all manually selected content that depends on this */ - ContentVector tree; - ReverseLookupTreeDependency(tree, this->selected); + ConstContentVector tree; + _network_content_client.ReverseLookupTreeDependency(tree, this->selected); char buf[8192] = ""; char *p = buf; - for (ContentIterator iter = tree.Begin(); iter != tree.End(); iter++) { - ContentInfo *ci = *iter; + for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) { + const ContentInfo *ci = *iter; if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue; p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name); @@ -626,34 +397,27 @@ public: if (id_v >= this->vscroll.cap) return; // click out of bounds id_v += this->vscroll.pos; - if (id_v >= this->infos.Length()) return; // click out of bounds + if (id_v >= _network_content_client.Length()) return; // click out of bounds - this->selected = this->infos[id_v]; + this->selected = *_network_content_client.Get(id_v); this->list_pos = id_v; - if (pt.x <= this->widget[NCLWW_CHECKBOX].right) this->ToggleSelectedState(); + if (pt.x <= this->widget[NCLWW_CHECKBOX].right) _network_content_client.ToggleSelectedState(this->selected); this->SetDirty(); } break; case NCLWW_SELECT_ALL: + _network_content_client.SelectAll(); + this->SetDirty(); + break; + case NCLWW_SELECT_UPDATE: - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - ContentInfo *ci = *iter; - if (ci->state == ContentInfo::UNSELECTED && (widget == NCLWW_SELECT_ALL || ci->update)) { - ci->state = ContentInfo::SELECTED; - CheckDependencyState(ci); - } - } + _network_content_client.SelectUpdate(); this->SetDirty(); break; case NCLWW_UNSELECT: - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - ContentInfo *ci = *iter; - /* No need to check dependencies; when everything's off nothing can depend */ - if (ci->IsSelected()) ci->state = ContentInfo::UNSELECTED; - } this->SetDirty(); break; @@ -662,14 +426,14 @@ public: break; case NCLWW_DOWNLOAD: - new NetworkContentDownloadStatusWindow(this->infos); + new NetworkContentDownloadStatusWindow(); break; } } virtual EventState OnKeyPress(uint16 key, uint16 keycode) { - if (this->infos.Length() == 0) return ES_HANDLED; + if (_network_content_client.Length() == 0) return ES_HANDLED; switch (keycode) { case WKC_UP: @@ -678,7 +442,7 @@ public: break; case WKC_DOWN: /* scroll down by one */ - if (this->list_pos < (int)this->infos.Length() - 1) this->list_pos++; + if (this->list_pos < (int)_network_content_client.Length() - 1) this->list_pos++; break; case WKC_PAGEUP: /* scroll up a page */ @@ -686,7 +450,7 @@ public: break; case WKC_PAGEDOWN: /* scroll down a page */ - this->list_pos = min(this->list_pos + this->vscroll.cap, (int)this->infos.Length() - 1); + this->list_pos = min(this->list_pos + this->vscroll.cap, (int)_network_content_client.Length() - 1); break; case WKC_HOME: /* jump to beginning */ @@ -694,18 +458,18 @@ public: break; case WKC_END: /* jump to end */ - this->list_pos = this->infos.Length() - 1; + this->list_pos = _network_content_client.Length() - 1; break; case WKC_SPACE: - this->ToggleSelectedState(); + _network_content_client.ToggleSelectedState(this->selected); this->SetDirty(); return ES_HANDLED; default: return ES_NOT_HANDLED; } - this->selected = this->infos[this->list_pos]; + this->selected = *_network_content_client.Get(this->list_pos); /* scroll to the new server if it is outside the current range */ this->ScrollToSelected(); @@ -721,7 +485,7 @@ public: this->widget[NCLWW_MATRIX].data = (this->vscroll.cap << 8) + 1; - SetVScrollCount(this, this->infos.Length()); + SetVScrollCount(this, _network_content_client.Length()); /* Make the matrix and details section grow both bigger (or smaller) */ delta.x /= 2; @@ -732,52 +496,25 @@ public: this->widget[NCLWW_DETAILS].left -= delta.x; } - virtual void OnReceiveContentInfo(ContentInfo *rci) + virtual void OnReceiveContentInfo(const ContentInfo *rci) { - /* Do we already have a stub for this? */ - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - ContentInfo *ci = *iter; - if (ci->type == rci->type && ci->unique_id == rci->unique_id && - memcmp(ci->md5sum, rci->md5sum, sizeof(ci->md5sum)) == 0) { - /* Preserve the name if possible */ - if (StrEmpty(rci->name)) strecpy(rci->name, ci->name, lastof(rci->name)); - - delete ci; - *iter = rci; - - this->SetDirty(); - return; - } - } - - /* Missing content info? Don't list it */ - if (rci->filesize == 0) { - delete rci; - return; - } - - /* Nothing selected, lets select something */ - if (this->selected == NULL) this->selected = rci; - - *this->infos.Append() = rci; - SetVScrollCount(this, this->infos.Length()); - - /* Incoming data means that we might need to reconsider dependencies */ - for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) { - CheckDependencyState(*iter); - } - + SetVScrollCount(this, _network_content_client.Length()); this->SetDirty(); } virtual void OnDownloadComplete(ContentID cid) { - /* When we know something that completed downloading, we have it! */ - ContentInfo *ci = this->GetContent(cid); - if (ci != NULL) { - ci->state = ContentInfo::ALREADY_HERE; - this->SetDirty(); + this->SetDirty(); + } + + virtual void OnConnect(bool success) + { + if (!success) { + ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_CONNECT, 0, 0); + delete this; } + + this->SetDirty(); } }; @@ -827,21 +564,27 @@ static const WindowDesc _network_content_list_desc = { void ShowNetworkContentListWindow(ContentVector *cv, ContentType type) { #if defined(WITH_ZLIB) - if (NetworkContent_Connect(NULL)) { - DeleteWindowById(WC_NETWORK_WINDOW, 1); - new NetworkContentListWindow(&_network_content_list_desc, cv, type); - NetworkContent_Disconnect(NULL); + if (cv == NULL) { + if (type == CONTENT_TYPE_END) { + _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS); + _network_content_client.RequestContentList(CONTENT_TYPE_AI); + _network_content_client.RequestContentList(CONTENT_TYPE_NEWGRF); + _network_content_client.RequestContentList(CONTENT_TYPE_AI_LIBRARY); + } else { + _network_content_client.RequestContentList(type); + } } else { - ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_CONNECT, 0, 0); + _network_content_client.RequestContentList(cv, true); + } + + new NetworkContentListWindow(&_network_content_list_desc, cv != NULL); #else - { - ShowErrorMessage(STR_CONTENT_NO_ZLIB_SUB, STR_CONTENT_NO_ZLIB, 0, 0); -#endif /* WITH_ZLIB */ - /* Connection failed... clean up the mess */ - if (cv != NULL) { - for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter; - } + ShowErrorMessage(STR_CONTENT_NO_ZLIB_SUB, STR_CONTENT_NO_ZLIB, 0, 0); + /* Connection failed... clean up the mess */ + if (cv != NULL) { + for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter; } +#endif /* WITH_ZLIB */ } #endif /* ENABLE_NETWORK */ |