~ubuntu-branches/debian/squeeze/openttd/squeeze

« back to all changes in this revision

Viewing changes to src/network/network_content.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jordi Mallach, Matthijs Kooijman, Jordi Mallach
  • Date: 2009-04-15 18:22:10 UTC
  • mfrom: (1.1.6 upstream) (2.1.3 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090415182210-22ktb8kdbp2tf3bm
[ Matthijs Kooijman ]
* New upstream release.
* Remove Debian specific desktop file, upstream provides one now. 
* Add debian/watch file.

[ Jordi Mallach ]
* Bump Standards-Version to 3.8.1, with no changes required.
* Move to debhelper compat 7. Bump Build-Depends accordingly.
* Use dh_prep.
* Add "set -e" to config script.
* Remove a few extra doc files that get installed by upstream Makefile.
* Add more complete copyright information.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: network_content.cpp 15711 2009-03-14 18:16:29Z rubidium $ */
 
2
 
 
3
/** @file network_content.cpp Content sending/receiving part of the network protocol. */
 
4
 
 
5
#if defined(ENABLE_NETWORK)
 
6
 
 
7
#include "../stdafx.h"
 
8
#include "../rev.h"
 
9
#include "../fileio_func.h"
 
10
#include "../string_func.h"
 
11
#include "../ai/ai.hpp"
 
12
#include "../window_func.h"
 
13
#include "../gui.h"
 
14
#include "../variables.h"
 
15
#include "network_content.h"
 
16
 
 
17
#include "table/strings.h"
 
18
 
 
19
#if defined(WITH_ZLIB)
 
20
#include <zlib.h>
 
21
#endif
 
22
 
 
23
extern bool TarListAddFile(const char *filename);
 
24
extern bool HasGraphicsSet(const ContentInfo *ci, bool md5sum);
 
25
extern bool HasScenario(const ContentInfo *ci, bool md5sum);
 
26
ClientNetworkContentSocketHandler _network_content_client;
 
27
 
 
28
/** Wrapper function for the HasProc */
 
29
static bool HasGRFConfig(const ContentInfo *ci, bool md5sum)
 
30
{
 
31
        return FindGRFConfig(BSWAP32(ci->unique_id), md5sum ? ci->md5sum : NULL) != NULL;
 
32
}
 
33
 
 
34
/**
 
35
 * Check whether a function piece of content is locally known.
 
36
 * Matches on the unique ID and possibly the MD5 checksum.
 
37
 * @param ci     the content info to search for
 
38
 * @param md5sum also match the MD5 checksum?
 
39
 * @return true iff it's known
 
40
 */
 
41
typedef bool (*HasProc)(const ContentInfo *ci, bool md5sum);
 
42
 
 
43
DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_INFO)
 
44
{
 
45
        ContentInfo *ci = new ContentInfo();
 
46
        ci->type     = (ContentType)p->Recv_uint8();
 
47
        ci->id       = (ContentID)p->Recv_uint32();
 
48
        ci->filesize = p->Recv_uint32();
 
49
 
 
50
        p->Recv_string(ci->name, lengthof(ci->name));
 
51
        p->Recv_string(ci->version, lengthof(ci->name));
 
52
        p->Recv_string(ci->url, lengthof(ci->url));
 
53
        p->Recv_string(ci->description, lengthof(ci->description),  true);
 
54
 
 
55
        ci->unique_id = p->Recv_uint32();
 
56
        for (uint j = 0; j < sizeof(ci->md5sum); j++) {
 
57
                ci->md5sum[j] = p->Recv_uint8();
 
58
        }
 
59
 
 
60
        ci->dependency_count = p->Recv_uint8();
 
61
        ci->dependencies = MallocT<ContentID>(ci->dependency_count);
 
62
        for (uint i = 0; i < ci->dependency_count; i++) ci->dependencies[i] = (ContentID)p->Recv_uint32();
 
63
 
 
64
        ci->tag_count = p->Recv_uint8();
 
65
        ci->tags = MallocT<char[32]>(ci->tag_count);
 
66
        for (uint i = 0; i < ci->tag_count; i++) p->Recv_string(ci->tags[i], lengthof(*ci->tags));
 
67
 
 
68
        if (!ci->IsValid()) {
 
69
                delete ci;
 
70
                this->Close();
 
71
                return false;
 
72
        }
 
73
 
 
74
        /* Find the appropriate check function */
 
75
        HasProc proc = NULL;
 
76
        switch (ci->type) {
 
77
                case CONTENT_TYPE_NEWGRF:
 
78
                        proc = HasGRFConfig;
 
79
                        break;
 
80
 
 
81
                case CONTENT_TYPE_BASE_GRAPHICS:
 
82
                        proc = HasGraphicsSet;
 
83
                        break;
 
84
 
 
85
                case CONTENT_TYPE_AI:
 
86
                case CONTENT_TYPE_AI_LIBRARY:
 
87
                        proc = AI::HasAI; break;
 
88
                        break;
 
89
 
 
90
                case CONTENT_TYPE_SCENARIO:
 
91
                case CONTENT_TYPE_HEIGHTMAP:
 
92
                        proc = HasScenario;
 
93
                        break;
 
94
 
 
95
                default:
 
96
                        break;
 
97
        }
 
98
 
 
99
        if (proc != NULL) {
 
100
                if (proc(ci, true)) {
 
101
                        ci->state = ContentInfo::ALREADY_HERE;
 
102
                } else {
 
103
                        ci->state = ContentInfo::UNSELECTED;
 
104
                        if (proc(ci, false)) ci->upgrade = true;
 
105
                }
 
106
        } else {
 
107
                ci->state = ContentInfo::UNSELECTED;
 
108
        }
 
109
 
 
110
        /* Something we don't have and has filesize 0 does not exist in te system */
 
111
        if (ci->state == ContentInfo::UNSELECTED && ci->filesize == 0) ci->state = ContentInfo::DOES_NOT_EXIST;
 
112
 
 
113
        /* Do we already have a stub for this? */
 
114
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
115
                ContentInfo *ici = *iter;
 
116
                if (ici->type == ci->type && ici->unique_id == ci->unique_id &&
 
117
                                memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) {
 
118
                        /* Preserve the name if possible */
 
119
                        if (StrEmpty(ci->name)) strecpy(ci->name, ici->name, lastof(ci->name));
 
120
                        if (ici->IsSelected()) ci->state = ici->state;
 
121
 
 
122
                        delete ici;
 
123
                        *iter = ci;
 
124
 
 
125
                        this->OnReceiveContentInfo(ci);
 
126
                        return true;
 
127
                }
 
128
        }
 
129
 
 
130
        /* Missing content info? Don't list it */
 
131
        if (ci->filesize == 0) {
 
132
                delete ci;
 
133
                return true;
 
134
        }
 
135
 
 
136
        *this->infos.Append() = ci;
 
137
 
 
138
        /* Incoming data means that we might need to reconsider dependencies */
 
139
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
140
                this->CheckDependencyState(*iter);
 
141
        }
 
142
 
 
143
        this->OnReceiveContentInfo(ci);
 
144
 
 
145
        return true;
 
146
}
 
147
 
 
148
void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
 
149
{
 
150
        if (type == CONTENT_TYPE_END) {
 
151
                this->RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
 
152
                this->RequestContentList(CONTENT_TYPE_SCENARIO);
 
153
                this->RequestContentList(CONTENT_TYPE_HEIGHTMAP);
 
154
                this->RequestContentList(CONTENT_TYPE_AI);
 
155
                this->RequestContentList(CONTENT_TYPE_NEWGRF);
 
156
                this->RequestContentList(CONTENT_TYPE_AI_LIBRARY);
 
157
                return;
 
158
        }
 
159
 
 
160
        this->Connect();
 
161
 
 
162
        Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST);
 
163
        p->Send_uint8 ((byte)type);
 
164
        p->Send_uint32(_openttd_newgrf_version);
 
165
 
 
166
        this->Send_Packet(p);
 
167
}
 
168
 
 
169
void ClientNetworkContentSocketHandler::RequestContentList(uint count, const ContentID *content_ids)
 
170
{
 
171
        this->Connect();
 
172
 
 
173
        while (count > 0) {
 
174
                /* We can "only" send a limited number of IDs in a single packet.
 
175
                 * A packet begins with the packet size and a byte for the type.
 
176
                 * Then this packet adds a byte for the content type and a uint16
 
177
                 * for the count in this packet. The rest of the packet can be
 
178
                 * used for the IDs. */
 
179
                uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
 
180
 
 
181
                Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID);
 
182
                p->Send_uint16(p_count);
 
183
 
 
184
                for (uint i = 0; i < p_count; i++) {
 
185
                        p->Send_uint32(content_ids[i]);
 
186
                }
 
187
 
 
188
                this->Send_Packet(p);
 
189
                count -= p_count;
 
190
                content_ids += count;
 
191
        }
 
192
}
 
193
 
 
194
void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bool send_md5sum)
 
195
{
 
196
        if (cv == NULL) return;
 
197
 
 
198
        this->Connect();
 
199
 
 
200
        /* 20 is sizeof(uint32) + sizeof(md5sum (byte[16])) */
 
201
        assert(cv->Length() < 255);
 
202
        assert(cv->Length() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) / (send_md5sum ? 20 : sizeof(uint32)));
 
203
 
 
204
        Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID);
 
205
        p->Send_uint8(cv->Length());
 
206
 
 
207
        for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
 
208
                const ContentInfo *ci = *iter;
 
209
                p->Send_uint8((byte)ci->type);
 
210
                p->Send_uint32(ci->unique_id);
 
211
                if (!send_md5sum) continue;
 
212
 
 
213
                for (uint j = 0; j < sizeof(ci->md5sum); j++) {
 
214
                        p->Send_uint8(ci->md5sum[j]);
 
215
                }
 
216
        }
 
217
 
 
218
        this->Send_Packet(p);
 
219
 
 
220
        for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) {
 
221
                ContentInfo *ci = *iter;
 
222
                bool found = false;
 
223
                for (ContentIterator iter2 = this->infos.Begin(); iter2 != this->infos.End(); iter2++) {
 
224
                        ContentInfo *ci2 = *iter2;
 
225
                        if (ci->type == ci2->type && ci->unique_id == ci2->unique_id &&
 
226
                                        (!send_md5sum || memcmp(ci->md5sum, ci2->md5sum, sizeof(ci->md5sum)) == 0)) {
 
227
                                found = true;
 
228
                                break;
 
229
                        }
 
230
                }
 
231
                if (!found) {
 
232
                        *this->infos.Append() = ci;
 
233
                } else {
 
234
                        delete ci;
 
235
                }
 
236
        }
 
237
}
 
238
 
 
239
void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uint &bytes)
 
240
{
 
241
        files = 0;
 
242
        bytes = 0;
 
243
 
 
244
        /** Make the list of items to download */
 
245
        ContentID *ids = MallocT<ContentID>(infos.Length());
 
246
        for (ContentIterator iter = infos.Begin(); iter != infos.End(); iter++) {
 
247
                const ContentInfo *ci = *iter;
 
248
                if (!ci->IsSelected()) continue;
 
249
 
 
250
                ids[files++] = ci->id;
 
251
                bytes += ci->filesize;
 
252
        }
 
253
 
 
254
        uint count = files;
 
255
        ContentID *content_ids = ids;
 
256
        this->Connect();
 
257
 
 
258
        while (count > 0) {
 
259
                /* We can "only" send a limited number of IDs in a single packet.
 
260
                 * A packet begins with the packet size and a byte for the type.
 
261
                 * Then this packet adds a uint16 for the count in this packet.
 
262
                 * The rest of the packet can be used for the IDs. */
 
263
                uint p_count = min(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
 
264
 
 
265
                Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT);
 
266
                p->Send_uint16(p_count);
 
267
 
 
268
                for (uint i = 0; i < p_count; i++) {
 
269
                        p->Send_uint32(content_ids[i]);
 
270
                }
 
271
 
 
272
                this->Send_Packet(p);
 
273
                count -= p_count;
 
274
                content_ids += count;
 
275
        }
 
276
 
 
277
        free(ids);
 
278
}
 
279
 
 
280
/**
 
281
 * Determine the full filename of a piece of content information
 
282
 * @param ci         the information to get the filename from
 
283
 * @param compressed should the filename end with .gz?
 
284
 * @return a statically allocated buffer with the filename or
 
285
 *         NULL when no filename could be made.
 
286
 */
 
287
static char *GetFullFilename(const ContentInfo *ci, bool compressed)
 
288
{
 
289
        Subdirectory dir;
 
290
        switch (ci->type) {
 
291
                default: return NULL;
 
292
                case CONTENT_TYPE_BASE_GRAPHICS: dir = DATA_DIR;       break;
 
293
                case CONTENT_TYPE_NEWGRF:        dir = DATA_DIR;       break;
 
294
                case CONTENT_TYPE_AI:            dir = AI_DIR;         break;
 
295
                case CONTENT_TYPE_AI_LIBRARY:    dir = AI_LIBRARY_DIR; break;
 
296
                case CONTENT_TYPE_SCENARIO:      dir = SCENARIO_DIR;   break;
 
297
                case CONTENT_TYPE_HEIGHTMAP:     dir = HEIGHTMAP_DIR;  break;
 
298
        }
 
299
 
 
300
        static char buf[MAX_PATH];
 
301
        FioGetFullPath(buf, lengthof(buf), SP_AUTODOWNLOAD_DIR, dir, ci->filename);
 
302
        strecat(buf, compressed ? ".tar.gz" : ".tar", lastof(buf));
 
303
 
 
304
        return buf;
 
305
}
 
306
 
 
307
/**
 
308
 * Gunzip a given file and remove the .gz if successful.
 
309
 * @param ci container with filename
 
310
 * @return true if the gunzip completed
 
311
 */
 
312
static bool GunzipFile(const ContentInfo *ci)
 
313
{
 
314
#if defined(WITH_ZLIB)
 
315
        bool ret = true;
 
316
        FILE *ftmp = fopen(GetFullFilename(ci, true), "rb");
 
317
        gzFile fin = gzdopen(fileno(ftmp), "rb");
 
318
        FILE *fout = fopen(GetFullFilename(ci, false), "wb");
 
319
 
 
320
        if (fin == NULL || fout == NULL) {
 
321
                ret = false;
 
322
                goto exit;
 
323
        }
 
324
 
 
325
        byte buff[8192];
 
326
        while (!gzeof(fin)) {
 
327
                int read = gzread(fin, buff, sizeof(buff));
 
328
                if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
 
329
                        ret = false;
 
330
                        break;
 
331
                }
 
332
        }
 
333
 
 
334
exit:
 
335
        if (fin != NULL) {
 
336
                /* Closes ftmp too! */
 
337
                gzclose(fin);
 
338
        } else if (ftmp != NULL) {
 
339
                /* In case the gz stream was opened correctly this will
 
340
                 * be closed by gzclose. */
 
341
                fclose(ftmp);
 
342
        }
 
343
        if (fout != NULL) fclose(fout);
 
344
 
 
345
        return ret;
 
346
#else
 
347
        NOT_REACHED();
 
348
#endif /* defined(WITH_ZLIB) */
 
349
}
 
350
 
 
351
DEF_CONTENT_RECEIVE_COMMAND(Client, PACKET_CONTENT_SERVER_CONTENT)
 
352
{
 
353
        if (this->curFile == NULL) {
 
354
                /* When we haven't opened a file this must be our first packet with metadata. */
 
355
                this->curInfo = new ContentInfo;
 
356
                this->curInfo->type     = (ContentType)p->Recv_uint8();
 
357
                this->curInfo->id       = (ContentID)p->Recv_uint32();
 
358
                this->curInfo->filesize = p->Recv_uint32();
 
359
                p->Recv_string(this->curInfo->filename, lengthof(this->curInfo->filename));
 
360
 
 
361
                if (!this->curInfo->IsValid()) {
 
362
                        delete this->curInfo;
 
363
                        this->curInfo = NULL;
 
364
                        this->Close();
 
365
                        return false;
 
366
                }
 
367
 
 
368
                if (this->curInfo->filesize != 0) {
 
369
                        /* The filesize is > 0, so we are going to download it */
 
370
                        const char *filename = GetFullFilename(this->curInfo, true);
 
371
                        if (filename == NULL) {
 
372
                                /* Unless that fails ofcourse... */
 
373
                                DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
 
374
                                ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, 0, 0);
 
375
                                this->Close();
 
376
                                return false;
 
377
                        }
 
378
 
 
379
                        this->curFile = fopen(filename, "wb");
 
380
                }
 
381
        } else {
 
382
                /* We have a file opened, thus are downloading internal content */
 
383
                size_t toRead = (size_t)(p->size - p->pos);
 
384
                if (fwrite(p->buffer + p->pos, 1, toRead, this->curFile) != toRead) {
 
385
                        DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
 
386
                        ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, 0, 0);
 
387
                        this->Close();
 
388
                        fclose(this->curFile);
 
389
                        this->curFile = NULL;
 
390
 
 
391
                        return false;
 
392
                }
 
393
 
 
394
                this->OnDownloadProgress(this->curInfo, (uint)toRead);
 
395
 
 
396
                if (toRead == 0) {
 
397
                        /* We read nothing; that's our marker for end-of-stream.
 
398
                         * Now gunzip the tar and make it known. */
 
399
                        fclose(this->curFile);
 
400
                        this->curFile = NULL;
 
401
 
 
402
                        if (GunzipFile(this->curInfo)) {
 
403
                                unlink(GetFullFilename(this->curInfo, true));
 
404
 
 
405
                                TarListAddFile(GetFullFilename(this->curInfo, false));
 
406
 
 
407
                                this->OnDownloadComplete(this->curInfo->id);
 
408
                        } else {
 
409
                                ShowErrorMessage(INVALID_STRING_ID, STR_CONTENT_ERROR_COULD_NOT_EXTRACT, 0, 0);
 
410
                        }
 
411
                }
 
412
        }
 
413
 
 
414
        /* We ended this file, so clean up the mess */
 
415
        if (this->curFile == NULL) {
 
416
                delete this->curInfo;
 
417
                this->curInfo = NULL;
 
418
        }
 
419
 
 
420
        return true;
 
421
}
 
422
 
 
423
/**
 
424
 * Create a socket handler with the given socket and (server) address.
 
425
 * @param s the socket to communicate over
 
426
 * @param sin the IP/port of the server
 
427
 */
 
428
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
 
429
        NetworkContentSocketHandler(INVALID_SOCKET, NULL),
 
430
        curFile(NULL),
 
431
        curInfo(NULL),
 
432
        isConnecting(false)
 
433
{
 
434
}
 
435
 
 
436
/** Clear up the mess ;) */
 
437
ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
 
438
{
 
439
        delete this->curInfo;
 
440
        if (this->curFile != NULL) fclose(this->curFile);
 
441
 
 
442
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
 
443
}
 
444
 
 
445
class NetworkContentConnecter : TCPConnecter {
 
446
public:
 
447
        NetworkContentConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
 
448
 
 
449
        virtual void OnFailure()
 
450
        {
 
451
                _network_content_client.isConnecting = false;
 
452
                _network_content_client.OnConnect(false);
 
453
        }
 
454
 
 
455
        virtual void OnConnect(SOCKET s)
 
456
        {
 
457
                assert(_network_content_client.sock == INVALID_SOCKET);
 
458
                _network_content_client.isConnecting = false;
 
459
                _network_content_client.sock = s;
 
460
                _network_content_client.has_quit = false;
 
461
                _network_content_client.OnConnect(true);
 
462
        }
 
463
};
 
464
 
 
465
/**
 
466
 * Connect with the content server.
 
467
 */
 
468
void ClientNetworkContentSocketHandler::Connect()
 
469
{
 
470
        this->lastActivity = _realtime_tick;
 
471
 
 
472
        if (this->sock != INVALID_SOCKET || this->isConnecting) return;
 
473
        this->isConnecting = true;
 
474
        new NetworkContentConnecter(NetworkAddress(NETWORK_CONTENT_SERVER_HOST, NETWORK_CONTENT_SERVER_PORT));
 
475
}
 
476
 
 
477
/**
 
478
 * Disconnect from the content server.
 
479
 */
 
480
void ClientNetworkContentSocketHandler::Close()
 
481
{
 
482
        if (this->sock == INVALID_SOCKET) return;
 
483
        NetworkContentSocketHandler::Close();
 
484
 
 
485
        this->OnDisconnect();
 
486
}
 
487
 
 
488
/**
 
489
 * Check whether we received/can send some data from/to the content server and
 
490
 * when that's the case handle it appropriately
 
491
 */
 
492
void ClientNetworkContentSocketHandler::SendReceive()
 
493
{
 
494
        if (this->sock == INVALID_SOCKET || this->isConnecting) return;
 
495
 
 
496
        if (this->lastActivity + IDLE_TIMEOUT < _realtime_tick) {
 
497
                this->Close();
 
498
                return;
 
499
        }
 
500
 
 
501
        fd_set read_fd, write_fd;
 
502
        struct timeval tv;
 
503
 
 
504
        FD_ZERO(&read_fd);
 
505
        FD_ZERO(&write_fd);
 
506
 
 
507
        FD_SET(this->sock, &read_fd);
 
508
        FD_SET(this->sock, &write_fd);
 
509
 
 
510
        tv.tv_sec = tv.tv_usec = 0; // don't block at all.
 
511
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
 
512
        select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv);
 
513
#else
 
514
        WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL);
 
515
#endif
 
516
        if (FD_ISSET(this->sock, &read_fd)) {
 
517
                this->Recv_Packets();
 
518
                this->lastActivity = _realtime_tick;
 
519
        }
 
520
 
 
521
        this->writable = !!FD_ISSET(this->sock, &write_fd);
 
522
        this->Send_Packets();
 
523
}
 
524
 
 
525
/**
 
526
 * Download information of a given Content ID if not already tried
 
527
 * @param cid the ID to try
 
528
 */
 
529
void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid)
 
530
{
 
531
        /* When we tried to download it already, don't try again */
 
532
        if (this->requested.Contains(cid)) return;
 
533
 
 
534
        *this->requested.Append() = cid;
 
535
        assert(this->requested.Contains(cid));
 
536
        this->RequestContentList(1, &cid);
 
537
}
 
538
 
 
539
/**
 
540
 * Get the content info based on a ContentID
 
541
 * @param cid the ContentID to search for
 
542
 * @return the ContentInfo or NULL if not found
 
543
 */
 
544
ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid)
 
545
{
 
546
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
547
                ContentInfo *ci = *iter;
 
548
                if (ci->id == cid) return ci;
 
549
        }
 
550
        return NULL;
 
551
}
 
552
 
 
553
 
 
554
/**
 
555
 * Select a specific content id.
 
556
 * @param cid the content ID to select
 
557
 */
 
558
void ClientNetworkContentSocketHandler::Select(ContentID cid)
 
559
{
 
560
        ContentInfo *ci = this->GetContent(cid);
 
561
        if (ci == NULL || ci->state != ContentInfo::UNSELECTED) return;
 
562
 
 
563
        ci->state = ContentInfo::SELECTED;
 
564
        this->CheckDependencyState(ci);
 
565
}
 
566
 
 
567
/**
 
568
 * Unselect a specific content id.
 
569
 * @param cid the content ID to deselect
 
570
 */
 
571
void ClientNetworkContentSocketHandler::Unselect(ContentID cid)
 
572
{
 
573
        ContentInfo *ci = this->GetContent(cid);
 
574
        if (ci == NULL || !ci->IsSelected()) return;
 
575
 
 
576
        ci->state = ContentInfo::UNSELECTED;
 
577
        this->CheckDependencyState(ci);
 
578
}
 
579
 
 
580
/** Select everything we can select */
 
581
void ClientNetworkContentSocketHandler::SelectAll()
 
582
{
 
583
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
584
                ContentInfo *ci = *iter;
 
585
                if (ci->state == ContentInfo::UNSELECTED) {
 
586
                        ci->state = ContentInfo::SELECTED;
 
587
                        this->CheckDependencyState(ci);
 
588
                }
 
589
        }
 
590
}
 
591
 
 
592
/** Select everything that's an update for something we've got */
 
593
void ClientNetworkContentSocketHandler::SelectUpgrade()
 
594
{
 
595
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
596
                ContentInfo *ci = *iter;
 
597
                if (ci->state == ContentInfo::UNSELECTED && ci->upgrade) {
 
598
                        ci->state = ContentInfo::SELECTED;
 
599
                        this->CheckDependencyState(ci);
 
600
                }
 
601
        }
 
602
}
 
603
 
 
604
/** Unselect everything that we've not downloaded so far. */
 
605
void ClientNetworkContentSocketHandler::UnselectAll()
 
606
{
 
607
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
608
                ContentInfo *ci = *iter;
 
609
                if (ci->IsSelected()) ci->state = ContentInfo::UNSELECTED;
 
610
        }
 
611
}
 
612
 
 
613
/** Toggle the state of a content info and check it's dependencies */
 
614
void ClientNetworkContentSocketHandler::ToggleSelectedState(const ContentInfo *ci)
 
615
{
 
616
        switch (ci->state) {
 
617
                case ContentInfo::SELECTED:
 
618
                case ContentInfo::AUTOSELECTED:
 
619
                        this->Unselect(ci->id);
 
620
                        break;
 
621
 
 
622
                case ContentInfo::UNSELECTED:
 
623
                        this->Select(ci->id);
 
624
                        break;
 
625
 
 
626
                default:
 
627
                        break;
 
628
        }
 
629
}
 
630
 
 
631
/**
 
632
 * Reverse lookup the dependencies of (direct) parents over a given child.
 
633
 * @param parents list to store all parents in (is not cleared)
 
634
 * @param child   the child to search the parents' dependencies for
 
635
 */
 
636
void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const
 
637
{
 
638
        for (ConstContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) {
 
639
                const ContentInfo *ci = *iter;
 
640
                if (ci == child) continue;
 
641
 
 
642
                for (uint i = 0; i < ci->dependency_count; i++) {
 
643
                        if (ci->dependencies[i] == child->id) {
 
644
                                *parents.Append() = ci;
 
645
                                break;
 
646
                        }
 
647
                }
 
648
        }
 
649
}
 
650
 
 
651
/**
 
652
 * Reverse lookup the dependencies of all parents over a given child.
 
653
 * @param tree  list to store all parents in (is not cleared)
 
654
 * @param child the child to search the parents' dependencies for
 
655
 */
 
656
void ClientNetworkContentSocketHandler::ReverseLookupTreeDependency(ConstContentVector &tree, const ContentInfo *child) const
 
657
{
 
658
        *tree.Append() = child;
 
659
 
 
660
        /* First find all direct parents */
 
661
        for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
 
662
                ConstContentVector parents;
 
663
                this->ReverseLookupDependency(parents, *iter);
 
664
 
 
665
                for (ConstContentIterator piter = parents.Begin(); piter != parents.End(); piter++) {
 
666
                        tree.Include(*piter);
 
667
                }
 
668
        }
 
669
}
 
670
 
 
671
/**
 
672
 * Check the dependencies (recursively) of this content info
 
673
 * @param ci the content info to check the dependencies of
 
674
 */
 
675
void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
 
676
{
 
677
        if (ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) {
 
678
                /* Selection is easy; just walk all children and set the
 
679
                 * autoselected state. That way we can see what we automatically
 
680
                 * selected and thus can unselect when a dependency is removed. */
 
681
                for (uint i = 0; i < ci->dependency_count; i++) {
 
682
                        ContentInfo *c = this->GetContent(ci->dependencies[i]);
 
683
                        if (c == NULL) {
 
684
                                this->DownloadContentInfo(ci->dependencies[i]);
 
685
                        } else if (c->state == ContentInfo::UNSELECTED) {
 
686
                                c->state = ContentInfo::AUTOSELECTED;
 
687
                                this->CheckDependencyState(c);
 
688
                        }
 
689
                }
 
690
                return;
 
691
        }
 
692
 
 
693
        if (ci->state != ContentInfo::UNSELECTED) return;
 
694
 
 
695
        /* For unselection we need to find the parents of us. We need to
 
696
         * unselect them. After that we unselect all children that we
 
697
         * depend on and are not used as dependency for us, but only when
 
698
         * we automatically selected them. */
 
699
        ConstContentVector parents;
 
700
        this->ReverseLookupDependency(parents, ci);
 
701
        for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
 
702
                const ContentInfo *c = *iter;
 
703
                if (!c->IsSelected()) continue;
 
704
 
 
705
                this->Unselect(c->id);
 
706
        }
 
707
 
 
708
        for (uint i = 0; i < ci->dependency_count; i++) {
 
709
                const ContentInfo *c = this->GetContent(ci->dependencies[i]);
 
710
                if (c == NULL) {
 
711
                        DownloadContentInfo(ci->dependencies[i]);
 
712
                        continue;
 
713
                }
 
714
                if (c->state != ContentInfo::AUTOSELECTED) continue;
 
715
 
 
716
                /* Only unselect when WE are the only parent. */
 
717
                parents.Clear();
 
718
                this->ReverseLookupDependency(parents, c);
 
719
 
 
720
                /* First check whether anything depends on us */
 
721
                int sel_count = 0;
 
722
                bool force_selection = false;
 
723
                for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
 
724
                        if ((*iter)->IsSelected()) sel_count++;
 
725
                        if ((*iter)->state == ContentInfo::SELECTED) force_selection = true;
 
726
                }
 
727
                if (sel_count == 0) {
 
728
                        /* Nothing depends on us */
 
729
                        this->Unselect(c->id);
 
730
                        continue;
 
731
                }
 
732
                /* Something manually selected depends directly on us */
 
733
                if (force_selection) continue;
 
734
 
 
735
                /* "Flood" search to find all items in the dependency graph*/
 
736
                parents.Clear();
 
737
                this->ReverseLookupTreeDependency(parents, c);
 
738
 
 
739
                /* Is there anything that is "force" selected?, if so... we're done. */
 
740
                for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
 
741
                        if ((*iter)->state != ContentInfo::SELECTED) continue;
 
742
 
 
743
                        force_selection = true;
 
744
                        break;
 
745
                }
 
746
 
 
747
                /* So something depended directly on us */
 
748
                if (force_selection) continue;
 
749
 
 
750
                /* Nothing depends on us, mark the whole graph as unselected.
 
751
                 * After that's done run over them once again to test their children
 
752
                 * to unselect. Don't do it immediatelly because it'll do exactly what
 
753
                 * we're doing now. */
 
754
                for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
 
755
                        const ContentInfo *c = *iter;
 
756
                        if (c->state == ContentInfo::AUTOSELECTED) this->Unselect(c->id);
 
757
                }
 
758
                for (ConstContentIterator iter = parents.Begin(); iter != parents.End(); iter++) {
 
759
                        this->CheckDependencyState(this->GetContent((*iter)->id));
 
760
                }
 
761
        }
 
762
}
 
763
 
 
764
void ClientNetworkContentSocketHandler::Clear()
 
765
{
 
766
        for (ContentIterator iter = this->infos.Begin(); iter != this->infos.End(); iter++) delete *iter;
 
767
 
 
768
        this->infos.Clear();
 
769
        this->requested.Clear();
 
770
}
 
771
 
 
772
/*** CALLBACK ***/
 
773
 
 
774
void ClientNetworkContentSocketHandler::OnConnect(bool success)
 
775
{
 
776
        for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
 
777
                ContentCallback *cb = *iter;
 
778
                cb->OnConnect(success);
 
779
                if (iter != this->callbacks.End() && *iter == cb) iter++;
 
780
        }
 
781
}
 
782
 
 
783
void ClientNetworkContentSocketHandler::OnDisconnect()
 
784
{
 
785
        for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
 
786
                ContentCallback *cb = *iter;
 
787
                cb->OnDisconnect();
 
788
                if (iter != this->callbacks.End() && *iter == cb) iter++;
 
789
        }
 
790
}
 
791
 
 
792
void ClientNetworkContentSocketHandler::OnReceiveContentInfo(const ContentInfo *ci)
 
793
{
 
794
        for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
 
795
                ContentCallback *cb = *iter;
 
796
                cb->OnReceiveContentInfo(ci);
 
797
                if (iter != this->callbacks.End() && *iter == cb) iter++;
 
798
        }
 
799
}
 
800
 
 
801
void ClientNetworkContentSocketHandler::OnDownloadProgress(const ContentInfo *ci, uint bytes)
 
802
{
 
803
        for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
 
804
                ContentCallback *cb = *iter;
 
805
                cb->OnDownloadProgress(ci, bytes);
 
806
                if (iter != this->callbacks.End() && *iter == cb) iter++;
 
807
        }
 
808
}
 
809
 
 
810
void ClientNetworkContentSocketHandler::OnDownloadComplete(ContentID cid)
 
811
{
 
812
        ContentInfo *ci = this->GetContent(cid);
 
813
        if (ci != NULL) {
 
814
                ci->state = ContentInfo::ALREADY_HERE;
 
815
        }
 
816
 
 
817
        for (ContentCallback **iter = this->callbacks.Begin(); iter != this->callbacks.End(); /* nothing */) {
 
818
                ContentCallback *cb = *iter;
 
819
                cb->OnDownloadComplete(cid);
 
820
                if (iter != this->callbacks.End() && *iter == cb) iter++;
 
821
        }
 
822
}
 
823
 
 
824
#endif /* ENABLE_NETWORK */