aboutsummaryrefslogtreecommitdiffstats
path: root/src/m_ver_check/UpdateChecker.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/m_ver_check/UpdateChecker.h')
-rw-r--r--src/m_ver_check/UpdateChecker.h124
1 files changed, 124 insertions, 0 deletions
diff --git a/src/m_ver_check/UpdateChecker.h b/src/m_ver_check/UpdateChecker.h
new file mode 100644
index 0000000..51a3ede
--- /dev/null
+++ b/src/m_ver_check/UpdateChecker.h
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend 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.
+ *
+ * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <QByteArray>
+#include <QDateTime>
+#include <QObject>
+#include <QUrl>
+#include <functional>
+#include <optional>
+
+#include "SoftwareVersion.h"
+
+/**
+ * @brief Everything the update UI needs to know, readable at any moment.
+ *
+ * A failed attempt never replaces what was known: `last_good` is the last
+ * result whose release list came back, and the attempt fields say how the
+ * most recent try went.
+ */
+struct UpdateSnapshot {
+ bool checking = false;
+ std::optional<SoftwareVersion> last_good;
+ QDateTime last_attempt;
+ bool last_attempt_failed = false;
+};
+
+enum class CheckMode {
+ kIfStale, ///< skip when a recent authoritative result for this build exists
+ kForce, ///< always ask the server (the user pressed the button)
+};
+
+/**
+ * @brief The one update check of the process.
+ *
+ * Every caller shares it: a Start() while a check is running joins that check
+ * instead of starting another. The transport, the store and the clock are
+ * handed in, so the whole state machine runs in a test without a network, a
+ * Host or an event loop.
+ */
+class UpdateChecker : public QObject {
+ Q_OBJECT
+
+ public:
+ /// Answers a GET: the HTTP status (0 when there was none) and the body.
+ using Reply = std::function<void(int http_status, const QByteArray& body)>;
+ using Fetcher = std::function<void(const QUrl& url, Reply reply)>;
+ using Clock = std::function<QDateTime()>;
+
+ struct Store {
+ std::function<QByteArray()> load;
+ std::function<void(const QByteArray&)> save;
+ };
+
+ struct Build {
+ QString version;
+ QString commit;
+ };
+
+ static constexpr qint64 kFreshSeconds = 24 * 60 * 60;
+ static constexpr auto kRepoApi =
+ "https://api.github.com/repos/saturneric/gpgfrontend";
+
+ UpdateChecker(Build build, Fetcher fetch, Store store, Clock clock = {},
+ QObject* parent = nullptr);
+
+ [[nodiscard]] auto State() const -> const UpdateSnapshot&;
+
+ /// Starts a check, joins the running one, or (kIfStale) does nothing.
+ void Start(CheckMode mode);
+
+ /// A recent authoritative result for this build: no startup check needed.
+ [[nodiscard]] auto IsFresh() const -> bool;
+
+ /// The state as stored; FromStorage() reads it back for @p build only.
+ [[nodiscard]] static auto ToStorage(const UpdateSnapshot& s) -> QByteArray;
+ [[nodiscard]] static auto FromStorage(const QByteArray& data,
+ const Build& build) -> UpdateSnapshot;
+
+ signals:
+ /// Any change of State(), including the start of a check.
+ void Changed();
+
+ private:
+ void finish();
+
+ Build build_;
+ Fetcher fetch_;
+ Store store_;
+ Clock clock_;
+
+ UpdateSnapshot state_;
+ SoftwareVersion fresh_;
+ int pending_ = 0;
+};
+
+/// Whether the startup check should open the update dialog now.
+auto ShouldPromptOnStartup(const UpdateSnapshot& s) -> bool;