/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/file_download.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "mainwidget.h" #include "mainwindow.h" #include "core/application.h" #include "storage/localstorage.h" #include "platform/platform_file_utilities.h" #include "main/main_session.h" #include "apiwrap.h" #include "core/crash_reports.h" #include "base/bytes.h" #include "base/openssl_help.h" #include "facades.h" #include "app.h" FileLoader::FileLoader( const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading, uint8 cacheTag) : _session(&Auth()) , _autoLoading(autoLoading) , _cacheTag(cacheTag) , _filename(toFile) , _file(_filename) , _toCache(toCache) , _fromCloud(fromCloud) , _size(size) , _locationType(locationType) { Expects(!_filename.isEmpty() || (_size <= Storage::kMaxFileInMemory)); } FileLoader::~FileLoader() = default; Main::Session &FileLoader::session() const { return *_session; } void FileLoader::finishWithBytes(const QByteArray &data) { _data = data; _localStatus = LocalStatus::Loaded; if (!_filename.isEmpty() && _toCache == LoadToCacheAsWell) { if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { cancel(true); return; } _file.seek(0); if (_file.write(_data) != qint64(_data.size())) { cancel(true); return; } } _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; Platform::File::PostprocessDownloaded( QFileInfo(_file).absoluteFilePath()); } Auth().downloaderTaskFinished().notify(); } QByteArray FileLoader::imageFormat(const QSize &shrinkBox) const { if (_imageFormat.isEmpty() && _locationType == UnknownFileLocation) { readImage(shrinkBox); } return _imageFormat; } QImage FileLoader::imageData(const QSize &shrinkBox) const { if (_imageData.isNull() && _locationType == UnknownFileLocation) { readImage(shrinkBox); } return _imageData; } void FileLoader::readImage(const QSize &shrinkBox) const { auto format = QByteArray(); auto image = App::readImage(_data, &format, false); if (!image.isNull()) { if (!shrinkBox.isEmpty() && (image.width() > shrinkBox.width() || image.height() > shrinkBox.height())) { _imageData = image.scaled(shrinkBox, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { _imageData = std::move(image); } _imageFormat = format; } } Data::FileOrigin FileLoader::fileOrigin() const { return Data::FileOrigin(); } float64 FileLoader::currentProgress() const { if (_finished) return 1.; if (!fullSize()) return 0.; return snap(float64(currentOffset()) / fullSize(), 0., 1.); } int FileLoader::fullSize() const { return _size; } bool FileLoader::setFileName(const QString &fileName) { if (_toCache != LoadToCacheAsWell || !_filename.isEmpty()) { return fileName.isEmpty() || (fileName == _filename); } _filename = fileName; _file.setFileName(_filename); return true; } void FileLoader::permitLoadFromCloud() { _fromCloud = LoadFromCloudOrLocal; } void FileLoader::notifyAboutProgress() { emit progress(this); } void FileLoader::localLoaded( const StorageImageSaved &result, const QByteArray &imageFormat, const QImage &imageData) { _localLoading = nullptr; if (result.data.isEmpty()) { _localStatus = LocalStatus::NotFound; start(); return; } if (!imageData.isNull()) { _imageFormat = imageFormat; _imageData = imageData; } finishWithBytes(result.data); notifyAboutProgress(); } void FileLoader::start() { if (_finished || tryLoadLocal()) { return; } else if (_fromCloud == LoadFromLocalOnly) { cancel(); return; } if (!_filename.isEmpty() && _toCache == LoadToFileOnly && !_fileIsOpen) { _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { return cancel(true); } } startLoading(); } void FileLoader::loadLocal(const Storage::Cache::Key &key) { const auto readImage = (_locationType != AudioFileLocation); auto done = [=, guard = _localLoading.make_guard()]( QByteArray &&value, QImage &&image, QByteArray &&format) mutable { crl::on_main(std::move(guard), [ =, value = std::move(value), image = std::move(image), format = std::move(format) ]() mutable { localLoaded( StorageImageSaved(std::move(value)), format, std::move(image)); }); }; session().data().cache().get(key, [=, callback = std::move(done)]( QByteArray &&value) mutable { if (readImage) { crl::async([ value = std::move(value), done = std::move(callback) ]() mutable { auto format = QByteArray(); auto image = App::readImage(value, &format, false); if (!image.isNull()) { done( std::move(value), std::move(image), std::move(format)); } else { done(std::move(value), {}, {}); } }); } else { callback(std::move(value), {}, {}); } }); } bool FileLoader::tryLoadLocal() { if (_localStatus == LocalStatus::NotFound || _localStatus == LocalStatus::Loaded) { return false; } else if (_localStatus == LocalStatus::Loading) { return true; } const auto weak = QPointer(this); if (_toCache == LoadToCacheAsWell) { loadLocal(cacheKey()); emit progress(this); } if (!weak) { return false; } else if (_localStatus != LocalStatus::NotTried) { return _finished; } else if (_localLoading) { _localStatus = LocalStatus::Loading; return true; } _localStatus = LocalStatus::NotFound; return false; } void FileLoader::cancel() { cancel(false); } void FileLoader::cancel(bool fail) { const auto started = (currentOffset() > 0); cancelHook(); _cancelled = true; _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; _file.remove(); } _data = QByteArray(); const auto weak = QPointer(this); if (fail) { emit failed(this, started); } else { emit progress(this); } if (weak) { _filename = QString(); _file.setFileName(_filename); } } int FileLoader::currentOffset() const { return (_fileIsOpen ? _file.size() : _data.size()) - _skippedBytes; } bool FileLoader::writeResultPart(int offset, bytes::const_span buffer) { Expects(!_finished); if (buffer.empty()) { return true; } if (_fileIsOpen) { auto fsize = _file.size(); if (offset < fsize) { _skippedBytes -= buffer.size(); } else if (offset > fsize) { _skippedBytes += offset - fsize; } _file.seek(offset); if (_file.write(reinterpret_cast(buffer.data()), buffer.size()) != qint64(buffer.size())) { cancel(true); return false; } return true; } _data.reserve(offset + buffer.size()); if (offset > _data.size()) { _skippedBytes += offset - _data.size(); _data.resize(offset); } if (offset == _data.size()) { _data.append(reinterpret_cast(buffer.data()), buffer.size()); } else { _skippedBytes -= buffer.size(); if (int64(offset + buffer.size()) > _data.size()) { _data.resize(offset + buffer.size()); } const auto dst = bytes::make_detached_span(_data).subspan( offset, buffer.size()); bytes::copy(dst, buffer); } return true; } QByteArray FileLoader::readLoadedPartBack(int offset, int size) { Expects(offset >= 0 && size > 0); if (_fileIsOpen) { if (_file.openMode() == QIODevice::WriteOnly) { _file.close(); _fileIsOpen = _file.open(QIODevice::ReadWrite); if (!_fileIsOpen) { cancel(true); return QByteArray(); } } if (!_file.seek(offset)) { return QByteArray(); } auto result = _file.read(size); return (result.size() == size) ? result : QByteArray(); } return (offset + size <= _data.size()) ? _data.mid(offset, size) : QByteArray(); } bool FileLoader::finalizeResult() { Expects(!_finished); if (!_filename.isEmpty() && (_toCache == LoadToCacheAsWell)) { if (!_fileIsOpen) { _fileIsOpen = _file.open(QIODevice::WriteOnly); } _file.seek(0); if (!_fileIsOpen || _file.write(_data) != qint64(_data.size())) { cancel(true); return false; } } _finished = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; Platform::File::PostprocessDownloaded( QFileInfo(_file).absoluteFilePath()); } if (_localStatus == LocalStatus::NotFound) { if (const auto key = fileLocationKey()) { if (!_filename.isEmpty()) { Local::writeFileLocation(*key, FileLocation(_filename)); } } if ((_toCache == LoadToCacheAsWell) && (_data.size() <= Storage::kMaxFileInMemory)) { session().data().cache().put( cacheKey(), Storage::Cache::Database::TaggedValue( base::duplicate(_data), _cacheTag)); } } Auth().downloaderTaskFinished().notify(); return true; }