Merge pull request #590 from GriffinRichards/update-promoter

Add update promoter
This commit is contained in:
GriffinR
2024-02-12 13:27:49 -05:00
committed by GitHub
18 changed files with 782 additions and 50 deletions

View File

@@ -8,9 +8,14 @@
#include <QSize>
#include <QKeySequence>
#include <QMultiMap>
#include <QDateTime>
#include <QUrl>
#include <QVersionNumber>
#include "events.h"
static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION);
// In both versions the default new map border is a generic tree
#define DEFAULT_BORDER_RSE (QList<uint16_t>{0x1D4, 0x1D5, 0x1DC, 0x1DD})
#define DEFAULT_BORDER_FRLG (QList<uint16_t>{0x14, 0x15, 0x1C, 0x1D})
@@ -74,6 +79,10 @@ public:
this->paletteEditorBitDepth = 24;
this->projectSettingsTab = 0;
this->warpBehaviorWarningDisabled = false;
this->checkForUpdates = true;
this->lastUpdateCheckTime = QDateTime();
this->lastUpdateCheckVersion = porymapVersion;
this->rateLimitTimes.clear();
}
void addRecentProject(QString project);
void setRecentProjects(QStringList projects);
@@ -105,6 +114,10 @@ public:
void setPaletteEditorBitDepth(int bitDepth);
void setProjectSettingsTab(int tab);
void setWarpBehaviorWarningDisabled(bool disabled);
void setCheckForUpdates(bool enabled);
void setLastUpdateCheckTime(QDateTime time);
void setLastUpdateCheckVersion(QVersionNumber version);
void setRateLimitTimes(QMap<QUrl, QDateTime> map);
QString getRecentProject();
QStringList getRecentProjects();
bool getReopenOnLaunch();
@@ -135,6 +148,10 @@ public:
int getPaletteEditorBitDepth();
int getProjectSettingsTab();
bool getWarpBehaviorWarningDisabled();
bool getCheckForUpdates();
QDateTime getLastUpdateCheckTime();
QVersionNumber getLastUpdateCheckVersion();
QMap<QUrl, QDateTime> getRateLimitTimes();
protected:
virtual QString getConfigFilepath() override;
virtual void parseConfigKeyValue(QString key, QString value) override;
@@ -183,6 +200,10 @@ private:
int paletteEditorBitDepth;
int projectSettingsTab;
bool warpBehaviorWarningDisabled;
bool checkForUpdates;
QDateTime lastUpdateCheckTime;
QVersionNumber lastUpdateCheckVersion;
QMap<QUrl, QDateTime> rateLimitTimes;
};
extern PorymapConfig porymapConfig;

87
include/core/network.h Normal file
View File

@@ -0,0 +1,87 @@
#ifndef NETWORK_H
#define NETWORK_H
/*
The two classes defined here provide a simplified interface for Qt's network classes QNetworkAccessManager and QNetworkReply.
With the Qt classes, the workflow for a GET is roughly: generate a QNetworkRequest, give this request to QNetworkAccessManager::get,
connect the returned object to QNetworkReply::finished, and in the slot of that connection handle the various HTTP headers and attributes,
then manage errors or process the webpage's body.
These classes handle generating the QNetworkRequest with a given URL and manage the HTTP headers in the reply. They will automatically
respect rate limits and return cached data if the webpage hasn't changed since previous requests. Instead of interacting with a QNetworkReply,
callers interact with a simplified NetworkReplyData.
Example that logs Porymap's description on GitHub:
NetworkAccessManager * manager = new NetworkAccessManager(this);
NetworkReplyData * reply = manager->get("https://api.github.com/repos/huderlem/porymap");
connect(reply, &NetworkReplyData::finished, [reply] () {
if (!reply->errorString().isEmpty()) {
logError(QString("Failed to read description: %1").arg(reply->errorString()));
} else {
auto webpage = QJsonDocument::fromJson(reply->body());
logInfo(QString("Porymap: %1").arg(webpage["description"].toString()));
}
reply->deleteLater();
});
*/
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDateTime>
class NetworkReplyData : public QObject
{
Q_OBJECT
public:
QUrl url() const { return m_url; }
QUrl nextUrl() const { return m_nextUrl; }
QByteArray body() const { return m_body; }
QString errorString() const { return m_error; }
QDateTime retryAfter() const { return m_retryAfter; }
bool isFinished() const { return m_finished; }
friend class NetworkAccessManager;
private:
QUrl m_url;
QUrl m_nextUrl;
QByteArray m_body;
QString m_error;
QDateTime m_retryAfter;
bool m_finished;
void finish() {
m_finished = true;
emit finished();
};
signals:
void finished();
};
class NetworkAccessManager : public QNetworkAccessManager
{
Q_OBJECT
public:
NetworkAccessManager(QObject * parent = nullptr);
~NetworkAccessManager();
NetworkReplyData * get(const QString &url);
NetworkReplyData * get(const QUrl &url);
private:
// For a more complex cache we could implement a QAbstractCache for the manager
struct CacheEntry {
QString eTag;
QByteArray data;
};
QMap<QUrl, CacheEntry*> cache;
QMap<QUrl, QDateTime> rateLimitTimes;
void processReply(QNetworkReply * reply, NetworkReplyData * data);
const QNetworkRequest getRequest(const QUrl &url);
};
#endif // NETWORK_H

View File

@@ -27,6 +27,7 @@
#include "preferenceeditor.h"
#include "projectsettingseditor.h"
#include "customscriptseditor.h"
#include "updatepromoter.h"
@@ -288,6 +289,7 @@ private slots:
void on_spinBox_SelectedCollision_valueChanged(int collision);
void on_actionRegion_Map_Editor_triggered();
void on_actionPreferences_triggered();
void on_actionCheck_for_Updates_triggered();
void togglePreferenceSpecificUi();
void on_actionProject_Settings_triggered();
void on_actionCustom_Scripts_triggered();
@@ -307,6 +309,8 @@ private:
QPointer<PreferenceEditor> preferenceEditor = nullptr;
QPointer<ProjectSettingsEditor> projectSettingsEditor = nullptr;
QPointer<CustomScriptsEditor> customScriptsEditor = nullptr;
QPointer<UpdatePromoter> updatePromoter = nullptr;
QPointer<NetworkAccessManager> networkAccessManager = nullptr;
FilterChildrenProxyModel *mapListProxyModel;
QStandardItemModel *mapListModel;
QList<QStandardItem*> *mapGroupItemsList;
@@ -397,6 +401,8 @@ private:
QObjectList shortcutableObjects() const;
void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false);
int insertTilesetLabel(QStringList * list, QString label);
void checkForUpdates(bool requestedByUser);
};
enum MapListUserRoles {

View File

@@ -52,7 +52,6 @@ public:
static QJSValue fromBlock(Block block);
static QJSValue fromTile(Tile tile);
static Tile toTile(QJSValue obj);
static QJSValue version(QList<int> versionNums);
static QJSValue dimensions(int width, int height);
static QJSValue position(int x, int y);
static const QImage * getImage(const QString &filepath, bool useCache);

View File

@@ -14,7 +14,6 @@ class AboutPorymap : public QMainWindow
public:
explicit AboutPorymap(QWidget *parent = nullptr);
~AboutPorymap();
QList<int> getVersionNumbers();
private:
Ui::AboutPorymap *ui;
};

View File

@@ -0,0 +1,50 @@
#ifndef UPDATEPROMOTER_H
#define UPDATEPROMOTER_H
#include "network.h"
#include <QDialog>
#include <QPushButton>
#include <QVersionNumber>
namespace Ui {
class UpdatePromoter;
}
class UpdatePromoter : public QDialog
{
Q_OBJECT
public:
explicit UpdatePromoter(QWidget *parent, NetworkAccessManager *manager);
~UpdatePromoter() {};
void checkForUpdates();
void updatePreferences();
private:
Ui::UpdatePromoter *ui;
NetworkAccessManager *const manager;
QPushButton * button_Downloads;
QPushButton * button_Retry;
QString changelog;
QUrl downloadUrl;
QVersionNumber newVersion;
bool foundReleases;
QSet<QUrl> visitedUrls; // Prevent infinite redirection
void resetDialog();
void get(const QUrl &url);
void processWebpage(const QJsonDocument &data, const QUrl &nextUrl);
void error(const QString &err, const QDateTime time = QDateTime());
private slots:
void dialogButtonClicked(QAbstractButton *button);
signals:
void changedPreferences();
};
#endif // UPDATEPROMOTER_H