#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 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 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 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 session, const std::vector> &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 session, DocumentData *data, not_null item) { auto latch = std::make_shared(1); auto lifetime = std::make_shared(); 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 session, const std::vector> &items, const ApiWrap::SendAction &action) { auto latch = std::make_shared(1); crl::on_main([=, &latch]{ session->api().forwardMessages(Data::ResolvedForwardDraft(items), action, [&]{ latch->countDown(); }); }); latch->await(std::chrono::minutes(1)); } void loadPhotoSync(not_null session, const std::pair, 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(1); auto lifetime = std::make_shared(); 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 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 session, const Api::SendAction &action) { auto latch = std::make_shared(1); auto lifetime = std::make_shared(); 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 session, Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, const std::shared_ptr &album, const Api::SendAction &action) { const auto size = list.files.size(); auto latch = std::make_shared(size); auto lifetime = std::make_shared(); 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 session, Api::MessageToSend &message, not_null document) { auto &action = message.action; crl::on_main([&] { Api::SendExistingDocument(std::move(message), document, std::nullopt); }); waitForMsgSync(session, action); } } // namespace AyuSync