Add resource downloader/updater

This commit is contained in:
Gres 2020-03-15 14:10:26 +03:00
parent c5889e1374
commit 40824e60de
16 changed files with 1376 additions and 78 deletions

View File

@ -1,7 +1,4 @@
<RCC>
<qresource prefix="/">
<file>version.json</file>
</qresource>
<qresource prefix="/icons">
<file alias="app.png">share/images/STIconBlue.png</file>
<file alias="st_success.png">share/images/STIconGreen.png</file>

View File

@ -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 \

36
share/updates/tessdata.py Normal file
View File

@ -0,0 +1,36 @@
import sys
import os
import subprocess
if len(sys.argv) < 2:
print("Usage:", sys.argv[0], "<tessdata_dir> [<download_url>]")
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('}')

View File

@ -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('}')

View File

@ -8,13 +8,26 @@
#include "task.h"
#include "translator.h"
#include "trayicon.h"
#include "updates.h"
#include <QApplication>
#include <QClipboard>
#include <QMessageBox>
#include <QNetworkProxy>
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<update::Loader>(QUrl(updatesUrl)))
{
tray_ = std::make_unique<TrayIcon>(*this);
capturer_ = std::make_unique<Capturer>(*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();

View File

@ -36,6 +36,7 @@ private:
std::unique_ptr<Corrector> corrector_;
std::unique_ptr<Translator> translator_;
std::unique_ptr<Representer> representer_;
std::unique_ptr<update::Loader> updater_;
TaskPtr last_;
int activeTaskCount_{0};
};

624
src/service/updates.cpp Normal file
View File

@ -0,0 +1,624 @@
#include "updates.h"
#include "debug.h"
#include <QComboBox>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QStandardPaths>
namespace update
{
namespace
{
const auto versionKey = "version";
const auto filesKey = "files";
QString toString(State state)
{
const QMap<State, QString> 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<Action, QString> 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<Installer>(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::Component> Model::parse(const QJsonObject &json) const
{
auto result = std::make_unique<Component>();
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<QString, QString> &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<State> 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<Component *>(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<Component *>(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<Component *>(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<Column, QString> 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<Component *>(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<Component *>(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<Component *>(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<QComboBox *>(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<QComboBox *>(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

128
src/service/updates.h Normal file
View File

@ -0,0 +1,128 @@
#pragma once
#include <QDate>
#include <QStyledItemDelegate>
#include <QUrl>
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<Action, File>;
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<QString, QString>& 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<File> files;
std::vector<std::unique_ptr<Component>> children;
Component* parent{nullptr};
int index{-1};
};
std::unique_ptr<Component> 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<Component> root_;
std::map<QString, QString> 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<QNetworkReply*, QString> componentReplyToPath_;
std::unique_ptr<Installer> installer_;
};
} // namespace update

View File

@ -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();

View File

@ -16,6 +16,8 @@ using Substitutions = std::unordered_multimap<LanguageId, Substitution>;
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;

View File

@ -1,14 +1,17 @@
#include "settingseditor.h"
#include "languagecodes.h"
#include "ui_settingseditor.h"
#include "updates.h"
#include "widgetstate.h"
#include <QFileDialog>
#include <QNetworkProxy>
#include <QSortFilterProxyModel>
#include <QStringListModel>
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<AutoUpdate, QString> 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();
}

View File

@ -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_;
};

View File

@ -14,16 +14,6 @@
<string>Настройки</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QListView" name="pagesList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
@ -34,6 +24,16 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QListView" name="pagesList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QStackedWidget" name="pagesView">
<property name="sizePolicy">
@ -459,22 +459,48 @@
</layout>
</widget>
<widget class="QWidget" name="pageUpdate">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>204</width>
<height>20</height>
</size>
</property>
</spacer>
</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">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Check for updates:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item>
<widget class="QComboBox" name="updateCombo"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="updateButton">
<item>
<widget class="QPushButton" name="checkUpdates">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -489,18 +515,19 @@
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="1" column="0" colspan="3">
<widget class="QTreeView" name="updatesView">
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>389</height>
</size>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="applyUpdates">
<property name="text">
<string>Apply updates</string>
</property>
</spacer>
</widget>
</item>
</layout>
</widget>

View File

@ -16,6 +16,11 @@ class Translator;
class Corrector;
class Recognizer;
namespace update
{
class Loader;
}
using TaskPtr = std::shared_ptr<Task>;
using LanguageId = QString;
using LanguageIds = QStringList;

405
updates.json Normal file
View File

@ -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"}
]}
}
}

View File

@ -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"
}
}