Add release channels for autoupdater (#2362)

Fetch releases from github and find corresponding installers on bintray
This commit is contained in:
ctrlaltca 2017-03-01 09:43:09 +01:00 committed by GitHub
parent b9cd942308
commit 7373819c32
16 changed files with 507 additions and 237 deletions

View file

@ -3,8 +3,10 @@ set(VERSION_STRING_H "${PROJECT_BINARY_DIR}/version_string.h")
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR})
set( hstring "extern const char *VERSION_STRING\;
extern const char *VERSION_COMMIT\;
extern const char *VERSION_DATE\;\n" )
set( cppstring "const char *VERSION_STRING = \"${PROJECT_VERSION_FRIENDLY}\"\;
const char *VERSION_COMMIT = \"${GIT_COMMIT_ID}\"\;
const char *VERSION_DATE = \"${GIT_COMMIT_DATE_FRIENDLY}\"\;\n")
file(WRITE ${PROJECT_BINARY_DIR}/version_string.cpp.txt ${cppstring} )

View file

@ -112,9 +112,9 @@ SET(cockatrice_SOURCES
src/settings/messagesettings.cpp
src/settings/gamefilterssettings.cpp
src/settings/layoutssettings.cpp
src/update_checker.cpp
src/update_downloader.cpp
src/logger.cpp
src/releasechannel.cpp
${VERSION_STRING_CPP}
)

View file

@ -27,6 +27,7 @@
#include "settingscache.h"
#include "thememanager.h"
#include "priceupdater.h"
#include "releasechannel.h"
#include "soundengine.h"
#include "sequenceEdit/shortcutstab.h"
@ -44,8 +45,18 @@ GeneralSettingsPage::GeneralSettingsPage()
}
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
// updates
QList<ReleaseChannel*> channels = settingsCache->getUpdateReleaseChannels();
foreach(ReleaseChannel* chan, channels)
{
updateReleaseChannelBox.insertItem(chan->getIndex(), tr(chan->getName().toUtf8()));
}
updateReleaseChannelBox.setCurrentIndex(settingsCache->getUpdateReleaseChannel()->getIndex());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
// pixmap cache
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
// 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size)
pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX);
@ -60,6 +71,7 @@ GeneralSettingsPage::GeneralSettingsPage()
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache, SLOT(setUpdateReleaseChannel(int)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString)));
@ -74,16 +86,18 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&languageBox, 0, 1);
personalGrid->addWidget(&pixmapCacheLabel, 1, 0);
personalGrid->addWidget(&pixmapCacheEdit, 1, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 2, 0);
personalGrid->addWidget(&picDownloadCheckBox, 3, 0, 1, 3);
personalGrid->addWidget(&defaultUrlLabel, 4, 0, 1, 1);
personalGrid->addWidget(defaultUrlEdit, 4, 1, 1, 1);
personalGrid->addWidget(&defaultUrlRestoreButton, 4, 2, 1, 1);
personalGrid->addWidget(&fallbackUrlLabel, 5, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 5, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 5, 2, 1, 1);
personalGrid->addWidget(&urlLinkLabel, 6, 1, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 7, 0, 1, 3);
personalGrid->addWidget(&updateReleaseChannelLabel, 2, 0);
personalGrid->addWidget(&updateReleaseChannelBox, 2, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 3, 0);
personalGrid->addWidget(&picDownloadCheckBox, 4, 0, 1, 3);
personalGrid->addWidget(&defaultUrlLabel, 5, 0, 1, 1);
personalGrid->addWidget(defaultUrlEdit, 5, 1, 1, 1);
personalGrid->addWidget(&defaultUrlRestoreButton, 5, 2, 1, 1);
personalGrid->addWidget(&fallbackUrlLabel, 6, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 6, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 6, 2, 1, 1);
personalGrid->addWidget(&urlLinkLabel, 7, 1, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 8, 0, 1, 3);
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
@ -269,8 +283,9 @@ void GeneralSettingsPage::retranslateUi()
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to set a custom picture url")));
clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures"));
updateNotificationCheckBox.setText(tr("Notify when new client features are available"));
clearDownloadedPicsButton.setText(tr("Reset/clear downloaded pictures"));
updateReleaseChannelLabel.setText(tr("Update channel"));
updateNotificationCheckBox.setText(tr("Notify when a new version is available"));
defaultUrlRestoreButton.setText(tr("Reset"));
fallbackUrlRestoreButton.setText(tr("Reset"));
}

View file

@ -63,6 +63,7 @@ private:
QComboBox languageBox;
QCheckBox picDownloadCheckBox;
QCheckBox updateNotificationCheckBox;
QComboBox updateReleaseChannelBox;
QLabel languageLabel;
QLabel pixmapCacheLabel;
QLabel deckPathLabel;
@ -73,6 +74,7 @@ private:
QLabel defaultUrlLabel;
QLabel fallbackUrlLabel;
QLabel urlLinkLabel;
QLabel updateReleaseChannelLabel;
QPushButton clearDownloadedPicsButton;
QPushButton defaultUrlRestoreButton;
QPushButton fallbackUrlRestoreButton;

View file

@ -1,9 +1,3 @@
#define HUMAN_DOWNLOAD_URL "https://bintray.com/cockatrice/Cockatrice/Cockatrice/_latestVersion"
#define API_DOWNLOAD_BASE_URL "https://dl.bintray.com/cockatrice/Cockatrice/"
#define DATE_LENGTH 10
#define MAX_DATE_LENGTH 100
#define SHORT_SHA1_HASH_LENGTH 7
#include <QtNetwork>
#include <QProgressDialog>
#include <QDesktopServices>
@ -16,12 +10,15 @@
#include <QApplication>
#include "dlg_update.h"
#include "releasechannel.h"
#include "settingscache.h"
#include "window_main.h"
DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) {
//Handle layout
text = new QLabel(this);
statusLabel = new QLabel(this);
descriptionLabel = new QLabel(tr("Current release channel:") + " " + tr(settingsCache->getUpdateReleaseChannel()->getName().toUtf8()), this);
progress = new QProgressBar(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
@ -38,7 +35,8 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) {
connect(ok, SIGNAL(clicked()), this, SLOT(closeDialog()));
QVBoxLayout *parentLayout = new QVBoxLayout(this);
parentLayout->addWidget(text);
parentLayout->addWidget(descriptionLabel);
parentLayout->addWidget(statusLabel);
parentLayout->addWidget(progress);
parentLayout->addWidget(buttonBox);
@ -47,11 +45,9 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) {
//Check for SSL (this probably isn't necessary)
if (!QSslSocket::supportsSsl()) {
enableUpdateButton(false);
QMessageBox::critical(
this,
tr("Error"),
tr("Cockatrice was not built with SSL support, so cannot download updates! "
"Please visit the download page and update manually."));
QMessageBox::critical(this, tr("Error"),
tr("Cockatrice was not built with SSL support, so you cannot download updates automatically! "
"Please visit the download page to update manually."));
}
//Initialize the checker and downloader class
@ -62,10 +58,10 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) {
connect(uDownloader, SIGNAL(error(QString)),
this, SLOT(downloadError(QString)));
uChecker = new UpdateChecker(this);
connect(uChecker, SIGNAL(finishedCheck(bool, bool, QVariantMap * )),
this, SLOT(finishedUpdateCheck(bool, bool, QVariantMap * )));
connect(uChecker, SIGNAL(error(QString)),
ReleaseChannel * channel = settingsCache->getUpdateReleaseChannel();
connect(channel, SIGNAL(finishedCheck(bool, bool, Release * )),
this, SLOT(finishedUpdateCheck(bool, bool, Release * )));
connect(channel, SIGNAL(error(QString)),
this, SLOT(updateCheckError(QString)));
//Check for updates
@ -79,8 +75,7 @@ void DlgUpdate::closeDialog() {
void DlgUpdate::gotoDownloadPage() {
QUrl openUrl(HUMAN_DOWNLOAD_URL);
QDesktopServices::openUrl(openUrl);
QDesktopServices::openUrl(settingsCache->getUpdateReleaseChannel()->getManualDownloadUrl());
}
void DlgUpdate::downloadUpdate() {
@ -94,12 +89,12 @@ void DlgUpdate::beginUpdateCheck() {
progress->setMinimum(0);
progress->setMaximum(0);
setLabel(tr("Checking for updates..."));
uChecker->check();
settingsCache->getUpdateReleaseChannel()->checkForUpdates();
}
void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, QVariantMap *build) {
void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release) {
QString commitHash, commitDate;
QString publishDate, versionName;
//Update the UI to say we've finished
progress->setMaximum(100);
@ -108,40 +103,34 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, QVaria
//If there are no available builds, then they can't auto update.
enableUpdateButton(isCompatible);
//If there is an update, save its URL and work out its name
if (isCompatible) {
QString endUrl = (*build)["path"].toString();
updateUrl = API_DOWNLOAD_BASE_URL + endUrl;
commitHash = (*build)["sha1"].toString().left(SHORT_SHA1_HASH_LENGTH);
commitDate = (*build)["created"].toString().remove(DATE_LENGTH, MAX_DATE_LENGTH);
}
//Give the user the appropriate message
if (needToUpdate) {
if (isCompatible) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Update Available",
"A new build (commit " + commitHash + ") from " + commitDate +
" is available. Download?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes)
downloadUpdate();
}
else
{
QMessageBox::information(this, tr("Cockatrice Update"),
tr("Your version of Cockatrice is out of date, but there are no packages"
" available for your operating system. You may have to use a developer build or build from source"
" yourself. Please visit the download page."));
}
}
else {
if (!needToUpdate) {
//If there's no need to update, tell them that. However we still allow them to run the
//downloader themselves if there's a compatible build
QMessageBox::information(this, tr("Cockatrice Update"), tr("Your version of Cockatrice is up to date."));
}
if (isCompatible) {
//If there is an update, save its URL and work out its name
updateUrl = release->getDownloadUrl();
publishDate = release->getPublishDate().toString(Qt::DefaultLocaleLongDate);
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Update Available",
tr("A new version is available:<br/>%1<br/>published on %2 ."
"<br/>More informations are available on the <a href=\"%3\">release changelog</a>"
"<br/>Do you want to update now?").arg(release->getName(), publishDate, release->getDescriptionUrl()),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes)
downloadUpdate();
} else {
QMessageBox::information(this, tr("Cockatrice Update"),
tr("A new version is available:<br/>%1<br/>published on %2 ."
"<br/>More informations are available on the <a href=\"%3\">release changelog</a>"
"<br/>Unfortunately there are no packages available for your operating system. "
"You may have to use a developer build or build from source yourself. Please visit the download page.").arg(release->getName(), publishDate, release->getDescriptionUrl()));
}
}
void DlgUpdate::enableUpdateButton(bool enable) {
@ -153,7 +142,7 @@ void DlgUpdate::enableOkButton(bool enable) {
}
void DlgUpdate::setLabel(QString newText) {
text->setText(newText);
statusLabel->setText(newText);
}
void DlgUpdate::updateCheckError(QString errorString) {
@ -176,8 +165,8 @@ void DlgUpdate::downloadSuccessful(QUrl filepath) {
close();
} else {
setLabel(tr("Error"));
QMessageBox::critical(this, tr("Update Error"), "Unable to open the installer. You might be able to manually update"
" by closing Cockatrice and running the installer at " + filepath.toLocalFile() + ".");
QMessageBox::critical(this, tr("Update Error"),
tr("Unable to open the installer. You might be able to manually update by closing Cockatrice and running the installer at %1.").arg(filepath.toLocalFile()));
}
}

View file

@ -4,8 +4,8 @@
#include <QtNetwork>
#include <QProgressDialog>
#include "update_checker.h"
#include "update_downloader.h"
class Release;
class DlgUpdate : public QDialog {
Q_OBJECT
@ -13,7 +13,7 @@ public:
DlgUpdate(QWidget *parent);
private slots:
void finishedUpdateCheck(bool needToUpdate, bool isCompatible, QVariantMap *build);
void finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release);
void gotoDownloadPage();
void downloadUpdate();
void updateCheckError(QString errorString);
@ -27,11 +27,10 @@ private:
void enableOkButton(bool enable);
void beginUpdateCheck();
void setLabel(QString text);
QLabel *text;
QLabel *statusLabel, *descriptionLabel;
QProgressBar *progress;
QPushButton *manualDownload, *gotoDownload, *ok;
QPushButton *cancel;
UpdateChecker *uChecker;
UpdateDownloader *uDownloader;
};

View file

@ -0,0 +1,312 @@
#include "releasechannel.h"
#include "qt-json/json.h"
#include "version_string.h"
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QMessageBox>
#define STABLERELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases/latest"
#define STABLETAG_URL "https://api.github.com/repos/Cockatrice/Cockatrice/git/refs/tags/"
#define STABLEFILES_URL "https://api.bintray.com/packages/cockatrice/Cockatrice/Cockatrice/files"
#define STABLEDOWNLOAD_URL "https://dl.bintray.com/cockatrice/Cockatrice/"
#define STABLEMANUALDOWNLOAD_URL "https://bintray.com/cockatrice/Cockatrice/Cockatrice/_latestVersion#files"
#define DEVRELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/commits/master"
#define DEVFILES_URL "https://api.bintray.com/packages/cockatrice/Cockatrice/Cockatrice-git/files"
#define DEVDOWNLOAD_URL "https://dl.bintray.com/cockatrice/Cockatrice/"
#define DEVMANUALDOWNLOAD_URL "https://bintray.com/cockatrice/Cockatrice/Cockatrice-git/_latestVersion#files"
#define GIT_SHORT_HASH_LEN 7
int ReleaseChannel::sharedIndex = 0;
ReleaseChannel::ReleaseChannel()
: response(nullptr), lastRelease(nullptr)
{
index = sharedIndex++;
netMan = new QNetworkAccessManager(this);
}
ReleaseChannel::~ReleaseChannel()
{
netMan->deleteLater();
}
void ReleaseChannel::checkForUpdates()
{
QString releaseChannelUrl = getReleaseChannelUrl();
qDebug() << "Searching for updates on the channel: " << releaseChannelUrl;
response = netMan->get(QNetworkRequest(releaseChannelUrl));
connect(response, SIGNAL(finished()), this, SLOT(releaseListFinished()));
}
#if defined(Q_OS_OSX)
bool ReleaseChannel::downloadMatchesCurrentOS(QVariantMap build)
{
return build["name"].toString().endsWith(".dmg");
}
#elif defined(Q_OS_WIN)
#include <QSysInfo>
bool ReleaseChannel::downloadMatchesCurrentOS(QVariantMap build)
{
QString wordSize = QSysInfo::buildAbi().split('-')[2];
QString arch;
if (wordSize == "llp64") {
arch = "win64";
} else if (wordSize == "ilp32") {
arch = "win32";
} else {
qWarning() << "Error checking for upgrade version: wordSize is" << wordSize;
return false;
}
auto fileName = build["name"].toString();
// Checking for .zip is a workaround for the May 6th 2016 release
auto zipName = arch + ".zip";
auto exeName = arch + ".exe";
return fileName.endsWith(exeName) || fileName.endsWith(zipName);
}
#else
bool ReleaseChannel::downloadMatchesCurrentOS(QVariantMap)
{
//If the OS doesn't fit one of the above #defines, then it will never match
return false;
}
#endif
QString StableReleaseChannel::getManualDownloadUrl() const
{
return QString(STABLEMANUALDOWNLOAD_URL);
}
QString StableReleaseChannel::getName() const
{
return tr("Stable releases");
}
QString StableReleaseChannel::getReleaseChannelUrl() const
{
return QString(STABLERELEASE_URL);
}
void StableReleaseChannel::releaseListFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
bool ok;
QString tmp = QString(reply->readAll());
reply->deleteLater();
QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap();
if (!ok) {
qWarning() << "No reply received from the release update server:" << tmp;
emit error(tr("No reply received from the release update server."));
return;
}
if(!(resultMap.contains("name") &&
resultMap.contains("html_url") &&
resultMap.contains("tag_name") &&
resultMap.contains("published_at"))) {
qWarning() << "Invalid received from the release update server:" << tmp;
emit error(tr("Invalid reply received from the release update server."));
return;
}
if(!lastRelease)
lastRelease = new Release;
lastRelease->setName(resultMap["name"].toString());
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
lastRelease->setPublishDate(resultMap["published_at"].toDate());
qDebug() << "Got reply from release server, size=" << tmp.size()
<< "name=" << lastRelease->getName()
<< "desc=" << lastRelease->getDescriptionUrl()
<< "date=" << lastRelease->getPublishDate();
QString url = QString(STABLETAG_URL) + resultMap["tag_name"].toString();
qDebug() << "Searching for a corresponding tag on the stable channel: " << url;
response = netMan->get(QNetworkRequest(url));
connect(response, SIGNAL(finished()), this, SLOT(tagListFinished()));
}
void StableReleaseChannel::tagListFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
bool ok;
QString tmp = QString(reply->readAll());
reply->deleteLater();
QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap();
if (!ok) {
qWarning() << "No reply received from the tag update server:" << tmp;
emit error(tr("No reply received from the tag update server."));
return;
}
if(!(resultMap.contains("object") &&
resultMap["object"].toMap().contains("sha"))) {
qWarning() << "Invalid received from the tag update server:" << tmp;
emit error(tr("Invalid reply received from the tag update server."));
return;
}
lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString());
qDebug() << "Got reply from tag server, size=" << tmp.size()
<< "commit=" << lastRelease->getCommitHash();
qDebug() << "Searching for a corresponding file on the stable channel: " << QString(STABLEFILES_URL);
response = netMan->get(QNetworkRequest(QString(STABLEFILES_URL)));
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
}
void StableReleaseChannel::fileListFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
bool ok;
QString tmp = QString(reply->readAll());
reply->deleteLater();
QVariantList resultList = QtJson::Json::parse(tmp, ok).toList();
if (!ok) {
qWarning() << "No reply received from the file update server:" << tmp;
emit error(tr("No reply received from the file update server."));
return;
}
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
QString myHash = QString(VERSION_COMMIT);
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;
bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
bool compatibleVersion = false;
foreach(QVariant file, resultList)
{
QVariantMap map = file.toMap();
// TODO: map github version to bintray version
/*
if(!map.contains("version"))
continue;
if(!map["version"].toString().endsWith(shortHash))
continue;
*/
if(!downloadMatchesCurrentOS(map))
continue;
compatibleVersion = true;
QString url = QString(STABLEDOWNLOAD_URL) + map["path"].toString();
lastRelease->setDownloadUrl(url);
qDebug() << "Found compatible version url=" << url;
break;
}
emit finishedCheck(needToUpdate, compatibleVersion, lastRelease);
}
QString DevReleaseChannel::getManualDownloadUrl() const
{
return QString(DEVMANUALDOWNLOAD_URL);
}
QString DevReleaseChannel::getName() const
{
return tr("Development snapshots");
}
QString DevReleaseChannel::getReleaseChannelUrl() const
{
return QString(DEVRELEASE_URL);
}
void DevReleaseChannel::releaseListFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
bool ok;
QString tmp = QString(reply->readAll());
reply->deleteLater();
QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap();
if (!ok) {
qWarning() << "No reply received from the release update server:" << tmp;
emit error(tr("No reply received from the release update server."));
return;
}
if(!(resultMap.contains("commit") &&
resultMap.contains("html_url") &&
resultMap.contains("sha") &&
resultMap["commit"].toMap().contains("author") &&
resultMap["commit"].toMap()["author"].toMap().contains("date"))) {
qWarning() << "Invalid received from the release update server:" << tmp;
emit error(tr("Invalid reply received from the release update server."));
return;
}
if(!lastRelease)
lastRelease = new Release;
lastRelease->setName("Commit " + resultMap["sha"].toString());
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
lastRelease->setCommitHash(resultMap["sha"].toString());
lastRelease->setPublishDate(resultMap["commit"].toMap()["author"].toMap()["date"].toDate());
qDebug() << "Got reply from release server, size=" << tmp.size()
<< "name=" << lastRelease->getName()
<< "desc=" << lastRelease->getDescriptionUrl()
<< "commit=" << lastRelease->getCommitHash()
<< "date=" << lastRelease->getPublishDate();
qDebug() << "Searching for a corresponding file on the dev channel: " << QString(DEVFILES_URL);
response = netMan->get(QNetworkRequest(QString(DEVFILES_URL)));
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
}
void DevReleaseChannel::fileListFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
bool ok;
QString tmp = QString(reply->readAll());
reply->deleteLater();
QVariantList resultList = QtJson::Json::parse(tmp, ok).toList();
if (!ok) {
qWarning() << "No reply received from the file update server:" << tmp;
emit error(tr("No reply received from the file update server."));
return;
}
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
QString myHash = QString(VERSION_COMMIT);
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;
bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
bool compatibleVersion = false;
foreach(QVariant file, resultList)
{
QVariantMap map = file.toMap();
if(!map.contains("version"))
continue;
if(!map["version"].toString().endsWith(shortHash))
continue;
if(!downloadMatchesCurrentOS(map))
continue;
compatibleVersion = true;
QString url = QString(DEVDOWNLOAD_URL) + map["path"].toString();
lastRelease->setDownloadUrl(url);
qDebug() << "Found compatible version url=" << url;
break;
}
emit finishedCheck(needToUpdate, compatibleVersion, lastRelease);
}

View file

@ -0,0 +1,93 @@
#ifndef RELEASECHANNEL_H
#define RELEASECHANNEL_H
#include <QString>
#include <QDate>
#include <QObject>
#include <QVariantMap>
class QNetworkReply;
class QNetworkAccessManager;
class Release {
friend class StableReleaseChannel;
friend class DevReleaseChannel;
public:
Release() {};
~Release() {};
private:
QString name, descriptionUrl, downloadUrl, commitHash;
QDate publishDate;
protected:
void setName(QString _name) { name = _name; }
void setDescriptionUrl(QString _descriptionUrl) { descriptionUrl = _descriptionUrl; }
void setDownloadUrl(QString _downloadUrl) { downloadUrl = _downloadUrl; }
void setCommitHash(QString _commitHash) { commitHash = _commitHash; }
void setPublishDate(QDate _publishDate) { publishDate = _publishDate; }
public:
QString getName() const { return name; }
QString getDescriptionUrl() const { return descriptionUrl; }
QString getDownloadUrl() const { return downloadUrl; }
QString getCommitHash() const { return commitHash; }
QDate getPublishDate() const { return publishDate; }
};
class ReleaseChannel: public QObject {
Q_OBJECT
public:
ReleaseChannel();
~ReleaseChannel();
protected:
// shared by all instances
static int sharedIndex;
int index;
QNetworkAccessManager *netMan;
QNetworkReply *response;
Release * lastRelease;
protected:
static bool downloadMatchesCurrentOS(QVariantMap build);
virtual QString getReleaseChannelUrl() const = 0;
public:
int getIndex() const { return index; }
Release * getLastRelease() { return lastRelease; }
virtual QString getManualDownloadUrl() const = 0;
virtual QString getName() const = 0;
void checkForUpdates();
signals:
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
void error(QString errorString);
protected slots:
virtual void releaseListFinished() = 0;
virtual void fileListFinished() = 0;
};
class StableReleaseChannel: public ReleaseChannel {
Q_OBJECT
public:
StableReleaseChannel() {};
~StableReleaseChannel() {};
virtual QString getManualDownloadUrl() const;
virtual QString getName() const;
protected:
virtual QString getReleaseChannelUrl() const;
protected slots:
virtual void releaseListFinished();
void tagListFinished();
virtual void fileListFinished();
};
class DevReleaseChannel: public ReleaseChannel {
Q_OBJECT
public:
DevReleaseChannel() {};
~DevReleaseChannel() {};
virtual QString getManualDownloadUrl() const;
virtual QString getName() const;
protected:
virtual QString getReleaseChannelUrl() const;
protected slots:
virtual void releaseListFinished();
virtual void fileListFinished();
};
#endif

View file

@ -1,4 +1,6 @@
#include "settingscache.h"
#include "releasechannel.h"
#include <QSettings>
#include <QFile>
#include <QDir>
@ -162,7 +164,13 @@ SettingsCache::SettingsCache()
if(!QFile(settingsPath+"global.ini").exists())
translateLegacySettings();
// updates - don't reorder them or their index in the settings won't match
releaseChannels << new StableReleaseChannel()
<< new DevReleaseChannel();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt();
lang = settings->value("personal/lang").toString();
keepalive = settings->value("personal/keepalive", 5).toInt();
@ -643,3 +651,9 @@ void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate)
notifyAboutUpdates = _notifyaboutupdate;
settings->setValue("personal/updatenotification", notifyAboutUpdates);
}
void SettingsCache::setUpdateReleaseChannel(int _updateReleaseChannel)
{
updateReleaseChannel = _updateReleaseChannel;
settings->setValue("personal/updatereleasechannel", updateReleaseChannel);
}

View file

@ -11,6 +11,8 @@
#include "settings/gamefilterssettings.h"
#include "settings/layoutssettings.h"
class ReleaseChannel;
// the falbacks are used for cards without a muid
#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
#define PIC_URL_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
@ -58,6 +60,7 @@ private:
QString lang;
QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates;
int updateReleaseChannel;
bool picDownload;
bool notificationsEnabled;
bool spectatorNotificationsEnabled;
@ -110,6 +113,7 @@ private:
QString getSafeConfigPath(QString configEntry, QString defaultPath) const;
QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
bool rememberGameSettings;
QList<ReleaseChannel*> releaseChannels;
public:
SettingsCache();
@ -131,6 +135,8 @@ public:
bool getNotificationsEnabled() const { return notificationsEnabled; }
bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; }
bool getNotifyAboutUpdates() const { return notifyAboutUpdates; }
ReleaseChannel * getUpdateReleaseChannel() const { return releaseChannels.at(updateReleaseChannel); }
QList<ReleaseChannel*> getUpdateReleaseChannels() const { return releaseChannels; }
bool getDoubleClickToPlay() const { return doubleClickToPlay; }
bool getPlayToStack() const { return playToStack; }
@ -249,6 +255,7 @@ public slots:
void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
void setRememberGameSettings(const bool _rememberGameSettings);
void setNotifyAboutUpdate(int _notifyaboutupdate);
void setUpdateReleaseChannel(int _updateReleaseChannel);
};
extern SettingsCache *settingsCache;

View file

@ -1,124 +0,0 @@
#include <algorithm>
#include <QMessageBox>
#include "update_checker.h"
#include "version_string.h"
#include "qt-json/json.h"
#define LATEST_FILES_URL "https://api.bintray.com/packages/cockatrice/Cockatrice/Cockatrice/files"
UpdateChecker::UpdateChecker(QObject *parent) : QObject(parent)
{
//Parse the commit date. We'll use this to check for new versions
//We know the format because it's based on `git log` which is documented here:
// https://git-scm.com/docs/git-log#_commit_formatting
buildDate = QDate::fromString(VERSION_DATE, "yyyy-MM-dd");
latestFilesUrl = QUrl(LATEST_FILES_URL);
response = NULL;
netMan = new QNetworkAccessManager(this);
build = NULL;
}
UpdateChecker::~UpdateChecker()
{
delete build;
}
void UpdateChecker::check()
{
response = netMan->get(QNetworkRequest(latestFilesUrl));
connect(response, SIGNAL(finished()),
this, SLOT(fileListFinished()));
}
#if defined(Q_OS_OSX)
bool UpdateChecker::downloadMatchesCurrentOS(QVariant build)
{
return build
.toMap()["name"]
.toString()
.endsWith(".dmg");
}
#elif defined(Q_OS_WIN)
bool UpdateChecker::downloadMatchesCurrentOS(QVariant build)
{
QString wordSize = QSysInfo::buildAbi().split('-')[2];
QString arch;
if (wordSize == "llp64") {
arch = "win64";
} else if (wordSize == "ilp32") {
arch = "win32";
} else {
qWarning() << "Error checking for upgrade version: wordSize is" << wordSize;
return false;
}
auto fileName = build
.toMap()["name"]
.toString();
// Checking for .zip is a workaround for the May 6th 2016 release
auto zipName = arch + ".zip";
auto exeName = arch + ".exe";
return fileName.endsWith(exeName) || fileName.endsWith(zipName);
}
#else
bool UpdateChecker::downloadMatchesCurrentOS(QVariant)
{
//If the OS doesn't fit one of the above #defines, then it will never match
return false;
}
#endif
QDate UpdateChecker::dateFromBuild(QVariant build)
{
QString formatString = "yyyy-MM-dd";
QString dateString = build.toMap()["created"].toString();
dateString = dateString.remove(formatString.length(), dateString.length());
return QDate::fromString(dateString, formatString);
}
QDate UpdateChecker::findOldestBuild(QVariantList allBuilds)
{
//Map the build array into an array of dates
std::vector<QDate> dateArray(allBuilds.size());
std::transform(allBuilds.begin(), allBuilds.end(), dateArray.begin(), dateFromBuild);
//Return the first date
return *std::min_element(dateArray.begin(), dateArray.end());
}
QVariantMap *UpdateChecker::findCompatibleBuild(QVariantList allBuilds)
{
QVariantList::iterator result = std::find_if(allBuilds.begin(), allBuilds.end(), downloadMatchesCurrentOS);
//If there is no compatible version, return NULL
if (result == allBuilds.end())
return NULL;
else {
QVariantMap *ret = new QVariantMap;
*ret = (*result).toMap();
return ret;
}
}
void UpdateChecker::fileListFinished()
{
try {
QVariantList builds = QtJson::Json::parse(response->readAll()).toList();
build = findCompatibleBuild(builds);
QDate bintrayBuildDate = findOldestBuild(builds);
bool needToUpdate = bintrayBuildDate > buildDate;
bool compatibleVersion = build != NULL;
emit finishedCheck(needToUpdate, compatibleVersion, build);
}
catch (const std::exception &exc) {
emit error(exc.what());
}
}

View file

@ -1,38 +0,0 @@
//
// Created by miguel on 28/12/15.
//
#ifndef COCKATRICE_UPDATECHECKER_H
#define COCKATRICE_UPDATECHECKER_H
#include <QObject>
#include <QUrl>
#include <QDate>
#include <QtNetwork>
class UpdateChecker : public QObject {
Q_OBJECT
public:
UpdateChecker(QObject *parent);
~UpdateChecker();
void check();
signals:
void finishedCheck(bool needToUpdate, bool isCompatible, QVariantMap *build);
void error(QString errorString);
private:
static QVariantMap *findCompatibleBuild();
static QDate findOldestBuild(QVariantList allBuilds);
static QDate dateFromBuild(QVariant build);
static QVariantMap *findCompatibleBuild(QVariantList allBuilds);
static bool downloadMatchesCurrentOS(QVariant build);
QVariantMap *build;
QUrl latestFilesUrl;
QDate buildDate;
QNetworkAccessManager *netMan;
QNetworkReply *response;
private slots:
void fileListFinished();
};
#endif //COCKATRICE_UPDATECHECKER_H

View file

@ -13,7 +13,6 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl) {
response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, SIGNAL(finished()), this, SLOT(fileFinished()));
connect(response, SIGNAL(readyRead()), this, SLOT(fileReadyRead()));
connect(response, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
connect(response, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
}

View file

@ -51,7 +51,6 @@
#include "settingscache.h"
#include "tab_game.h"
#include "version_string.h"
#include "update_checker.h"
#include "carddatabase.h"
#include "window_sets.h"
#include "dlg_edit_tokens.h"

View file

@ -29,7 +29,6 @@
#include "abstractclient.h"
#include "pb/response.pb.h"
#include "update_checker.h"
class TabSupervisor;
class RemoteClient;

View file

@ -23,6 +23,8 @@ SET(oracle_SOURCES
../cockatrice/src/settings/layoutssettings.cpp
../cockatrice/src/thememanager.cpp
../cockatrice/src/qt-json/json.cpp
../cockatrice/src/releasechannel.cpp
${VERSION_STRING_CPP}
)
set(oracle_RESOURCES oracle.qrc)