mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-30 23:38:25 +00:00
Add autoupdating for templates (support).
This commit is contained in:
parent
ccaec28d0b
commit
1411dfb711
@ -1589,11 +1589,6 @@ namespace App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValidPhone(QString phone) {
|
|
||||||
phone = phone.replace(QRegularExpression(qsl("[^\\d]")), QString());
|
|
||||||
return phone.length() >= 8 || phone == qsl("777") || phone == qsl("333") || phone == qsl("111") || (phone.startsWith(qsl("42")) && (phone.length() == 2 || phone.length() == 5 || phone == qsl("4242")));
|
|
||||||
}
|
|
||||||
|
|
||||||
void quit() {
|
void quit() {
|
||||||
if (quitting()) {
|
if (quitting()) {
|
||||||
return;
|
return;
|
||||||
|
@ -197,8 +197,6 @@ namespace App {
|
|||||||
|
|
||||||
void checkImageCacheSize();
|
void checkImageCacheSize();
|
||||||
|
|
||||||
bool isValidPhone(QString phone);
|
|
||||||
|
|
||||||
enum LaunchState {
|
enum LaunchState {
|
||||||
Launched = 0,
|
Launched = 0,
|
||||||
QuitRequested = 1,
|
QuitRequested = 1,
|
||||||
|
@ -36,6 +36,16 @@ constexpr auto kMaxGroupChannelTitle = 255; // See also edit_peer_info_box.
|
|||||||
constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box.
|
constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box.
|
||||||
constexpr auto kMinUsernameLength = 5;
|
constexpr auto kMinUsernameLength = 5;
|
||||||
|
|
||||||
|
bool IsValidPhone(QString phone) {
|
||||||
|
phone = phone.replace(QRegularExpression(qsl("[^\\d]")), QString());
|
||||||
|
return (phone.length() >= 8)
|
||||||
|
|| (phone == qsl("333"))
|
||||||
|
|| (phone.startsWith(qsl("42"))
|
||||||
|
&& (phone.length() == 2
|
||||||
|
|| phone.length() == 5
|
||||||
|
|| phone == qsl("4242")));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
style::InputField CreateBioFieldStyle() {
|
style::InputField CreateBioFieldStyle() {
|
||||||
@ -208,7 +218,7 @@ void AddContactBox::save() {
|
|||||||
_first->showError();
|
_first->showError();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!_user && !App::isValidPhone(phone)) {
|
} else if (!_user && !IsValidPhone(phone)) {
|
||||||
_phone->setFocus();
|
_phone->setFocus();
|
||||||
_phone->showError();
|
_phone->showError();
|
||||||
return;
|
return;
|
||||||
|
@ -412,7 +412,7 @@ public:
|
|||||||
|
|
||||||
void serviceNotification(
|
void serviceNotification(
|
||||||
const TextWithEntities &message,
|
const TextWithEntities &message,
|
||||||
const MTPMessageMedia &media);
|
const MTPMessageMedia &media = MTP_messageMediaEmpty());
|
||||||
|
|
||||||
void forgetMedia();
|
void forgetMedia();
|
||||||
|
|
||||||
|
@ -49,6 +49,13 @@ Locale: ") + Platform::SystemLanguage();
|
|||||||
UrlClickHandler::Open(url);
|
UrlClickHandler::Open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AllowPhoneAttempt(const QString &phone) {
|
||||||
|
const auto digits = ranges::count_if(
|
||||||
|
phone,
|
||||||
|
[](QChar ch) { return ch.isNumber(); });
|
||||||
|
return (digits > 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, data)
|
PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, data)
|
||||||
@ -138,7 +145,8 @@ void PhoneWidget::onInputChange() {
|
|||||||
void PhoneWidget::submit() {
|
void PhoneWidget::submit() {
|
||||||
if (_sentRequest || isHidden()) return;
|
if (_sentRequest || isHidden()) return;
|
||||||
|
|
||||||
if (!App::isValidPhone(fullNumber())) {
|
const auto phone = fullNumber();
|
||||||
|
if (!AllowPhoneAttempt(phone)) {
|
||||||
showPhoneError(langFactory(lng_bad_phone));
|
showPhoneError(langFactory(lng_bad_phone));
|
||||||
_phone->setFocus();
|
_phone->setFocus();
|
||||||
return;
|
return;
|
||||||
@ -148,7 +156,7 @@ void PhoneWidget::submit() {
|
|||||||
|
|
||||||
_checkRequest->start(1000);
|
_checkRequest->start(1000);
|
||||||
|
|
||||||
_sentPhone = fullNumber();
|
_sentPhone = phone;
|
||||||
Messenger::Instance().mtp()->setUserPhone(_sentPhone);
|
Messenger::Instance().mtp()->setUserPhone(_sentPhone);
|
||||||
//_sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail));
|
//_sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail));
|
||||||
_sentRequest = MTP::send(
|
_sentRequest = MTP::send(
|
||||||
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "support/support_common.h"
|
#include "support/support_common.h"
|
||||||
|
#include "support/support_templates.h"
|
||||||
#include "auth_session.h"
|
#include "auth_session.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
@ -960,8 +961,16 @@ void SetupSupport(not_null<Ui::VerticalLayout*> container) {
|
|||||||
Local::writeUserSettings();
|
Local::writeUserSettings();
|
||||||
}, inner->lifetime());
|
}, inner->lifetime());
|
||||||
|
|
||||||
|
|
||||||
AddSkip(inner, st::settingsCheckboxesSkip);
|
AddSkip(inner, st::settingsCheckboxesSkip);
|
||||||
|
|
||||||
|
AddButton(
|
||||||
|
inner,
|
||||||
|
rpl::single(qsl("Reload templates")),
|
||||||
|
st::settingsButton
|
||||||
|
)->addClickHandler([=] {
|
||||||
|
Auth().supportTemplates()->reload();
|
||||||
|
});
|
||||||
|
AddSkip(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::Chat(QWidget *parent, not_null<UserData*> self)
|
Chat::Chat(QWidget *parent, not_null<UserData*> self)
|
||||||
|
@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
*/
|
*/
|
||||||
#include "support/support_templates.h"
|
#include "support/support_templates.h"
|
||||||
|
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "auth_session.h"
|
||||||
|
|
||||||
namespace Support {
|
namespace Support {
|
||||||
namespace details {
|
namespace details {
|
||||||
namespace {
|
namespace {
|
||||||
@ -14,6 +17,18 @@ namespace {
|
|||||||
constexpr auto kQueryLimit = 10;
|
constexpr auto kQueryLimit = 10;
|
||||||
constexpr auto kWeightStep = 1000;
|
constexpr auto kWeightStep = 1000;
|
||||||
|
|
||||||
|
struct Delta {
|
||||||
|
std::vector<const TemplatesQuestion*> added;
|
||||||
|
std::vector<const TemplatesQuestion*> changed;
|
||||||
|
std::vector<const TemplatesQuestion*> removed;
|
||||||
|
|
||||||
|
std::map<QString, QStringList> keys;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return !added.empty() || !changed.empty() || !removed.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool IsTemplatesFile(const QString &file) {
|
bool IsTemplatesFile(const QString &file) {
|
||||||
return file.startsWith(qstr("tl_"), Qt::CaseInsensitive)
|
return file.startsWith(qstr("tl_"), Qt::CaseInsensitive)
|
||||||
&& file.endsWith(qstr(".txt"), Qt::CaseInsensitive);
|
&& file.endsWith(qstr(".txt"), Qt::CaseInsensitive);
|
||||||
@ -35,79 +50,117 @@ struct FileResult {
|
|||||||
QStringList errors;
|
QStringList errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileResult ReadFromBlob(const QByteArray &blob) {
|
enum class ReadState {
|
||||||
auto result = FileResult();
|
None,
|
||||||
const auto lines = blob.split('\n');
|
Question,
|
||||||
|
Keys,
|
||||||
|
Value,
|
||||||
|
Url,
|
||||||
|
};
|
||||||
|
|
||||||
enum class State {
|
template <typename StateChange, typename LineCallback>
|
||||||
None,
|
void ReadByLine(
|
||||||
Question,
|
const QByteArray &blob,
|
||||||
Keys,
|
StateChange &&stateChange,
|
||||||
Value,
|
LineCallback &&lineCallback) {
|
||||||
MoreValue,
|
using State = ReadState;
|
||||||
Url,
|
|
||||||
};
|
|
||||||
auto state = State::None;
|
auto state = State::None;
|
||||||
QStringList keys;
|
auto hadKeys = false;
|
||||||
QString question, value;
|
auto hadValue = false;
|
||||||
const auto pushQuestion = [&] {
|
for (const auto &utf : blob.split('\n')) {
|
||||||
const auto normalized = NormalizeQuestion(question);
|
|
||||||
if (!normalized.isEmpty()) {
|
|
||||||
result.result.questions.emplace(
|
|
||||||
normalized,
|
|
||||||
TemplatesQuestion{ question, keys, value });
|
|
||||||
}
|
|
||||||
question = value = QString();
|
|
||||||
keys = QStringList();
|
|
||||||
};
|
|
||||||
for (const auto &utf : lines) {
|
|
||||||
const auto line = QString::fromUtf8(utf).trimmed();
|
const auto line = QString::fromUtf8(utf).trimmed();
|
||||||
const auto match = QRegularExpression(
|
const auto match = QRegularExpression(
|
||||||
qsl("^\\{([A-Z_]+)\\}$")
|
qsl("^\\{([A-Z_]+)\\}$")
|
||||||
).match(line);
|
).match(line);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
const auto token = match.captured(1);
|
const auto token = match.captured(1);
|
||||||
if (state == State::Value || state == State::MoreValue) {
|
if (state == State::Value) {
|
||||||
pushQuestion();
|
hadKeys = hadValue = false;
|
||||||
}
|
}
|
||||||
if (token == qstr("VALUE")) {
|
const auto newState = [&] {
|
||||||
state = value.isEmpty() ? State::Value : State::None;
|
if (token == qstr("VALUE")) {
|
||||||
} else if (token == qstr("KEYS")) {
|
return hadValue ? State::None : State::Value;
|
||||||
state = keys.isEmpty() ? State::Keys : State::None;
|
} else if (token == qstr("KEYS")) {
|
||||||
} else if (token == qstr("QUESTION")) {
|
return hadKeys ? State::None : State::Keys;
|
||||||
state = State::Question;
|
} else if (token == qstr("QUESTION")) {
|
||||||
} else if (token == qstr("URL")) {
|
return State::Question;
|
||||||
state = State::Url;
|
} else if (token == qstr("URL")) {
|
||||||
} else {
|
return State::Url;
|
||||||
state = State::None;
|
} else {
|
||||||
|
return State::None;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
stateChange(state, newState);
|
||||||
|
state = newState;
|
||||||
|
lineCallback(state, line, true);
|
||||||
|
} else {
|
||||||
|
if (!line.isEmpty()) {
|
||||||
|
if (state == State::Value) {
|
||||||
|
hadValue = true;
|
||||||
|
} else if (state == State::Keys) {
|
||||||
|
hadKeys = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
lineCallback(state, line, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
QString ReadByLineGetUrl(const QByteArray &blob, Callback &&callback) {
|
||||||
|
using State = ReadState;
|
||||||
|
auto url = QString();
|
||||||
|
auto question = TemplatesQuestion();
|
||||||
|
const auto call = [&] {
|
||||||
|
while (question.value.endsWith('\n')) {
|
||||||
|
question.value.chop(1);
|
||||||
|
}
|
||||||
|
return callback(base::take(question));
|
||||||
|
};
|
||||||
|
ReadByLine(blob, [&](State was, State now) {
|
||||||
|
if (was == State::Value) {
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
}, [&](State state, const QString &line, bool stateChangeLine) {
|
||||||
|
if (stateChangeLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State::Keys:
|
case State::Keys:
|
||||||
if (!line.isEmpty()) {
|
if (!line.isEmpty()) {
|
||||||
keys.push_back(line);
|
question.keys.push_back(line);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case State::MoreValue:
|
|
||||||
value += '\n';
|
|
||||||
[[fallthrough]];
|
|
||||||
case State::Value:
|
case State::Value:
|
||||||
value += line;
|
if (!question.value.isEmpty()) {
|
||||||
state = State::MoreValue;
|
question.value += '\n';
|
||||||
|
}
|
||||||
|
question.value += line;
|
||||||
break;
|
break;
|
||||||
case State::Question:
|
case State::Question:
|
||||||
if (question.isEmpty()) question = line;
|
if (question.question.isEmpty()) {
|
||||||
|
question.question = line;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case State::Url:
|
case State::Url:
|
||||||
if (result.result.url.isEmpty()) {
|
if (url.isEmpty()) {
|
||||||
result.result.url = line;
|
url = line;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
pushQuestion();
|
call();
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileResult ReadFromBlob(const QByteArray &blob) {
|
||||||
|
auto result = FileResult();
|
||||||
|
result.result.url = ReadByLineGetUrl(blob, [&](TemplatesQuestion &&q) {
|
||||||
|
const auto normalized = NormalizeQuestion(q.question);
|
||||||
|
if (!normalized.isEmpty()) {
|
||||||
|
result.result.questions.emplace(normalized, std::move(q));
|
||||||
|
}
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +179,64 @@ FileResult ReadFile(const QString &path) {
|
|||||||
return ReadFromBlob(blob);
|
return ReadFromBlob(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WriteWithOwnUrlAndKeys(
|
||||||
|
QIODevice &device,
|
||||||
|
const QByteArray &blob,
|
||||||
|
const QString &url,
|
||||||
|
const Delta &delta) {
|
||||||
|
device.write("{URL}\n");
|
||||||
|
device.write(url.toUtf8());
|
||||||
|
device.write("\n\n");
|
||||||
|
|
||||||
|
using State = ReadState;
|
||||||
|
auto question = QString();
|
||||||
|
auto normalized = QString();
|
||||||
|
auto ownKeysWritten = false;
|
||||||
|
ReadByLine(blob, [&](State was, State now) {
|
||||||
|
if (was == State::Value) {
|
||||||
|
question = normalized = QString();
|
||||||
|
}
|
||||||
|
}, [&](State state, const QString &line, bool stateChangeLine) {
|
||||||
|
const auto writeLine = [&] {
|
||||||
|
device.write(line.toUtf8());
|
||||||
|
device.write("\n", 1);
|
||||||
|
};
|
||||||
|
switch (state) {
|
||||||
|
case State::Keys:
|
||||||
|
if (stateChangeLine) {
|
||||||
|
writeLine();
|
||||||
|
ownKeysWritten = [&] {
|
||||||
|
if (normalized.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto i = delta.keys.find(normalized);
|
||||||
|
if (i == end(delta.keys)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
device.write(i->second.join('\n').toUtf8());
|
||||||
|
device.write("\n", 1);
|
||||||
|
return true;
|
||||||
|
}();
|
||||||
|
} else if (!ownKeysWritten) {
|
||||||
|
writeLine();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::Value:
|
||||||
|
writeLine();
|
||||||
|
break;
|
||||||
|
case State::Question:
|
||||||
|
writeLine();
|
||||||
|
if (!stateChangeLine && question.isEmpty()) {
|
||||||
|
question = line;
|
||||||
|
normalized = NormalizeQuestion(line);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::Url:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
struct FilesResult {
|
struct FilesResult {
|
||||||
TemplatesData result;
|
TemplatesData result;
|
||||||
TemplatesIndex index;
|
TemplatesIndex index;
|
||||||
@ -175,15 +286,123 @@ TemplatesIndex ComputeIndex(const TemplatesData &data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto to_vector = [](auto &&range) {
|
|
||||||
return range | ranges::to_vector;
|
|
||||||
};
|
|
||||||
auto result = TemplatesIndex();
|
auto result = TemplatesIndex();
|
||||||
for (const auto &[ch, unique] : uniqueFirst) {
|
for (const auto &[ch, unique] : uniqueFirst) {
|
||||||
result.first.emplace(ch, to_vector(unique));
|
result.first.emplace(ch, unique | ranges::to_vector);
|
||||||
}
|
}
|
||||||
for (const auto &[id, unique] : uniqueFull) {
|
for (const auto &[id, unique] : uniqueFull) {
|
||||||
result.full.emplace(id, to_vector(unique));
|
result.full.emplace(id, unique | ranges::to_vector);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplaceFileIndex(
|
||||||
|
TemplatesIndex &result,
|
||||||
|
TemplatesIndex &&source,
|
||||||
|
const QString &path) {
|
||||||
|
for (auto i = begin(result.full); i != end(result.full);) {
|
||||||
|
if (i->first.first == path) {
|
||||||
|
i = result.full.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &[id, list] : source.full) {
|
||||||
|
result.full.emplace(id, std::move(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
using Id = TemplatesIndex::Id;
|
||||||
|
for (auto &[ch, list] : result.first) {
|
||||||
|
auto i = ranges::lower_bound(
|
||||||
|
list,
|
||||||
|
std::make_pair(path, QString()));
|
||||||
|
auto j = std::find_if(i, end(list), [&](const Id &id) {
|
||||||
|
return id.first != path;
|
||||||
|
});
|
||||||
|
list.erase(i, j);
|
||||||
|
}
|
||||||
|
for (auto &[ch, list] : source.first) {
|
||||||
|
auto &to = result.first[ch];
|
||||||
|
to.insert(
|
||||||
|
end(to),
|
||||||
|
std::make_move_iterator(begin(list)),
|
||||||
|
std::make_move_iterator(end(list)));
|
||||||
|
ranges::sort(to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Delta ComputeDelta(const TemplatesFile &was, const TemplatesFile &now) {
|
||||||
|
auto result = Delta();
|
||||||
|
for (const auto &[normalized, question] : now.questions) {
|
||||||
|
const auto i = was.questions.find(normalized);
|
||||||
|
if (i == end(was.questions)) {
|
||||||
|
result.added.push_back(&question);
|
||||||
|
} else {
|
||||||
|
result.keys.emplace(normalized, i->second.keys);
|
||||||
|
if (i->second.value != question.value) {
|
||||||
|
result.changed.push_back(&question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &[normalized, question] : was.questions) {
|
||||||
|
if (result.keys.find(normalized) == end(result.keys)) {
|
||||||
|
result.removed.push_back(&question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FormatUpdateNotification(const QString &path, const Delta &delta) {
|
||||||
|
auto result = qsl("Template file '%1' updated!\n\n").arg(path);
|
||||||
|
if (!delta.added.empty()) {
|
||||||
|
result += qstr("-------- Added --------\n\n");
|
||||||
|
for (const auto question : delta.added) {
|
||||||
|
result += qsl("Q: %1\nK: %2\nA: %3\n\n"
|
||||||
|
).arg(question->question
|
||||||
|
).arg(question->keys.join(qsl(", "))
|
||||||
|
).arg(question->value.trimmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!delta.changed.empty()) {
|
||||||
|
result += qstr("-------- Modified --------\n\n");
|
||||||
|
for (const auto question : delta.changed) {
|
||||||
|
result += qsl("Q: %1\nA: %2\n\n"
|
||||||
|
).arg(question->question
|
||||||
|
).arg(question->value.trimmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!delta.removed.empty()) {
|
||||||
|
result += qstr("-------- Removed --------\n\n");
|
||||||
|
for (const auto question : delta.removed) {
|
||||||
|
result += qsl("Q: %1\n\n").arg(question->question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString UpdateFile(
|
||||||
|
const QString &path,
|
||||||
|
const QByteArray &content,
|
||||||
|
const QString &url,
|
||||||
|
const Delta &delta) {
|
||||||
|
auto result = QString();
|
||||||
|
const auto full = cWorkingDir() + "TEMPLATES/" + path;
|
||||||
|
const auto old = full + qstr(".old");
|
||||||
|
QFile(old).remove();
|
||||||
|
if (QFile(full).copy(old)) {
|
||||||
|
result += qsl("(old file saved at '%1')"
|
||||||
|
).arg(path + qstr(".old"));
|
||||||
|
|
||||||
|
QFile f(full);
|
||||||
|
if (f.open(QIODevice::WriteOnly)) {
|
||||||
|
WriteWithOwnUrlAndKeys(f, content, url, delta);
|
||||||
|
} else {
|
||||||
|
result += qsl("\n\nError: could not open new file '%1'!"
|
||||||
|
).arg(full);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += qsl("Error: could not save old file '%1'!"
|
||||||
|
).arg(old);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -191,6 +410,11 @@ TemplatesIndex ComputeIndex(const TemplatesData &data) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
} // namespace details
|
} // namespace details
|
||||||
|
|
||||||
|
struct Templates::Updates {
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
std::map<QString, QNetworkReply*> requests;
|
||||||
|
};
|
||||||
|
|
||||||
Templates::Templates(not_null<AuthSession*> session) : _session(session) {
|
Templates::Templates(not_null<AuthSession*> session) : _session(session) {
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
@ -198,7 +422,7 @@ Templates::Templates(not_null<AuthSession*> session) : _session(session) {
|
|||||||
void Templates::reload() {
|
void Templates::reload() {
|
||||||
if (_reloadAfterRead) {
|
if (_reloadAfterRead) {
|
||||||
return;
|
return;
|
||||||
} else if (_reading.alive()) {
|
} else if (_reading.alive() || _updates) {
|
||||||
_reloadAfterRead = true;
|
_reloadAfterRead = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -230,6 +454,112 @@ void Templates::reload() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Templates::ensureUpdatesCreated() {
|
||||||
|
if (_updates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updates = std::make_unique<Updates>();
|
||||||
|
QObject::connect(
|
||||||
|
&_updates->manager,
|
||||||
|
&QNetworkAccessManager::finished,
|
||||||
|
[=](QNetworkReply *reply) { updateRequestFinished(reply); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Templates::update() {
|
||||||
|
auto errors = QStringList();
|
||||||
|
const auto sendRequest = [&](const QString &path, const QString &url) {
|
||||||
|
ensureUpdatesCreated();
|
||||||
|
if (_updates->requests.find(path) != end(_updates->requests)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updates->requests.emplace(
|
||||||
|
path,
|
||||||
|
_updates->manager.get(QNetworkRequest(url)));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[path, file] : _data.files) {
|
||||||
|
if (!file.url.isEmpty()) {
|
||||||
|
sendRequest(path, file.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Templates::updateRequestFinished(QNetworkReply *reply) {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
const auto path = [&] {
|
||||||
|
for (const auto &[file, sent] : _updates->requests) {
|
||||||
|
if (sent == reply) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}();
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updates->requests[path] = nullptr;
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
const auto message = qsl(
|
||||||
|
"Error: template update failed, url '%1', error %2, %3"
|
||||||
|
).arg(reply->url().toDisplayString()
|
||||||
|
).arg(reply->error()
|
||||||
|
).arg(reply->errorString());
|
||||||
|
_session->data().serviceNotification({ message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG(("Got template from url '%1'"
|
||||||
|
).arg(reply->url().toDisplayString()));
|
||||||
|
const auto content = reply->readAll();
|
||||||
|
crl::async([=, weak = base::make_weak(this)] {
|
||||||
|
auto result = details::ReadFromBlob(content);
|
||||||
|
auto one = details::TemplatesData();
|
||||||
|
one.files.emplace(path, std::move(result.result));
|
||||||
|
auto index = details::ComputeIndex(one);
|
||||||
|
crl::on_main(weak, [
|
||||||
|
=,
|
||||||
|
one = std::move(one),
|
||||||
|
errors = std::move(result.errors),
|
||||||
|
index = std::move(index)
|
||||||
|
]() mutable {
|
||||||
|
details::ReplaceFileIndex(_index, details::ComputeIndex(one), path);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
_errors.fire(std::move(errors));
|
||||||
|
}
|
||||||
|
auto &existing = _data.files.at(path);
|
||||||
|
auto &parsed = one.files.at(path);
|
||||||
|
if (const auto delta = details::ComputeDelta(existing, parsed)) {
|
||||||
|
const auto text = details::FormatUpdateNotification(
|
||||||
|
path,
|
||||||
|
delta);
|
||||||
|
const auto copy = details::UpdateFile(
|
||||||
|
path,
|
||||||
|
content,
|
||||||
|
existing.url,
|
||||||
|
delta);
|
||||||
|
const auto full = text + copy;
|
||||||
|
_session->data().serviceNotification({ full });
|
||||||
|
}
|
||||||
|
_data.files.at(path) = std::move(one.files.at(path));
|
||||||
|
|
||||||
|
_updates->requests.erase(path);
|
||||||
|
checkUpdateFinished();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Templates::checkUpdateFinished() {
|
||||||
|
if (!_updates || !_updates->requests.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updates = nullptr;
|
||||||
|
if (base::take(_reloadAfterRead)) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Templates::~Templates() = default;
|
||||||
|
|
||||||
auto Templates::query(const QString &text) const -> std::vector<Question> {
|
auto Templates::query(const QString &text) const -> std::vector<Question> {
|
||||||
const auto words = TextUtilities::PrepareSearchWords(text);
|
const auto words = TextUtilities::PrepareSearchWords(text);
|
||||||
const auto questions = [&](const QString &word) {
|
const auto questions = [&](const QString &word) {
|
||||||
@ -295,8 +625,4 @@ auto Templates::query(const QString &text) const -> std::vector<Question> {
|
|||||||
}) | ranges::view::take(details::kQueryLimit) | ranges::to_vector;
|
}) | ranges::view::take(details::kQueryLimit) | ranges::to_vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Templates::update() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Support
|
} // namespace Support
|
||||||
|
@ -52,8 +52,15 @@ public:
|
|||||||
return _errors.events();
|
return _errors.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~Templates();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Updates;
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
void ensureUpdatesCreated();
|
||||||
|
void updateRequestFinished(QNetworkReply *reply);
|
||||||
|
void checkUpdateFinished();
|
||||||
|
|
||||||
not_null<AuthSession*> _session;
|
not_null<AuthSession*> _session;
|
||||||
|
|
||||||
@ -63,6 +70,8 @@ private:
|
|||||||
base::binary_guard _reading;
|
base::binary_guard _reading;
|
||||||
bool _reloadAfterRead = false;
|
bool _reloadAfterRead = false;
|
||||||
|
|
||||||
|
std::unique_ptr<Updates> _updates;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Support
|
} // namespace Support
|
||||||
|
Loading…
Reference in New Issue
Block a user