From 722896d41f15a8bc78a864f7adcfd0527648073c Mon Sep 17 00:00:00 2001
From: Petr Mrázek <peterix@gmail.com>
Date: Mon, 5 Dec 2016 02:29:08 +0100
Subject: NOISSUE Translations model and initial setup wizard work

---
 api/logic/CMakeLists.txt                     |   5 +-
 api/logic/trans/TranslationDownloader.cpp    |  51 -----
 api/logic/trans/TranslationDownloader.h      |  34 ---
 api/logic/translations/TranslationsModel.cpp | 315 +++++++++++++++++++++++++++
 api/logic/translations/TranslationsModel.h   |  61 ++++++
 5 files changed, 378 insertions(+), 88 deletions(-)
 delete mode 100644 api/logic/trans/TranslationDownloader.cpp
 delete mode 100644 api/logic/trans/TranslationDownloader.h
 create mode 100644 api/logic/translations/TranslationsModel.cpp
 create mode 100644 api/logic/translations/TranslationsModel.h

(limited to 'api/logic')

diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
index 02e9afbe..ffecb073 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -412,9 +412,8 @@ add_unit_test(JavaVersion
 	)
 
 set(TRANSLATIONS_SOURCES
-	# Translations
-	trans/TranslationDownloader.h
-	trans/TranslationDownloader.cpp
+	translations/TranslationsModel.h
+	translations/TranslationsModel.cpp
 )
 
 set(TOOLS_SOURCES
diff --git a/api/logic/trans/TranslationDownloader.cpp b/api/logic/trans/TranslationDownloader.cpp
deleted file mode 100644
index 61e24c9a..00000000
--- a/api/logic/trans/TranslationDownloader.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "TranslationDownloader.h"
-#include "net/NetJob.h"
-#include "net/Download.h"
-#include "net/URLConstants.h"
-#include "Env.h"
-#include <QDebug>
-
-TranslationDownloader::TranslationDownloader()
-{
-}
-void TranslationDownloader::downloadTranslations()
-{
-	qDebug() << "Downloading Translations Index...";
-	m_index_job.reset(new NetJob("Translations Index"));
-	m_index_task = Net::Download::makeByteArray(QUrl("http://files.multimc.org/translations/index"), &m_data);
-	m_index_job->addNetAction(m_index_task);
-	connect(m_index_job.get(), &NetJob::failed, this, &TranslationDownloader::indexFailed);
-	connect(m_index_job.get(), &NetJob::succeeded, this, &TranslationDownloader::indexRecieved);
-	m_index_job->start();
-}
-void TranslationDownloader::indexRecieved()
-{
-	qDebug() << "Got translations index!";
-	m_dl_job.reset(new NetJob("Translations"));
-	QList<QByteArray> lines = m_data.split('\n');
-	m_data.clear();
-	for (const auto line : lines)
-	{
-		if (!line.isEmpty())
-		{
-			MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + line);
-			entry->setStale(true);
-			m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + line), entry));
-		}
-	}
-	connect(m_dl_job.get(), &NetJob::succeeded, this, &TranslationDownloader::dlGood);
-	connect(m_dl_job.get(), &NetJob::failed, this, &TranslationDownloader::dlFailed);
-	m_dl_job->start();
-}
-void TranslationDownloader::dlFailed(QString reason)
-{
-	qCritical() << "Translations Download Failed:" << reason;
-}
-void TranslationDownloader::dlGood()
-{
-	qDebug() << "Got translations!";
-}
-void TranslationDownloader::indexFailed(QString reason)
-{
-	qCritical() << "Translations Index Download Failed:" << reason;
-}
diff --git a/api/logic/trans/TranslationDownloader.h b/api/logic/trans/TranslationDownloader.h
deleted file mode 100644
index ad3a648d..00000000
--- a/api/logic/trans/TranslationDownloader.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include <QList>
-#include <QUrl>
-#include <memory>
-#include <QObject>
-#include <net/NetJob.h>
-#include "multimc_logic_export.h"
-namespace Net{
-class Download;
-}
-class NetJob;
-
-class MULTIMC_LOGIC_EXPORT TranslationDownloader : public QObject
-{
-	Q_OBJECT
-
-public:
-	TranslationDownloader();
-
-	void downloadTranslations();
-
-private slots:
-	void indexRecieved();
-	void indexFailed(QString reason);
-	void dlFailed(QString reason);
-	void dlGood();
-
-private:
-	std::shared_ptr<Net::Download> m_index_task;
-	NetJobPtr m_dl_job;
-	NetJobPtr m_index_job;
-	QByteArray m_data;
-};
diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp
new file mode 100644
index 00000000..1d44484a
--- /dev/null
+++ b/api/logic/translations/TranslationsModel.cpp
@@ -0,0 +1,315 @@
+#include "TranslationsModel.h"
+
+#include <QCoreApplication>
+#include <QTranslator>
+#include <QLocale>
+#include <QDir>
+#include <QLibraryInfo>
+#include <QDebug>
+#include <FileSystem.h>
+#include <net/NetJob.h>
+#include <Env.h>
+#include <net/URLConstants.h>
+
+const static QLatin1Literal defaultLangCode("en");
+
+struct Language
+{
+	QString key;
+	QLocale locale;
+	bool updated;
+};
+
+struct TranslationsModel::Private
+{
+	QDir m_dir;
+
+	// initial state is just english
+	QVector<Language> m_languages = {{defaultLangCode, QLocale(defaultLangCode), false}};
+	QString m_selectedLanguage = defaultLangCode;
+	std::unique_ptr<QTranslator> m_qt_translator;
+	std::unique_ptr<QTranslator> m_app_translator;
+
+	std::shared_ptr<Net::Download> m_index_task;
+	QString m_downloadingTranslation;
+	NetJobPtr m_dl_job;
+	NetJobPtr m_index_job;
+	QString m_nextDownload;
+};
+
+TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent)
+{
+	d.reset(new Private);
+	d->m_dir.setPath(path);
+	loadLocalIndex();
+}
+
+TranslationsModel::~TranslationsModel()
+{
+}
+
+QVariant TranslationsModel::data(const QModelIndex& index, int role) const
+{
+	if (!index.isValid())
+		return QVariant();
+
+	int row = index.row();
+
+	if (row < 0 || row >= d->m_languages.size())
+		return QVariant();
+
+	switch (role)
+	{
+	case Qt::DisplayRole:
+		return d->m_languages[row].locale.nativeLanguageName();
+	case Qt::UserRole:
+		return d->m_languages[row].key;
+	default:
+		return QVariant();
+	}
+}
+
+int TranslationsModel::rowCount(const QModelIndex& parent) const
+{
+	return d->m_languages.size();
+}
+
+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(!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(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();
+	}
+
+	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() << "Loading Application Language File failed.";
+			d->m_app_translator.reset();
+		}
+		else
+		{
+			successful = true;
+		}
+	}
+	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()
+{
+	qDebug() << "Downloading Translations Index...";
+	d->m_index_job.reset(new NetJob("Translations Index"));
+	MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index");
+	d->m_index_task = Net::Download::makeCached(QUrl("http://files.multimc.org/translations/index"), 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::indexRecieved);
+	d->m_index_job->start();
+}
+
+void TranslationsModel::indexRecieved()
+{
+	qDebug() << "Got translations index!";
+	d->m_index_job.reset();
+	loadLocalIndex();
+	if(d->m_selectedLanguage != defaultLangCode)
+	{
+		downloadTranslation(d->m_selectedLanguage);
+	}
+}
+
+void TranslationsModel::loadLocalIndex()
+{
+	QByteArray data;
+	try
+	{
+		data = FS::read(d->m_dir.absoluteFilePath("index"));
+	}
+	catch (Exception &e)
+	{
+		qCritical() << "Translations Download Failed: index file not readable";
+		return;
+	}
+	QVector<Language> languages;
+	QList<QByteArray> lines = data.split('\n');
+	// add the default english.
+	languages.append({defaultLangCode, QLocale(defaultLangCode), true});
+	for (const auto line : lines)
+	{
+		if(!line.isEmpty())
+		{
+			auto str = QString::fromLatin1(line);
+			str.remove(".qm");
+			languages.append({str, QLocale(str), false});
+		}
+	}
+	beginResetModel();
+	d->m_languages.swap(languages);
+	endResetModel();
+}
+
+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;
+	}
+	d->m_downloadingTranslation = key;
+	MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm");
+	entry->setStale(true);
+	d->m_dl_job.reset(new NetJob("Translation for " + key));
+	d->m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + key + ".qm"), entry));
+	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();
+}
diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h
new file mode 100644
index 00000000..90184b79
--- /dev/null
+++ b/api/logic/translations/TranslationsModel.h
@@ -0,0 +1,61 @@
+/* Copyright 2013-2016 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.
+ */
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <memory>
+#include "multimc_logic_export.h"
+
+struct Language;
+
+class MULTIMC_LOGIC_EXPORT TranslationsModel : public QAbstractListModel
+{
+	Q_OBJECT
+public:
+	explicit TranslationsModel(QString path, QObject *parent = 0);
+	virtual ~TranslationsModel();
+
+	virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+	virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+	bool selectLanguage(QString key);
+	void updateLanguage(QString key);
+	QModelIndex selectedIndex();
+	QString selectedLanguage();
+
+	void downloadIndex();
+
+private:
+	Language *findLanguage(const QString & key);
+	void loadLocalIndex();
+	void downloadTranslation(QString key);
+	void downloadNext();
+
+	// hide copy constructor
+	TranslationsModel(const TranslationsModel &) = delete;
+	// hide assign op
+	TranslationsModel &operator=(const TranslationsModel &) = delete;
+
+private slots:
+	void indexRecieved();
+	void indexFailed(QString reason);
+	void dlFailed(QString reason);
+	void dlGood();
+
+private: /* data */
+	struct Private;
+	std::unique_ptr<Private> d;
+};
-- 
cgit