tdesktop/Telegram/SourceFiles/storage/storage_media_prepare.cpp

369 lines
9.9 KiB
C++
Raw Normal View History

2017-12-19 16:57:42 +00:00
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
2017-12-19 16:57:42 +00:00
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
2017-12-19 16:57:42 +00:00
*/
#include "storage/storage_media_prepare.h"
#include "platform/platform_file_utilities.h"
#include "storage/localimageloader.h"
#include "core/mime_type.h"
#include "ui/image/image_prepare.h"
2020-10-13 10:00:35 +00:00
#include "ui/chat/attach/attach_extensions.h"
#include "ui/chat/attach/attach_prepare.h"
#include "app.h"
2017-12-19 16:57:42 +00:00
2019-09-04 07:19:15 +00:00
#include <QtCore/QSemaphore>
#include <QtCore/QMimeData>
2017-12-19 16:57:42 +00:00
namespace Storage {
namespace {
using Ui::PreparedFileInformation;
using Ui::PreparedFile;
using Ui::PreparedList;
2017-12-19 16:57:42 +00:00
bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
for (const auto &extension : extensions) {
const auto ext = file.right(extension.size());
if (ext.compare(extension, Qt::CaseInsensitive) == 0) {
return true;
}
}
return false;
}
2019-07-15 12:08:35 +00:00
bool ValidPhotoForAlbum(
const PreparedFileInformation::Image &image,
2019-07-15 12:08:35 +00:00
const QString &mime) {
if (image.animated || Core::IsMimeSticker(mime)) {
2017-12-19 16:57:42 +00:00
return false;
}
const auto width = image.data.width();
const auto height = image.data.height();
2020-10-13 17:15:52 +00:00
return Ui::ValidateThumbDimensions(width, height);
2017-12-19 16:57:42 +00:00
}
bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) {
2017-12-19 16:57:42 +00:00
const auto width = video.thumbnail.width();
const auto height = video.thumbnail.height();
2020-10-13 17:15:52 +00:00
return Ui::ValidateThumbDimensions(width, height);
2017-12-19 16:57:42 +00:00
}
QSize PrepareShownDimensions(const QImage &preview) {
constexpr auto kMaxWidth = 1280;
constexpr auto kMaxHeight = 1280;
const auto result = preview.size();
return (result.width() > kMaxWidth || result.height() > kMaxHeight)
? result.scaled(kMaxWidth, kMaxHeight, Qt::KeepAspectRatio)
: result;
}
2020-10-16 07:52:55 +00:00
bool PrepareDetailsIsWaiting(
2017-12-19 16:57:42 +00:00
QSemaphore &semaphore,
PreparedFile &file,
int previewWidth) {
2017-12-30 21:28:38 +00:00
crl::async([=, &semaphore, &file] {
2017-12-19 16:57:42 +00:00
const auto guard = gsl::finally([&] { semaphore.release(); });
if (!file.path.isEmpty()) {
file.mime = Core::MimeTypeForFile(QFileInfo(file.path)).name();
file.information = FileLoadTask::ReadMediaInformation(
file.path,
QByteArray(),
file.mime);
} else if (!file.content.isEmpty()) {
file.mime = Core::MimeTypeForData(file.content).name();
file.information = FileLoadTask::ReadMediaInformation(
QString(),
file.content,
file.mime);
} else {
Assert(file.information != nullptr);
}
using Image = PreparedFileInformation::Image;
using Video = PreparedFileInformation::Video;
if (const auto image = std::get_if<Image>(
2017-12-19 16:57:42 +00:00
&file.information->media)) {
2019-07-15 12:08:35 +00:00
if (ValidPhotoForAlbum(*image, file.mime)) {
file.shownDimensions = PrepareShownDimensions(image->data);
file.preview = Images::prepareOpaque(image->data.scaledToWidth(
2019-09-13 10:24:06 +00:00
std::min(previewWidth, style::ConvertScale(image->data.width()))
2017-12-22 17:42:33 +00:00
* cIntRetinaFactor(),
Qt::SmoothTransformation));
Assert(!file.preview.isNull());
2017-12-19 16:57:42 +00:00
file.preview.setDevicePixelRatio(cRetinaFactor());
file.type = PreparedFile::AlbumType::Photo;
} else if (Core::IsMimeSticker(file.mime)) {
file.type = PreparedFile::AlbumType::None;
2017-12-19 16:57:42 +00:00
}
} else if (const auto video = std::get_if<Video>(
2017-12-19 16:57:42 +00:00
&file.information->media)) {
if (ValidVideoForAlbum(*video)) {
auto blurred = Images::prepareBlur(Images::prepareOpaque(video->thumbnail));
file.shownDimensions = PrepareShownDimensions(video->thumbnail);
2017-12-19 16:57:42 +00:00
file.preview = std::move(blurred).scaledToWidth(
previewWidth * cIntRetinaFactor(),
Qt::SmoothTransformation);
Assert(!file.preview.isNull());
2017-12-19 16:57:42 +00:00
file.preview.setDevicePixelRatio(cRetinaFactor());
file.type = PreparedFile::AlbumType::Video;
}
}
});
return true;
}
2020-10-16 07:52:55 +00:00
void PrepareDetailsInParallel(PreparedList &result, int previewWidth) {
Expects(result.files.size() <= Ui::MaxAlbumItems());
2017-12-19 16:57:42 +00:00
auto waiting = 0;
QSemaphore semaphore;
for (auto &file : result.files) {
2020-10-16 07:52:55 +00:00
if (PrepareDetailsIsWaiting(
semaphore,
file,
previewWidth)) {
2017-12-19 16:57:42 +00:00
++waiting;
}
}
if (waiting > 0) {
semaphore.acquire(waiting);
}
}
} // namespace
bool ValidateDragData(not_null<const QMimeData*> data, bool isAlbum) {
if (data->urls().size() > 1) {
return false;
} else if (data->hasImage()) {
return true;
}
if (isAlbum && data->hasUrls()) {
const auto url = data->urls().front();
if (url.isLocalFile()) {
using namespace Core;
const auto info = QFileInfo(Platform::File::UrlToLocal(url));
return IsMimeAcceptedForAlbum(MimeTypeForFile(info).name());
}
}
return true;
}
2017-12-19 16:57:42 +00:00
MimeDataState ComputeMimeDataState(const QMimeData *data) {
if (!data || data->hasFormat(qsl("application/x-td-forward"))) {
2017-12-19 16:57:42 +00:00
return MimeDataState::None;
}
if (data->hasImage()) {
return MimeDataState::Image;
}
const auto uriListFormat = qsl("text/uri-list");
if (!data->hasFormat(uriListFormat)) {
return MimeDataState::None;
}
const auto &urls = data->urls();
if (urls.isEmpty()) {
return MimeDataState::None;
}
2020-10-13 10:00:35 +00:00
const auto imageExtensions = Ui::ImageExtensions();
2017-12-19 16:57:42 +00:00
auto files = QStringList();
auto allAreSmallImages = true;
for (const auto &url : urls) {
if (!url.isLocalFile()) {
return MimeDataState::None;
}
const auto file = Platform::File::UrlToLocal(url);
const auto info = QFileInfo(file);
if (info.isDir()) {
return MimeDataState::None;
}
const auto filesize = info.size();
2020-07-06 08:18:11 +00:00
if (filesize > kFileSizeLimit) {
2017-12-19 16:57:42 +00:00
return MimeDataState::None;
} else if (allAreSmallImages) {
if (filesize > App::kImageSizeLimit) {
allAreSmallImages = false;
} else if (!HasExtensionFrom(file, imageExtensions)) {
allAreSmallImages = false;
}
}
}
return allAreSmallImages
? MimeDataState::PhotoFiles
: MimeDataState::Files;
}
PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth) {
auto locals = QStringList();
locals.reserve(files.size());
for (const auto &url : files) {
if (!url.isLocalFile()) {
return {
PreparedList::Error::NonLocalUrl,
url.toDisplayString()
};
}
locals.push_back(Platform::File::UrlToLocal(url));
}
return PrepareMediaList(locals, previewWidth);
}
PreparedList PrepareMediaList(const QStringList &files, int previewWidth) {
auto result = PreparedList();
result.files.reserve(files.size());
2020-10-13 10:00:35 +00:00
const auto extensionsToCompress = Ui::ExtensionsForCompression();
2017-12-19 16:57:42 +00:00
for (const auto &file : files) {
const auto fileinfo = QFileInfo(file);
const auto filesize = fileinfo.size();
if (fileinfo.isDir()) {
return {
PreparedList::Error::Directory,
file
};
} else if (filesize <= 0) {
return {
PreparedList::Error::EmptyFile,
file
};
2020-07-06 08:18:11 +00:00
} else if (filesize > kFileSizeLimit) {
2017-12-19 16:57:42 +00:00
return {
PreparedList::Error::TooLargeFile,
file
};
}
2020-10-16 07:52:55 +00:00
if (result.files.size() < Ui::MaxAlbumItems()) {
result.files.emplace_back(file);
result.files.back().size = filesize;
} else {
result.filesToProcess.emplace_back(file);
result.files.back().size = filesize;
2017-12-19 16:57:42 +00:00
}
}
2020-10-16 07:52:55 +00:00
PrepareDetailsInParallel(result, previewWidth);
return result;
}
PreparedList PrepareMediaFromImage(
QImage &&image,
QByteArray &&content,
int previewWidth) {
auto result = PreparedList();
auto file = PreparedFile(QString());
file.content = content;
if (file.content.isEmpty()) {
file.information = std::make_unique<PreparedFileInformation>();
const auto animated = false;
FileLoadTask::FillImageInformation(
std::move(image),
animated,
file.information);
2017-12-19 16:57:42 +00:00
}
result.files.push_back(std::move(file));
2020-10-16 07:52:55 +00:00
PrepareDetailsInParallel(result, previewWidth);
2017-12-19 16:57:42 +00:00
return result;
}
std::optional<PreparedList> PreparedFileFromFilesDialog(
FileDialog::OpenResult &&result,
bool isAlbum,
Fn<void(tr::phrase<>)> errorCallback,
int previewWidth) {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return std::nullopt;
}
if (!result.remoteContent.isEmpty()) {
auto list = PrepareMediaFromImage(
QImage(),
std::move(result.remoteContent),
previewWidth);
const auto mimeFile = list.files.front().mime;
if (Core::IsMimeSticker(mimeFile)) {
errorCallback(tr::lng_edit_media_invalid_file);
return std::nullopt;
}
if (isAlbum) {
const auto file = &list.files.front();
if (!Core::IsMimeAcceptedForAlbum(mimeFile)
|| file->type == PreparedFile::AlbumType::File
|| file->type == PreparedFile::AlbumType::File) {
errorCallback(tr::lng_edit_media_album_error);
return std::nullopt;
}
}
Expects(list.files.size() == 1);
2020-06-30 16:25:27 +00:00
return list;
} else if (!result.paths.isEmpty()) {
const auto isSingleFile = (result.paths.size() == 1);
auto temp = PrepareMediaList(result.paths, previewWidth);
if (temp.error != PreparedList::Error::None) {
errorCallback(tr::lng_send_media_invalid_files);
return std::nullopt;
}
auto filteredFiles = ranges::view::all(
temp.files
) | ranges::view::filter([&](const auto &file) {
const auto info = QFileInfo(file.path);
if (Core::IsMimeSticker(Core::MimeTypeForFile(info).name())) {
if (isSingleFile) {
errorCallback(tr::lng_edit_media_invalid_file);
}
return false;
}
if (!isAlbum) {
return true;
}
using Info = PreparedFileInformation;
const auto media = &file.information->media;
const auto valid = v::match(*media, [](const Info::Image &data) {
2020-10-13 17:15:52 +00:00
return Ui::ValidateThumbDimensions(
data.data.width(),
data.data.height())
&& !data.animated;
}, [](Info::Video &data) {
data.isGifv = false;
return true;
}, [](auto &&other) {
return false;
});
if (!valid && isSingleFile) {
errorCallback(tr::lng_edit_media_album_error);
}
return valid;
}) | ranges::view::transform([](auto &file) {
return std::move(file);
}) | ranges::to_vector;
if (!filteredFiles.size()) {
if (!isSingleFile) {
errorCallback(tr::lng_send_media_invalid_files);
}
return std::nullopt;
}
auto list = PreparedList(temp.error, temp.errorData);
list.files = std::move(filteredFiles);
2020-06-30 16:25:27 +00:00
return list;
}
return std::nullopt;
}
2017-12-19 16:57:42 +00:00
} // namespace Storage