| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899 |
- /*
- 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 "data/data_document.h"
- #include "data/data_document_resolver.h"
- #include "data/data_session.h"
- #include "data/data_streaming.h"
- #include "data/data_document_media.h"
- #include "data/data_reply_preview.h"
- #include "data/data_web_page.h"
- #include "lang/lang_keys.h"
- #include "inline_bots/inline_bot_layout_item.h"
- #include "main/main_session.h"
- #include "mainwidget.h"
- #include "core/file_utilities.h"
- #include "core/mime_type.h"
- #include "data/stickers/data_stickers.h"
- #include "media/audio/media_audio.h"
- #include "media/player/media_player_instance.h"
- #include "media/streaming/media_streaming_loader_mtproto.h"
- #include "media/streaming/media_streaming_loader_local.h"
- #include "storage/localstorage.h"
- #include "storage/storage_account.h"
- #include "storage/streamed_file_downloader.h"
- #include "storage/file_download_mtproto.h"
- #include "storage/file_download_web.h"
- #include "base/options.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/view/media/history_view_gif.h"
- #include "window/window_session_controller.h"
- #include "ui/boxes/confirm_box.h"
- #include "base/base_file_utilities.h"
- #include "mainwindow.h"
- #include "core/application.h"
- #include "lottie/lottie_animation.h"
- #include "boxes/abstract_box.h" // Ui::hideLayer().
- #include <QtCore/QBuffer>
- #include <QtCore/QMimeType>
- #include <QtCore/QMimeDatabase>
- namespace {
- constexpr auto kDefaultCoverThumbnailSize = 100;
- constexpr auto kMaxAllowedPreloadPrefix = 6 * 1024 * 1024;
- constexpr auto kDefaultWebmEmojiSize = 100;
- constexpr auto kDefaultWebmStickerLargerSize = kStickerSideSize;
- const auto kLottieStickerDimensions = QSize(
- kStickerSideSize,
- kStickerSideSize);
- QString JoinStringList(const QStringList &list, const QString &separator) {
- const auto count = list.size();
- if (!count) {
- return QString();
- }
- auto result = QString();
- auto fullsize = separator.size() * (count - 1);
- for (const auto &string : list) {
- fullsize += string.size();
- }
- result.reserve(fullsize);
- result.append(list[0]);
- for (auto i = 1; i != count; ++i) {
- result.append(separator).append(list[i]);
- }
- return result;
- }
- void UpdateStickerSetIdentifier(
- StickerSetIdentifier &now,
- const MTPInputStickerSet &from) {
- now = from.match([&](const MTPDinputStickerSetID &data) {
- return StickerSetIdentifier{
- .id = data.vid().v,
- .accessHash = data.vaccess_hash().v,
- };
- }, [](const auto &) {
- return StickerSetIdentifier();
- });
- }
- } // namespace
- QString FileNameUnsafe(
- not_null<Main::Session*> session,
- const QString &title,
- const QString &filter,
- const QString &prefix,
- QString name,
- bool savingAs,
- const QDir &dir) {
- name = base::FileNameFromUserString(name);
- if (Core::App().settings().askDownloadPath() || savingAs) {
- if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) {
- name = filedialogDefaultName(prefix, name);
- } else if (dir.path() != u"."_q) {
- QString path = dir.absolutePath();
- if (path != cDialogLastPath()) {
- cSetDialogLastPath(path);
- Local::writeSettings();
- }
- }
- // check if extension of filename is present in filter
- // it should be in first filter section on the first place
- // place it there, if it is not
- QString ext = QFileInfo(name).suffix(), fil = filter, sep = u";;"_q;
- if (!ext.isEmpty()) {
- if (QRegularExpression(u"^[a-zA-Z_0-9]+$"_q).match(ext).hasMatch()) {
- QStringList filters = filter.split(sep);
- if (filters.size() > 1) {
- const auto &first = filters.at(0);
- int32 start = first.indexOf(u"(*."_q);
- if (start >= 0) {
- if (!QRegularExpression(u"\\(\\*\\."_q + ext + u"[\\)\\s]"_q, QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) {
- QRegularExpressionMatch m = QRegularExpression(u" \\*\\."_q + ext + u"[\\)\\s]"_q, QRegularExpression::CaseInsensitiveOption).match(first);
- if (m.hasMatch() && m.capturedStart() > start + 3) {
- int32 oldpos = m.capturedStart(), oldend = m.capturedEnd();
- fil = first.mid(0, start + 3) + ext + u" *."_q + first.mid(start + 3, oldpos - start - 3) + first.mid(oldend - 1) + sep + JoinStringList(filters.mid(1), sep);
- } else {
- fil = first.mid(0, start + 3) + ext + u" *."_q + first.mid(start + 3) + sep + JoinStringList(filters.mid(1), sep);
- }
- }
- } else {
- fil = QString();
- }
- } else {
- fil = QString();
- }
- } else {
- fil = QString();
- }
- }
- return filedialogGetSaveFile(name, title, fil, name) ? name : QString();
- }
- auto path = [&] {
- const auto path = Core::App().settings().downloadPath();
- if (path.isEmpty()) {
- return File::DefaultDownloadPath(session);
- } else if (path == FileDialog::Tmp()) {
- return session->local().tempDirectory();
- } else {
- return path;
- }
- }();
- if (path.isEmpty()) return QString();
- if (name.isEmpty()) name = u".unknown"_q;
- if (name.at(0) == QChar::fromLatin1('.')) {
- if (!QDir().exists(path)) QDir().mkpath(path);
- return filedialogDefaultName(prefix, name, path);
- }
- if (dir.path() != u"."_q) {
- path = dir.absolutePath() + '/';
- }
- QString nameStart, extension;
- int32 extPos = name.lastIndexOf('.');
- if (extPos >= 0) {
- nameStart = name.mid(0, extPos);
- extension = name.mid(extPos);
- } else {
- nameStart = name;
- }
- QString nameBase = path + nameStart;
- name = nameBase + extension;
- for (int i = 0; QFileInfo::exists(name); ++i) {
- name = nameBase + u" (%1)"_q.arg(i + 2) + extension;
- }
- if (!QDir().exists(path)) QDir().mkpath(path);
- return name;
- }
- QString FileNameForSave(
- not_null<Main::Session*> session,
- const QString &title,
- const QString &filter,
- const QString &prefix,
- QString name,
- bool savingAs,
- const QDir &dir) {
- const auto result = FileNameUnsafe(
- session,
- title,
- filter,
- prefix,
- name,
- savingAs,
- dir);
- #ifdef Q_OS_WIN
- const auto lower = result.trimmed().toLower();
- const auto kBadExtensions = { u".lnk"_q, u".scf"_q };
- const auto kMaskExtension = u".download"_q;
- for (const auto extension : kBadExtensions) {
- if (lower.endsWith(extension)) {
- return result + kMaskExtension;
- }
- }
- #endif // Q_OS_WIN
- return result;
- }
- QString DocumentFileNameForSave(
- not_null<const DocumentData*> data,
- bool forceSavingAs,
- const QString &already,
- const QDir &dir) {
- auto alreadySavingFilename = data->loadingFilePath();
- if (!alreadySavingFilename.isEmpty()) {
- return alreadySavingFilename;
- }
- QString name, filter, caption, prefix;
- const auto mimeType = Core::MimeTypeForName(data->mimeString());
- QStringList p = mimeType.globPatterns();
- QString pattern = p.isEmpty() ? QString() : p.front();
- if (data->isVoiceMessage()) {
- auto mp3 = data->hasMimeType(u"audio/mp3"_q);
- name = already.isEmpty() ? (mp3 ? u".mp3"_q : u".ogg"_q) : already;
- filter = mp3 ? u"MP3 Audio (*.mp3);;"_q : u"OGG Opus Audio (*.ogg);;"_q;
- filter += FileDialog::AllFilesFilter();
- caption = tr::lng_save_audio(tr::now);
- prefix = u"audio"_q;
- } else if (data->isVideoFile()) {
- name = already.isEmpty() ? data->filename() : already;
- if (name.isEmpty()) {
- name = pattern.isEmpty() ? u".mov"_q : pattern.replace('*', QString());
- }
- if (pattern.isEmpty()) {
- filter = u"MOV Video (*.mov);;"_q + FileDialog::AllFilesFilter();
- } else {
- filter = mimeType.filterString() + u";;"_q + FileDialog::AllFilesFilter();
- }
- caption = tr::lng_save_video(tr::now);
- prefix = u"video"_q;
- } else {
- name = already.isEmpty() ? data->filename() : already;
- if (name.isEmpty()) {
- name = pattern.isEmpty() ? u".unknown"_q : pattern.replace('*', QString());
- }
- if (pattern.isEmpty()) {
- filter = QString();
- } else {
- filter = mimeType.filterString() + u";;"_q + FileDialog::AllFilesFilter();
- }
- caption = data->isAudioFile()
- ? tr::lng_save_audio_file(tr::now)
- : tr::lng_save_file(tr::now);
- prefix = u"doc"_q;
- }
- return FileNameForSave(
- &data->session(),
- caption,
- filter,
- prefix,
- name,
- forceSavingAs,
- dir);
- }
- Data::FileOrigin StickerData::setOrigin() const {
- return set.id
- ? Data::FileOrigin(
- Data::FileOriginStickerSet(set.id, set.accessHash))
- : Data::FileOrigin();
- }
- bool StickerData::isStatic() const {
- return (type == StickerType::Webp);
- }
- bool StickerData::isLottie() const {
- return (type == StickerType::Tgs);
- }
- bool StickerData::isAnimated() const {
- return !isStatic();
- }
- bool StickerData::isWebm() const {
- return (type == StickerType::Webm);
- }
- VoiceData::~VoiceData() {
- if (!waveform.isEmpty()
- && waveform[0] == -1
- && waveform.size() > int32(sizeof(TaskId))) {
- auto taskId = TaskId();
- memcpy(&taskId, waveform.constData() + 1, sizeof(taskId));
- Local::cancelTask(taskId);
- }
- }
- DocumentData::DocumentData(not_null<Data::Session*> owner, DocumentId id)
- : id(id)
- , _owner(owner) {
- }
- DocumentData::~DocumentData() {
- base::take(_thumbnail.loader).reset();
- base::take(_videoThumbnail.loader).reset();
- destroyLoader();
- }
- Data::Session &DocumentData::owner() const {
- return *_owner;
- }
- Main::Session &DocumentData::session() const {
- return _owner->session();
- }
- void DocumentData::setattributes(
- const QVector<MTPDocumentAttribute> &attributes) {
- _duration = -1;
- _flags &= ~(Flag::ImageType
- | Flag::HasAttachedStickers
- | Flag::UseTextColor
- | Flag::SilentVideo
- | kStreamingSupportedMask);
- _flags |= kStreamingSupportedUnknown;
- validateLottieSticker();
- auto wasVideoData = isVideoFile() ? std::move(_additional) : nullptr;
- _videoPreloadPrefix = 0;
- for (const auto &attribute : attributes) {
- attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
- dimensions = QSize(data.vw().v, data.vh().v);
- }, [&](const MTPDdocumentAttributeAnimated &data) {
- if (type == FileDocument
- || type == VideoDocument
- || (sticker() && sticker()->type != StickerType::Webm)) {
- type = AnimatedDocument;
- _additional = nullptr;
- }
- }, [&](const MTPDdocumentAttributeSticker &data) {
- const auto was = type;
- if (type == FileDocument || type == VideoDocument) {
- type = StickerDocument;
- _additional = std::make_unique<StickerData>();
- }
- if (const auto info = sticker()) {
- info->setType = data.is_mask()
- ? Data::StickersType::Masks
- : Data::StickersType::Stickers;
- if (was == VideoDocument) {
- info->type = StickerType::Webm;
- }
- info->alt = qs(data.valt());
- UpdateStickerSetIdentifier(info->set, data.vstickerset());
- }
- }, [&](const MTPDdocumentAttributeCustomEmoji &data) {
- const auto was = type;
- if (type == FileDocument || type == VideoDocument) {
- type = StickerDocument;
- _additional = std::make_unique<StickerData>();
- }
- if (const auto info = sticker()) {
- info->setType = Data::StickersType::Emoji;
- if (was == VideoDocument) {
- info->type = StickerType::Webm;
- }
- info->alt = qs(data.valt());
- if (data.is_free()) {
- _flags &= ~Flag::PremiumSticker;
- } else {
- _flags |= Flag::PremiumSticker;
- }
- if (data.is_text_color()) {
- _flags |= Flag::UseTextColor;
- }
- UpdateStickerSetIdentifier(info->set, data.vstickerset());
- }
- }, [&](const MTPDdocumentAttributeVideo &data) {
- if (type == FileDocument) {
- type = data.is_round_message()
- ? RoundVideoDocument
- : VideoDocument;
- if (data.is_round_message()) {
- _additional = std::make_unique<RoundData>();
- } else {
- if (const auto size = data.vpreload_prefix_size()) {
- if (size->v > 0
- && size->v < kMaxAllowedPreloadPrefix) {
- _videoPreloadPrefix = size->v;
- }
- }
- _additional = wasVideoData
- ? std::move(wasVideoData)
- : std::make_unique<VideoData>();
- video()->codec = qs(
- data.vvideo_codec().value_or_empty());
- }
- } else if (type == VideoDocument && wasVideoData) {
- _additional = std::move(wasVideoData);
- } else if (const auto info = sticker()) {
- info->type = StickerType::Webm;
- }
- _duration = crl::time(
- base::SafeRound(data.vduration().v * 1000));
- setMaybeSupportsStreaming(data.is_supports_streaming());
- if (data.is_nosound()) {
- _flags |= Flag::SilentVideo;
- }
- dimensions = QSize(data.vw().v, data.vh().v);
- }, [&](const MTPDdocumentAttributeAudio &data) {
- if (type == FileDocument) {
- if (data.is_voice()) {
- type = VoiceDocument;
- _additional = std::make_unique<VoiceData>();
- } else {
- type = SongDocument;
- _additional = std::make_unique<SongData>();
- }
- }
- if (const auto voiceData = voice() ? voice() : round()) {
- _duration = data.vduration().v * crl::time(1000);
- voiceData->waveform = documentWaveformDecode(
- data.vwaveform().value_or_empty());
- voiceData->wavemax = voiceData->waveform.empty()
- ? uchar(0)
- : *ranges::max_element(voiceData->waveform);
- } else if (const auto songData = song()) {
- _duration = data.vduration().v * crl::time(1000);
- songData->title = qs(data.vtitle().value_or_empty());
- songData->performer = qs(data.vperformer().value_or_empty());
- refreshPossibleCoverThumbnail();
- }
- }, [&](const MTPDdocumentAttributeFilename &data) {
- setFileName(qs(data.vfile_name()));
- }, [&](const MTPDdocumentAttributeHasStickers &data) {
- _flags |= Flag::HasAttachedStickers;
- });
- }
- // Any "video/webm" file is treated as a video-sticker.
- if (hasMimeType(u"video/webm"_q)) {
- if (type == FileDocument) {
- type = StickerDocument;
- _additional = std::make_unique<StickerData>();
- }
- if (type == StickerDocument) {
- sticker()->type = StickerType::Webm;
- }
- }
- // If "video/webm" sticker without dimensions we set them to default.
- if (const auto info = sticker(); info
- && info->set
- && info->type == StickerType::Webm
- && dimensions.isEmpty()) {
- if (info->setType == Data::StickersType::Emoji) {
- // Always fixed.
- dimensions = { kDefaultWebmEmojiSize, kDefaultWebmEmojiSize };
- } else if (info->setType == Data::StickersType::Stickers) {
- // May have aspect != 1, so we count it from the thumbnail.
- const auto thumbnail = QSize(
- _thumbnail.location.width(),
- _thumbnail.location.height()
- ).scaled(
- kDefaultWebmStickerLargerSize,
- kDefaultWebmStickerLargerSize,
- Qt::KeepAspectRatio);
- if (!thumbnail.isEmpty()) {
- dimensions = thumbnail;
- }
- }
- }
- // Check sticker size/dimensions properties (for sticker of any type).
- if (type == StickerDocument
- && ((size > Storage::kMaxStickerBytesSize)
- || (!sticker()->isLottie()
- && !GoodStickerDimensions(
- dimensions.width(),
- dimensions.height())))) {
- type = FileDocument;
- _additional = nullptr;
- }
- if (!_filename.isEmpty()) {
- using Type = Core::NameType;
- if (type == VideoDocument
- || type == AnimatedDocument
- || type == RoundVideoDocument
- || isAnimation()) {
- if (!enforceNameType(Type::Video)) {
- type = FileDocument;
- _additional = nullptr;
- }
- }
- if (type == SongDocument || type == VoiceDocument || isAudioFile()) {
- if (!enforceNameType(Type::Audio)) {
- type = FileDocument;
- _additional = nullptr;
- }
- }
- if (!Core::NameTypeAllowsThumbnail(_nameType)) {
- _inlineThumbnailBytes = {};
- _flags &= ~Flag::InlineThumbnailIsPath;
- _thumbnail.clear();
- _videoThumbnail.clear();
- }
- }
- if (isAudioFile()
- || isAnimation()
- || isVoiceMessage()
- || storyMedia()) {
- setMaybeSupportsStreaming(true);
- }
- }
- void DocumentData::setVideoQualities(const QVector<MTPDocument> &list) {
- auto qualities = std::vector<not_null<DocumentData*>>();
- qualities.reserve(list.size());
- for (const auto &document : list) {
- qualities.push_back(owner().processDocument(document));
- }
- setVideoQualities(std::move(qualities));
- }
- void DocumentData::setVideoQualities(
- std::vector<not_null<DocumentData*>> qualities) {
- const auto data = video();
- if (!data) {
- return;
- }
- auto count = int(qualities.size());
- if (qualities.empty()) {
- return;
- }
- const auto good = [&](not_null<DocumentData*> document) {
- return document->isVideoFile()
- && !document->dimensions.isEmpty()
- && !document->inappPlaybackFailed()
- && document->useStreamingLoader()
- && document->canBeStreamed(nullptr);
- };
- ranges::sort(
- qualities,
- ranges::greater(),
- &DocumentData::resolveVideoQuality);
- for (auto i = 0; i != count - 1;) {
- const auto my = qualities[i];
- const auto next = qualities[i + 1];
- const auto myQuality = my->resolveVideoQuality();
- const auto nextQuality = next->resolveVideoQuality();
- const auto myGood = good(my);
- const auto nextGood = good(next);
- if (!myGood || !nextGood || myQuality == nextQuality) {
- const auto removeMe = !myGood
- || (nextGood && (my->size > next->size));
- const auto from = i + (removeMe ? 1 : 2);
- for (auto j = from; j != count; ++j) {
- qualities[j - 1] = qualities[j];
- }
- --count;
- } else {
- ++i;
- }
- }
- if (!qualities[count - 1]->resolveVideoQuality()) {
- --count;
- }
- qualities.erase(qualities.begin() + count, qualities.end());
- if (!qualities.empty()) {
- if (const auto mine = resolveVideoQuality()) {
- if (mine > qualities.front()->resolveVideoQuality()) {
- qualities.insert(begin(qualities), this);
- }
- }
- }
- data->qualities = std::move(qualities);
- }
- int DocumentData::resolveVideoQuality() const {
- const auto size = isVideoFile() ? dimensions : QSize();
- return size.isEmpty() ? 0 : std::min(size.width(), size.height());
- }
- auto DocumentData::resolveQualities(HistoryItem *context) const
- -> const std::vector<not_null<DocumentData*>> & {
- static const auto empty = std::vector<not_null<DocumentData*>>();
- const auto info = video();
- const auto media = context ? context->media() : nullptr;
- if (!info || !media || media->document() != this) {
- return empty;
- }
- return media->hasQualitiesList() ? info->qualities : empty;
- }
- not_null<DocumentData*> DocumentData::chooseQuality(
- HistoryItem *context,
- Media::VideoQuality request) {
- const auto &list = resolveQualities(context);
- if (list.empty() || !request.height) {
- return this;
- }
- const auto height = int(request.height);
- auto closest = this;
- auto closestAbs = std::abs(height - resolveVideoQuality());
- auto closestSize = size;
- for (const auto &quality : list) {
- const auto abs = std::abs(height - quality->resolveVideoQuality());
- if (abs < closestAbs
- || (abs == closestAbs && quality->size < closestSize)) {
- closest = quality;
- closestAbs = abs;
- closestSize = quality->size;
- }
- }
- return closest;
- }
- void DocumentData::validateLottieSticker() {
- if (type == FileDocument
- && hasMimeType(u"application/x-tgsticker"_q)) {
- type = StickerDocument;
- _additional = std::make_unique<StickerData>();
- sticker()->type = StickerType::Tgs;
- dimensions = kLottieStickerDimensions;
- }
- }
- void DocumentData::setDataAndCache(const QByteArray &data) {
- if (const auto media = activeMediaView()) {
- media->setBytes(data);
- }
- if (saveToCache() && data.size() <= Storage::kMaxFileInMemory) {
- owner().cache().put(
- cacheKey(),
- Storage::Cache::Database::TaggedValue(
- base::duplicate(data),
- cacheTag()));
- }
- }
- bool DocumentData::checkWallPaperProperties() {
- if (type == WallPaperDocument) {
- return true;
- }
- if (type != FileDocument
- || !hasThumbnail()
- || dimensions.isEmpty()
- || dimensions.width() > Storage::kMaxWallPaperDimension
- || dimensions.height() > Storage::kMaxWallPaperDimension
- || size > Storage::kMaxWallPaperInMemory) {
- return false;
- }
- type = WallPaperDocument;
- return true;
- }
- void DocumentData::updateThumbnails(
- const InlineImageLocation &inlineThumbnail,
- const ImageWithLocation &thumbnail,
- const ImageWithLocation &videoThumbnail,
- bool isPremiumSticker) {
- if (!_filename.isEmpty()
- && !Core::NameTypeAllowsThumbnail(Core::DetectNameType(_filename))) {
- return;
- }
- if (!inlineThumbnail.bytes.isEmpty()
- && _inlineThumbnailBytes.isEmpty()) {
- _inlineThumbnailBytes = inlineThumbnail.bytes;
- if (inlineThumbnail.isPath) {
- _flags |= Flag::InlineThumbnailIsPath;
- } else {
- _flags &= ~Flag::InlineThumbnailIsPath;
- }
- }
- if (!sticker() || sticker()->setType != Data::StickersType::Emoji) {
- if (isPremiumSticker) {
- _flags |= Flag::PremiumSticker;
- } else {
- _flags &= ~Flag::PremiumSticker;
- }
- }
- Data::UpdateCloudFile(
- _thumbnail,
- thumbnail,
- owner().cache(),
- Data::kImageCacheTag,
- [&](Data::FileOrigin origin) { loadThumbnail(origin); },
- [&](QImage preloaded, QByteArray) {
- if (const auto media = activeMediaView()) {
- media->setThumbnail(std::move(preloaded));
- }
- });
- Data::UpdateCloudFile(
- _videoThumbnail,
- videoThumbnail,
- owner().cache(),
- Data::kAnimationCacheTag,
- [&](Data::FileOrigin origin) { loadVideoThumbnail(origin); });
- }
- bool DocumentData::isWallPaper() const {
- return (type == WallPaperDocument);
- }
- bool DocumentData::isPatternWallPaper() const {
- return isWallPaper()
- && (isPatternWallPaperPNG() || isPatternWallPaperSVG());
- }
- bool DocumentData::isPatternWallPaperPNG() const {
- return isWallPaper() && hasMimeType(u"image/png"_q);
- }
- bool DocumentData::isPatternWallPaperSVG() const {
- return isWallPaper() && hasMimeType(u"application/x-tgwallpattern"_q);
- }
- bool DocumentData::isPremiumSticker() const {
- if (!(_flags & Flag::PremiumSticker)) {
- return false;
- }
- const auto info = sticker();
- return info && info->setType == Data::StickersType::Stickers;
- }
- bool DocumentData::isPremiumEmoji() const {
- if (!(_flags & Flag::PremiumSticker)) {
- return false;
- }
- const auto info = sticker();
- return info && info->setType == Data::StickersType::Emoji;
- }
- bool DocumentData::emojiUsesTextColor() const {
- return (_flags & Flag::UseTextColor);
- }
- void DocumentData::overrideEmojiUsesTextColor(bool value) {
- if (value) {
- _flags |= Flag::UseTextColor;
- } else {
- _flags &= ~Flag::UseTextColor;
- }
- }
- bool DocumentData::hasThumbnail() const {
- return _thumbnail.location.valid()
- && !thumbnailFailed()
- && !(_flags & Flag::PossibleCoverThumbnail);
- }
- bool DocumentData::thumbnailLoading() const {
- return _thumbnail.loader != nullptr;
- }
- bool DocumentData::thumbnailFailed() const {
- return (_thumbnail.flags & Data::CloudFile::Flag::Failed);
- }
- void DocumentData::loadThumbnail(Data::FileOrigin origin) {
- const auto autoLoading = false;
- const auto finalCheck = [=] {
- if (const auto active = activeMediaView()) {
- return !active->thumbnail();
- }
- return true;
- };
- const auto done = [=](QImage result, QByteArray) {
- _flags &= ~Flag::PossibleCoverThumbnail;
- if (const auto active = activeMediaView()) {
- active->setThumbnail(std::move(result));
- }
- };
- Data::LoadCloudFile(
- &session(),
- _thumbnail,
- origin,
- LoadFromCloudOrLocal,
- autoLoading,
- Data::kImageCacheTag,
- finalCheck,
- done);
- }
- const ImageLocation &DocumentData::thumbnailLocation() const {
- return _thumbnail.location;
- }
- int DocumentData::thumbnailByteSize() const {
- return _thumbnail.byteSize;
- }
- bool DocumentData::hasVideoThumbnail() const {
- return _videoThumbnail.location.valid();
- }
- bool DocumentData::videoThumbnailLoading() const {
- return _videoThumbnail.loader != nullptr;
- }
- bool DocumentData::videoThumbnailFailed() const {
- return (_videoThumbnail.flags & Data::CloudFile::Flag::Failed);
- }
- void DocumentData::loadVideoThumbnail(Data::FileOrigin origin) {
- const auto autoLoading = false;
- const auto finalCheck = [=] {
- if (const auto active = activeMediaView()) {
- return active->videoThumbnailContent().isEmpty();
- }
- return true;
- };
- const auto done = [=](QByteArray result) {
- if (const auto active = activeMediaView()) {
- active->setVideoThumbnail(std::move(result));
- }
- };
- Data::LoadCloudFile(
- &session(),
- _videoThumbnail,
- origin,
- LoadFromCloudOrLocal,
- autoLoading,
- Data::kAnimationCacheTag,
- finalCheck,
- done);
- }
- const ImageLocation &DocumentData::videoThumbnailLocation() const {
- return _videoThumbnail.location;
- }
- int DocumentData::videoThumbnailByteSize() const {
- return _videoThumbnail.byteSize;
- }
- Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const {
- return Data::DocumentThumbCacheKey(_dc, id);
- }
- bool DocumentData::goodThumbnailChecked() const {
- return (_goodThumbnailState & GoodThumbnailFlag::Mask)
- == GoodThumbnailFlag::Checked;
- }
- bool DocumentData::goodThumbnailGenerating() const {
- return (_goodThumbnailState & GoodThumbnailFlag::Mask)
- == GoodThumbnailFlag::Generating;
- }
- bool DocumentData::goodThumbnailNoData() const {
- return (_goodThumbnailState & GoodThumbnailFlag::Mask)
- == GoodThumbnailFlag::NoData;
- }
- void DocumentData::setGoodThumbnailGenerating() {
- _goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
- | GoodThumbnailFlag::Generating;
- }
- void DocumentData::setGoodThumbnailDataReady() {
- _goodThumbnailState = GoodThumbnailFlag::DataReady
- | (goodThumbnailNoData()
- ? GoodThumbnailFlag(0)
- : (_goodThumbnailState & GoodThumbnailFlag::Mask));
- }
- void DocumentData::setGoodThumbnailChecked(bool hasData) {
- if (!hasData && (_goodThumbnailState & GoodThumbnailFlag::DataReady)) {
- _goodThumbnailState &= ~GoodThumbnailFlag::DataReady;
- _goodThumbnailState &= ~GoodThumbnailFlag::Mask;
- Data::DocumentMedia::CheckGoodThumbnail(this);
- return;
- }
- _goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
- | (hasData
- ? GoodThumbnailFlag::Checked
- : GoodThumbnailFlag::NoData);
- }
- std::shared_ptr<Data::DocumentMedia> DocumentData::createMediaView() {
- if (auto result = activeMediaView()) {
- return result;
- }
- auto result = std::make_shared<Data::DocumentMedia>(this);
- _media = result;
- return result;
- }
- std::shared_ptr<Data::DocumentMedia> DocumentData::activeMediaView() const {
- return _media.lock();
- }
- void DocumentData::setGoodThumbnailPhoto(not_null<PhotoData*> photo) {
- _goodThumbnailPhoto = photo;
- }
- PhotoData *DocumentData::goodThumbnailPhoto() const {
- return _goodThumbnailPhoto;
- }
- Storage::Cache::Key DocumentData::bigFileBaseCacheKey() const {
- return hasRemoteLocation()
- ? StorageFileLocation(
- _dc,
- session().userId(),
- MTP_inputDocumentFileLocation(
- MTP_long(id),
- MTP_long(_access),
- MTP_bytes(_fileReference),
- MTP_string())).bigFileBaseCacheKey()
- : Storage::Cache::Key();
- }
- void DocumentData::forceToCache(bool force) {
- if (force) {
- _flags |= Flag::ForceToCache;
- } else {
- _flags &= ~Flag::ForceToCache;
- }
- }
- bool DocumentData::saveToCache() const {
- return (size < Storage::kMaxFileInMemory)
- && ((type == StickerDocument)
- || (_flags & Flag::ForceToCache)
- || isAnimation()
- || isVoiceMessage()
- || isWallPaper()
- || isTheme()
- || (hasMimeType(u"image/png"_q)
- && _filename.startsWith("image_")));
- }
- void DocumentData::automaticLoadSettingsChanged() {
- if (!cancelled() || status != FileReady) {
- return;
- }
- _loader = nullptr;
- resetCancelled();
- }
- void DocumentData::finishLoad() {
- // NB! _loader may be in ~FileLoader() already.
- const auto guard = gsl::finally([&] {
- destroyLoader();
- });
- if (!_loader || _loader->cancelled()) {
- _flags |= Flag::DownloadCancelled;
- return;
- }
- setLocation(Core::FileLocation(_loader->fileName()));
- setGoodThumbnailDataReady();
- if (const auto media = activeMediaView()) {
- media->setBytes(_loader->bytes());
- media->checkStickerLarge(_loader.get());
- }
- }
- void DocumentData::destroyLoader() {
- if (!_loader) {
- return;
- }
- const auto loader = base::take(_loader);
- if (cancelled()) {
- loader->cancel();
- }
- }
- bool DocumentData::loading() const {
- return (_loader != nullptr);
- }
- QString DocumentData::loadingFilePath() const {
- return loading() ? _loader->fileName() : QString();
- }
- bool DocumentData::displayLoading() const {
- return loading()
- ? !_loader->loadingLocal()
- : (uploading() && !waitingForAlbum());
- }
- float64 DocumentData::progress() const {
- if (uploading()) {
- if (uploadingData->size > 0) {
- const auto result = float64(uploadingData->offset)
- / float64(uploadingData->size);
- return std::clamp(result, 0., 1.);
- }
- return 0.;
- }
- return loading() ? _loader->currentProgress() : 0.;
- }
- int64 DocumentData::loadOffset() const {
- return loading() ? _loader->currentOffset() : 0;
- }
- bool DocumentData::uploading() const {
- return (uploadingData != nullptr);
- }
- bool DocumentData::loadedInMediaCache() const {
- return (_flags & Flag::LoadedInMediaCache);
- }
- void DocumentData::setLoadedInMediaCache(bool loaded) {
- const auto flags = loaded
- ? (_flags | Flag::LoadedInMediaCache)
- : (_flags & ~Flag::LoadedInMediaCache);
- if (_flags == flags) {
- return;
- }
- _flags = flags;
- if (filepath().isEmpty()) {
- if (loadedInMediaCache()) {
- session().local().writeFileLocation(
- mediaKey(),
- Core::FileLocation::InMediaCacheLocation());
- } else {
- session().local().removeFileLocation(mediaKey());
- }
- owner().requestDocumentViewRepaint(this);
- }
- }
- ChatRestriction DocumentData::requiredSendRight() const {
- return isVideoFile()
- ? ChatRestriction::SendVideos
- : isSong()
- ? ChatRestriction::SendMusic
- : isVoiceMessage()
- ? ChatRestriction::SendVoiceMessages
- : isVideoMessage()
- ? ChatRestriction::SendVideoMessages
- : sticker()
- ? ChatRestriction::SendStickers
- : isAnimation()
- ? ChatRestriction::SendGifs
- : ChatRestriction::SendFiles;
- }
- void DocumentData::setFileName(const QString &remoteFileName) {
- _filename = remoteFileName;
- // We don't want LTR/RTL mark/embedding/override/isolate chars
- // in filenames, because they introduce a security issue, when
- // an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg".
- QChar controls[] = {
- QChar(0x200E), // LTR Mark
- QChar(0x200F), // RTL Mark
- QChar(0x202A), // LTR Embedding
- QChar(0x202B), // RTL Embedding
- QChar(0x202D), // LTR Override
- QChar(0x202E), // RTL Override
- QChar(0x2066), // LTR Isolate
- QChar(0x2067), // RTL Isolate
- };
- for (const auto &ch : controls) {
- _filename = std::move(_filename).replace(ch, "_");
- }
- _nameType = Core::DetectNameType(_filename);
- }
- bool DocumentData::enforceNameType(Core::NameType nameType) {
- if (_nameType == nameType) {
- return true;
- }
- const auto base = _filename.isEmpty() ? u"file"_q : _filename;
- const auto mime = Core::MimeTypeForName(mimeString());
- const auto patterns = mime.globPatterns();
- for (const auto &pattern : mime.globPatterns()) {
- const auto now = base + QString(pattern).replace('*', QString());
- if (Core::DetectNameType(now) == nameType) {
- _filename = now;
- _nameType = nameType;
- return true;
- }
- }
- return false;
- }
- void DocumentData::setLoadedInMediaCacheLocation() {
- _location = Core::FileLocation();
- _flags |= Flag::LoadedInMediaCache;
- }
- void DocumentData::setWaitingForAlbum() {
- if (uploading()) {
- uploadingData->waitingForAlbum = true;
- }
- }
- bool DocumentData::waitingForAlbum() const {
- return uploading() && uploadingData->waitingForAlbum;
- }
- void DocumentData::save(
- Data::FileOrigin origin,
- const QString &toFile,
- LoadFromCloudSetting fromCloud,
- bool autoLoading) {
- if (const auto media = activeMediaView(); media && media->loaded(true)) {
- auto &l = location(true);
- if (!toFile.isEmpty()) {
- if (!media->bytes().isEmpty()) {
- QFile f(toFile);
- f.open(QIODevice::WriteOnly);
- f.write(media->bytes());
- f.close();
- setLocation(Core::FileLocation(toFile));
- session().local().writeFileLocation(
- mediaKey(),
- Core::FileLocation(toFile));
- } else if (l.accessEnable()) {
- const auto &alreadyName = l.name();
- if (alreadyName != toFile) {
- QFile(toFile).remove();
- QFile(alreadyName).copy(toFile);
- }
- l.accessDisable();
- }
- }
- return;
- }
- if (_loader) {
- if (!_loader->setFileName(toFile)) {
- cancel();
- }
- }
- resetCancelled();
- if (_loader) {
- if (fromCloud == LoadFromCloudOrLocal) {
- _loader->permitLoadFromCloud();
- }
- } else {
- status = FileReady;
- auto reader = owner().streaming().sharedReader(this, origin, true);
- if (reader) {
- _loader = std::make_unique<Storage::StreamedFileDownloader>(
- &session(),
- id,
- _dc,
- origin,
- Data::DocumentCacheKey(_dc, id),
- mediaKey(),
- std::move(reader),
- toFile,
- size,
- locationType(),
- (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly),
- fromCloud,
- autoLoading,
- cacheTag());
- } else if (hasWebLocation()) {
- _loader = std::make_unique<mtpFileLoader>(
- &session(),
- _urlLocation,
- size,
- size,
- fromCloud,
- autoLoading,
- cacheTag());
- } else if (!_access && !_url.isEmpty()) {
- _loader = std::make_unique<webFileLoader>(
- &session(),
- _url,
- toFile,
- fromCloud,
- autoLoading,
- cacheTag());
- } else {
- _loader = std::make_unique<mtpFileLoader>(
- &session(),
- StorageFileLocation(
- _dc,
- session().userId(),
- MTP_inputDocumentFileLocation(
- MTP_long(id),
- MTP_long(_access),
- MTP_bytes(_fileReference),
- MTP_string())),
- origin,
- locationType(),
- toFile,
- size,
- size,
- (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly),
- fromCloud,
- autoLoading,
- cacheTag());
- }
- handleLoaderUpdates();
- }
- if (loading()) {
- _loader->start();
- }
- // This affects a display of tooltips.
- // _owner->notifyDocumentLayoutChanged(this);
- }
- void DocumentData::handleLoaderUpdates() {
- _loader->updates(
- ) | rpl::start_with_next_error_done([=] {
- _owner->documentLoadProgress(this);
- }, [=](FileLoader::Error error) {
- using FailureReason = FileLoader::FailureReason;
- if (error.started && _loader) {
- const auto origin = _loader->fileOrigin();
- const auto failedFileName = _loader->fileName();
- const auto retry = [=] {
- Ui::hideLayer();
- save(origin, failedFileName);
- };
- Ui::show(Ui::MakeConfirmBox({
- tr::lng_download_finish_failed(),
- crl::guard(&session(), retry)
- }));
- } else if (error.failureReason == FailureReason::FileWriteFailure) {
- if (!Core::App().settings().downloadPath().isEmpty()) {
- Core::App().settings().setDownloadPathBookmark(QByteArray());
- Core::App().settings().setDownloadPath(QString());
- Core::App().saveSettingsDelayed();
- InvokeQueued(qApp, [] {
- Ui::show(
- Ui::MakeInformBox(
- tr::lng_download_path_failed(tr::now)));
- });
- }
- }
- finishLoad();
- status = FileDownloadFailed;
- _owner->documentLoadFail(this, error.started);
- }, [=] {
- finishLoad();
- _owner->documentLoadDone(this);
- }, _loader->lifetime());
- }
- void DocumentData::cancel() {
- if (!loading()) {
- return;
- }
- _flags |= Flag::DownloadCancelled;
- destroyLoader();
- _owner->documentLoadDone(this);
- }
- bool DocumentData::cancelled() const {
- return (_flags & Flag::DownloadCancelled);
- }
- void DocumentData::resetCancelled() {
- _flags &= ~Flag::DownloadCancelled;
- }
- VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
- auto bitsCount = static_cast<int>(encoded5bit.size() * 8);
- auto valuesCount = bitsCount / 5;
- if (!valuesCount) {
- return VoiceWaveform();
- }
- // Read each 5 bit of encoded5bit as 0-31 unsigned char.
- // We count the index of the byte in which the desired 5-bit sequence starts.
- // And then we read a uint16 starting from that byte to guarantee to get all of those 5 bits.
- //
- // BUT! if it is the last byte we have, we're not allowed to read a uint16 starting with it.
- // Because it will be an overflow (we'll access one byte after the available memory).
- // We see, that only the last 5 bits could start in the last available byte and be problematic.
- // So we read in a general way all the entries in a general way except the last one.
- auto result = VoiceWaveform(valuesCount, 0);
- auto bitsData = encoded5bit.constData();
- for (auto i = 0, l = valuesCount - 1; i != l; ++i) {
- auto byteIndex = (i * 5) / 8;
- auto bitShift = (i * 5) % 8;
- auto value = *reinterpret_cast<const uint16*>(bitsData + byteIndex);
- result[i] = static_cast<char>((value >> bitShift) & 0x1F);
- }
- auto lastByteIndex = ((valuesCount - 1) * 5) / 8;
- auto lastBitShift = ((valuesCount - 1) * 5) % 8;
- auto lastValue = (lastByteIndex == encoded5bit.size() - 1)
- ? static_cast<uint16>(*reinterpret_cast<const uchar*>(bitsData + lastByteIndex))
- : *reinterpret_cast<const uint16*>(bitsData + lastByteIndex);
- result[valuesCount - 1] = static_cast<char>((lastValue >> lastBitShift) & 0x1F);
- return result;
- }
- QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) {
- auto bitsCount = waveform.size() * 5;
- auto bytesCount = (bitsCount + 7) / 8;
- auto result = QByteArray(bytesCount + 1, 0);
- auto bitsData = result.data();
- // Write each 0-31 unsigned char as 5 bit to result.
- // We reserve one extra byte to be able to dereference any of required bytes
- // as a uint16 without overflowing, even the byte with index "bytesCount - 1".
- for (auto i = 0, l = int(waveform.size()); i < l; ++i) {
- auto byteIndex = (i * 5) / 8;
- auto bitShift = (i * 5) % 8;
- auto value = (static_cast<uint16>(waveform[i]) & 0x1F) << bitShift;
- *reinterpret_cast<uint16*>(bitsData + byteIndex) |= value;
- }
- result.resize(bytesCount);
- return result;
- }
- const Core::FileLocation &DocumentData::location(bool check) const {
- if (check && !_location.check()) {
- const auto location = session().local().readFileLocation(mediaKey());
- const auto that = const_cast<DocumentData*>(this);
- if (location.inMediaCache()) {
- that->setLoadedInMediaCacheLocation();
- } else {
- that->_location = location;
- }
- }
- return _location;
- }
- void DocumentData::setLocation(const Core::FileLocation &loc) {
- if (loc.inMediaCache()) {
- setLoadedInMediaCacheLocation();
- } else if (loc.check()) {
- _location = loc;
- }
- }
- QString DocumentData::filepath(bool check) const {
- return (check && _location.name().isEmpty())
- ? QString()
- : location(check).name();
- }
- bool DocumentData::saveFromData() {
- return !filepath(true).isEmpty() || saveFromDataChecked();
- }
- bool DocumentData::saveFromDataSilent() {
- return !filepath(true).isEmpty()
- || (Core::App().canSaveFileWithoutAskingForPath()
- && saveFromDataChecked());
- }
- bool DocumentData::saveFromDataChecked() {
- const auto media = activeMediaView();
- if (!media) {
- return false;
- }
- const auto bytes = media->bytes();
- if (bytes.isEmpty()) {
- return false;
- }
- const auto path = DocumentFileNameForSave(this);
- if (path.isEmpty()) {
- return false;
- }
- auto file = QFile(path);
- if (!file.open(QIODevice::WriteOnly)
- || file.write(bytes) != bytes.size()) {
- return false;
- }
- file.close();
- _location = Core::FileLocation(path);
- session().local().writeFileLocation(mediaKey(), _location);
- return true;
- }
- void DocumentData::refreshPossibleCoverThumbnail() {
- Expects(isSong());
- if (_thumbnail.location.valid()) {
- return;
- }
- const auto songData = song();
- if (songData->performer.isEmpty()
- || songData->title.isEmpty()
- // Ignore cover for voice chat records.
- || hasMimeType(u"audio/ogg"_q)) {
- return;
- }
- const auto size = kDefaultCoverThumbnailSize;
- const auto location = ImageWithLocation{
- .location = ImageLocation(
- { AudioAlbumThumbLocation{ id } },
- size,
- size)
- };
- _flags |= Flag::PossibleCoverThumbnail;
- updateThumbnails({}, location, {}, false);
- loadThumbnail({});
- }
- bool DocumentData::isStickerSetInstalled() const {
- Expects(sticker() != nullptr);
- using SetFlag = Data::StickersSetFlag;
- const auto &sets = _owner->stickers().sets();
- if (const auto id = sticker()->set.id) {
- const auto i = sets.find(id);
- return (i != sets.cend())
- && !(i->second->flags & SetFlag::Archived)
- && (i->second->flags & SetFlag::Installed);
- } else {
- return false;
- }
- }
- Image *DocumentData::getReplyPreview(
- Data::FileOrigin origin,
- not_null<PeerData*> context,
- bool spoiler) {
- if (!hasThumbnail()) {
- return nullptr;
- } else if (!_replyPreview) {
- _replyPreview = std::make_unique<Data::ReplyPreview>(this);
- }
- return _replyPreview->image(origin, context, spoiler);
- }
- Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
- const auto media = item->media();
- const auto spoiler = media && media->hasSpoiler();
- return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
- }
- bool DocumentData::replyPreviewLoaded(bool spoiler) const {
- if (!hasThumbnail()) {
- return true;
- } else if (!_replyPreview) {
- return false;
- }
- return _replyPreview->loaded(spoiler);
- }
- StickerData *DocumentData::sticker() const {
- return (type == StickerDocument)
- ? static_cast<StickerData*>(_additional.get())
- : nullptr;
- }
- Data::FileOrigin DocumentData::stickerSetOrigin() const {
- if (const auto data = sticker()) {
- if (const auto result = data->setOrigin()) {
- return result;
- } else if (owner().stickers().isFaved(this)) {
- return Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0);
- }
- }
- return Data::FileOrigin();
- }
- Data::FileOrigin DocumentData::stickerOrGifOrigin() const {
- return (sticker()
- ? stickerSetOrigin()
- : isGifv()
- ? Data::FileOriginSavedGifs()
- : Data::FileOrigin());
- }
- SongData *DocumentData::song() {
- return isSong()
- ? static_cast<SongData*>(_additional.get())
- : nullptr;
- }
- const SongData *DocumentData::song() const {
- return const_cast<DocumentData*>(this)->song();
- }
- VoiceData *DocumentData::voice() {
- return isVoiceMessage()
- ? static_cast<VoiceData*>(_additional.get())
- : nullptr;
- }
- const VoiceData *DocumentData::voice() const {
- return const_cast<DocumentData*>(this)->voice();
- }
- RoundData *DocumentData::round() {
- return isVideoMessage()
- ? static_cast<RoundData*>(_additional.get())
- : nullptr;
- }
- const RoundData *DocumentData::round() const {
- return const_cast<DocumentData*>(this)->round();
- }
- VideoData *DocumentData::video() {
- return isVideoFile()
- ? static_cast<VideoData*>(_additional.get())
- : nullptr;
- }
- const VideoData *DocumentData::video() const {
- return const_cast<DocumentData*>(this)->video();
- }
- bool DocumentData::hasRemoteLocation() const {
- return (_dc != 0 && _access != 0);
- }
- bool DocumentData::useStreamingLoader() const {
- if (size <= 0) {
- return false;
- } else if (const auto info = sticker()) {
- return info->isWebm();
- }
- return isAnimation()
- || isVideoFile()
- || isAudioFile()
- || isVoiceMessage();
- }
- bool DocumentData::canBeStreamed(HistoryItem *item) const {
- // Streaming couldn't be used with external player
- // Maybe someone brave will implement this once upon a time...
- static const auto &ExternalVideoPlayer = base::options::lookup<bool>(
- Data::kOptionExternalVideoPlayer);
- return hasRemoteLocation()
- && supportsStreaming()
- && (!isVideoFile()
- || storyMedia()
- || !ExternalVideoPlayer.value()
- || (item && !item->allowsForward()));
- }
- void DocumentData::setInappPlaybackFailed() {
- _flags |= Flag::StreamingPlaybackFailed;
- }
- bool DocumentData::inappPlaybackFailed() const {
- return (_flags & Flag::StreamingPlaybackFailed);
- }
- int DocumentData::videoPreloadPrefix() const {
- return _videoPreloadPrefix;
- }
- StorageFileLocation DocumentData::videoPreloadLocation() const {
- return hasRemoteLocation()
- ? StorageFileLocation(
- _dc,
- session().userId(),
- MTP_inputDocumentFileLocation(
- MTP_long(id),
- MTP_long(_access),
- MTP_bytes(_fileReference),
- MTP_string()))
- : StorageFileLocation();
- }
- auto DocumentData::createStreamingLoader(
- Data::FileOrigin origin,
- bool forceRemoteLoader) const
- -> std::unique_ptr<Media::Streaming::Loader> {
- if (!useStreamingLoader()) {
- return nullptr;
- }
- if (!forceRemoteLoader) {
- const auto media = activeMediaView();
- const auto &location = this->location(true);
- if (media && !media->bytes().isEmpty()) {
- return Media::Streaming::MakeBytesLoader(media->bytes());
- } else if (!location.isEmpty() && location.accessEnable()) {
- auto result = Media::Streaming::MakeFileLoader(location.name());
- location.accessDisable();
- return result;
- }
- }
- return hasRemoteLocation()
- ? std::make_unique<Media::Streaming::LoaderMtproto>(
- &session().downloader(),
- StorageFileLocation(
- _dc,
- session().userId(),
- MTP_inputDocumentFileLocation(
- MTP_long(id),
- MTP_long(_access),
- MTP_bytes(_fileReference),
- MTP_string())),
- size,
- origin)
- : nullptr;
- }
- bool DocumentData::hasWebLocation() const {
- return !_urlLocation.url().isEmpty();
- }
- bool DocumentData::isNull() const {
- return !hasRemoteLocation()
- && !hasWebLocation()
- && _url.isEmpty()
- && !uploading()
- && _location.isEmpty();
- }
- MTPInputDocument DocumentData::mtpInput() const {
- if (_access) {
- return MTP_inputDocument(
- MTP_long(id),
- MTP_long(_access),
- MTP_bytes(_fileReference));
- }
- return MTP_inputDocumentEmpty();
- }
- QByteArray DocumentData::fileReference() const {
- return _fileReference;
- }
- void DocumentData::refreshFileReference(const QByteArray &value) {
- _fileReference = value;
- _thumbnail.location.refreshFileReference(value);
- _videoThumbnail.location.refreshFileReference(value);
- }
- QString DocumentData::filename() const {
- return _filename;
- }
- Core::NameType DocumentData::nameType() const {
- return _nameType;
- }
- QString DocumentData::mimeString() const {
- return _mimeString;
- }
- bool DocumentData::hasMimeType(const QString &mime) const {
- return (_mimeString == mime);
- }
- void DocumentData::setMimeString(const QString &mime) {
- _mimeString = mime;
- _mimeString = std::move(_mimeString).toLower();
- }
- MediaKey DocumentData::mediaKey() const {
- return ::mediaKey(locationType(), _dc, id);
- }
- Storage::Cache::Key DocumentData::cacheKey() const {
- if (hasWebLocation()) {
- return Data::WebDocumentCacheKey(_urlLocation);
- } else if (!_access && !_url.isEmpty()) {
- return Data::UrlCacheKey(_url);
- } else {
- return Data::DocumentCacheKey(_dc, id);
- }
- }
- uint8 DocumentData::cacheTag() const {
- if (type == StickerDocument) {
- return Data::kStickerCacheTag;
- } else if (isVoiceMessage()) {
- return Data::kVoiceMessageCacheTag;
- } else if (isVideoMessage()) {
- return Data::kVideoMessageCacheTag;
- } else if (isAnimation()) {
- return Data::kAnimationCacheTag;
- } else if (isWallPaper()) {
- return Data::kImageCacheTag;
- }
- return 0;
- }
- LocationType DocumentData::locationType() const {
- return isVoiceMessage()
- ? AudioFileLocation
- : isVideoFile()
- ? VideoFileLocation
- : DocumentFileLocation;
- }
- void DocumentData::forceIsStreamedAnimation() {
- type = AnimatedDocument;
- _additional = nullptr;
- setMaybeSupportsStreaming(true);
- }
- bool DocumentData::isVoiceMessage() const {
- return (type == VoiceDocument);
- }
- bool DocumentData::isVideoMessage() const {
- return (type == RoundVideoDocument);
- }
- bool DocumentData::isAnimation() const {
- return (type == AnimatedDocument)
- || isVideoMessage()
- || ((_filename.isEmpty()
- || _nameType == Core::NameType::Image
- || _nameType == Core::NameType::Video)
- && hasMimeType(u"image/gif"_q)
- && !(_flags & Flag::StreamingPlaybackFailed));
- }
- bool DocumentData::isGifv() const {
- return (type == AnimatedDocument)
- && hasMimeType(u"video/mp4"_q);
- }
- bool DocumentData::isTheme() const {
- return _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive)
- || _filename.endsWith(u".tdesktop-palette"_q, Qt::CaseInsensitive)
- || (hasMimeType(u"application/x-tgtheme-tdesktop"_q)
- && (_filename.isEmpty()
- || _nameType == Core::NameType::ThemeFile));
- }
- bool DocumentData::isSong() const {
- return (type == SongDocument);
- }
- bool DocumentData::isSongWithCover() const {
- return isSong() && hasThumbnail();
- }
- bool DocumentData::isAudioFile() const {
- if (isVoiceMessage() || isVideoFile()) {
- return false;
- } else if (isSong()) {
- return true;
- }
- const auto prefix = u"audio/"_q;
- if (!_mimeString.startsWith(prefix, Qt::CaseInsensitive)) {
- if (_filename.endsWith(u".opus"_q, Qt::CaseInsensitive)) {
- return true;
- }
- return false;
- } else if (!_filename.isEmpty()
- && _nameType != Core::NameType::Audio
- && _nameType != Core::NameType::Video) {
- return false;
- }
- const auto left = _mimeString.mid(prefix.size());
- const auto types = { u"x-wav"_q, u"wav"_q, u"mp4"_q };
- return ranges::contains(types, left);
- }
- bool DocumentData::isSharedMediaMusic() const {
- return isSong();
- }
- bool DocumentData::isVideoFile() const {
- return (type == VideoDocument);
- }
- bool DocumentData::isSilentVideo() const {
- return _flags & Flag::SilentVideo;
- }
- crl::time DocumentData::duration() const {
- return std::max(_duration, crl::time());
- }
- bool DocumentData::hasDuration() const {
- return _duration >= 0;
- }
- bool DocumentData::isImage() const {
- return (_flags & Flag::ImageType);
- }
- bool DocumentData::hasAttachedStickers() const {
- return (_flags & Flag::HasAttachedStickers);
- }
- bool DocumentData::supportsStreaming() const {
- return (_flags & kStreamingSupportedMask) == kStreamingSupportedMaybeYes;
- }
- void DocumentData::setNotSupportsStreaming() {
- _flags &= ~kStreamingSupportedMask;
- _flags |= kStreamingSupportedNo;
- }
- void DocumentData::setMaybeSupportsStreaming(bool supports) {
- if ((_flags & kStreamingSupportedMask) == kStreamingSupportedNo) {
- return;
- }
- _flags &= ~kStreamingSupportedMask;
- _flags |= supports
- ? kStreamingSupportedMaybeYes
- : kStreamingSupportedMaybeNo;
- }
- void DocumentData::recountIsImage() {
- const auto isImage = !isAnimation()
- && !isVideoFile()
- && Core::FileIsImage(filename(), mimeString());
- if (isImage) {
- _flags |= Flag::ImageType;
- } else {
- _flags &= ~Flag::ImageType;
- }
- }
- void DocumentData::setRemoteLocation(
- int32 dc,
- uint64 access,
- const QByteArray &fileReference) {
- _fileReference = fileReference;
- if (_dc != dc || _access != access) {
- _dc = dc;
- _access = access;
- if (!isNull()) {
- if (_location.check()) {
- session().local().writeFileLocation(mediaKey(), _location);
- } else {
- _location = session().local().readFileLocation(mediaKey());
- if (_location.inMediaCache()) {
- setLoadedInMediaCacheLocation();
- } else if (_location.isEmpty() && loadedInMediaCache()) {
- session().local().writeFileLocation(
- mediaKey(),
- Core::FileLocation::InMediaCacheLocation());
- }
- }
- }
- }
- }
- void DocumentData::setStoryMedia(bool value) {
- if (value) {
- _flags |= Flag::StoryDocument;
- setMaybeSupportsStreaming(true);
- } else {
- _flags &= ~Flag::StoryDocument;
- }
- }
- bool DocumentData::storyMedia() const {
- return (_flags & Flag::StoryDocument);
- }
- void DocumentData::setContentUrl(const QString &url) {
- _url = url;
- }
- void DocumentData::setWebLocation(const WebFileLocation &location) {
- _urlLocation = location;
- }
- void DocumentData::collectLocalData(not_null<DocumentData*> local) {
- if (local == this) {
- return;
- }
- _owner->cache().copyIfEmpty(local->cacheKey(), cacheKey());
- if (const auto localMedia = local->activeMediaView()) {
- auto media = createMediaView();
- media->collectLocalData(localMedia.get());
- _owner->keepAlive(std::move(media));
- }
- if (!local->_location.inMediaCache() && !local->_location.isEmpty()) {
- _location = local->_location;
- session().local().writeFileLocation(mediaKey(), _location);
- }
- }
- PhotoData *LookupVideoCover(
- not_null<DocumentData*> document,
- HistoryItem *item) {
- const auto media = item ? item->media() : nullptr;
- if (const auto webpage = media ? media->webpage() : nullptr) {
- if (webpage->document == document && webpage->photoIsVideoCover) {
- return webpage->photo;
- }
- return nullptr;
- }
- return (media && media->document() == document)
- ? media->videoCover()
- : nullptr;
- }
|