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;
+ }
+}