diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp new file mode 100644 --- /dev/null +++ b/src/updatechecker.cpp @@ -0,0 +1,222 @@ +/* + Copyright © 2017 Hasan Yavuz Özderya + + This file is part of serialplot. + + serialplot is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + serialplot is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with serialplot. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "updatechecker.h" + +// This link returns the list of downloads in JSON format. Note that we only use +// the first page because results are sorted new to old. +const char BB_DOWNLOADS_URL[] = "https://api.bitbucket.org/2.0/repositories/hyozd/serialplot/downloads?fields=values.name,values.links.self.href"; + +UpdateChecker::UpdateChecker(QObject *parent) : + QObject(parent), nam(this) +{ + activeReply = NULL; + + connect(&nam, &QNetworkAccessManager::finished, + this, &UpdateChecker::onReqFinished); +} + +bool UpdateChecker::isChecking() const +{ + return activeReply != NULL && !activeReply->isFinished(); +} + +void UpdateChecker::checkUpdate() +{ + if (isChecking()) return; + + auto req = QNetworkRequest(QUrl(BB_DOWNLOADS_URL)); + activeReply = nam.get(req); +} + +void UpdateChecker::cancelCheck() +{ + if (activeReply != NULL) activeReply->abort(); +} + +void UpdateChecker::onReqFinished(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + emit checkFailed(QString("Network error: ") + reply->errorString()); + } + else + { + QJsonParseError error; + auto data = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + emit checkFailed(QString("JSon parsing error: ") + error.errorString()); + } + else + { + QList files; + if (!parseData(data, files)) + { + // TODO: emit detailed data contents for logging + emit checkFailed("Data parsing error."); + } + else + { + FileInfo updateFile; + if (findUpdate(files, updateFile)) + { + emit checkFinished( + true, updateFile.version.toString(), updateFile.link); + } + else + { + emit checkFinished(false, "", ""); + } + } + } + } + reply->deleteLater(); + activeReply = NULL; +} + +bool UpdateChecker::parseData(const QJsonDocument& data, QList& files) const +{ + /* Data is expected to be in this form: + + { + "values": [ + { + "name": "serialplot-0.9.1-x86_64.AppImage", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/hyOzd/serialplot/downloads/serialplot-0.9.1-x86_64.AppImage" + } + } + }, ... ] + } + */ + + if (!data.isObject()) return false; + + auto values = data.object()["values"]; + if (values == QJsonValue::Undefined || !values.isArray()) return false; + + for (auto value : values.toArray()) + { + if (!value.isObject()) return false; + + auto name = value.toObject().value("name"); + if (name.isUndefined() || !name.isString()) + return false; + + auto links = value.toObject().value("links"); + if (links.isUndefined() || !links.isObject()) + return false; + + auto self = links.toObject().value("self"); + if (self.isUndefined() || !self.isObject()) + return false; + + auto href = self.toObject().value("href"); + if (href.isUndefined() || !href.isString()) + return false; + + FileInfo finfo; + finfo.name = name.toString(); + finfo.link = href.toString(); + finfo.hasVersion = VersionNumber::extract(name.toString(), finfo.version); + + if (finfo.name.contains("amd64") || + finfo.name.contains("x86_64") || + finfo.name.contains("win64")) + { + finfo.arch = FileArch::amd64; + } + else if (finfo.name.contains("win32") || + finfo.name.contains("i386")) + { + finfo.arch = FileArch::i386; + } + else + { + finfo.arch = FileArch::unknown; + } + + files += finfo; + } + + return true; +} + +bool UpdateChecker::findUpdate(const QList& files, FileInfo& foundFile) const +{ + QList fflist; + + // filter the file list according to extension and version number + for (int i = 0; i < files.length(); i++) + { + // file type to look +#if defined(Q_OS_WIN) + const char ext[] = ".exe"; +#else // of course linux + const char ext[] = ".appimage"; +#endif + + // file architecture to look +#if defined(Q_PROCESSOR_X86_64) + const FileArch arch = FileArch::amd64; +#elif defined(Q_PROCESSOR_X86_32) + const FileArch arch = FileArch::i386; +#elif defined(Q_PROCESSOR_ARM) + const FileArch arch = FileArch::arm; +#else + #error Unknown architecture for update file detection. +#endif + + // filter the file list + auto file = files[i]; + if (file.name.contains(ext, Qt::CaseInsensitive) && + file.arch == arch && + file.hasVersion && file.version > CurrentVersion) + { + fflist += file; + } + } + + // sort and find most up to date file + if (!fflist.empty()) + { + std::sort(fflist.begin(), fflist.end(), + [](const FileInfo& a, const FileInfo& b) + { + return a.version > b.version; + }); + + foundFile = fflist[0]; + return true; + } + else + { + return false; + } +}