mirror of https://github.com/procxx/kepka.git
Load emoji sets from the cloud.
This commit is contained in:
parent
de00e0e15c
commit
f48ae29f22
|
@ -172,15 +172,28 @@ public:
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int goToFirstFile() {
|
||||||
|
if (error() == UNZ_OK) {
|
||||||
|
_error = _handle ? unzGoToFirstFile(_handle) : -1;
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
int goToNextFile() {
|
||||||
|
if (error() == UNZ_OK) {
|
||||||
|
_error = _handle ? unzGoToNextFile(_handle) : -1;
|
||||||
|
}
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
int getCurrentFileInfo(
|
int getCurrentFileInfo(
|
||||||
unz_file_info *pfile_info,
|
unz_file_info *pfile_info,
|
||||||
char *szFileName,
|
char *szFileName,
|
||||||
uLong fileNameBufferSize,
|
uLong fileNameBufferSize,
|
||||||
void *extraField,
|
void *extraField,
|
||||||
uLong extraFieldBufferSize,
|
uLong extraFieldBufferSize,
|
||||||
char *szComment,
|
char *szComment,
|
||||||
uLong commentBufferSize
|
uLong commentBufferSize) {
|
||||||
) {
|
|
||||||
if (error() == UNZ_OK) {
|
if (error() == UNZ_OK) {
|
||||||
_error = _handle ? unzGetCurrentFileInfo(
|
_error = _handle ? unzGetCurrentFileInfo(
|
||||||
_handle,
|
_handle,
|
||||||
|
@ -196,6 +209,21 @@ public:
|
||||||
return error();
|
return error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getCurrentFileName() {
|
||||||
|
unz_file_info info = { 0 };
|
||||||
|
constexpr auto kMaxName = 128;
|
||||||
|
char name[kMaxName + 1] = { 0 };
|
||||||
|
const auto result = getCurrentFileInfo(
|
||||||
|
&info,
|
||||||
|
name,
|
||||||
|
kMaxName,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
0);
|
||||||
|
return (result == UNZ_OK) ? QString::fromUtf8(name) : QString();
|
||||||
|
}
|
||||||
|
|
||||||
int openCurrentFile() {
|
int openCurrentFile() {
|
||||||
if (error() == UNZ_OK) {
|
if (error() == UNZ_OK) {
|
||||||
_error = _handle ? unzOpenCurrentFile(_handle) : -1;
|
_error = _handle ? unzOpenCurrentFile(_handle) : -1;
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "chat_helpers/emoji_sets_manager.h"
|
#include "chat_helpers/emoji_sets_manager.h"
|
||||||
|
|
||||||
|
#include "mtproto/dedicated_file_loader.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/fade_wrap.h"
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
@ -14,7 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/effects/radial_animation.h"
|
#include "ui/effects/radial_animation.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "base/zlib_help.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
|
#include "messenger.h"
|
||||||
|
#include "mainwidget.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
|
@ -48,18 +52,7 @@ struct Active {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Loading {
|
using Loading = MTP::DedicatedLoader::Progress;
|
||||||
int offset = 0;
|
|
||||||
int size = 0;
|
|
||||||
|
|
||||||
inline bool operator<(const Loading &other) const {
|
|
||||||
return (offset < other.offset)
|
|
||||||
|| (offset == other.offset && size < other.size);
|
|
||||||
}
|
|
||||||
inline bool operator==(const Loading &other) const {
|
|
||||||
return (offset == other.offset) && (size == other.size);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
struct Failed {
|
struct Failed {
|
||||||
inline bool operator<(const Failed &other) const {
|
inline bool operator<(const Failed &other) const {
|
||||||
return false;
|
return false;
|
||||||
|
@ -77,17 +70,28 @@ using SetState = base::variant<
|
||||||
|
|
||||||
class Loader : public QObject {
|
class Loader : public QObject {
|
||||||
public:
|
public:
|
||||||
explicit Loader(int id);
|
Loader(QObject *parent, int id);
|
||||||
|
|
||||||
int id() const;
|
int id() const;
|
||||||
|
|
||||||
rpl::producer<SetState> state() const;
|
rpl::producer<SetState> state() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setImplementation(std::unique_ptr<MTP::DedicatedLoader> loader);
|
||||||
|
void unpack(const QString &path);
|
||||||
|
void finalize(const QString &path);
|
||||||
|
bool goodName(const QString &name) const;
|
||||||
|
bool writeCurrentFile(zlib::FileToRead &zip, const QString name) const;
|
||||||
|
void destroy();
|
||||||
|
void fail();
|
||||||
|
|
||||||
int _id = 0;
|
int _id = 0;
|
||||||
int _size = 0;
|
int _size = 0;
|
||||||
rpl::variable<SetState> _state;
|
rpl::variable<SetState> _state;
|
||||||
|
|
||||||
|
MTP::WeakInstance _mtproto;
|
||||||
|
std::unique_ptr<MTP::DedicatedLoader> _implementation;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Inner : public Ui::RpWidget {
|
class Inner : public Ui::RpWidget {
|
||||||
|
@ -111,6 +115,7 @@ private:
|
||||||
void setupCheck();
|
void setupCheck();
|
||||||
void setupLabels(const Set &set);
|
void setupLabels(const Set &set);
|
||||||
void setupHandler();
|
void setupHandler();
|
||||||
|
void load();
|
||||||
|
|
||||||
int _id = 0;
|
int _id = 0;
|
||||||
rpl::variable<SetState> _state;
|
rpl::variable<SetState> _state;
|
||||||
|
@ -126,6 +131,13 @@ int GetDownloadSize(int id) {
|
||||||
return ranges::find(sets, id, &Set::id)->size;
|
return ranges::find(sets, id, &Set::id)->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
|
||||||
|
constexpr auto kUsername = "tdhbcfiles";
|
||||||
|
const auto sets = Sets();
|
||||||
|
const auto i = ranges::find(sets, id, &Set::id);
|
||||||
|
return MTP::DedicatedLoader::Location{ kUsername, i->postId };
|
||||||
|
}
|
||||||
|
|
||||||
SetState ComputeState(int id) {
|
SetState ComputeState(int id) {
|
||||||
if (id == CurrentSetId()) {
|
if (id == CurrentSetId()) {
|
||||||
return Active();
|
return Active();
|
||||||
|
@ -145,16 +157,37 @@ QString StateDescription(const SetState &state) {
|
||||||
}, [](const Loading &data) {
|
}, [](const Loading &data) {
|
||||||
return lng_emoji_set_loading(
|
return lng_emoji_set_loading(
|
||||||
lt_progress,
|
lt_progress,
|
||||||
formatDownloadText(data.offset, data.size));
|
formatDownloadText(data.already, data.size));
|
||||||
}, [](const Failed &data) {
|
}, [](const Failed &data) {
|
||||||
return lang(lng_attach_failed);
|
return lang(lng_attach_failed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::Loader(int id)
|
QByteArray ReadFinalFile(const QString &path) {
|
||||||
: _id(id)
|
constexpr auto kMaxZipSize = 10 * 1024 * 1024;
|
||||||
|
auto file = QFile(path);
|
||||||
|
if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
return file.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader::Loader(QObject *parent, int id)
|
||||||
|
: QObject(parent)
|
||||||
|
, _id(id)
|
||||||
, _size(GetDownloadSize(_id))
|
, _size(GetDownloadSize(_id))
|
||||||
, _state(Loading{ 0, _size }) {
|
, _state(Loading{ 0, _size })
|
||||||
|
, _mtproto(Messenger::Instance().mtp()) {
|
||||||
|
const auto ready = [=](std::unique_ptr<MTP::DedicatedLoader> loader) {
|
||||||
|
if (loader) {
|
||||||
|
setImplementation(std::move(loader));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto location = GetDownloadLocation(id);
|
||||||
|
const auto folder = internal::SetDataPath(id);
|
||||||
|
MTP::StartDedicatedLoader(&_mtproto, location, folder, ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Loader::id() const {
|
int Loader::id() const {
|
||||||
|
@ -165,6 +198,95 @@ rpl::producer<SetState> Loader::state() const {
|
||||||
return _state.value();
|
return _state.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Loader::setImplementation(
|
||||||
|
std::unique_ptr<MTP::DedicatedLoader> loader) {
|
||||||
|
_implementation = std::move(loader);
|
||||||
|
auto convert = [](auto value) {
|
||||||
|
return SetState(value);
|
||||||
|
};
|
||||||
|
_state = _implementation->progress(
|
||||||
|
) | rpl::map([](const Loading &state) {
|
||||||
|
return SetState(state);
|
||||||
|
});
|
||||||
|
_implementation->failed(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
fail();
|
||||||
|
}, _implementation->lifetime());
|
||||||
|
|
||||||
|
_implementation->ready(
|
||||||
|
) | rpl::start_with_next([=](const QString &filepath) {
|
||||||
|
unpack(filepath);
|
||||||
|
}, _implementation->lifetime());
|
||||||
|
|
||||||
|
QDir(internal::SetDataPath(_id)).removeRecursively();
|
||||||
|
_implementation->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loader::unpack(const QString &path) {
|
||||||
|
const auto bytes = ReadFinalFile(path);
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto zip = zlib::FileToRead(bytes);
|
||||||
|
if (zip.goToFirstFile() != UNZ_OK) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
const auto name = zip.getCurrentFileName();
|
||||||
|
if (goodName(name) && !writeCurrentFile(zip, name)) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto jump = zip.goToNextFile();
|
||||||
|
if (jump == UNZ_END_OF_LIST_OF_FILE) {
|
||||||
|
break;
|
||||||
|
} else if (jump != UNZ_OK) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
finalize(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Loader::goodName(const QString &name) const {
|
||||||
|
return (name == qstr("config.json"))
|
||||||
|
|| (name.startsWith(qstr("emoji_"))
|
||||||
|
&& name.endsWith(qstr(".webp")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loader::finalize(const QString &path) {
|
||||||
|
QFile(path).remove();
|
||||||
|
if (!SwitchToSet(_id)) {
|
||||||
|
fail();
|
||||||
|
} else {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loader::fail() {
|
||||||
|
_state = Failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loader::destroy() {
|
||||||
|
GlobalLoaderValues.fire(nullptr);
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Loader::writeCurrentFile(
|
||||||
|
zlib::FileToRead &zip,
|
||||||
|
const QString name) const {
|
||||||
|
constexpr auto kMaxSize = 10 * 1024 * 1024;
|
||||||
|
const auto content = zip.readCurrentFileContent(kMaxSize);
|
||||||
|
if (content.isEmpty() || zip.error() != UNZ_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto folder = internal::SetDataPath(_id) + '/';
|
||||||
|
auto file = QFile(folder + name);
|
||||||
|
return file.open(QIODevice::WriteOnly)
|
||||||
|
&& (file.write(content) == content.size());
|
||||||
|
}
|
||||||
|
|
||||||
Inner::Inner(QWidget *parent) : RpWidget(parent) {
|
Inner::Inner(QWidget *parent) : RpWidget(parent) {
|
||||||
setupContent();
|
setupContent();
|
||||||
}
|
}
|
||||||
|
@ -233,9 +355,14 @@ void Row::setupHandler() {
|
||||||
const auto &state = _state.current();
|
const auto &state = _state.current();
|
||||||
return state.is<Ready>() || state.is<Available>();
|
return state.is<Ready>() || state.is<Available>();
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [=] {
|
if (_state.current().is<Available>()) {
|
||||||
|
load();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto delay = st::defaultRippleAnimation.hideDuration;
|
||||||
|
App::CallDelayed(delay, this, [=] {
|
||||||
if (!SwitchToSet(_id)) {
|
if (!SwitchToSet(_id)) {
|
||||||
// load
|
load();
|
||||||
} else {
|
} else {
|
||||||
delete GlobalLoader;
|
delete GlobalLoader;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +370,7 @@ void Row::setupHandler() {
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_state.value(
|
_state.value(
|
||||||
) | rpl::map([](const SetState &state) {
|
) | rpl::map([=](const SetState &state) {
|
||||||
return state.is<Ready>() || state.is<Available>();
|
return state.is<Ready>() || state.is<Available>();
|
||||||
}) | rpl::start_with_next([=](bool active) {
|
}) | rpl::start_with_next([=](bool active) {
|
||||||
setDisabled(!active);
|
setDisabled(!active);
|
||||||
|
@ -251,6 +378,11 @@ void Row::setupHandler() {
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Row::load() {
|
||||||
|
GlobalLoader = Ui::CreateChild<Loader>(App::main(), _id);
|
||||||
|
GlobalLoaderValues.fire(GlobalLoader.data());
|
||||||
|
}
|
||||||
|
|
||||||
void Row::setupCheck() {
|
void Row::setupCheck() {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
|
@ -286,10 +418,12 @@ void Row::setupLabels(const Set &set) {
|
||||||
set.name,
|
set.name,
|
||||||
Ui::FlatLabel::InitType::Simple,
|
Ui::FlatLabel::InitType::Simple,
|
||||||
st::localStorageRowTitle);
|
st::localStorageRowTitle);
|
||||||
|
name->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
const auto status = Ui::CreateChild<Ui::FlatLabel>(
|
const auto status = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
this,
|
this,
|
||||||
_state.value() | rpl::map(StateDescription),
|
_state.value() | rpl::map(StateDescription),
|
||||||
st::localStorageRowSize);
|
st::localStorageRowSize);
|
||||||
|
status->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
sizeValue(
|
sizeValue(
|
||||||
) | rpl::start_with_next([=](QSize size) {
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
|
|
|
@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/bytes.h"
|
#include "base/bytes.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "messenger.h"
|
#include "messenger.h"
|
||||||
#include "mtproto/session.h"
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
|
@ -39,8 +38,6 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kUpdaterTimeout = 10 * TimeMs(1000);
|
constexpr auto kUpdaterTimeout = 10 * TimeMs(1000);
|
||||||
constexpr auto kMaxResponseSize = 1024 * 1024;
|
constexpr auto kMaxResponseSize = 1024 * 1024;
|
||||||
constexpr auto kMaxUpdateSize = 256 * 1024 * 1024;
|
|
||||||
constexpr auto kChunkSize = 128 * 1024;
|
|
||||||
|
|
||||||
#ifdef TDESKTOP_DISABLE_AUTOUPDATE
|
#ifdef TDESKTOP_DISABLE_AUTOUPDATE
|
||||||
bool UpdaterIsDisabled = true;
|
bool UpdaterIsDisabled = true;
|
||||||
|
@ -64,52 +61,7 @@ using VersionInt = int;
|
||||||
using VersionChar = wchar_t;
|
using VersionChar = wchar_t;
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
class Loader : public base::has_weak_ptr {
|
using Loader = MTP::AbstractDedicatedLoader;
|
||||||
public:
|
|
||||||
Loader(const QString &filename, int chunkSize);
|
|
||||||
|
|
||||||
void start();
|
|
||||||
|
|
||||||
int alreadySize() const;
|
|
||||||
int totalSize() const;
|
|
||||||
|
|
||||||
rpl::producer<Progress> progress() const;
|
|
||||||
rpl::producer<QString> ready() const;
|
|
||||||
rpl::producer<> failed() const;
|
|
||||||
|
|
||||||
rpl::lifetime &lifetime();
|
|
||||||
|
|
||||||
virtual ~Loader() = default;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool startOutput();
|
|
||||||
void threadSafeFailed();
|
|
||||||
|
|
||||||
// Single threaded.
|
|
||||||
void writeChunk(bytes::const_span data, int totalSize);
|
|
||||||
|
|
||||||
private:
|
|
||||||
virtual void startLoading() = 0;
|
|
||||||
|
|
||||||
bool validateOutput();
|
|
||||||
void threadSafeProgress(Progress progress);
|
|
||||||
void threadSafeReady();
|
|
||||||
|
|
||||||
QString _filename;
|
|
||||||
QString _filepath;
|
|
||||||
int _chunkSize = 0;
|
|
||||||
|
|
||||||
QFile _output;
|
|
||||||
int _alreadySize = 0;
|
|
||||||
int _totalSize = 0;
|
|
||||||
mutable QMutex _sizesMutex;
|
|
||||||
rpl::event_stream<Progress> _progress;
|
|
||||||
rpl::event_stream<QString> _ready;
|
|
||||||
rpl::event_stream<> _failed;
|
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class Checker : public base::has_weak_ptr {
|
class Checker : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
|
@ -212,31 +164,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MtpWeak : private QObject, private base::Subscriber {
|
|
||||||
public:
|
|
||||||
MtpWeak(QPointer<MTP::Instance> instance);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void send(
|
|
||||||
const T &request,
|
|
||||||
Fn<void(const typename T::ResponseType &result)> done,
|
|
||||||
Fn<void(const RPCError &error)> fail,
|
|
||||||
MTP::ShiftedDcId dcId = 0);
|
|
||||||
|
|
||||||
bool valid() const;
|
|
||||||
QPointer<MTP::Instance> instance() const;
|
|
||||||
|
|
||||||
~MtpWeak();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void die();
|
|
||||||
bool removeRequest(mtpRequestId requestId);
|
|
||||||
|
|
||||||
QPointer<MTP::Instance> _instance;
|
|
||||||
std::map<mtpRequestId, Fn<void(const RPCError &)>> _requests;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class MtpChecker : public Checker {
|
class MtpChecker : public Checker {
|
||||||
public:
|
public:
|
||||||
MtpChecker(QPointer<MTP::Instance> instance, bool testing);
|
MtpChecker(QPointer<MTP::Instance> instance, bool testing);
|
||||||
|
@ -244,23 +171,11 @@ public:
|
||||||
void start() override;
|
void start() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FileLocation {
|
using FileLocation = MTP::DedicatedLoader::Location;
|
||||||
QString username;
|
|
||||||
int32 postId = 0;
|
|
||||||
};
|
|
||||||
struct ParsedFile {
|
|
||||||
QString name;
|
|
||||||
int32 size = 0;
|
|
||||||
MTP::DcId dcId = 0;
|
|
||||||
MTPInputFileLocation location;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Checker::fail;
|
using Checker::fail;
|
||||||
Fn<void(const RPCError &error)> failHandler();
|
Fn<void(const RPCError &error)> failHandler();
|
||||||
|
|
||||||
void resolveChannel(
|
|
||||||
const QString &username,
|
|
||||||
Fn<void(const MTPInputChannel &channel)> callback);
|
|
||||||
void gotMessage(const MTPmessages_Messages &result);
|
void gotMessage(const MTPmessages_Messages &result);
|
||||||
std::optional<FileLocation> parseMessage(
|
std::optional<FileLocation> parseMessage(
|
||||||
const MTPmessages_Messages &result) const;
|
const MTPmessages_Messages &result) const;
|
||||||
|
@ -268,42 +183,8 @@ private:
|
||||||
FileLocation validateLatestLocation(
|
FileLocation validateLatestLocation(
|
||||||
uint64 availableVersion,
|
uint64 availableVersion,
|
||||||
const FileLocation &location) const;
|
const FileLocation &location) const;
|
||||||
void gotFile(const MTPmessages_Messages &result);
|
|
||||||
std::optional<ParsedFile> parseFile(
|
|
||||||
const MTPmessages_Messages &result) const;
|
|
||||||
|
|
||||||
MtpWeak _mtp;
|
MTP::WeakInstance _mtp;
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class MtpLoader : public Loader {
|
|
||||||
public:
|
|
||||||
MtpLoader(
|
|
||||||
QPointer<MTP::Instance> instance,
|
|
||||||
const QString &name,
|
|
||||||
int32 size,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const MTPInputFileLocation &location);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Request {
|
|
||||||
int offset = 0;
|
|
||||||
QByteArray bytes;
|
|
||||||
};
|
|
||||||
void startLoading() override;
|
|
||||||
void sendRequest();
|
|
||||||
void gotPart(int offset, const MTPupload_File &result);
|
|
||||||
Fn<void(const RPCError &)> failHandler();
|
|
||||||
|
|
||||||
static constexpr auto kRequestsCount = 2;
|
|
||||||
static constexpr auto kNextRequestDelay = TimeMs(20);
|
|
||||||
|
|
||||||
std::deque<Request> _requests;
|
|
||||||
int32 _size = 0;
|
|
||||||
int _offset = 0;
|
|
||||||
MTP::DcId _dcId = 0;
|
|
||||||
MTPInputFileLocation _location;
|
|
||||||
MtpWeak _mtp;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -316,12 +197,16 @@ std::shared_ptr<Updater> GetUpdaterInstance() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString UpdatesFolder() {
|
||||||
|
return cWorkingDir() + qsl("tupdates");
|
||||||
|
}
|
||||||
|
|
||||||
void ClearAll() {
|
void ClearAll() {
|
||||||
psDeleteDir(cWorkingDir() + qsl("tupdates"));
|
psDeleteDir(UpdatesFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FindUpdateFile() {
|
QString FindUpdateFile() {
|
||||||
QDir updates(cWorkingDir() + "tupdates");
|
QDir updates(UpdatesFolder());
|
||||||
if (!updates.exists()) {
|
if (!updates.exists()) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
@ -594,24 +479,6 @@ bool UnpackUpdate(const QString &filepath) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<MTPInputChannel> ExtractChannel(
|
|
||||||
const MTPcontacts_ResolvedPeer &result) {
|
|
||||||
const auto &data = result.c_contacts_resolvedPeer();
|
|
||||||
if (const auto peer = peerFromMTP(data.vpeer)) {
|
|
||||||
for (const auto &chat : data.vchats.v) {
|
|
||||||
if (chat.type() == mtpc_channel) {
|
|
||||||
const auto &channel = chat.c_channel();
|
|
||||||
if (peer == peerFromChannel(channel.vid)) {
|
|
||||||
return MTP_inputChannel(
|
|
||||||
channel.vid,
|
|
||||||
channel.vaccess_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
bool ParseCommonMap(
|
bool ParseCommonMap(
|
||||||
const QByteArray &json,
|
const QByteArray &json,
|
||||||
|
@ -714,156 +581,6 @@ bool ParseCommonMap(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<MTPMessage> GetMessagesElement(
|
|
||||||
const MTPmessages_Messages &list) {
|
|
||||||
const auto get = [](auto &&data) -> std::optional<MTPMessage> {
|
|
||||||
return data.vmessages.v.isEmpty()
|
|
||||||
? std::nullopt
|
|
||||||
: base::make_optional(data.vmessages.v[0]);
|
|
||||||
};
|
|
||||||
switch (list.type()) {
|
|
||||||
case mtpc_messages_messages:
|
|
||||||
return get(list.c_messages_messages());
|
|
||||||
case mtpc_messages_messagesSlice:
|
|
||||||
return get(list.c_messages_messagesSlice());
|
|
||||||
case mtpc_messages_channelMessages:
|
|
||||||
return get(list.c_messages_channelMessages());
|
|
||||||
case mtpc_messages_messagesNotModified:
|
|
||||||
return std::nullopt;
|
|
||||||
default: Unexpected("Type of messages.Messages (GetMessagesElement)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader::Loader(const QString &filename, int chunkSize)
|
|
||||||
: _filename(filename)
|
|
||||||
, _chunkSize(chunkSize) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::start() {
|
|
||||||
if (!validateOutput()
|
|
||||||
|| (!_output.isOpen() && !_output.open(QIODevice::Append))) {
|
|
||||||
QFile(_filepath).remove();
|
|
||||||
threadSafeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(("Update Info: Starting loading '%1' from %2 offset."
|
|
||||||
).arg(_filename
|
|
||||||
).arg(alreadySize()));
|
|
||||||
startLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Loader::alreadySize() const {
|
|
||||||
QMutexLocker lock(&_sizesMutex);
|
|
||||||
return _alreadySize;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Loader::totalSize() const {
|
|
||||||
QMutexLocker lock(&_sizesMutex);
|
|
||||||
return _totalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<QString> Loader::ready() const {
|
|
||||||
return _ready.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<Progress> Loader::progress() const {
|
|
||||||
return _progress.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<> Loader::failed() const {
|
|
||||||
return _failed.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Loader::validateOutput() {
|
|
||||||
if (_filename.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto folder = cWorkingDir() + qsl("tupdates/");
|
|
||||||
_filepath = folder + _filename;
|
|
||||||
|
|
||||||
QFileInfo info(_filepath);
|
|
||||||
QDir dir(folder);
|
|
||||||
if (dir.exists()) {
|
|
||||||
const auto all = dir.entryInfoList(QDir::Files);
|
|
||||||
for (auto i = all.begin(), e = all.end(); i != e; ++i) {
|
|
||||||
if (i->absoluteFilePath() != info.absoluteFilePath()) {
|
|
||||||
QFile::remove(i->absoluteFilePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dir.mkdir(dir.absolutePath());
|
|
||||||
}
|
|
||||||
_output.setFileName(_filepath);
|
|
||||||
|
|
||||||
if (!info.exists()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto fullSize = info.size();
|
|
||||||
if (fullSize < _chunkSize || fullSize > kMaxUpdateSize) {
|
|
||||||
return _output.remove();
|
|
||||||
}
|
|
||||||
const auto goodSize = int((fullSize % _chunkSize)
|
|
||||||
? (fullSize - (fullSize % _chunkSize))
|
|
||||||
: fullSize);
|
|
||||||
if (_output.resize(goodSize)) {
|
|
||||||
_alreadySize = goodSize;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::threadSafeProgress(Progress progress) {
|
|
||||||
crl::on_main(this, [=] {
|
|
||||||
_progress.fire_copy(progress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::threadSafeReady() {
|
|
||||||
crl::on_main(this, [=] {
|
|
||||||
_ready.fire_copy(_filepath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::threadSafeFailed() {
|
|
||||||
crl::on_main(this, [=] {
|
|
||||||
_failed.fire({});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loader::writeChunk(bytes::const_span data, int totalSize) {
|
|
||||||
const auto size = data.size();
|
|
||||||
if (size > 0) {
|
|
||||||
const auto written = _output.write(QByteArray::fromRawData(
|
|
||||||
reinterpret_cast<const char*>(data.data()),
|
|
||||||
size));
|
|
||||||
if (written != size) {
|
|
||||||
threadSafeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto progress = [&] {
|
|
||||||
QMutexLocker lock(&_sizesMutex);
|
|
||||||
if (!_totalSize) {
|
|
||||||
_totalSize = totalSize;
|
|
||||||
}
|
|
||||||
_alreadySize += size;
|
|
||||||
return Progress { _alreadySize, _totalSize };
|
|
||||||
}();
|
|
||||||
|
|
||||||
if (progress.size > 0 && progress.already >= progress.size) {
|
|
||||||
_output.close();
|
|
||||||
threadSafeReady();
|
|
||||||
} else {
|
|
||||||
threadSafeProgress(progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::lifetime &Loader::lifetime() {
|
|
||||||
return _lifetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
Checker::Checker(bool testing) : _testing(testing) {
|
Checker::Checker(bool testing) : _testing(testing) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1037,7 +754,7 @@ HttpChecker::~HttpChecker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpLoader::HttpLoader(const QString &url)
|
HttpLoader::HttpLoader(const QString &url)
|
||||||
: Loader(ExtractFilename(url), kChunkSize)
|
: Loader(UpdatesFolder() + '/' + ExtractFilename(url), kChunkSize)
|
||||||
, _url(url) {
|
, _url(url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1159,96 +876,6 @@ void HttpLoaderActor::partFailed(QNetworkReply::NetworkError e) {
|
||||||
_parent->threadSafeFailed();
|
_parent->threadSafeFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
MtpWeak::MtpWeak(QPointer<MTP::Instance> instance)
|
|
||||||
: _instance(instance) {
|
|
||||||
if (!valid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(_instance, &QObject::destroyed, this, [=] {
|
|
||||||
_instance = nullptr;
|
|
||||||
die();
|
|
||||||
});
|
|
||||||
subscribe(Messenger::Instance().authSessionChanged(), [=] {
|
|
||||||
if (!AuthSession::Exists()) {
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MtpWeak::valid() const {
|
|
||||||
return (_instance != nullptr) && AuthSession::Exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointer<MTP::Instance> MtpWeak::instance() const {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MtpWeak::die() {
|
|
||||||
const auto instance = _instance.data();
|
|
||||||
for (const auto &[requestId, fail] : base::take(_requests)) {
|
|
||||||
if (instance) {
|
|
||||||
instance->cancel(requestId);
|
|
||||||
}
|
|
||||||
fail(MTP::internal::rpcClientError("UNAVAILABLE"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void MtpWeak::send(
|
|
||||||
const T &request,
|
|
||||||
Fn<void(const typename T::ResponseType &result)> done,
|
|
||||||
Fn<void(const RPCError &error)> fail,
|
|
||||||
MTP::ShiftedDcId dcId) {
|
|
||||||
using Response = typename T::ResponseType;
|
|
||||||
if (!valid()) {
|
|
||||||
InvokeQueued(this, [=] {
|
|
||||||
fail(MTP::internal::rpcClientError("UNAVAILABLE"));
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto onDone = crl::guard((QObject*)this, [=](
|
|
||||||
const Response &result,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
if (removeRequest(requestId)) {
|
|
||||||
done(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const auto onFail = crl::guard((QObject*)this, [=](
|
|
||||||
const RPCError &error,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
if (MTP::isDefaultHandledError(error)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (removeRequest(requestId)) {
|
|
||||||
fail(error);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
const auto requestId = _instance->send(
|
|
||||||
request,
|
|
||||||
rpcDone(onDone),
|
|
||||||
rpcFail(onFail),
|
|
||||||
dcId);
|
|
||||||
_requests.emplace(requestId, fail);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MtpWeak::removeRequest(mtpRequestId requestId) {
|
|
||||||
if (const auto i = _requests.find(requestId); i != end(_requests)) {
|
|
||||||
_requests.erase(i);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MtpWeak::~MtpWeak() {
|
|
||||||
if (const auto instance = _instance.data()) {
|
|
||||||
for (const auto &[requestId, fail] : base::take(_requests)) {
|
|
||||||
instance->cancel(requestId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MtpChecker::MtpChecker(QPointer<MTP::Instance> instance, bool testing)
|
MtpChecker::MtpChecker(QPointer<MTP::Instance> instance, bool testing)
|
||||||
: Checker(testing)
|
: Checker(testing)
|
||||||
, _mtp(instance) {
|
, _mtp(instance) {
|
||||||
|
@ -1260,8 +887,8 @@ void MtpChecker::start() {
|
||||||
crl::on_main(this, [=] { fail(); });
|
crl::on_main(this, [=] { fail(); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
constexpr auto kFeedUsername = "tdhbcfeed";
|
constexpr auto kFeed = "tdhbcfeed";
|
||||||
resolveChannel(kFeedUsername, [=](const MTPInputChannel &channel) {
|
MTP::ResolveChannel(&_mtp, kFeed, [=](const MTPInputChannel &channel) {
|
||||||
_mtp.send(
|
_mtp.send(
|
||||||
MTPmessages_GetHistory(
|
MTPmessages_GetHistory(
|
||||||
MTP_inputPeerChannel(
|
MTP_inputPeerChannel(
|
||||||
|
@ -1276,53 +903,7 @@ void MtpChecker::start() {
|
||||||
MTP_int(0)), // hash
|
MTP_int(0)), // hash
|
||||||
[=](const MTPmessages_Messages &result) { gotMessage(result); },
|
[=](const MTPmessages_Messages &result) { gotMessage(result); },
|
||||||
failHandler());
|
failHandler());
|
||||||
});
|
}, [=] { fail(); });
|
||||||
}
|
|
||||||
|
|
||||||
void MtpChecker::resolveChannel(
|
|
||||||
const QString &username,
|
|
||||||
Fn<void(const MTPInputChannel &channel)> callback) {
|
|
||||||
const auto failed = [&] {
|
|
||||||
LOG(("Update Error: MTP channel '%1' resolve failed."
|
|
||||||
).arg(username));
|
|
||||||
fail();
|
|
||||||
};
|
|
||||||
if (!AuthSession::Exists()) {
|
|
||||||
failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ResolveResult {
|
|
||||||
base::weak_ptr<AuthSession> auth;
|
|
||||||
MTPInputChannel channel;
|
|
||||||
};
|
|
||||||
static std::map<QString, ResolveResult> ResolveCache;
|
|
||||||
|
|
||||||
const auto i = ResolveCache.find(username);
|
|
||||||
if (i != end(ResolveCache)) {
|
|
||||||
if (i->second.auth.get() == &Auth()) {
|
|
||||||
callback(i->second.channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ResolveCache.erase(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto doneHandler = [=](const MTPcontacts_ResolvedPeer &result) {
|
|
||||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
|
||||||
|
|
||||||
if (const auto channel = ExtractChannel(result)) {
|
|
||||||
ResolveCache.emplace(
|
|
||||||
username,
|
|
||||||
ResolveResult { base::make_weak(&Auth()), *channel });
|
|
||||||
callback(*channel);
|
|
||||||
} else {
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_mtp.send(
|
|
||||||
MTPcontacts_ResolveUsername(MTP_string(username)),
|
|
||||||
doneHandler,
|
|
||||||
failHandler());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MtpChecker::gotMessage(const MTPmessages_Messages &result) {
|
void MtpChecker::gotMessage(const MTPmessages_Messages &result) {
|
||||||
|
@ -1334,21 +915,19 @@ void MtpChecker::gotMessage(const MTPmessages_Messages &result) {
|
||||||
done(nullptr);
|
done(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolveChannel(location->username, [=](const MTPInputChannel &channel) {
|
const auto ready = [=](std::unique_ptr<MTP::DedicatedLoader> loader) {
|
||||||
_mtp.send(
|
if (loader) {
|
||||||
MTPchannels_GetMessages(
|
done(std::move(loader));
|
||||||
channel,
|
} else {
|
||||||
MTP_vector<MTPInputMessage>(
|
fail();
|
||||||
1,
|
}
|
||||||
MTP_inputMessageID(MTP_int(location->postId)))),
|
};
|
||||||
[=](const MTPmessages_Messages &result) { gotFile(result); },
|
MTP::StartDedicatedLoader(&_mtp, *location, UpdatesFolder(), ready);
|
||||||
failHandler());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MtpChecker::parseMessage(const MTPmessages_Messages &result) const
|
auto MtpChecker::parseMessage(const MTPmessages_Messages &result) const
|
||||||
-> std::optional<FileLocation> {
|
-> std::optional<FileLocation> {
|
||||||
const auto message = GetMessagesElement(result);
|
const auto message = MTP::GetMessagesElement(result);
|
||||||
if (!message || message->type() != mtpc_message) {
|
if (!message || message->type() != mtpc_message) {
|
||||||
LOG(("Update Error: MTP feed message not found."));
|
LOG(("Update Error: MTP feed message not found."));
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1413,64 +992,6 @@ auto MtpChecker::validateLatestLocation(
|
||||||
return (availableVersion <= myVersion) ? FileLocation() : location;
|
return (availableVersion <= myVersion) ? FileLocation() : location;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MtpChecker::gotFile(const MTPmessages_Messages &result) {
|
|
||||||
if (const auto file = parseFile(result)) {
|
|
||||||
done(std::make_shared<MtpLoader>(
|
|
||||||
_mtp.instance(),
|
|
||||||
file->name,
|
|
||||||
file->size,
|
|
||||||
file->dcId,
|
|
||||||
file->location));
|
|
||||||
} else {
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MtpChecker::parseFile(const MTPmessages_Messages &result) const
|
|
||||||
-> std::optional<ParsedFile> {
|
|
||||||
const auto message = GetMessagesElement(result);
|
|
||||||
if (!message || message->type() != mtpc_message) {
|
|
||||||
LOG(("Update Error: MTP file message not found."));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto &data = message->c_message();
|
|
||||||
if (!data.has_media()
|
|
||||||
|| data.vmedia.type() != mtpc_messageMediaDocument) {
|
|
||||||
LOG(("Update Error: MTP file media not found."));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto &document = data.vmedia.c_messageMediaDocument();
|
|
||||||
if (!document.has_document()
|
|
||||||
|| document.vdocument.type() != mtpc_document) {
|
|
||||||
LOG(("Update Error: MTP file not found."));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto &fields = document.vdocument.c_document();
|
|
||||||
const auto name = [&] {
|
|
||||||
for (const auto &attribute : fields.vattributes.v) {
|
|
||||||
if (attribute.type() == mtpc_documentAttributeFilename) {
|
|
||||||
const auto &data = attribute.c_documentAttributeFilename();
|
|
||||||
return qs(data.vfile_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}();
|
|
||||||
if (name.isEmpty()) {
|
|
||||||
LOG(("Update Error: MTP file name not found."));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto size = fields.vsize.v;
|
|
||||||
if (size <= 0) {
|
|
||||||
LOG(("Update Error: MTP file size is invalid."));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto location = MTP_inputDocumentFileLocation(
|
|
||||||
fields.vid,
|
|
||||||
fields.vaccess_hash,
|
|
||||||
fields.vfile_reference);
|
|
||||||
return ParsedFile { name, size, fields.vdc_id.v, location };
|
|
||||||
}
|
|
||||||
|
|
||||||
Fn<void(const RPCError &error)> MtpChecker::failHandler() {
|
Fn<void(const RPCError &error)> MtpChecker::failHandler() {
|
||||||
return [=](const RPCError &error) {
|
return [=](const RPCError &error) {
|
||||||
LOG(("Update Error: MTP check failed with '%1'"
|
LOG(("Update Error: MTP check failed with '%1'"
|
||||||
|
@ -1479,88 +1000,6 @@ Fn<void(const RPCError &error)> MtpChecker::failHandler() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
MtpLoader::MtpLoader(
|
|
||||||
QPointer<MTP::Instance> instance,
|
|
||||||
const QString &name,
|
|
||||||
int32 size,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const MTPInputFileLocation &location)
|
|
||||||
: Loader(name, kChunkSize)
|
|
||||||
, _size(size)
|
|
||||||
, _dcId(dcId)
|
|
||||||
, _location(location)
|
|
||||||
, _mtp(instance) {
|
|
||||||
Expects(_size > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MtpLoader::startLoading() {
|
|
||||||
if (!_mtp.valid()) {
|
|
||||||
LOG(("Update Error: MTP is unavailable."));
|
|
||||||
threadSafeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(("Update Info: Loading using MTP from '%1'.").arg(_dcId));
|
|
||||||
_offset = alreadySize();
|
|
||||||
writeChunk({}, _size);
|
|
||||||
sendRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MtpLoader::sendRequest() {
|
|
||||||
if (_requests.size() >= kRequestsCount || _offset >= _size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto offset = _offset;
|
|
||||||
_requests.push_back({ offset });
|
|
||||||
_mtp.send(
|
|
||||||
MTPupload_GetFile(_location, MTP_int(offset), MTP_int(kChunkSize)),
|
|
||||||
[=](const MTPupload_File &result) { gotPart(offset, result); },
|
|
||||||
failHandler(),
|
|
||||||
MTP::updaterDcId(_dcId));
|
|
||||||
_offset += kChunkSize;
|
|
||||||
|
|
||||||
if (_requests.size() < kRequestsCount) {
|
|
||||||
App::CallDelayed(kNextRequestDelay, this, [=] { sendRequest(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MtpLoader::gotPart(int offset, const MTPupload_File &result) {
|
|
||||||
Expects(!_requests.empty());
|
|
||||||
|
|
||||||
if (result.type() == mtpc_upload_fileCdnRedirect) {
|
|
||||||
LOG(("Update Error: MTP does not support cdn right now."));
|
|
||||||
threadSafeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto &data = result.c_upload_file();
|
|
||||||
if (data.vbytes.v.isEmpty()) {
|
|
||||||
LOG(("Update Error: MTP empty part received."));
|
|
||||||
threadSafeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto i = ranges::find(
|
|
||||||
_requests,
|
|
||||||
offset,
|
|
||||||
[](const Request &request) { return request.offset; });
|
|
||||||
Assert(i != end(_requests));
|
|
||||||
|
|
||||||
i->bytes = data.vbytes.v;
|
|
||||||
while (!_requests.empty() && !_requests.front().bytes.isEmpty()) {
|
|
||||||
writeChunk(bytes::make_span(_requests.front().bytes), _size);
|
|
||||||
_requests.pop_front();
|
|
||||||
}
|
|
||||||
sendRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
Fn<void(const RPCError &)> MtpLoader::failHandler() {
|
|
||||||
return [=](const RPCError &error) {
|
|
||||||
LOG(("Update Error: MTP load failed with '%1'"
|
|
||||||
).arg(QString::number(error.code()) + ':' + error.type()));
|
|
||||||
threadSafeFailed();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool UpdaterDisabled() {
|
bool UpdaterDisabled() {
|
||||||
|
@ -1847,7 +1286,6 @@ void Updater::test() {
|
||||||
|
|
||||||
void Updater::setMtproto(const QPointer<MTP::Instance> &mtproto) {
|
void Updater::setMtproto(const QPointer<MTP::Instance> &mtproto) {
|
||||||
_mtproto = mtproto;
|
_mtproto = mtproto;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Updater::handleTimeout() {
|
void Updater::handleTimeout() {
|
||||||
|
@ -1892,6 +1330,7 @@ bool Updater::tryLoaders() {
|
||||||
}, loader->lifetime());
|
}, loader->lifetime());
|
||||||
|
|
||||||
_retryTimer.callOnce(kUpdaterTimeout);
|
_retryTimer.callOnce(kUpdaterTimeout);
|
||||||
|
loader->wipeFolder();
|
||||||
loader->start();
|
loader->start();
|
||||||
} else {
|
} else {
|
||||||
_isLatest.fire({});
|
_isLatest.fire({});
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "mtproto/dedicated_file_loader.h"
|
||||||
|
|
||||||
namespace MTP {
|
namespace MTP {
|
||||||
class Instance;
|
class Instance;
|
||||||
} // namespace MTP
|
} // namespace MTP
|
||||||
|
@ -25,10 +27,7 @@ public:
|
||||||
Download,
|
Download,
|
||||||
Ready,
|
Ready,
|
||||||
};
|
};
|
||||||
struct Progress {
|
using Progress = MTP::AbstractDedicatedLoader::Progress;
|
||||||
int64 already;
|
|
||||||
int64 size;
|
|
||||||
};
|
|
||||||
|
|
||||||
UpdateChecker();
|
UpdateChecker();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,461 @@
|
||||||
|
/*
|
||||||
|
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 "mtproto/dedicated_file_loader.h"
|
||||||
|
|
||||||
|
#include "mtproto/session.h"
|
||||||
|
#include "auth_session.h"
|
||||||
|
#include "messenger.h"
|
||||||
|
|
||||||
|
namespace MTP {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::optional<MTPInputChannel> ExtractChannel(
|
||||||
|
const MTPcontacts_ResolvedPeer &result) {
|
||||||
|
const auto &data = result.c_contacts_resolvedPeer();
|
||||||
|
if (const auto peer = peerFromMTP(data.vpeer)) {
|
||||||
|
for (const auto &chat : data.vchats.v) {
|
||||||
|
if (chat.type() == mtpc_channel) {
|
||||||
|
const auto &channel = chat.c_channel();
|
||||||
|
if (peer == peerFromChannel(channel.vid)) {
|
||||||
|
return MTP_inputChannel(
|
||||||
|
channel.vid,
|
||||||
|
channel.vaccess_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DedicatedLoader::File> ParseFile(
|
||||||
|
const MTPmessages_Messages &result) {
|
||||||
|
const auto message = GetMessagesElement(result);
|
||||||
|
if (!message || message->type() != mtpc_message) {
|
||||||
|
LOG(("Update Error: MTP file message not found."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto &data = message->c_message();
|
||||||
|
if (!data.has_media()
|
||||||
|
|| data.vmedia.type() != mtpc_messageMediaDocument) {
|
||||||
|
LOG(("Update Error: MTP file media not found."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto &document = data.vmedia.c_messageMediaDocument();
|
||||||
|
if (!document.has_document()
|
||||||
|
|| document.vdocument.type() != mtpc_document) {
|
||||||
|
LOG(("Update Error: MTP file not found."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto &fields = document.vdocument.c_document();
|
||||||
|
const auto name = [&] {
|
||||||
|
for (const auto &attribute : fields.vattributes.v) {
|
||||||
|
if (attribute.type() == mtpc_documentAttributeFilename) {
|
||||||
|
const auto &data = attribute.c_documentAttributeFilename();
|
||||||
|
return qs(data.vfile_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}();
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
LOG(("Update Error: MTP file name not found."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto size = fields.vsize.v;
|
||||||
|
if (size <= 0) {
|
||||||
|
LOG(("Update Error: MTP file size is invalid."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto location = MTP_inputDocumentFileLocation(
|
||||||
|
fields.vid,
|
||||||
|
fields.vaccess_hash,
|
||||||
|
fields.vfile_reference);
|
||||||
|
return DedicatedLoader::File{ name, size, fields.vdc_id.v, location };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WeakInstance::WeakInstance(QPointer<MTP::Instance> instance)
|
||||||
|
: _instance(instance) {
|
||||||
|
if (!valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(_instance, &QObject::destroyed, this, [=] {
|
||||||
|
_instance = nullptr;
|
||||||
|
die();
|
||||||
|
});
|
||||||
|
subscribe(Messenger::Instance().authSessionChanged(), [=] {
|
||||||
|
if (!AuthSession::Exists()) {
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeakInstance::valid() const {
|
||||||
|
return (_instance != nullptr) && AuthSession::Exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointer<MTP::Instance> WeakInstance::instance() const {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeakInstance::die() {
|
||||||
|
const auto instance = _instance.data();
|
||||||
|
for (const auto &[requestId, fail] : base::take(_requests)) {
|
||||||
|
if (instance) {
|
||||||
|
instance->cancel(requestId);
|
||||||
|
}
|
||||||
|
fail(MTP::internal::rpcClientError("UNAVAILABLE"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WeakInstance::removeRequest(mtpRequestId requestId) {
|
||||||
|
if (const auto i = _requests.find(requestId); i != end(_requests)) {
|
||||||
|
_requests.erase(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeakInstance::reportUnavailable(
|
||||||
|
Fn<void(const RPCError &error)> callback) {
|
||||||
|
InvokeQueued(this, [=] {
|
||||||
|
callback(MTP::internal::rpcClientError("UNAVAILABLE"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WeakInstance::~WeakInstance() {
|
||||||
|
if (const auto instance = _instance.data()) {
|
||||||
|
for (const auto &[requestId, fail] : base::take(_requests)) {
|
||||||
|
instance->cancel(requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractDedicatedLoader::AbstractDedicatedLoader(
|
||||||
|
const QString &filepath,
|
||||||
|
int chunkSize)
|
||||||
|
: _filepath(filepath)
|
||||||
|
, _chunkSize(chunkSize) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::start() {
|
||||||
|
if (!validateOutput()
|
||||||
|
|| (!_output.isOpen() && !_output.open(QIODevice::Append))) {
|
||||||
|
QFile(_filepath).remove();
|
||||||
|
threadSafeFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(("Update Info: Starting loading '%1' from %2 offset."
|
||||||
|
).arg(_filepath
|
||||||
|
).arg(alreadySize()));
|
||||||
|
startLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AbstractDedicatedLoader::alreadySize() const {
|
||||||
|
QMutexLocker lock(&_sizesMutex);
|
||||||
|
return _alreadySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AbstractDedicatedLoader::totalSize() const {
|
||||||
|
QMutexLocker lock(&_sizesMutex);
|
||||||
|
return _totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> AbstractDedicatedLoader::ready() const {
|
||||||
|
return _ready.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto AbstractDedicatedLoader::progress() const -> rpl::producer<Progress> {
|
||||||
|
return _progress.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> AbstractDedicatedLoader::failed() const {
|
||||||
|
return _failed.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::wipeFolder() {
|
||||||
|
QFileInfo info(_filepath);
|
||||||
|
const auto dir = info.dir();
|
||||||
|
const auto all = dir.entryInfoList(QDir::Files);
|
||||||
|
for (auto i = all.begin(), e = all.end(); i != e; ++i) {
|
||||||
|
if (i->absoluteFilePath() != info.absoluteFilePath()) {
|
||||||
|
QFile::remove(i->absoluteFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractDedicatedLoader::validateOutput() {
|
||||||
|
if (_filepath.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo info(_filepath);
|
||||||
|
const auto dir = info.dir();
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdir(dir.absolutePath());
|
||||||
|
}
|
||||||
|
_output.setFileName(_filepath);
|
||||||
|
|
||||||
|
if (!info.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto fullSize = info.size();
|
||||||
|
if (fullSize < _chunkSize || fullSize > kMaxFileSize) {
|
||||||
|
return _output.remove();
|
||||||
|
}
|
||||||
|
const auto goodSize = int((fullSize % _chunkSize)
|
||||||
|
? (fullSize - (fullSize % _chunkSize))
|
||||||
|
: fullSize);
|
||||||
|
if (_output.resize(goodSize)) {
|
||||||
|
_alreadySize = goodSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::threadSafeProgress(Progress progress) {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
_progress.fire_copy(progress);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::threadSafeReady() {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
_ready.fire_copy(_filepath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::threadSafeFailed() {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
_failed.fire({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractDedicatedLoader::writeChunk(bytes::const_span data, int totalSize) {
|
||||||
|
const auto size = data.size();
|
||||||
|
if (size > 0) {
|
||||||
|
const auto written = _output.write(QByteArray::fromRawData(
|
||||||
|
reinterpret_cast<const char*>(data.data()),
|
||||||
|
size));
|
||||||
|
if (written != size) {
|
||||||
|
threadSafeFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto progress = [&] {
|
||||||
|
QMutexLocker lock(&_sizesMutex);
|
||||||
|
if (!_totalSize) {
|
||||||
|
_totalSize = totalSize;
|
||||||
|
}
|
||||||
|
_alreadySize += size;
|
||||||
|
return Progress { _alreadySize, _totalSize };
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (progress.size > 0 && progress.already >= progress.size) {
|
||||||
|
_output.close();
|
||||||
|
threadSafeReady();
|
||||||
|
} else {
|
||||||
|
threadSafeProgress(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::lifetime &AbstractDedicatedLoader::lifetime() {
|
||||||
|
return _lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
DedicatedLoader::DedicatedLoader(
|
||||||
|
QPointer<MTP::Instance> instance,
|
||||||
|
const QString &folder,
|
||||||
|
const File &file)
|
||||||
|
: AbstractDedicatedLoader(folder + '/' + file.name, kChunkSize)
|
||||||
|
, _size(file.size)
|
||||||
|
, _dcId(file.dcId)
|
||||||
|
, _location(file.location)
|
||||||
|
, _mtp(instance) {
|
||||||
|
Expects(_size > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DedicatedLoader::startLoading() {
|
||||||
|
if (!_mtp.valid()) {
|
||||||
|
LOG(("Update Error: MTP is unavailable."));
|
||||||
|
threadSafeFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(("Update Info: Loading using MTP from '%1'.").arg(_dcId));
|
||||||
|
_offset = alreadySize();
|
||||||
|
writeChunk({}, _size);
|
||||||
|
sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DedicatedLoader::sendRequest() {
|
||||||
|
if (_requests.size() >= kRequestsCount || _offset >= _size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto offset = _offset;
|
||||||
|
_requests.push_back({ offset });
|
||||||
|
_mtp.send(
|
||||||
|
MTPupload_GetFile(_location, MTP_int(offset), MTP_int(kChunkSize)),
|
||||||
|
[=](const MTPupload_File &result) { gotPart(offset, result); },
|
||||||
|
failHandler(),
|
||||||
|
MTP::updaterDcId(_dcId));
|
||||||
|
_offset += kChunkSize;
|
||||||
|
|
||||||
|
if (_requests.size() < kRequestsCount) {
|
||||||
|
App::CallDelayed(kNextRequestDelay, this, [=] { sendRequest(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DedicatedLoader::gotPart(int offset, const MTPupload_File &result) {
|
||||||
|
Expects(!_requests.empty());
|
||||||
|
|
||||||
|
if (result.type() == mtpc_upload_fileCdnRedirect) {
|
||||||
|
LOG(("Update Error: MTP does not support cdn right now."));
|
||||||
|
threadSafeFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &data = result.c_upload_file();
|
||||||
|
if (data.vbytes.v.isEmpty()) {
|
||||||
|
LOG(("Update Error: MTP empty part received."));
|
||||||
|
threadSafeFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto i = ranges::find(
|
||||||
|
_requests,
|
||||||
|
offset,
|
||||||
|
[](const Request &request) { return request.offset; });
|
||||||
|
Assert(i != end(_requests));
|
||||||
|
|
||||||
|
i->bytes = data.vbytes.v;
|
||||||
|
while (!_requests.empty() && !_requests.front().bytes.isEmpty()) {
|
||||||
|
writeChunk(bytes::make_span(_requests.front().bytes), _size);
|
||||||
|
_requests.pop_front();
|
||||||
|
}
|
||||||
|
sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
Fn<void(const RPCError &)> DedicatedLoader::failHandler() {
|
||||||
|
return [=](const RPCError &error) {
|
||||||
|
LOG(("Update Error: MTP load failed with '%1'"
|
||||||
|
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||||
|
threadSafeFailed();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolveChannel(
|
||||||
|
not_null<MTP::WeakInstance*> mtp,
|
||||||
|
const QString &username,
|
||||||
|
Fn<void(const MTPInputChannel &channel)> done,
|
||||||
|
Fn<void()> fail) {
|
||||||
|
const auto failed = [&] {
|
||||||
|
LOG(("Dedicated MTP Error: Channel '%1' resolve failed."
|
||||||
|
).arg(username));
|
||||||
|
fail();
|
||||||
|
};
|
||||||
|
if (!AuthSession::Exists()) {
|
||||||
|
failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ResolveResult {
|
||||||
|
base::weak_ptr<AuthSession> auth;
|
||||||
|
MTPInputChannel channel;
|
||||||
|
};
|
||||||
|
static std::map<QString, ResolveResult> ResolveCache;
|
||||||
|
|
||||||
|
const auto i = ResolveCache.find(username);
|
||||||
|
if (i != end(ResolveCache)) {
|
||||||
|
if (i->second.auth.get() == &Auth()) {
|
||||||
|
done(i->second.channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResolveCache.erase(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto doneHandler = [=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
|
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||||
|
|
||||||
|
if (const auto channel = ExtractChannel(result)) {
|
||||||
|
ResolveCache.emplace(
|
||||||
|
username,
|
||||||
|
ResolveResult { base::make_weak(&Auth()), *channel });
|
||||||
|
done(*channel);
|
||||||
|
} else {
|
||||||
|
failed();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto failHandler = [=](const RPCError &error) {
|
||||||
|
LOG(("Dedicated MTP Error: Resolve failed with '%1'"
|
||||||
|
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||||
|
fail();
|
||||||
|
};
|
||||||
|
mtp->send(
|
||||||
|
MTPcontacts_ResolveUsername(MTP_string(username)),
|
||||||
|
doneHandler,
|
||||||
|
failHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MTPMessage> GetMessagesElement(
|
||||||
|
const MTPmessages_Messages &list) {
|
||||||
|
const auto get = [](auto &&data) -> std::optional<MTPMessage> {
|
||||||
|
return data.vmessages.v.isEmpty()
|
||||||
|
? std::nullopt
|
||||||
|
: base::make_optional(data.vmessages.v[0]);
|
||||||
|
};
|
||||||
|
switch (list.type()) {
|
||||||
|
case mtpc_messages_messages:
|
||||||
|
return get(list.c_messages_messages());
|
||||||
|
case mtpc_messages_messagesSlice:
|
||||||
|
return get(list.c_messages_messagesSlice());
|
||||||
|
case mtpc_messages_channelMessages:
|
||||||
|
return get(list.c_messages_channelMessages());
|
||||||
|
case mtpc_messages_messagesNotModified:
|
||||||
|
return std::nullopt;
|
||||||
|
default: Unexpected("Type of messages.Messages (GetMessagesElement)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartDedicatedLoader(
|
||||||
|
not_null<MTP::WeakInstance*> mtp,
|
||||||
|
const DedicatedLoader::Location &location,
|
||||||
|
const QString &folder,
|
||||||
|
Fn<void(std::unique_ptr<DedicatedLoader>)> ready) {
|
||||||
|
const auto doneHandler = [=](const MTPmessages_Messages &result) {
|
||||||
|
const auto file = ParseFile(result);
|
||||||
|
ready(file
|
||||||
|
? std::make_unique<MTP::DedicatedLoader>(
|
||||||
|
mtp->instance(),
|
||||||
|
folder,
|
||||||
|
*file)
|
||||||
|
: nullptr);
|
||||||
|
};
|
||||||
|
const auto failHandler = [=](const RPCError &error) {
|
||||||
|
LOG(("Update Error: MTP check failed with '%1'"
|
||||||
|
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||||
|
ready(nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto [username, postId] = location;
|
||||||
|
ResolveChannel(mtp, username, [=, postId = postId](
|
||||||
|
const MTPInputChannel &channel) {
|
||||||
|
mtp->send(
|
||||||
|
MTPchannels_GetMessages(
|
||||||
|
channel,
|
||||||
|
MTP_vector<MTPInputMessage>(
|
||||||
|
1,
|
||||||
|
MTP_inputMessageID(MTP_int(postId)))),
|
||||||
|
doneHandler,
|
||||||
|
failHandler);
|
||||||
|
}, [=] { ready(nullptr); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MTP
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace MTP {
|
||||||
|
|
||||||
|
class Instance;
|
||||||
|
|
||||||
|
class WeakInstance : private QObject, private base::Subscriber {
|
||||||
|
public:
|
||||||
|
WeakInstance(QPointer<Instance> instance);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void send(
|
||||||
|
const T &request,
|
||||||
|
Fn<void(const typename T::ResponseType &result)> done,
|
||||||
|
Fn<void(const RPCError &error)> fail,
|
||||||
|
ShiftedDcId dcId = 0);
|
||||||
|
|
||||||
|
bool valid() const;
|
||||||
|
QPointer<Instance> instance() const;
|
||||||
|
|
||||||
|
~WeakInstance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void die();
|
||||||
|
bool removeRequest(mtpRequestId requestId);
|
||||||
|
void reportUnavailable(Fn<void(const RPCError &error)> callback);
|
||||||
|
|
||||||
|
QPointer<Instance> _instance;
|
||||||
|
std::map<mtpRequestId, Fn<void(const RPCError &)>> _requests;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class AbstractDedicatedLoader : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
AbstractDedicatedLoader(const QString &filepath, int chunkSize);
|
||||||
|
|
||||||
|
static constexpr auto kChunkSize = 128 * 1024;
|
||||||
|
static constexpr auto kMaxFileSize = 256 * 1024 * 1024;
|
||||||
|
|
||||||
|
struct Progress {
|
||||||
|
int64 already;
|
||||||
|
int64 size;
|
||||||
|
|
||||||
|
inline bool operator<(const Progress &other) const {
|
||||||
|
return (already < other.already)
|
||||||
|
|| (already == other.already && size < other.size);
|
||||||
|
}
|
||||||
|
inline bool operator==(const Progress &other) const {
|
||||||
|
return (already == other.already) && (size == other.size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void wipeFolder();
|
||||||
|
void wipeOutput();
|
||||||
|
|
||||||
|
int alreadySize() const;
|
||||||
|
int totalSize() const;
|
||||||
|
|
||||||
|
rpl::producer<Progress> progress() const;
|
||||||
|
rpl::producer<QString> ready() const;
|
||||||
|
rpl::producer<> failed() const;
|
||||||
|
|
||||||
|
rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
virtual ~AbstractDedicatedLoader() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void threadSafeFailed();
|
||||||
|
|
||||||
|
// Single threaded.
|
||||||
|
void writeChunk(bytes::const_span data, int totalSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void startLoading() = 0;
|
||||||
|
|
||||||
|
bool validateOutput();
|
||||||
|
void threadSafeProgress(Progress progress);
|
||||||
|
void threadSafeReady();
|
||||||
|
|
||||||
|
QString _filepath;
|
||||||
|
int _chunkSize = 0;
|
||||||
|
|
||||||
|
QFile _output;
|
||||||
|
int _alreadySize = 0;
|
||||||
|
int _totalSize = 0;
|
||||||
|
mutable QMutex _sizesMutex;
|
||||||
|
rpl::event_stream<Progress> _progress;
|
||||||
|
rpl::event_stream<QString> _ready;
|
||||||
|
rpl::event_stream<> _failed;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class DedicatedLoader : public AbstractDedicatedLoader {
|
||||||
|
public:
|
||||||
|
struct Location {
|
||||||
|
QString username;
|
||||||
|
int32 postId = 0;
|
||||||
|
};
|
||||||
|
struct File {
|
||||||
|
QString name;
|
||||||
|
int32 size = 0;
|
||||||
|
DcId dcId = 0;
|
||||||
|
MTPInputFileLocation location;
|
||||||
|
};
|
||||||
|
|
||||||
|
DedicatedLoader(
|
||||||
|
QPointer<Instance> instance,
|
||||||
|
const QString &folder,
|
||||||
|
const File &file);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Request {
|
||||||
|
int offset = 0;
|
||||||
|
QByteArray bytes;
|
||||||
|
};
|
||||||
|
void startLoading() override;
|
||||||
|
void sendRequest();
|
||||||
|
void gotPart(int offset, const MTPupload_File &result);
|
||||||
|
Fn<void(const RPCError &)> failHandler();
|
||||||
|
|
||||||
|
static constexpr auto kRequestsCount = 2;
|
||||||
|
static constexpr auto kNextRequestDelay = TimeMs(20);
|
||||||
|
|
||||||
|
std::deque<Request> _requests;
|
||||||
|
int32 _size = 0;
|
||||||
|
int _offset = 0;
|
||||||
|
DcId _dcId = 0;
|
||||||
|
MTPInputFileLocation _location;
|
||||||
|
WeakInstance _mtp;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
void ResolveChannel(
|
||||||
|
not_null<MTP::WeakInstance*> mtp,
|
||||||
|
const QString &username,
|
||||||
|
Fn<void(const MTPInputChannel &channel)> done,
|
||||||
|
Fn<void()> fail);
|
||||||
|
|
||||||
|
std::optional<MTPMessage> GetMessagesElement(
|
||||||
|
const MTPmessages_Messages &list);
|
||||||
|
|
||||||
|
void StartDedicatedLoader(
|
||||||
|
not_null<MTP::WeakInstance*> mtp,
|
||||||
|
const DedicatedLoader::Location &location,
|
||||||
|
const QString &folder,
|
||||||
|
Fn<void(std::unique_ptr<DedicatedLoader>)> ready);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WeakInstance::send(
|
||||||
|
const T &request,
|
||||||
|
Fn<void(const typename T::ResponseType &result)> done,
|
||||||
|
Fn<void(const RPCError &error)> fail,
|
||||||
|
MTP::ShiftedDcId dcId) {
|
||||||
|
using Response = typename T::ResponseType;
|
||||||
|
if (!valid()) {
|
||||||
|
reportUnavailable(fail);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto onDone = crl::guard((QObject*)this, [=](
|
||||||
|
const Response &result,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
if (removeRequest(requestId)) {
|
||||||
|
done(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const auto onFail = crl::guard((QObject*)this, [=](
|
||||||
|
const RPCError &error,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
if (MTP::isDefaultHandledError(error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (removeRequest(requestId)) {
|
||||||
|
fail(error);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const auto requestId = _instance->send(
|
||||||
|
request,
|
||||||
|
rpcDone(onDone),
|
||||||
|
rpcFail(onFail),
|
||||||
|
dcId);
|
||||||
|
_requests.emplace(requestId, fail);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MTP
|
|
@ -124,12 +124,6 @@ bool IsValidSetId(int id) {
|
||||||
return (id == 0) || (id > 0 && id < kMaxId);
|
return (id == 0) || (id > 0 && id < kMaxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QString SetDataPath(int id) {
|
|
||||||
Expects(IsValidSetId(id) && id != 0);
|
|
||||||
|
|
||||||
return CacheFileFolder() + "/set" + QString::number(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32 ComputeVersion(int id) {
|
uint32 ComputeVersion(int id) {
|
||||||
Expects(IsValidSetId(id));
|
Expects(IsValidSetId(id));
|
||||||
|
|
||||||
|
@ -166,7 +160,7 @@ void ClearCurrentSetId() {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QDir(SetDataPath(id)).removeRecursively();
|
QDir(internal::SetDataPath(id)).removeRecursively();
|
||||||
SwitchToSet(0);
|
SwitchToSet(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +269,7 @@ std::vector<QImage> LoadSprites(int id) {
|
||||||
|
|
||||||
auto result = std::vector<QImage>();
|
auto result = std::vector<QImage>();
|
||||||
const auto folder = (id != 0)
|
const auto folder = (id != 0)
|
||||||
? SetDataPath(id) + '/'
|
? internal::SetDataPath(id) + '/'
|
||||||
: qsl(":/gui/emoji/");
|
: qsl(":/gui/emoji/");
|
||||||
const auto base = folder + "emoji_";
|
const auto base = folder + "emoji_";
|
||||||
return ranges::view::ints(
|
return ranges::view::ints(
|
||||||
|
@ -295,7 +289,7 @@ bool ValidateConfig(int id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
constexpr auto kSizeLimit = 65536;
|
constexpr auto kSizeLimit = 65536;
|
||||||
auto config = QFile(SetDataPath(id) + "/config.json");
|
auto config = QFile(internal::SetDataPath(id) + "/config.json");
|
||||||
if (!config.open(QIODevice::ReadOnly) || config.size() > kSizeLimit) {
|
if (!config.open(QIODevice::ReadOnly) || config.size() > kSizeLimit) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -473,6 +467,16 @@ void ClearUniversalChecked() {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
QString SetDataPath(int id) {
|
||||||
|
Expects(IsValidSetId(id) && id != 0);
|
||||||
|
|
||||||
|
return CacheFileFolder() + "/set" + QString::number(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
void Init() {
|
void Init() {
|
||||||
internal::Init();
|
internal::Init();
|
||||||
|
|
||||||
|
@ -558,7 +562,7 @@ bool SetIsReady(int id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const auto folder = SetDataPath(id) + '/';
|
const auto folder = internal::SetDataPath(id) + '/';
|
||||||
auto names = ranges::view::ints(
|
auto names = ranges::view::ints(
|
||||||
0,
|
0,
|
||||||
SpritesCount + 1
|
SpritesCount + 1
|
||||||
|
|
|
@ -12,6 +12,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Emoji {
|
namespace Emoji {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
[[nodiscard]] QString SetDataPath(int id);
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
constexpr auto kRecentLimit = 42;
|
constexpr auto kRecentLimit = 42;
|
||||||
|
|
||||||
|
|
|
@ -463,6 +463,8 @@
|
||||||
<(src_loc)/mtproto/dcenter.h
|
<(src_loc)/mtproto/dcenter.h
|
||||||
<(src_loc)/mtproto/dc_options.cpp
|
<(src_loc)/mtproto/dc_options.cpp
|
||||||
<(src_loc)/mtproto/dc_options.h
|
<(src_loc)/mtproto/dc_options.h
|
||||||
|
<(src_loc)/mtproto/dedicated_file_loader.cpp
|
||||||
|
<(src_loc)/mtproto/dedicated_file_loader.h
|
||||||
<(src_loc)/mtproto/facade.cpp
|
<(src_loc)/mtproto/facade.cpp
|
||||||
<(src_loc)/mtproto/facade.h
|
<(src_loc)/mtproto/facade.h
|
||||||
<(src_loc)/mtproto/mtp_instance.cpp
|
<(src_loc)/mtproto/mtp_instance.cpp
|
||||||
|
|
Loading…
Reference in New Issue