1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 */
17 
18 #include "checkupdatesdialog.h"
19 #include <QNetworkReply>
20 #include <QNetworkAccessManager>
21 #include <QHBoxLayout>
22 #include <QVBoxLayout>
23 #include <QLabel>
24 #include <QProgressBar>
25 #include <QPushButton>
26 #include <QDesktopServices>
27 #include <QVersionNumber>
28 #include <QXmlStreamReader>
29 
30 #include "util.h"
31 
32 
CheckUpdatesDialog()33 CheckUpdatesDialog::CheckUpdatesDialog()
34 {
35     Qt::WindowFlags eFlags = Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint;
36     setWindowFlags(eFlags);
37     setMinimumSize(QSize(400, 150));
38 
39     QLabel* logoLabel = new QLabel;
40     logoLabel->setPixmap(QPixmap(":/icons/logo.png"));
41     logoLabel->setFixedSize(QSize(72, 72));
42 
43     mTitleLabel = new QLabel;
44     mTitleLabel->setText(tr("Checking for Updates...", "status description in the check-for-update dialog"));
45 
46     mDetailLabel = new QLabel;
47     mDetailLabel->setWordWrap(true);
48 
49     //If minimum and maximum both are set to 0, the bar shows a busy indicator instead of a percentage of steps.
50     mProgressBar = new QProgressBar;
51     mProgressBar->setMaximum(0);
52     mProgressBar->setMinimum(0);
53     mProgressBar->setValue(0);
54     mProgressBar->setTextVisible(false);
55 
56     mDownloadButton = new QPushButton(tr("Download"));
57     mCloseButton = new QPushButton(tr("Close"));
58 
59     QHBoxLayout* hButtonLayout = new QHBoxLayout;
60     hButtonLayout->addWidget(mDownloadButton);
61     hButtonLayout->addWidget(mCloseButton);
62 
63     QVBoxLayout* vLayout = new QVBoxLayout;
64     vLayout->addWidget(mTitleLabel);
65     vLayout->addWidget(mDetailLabel);
66     vLayout->addWidget(mProgressBar);
67     vLayout->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));
68     vLayout->addLayout(hButtonLayout);
69 
70     QHBoxLayout* mainLayout = new QHBoxLayout;
71     mainLayout->addWidget(logoLabel);
72     mainLayout->addLayout(vLayout);
73     setLayout(mainLayout);
74 
75     mDownloadButton->setDisabled(true);
76 
77     connect(mDownloadButton, &QPushButton::clicked, this, &CheckUpdatesDialog::gotoDownloadPage);
78     connect(mCloseButton, &QPushButton::clicked, this, &CheckUpdatesDialog::closeDialog);
79 }
80 
~CheckUpdatesDialog()81 CheckUpdatesDialog::~CheckUpdatesDialog()
82 {
83     mNetworkManager->deleteLater();
84 }
85 
startChecking()86 void CheckUpdatesDialog::startChecking()
87 {
88 #ifdef NIGHTLY
89     nightlyBuildCheck();
90 #else
91     regularBuildCheck();
92 #endif
93 }
94 
regularBuildCheck()95 void CheckUpdatesDialog::regularBuildCheck()
96 {
97     mNetworkManager = new QNetworkAccessManager(this);
98     QUrl url("https://github.com/pencil2d/pencil/releases.atom");
99 
100     QNetworkRequest req;
101     req.setUrl(url);
102     req.setRawHeader("User-Agent", "Mozilla Firefox");
103 
104     mNetworkManager->get(req);
105     connect(mNetworkManager, &QNetworkAccessManager::finished, this, &CheckUpdatesDialog::networkRequestFinished);
106 }
107 
nightlyBuildCheck()108 void CheckUpdatesDialog::nightlyBuildCheck()
109 {
110     mTitleLabel->setText(tr("<b>You are using a Pencil2D nightly build</b>"));
111     mDetailLabel->setText(tr("Please go %1 here %2 to check new nightly builds.")
112                           .arg("<a href=\"https://www.pencil2d.org/download/#nightlybuild\">").arg("</a>"));
113     mDetailLabel->setOpenExternalLinks(true);
114     mProgressBar->setRange(0, 1);
115     mProgressBar->setValue(1);
116     mDownloadButton->setEnabled(false);
117 }
118 
networkErrorHappened()119 void CheckUpdatesDialog::networkErrorHappened()
120 {
121     mTitleLabel->setText(tr("<b>An error occurred while checking for updates</b>", "error msg of check-for-update"));
122     mDetailLabel->setText(tr("Please check your internet connection and try again later.", "error msg of check-for-update"));
123     mProgressBar->setRange(0, 1);
124     mProgressBar->setValue(1);
125     mDownloadButton->setEnabled(false);
126 }
127 
networkResponseIsEmpty()128 void CheckUpdatesDialog::networkResponseIsEmpty()
129 {
130     mTitleLabel->setText(tr("<b>An error occurred while checking for updates</b>", "error msg of check-for-update"));
131     mDetailLabel->setText(tr("Network response is empty", "error msg of check-for-update"));
132     mProgressBar->setRange(0, 1);
133     mProgressBar->setValue(1);
134     mDownloadButton->setEnabled(false);
135 }
136 
invalidReleaseXml()137 void CheckUpdatesDialog::invalidReleaseXml()
138 {
139     mTitleLabel->setText(tr("<b>An error occurred while checking for updates</b>", "error msg of check-for-update"));
140     mDetailLabel->setText(tr("Couldn't retrieve the version information", "error msg of check-for-update"));
141     mProgressBar->setRange(0, 1);
142     mProgressBar->setValue(1);
143     mDownloadButton->setEnabled(false);
144 }
145 
networkRequestFinished(QNetworkReply * reply)146 void CheckUpdatesDialog::networkRequestFinished(QNetworkReply* reply)
147 {
148     reply->deleteLater();
149 
150     QNetworkReply::NetworkError errorCode = reply->error();
151     if (errorCode != QNetworkReply::NoError)
152     {
153         // show error message
154         qDebug() << reply->errorString();
155         networkErrorHappened();
156         return;
157     }
158 
159     auto releasesAtom = QString::fromUtf8(reply->readAll()).trimmed();
160     if (releasesAtom.isEmpty())
161     {
162         networkResponseIsEmpty();
163         return;
164     }
165 
166     QString latestVersionString = getVersionNumberFromXml(releasesAtom);
167     if (latestVersionString == "0.0.1")
168     {
169         invalidReleaseXml();
170         return;
171     }
172 
173     bool isNewVersionAvailable = compareVersion(APP_VERSION, latestVersionString);
174     if (isNewVersionAvailable)
175     {
176         mTitleLabel->setText(tr("<b>A new version of Pencil2D is available!</b>"));
177         mDetailLabel->setText(tr("Pencil2D %1 is now available -- you have %2. Would you like to download it?")
178                               .arg(latestVersionString)
179                               .arg(APP_VERSION));
180         mProgressBar->hide();
181         mDownloadButton->setEnabled(true);
182     }
183     else
184     {
185         mTitleLabel->setText(tr("<b>Pencil2D is up to date</b>"));
186         mDetailLabel->setText(tr("Version %1").arg(APP_VERSION));
187         mProgressBar->setRange(0, 1);
188         mProgressBar->setValue(1);
189         mDownloadButton->setEnabled(false);
190     }
191 }
192 
compareVersion(QString currentVersion,QString latestVersion)193 bool CheckUpdatesDialog::compareVersion(QString currentVersion, QString latestVersion)
194 {
195     return QVersionNumber::fromString(currentVersion) < QVersionNumber::fromString(latestVersion);
196 }
197 
getVersionNumberFromXml(QString xml)198 QString CheckUpdatesDialog::getVersionNumberFromXml(QString xml)
199 {
200     // XML source: https://github.com/pencil2d/pencil/releases.atom
201 
202     QXmlStreamReader xmlReader(xml);
203 
204     while (!xmlReader.atEnd() && !xmlReader.hasError())
205     {
206         QXmlStreamReader::TokenType tokenType = xmlReader.readNext();
207         if (tokenType == QXmlStreamReader::StartElement && xmlReader.name() == "entry")
208         {
209             while (!xmlReader.atEnd() && !xmlReader.hasError())
210             {
211                 xmlReader.readNext();
212                 if (xmlReader.name() == "title")
213                 {
214                     QString titleTag = xmlReader.readElementText();
215                     return titleTag.remove(QRegExp("^v")); // remove the leading 'v'
216                 }
217             }
218         }
219     }
220     if (xmlReader.error() != QXmlStreamReader::NoError)
221     {
222         qDebug() << xmlReader.errorString();
223         return "0.0.1";
224     }
225     return "0.0.1";
226 }
227 
gotoDownloadPage()228 void CheckUpdatesDialog::gotoDownloadPage()
229 {
230     QString url = "https://pencil2d.org/download";
231     QDesktopServices::openUrl(QUrl(url));
232 }
233 
closeDialog()234 void CheckUpdatesDialog::closeDialog()
235 {
236     done(QDialog::Accepted);
237 }
238