/* 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; } }