mirror of
https://github.com/OpenTTD/OpenTTD.git
synced 2025-03-06 06:15:04 +00:00
(svn r18994) -Change: content mirroring support (based on work by TrueBrain).
This commit is contained in:
parent
c75f19f40b
commit
2db44fc18e
@ -3415,6 +3415,14 @@
|
||||
RelativePath=".\..\src\network\core\tcp_game.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\tcp_http.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\tcp_http.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\udp.cpp"
|
||||
>
|
||||
|
@ -3412,6 +3412,14 @@
|
||||
RelativePath=".\..\src\network\core\tcp_game.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\tcp_http.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\tcp_http.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\network\core\udp.cpp"
|
||||
>
|
||||
|
@ -803,6 +803,8 @@ network/core/tcp_content.cpp
|
||||
network/core/tcp_content.h
|
||||
network/core/tcp_game.cpp
|
||||
network/core/tcp_game.h
|
||||
network/core/tcp_http.cpp
|
||||
network/core/tcp_http.h
|
||||
network/core/udp.cpp
|
||||
network/core/udp.h
|
||||
|
||||
|
@ -18,12 +18,17 @@
|
||||
#define NETWORK_MASTER_SERVER_HOST "master.openttd.org"
|
||||
/** DNS hostname of the content server */
|
||||
#define NETWORK_CONTENT_SERVER_HOST "content.openttd.org"
|
||||
/** DNS hostname of the HTTP-content mirror server */
|
||||
#define NETWORK_CONTENT_MIRROR_HOST "binaries.openttd.org"
|
||||
/** URL of the HTTP mirror system */
|
||||
#define NETWORK_CONTENT_MIRROR_URL "/bananas"
|
||||
/** Message sent to the masterserver to 'identify' this client as OpenTTD */
|
||||
#define NETWORK_MASTER_SERVER_WELCOME_MESSAGE "OpenTTDRegister"
|
||||
|
||||
enum {
|
||||
NETWORK_MASTER_SERVER_PORT = 3978, ///< The default port of the master server (UDP)
|
||||
NETWORK_CONTENT_SERVER_PORT = 3978, ///< The default port of the content server (TCP)
|
||||
NETWORK_CONTENT_MIRROR_PORT = 80, ///< The default port of the content mirror (TCP)
|
||||
NETWORK_DEFAULT_PORT = 3979, ///< The default port of the game server (TCP & UDP)
|
||||
NETWORK_DEFAULT_DEBUGLOG_PORT = 3982, ///< The default port debug-log is sent too (TCP)
|
||||
|
||||
|
317
src/network/core/tcp_http.cpp
Normal file
317
src/network/core/tcp_http.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD 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, version 2.
|
||||
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_http.cpp Basic functions to receive and send HTTP TCP packets.
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_NETWORK
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../rev.h"
|
||||
#include "../network_func.h"
|
||||
|
||||
#include "tcp.h"
|
||||
#include "tcp_http.h"
|
||||
|
||||
/** List of open HTTP connections. */
|
||||
static SmallVector<NetworkHTTPSocketHandler *, 1> _http_connections;
|
||||
|
||||
NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
|
||||
HTTPCallback *callback, const char *host, const char *url,
|
||||
const char *data, int depth) :
|
||||
NetworkSocketHandler(),
|
||||
recv_pos(0),
|
||||
recv_length(0),
|
||||
callback(callback),
|
||||
data(data),
|
||||
redirect_depth(depth),
|
||||
sock(s)
|
||||
{
|
||||
int bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == NULL ? 0 : strlen(data)) + 128;
|
||||
char *buffer = AllocaM(char, bufferSize);
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
|
||||
if (data != NULL) {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, _openttd_revision, (int)strlen(data), data);
|
||||
} else {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\n\r\n", url, host, _openttd_revision);
|
||||
}
|
||||
|
||||
ssize_t size = strlen(buffer);
|
||||
ssize_t res = send(this->sock, (const char*)buffer, size, 0);
|
||||
if (res != size) {
|
||||
/* Sending all data failed. Socket can't handle this little bit
|
||||
* of information? Just fall back to the old system! */
|
||||
this->callback->OnFailure();
|
||||
delete this;
|
||||
}
|
||||
|
||||
*_http_connections.Append() = this;
|
||||
}
|
||||
|
||||
NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
|
||||
{
|
||||
this->CloseConnection();
|
||||
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
free((void*)this->data);
|
||||
}
|
||||
|
||||
NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
NetworkSocketHandler::CloseConnection(error);
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to simplify the error handling.
|
||||
* @param msg the error message to show.
|
||||
*/
|
||||
#define return_error(msg) { DEBUG(net, 0, msg); return -1; }
|
||||
|
||||
static const char * const NEWLINE = "\r\n"; ///< End of line marker
|
||||
static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
|
||||
static const char * const HTTP_1_0 = "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers
|
||||
static const char * const HTTP_1_1 = "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers
|
||||
static const char * const CONTENT_LENGTH = "Content-Length: "; ///< Header for the length of the content
|
||||
static const char * const LOCATION = "Location: "; ///< Header for location
|
||||
|
||||
/**
|
||||
* Handle the header of a HTTP reply.
|
||||
* @return amount of data to continue downloading.
|
||||
* > 0: we need to download N bytes.
|
||||
* = 0: we're being redirected.
|
||||
* < 0: an error occured. Downloading failed.
|
||||
* @note if an error occured the header might not be in its
|
||||
* original state. No effort is undertaken to bring
|
||||
* the header in its original state.
|
||||
*/
|
||||
int NetworkHTTPSocketHandler::HandleHeader()
|
||||
{
|
||||
assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
|
||||
assert(strstr(this->recv_buffer, END_OF_HEADER) != NULL);
|
||||
|
||||
/* We expect a HTTP/1.[01] reply */
|
||||
if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
|
||||
strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
|
||||
return_error("[tcp/http] received invalid HTTP reply");
|
||||
}
|
||||
|
||||
char *status = this->recv_buffer + strlen(HTTP_1_0);
|
||||
if (strncmp(status, "200", 3) == 0) {
|
||||
/* We are going to receive a document. */
|
||||
|
||||
/* Get the length of the document to receive */
|
||||
char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
|
||||
if (length == NULL) return_error("[tcp/http] missing 'content-length' header");
|
||||
|
||||
/* Skip the header */
|
||||
length += strlen(CONTENT_LENGTH);
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
char *end_of_line = strstr(length, NEWLINE);
|
||||
|
||||
/* Read the length */
|
||||
*end_of_line = '\0';
|
||||
int len = atoi(length);
|
||||
/* Restore the header. */
|
||||
*end_of_line = '\r';
|
||||
|
||||
/* Make sure we're going to download at least something;
|
||||
* zero sized files are, for OpenTTD's purposes, always
|
||||
* wrong. You can't have gzips of 0 bytes! */
|
||||
if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
|
||||
return len;
|
||||
}
|
||||
|
||||
if (strncmp(status, "301", 3) != 0 &&
|
||||
strncmp(status, "302", 3) != 0 &&
|
||||
strncmp(status, "303", 3) != 0 &&
|
||||
strncmp(status, "307", 3) != 0) {
|
||||
/* We are not going to be redirected :(. */
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
*strstr(status, NEWLINE) = '\0';
|
||||
DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
|
||||
|
||||
/* Redirect to other URL */
|
||||
char *uri = strcasestr(this->recv_buffer, LOCATION);
|
||||
if (uri == NULL) return_error("[tcp/http] missing 'location' header for redirect");
|
||||
|
||||
uri += strlen(LOCATION);
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
char *end_of_line = strstr(uri, NEWLINE);
|
||||
*end_of_line = '\0';
|
||||
|
||||
DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
|
||||
|
||||
int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
|
||||
if (ret != 0) return ret;
|
||||
|
||||
/* We've relinguished control of data now. */
|
||||
this->data = NULL;
|
||||
|
||||
/* Restore the header. */
|
||||
*end_of_line = '\r';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
|
||||
{
|
||||
char *hname = strstr(uri, "://");
|
||||
if (hname == NULL) return_error("[tcp/http] invalid location");
|
||||
|
||||
hname += 3;
|
||||
|
||||
char *url = strchr(hname, '/');
|
||||
if (url == NULL) return_error("[tcp/http] invalid location");
|
||||
|
||||
*url = '\0';
|
||||
|
||||
/* Fetch the hostname, and possible port number. */
|
||||
const char *company = NULL;
|
||||
const char *port = NULL;
|
||||
ParseConnectionString(&company, &port, hname);
|
||||
if (company != NULL) return_error("[tcp/http] invalid hostname");
|
||||
|
||||
NetworkAddress address(hname, port == NULL ? 80 : atoi(port));
|
||||
|
||||
/* Restore the URL. */
|
||||
*url = '/';
|
||||
new NetworkHTTPContentConnecter(address, callback, url, data, depth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef return_error
|
||||
|
||||
/**
|
||||
* Handle receiving of HTTP data.
|
||||
* @return state of the receival of HTTP data.
|
||||
* > 0: we need more cycles for downloading
|
||||
* = 0: we are done downloading
|
||||
* < 0: we have hit an error
|
||||
*/
|
||||
int NetworkHTTPSocketHandler::Receive()
|
||||
{
|
||||
for (;;) {
|
||||
ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
|
||||
if (res == -1) {
|
||||
int err = GET_LAST_ERROR();
|
||||
if (err != EWOULDBLOCK) {
|
||||
/* Something went wrong... (104 is connection reset by peer) */
|
||||
if (err != 104) DEBUG(net, 0, "recv failed with error %d", err);
|
||||
return -1;
|
||||
}
|
||||
/* Connection would block, so stop for now */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* No more data... did we get everything we wanted? */
|
||||
if (res == 0) {
|
||||
if (this->recv_length != 0) return -1;
|
||||
|
||||
this->callback->OnReceiveData(NULL, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wait till we read the end-of-header identifier */
|
||||
if (this->recv_length == 0) {
|
||||
int read = this->recv_pos + res;
|
||||
int end = min(read, lengthof(this->recv_buffer) - 1);
|
||||
|
||||
/* Do a 'safe' search for the end of the header. */
|
||||
char prev = this->recv_buffer[end];
|
||||
this->recv_buffer[end] = '\0';
|
||||
char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
|
||||
this->recv_buffer[end] = prev;
|
||||
|
||||
if (end_of_header == NULL) {
|
||||
if (read == lengthof(this->recv_buffer)) {
|
||||
DEBUG(net, 0, "[tcp/http] header too big");
|
||||
return -1;
|
||||
}
|
||||
this->recv_pos = read;
|
||||
} else {
|
||||
int ret = this->HandleHeader();
|
||||
if (ret <= 0) return ret;
|
||||
|
||||
this->recv_length = ret;
|
||||
|
||||
end_of_header += strlen(END_OF_HEADER);
|
||||
int len = min(read - (end_of_header - this->recv_buffer), res);
|
||||
if (len != 0) {
|
||||
this->callback->OnReceiveData(end_of_header, len);
|
||||
this->recv_length -= len;
|
||||
}
|
||||
|
||||
this->recv_pos = 0;
|
||||
}
|
||||
} else {
|
||||
res = min(this->recv_length, res);
|
||||
/* Receive whatever we're expecting. */
|
||||
this->callback->OnReceiveData(this->recv_buffer, res);
|
||||
this->recv_length -= res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
||||
{
|
||||
/* No connections, just bail out. */
|
||||
if (_http_connections.Length() == 0) return;
|
||||
|
||||
fd_set read_fd;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&read_fd);
|
||||
for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); iter++) {
|
||||
FD_SET((*iter)->sock, &read_fd);
|
||||
}
|
||||
|
||||
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
|
||||
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
|
||||
int n = select(FD_SETSIZE, &read_fd, NULL, NULL, &tv);
|
||||
#else
|
||||
int n = WaitSelect(FD_SETSIZE, &read_fd, NULL, NULL, &tv, NULL);
|
||||
#endif
|
||||
if (n == -1) return;
|
||||
|
||||
for (NetworkHTTPSocketHandler **iter = _http_connections.Begin(); iter < _http_connections.End(); /* nothing */) {
|
||||
NetworkHTTPSocketHandler *cur = *iter;
|
||||
|
||||
if (FD_ISSET(cur->sock, &read_fd)) {
|
||||
int ret = cur->Receive();
|
||||
/* First send the failure. */
|
||||
if (ret < 0) cur->callback->OnFailure();
|
||||
if (ret <= 0) {
|
||||
/* Then... the connection can be closed */
|
||||
cur->CloseConnection();
|
||||
_http_connections.Erase(iter);
|
||||
delete cur;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ENABLE_NETWORK */
|
143
src/network/core/tcp_http.h
Normal file
143
src/network/core/tcp_http.h
Normal file
@ -0,0 +1,143 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD 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, version 2.
|
||||
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_http.h Basic functions to receive and send HTTP TCP packets.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_TCP_HTTP_H
|
||||
#define NETWORK_CORE_TCP_HTTP_H
|
||||
|
||||
#include "address.h"
|
||||
|
||||
#ifdef ENABLE_NETWORK
|
||||
|
||||
/** Callback for when the HTTP handler has something to tell us. */
|
||||
struct HTTPCallback {
|
||||
/**
|
||||
* An error has occured and the connection has been closed.
|
||||
* @note HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnFailure() = 0;
|
||||
|
||||
/**
|
||||
* We're receiving data.
|
||||
* @param data the received data, NULL when all data has been received.
|
||||
* @param length the amount of received data, 0 when all data has been received.
|
||||
* @note When NULL is sent the HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnReceiveData(const char *data, size_t length) = 0;
|
||||
};
|
||||
|
||||
/** Base socket handler for HTTP traffic. */
|
||||
class NetworkHTTPSocketHandler : public NetworkSocketHandler {
|
||||
private:
|
||||
char recv_buffer[4096]; ///< Partially received message.
|
||||
int recv_pos; ///< Current position in buffer.
|
||||
int recv_length; ///< Length of the data still retrieving.
|
||||
HTTPCallback *callback; ///< The callback to call for the incoming data.
|
||||
const char *data; ///< The (POST) data we might want to forward (to a redirect).
|
||||
int redirect_depth; ///< The depth of the redirection.
|
||||
|
||||
int HandleHeader();
|
||||
int Receive();
|
||||
public:
|
||||
SOCKET sock; ///< The socket currently connected to
|
||||
|
||||
/**
|
||||
* Whether this socket is currently bound to a socket.
|
||||
* @return true when the socket is bound, false otherwise
|
||||
*/
|
||||
bool IsConnected() const
|
||||
{
|
||||
return this->sock != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
virtual NetworkRecvStatus CloseConnection(bool error = true);
|
||||
|
||||
/**
|
||||
* Start the querying
|
||||
* @param sock the socket of this connection
|
||||
* @param callback the callback for HTTP retrieval
|
||||
* @param url the url at the server
|
||||
* @param data the data to send
|
||||
* @param depth the depth (redirect recursion) of the queries
|
||||
*/
|
||||
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback,
|
||||
const char *host, const char *url, const char *data, int depth);
|
||||
|
||||
/** Free whatever needs to be freed. */
|
||||
~NetworkHTTPSocketHandler();
|
||||
|
||||
/**
|
||||
* Connect to the given URI.
|
||||
* @param uri the URI to connect to.
|
||||
* @param callback the callback to send data back on.
|
||||
* @param data the data we want to send (as POST).
|
||||
* @param depth the recursion/redirect depth.
|
||||
*/
|
||||
static int Connect(char *uri, HTTPCallback *callback,
|
||||
const char *data = NULL, int depth = 0);
|
||||
|
||||
/**
|
||||
* Do the receiving for all HTTP connections.
|
||||
*/
|
||||
static void HTTPReceive();
|
||||
};
|
||||
|
||||
/** Connect with a HTTP server and do ONE query. */
|
||||
class NetworkHTTPContentConnecter : TCPConnecter {
|
||||
HTTPCallback *callback; ///< Callback to tell that we received some data (or won't).
|
||||
const char *url; ///< The URL we want to get at the server.
|
||||
const char *data; ///< The data to send
|
||||
int depth; ///< How far we have recursed
|
||||
|
||||
public:
|
||||
/**
|
||||
* Start the connecting.
|
||||
* @param address the address to connect to
|
||||
* @param callback the callback for HTTP retrieval
|
||||
* @param url the url at the server
|
||||
* @param data the data to send
|
||||
* @param depth the depth (redirect recursion) of the queries
|
||||
*/
|
||||
NetworkHTTPContentConnecter(const NetworkAddress &address,
|
||||
HTTPCallback *callback, const char *url,
|
||||
const char *data = NULL, int depth = 0) :
|
||||
TCPConnecter(address),
|
||||
callback(callback),
|
||||
url(strdup(url)),
|
||||
data(data),
|
||||
depth(depth)
|
||||
{
|
||||
}
|
||||
|
||||
/** Free all our allocated data. */
|
||||
~NetworkHTTPContentConnecter()
|
||||
{
|
||||
free((void*)this->url);
|
||||
}
|
||||
|
||||
virtual void OnFailure()
|
||||
{
|
||||
this->callback->OnFailure();
|
||||
free((void*)this->data);
|
||||
}
|
||||
|
||||
virtual void OnConnect(SOCKET s)
|
||||
{
|
||||
new NetworkHTTPSocketHandler(s, this->callback, this->address.GetHostname(), this->url, this->data, this->depth);
|
||||
/* We've relinguished control of data now. */
|
||||
this->data = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ENABLE_NETWORK */
|
||||
|
||||
#endif /* NETWORK_CORE_HTTP_H */
|
@ -1050,6 +1050,7 @@ void NetworkUDPGameLoop()
|
||||
{
|
||||
_network_content_client.SendReceive();
|
||||
TCPConnecter::CheckCallbacks();
|
||||
NetworkHTTPSocketHandler::HTTPReceive();
|
||||
|
||||
if (_network_udp_server) {
|
||||
_udp_server_socket->ReceivePackets();
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "../gui.h"
|
||||
#include "../variables.h"
|
||||
#include "../base_media_base.h"
|
||||
#include "../settings_type.h"
|
||||
#include "network_content.h"
|
||||
|
||||
#include "table/strings.h"
|
||||
@ -251,7 +252,7 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes)
|
||||
void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
|
||||
{
|
||||
bytes = 0;
|
||||
|
||||
@ -269,8 +270,41 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin
|
||||
/* If there's nothing to download, do nothing. */
|
||||
if (files == 0) return;
|
||||
|
||||
uint count = files;
|
||||
ContentID *content_ids = content.Begin();
|
||||
if (_settings_client.network.no_http_content_downloads || fallback) {
|
||||
this->DownloadSelectedContentFallback(content);
|
||||
} else {
|
||||
this->DownloadSelectedContentHTTP(content);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const ContentIDList &content)
|
||||
{
|
||||
uint count = content.Length();
|
||||
|
||||
/* Allocate memory for the whole request.
|
||||
* Requests are "id\nid\n..." (as strings), so assume the maximum ID,
|
||||
* which is uint32 so 10 characters long. Then the newlines and
|
||||
* multiply that all with the count and then add the '\0'. */
|
||||
uint bytes = (10 + 1) * count + 1;
|
||||
char *content_request = MallocT<char>(bytes);
|
||||
const char *lastof = content_request + bytes - 1;
|
||||
|
||||
char *p = content_request;
|
||||
for (const ContentID *id = content.Begin(); id != content.End(); id++) {
|
||||
p += seprintf(p, lastof, "%d\n", *id);
|
||||
}
|
||||
|
||||
this->http_response_index = -1;
|
||||
|
||||
NetworkAddress address(NETWORK_CONTENT_MIRROR_HOST, NETWORK_CONTENT_MIRROR_PORT);
|
||||
new NetworkHTTPContentConnecter(address, this, NETWORK_CONTENT_MIRROR_URL, content_request);
|
||||
/* NetworkHTTPContentConnecter takes over freeing of content_request! */
|
||||
}
|
||||
|
||||
void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const ContentIDList &content)
|
||||
{
|
||||
uint count = content.Length();
|
||||
const ContentID *content_ids = content.Begin();
|
||||
this->Connect();
|
||||
|
||||
while (count > 0) {
|
||||
@ -451,6 +485,146 @@ void ClientNetworkContentSocketHandler::AfterDownload()
|
||||
}
|
||||
}
|
||||
|
||||
/* Also called to just clean up the mess. */
|
||||
void ClientNetworkContentSocketHandler::OnFailure()
|
||||
{
|
||||
/* If we fail, download the rest via the 'old' system. */
|
||||
uint files, bytes;
|
||||
this->DownloadSelectedContent(files, bytes, true);
|
||||
|
||||
this->http_response.Reset();
|
||||
this->http_response_index = -2;
|
||||
|
||||
if (this->curFile != NULL) {
|
||||
fclose(this->curFile);
|
||||
this->curFile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length)
|
||||
{
|
||||
assert(data == NULL || length != 0);
|
||||
|
||||
/* Ignore any latent data coming from a connection we closed. */
|
||||
if (this->http_response_index == -2) return;
|
||||
|
||||
if (this->http_response_index == -1) {
|
||||
if (data != NULL) {
|
||||
/* Append the rest of the response. */
|
||||
memcpy(this->http_response.Append(length), data, length);
|
||||
return;
|
||||
} else {
|
||||
/* Make sure the response is properly terminated. */
|
||||
*this->http_response.Append() = '\0';
|
||||
|
||||
/* And prepare for receiving the rest of the data. */
|
||||
this->http_response_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (data != NULL) {
|
||||
/* We have data, so write it to the file. */
|
||||
if (fwrite(data, 1, length, this->curFile) != length) {
|
||||
/* Writing failed somehow, let try via the old method. */
|
||||
this->OnFailure();
|
||||
} else {
|
||||
/* Just received the data. */
|
||||
this->OnDownloadProgress(this->curInfo, (uint)length);
|
||||
}
|
||||
/* Nothing more to do now. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->curFile != NULL) {
|
||||
/* We've finished downloading a file. */
|
||||
this->AfterDownload();
|
||||
}
|
||||
|
||||
if ((uint)this->http_response_index >= this->http_response.Length()) {
|
||||
/* It's not a real failure, but if there's
|
||||
* nothing more to download it helps with
|
||||
* cleaning up the stuff we allocated. */
|
||||
this->OnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
delete this->curInfo;
|
||||
/* When we haven't opened a file this must be our first packet with metadata. */
|
||||
this->curInfo = new ContentInfo;
|
||||
|
||||
/** Check p for not being null and return calling OnFailure if that's not the case. */
|
||||
#define check(p) { if ((p) == NULL) { this->OnFailure(); return; } }
|
||||
/** Check p for not being null and then terminate, or return calling OnFailure. */
|
||||
#define check_and_terminate(p) { check(p); *(p) = '\0'; }
|
||||
|
||||
for (;;) {
|
||||
char *str = this->http_response.Begin() + this->http_response_index;
|
||||
char *p = strchr(str, '\n');
|
||||
check_and_terminate(p);
|
||||
|
||||
/* Update the index for the next one */
|
||||
this->http_response_index += strlen(str) + 1;
|
||||
|
||||
/* Read the ID */
|
||||
p = strchr(str, ',');
|
||||
check_and_terminate(p);
|
||||
this->curInfo->id = (ContentID)atoi(str);
|
||||
|
||||
/* Read the type */
|
||||
str = p + 1;
|
||||
p = strchr(str, ',');
|
||||
check_and_terminate(p);
|
||||
this->curInfo->type = (ContentType)atoi(str);
|
||||
|
||||
/* Read the file size */
|
||||
str = p + 1;
|
||||
p = strchr(str, ',');
|
||||
check_and_terminate(p);
|
||||
this->curInfo->filesize = atoi(str);
|
||||
|
||||
/* Read the URL */
|
||||
str = p + 1;
|
||||
/* Is it a fallback URL? If so, just continue with the next one. */
|
||||
if (strncmp(str, "ottd", 4) == 0) {
|
||||
if ((uint)this->http_response_index >= this->http_response.Length()) {
|
||||
/* Have we gone through all lines? */
|
||||
this->OnFailure();
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
p = strrchr(str, '/');
|
||||
check(p);
|
||||
|
||||
char tmp[MAX_PATH];
|
||||
if (strecpy(tmp, p, lastof(tmp)) == lastof(tmp)) {
|
||||
this->OnFailure();
|
||||
return;
|
||||
}
|
||||
/* Remove the extension from the string. */
|
||||
for (uint i = 0; i < 2; i++) {
|
||||
p = strrchr(tmp, '.');
|
||||
check_and_terminate(p);
|
||||
}
|
||||
|
||||
/* Copy the string, without extension, to the filename. */
|
||||
strecpy(this->curInfo->filename, tmp, lastof(this->curInfo->filename));
|
||||
|
||||
/* Request the next file. */
|
||||
if (!this->BeforeDownload()) {
|
||||
this->OnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkHTTPSocketHandler::Connect(str, this);
|
||||
return;
|
||||
}
|
||||
|
||||
#undef check
|
||||
#undef check_and_terminate
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket handler with the given socket and (server) address.
|
||||
* @param s the socket to communicate over
|
||||
@ -458,6 +632,7 @@ void ClientNetworkContentSocketHandler::AfterDownload()
|
||||
*/
|
||||
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
|
||||
NetworkContentSocketHandler(),
|
||||
http_response_index(-2),
|
||||
curFile(NULL),
|
||||
curInfo(NULL),
|
||||
isConnecting(false)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define NETWORK_CONTENT_H
|
||||
|
||||
#include "core/tcp_content.h"
|
||||
#include "core/tcp_http.h"
|
||||
|
||||
#if defined(ENABLE_NETWORK)
|
||||
|
||||
@ -63,12 +64,14 @@ struct ContentCallback {
|
||||
/**
|
||||
* Socket handler for the content server connection
|
||||
*/
|
||||
class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler, ContentCallback {
|
||||
class ClientNetworkContentSocketHandler : public NetworkContentSocketHandler, ContentCallback, HTTPCallback {
|
||||
protected:
|
||||
typedef SmallVector<ContentID, 4> ContentIDList;
|
||||
SmallVector<ContentCallback *, 2> callbacks; ///< Callbacks to notify "the world"
|
||||
ContentIDList requested; ///< ContentIDs we already requested (so we don't do it again)
|
||||
ContentVector infos; ///< All content info we received
|
||||
SmallVector<char, 1024> http_response; ///< The HTTP response to the requests we've been doing
|
||||
int http_response_index; ///< Where we are, in the response, with handling it
|
||||
|
||||
FILE *curFile; ///< Currently downloaded file
|
||||
ContentInfo *curInfo; ///< Information about the currently downloaded file
|
||||
@ -89,8 +92,14 @@ protected:
|
||||
void OnDownloadProgress(const ContentInfo *ci, uint bytes);
|
||||
void OnDownloadComplete(ContentID cid);
|
||||
|
||||
void OnFailure();
|
||||
void OnReceiveData(const char *data, size_t length);
|
||||
|
||||
bool BeforeDownload();
|
||||
void AfterDownload();
|
||||
|
||||
void DownloadSelectedContentHTTP(const ContentIDList &content);
|
||||
void DownloadSelectedContentFallback(const ContentIDList &content);
|
||||
public:
|
||||
/** The idle timeout; when to close the connection because it's idle. */
|
||||
static const int IDLE_TIMEOUT = 60 * 1000;
|
||||
@ -106,7 +115,7 @@ public:
|
||||
void RequestContentList(uint count, const ContentID *content_ids);
|
||||
void RequestContentList(ContentVector *cv, bool send_md5sum = true);
|
||||
|
||||
void DownloadSelectedContent(uint &files, uint &bytes);
|
||||
void DownloadSelectedContent(uint &files, uint &bytes, bool fallback = false);
|
||||
|
||||
void Select(ContentID cid);
|
||||
void Unselect(ContentID cid);
|
||||
|
@ -147,6 +147,7 @@ struct NetworkSettings {
|
||||
bool reload_cfg; ///< reload the config file before restarting
|
||||
char last_host[NETWORK_HOSTNAME_LENGTH]; ///< IP address of the last joined server
|
||||
uint16 last_port; ///< port of the last joined server
|
||||
bool no_http_content_downloads; ///< do not do content downloads over HTTP
|
||||
#else /* ENABLE_NETWORK */
|
||||
#endif
|
||||
};
|
||||
|
@ -631,6 +631,7 @@ const SettingDesc _settings[] = {
|
||||
SDTC_BOOL(network.reload_cfg, S, NO, false, STR_NULL, NULL),
|
||||
SDTC_STR(network.last_host, SLE_STRB, S, 0, "", STR_NULL, NULL),
|
||||
SDTC_VAR(network.last_port, SLE_UINT16, S, 0, 0, 0, UINT16_MAX, 0, STR_NULL, NULL),
|
||||
SDTC_BOOL(network.no_http_content_downloads, S, 0, false, STR_NULL, NULL),
|
||||
#endif /* ENABLE_NETWORK */
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user