749 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			749 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-3.0-only
 | |
| /*
 | |
|  *  PolyMC - Minecraft Launcher
 | |
|  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
 | |
|  *
 | |
|  *  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, version 3.
 | |
|  *
 | |
|  *  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 <https://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * This file incorporates work covered by the following copyright and
 | |
|  * permission notice:
 | |
|  *
 | |
|  *      Copyright 2013-2021 MultiMC Contributors
 | |
|  *
 | |
|  *      Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  *      you may not use this file except in compliance with the License.
 | |
|  *      You may obtain a copy of the License at
 | |
|  *
 | |
|  *          http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  *      Unless required by applicable law or agreed to in writing, software
 | |
|  *      distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  *      See the License for the specific language governing permissions and
 | |
|  *      limitations under the License.
 | |
|  */
 | |
| 
 | |
| #include "TranslationsModel.h"
 | |
| 
 | |
| #include <QCoreApplication>
 | |
| #include <QTranslator>
 | |
| #include <QLocale>
 | |
| #include <QDir>
 | |
| #include <QLibraryInfo>
 | |
| #include <QDebug>
 | |
| 
 | |
| #include "FileSystem.h"
 | |
| #include "net/NetJob.h"
 | |
| #include "net/ChecksumValidator.h"
 | |
| #include "BuildConfig.h"
 | |
| #include "Json.h"
 | |
| 
 | |
| #include "POTranslator.h"
 | |
| 
 | |
| #include "Application.h"
 | |
| 
 | |
| const static QLatin1Literal defaultLangCode("en_US");
 | |
| 
 | |
| enum class FileType
 | |
| {
 | |
|     NONE,
 | |
|     QM,
 | |
|     PO
 | |
| };
 | |
| 
 | |
| struct Language
 | |
| {
 | |
|     Language()
 | |
|     {
 | |
|         updated = true;
 | |
|     }
 | |
|     Language(const QString & _key)
 | |
|     {
 | |
|         key = _key;
 | |
|         locale = QLocale(key);
 | |
|         updated = (key == defaultLangCode);
 | |
|     }
 | |
| 
 | |
|     QString languageName() const {
 | |
|         QString result;
 | |
|         if(key == "ja_KANJI") {
 | |
|             result = locale.nativeLanguageName() + u8" (漢字)";
 | |
|         }
 | |
|         else if(key == "es_UY") {
 | |
|             result = u8"español de Latinoamérica";
 | |
|         }
 | |
|         else {
 | |
|             result = locale.nativeLanguageName();
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     float percentTranslated() const
 | |
|     {
 | |
|         if (total == 0)
 | |
|         {
 | |
|             return 100.0f;
 | |
|         }
 | |
|         return 100.0f * float(translated) / float(total);
 | |
|     }
 | |
| 
 | |
|     void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy)
 | |
|     {
 | |
|         translated = _translated;
 | |
|         untranslated = _untranslated;
 | |
|         fuzzy = _fuzzy;
 | |
|         total = translated + untranslated + fuzzy;
 | |
|     }
 | |
| 
 | |
|     bool isOfSameNameAs(const Language& other) const
 | |
|     {
 | |
|         return key == other.key;
 | |
|     }
 | |
| 
 | |
|     bool isIdenticalTo(const Language& other) const
 | |
|     {
 | |
|         return
 | |
|         (
 | |
|             key == other.key &&
 | |
|             file_name == other.file_name &&
 | |
|             file_size == other.file_size &&
 | |
|             file_sha1 == other.file_sha1 &&
 | |
|             translated == other.translated &&
 | |
|             fuzzy == other.fuzzy &&
 | |
|             total == other.fuzzy &&
 | |
|             localFileType == other.localFileType
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     Language & apply(Language & other)
 | |
|     {
 | |
|         if(!isOfSameNameAs(other))
 | |
|         {
 | |
|             return *this;
 | |
|         }
 | |
|         file_name = other.file_name;
 | |
|         file_size = other.file_size;
 | |
|         file_sha1 = other.file_sha1;
 | |
|         translated = other.translated;
 | |
|         fuzzy = other.fuzzy;
 | |
|         total = other.total;
 | |
|         localFileType = other.localFileType;
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     QString key;
 | |
|     QLocale locale;
 | |
|     bool updated;
 | |
| 
 | |
|     QString file_name = QString();
 | |
|     std::size_t file_size = 0;
 | |
|     QString file_sha1 = QString();
 | |
| 
 | |
|     unsigned translated = 0;
 | |
|     unsigned untranslated = 0;
 | |
|     unsigned fuzzy = 0;
 | |
|     unsigned total = 0;
 | |
| 
 | |
|     FileType localFileType = FileType::NONE;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| struct TranslationsModel::Private
 | |
| {
 | |
|     QDir m_dir;
 | |
| 
 | |
|     // initial state is just english
 | |
|     QVector<Language> m_languages = {Language (defaultLangCode)};
 | |
| 
 | |
|     QString m_selectedLanguage = defaultLangCode;
 | |
|     std::unique_ptr<QTranslator> m_qt_translator;
 | |
|     std::unique_ptr<QTranslator> m_app_translator;
 | |
| 
 | |
|     Net::Download::Ptr m_index_task;
 | |
|     QString m_downloadingTranslation;
 | |
|     NetJob::Ptr m_dl_job;
 | |
|     NetJob::Ptr m_index_job;
 | |
|     QString m_nextDownload;
 | |
| 
 | |
|     std::unique_ptr<POTranslator> m_po_translator;
 | |
|     QFileSystemWatcher *watcher;
 | |
| 
 | |
|     const QString m_system_locale = QLocale::system().name();
 | |
|     const QString m_system_language = m_system_locale.split('_').front();
 | |
| 
 | |
|     bool no_language_set = false;
 | |
| };
 | |
| 
 | |
| TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent)
 | |
| {
 | |
|     d.reset(new Private);
 | |
|     d->m_dir.setPath(path);
 | |
|     FS::ensureFolderPathExists(path);
 | |
|     reloadLocalFiles();
 | |
| 
 | |
|     d->watcher = new QFileSystemWatcher(this);
 | |
|     connect(d->watcher, &QFileSystemWatcher::directoryChanged, this, &TranslationsModel::translationDirChanged);
 | |
|     d->watcher->addPath(d->m_dir.canonicalPath());
 | |
| }
 | |
| 
 | |
| TranslationsModel::~TranslationsModel()
 | |
| {
 | |
| }
 | |
| 
 | |
| void TranslationsModel::translationDirChanged(const QString& path)
 | |
| {
 | |
|     qDebug() << "Dir changed:" << path;
 | |
|     if (!d->no_language_set)
 | |
|     {
 | |
|         reloadLocalFiles();
 | |
|     }
 | |
|     selectLanguage(selectedLanguage());
 | |
| }
 | |
| 
 | |
| void TranslationsModel::indexReceived()
 | |
| {
 | |
|     qDebug() << "Got translations index!";
 | |
|     d->m_index_job.reset();
 | |
| 
 | |
|     if (d->no_language_set)
 | |
|     {
 | |
|         reloadLocalFiles();
 | |
| 
 | |
|         auto language = d->m_system_locale;
 | |
|         if (!findLanguage(language))
 | |
|         {
 | |
|             language = d->m_system_language;
 | |
|         }
 | |
|         selectLanguage(language);
 | |
|         if (selectedLanguage() != defaultLangCode)
 | |
|         {
 | |
|             updateLanguage(selectedLanguage());
 | |
|         }
 | |
|         APPLICATION->settings()->set("Language", selectedLanguage());
 | |
|         d->no_language_set = false;
 | |
|     }
 | |
| 
 | |
|     else if(d->m_selectedLanguage != defaultLangCode)
 | |
|     {
 | |
|         downloadTranslation(d->m_selectedLanguage);
 | |
|     }
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| void readIndex(const QString & path, QMap<QString, Language>& languages)
 | |
| {
 | |
|     QByteArray data;
 | |
|     try
 | |
|     {
 | |
|         data = FS::read(path);
 | |
|     }
 | |
|     catch (const Exception &e)
 | |
|     {
 | |
|         qCritical() << "Translations Download Failed: index file not readable";
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     int index = 1;
 | |
|     try
 | |
|     {
 | |
|         auto toplevel_doc = Json::requireDocument(data);
 | |
|         auto doc = Json::requireObject(toplevel_doc);
 | |
|         auto file_type = Json::requireString(doc, "file_type");
 | |
|         if(file_type != "MMC-TRANSLATION-INDEX")
 | |
|         {
 | |
|             qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type;
 | |
|             return;
 | |
|         }
 | |
|         auto version = Json::requireInteger(doc, "version");
 | |
|         if(version > 2)
 | |
|         {
 | |
|             qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type;
 | |
|             return;
 | |
|         }
 | |
|         auto langObjs = Json::requireObject(doc, "languages");
 | |
|         for(auto iter = langObjs.begin(); iter != langObjs.end(); iter++)
 | |
|         {
 | |
|             Language lang(iter.key());
 | |
| 
 | |
|             auto langObj =  Json::requireObject(iter.value());
 | |
|             lang.setTranslationStats(
 | |
|                 Json::ensureInteger(langObj, "translated", 0),
 | |
|                 Json::ensureInteger(langObj, "untranslated", 0),
 | |
|                 Json::ensureInteger(langObj, "fuzzy", 0)
 | |
|             );
 | |
|             lang.file_name = Json::requireString(langObj, "file");
 | |
|             lang.file_sha1 = Json::requireString(langObj, "sha1");
 | |
|             lang.file_size = Json::requireInteger(langObj, "size");
 | |
| 
 | |
|             languages.insert(lang.key, lang);
 | |
|             index++;
 | |
|         }
 | |
|     }
 | |
|     catch (Json::JsonException & e)
 | |
|     {
 | |
|         qCritical() << "Translations Download Failed: index file could not be parsed as json";
 | |
|     }
 | |
| }
 | |
| }
 | |
| 
 | |
| void TranslationsModel::reloadLocalFiles()
 | |
| {
 | |
|     QMap<QString, Language> languages = {{defaultLangCode, Language(defaultLangCode)}};
 | |
| 
 | |
|     readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages);
 | |
|     auto entries = d->m_dir.entryInfoList({"mmc_*.qm", "*.po"}, QDir::Files | QDir::NoDotAndDotDot);
 | |
|     for(auto & entry: entries)
 | |
|     {
 | |
|         auto completeSuffix = entry.completeSuffix();
 | |
|         QString langCode;
 | |
|         FileType fileType = FileType::NONE;
 | |
|         if(completeSuffix == "qm")
 | |
|         {
 | |
|             langCode = entry.baseName().remove(0,4);
 | |
|             fileType = FileType::QM;
 | |
|         }
 | |
|         else if(completeSuffix == "po")
 | |
|         {
 | |
|             langCode = entry.baseName();
 | |
|             fileType = FileType::PO;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         auto langIter = languages.find(langCode);
 | |
|         if(langIter != languages.end())
 | |
|         {
 | |
|             auto & language = *langIter;
 | |
|             if(int(fileType) > int(language.localFileType))
 | |
|             {
 | |
|                 language.localFileType = fileType;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             if(fileType == FileType::PO)
 | |
|             {
 | |
|                 Language localFound(langCode);
 | |
|                 localFound.localFileType = FileType::PO;
 | |
|                 languages.insert(langCode, localFound);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // changed and removed languages
 | |
|     for(auto iter = d->m_languages.begin(); iter != d->m_languages.end();)
 | |
|     {
 | |
|         auto &language = *iter;
 | |
|         auto row = iter - d->m_languages.begin();
 | |
| 
 | |
|         auto updatedLanguageIter = languages.find(language.key);
 | |
|         if(updatedLanguageIter != languages.end())
 | |
|         {
 | |
|             if(language.isIdenticalTo(*updatedLanguageIter))
 | |
|             {
 | |
|                 languages.remove(language.key);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 language.apply(*updatedLanguageIter);
 | |
|                 emit dataChanged(index(row), index(row));
 | |
|                 languages.remove(language.key);
 | |
|             }
 | |
|             iter++;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             beginRemoveRows(QModelIndex(), row, row);
 | |
|             iter = d->m_languages.erase(iter);
 | |
|             endRemoveRows();
 | |
|         }
 | |
|     }
 | |
|     // added languages
 | |
|     if(languages.isEmpty())
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     beginInsertRows(QModelIndex(), 0, d->m_languages.size() + languages.size() - 1);
 | |
|     for(auto & language: languages)
 | |
|     {
 | |
|         d->m_languages.append(language);
 | |
|     }
 | |
|     std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) {
 | |
|         if (a.key != b.key)
 | |
|         {
 | |
|             if (a.key == d->m_system_locale || a.key == d->m_system_language)
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|             if (b.key == d->m_system_locale || b.key == d->m_system_language)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         return a.key < b.key;
 | |
|     });
 | |
|     endInsertRows();
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| enum class Column
 | |
| {
 | |
|     Language,
 | |
|     Completeness
 | |
| };
 | |
| }
 | |
| 
 | |
| 
 | |
| QVariant TranslationsModel::data(const QModelIndex& index, int role) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|         return QVariant();
 | |
| 
 | |
|     int row = index.row();
 | |
|     auto column = static_cast<Column>(index.column());
 | |
| 
 | |
|     if (row < 0 || row >= d->m_languages.size())
 | |
|         return QVariant();
 | |
| 
 | |
|     auto & lang = d->m_languages[row];
 | |
|     switch (role)
 | |
|     {
 | |
|     case Qt::DisplayRole:
 | |
|     {
 | |
|         switch(column)
 | |
|         {
 | |
|             case Column::Language:
 | |
|             {
 | |
|                 return lang.languageName();
 | |
|             }
 | |
|             case Column::Completeness:
 | |
|             {
 | |
|                 QString text;
 | |
|                 text.sprintf("%3.1f %%", lang.percentTranslated());
 | |
|                 return text;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     case Qt::ToolTipRole:
 | |
|     {
 | |
|         return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total));
 | |
|     }
 | |
|     case Qt::UserRole:
 | |
|         return lang.key;
 | |
|     default:
 | |
|         return QVariant();
 | |
|     }
 | |
| }
 | |
| 
 | |
| QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const
 | |
| {
 | |
|     auto column = static_cast<Column>(section);
 | |
|     if(role == Qt::DisplayRole)
 | |
|     {
 | |
|         switch(column)
 | |
|         {
 | |
|             case Column::Language:
 | |
|             {
 | |
|                 return tr("Language");
 | |
|             }
 | |
|             case Column::Completeness:
 | |
|             {
 | |
|                 return tr("Completeness");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else if(role == Qt::ToolTipRole)
 | |
|     {
 | |
|         switch(column)
 | |
|         {
 | |
|             case Column::Language:
 | |
|             {
 | |
|                 return tr("The native language name.");
 | |
|             }
 | |
|             case Column::Completeness:
 | |
|             {
 | |
|                 return tr("Completeness is the percentage of fully translated strings, not counting automatically guessed ones.");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return QAbstractListModel::headerData(section, orientation, role);
 | |
| }
 | |
| 
 | |
| int TranslationsModel::rowCount(const QModelIndex& parent) const
 | |
| {
 | |
|     return d->m_languages.size();
 | |
| }
 | |
| 
 | |
| int TranslationsModel::columnCount(const QModelIndex& parent) const
 | |
| {
 | |
|     return 2;
 | |
| }
 | |
| 
 | |
| Language * TranslationsModel::findLanguage(const QString& key)
 | |
| {
 | |
|     auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang)
 | |
|     {
 | |
|         return lang.key == key;
 | |
|     });
 | |
|     if(found == d->m_languages.end())
 | |
|     {
 | |
|         return nullptr;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return found;
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool TranslationsModel::selectLanguage(QString key)
 | |
| {
 | |
|     QString &langCode = key;
 | |
|     auto langPtr = findLanguage(key);
 | |
| 
 | |
|     if (langCode.isEmpty())
 | |
|     {
 | |
|         d->no_language_set = true;
 | |
|     }
 | |
| 
 | |
|     if(!langPtr)
 | |
|     {
 | |
|         qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
 | |
|         langCode = defaultLangCode;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         langCode = langPtr->key;
 | |
|     }
 | |
| 
 | |
|     // uninstall existing translators if there are any
 | |
|     if (d->m_app_translator)
 | |
|     {
 | |
|         QCoreApplication::removeTranslator(d->m_app_translator.get());
 | |
|         d->m_app_translator.reset();
 | |
|     }
 | |
|     if (d->m_qt_translator)
 | |
|     {
 | |
|         QCoreApplication::removeTranslator(d->m_qt_translator.get());
 | |
|         d->m_qt_translator.reset();
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * FIXME: potential source of crashes:
 | |
|      * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created.
 | |
|      * This function is not reentrant.
 | |
|      */
 | |
|     QLocale locale = QLocale(langCode);
 | |
|     QLocale::setDefault(locale);
 | |
| 
 | |
|     // if it's the default UI language, finish
 | |
|     if(langCode == defaultLangCode)
 | |
|     {
 | |
|         d->m_selectedLanguage = langCode;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     // otherwise install new translations
 | |
|     bool successful = false;
 | |
|     // FIXME: this is likely never present. FIX IT.
 | |
|     d->m_qt_translator.reset(new QTranslator());
 | |
|     if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
 | |
|     {
 | |
|         qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "...";
 | |
|         if (!QCoreApplication::installTranslator(d->m_qt_translator.get()))
 | |
|         {
 | |
|             qCritical() << "Loading Qt Language File failed.";
 | |
|             d->m_qt_translator.reset();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             successful = true;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         d->m_qt_translator.reset();
 | |
|     }
 | |
| 
 | |
|     if(langPtr->localFileType == FileType::PO)
 | |
|     {
 | |
|         qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
 | |
|         auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po"));
 | |
|         if(!poTranslator->isEmpty())
 | |
|         {
 | |
|             if (!QCoreApplication::installTranslator(poTranslator))
 | |
|             {
 | |
|                 delete poTranslator;
 | |
|                 qCritical() << "Installing Application Language File failed.";
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 d->m_app_translator.reset(poTranslator);
 | |
|                 successful = true;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             qCritical() << "Loading Application Language File failed.";
 | |
|             d->m_app_translator.reset();
 | |
|         }
 | |
|     }
 | |
|     else if(langPtr->localFileType == FileType::QM)
 | |
|     {
 | |
|         d->m_app_translator.reset(new QTranslator());
 | |
|         if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path()))
 | |
|         {
 | |
|             qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
 | |
|             if (!QCoreApplication::installTranslator(d->m_app_translator.get()))
 | |
|             {
 | |
|                 qCritical() << "Installing Application Language File failed.";
 | |
|                 d->m_app_translator.reset();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 successful = true;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             d->m_app_translator.reset();
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         d->m_app_translator.reset();
 | |
|     }
 | |
|     d->m_selectedLanguage = langCode;
 | |
|     return successful;
 | |
| }
 | |
| 
 | |
| QModelIndex TranslationsModel::selectedIndex()
 | |
| {
 | |
|     auto found = findLanguage(d->m_selectedLanguage);
 | |
|     if(found)
 | |
|     {
 | |
|         // QVector iterator freely converts to pointer to contained type
 | |
|         return index(found - d->m_languages.begin(), 0, QModelIndex());
 | |
|     }
 | |
|     return QModelIndex();
 | |
| }
 | |
| 
 | |
| QString TranslationsModel::selectedLanguage()
 | |
| {
 | |
|     return d->m_selectedLanguage;
 | |
| }
 | |
| 
 | |
| void TranslationsModel::downloadIndex()
 | |
| {
 | |
|     if(d->m_index_job || d->m_dl_job)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     qDebug() << "Downloading Translations Index...";
 | |
|     d->m_index_job = new NetJob("Translations Index", APPLICATION->network());
 | |
|     MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json");
 | |
|     entry->setStale(true);
 | |
|     d->m_index_task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry);
 | |
|     d->m_index_job->addNetAction(d->m_index_task);
 | |
|     connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed);
 | |
|     connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived);
 | |
|     d->m_index_job->start();
 | |
| }
 | |
| 
 | |
| void TranslationsModel::updateLanguage(QString key)
 | |
| {
 | |
|     if(key == defaultLangCode)
 | |
|     {
 | |
|         qWarning() << "Cannot update builtin language" << key;
 | |
|         return;
 | |
|     }
 | |
|     auto found = findLanguage(key);
 | |
|     if(!found)
 | |
|     {
 | |
|         qWarning() << "Cannot update invalid language" << key;
 | |
|         return;
 | |
|     }
 | |
|     if(!found->updated)
 | |
|     {
 | |
|         downloadTranslation(key);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void TranslationsModel::downloadTranslation(QString key)
 | |
| {
 | |
|     if(d->m_dl_job)
 | |
|     {
 | |
|         d->m_nextDownload = key;
 | |
|         return;
 | |
|     }
 | |
|     auto lang = findLanguage(key);
 | |
|     if(!lang)
 | |
|     {
 | |
|         qWarning() << "Will not download an unknown translation" << key;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     d->m_downloadingTranslation = key;
 | |
|     MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm");
 | |
|     entry->setStale(true);
 | |
| 
 | |
|     auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
 | |
|     auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1());
 | |
|     dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
 | |
|     dl->setProgress(dl->getProgress(), lang->file_size);
 | |
| 
 | |
|     d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network());
 | |
|     d->m_dl_job->addNetAction(dl);
 | |
| 
 | |
|     connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood);
 | |
|     connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed);
 | |
| 
 | |
|     d->m_dl_job->start();
 | |
| }
 | |
| 
 | |
| void TranslationsModel::downloadNext()
 | |
| {
 | |
|     if(!d->m_nextDownload.isEmpty())
 | |
|     {
 | |
|         downloadTranslation(d->m_nextDownload);
 | |
|         d->m_nextDownload.clear();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void TranslationsModel::dlFailed(QString reason)
 | |
| {
 | |
|     qCritical() << "Translations Download Failed:" << reason;
 | |
|     d->m_dl_job.reset();
 | |
|     downloadNext();
 | |
| }
 | |
| 
 | |
| void TranslationsModel::dlGood()
 | |
| {
 | |
|     qDebug() << "Got translation:" << d->m_downloadingTranslation;
 | |
| 
 | |
|     if(d->m_downloadingTranslation == d->m_selectedLanguage)
 | |
|     {
 | |
|         selectLanguage(d->m_selectedLanguage);
 | |
|     }
 | |
|     d->m_dl_job.reset();
 | |
|     downloadNext();
 | |
| }
 | |
| 
 | |
| void TranslationsModel::indexFailed(QString reason)
 | |
| {
 | |
|     qCritical() << "Translations Index Download Failed:" << reason;
 | |
|     d->m_index_job.reset();
 | |
| }
 |