file_download.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  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 "storage/file_download.h"
  8. #include "data/data_document.h"
  9. #include "data/data_session.h"
  10. #include "data/data_file_origin.h"
  11. #include "mainwidget.h"
  12. #include "mainwindow.h"
  13. #include "core/application.h"
  14. #include "core/file_location.h"
  15. #include "storage/storage_account.h"
  16. #include "storage/file_download_mtproto.h"
  17. #include "storage/file_download_web.h"
  18. #include "platform/platform_file_utilities.h"
  19. #include "main/main_session.h"
  20. #include "apiwrap.h"
  21. #include "core/crash_reports.h"
  22. #include "base/bytes.h"
  23. namespace {
  24. class FromMemoryLoader final : public FileLoader {
  25. public:
  26. FromMemoryLoader(
  27. not_null<Main::Session*> session,
  28. const QByteArray &data,
  29. const QString &toFile,
  30. int64 loadSize,
  31. int64 fullSize,
  32. LocationType locationType,
  33. LoadToCacheSetting toCache,
  34. LoadFromCloudSetting fromCloud,
  35. bool autoLoading,
  36. uint8 cacheTag);
  37. private:
  38. Storage::Cache::Key cacheKey() const override;
  39. std::optional<MediaKey> fileLocationKey() const override;
  40. void cancelHook() override;
  41. void startLoading() override;
  42. QByteArray _data;
  43. };
  44. FromMemoryLoader::FromMemoryLoader(
  45. not_null<Main::Session*> session,
  46. const QByteArray &data,
  47. const QString &toFile,
  48. int64 loadSize,
  49. int64 fullSize,
  50. LocationType locationType,
  51. LoadToCacheSetting toCache,
  52. LoadFromCloudSetting fromCloud,
  53. bool autoLoading,
  54. uint8 cacheTag
  55. ) : FileLoader(
  56. session,
  57. toFile,
  58. loadSize,
  59. fullSize,
  60. locationType,
  61. toCache,
  62. fromCloud,
  63. autoLoading,
  64. cacheTag)
  65. , _data(data) {
  66. }
  67. Storage::Cache::Key FromMemoryLoader::cacheKey() const {
  68. return {};
  69. }
  70. std::optional<MediaKey> FromMemoryLoader::fileLocationKey() const {
  71. return std::nullopt;
  72. }
  73. void FromMemoryLoader::cancelHook() {
  74. }
  75. void FromMemoryLoader::startLoading() {
  76. finishWithBytes(_data);
  77. }
  78. } // namespace
  79. FileLoader::FileLoader(
  80. not_null<Main::Session*> session,
  81. const QString &toFile,
  82. int64 loadSize,
  83. int64 fullSize,
  84. LocationType locationType,
  85. LoadToCacheSetting toCache,
  86. LoadFromCloudSetting fromCloud,
  87. bool autoLoading,
  88. uint8 cacheTag)
  89. : _session(session)
  90. , _autoLoading(autoLoading)
  91. , _cacheTag(cacheTag)
  92. , _filename(toFile)
  93. , _file(_filename)
  94. , _toCache(toCache)
  95. , _fromCloud(fromCloud)
  96. , _loadSize(loadSize)
  97. , _fullSize(fullSize)
  98. , _locationType(locationType) {
  99. Expects(_loadSize <= _fullSize);
  100. Expects(!_filename.isEmpty() || (_fullSize <= Storage::kMaxFileInMemory));
  101. }
  102. FileLoader::~FileLoader() {
  103. Expects(_finished);
  104. }
  105. Main::Session &FileLoader::session() const {
  106. return *_session;
  107. }
  108. void FileLoader::finishWithBytes(const QByteArray &data) {
  109. _data = data;
  110. _localStatus = LocalStatus::Loaded;
  111. if (!_filename.isEmpty() && _toCache == LoadToCacheAsWell) {
  112. if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly);
  113. if (!_fileIsOpen) {
  114. cancel(FailureReason::FileWriteFailure);
  115. return;
  116. }
  117. _file.seek(0);
  118. if (_file.write(_data) != qint64(_data.size())) {
  119. cancel(FailureReason::FileWriteFailure);
  120. return;
  121. }
  122. }
  123. _finished = true;
  124. if (_fileIsOpen) {
  125. _file.close();
  126. _fileIsOpen = false;
  127. Platform::File::PostprocessDownloaded(
  128. QFileInfo(_file).absoluteFilePath());
  129. }
  130. const auto session = _session;
  131. _updates.fire_done();
  132. session->notifyDownloaderTaskFinished();
  133. }
  134. QImage FileLoader::imageData(int progressiveSizeLimit) const {
  135. if (_imageData.isNull() && _locationType == UnknownFileLocation) {
  136. readImage(progressiveSizeLimit);
  137. }
  138. return _imageData;
  139. }
  140. void FileLoader::readImage(int progressiveSizeLimit) const {
  141. const auto buffer = progressiveSizeLimit
  142. ? QByteArray::fromRawData(_data.data(), progressiveSizeLimit)
  143. : _data;
  144. auto read = Images::Read({ .content = buffer });
  145. if (!read.image.isNull()) {
  146. _imageData = std::move(read.image);
  147. _imageFormat = read.format;
  148. }
  149. }
  150. Data::FileOrigin FileLoader::fileOrigin() const {
  151. return Data::FileOrigin();
  152. }
  153. float64 FileLoader::currentProgress() const {
  154. return _finished
  155. ? 1.
  156. : !_loadSize
  157. ? 0.
  158. : std::clamp(float64(currentOffset()) / _loadSize, 0., 1.);
  159. }
  160. bool FileLoader::setFileName(const QString &fileName) {
  161. if (_toCache != LoadToCacheAsWell || !_filename.isEmpty()) {
  162. return fileName.isEmpty() || (fileName == _filename);
  163. }
  164. _filename = fileName;
  165. _file.setFileName(_filename);
  166. return true;
  167. }
  168. void FileLoader::permitLoadFromCloud() {
  169. _fromCloud = LoadFromCloudOrLocal;
  170. }
  171. void FileLoader::increaseLoadSize(int64 size, bool autoLoading) {
  172. Expects(size > _loadSize);
  173. Expects(size <= _fullSize);
  174. _loadSize = size;
  175. _autoLoading = autoLoading;
  176. }
  177. void FileLoader::notifyAboutProgress() {
  178. _updates.fire({});
  179. }
  180. void FileLoader::localLoaded(
  181. const StorageImageSaved &result,
  182. const QByteArray &imageFormat,
  183. const QImage &imageData) {
  184. _localLoading = nullptr;
  185. if (result.data.isEmpty()) {
  186. _localStatus = LocalStatus::NotFound;
  187. start();
  188. return;
  189. }
  190. const auto partial = result.data.startsWith("partial:");
  191. constexpr auto kPrefix = 8;
  192. if (partial && result.data.size() < _loadSize + kPrefix) {
  193. _localStatus = LocalStatus::NotFound;
  194. if (checkForOpen()) {
  195. startLoadingWithPartial(result.data);
  196. }
  197. return;
  198. }
  199. if (!imageData.isNull()) {
  200. _imageFormat = imageFormat;
  201. _imageData = imageData;
  202. }
  203. finishWithBytes(partial
  204. ? QByteArray::fromRawData(
  205. result.data.data() + kPrefix,
  206. result.data.size() - kPrefix)
  207. : result.data);
  208. }
  209. void FileLoader::start() {
  210. if (_finished || tryLoadLocal()) {
  211. return;
  212. } else if (_fromCloud == LoadFromLocalOnly) {
  213. cancel();
  214. return;
  215. }
  216. if (checkForOpen()) {
  217. startLoading();
  218. }
  219. }
  220. bool FileLoader::checkForOpen() {
  221. if (_filename.isEmpty()
  222. || (_toCache != LoadToFileOnly)
  223. || _fileIsOpen) {
  224. return true;
  225. }
  226. _fileIsOpen = _file.open(QIODevice::WriteOnly);
  227. if (_fileIsOpen) {
  228. return true;
  229. }
  230. cancel(FailureReason::FileWriteFailure);
  231. return false;
  232. }
  233. void FileLoader::loadLocal(const Storage::Cache::Key &key) {
  234. const auto readImage = (_locationType != AudioFileLocation);
  235. auto done = [=, guard = _localLoading.make_guard()](
  236. QByteArray &&value,
  237. QImage &&image,
  238. QByteArray &&format) mutable {
  239. crl::on_main(std::move(guard), [
  240. =,
  241. value = std::move(value),
  242. image = std::move(image),
  243. format = std::move(format)
  244. ]() mutable {
  245. localLoaded(
  246. StorageImageSaved(std::move(value)),
  247. format,
  248. std::move(image));
  249. });
  250. };
  251. _session->data().cache().get(key, [=, callback = std::move(done)](
  252. QByteArray &&value) mutable {
  253. if (readImage && !value.startsWith("partial:")) {
  254. crl::async([
  255. value = std::move(value),
  256. done = std::move(callback)
  257. ]() mutable {
  258. auto read = Images::Read({ .content = value });
  259. if (!read.image.isNull()) {
  260. done(
  261. std::move(value),
  262. std::move(read.image),
  263. std::move(read.format));
  264. } else {
  265. done(std::move(value), {}, {});
  266. }
  267. });
  268. } else {
  269. callback(std::move(value), {}, {});
  270. }
  271. });
  272. }
  273. bool FileLoader::tryLoadLocal() {
  274. if (_localStatus == LocalStatus::NotFound
  275. || _localStatus == LocalStatus::Loaded) {
  276. return false;
  277. } else if (_localStatus == LocalStatus::Loading) {
  278. return true;
  279. }
  280. if (_toCache == LoadToCacheAsWell) {
  281. const auto key = cacheKey();
  282. if (key.low || key.high) {
  283. loadLocal(key);
  284. notifyAboutProgress();
  285. }
  286. }
  287. if (_localStatus != LocalStatus::NotTried) {
  288. return _finished;
  289. } else if (_localLoading) {
  290. _localStatus = LocalStatus::Loading;
  291. return true;
  292. }
  293. _localStatus = LocalStatus::NotFound;
  294. return false;
  295. }
  296. void FileLoader::cancel() {
  297. cancel(FailureReason::NoFailure);
  298. }
  299. void FileLoader::cancel(FailureReason fail) {
  300. const auto started = (currentOffset() > 0);
  301. cancelHook();
  302. _cancelled = true;
  303. _finished = true;
  304. if (_fileIsOpen) {
  305. _file.close();
  306. _fileIsOpen = false;
  307. _file.remove();
  308. }
  309. _data = QByteArray();
  310. const auto weak = base::make_weak(this);
  311. if (fail != FailureReason::NoFailure) {
  312. _updates.fire_error_copy({ fail, started });
  313. } else {
  314. _updates.fire_done();
  315. }
  316. if (weak) {
  317. _filename = QString();
  318. _file.setFileName(_filename);
  319. }
  320. }
  321. int64 FileLoader::currentOffset() const {
  322. return (_fileIsOpen ? _file.size() : _data.size()) - _skippedBytes;
  323. }
  324. bool FileLoader::writeResultPart(int64 offset, bytes::const_span buffer) {
  325. Expects(!_finished);
  326. if (buffer.empty()) {
  327. return true;
  328. }
  329. if (_fileIsOpen) {
  330. auto fsize = _file.size();
  331. if (offset < fsize) {
  332. _skippedBytes -= buffer.size();
  333. } else if (offset > fsize) {
  334. _skippedBytes += offset - fsize;
  335. }
  336. _file.seek(offset);
  337. if (_file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size()) != qint64(buffer.size())) {
  338. cancel(FailureReason::FileWriteFailure);
  339. return false;
  340. }
  341. return true;
  342. }
  343. _data.reserve(offset + buffer.size());
  344. if (offset > _data.size()) {
  345. _skippedBytes += offset - _data.size();
  346. _data.resize(offset);
  347. }
  348. if (offset == _data.size()) {
  349. _data.append(reinterpret_cast<const char*>(buffer.data()), buffer.size());
  350. } else {
  351. _skippedBytes -= buffer.size();
  352. if (int64(offset + buffer.size()) > _data.size()) {
  353. _data.resize(offset + buffer.size());
  354. }
  355. const auto dst = bytes::make_detached_span(_data).subspan(
  356. offset,
  357. buffer.size());
  358. bytes::copy(dst, buffer);
  359. }
  360. return true;
  361. }
  362. QByteArray FileLoader::readLoadedPartBack(int64 offset, int size) {
  363. Expects(offset >= 0 && size > 0);
  364. if (_fileIsOpen) {
  365. if (_file.openMode() == QIODevice::WriteOnly) {
  366. _file.close();
  367. _fileIsOpen = _file.open(QIODevice::ReadWrite);
  368. if (!_fileIsOpen) {
  369. cancel(FailureReason::FileWriteFailure);
  370. return QByteArray();
  371. }
  372. }
  373. if (!_file.seek(offset)) {
  374. return QByteArray();
  375. }
  376. auto result = _file.read(size);
  377. return (result.size() == size) ? result : QByteArray();
  378. }
  379. return (offset + size <= _data.size())
  380. ? _data.mid(offset, size)
  381. : QByteArray();
  382. }
  383. bool FileLoader::finalizeResult() {
  384. Expects(!_finished);
  385. if (!_filename.isEmpty() && (_toCache == LoadToCacheAsWell)) {
  386. if (!_fileIsOpen) {
  387. _fileIsOpen = _file.open(QIODevice::WriteOnly);
  388. }
  389. _file.seek(0);
  390. if (!_fileIsOpen || _file.write(_data) != qint64(_data.size())) {
  391. cancel(FailureReason::FileWriteFailure);
  392. return false;
  393. }
  394. }
  395. _finished = true;
  396. if (_fileIsOpen) {
  397. _file.close();
  398. _fileIsOpen = false;
  399. Platform::File::PostprocessDownloaded(
  400. QFileInfo(_file).absoluteFilePath());
  401. }
  402. if (_localStatus == LocalStatus::NotFound) {
  403. if (const auto key = fileLocationKey()) {
  404. if (!_filename.isEmpty()) {
  405. _session->local().writeFileLocation(
  406. *key,
  407. Core::FileLocation(_filename));
  408. }
  409. }
  410. const auto key = cacheKey();
  411. if ((_toCache == LoadToCacheAsWell)
  412. && (_data.size() <= Storage::kMaxFileInMemory)
  413. && (key.low || key.high)) {
  414. _session->data().cache().put(
  415. cacheKey(),
  416. Storage::Cache::Database::TaggedValue(
  417. base::duplicate((!_fullSize || _data.size() == _fullSize)
  418. ? _data
  419. : ("partial:" + _data)),
  420. _cacheTag));
  421. }
  422. }
  423. const auto session = _session;
  424. _updates.fire_done();
  425. session->notifyDownloaderTaskFinished();
  426. return true;
  427. }
  428. std::unique_ptr<FileLoader> CreateFileLoader(
  429. not_null<Main::Session*> session,
  430. const DownloadLocation &location,
  431. Data::FileOrigin origin,
  432. const QString &toFile,
  433. int64 loadSize,
  434. int64 fullSize,
  435. LocationType locationType,
  436. LoadToCacheSetting toCache,
  437. LoadFromCloudSetting fromCloud,
  438. bool autoLoading,
  439. uint8 cacheTag) {
  440. auto result = std::unique_ptr<FileLoader>();
  441. v::match(location.data, [&](const StorageFileLocation &data) {
  442. result = std::make_unique<mtpFileLoader>(
  443. session,
  444. data,
  445. origin,
  446. locationType,
  447. toFile,
  448. loadSize,
  449. fullSize,
  450. toCache,
  451. fromCloud,
  452. autoLoading,
  453. cacheTag);
  454. }, [&](const WebFileLocation &data) {
  455. result = std::make_unique<mtpFileLoader>(
  456. session,
  457. data,
  458. loadSize,
  459. fullSize,
  460. fromCloud,
  461. autoLoading,
  462. cacheTag);
  463. }, [&](const GeoPointLocation &data) {
  464. result = std::make_unique<mtpFileLoader>(
  465. session,
  466. data,
  467. loadSize,
  468. fullSize,
  469. fromCloud,
  470. autoLoading,
  471. cacheTag);
  472. }, [&](const PlainUrlLocation &data) {
  473. result = std::make_unique<webFileLoader>(
  474. session,
  475. data.url,
  476. toFile,
  477. fromCloud,
  478. autoLoading,
  479. cacheTag);
  480. }, [&](const AudioAlbumThumbLocation &data) {
  481. result = std::make_unique<mtpFileLoader>(
  482. session,
  483. data,
  484. loadSize,
  485. fullSize,
  486. fromCloud,
  487. autoLoading,
  488. cacheTag);
  489. }, [&](const InMemoryLocation &data) {
  490. result = std::make_unique<FromMemoryLoader>(
  491. session,
  492. data.bytes,
  493. toFile,
  494. loadSize,
  495. fullSize,
  496. locationType,
  497. toCache,
  498. LoadFromCloudOrLocal,
  499. autoLoading,
  500. cacheTag);
  501. });
  502. Ensures(result != nullptr);
  503. return result;
  504. }