778 lines
15 KiB
C++
778 lines
15 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "ui/image/image_source.h"
|
|
|
|
#include "storage/file_download.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "storage/cache/storage_cache_database.h"
|
|
#include "history/history_item.h"
|
|
#include "history/history.h"
|
|
#include "auth_session.h"
|
|
|
|
namespace Images {
|
|
|
|
ImageSource::ImageSource(QImage &&data, const QByteArray &format)
|
|
: _data(std::move(data))
|
|
, _format(format)
|
|
, _width(_data.width())
|
|
, _height(_data.height()) {
|
|
}
|
|
|
|
void ImageSource::load(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
if (_data.isNull() && !_bytes.isEmpty()) {
|
|
_data = App::readImage(_bytes, &_format, false);
|
|
}
|
|
}
|
|
|
|
void ImageSource::loadEvenCancelled(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
load(origin, loadFirst, prior);
|
|
}
|
|
|
|
QImage ImageSource::takeLoaded() {
|
|
load({}, false, false);
|
|
return _data;
|
|
}
|
|
|
|
void ImageSource::unload() {
|
|
if (_bytes.isEmpty() && !_data.isNull()) {
|
|
if (_format != "JPG") {
|
|
_format = "PNG";
|
|
}
|
|
{
|
|
QBuffer buffer(&_bytes);
|
|
_data.save(&buffer, _format);
|
|
}
|
|
Assert(!_bytes.isEmpty());
|
|
}
|
|
_data = QImage();
|
|
}
|
|
|
|
void ImageSource::automaticLoad(
|
|
Data::FileOrigin origin,
|
|
const HistoryItem *item) {
|
|
}
|
|
|
|
void ImageSource::automaticLoadSettingsChanged() {
|
|
}
|
|
|
|
bool ImageSource::loading() {
|
|
return false;
|
|
}
|
|
|
|
bool ImageSource::displayLoading() {
|
|
return false;
|
|
}
|
|
|
|
void ImageSource::cancel() {
|
|
}
|
|
|
|
float64 ImageSource::progress() {
|
|
return 1.;
|
|
}
|
|
|
|
int ImageSource::loadOffset() {
|
|
return 0;
|
|
}
|
|
|
|
const StorageImageLocation &ImageSource::location() {
|
|
return StorageImageLocation::Null;
|
|
}
|
|
|
|
void ImageSource::refreshFileReference(const QByteArray &data) {
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> ImageSource::cacheKey() {
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ImageSource::setDelayedStorageLocation(
|
|
const StorageImageLocation &location) {
|
|
}
|
|
|
|
void ImageSource::performDelayedLoad(Data::FileOrigin origin) {
|
|
}
|
|
|
|
bool ImageSource::isDelayedStorageImage() const {
|
|
return false;
|
|
}
|
|
|
|
void ImageSource::setImageBytes(const QByteArray &bytes) {
|
|
}
|
|
|
|
int ImageSource::width() {
|
|
return _width;
|
|
}
|
|
|
|
int ImageSource::height() {
|
|
return _height;
|
|
}
|
|
|
|
int ImageSource::bytesSize() {
|
|
return _bytes.size();
|
|
}
|
|
|
|
void ImageSource::setInformation(int size, int width, int height) {
|
|
if (width && height) {
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
}
|
|
|
|
QByteArray ImageSource::bytesForCache() {
|
|
auto result = QByteArray();
|
|
{
|
|
QBuffer buffer(&result);
|
|
if (!_data.save(&buffer, _format)) {
|
|
if (_data.save(&buffer, "PNG")) {
|
|
_format = "PNG";
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
LocalFileSource::LocalFileSource(
|
|
const QString &path,
|
|
const QByteArray &content,
|
|
const QByteArray &format,
|
|
QImage &&data)
|
|
: _path(path)
|
|
, _bytes(content)
|
|
, _format(format)
|
|
, _data(std::move(data))
|
|
, _width(_data.width())
|
|
, _height(_data.height()) {
|
|
}
|
|
|
|
void LocalFileSource::load(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
if (!_data.isNull()) {
|
|
return;
|
|
}
|
|
if (_bytes.isEmpty()) {
|
|
QFile f(_path);
|
|
if (f.size() <= App::kImageSizeLimit && f.open(QIODevice::ReadOnly)) {
|
|
_bytes = f.readAll();
|
|
}
|
|
if (_bytes.isEmpty()) {
|
|
_bytes = "(bad)";
|
|
}
|
|
}
|
|
if (_bytes != "(bad)") {
|
|
_data = App::readImage(_bytes, &_format, false, nullptr);
|
|
}
|
|
_width = std::max(_data.width(), 1);
|
|
_height = std::max(_data.height(), 1);
|
|
}
|
|
|
|
void LocalFileSource::loadEvenCancelled(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
load(origin, loadFirst, prior);
|
|
}
|
|
|
|
QImage LocalFileSource::takeLoaded() {
|
|
return std::move(_data);
|
|
}
|
|
|
|
void LocalFileSource::unload() {
|
|
_data = QImage();
|
|
}
|
|
|
|
void LocalFileSource::automaticLoad(
|
|
Data::FileOrigin origin,
|
|
const HistoryItem *item) {
|
|
}
|
|
|
|
void LocalFileSource::automaticLoadSettingsChanged() {
|
|
}
|
|
|
|
bool LocalFileSource::loading() {
|
|
return false;
|
|
}
|
|
|
|
bool LocalFileSource::displayLoading() {
|
|
return false;
|
|
}
|
|
|
|
void LocalFileSource::cancel() {
|
|
}
|
|
|
|
float64 LocalFileSource::progress() {
|
|
return 1.;
|
|
}
|
|
|
|
int LocalFileSource::loadOffset() {
|
|
return 0;
|
|
}
|
|
|
|
const StorageImageLocation &LocalFileSource::location() {
|
|
return StorageImageLocation::Null;
|
|
}
|
|
|
|
void LocalFileSource::refreshFileReference(const QByteArray &data) {
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> LocalFileSource::cacheKey() {
|
|
return std::nullopt;
|
|
}
|
|
|
|
void LocalFileSource::setDelayedStorageLocation(
|
|
const StorageImageLocation &location) {
|
|
}
|
|
|
|
void LocalFileSource::performDelayedLoad(Data::FileOrigin origin) {
|
|
}
|
|
|
|
bool LocalFileSource::isDelayedStorageImage() const {
|
|
return false;
|
|
}
|
|
|
|
void LocalFileSource::setImageBytes(const QByteArray &bytes) {
|
|
_bytes = bytes;
|
|
load({}, false, true);
|
|
}
|
|
|
|
int LocalFileSource::width() {
|
|
ensureDimensionsKnown();
|
|
return _width;
|
|
}
|
|
|
|
int LocalFileSource::height() {
|
|
ensureDimensionsKnown();
|
|
return _height;
|
|
}
|
|
|
|
int LocalFileSource::bytesSize() {
|
|
ensureDimensionsKnown();
|
|
return _bytes.size();
|
|
}
|
|
|
|
void LocalFileSource::setInformation(int size, int width, int height) {
|
|
ensureDimensionsKnown(); // First load _bytes.
|
|
if (width && height) {
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
}
|
|
|
|
void LocalFileSource::ensureDimensionsKnown() {
|
|
if (!_width || !_height) {
|
|
load({}, false, false);
|
|
}
|
|
}
|
|
|
|
QByteArray LocalFileSource::bytesForCache() {
|
|
ensureDimensionsKnown();
|
|
return (_bytes == "(bad)") ? QByteArray() : _bytes;
|
|
}
|
|
|
|
QImage RemoteSource::takeLoaded() {
|
|
if (!loaderValid() || !_loader->finished()) {
|
|
return QImage();
|
|
}
|
|
|
|
auto data = _loader->imageData(shrinkBox());
|
|
if (data.isNull()) {
|
|
destroyLoader(CancelledFileLoader);
|
|
return QImage();
|
|
}
|
|
|
|
setInformation(_loader->bytes().size(), data.width(), data.height());
|
|
|
|
destroyLoader();
|
|
|
|
return data;
|
|
}
|
|
|
|
bool RemoteSource::loaderValid() const {
|
|
return _loader && !cancelled();
|
|
}
|
|
|
|
void RemoteSource::destroyLoader(FileLoader *newValue) {
|
|
Expects(loaderValid());
|
|
|
|
const auto loader = std::exchange(_loader, newValue);
|
|
if (cancelled()) {
|
|
loader->cancel();
|
|
}
|
|
loader->stop();
|
|
delete loader;
|
|
}
|
|
|
|
void RemoteSource::loadLocal() {
|
|
if (loaderValid()) {
|
|
return;
|
|
}
|
|
|
|
_loader = createLoader(Data::FileOrigin(), LoadFromLocalOnly, true);
|
|
if (_loader) _loader->start();
|
|
}
|
|
|
|
void RemoteSource::setImageBytes(const QByteArray &bytes) {
|
|
if (bytes.isEmpty()) {
|
|
return;
|
|
} else if (loaderValid()) {
|
|
unload();
|
|
}
|
|
_loader = createLoader({}, LoadFromLocalOnly, true);
|
|
_loader->finishWithBytes(bytes);
|
|
|
|
const auto location = this->location();
|
|
if (!location.isNull()
|
|
&& !bytes.isEmpty()
|
|
&& bytes.size() <= Storage::kMaxFileInMemory) {
|
|
Auth().data().cache().putIfEmpty(
|
|
Data::StorageCacheKey(location),
|
|
Storage::Cache::Database::TaggedValue(
|
|
base::duplicate(bytes),
|
|
Data::kImageCacheTag));
|
|
}
|
|
}
|
|
|
|
bool RemoteSource::loading() {
|
|
return loaderValid();
|
|
}
|
|
|
|
void RemoteSource::automaticLoad(
|
|
Data::FileOrigin origin,
|
|
const HistoryItem *item) {
|
|
if (!item || cancelled()) {
|
|
return;
|
|
}
|
|
const auto loadFromCloud = Data::AutoDownload::Should(
|
|
Auth().settings().autoDownload(),
|
|
item->history()->peer,
|
|
this);
|
|
|
|
if (_loader) {
|
|
if (loadFromCloud) {
|
|
_loader->permitLoadFromCloud();
|
|
}
|
|
} else {
|
|
_loader = createLoader(
|
|
origin,
|
|
loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly,
|
|
true);
|
|
}
|
|
if (loaderValid()) {
|
|
_loader->start();
|
|
}
|
|
}
|
|
|
|
void RemoteSource::automaticLoadSettingsChanged() {
|
|
if (_loader == CancelledFileLoader) {
|
|
_loader = nullptr;
|
|
}
|
|
}
|
|
|
|
void RemoteSource::load(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
if (!_loader) {
|
|
_loader = createLoader(origin, LoadFromCloudOrLocal, false);
|
|
}
|
|
if (loaderValid()) {
|
|
_loader->start(loadFirst, prior);
|
|
}
|
|
}
|
|
|
|
bool RemoteSource::cancelled() const {
|
|
return (_loader == CancelledFileLoader);
|
|
}
|
|
|
|
void RemoteSource::loadEvenCancelled(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
if (cancelled()) {
|
|
_loader = nullptr;
|
|
}
|
|
return load(origin, loadFirst, prior);
|
|
}
|
|
|
|
bool RemoteSource::displayLoading() {
|
|
return loaderValid()
|
|
&& (!_loader->loadingLocal() || !_loader->autoLoading());
|
|
}
|
|
|
|
void RemoteSource::cancel() {
|
|
if (!loaderValid()) return;
|
|
|
|
destroyLoader(CancelledFileLoader);
|
|
}
|
|
|
|
void RemoteSource::unload() {
|
|
if (loaderValid()) {
|
|
delete base::take(_loader);
|
|
}
|
|
}
|
|
|
|
float64 RemoteSource::progress() {
|
|
return loaderValid() ? _loader->currentProgress() : 0.;
|
|
}
|
|
|
|
int RemoteSource::loadOffset() {
|
|
return loaderValid() ? _loader->currentOffset() : 0;
|
|
}
|
|
|
|
RemoteSource::~RemoteSource() {
|
|
unload();
|
|
}
|
|
|
|
const StorageImageLocation &RemoteSource::location() {
|
|
return StorageImageLocation::Null;
|
|
}
|
|
|
|
void RemoteSource::refreshFileReference(const QByteArray &data) {
|
|
}
|
|
|
|
void RemoteSource::setDelayedStorageLocation(
|
|
const StorageImageLocation &location) {
|
|
}
|
|
|
|
void RemoteSource::performDelayedLoad(Data::FileOrigin origin) {
|
|
}
|
|
|
|
bool RemoteSource::isDelayedStorageImage() const {
|
|
return false;
|
|
}
|
|
|
|
QByteArray RemoteSource::bytesForCache() {
|
|
return QByteArray();
|
|
}
|
|
|
|
StorageSource::StorageSource(const StorageImageLocation &location, int size)
|
|
: _location(location)
|
|
, _size(size) {
|
|
}
|
|
|
|
void StorageSource::refreshFileReference(const QByteArray &data) {
|
|
_location.refreshFileReference(data);
|
|
}
|
|
|
|
const StorageImageLocation &StorageSource::location() {
|
|
return _location;
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> StorageSource::cacheKey() {
|
|
return _location.isNull()
|
|
? std::nullopt
|
|
: base::make_optional(Data::StorageCacheKey(_location));
|
|
}
|
|
|
|
int StorageSource::width() {
|
|
return _location.width();
|
|
}
|
|
|
|
int StorageSource::height() {
|
|
return _location.height();
|
|
}
|
|
|
|
int StorageSource::bytesSize() {
|
|
return _size;
|
|
}
|
|
|
|
void StorageSource::setInformation(int size, int width, int height) {
|
|
if (size) {
|
|
_size = size;
|
|
}
|
|
if (width && height) {
|
|
_location.setSize(width, height);
|
|
}
|
|
}
|
|
|
|
QSize StorageSource::shrinkBox() const {
|
|
return QSize();
|
|
}
|
|
|
|
FileLoader *StorageSource::createLoader(
|
|
Data::FileOrigin origin,
|
|
LoadFromCloudSetting fromCloud,
|
|
bool autoLoading) {
|
|
if (_location.isNull()) {
|
|
return nullptr;
|
|
}
|
|
return new mtpFileLoader(
|
|
&_location,
|
|
origin,
|
|
_size,
|
|
fromCloud,
|
|
autoLoading,
|
|
Data::kImageCacheTag);
|
|
}
|
|
|
|
WebCachedSource::WebCachedSource(
|
|
const WebFileLocation &location,
|
|
QSize box,
|
|
int size)
|
|
: _location(location)
|
|
, _box(box)
|
|
, _size(size) {
|
|
}
|
|
|
|
WebCachedSource::WebCachedSource(
|
|
const WebFileLocation &location,
|
|
int width,
|
|
int height,
|
|
int size)
|
|
: _location(location)
|
|
, _width(width)
|
|
, _height(height)
|
|
, _size(size) {
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> WebCachedSource::cacheKey() {
|
|
return _location.isNull()
|
|
? std::nullopt
|
|
: base::make_optional(Data::WebDocumentCacheKey(_location));
|
|
}
|
|
|
|
int WebCachedSource::width() {
|
|
return _width;
|
|
}
|
|
|
|
int WebCachedSource::height() {
|
|
return _height;
|
|
}
|
|
|
|
int WebCachedSource::bytesSize() {
|
|
return _size;
|
|
}
|
|
|
|
void WebCachedSource::setInformation(int size, int width, int height) {
|
|
if (size) {
|
|
_size = size;
|
|
}
|
|
if (width && height) {
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
}
|
|
|
|
QSize WebCachedSource::shrinkBox() const {
|
|
return _box;
|
|
}
|
|
|
|
FileLoader *WebCachedSource::createLoader(
|
|
Data::FileOrigin origin,
|
|
LoadFromCloudSetting fromCloud,
|
|
bool autoLoading) {
|
|
return _location.isNull()
|
|
? nullptr
|
|
: new mtpFileLoader(
|
|
&_location,
|
|
_size,
|
|
fromCloud,
|
|
autoLoading,
|
|
Data::kImageCacheTag);
|
|
}
|
|
|
|
GeoPointSource::GeoPointSource(const GeoPointLocation &location)
|
|
: _location(location) {
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> GeoPointSource::cacheKey() {
|
|
return Data::GeoPointCacheKey(_location);
|
|
}
|
|
|
|
int GeoPointSource::width() {
|
|
return _location.width * _location.scale;
|
|
}
|
|
|
|
int GeoPointSource::height() {
|
|
return _location.height * _location.scale;
|
|
}
|
|
|
|
int GeoPointSource::bytesSize() {
|
|
return _size;
|
|
}
|
|
|
|
void GeoPointSource::setInformation(int size, int width, int height) {
|
|
Expects(_location.scale != 0);
|
|
|
|
if (size) {
|
|
_size = size;
|
|
}
|
|
if (width && height) {
|
|
_location.width = width / _location.scale;
|
|
_location.height = height / _location.scale;
|
|
}
|
|
}
|
|
|
|
QSize GeoPointSource::shrinkBox() const {
|
|
return QSize();
|
|
}
|
|
|
|
FileLoader *GeoPointSource::createLoader(
|
|
Data::FileOrigin origin,
|
|
LoadFromCloudSetting fromCloud,
|
|
bool autoLoading) {
|
|
return new mtpFileLoader(
|
|
&_location,
|
|
_size,
|
|
fromCloud,
|
|
autoLoading,
|
|
Data::kImageCacheTag);
|
|
}
|
|
|
|
DelayedStorageSource::DelayedStorageSource()
|
|
: StorageSource(StorageImageLocation(), 0) {
|
|
}
|
|
|
|
DelayedStorageSource::DelayedStorageSource(int w, int h)
|
|
: StorageSource(StorageImageLocation(w, h, 0, 0, 0, 0, {}), 0) {
|
|
}
|
|
|
|
void DelayedStorageSource::setDelayedStorageLocation(
|
|
const StorageImageLocation &location) {
|
|
_location = location;
|
|
}
|
|
|
|
void DelayedStorageSource::performDelayedLoad(Data::FileOrigin origin) {
|
|
if (!_loadRequested) {
|
|
return;
|
|
}
|
|
_loadRequested = false;
|
|
if (_loadCancelled) {
|
|
return;
|
|
}
|
|
if (base::take(_loadFromCloud)) {
|
|
load(origin, false, true);
|
|
} else {
|
|
loadLocal();
|
|
}
|
|
}
|
|
|
|
void DelayedStorageSource::automaticLoad(
|
|
Data::FileOrigin origin,
|
|
const HistoryItem *item) {
|
|
if (_location.isNull()) {
|
|
if (!_loadCancelled && item) {
|
|
const auto loadFromCloud = Data::AutoDownload::Should(
|
|
Auth().settings().autoDownload(),
|
|
item->history()->peer,
|
|
this);
|
|
|
|
if (_loadRequested) {
|
|
if (loadFromCloud) _loadFromCloud = loadFromCloud;
|
|
} else {
|
|
_loadFromCloud = loadFromCloud;
|
|
_loadRequested = true;
|
|
}
|
|
}
|
|
} else {
|
|
StorageSource::automaticLoad(origin, item);
|
|
}
|
|
}
|
|
|
|
void DelayedStorageSource::automaticLoadSettingsChanged() {
|
|
if (_loadCancelled) _loadCancelled = false;
|
|
StorageSource::automaticLoadSettingsChanged();
|
|
}
|
|
|
|
void DelayedStorageSource::load(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
if (_location.isNull()) {
|
|
_loadRequested = _loadFromCloud = true;
|
|
} else {
|
|
StorageSource::load(origin, loadFirst, prior);
|
|
}
|
|
}
|
|
|
|
void DelayedStorageSource::loadEvenCancelled(
|
|
Data::FileOrigin origin,
|
|
bool loadFirst,
|
|
bool prior) {
|
|
_loadCancelled = false;
|
|
StorageSource::loadEvenCancelled(origin, loadFirst, prior);
|
|
}
|
|
|
|
bool DelayedStorageSource::displayLoading() {
|
|
return _location.isNull() ? true : StorageSource::displayLoading();
|
|
}
|
|
|
|
void DelayedStorageSource::cancel() {
|
|
if (_loadRequested) {
|
|
_loadRequested = false;
|
|
}
|
|
StorageSource::cancel();
|
|
}
|
|
|
|
bool DelayedStorageSource::isDelayedStorageImage() const {
|
|
return true;
|
|
}
|
|
|
|
WebUrlSource::WebUrlSource(const QString &url, QSize box)
|
|
: _url(url)
|
|
, _box(box) {
|
|
}
|
|
|
|
WebUrlSource::WebUrlSource(const QString &url, int width, int height)
|
|
: _url(url)
|
|
, _width(width)
|
|
, _height(height) {
|
|
}
|
|
|
|
std::optional<Storage::Cache::Key> WebUrlSource::cacheKey() {
|
|
return Data::UrlCacheKey(_url);
|
|
}
|
|
|
|
int WebUrlSource::width() {
|
|
return _width;
|
|
}
|
|
|
|
int WebUrlSource::height() {
|
|
return _height;
|
|
}
|
|
|
|
int WebUrlSource::bytesSize() {
|
|
return _size;
|
|
}
|
|
|
|
void WebUrlSource::setInformation(int size, int width, int height) {
|
|
if (size) {
|
|
_size = size;
|
|
}
|
|
if (width && height) {
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
}
|
|
|
|
QSize WebUrlSource::shrinkBox() const {
|
|
return _box;
|
|
}
|
|
|
|
FileLoader *WebUrlSource::createLoader(
|
|
Data::FileOrigin origin,
|
|
LoadFromCloudSetting fromCloud,
|
|
bool autoLoading) {
|
|
return new webFileLoader(
|
|
_url,
|
|
QString(),
|
|
fromCloud,
|
|
autoLoading,
|
|
Data::kImageCacheTag);
|
|
}
|
|
|
|
} // namespace Images
|