data_document.cpp 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/data_document.h"
  8. #include "data/data_document_resolver.h"
  9. #include "data/data_session.h"
  10. #include "data/data_streaming.h"
  11. #include "data/data_document_media.h"
  12. #include "data/data_reply_preview.h"
  13. #include "data/data_web_page.h"
  14. #include "lang/lang_keys.h"
  15. #include "inline_bots/inline_bot_layout_item.h"
  16. #include "main/main_session.h"
  17. #include "mainwidget.h"
  18. #include "core/file_utilities.h"
  19. #include "core/mime_type.h"
  20. #include "data/stickers/data_stickers.h"
  21. #include "media/audio/media_audio.h"
  22. #include "media/player/media_player_instance.h"
  23. #include "media/streaming/media_streaming_loader_mtproto.h"
  24. #include "media/streaming/media_streaming_loader_local.h"
  25. #include "storage/localstorage.h"
  26. #include "storage/storage_account.h"
  27. #include "storage/streamed_file_downloader.h"
  28. #include "storage/file_download_mtproto.h"
  29. #include "storage/file_download_web.h"
  30. #include "base/options.h"
  31. #include "history/history.h"
  32. #include "history/history_item.h"
  33. #include "history/view/media/history_view_gif.h"
  34. #include "window/window_session_controller.h"
  35. #include "ui/boxes/confirm_box.h"
  36. #include "base/base_file_utilities.h"
  37. #include "mainwindow.h"
  38. #include "core/application.h"
  39. #include "lottie/lottie_animation.h"
  40. #include "boxes/abstract_box.h" // Ui::hideLayer().
  41. #include <QtCore/QBuffer>
  42. #include <QtCore/QMimeType>
  43. #include <QtCore/QMimeDatabase>
  44. namespace {
  45. constexpr auto kDefaultCoverThumbnailSize = 100;
  46. constexpr auto kMaxAllowedPreloadPrefix = 6 * 1024 * 1024;
  47. constexpr auto kDefaultWebmEmojiSize = 100;
  48. constexpr auto kDefaultWebmStickerLargerSize = kStickerSideSize;
  49. const auto kLottieStickerDimensions = QSize(
  50. kStickerSideSize,
  51. kStickerSideSize);
  52. QString JoinStringList(const QStringList &list, const QString &separator) {
  53. const auto count = list.size();
  54. if (!count) {
  55. return QString();
  56. }
  57. auto result = QString();
  58. auto fullsize = separator.size() * (count - 1);
  59. for (const auto &string : list) {
  60. fullsize += string.size();
  61. }
  62. result.reserve(fullsize);
  63. result.append(list[0]);
  64. for (auto i = 1; i != count; ++i) {
  65. result.append(separator).append(list[i]);
  66. }
  67. return result;
  68. }
  69. void UpdateStickerSetIdentifier(
  70. StickerSetIdentifier &now,
  71. const MTPInputStickerSet &from) {
  72. now = from.match([&](const MTPDinputStickerSetID &data) {
  73. return StickerSetIdentifier{
  74. .id = data.vid().v,
  75. .accessHash = data.vaccess_hash().v,
  76. };
  77. }, [](const auto &) {
  78. return StickerSetIdentifier();
  79. });
  80. }
  81. } // namespace
  82. QString FileNameUnsafe(
  83. not_null<Main::Session*> session,
  84. const QString &title,
  85. const QString &filter,
  86. const QString &prefix,
  87. QString name,
  88. bool savingAs,
  89. const QDir &dir) {
  90. name = base::FileNameFromUserString(name);
  91. if (Core::App().settings().askDownloadPath() || savingAs) {
  92. if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) {
  93. name = filedialogDefaultName(prefix, name);
  94. } else if (dir.path() != u"."_q) {
  95. QString path = dir.absolutePath();
  96. if (path != cDialogLastPath()) {
  97. cSetDialogLastPath(path);
  98. Local::writeSettings();
  99. }
  100. }
  101. // check if extension of filename is present in filter
  102. // it should be in first filter section on the first place
  103. // place it there, if it is not
  104. QString ext = QFileInfo(name).suffix(), fil = filter, sep = u";;"_q;
  105. if (!ext.isEmpty()) {
  106. if (QRegularExpression(u"^[a-zA-Z_0-9]+$"_q).match(ext).hasMatch()) {
  107. QStringList filters = filter.split(sep);
  108. if (filters.size() > 1) {
  109. const auto &first = filters.at(0);
  110. int32 start = first.indexOf(u"(*."_q);
  111. if (start >= 0) {
  112. if (!QRegularExpression(u"\\(\\*\\."_q + ext + u"[\\)\\s]"_q, QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) {
  113. QRegularExpressionMatch m = QRegularExpression(u" \\*\\."_q + ext + u"[\\)\\s]"_q, QRegularExpression::CaseInsensitiveOption).match(first);
  114. if (m.hasMatch() && m.capturedStart() > start + 3) {
  115. int32 oldpos = m.capturedStart(), oldend = m.capturedEnd();
  116. 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);
  117. } else {
  118. fil = first.mid(0, start + 3) + ext + u" *."_q + first.mid(start + 3) + sep + JoinStringList(filters.mid(1), sep);
  119. }
  120. }
  121. } else {
  122. fil = QString();
  123. }
  124. } else {
  125. fil = QString();
  126. }
  127. } else {
  128. fil = QString();
  129. }
  130. }
  131. return filedialogGetSaveFile(name, title, fil, name) ? name : QString();
  132. }
  133. auto path = [&] {
  134. const auto path = Core::App().settings().downloadPath();
  135. if (path.isEmpty()) {
  136. return File::DefaultDownloadPath(session);
  137. } else if (path == FileDialog::Tmp()) {
  138. return session->local().tempDirectory();
  139. } else {
  140. return path;
  141. }
  142. }();
  143. if (path.isEmpty()) return QString();
  144. if (name.isEmpty()) name = u".unknown"_q;
  145. if (name.at(0) == QChar::fromLatin1('.')) {
  146. if (!QDir().exists(path)) QDir().mkpath(path);
  147. return filedialogDefaultName(prefix, name, path);
  148. }
  149. if (dir.path() != u"."_q) {
  150. path = dir.absolutePath() + '/';
  151. }
  152. QString nameStart, extension;
  153. int32 extPos = name.lastIndexOf('.');
  154. if (extPos >= 0) {
  155. nameStart = name.mid(0, extPos);
  156. extension = name.mid(extPos);
  157. } else {
  158. nameStart = name;
  159. }
  160. QString nameBase = path + nameStart;
  161. name = nameBase + extension;
  162. for (int i = 0; QFileInfo::exists(name); ++i) {
  163. name = nameBase + u" (%1)"_q.arg(i + 2) + extension;
  164. }
  165. if (!QDir().exists(path)) QDir().mkpath(path);
  166. return name;
  167. }
  168. QString FileNameForSave(
  169. not_null<Main::Session*> session,
  170. const QString &title,
  171. const QString &filter,
  172. const QString &prefix,
  173. QString name,
  174. bool savingAs,
  175. const QDir &dir) {
  176. const auto result = FileNameUnsafe(
  177. session,
  178. title,
  179. filter,
  180. prefix,
  181. name,
  182. savingAs,
  183. dir);
  184. #ifdef Q_OS_WIN
  185. const auto lower = result.trimmed().toLower();
  186. const auto kBadExtensions = { u".lnk"_q, u".scf"_q };
  187. const auto kMaskExtension = u".download"_q;
  188. for (const auto extension : kBadExtensions) {
  189. if (lower.endsWith(extension)) {
  190. return result + kMaskExtension;
  191. }
  192. }
  193. #endif // Q_OS_WIN
  194. return result;
  195. }
  196. QString DocumentFileNameForSave(
  197. not_null<const DocumentData*> data,
  198. bool forceSavingAs,
  199. const QString &already,
  200. const QDir &dir) {
  201. auto alreadySavingFilename = data->loadingFilePath();
  202. if (!alreadySavingFilename.isEmpty()) {
  203. return alreadySavingFilename;
  204. }
  205. QString name, filter, caption, prefix;
  206. const auto mimeType = Core::MimeTypeForName(data->mimeString());
  207. QStringList p = mimeType.globPatterns();
  208. QString pattern = p.isEmpty() ? QString() : p.front();
  209. if (data->isVoiceMessage()) {
  210. auto mp3 = data->hasMimeType(u"audio/mp3"_q);
  211. name = already.isEmpty() ? (mp3 ? u".mp3"_q : u".ogg"_q) : already;
  212. filter = mp3 ? u"MP3 Audio (*.mp3);;"_q : u"OGG Opus Audio (*.ogg);;"_q;
  213. filter += FileDialog::AllFilesFilter();
  214. caption = tr::lng_save_audio(tr::now);
  215. prefix = u"audio"_q;
  216. } else if (data->isVideoFile()) {
  217. name = already.isEmpty() ? data->filename() : already;
  218. if (name.isEmpty()) {
  219. name = pattern.isEmpty() ? u".mov"_q : pattern.replace('*', QString());
  220. }
  221. if (pattern.isEmpty()) {
  222. filter = u"MOV Video (*.mov);;"_q + FileDialog::AllFilesFilter();
  223. } else {
  224. filter = mimeType.filterString() + u";;"_q + FileDialog::AllFilesFilter();
  225. }
  226. caption = tr::lng_save_video(tr::now);
  227. prefix = u"video"_q;
  228. } else {
  229. name = already.isEmpty() ? data->filename() : already;
  230. if (name.isEmpty()) {
  231. name = pattern.isEmpty() ? u".unknown"_q : pattern.replace('*', QString());
  232. }
  233. if (pattern.isEmpty()) {
  234. filter = QString();
  235. } else {
  236. filter = mimeType.filterString() + u";;"_q + FileDialog::AllFilesFilter();
  237. }
  238. caption = data->isAudioFile()
  239. ? tr::lng_save_audio_file(tr::now)
  240. : tr::lng_save_file(tr::now);
  241. prefix = u"doc"_q;
  242. }
  243. return FileNameForSave(
  244. &data->session(),
  245. caption,
  246. filter,
  247. prefix,
  248. name,
  249. forceSavingAs,
  250. dir);
  251. }
  252. Data::FileOrigin StickerData::setOrigin() const {
  253. return set.id
  254. ? Data::FileOrigin(
  255. Data::FileOriginStickerSet(set.id, set.accessHash))
  256. : Data::FileOrigin();
  257. }
  258. bool StickerData::isStatic() const {
  259. return (type == StickerType::Webp);
  260. }
  261. bool StickerData::isLottie() const {
  262. return (type == StickerType::Tgs);
  263. }
  264. bool StickerData::isAnimated() const {
  265. return !isStatic();
  266. }
  267. bool StickerData::isWebm() const {
  268. return (type == StickerType::Webm);
  269. }
  270. VoiceData::~VoiceData() {
  271. if (!waveform.isEmpty()
  272. && waveform[0] == -1
  273. && waveform.size() > int32(sizeof(TaskId))) {
  274. auto taskId = TaskId();
  275. memcpy(&taskId, waveform.constData() + 1, sizeof(taskId));
  276. Local::cancelTask(taskId);
  277. }
  278. }
  279. DocumentData::DocumentData(not_null<Data::Session*> owner, DocumentId id)
  280. : id(id)
  281. , _owner(owner) {
  282. }
  283. DocumentData::~DocumentData() {
  284. base::take(_thumbnail.loader).reset();
  285. base::take(_videoThumbnail.loader).reset();
  286. destroyLoader();
  287. }
  288. Data::Session &DocumentData::owner() const {
  289. return *_owner;
  290. }
  291. Main::Session &DocumentData::session() const {
  292. return _owner->session();
  293. }
  294. void DocumentData::setattributes(
  295. const QVector<MTPDocumentAttribute> &attributes) {
  296. _duration = -1;
  297. _flags &= ~(Flag::ImageType
  298. | Flag::HasAttachedStickers
  299. | Flag::UseTextColor
  300. | Flag::SilentVideo
  301. | kStreamingSupportedMask);
  302. _flags |= kStreamingSupportedUnknown;
  303. validateLottieSticker();
  304. auto wasVideoData = isVideoFile() ? std::move(_additional) : nullptr;
  305. _videoPreloadPrefix = 0;
  306. for (const auto &attribute : attributes) {
  307. attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
  308. dimensions = QSize(data.vw().v, data.vh().v);
  309. }, [&](const MTPDdocumentAttributeAnimated &data) {
  310. if (type == FileDocument
  311. || type == VideoDocument
  312. || (sticker() && sticker()->type != StickerType::Webm)) {
  313. type = AnimatedDocument;
  314. _additional = nullptr;
  315. }
  316. }, [&](const MTPDdocumentAttributeSticker &data) {
  317. const auto was = type;
  318. if (type == FileDocument || type == VideoDocument) {
  319. type = StickerDocument;
  320. _additional = std::make_unique<StickerData>();
  321. }
  322. if (const auto info = sticker()) {
  323. info->setType = data.is_mask()
  324. ? Data::StickersType::Masks
  325. : Data::StickersType::Stickers;
  326. if (was == VideoDocument) {
  327. info->type = StickerType::Webm;
  328. }
  329. info->alt = qs(data.valt());
  330. UpdateStickerSetIdentifier(info->set, data.vstickerset());
  331. }
  332. }, [&](const MTPDdocumentAttributeCustomEmoji &data) {
  333. const auto was = type;
  334. if (type == FileDocument || type == VideoDocument) {
  335. type = StickerDocument;
  336. _additional = std::make_unique<StickerData>();
  337. }
  338. if (const auto info = sticker()) {
  339. info->setType = Data::StickersType::Emoji;
  340. if (was == VideoDocument) {
  341. info->type = StickerType::Webm;
  342. }
  343. info->alt = qs(data.valt());
  344. if (data.is_free()) {
  345. _flags &= ~Flag::PremiumSticker;
  346. } else {
  347. _flags |= Flag::PremiumSticker;
  348. }
  349. if (data.is_text_color()) {
  350. _flags |= Flag::UseTextColor;
  351. }
  352. UpdateStickerSetIdentifier(info->set, data.vstickerset());
  353. }
  354. }, [&](const MTPDdocumentAttributeVideo &data) {
  355. if (type == FileDocument) {
  356. type = data.is_round_message()
  357. ? RoundVideoDocument
  358. : VideoDocument;
  359. if (data.is_round_message()) {
  360. _additional = std::make_unique<RoundData>();
  361. } else {
  362. if (const auto size = data.vpreload_prefix_size()) {
  363. if (size->v > 0
  364. && size->v < kMaxAllowedPreloadPrefix) {
  365. _videoPreloadPrefix = size->v;
  366. }
  367. }
  368. _additional = wasVideoData
  369. ? std::move(wasVideoData)
  370. : std::make_unique<VideoData>();
  371. video()->codec = qs(
  372. data.vvideo_codec().value_or_empty());
  373. }
  374. } else if (type == VideoDocument && wasVideoData) {
  375. _additional = std::move(wasVideoData);
  376. } else if (const auto info = sticker()) {
  377. info->type = StickerType::Webm;
  378. }
  379. _duration = crl::time(
  380. base::SafeRound(data.vduration().v * 1000));
  381. setMaybeSupportsStreaming(data.is_supports_streaming());
  382. if (data.is_nosound()) {
  383. _flags |= Flag::SilentVideo;
  384. }
  385. dimensions = QSize(data.vw().v, data.vh().v);
  386. }, [&](const MTPDdocumentAttributeAudio &data) {
  387. if (type == FileDocument) {
  388. if (data.is_voice()) {
  389. type = VoiceDocument;
  390. _additional = std::make_unique<VoiceData>();
  391. } else {
  392. type = SongDocument;
  393. _additional = std::make_unique<SongData>();
  394. }
  395. }
  396. if (const auto voiceData = voice() ? voice() : round()) {
  397. _duration = data.vduration().v * crl::time(1000);
  398. voiceData->waveform = documentWaveformDecode(
  399. data.vwaveform().value_or_empty());
  400. voiceData->wavemax = voiceData->waveform.empty()
  401. ? uchar(0)
  402. : *ranges::max_element(voiceData->waveform);
  403. } else if (const auto songData = song()) {
  404. _duration = data.vduration().v * crl::time(1000);
  405. songData->title = qs(data.vtitle().value_or_empty());
  406. songData->performer = qs(data.vperformer().value_or_empty());
  407. refreshPossibleCoverThumbnail();
  408. }
  409. }, [&](const MTPDdocumentAttributeFilename &data) {
  410. setFileName(qs(data.vfile_name()));
  411. }, [&](const MTPDdocumentAttributeHasStickers &data) {
  412. _flags |= Flag::HasAttachedStickers;
  413. });
  414. }
  415. // Any "video/webm" file is treated as a video-sticker.
  416. if (hasMimeType(u"video/webm"_q)) {
  417. if (type == FileDocument) {
  418. type = StickerDocument;
  419. _additional = std::make_unique<StickerData>();
  420. }
  421. if (type == StickerDocument) {
  422. sticker()->type = StickerType::Webm;
  423. }
  424. }
  425. // If "video/webm" sticker without dimensions we set them to default.
  426. if (const auto info = sticker(); info
  427. && info->set
  428. && info->type == StickerType::Webm
  429. && dimensions.isEmpty()) {
  430. if (info->setType == Data::StickersType::Emoji) {
  431. // Always fixed.
  432. dimensions = { kDefaultWebmEmojiSize, kDefaultWebmEmojiSize };
  433. } else if (info->setType == Data::StickersType::Stickers) {
  434. // May have aspect != 1, so we count it from the thumbnail.
  435. const auto thumbnail = QSize(
  436. _thumbnail.location.width(),
  437. _thumbnail.location.height()
  438. ).scaled(
  439. kDefaultWebmStickerLargerSize,
  440. kDefaultWebmStickerLargerSize,
  441. Qt::KeepAspectRatio);
  442. if (!thumbnail.isEmpty()) {
  443. dimensions = thumbnail;
  444. }
  445. }
  446. }
  447. // Check sticker size/dimensions properties (for sticker of any type).
  448. if (type == StickerDocument
  449. && ((size > Storage::kMaxStickerBytesSize)
  450. || (!sticker()->isLottie()
  451. && !GoodStickerDimensions(
  452. dimensions.width(),
  453. dimensions.height())))) {
  454. type = FileDocument;
  455. _additional = nullptr;
  456. }
  457. if (!_filename.isEmpty()) {
  458. using Type = Core::NameType;
  459. if (type == VideoDocument
  460. || type == AnimatedDocument
  461. || type == RoundVideoDocument
  462. || isAnimation()) {
  463. if (!enforceNameType(Type::Video)) {
  464. type = FileDocument;
  465. _additional = nullptr;
  466. }
  467. }
  468. if (type == SongDocument || type == VoiceDocument || isAudioFile()) {
  469. if (!enforceNameType(Type::Audio)) {
  470. type = FileDocument;
  471. _additional = nullptr;
  472. }
  473. }
  474. if (!Core::NameTypeAllowsThumbnail(_nameType)) {
  475. _inlineThumbnailBytes = {};
  476. _flags &= ~Flag::InlineThumbnailIsPath;
  477. _thumbnail.clear();
  478. _videoThumbnail.clear();
  479. }
  480. }
  481. if (isAudioFile()
  482. || isAnimation()
  483. || isVoiceMessage()
  484. || storyMedia()) {
  485. setMaybeSupportsStreaming(true);
  486. }
  487. }
  488. void DocumentData::setVideoQualities(const QVector<MTPDocument> &list) {
  489. auto qualities = std::vector<not_null<DocumentData*>>();
  490. qualities.reserve(list.size());
  491. for (const auto &document : list) {
  492. qualities.push_back(owner().processDocument(document));
  493. }
  494. setVideoQualities(std::move(qualities));
  495. }
  496. void DocumentData::setVideoQualities(
  497. std::vector<not_null<DocumentData*>> qualities) {
  498. const auto data = video();
  499. if (!data) {
  500. return;
  501. }
  502. auto count = int(qualities.size());
  503. if (qualities.empty()) {
  504. return;
  505. }
  506. const auto good = [&](not_null<DocumentData*> document) {
  507. return document->isVideoFile()
  508. && !document->dimensions.isEmpty()
  509. && !document->inappPlaybackFailed()
  510. && document->useStreamingLoader()
  511. && document->canBeStreamed(nullptr);
  512. };
  513. ranges::sort(
  514. qualities,
  515. ranges::greater(),
  516. &DocumentData::resolveVideoQuality);
  517. for (auto i = 0; i != count - 1;) {
  518. const auto my = qualities[i];
  519. const auto next = qualities[i + 1];
  520. const auto myQuality = my->resolveVideoQuality();
  521. const auto nextQuality = next->resolveVideoQuality();
  522. const auto myGood = good(my);
  523. const auto nextGood = good(next);
  524. if (!myGood || !nextGood || myQuality == nextQuality) {
  525. const auto removeMe = !myGood
  526. || (nextGood && (my->size > next->size));
  527. const auto from = i + (removeMe ? 1 : 2);
  528. for (auto j = from; j != count; ++j) {
  529. qualities[j - 1] = qualities[j];
  530. }
  531. --count;
  532. } else {
  533. ++i;
  534. }
  535. }
  536. if (!qualities[count - 1]->resolveVideoQuality()) {
  537. --count;
  538. }
  539. qualities.erase(qualities.begin() + count, qualities.end());
  540. if (!qualities.empty()) {
  541. if (const auto mine = resolveVideoQuality()) {
  542. if (mine > qualities.front()->resolveVideoQuality()) {
  543. qualities.insert(begin(qualities), this);
  544. }
  545. }
  546. }
  547. data->qualities = std::move(qualities);
  548. }
  549. int DocumentData::resolveVideoQuality() const {
  550. const auto size = isVideoFile() ? dimensions : QSize();
  551. return size.isEmpty() ? 0 : std::min(size.width(), size.height());
  552. }
  553. auto DocumentData::resolveQualities(HistoryItem *context) const
  554. -> const std::vector<not_null<DocumentData*>> & {
  555. static const auto empty = std::vector<not_null<DocumentData*>>();
  556. const auto info = video();
  557. const auto media = context ? context->media() : nullptr;
  558. if (!info || !media || media->document() != this) {
  559. return empty;
  560. }
  561. return media->hasQualitiesList() ? info->qualities : empty;
  562. }
  563. not_null<DocumentData*> DocumentData::chooseQuality(
  564. HistoryItem *context,
  565. Media::VideoQuality request) {
  566. const auto &list = resolveQualities(context);
  567. if (list.empty() || !request.height) {
  568. return this;
  569. }
  570. const auto height = int(request.height);
  571. auto closest = this;
  572. auto closestAbs = std::abs(height - resolveVideoQuality());
  573. auto closestSize = size;
  574. for (const auto &quality : list) {
  575. const auto abs = std::abs(height - quality->resolveVideoQuality());
  576. if (abs < closestAbs
  577. || (abs == closestAbs && quality->size < closestSize)) {
  578. closest = quality;
  579. closestAbs = abs;
  580. closestSize = quality->size;
  581. }
  582. }
  583. return closest;
  584. }
  585. void DocumentData::validateLottieSticker() {
  586. if (type == FileDocument
  587. && hasMimeType(u"application/x-tgsticker"_q)) {
  588. type = StickerDocument;
  589. _additional = std::make_unique<StickerData>();
  590. sticker()->type = StickerType::Tgs;
  591. dimensions = kLottieStickerDimensions;
  592. }
  593. }
  594. void DocumentData::setDataAndCache(const QByteArray &data) {
  595. if (const auto media = activeMediaView()) {
  596. media->setBytes(data);
  597. }
  598. if (saveToCache() && data.size() <= Storage::kMaxFileInMemory) {
  599. owner().cache().put(
  600. cacheKey(),
  601. Storage::Cache::Database::TaggedValue(
  602. base::duplicate(data),
  603. cacheTag()));
  604. }
  605. }
  606. bool DocumentData::checkWallPaperProperties() {
  607. if (type == WallPaperDocument) {
  608. return true;
  609. }
  610. if (type != FileDocument
  611. || !hasThumbnail()
  612. || dimensions.isEmpty()
  613. || dimensions.width() > Storage::kMaxWallPaperDimension
  614. || dimensions.height() > Storage::kMaxWallPaperDimension
  615. || size > Storage::kMaxWallPaperInMemory) {
  616. return false;
  617. }
  618. type = WallPaperDocument;
  619. return true;
  620. }
  621. void DocumentData::updateThumbnails(
  622. const InlineImageLocation &inlineThumbnail,
  623. const ImageWithLocation &thumbnail,
  624. const ImageWithLocation &videoThumbnail,
  625. bool isPremiumSticker) {
  626. if (!_filename.isEmpty()
  627. && !Core::NameTypeAllowsThumbnail(Core::DetectNameType(_filename))) {
  628. return;
  629. }
  630. if (!inlineThumbnail.bytes.isEmpty()
  631. && _inlineThumbnailBytes.isEmpty()) {
  632. _inlineThumbnailBytes = inlineThumbnail.bytes;
  633. if (inlineThumbnail.isPath) {
  634. _flags |= Flag::InlineThumbnailIsPath;
  635. } else {
  636. _flags &= ~Flag::InlineThumbnailIsPath;
  637. }
  638. }
  639. if (!sticker() || sticker()->setType != Data::StickersType::Emoji) {
  640. if (isPremiumSticker) {
  641. _flags |= Flag::PremiumSticker;
  642. } else {
  643. _flags &= ~Flag::PremiumSticker;
  644. }
  645. }
  646. Data::UpdateCloudFile(
  647. _thumbnail,
  648. thumbnail,
  649. owner().cache(),
  650. Data::kImageCacheTag,
  651. [&](Data::FileOrigin origin) { loadThumbnail(origin); },
  652. [&](QImage preloaded, QByteArray) {
  653. if (const auto media = activeMediaView()) {
  654. media->setThumbnail(std::move(preloaded));
  655. }
  656. });
  657. Data::UpdateCloudFile(
  658. _videoThumbnail,
  659. videoThumbnail,
  660. owner().cache(),
  661. Data::kAnimationCacheTag,
  662. [&](Data::FileOrigin origin) { loadVideoThumbnail(origin); });
  663. }
  664. bool DocumentData::isWallPaper() const {
  665. return (type == WallPaperDocument);
  666. }
  667. bool DocumentData::isPatternWallPaper() const {
  668. return isWallPaper()
  669. && (isPatternWallPaperPNG() || isPatternWallPaperSVG());
  670. }
  671. bool DocumentData::isPatternWallPaperPNG() const {
  672. return isWallPaper() && hasMimeType(u"image/png"_q);
  673. }
  674. bool DocumentData::isPatternWallPaperSVG() const {
  675. return isWallPaper() && hasMimeType(u"application/x-tgwallpattern"_q);
  676. }
  677. bool DocumentData::isPremiumSticker() const {
  678. if (!(_flags & Flag::PremiumSticker)) {
  679. return false;
  680. }
  681. const auto info = sticker();
  682. return info && info->setType == Data::StickersType::Stickers;
  683. }
  684. bool DocumentData::isPremiumEmoji() const {
  685. if (!(_flags & Flag::PremiumSticker)) {
  686. return false;
  687. }
  688. const auto info = sticker();
  689. return info && info->setType == Data::StickersType::Emoji;
  690. }
  691. bool DocumentData::emojiUsesTextColor() const {
  692. return (_flags & Flag::UseTextColor);
  693. }
  694. void DocumentData::overrideEmojiUsesTextColor(bool value) {
  695. if (value) {
  696. _flags |= Flag::UseTextColor;
  697. } else {
  698. _flags &= ~Flag::UseTextColor;
  699. }
  700. }
  701. bool DocumentData::hasThumbnail() const {
  702. return _thumbnail.location.valid()
  703. && !thumbnailFailed()
  704. && !(_flags & Flag::PossibleCoverThumbnail);
  705. }
  706. bool DocumentData::thumbnailLoading() const {
  707. return _thumbnail.loader != nullptr;
  708. }
  709. bool DocumentData::thumbnailFailed() const {
  710. return (_thumbnail.flags & Data::CloudFile::Flag::Failed);
  711. }
  712. void DocumentData::loadThumbnail(Data::FileOrigin origin) {
  713. const auto autoLoading = false;
  714. const auto finalCheck = [=] {
  715. if (const auto active = activeMediaView()) {
  716. return !active->thumbnail();
  717. }
  718. return true;
  719. };
  720. const auto done = [=](QImage result, QByteArray) {
  721. _flags &= ~Flag::PossibleCoverThumbnail;
  722. if (const auto active = activeMediaView()) {
  723. active->setThumbnail(std::move(result));
  724. }
  725. };
  726. Data::LoadCloudFile(
  727. &session(),
  728. _thumbnail,
  729. origin,
  730. LoadFromCloudOrLocal,
  731. autoLoading,
  732. Data::kImageCacheTag,
  733. finalCheck,
  734. done);
  735. }
  736. const ImageLocation &DocumentData::thumbnailLocation() const {
  737. return _thumbnail.location;
  738. }
  739. int DocumentData::thumbnailByteSize() const {
  740. return _thumbnail.byteSize;
  741. }
  742. bool DocumentData::hasVideoThumbnail() const {
  743. return _videoThumbnail.location.valid();
  744. }
  745. bool DocumentData::videoThumbnailLoading() const {
  746. return _videoThumbnail.loader != nullptr;
  747. }
  748. bool DocumentData::videoThumbnailFailed() const {
  749. return (_videoThumbnail.flags & Data::CloudFile::Flag::Failed);
  750. }
  751. void DocumentData::loadVideoThumbnail(Data::FileOrigin origin) {
  752. const auto autoLoading = false;
  753. const auto finalCheck = [=] {
  754. if (const auto active = activeMediaView()) {
  755. return active->videoThumbnailContent().isEmpty();
  756. }
  757. return true;
  758. };
  759. const auto done = [=](QByteArray result) {
  760. if (const auto active = activeMediaView()) {
  761. active->setVideoThumbnail(std::move(result));
  762. }
  763. };
  764. Data::LoadCloudFile(
  765. &session(),
  766. _videoThumbnail,
  767. origin,
  768. LoadFromCloudOrLocal,
  769. autoLoading,
  770. Data::kAnimationCacheTag,
  771. finalCheck,
  772. done);
  773. }
  774. const ImageLocation &DocumentData::videoThumbnailLocation() const {
  775. return _videoThumbnail.location;
  776. }
  777. int DocumentData::videoThumbnailByteSize() const {
  778. return _videoThumbnail.byteSize;
  779. }
  780. Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const {
  781. return Data::DocumentThumbCacheKey(_dc, id);
  782. }
  783. bool DocumentData::goodThumbnailChecked() const {
  784. return (_goodThumbnailState & GoodThumbnailFlag::Mask)
  785. == GoodThumbnailFlag::Checked;
  786. }
  787. bool DocumentData::goodThumbnailGenerating() const {
  788. return (_goodThumbnailState & GoodThumbnailFlag::Mask)
  789. == GoodThumbnailFlag::Generating;
  790. }
  791. bool DocumentData::goodThumbnailNoData() const {
  792. return (_goodThumbnailState & GoodThumbnailFlag::Mask)
  793. == GoodThumbnailFlag::NoData;
  794. }
  795. void DocumentData::setGoodThumbnailGenerating() {
  796. _goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
  797. | GoodThumbnailFlag::Generating;
  798. }
  799. void DocumentData::setGoodThumbnailDataReady() {
  800. _goodThumbnailState = GoodThumbnailFlag::DataReady
  801. | (goodThumbnailNoData()
  802. ? GoodThumbnailFlag(0)
  803. : (_goodThumbnailState & GoodThumbnailFlag::Mask));
  804. }
  805. void DocumentData::setGoodThumbnailChecked(bool hasData) {
  806. if (!hasData && (_goodThumbnailState & GoodThumbnailFlag::DataReady)) {
  807. _goodThumbnailState &= ~GoodThumbnailFlag::DataReady;
  808. _goodThumbnailState &= ~GoodThumbnailFlag::Mask;
  809. Data::DocumentMedia::CheckGoodThumbnail(this);
  810. return;
  811. }
  812. _goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
  813. | (hasData
  814. ? GoodThumbnailFlag::Checked
  815. : GoodThumbnailFlag::NoData);
  816. }
  817. std::shared_ptr<Data::DocumentMedia> DocumentData::createMediaView() {
  818. if (auto result = activeMediaView()) {
  819. return result;
  820. }
  821. auto result = std::make_shared<Data::DocumentMedia>(this);
  822. _media = result;
  823. return result;
  824. }
  825. std::shared_ptr<Data::DocumentMedia> DocumentData::activeMediaView() const {
  826. return _media.lock();
  827. }
  828. void DocumentData::setGoodThumbnailPhoto(not_null<PhotoData*> photo) {
  829. _goodThumbnailPhoto = photo;
  830. }
  831. PhotoData *DocumentData::goodThumbnailPhoto() const {
  832. return _goodThumbnailPhoto;
  833. }
  834. Storage::Cache::Key DocumentData::bigFileBaseCacheKey() const {
  835. return hasRemoteLocation()
  836. ? StorageFileLocation(
  837. _dc,
  838. session().userId(),
  839. MTP_inputDocumentFileLocation(
  840. MTP_long(id),
  841. MTP_long(_access),
  842. MTP_bytes(_fileReference),
  843. MTP_string())).bigFileBaseCacheKey()
  844. : Storage::Cache::Key();
  845. }
  846. void DocumentData::forceToCache(bool force) {
  847. if (force) {
  848. _flags |= Flag::ForceToCache;
  849. } else {
  850. _flags &= ~Flag::ForceToCache;
  851. }
  852. }
  853. bool DocumentData::saveToCache() const {
  854. return (size < Storage::kMaxFileInMemory)
  855. && ((type == StickerDocument)
  856. || (_flags & Flag::ForceToCache)
  857. || isAnimation()
  858. || isVoiceMessage()
  859. || isWallPaper()
  860. || isTheme()
  861. || (hasMimeType(u"image/png"_q)
  862. && _filename.startsWith("image_")));
  863. }
  864. void DocumentData::automaticLoadSettingsChanged() {
  865. if (!cancelled() || status != FileReady) {
  866. return;
  867. }
  868. _loader = nullptr;
  869. resetCancelled();
  870. }
  871. void DocumentData::finishLoad() {
  872. // NB! _loader may be in ~FileLoader() already.
  873. const auto guard = gsl::finally([&] {
  874. destroyLoader();
  875. });
  876. if (!_loader || _loader->cancelled()) {
  877. _flags |= Flag::DownloadCancelled;
  878. return;
  879. }
  880. setLocation(Core::FileLocation(_loader->fileName()));
  881. setGoodThumbnailDataReady();
  882. if (const auto media = activeMediaView()) {
  883. media->setBytes(_loader->bytes());
  884. media->checkStickerLarge(_loader.get());
  885. }
  886. }
  887. void DocumentData::destroyLoader() {
  888. if (!_loader) {
  889. return;
  890. }
  891. const auto loader = base::take(_loader);
  892. if (cancelled()) {
  893. loader->cancel();
  894. }
  895. }
  896. bool DocumentData::loading() const {
  897. return (_loader != nullptr);
  898. }
  899. QString DocumentData::loadingFilePath() const {
  900. return loading() ? _loader->fileName() : QString();
  901. }
  902. bool DocumentData::displayLoading() const {
  903. return loading()
  904. ? !_loader->loadingLocal()
  905. : (uploading() && !waitingForAlbum());
  906. }
  907. float64 DocumentData::progress() const {
  908. if (uploading()) {
  909. if (uploadingData->size > 0) {
  910. const auto result = float64(uploadingData->offset)
  911. / float64(uploadingData->size);
  912. return std::clamp(result, 0., 1.);
  913. }
  914. return 0.;
  915. }
  916. return loading() ? _loader->currentProgress() : 0.;
  917. }
  918. int64 DocumentData::loadOffset() const {
  919. return loading() ? _loader->currentOffset() : 0;
  920. }
  921. bool DocumentData::uploading() const {
  922. return (uploadingData != nullptr);
  923. }
  924. bool DocumentData::loadedInMediaCache() const {
  925. return (_flags & Flag::LoadedInMediaCache);
  926. }
  927. void DocumentData::setLoadedInMediaCache(bool loaded) {
  928. const auto flags = loaded
  929. ? (_flags | Flag::LoadedInMediaCache)
  930. : (_flags & ~Flag::LoadedInMediaCache);
  931. if (_flags == flags) {
  932. return;
  933. }
  934. _flags = flags;
  935. if (filepath().isEmpty()) {
  936. if (loadedInMediaCache()) {
  937. session().local().writeFileLocation(
  938. mediaKey(),
  939. Core::FileLocation::InMediaCacheLocation());
  940. } else {
  941. session().local().removeFileLocation(mediaKey());
  942. }
  943. owner().requestDocumentViewRepaint(this);
  944. }
  945. }
  946. ChatRestriction DocumentData::requiredSendRight() const {
  947. return isVideoFile()
  948. ? ChatRestriction::SendVideos
  949. : isSong()
  950. ? ChatRestriction::SendMusic
  951. : isVoiceMessage()
  952. ? ChatRestriction::SendVoiceMessages
  953. : isVideoMessage()
  954. ? ChatRestriction::SendVideoMessages
  955. : sticker()
  956. ? ChatRestriction::SendStickers
  957. : isAnimation()
  958. ? ChatRestriction::SendGifs
  959. : ChatRestriction::SendFiles;
  960. }
  961. void DocumentData::setFileName(const QString &remoteFileName) {
  962. _filename = remoteFileName;
  963. // We don't want LTR/RTL mark/embedding/override/isolate chars
  964. // in filenames, because they introduce a security issue, when
  965. // an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg".
  966. QChar controls[] = {
  967. QChar(0x200E), // LTR Mark
  968. QChar(0x200F), // RTL Mark
  969. QChar(0x202A), // LTR Embedding
  970. QChar(0x202B), // RTL Embedding
  971. QChar(0x202D), // LTR Override
  972. QChar(0x202E), // RTL Override
  973. QChar(0x2066), // LTR Isolate
  974. QChar(0x2067), // RTL Isolate
  975. };
  976. for (const auto &ch : controls) {
  977. _filename = std::move(_filename).replace(ch, "_");
  978. }
  979. _nameType = Core::DetectNameType(_filename);
  980. }
  981. bool DocumentData::enforceNameType(Core::NameType nameType) {
  982. if (_nameType == nameType) {
  983. return true;
  984. }
  985. const auto base = _filename.isEmpty() ? u"file"_q : _filename;
  986. const auto mime = Core::MimeTypeForName(mimeString());
  987. const auto patterns = mime.globPatterns();
  988. for (const auto &pattern : mime.globPatterns()) {
  989. const auto now = base + QString(pattern).replace('*', QString());
  990. if (Core::DetectNameType(now) == nameType) {
  991. _filename = now;
  992. _nameType = nameType;
  993. return true;
  994. }
  995. }
  996. return false;
  997. }
  998. void DocumentData::setLoadedInMediaCacheLocation() {
  999. _location = Core::FileLocation();
  1000. _flags |= Flag::LoadedInMediaCache;
  1001. }
  1002. void DocumentData::setWaitingForAlbum() {
  1003. if (uploading()) {
  1004. uploadingData->waitingForAlbum = true;
  1005. }
  1006. }
  1007. bool DocumentData::waitingForAlbum() const {
  1008. return uploading() && uploadingData->waitingForAlbum;
  1009. }
  1010. void DocumentData::save(
  1011. Data::FileOrigin origin,
  1012. const QString &toFile,
  1013. LoadFromCloudSetting fromCloud,
  1014. bool autoLoading) {
  1015. if (const auto media = activeMediaView(); media && media->loaded(true)) {
  1016. auto &l = location(true);
  1017. if (!toFile.isEmpty()) {
  1018. if (!media->bytes().isEmpty()) {
  1019. QFile f(toFile);
  1020. f.open(QIODevice::WriteOnly);
  1021. f.write(media->bytes());
  1022. f.close();
  1023. setLocation(Core::FileLocation(toFile));
  1024. session().local().writeFileLocation(
  1025. mediaKey(),
  1026. Core::FileLocation(toFile));
  1027. } else if (l.accessEnable()) {
  1028. const auto &alreadyName = l.name();
  1029. if (alreadyName != toFile) {
  1030. QFile(toFile).remove();
  1031. QFile(alreadyName).copy(toFile);
  1032. }
  1033. l.accessDisable();
  1034. }
  1035. }
  1036. return;
  1037. }
  1038. if (_loader) {
  1039. if (!_loader->setFileName(toFile)) {
  1040. cancel();
  1041. }
  1042. }
  1043. resetCancelled();
  1044. if (_loader) {
  1045. if (fromCloud == LoadFromCloudOrLocal) {
  1046. _loader->permitLoadFromCloud();
  1047. }
  1048. } else {
  1049. status = FileReady;
  1050. auto reader = owner().streaming().sharedReader(this, origin, true);
  1051. if (reader) {
  1052. _loader = std::make_unique<Storage::StreamedFileDownloader>(
  1053. &session(),
  1054. id,
  1055. _dc,
  1056. origin,
  1057. Data::DocumentCacheKey(_dc, id),
  1058. mediaKey(),
  1059. std::move(reader),
  1060. toFile,
  1061. size,
  1062. locationType(),
  1063. (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly),
  1064. fromCloud,
  1065. autoLoading,
  1066. cacheTag());
  1067. } else if (hasWebLocation()) {
  1068. _loader = std::make_unique<mtpFileLoader>(
  1069. &session(),
  1070. _urlLocation,
  1071. size,
  1072. size,
  1073. fromCloud,
  1074. autoLoading,
  1075. cacheTag());
  1076. } else if (!_access && !_url.isEmpty()) {
  1077. _loader = std::make_unique<webFileLoader>(
  1078. &session(),
  1079. _url,
  1080. toFile,
  1081. fromCloud,
  1082. autoLoading,
  1083. cacheTag());
  1084. } else {
  1085. _loader = std::make_unique<mtpFileLoader>(
  1086. &session(),
  1087. StorageFileLocation(
  1088. _dc,
  1089. session().userId(),
  1090. MTP_inputDocumentFileLocation(
  1091. MTP_long(id),
  1092. MTP_long(_access),
  1093. MTP_bytes(_fileReference),
  1094. MTP_string())),
  1095. origin,
  1096. locationType(),
  1097. toFile,
  1098. size,
  1099. size,
  1100. (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly),
  1101. fromCloud,
  1102. autoLoading,
  1103. cacheTag());
  1104. }
  1105. handleLoaderUpdates();
  1106. }
  1107. if (loading()) {
  1108. _loader->start();
  1109. }
  1110. // This affects a display of tooltips.
  1111. // _owner->notifyDocumentLayoutChanged(this);
  1112. }
  1113. void DocumentData::handleLoaderUpdates() {
  1114. _loader->updates(
  1115. ) | rpl::start_with_next_error_done([=] {
  1116. _owner->documentLoadProgress(this);
  1117. }, [=](FileLoader::Error error) {
  1118. using FailureReason = FileLoader::FailureReason;
  1119. if (error.started && _loader) {
  1120. const auto origin = _loader->fileOrigin();
  1121. const auto failedFileName = _loader->fileName();
  1122. const auto retry = [=] {
  1123. Ui::hideLayer();
  1124. save(origin, failedFileName);
  1125. };
  1126. Ui::show(Ui::MakeConfirmBox({
  1127. tr::lng_download_finish_failed(),
  1128. crl::guard(&session(), retry)
  1129. }));
  1130. } else if (error.failureReason == FailureReason::FileWriteFailure) {
  1131. if (!Core::App().settings().downloadPath().isEmpty()) {
  1132. Core::App().settings().setDownloadPathBookmark(QByteArray());
  1133. Core::App().settings().setDownloadPath(QString());
  1134. Core::App().saveSettingsDelayed();
  1135. InvokeQueued(qApp, [] {
  1136. Ui::show(
  1137. Ui::MakeInformBox(
  1138. tr::lng_download_path_failed(tr::now)));
  1139. });
  1140. }
  1141. }
  1142. finishLoad();
  1143. status = FileDownloadFailed;
  1144. _owner->documentLoadFail(this, error.started);
  1145. }, [=] {
  1146. finishLoad();
  1147. _owner->documentLoadDone(this);
  1148. }, _loader->lifetime());
  1149. }
  1150. void DocumentData::cancel() {
  1151. if (!loading()) {
  1152. return;
  1153. }
  1154. _flags |= Flag::DownloadCancelled;
  1155. destroyLoader();
  1156. _owner->documentLoadDone(this);
  1157. }
  1158. bool DocumentData::cancelled() const {
  1159. return (_flags & Flag::DownloadCancelled);
  1160. }
  1161. void DocumentData::resetCancelled() {
  1162. _flags &= ~Flag::DownloadCancelled;
  1163. }
  1164. VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
  1165. auto bitsCount = static_cast<int>(encoded5bit.size() * 8);
  1166. auto valuesCount = bitsCount / 5;
  1167. if (!valuesCount) {
  1168. return VoiceWaveform();
  1169. }
  1170. // Read each 5 bit of encoded5bit as 0-31 unsigned char.
  1171. // We count the index of the byte in which the desired 5-bit sequence starts.
  1172. // And then we read a uint16 starting from that byte to guarantee to get all of those 5 bits.
  1173. //
  1174. // BUT! if it is the last byte we have, we're not allowed to read a uint16 starting with it.
  1175. // Because it will be an overflow (we'll access one byte after the available memory).
  1176. // We see, that only the last 5 bits could start in the last available byte and be problematic.
  1177. // So we read in a general way all the entries in a general way except the last one.
  1178. auto result = VoiceWaveform(valuesCount, 0);
  1179. auto bitsData = encoded5bit.constData();
  1180. for (auto i = 0, l = valuesCount - 1; i != l; ++i) {
  1181. auto byteIndex = (i * 5) / 8;
  1182. auto bitShift = (i * 5) % 8;
  1183. auto value = *reinterpret_cast<const uint16*>(bitsData + byteIndex);
  1184. result[i] = static_cast<char>((value >> bitShift) & 0x1F);
  1185. }
  1186. auto lastByteIndex = ((valuesCount - 1) * 5) / 8;
  1187. auto lastBitShift = ((valuesCount - 1) * 5) % 8;
  1188. auto lastValue = (lastByteIndex == encoded5bit.size() - 1)
  1189. ? static_cast<uint16>(*reinterpret_cast<const uchar*>(bitsData + lastByteIndex))
  1190. : *reinterpret_cast<const uint16*>(bitsData + lastByteIndex);
  1191. result[valuesCount - 1] = static_cast<char>((lastValue >> lastBitShift) & 0x1F);
  1192. return result;
  1193. }
  1194. QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) {
  1195. auto bitsCount = waveform.size() * 5;
  1196. auto bytesCount = (bitsCount + 7) / 8;
  1197. auto result = QByteArray(bytesCount + 1, 0);
  1198. auto bitsData = result.data();
  1199. // Write each 0-31 unsigned char as 5 bit to result.
  1200. // We reserve one extra byte to be able to dereference any of required bytes
  1201. // as a uint16 without overflowing, even the byte with index "bytesCount - 1".
  1202. for (auto i = 0, l = int(waveform.size()); i < l; ++i) {
  1203. auto byteIndex = (i * 5) / 8;
  1204. auto bitShift = (i * 5) % 8;
  1205. auto value = (static_cast<uint16>(waveform[i]) & 0x1F) << bitShift;
  1206. *reinterpret_cast<uint16*>(bitsData + byteIndex) |= value;
  1207. }
  1208. result.resize(bytesCount);
  1209. return result;
  1210. }
  1211. const Core::FileLocation &DocumentData::location(bool check) const {
  1212. if (check && !_location.check()) {
  1213. const auto location = session().local().readFileLocation(mediaKey());
  1214. const auto that = const_cast<DocumentData*>(this);
  1215. if (location.inMediaCache()) {
  1216. that->setLoadedInMediaCacheLocation();
  1217. } else {
  1218. that->_location = location;
  1219. }
  1220. }
  1221. return _location;
  1222. }
  1223. void DocumentData::setLocation(const Core::FileLocation &loc) {
  1224. if (loc.inMediaCache()) {
  1225. setLoadedInMediaCacheLocation();
  1226. } else if (loc.check()) {
  1227. _location = loc;
  1228. }
  1229. }
  1230. QString DocumentData::filepath(bool check) const {
  1231. return (check && _location.name().isEmpty())
  1232. ? QString()
  1233. : location(check).name();
  1234. }
  1235. bool DocumentData::saveFromData() {
  1236. return !filepath(true).isEmpty() || saveFromDataChecked();
  1237. }
  1238. bool DocumentData::saveFromDataSilent() {
  1239. return !filepath(true).isEmpty()
  1240. || (Core::App().canSaveFileWithoutAskingForPath()
  1241. && saveFromDataChecked());
  1242. }
  1243. bool DocumentData::saveFromDataChecked() {
  1244. const auto media = activeMediaView();
  1245. if (!media) {
  1246. return false;
  1247. }
  1248. const auto bytes = media->bytes();
  1249. if (bytes.isEmpty()) {
  1250. return false;
  1251. }
  1252. const auto path = DocumentFileNameForSave(this);
  1253. if (path.isEmpty()) {
  1254. return false;
  1255. }
  1256. auto file = QFile(path);
  1257. if (!file.open(QIODevice::WriteOnly)
  1258. || file.write(bytes) != bytes.size()) {
  1259. return false;
  1260. }
  1261. file.close();
  1262. _location = Core::FileLocation(path);
  1263. session().local().writeFileLocation(mediaKey(), _location);
  1264. return true;
  1265. }
  1266. void DocumentData::refreshPossibleCoverThumbnail() {
  1267. Expects(isSong());
  1268. if (_thumbnail.location.valid()) {
  1269. return;
  1270. }
  1271. const auto songData = song();
  1272. if (songData->performer.isEmpty()
  1273. || songData->title.isEmpty()
  1274. // Ignore cover for voice chat records.
  1275. || hasMimeType(u"audio/ogg"_q)) {
  1276. return;
  1277. }
  1278. const auto size = kDefaultCoverThumbnailSize;
  1279. const auto location = ImageWithLocation{
  1280. .location = ImageLocation(
  1281. { AudioAlbumThumbLocation{ id } },
  1282. size,
  1283. size)
  1284. };
  1285. _flags |= Flag::PossibleCoverThumbnail;
  1286. updateThumbnails({}, location, {}, false);
  1287. loadThumbnail({});
  1288. }
  1289. bool DocumentData::isStickerSetInstalled() const {
  1290. Expects(sticker() != nullptr);
  1291. using SetFlag = Data::StickersSetFlag;
  1292. const auto &sets = _owner->stickers().sets();
  1293. if (const auto id = sticker()->set.id) {
  1294. const auto i = sets.find(id);
  1295. return (i != sets.cend())
  1296. && !(i->second->flags & SetFlag::Archived)
  1297. && (i->second->flags & SetFlag::Installed);
  1298. } else {
  1299. return false;
  1300. }
  1301. }
  1302. Image *DocumentData::getReplyPreview(
  1303. Data::FileOrigin origin,
  1304. not_null<PeerData*> context,
  1305. bool spoiler) {
  1306. if (!hasThumbnail()) {
  1307. return nullptr;
  1308. } else if (!_replyPreview) {
  1309. _replyPreview = std::make_unique<Data::ReplyPreview>(this);
  1310. }
  1311. return _replyPreview->image(origin, context, spoiler);
  1312. }
  1313. Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
  1314. const auto media = item->media();
  1315. const auto spoiler = media && media->hasSpoiler();
  1316. return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
  1317. }
  1318. bool DocumentData::replyPreviewLoaded(bool spoiler) const {
  1319. if (!hasThumbnail()) {
  1320. return true;
  1321. } else if (!_replyPreview) {
  1322. return false;
  1323. }
  1324. return _replyPreview->loaded(spoiler);
  1325. }
  1326. StickerData *DocumentData::sticker() const {
  1327. return (type == StickerDocument)
  1328. ? static_cast<StickerData*>(_additional.get())
  1329. : nullptr;
  1330. }
  1331. Data::FileOrigin DocumentData::stickerSetOrigin() const {
  1332. if (const auto data = sticker()) {
  1333. if (const auto result = data->setOrigin()) {
  1334. return result;
  1335. } else if (owner().stickers().isFaved(this)) {
  1336. return Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0);
  1337. }
  1338. }
  1339. return Data::FileOrigin();
  1340. }
  1341. Data::FileOrigin DocumentData::stickerOrGifOrigin() const {
  1342. return (sticker()
  1343. ? stickerSetOrigin()
  1344. : isGifv()
  1345. ? Data::FileOriginSavedGifs()
  1346. : Data::FileOrigin());
  1347. }
  1348. SongData *DocumentData::song() {
  1349. return isSong()
  1350. ? static_cast<SongData*>(_additional.get())
  1351. : nullptr;
  1352. }
  1353. const SongData *DocumentData::song() const {
  1354. return const_cast<DocumentData*>(this)->song();
  1355. }
  1356. VoiceData *DocumentData::voice() {
  1357. return isVoiceMessage()
  1358. ? static_cast<VoiceData*>(_additional.get())
  1359. : nullptr;
  1360. }
  1361. const VoiceData *DocumentData::voice() const {
  1362. return const_cast<DocumentData*>(this)->voice();
  1363. }
  1364. RoundData *DocumentData::round() {
  1365. return isVideoMessage()
  1366. ? static_cast<RoundData*>(_additional.get())
  1367. : nullptr;
  1368. }
  1369. const RoundData *DocumentData::round() const {
  1370. return const_cast<DocumentData*>(this)->round();
  1371. }
  1372. VideoData *DocumentData::video() {
  1373. return isVideoFile()
  1374. ? static_cast<VideoData*>(_additional.get())
  1375. : nullptr;
  1376. }
  1377. const VideoData *DocumentData::video() const {
  1378. return const_cast<DocumentData*>(this)->video();
  1379. }
  1380. bool DocumentData::hasRemoteLocation() const {
  1381. return (_dc != 0 && _access != 0);
  1382. }
  1383. bool DocumentData::useStreamingLoader() const {
  1384. if (size <= 0) {
  1385. return false;
  1386. } else if (const auto info = sticker()) {
  1387. return info->isWebm();
  1388. }
  1389. return isAnimation()
  1390. || isVideoFile()
  1391. || isAudioFile()
  1392. || isVoiceMessage();
  1393. }
  1394. bool DocumentData::canBeStreamed(HistoryItem *item) const {
  1395. // Streaming couldn't be used with external player
  1396. // Maybe someone brave will implement this once upon a time...
  1397. static const auto &ExternalVideoPlayer = base::options::lookup<bool>(
  1398. Data::kOptionExternalVideoPlayer);
  1399. return hasRemoteLocation()
  1400. && supportsStreaming()
  1401. && (!isVideoFile()
  1402. || storyMedia()
  1403. || !ExternalVideoPlayer.value()
  1404. || (item && !item->allowsForward()));
  1405. }
  1406. void DocumentData::setInappPlaybackFailed() {
  1407. _flags |= Flag::StreamingPlaybackFailed;
  1408. }
  1409. bool DocumentData::inappPlaybackFailed() const {
  1410. return (_flags & Flag::StreamingPlaybackFailed);
  1411. }
  1412. int DocumentData::videoPreloadPrefix() const {
  1413. return _videoPreloadPrefix;
  1414. }
  1415. StorageFileLocation DocumentData::videoPreloadLocation() const {
  1416. return hasRemoteLocation()
  1417. ? StorageFileLocation(
  1418. _dc,
  1419. session().userId(),
  1420. MTP_inputDocumentFileLocation(
  1421. MTP_long(id),
  1422. MTP_long(_access),
  1423. MTP_bytes(_fileReference),
  1424. MTP_string()))
  1425. : StorageFileLocation();
  1426. }
  1427. auto DocumentData::createStreamingLoader(
  1428. Data::FileOrigin origin,
  1429. bool forceRemoteLoader) const
  1430. -> std::unique_ptr<Media::Streaming::Loader> {
  1431. if (!useStreamingLoader()) {
  1432. return nullptr;
  1433. }
  1434. if (!forceRemoteLoader) {
  1435. const auto media = activeMediaView();
  1436. const auto &location = this->location(true);
  1437. if (media && !media->bytes().isEmpty()) {
  1438. return Media::Streaming::MakeBytesLoader(media->bytes());
  1439. } else if (!location.isEmpty() && location.accessEnable()) {
  1440. auto result = Media::Streaming::MakeFileLoader(location.name());
  1441. location.accessDisable();
  1442. return result;
  1443. }
  1444. }
  1445. return hasRemoteLocation()
  1446. ? std::make_unique<Media::Streaming::LoaderMtproto>(
  1447. &session().downloader(),
  1448. StorageFileLocation(
  1449. _dc,
  1450. session().userId(),
  1451. MTP_inputDocumentFileLocation(
  1452. MTP_long(id),
  1453. MTP_long(_access),
  1454. MTP_bytes(_fileReference),
  1455. MTP_string())),
  1456. size,
  1457. origin)
  1458. : nullptr;
  1459. }
  1460. bool DocumentData::hasWebLocation() const {
  1461. return !_urlLocation.url().isEmpty();
  1462. }
  1463. bool DocumentData::isNull() const {
  1464. return !hasRemoteLocation()
  1465. && !hasWebLocation()
  1466. && _url.isEmpty()
  1467. && !uploading()
  1468. && _location.isEmpty();
  1469. }
  1470. MTPInputDocument DocumentData::mtpInput() const {
  1471. if (_access) {
  1472. return MTP_inputDocument(
  1473. MTP_long(id),
  1474. MTP_long(_access),
  1475. MTP_bytes(_fileReference));
  1476. }
  1477. return MTP_inputDocumentEmpty();
  1478. }
  1479. QByteArray DocumentData::fileReference() const {
  1480. return _fileReference;
  1481. }
  1482. void DocumentData::refreshFileReference(const QByteArray &value) {
  1483. _fileReference = value;
  1484. _thumbnail.location.refreshFileReference(value);
  1485. _videoThumbnail.location.refreshFileReference(value);
  1486. }
  1487. QString DocumentData::filename() const {
  1488. return _filename;
  1489. }
  1490. Core::NameType DocumentData::nameType() const {
  1491. return _nameType;
  1492. }
  1493. QString DocumentData::mimeString() const {
  1494. return _mimeString;
  1495. }
  1496. bool DocumentData::hasMimeType(const QString &mime) const {
  1497. return (_mimeString == mime);
  1498. }
  1499. void DocumentData::setMimeString(const QString &mime) {
  1500. _mimeString = mime;
  1501. _mimeString = std::move(_mimeString).toLower();
  1502. }
  1503. MediaKey DocumentData::mediaKey() const {
  1504. return ::mediaKey(locationType(), _dc, id);
  1505. }
  1506. Storage::Cache::Key DocumentData::cacheKey() const {
  1507. if (hasWebLocation()) {
  1508. return Data::WebDocumentCacheKey(_urlLocation);
  1509. } else if (!_access && !_url.isEmpty()) {
  1510. return Data::UrlCacheKey(_url);
  1511. } else {
  1512. return Data::DocumentCacheKey(_dc, id);
  1513. }
  1514. }
  1515. uint8 DocumentData::cacheTag() const {
  1516. if (type == StickerDocument) {
  1517. return Data::kStickerCacheTag;
  1518. } else if (isVoiceMessage()) {
  1519. return Data::kVoiceMessageCacheTag;
  1520. } else if (isVideoMessage()) {
  1521. return Data::kVideoMessageCacheTag;
  1522. } else if (isAnimation()) {
  1523. return Data::kAnimationCacheTag;
  1524. } else if (isWallPaper()) {
  1525. return Data::kImageCacheTag;
  1526. }
  1527. return 0;
  1528. }
  1529. LocationType DocumentData::locationType() const {
  1530. return isVoiceMessage()
  1531. ? AudioFileLocation
  1532. : isVideoFile()
  1533. ? VideoFileLocation
  1534. : DocumentFileLocation;
  1535. }
  1536. void DocumentData::forceIsStreamedAnimation() {
  1537. type = AnimatedDocument;
  1538. _additional = nullptr;
  1539. setMaybeSupportsStreaming(true);
  1540. }
  1541. bool DocumentData::isVoiceMessage() const {
  1542. return (type == VoiceDocument);
  1543. }
  1544. bool DocumentData::isVideoMessage() const {
  1545. return (type == RoundVideoDocument);
  1546. }
  1547. bool DocumentData::isAnimation() const {
  1548. return (type == AnimatedDocument)
  1549. || isVideoMessage()
  1550. || ((_filename.isEmpty()
  1551. || _nameType == Core::NameType::Image
  1552. || _nameType == Core::NameType::Video)
  1553. && hasMimeType(u"image/gif"_q)
  1554. && !(_flags & Flag::StreamingPlaybackFailed));
  1555. }
  1556. bool DocumentData::isGifv() const {
  1557. return (type == AnimatedDocument)
  1558. && hasMimeType(u"video/mp4"_q);
  1559. }
  1560. bool DocumentData::isTheme() const {
  1561. return _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive)
  1562. || _filename.endsWith(u".tdesktop-palette"_q, Qt::CaseInsensitive)
  1563. || (hasMimeType(u"application/x-tgtheme-tdesktop"_q)
  1564. && (_filename.isEmpty()
  1565. || _nameType == Core::NameType::ThemeFile));
  1566. }
  1567. bool DocumentData::isSong() const {
  1568. return (type == SongDocument);
  1569. }
  1570. bool DocumentData::isSongWithCover() const {
  1571. return isSong() && hasThumbnail();
  1572. }
  1573. bool DocumentData::isAudioFile() const {
  1574. if (isVoiceMessage() || isVideoFile()) {
  1575. return false;
  1576. } else if (isSong()) {
  1577. return true;
  1578. }
  1579. const auto prefix = u"audio/"_q;
  1580. if (!_mimeString.startsWith(prefix, Qt::CaseInsensitive)) {
  1581. if (_filename.endsWith(u".opus"_q, Qt::CaseInsensitive)) {
  1582. return true;
  1583. }
  1584. return false;
  1585. } else if (!_filename.isEmpty()
  1586. && _nameType != Core::NameType::Audio
  1587. && _nameType != Core::NameType::Video) {
  1588. return false;
  1589. }
  1590. const auto left = _mimeString.mid(prefix.size());
  1591. const auto types = { u"x-wav"_q, u"wav"_q, u"mp4"_q };
  1592. return ranges::contains(types, left);
  1593. }
  1594. bool DocumentData::isSharedMediaMusic() const {
  1595. return isSong();
  1596. }
  1597. bool DocumentData::isVideoFile() const {
  1598. return (type == VideoDocument);
  1599. }
  1600. bool DocumentData::isSilentVideo() const {
  1601. return _flags & Flag::SilentVideo;
  1602. }
  1603. crl::time DocumentData::duration() const {
  1604. return std::max(_duration, crl::time());
  1605. }
  1606. bool DocumentData::hasDuration() const {
  1607. return _duration >= 0;
  1608. }
  1609. bool DocumentData::isImage() const {
  1610. return (_flags & Flag::ImageType);
  1611. }
  1612. bool DocumentData::hasAttachedStickers() const {
  1613. return (_flags & Flag::HasAttachedStickers);
  1614. }
  1615. bool DocumentData::supportsStreaming() const {
  1616. return (_flags & kStreamingSupportedMask) == kStreamingSupportedMaybeYes;
  1617. }
  1618. void DocumentData::setNotSupportsStreaming() {
  1619. _flags &= ~kStreamingSupportedMask;
  1620. _flags |= kStreamingSupportedNo;
  1621. }
  1622. void DocumentData::setMaybeSupportsStreaming(bool supports) {
  1623. if ((_flags & kStreamingSupportedMask) == kStreamingSupportedNo) {
  1624. return;
  1625. }
  1626. _flags &= ~kStreamingSupportedMask;
  1627. _flags |= supports
  1628. ? kStreamingSupportedMaybeYes
  1629. : kStreamingSupportedMaybeNo;
  1630. }
  1631. void DocumentData::recountIsImage() {
  1632. const auto isImage = !isAnimation()
  1633. && !isVideoFile()
  1634. && Core::FileIsImage(filename(), mimeString());
  1635. if (isImage) {
  1636. _flags |= Flag::ImageType;
  1637. } else {
  1638. _flags &= ~Flag::ImageType;
  1639. }
  1640. }
  1641. void DocumentData::setRemoteLocation(
  1642. int32 dc,
  1643. uint64 access,
  1644. const QByteArray &fileReference) {
  1645. _fileReference = fileReference;
  1646. if (_dc != dc || _access != access) {
  1647. _dc = dc;
  1648. _access = access;
  1649. if (!isNull()) {
  1650. if (_location.check()) {
  1651. session().local().writeFileLocation(mediaKey(), _location);
  1652. } else {
  1653. _location = session().local().readFileLocation(mediaKey());
  1654. if (_location.inMediaCache()) {
  1655. setLoadedInMediaCacheLocation();
  1656. } else if (_location.isEmpty() && loadedInMediaCache()) {
  1657. session().local().writeFileLocation(
  1658. mediaKey(),
  1659. Core::FileLocation::InMediaCacheLocation());
  1660. }
  1661. }
  1662. }
  1663. }
  1664. }
  1665. void DocumentData::setStoryMedia(bool value) {
  1666. if (value) {
  1667. _flags |= Flag::StoryDocument;
  1668. setMaybeSupportsStreaming(true);
  1669. } else {
  1670. _flags &= ~Flag::StoryDocument;
  1671. }
  1672. }
  1673. bool DocumentData::storyMedia() const {
  1674. return (_flags & Flag::StoryDocument);
  1675. }
  1676. void DocumentData::setContentUrl(const QString &url) {
  1677. _url = url;
  1678. }
  1679. void DocumentData::setWebLocation(const WebFileLocation &location) {
  1680. _urlLocation = location;
  1681. }
  1682. void DocumentData::collectLocalData(not_null<DocumentData*> local) {
  1683. if (local == this) {
  1684. return;
  1685. }
  1686. _owner->cache().copyIfEmpty(local->cacheKey(), cacheKey());
  1687. if (const auto localMedia = local->activeMediaView()) {
  1688. auto media = createMediaView();
  1689. media->collectLocalData(localMedia.get());
  1690. _owner->keepAlive(std::move(media));
  1691. }
  1692. if (!local->_location.inMediaCache() && !local->_location.isEmpty()) {
  1693. _location = local->_location;
  1694. session().local().writeFileLocation(mediaKey(), _location);
  1695. }
  1696. }
  1697. PhotoData *LookupVideoCover(
  1698. not_null<DocumentData*> document,
  1699. HistoryItem *item) {
  1700. const auto media = item ? item->media() : nullptr;
  1701. if (const auto webpage = media ? media->webpage() : nullptr) {
  1702. if (webpage->document == document && webpage->photoIsVideoCover) {
  1703. return webpage->photo;
  1704. }
  1705. return nullptr;
  1706. }
  1707. return (media && media->document() == document)
  1708. ? media->videoCover()
  1709. : nullptr;
  1710. }