network_content.cpp

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