network_content.cpp

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

Generated on Fri Apr 30 21:55:21 2010 for OpenTTD by  doxygen 1.6.1