diff --git a/cmake/createversionfile.cmake b/cmake/createversionfile.cmake index 85c29e72..b27a2e4f 100644 --- a/cmake/createversionfile.cmake +++ b/cmake/createversionfile.cmake @@ -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} ) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 2ecb5293..458cdc8f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -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} ) diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 753bb1e8..856f5a72 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -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 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("%2").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")); } diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index 52d3c9ba..c331be89 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -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; diff --git a/cockatrice/src/dlg_update.cpp b/cockatrice/src/dlg_update.cpp index 7214babb..8c67e83d 100644 --- a/cockatrice/src/dlg_update.cpp +++ b/cockatrice/src/dlg_update.cpp @@ -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 #include #include @@ -16,12 +10,15 @@ #include #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:
%1
published on %2 ." + "
More informations are available on the release changelog" + "
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:
%1
published on %2 ." + "
More informations are available on the release changelog" + "
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())); } } diff --git a/cockatrice/src/dlg_update.h b/cockatrice/src/dlg_update.h index 9baa3786..67f1426d 100644 --- a/cockatrice/src/dlg_update.h +++ b/cockatrice/src/dlg_update.h @@ -4,8 +4,8 @@ #include #include -#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; }; diff --git a/cockatrice/src/releasechannel.cpp b/cockatrice/src/releasechannel.cpp new file mode 100644 index 00000000..d3b6ddda --- /dev/null +++ b/cockatrice/src/releasechannel.cpp @@ -0,0 +1,312 @@ +#include "releasechannel.h" +#include "qt-json/json.h" +#include "version_string.h" + +#include +#include +#include + +#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 + +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(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(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(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(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(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); +} diff --git a/cockatrice/src/releasechannel.h b/cockatrice/src/releasechannel.h new file mode 100644 index 00000000..790d90fe --- /dev/null +++ b/cockatrice/src/releasechannel.h @@ -0,0 +1,93 @@ +#ifndef RELEASECHANNEL_H +#define RELEASECHANNEL_H + +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 857f7094..349d3758 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -1,4 +1,6 @@ #include "settingscache.h" +#include "releasechannel.h" + #include #include #include @@ -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(); @@ -642,4 +650,10 @@ void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate) { notifyAboutUpdates = _notifyaboutupdate; settings->setValue("personal/updatenotification", notifyAboutUpdates); -} \ No newline at end of file +} + +void SettingsCache::setUpdateReleaseChannel(int _updateReleaseChannel) +{ + updateReleaseChannel = _updateReleaseChannel; + settings->setValue("personal/updatereleasechannel", updateReleaseChannel); +} diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 63ad9622..dbc16d87 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -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 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 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; diff --git a/cockatrice/src/update_checker.cpp b/cockatrice/src/update_checker.cpp deleted file mode 100644 index 3e60a1b0..00000000 --- a/cockatrice/src/update_checker.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include -#include - -#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 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()); - } -} diff --git a/cockatrice/src/update_checker.h b/cockatrice/src/update_checker.h deleted file mode 100644 index 3ba044be..00000000 --- a/cockatrice/src/update_checker.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by miguel on 28/12/15. -// - -#ifndef COCKATRICE_UPDATECHECKER_H -#define COCKATRICE_UPDATECHECKER_H - -#include -#include -#include -#include - -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 diff --git a/cockatrice/src/update_downloader.cpp b/cockatrice/src/update_downloader.cpp index 2427814e..362514b0 100644 --- a/cockatrice/src/update_downloader.cpp +++ b/cockatrice/src/update_downloader.cpp @@ -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))); } diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 86069c73..11a69852 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -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" diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index ff77a44d..abeaa531 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -29,7 +29,6 @@ #include "abstractclient.h" #include "pb/response.pb.h" -#include "update_checker.h" class TabSupervisor; class RemoteClient; diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 831bbce1..4a53111a 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -23,7 +23,9 @@ 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)