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