keepassxc

Форк
0
/
UpdateChecker.cpp 
176 строк · 5.2 Кб
1
/*
2
 *  Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "UpdateChecker.h"
19

20
#include "config-keepassx.h"
21
#include "core/Clock.h"
22
#include "core/Config.h"
23
#include "core/NetworkManager.h"
24

25
#include <QJsonArray>
26
#include <QJsonDocument>
27
#include <QJsonObject>
28
#include <QNetworkReply>
29
#include <QRegularExpression>
30

31
const QString UpdateChecker::ErrorVersion("error");
32
UpdateChecker* UpdateChecker::m_instance(nullptr);
33

34
UpdateChecker::UpdateChecker(QObject* parent)
35
    : QObject(parent)
36
    , m_reply(nullptr)
37
    , m_isManuallyRequested(false)
38
{
39
}
40

41
UpdateChecker::~UpdateChecker()
42
{
43
}
44

45
void UpdateChecker::checkForUpdates(bool manuallyRequested)
46
{
47
    // Skip update if we are already performing one
48
    if (m_reply) {
49
        return;
50
    }
51

52
    auto nextCheck = config()->get(Config::GUI_CheckForUpdatesNextCheck).toULongLong();
53
    m_isManuallyRequested = manuallyRequested;
54

55
    if (m_isManuallyRequested || Clock::currentSecondsSinceEpoch() >= nextCheck) {
56
        m_bytesReceived.clear();
57

58
        QString apiUrlStr = QString("https://api.github.com/repos/keepassxreboot/keepassxc/releases");
59

60
        if (!config()->get(Config::GUI_CheckForUpdatesIncludeBetas).toBool()) {
61
            apiUrlStr += "/latest";
62
        }
63

64
        QUrl apiUrl = QUrl(apiUrlStr);
65

66
        QNetworkRequest request(apiUrl);
67
        request.setRawHeader("Accept", "application/json");
68

69
        m_reply = getNetMgr()->get(request);
70

71
        connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished);
72
        connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead);
73
    }
74
}
75

76
void UpdateChecker::fetchReadyRead()
77
{
78
    m_bytesReceived += m_reply->readAll();
79
}
80

81
void UpdateChecker::fetchFinished()
82
{
83
    bool error = (m_reply->error() != QNetworkReply::NoError);
84
    bool hasNewVersion = false;
85
    QUrl url = m_reply->url();
86
    QString version = "";
87

88
    m_reply->deleteLater();
89
    m_reply = nullptr;
90

91
    if (!error) {
92
        QJsonDocument jsonResponse = QJsonDocument::fromJson(m_bytesReceived);
93
        QJsonObject jsonObject = jsonResponse.object();
94

95
        if (config()->get(Config::GUI_CheckForUpdatesIncludeBetas).toBool()) {
96
            QJsonArray jsonArray = jsonResponse.array();
97
            jsonObject = jsonArray.at(0).toObject();
98
        }
99

100
        if (!jsonObject.value("tag_name").isUndefined()) {
101
            version = jsonObject.value("tag_name").toString();
102
            hasNewVersion = compareVersions(QString(KEEPASSXC_VERSION), version);
103
        }
104

105
        // Check again in 7 days
106
        // TODO: change to toSecsSinceEpoch() when min Qt >= 5.8
107
        config()->set(Config::GUI_CheckForUpdatesNextCheck, Clock::currentDateTime().addDays(7).toTime_t());
108
    } else {
109
        version = ErrorVersion;
110
    }
111

112
    emit updateCheckFinished(hasNewVersion, version, m_isManuallyRequested);
113
}
114

115
bool UpdateChecker::compareVersions(const QString& localVersion, const QString& remoteVersion)
116
{
117
    // Quick full-string equivalence check
118
    if (localVersion == remoteVersion) {
119
        return false;
120
    }
121

122
    QRegularExpression verRegex(R"(^((?:\d+\.){2}\d+)(?:-(\w+?)(\d+)?)?$)");
123

124
    auto lmatch = verRegex.match(localVersion);
125
    auto rmatch = verRegex.match(remoteVersion);
126

127
    auto lVersion = lmatch.captured(1).split(".");
128
    auto lSuffix = lmatch.captured(2);
129
    auto lBetaNum = lmatch.captured(3);
130

131
    auto rVersion = rmatch.captured(1).split(".");
132
    auto rSuffix = rmatch.captured(2);
133
    auto rBetaNum = rmatch.captured(3);
134

135
    if (!lVersion.isEmpty() && !rVersion.isEmpty()) {
136
        if (lSuffix.compare("snapshot", Qt::CaseInsensitive) == 0) {
137
            // Snapshots are not checked for version updates
138
            return false;
139
        }
140

141
        // Check "-beta[X]" versions
142
        if (lVersion == rVersion && !lSuffix.isEmpty()) {
143
            // Check if stable version has been released or new beta is available
144
            // otherwise the version numbers are equal
145
            return rSuffix.isEmpty() || lBetaNum.toInt() < rBetaNum.toInt();
146
        }
147

148
        for (int i = 0; i < 3; i++) {
149
            int l = lVersion[i].toInt();
150
            int r = rVersion[i].toInt();
151

152
            if (l == r) {
153
                continue;
154
            }
155

156
            if (l > r) {
157
                return false; // Installed version is newer than release
158
            } else {
159
                return true; // Installed version is outdated
160
            }
161
        }
162

163
        return false; // Installed version is the same
164
    }
165

166
    return false; // Invalid version string
167
}
168

169
UpdateChecker* UpdateChecker::instance()
170
{
171
    if (!m_instance) {
172
        m_instance = new UpdateChecker();
173
    }
174

175
    return m_instance;
176
}
177

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.