From 40824e60def9d5709872fdc07f78e03c4dc775bf Mon Sep 17 00:00:00 2001 From: Gres Date: Sun, 15 Mar 2020 14:10:26 +0300 Subject: [PATCH] Add resource downloader/updater --- recources.qrc | 3 - screen-translator.pro | 2 + share/updates/tessdata.py | 36 ++ share/updates/translators.py | 33 ++ src/manager.cpp | 30 +- src/manager.h | 1 + src/service/updates.cpp | 624 +++++++++++++++++++++++++++++++++++ src/service/updates.h | 128 +++++++ src/settings.cpp | 6 +- src/settings.h | 4 +- src/settingseditor.cpp | 52 ++- src/settingseditor.h | 8 +- src/settingseditor.ui | 81 +++-- src/stfwd.h | 5 + updates.json | 405 +++++++++++++++++++++++ version.json | 36 +- 16 files changed, 1376 insertions(+), 78 deletions(-) create mode 100644 share/updates/tessdata.py create mode 100644 share/updates/translators.py create mode 100644 src/service/updates.cpp create mode 100644 src/service/updates.h create mode 100644 updates.json diff --git a/recources.qrc b/recources.qrc index cc0e5c2..4ee1430 100644 --- a/recources.qrc +++ b/recources.qrc @@ -1,7 +1,4 @@ - - version.json - share/images/STIconBlue.png share/images/STIconGreen.png diff --git a/screen-translator.pro b/screen-translator.pro index bdf93d2..7cb9f15 100644 --- a/screen-translator.pro +++ b/screen-translator.pro @@ -44,6 +44,7 @@ HEADERS += \ src/service/debug.h \ src/service/globalaction.h \ src/service/singleapplication.h \ + src/service/updates.h \ src/service/widgetstate.h \ src/settings.h \ src/settingseditor.h \ @@ -71,6 +72,7 @@ SOURCES += \ src/service/debug.cpp \ src/service/globalaction.cpp \ src/service/singleapplication.cpp \ + src/service/updates.cpp \ src/service/widgetstate.cpp \ src/settings.cpp \ src/settingseditor.cpp \ diff --git a/share/updates/tessdata.py b/share/updates/tessdata.py new file mode 100644 index 0000000..cdf99e3 --- /dev/null +++ b/share/updates/tessdata.py @@ -0,0 +1,36 @@ +import sys +import os +import subprocess + +if len(sys.argv) < 2: + print("Usage:", sys.argv[0], " []") + exit(1) + +tessdata_dir = sys.argv[1] + + +download_url = "https://github.com/tesseract-ocr/tessdata_best/raw/master" +if len(sys.argv) > 2: + download_url = sys.argv[2] + +files = {} +with os.scandir(tessdata_dir) as it: + for f in it: + if not f.is_file() or f.name in ["LICENSE", "README.md"]: + continue + name = f.name[:f.name.index('.')] + files.setdefault(name, []).append(f.name) + +print(',"recognizers": {') +comma = '' +for name, file_names in files.items(): + print(' {}"{}":{{"files":['.format(comma, name)) + comma = ', ' + for file_name in file_names: + git_cmd = ['git', 'log', '-1', '--pretty=format:%cI', file_name] + date = subprocess.run(git_cmd, cwd=tessdata_dir, universal_newlines=True, + stdout=subprocess.PIPE, check=True).stdout + print(' {{"url":"{}/{}", "path":"$tessdata$/{}", "date":"{}"}}'.format( + download_url, file_name, file_name, date)) + print(' ]}') +print('}') diff --git a/share/updates/translators.py b/share/updates/translators.py new file mode 100644 index 0000000..6109d39 --- /dev/null +++ b/share/updates/translators.py @@ -0,0 +1,33 @@ +import sys +import os +import hashlib + +download_url = "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master" + +if len(sys.argv) > 1: + download_url = sys.argv[1] + +subdir = 'translators' +root = os.path.abspath(os.path.basename(__file__) + '/..') +translators_dir = root + '/' + subdir + +files = {} +with os.scandir(translators_dir) as it: + for f in it: + if not f.is_file() or not f.name.endswith('.js'): + continue + name = f.name[:f.name.index('.')] + files[name] = f.name + +print(',"translators":{') +comma = '' +for name, file_name in files.items(): + print(' {}"{}": {{"files":['.format(comma, name)) + comma = ',' + md5 = hashlib.md5() + with open(os.path.join(translators_dir, file_name), 'rb') as f: + md5.update(f.read()) + print(' {{"url":"{}/{}", "path":"$translators$/{}", "md5":"{}"}}'.format( + download_url, subdir + '/' + file_name, file_name, md5.hexdigest())) + print(' ]}') +print('}') diff --git a/src/manager.cpp b/src/manager.cpp index 990f0db..5184239 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -8,13 +8,26 @@ #include "task.h" #include "translator.h" #include "trayicon.h" +#include "updates.h" #include #include #include #include +namespace +{ +#ifdef DEVELOP +const auto updatesUrl = "http://localhost:8081/updates.json"; +#else +const auto updatesUrl = + "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/" + "updates.json"; +#endif +} // namespace + Manager::Manager() + : updater_(std::make_unique(QUrl(updatesUrl))) { tray_ = std::make_unique(*this); capturer_ = std::make_unique(*this); @@ -31,6 +44,16 @@ Manager::Manager() if (settings.showMessageOnStart) tray_->showInformation(QObject::tr("Screen translator started")); + + QObject::connect(updater_.get(), &update::Loader::error, // + tray_.get(), &TrayIcon::showError); + QObject::connect(updater_.get(), &update::Loader::updated, // + tray_.get(), [this] { + tray_->showInformation(QObject::tr("Update completed")); + }); +#ifdef DEVELOP + updater_->checkForUpdates(); +#endif } Manager::~Manager() = default; @@ -40,6 +63,11 @@ void Manager::updateSettings(const Settings &settings) LTRACE() << "updateSettings"; setupProxy(settings); + updater_->model()->setExpansions({ + {"$translators$", settings.translatorsDir}, + {"$tessdata$", settings.tessdataPath}, + }); + tray_->updateSettings(settings); capturer_->updateSettings(settings); recognizer_->updateSettings(settings); @@ -188,7 +216,7 @@ void Manager::showLast() void Manager::settings() { - SettingsEditor editor; + SettingsEditor editor(*updater_); Settings settings; settings.load(); diff --git a/src/manager.h b/src/manager.h index f0291a1..2ad58d2 100644 --- a/src/manager.h +++ b/src/manager.h @@ -36,6 +36,7 @@ private: std::unique_ptr corrector_; std::unique_ptr translator_; std::unique_ptr representer_; + std::unique_ptr updater_; TaskPtr last_; int activeTaskCount_{0}; }; diff --git a/src/service/updates.cpp b/src/service/updates.cpp new file mode 100644 index 0000000..701e67a --- /dev/null +++ b/src/service/updates.cpp @@ -0,0 +1,624 @@ +#include "updates.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace update +{ +namespace +{ +const auto versionKey = "version"; +const auto filesKey = "files"; + +QString toString(State state) +{ + const QMap names{ + {State::NotAvailable, {}}, + {State::NotInstalled, QObject::tr("Not installed")}, + {State::UpdateAvailable, QObject::tr("Update available")}, + {State::Actual, QObject::tr("Up to date")}, + }; + return names.value(state); +} + +QString toString(Action action) +{ + const QMap names{ + {Action::NoAction, {}}, + {Action::Remove, QObject::tr("Remove")}, + {Action::Install, QObject::tr("Install/Update")}, + }; + return names.value(action); +} + +} // namespace + +Loader::Loader(const QUrl &updateUrl, QObject *parent) + : QObject(parent) + , network_(new QNetworkAccessManager(this)) + , model_(new Model(this)) + , updateUrl_(updateUrl) + , downloadPath_( + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + "/updates") +{ + connect(network_, &QNetworkAccessManager::finished, // + this, &Loader::handleReply); +} + +void Loader::checkForUpdates() +{ + auto reply = network_->get(QNetworkRequest(updateUrl_)); + if (reply->error() == QNetworkReply::NoError) + return; + + reply->deleteLater(); + emit error(toError(*reply)); +} + +QString Loader::toError(QNetworkReply &reply) const +{ + return tr("Failed to download file %1. Error %2") + .arg(reply.url().toString(), reply.errorString()); +} + +void Loader::applyUserActions() +{ + SOFT_ASSERT(model_, return ); + if (installer_ || !componentReplyToPath_.empty()) { + emit error(tr("Update already in process")); + return; + } + + auto actions = model_->userActions(); + if (actions.empty()) { + emit error(tr("No actions to apply")); + return; + } + + for (auto &action : actions) { + if (action.first != Action::Install) + continue; + + auto &file = action.second; + + auto reply = network_->get(QNetworkRequest(file.url)); + if (reply->error() != QNetworkReply::NoError) { + finishUpdate(toError(*reply)); + break; + } + + file.downloadPath = downloadPath_ + '/' + file.rawPath; + componentReplyToPath_.emplace(reply, file.downloadPath); + } + + installer_ = std::make_unique(actions); + + if (componentReplyToPath_.empty()) // no downloads + commitUpdate(); +} + +void Loader::finishUpdate(const QString &error) +{ + installer_.reset(); + for (const auto &i : componentReplyToPath_) i.first->deleteLater(); + componentReplyToPath_.clear(); + if (!error.isEmpty()) + emit this->error(error); + SOFT_ASSERT(model_, return ); + model_->updateStates(); +} + +void Loader::handleReply(QNetworkReply *reply) +{ + reply->deleteLater(); + + const auto isUpdatesReply = reply->url() == updateUrl_; + + if (reply->error() != QNetworkReply::NoError) { + emit error(toError(*reply)); + if (!isUpdatesReply) + finishUpdate(); + return; + } + + const auto replyData = reply->readAll(); + + if (isUpdatesReply) { + SOFT_ASSERT(model_, return ); + model_->parse(replyData); + return; + } + + SOFT_ASSERT(componentReplyToPath_.count(reply) == 1, return ); + + auto replyIt = componentReplyToPath_.find(reply); + const auto &fileName = replyIt->second; + + auto dir = QFileInfo(fileName).absoluteDir(); + if (!dir.exists()) + dir.mkpath("."); + + QFile f(fileName); + if (!f.open(QFile::WriteOnly)) { + const auto error = + tr("Failed to save downloaded file %1 to %2. Error %3") + .arg(reply->url().toString(), f.fileName(), f.errorString()); + finishUpdate(error); + return; + } + f.write(replyData); + f.close(); + + componentReplyToPath_.erase(replyIt); + + if (componentReplyToPath_.empty()) + commitUpdate(); +} + +void Loader::commitUpdate() +{ + SOFT_ASSERT(installer_, return ); + if (installer_->commit()) { + emit updated(); + } else { + emit error(tr("Update failed: %1").arg(installer_->errorString())); + } + finishUpdate(); +} + +Model *Loader::model() const +{ + return model_; +} + +Model::Model(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +void Model::parse(const QByteArray &data) +{ + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(data, &error); + if (doc.isNull()) { + LERROR() << error.errorString(); + return; + } + + const auto json = doc.object(); + const auto version = json[versionKey].toInt(); + if (version != 1) { + LERROR() << "Wrong updates.json version" << version; + return; + } + + beginResetModel(); + + root_ = parse(json); + if (root_) + updateState(*root_); + + endResetModel(); +} + +std::unique_ptr Model::parse(const QJsonObject &json) const +{ + auto result = std::make_unique(); + + if (json[filesKey].isArray()) { // concrete component + const auto host = json["host"].toString().toLower(); + if (!host.isEmpty()) { +#if defined(Q_OS_LINUX) && defined(Q_PROCESSOR_X86_64) + if (host != "linux") + return {}; +#elif defined(Q_OS_WINDOWS) && defined(Q_PROCESSOR_X86_64) + if (host != "win64") + return {}; +#elif defined(Q_OS_WINDOWS) && defined(Q_PROCESSOR_X86) + if (host != "win32") + return {}; +#elif defined(Q_OS_MACOS) + if (host != "macos") + return {}; +#else + return {}; +#endif + } + + result->version = json["version"].toString(); + + const auto files = json[filesKey].toArray(); + result->files.reserve(files.size()); + + for (const auto &fileInfo : files) { + const auto object = fileInfo.toObject(); + File file; + file.url = object["url"].toString(); + file.rawPath = object["path"].toString(); + file.md5 = object["md5"].toString(); + file.versionDate = + QDateTime::fromString(object["date"].toString(), Qt::ISODate); + result->files.push_back(file); + } + + return result; + } + + result->children.reserve(json.size()); + auto index = -1; + for (const auto &name : json.keys()) { + if (name == versionKey) + continue; + + auto child = parse(json[name].toObject()); + if (!child) + continue; + child->name = name; + child->index = ++index; + child->parent = result.get(); + result->children.push_back(std::move(child)); + } + return result; +} + +void Model::setExpansions(const std::map &expansions) +{ + expansions_ = expansions; + updateStates(); +} + +UserActions Model::userActions() const +{ + if (!root_) + return {}; + + UserActions result; + fillUserActions(result, *root_); + return result; +} + +void Model::fillUserActions(UserActions &actions, Component &component) const +{ + if (!component.files.empty()) { + if (component.action == Action::NoAction) + return; + + for (auto &file : component.files) actions.emplace(component.action, file); + return; + } + + if (!component.children.empty()) { + for (auto &child : component.children) fillUserActions(actions, *child); + return; + } +} + +void Model::updateStates() +{ + if (!root_) + return; + + updateState(*root_); + emitColumnsChanged(QModelIndex()); +} + +void Model::updateState(Model::Component &component) +{ + if (!component.files.empty()) { + std::vector states; + states.reserve(component.files.size()); + + for (auto &file : component.files) { + file.expandedPath = expanded(file.rawPath); + states.push_back(currentState(file)); + } + + component.state = *std::min_element(states.cbegin(), states.cend()); + component.action = Action::NoAction; + return; + } + + if (!component.children.empty()) { + for (auto &child : component.children) updateState(*child); + return; + } +} + +State Model::currentState(const File &file) const +{ + if (!file.url.isValid() || file.expandedPath.isEmpty() || + (file.md5.isEmpty() && !file.versionDate.isValid())) + return State::NotAvailable; + + if (!QFile::exists(file.expandedPath)) + return State::NotInstalled; + + if (file.versionDate.isValid()) { + QFileInfo info(file.expandedPath); + const auto date = info.fileTime(QFile::FileModificationTime); + if (!date.isValid()) + return State::NotInstalled; + return date >= file.versionDate ? State::Actual : State::UpdateAvailable; + } + + QFile f(file.expandedPath); + if (!f.open(QFile::ReadOnly)) + return State::NotInstalled; + + const auto data = f.readAll(); + const auto md5 = + QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); + if (md5 != file.md5) + return State::UpdateAvailable; + return State::Actual; +} + +QString Model::expanded(const QString &source) const +{ + auto result = source; + + for (const auto &expansion : expansions_) { + if (!result.contains(expansion.first)) + continue; + result.replace(expansion.first, expansion.second); + } + + return result; +} + +void Model::emitColumnsChanged(const QModelIndex &parent) +{ + const auto count = rowCount(parent); + if (count == 0) + return; + + emit dataChanged(index(0, int(Column::State), parent), + index(count - 1, int(Column::Action), parent), + {Qt::DisplayRole, Qt::EditRole}); + + for (auto i = 0; i < count; ++i) emitColumnsChanged(index(0, 0, parent)); +} + +QModelIndex Model::index(int row, int column, const QModelIndex &parent) const +{ + if (!root_) + return {}; + if (auto ptr = static_cast(parent.internalPointer())) { + SOFT_ASSERT(row >= 0 && row < int(ptr->children.size()), return {}); + return createIndex(row, column, ptr->children[row].get()); + } + if (row < 0 && row >= int(root_->children.size())) + return {}; + return createIndex(row, column, root_->children[row].get()); +} + +QModelIndex Model::parent(const QModelIndex &child) const +{ + auto ptr = static_cast(child.internalPointer()); + if (auto parent = ptr->parent) + return createIndex(parent->index, 0, parent); + return {}; +} + +int Model::rowCount(const QModelIndex &parent) const +{ + if (auto ptr = static_cast(parent.internalPointer())) { + return ptr->children.size(); + } + return root_ ? root_->children.size() : 0; +} + +int Model::columnCount(const QModelIndex & /*parent*/) const +{ + return int(Column::Count); +} + +QVariant Model::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return {}; + + if (orientation == Qt::Vertical) + return section + 1; + + const QMap names{ + {Column::Name, tr("Name")}, {Column::State, tr("State")}, + {Column::Action, tr("Action")}, {Column::Version, tr("Version")}, + {Column::Files, tr("Files")}, + }; + return names.value(Column(section)); +} + +QVariant Model::data(const QModelIndex &index, int role) const +{ + if ((role != Qt::DisplayRole && role != Qt::EditRole) || !index.isValid()) + return {}; + + auto ptr = static_cast(index.internalPointer()); + SOFT_ASSERT(ptr, return {}); + + switch (index.column()) { + case int(Column::Name): return ptr->name; + case int(Column::State): return toString(ptr->state); + case int(Column::Action): return toString(ptr->action); + case int(Column::Version): return ptr->version; + case int(Column::Files): { + QStringList files; + files.reserve(ptr->files.size()); + for (const auto &f : ptr->files) files.append(f.expandedPath); + return files.join(','); + } + } + + return {}; +} + +bool Model::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || role != Qt::EditRole) + return false; + + auto ptr = static_cast(index.internalPointer()); + SOFT_ASSERT(ptr, return false); + + if (index.column() != int(Column::Action)) + return false; + + const auto newAction = Action( + std::clamp(value.toInt(), int(Action::NoAction), int(Action::Install))); + if (ptr->action == newAction) + return false; + + ptr->action = newAction; + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); + + return true; +} + +Qt::ItemFlags Model::flags(const QModelIndex &index) const +{ + auto ptr = static_cast(index.internalPointer()); + SOFT_ASSERT(ptr, return {}); + auto result = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if (index.column() != int(Column::Action)) + return result; + + if (ptr->state == State::NotAvailable) + return result; + + result |= Qt::ItemIsEditable; + return result; +} + +ActionDelegate::ActionDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget *ActionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem & /*option*/, + const QModelIndex & /*index*/) const +{ + auto combo = new QComboBox(parent); + combo->setEditable(false); + combo->addItems({toString(Action::NoAction), toString(Action::Remove), + toString(Action::Install)}); + return combo; +} + +void ActionDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + auto combo = qobject_cast(editor); + SOFT_ASSERT(combo, return ); + combo->setCurrentText(index.data(Qt::EditRole).toString()); +} + +void ActionDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + auto combo = qobject_cast(editor); + SOFT_ASSERT(combo, return ); + model->setData(index, combo->currentIndex()); +} + +Installer::Installer(const UserActions &actions) + : actions_(actions) +{ +} + +bool Installer::commit() +{ + if (!checkIsPossible()) + return false; + + for (const auto &action : actions_) { + const auto &file = action.second; + + if (action.first == Action::Remove) { + QFile f(file.expandedPath); + if (!f.exists()) + continue; + if (!f.remove()) { + errors_.append(QObject::tr("Failed to remove file %1. Error %2") + .arg(f.fileName(), f.errorString())); + } + continue; + } + + if (action.first == Action::Install) { + auto installDir = QFileInfo(file.expandedPath).absoluteDir(); + if (!installDir.exists() && !installDir.mkpath(".")) { + errors_.append(QObject::tr("Failed to create path %1") + .arg(installDir.absolutePath())); + continue; + } + { + QFile existing(file.expandedPath); + if (existing.exists() && !existing.remove()) { + errors_.append(QObject::tr("Failed to remove file %1. Error %2") + .arg(existing.fileName(), existing.errorString())); + continue; + } + } + QFile f(file.downloadPath); + if (!f.rename(file.expandedPath)) { + errors_.append( + QObject::tr("Failed to move file %1 to %2. Error %3") + .arg(f.fileName(), file.expandedPath, f.errorString())); + continue; + } + } + } + + return errors_.isEmpty(); +} + +bool Installer::checkIsPossible() +{ + errors_.clear(); + + for (const auto &action : actions_) { + const auto &file = action.second; + QFileInfo installDir(QFileInfo(file.expandedPath).absolutePath()); + + if (action.first == Action::Remove) { + if (!QFile::exists(file.expandedPath)) + continue; + if (installDir.exists() && !installDir.isWritable()) { + errors_.append(QObject::tr("Directory is not writable %1") + .arg(installDir.absolutePath())); + } + continue; + } + + if (action.first == Action::Install) { + if (!QFileInfo::exists(file.downloadPath)) { + errors_.append(QObject::tr("Downloaded file not exists %1") + .arg(file.downloadPath)); + } + if (installDir.exists() && !installDir.isWritable()) { + errors_.append(QObject::tr("Directory is not writable %1") + .arg(installDir.absolutePath())); + } + } + } + errors_.removeDuplicates(); + + return errors_.isEmpty(); +} + +QString Installer::errorString() const +{ + return errors_.join('\n'); +} + +} // namespace update diff --git a/src/service/updates.h b/src/service/updates.h new file mode 100644 index 0000000..f761e46 --- /dev/null +++ b/src/service/updates.h @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +namespace update +{ +enum class State { NotAvailable, NotInstalled, UpdateAvailable, Actual }; +enum class Action { NoAction, Remove, Install }; + +struct File { + QUrl url; + QString rawPath; + QString expandedPath; + QString downloadPath; + QString md5; + QDateTime versionDate; +}; + +using UserActions = std::multimap; + +class ActionDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit ActionDelegate(QObject* parent = nullptr); + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, + const QModelIndex& index) const override; +}; + +class Model : public QAbstractItemModel +{ + Q_OBJECT +public: + enum class Column { Name, State, Action, Version, Files, Count }; + + explicit Model(QObject* parent = nullptr); + + void parse(const QByteArray& data); + void setExpansions(const std::map& expansions); + UserActions userActions() const; + void updateStates(); + + QModelIndex index(int row, int column, + const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex& child) const override; + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant headerData(int section, Qt::Orientation orientation, + 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 files; + std::vector> children; + Component* parent{nullptr}; + int index{-1}; + }; + + std::unique_ptr parse(const QJsonObject& json) const; + void updateState(Component& component); + void fillUserActions(UserActions& actions, Component& component) const; + State currentState(const File& file) const; + QString expanded(const QString& source) const; + void emitColumnsChanged(const QModelIndex& parent); + + std::unique_ptr root_; + std::map expansions_; +}; + +class Installer +{ +public: + explicit Installer(const UserActions& actions); + bool commit(); + QString errorString() const; + +private: + bool checkIsPossible(); + + UserActions actions_; + QStringList errors_; +}; + +class Loader : public QObject +{ + Q_OBJECT +public: + explicit Loader(const QUrl& updateUrl, QObject* parent = nullptr); + + void checkForUpdates(); + void applyUserActions(); + Model* model() const; + +signals: + void updated(); + void error(const QString& error); + +private: + void handleReply(QNetworkReply* reply); + QString toError(QNetworkReply& reply) const; + void finishUpdate(const QString& error = {}); + void commitUpdate(); + + QNetworkAccessManager* network_; + Model* model_; + QUrl updateUrl_; + QString downloadPath_; + std::map componentReplyToPath_; + std::unique_ptr installer_; +}; + +} // namespace update diff --git a/src/settings.cpp b/src/settings.cpp index 6e930b8..0ac7051 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -118,7 +118,7 @@ void Settings::save() settings.remove(qs_proxyPassword); } - settings.setValue(qs_autoUpdateType, autoUpdateType); + settings.setValue(qs_autoUpdateType, int(autoUpdateType)); settings.endGroup(); @@ -178,7 +178,9 @@ void Settings::load() settings.value(qs_proxySavePassword, proxySavePassword).toBool(); proxyPassword = shuffle(settings.value(qs_proxyPassword).toString()); - autoUpdateType = settings.value(qs_autoUpdateType, autoUpdateType).toInt(); + autoUpdateType = AutoUpdate( + std::clamp(settings.value(qs_autoUpdateType, int(autoUpdateType)).toInt(), + int(AutoUpdate::Disabled), int(AutoUpdate::Monthly))); settings.endGroup(); diff --git a/src/settings.h b/src/settings.h index 7f7f6a7..8f84b79 100644 --- a/src/settings.h +++ b/src/settings.h @@ -16,6 +16,8 @@ using Substitutions = std::unordered_multimap; enum class ProxyType { Disabled, System, Socks5, Http }; +enum class AutoUpdate { Disabled, Daily, Weekly, Monthly }; + class Settings { public: @@ -36,7 +38,7 @@ public: QString proxyPassword; bool proxySavePassword{false}; - int autoUpdateType{0}; // Never + AutoUpdate autoUpdateType{AutoUpdate::Disabled}; QString lastUpdateCheck{""}; Substitutions userSubstitutions; diff --git a/src/settingseditor.cpp b/src/settingseditor.cpp index c1f7b39..be388a5 100644 --- a/src/settingseditor.cpp +++ b/src/settingseditor.cpp @@ -1,14 +1,17 @@ #include "settingseditor.h" #include "languagecodes.h" #include "ui_settingseditor.h" +#include "updates.h" #include "widgetstate.h" #include #include +#include #include -SettingsEditor::SettingsEditor() +SettingsEditor::SettingsEditor(update::Loader &updater) : ui(new Ui::SettingsEditor) + , updater_(updater) { ui->setupUi(this); @@ -55,8 +58,30 @@ SettingsEditor::SettingsEditor() updateTranslationLanguages(); // updates - ui->updateCombo->addItems( - {tr("Never"), tr("Daily"), tr("Weekly"), tr("Monthly")}); + QMap updateTypes; + updateTypes.insert(AutoUpdate::Disabled, tr("Disabled")); + updateTypes.insert(AutoUpdate::Daily, tr("Daily")); + updateTypes.insert(AutoUpdate::Weekly, tr("Weekly")); + updateTypes.insert(AutoUpdate::Monthly, tr("Monthly")); + ui->updateCombo->addItems(updateTypes.values()); + + auto updatesProxy = new QSortFilterProxyModel(this); + updatesProxy->setSourceModel(updater_.model()); + ui->updatesView->setModel(updatesProxy); + ui->updatesView->setItemDelegateForColumn(int(update::Model::Column::Action), + new update::ActionDelegate(this)); +#ifndef DEVELOP + ui->updatesView->hideColumn(int(update::Model::Column::Files)); +#endif + adjustUpdatesView(); + connect(updater_.model(), &QAbstractItemModel::modelReset, // + this, &SettingsEditor::adjustUpdatesView); + connect(&updater_, &update::Loader::updated, // + this, &SettingsEditor::adjustUpdatesView); + connect(ui->checkUpdates, &QPushButton::clicked, // + &updater_, &update::Loader::checkForUpdates); + connect(ui->applyUpdates, &QPushButton::clicked, // + &updater_, &update::Loader::applyUserActions); new WidgetState(this); } @@ -140,7 +165,9 @@ void SettingsEditor::setSettings(const Settings &settings) ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors); ui->translatorDebugCheck->setChecked(settings.debugMode); ui->translateTimeoutSpin->setValue(settings.translationTimeout.count()); - updateTranslators(settings.translatorsDir, settings.translators); + translatorsPath_ = settings.translatorsDir; + enabledTranslators_ = settings.translators; + updateTranslators(); if (auto lang = langs.findById(settings.targetLanguage)) ui->translateLangCombo->setCurrentText(lang->name); @@ -193,12 +220,11 @@ void SettingsEditor::updateTesseractLanguages() ui->tesseractLangCombo->addItems(names); } -void SettingsEditor::updateTranslators(const QString &path, - const QStringList &enabled) +void SettingsEditor::updateTranslators() { ui->translatorList->clear(); - QDir dir(path); + QDir dir(translatorsPath_); if (!dir.exists()) return; @@ -208,8 +234,9 @@ void SettingsEditor::updateTranslators(const QString &path, for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) { auto item = ui->translatorList->item(i); - item->setCheckState(enabled.contains(item->text()) ? Qt::Checked - : Qt::Unchecked); + item->setCheckState(enabledTranslators_.contains(item->text()) + ? Qt::Checked + : Qt::Unchecked); } } @@ -227,3 +254,10 @@ void SettingsEditor::updateTranslationLanguages() std::sort(names.begin(), names.end()); ui->translateLangCombo->addItems(names); } + +void SettingsEditor::adjustUpdatesView() +{ + ui->updatesView->resizeColumnToContents(int(update::Model::Column::Name)); + updateTesseractLanguages(); + updateTranslators(); +} diff --git a/src/settingseditor.h b/src/settingseditor.h index 1a95568..f6d2c12 100644 --- a/src/settingseditor.h +++ b/src/settingseditor.h @@ -14,7 +14,7 @@ class SettingsEditor : public QDialog Q_OBJECT public: - explicit SettingsEditor(); + explicit SettingsEditor(update::Loader &updater); ~SettingsEditor(); Settings settings() const; @@ -24,8 +24,12 @@ private: void updateCurrentPage(); void openTessdataDialog(); void updateTesseractLanguages(); - void updateTranslators(const QString &path, const QStringList &enabled); + void updateTranslators(); void updateTranslationLanguages(); + void adjustUpdatesView(); Ui::SettingsEditor *ui; + update::Loader &updater_; + QString translatorsPath_; + QStringList enabledTranslators_; }; diff --git a/src/settingseditor.ui b/src/settingseditor.ui index bde7886..e6745b0 100644 --- a/src/settingseditor.ui +++ b/src/settingseditor.ui @@ -14,16 +14,6 @@ Настройки - - - - - 0 - 0 - - - - @@ -34,6 +24,16 @@ + + + + + 0 + 0 + + + + @@ -459,22 +459,48 @@ - - + + + + + Qt::Horizontal + + + + 204 + 20 + + + + + + + + Qt::Horizontal + + + + 203 + 20 + + + + + - - + + Check for updates: - + - - + + 0 @@ -489,18 +515,19 @@ - - - - Qt::Vertical + + + + true - - - 20 - 389 - + + + + + + Apply updates - + diff --git a/src/stfwd.h b/src/stfwd.h index bfd7b81..9f72f0d 100644 --- a/src/stfwd.h +++ b/src/stfwd.h @@ -16,6 +16,11 @@ class Translator; class Corrector; class Recognizer; +namespace update +{ +class Loader; +} + using TaskPtr = std::shared_ptr; using LanguageId = QString; using LanguageIds = QStringList; diff --git a/updates.json b/updates.json new file mode 100644 index 0000000..cb2cf94 --- /dev/null +++ b/updates.json @@ -0,0 +1,405 @@ +{ +"version":1 + +,"app":{ + "win32":{"version":"3.0.0", "host":"win32", "files":[]} + ,"win64":{"version":"3.0.0", "host":"win64", "files":[]} + ,"linux":{"version":"3.0.0", "host":"linux", "files":[]} +} + +,"recognizers": { + "mal":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mal.traineddata", "path":"$tessdata$/mal.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "guj":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/guj.traineddata", "path":"$tessdata$/guj.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "isl":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/isl.traineddata", "path":"$tessdata$/isl.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "san":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/san.traineddata", "path":"$tessdata$/san.traineddata", "date":"2017-09-15T18:37:50+05:30"} + ]} + , "afr":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/afr.traineddata", "path":"$tessdata$/afr.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "pol":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/pol.traineddata", "path":"$tessdata$/pol.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "fil":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fil.traineddata", "path":"$tessdata$/fil.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "por":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/por.traineddata", "path":"$tessdata$/por.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "bos":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/bos.traineddata", "path":"$tessdata$/bos.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "mlt":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mlt.traineddata", "path":"$tessdata$/mlt.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "sin":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/sin.traineddata", "path":"$tessdata$/sin.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "rus":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/rus.traineddata", "path":"$tessdata$/rus.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ori":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ori.traineddata", "path":"$tessdata$/ori.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "chi_tra_vert":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_tra_vert.traineddata", "path":"$tessdata$/chi_tra_vert.traineddata", "date":"2019-05-21T17:48:52+02:00"} + ]} + , "kmr":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kmr.traineddata", "path":"$tessdata$/kmr.traineddata", "date":"2018-04-25T22:47:45+05:30"} + ]} + , "osd":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/osd.traineddata", "path":"$tessdata$/osd.traineddata", "date":"2017-09-15T11:44:08-07:00"} + ]} + , "srp":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/srp.traineddata", "path":"$tessdata$/srp.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "dan":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/dan.traineddata", "path":"$tessdata$/dan.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tam":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tam.traineddata", "path":"$tessdata$/tam.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "syr":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/syr.traineddata", "path":"$tessdata$/syr.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "gle":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/gle.traineddata", "path":"$tessdata$/gle.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "vie":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/vie.traineddata", "path":"$tessdata$/vie.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "oci":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/oci.traineddata", "path":"$tessdata$/oci.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tur":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tur.traineddata", "path":"$tessdata$/tur.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "bel":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/bel.traineddata", "path":"$tessdata$/bel.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "bre":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/bre.traineddata", "path":"$tessdata$/bre.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tir":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tir.traineddata", "path":"$tessdata$/tir.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "bul":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/bul.traineddata", "path":"$tessdata$/bul.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "chi_sim":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_sim.traineddata", "path":"$tessdata$/chi_sim.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "que":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/que.traineddata", "path":"$tessdata$/que.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "deu":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/deu.traineddata", "path":"$tessdata$/deu.traineddata", "date":"2018-02-01T15:29:03+01:00"} + ]} + , "swa":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/swa.traineddata", "path":"$tessdata$/swa.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "sqi":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/sqi.traineddata", "path":"$tessdata$/sqi.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "yid":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/yid.traineddata", "path":"$tessdata$/yid.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "aze_cyrl":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/aze_cyrl.traineddata", "path":"$tessdata$/aze_cyrl.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ben":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ben.traineddata", "path":"$tessdata$/ben.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "uig":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/uig.traineddata", "path":"$tessdata$/uig.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tha":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tha.traineddata", "path":"$tessdata$/tha.traineddata", "date":"2019-05-21T17:50:06+02:00"} + ]} + , "spa_old":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/spa_old.traineddata", "path":"$tessdata$/spa_old.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "div":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/div.traineddata", "path":"$tessdata$/div.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "fin":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fin.traineddata", "path":"$tessdata$/fin.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "eng":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/eng.traineddata", "path":"$tessdata$/eng.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "slk":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/slk.traineddata", "path":"$tessdata$/slk.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ltz":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ltz.traineddata", "path":"$tessdata$/ltz.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "gla":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/gla.traineddata", "path":"$tessdata$/gla.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "nld":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/nld.traineddata", "path":"$tessdata$/nld.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "eus":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/eus.traineddata", "path":"$tessdata$/eus.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "cat":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/cat.traineddata", "path":"$tessdata$/cat.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "amh":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/amh.traineddata", "path":"$tessdata$/amh.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "lit":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/lit.traineddata", "path":"$tessdata$/lit.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "fao":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fao.traineddata", "path":"$tessdata$/fao.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ind":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ind.traineddata", "path":"$tessdata$/ind.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "pus":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/pus.traineddata", "path":"$tessdata$/pus.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "aze":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/aze.traineddata", "path":"$tessdata$/aze.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kat":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kat.traineddata", "path":"$tessdata$/kat.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tat":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tat.traineddata", "path":"$tessdata$/tat.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "chi_sim_vert":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_sim_vert.traineddata", "path":"$tessdata$/chi_sim_vert.traineddata", "date":"2019-05-21T17:48:52+02:00"} + ]} + , "ton":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ton.traineddata", "path":"$tessdata$/ton.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "mya":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mya.traineddata", "path":"$tessdata$/mya.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kor":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kor.traineddata", "path":"$tessdata$/kor.traineddata", "date":"2018-04-09T19:58:26+05:30"} + ]} + , "nor":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/nor.traineddata", "path":"$tessdata$/nor.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "grc":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/grc.traineddata", "path":"$tessdata$/grc.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "jpn":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/jpn.traineddata", "path":"$tessdata$/jpn.traineddata", "date":"2019-05-21T17:49:35+02:00"} + ]} + , "fas":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fas.traineddata", "path":"$tessdata$/fas.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kan":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kan.traineddata", "path":"$tessdata$/kan.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "mkd":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mkd.traineddata", "path":"$tessdata$/mkd.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tgk":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tgk.traineddata", "path":"$tessdata$/tgk.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "hye":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/hye.traineddata", "path":"$tessdata$/hye.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "fra":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fra.traineddata", "path":"$tessdata$/fra.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "urd":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/urd.traineddata", "path":"$tessdata$/urd.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "hin":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/hin.traineddata", "path":"$tessdata$/hin.traineddata", "date":"2017-09-15T18:37:50+05:30"} + ]} + , "heb":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/heb.traineddata", "path":"$tessdata$/heb.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kat_old":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kat_old.traineddata", "path":"$tessdata$/kat_old.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "yor":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/yor.traineddata", "path":"$tessdata$/yor.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "msa":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/msa.traineddata", "path":"$tessdata$/msa.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "hat":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/hat.traineddata", "path":"$tessdata$/hat.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ita_old":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ita_old.traineddata", "path":"$tessdata$/ita_old.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "swe":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/swe.traineddata", "path":"$tessdata$/swe.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "srp_latn":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/srp_latn.traineddata", "path":"$tessdata$/srp_latn.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "jav":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/jav.traineddata", "path":"$tessdata$/jav.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "bod":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/bod.traineddata", "path":"$tessdata$/bod.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "mar":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mar.traineddata", "path":"$tessdata$/mar.traineddata", "date":"2017-09-15T21:22:28+05:30"} + ]} + , "chi_tra":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_tra.traineddata", "path":"$tessdata$/chi_tra.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ita":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ita.traineddata", "path":"$tessdata$/ita.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "mon":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mon.traineddata", "path":"$tessdata$/mon.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "fry":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/fry.traineddata", "path":"$tessdata$/fry.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ces":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ces.traineddata", "path":"$tessdata$/ces.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "asm":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/asm.traineddata", "path":"$tessdata$/asm.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "tel":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/tel.traineddata", "path":"$tessdata$/tel.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kir":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kir.traineddata", "path":"$tessdata$/kir.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "cym":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/cym.traineddata", "path":"$tessdata$/cym.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ceb":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ceb.traineddata", "path":"$tessdata$/ceb.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "sun":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/sun.traineddata", "path":"$tessdata$/sun.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "cos":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/cos.traineddata", "path":"$tessdata$/cos.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "hrv":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/hrv.traineddata", "path":"$tessdata$/hrv.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "iku":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/iku.traineddata", "path":"$tessdata$/iku.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "hun":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/hun.traineddata", "path":"$tessdata$/hun.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "snd":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/snd.traineddata", "path":"$tessdata$/snd.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "pan":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/pan.traineddata", "path":"$tessdata$/pan.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "dzo":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/dzo.traineddata", "path":"$tessdata$/dzo.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "nep":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/nep.traineddata", "path":"$tessdata$/nep.traineddata", "date":"2017-09-15T21:22:28+05:30"} + ]} + , "uzb_cyrl":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/uzb_cyrl.traineddata", "path":"$tessdata$/uzb_cyrl.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kor_vert":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kor_vert.traineddata", "path":"$tessdata$/kor_vert.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "slv":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/slv.traineddata", "path":"$tessdata$/slv.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "est":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/est.traineddata", "path":"$tessdata$/est.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "lat":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/lat.traineddata", "path":"$tessdata$/lat.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "epo":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/epo.traineddata", "path":"$tessdata$/epo.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "spa":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/spa.traineddata", "path":"$tessdata$/spa.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "jpn_vert":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/jpn_vert.traineddata", "path":"$tessdata$/jpn_vert.traineddata", "date":"2019-05-21T17:49:35+02:00"} + ]} + , "mri":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/mri.traineddata", "path":"$tessdata$/mri.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ron":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ron.traineddata", "path":"$tessdata$/ron.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "khm":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/khm.traineddata", "path":"$tessdata$/khm.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "frm":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/frm.traineddata", "path":"$tessdata$/frm.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ukr":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ukr.traineddata", "path":"$tessdata$/ukr.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "frk":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/frk.traineddata", "path":"$tessdata$/frk.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "glg":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/glg.traineddata", "path":"$tessdata$/glg.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "lav":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/lav.traineddata", "path":"$tessdata$/lav.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ara":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ara.traineddata", "path":"$tessdata$/ara.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "kaz":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/kaz.traineddata", "path":"$tessdata$/kaz.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "enm":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/enm.traineddata", "path":"$tessdata$/enm.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "uzb":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/uzb.traineddata", "path":"$tessdata$/uzb.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "ell":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/ell.traineddata", "path":"$tessdata$/ell.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "lao":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/lao.traineddata", "path":"$tessdata$/lao.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + , "chr":{"files":[ + {"url":"https://github.com/tesseract-ocr/tessdata_best/raw/master/chr.traineddata", "path":"$tessdata$/chr.traineddata", "date":"2017-09-14T14:45:10-07:00"} + ]} + } + +,"translators":{ + "bing": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/bing.js", "path":"$translators$/bing.js", "md5":"23178a0a19f54c8dc6befd18c67decf7"} + ]} + ,"google": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google.js", "path":"$translators$/google.js", "md5":"232ffcfff1ab743d295e6c8a04695272"} + ]} + ,"google_api": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google_api.js", "path":"$translators$/google_api.js", "md5":"7fa9fb03d4cfc3e69cd0120c51d82c95"} + ]} + ,"yandex": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/yandex.js", "path":"$translators$/yandex.js", "md5":"2c4d64c30fa74b875f48dc99f2745c1f"} + ]} + ,"deepl": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/deepl.js", "path":"$translators$/deepl.js", "md5":"05ac33e02469710bc69fc20c55773c91"} + ]} + ,"papago": {"files":[ + {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/papago.js", "path":"$translators$/papago.js", "md5":"c3c6bb456a48a47f50e634ee43862434"} + ]} +} +} diff --git a/version.json b/version.json index 7ad8d69..5a5b234 100644 --- a/version.json +++ b/version.json @@ -2,44 +2,14 @@ "version": 1, "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/version.json", "Application": { - "version": 2, + "version": 3, "compatibleVersion": 2, "built_in": true, - "versionString": "2.0.2", + "versionString": "3.0.0", "permissions": "0x7755", - "url_win": "https://github.com/OneMoreGres/ScreenTranslator/releases/download/2.0.2/ScreenTranslator.exe", + "url_win": "disabled", "path_win": "ScreenTranslator.exe", "url_linux": "disabled", "path_linux": "ScreenTranslator" - }, - "Bing translator": { - "version": 6, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/bing.js", - "path": "translators/bing.js" - }, - "Google translator": { - "version": 4, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google.js", - "path": "translators/google.js" - }, - "Google API translator": { - "version": 1, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google_api.js", - "path": "translators/google_api.js" - }, - "Yandex translator": { - "version": 4, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/yandex.js", - "path": "translators/yandex.js" - }, - "DeepL translator": { - "version": 1, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/deepl.js", - "path": "translators/deepl.js" - }, - "Papago translator": { - "version": 1, - "url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/papago.js", - "path": "translators/papago.js" } }