tdesktop/Telegram/SourceFiles/storage/file_upload.cpp
2024-04-05 09:51:20 +04:00

909 lines
25 KiB
C++

/*
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 "storage/file_upload.h"
#include "api/api_editing.h"
#include "api/api_send_progress.h"
#include "storage/localimageloader.h"
#include "storage/file_download.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "ui/image/image_location_factory.h"
#include "history/history_item.h"
#include "history/history.h"
#include "core/file_location.h"
#include "core/mime_type.h"
#include "main/main_session.h"
#include "apiwrap.h"
namespace Storage {
namespace {
// max 1mb uploaded at the same time in each session
constexpr auto kMaxUploadPerSession = 1024 * 1024;
constexpr auto kDocumentMaxPartsCountDefault = 4000;
// 32kb for tiny document ( < 1mb )
constexpr auto kDocumentUploadPartSize0 = 32 * 1024;
// 64kb for little document ( <= 32mb )
constexpr auto kDocumentUploadPartSize1 = 64 * 1024;
// 128kb for small document ( <= 375mb )
constexpr auto kDocumentUploadPartSize2 = 128 * 1024;
// 256kb for medium document ( <= 750mb )
constexpr auto kDocumentUploadPartSize3 = 256 * 1024;
// 512kb for large document ( <= 1500mb )
constexpr auto kDocumentUploadPartSize4 = 512 * 1024;
// One part each half second, if not uploaded faster.
constexpr auto kUploadRequestInterval = crl::time(250);
// How much time without upload causes additional session kill.
constexpr auto kKillSessionTimeout = 15 * crl::time(1000);
// How much wait after session kill before killing another one.
constexpr auto kWaitForNormalizeTimeout = 8 * crl::time(1000);
constexpr auto kMaxSessionsCount = 8;
constexpr auto kFastRequestThreshold = 1 * crl::time(1000);
constexpr auto kSlowRequestThreshold = 8 * crl::time(1000);
// Request is 'fast' if it was done in less than 1s and
// (it-s size + queued before size) >= 512kb.
constexpr auto kAcceptAsFastIfTotalAtLeast = 512 * 1024;
[[nodiscard]] const char *ThumbnailFormat(const QString &mime) {
return Core::IsMimeSticker(mime) ? "WEBP" : "JPG";
}
} // namespace
struct Uploader::Entry {
Entry(FullMsgId itemId, const std::shared_ptr<FilePrepareResult> &file);
void setDocSize(int64 size);
bool setPartSize(int partSize);
// const, but non-const for the move-assignment in the
FullMsgId itemId;
std::shared_ptr<FilePrepareResult> file;
not_null<std::vector<QByteArray>*> parts;
uint64 partsOfId = 0;
int64 sentSize = 0;
ushort partsSent = 0;
ushort partsWaiting = 0;
HashMd5 md5Hash;
std::unique_ptr<QFile> docFile;
int64 docSize = 0;
int64 docSentSize = 0;
int docPartSize = 0;
ushort docPartsSent = 0;
ushort docPartsCount = 0;
ushort docPartsWaiting = 0;
};
struct Uploader::Request {
FullMsgId itemId;
crl::time sent = 0;
QByteArray bytes;
int queued = 0;
ushort part = 0;
uchar dcIndex = 0;
bool docPart = false;
bool bigPart = false;
bool nonPremiumDelayed = false;
};
Uploader::Entry::Entry(
FullMsgId itemId,
const std::shared_ptr<FilePrepareResult> &file)
: itemId(itemId)
, file(file)
, parts((file->type == SendMediaType::Photo
|| file->type == SendMediaType::Secure)
? &file->fileparts
: &file->thumbparts)
, partsOfId((file->type == SendMediaType::Photo
|| file->type == SendMediaType::Secure)
? file->id
: file->thumbId) {
if (file->type == SendMediaType::File
|| file->type == SendMediaType::ThemeFile
|| file->type == SendMediaType::Audio) {
setDocSize(file->filesize);
}
}
void Uploader::Entry::setDocSize(int64 size) {
docSize = size;
constexpr auto limit0 = 1024 * 1024;
constexpr auto limit1 = 32 * limit0;
if (docSize >= limit0 || !setPartSize(kDocumentUploadPartSize0)) {
if (docSize > limit1 || !setPartSize(kDocumentUploadPartSize1)) {
if (!setPartSize(kDocumentUploadPartSize2)) {
if (!setPartSize(kDocumentUploadPartSize3)) {
setPartSize(kDocumentUploadPartSize4);
}
}
}
}
}
bool Uploader::Entry::setPartSize(int partSize) {
docPartSize = partSize;
docPartsCount = (docSize + docPartSize - 1) / docPartSize;
return (docPartsCount <= kDocumentMaxPartsCountDefault);
}
Uploader::Uploader(not_null<ApiWrap*> api)
: _api(api)
, _nextTimer([=] { maybeSend(); })
, _stopSessionsTimer([=] { stopSessions(); }) {
const auto session = &_api->session();
photoReady(
) | rpl::start_with_next([=](UploadedMedia &&data) {
if (data.edit) {
const auto item = session->data().message(data.fullId);
Api::EditMessageWithUploadedPhoto(
item,
std::move(data.info),
data.options);
} else {
_api->sendUploadedPhoto(
data.fullId,
std::move(data.info),
data.options);
}
}, _lifetime);
documentReady(
) | rpl::start_with_next([=](UploadedMedia &&data) {
if (data.edit) {
const auto item = session->data().message(data.fullId);
Api::EditMessageWithUploadedDocument(
item,
std::move(data.info),
data.options);
} else {
_api->sendUploadedDocument(
data.fullId,
std::move(data.info),
data.options);
}
}, _lifetime);
photoProgress(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
processPhotoProgress(fullId);
}, _lifetime);
photoFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
processPhotoFailed(fullId);
}, _lifetime);
documentProgress(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
processDocumentProgress(fullId);
}, _lifetime);
documentFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) {
processDocumentFailed(fullId);
}, _lifetime);
_api->instance().nonPremiumDelayedRequests(
) | rpl::start_with_next([=](mtpRequestId id) {
const auto i = _requests.find(id);
if (i != end(_requests)) {
i->second.nonPremiumDelayed = true;
}
}, _lifetime);
}
void Uploader::processPhotoProgress(FullMsgId itemId) {
if (const auto item = session().data().message(itemId)) {
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto);
}
}
void Uploader::processDocumentProgress(FullMsgId itemId) {
if (const auto item = session().data().message(itemId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;
const auto sendAction = (document && document->isVoiceMessage())
? Api::SendProgressType::UploadVoice
: Api::SendProgressType::UploadFile;
const auto progress = (document && document->uploading())
? ((document->uploadingData->offset * 100)
/ document->uploadingData->size)
: 0;
sendProgressUpdate(item, sendAction, progress);
}
}
void Uploader::processPhotoFailed(FullMsgId itemId) {
if (const auto item = session().data().message(itemId)) {
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto, -1);
}
}
void Uploader::processDocumentFailed(FullMsgId itemId) {
if (const auto item = session().data().message(itemId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;
const auto sendAction = (document && document->isVoiceMessage())
? Api::SendProgressType::UploadVoice
: Api::SendProgressType::UploadFile;
sendProgressUpdate(item, sendAction, -1);
}
}
void Uploader::sendProgressUpdate(
not_null<HistoryItem*> item,
Api::SendProgressType type,
int progress) {
const auto history = item->history();
auto &manager = _api->session().sendProgressManager();
manager.update(history, type, progress);
if (const auto replyTo = item->replyToTop()) {
if (history->peer->isMegagroup()) {
manager.update(history, replyTo, type, progress);
}
} else if (history->isForum()) {
manager.update(history, item->topicRootId(), type, progress);
}
_api->session().data().requestItemRepaint(item);
}
Uploader::~Uploader() {
clear();
}
Main::Session &Uploader::session() const {
return _api->session();
}
FullMsgId Uploader::currentUploadId() const {
return _queue.empty() ? FullMsgId() : _queue.front().itemId;
}
void Uploader::upload(
FullMsgId itemId,
const std::shared_ptr<FilePrepareResult> &file) {
if (file->type == SendMediaType::Photo) {
const auto photo = session().data().processPhoto(
file->photo,
file->photoThumbs);
photo->uploadingData = std::make_unique<Data::UploadState>(
file->partssize);
} else if (file->type == SendMediaType::File
|| file->type == SendMediaType::ThemeFile
|| file->type == SendMediaType::Audio) {
const auto document = file->thumb.isNull()
? session().data().processDocument(file->document)
: session().data().processDocument(
file->document,
Images::FromImageInMemory(
file->thumb,
ThumbnailFormat(file->filemime),
file->thumbbytes));
document->uploadingData = std::make_unique<Data::UploadState>(
document->size);
if (const auto active = document->activeMediaView()) {
if (!file->goodThumbnail.isNull()) {
active->setGoodThumbnail(std::move(file->goodThumbnail));
}
if (!file->thumb.isNull()) {
active->setThumbnail(file->thumb);
}
}
if (!file->goodThumbnailBytes.isEmpty()) {
document->owner().cache().putIfEmpty(
document->goodThumbnailCacheKey(),
Storage::Cache::Database::TaggedValue(
std::move(file->goodThumbnailBytes),
Data::kImageCacheTag));
}
if (!file->content.isEmpty()) {
document->setDataAndCache(file->content);
}
if (!file->filepath.isEmpty()) {
document->setLocation(Core::FileLocation(file->filepath));
}
if (file->type == SendMediaType::ThemeFile) {
document->checkWallPaperProperties();
}
}
_queue.push_back({ itemId, file });
if (!_nextTimer.isActive()) {
maybeSend();
}
}
void Uploader::failed(FullMsgId itemId) {
const auto i = ranges::find(_queue, itemId, &Entry::itemId);
if (i != end(_queue)) {
const auto entry = std::move(*i);
_queue.erase(i);
notifyFailed(entry);
}
cancelRequests(itemId);
maybeFinishFront();
crl::on_main(this, [=] {
maybeSend();
});
}
void Uploader::notifyFailed(const Entry &entry) {
const auto type = entry.file->type;
if (type == SendMediaType::Photo) {
_photoFailed.fire_copy(entry.itemId);
} else if (type == SendMediaType::File
|| type == SendMediaType::ThemeFile
|| type == SendMediaType::Audio) {
const auto document = session().data().document(entry.file->id);
if (document->uploading()) {
document->status = FileUploadFailed;
}
_documentFailed.fire_copy(entry.itemId);
} else if (type == SendMediaType::Secure) {
_secureFailed.fire_copy(entry.itemId);
} else {
Unexpected("Type in Uploader::failed.");
}
}
void Uploader::stopSessions() {
if (ranges::any_of(_sentPerDcIndex, rpl::mappers::_1 != 0)) {
_stopSessionsTimer.callOnce(kKillSessionTimeout);
} else {
for (auto i = 0; i != int(_sentPerDcIndex.size()); ++i) {
_api->instance().stopSession(MTP::uploadDcId(i));
}
_sentPerDcIndex.clear();
_dcIndicesWithFastRequests.clear();
}
}
QByteArray Uploader::readDocPart(not_null<Entry*> entry) {
const auto checked = [&](QByteArray result) {
if ((entry->file->type == SendMediaType::File
|| entry->file->type == SendMediaType::ThemeFile
|| entry->file->type == SendMediaType::Audio)
&& entry->docSize <= kUseBigFilesFrom) {
entry->md5Hash.feed(result.data(), result.size());
}
if (result.isEmpty()
|| (result.size() > entry->docPartSize)
|| ((result.size() < entry->docPartSize
&& entry->docPartsSent + 1 != entry->docPartsCount))) {
return QByteArray();
}
return result;
};
auto &content = entry->file->content;
if (!content.isEmpty()) {
const auto offset = entry->docPartsSent * entry->docPartSize;
return checked(content.mid(offset, entry->docPartSize));
} else if (!entry->docFile) {
const auto filepath = entry->file->filepath;
entry->docFile = std::make_unique<QFile>(filepath);
if (!entry->docFile->open(QIODevice::ReadOnly)) {
return QByteArray();
}
}
return checked(entry->docFile->read(entry->docPartSize));
}
bool Uploader::canAddDcIndex() const {
const auto count = int(_sentPerDcIndex.size());
return (count < kMaxSessionsCount)
&& (count == int(_dcIndicesWithFastRequests.size()));
}
std::optional<uchar> Uploader::chooseDcIndexForNextRequest(
const base::flat_set<uchar> &used) {
for (auto i = 0, count = int(_sentPerDcIndex.size()); i != count; ++i) {
if (!_sentPerDcIndex[i] && !used.contains(i)) {
return i;
}
}
if (canAddDcIndex()) {
const auto result = int(_sentPerDcIndex.size());
_sentPerDcIndex.push_back(0);
_dcIndicesWithFastRequests.clear();
_latestDcIndexAdded = crl::now();
DEBUG_LOG(("Uploader: Added dc index %1.").arg(result));
return result;
}
auto result = std::optional<int>();
for (auto i = 0, count = int(_sentPerDcIndex.size()); i != count; ++i) {
if (!used.contains(i)
&& (!result.has_value()
|| _sentPerDcIndex[i] < _sentPerDcIndex[*result])) {
result = i;
}
}
return result;
}
Uploader::Entry *Uploader::chooseEntryForNextRequest() {
if (!_pendingFromRemovedDcIndices.empty()) {
const auto itemId = _pendingFromRemovedDcIndices.front().itemId;
const auto i = ranges::find(_queue, itemId, &Entry::itemId);
Assert(i != end(_queue));
return &*i;
}
for (auto i = begin(_queue); i != end(_queue); ++i) {
if (i->partsSent < i->parts->size()
|| i->docPartsSent < i->docPartsCount) {
return &*i;
}
}
return nullptr;
}
auto Uploader::sendPart(not_null<Entry*> entry, uchar dcIndex)
-> SendResult {
return !_pendingFromRemovedDcIndices.empty()
? sendPendingPart(entry, dcIndex)
: (entry->partsSent < entry->parts->size())
? sendSlicedPart(entry, dcIndex)
: sendDocPart(entry, dcIndex);
}
template <typename Prepared>
void Uploader::sendPreparedRequest(Prepared &&prepared, Request &&request) {
auto &sentInSession = _sentPerDcIndex[request.dcIndex];
const auto queued = sentInSession;
sentInSession += int(request.bytes.size());
const auto requestId = _api->request(
std::move(prepared)
).done([=](const MTPBool &result, mtpRequestId requestId) {
partLoaded(result, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
partFailed(error, requestId);
}).toDC(MTP::uploadDcId(request.dcIndex)).send();
request.sent = crl::now();
request.queued = queued;
_requests.emplace(requestId, std::move(request));
}
auto Uploader::sendPendingPart(not_null<Entry*> entry, uchar dcIndex)
-> SendResult {
Expects(!_pendingFromRemovedDcIndices.empty());
Expects(_pendingFromRemovedDcIndices.front().itemId == entry->itemId);
auto request = std::move(_pendingFromRemovedDcIndices.front());
_pendingFromRemovedDcIndices.erase(begin(_pendingFromRemovedDcIndices));
const auto part = request.part;
const auto bytes = request.bytes;
request.dcIndex = dcIndex;
if (request.bigPart) {
sendPreparedRequest(MTPupload_SaveBigFilePart(
MTP_long(entry->file->id),
MTP_int(part),
MTP_int(entry->docPartsCount),
MTP_bytes(bytes)
), std::move(request));
} else {
const auto id = request.docPart ? entry->file->id : entry->partsOfId;
sendPreparedRequest(MTPupload_SaveFilePart(
MTP_long(id),
MTP_int(part),
MTP_bytes(bytes)
), std::move(request));
}
return SendResult::Success;
}
auto Uploader::sendDocPart(not_null<Entry*> entry, uchar dcIndex)
-> SendResult {
const auto itemId = entry->itemId;
const auto alreadySent = _sentPerDcIndex[dcIndex];
const auto willProbablyBeSent = entry->docPartSize;
if (alreadySent + willProbablyBeSent > kMaxUploadPerSession) {
return SendResult::DcIndexFull;
}
Assert(entry->docPartsSent < entry->docPartsCount);
const auto partBytes = readDocPart(entry);
if (partBytes.isEmpty()) {
failed(itemId);
return SendResult::Failed;
}
const auto part = entry->docPartsSent++;
++entry->docPartsWaiting;
const auto send = [&](auto &&request, bool big) {
sendPreparedRequest(std::move(request), {
.itemId = itemId,
.bytes = partBytes,
.part = part,
.dcIndex = dcIndex,
.docPart = true,
.bigPart = big,
});
};
if (entry->docSize > kUseBigFilesFrom) {
send(MTPupload_SaveBigFilePart(
MTP_long(entry->file->id),
MTP_int(part),
MTP_int(entry->docPartsCount),
MTP_bytes(partBytes)
), true);
} else {
send(MTPupload_SaveFilePart(
MTP_long(entry->file->id),
MTP_int(part),
MTP_bytes(partBytes)
), false);
}
return SendResult::Success;
}
auto Uploader::sendSlicedPart(not_null<Entry*> entry, uchar dcIndex)
-> SendResult {
const auto itemId = entry->itemId;
const auto alreadySent = _sentPerDcIndex[dcIndex];
const auto willBeSent = entry->parts->at(entry->partsSent).size();
if (alreadySent + willBeSent >= kMaxUploadPerSession) {
return SendResult::DcIndexFull;
}
++entry->partsWaiting;
const auto index = entry->partsSent++;
const auto partBytes = entry->parts->at(index);
sendPreparedRequest(MTPupload_SaveFilePart(
MTP_long(entry->partsOfId),
MTP_int(index),
MTP_bytes(partBytes)
), {
.itemId = itemId,
.bytes = partBytes,
.dcIndex = dcIndex,
});
return SendResult::Success;
}
void Uploader::maybeSend() {
const auto stopping = _stopSessionsTimer.isActive();
if (_queue.empty()) {
if (!stopping) {
_stopSessionsTimer.callOnce(kKillSessionTimeout);
}
_pausedId = FullMsgId();
return;
} else if (_pausedId) {
return;
} else if (stopping) {
_stopSessionsTimer.cancel();
}
auto usedDcIndices = base::flat_set<uchar>();
while (true) {
const auto maybeDcIndex = chooseDcIndexForNextRequest(usedDcIndices);
if (!maybeDcIndex.has_value()) {
break;
}
const auto dcIndex = *maybeDcIndex;
while (true) {
const auto entry = chooseEntryForNextRequest();
if (!entry) {
return;
}
const auto result = sendPart(entry, dcIndex);
if (result == SendResult::DcIndexFull) {
return;
} else if (result == SendResult::Success) {
break;
}
// If this entry failed, we try the next one.
}
if (_sentPerDcIndex[dcIndex] >= kAcceptAsFastIfTotalAtLeast) {
usedDcIndices.emplace(dcIndex);
}
}
if (usedDcIndices.empty()) {
_nextTimer.cancel();
} else {
_nextTimer.callOnce(kUploadRequestInterval);
}
}
void Uploader::cancel(FullMsgId itemId) {
failed(itemId);
}
void Uploader::cancelAll() {
while (!_queue.empty()) {
failed(_queue.front().itemId);
}
clear();
unpause();
}
void Uploader::pause(FullMsgId itemId) {
_pausedId = itemId;
}
void Uploader::unpause() {
_pausedId = FullMsgId();
maybeSend();
}
void Uploader::cancelRequests(FullMsgId itemId) {
for (auto i = begin(_requests); i != end(_requests);) {
if (i->second.itemId == itemId) {
const auto bytes = int(i->second.bytes.size());
_sentPerDcIndex[i->second.dcIndex] -= bytes;
_api->request(i->first).cancel();
i = _requests.erase(i);
} else {
++i;
}
}
_pendingFromRemovedDcIndices.erase(ranges::remove(
_pendingFromRemovedDcIndices,
itemId,
&Request::itemId
), end(_pendingFromRemovedDcIndices));
}
void Uploader::cancelAllRequests() {
for (const auto &[requestId, request] : base::take(_requests)) {
_api->request(requestId).cancel();
}
ranges::fill(_sentPerDcIndex, 0);
}
void Uploader::clear() {
_queue.clear();
cancelAllRequests();
stopSessions();
_stopSessionsTimer.cancel();
}
Uploader::Request Uploader::finishRequest(mtpRequestId requestId) {
const auto taken = _requests.take(requestId);
Assert(taken.has_value());
_sentPerDcIndex[taken->dcIndex] -= int(taken->bytes.size());
return *taken;
}
void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
const auto request = finishRequest(requestId);
const auto bytes = int(request.bytes.size());
const auto itemId = request.itemId;
if (mtpIsFalse(result)) { // failed to upload current file
failed(itemId);
return;
}
const auto i = ranges::find(_queue, itemId, &Entry::itemId);
Assert(i != end(_queue));
auto &entry = *i;
const auto now = crl::now();
const auto duration = now - request.sent;
const auto fast = (duration < kFastRequestThreshold);
const auto slowish = !fast;
const auto slow = (duration >= kSlowRequestThreshold);
if (slowish) {
_dcIndicesWithFastRequests.clear();
if (slow) {
const auto elapsed = (now - _latestDcIndexRemoved);
const auto remove = (elapsed >= kWaitForNormalizeTimeout);
if (remove && _sentPerDcIndex.size() > 1) {
DEBUG_LOG(("Uploader: Slow request, removing dc index."));
removeDcIndex();
_latestDcIndexRemoved = now;
} else {
DEBUG_LOG(("Uploader: Slow request, clear fast records."));
}
} else {
DEBUG_LOG(("Uploader: Slow-ish request, clear fast records."));
}
} else if (request.sent > _latestDcIndexAdded
&& (request.queued + bytes >= kAcceptAsFastIfTotalAtLeast)) {
if (_dcIndicesWithFastRequests.emplace(request.dcIndex).second) {
DEBUG_LOG(("Uploader: Mark %1 of %2 as fast."
).arg(request.dcIndex
).arg(_sentPerDcIndex.size()));
}
}
if (request.docPart) {
--entry.docPartsWaiting;
entry.docSentSize += bytes;
} else {
--entry.partsWaiting;
entry.sentSize += bytes;
}
if (entry.file->type == SendMediaType::Photo) {
const auto photo = session().data().photo(entry.file->id);
if (photo->uploading()) {
photo->uploadingData->size = entry.file->partssize;
photo->uploadingData->offset = entry.sentSize;
}
_photoProgress.fire_copy(itemId);
} else if (entry.file->type == SendMediaType::File
|| entry.file->type == SendMediaType::ThemeFile
|| entry.file->type == SendMediaType::Audio) {
const auto document = session().data().document(entry.file->id);
if (document->uploading()) {
document->uploadingData->offset = std::min(
document->uploadingData->size,
entry.docSentSize);
}
_documentProgress.fire_copy(itemId);
} else if (entry.file->type == SendMediaType::Secure) {
_secureProgress.fire_copy({
.fullId = itemId,
.offset = entry.sentSize,
.size = entry.file->partssize,
});
}
if (request.nonPremiumDelayed) {
_nonPremiumDelays.fire_copy(itemId);
}
if (!_queue.empty() && itemId == _queue.front().itemId) {
maybeFinishFront();
}
maybeSend();
}
void Uploader::removeDcIndex() {
Expects(_sentPerDcIndex.size() > 1);
const auto dcIndex = int(_sentPerDcIndex.size()) - 1;
for (auto i = begin(_requests); i != end(_requests);) {
if (i->second.dcIndex == dcIndex) {
const auto bytes = int(i->second.bytes.size());
_sentPerDcIndex[dcIndex] -= bytes;
_api->request(i->first).cancel();
_pendingFromRemovedDcIndices.push_back(std::move(i->second));
i = _requests.erase(i);
} else {
++i;
}
}
Assert(_sentPerDcIndex.back() == 0);
_sentPerDcIndex.pop_back();
_dcIndicesWithFastRequests.remove(dcIndex);
_api->instance().stopSession(MTP::uploadDcId(dcIndex));
DEBUG_LOG(("Uploader: Removed dc index %1.").arg(dcIndex));
}
void Uploader::maybeFinishFront() {
while (!_queue.empty()) {
const auto &entry = _queue.front();
if (entry.partsSent >= entry.parts->size()
&& entry.docPartsSent >= entry.docPartsCount
&& !entry.partsWaiting
&& !entry.docPartsWaiting) {
finishFront();
} else {
break;
}
}
}
void Uploader::finishFront() {
Expects(!_queue.empty());
auto entry = std::move(_queue.front());
_queue.erase(_queue.begin());
const auto options = entry.file
? entry.file->to.options
: Api::SendOptions();
const auto edit = entry.file &&
entry.file->to.replaceMediaOf;
const auto attachedStickers = entry.file
? entry.file->attachedStickers
: std::vector<MTPInputDocument>();
if (entry.file->type == SendMediaType::Photo) {
auto photoFilename = entry.file->filename;
if (!photoFilename.endsWith(u".jpg"_q, Qt::CaseInsensitive)) {
// Server has some extensions checking for inputMediaUploadedPhoto,
// so force the extension to be .jpg anyway. It doesn't matter,
// because the filename from inputFile is not used anywhere.
photoFilename += u".jpg"_q;
}
const auto md5 = entry.file->filemd5;
const auto file = MTP_inputFile(
MTP_long(entry.file->id),
MTP_int(entry.parts->size()),
MTP_string(photoFilename),
MTP_bytes(md5));
_photoReady.fire({
.fullId = entry.itemId,
.info = {
.file = file,
.attachedStickers = attachedStickers,
},
.options = options,
.edit = edit,
});
} else if (entry.file->type == SendMediaType::File
|| entry.file->type == SendMediaType::ThemeFile
|| entry.file->type == SendMediaType::Audio) {
QByteArray docMd5(32, Qt::Uninitialized);
hashMd5Hex(entry.md5Hash.result(), docMd5.data());
const auto file = (entry.docSize > kUseBigFilesFrom)
? MTP_inputFileBig(
MTP_long(entry.file->id),
MTP_int(entry.docPartsCount),
MTP_string(entry.file->filename))
: MTP_inputFile(
MTP_long(entry.file->id),
MTP_int(entry.docPartsCount),
MTP_string(entry.file->filename),
MTP_bytes(docMd5));
const auto thumb = [&]() -> std::optional<MTPInputFile> {
if (entry.parts->empty()) {
return std::nullopt;
}
const auto thumbFilename = entry.file->thumbname;
const auto thumbMd5 = entry.file->thumbmd5;
return MTP_inputFile(
MTP_long(entry.file->thumbId),
MTP_int(entry.parts->size()),
MTP_string(thumbFilename),
MTP_bytes(thumbMd5));
}();
_documentReady.fire({
.fullId = entry.itemId,
.info = {
.file = file,
.thumb = thumb,
.attachedStickers = attachedStickers,
},
.options = options,
.edit = edit,
});
} else if (entry.file->type == SendMediaType::Secure) {
_secureReady.fire({
entry.itemId,
entry.file->id,
int(entry.parts->size()),
});
}
}
void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) {
const auto request = finishRequest(requestId);
failed(request.itemId);
}
} // namespace Storage