GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/updaterwindow.cpp Lines: 88 609 14.4 %
Date: 2021-03-17 Branches: 60 826 7.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "gui/windows/updaterwindow.h"
25
26
#include "client.h"
27
#include "configuration.h"
28
#include "main.h"
29
#include "settings.h"
30
31
#include "enums/gui/layouttype.h"
32
33
#include "fs/files.h"
34
#include "fs/mkdir.h"
35
#include "fs/paths.h"
36
37
#include "fs/virtfs/fs.h"
38
39
#include "gui/widgets/button.h"
40
#include "gui/widgets/containerplacer.h"
41
#include "gui/widgets/itemlinkhandler.h"
42
#include "gui/widgets/label.h"
43
#include "gui/widgets/layout.h"
44
#include "gui/widgets/progressbar.h"
45
#include "gui/widgets/scrollarea.h"
46
#include "gui/widgets/staticbrowserbox.h"
47
48
#include "net/download.h"
49
#include "net/updatetypeoperators.h"
50
51
#include "resources/db/moddb.h"
52
53
#include "utils/delete2.h"
54
#include "utils/foreach.h"
55
#include "utils/gettext.h"
56
57
#include <sys/stat.h>
58
59
#include <fstream>
60
#include <sstream>
61
62
#include "debug.h"
63
64
UpdaterWindow *updaterWindow = nullptr;
65
66
3
const std::string xmlUpdateFile("resources.xml");
67
3
const std::string txtUpdateFile("resources2.txt");
68
2
const std::string updateServer2
69
2
    ("http://download.manaplus.org/manaplus/updates/");
70
2
const std::string updateServer3
71
2
    ("http://download2.manaplus.org/manaplus/updates/");
72
2
const std::string updateServer4
73
2
    ("http://download.evolonline.org/manaplus/updates/");
74
2
const std::string updateServer5
75
2
    ("http://download3.manaplus.org/manaplus/updates/");
76
77
/**
78
 * Load the given file into a vector of updateFiles.
79
 */
80
static STD_VECTOR<UpdateFile> loadXMLFile(const std::string &fileName,
81
                                          const bool loadMods)
82
{
83
    STD_VECTOR<UpdateFile> files;
84
    XML::Document doc(fileName, UseVirtFs_false, SkipError_false);
85
    XmlNodeConstPtrConst rootNode = doc.rootNode();
86
87
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "updates"))
88
    {
89
        logger->log("Error loading update file: %s", fileName.c_str());
90
        return files;
91
    }
92
93
    for_each_xml_child_node(fileNode, rootNode)
94
    {
95
        const bool isMod = xmlNameEqual(fileNode, "mod");
96
        if (!xmlNameEqual(fileNode, "update") && !isMod)
97
            continue;
98
99
        UpdateFile file;
100
        file.name = XML::getProperty(fileNode, "file", "");
101
        file.hash = XML::getProperty(fileNode, "hash", "");
102
        file.type = XML::getProperty(fileNode, "type", "data");
103
        file.desc = XML::getProperty(fileNode, "description", "");
104
        file.group = XML::getProperty(fileNode, "group", "");
105
        if (!file.group.empty() && (!isMod || !loadMods))
106
            continue;
107
108
        const std::string version = XML::getProperty(
109
            fileNode, "version", "");
110
        if (!version.empty())
111
        {
112
            if (version > CHECK_VERSION)
113
                continue;
114
        }
115
        const std::string notVersion = XML::getProperty(
116
            fileNode, "notVersion", "");
117
        if (!notVersion.empty())
118
        {
119
            if (notVersion <= CHECK_VERSION)
120
                continue;
121
        }
122
        if (XML::getProperty(fileNode, "required", "yes") == "yes")
123
            file.required = true;
124
        else
125
            file.required = false;
126
127
        if (checkPath(file.name))
128
            files.push_back(file);
129
    }
130
131
    return files;
132
}
133
134
static STD_VECTOR<UpdateFile> loadTxtFile(const std::string &fileName)
135
{
136
    STD_VECTOR<UpdateFile> files;
137
    std::ifstream fileHandler;
138
    fileHandler.open(fileName.c_str(), std::ios::in);
139
140
    if (fileHandler.is_open())
141
    {
142
        while (fileHandler.good())
143
        {
144
            char name[256];
145
            char hash[50];
146
            fileHandler.getline(name, 256, ' ');
147
            fileHandler.getline(hash, 50);
148
149
            UpdateFile thisFile;
150
            thisFile.name = name;
151
            thisFile.hash = hash;
152
            thisFile.type = "data";
153
            thisFile.group.clear();
154
            thisFile.required = true;
155
            thisFile.desc.clear();
156
157
            if (!thisFile.name.empty() && checkPath(thisFile.name))
158
                files.push_back(thisFile);
159
        }
160
    }
161
    else
162
    {
163
        logger->log("Error loading update file: %s", fileName.c_str());
164
    }
165
    fileHandler.close();
166
167
    return files;
168
}
169
170
1
UpdaterWindow::UpdaterWindow(const std::string &restrict updateHost,
171
                             const std::string &restrict updatesDir,
172
                             const bool applyUpdates,
173
1
                             const UpdateTypeT updateType) :
174
    // TRANSLATORS: updater window name
175
1
    Window(_("Updating..."), Modal_false, nullptr, "update.xml"),
176
    ActionListener(),
177
    KeyListener(),
178
    mDownloadProgress(0.0F),
179
    mUpdateHost(updateHost),
180
    mUpdatesDir(updatesDir),
181
    mUpdatesDirReal(updatesDir),
182
    mCurrentFile("news.txt"),
183
    mNewLabelCaption(),
184
    mDownloadMutex(),
185
    mCurrentChecksum(0),
186
    mMemoryBuffer(nullptr),
187
    mDownload(nullptr),
188
    mUpdateFiles(),
189
    mTempUpdateFiles(),
190
    mUpdateServerPath(mUpdateHost),
191

1
    mItemLinkHandler(new ItemLinkHandler),
192
    // TRANSLATORS: updater window label
193

2
    mLabel(new Label(this, _("Connecting..."))),
194
    // TRANSLATORS: updater window button
195

2
    mCancelButton(new Button(this, _("Cancel"), "cancel", BUTTON_SKIN, this)),
196
    // TRANSLATORS: updater window button
197

2
    mPlayButton(new Button(this, _("Play"), "play", BUTTON_SKIN, this)),
198
    mProgressBar(new ProgressBar(this, 0.0, 310, 0,
199
        ProgressColorId::PROG_UPDATE,
200

1
        "updateprogressbar.xml", "updateprogressbar_fill.xml")),
201
    mBrowserBox(new StaticBrowserBox(this, Opaque_true,
202

1
        "browserbox.xml")),
203
1
    mScrollArea(new ScrollArea(this, mBrowserBox,
204

2
        Opaque_true, "update_background.xml")),
205
    mDownloadStatus(UpdateDownloadStatus::UPDATE_NEWS),
206
    mDownloadedBytes(0),
207
    mUpdateIndex(0),
208
    mUpdateIndexOffset(0),
209
    mUpdateType(updateType),
210
    mStoreInMemory(true),
211
    mDownloadComplete(true),
212
    mUserCancel(false),
213
    mLoadUpdates(applyUpdates),
214
    mValidateXml(false),
215






49
    mSkipPatches(false)
216
{
217
5
    setWindowName("UpdaterWindow");
218
1
    setResizable(true);
219
1
    setDefaultSize(450, 400, ImagePosition::CENTER, 0, 0);
220
1
    setMinWidth(310);
221
1
    setMinHeight(220);
222
223
2
    mProgressBar->setSmoothProgress(false);
224
2
    mBrowserBox->setOpaque(Opaque_false);
225
1
    mBrowserBox->setLinkHandler(mItemLinkHandler);
226
2
    mBrowserBox->setProcessVars(true);
227
2
    mBrowserBox->setEnableKeys(true);
228
2
    mBrowserBox->setEnableTabs(true);
229
2
    mBrowserBox->setEnableImages(true);
230
2
    mPlayButton->setEnabled(false);
231
232
1
    ContainerPlacer placer(nullptr, nullptr);
233
1
    placer = getPlacer(0, 0);
234
235
2
    placer(0, 0, mScrollArea, 5, 3).setPadding(3);
236
1
    placer(0, 3, mLabel, 5, 1);
237
1
    placer(0, 4, mProgressBar, 5, 1);
238
1
    placer(3, 5, mCancelButton, 1, 1);
239
1
    placer(4, 5, mPlayButton, 1, 1);
240
241
1
    Layout &layout = getLayout();
242
1
    layout.setRowHeight(0, LayoutType::SET);
243
244
1
    addKeyListener(this);
245
246
2
    if (mUpdateHost.empty())
247
    {
248
1
        const STD_VECTOR<std::string> &mirrors = settings.updateMirrors;
249
4
        if (mirrors.begin() != mirrors.end())
250
            mUpdateHost = *mirrors.begin();
251
1
        mSkipPatches = true;
252
    }
253
254
1
    loadWindowState();
255
1
}
256
257
1
void UpdaterWindow::postInit()
258
{
259
1
    Window::postInit();
260
1
    setVisible(Visible_true);
261
1
    mCancelButton->requestFocus();
262
1
    removeProtocol(mUpdateServerPath);
263
264
1
    download();
265
1
}
266
267
12
UpdaterWindow::~UpdaterWindow()
268
{
269
1
    if (mLoadUpdates)
270
        loadUpdates();
271
272
1
    if (mDownload != nullptr)
273
    {
274
1
        mDownload->cancel();
275
276
1
        delete2(mDownload)
277
    }
278
1
    free(mMemoryBuffer);
279
1
    delete2(mItemLinkHandler)
280
2
}
281
282
void UpdaterWindow::setProgress(const float p)
283
{
284
    // Do delayed progress bar update, since Guichan isn't thread-safe
285
    MutexLocker lock(&mDownloadMutex);
286
    mDownloadProgress = p;
287
}
288
289
1
void UpdaterWindow::setLabel(const std::string &str)
290
{
291
    // Do delayed label text update, since Guichan isn't thread-safe
292
3
    MutexLocker lock(&mDownloadMutex);
293
2
    mNewLabelCaption = str;
294
1
}
295
296
void UpdaterWindow::enable()
297
{
298
    mCancelButton->setEnabled(false);
299
    mPlayButton->setEnabled(true);
300
    mPlayButton->requestFocus();
301
302
    if (client->getState() != State::GAME)
303
    {
304
        if ((mUpdateType & UpdateType::Close) != 0)
305
            client->setState(State::LOAD_DATA);
306
    }
307
    else
308
    {
309
        deleteSelf();
310
    }
311
}
312
313
void UpdaterWindow::action(const ActionEvent &event)
314
{
315
    const std::string &eventId = event.getId();
316
    if (eventId == "cancel")
317
    {
318
        // Register the user cancel
319
        mUserCancel = true;
320
        // Skip the updating process
321
        if (mDownloadStatus != UpdateDownloadStatus::UPDATE_COMPLETE)
322
        {
323
            if (mDownload != nullptr)
324
                mDownload->cancel();
325
            mDownloadStatus = UpdateDownloadStatus::UPDATE_ERROR;
326
        }
327
    }
328
    else if (eventId == "play")
329
    {
330
        if (client->getState() != State::GAME)
331
            client->setState(State::LOAD_DATA);
332
        else
333
            deleteSelf();
334
    }
335
}
336
337
void UpdaterWindow::keyPressed(KeyEvent &event)
338
{
339
    const InputActionT actionId = event.getActionId();
340
    if (actionId == InputAction::GUI_CANCEL)
341
    {
342
        action(ActionEvent(nullptr, mCancelButton->getActionEventId()));
343
        if (client->getState() != State::GAME)
344
            client->setState(State::LOGIN);
345
        else
346
            deleteSelf();
347
    }
348
    else if (actionId == InputAction::GUI_SELECT ||
349
             actionId == InputAction::GUI_SELECT2)
350
    {
351
        if (mDownloadStatus == UpdateDownloadStatus::UPDATE_COMPLETE ||
352
            mDownloadStatus == UpdateDownloadStatus::UPDATE_ERROR ||
353
            mDownloadStatus == UpdateDownloadStatus::UPDATE_IDLE)
354
        {
355
            action(ActionEvent(nullptr, mPlayButton->getActionEventId()));
356
        }
357
        else
358
        {
359
            action(ActionEvent(nullptr, mCancelButton->getActionEventId()));
360
        }
361
    }
362
}
363
364
void UpdaterWindow::loadNews()
365
{
366
    if (mMemoryBuffer == nullptr)
367
    {
368
        logger->log1("Couldn't load news");
369
        return;
370
    }
371
372
    // Reallocate and include terminating 0 character
373
    mMemoryBuffer = static_cast<char*>(realloc(
374
        mMemoryBuffer, mDownloadedBytes + 1));
375
    if (mMemoryBuffer == nullptr)
376
    {
377
        logger->log1("Couldn't load news");
378
        return;
379
    }
380
    mMemoryBuffer[mDownloadedBytes] = '\0';
381
    mBrowserBox->clearRows();
382
383
    std::string newsName = mUpdatesDir + "/local/help/news.txt";
384
    mkdir_r((mUpdatesDir + "/local/help/").c_str());
385
    bool firstLine(true);
386
    std::ofstream file;
387
    std::stringstream ss(mMemoryBuffer);
388
    std::string line;
389
    file.open(newsName.c_str(), std::ios::out);
390
    int cnt = 0;
391
    const int maxNews = 50;
392
    while (std::getline(ss, line, '\n'))
393
    {
394
        cnt ++;
395
        if (firstLine)
396
        {
397
            firstLine = false;
398
            const size_t i = line.find("##9 Latest client version: ##6");
399
            if (i == 0U)
400
                continue;
401
402
            if (file.is_open())
403
                file << line << std::endl;
404
            if (cnt < maxNews)
405
                mBrowserBox->addRow(line, false);
406
        }
407
        else
408
        {
409
            if (file.is_open())
410
                file << line << std::endl;
411
            if (cnt < maxNews)
412
                mBrowserBox->addRow(line, false);
413
        }
414
    }
415
416
    file.close();
417
    if (cnt > maxNews)
418
    {
419
        mBrowserBox->addRow("", false);
420
        // TRANSLATORS: updater window checkbox
421
        mBrowserBox->addRow("news", _("Show all news (can be slow)"));
422
        mBrowserBox->addRow("", false);
423
    }
424
    // Free the memory buffer now that we don't need it anymore
425
    free(mMemoryBuffer);
426
    mMemoryBuffer = nullptr;
427
    mDownloadedBytes = 0;
428
429
    mBrowserBox->updateHeight();
430
    mScrollArea->setVerticalScrollAmount(0);
431
}
432
433
void UpdaterWindow::loadPatch()
434
{
435
    if (mMemoryBuffer == nullptr)
436
    {
437
        logger->log1("Couldn't load patch");
438
        return;
439
    }
440
441
    // Reallocate and include terminating 0 character
442
    mMemoryBuffer = static_cast<char*>(
443
        realloc(mMemoryBuffer, mDownloadedBytes + 1));
444
    if (mMemoryBuffer == nullptr)
445
    {
446
        logger->log1("Couldn't load patch");
447
        return;
448
    }
449
    mMemoryBuffer[mDownloadedBytes] = '\0';
450
451
    std::string version;
452
453
    // Tokenize and add each line separately
454
    char *line = strtok(mMemoryBuffer, "\n");
455
    if (line != nullptr)
456
    {
457
        version = line;
458
        if (serverVersion == 0)
459
        {
460
            line = strtok(nullptr, "\n");
461
            if (line != nullptr)
462
            {
463
                mBrowserBox->addRow(strprintf("##9 Latest client version: "
464
                    "##6ManaPlus %s##0", line), true);
465
            }
466
        }
467
        if (version > CHECK_VERSION)
468
        {
469
            mBrowserBox->addRow("", true);
470
#if defined(ANDROID)
471
            const std::string url = "androidDownloadUrl";
472
            const std::string text = "androidDownloadUrl";
473
#elif defined(WIN32)
474
            const std::string url = "windowsDownloadUrl";
475
            const std::string text = "windowsDownloadUrl";
476
#else  // defined(ANDROID)
477
478
            const std::string url = "otherDownloadUrl";
479
            const std::string text = "otherDownloadUrl";
480
#endif  // defined(ANDROID)
481
482
            mBrowserBox->addRow(std::string("  ##1[@@").append(
483
                branding.getStringValue(url)).append("|").append(
484
                branding.getStringValue(text)).append("@@]"), true);
485
            mBrowserBox->addRow("##1You can download it from", true);
486
            mBrowserBox->addRow("##1ManaPlus updated.", true);
487
        }
488
        else
489
        {
490
            mBrowserBox->addRow("You have latest client version.", true);
491
        }
492
    }
493
494
    // Free the memory buffer now that we don't need it anymore
495
    free(mMemoryBuffer);
496
    mMemoryBuffer = nullptr;
497
    mDownloadedBytes = 0;
498
499
    mBrowserBox->updateHeight();
500
    mScrollArea->setVerticalScrollAmount(0);
501
}
502
503
int UpdaterWindow::updateProgress(void *ptr,
504
                                  const DownloadStatusT status,
505
                                  size_t dt,
506
                                  const size_t dn)
507
{
508
    UpdaterWindow *const uw = reinterpret_cast<UpdaterWindow *>(ptr);
509
    if (uw == nullptr)
510
        return -1;
511
512
    if (status == DownloadStatus::Complete)
513
    {
514
        uw->mDownloadComplete = true;
515
    }
516
    else if (status == DownloadStatus::Error ||
517
             status == DownloadStatus::Cancelled)
518
    {
519
        if (uw->mDownloadStatus == UpdateDownloadStatus::UPDATE_COMPLETE ||
520
            uw->mDownloadStatus == UpdateDownloadStatus::UPDATE_NEWS)
521
        {   // ignoring error in last state (was UPDATE_PATCH)
522
            uw->mDownloadStatus = UpdateDownloadStatus::UPDATE_COMPLETE;
523
            uw->mDownloadComplete = true;
524
            free(uw->mMemoryBuffer);
525
            uw->mMemoryBuffer = nullptr;
526
        }
527
        else
528
        {
529
            uw->mDownloadStatus = UpdateDownloadStatus::UPDATE_ERROR;
530
        }
531
    }
532
533
    if (dt == 0U)
534
        dt = 1;
535
536
    float progress = static_cast<float>(dn) /
537
                     static_cast<float>(dt);
538
539
    if (progress != progress)
540
        progress = 0.0F;  // check for NaN
541
    if (progress < 0.0F)
542
        progress = 0.0F;  // no idea how this could ever happen,
543
                          // but why not check for it anyway.
544
    if (progress > 1.0F)
545
        progress = 1.0F;
546
547
    uw->setLabel(std::string(uw->mCurrentFile).append(" (")
548
        .append(toString(CAST_S32(progress * 100))).append("%)"));
549
550
    uw->setProgress(progress);
551
552
    if ((client->getState() != State::UPDATE &&
553
        client->getState() != State::GAME) ||
554
        uw->mDownloadStatus == UpdateDownloadStatus::UPDATE_ERROR)
555
    {
556
        // If the action was canceled return an error code to stop the mThread
557
        return -1;
558
    }
559
560
    return 0;
561
}
562
563
size_t UpdaterWindow::memoryWrite(void *ptr, size_t size,
564
                                  size_t nmemb, void *stream)
565
{
566
    UpdaterWindow *const uw = reinterpret_cast<UpdaterWindow *>(stream);
567
    const size_t totalMem = size * nmemb;
568
    if (uw == nullptr)
569
        return 0;
570
    uw->mMemoryBuffer = static_cast<char*>(realloc(uw->mMemoryBuffer,
571
        CAST_SIZE(uw->mDownloadedBytes) + totalMem));
572
    if (uw->mMemoryBuffer != nullptr)
573
    {
574
        memcpy(&(uw->mMemoryBuffer[uw->mDownloadedBytes]), ptr, totalMem);
575
        uw->mDownloadedBytes += CAST_S32(totalMem);
576
    }
577
578
    return totalMem;
579
}
580
581
1
void UpdaterWindow::download()
582
{
583
1
    if (mDownload != nullptr)
584
    {
585
        mDownload->cancel();
586
        delete mDownload;
587
    }
588
1
    if (mDownloadStatus == UpdateDownloadStatus::UPDATE_PATCH)
589
    {
590
        mDownload = new Net::Download(this,
591
            branding.getStringValue("updateMirror1") + mCurrentFile,
592
            &updateProgress,
593
            true, false, mValidateXml);
594
        for (int f = 2; f < 8; f ++)
595
        {
596
            const std::string url = branding.getStringValue(
597
                "updateMirror" + toString(f));
598
            if (!url.empty())
599
                mDownload->addMirror(url + mCurrentFile);
600
        }
601
    }
602
    else
603
    {
604
1
        mDownload = new Net::Download(this,
605
4
            urlJoin(mUpdateHost, mCurrentFile),
606
            &updateProgress,
607

1
            false, false, mValidateXml);
608
609
1
        if (mDownloadStatus == UpdateDownloadStatus::UPDATE_LIST2 ||
610
            mDownloadStatus == UpdateDownloadStatus::UPDATE_RESOURCES2)
611
        {
612
            const std::string str = urlJoin(mUpdateServerPath, mCurrentFile);
613
            mDownload->addMirror(updateServer3 + str);
614
            mDownload->addMirror(updateServer4 + str);
615
            mDownload->addMirror(updateServer5 + str);
616
        }
617
        else
618
        {
619
1
            const STD_VECTOR<std::string> &mirrors = settings.updateMirrors;
620
4
            FOR_EACH (STD_VECTOR<std::string>::const_iterator, it, mirrors)
621
            {
622
                mDownload->addMirror(pathJoin(*it,
623
                    mCurrentFile));
624
            }
625
        }
626
    }
627
628
1
    if (mStoreInMemory)
629
    {
630
1
        mDownload->setWriteFunction(&UpdaterWindow::memoryWrite);
631
    }
632
    else
633
    {
634
        if (mDownloadStatus == UpdateDownloadStatus::UPDATE_RESOURCES)
635
        {
636
            mDownload->setFile(pathJoin(mUpdatesDir, mCurrentFile),
637
                mCurrentChecksum);
638
        }
639
        else
640
        {
641
            mDownload->setFile(pathJoin(mUpdatesDir,
642
                mCurrentFile),
643
                -1);
644
        }
645
    }
646
647
1
    if (mDownloadStatus != UpdateDownloadStatus::UPDATE_RESOURCES)
648
1
        mDownload->noCache();
649
650
2
    setLabel(mCurrentFile + " (0%)");
651
1
    mDownloadComplete = false;
652
653
1
    mDownload->start();
654
1
}
655
656
void UpdaterWindow::loadUpdates()
657
{
658
    if (mUpdateFiles.empty())
659
    {   // updates not downloaded
660
        mUpdateFiles = loadXMLFile(pathJoin(mUpdatesDir, xmlUpdateFile),
661
            false);
662
        if (mUpdateFiles.empty())
663
        {
664
            logger->log("Warning this server does not have a"
665
                        " %s file falling back to %s", xmlUpdateFile.c_str(),
666
                        txtUpdateFile.c_str());
667
            mUpdateFiles = loadTxtFile(pathJoin(mUpdatesDir,
668
                txtUpdateFile));
669
        }
670
    }
671
672
    std::string fixPath = mUpdatesDir + "/fix";
673
    const unsigned sz = CAST_U32(mUpdateFiles.size());
674
    for (mUpdateIndex = 0; mUpdateIndex < sz; mUpdateIndex++)
675
    {
676
        const UpdateFile &file = mUpdateFiles[mUpdateIndex];
677
        if (!file.group.empty())
678
            continue;
679
        UpdaterWindow::addUpdateFile(mUpdatesDir,
680
            fixPath,
681
            file.name,
682
            Append_false);
683
    }
684
    loadManaPlusUpdates(mUpdatesDir);
685
    loadMods(mUpdatesDir, mUpdateFiles);
686
}
687
688
void UpdaterWindow::loadLocalUpdates(const std::string &dir)
689
{
690
    STD_VECTOR<UpdateFile> updateFiles = loadXMLFile(
691
        pathJoin(dir, xmlUpdateFile),
692
        false);
693
694
    if (updateFiles.empty())
695
    {
696
        logger->log("Warning this server does not have a"
697
                    " %s file falling back to %s", xmlUpdateFile.c_str(),
698
                    txtUpdateFile.c_str());
699
        updateFiles = loadTxtFile(pathJoin(dir,
700
            txtUpdateFile));
701
    }
702
703
    const std::string fixPath = dir + "/fix";
704
    for (unsigned int updateIndex = 0,
705
         fsz = CAST_U32(updateFiles.size());
706
         updateIndex < fsz;
707
         updateIndex++)
708
    {
709
        const UpdateFile &file = updateFiles[updateIndex];
710
        if (!file.group.empty())
711
            continue;
712
        UpdaterWindow::addUpdateFile(dir,
713
            fixPath,
714
            file.name,
715
            Append_false);
716
    }
717
    loadManaPlusUpdates(dir);
718
    loadMods(dir, updateFiles);
719
}
720
721
void UpdaterWindow::unloadUpdates(const std::string &dir)
722
{
723
    STD_VECTOR<UpdateFile> updateFiles = loadXMLFile(
724
        pathJoin(dir, xmlUpdateFile),
725
        true);
726
727
    if (updateFiles.empty())
728
    {
729
        updateFiles = loadTxtFile(pathJoin(dir,
730
            txtUpdateFile));
731
    }
732
733
    const std::string fixPath = dir + "/fix";
734
    for (unsigned int updateIndex = 0,
735
         fsz = CAST_U32(updateFiles.size());
736
         updateIndex < fsz;
737
         updateIndex++)
738
    {
739
        UpdaterWindow::removeUpdateFile(dir,
740
            fixPath,
741
            updateFiles[updateIndex].name);
742
    }
743
    unloadManaPlusUpdates(dir);
744
}
745
746
void UpdaterWindow::loadManaPlusUpdates(const std::string &dir)
747
{
748
    std::string fixPath = dir + "/fix";
749
    STD_VECTOR<UpdateFile> updateFiles = loadXMLFile(
750
        pathJoin(fixPath, xmlUpdateFile),
751
        false);
752
753
    for (unsigned int updateIndex = 0,
754
         fsz = CAST_U32(updateFiles.size());
755
         updateIndex < fsz;
756
         updateIndex++)
757
    {
758
        const UpdateFile &file = updateFiles[updateIndex];
759
        if (!file.group.empty())
760
            continue;
761
        const std::string name = file.name;
762
        if (strStartWith(name, "manaplus_"))
763
        {
764
            struct stat statbuf;
765
            std::string fileName = pathJoin(fixPath,
766
                name);
767
            if (stat(fileName.c_str(), &statbuf) == 0)
768
            {
769
                VirtFs::mountZip(fileName,
770
                    Append_false);
771
            }
772
        }
773
    }
774
}
775
776
void UpdaterWindow::unloadManaPlusUpdates(const std::string &dir)
777
{
778
    const std::string fixPath = dir + "/fix";
779
    const STD_VECTOR<UpdateFile> updateFiles = loadXMLFile(
780
        pathJoin(fixPath, xmlUpdateFile),
781
        true);
782
783
    for (unsigned int updateIndex = 0,
784
         fsz = CAST_U32(updateFiles.size());
785
         updateIndex < fsz;
786
         updateIndex++)
787
    {
788
        std::string name = updateFiles[updateIndex].name;
789
        if (strStartWith(name, "manaplus_"))
790
        {
791
            struct stat statbuf;
792
            const std::string file = pathJoin(
793
                fixPath, name);
794
            if (stat(file.c_str(), &statbuf) == 0)
795
                VirtFs::unmountZip(file);
796
        }
797
    }
798
}
799
800
void UpdaterWindow::addUpdateFile(const std::string &restrict path,
801
                                  const std::string &restrict fixPath,
802
                                  const std::string &restrict file,
803
                                  const Append append)
804
{
805
    const std::string tmpPath = pathJoin(path, file);
806
    if (append == Append_false)
807
        VirtFs::mountZip(tmpPath, append);
808
809
    const std::string fixFile = pathJoin(fixPath, file);
810
    struct stat statbuf;
811
    if (stat(fixFile.c_str(), &statbuf) == 0)
812
        VirtFs::mountZip(fixFile, append);
813
814
    if (append == Append_true)
815
        VirtFs::mountZip(tmpPath, append);
816
}
817
818
void UpdaterWindow::removeUpdateFile(const std::string &restrict path,
819
                                     const std::string &restrict fixPath,
820
                                     const std::string &restrict file)
821
{
822
    VirtFs::unmountZip(pathJoin(path, file));
823
    const std::string fixFile = pathJoin(fixPath, file);
824
    struct stat statbuf;
825
    if (stat(fixFile.c_str(), &statbuf) == 0)
826
        VirtFs::unmountZip(fixFile);
827
}
828
829
void UpdaterWindow::logic()
830
{
831
    BLOCK_START("UpdaterWindow::logic")
832
    // Update Scroll logic
833
    mScrollArea->logic();
834
835
    // Synchronize label caption when necessary
836
    {
837
        MutexLocker lock(&mDownloadMutex);
838
839
        if (mLabel->getCaption() != mNewLabelCaption)
840
        {
841
            mLabel->setCaption(mNewLabelCaption);
842
            mLabel->adjustSize();
843
        }
844
845
        mProgressBar->setProgress(mDownloadProgress);
846
        if (!mUpdateFiles.empty() &&
847
            CAST_SIZE(mUpdateIndex) <= mUpdateFiles.size())
848
        {
849
            mProgressBar->setText(strprintf("%u/%u", mUpdateIndex
850
                + mUpdateIndexOffset + 1, CAST_U32(
851
                mUpdateFiles.size()) + CAST_S32(
852
                mTempUpdateFiles.size()) + 1));
853
        }
854
        else
855
        {
856
            mProgressBar->setText("");
857
        }
858
    }
859
860
    switch (mDownloadStatus)
861
    {
862
        case UpdateDownloadStatus::UPDATE_ERROR:
863
            mBrowserBox->addRow("", false);
864
            // TRANSLATORS: update message
865
            mBrowserBox->addRow(_("##1  The update process is incomplete."),
866
                false);
867
            // TRANSLATORS: Continues "The update process is incomplete.".
868
            mBrowserBox->addRow(_("##1  It is strongly recommended that"),
869
                false);
870
            // TRANSLATORS: Begins "It is strongly recommended that".
871
            mBrowserBox->addRow(_("##1  you try again later."),
872
                false);
873
            if (mDownload != nullptr)
874
                mBrowserBox->addRow(mDownload->getError(), false);
875
            mBrowserBox->updateHeight();
876
            mScrollArea->setVerticalScrollAmount(
877
                    mScrollArea->getVerticalMaxScroll());
878
            mDownloadStatus = UpdateDownloadStatus::UPDATE_COMPLETE;
879
            break;
880
        case UpdateDownloadStatus::UPDATE_NEWS:
881
            if (mDownloadComplete)
882
            {
883
                // Parse current memory buffer as news and dispose of the data
884
                loadNews();
885
886
                mValidateXml = true;
887
                mCurrentFile = xmlUpdateFile;
888
                mStoreInMemory = false;
889
                mDownloadStatus = UpdateDownloadStatus::UPDATE_LIST;
890
                download();  // download() changes mDownloadComplete to false
891
            }
892
            break;
893
        case UpdateDownloadStatus::UPDATE_PATCH:
894
            if (mDownloadComplete)
895
            {
896
                // Parse current memory buffer as news and dispose of the data
897
                loadPatch();
898
899
                mUpdateHost = updateServer2 + mUpdateServerPath;
900
                mUpdatesDir = pathJoin(mUpdatesDir, "fix");
901
                mCurrentFile = xmlUpdateFile;
902
                mValidateXml = true;
903
                mStoreInMemory = false;
904
                mDownloadStatus = UpdateDownloadStatus::UPDATE_LIST2;
905
                download();
906
            }
907
            break;
908
909
        case UpdateDownloadStatus::UPDATE_LIST:
910
            if (mDownloadComplete)
911
            {
912
                if (mCurrentFile == xmlUpdateFile)
913
                {
914
                    mUpdateFiles = loadXMLFile(pathJoin(
915
                        mUpdatesDir, xmlUpdateFile),
916
                        true);
917
918
                    if (mUpdateFiles.empty())
919
                    {
920
                        logger->log("Warning this server does not have a %s"
921
                                    " file falling back to %s",
922
                                    xmlUpdateFile.c_str(),
923
                                    txtUpdateFile.c_str());
924
925
                        // If the resources.xml file fails,
926
                        // fall back onto a older version
927
                        mCurrentFile = txtUpdateFile;
928
                        mValidateXml = false;
929
                        mStoreInMemory = false;
930
                        mDownloadStatus = UpdateDownloadStatus::UPDATE_LIST;
931
                        download();
932
                        break;
933
                    }
934
                }
935
                else if (mCurrentFile == txtUpdateFile)
936
                {
937
                    mValidateXml = true;
938
                    mUpdateFiles = loadTxtFile(pathJoin(mUpdatesDir,
939
                        txtUpdateFile));
940
                }
941
                mStoreInMemory = false;
942
                mDownloadStatus = UpdateDownloadStatus::UPDATE_RESOURCES;
943
            }
944
            break;
945
        case UpdateDownloadStatus::UPDATE_RESOURCES:
946
            if (mDownloadComplete)
947
            {
948
                if (CAST_SIZE(mUpdateIndex) < mUpdateFiles.size())
949
                {
950
                    UpdateFile thisFile = mUpdateFiles[mUpdateIndex];
951
                    if (thisFile.type == "music"
952
                        && !config.getBoolValue("download-music"))
953
                    {
954
                        mUpdateIndex++;
955
                        break;
956
                    }
957
                    mCurrentFile = thisFile.name;
958
                    std::string checksum;
959
                    checksum = thisFile.hash;
960
                    std::stringstream ss(checksum);
961
                    ss >> std::hex >> mCurrentChecksum;
962
963
                    std::ifstream temp(pathJoin(mUpdatesDir,
964
                        mCurrentFile).c_str());
965
966
                    mValidateXml = false;
967
                    if (!temp.is_open() || !validateFile(pathJoin(
968
                        mUpdatesDir, mCurrentFile),
969
                        mCurrentChecksum))
970
                    {
971
                        temp.close();
972
                        download();
973
                    }
974
                    else
975
                    {
976
                        temp.close();
977
                        logger->log("%s already here", mCurrentFile.c_str());
978
                    }
979
                    mUpdateIndex++;
980
                }
981
                else
982
                {
983
                    if (!mSkipPatches)
984
                    {
985
                        // Download of updates completed
986
                        mCurrentFile = "latest.txt";
987
                        mStoreInMemory = true;
988
                        mDownloadStatus = UpdateDownloadStatus::UPDATE_PATCH;
989
                        mValidateXml = false;
990
                        download();  // download() changes
991
                                     // mDownloadComplete to false
992
                    }
993
                    else
994
                    {
995
                        mDownloadStatus =
996
                            UpdateDownloadStatus::UPDATE_COMPLETE;
997
                    }
998
                }
999
            }
1000
            break;
1001
        case UpdateDownloadStatus::UPDATE_LIST2:
1002
            if (mDownloadComplete)
1003
            {
1004
                if (mCurrentFile == xmlUpdateFile)
1005
                {
1006
                    mTempUpdateFiles = loadXMLFile(pathJoin(
1007
                        mUpdatesDir, xmlUpdateFile),
1008
                        true);
1009
                }
1010
                mUpdateIndexOffset = mUpdateIndex;
1011
                mUpdateIndex = 0;
1012
                mValidateXml = true;
1013
                mStoreInMemory = false;
1014
                mDownloadStatus = UpdateDownloadStatus::UPDATE_RESOURCES2;
1015
                download();
1016
            }
1017
            break;
1018
        case UpdateDownloadStatus::UPDATE_RESOURCES2:
1019
            if (mDownloadComplete)
1020
            {
1021
                mValidateXml = false;
1022
                if (CAST_SIZE(mUpdateIndex)
1023
                    < mTempUpdateFiles.size())
1024
                {
1025
                    const UpdateFile thisFile = mTempUpdateFiles[mUpdateIndex];
1026
                    mCurrentFile = thisFile.name;
1027
                    std::string checksum;
1028
                    checksum = thisFile.hash;
1029
                    std::stringstream ss(checksum);
1030
                    ss >> std::hex >> mCurrentChecksum;
1031
1032
                    std::ifstream temp((pathJoin(mUpdatesDir,
1033
                        mCurrentFile)).c_str());
1034
1035
                    if (!temp.is_open() || !validateFile(pathJoin(
1036
                        mUpdatesDir, mCurrentFile),
1037
                        mCurrentChecksum))
1038
                    {
1039
                        temp.close();
1040
                        download();
1041
                    }
1042
                    else
1043
                    {
1044
                        temp.close();
1045
                        logger->log("%s already here", mCurrentFile.c_str());
1046
                    }
1047
                    mUpdateIndex++;
1048
                }
1049
                else
1050
                {
1051
                    mUpdatesDir = mUpdatesDirReal;
1052
                    mDownloadStatus = UpdateDownloadStatus::UPDATE_COMPLETE;
1053
                }
1054
            }
1055
            break;
1056
        case UpdateDownloadStatus::UPDATE_COMPLETE:
1057
            mUpdatesDir = mUpdatesDirReal;
1058
            enable();
1059
            // TRANSLATORS: updater window label
1060
            setLabel(_("Completed"));
1061
            mDownloadStatus = UpdateDownloadStatus::UPDATE_IDLE;
1062
            break;
1063
        case UpdateDownloadStatus::UPDATE_IDLE:
1064
            break;
1065
        default:
1066
            logger->log("UpdaterWindow::logic unknown status: "
1067
                        + toString(CAST_U32(mDownloadStatus)));
1068
            break;
1069
    }
1070
    BLOCK_END("UpdaterWindow::logic")
1071
}
1072
1073
bool UpdaterWindow::validateFile(const std::string &filePath,
1074
                                 const unsigned long hash)
1075
{
1076
    FILE *const file = fopen(filePath.c_str(), "rb");
1077
    if (file == nullptr)
1078
        return false;
1079
1080
    const unsigned long adler = Net::Download::fadler32(file);
1081
    fclose(file);
1082
    return adler == hash;
1083
}
1084
1085
unsigned long UpdaterWindow::getFileHash(const std::string &filePath)
1086
{
1087
    int size = 0;
1088
    const char *const buf = VirtFs::loadFile(filePath, size);
1089
    if (buf == nullptr)
1090
        return 0;
1091
    unsigned long res = Net::Download::adlerBuffer(buf, size);
1092
    delete [] buf;
1093
    return res;
1094
}
1095
1096
void UpdaterWindow::loadFile(std::string file)
1097
{
1098
    mBrowserBox->clearRows();
1099
    trim(file);
1100
1101
    StringVect lines;
1102
    Files::loadTextFileLocal(mUpdatesDir + "/local/help/news.txt", lines);
1103
1104
    for (size_t i = 0, sz = lines.size(); i < sz; ++i)
1105
        mBrowserBox->addRow(lines[i], false);
1106
    mBrowserBox->updateHeight();
1107
}
1108
1109
void UpdaterWindow::loadMods(const std::string &dir,
1110
                             const STD_VECTOR<UpdateFile> &updateFiles)
1111
{
1112
    ModDB::load();
1113
    std::string modsString = serverConfig.getValue("mods", "");
1114
    std::set<std::string> modsList;
1115
    splitToStringSet(modsList, modsString, '|');
1116
1117
    const std::string fixPath = dir + "/fix";
1118
    for (unsigned int updateIndex = 0,
1119
         fsz = CAST_U32(updateFiles.size());
1120
         updateIndex < fsz;
1121
         updateIndex ++)
1122
    {
1123
        const UpdateFile &file = updateFiles[updateIndex];
1124
        if (file.group.empty())
1125
            continue;
1126
        const std::set<std::string>::const_iterator
1127
            it = modsList.find(file.group);
1128
        if (it != modsList.end())
1129
        {
1130
            UpdaterWindow::addUpdateFile(dir,
1131
                fixPath,
1132
                file.name,
1133
                Append_false);
1134
        }
1135
    }
1136
1137
    STD_VECTOR<UpdateFile> updateFiles2 = loadXMLFile(
1138
        pathJoin(fixPath, xmlUpdateFile),
1139
        true);
1140
1141
    for (unsigned int updateIndex = 0,
1142
         fsz = CAST_U32(updateFiles2.size());
1143
         updateIndex < fsz;
1144
         updateIndex++)
1145
    {
1146
        const UpdateFile &file = updateFiles2[updateIndex];
1147
        if (file.group.empty())
1148
            continue;
1149
        std::string name = file.name;
1150
        if (strStartWith(name, "manaplus_"))
1151
        {
1152
            const std::set<std::string>::const_iterator
1153
                it = modsList.find(file.group);
1154
            if (it != modsList.end())
1155
            {
1156
                struct stat statbuf;
1157
                std::string fileName = pathJoin(fixPath,
1158
                    name);
1159
                if (stat(fileName.c_str(), &statbuf) == 0)
1160
                {
1161
                    VirtFs::mountZip(fileName,
1162
                        Append_false);
1163
                }
1164
            }
1165
        }
1166
    }
1167
1168
    loadDirMods(dir + "/local/");
1169
}
1170
1171
void UpdaterWindow::loadDirMods(const std::string &dir)
1172
{
1173
    ModDB::load();
1174
    const ModInfos &mods = ModDB::getAll();
1175
1176
    std::string modsString = serverConfig.getValue("mods", "");
1177
    StringVect modsList;
1178
    splitToStringVector(modsList, modsString, '|');
1179
    FOR_EACH (StringVectCIter, it, modsList)
1180
    {
1181
        const std::string &name = *it;
1182
        const ModInfoCIterator modIt = mods.find(name);
1183
        if (modIt == mods.end())
1184
            continue;
1185
        const ModInfo *const mod = (*modIt).second;
1186
        if (mod != nullptr)
1187
        {
1188
            const std::string &localDir = mod->getLocalDir();
1189
            if (!localDir.empty())
1190
            {
1191
                VirtFs::mountDir(pathJoin(dir, localDir),
1192
                    Append_false);
1193
            }
1194
        }
1195
    }
1196
}
1197
1198
void UpdaterWindow::unloadMods(const std::string &dir)
1199
{
1200
    const ModInfos &mods = ModDB::getAll();
1201
    std::string modsString = serverConfig.getValue("mods", "");
1202
    StringVect modsList;
1203
    splitToStringVector(modsList, modsString, '|');
1204
    FOR_EACH (StringVectCIter, it, modsList)
1205
    {
1206
        const std::string &name = *it;
1207
        const ModInfoCIterator modIt = mods.find(name);
1208
        if (modIt == mods.end())
1209
            continue;
1210
        const ModInfo *const mod = (*modIt).second;
1211
        if (mod != nullptr)
1212
        {
1213
            const std::string &localDir = mod->getLocalDir();
1214
            if (!localDir.empty())
1215
                VirtFs::unmountDir(pathJoin(dir, localDir));
1216
        }
1217
    }
1218
}
1219
1220
void UpdaterWindow::deleteSelf()
1221
{
1222
    scheduleDelete();
1223
    updaterWindow = nullptr;
1224

3
}