AyuGramDesktop/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp
2025-06-25 19:15:48 +05:00

287 lines
7.6 KiB
C++

#include "ayu_sync.h"
#include "api/api_sending.h"
#include "apiwrap.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "history/history_item.h"
#include "main/main_session.h"
#include "storage/file_download_mtproto.h"
class TimedCountDownLatch
{
public:
explicit TimedCountDownLatch(int count) : count_(count) {}
void countDown() {
std::unique_lock lock(mutex_);
if (count_ > 0) {
count_--;
}
if (count_ == 0) {
cv_.notify_all();
}
}
bool await(std::chrono::milliseconds timeout) {
std::unique_lock lock(mutex_);
if (count_ == 0) {
return true;
}
return cv_.wait_for(lock, timeout, [this] { return count_ == 0; });
}
private:
std::mutex mutex_;
std::condition_variable cv_;
int count_;
};
namespace AyuSync {
QString pathForSave(not_null<Main::Session *> session) {
const auto path = Core::App().settings().downloadPath();
if (path.isEmpty()) {
return File::DefaultDownloadPath(session);
}
if (path == FileDialog::Tmp()) {
return session->local().tempDirectory();
}
return path;
}
QString filePath(not_null<Main::Session *> session, const Data::Media *media) {
if (const auto document = media->document()) {
if (!document->filename().isEmpty()) {
return pathForSave(session) + media->document()->filename();
}
if (const auto name = document->filepath(true); !name.isEmpty()) {
return name;
}
if (document->isVoiceMessage()) {
return pathForSave(session) + "audio_" + QString::number(document->getDC()) + "_" +
QString::number(document->id) + ".ogg";
}
if (document->isVideoMessage()) {
return pathForSave(session) + "round_" + QString::number(document->getDC()) + "_" +
QString::number(document->id) + ".mp4";
}
} else if (const auto photo = media->photo()) {
return pathForSave(session) + QString::number(photo->getDC()) + "_" + QString::number(photo->id) + ".jpg";
}
return QString();
}
qint64 fileSize(not_null<HistoryItem *> item) {
if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) {
QFile file(path);
if (file.exists()) {
return file.size();
}
}
return 0;
}
void loadDocuments(not_null<Main::Session *> session, const std::vector<not_null<HistoryItem *>> &items) {
for (const auto &item : items) {
if (const auto data = item->media()->document()) {
if (data->isGifv()) {
// no need to download it
continue;
}
const auto size = fileSize(item);
if (size == data->size) {
continue;
}
if (size && size < data->size) {
// in case there some unfinished file
QFile file(filePath(session, item->media()));
file.remove();
}
loadDocumentSync(session, data, item);
} else if (auto photo = item->media()->photo()) {
if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) {
continue;
}
loadPhotoSync(session, std::pair(photo, item->fullId()));
}
}
}
void loadDocumentSync(not_null<Main::Session *> session, DocumentData *data, not_null<HistoryItem *> item) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media()));
session->downloaderTaskFinished()
| rpl::filter([&]{
return item->media()->document()->status == FileDownloadFailed || fileSize(item) == data->size;
}) | rpl::start_with_next([&]() mutable{
latch->countDown();
base::take(lifetime)->destroy();
}, *lifetime);
latch->await(std::chrono::minutes(5));
}
void forwardMessagesSync(not_null<Main::Session *> session,
const std::vector<not_null<HistoryItem *>> &items,
const ApiWrap::SendAction &action) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
crl::on_main([=, &latch]{
session->api().forwardMessages(Data::ResolvedForwardDraft(items), action, [&]{
latch->countDown();
});
});
latch->await(std::chrono::minutes(1));
}
void loadPhotoSync(not_null<Main::Session *> session, const std::pair<not_null<PhotoData *>, FullMsgId> &photo) {
const auto folderPath = pathForSave(session);
const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath;
const auto path = downloadPath.isEmpty()
? File::DefaultDownloadPath(session)
: downloadPath == FileDialog::Tmp()
? session->local().tempDirectory()
: downloadPath;
if (path.isEmpty()) {
return;
}
if (!QDir().mkpath(path)) {
return;
}
const auto view = photo.first->createMediaView();
if (!view) {
return;
}
view->wanted(Data::PhotoSize::Large, photo.second);
const auto finalCheck = [=]{
return !photo.first->loading();
};
const auto saveToFiles = [=]{
QDir directory(path);
const auto dir = directory.absolutePath();
const auto nameBase = dir.endsWith('/') ? dir : dir + '/';
const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id) + ".jpg";
view->saveToFile(fullPath);
};
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
if (finalCheck()) {
saveToFiles();
} else {
session->downloaderTaskFinished() | rpl::filter([&]{
return finalCheck();
}) | rpl::start_with_next([&]() mutable{
saveToFiles();
latch->countDown();
base::take(lifetime)->destroy();
},*lifetime);
}
latch->await(std::chrono::minutes(5));
}
void sendMessageSync(not_null<Main::Session *> session, Api::MessageToSend &message) {
crl::on_main([=, &message] {
// we cannot send events to objects
// owned by a different thread
// because sendMessage updates UI too
session->api().sendMessage(std::move(message));
});
waitForMsgSync(session, message.action);
}
void waitForMsgSync(not_null<Main::Session *> session, const Api::SendAction &action) {
auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>();
session->data().itemIdChanged()
| rpl::filter([&](const Data::Session::IdChange &update){
return action.history->peer->id == update.newId.peer;
}) | rpl::start_with_next([&]{
latch->countDown();
base::take(lifetime)->destroy();
},*lifetime);
latch->await(std::chrono::minutes(2));
}
void sendDocumentSync(not_null<Main::Session *> session,
Ui::PreparedList &&list,
SendMediaType type,
TextWithTags &&caption,
const std::shared_ptr<SendingAlbum> &album,
const Api::SendAction &action) {
const auto size = list.files.size();
auto latch = std::make_shared<TimedCountDownLatch>(size);
auto lifetime = std::make_shared<rpl::lifetime>();
crl::on_main([=, list = std::move(list), caption = std::move(caption)]() mutable{
session->api().sendFiles(std::move(list), type, std::move(caption), album, action);
});
// probably need to handle
// session->uploader().photoFailed()
// and
// session->uploader().documentFailed()
// too
rpl::merge(
session->uploader().documentReady(),
session->uploader().photoReady()
) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto){
return docOrPhoto.fullId.peer == action.history->peer->id;
}) | rpl::start_with_next([&]{
latch->countDown();
}, *lifetime);
latch->await(std::chrono::minutes(5 * size));
base::take(lifetime)->destroy();
}
void sendGifOrStickSync(not_null<Main::Session *> session,
Api::MessageToSend &message,
not_null<DocumentData *> document) {
auto &action = message.action;
crl::on_main([&] {
Api::SendExistingDocument(std::move(message), document, std::nullopt);
});
waitForMsgSync(session, action);
}
} // namespace AyuSync