OpenTTD
network_content.cpp
Go to the documentation of this file.
1 /* $Id: network_content.cpp 26489 2014-04-23 21:23:21Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #if defined(ENABLE_NETWORK)
13 
14 #include "../stdafx.h"
15 #include "../rev.h"
16 #include "../ai/ai.hpp"
17 #include "../game/game.hpp"
18 #include "../window_func.h"
19 #include "../error.h"
20 #include "../base_media_base.h"
21 #include "../settings_type.h"
22 #include "network_content.h"
23 
24 #include "table/strings.h"
25 
26 #if defined(WITH_ZLIB)
27 #include <zlib.h>
28 #endif
29 
30 #include "../safeguards.h"
31 
32 extern bool HasScenario(const ContentInfo *ci, bool md5sum);
33 
36 
38 static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
39 {
40  return FindGRFConfig(BSWAP32(ci->unique_id), md5sum ? FGCM_EXACT : FGCM_ANY, md5sum ? ci->md5sum : NULL) != NULL;
41 }
42 
50 typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
51 
52 bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p)
53 {
54  ContentInfo *ci = new ContentInfo();
55  ci->type = (ContentType)p->Recv_uint8();
56  ci->id = (ContentID)p->Recv_uint32();
57  ci->filesize = p->Recv_uint32();
58 
59  p->Recv_string(ci->name, lengthof(ci->name));
60  p->Recv_string(ci->version, lengthof(ci->name));
61  p->Recv_string(ci->url, lengthof(ci->url));
63 
64  ci->unique_id = p->Recv_uint32();
65  for (uint j = 0; j < sizeof(ci->md5sum); j++) {
66  ci->md5sum[j] = p->Recv_uint8();
67  }
68 
69  ci->dependency_count = p->Recv_uint8();
70  ci->dependencies = MallocT<ContentID>(ci->dependency_count);
71  for (uint i = 0; i < ci->dependency_count; i++) ci->dependencies[i] = (ContentID)p->Recv_uint32();
72 
73  ci->tag_count = p->Recv_uint8();
74  ci->tags = MallocT<char[32]>(ci->tag_count);
75  for (uint i = 0; i < ci->tag_count; i++) p->Recv_string(ci->tags[i], lengthof(*ci->tags));
76 
77  if (!ci->IsValid()) {
78  delete ci;
79  this->Close();
80  return false;
81  }
82 
83  /* Find the appropriate check function */
84  HasProc proc = NULL;
85  switch (ci->type) {
87  proc = HasGRFConfig;
88  break;
89 
91  proc = BaseGraphics::HasSet;
92  break;
93 
95  proc = BaseMusic::HasSet;
96  break;
97 
99  proc = BaseSounds::HasSet;
100  break;
101 
102  case CONTENT_TYPE_AI:
103  proc = AI::HasAI; break;
104  break;
105 
107  proc = AI::HasAILibrary; break;
108  break;
109 
110  case CONTENT_TYPE_GAME:
111  proc = Game::HasGame; break;
112  break;
113 
115  proc = Game::HasGameLibrary; break;
116  break;
117 
120  proc = HasScenario;
121  break;
122 
123  default:
124  break;
125  }
126 
127  if (proc != NULL) {
128  if (proc(ci, true)) {
130  } else {
132  if (proc(ci, false)) ci->upgrade = true;
133  }
134  } else {
136  }
137 
138  /* Something we don't have and has filesize 0 does not exist in te system */
140 
141  /* Do we already have a stub for this? */
142  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
143  ContentInfo *ici = *iter;
144  if (ici->type == ci->type && ici->unique_id == ci->unique_id &&
145  memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) {
146  /* Preserve the name if possible */
147  if (StrEmpty(ci->name)) strecpy(ci->name, ici->name, lastof(ci->name));
148  if (ici->IsSelected()) ci->state = ici->state;
149 
150  /*
151  * As ici might be selected by the content window we cannot delete that.
152  * However, we want to keep most of the values of ci, except the values
153  * we (just) already preserved.
154  * So transfer data and ownership of allocated memory from ci to ici.
155  */
156  ici->TransferFrom(ci);
157  delete ci;
158 
159  this->OnReceiveContentInfo(ici);
160  return true;
161  }
162  }
163 
164  /* Missing content info? Don't list it */
165  if (ci->filesize == 0) {
166  delete ci;
167  return true;
168  }
169 
170  *this->infos.Append() = ci;
171 
172  /* Incoming data means that we might need to reconsider dependencies */
173  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
174  this->CheckDependencyState(*iter);
175  }
176 
177  this->OnReceiveContentInfo(ci);
178 
179  return true;
180 }
181 
187 {
188  if (type == CONTENT_TYPE_END) {
199  return;
200  }
201 
202  this->Connect();
203 
205  p->Send_uint8 ((byte)type);
206  p->Send_uint32(_openttd_newgrf_version);
207 
208  this->SendPacket(p);
209 }
210 
217 {
218  this->Connect();
219 
220  while (count > 0) {
221  /* We can "only" send a limited number of IDs in a single packet.
222  * A packet begins with the packet size and a byte for the type.
223  * Then this packet adds a byte for the content type and a uint16
224  * for the count in this packet. The rest of the packet can be
225  * used for the IDs. */
226  uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
227 
229  p->Send_uint16(p_count);
230 
231  for (uint i = 0; i < p_count; i++) {
232  p->Send_uint32(content_ids[i]);
233  }
234 
235  this->SendPacket(p);
236  count -= p_count;
237  content_ids += p_count;
238  }
239 }
240 
247 {
248  if (cv == NULL) return;
249 
250  this->Connect();
251 
252  /* 20 is sizeof(uint32) + sizeof(md5sum (byte[16])) */
253  assert(cv->Length() < 255);
254  assert(cv->Length() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) / (send_md5sum ? 20 : sizeof(uint32)));
255 
257  p->Send_uint8(cv->Length());
258 
259  for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
260  const ContentInfo *ci = *iter;
261  p->Send_uint8((byte)ci->type);
262  p->Send_uint32(ci->unique_id);
263  if (!send_md5sum) continue;
264 
265  for (uint j = 0; j < sizeof(ci->md5sum); j++) {
266  p->Send_uint8(ci->md5sum[j]);
267  }
268  }
269 
270  this->SendPacket(p);
271 
272  for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
273  ContentInfo *ci = *iter;
274  bool found = false;
275  for (ContentIterator iter2 = this->infos.Begin(); iter2 != this->infos.End(); iter2++) {
276  ContentInfo *ci2 = *iter2;
277  if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
278  (!send_md5sum || memcmp(ci->md5sum, ci2->md5sum, sizeof(ci->md5sum)) == 0)) {
279  found = true;
280  break;
281  }
282  }
283  if (!found) {
284  *this->infos.Append() = ci;
285  } else {
286  delete ci;
287  }
288  }
289 }
290 
297 void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes, bool fallback)
298 {
299  bytes = 0;
300 
301  ContentIDList content;
302  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
303  const ContentInfo *ci = *iter;
304  if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
305 
306  *content.Append() = ci->id;
307  bytes += ci->filesize;
308  }
309 
310  files = content.Length();
311 
312  /* If there's nothing to download, do nothing. */
313  if (files == 0) return;
314 
316  this->DownloadSelectedContentFallback(content);
317  } else {
318  this->DownloadSelectedContentHTTP(content);
319  }
320 }
321 
327 {
328  uint count = content.Length();
329 
330  /* Allocate memory for the whole request.
331  * Requests are "id\nid\n..." (as strings), so assume the maximum ID,
332  * which is uint32 so 10 characters long. Then the newlines and
333  * multiply that all with the count and then add the '\0'. */
334  uint bytes = (10 + 1) * count + 1;
335  char *content_request = MallocT<char>(bytes);
336  const char *lastof = content_request + bytes - 1;
337 
338  char *p = content_request;
339  for (const ContentID *id = content.Begin(); id != content.End(); id++) {
340  p += seprintf(p, lastof, "%d\n", *id);
341  }
342 
343  this->http_response_index = -1;
344 
346  new NetworkHTTPContentConnecter(address, this, NETWORK_CONTENT_MIRROR_URL, content_request);
347  /* NetworkHTTPContentConnecter takes over freeing of content_request! */
348 }
349 
355 {
356  uint count = content.Length();
357  const ContentID *content_ids = content.Begin();
358  this->Connect();
359 
360  while (count > 0) {
361  /* We can "only" send a limited number of IDs in a single packet.
362  * A packet begins with the packet size and a byte for the type.
363  * Then this packet adds a uint16 for the count in this packet.
364  * The rest of the packet can be used for the IDs. */
365  uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
366 
368  p->Send_uint16(p_count);
369 
370  for (uint i = 0; i < p_count; i++) {
371  p->Send_uint32(content_ids[i]);
372  }
373 
374  this->SendPacket(p);
375  count -= p_count;
376  content_ids += p_count;
377  }
378 }
379 
387 static char *GetFullFilename(const ContentInfo *ci, bool compressed)
388 {
390  if (dir == NO_DIRECTORY) return NULL;
391 
392  static char buf[MAX_PATH];
393  FioGetFullPath(buf, lastof(buf), SP_AUTODOWNLOAD_DIR, dir, ci->filename);
394  strecat(buf, compressed ? ".tar.gz" : ".tar", lastof(buf));
395 
396  return buf;
397 }
398 
404 static bool GunzipFile(const ContentInfo *ci)
405 {
406 #if defined(WITH_ZLIB)
407  bool ret = true;
408  FILE *ftmp = fopen(GetFullFilename(ci, true), "rb");
409  if (ftmp == NULL) return false;
410 
411  gzFile fin = gzdopen(fileno(ftmp), "rb");
412  FILE *fout = fopen(GetFullFilename(ci, false), "wb");
413 
414  if (fin == NULL || fout == NULL) {
415  ret = false;
416  } else {
417  byte buff[8192];
418  for (;;) {
419  int read = gzread(fin, buff, sizeof(buff));
420  if (read == 0) {
421  /* If gzread() returns 0, either the end-of-file has been
422  * reached or an underlying read error has occurred.
423  *
424  * gzeof() can't be used, because:
425  * 1.2.5 - it is safe, 1 means 'everything was OK'
426  * 1.2.3.5, 1.2.4 - 0 or 1 is returned 'randomly'
427  * 1.2.3.3 - 1 is returned for truncated archive
428  *
429  * So we use gzerror(). When proper end of archive
430  * has been reached, then:
431  * errnum == Z_STREAM_END in 1.2.3.3,
432  * errnum == 0 in 1.2.4 and 1.2.5 */
433  int errnum;
434  gzerror(fin, &errnum);
435  if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
436  break;
437  }
438  if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
439  /* If gzread() returns -1, there was an error in archive */
440  ret = false;
441  break;
442  }
443  /* DO NOT DO THIS! It will fail to detect broken archive with 1.2.3.3!
444  * if (read < sizeof(buff)) break; */
445  }
446  }
447 
448  if (fin != NULL) {
449  /* Closes ftmp too! */
450  gzclose(fin);
451  } else if (ftmp != NULL) {
452  /* In case the gz stream was opened correctly this will
453  * be closed by gzclose. */
454  fclose(ftmp);
455  }
456  if (fout != NULL) fclose(fout);
457 
458  return ret;
459 #else
460  NOT_REACHED();
461 #endif /* defined(WITH_ZLIB) */
462 }
463 
464 bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p)
465 {
466  if (this->curFile == NULL) {
467  delete this->curInfo;
468  /* When we haven't opened a file this must be our first packet with metadata. */
469  this->curInfo = new ContentInfo;
470  this->curInfo->type = (ContentType)p->Recv_uint8();
471  this->curInfo->id = (ContentID)p->Recv_uint32();
472  this->curInfo->filesize = p->Recv_uint32();
473  p->Recv_string(this->curInfo->filename, lengthof(this->curInfo->filename));
474 
475  if (!this->BeforeDownload()) {
476  this->Close();
477  return false;
478  }
479  } else {
480  /* We have a file opened, thus are downloading internal content */
481  size_t toRead = (size_t)(p->size - p->pos);
482  if (fwrite(p->buffer + p->pos, 1, toRead, this->curFile) != toRead) {
484  ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
485  this->Close();
486  fclose(this->curFile);
487  this->curFile = NULL;
488 
489  return false;
490  }
491 
492  this->OnDownloadProgress(this->curInfo, (int)toRead);
493 
494  if (toRead == 0) this->AfterDownload();
495  }
496 
497  return true;
498 }
499 
505 {
506  if (!this->curInfo->IsValid()) {
507  delete this->curInfo;
508  this->curInfo = NULL;
509  return false;
510  }
511 
512  if (this->curInfo->filesize != 0) {
513  /* The filesize is > 0, so we are going to download it */
514  const char *filename = GetFullFilename(this->curInfo, true);
515  if (filename == NULL || (this->curFile = fopen(filename, "wb")) == NULL) {
516  /* Unless that fails of course... */
518  ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
519  return false;
520  }
521  }
522  return true;
523 }
524 
530 {
531  /* We read nothing; that's our marker for end-of-stream.
532  * Now gunzip the tar and make it known. */
533  fclose(this->curFile);
534  this->curFile = NULL;
535 
536  if (GunzipFile(this->curInfo)) {
537  unlink(GetFullFilename(this->curInfo, true));
538 
540  if (sd == NO_DIRECTORY) NOT_REACHED();
541 
542  TarScanner ts;
543  ts.AddFile(sd, GetFullFilename(this->curInfo, false));
544 
545  if (this->curInfo->type == CONTENT_TYPE_BASE_MUSIC) {
546  /* Music can't be in a tar. So extract the tar! */
548  unlink(GetFullFilename(this->curInfo, false));
549  }
550 
551  this->OnDownloadComplete(this->curInfo->id);
552  } else {
553  ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_EXTRACT, INVALID_STRING_ID, WL_ERROR);
554  }
555 }
556 
557 /* Also called to just clean up the mess. */
558 void ClientNetworkContentSocketHandler::OnFailure()
559 {
560  /* If we fail, download the rest via the 'old' system. */
561  uint files, bytes;
562  this->DownloadSelectedContent(files, bytes, true);
563 
564  this->http_response.Reset();
565  this->http_response_index = -2;
566 
567  if (this->curFile != NULL) {
568  /* Revert the download progress when we are going for the old system. */
569  long size = ftell(this->curFile);
570  if (size > 0) this->OnDownloadProgress(this->curInfo, (int)-size);
571 
572  fclose(this->curFile);
573  this->curFile = NULL;
574  }
575 }
576 
577 void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length)
578 {
579  assert(data == NULL || length != 0);
580 
581  /* Ignore any latent data coming from a connection we closed. */
582  if (this->http_response_index == -2) return;
583 
584  if (this->http_response_index == -1) {
585  if (data != NULL) {
586  /* Append the rest of the response. */
587  memcpy(this->http_response.Append((uint)length), data, length);
588  return;
589  } else {
590  /* Make sure the response is properly terminated. */
591  *this->http_response.Append() = '\0';
592 
593  /* And prepare for receiving the rest of the data. */
594  this->http_response_index = 0;
595  }
596  }
597 
598  if (data != NULL) {
599  /* We have data, so write it to the file. */
600  if (fwrite(data, 1, length, this->curFile) != length) {
601  /* Writing failed somehow, let try via the old method. */
602  this->OnFailure();
603  } else {
604  /* Just received the data. */
605  this->OnDownloadProgress(this->curInfo, (int)length);
606  }
607  /* Nothing more to do now. */
608  return;
609  }
610 
611  if (this->curFile != NULL) {
612  /* We've finished downloading a file. */
613  this->AfterDownload();
614  }
615 
616  if ((uint)this->http_response_index >= this->http_response.Length()) {
617  /* It's not a real failure, but if there's
618  * nothing more to download it helps with
619  * cleaning up the stuff we allocated. */
620  this->OnFailure();
621  return;
622  }
623 
624  delete this->curInfo;
625  /* When we haven't opened a file this must be our first packet with metadata. */
626  this->curInfo = new ContentInfo;
627 
629 #define check_not_null(p) { if ((p) == NULL) { this->OnFailure(); return; } }
630 
631 #define check_and_terminate(p) { check_not_null(p); *(p) = '\0'; }
632 
633  for (;;) {
634  char *str = this->http_response.Begin() + this->http_response_index;
635  char *p = strchr(str, '\n');
636  check_and_terminate(p);
637 
638  /* Update the index for the next one */
639  this->http_response_index += (int)strlen(str) + 1;
640 
641  /* Read the ID */
642  p = strchr(str, ',');
643  check_and_terminate(p);
644  this->curInfo->id = (ContentID)atoi(str);
645 
646  /* Read the type */
647  str = p + 1;
648  p = strchr(str, ',');
649  check_and_terminate(p);
650  this->curInfo->type = (ContentType)atoi(str);
651 
652  /* Read the file size */
653  str = p + 1;
654  p = strchr(str, ',');
655  check_and_terminate(p);
656  this->curInfo->filesize = atoi(str);
657 
658  /* Read the URL */
659  str = p + 1;
660  /* Is it a fallback URL? If so, just continue with the next one. */
661  if (strncmp(str, "ottd", 4) == 0) {
662  if ((uint)this->http_response_index >= this->http_response.Length()) {
663  /* Have we gone through all lines? */
664  this->OnFailure();
665  return;
666  }
667  continue;
668  }
669 
670  p = strrchr(str, '/');
671  check_not_null(p);
672  p++; // Start after the '/'
673 
674  char tmp[MAX_PATH];
675  if (strecpy(tmp, p, lastof(tmp)) == lastof(tmp)) {
676  this->OnFailure();
677  return;
678  }
679  /* Remove the extension from the string. */
680  for (uint i = 0; i < 2; i++) {
681  p = strrchr(tmp, '.');
682  check_and_terminate(p);
683  }
684 
685  /* Copy the string, without extension, to the filename. */
686  strecpy(this->curInfo->filename, tmp, lastof(this->curInfo->filename));
687 
688  /* Request the next file. */
689  if (!this->BeforeDownload()) {
690  this->OnFailure();
691  return;
692  }
693 
695  return;
696  }
697 
698 #undef check
699 #undef check_and_terminate
700 }
701 
707  http_response_index(-2),
708  curFile(NULL),
709  curInfo(NULL),
710  isConnecting(false),
711  lastActivity(_realtime_tick)
712 {
713 }
714 
717 {
718  delete this->curInfo;
719  if (this->curFile != NULL) fclose(this->curFile);
720 
721  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
722 }
723 
726 public:
732 
733  virtual void OnFailure()
734  {
735  _network_content_client.isConnecting = false;
736  _network_content_client.OnConnect(false);
737  }
738 
739  virtual void OnConnect(SOCKET s)
740  {
741  assert(_network_content_client.sock == INVALID_SOCKET);
742  _network_content_client.isConnecting = false;
743  _network_content_client.sock = s;
744  _network_content_client.Reopen();
745  _network_content_client.OnConnect(true);
746  }
747 };
748 
753 {
755 
756  if (this->sock != INVALID_SOCKET || this->isConnecting) return;
757  this->isConnecting = true;
759 }
760 
765 {
766  if (this->sock == INVALID_SOCKET) return;
768 
769  this->OnDisconnect();
770 }
771 
777 {
778  if (this->sock == INVALID_SOCKET || this->isConnecting) return;
779 
780  if (this->lastActivity + IDLE_TIMEOUT < _realtime_tick) {
781  this->Close();
782  return;
783  }
784 
785  if (this->CanSendReceive()) {
786  if (this->ReceivePackets()) {
787  /* Only update activity once a packet is received, instead of everytime we try it. */
789  }
790  }
791 
792  this->SendPackets();
793 }
794 
800 {
801  /* When we tried to download it already, don't try again */
802  if (this->requested.Contains(cid)) return;
803 
804  *this->requested.Append() = cid;
805  assert(this->requested.Contains(cid));
806  this->RequestContentList(1, &cid);
807 }
808 
815 {
816  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
817  ContentInfo *ci = *iter;
818  if (ci->id == cid) return ci;
819  }
820  return NULL;
821 }
822 
823 
829 {
830  ContentInfo *ci = this->GetContent(cid);
831  if (ci == NULL || ci->state != ContentInfo::UNSELECTED) return;
832 
834  this->CheckDependencyState(ci);
835 }
836 
842 {
843  ContentInfo *ci = this->GetContent(cid);
844  if (ci == NULL || !ci->IsSelected()) return;
845 
847  this->CheckDependencyState(ci);
848 }
849 
852 {
853  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
854  ContentInfo *ci = *iter;
855  if (ci->state == ContentInfo::UNSELECTED) {
857  this->CheckDependencyState(ci);
858  }
859  }
860 }
861 
864 {
865  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
866  ContentInfo *ci = *iter;
867  if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
869  this->CheckDependencyState(ci);
870  }
871  }
872 }
873 
876 {
877  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
878  ContentInfo *ci = *iter;
880  }
881 }
882 
885 {
886  switch (ci->state) {
889  this->Unselect(ci->id);
890  break;
891 
893  this->Select(ci->id);
894  break;
895 
896  default:
897  break;
898  }
899 }
900 
907 {
908  for (ConstContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
909  const ContentInfo *ci = *iter;
910  if (ci == child) continue;
911 
912  for (uint i = 0; i < ci->dependency_count; i++) {
913  if (ci->dependencies[i] == child->id) {
914  *parents.Append() = ci;
915  break;
916  }
917  }
918  }
919 }
920 
927 {
928  *tree.Append() = child;
929 
930  /* First find all direct parents. We can't use the "normal" iterator as
931  * we are including stuff into the vector and as such the vector's data
932  * store can be reallocated (and thus move), which means out iterating
933  * pointer gets invalid. So fall back to the indices. */
934  for (uint i = 0; i < tree.Length(); i++) {
935  ConstContentVector parents;
936  this->ReverseLookupDependency(parents, tree[i]);
937 
938  for (ConstContentIterator piter = parents.Begin(); piter != parents.End(); piter++) {
939  tree.Include(*piter);
940  }
941  }
942 }
943 
949 {
950  if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
951  /* Selection is easy; just walk all children and set the
952  * autoselected state. That way we can see what we automatically
953  * selected and thus can unselect when a dependency is removed. */
954  for (uint i = 0; i < ci->dependency_count; i++) {
955  ContentInfo *c = this->GetContent(ci->dependencies[i]);
956  if (c == NULL) {
957  this->DownloadContentInfo(ci->dependencies[i]);
958  } else if (c->state == ContentInfo::UNSELECTED) {
960  this->CheckDependencyState(c);
961  }
962  }
963  return;
964  }
965 
966  if (ci->state != ContentInfo::UNSELECTED) return;
967 
968  /* For unselection we need to find the parents of us. We need to
969  * unselect them. After that we unselect all children that we
970  * depend on and are not used as dependency for us, but only when
971  * we automatically selected them. */
972  ConstContentVector parents;
973  this->ReverseLookupDependency(parents, ci);
974  for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
975  const ContentInfo *c = *iter;
976  if (!c->IsSelected()) continue;
977 
978  this->Unselect(c->id);
979  }
980 
981  for (uint i = 0; i < ci->dependency_count; i++) {
982  const ContentInfo *c = this->GetContent(ci->dependencies[i]);
983  if (c == NULL) {
985  continue;
986  }
987  if (c->state != ContentInfo::AUTOSELECTED) continue;
988 
989  /* Only unselect when WE are the only parent. */
990  parents.Clear();
991  this->ReverseLookupDependency(parents, c);
992 
993  /* First check whether anything depends on us */
994  int sel_count = 0;
995  bool force_selection = false;
996  for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
997  if ((*iter)->IsSelected()) sel_count++;
998  if ((*iter)->state == ContentInfo::SELECTED) force_selection = true;
999  }
1000  if (sel_count == 0) {
1001  /* Nothing depends on us */
1002  this->Unselect(c->id);
1003  continue;
1004  }
1005  /* Something manually selected depends directly on us */
1006  if (force_selection) continue;
1007 
1008  /* "Flood" search to find all items in the dependency graph*/
1009  parents.Clear();
1010  this->ReverseLookupTreeDependency(parents, c);
1011 
1012  /* Is there anything that is "force" selected?, if so... we're done. */
1013  for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
1014  if ((*iter)->state != ContentInfo::SELECTED) continue;
1015 
1016  force_selection = true;
1017  break;
1018  }
1019 
1020  /* So something depended directly on us */
1021  if (force_selection) continue;
1022 
1023  /* Nothing depends on us, mark the whole graph as unselected.
1024  * After that's done run over them once again to test their children
1025  * to unselect. Don't do it immediately because it'll do exactly what
1026  * we're doing now. */
1027  for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
1028  const ContentInfo *c = *iter;
1029  if (c->state == ContentInfo::AUTOSELECTED) this->Unselect(c->id);
1030  }
1031  for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
1032  this->CheckDependencyState(this->GetContent((*iter)->id));
1033  }
1034  }
1035 }
1036 
1039 {
1040  for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
1041 
1042  this->infos.Clear();
1043  this->requested.Clear();
1044 }
1045 
1046 /*** CALLBACK ***/
1047 
1048 void ClientNetworkContentSocketHandler::OnConnect(bool success)
1049 {
1050  for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
1051  ContentCallback *cb = *iter;
1052  cb->OnConnect(success);
1053  if (iter != this->callbacks.End() && *iter == cb) iter++;
1054  }
1055 }
1056 
1057 void ClientNetworkContentSocketHandler::OnDisconnect()
1058 {
1059  for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
1060  ContentCallback *cb = *iter;
1061  cb->OnDisconnect();
1062  if (iter != this->callbacks.End() && *iter == cb) iter++;
1063  }
1064 }
1065 
1066 void ClientNetworkContentSocketHandler::OnReceiveContentInfo(const ContentInfo *ci)
1067 {
1068  for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
1069  ContentCallback *cb = *iter;
1070  cb->OnReceiveContentInfo(ci);
1071  if (iter != this->callbacks.End() && *iter == cb) iter++;
1072  }
1073 }
1074 
1075 void ClientNetworkContentSocketHandler::OnDownloadProgress(const ContentInfo *ci, int bytes)
1076 {
1077  for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
1078  ContentCallback *cb = *iter;
1079  cb->OnDownloadProgress(ci, bytes);
1080  if (iter != this->callbacks.End() && *iter == cb) iter++;
1081  }
1082 }
1083 
1084 void ClientNetworkContentSocketHandler::OnDownloadComplete(ContentID cid)
1085 {
1086  ContentInfo *ci = this->GetContent(cid);
1087  if (ci != NULL) {
1089  }
1090 
1091  for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
1092  ContentCallback *cb = *iter;
1093  cb->OnDownloadComplete(cid);
1094  if (iter != this->callbacks.End() && *iter == cb) iter++;
1095  }
1096 }
1097 
1098 #endif /* ENABLE_NETWORK */