/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2012 The Mana Developers * * This file is part of The Mana Client. * * This program 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; either version 2 of the License, or * any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "gui/updaterwindow.h" #include "client.h" #include "configuration.h" #include "log.h" #include "gui/sdlinput.h" #include "gui/widgets/browserbox.h" #include "gui/widgets/button.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/progressbar.h" #include "gui/widgets/scrollarea.h" #include "gui/widgets/itemlinkhandler.h" #include "net/download.h" #include "resources/resourcemanager.h" #include "utils/gettext.h" #include "utils/stringutils.h" #include "utils/xml.h" #include #include constexpr char xmlUpdateFile[] = "resources.xml"; constexpr char txtUpdateFile[] = "resources2.txt"; /** * Load the given file into a vector of updateFiles. */ std::vector loadXMLFile(const std::string &fileName) { std::vector files; XML::Document doc(fileName, false); XML::Node rootNode = doc.rootNode(); if (!rootNode || rootNode.name() != "updates") { logger->log("Error loading update file: %s", fileName.c_str()); return files; } for (auto fileNode : rootNode.children()) { // Ignore all tags except for the "update" tags if (fileNode.name() != "update") continue; UpdateFile file; file.name = fileNode.getProperty("file", std::string()); file.hash = fileNode.getProperty("hash", std::string()); file.type = fileNode.getProperty("type", "data"); file.desc = fileNode.getProperty("description", std::string()); file.required = fileNode.getProperty("required", "yes") == "yes"; files.push_back(file); } return files; } std::vector loadTxtFile(const std::string &fileName) { std::vector files; std::ifstream fileHandler; fileHandler.open(fileName, std::ios::in); if (fileHandler.is_open()) { while (fileHandler.good()) { char name[256]; char hash[50]; fileHandler.getline(name, 256, ' '); fileHandler.getline(hash, 50); UpdateFile thisFile; thisFile.name = name; thisFile.hash = hash; thisFile.type = "data"; thisFile.required = true; if (!thisFile.name.empty()) files.push_back(thisFile); } } else { logger->log("Error loading update file: %s", fileName.c_str()); } fileHandler.close(); return files; } UpdaterWindow::UpdaterWindow(const std::string &updateHost, const std::string &updatesDir, bool applyUpdates): Window(_("Updating...")), mUpdateHost(updateHost), mUpdatesDir(updatesDir), mLoadUpdates(applyUpdates), mLinkHandler(std::make_unique(this)) { setWindowName("UpdaterWindow"); setResizable(true); setDefaultSize(450, 400, ImageRect::CENTER); setMinWidth(320); setMinHeight(240); mBrowserBox = new BrowserBox(BrowserBox::AUTO_WRAP); mScrollArea = new ScrollArea(mBrowserBox); mLabel = new Label(_("Connecting...")); mProgressBar = new ProgressBar(0.0, 310, 20); mCancelButton = new Button(_("Cancel"), "cancel", this); mPlayButton = new Button(_("Play"), "play", this); mBrowserBox->setLinkHandler(mLinkHandler.get()); mProgressBar->setSmoothProgress(false); mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); mPlayButton->setEnabled(false); place(0, 0, mScrollArea, 5, 3).setPadding(3); place(0, 3, mLabel, 5); place(0, 4, mProgressBar, 5); place(3, 5, mCancelButton); place(4, 5, mPlayButton); Layout &layout = getLayout(); layout.setRowHeight(0, Layout::AUTO_SET); addKeyListener(this); loadWindowState(); setVisible(true); mCancelButton->requestFocus(); startDownload("news.txt", true); } UpdaterWindow::~UpdaterWindow() { if (mLoadUpdates) loadUpdates(); } void UpdaterWindow::setLabel(const std::string &str) { mLabel->setCaption(str); mLabel->adjustSize(); } void UpdaterWindow::enablePlay() { mCancelButton->setEnabled(false); mPlayButton->setEnabled(true); mPlayButton->requestFocus(); } void UpdaterWindow::action(const gcn::ActionEvent &event) { if (event.getId() == "cancel") cancel(); else if (event.getId() == "play") play(); } void UpdaterWindow::keyPressed(gcn::KeyEvent &keyEvent) { gcn::Key key = keyEvent.getKey(); if (key.getValue() == Key::ESCAPE) { if (!cancel()) { mLoadUpdates = false; Client::setState(STATE_WORLD_SELECT); } } else if (key.getValue() == Key::ENTER) { play(); } } bool UpdaterWindow::cancel() { // Skip the updating process if (mDialogState != DialogState::Done) { mDownload->cancel(); return true; } return false; } void UpdaterWindow::play() { if (mPlayButton->isEnabled()) Client::setState(STATE_LOAD_DATA); } void UpdaterWindow::loadNews() { mBrowserBox->clearRows(); mBrowserBox->addRows(mDownload->getBuffer()); mScrollArea->setVerticalScrollAmount(0); } void UpdaterWindow::startDownload(const std::string &fileName, bool storeInMemory, std::optional adler32) { mDownload = std::make_unique(mUpdateHost + "/" + fileName); mCurrentFile = fileName; if (storeInMemory) mDownload->setUseBuffer(); else mDownload->setFile(mUpdatesDir + "/" + fileName, adler32); if (mDialogState != DialogState::DownloadResources) mDownload->noCache(); mDownload->start(); } void UpdaterWindow::loadUpdates() { if (mUpdateFiles.empty()) { // updates not downloaded mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); if (mUpdateFiles.empty()) { logger->log("Warning this server does not have a" " %s file falling back to %s", xmlUpdateFile, txtUpdateFile); mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); } } for (const UpdateFile &file : mUpdateFiles) ResourceManager::addToSearchPath(mUpdatesDir + "/" + file.name, false); } void UpdaterWindow::logic() { Window::logic(); if (mDialogState == DialogState::Done) return; const auto state = mDownload->getState(); float progress = 0.0f; switch (state.status) { case DownloadStatus::InProgress: { setLabel(mCurrentFile + " (" + toString((int) (state.progress * 100)) + "%)"); progress = state.progress; break; } case DownloadStatus::Canceled: mDialogState = DialogState::Done; enablePlay(); setLabel(_("Download canceled")); break; case DownloadStatus::Error: { mDialogState = DialogState::Done; std::string error = "##1"; error += mDownload->getError(); error += "\n\n##1"; error += _("The update process is incomplete. " "It is strongly recommended that you try again later."); mBrowserBox->addRows(error); int maxScroll = mScrollArea->getVerticalMaxScroll(); mScrollArea->setVerticalScrollAmount(maxScroll); enablePlay(); setLabel(_("Error while downloading")); break; } case DownloadStatus::Complete: downloadCompleted(); break; } mProgressBar->setProgress(progress); } void UpdaterWindow::downloadCompleted() { switch (mDialogState) { case DialogState::DownloadNews: loadNews(); mDialogState = DialogState::DownloadList; startDownload(xmlUpdateFile, false); break; case DialogState::DownloadList: if (mCurrentFile == xmlUpdateFile) { mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); if (mUpdateFiles.empty()) { logger->log("Warning this server does not have a %s" " file falling back to %s", xmlUpdateFile, txtUpdateFile); // If the resources.xml file fails, fall back onto a older version mDialogState = DialogState::DownloadList; startDownload(txtUpdateFile, false); break; } } else if (mCurrentFile == txtUpdateFile) { mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); } mDialogState = DialogState::DownloadResources; break; case DialogState::DownloadResources: if (mUpdateIndex < mUpdateFiles.size()) { const UpdateFile &thisFile = mUpdateFiles[mUpdateIndex]; if (!thisFile.required) { if (!(thisFile.type == "music" && config.downloadMusic)) { mUpdateIndex++; break; } } unsigned long checksum; std::stringstream ss(thisFile.hash); ss >> std::hex >> checksum; std::string filename = mUpdatesDir + "/" + thisFile.name; FILE *file = fopen(filename.c_str(), "r+b"); if (!file || Net::Download::fadler32(file) != checksum) { if (file) fclose(file); startDownload(thisFile.name, false, checksum); } else { fclose(file); logger->log("%s already here", thisFile.name.c_str()); } mUpdateIndex++; } else { // Download of updates completed mDialogState = DialogState::Done; enablePlay(); setLabel(_("Completed")); } break; case DialogState::Done: break; } }