Add resource downloader/updater
This commit is contained in:
parent
c5889e1374
commit
40824e60de
@ -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>
|
||||
|
@ -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
36
share/updates/tessdata.py
Normal 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('}')
|
33
share/updates/translators.py
Normal file
33
share/updates/translators.py
Normal 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('}')
|
@ -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();
|
||||
|
@ -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
624
src/service/updates.cpp
Normal 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
128
src/service/updates.h
Normal 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
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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
405
updates.json
Normal 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"}
|
||||
]}
|
||||
}
|
||||
}
|
36
version.json
36
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"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user