Updates refactoring

Install updates immediately after click
Separate loader and installer logic
This commit is contained in:
Gres 2021-04-08 23:11:21 +03:00
parent 9706ab1c12
commit 413cae80c4
10 changed files with 698 additions and 1073 deletions

View File

@ -616,74 +616,69 @@ Ctrl - продолжить выделять</translation>
<translation>Любой язык</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="42"/>
<location filename="../../src/manager.cpp" line="41"/>
<source>app</source>
<translation>приложение</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="43"/>
<location filename="../../src/manager.cpp" line="42"/>
<source>recognizers</source>
<translation>распознавание</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="44"/>
<location filename="../../src/manager.cpp" line="43"/>
<source>correction</source>
<translation>коррекция</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="45"/>
<location filename="../../src/manager.cpp" line="44"/>
<source>translators</source>
<translation>перевод</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="60"/>
<location filename="../../src/manager.cpp" line="59"/>
<source>Screen translator started</source>
<translation>Экранный переводчик запущен</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="68"/>
<source>Update completed</source>
<translation>Обновление завершено</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="72"/>
<location filename="../../src/manager.cpp" line="67"/>
<source>Updates available</source>
<translation>Доступны обновления</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="98"/>
<location filename="../../src/manager.cpp" line="95"/>
<source>Current version might be outdated.
Check for updates to silence this warning</source>
<translation>Текущая версия может быть устаревшей.
Проверьте обновления, чтобы отключить это сообщение</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="127"/>
<location filename="../../src/manager.cpp" line="124"/>
<source>No recognition languages available. Install some via Settings-&gt;Updates</source>
<translation>Нет доступных языков распознавания. Установите нужные через Настройки-&gt;Обновление</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="132"/>
<location filename="../../src/manager.cpp" line="129"/>
<source>Recognition language not set. Go to Settings-&gt;Recognition and set it</source>
<translation>Язык распознавания не задан. Задайте его в Настройки-&gt;Распознавание</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="136"/>
<location filename="../../src/manager.cpp" line="133"/>
<source>No translators enabled. Go to Settings-&gt;Translation and select some</source>
<translation>Не выбран ни один переводчик. Активируйте хотя бы один в Настройки-&gt;Перевод</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="141"/>
<location filename="../../src/manager.cpp" line="138"/>
<source>Translation language not set. Go to Settings-&gt;Translation and set it</source>
<translation>Язык перевода не задан. Задайте его в Настройки-&gt;Перевод</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="209"/>
<location filename="../../src/manager.cpp" line="205"/>
<source>Failed to set log file: %1</source>
<translation>Ошибка установки лог-файла: %1</translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="215"/>
<location filename="../../src/manager.cpp" line="211"/>
<source>Started logging to file: %1</source>
<translation>Начата запись в лог-файл: %1</translation>
</message>
@ -732,32 +727,32 @@ in %1</source>
<translation>Не восстанавливать интерфейс пользователя (размер и положения окна и т.д.)</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="117"/>
<location filename="../../src/settingseditor.cpp" line="116"/>
<source>&lt;p&gt;Optical character recognition (OCR) and translation tool&lt;/p&gt;</source>
<translation>&lt;p&gt;Инструмент оптического распознавания текста (OCR) и перевода&lt;/p&gt;</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="119"/>
<location filename="../../src/settingseditor.cpp" line="118"/>
<source>&lt;p&gt;Version: %1&lt;/p&gt;</source>
<translation>&lt;p&gt;Версия: %1&lt;/p&gt;</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="121"/>
<location filename="../../src/settingseditor.cpp" line="120"/>
<source>&lt;p&gt;Changelog: &lt;a href=&quot;%1&quot;&gt;%2&lt;/a&gt;&lt;/p&gt;</source>
<translation>&lt;p&gt;Список изменений: &lt;a href=&quot;%1&quot;&gt;%2&lt;/a&gt;&lt;/p&gt;</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="123"/>
<location filename="../../src/settingseditor.cpp" line="122"/>
<source>&lt;p&gt;License: &lt;a href=&quot;%3&quot;&gt;MIT&lt;/a&gt;&lt;/p&gt;</source>
<translation>&lt;p&gt;Лицензия: &lt;a href=&quot;%3&quot;&gt;MIT&lt;/a&gt;&lt;/p&gt;</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="124"/>
<location filename="../../src/settingseditor.cpp" line="123"/>
<source>&lt;p&gt;Author: Gres (&lt;a href=&quot;mailto:%1&quot;&gt;%1&lt;/a&gt;)&lt;/p&gt;</source>
<translation>&lt;p&gt;Автор: Gres (&lt;a href=&quot;mailto:%1&quot;&gt;%1&lt;/a&gt;)&lt;/p&gt;</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="126"/>
<location filename="../../src/settingseditor.cpp" line="125"/>
<source>&lt;p&gt;Issues: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;&lt;/p&gt;</source>
<translation>&lt;p&gt;Поддержка: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;&lt;/p&gt;</translation>
</message>
@ -1034,20 +1029,15 @@ in %1</source>
<translation>Показывать распознанное</translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="648"/>
<location filename="../../src/settingseditor.ui" line="629"/>
<source>Update check interval (days):</source>
<translation>Интервал проверки обновления (дней):</translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="655"/>
<location filename="../../src/settingseditor.ui" line="636"/>
<source>0 - disabled</source>
<translation>- отключено</translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="688"/>
<source>Apply updates</source>
<translation>Применить изменения</translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="390"/>
<source>Translate text</source>
@ -1074,7 +1064,7 @@ in %1</source>
<translation>Окно</translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="671"/>
<location filename="../../src/settingseditor.ui" line="652"/>
<source>Check now</source>
<translation>Проверить сейчас</translation>
</message>
@ -1159,7 +1149,7 @@ in %1</source>
<translation>Текст для проверки</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="134"/>
<location filename="../../src/settingseditor.cpp" line="133"/>
<source>The program workflow consists of the following steps:
1. Selection on the screen area
2. Recognition of the selected area
@ -1182,7 +1172,7 @@ Then set default recognition and translation languages, enable some (or all) tra
Далее установите языки распознавания и перевода по умолчанию, активируйте некоторые (или все) переводчики и настройку &quot;переводить текст&quot;, если нужно.</translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="366"/>
<location filename="../../src/settingseditor.cpp" line="363"/>
<source>Portable changed. Apply settings first</source>
<translation>Portable режиме изменени. Сначала применить настройки</translation>
</message>
@ -1310,73 +1300,94 @@ Most likely they are already in use by another program</source>
<context>
<name>Updates</name>
<message>
<location filename="../../src/service/updates.cpp" line="90"/>
<location filename="../../src/service/updates.cpp" line="88"/>
<source>Tb</source>
<translation>Тб</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="94"/>
<location filename="../../src/service/updates.cpp" line="92"/>
<source>Gb</source>
<translation>Гб</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="98"/>
<location filename="../../src/service/updates.cpp" line="96"/>
<source>Mb</source>
<translation>Мб</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="102"/>
<location filename="../../src/service/updates.cpp" line="100"/>
<source>Kb</source>
<translation>Кб</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="105"/>
<location filename="../../src/service/updates.cpp" line="103"/>
<source>bytes</source>
<translation>байт</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="113"/>
<location filename="../../src/service/updates.cpp" line="111"/>
<source>Not installed</source>
<translation>Не установлено</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="115"/>
<location filename="../../src/service/updates.cpp" line="113"/>
<source>Update available</source>
<translation>Доступно обновление</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="116"/>
<location filename="../../src/service/updates.cpp" line="114"/>
<source>Up to date</source>
<translation>Последняя версия</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="125"/>
<location filename="../../src/service/updates.cpp" line="122"/>
<source>Remove</source>
<translation>Удалить</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="126"/>
<location filename="../../src/service/updates.cpp" line="123"/>
<source>Install/Update</source>
<translation>Установить/Обновить</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="1032"/>
<location filename="../../src/service/updates.cpp" line="1049"/>
<location filename="../../src/service/updates.cpp" line="628"/>
<source>Directory is not writable
%1</source>
<translation>Папка недоступна для записи
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="1041"/>
<source>Downloaded file not exists
%1</source>
<translation>Скачанный файл не существует
%1</translation>
<location filename="../../src/service/updates.cpp" line="657"/>
<source>Failed to create temp file
%1
Error %2</source>
<translation>Ошибка создания временного файла
%1
%2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="1061"/>
<location filename="../../src/service/updates.cpp" line="1079"/>
<location filename="../../src/service/updates.cpp" line="665"/>
<source>Failed to write to temp file
%1
Error %2</source>
<translation>Ошибка записи во временный файл
%1
%2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="682"/>
<source>Failed to copy file
%1
to %2
Error %3</source>
<translation>Ошибка копирования файла
%1
в %2
%3</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="640"/>
<location filename="../../src/service/updates.cpp" line="675"/>
<source>Failed to remove file
%1
Error %2</source>
@ -1385,23 +1396,12 @@ Error %2</source>
Текст %2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="1072"/>
<location filename="../../src/service/updates.cpp" line="650"/>
<source>Failed to create path
%1</source>
<translation>Ошибка создания папки
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="1087"/>
<source>Failed to move file
%1
to %2
Error %3</source>
<translation>Ошибка переименования файла
%1
в %2
Текст %3</translation>
</message>
</context>
<context>
<name>WebPage</name>
@ -1414,142 +1414,76 @@ Error %3</source>
<context>
<name>update::Loader</name>
<message>
<location filename="../../src/service/updates.cpp" line="220"/>
<source>Empty updates info from
%1</source>
<translation>Пустой файл обновлений из
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="229"/>
<source>Empty updates info unpacked from
%1</source>
<translation>Пустой файл обновлений распакован из
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="237"/>
<source>Failed to parse updates from
%1 (%2)</source>
<translation>Ошибка разбора файла обновлений
%1 (%2)</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="251"/>
<location filename="../../src/service/updates.cpp" line="576"/>
<source>Failed to download file
%1. Error %2</source>
<translation>Ошибка скачивания файла
%1. Текст %2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="259"/>
<source>Already updating</source>
<translation>Обновление в процессе</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="265"/>
<source>No actions selected</source>
<translation>Нет выделенных действий</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="330"/>
<source>Failed to create temp path
%1</source>
<translation>Ошибка создания временной папки
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="337"/>
<location filename="../../src/service/updates.cpp" line="584"/>
<source>Empty data downloaded from
%1</source>
<translation>Пустой файл из
%1</translation>
</message>
</context>
<context>
<name>update::Model</name>
<message>
<location filename="../../src/service/updates.cpp" line="350"/>
<location filename="../../src/service/updates.cpp" line="158"/>
<source>Failed to parse: %1 at %2</source>
<translation>Ошибка разбора: %1 в %2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="166"/>
<source>Wrong updates version: %1</source>
<translation>Неверная версия файла обновлений: %1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="178"/>
<source>No data parsed</source>
<translation>Нет разобранных данных</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="481"/>
<source>Name</source>
<translation>Название</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="481"/>
<source>State</source>
<translation>Состояние</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="482"/>
<source>Size</source>
<translation>Размер</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="482"/>
<source>Version</source>
<translation>Версия</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="483"/>
<source>Progress</source>
<translation>Прогресс</translation>
</message>
</context>
<context>
<name>update::Updater</name>
<message>
<location filename="../../src/service/updates.cpp" line="850"/>
<source>Empty data unpacked from
%1</source>
<translation>Пустой файл распакован из
%1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="360"/>
<source>Failed to save downloaded file
%1
to %2
Error %3</source>
<translation>Ошибка сохранения скачанного файла
%1
в %2
Текст %3</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="398"/>
<source>Update failed: %1</source>
<translation>Ошибка обновления: %1</translation>
</message>
</context>
<context>
<name>update::Model</name>
<message>
<location filename="../../src/service/updates.cpp" line="460"/>
<source>Select all updates</source>
<translation>Выбрать все обновления</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="461"/>
<source>Reset actions</source>
<translation>Сбросить действия</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="499"/>
<source>Failed to parse: %1 at %2</source>
<translation>Ошибка разбора: %1 в %2</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="507"/>
<source>Wrong updates version: %1</source>
<translation>Неверная версия файла обновлений: %1</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="519"/>
<source>No data parsed</source>
<translation>Нет разобранных данных</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="876"/>
<source>Name</source>
<translation>Название</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="876"/>
<source>State</source>
<translation>Состояние</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="877"/>
<source>Action</source>
<translation>Действие</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="877"/>
<source>Size</source>
<translation>Размер</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="878"/>
<source>Version</source>
<translation>Версия</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="878"/>
<source>Progress</source>
<translation>Прогресс</translation>
</message>
<message>
<location filename="../../src/service/updates.cpp" line="879"/>
<source>Files</source>
<translation>Файла</translation>
<location filename="../../src/service/updates.cpp" line="921"/>
<source>Update all</source>
<translation>Обновить все</translation>
</message>
</context>
</TS>

View File

@ -33,8 +33,7 @@ using Loader = update::Loader;
Manager::Manager()
: models_(std::make_unique<CommonModels>())
, settings_(std::make_unique<Settings>())
, updater_(std::make_unique<Loader>(Loader::Urls{{updatesUrl}}))
, updateAutoChecker_(std::make_unique<update::AutoChecker>(*updater_))
, updater_(std::make_unique<update::Updater>(QVector<QUrl>{{updatesUrl}}))
{
SOFT_ASSERT(settings_, return );
@ -61,13 +60,9 @@ Manager::Manager()
warnIfOutdated();
QObject::connect(updater_.get(), &update::Loader::error, //
QObject::connect(updater_.get(), &update::Updater::error, //
tray_.get(), &TrayIcon::showError);
QObject::connect(updater_.get(), &update::Loader::updated, //
tray_.get(), [this] {
tray_->showInformation(QObject::tr("Update completed"));
});
QObject::connect(updater_.get(), &update::Loader::updatesAvailable, //
QObject::connect(updater_.get(), &update::Updater::updatesAvailable, //
tray_.get(), [this] {
tray_->showInformation(QObject::tr("Updates available"));
});
@ -76,8 +71,10 @@ Manager::Manager()
Manager::~Manager()
{
SOFT_ASSERT(settings_, return );
if (updateAutoChecker_ && updateAutoChecker_->isLastCheckDateChanged()) {
settings_->lastUpdateCheck = updateAutoChecker_->lastCheckDate();
SOFT_ASSERT(updater_, return );
if (updater_->lastUpdateCheck().isValid() &&
settings_->lastUpdateCheck != updater_->lastUpdateCheck()) {
settings_->lastUpdateCheck = updater_->lastUpdateCheck();
settings_->saveLastUpdateCheck();
LTRACE() << "saved last update time";
}
@ -170,16 +167,15 @@ void Manager::setupProxy(const Settings &settings)
void Manager::setupUpdates(const Settings &settings)
{
updater_->model()->setExpansions({
updater_->setExpansions({
{"$translators$", settings.translatorsDir},
{"$tessdata$", settings.tessdataPath},
{"$hunspell$", settings.hunspellDir},
{"$appdir$", QApplication::applicationDirPath()},
});
SOFT_ASSERT(updateAutoChecker_, return );
updateAutoChecker_->setLastCheckDate(settings.lastUpdateCheck);
updateAutoChecker_->setCheckIntervalDays(settings.autoUpdateIntervalDays);
updater_->setAutoUpdate(settings.autoUpdateIntervalDays,
settings.lastUpdateCheck);
}
bool Manager::setupTrace(bool isOn)

View File

@ -43,7 +43,6 @@ private:
std::unique_ptr<Corrector> corrector_;
std::unique_ptr<Translator> translator_;
std::unique_ptr<Representer> representer_;
std::unique_ptr<update::Loader> updater_;
std::unique_ptr<update::AutoChecker> updateAutoChecker_;
std::unique_ptr<update::Updater> updater_;
int activeTaskCount_{0};
};

File diff suppressed because it is too large Load Diff

View File

@ -11,20 +11,19 @@ class QTreeView;
namespace update
{
enum class State { NotAvailable, NotInstalled, UpdateAvailable, Actual };
enum class Action { NoAction, Remove, Install };
enum class Action { Install, Remove };
class Updater;
struct File {
QVector<QUrl> urls;
QString rawPath;
QString expandedPath;
QString downloadPath;
QString md5;
QDateTime versionDate;
int progress{0};
};
using UserActions = std::multimap<Action, File>;
class UpdateDelegate : public QStyledItemDelegate
{
Q_OBJECT
@ -38,30 +37,17 @@ class Model : public QAbstractItemModel
{
Q_OBJECT
public:
enum class Column {
Name,
State,
Action,
Size,
Version,
Progress,
Files,
Count
};
enum class Column { Name, State, Size, Version, Progress, Count };
explicit Model(QObject* parent = nullptr);
void initView(QTreeView* view);
explicit Model(Updater& updater);
QString parse(const QByteArray& data);
void setExpansions(const std::map<QString, QString>& expansions);
UserActions userActions() const;
void setExpansions(const QHash<QString, QString>& expansions);
void updateStates();
bool hasUpdates() const;
void updateProgress(const QUrl& url, int progress);
void resetProgress();
void selectAllUpdates();
void resetActions();
void tryAction(Action action, const QModelIndex& index);
QModelIndex index(int row, int column,
const QModelIndex& parent) const override;
@ -72,16 +58,13 @@ public:
int role) const override;
QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
bool setData(const QModelIndex& index, const QVariant& value,
int role) override;
private:
struct Component {
QString name;
State state{State::NotAvailable};
Action action{Action::NoAction};
QString version;
std::vector<File> files;
QVector<File> files;
bool checkOnly{false};
std::vector<std::unique_ptr<Component>> children;
Component* parent{nullptr};
@ -91,33 +74,14 @@ private:
};
std::unique_ptr<Component> parse(const QJsonObject& json) const;
void updateProgress(Component& component, const QUrl& url, int progress);
void updateState(Component& component);
State currentState(const File& file) const;
QString expanded(const QString& source) const;
Component* toComponent(const QModelIndex& index) const;
QModelIndex toIndex(const Component& component, int column) const;
Updater& updater_;
std::unique_ptr<Component> root_;
std::map<QString, QString> expansions_;
};
class Installer
{
public:
explicit Installer(const UserActions& actions);
bool commit();
QString errorString() const;
private:
void remove(const File& file);
void install(const File& file);
void checkRemove(const File& file);
void checkInstall(const File& file);
bool checkIsPossible();
UserActions actions_;
QStringList errors_;
QHash<QString, QString> expansions_;
};
class Loader : public QObject
@ -125,60 +89,85 @@ class Loader : public QObject
Q_OBJECT
public:
using Urls = QVector<QUrl>;
explicit Loader(const Urls& updateUrls, QObject* parent = nullptr);
explicit Loader(Updater& updater);
void checkForUpdates();
void applyUserActions();
Model* model() const;
signals:
void updatesAvailable();
void updated();
void error(const QString& error);
void download(const Urls& urls);
private:
void addError(const QString& text);
void dumpErrors();
void start(const Urls& urls, const QUrl& previous, const QString& error);
void handleReply(QNetworkReply* reply);
bool handleComponentReply(QNetworkReply* reply);
void handleUpdateReply(QNetworkReply* reply);
QString toError(QNetworkReply& reply) const;
void finishUpdate(const QString& error = {});
void commitUpdate();
void updateProgress(qint64 bytesSent, qint64 bytesTotal);
bool startDownload(File& file);
void startDownloadUpdates(const QUrl& previous);
Updater& updater_;
QNetworkAccessManager* network_;
Model* model_;
Urls updateUrls_;
QString downloadPath_;
std::map<QNetworkReply*, File*> downloads_;
QStringList errors_;
UserActions currentActions_;
QHash<QNetworkReply*, Urls> downloads_;
};
class Installer
{
public:
void remove(const File& file);
void install(const File& file, const QByteArray& data);
void checkInstall(const File& file);
const QString& error() const;
private:
QString error_;
};
class AutoChecker : public QObject
{
Q_OBJECT
public:
explicit AutoChecker(Loader& loader, QObject* parent = nullptr);
AutoChecker(Updater& updater, int intervalDays, const QDateTime& lastCheck);
~AutoChecker();
bool isLastCheckDateChanged() const;
QDateTime lastCheckDate() const;
void setCheckIntervalDays(int days);
void setLastCheckDate(const QDateTime& dt);
const QDateTime& lastCheckDate() const;
private:
void handleModelReset();
void updateLastCheckDate();
void scheduleNextCheck();
Loader& loader_;
bool isLastCheckDateChanged_{false};
Updater& updater_;
int checkIntervalDays_{0};
QDateTime lastCheckDate_;
std::unique_ptr<QTimer> timer_;
};
class Updater : public QObject
{
Q_OBJECT
public:
explicit Updater(const QVector<QUrl>& updateUrls);
void initView(QTreeView* view);
void setExpansions(const QHash<QString, QString>& expansions);
void checkForUpdates();
QDateTime lastUpdateCheck() const;
void setAutoUpdate(int intervalDays, const QDateTime& lastCheck);
void applyAction(Action action, const QVector<File>& files);
void downloaded(const QUrl& url, const QByteArray& data);
void updateProgress(const QUrl& url, qint64 bytesSent, qint64 bytesTotal);
void downloadFailed(const QUrl& url, const QString& error);
signals:
void checkedForUpdates();
void updatesAvailable();
void updated();
void error(const QString& error);
private:
void handleModelDoubleClick(const QModelIndex& index);
void showModelContextMenu();
int findDownload(const QUrl& url) const;
QModelIndex fromProxy(const QModelIndex& index) const;
std::unique_ptr<Model> model_;
std::unique_ptr<Loader> loader_;
std::unique_ptr<AutoChecker> autoChecker_;
QVector<QUrl> updateUrls_;
QVector<File> downloading_;
};
} // namespace update

View File

@ -9,7 +9,7 @@
#include <QColorDialog>
SettingsEditor::SettingsEditor(Manager &manager, update::Loader &updater)
SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
: ui(new Ui::SettingsEditor)
, manager_(manager)
, updater_(updater)
@ -92,16 +92,15 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Loader &updater)
this, [this] { pickColor(ColorContext::Bagkround); });
// updates
updater.model()->initView(ui->updatesView);
ui->updatesView->header()->setObjectName("updatesHeader");
updater_.initView(ui->updatesView);
adjustUpdatesView();
connect(updater_.model(), &QAbstractItemModel::modelReset, //
connect(&updater_, &update::Updater::checkedForUpdates, //
this, &SettingsEditor::adjustUpdatesView);
connect(&updater_, &update::Loader::updated, //
connect(&updater_, &update::Updater::updated, //
this, &SettingsEditor::adjustUpdatesView);
connect(ui->checkUpdates, &QPushButton::clicked, //
&updater_, &update::Loader::checkForUpdates);
connect(ui->applyUpdates, &QPushButton::clicked, //
&updater_, &update::Loader::applyUserActions);
&updater_, &update::Updater::checkForUpdates);
// about
{
@ -317,8 +316,6 @@ void SettingsEditor::updateTranslators()
void SettingsEditor::adjustUpdatesView()
{
ui->updatesView->resizeColumnToContents(int(update::Model::Column::Name));
if (ui->tessdataPath->text().isEmpty()) // not inited yet
return;

View File

@ -16,7 +16,7 @@ class SettingsEditor : public QDialog
Q_OBJECT
public:
SettingsEditor(Manager &manager, update::Loader &updater);
SettingsEditor(Manager &manager, update::Updater &updater);
~SettingsEditor();
Settings settings() const;
@ -35,7 +35,7 @@ private:
Ui::SettingsEditor *ui;
Manager &manager_;
update::Loader &updater_;
update::Updater &updater_;
CommonModels models_;
QStringList enabledTranslators_;
bool wasPortable_{false};

View File

@ -613,33 +613,14 @@
</widget>
<widget class="QWidget" name="pageUpdate">
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<item row="1" column="0" colspan="2">
<widget class="QTreeView" name="updatesView">
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>204</width>
<height>20</height>
</size>
</property>
</spacer>
</widget>
</item>
<item row="2" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>203</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<item row="0" column="0" colspan="2">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -675,20 +656,6 @@
</layout>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QTreeView" name="updatesView">
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="applyUpdates">
<property name="text">
<string>Apply updates</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageAbout">

View File

@ -22,8 +22,7 @@ class CommonModels;
namespace update
{
class Loader;
class AutoChecker;
class Updater;
} // namespace update
using TaskPtr = std::shared_ptr<Task>;

View File

@ -14,10 +14,9 @@ const auto f1 = "from1.txt";
const auto t1 = "test/to1.txt";
const auto data = "sample";
File toFile(const QString& from, const QString& to)
File toFile(const QString& to)
{
File result;
result.downloadPath = from;
result.expandedPath = to;
return result;
}
@ -47,41 +46,24 @@ bool removeFile(const QString& name)
}
} // namespace
TEST(UpdateInstaller, Empty)
{
UserActions actions;
Installer testee(actions);
ASSERT_TRUE(testee.commit());
}
TEST(UpdateInstaller, SuccessInstall)
{
ASSERT_TRUE(removeFile(t1));
ASSERT_TRUE(writeFile(f1, data));
UserActions actions{{Action::Install, toFile(f1, t1)}};
Installer testee(actions);
ASSERT_TRUE(testee.commit());
Installer testee;
testee.install(toFile(t1), data);
ASSERT_EQ(data, readFile(t1));
ASSERT_TRUE(testee.error().isEmpty());
}
TEST(UpdateInstaller, SuccessRemove)
{
ASSERT_TRUE(writeFile(f1, data));
UserActions actions{{Action::Remove, toFile(f1, f1)}};
Installer testee(actions);
ASSERT_TRUE(testee.commit());
Installer testee;
testee.remove(toFile(f1));
ASSERT_FALSE(QFile::exists(f1));
}
TEST(UpdateInstaller, FailInstallNoSource)
{
ASSERT_TRUE(removeFile(f1));
UserActions actions{{Action::Install, toFile(f1, t1)}};
Installer testee(actions);
ASSERT_FALSE(testee.commit());
ASSERT_TRUE(testee.error().isEmpty());
}
TEST(UpdateInstaller, FailInstallNoWritable)
@ -90,9 +72,9 @@ TEST(UpdateInstaller, FailInstallNoWritable)
QFile f(t1);
ASSERT_FALSE(f.isWritable());
UserActions actions{{Action::Install, toFile(f1, t1)}};
Installer testee(actions);
ASSERT_FALSE(testee.commit());
Installer testee;
testee.install(toFile(t1), data);
ASSERT_FALSE(testee.error().isEmpty());
}
TEST(UpdateInstaller, FailRemove)
@ -103,9 +85,9 @@ TEST(UpdateInstaller, FailRemove)
return;
ASSERT_FALSE(QFile::copy(f1, f1 + "1")); // non writable
UserActions actions{{Action::Remove, toFile(f1, f1)}};
Installer testee(actions);
ASSERT_FALSE(testee.commit());
Installer testee;
testee.remove(toFile(f1));
ASSERT_FALSE(testee.error().isEmpty());
}
TEST(UpdateModel, ParseFail)
@ -113,7 +95,8 @@ TEST(UpdateModel, ParseFail)
const auto updates = R"({
})";
Model testee;
Updater updater({});
Model testee(updater);
const auto error = testee.parse(updates);
ASSERT_FALSE(error.isEmpty());
@ -134,7 +117,8 @@ TEST(UpdateModel, Parse)
}
})";
Model testee;
Updater updater({});
Model testee(updater);
const auto error = testee.parse(updates);
ASSERT_TRUE(error.isEmpty());
@ -146,40 +130,3 @@ TEST(UpdateModel, Parse)
comp1.sibling(comp1.row(), int(Model::Column::Name)).data().toString();
ASSERT_EQ("comp1", comp1Name);
}
TEST(UpdateLoader, InstallFile)
{
const auto uf = "updates.json";
const auto url = QUrl::fromLocalFile(QFileInfo(f1).absoluteFilePath());
const auto updates = QString(R"({
"version":1,
"comp1":{"files":[{"url":"%1", "path":"./%2", "md5":"1"}]}
})")
.arg(url.toString(), t1);
ASSERT_TRUE(writeFile(uf, updates.toUtf8()));
ASSERT_TRUE(writeFile(f1, updates.toUtf8()));
ASSERT_TRUE(removeFile(t1));
Loader testee({QUrl::fromLocalFile(QFileInfo(uf).absoluteFilePath())});
testee.checkForUpdates();
QCoreApplication::processEvents();
auto model = testee.model();
ASSERT_NE(nullptr, model);
ASSERT_EQ(1, model->rowCount({}));
const auto comp1 = model->index(0, int(Model::Column::Action), {});
model->setData(comp1, int(Action::Install), Qt::EditRole);
QSignalSpy okSpy(&testee, &Loader::updated);
QSignalSpy errorSpy(&testee, &Loader::error);
testee.applyUserActions();
QCoreApplication::processEvents();
ASSERT_EQ(1, okSpy.count());
ASSERT_EQ(0, errorSpy.count());
ASSERT_TRUE(QFile::exists(f1));
ASSERT_EQ(updates, readFile(t1));
}