Support "Device Storage" web app feature.
This commit is contained in:
@@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "inline_bots/inline_bot_confirm_prepared.h"
|
||||
#include "inline_bots/inline_bot_downloads.h"
|
||||
#include "inline_bots/inline_bot_storage.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
@@ -1502,7 +1503,7 @@ void WebViewInstance::botHandleInvoice(QString slug) {
|
||||
}
|
||||
};
|
||||
Payments::CheckoutProcess::Start(
|
||||
&_bot->session(),
|
||||
_session,
|
||||
slug,
|
||||
reactivate,
|
||||
nonPanelPaymentFormFactory(reactivate));
|
||||
@@ -1698,6 +1699,20 @@ void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool WebViewInstance::botStorageWrite(
|
||||
QString key,
|
||||
std::optional<QString> value) {
|
||||
return _session->attachWebView().storage().write(_bot->id, key, value);
|
||||
}
|
||||
|
||||
std::optional<QString> WebViewInstance::botStorageRead(QString key) {
|
||||
return _session->attachWebView().storage().read(_bot->id, key);
|
||||
}
|
||||
|
||||
void WebViewInstance::botStorageClear() {
|
||||
_session->attachWebView().storage().clear(_bot->id);
|
||||
}
|
||||
|
||||
void WebViewInstance::botRequestEmojiStatusAccess(
|
||||
Fn<void(bool allowed)> callback) {
|
||||
if (_bot->botInfo->canManageEmojiStatus) {
|
||||
@@ -1955,20 +1970,20 @@ void WebViewInstance::botDownloadFile(
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
_bot->session().attachWebView().downloads().start({
|
||||
_session->attachWebView().downloads().start({
|
||||
.bot = _bot,
|
||||
.url = request.url,
|
||||
.path = path,
|
||||
});
|
||||
callback(true);
|
||||
};
|
||||
_bot->session().api().request(MTPbots_CheckDownloadFileParams(
|
||||
_session->api().request(MTPbots_CheckDownloadFileParams(
|
||||
_bot->inputUser,
|
||||
MTP_string(request.name),
|
||||
MTP_string(request.url)
|
||||
)).done([=] {
|
||||
_panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{
|
||||
.session = &_bot->session(),
|
||||
.session = _session,
|
||||
.bot = _bot->name(),
|
||||
.name = base::FileNameFromUserString(request.name),
|
||||
.url = request.url,
|
||||
@@ -2018,7 +2033,7 @@ void WebViewInstance::botOpenPrivacyPolicy() {
|
||||
};
|
||||
const auto openUrl = [=](const QString &url) {
|
||||
Core::App().iv().openWithIvPreferred(
|
||||
&_bot->session(),
|
||||
_session,
|
||||
url,
|
||||
makeOtherContext(false));
|
||||
};
|
||||
@@ -2092,6 +2107,7 @@ std::shared_ptr<Main::SessionShow> WebViewInstance::uiShow() {
|
||||
AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _downloads(std::make_unique<Downloads>(session))
|
||||
, _storage(std::make_unique<Storage>(session))
|
||||
, _refreshTimer([=] { requestBots(); }) {
|
||||
_refreshTimer.callEach(kRefreshBotsTimeout);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace InlineBots {
|
||||
|
||||
class WebViewInstance;
|
||||
class Downloads;
|
||||
class Storage;
|
||||
|
||||
enum class PeerType : uint8 {
|
||||
SameBot = 0x01,
|
||||
@@ -276,6 +277,9 @@ private:
|
||||
QString query) override;
|
||||
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
bool botStorageWrite(QString key, std::optional<QString> value) override;
|
||||
std::optional<QString> botStorageRead(QString key) override;
|
||||
void botStorageClear() override;
|
||||
void botRequestEmojiStatusAccess(
|
||||
Fn<void(bool allowed)> callback) override;
|
||||
void botSharePhone(Fn<void(bool shared)> callback) override;
|
||||
@@ -322,6 +326,9 @@ public:
|
||||
[[nodiscard]] Downloads &downloads() const {
|
||||
return *_downloads;
|
||||
}
|
||||
[[nodiscard]] Storage &storage() const {
|
||||
return *_storage;
|
||||
}
|
||||
|
||||
void open(WebViewDescriptor &&descriptor);
|
||||
void openByUsername(
|
||||
@@ -394,6 +401,7 @@ private:
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const std::unique_ptr<Downloads> _downloads;
|
||||
const std::unique_ptr<Storage> _storage;
|
||||
|
||||
base::Timer _refreshTimer;
|
||||
|
||||
|
||||
181
Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp
Normal file
181
Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "inline_bots/inline_bot_storage.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "storage/storage_account.h"
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace InlineBots {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxStorageSize = (5 << 20);
|
||||
|
||||
[[nodiscard]] uint64 KeyHash(const QString &key) {
|
||||
return XXH64(key.data(), key.size(), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Storage::Storage(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
bool Storage::write(
|
||||
PeerId botId,
|
||||
const QString &key,
|
||||
const std::optional<QString> &value) {
|
||||
if (value && value->size() > kMaxStorageSize) {
|
||||
return false;
|
||||
}
|
||||
readFromDisk(botId);
|
||||
auto i = _lists.find(botId);
|
||||
if (i == end(_lists)) {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
i = _lists.emplace(botId).first;
|
||||
}
|
||||
auto &list = i->second;
|
||||
const auto hash = KeyHash(key);
|
||||
auto j = list.data.find(hash);
|
||||
if (j == end(list.data)) {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
j = list.data.emplace(hash).first;
|
||||
}
|
||||
auto &bykey = j->second;
|
||||
const auto k = ranges::find(bykey, key, &Entry::key);
|
||||
if (k == end(bykey) && !value) {
|
||||
return true;
|
||||
}
|
||||
const auto size = list.totalSize
|
||||
- (k != end(bykey) ? (key.size() + k->value.size()) : 0)
|
||||
+ (value ? (key.size() + value->size()) : 0);
|
||||
if (size > kMaxStorageSize) {
|
||||
return false;
|
||||
}
|
||||
if (k == end(bykey)) {
|
||||
bykey.emplace_back(key, *value);
|
||||
++list.keysCount;
|
||||
} else if (value) {
|
||||
k->value = *value;
|
||||
} else {
|
||||
bykey.erase(k);
|
||||
--list.keysCount;
|
||||
}
|
||||
if (bykey.empty()) {
|
||||
list.data.erase(j);
|
||||
if (list.data.empty()) {
|
||||
Assert(size == 0);
|
||||
_lists.erase(i);
|
||||
} else {
|
||||
list.totalSize = size;
|
||||
}
|
||||
} else {
|
||||
list.totalSize = size;
|
||||
}
|
||||
saveToDisk(botId);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> Storage::read(PeerId botId, const QString &key) {
|
||||
readFromDisk(botId);
|
||||
const auto i = _lists.find(botId);
|
||||
if (i == end(_lists)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &list = i->second;
|
||||
const auto j = list.data.find(KeyHash(key));
|
||||
if (j == end(list.data)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &bykey = j->second;
|
||||
const auto k = ranges::find(bykey, key, &Entry::key);
|
||||
if (k == end(bykey)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return k->value;
|
||||
}
|
||||
|
||||
void Storage::clear(PeerId botId) {
|
||||
if (_lists.remove(botId)) {
|
||||
saveToDisk(botId);
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::saveToDisk(PeerId botId) {
|
||||
const auto i = _lists.find(botId);
|
||||
if (i != end(_lists)) {
|
||||
_session->local().writeBotStorage(botId, Serialize(i->second));
|
||||
} else {
|
||||
_session->local().writeBotStorage(botId, QByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::readFromDisk(PeerId botId) {
|
||||
const auto serialized = _session->local().readBotStorage(botId);
|
||||
if (!serialized.isEmpty()) {
|
||||
_lists[botId] = Deserialize(serialized);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Storage::Serialize(const List &list) {
|
||||
auto result = QByteArray();
|
||||
const auto size = sizeof(quint32)
|
||||
+ (list.keysCount * sizeof(quint32))
|
||||
+ (list.totalSize * sizeof(ushort));
|
||||
result.reserve(size);
|
||||
{
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
auto count = 0;
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream << quint32(list.keysCount);
|
||||
for (const auto &[hash, bykey] : list.data) {
|
||||
for (const auto &entry : bykey) {
|
||||
stream << entry.key << entry.value;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
Assert(count == list.keysCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Storage::List Storage::Deserialize(const QByteArray &serialized) {
|
||||
QDataStream stream(serialized);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
auto count = quint32();
|
||||
auto result = List();
|
||||
stream >> count;
|
||||
if (count > kMaxStorageSize) {
|
||||
return {};
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
auto entry = Entry();
|
||||
stream >> entry.key >> entry.value;
|
||||
const auto hash = KeyHash(entry.key);
|
||||
auto j = result.data.find(hash);
|
||||
if (j == end(result.data)) {
|
||||
j = result.data.emplace(hash).first;
|
||||
}
|
||||
auto &bykey = j->second;
|
||||
const auto k = ranges::find(bykey, entry.key, &Entry::key);
|
||||
if (k == end(bykey)) {
|
||||
bykey.push_back(entry);
|
||||
result.totalSize += entry.key.size() + entry.value.size();
|
||||
++result.keysCount;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace InlineBots
|
||||
52
Telegram/SourceFiles/inline_bots/inline_bot_storage.h
Normal file
52
Telegram/SourceFiles/inline_bots/inline_bot_storage.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace InlineBots {
|
||||
|
||||
class Storage final {
|
||||
public:
|
||||
explicit Storage(not_null<Main::Session*> session);
|
||||
|
||||
bool write(
|
||||
PeerId botId,
|
||||
const QString &key,
|
||||
const std::optional<QString> &value);
|
||||
std::optional<QString> read(PeerId botId, const QString &key);
|
||||
void clear(PeerId botId);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
QString key;
|
||||
QString value;
|
||||
};
|
||||
struct List {
|
||||
base::flat_map<uint64, std::vector<Entry>> data;
|
||||
int keysCount = 0;
|
||||
int totalSize = 0;
|
||||
};
|
||||
|
||||
void saveToDisk(PeerId botId);
|
||||
void readFromDisk(PeerId botId);
|
||||
|
||||
[[nodiscard]] static QByteArray Serialize(const List &list);
|
||||
[[nodiscard]] static List Deserialize(const QByteArray &serialized);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<PeerId, List> _lists;
|
||||
|
||||
};
|
||||
|
||||
} // namespace InlineBots
|
||||
Reference in New Issue
Block a user