network_content.cpp

Go to the documentation of this file.
00001 /* $Id: network_content.cpp 21898 2011-01-23 11:20:55Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * 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.
00006  * 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.
00007  * 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/>.
00008  */
00009 
00012 #if defined(ENABLE_NETWORK)
00013 
00014 #include "../stdafx.h"
00015 #include "../rev.h"
00016 #include "../ai/ai.hpp"
00017 #include "../window_func.h"
00018 #include "../gui.h"
00019 #include "../base_media_base.h"
00020 #include "../settings_type.h"
00021 #include "network_content.h"
00022 
00023 #include "table/strings.h"
00024 
00025 #if defined(WITH_ZLIB)
00026 #include <zlib.h>
00027 #endif
00028 
00029 extern bool HasScenario(const ContentInfo *ci, bool md5sum);
00030 ClientNetworkContentSocketHandler _network_content_client;
00031 
00033 static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
00034 {
00035   return FindGRFConfig(BSWAP32(ci->unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? ci->md5sum : NULL) != NULL;
00036 }
00037 
00045 typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
00046 
00047 DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_INFO)
00048 {
00049   ContentInfo *ci = new ContentInfo();
00050   ci->type     = (ContentType)p->Recv_uint8();
00051   ci->id       = (ContentID)p->Recv_uint32();
00052   ci->filesize = p->Recv_uint32();
00053 
00054   p->Recv_string(ci->name, lengthof(ci->name));
00055   p->Recv_string(ci->version, lengthof(ci->name));
00056   p->Recv_string(ci->url, lengthof(ci->url));
00057   p->Recv_string(ci->description, lengthof(ci->description),  true);
00058 
00059   ci->unique_id = p->Recv_uint32();
00060   for (uint j = 0; j < sizeof(ci->md5sum); j++) {
00061     ci->md5sum[j] = p->Recv_uint8();
00062   }
00063 
00064   ci->dependency_count = p->Recv_uint8();
00065   ci->dependencies = MallocT<ContentID>(ci->dependency_count);
00066   for (uint i = 0; i < ci->dependency_count; i++) ci->dependencies[i] = (ContentID)p->Recv_uint32();
00067 
00068   ci->tag_count = p->Recv_uint8();
00069   ci->tags = MallocT<char[32]>(ci->tag_count);
00070   for (uint i = 0; i < ci->tag_count; i++) p->Recv_string(ci->tags[i], lengthof(*ci->tags));
00071 
00072   if (!ci->IsValid()) {
00073     delete ci;
00074     this->Close();
00075     return false;
00076   }
00077 
00078   /* Find the appropriate check function */
00079   HasProc proc = NULL;
00080   switch (ci->type) {
00081     case CONTENT_TYPE_NEWGRF:
00082       proc = HasGRFConfig;
00083       break;
00084 
00085     case CONTENT_TYPE_BASE_GRAPHICS:
00086       proc = BaseGraphics::HasSet;
00087       break;
00088 
00089     case CONTENT_TYPE_BASE_MUSIC:
00090       proc = BaseMusic::HasSet;
00091       break;
00092 
00093     case CONTENT_TYPE_BASE_SOUNDS:
00094       proc = BaseSounds::HasSet;
00095       break;
00096 
00097     case CONTENT_TYPE_AI:
00098     case CONTENT_TYPE_AI_LIBRARY:
00099       proc = AI::HasAI; break;
00100       break;
00101 
00102     case CONTENT_TYPE_SCENARIO:
00103     case CONTENT_TYPE_HEIGHTMAP:
00104       proc = HasScenario;
00105       break;
00106 
00107     default:
00108       break;
00109   }
00110 
00111   if (proc != NULL) {
00112     if (proc(ci, true)) {
00113       ci->state = ContentInfo::ALREADY_HERE;
00114     } else {
00115       ci->state = ContentInfo::UNSELECTED;
00116       if (proc(ci, false)) ci->upgrade = true;
00117     }
00118   } else {
00119     ci->state = ContentInfo::UNSELECTED;
00120   }
00121 
00122   /* Something we don't have and has filesize 0 does not exist in te system */
00123   if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST;
00124 
00125   /* Do we already have a stub for this? */
00126   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00127     ContentInfo *ici = *iter;
00128     if (ici->type == ci->type && ici->unique_id == ci->unique_id &&
00129         memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) {
00130       /* Preserve the name if possible */
00131       if (StrEmpty(ci->name)) strecpy(ci->name, ici->name, lastof(ci->name));
00132       if (ici->IsSelected()) ci->state = ici->state;
00133 
00134       /*
00135        * As ici might be selected by the content window we cannot delete that.
00136        * However, we want to keep most of the values of ci, except the values
00137        * we (just) already preserved.
00138        * So transfer data and ownership of allocated memory from ci to ici.
00139        */
00140       ici->TransferFrom(ci);
00141       delete ci;
00142 
00143       this->OnReceiveContentInfo(ici);
00144       return true;
00145     }
00146   }
00147 
00148   /* Missing content info? Don't list it */
00149   if (ci->filesize == 0) {
00150     delete ci;
00151     return true;
00152   }
00153 
00154   *this->infos.Append() = ci;
00155 
00156   /* Incoming data means that we might need to reconsider dependencies */
00157   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00158     this->CheckDependencyState(*iter);
00159   }
00160 
00161   this->OnReceiveContentInfo(ci);
00162 
00163   return true;
00164 }
00165 
00166 void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
00167 {
00168   if (type == CONTENT_TYPE_END) {
00169     this->RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
00170     this->RequestContentList(CONTENT_TYPE_BASE_MUSIC);
00171     this->RequestContentList(CONTENT_TYPE_BASE_SOUNDS);
00172     this->RequestContentList(CONTENT_TYPE_SCENARIO);
00173     this->RequestContentList(CONTENT_TYPE_HEIGHTMAP);
00174 #ifdef ENABLE_AI
00175     this->RequestContentList(CONTENT_TYPE_AI);
00176     this->RequestContentList(CONTENT_TYPE_AI_LIBRARY);
00177 #endif /* ENABLE_AI */
00178     this->RequestContentList(CONTENT_TYPE_NEWGRF);
00179     return;
00180   }
00181 
00182   this->Connect();
00183 
00184   Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST);
00185   p->Send_uint8 ((byte)type);
00186   p->Send_uint32(_openttd_newgrf_version);
00187 
00188   this->SendPacket(p);
00189 }
00190 
00191 void ClientNetworkContentSocketHandler::RequestContentList(uint count, const ContentID *content_ids)
00192 {
00193   this->Connect();
00194 
00195   while (count > 0) {
00196     /* We can "only" send a limited number of IDs in a single packet.
00197      * A packet begins with the packet size and a byte for the type.
00198      * Then this packet adds a byte for the content type and a uint16
00199      * for the count in this packet. The rest of the packet can be
00200      * used for the IDs. */
00201     uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
00202 
00203     Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID);
00204     p->Send_uint16(p_count);
00205 
00206     for (uint i = 0; i < p_count; i++) {
00207       p->Send_uint32(content_ids[i]);
00208     }
00209 
00210     this->SendPacket(p);
00211     count -= p_count;
00212     content_ids += p_count;
00213   }
00214 }
00215 
00216 void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bool send_md5sum)
00217 {
00218   if (cv == NULL) return;
00219 
00220   this->Connect();
00221 
00222   /* 20 is sizeof(uint32) + sizeof(md5sum (byte[16])) */
00223   assert(cv->Length() < 255);
00224   assert(cv->Length() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) / (send_md5sum ? 20 : sizeof(uint32)));
00225 
00226   Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID);
00227   p->Send_uint8(cv->Length());
00228 
00229   for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
00230     const ContentInfo *ci = *iter;
00231     p->Send_uint8((byte)ci->type);
00232     p->Send_uint32(ci->unique_id);
00233     if (!send_md5sum) continue;
00234 
00235     for (uint j = 0; j < sizeof(ci->md5sum); j++) {
00236       p->Send_uint8(ci->md5sum[j]);
00237     }
00238   }
00239 
00240   this->SendPacket(p);
00241 
00242   for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
00243     ContentInfo *ci = *iter;
00244     bool found = false;
00245     for (ContentIterator iter2 = this->infos.Begin(); iter2 != this->infos.End(); iter2++) {
00246       ContentInfo *ci2 = *iter2;
00247       if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
00248           (!send_md5sum || memcmp(ci->md5sum, ci2->md5sum, sizeof(ci->md5sum)) == 0)) {
00249         found = true;
00250         break;
00251       }
00252     }
00253     if (!found) {
00254       *this->infos.Append() = ci;
00255     } else {
00256       delete ci;
00257     }
00258   }
00259 }
00260 
00261 void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
00262 {
00263   bytes = 0;
00264 
00265   ContentIDList content;
00266   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00267     const ContentInfo *ci = *iter;
00268     if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
00269 
00270     *content.Append() = ci->id;
00271     bytes += ci->filesize;
00272   }
00273 
00274   files = content.Length();
00275 
00276   /* If there's nothing to download, do nothing. */
00277   if (files == 0) return;
00278 
00279   if (_settings_client.network.no_http_content_downloads || fallback) {
00280     this->DownloadSelectedContentFallback(content);
00281   } else {
00282     this->DownloadSelectedContentHTTP(content);
00283   }
00284 }
00285 
00286 void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const ContentIDList &content)
00287 {
00288   uint count = content.Length();
00289 
00290   /* Allocate memory for the whole request.
00291    * Requests are "id\nid\n..." (as strings), so assume the maximum ID,
00292    * which is uint32 so 10 characters long. Then the newlines and
00293    * multiply that all with the count and then add the '\0'. */
00294   uint bytes = (10 + 1) * count + 1;
00295   char *content_request = MallocT<char>(bytes);
00296   const char *lastof = content_request + bytes - 1;
00297 
00298   char *p = content_request;
00299   for (const ContentID *id = content.Begin(); id != content.End(); id++) {
00300     p += seprintf(p, lastof, "%d\n", *id);
00301   }
00302 
00303   this->http_response_index = -1;
00304 
00305   NetworkAddress address(NETWORK_CONTENT_MIRROR_HOST, NETWORK_CONTENT_MIRROR_PORT);
00306   new NetworkHTTPContentConnecter(address, this, NETWORK_CONTENT_MIRROR_URL, content_request);
00307   /* NetworkHTTPContentConnecter takes over freeing of content_request! */
00308 }
00309 
00310 void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const ContentIDList &content)
00311 {
00312   uint count = content.Length();
00313   const ContentID *content_ids = content.Begin();
00314   this->Connect();
00315 
00316   while (count > 0) {
00317     /* We can "only" send a limited number of IDs in a single packet.
00318      * A packet begins with the packet size and a byte for the type.
00319      * Then this packet adds a uint16 for the count in this packet.
00320      * The rest of the packet can be used for the IDs. */
00321     uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
00322 
00323     Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT);
00324     p->Send_uint16(p_count);
00325 
00326     for (uint i = 0; i < p_count; i++) {
00327       p->Send_uint32(content_ids[i]);
00328     }
00329 
00330     this->SendPacket(p);
00331     count -= p_count;
00332     content_ids += p_count;
00333   }
00334 }
00335 
00343 static char *GetFullFilename(const ContentInfo *ci, bool compressed)
00344 {
00345   Subdirectory dir;
00346   switch (ci->type) {
00347     default: return NULL;
00348     case CONTENT_TYPE_BASE_GRAPHICS: dir = DATA_DIR;       break;
00349     case CONTENT_TYPE_BASE_MUSIC:    dir = GM_DIR;         break;
00350     case CONTENT_TYPE_BASE_SOUNDS:   dir = DATA_DIR;       break;
00351     case CONTENT_TYPE_NEWGRF:        dir = DATA_DIR;       break;
00352     case CONTENT_TYPE_AI:            dir = AI_DIR;         break;
00353     case CONTENT_TYPE_AI_LIBRARY:    dir = AI_LIBRARY_DIR; break;
00354     case CONTENT_TYPE_SCENARIO:      dir = SCENARIO_DIR;   break;
00355     case CONTENT_TYPE_HEIGHTMAP:     dir = HEIGHTMAP_DIR;  break;
00356   }
00357 
00358   static char buf[MAX_PATH];
00359   FioGetFullPath(buf, lengthof(buf), SP_AUTODOWNLOAD_DIR, dir, ci->filename);
00360   strecat(buf, compressed ? ".tar.gz" : ".tar", lastof(buf));
00361 
00362   return buf;
00363 }
00364 
00370 static bool GunzipFile(const ContentInfo *ci)
00371 {
00372 #if defined(WITH_ZLIB)
00373   bool ret = true;
00374   FILE *ftmp = fopen(GetFullFilename(ci, true), "rb");
00375   gzFile fin = gzdopen(fileno(ftmp), "rb");
00376   FILE *fout = fopen(GetFullFilename(ci, false), "wb");
00377 
00378   if (fin == NULL || fout == NULL) {
00379     ret = false;
00380   } else {
00381     byte buff[8192];
00382     while (1) {
00383       int read = gzread(fin, buff, sizeof(buff));
00384       if (read == 0) {
00385         /* If gzread() returns 0, either the end-of-file has been
00386          * reached or an underlying read error has occurred.
00387          *
00388          * gzeof() can't be used, because:
00389          * 1.2.5 - it is safe, 1 means 'everything was OK'
00390          * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
00391          * 1.2.3.3 - 1 is returned for truncated archive
00392          *
00393          * So we use gzerror(). When proper end of archive
00394          * has been reached, then:
00395          * errnum == Z_STREAM_END in 1.2.3.3,
00396          * errnum == 0 in 1.2.4 and 1.2.5 */
00397         int errnum;
00398         gzerror(fin, &errnum);
00399         if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
00400         break;
00401       }
00402       if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
00403         /* If gzread() returns -1, there was an error in archive */
00404         ret = false;
00405         break;
00406       }
00407       /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
00408        * if (read < sizeof(buff)) break; */
00409     }
00410   }
00411 
00412   if (fin != NULL) {
00413     /* Closes ftmp too! */
00414     gzclose(fin);
00415   } else if (ftmp != NULL) {
00416     /* In case the gz stream was opened correctly this will
00417      * be closed by gzclose. */
00418     fclose(ftmp);
00419   }
00420   if (fout != NULL) fclose(fout);
00421 
00422   return ret;
00423 #else
00424   NOT_REACHED();
00425 #endif /* defined(WITH_ZLIB) */
00426 }
00427 
00428 DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT)
00429 {
00430   if (this->curFile == NULL) {
00431     delete this->curInfo;
00432     /* When we haven't opened a file this must be our first packet with metadata. */
00433     this->curInfo = new ContentInfo;
00434     this->curInfo->type     = (ContentType)p->Recv_uint8();
00435     this->curInfo->id       = (ContentID)p->Recv_uint32();
00436     this->curInfo->filesize = p->Recv_uint32();
00437     p->Recv_string(this->curInfo->filename, lengthof(this->curInfo->filename));
00438 
00439     if (!this->BeforeDownload()) {
00440       this->Close();
00441       return false;
00442     }
00443   } else {
00444     /* We have a file opened, thus are downloading internal content */
00445     size_t toRead = (size_t)(p->size - p->pos);
00446     if (fwrite(p->buffer + p->pos, 1, toRead, this->curFile) != toRead) {
00447       DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
00448       ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
00449       this->Close();
00450       fclose(this->curFile);
00451       this->curFile = NULL;
00452 
00453       return false;
00454     }
00455 
00456     this->OnDownloadProgress(this->curInfo, (uint)toRead);
00457 
00458     if (toRead == 0) this->AfterDownload();
00459   }
00460 
00461   return true;
00462 }
00463 
00468 bool ClientNetworkContentSocketHandler::BeforeDownload()
00469 {
00470   if (!this->curInfo->IsValid()) {
00471     delete this->curInfo;
00472     this->curInfo = NULL;
00473     return false;
00474   }
00475 
00476   if (this->curInfo->filesize != 0) {
00477     /* The filesize is > 0, so we are going to download it */
00478     const char *filename = GetFullFilename(this->curInfo, true);
00479     if (filename == NULL || (this->curFile = fopen(filename, "wb")) == NULL) {
00480       /* Unless that fails ofcourse... */
00481       DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
00482       ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
00483       return false;
00484     }
00485   }
00486   return true;
00487 }
00488 
00493 void ClientNetworkContentSocketHandler::AfterDownload()
00494 {
00495   /* We read nothing; that's our marker for end-of-stream.
00496    * Now gunzip the tar and make it known. */
00497   fclose(this->curFile);
00498   this->curFile = NULL;
00499 
00500   if (GunzipFile(this->curInfo)) {
00501     unlink(GetFullFilename(this->curInfo, true));
00502 
00503     TarScanner ts;
00504     ts.AddFile(GetFullFilename(this->curInfo, false), 0);
00505 
00506     if (this->curInfo->type == CONTENT_TYPE_BASE_MUSIC) {
00507       /* Music can't be in a tar. So extract the tar! */
00508       ExtractTar(GetFullFilename(this->curInfo, false));
00509       unlink(GetFullFilename(this->curInfo, false));
00510     }
00511 
00512     this->OnDownloadComplete(this->curInfo->id);
00513   } else {
00514     ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_EXTRACT, INVALID_STRING_ID, WL_ERROR);
00515   }
00516 }
00517 
00518 /* Also called to just clean up the mess. */
00519 void ClientNetworkContentSocketHandler::OnFailure()
00520 {
00521   /* If we fail, download the rest via the 'old' system. */
00522   uint files, bytes;
00523   this->DownloadSelectedContent(files, bytes, true);
00524 
00525   this->http_response.Reset();
00526   this->http_response_index = -2;
00527 
00528   if (this->curFile != NULL) {
00529     fclose(this->curFile);
00530     this->curFile = NULL;
00531   }
00532 }
00533 
00534 void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length)
00535 {
00536   assert(data == NULL || length != 0);
00537 
00538   /* Ignore any latent data coming from a connection we closed. */
00539   if (this->http_response_index == -2) return;
00540 
00541   if (this->http_response_index == -1) {
00542     if (data != NULL) {
00543       /* Append the rest of the response. */
00544       memcpy(this->http_response.Append((uint)length), data, length);
00545       return;
00546     } else {
00547       /* Make sure the response is properly terminated. */
00548       *this->http_response.Append() = '\0';
00549 
00550       /* And prepare for receiving the rest of the data. */
00551       this->http_response_index = 0;
00552     }
00553   }
00554 
00555   if (data != NULL) {
00556     /* We have data, so write it to the file. */
00557     if (fwrite(data, 1, length, this->curFile) != length) {
00558       /* Writing failed somehow, let try via the old method. */
00559       this->OnFailure();
00560     } else {
00561       /* Just received the data. */
00562       this->OnDownloadProgress(this->curInfo, (uint)length);
00563     }
00564     /* Nothing more to do now. */
00565     return;
00566   }
00567 
00568   if (this->curFile != NULL) {
00569     /* We've finished downloading a file. */
00570     this->AfterDownload();
00571   }
00572 
00573   if ((uint)this->http_response_index >= this->http_response.Length()) {
00574     /* It's not a real failure, but if there's
00575      * nothing more to download it helps with
00576      * cleaning up the stuff we allocated. */
00577     this->OnFailure();
00578     return;
00579   }
00580 
00581   delete this->curInfo;
00582   /* When we haven't opened a file this must be our first packet with metadata. */
00583   this->curInfo = new ContentInfo;
00584 
00586 #define check_not_null(p) { if ((p) == NULL) { this->OnFailure(); return; } }
00587 
00588 #define check_and_terminate(p) { check_not_null(p); *(p) = '\0'; }
00589 
00590   for (;;) {
00591     char *str = this->http_response.Begin() + this->http_response_index;
00592     char *p = strchr(str, '\n');
00593     check_and_terminate(p);
00594 
00595     /* Update the index for the next one */
00596     this->http_response_index += (int)strlen(str) + 1;
00597 
00598     /* Read the ID */
00599     p = strchr(str, ',');
00600     check_and_terminate(p);
00601     this->curInfo->id = (ContentID)atoi(str);
00602 
00603     /* Read the type */
00604     str = p + 1;
00605     p = strchr(str, ',');
00606     check_and_terminate(p);
00607     this->curInfo->type = (ContentType)atoi(str);
00608 
00609     /* Read the file size */
00610     str = p + 1;
00611     p = strchr(str, ',');
00612     check_and_terminate(p);
00613     this->curInfo->filesize = atoi(str);
00614 
00615     /* Read the URL */
00616     str = p + 1;
00617     /* Is it a fallback URL? If so, just continue with the next one. */
00618     if (strncmp(str, "ottd", 4) == 0) {
00619       if ((uint)this->http_response_index >= this->http_response.Length()) {
00620         /* Have we gone through all lines? */
00621         this->OnFailure();
00622         return;
00623       }
00624       continue;
00625     }
00626 
00627     p = strrchr(str, '/');
00628     check_not_null(p);
00629     p++; // Start after the '/'
00630 
00631     char tmp[MAX_PATH];
00632     if (strecpy(tmp, p, lastof(tmp)) == lastof(tmp)) {
00633       this->OnFailure();
00634       return;
00635     }
00636     /* Remove the extension from the string. */
00637     for (uint i = 0; i < 2; i++) {
00638       p = strrchr(tmp, '.');
00639       check_and_terminate(p);
00640     }
00641 
00642     /* Copy the string, without extension, to the filename. */
00643     strecpy(this->curInfo->filename, tmp, lastof(this->curInfo->filename));
00644 
00645     /* Request the next file. */
00646     if (!this->BeforeDownload()) {
00647       this->OnFailure();
00648       return;
00649     }
00650 
00651     NetworkHTTPSocketHandler::Connect(str, this);
00652     return;
00653   }
00654 
00655 #undef check
00656 #undef check_and_terminate
00657 }
00658 
00664 ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
00665   NetworkContentSocketHandler(),
00666   http_response_index(-2),
00667   curFile(NULL),
00668   curInfo(NULL),
00669   isConnecting(false)
00670 {
00671 }
00672 
00674 ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
00675 {
00676   delete this->curInfo;
00677   if (this->curFile != NULL) fclose(this->curFile);
00678 
00679   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
00680 }
00681 
00682 class NetworkContentConnecter : TCPConnecter {
00683 public:
00684   NetworkContentConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
00685 
00686   virtual void OnFailure()
00687   {
00688     _network_content_client.isConnecting = false;
00689     _network_content_client.OnConnect(false);
00690   }
00691 
00692   virtual void OnConnect(SOCKET s)
00693   {
00694     assert(_network_content_client.sock == INVALID_SOCKET);
00695     _network_content_client.isConnecting = false;
00696     _network_content_client.sock = s;
00697     _network_content_client.Reopen();
00698     _network_content_client.OnConnect(true);
00699   }
00700 };
00701 
00705 void ClientNetworkContentSocketHandler::Connect()
00706 {
00707   this->lastActivity = _realtime_tick;
00708 
00709   if (this->sock != INVALID_SOCKET || this->isConnecting) return;
00710   this->isConnecting = true;
00711   new NetworkContentConnecter(NetworkAddress(NETWORK_CONTENT_SERVER_HOST, NETWORK_CONTENT_SERVER_PORT, AF_UNSPEC));
00712 }
00713 
00717 void ClientNetworkContentSocketHandler::Close()
00718 {
00719   if (this->sock == INVALID_SOCKET) return;
00720   NetworkContentSocketHandler::Close();
00721 
00722   this->OnDisconnect();
00723 }
00724 
00729 void ClientNetworkContentSocketHandler::SendReceive()
00730 {
00731   if (this->sock == INVALID_SOCKET || this->isConnecting) return;
00732 
00733   if (this->lastActivity + IDLE_TIMEOUT < _realtime_tick) {
00734     this->Close();
00735     return;
00736   }
00737 
00738   if (this->CanSendReceive()) {
00739     this->ReceivePackets();
00740     this->lastActivity = _realtime_tick;
00741   }
00742 
00743   this->SendPackets();
00744 }
00745 
00750 void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid)
00751 {
00752   /* When we tried to download it already, don't try again */
00753   if (this->requested.Contains(cid)) return;
00754 
00755   *this->requested.Append() = cid;
00756   assert(this->requested.Contains(cid));
00757   this->RequestContentList(1, &cid);
00758 }
00759 
00765 ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid)
00766 {
00767   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00768     ContentInfo *ci = *iter;
00769     if (ci->id == cid) return ci;
00770   }
00771   return NULL;
00772 }
00773 
00774 
00779 void ClientNetworkContentSocketHandler::Select(ContentID cid)
00780 {
00781   ContentInfo *ci = this->GetContent(cid);
00782   if (ci == NULL || ci->state != ContentInfo::UNSELECTED) return;
00783 
00784   ci->state = ContentInfo::SELECTED;
00785   this->CheckDependencyState(ci);
00786 }
00787 
00792 void ClientNetworkContentSocketHandler::Unselect(ContentID cid)
00793 {
00794   ContentInfo *ci = this->GetContent(cid);
00795   if (ci == NULL || !ci->IsSelected()) return;
00796 
00797   ci->state = ContentInfo::UNSELECTED;
00798   this->CheckDependencyState(ci);
00799 }
00800 
00802 void ClientNetworkContentSocketHandler::SelectAll()
00803 {
00804   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00805     ContentInfo *ci = *iter;
00806     if (ci->state == ContentInfo::UNSELECTED) {
00807       ci->state = ContentInfo::SELECTED;
00808       this->CheckDependencyState(ci);
00809     }
00810   }
00811 }
00812 
00814 void ClientNetworkContentSocketHandler::SelectUpgrade()
00815 {
00816   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00817     ContentInfo *ci = *iter;
00818     if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
00819       ci->state = ContentInfo::SELECTED;
00820       this->CheckDependencyState(ci);
00821     }
00822   }
00823 }
00824 
00826 void ClientNetworkContentSocketHandler::UnselectAll()
00827 {
00828   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00829     ContentInfo *ci = *iter;
00830     if (ci->IsSelected() && ci->state != ContentInfo::ALREADY_HERE) ci->state = ContentInfo::UNSELECTED;
00831   }
00832 }
00833 
00835 void ClientNetworkContentSocketHandler::ToggleSelectedState(const ContentInfo *ci)
00836 {
00837   switch (ci->state) {
00838     case ContentInfo::SELECTED:
00839     case ContentInfo::AUTOSELECTED:
00840       this->Unselect(ci->id);
00841       break;
00842 
00843     case ContentInfo::UNSELECTED:
00844       this->Select(ci->id);
00845       break;
00846 
00847     default:
00848       break;
00849   }
00850 }
00851 
00857 void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const
00858 {
00859   for (ConstContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
00860     const ContentInfo *ci = *iter;
00861     if (ci == child) continue;
00862 
00863     for (uint i = 0; i < ci->dependency_count; i++) {
00864       if (ci->dependencies[i] == child->id) {
00865         *parents.Append() = ci;
00866         break;
00867       }
00868     }
00869   }
00870 }
00871 
00877 void ClientNetworkContentSocketHandler::ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const
00878 {
00879   *tree.Append() = child;
00880 
00881   /* First find all direct parents. We can't use the "normal" iterator as
00882    * we are including stuff into the vector and as such the vector's data
00883    * store can be reallocated (and thus move), which means out iterating
00884    * pointer gets invalid. So fall back to the indices. */
00885   for (uint i = 0; i < tree.Length(); i++) {
00886     ConstContentVector parents;
00887     this->ReverseLookupDependency(parents, tree[i]);
00888 
00889     for (ConstContentIterator piter = parents.Begin(); piter != parents.End(); piter++) {
00890       tree.Include(*piter);
00891     }
00892   }
00893 }
00894 
00899 void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
00900 {
00901   if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
00902     /* Selection is easy; just walk all children and set the
00903      * autoselected state. That way we can see what we automatically
00904      * selected and thus can unselect when a dependency is removed. */
00905     for (uint i = 0; i < ci->dependency_count; i++) {
00906       ContentInfo *c = this->GetContent(ci->dependencies[i]);
00907       if (c == NULL) {
00908         this->DownloadContentInfo(ci->dependencies[i]);
00909       } else if (c->state == ContentInfo::UNSELECTED) {
00910         c->state = ContentInfo::AUTOSELECTED;
00911         this->CheckDependencyState(c);
00912       }
00913     }
00914     return;
00915   }
00916 
00917   if (ci->state != ContentInfo::UNSELECTED) return;
00918 
00919   /* For unselection we need to find the parents of us. We need to
00920    * unselect them. After that we unselect all children that we
00921    * depend on and are not used as dependency for us, but only when
00922    * we automatically selected them. */
00923   ConstContentVector parents;
00924   this->ReverseLookupDependency(parents, ci);
00925   for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
00926     const ContentInfo *c = *iter;
00927     if (!c->IsSelected()) continue;
00928 
00929     this->Unselect(c->id);
00930   }
00931 
00932   for (uint i = 0; i < ci->dependency_count; i++) {
00933     const ContentInfo *c = this->GetContent(ci->dependencies[i]);
00934     if (c == NULL) {
00935       DownloadContentInfo(ci->dependencies[i]);
00936       continue;
00937     }
00938     if (c->state != ContentInfo::AUTOSELECTED) continue;
00939 
00940     /* Only unselect when WE are the only parent. */
00941     parents.Clear();
00942     this->ReverseLookupDependency(parents, c);
00943 
00944     /* First check whether anything depends on us */
00945     int sel_count = 0;
00946     bool force_selection = false;
00947     for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
00948       if ((*iter)->IsSelected()) sel_count++;
00949       if ((*iter)->state == ContentInfo::SELECTED) force_selection = true;
00950     }
00951     if (sel_count == 0) {
00952       /* Nothing depends on us */
00953       this->Unselect(c->id);
00954       continue;
00955     }
00956     /* Something manually selected depends directly on us */
00957     if (force_selection) continue;
00958 
00959     /* "Flood" search to find all items in the dependency graph*/
00960     parents.Clear();
00961     this->ReverseLookupTreeDependency(parents, c);
00962 
00963     /* Is there anything that is "force" selected?, if so... we're done. */
00964     for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
00965       if ((*iter)->state != ContentInfo::SELECTED) continue;
00966 
00967       force_selection = true;
00968       break;
00969     }
00970 
00971     /* So something depended directly on us */
00972     if (force_selection) continue;
00973 
00974     /* Nothing depends on us, mark the whole graph as unselected.
00975      * After that's done run over them once again to test their children
00976      * to unselect. Don't do it immediately because it'll do exactly what
00977      * we're doing now. */
00978     for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
00979       const ContentInfo *c = *iter;
00980       if (c->state == ContentInfo::AUTOSELECTED) this->Unselect(c->id);
00981     }
00982     for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
00983       this->CheckDependencyState(this->GetContent((*iter)->id));
00984     }
00985   }
00986 }
00987 
00989 void ClientNetworkContentSocketHandler::Clear()
00990 {
00991   for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
00992 
00993   this->infos.Clear();
00994   this->requested.Clear();
00995 }
00996 
00997 /*** CALLBACK ***/
00998 
00999 void ClientNetworkContentSocketHandler::OnConnect(bool success)
01000 {
01001   for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
01002     ContentCallback *cb = *iter;
01003     cb->OnConnect(success);
01004     if (iter != this->callbacks.End() && *iter == cb) iter++;
01005   }
01006 }
01007 
01008 void ClientNetworkContentSocketHandler::OnDisconnect()
01009 {
01010   for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
01011     ContentCallback *cb = *iter;
01012     cb->OnDisconnect();
01013     if (iter != this->callbacks.End() && *iter == cb) iter++;
01014   }
01015 }
01016 
01017 void ClientNetworkContentSocketHandler::OnReceiveContentInfo(const ContentInfo *ci)
01018 {
01019   for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
01020     ContentCallback *cb = *iter;
01021     cb->OnReceiveContentInfo(ci);
01022     if (iter != this->callbacks.End() && *iter == cb) iter++;
01023   }
01024 }
01025 
01026 void ClientNetworkContentSocketHandler::OnDownloadProgress(const ContentInfo *ci, uint bytes)
01027 {
01028   for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
01029     ContentCallback *cb = *iter;
01030     cb->OnDownloadProgress(ci, bytes);
01031     if (iter != this->callbacks.End() && *iter == cb) iter++;
01032   }
01033 }
01034 
01035 void ClientNetworkContentSocketHandler::OnDownloadComplete(ContentID cid)
01036 {
01037   ContentInfo *ci = this->GetContent(cid);
01038   if (ci != NULL) {
01039     ci->state = ContentInfo::ALREADY_HERE;
01040   }
01041 
01042   for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
01043     ContentCallback *cb = *iter;
01044     cb->OnDownloadComplete(cid);
01045     if (iter != this->callbacks.End() && *iter == cb) iter++;
01046   }
01047 }
01048 
01049 #endif /* ENABLE_NETWORK */

Generated on Fri Feb 4 20:53:42 2011 for OpenTTD by  doxygen 1.6.1