Added Updater class.
This commit is contained in:
parent
37f2293156
commit
9892a9f29b
@ -6,5 +6,6 @@
|
|||||||
<file>images/STIconGreen.png</file>
|
<file>images/STIconGreen.png</file>
|
||||||
<file>images/STIconOrange.png</file>
|
<file>images/STIconOrange.png</file>
|
||||||
<file>images/STIconRed.png</file>
|
<file>images/STIconRed.png</file>
|
||||||
|
<file>version.json</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -41,7 +41,8 @@ SOURCES += main.cpp\
|
|||||||
WebTranslatorProxy.cpp \
|
WebTranslatorProxy.cpp \
|
||||||
TranslatorHelper.cpp \
|
TranslatorHelper.cpp \
|
||||||
RecognizerHelper.cpp \
|
RecognizerHelper.cpp \
|
||||||
Utils.cpp
|
Utils.cpp \
|
||||||
|
Updater.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
Manager.h \
|
Manager.h \
|
||||||
@ -59,7 +60,8 @@ HEADERS += \
|
|||||||
StAssert.h \
|
StAssert.h \
|
||||||
TranslatorHelper.h \
|
TranslatorHelper.h \
|
||||||
RecognizerHelper.h \
|
RecognizerHelper.h \
|
||||||
Utils.h
|
Utils.h \
|
||||||
|
Updater.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
SettingsEditor.ui \
|
SettingsEditor.ui \
|
||||||
@ -76,6 +78,7 @@ TRANSLATIONS += \
|
|||||||
OTHER_FILES += \
|
OTHER_FILES += \
|
||||||
app.rc \
|
app.rc \
|
||||||
images/icon.ico \
|
images/icon.ico \
|
||||||
|
version.json \
|
||||||
README.md \
|
README.md \
|
||||||
uncrustify.cfg\
|
uncrustify.cfg\
|
||||||
translators/google.js \
|
translators/google.js \
|
||||||
|
263
Updater.cpp
Normal file
263
Updater.cpp
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "Updater.h"
|
||||||
|
#include "StAssert.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#define FIELD(NAME) const QString _ ## NAME = #NAME
|
||||||
|
FIELD (Application);
|
||||||
|
|
||||||
|
FIELD (name);
|
||||||
|
FIELD (version);
|
||||||
|
FIELD (compatibleVersion);
|
||||||
|
FIELD (built_in);
|
||||||
|
FIELD (versionString);
|
||||||
|
FIELD (permissions);
|
||||||
|
FIELD (url);
|
||||||
|
FIELD (path);
|
||||||
|
#undef FIELD
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
const QString _platform = "_win";
|
||||||
|
#elif defined(Q_OS_LINUX)
|
||||||
|
const QString _platform = "_linux";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString versionField (const QJsonObject &component, const QString &field) {
|
||||||
|
return component.contains (field + _platform)
|
||||||
|
? component[field + _platform].toString ()
|
||||||
|
: component[field].toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo fileDir (const QString &fileName) {
|
||||||
|
return QFileInfo (fileName).absolutePath ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater::Updater (QObject *parent)
|
||||||
|
: QObject (parent),
|
||||||
|
network_ (new QNetworkAccessManager (this)),
|
||||||
|
componentsUpdating_ (0) {
|
||||||
|
updatesFileName_ = "updates.json";
|
||||||
|
backupSuffix_ = "_backup";
|
||||||
|
connect (network_, SIGNAL (finished (QNetworkReply *)),
|
||||||
|
SLOT (replyFinished (QNetworkReply *)));
|
||||||
|
|
||||||
|
getCurrentVersion ();
|
||||||
|
updateCurrentVersion ();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime Updater::nextCheckTime (const QDateTime &lastCheckTime, int updateType) const {
|
||||||
|
QDateTime nextTime;
|
||||||
|
switch (updateType) {
|
||||||
|
case UpdateTypeDaily:
|
||||||
|
nextTime = lastCheckTime.addDays (1);
|
||||||
|
break;
|
||||||
|
case UpdateTypeWeekly:
|
||||||
|
nextTime = lastCheckTime.addDays (7);
|
||||||
|
break;
|
||||||
|
case UpdateTypeMonthly:
|
||||||
|
nextTime = lastCheckTime.addDays (30);
|
||||||
|
break;
|
||||||
|
case UpdateTypeNever:
|
||||||
|
default:
|
||||||
|
return QDateTime ();
|
||||||
|
}
|
||||||
|
if (nextTime < QDateTime::currentDateTime ()) {
|
||||||
|
return QDateTime::currentDateTime ().addSecs (5);
|
||||||
|
}
|
||||||
|
return nextTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::getCurrentVersion () {
|
||||||
|
QFile f (":/version.json");
|
||||||
|
if (f.open (QFile::ReadOnly)) {
|
||||||
|
currentVersion_ = QJsonDocument::fromJson (f.readAll ()).object ();
|
||||||
|
f.close ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit error (tr ("Ошибка определения текущей версии. Обновление недоступно."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::updateCurrentVersion () {
|
||||||
|
QFile f (updatesFileName_);
|
||||||
|
if (!f.open (QFile::ReadOnly)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object ();
|
||||||
|
f.close ();
|
||||||
|
foreach (const QString &component, updated.keys ()) {
|
||||||
|
QJsonObject current = currentVersion_[component].toObject ();
|
||||||
|
int updatedVersion = updated[component].toInt ();
|
||||||
|
if (current[_built_in].toBool () || current[_version].toInt () >= updatedVersion) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current[_version] = updatedVersion;
|
||||||
|
currentVersion_[component] = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Updater::currentAppVersion () const {
|
||||||
|
return currentVersion_[_Application].toObject ()[_versionString].toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::checkForUpdates () {
|
||||||
|
getAvailableVersion ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::getAvailableVersion () {
|
||||||
|
QNetworkRequest request (versionField (currentVersion_, _url));
|
||||||
|
request.setAttribute (QNetworkRequest::User, _version);
|
||||||
|
network_->get (request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::replyFinished (QNetworkReply *reply) {
|
||||||
|
if (reply->error () != QNetworkReply::NoError) {
|
||||||
|
emit tr ("Ошибка загрузки информации для обновления.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray content = reply->readAll ();
|
||||||
|
QString component = reply->request ().attribute (QNetworkRequest::User).toString ();
|
||||||
|
if (component == _version) {
|
||||||
|
availableVersion_ = QJsonDocument::fromJson (content).object ();
|
||||||
|
parseAvailableVersion ();
|
||||||
|
}
|
||||||
|
else if (availableVersion_.contains (component) && !content.isEmpty ()) {
|
||||||
|
installComponent (component, content);
|
||||||
|
}
|
||||||
|
reply->deleteLater ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::parseAvailableVersion () {
|
||||||
|
QStringList inaccessible, incompatible;
|
||||||
|
QStringList updateList;
|
||||||
|
QDir currentDir;
|
||||||
|
foreach (const QString &component, availableVersion_.keys ()) {
|
||||||
|
QJsonObject available = availableVersion_[component].toObject ();
|
||||||
|
QJsonObject current = currentVersion_[component].toObject ();
|
||||||
|
QString path = versionField (available, _path);
|
||||||
|
if (path.isEmpty ()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo installDir = fileDir (path);
|
||||||
|
if (!installDir.exists ()) {
|
||||||
|
currentDir.mkpath (installDir.absoluteFilePath ());
|
||||||
|
}
|
||||||
|
if (!installDir.isWritable ()) { // check dir because install = remove + make new
|
||||||
|
inaccessible << installDir.absoluteFilePath ();
|
||||||
|
}
|
||||||
|
if (current[_version].toInt () < available[_compatibleVersion].toInt ()) {
|
||||||
|
incompatible << component;
|
||||||
|
}
|
||||||
|
if (!QFile::exists (path) || current[_version].toInt () < available[_version].toInt ()) {
|
||||||
|
updateList << component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateList.isEmpty ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo updateFileDir = fileDir (updatesFileName_);
|
||||||
|
if (!updateFileDir.isWritable ()) {
|
||||||
|
inaccessible << updateFileDir.absoluteFilePath ();
|
||||||
|
}
|
||||||
|
inaccessible.removeDuplicates ();
|
||||||
|
|
||||||
|
QString message = tr ("Доступно обновлений: %1.\n").arg (updateList.size ());
|
||||||
|
QMessageBox::StandardButtons buttons = QMessageBox::Ok;
|
||||||
|
if (!incompatible.isEmpty ()) {
|
||||||
|
message += tr ("Несовместимых обновлений: %1.\nВыполните обновление вручную.")
|
||||||
|
.arg (incompatible.size ());
|
||||||
|
}
|
||||||
|
else if (!inaccessible.isEmpty ()) {
|
||||||
|
message += tr ("Недоступных для записи директорий: %1.\n%2\nИзмените права доступа и "
|
||||||
|
"повторите попытку или выполните обновление вручную.")
|
||||||
|
.arg (inaccessible.size ()).arg (inaccessible.join ("\n"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message += tr ("Обновить?");
|
||||||
|
buttons = QMessageBox::Yes | QMessageBox::No;
|
||||||
|
}
|
||||||
|
int result = QMessageBox::question (NULL, tr ("Обновление"), message, buttons);
|
||||||
|
if (result == QMessageBox::Yes) {
|
||||||
|
componentsUpdating_ = updateList.size ();
|
||||||
|
foreach (const QString &component, updateList) {
|
||||||
|
getComponent (component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::getComponent (const QString &component) {
|
||||||
|
QJsonObject available = availableVersion_[component].toObject ();
|
||||||
|
QString path = versionField (available, _path);
|
||||||
|
if (path.isEmpty ()) {
|
||||||
|
--componentsUpdating_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString url = versionField (available, _url);
|
||||||
|
if (url.isEmpty ()) { // just remove component
|
||||||
|
installComponent (component, QByteArray ());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QNetworkRequest request (url);
|
||||||
|
request.setAttribute (QNetworkRequest::User, component);
|
||||||
|
network_->get (request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::installComponent (const QString &component, const QByteArray &newContent) {
|
||||||
|
--componentsUpdating_;
|
||||||
|
ST_ASSERT (availableVersion_.contains (component));
|
||||||
|
QJsonObject available = availableVersion_[component].toObject ();
|
||||||
|
QString path = versionField (available, _path);
|
||||||
|
ST_ASSERT (!path.isEmpty ());
|
||||||
|
|
||||||
|
QString backup = path + backupSuffix_;
|
||||||
|
QFile::remove (backup);
|
||||||
|
QFile::rename (path, backup);
|
||||||
|
|
||||||
|
if (!newContent.isEmpty ()) {
|
||||||
|
QFile f (path);
|
||||||
|
if (!f.open (QFile::WriteOnly)) {
|
||||||
|
emit error (tr ("Ошибка обновления файла (%1).").arg (path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
f.write (newContent);
|
||||||
|
f.close ();
|
||||||
|
bool ok;
|
||||||
|
QFileDevice::Permissions perm (available[_permissions].toString ().toUInt (&ok, 16));
|
||||||
|
if (ok) {
|
||||||
|
f.setPermissions (perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateVersionInfo (component, available[_version].toInt ());
|
||||||
|
|
||||||
|
if (componentsUpdating_ == 0) {
|
||||||
|
emit updated ();
|
||||||
|
QString message = tr ("Обновление завершено. Для активации некоторых компонентов "
|
||||||
|
"может потребоваться перезапуск.");
|
||||||
|
QMessageBox::information (NULL, tr ("Обновление"), message, QMessageBox::Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::updateVersionInfo (const QString &component, int version) {
|
||||||
|
QFile f (updatesFileName_);
|
||||||
|
if (!f.open (QFile::ReadWrite)) {
|
||||||
|
emit error (tr ("Ошибка обновления файла с текущей версией."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject updated = QJsonDocument::fromJson (f.readAll ()).object ();
|
||||||
|
updated[component] = version;
|
||||||
|
f.seek (0);
|
||||||
|
f.write (QJsonDocument (updated).toJson ());
|
||||||
|
f.close ();
|
||||||
|
}
|
65
Updater.h
Normal file
65
Updater.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#ifndef UPDATER_H
|
||||||
|
#define UPDATER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The Updater class.
|
||||||
|
*
|
||||||
|
* Allows to download and copy files from remote source to local machine.
|
||||||
|
*/
|
||||||
|
class Updater : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum UpdateType {
|
||||||
|
UpdateTypeNever, UpdateTypeDaily, UpdateTypeWeekly, UpdateTypeMonthly
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Updater (QObject *parent = 0);
|
||||||
|
|
||||||
|
QString currentAppVersion () const;
|
||||||
|
|
||||||
|
//! Initiate updates check.
|
||||||
|
void checkForUpdates ();
|
||||||
|
|
||||||
|
//! Get nearest update check time based on given settings.
|
||||||
|
QDateTime nextCheckTime (const QDateTime &lastCheckTime, int updateType) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void error (const QString &message);
|
||||||
|
//! Emited after all components updated.
|
||||||
|
void updated ();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
//! Handle remote downloads finish.
|
||||||
|
void replyFinished (QNetworkReply *reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Load current version info (built-in).
|
||||||
|
void getCurrentVersion ();
|
||||||
|
//! Update current version info with information about preformed updates.
|
||||||
|
void updateCurrentVersion ();
|
||||||
|
//! Load latest available version info from remote source.
|
||||||
|
void getAvailableVersion ();
|
||||||
|
//! Check is updates available, prompt user and start update.
|
||||||
|
void parseAvailableVersion ();
|
||||||
|
//! Start update of given component.
|
||||||
|
void getComponent (const QString &component);
|
||||||
|
//! Finalize update of given component with given new content.
|
||||||
|
void installComponent (const QString &component, const QByteArray &newContent);
|
||||||
|
//! Save information about component update on disk (for updateCurrentVersion()).
|
||||||
|
void updateVersionInfo (const QString &component, int version);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QNetworkAccessManager *network_;
|
||||||
|
QJsonObject availableVersion_;
|
||||||
|
QJsonObject currentVersion_;
|
||||||
|
QString updatesFileName_;
|
||||||
|
int componentsUpdating_;
|
||||||
|
QString backupSuffix_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UPDATER_H
|
30
version.json
Normal file
30
version.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"url": "https://cdn.rawgit.com/OneMoreGres/ScreenTranslator/master/version.json",
|
||||||
|
"Application": {
|
||||||
|
"version": 1,
|
||||||
|
"compatibleVersion": 1,
|
||||||
|
"built_in": true,
|
||||||
|
"versionString": "2.0.0",
|
||||||
|
"permissions": "0x7755",
|
||||||
|
"url_win": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/releases/2.0.0/ScreenTranslator.exe",
|
||||||
|
"path_win": "ScreenTranslator.exe",
|
||||||
|
"url_linux": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/releases/2.0.0/ScreenTranslator",
|
||||||
|
"path_linux": "ScreenTranslator"
|
||||||
|
},
|
||||||
|
"Bing translator": {
|
||||||
|
"version": 1,
|
||||||
|
"url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/bing.js",
|
||||||
|
"path": "translators/bing.js"
|
||||||
|
},
|
||||||
|
"Google translator": {
|
||||||
|
"version": 1,
|
||||||
|
"url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google.js",
|
||||||
|
"path": "translators/google.js"
|
||||||
|
},
|
||||||
|
"Yandex translator": {
|
||||||
|
"version": 1,
|
||||||
|
"url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/yandex.js",
|
||||||
|
"path": "translators/yandex.js"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user