diff --git a/CMakeLists.txt b/CMakeLists.txt index c026de4d..7ac13fa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,8 @@ SET(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.") # Updater enabled? SET(MultiMC_UPDATER false CACHE BOOL "Whether or not the update system is enabled. If this is enabled, you must also set MultiMC_CHANLIST_URL and MultiMC_VERSION_CHANNEL in order for it to work properly.") +# Notification URL +SET(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.") # Build a version string to display in the configure logs. SET(MultiMC_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}") @@ -337,6 +339,8 @@ logic/updater/UpdateChecker.h logic/updater/UpdateChecker.cpp logic/updater/DownloadUpdateTask.h logic/updater/DownloadUpdateTask.cpp +logic/updater/NotificationChecker.h +logic/updater/NotificationChecker.cpp # News System logic/news/NewsChecker.h diff --git a/MultiMC.cpp b/MultiMC.cpp index 619a7e0a..b105fd66 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -26,6 +26,7 @@ #include "logic/JavaUtils.h" #include "logic/updater/UpdateChecker.h" +#include "logic/updater/NotificationChecker.h" #include "pathutils.h" #include "cmdutils.h" @@ -182,6 +183,9 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &data_dir_override) // initialize the updater m_updateChecker.reset(new UpdateChecker()); + // initialize the notification checker + m_notificationChecker.reset(new NotificationChecker()); + // initialize the news checker m_newsChecker.reset(new NewsChecker(NEWS_RSS_URL)); @@ -350,6 +354,7 @@ void MultiMC::initGlobalSettings() // Updates m_settings->registerSetting("UseDevBuilds", false); m_settings->registerSetting("AutoUpdate", true); + m_settings->registerSetting("ShownNotifications", QString()); // FTB m_settings->registerSetting("TrackFTBInstances", false); diff --git a/MultiMC.h b/MultiMC.h index 91731afa..3a25aa5e 100644 --- a/MultiMC.h +++ b/MultiMC.h @@ -17,6 +17,7 @@ class QNetworkAccessManager; class ForgeVersionList; class JavaVersionList; class UpdateChecker; +class NotificationChecker; class NewsChecker; #if defined(MMC) @@ -90,6 +91,11 @@ public: return m_updateChecker; } + std::shared_ptr notificationChecker() + { + return m_notificationChecker; + } + std::shared_ptr newsChecker() { return m_newsChecker; @@ -166,6 +172,7 @@ private: std::shared_ptr m_settings; std::shared_ptr m_instances; std::shared_ptr m_updateChecker; + std::shared_ptr m_notificationChecker; std::shared_ptr m_newsChecker; std::shared_ptr m_accounts; std::shared_ptr m_icons; diff --git a/config.h.in b/config.h.in index aa604056..9681b825 100644 --- a/config.h.in +++ b/config.h.in @@ -10,6 +10,12 @@ // URL for the updater's channel #define CHANLIST_URL "@MultiMC_CHANLIST_URL@" +// URL for notifications +#define NOTIFICATION_URL "@MultiMC_NOTIFICATION_URL@" + +// Used for matching notifications +#define FULL_VERSION_STR "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@" + // The commit hash of this build #define GIT_COMMIT "@MultiMC_GIT_COMMIT@" diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 2b911b2c..cf34a46b 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -92,6 +92,7 @@ #include "logic/assets/AssetsUtils.h" #include "logic/assets/AssetsMigrateTask.h" #include +#include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) @@ -279,6 +280,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // if automatic update checks are allowed, start one. if (MMC->settings()->get("AutoUpdate").toBool()) on_actionCheckUpdate_triggered(); + + connect(MMC->notificationChecker().get(), &NotificationChecker::notificationCheckFinished, + this, &MainWindow::notificationsChanged); } const QString currentInstanceId = MMC->settings()->get("SelectedInstance").toString(); @@ -495,6 +499,58 @@ void MainWindow::updateAvailable(QString repo, QString versionName, int versionI } } +QList stringToIntList(const QString &string) +{ + QStringList split = string.split(',', QString::SkipEmptyParts); + QList out; + for (int i = 0; i < split.size(); ++i) + { + out.append(split.at(i).toInt()); + } + return out; +} +QString intListToString(const QList &list) +{ + QStringList slist; + for (int i = 0; i < list.size(); ++i) + { + slist.append(QString::number(list.at(i))); + } + return slist.join(','); +} +void MainWindow::notificationsChanged() +{ + QList entries = + MMC->notificationChecker()->notificationEntries(); + QList shownNotifications = + stringToIntList(MMC->settings()->get("ShownNotifications").toString()); + for (auto it = entries.begin(); it != entries.end(); ++it) + { + NotificationChecker::NotificationEntry entry = *it; + if (!shownNotifications.contains(entry.id) && entry.applies()) + { + QMessageBox::Icon icon; + switch (entry.type) + { + case NotificationChecker::NotificationEntry::Critical: + icon = QMessageBox::Critical; + break; + case NotificationChecker::NotificationEntry::Warning: + icon = QMessageBox::Warning; + break; + case NotificationChecker::NotificationEntry::Information: + icon = QMessageBox::Information; + break; + } + + QMessageBox box(icon, tr("Notification"), entry.message, QMessageBox::Ok, this); + box.exec(); + shownNotifications.append(entry.id); + } + } + MMC->settings()->set("ShownNotifications", intListToString(shownNotifications)); +} + void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit) { QLOG_INFO() << "Downloading updates."; diff --git a/gui/MainWindow.h b/gui/MainWindow.h index f2315ee6..7089b98b 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -157,6 +157,8 @@ slots: void updateAvailable(QString repo, QString versionName, int versionId); + void notificationsChanged(); + void activeAccountChanged(); void changeActiveAccount(); diff --git a/logic/updater/NotificationChecker.cpp b/logic/updater/NotificationChecker.cpp new file mode 100644 index 00000000..a3d7a945 --- /dev/null +++ b/logic/updater/NotificationChecker.cpp @@ -0,0 +1,113 @@ +#include "NotificationChecker.h" + +#include +#include +#include + +#include "MultiMC.h" +#include "logic/net/CacheDownload.h" +#include "config.h" + +NotificationChecker::NotificationChecker(QObject *parent) + : QObject(parent), m_notificationsUrl(QUrl(NOTIFICATION_URL)) +{ + // this will call checkForNotifications once the event loop is running + QMetaObject::invokeMethod(this, "checkForNotifications", Qt::QueuedConnection); +} + +QUrl NotificationChecker::notificationsUrl() const +{ + return m_notificationsUrl; +} +void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) +{ + m_notificationsUrl = notificationsUrl; +} + +QList NotificationChecker::notificationEntries() const +{ + return m_entries; +} + +void NotificationChecker::checkForNotifications() +{ + if (m_checkJob) + { + return; + } + m_checkJob.reset(new NetJob("Checking for notifications")); + auto entry = MMC->metacache()->resolveEntry("root", "notifications.json"); + entry->stale = true; + m_checkJob->addNetAction(m_download = CacheDownload::make(m_notificationsUrl, entry)); + connect(m_download.get(), &CacheDownload::succeeded, this, + &NotificationChecker::downloadSucceeded); + m_checkJob->start(); +} + +void NotificationChecker::downloadSucceeded(int) +{ + m_entries.clear(); + + QFile file(m_download->m_output_file.fileName()); + if (file.open(QFile::ReadOnly)) + { + QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); + for (auto it = root.begin(); it != root.end(); ++it) + { + QJsonObject obj = (*it).toObject(); + NotificationEntry entry; + entry.id = obj.value("id").toInt(); + entry.message = obj.value("message").toString(); + entry.channel = obj.value("channel").toString(); + entry.buildtype = obj.value("buildtype").toString(); + entry.from = obj.value("from").toString(); + entry.to = obj.value("to").toString(); + const QString type = obj.value("type").toString("critical"); + if (type == "critical") + { + entry.type = NotificationEntry::Critical; + } + else if (type == "warning") + { + entry.type = NotificationEntry::Warning; + } + else if (type == "information") + { + entry.type = NotificationEntry::Information; + } + m_entries.append(entry); + } + } + + m_checkJob.reset(); + + emit notificationCheckFinished(); +} + +bool NotificationChecker::NotificationEntry::applies() const +{ + bool channelApplies = channel.isEmpty() || channel == VERSION_CHANNEL; + bool buildtypeApplies = buildtype.isEmpty() || buildtype == VERSION_BUILD_TYPE; + bool fromApplies = + from.isEmpty() || from == FULL_VERSION_STR || !versionLessThan(FULL_VERSION_STR, from); + bool toApplies = + to.isEmpty() || to == FULL_VERSION_STR || !versionLessThan(to, FULL_VERSION_STR); + return channelApplies && buildtypeApplies && fromApplies && toApplies; +} + +bool NotificationChecker::NotificationEntry::versionLessThan(const QString &v1, + const QString &v2) +{ + QStringList l1 = v1.split('.'); + QStringList l2 = v2.split('.'); + while (!l1.isEmpty() && !l2.isEmpty()) + { + int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); + int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); + if (one != two) + { + return one < two; + } + } + return false; +} diff --git a/logic/updater/NotificationChecker.h b/logic/updater/NotificationChecker.h new file mode 100644 index 00000000..20541757 --- /dev/null +++ b/logic/updater/NotificationChecker.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "logic/net/NetJob.h" +#include "logic/net/CacheDownload.h" + +class NotificationChecker : public QObject +{ + Q_OBJECT + +public: + explicit NotificationChecker(QObject *parent = 0); + + QUrl notificationsUrl() const; + void setNotificationsUrl(const QUrl ¬ificationsUrl); + + struct NotificationEntry + { + int id; + QString message; + enum + { + Critical, + Warning, + Information + } type; + QString channel; + QString buildtype; + QString from; + QString to; + bool applies() const; + static bool versionLessThan(const QString &v1, const QString &v2); + }; + + QList notificationEntries() const; + +public +slots: + void checkForNotifications(); + +private +slots: + void downloadSucceeded(int); + +signals: + void notificationCheckFinished(); + +private: + QList m_entries; + QUrl m_notificationsUrl; + NetJobPtr m_checkJob; + CacheDownloadPtr m_download; +};