303 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
	
		
			8 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 "storage/storage_media_prepare.h"
 | |
| 
 | |
| #include "platform/platform_file_utilities.h"
 | |
| #include "storage/localimageloader.h"
 | |
| 
 | |
| namespace Storage {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kMaxAlbumCount = 10;
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| bool ValidPhotoForAlbum(const FileMediaInformation::Image &image) {
 | |
| 	if (image.animated) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	const auto width = image.data.width();
 | |
| 	const auto height = image.data.height();
 | |
| 	return ValidateThumbDimensions(width, height);
 | |
| }
 | |
| 
 | |
| bool ValidVideoForAlbum(const FileMediaInformation::Video &video) {
 | |
| 	const auto width = video.thumbnail.width();
 | |
| 	const auto height = video.thumbnail.height();
 | |
| 	return ValidateThumbDimensions(width, height);
 | |
| }
 | |
| 
 | |
| bool PrepareAlbumMediaIsWaiting(
 | |
| 		QSemaphore &semaphore,
 | |
| 		PreparedFile &file,
 | |
| 		int previewWidth) {
 | |
| 	// TODO: Use some special thread queue, like a separate QThreadPool.
 | |
| 	crl::async([=, &semaphore, &file] {
 | |
| 		const auto guard = gsl::finally([&] { semaphore.release(); });
 | |
| 		if (!file.path.isEmpty()) {
 | |
| 			file.mime = mimeTypeForFile(QFileInfo(file.path)).name();
 | |
| 			file.information = FileLoadTask::ReadMediaInformation(
 | |
| 				file.path,
 | |
| 				QByteArray(),
 | |
| 				file.mime);
 | |
| 		} else if (!file.content.isEmpty()) {
 | |
| 			file.mime = mimeTypeForData(file.content).name();
 | |
| 			file.information = FileLoadTask::ReadMediaInformation(
 | |
| 				QString(),
 | |
| 				file.content,
 | |
| 				file.mime);
 | |
| 		} else {
 | |
| 			Assert(file.information != nullptr);
 | |
| 		}
 | |
| 
 | |
| 		using Image = FileMediaInformation::Image;
 | |
| 		using Video = FileMediaInformation::Video;
 | |
| 		if (const auto image = base::get_if<Image>(
 | |
| 				&file.information->media)) {
 | |
| 			if (ValidPhotoForAlbum(*image)) {
 | |
| 				file.preview = Images::prepareOpaque(image->data.scaledToWidth(
 | |
| 					std::min(previewWidth, convertScale(image->data.width()))
 | |
| 						* cIntRetinaFactor(),
 | |
| 					Qt::SmoothTransformation));
 | |
| 				file.preview.setDevicePixelRatio(cRetinaFactor());
 | |
| 				file.type = PreparedFile::AlbumType::Photo;
 | |
| 			}
 | |
| 		} else if (const auto video = base::get_if<Video>(
 | |
| 				&file.information->media)) {
 | |
| 			if (ValidVideoForAlbum(*video)) {
 | |
| 				auto blurred = Images::prepareBlur(Images::prepareOpaque(video->thumbnail));
 | |
| 				file.preview = std::move(blurred).scaledToWidth(
 | |
| 					previewWidth * cIntRetinaFactor(),
 | |
| 					Qt::SmoothTransformation);
 | |
| 				file.preview.setDevicePixelRatio(cRetinaFactor());
 | |
| 				file.type = PreparedFile::AlbumType::Video;
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void PrepareAlbum(PreparedList &result, int previewWidth) {
 | |
| 	const auto count = int(result.files.size());
 | |
| 	if (count > kMaxAlbumCount) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	result.albumIsPossible = (count > 1);
 | |
| 	auto waiting = 0;
 | |
| 	QSemaphore semaphore;
 | |
| 	for (auto &file : result.files) {
 | |
| 		if (PrepareAlbumMediaIsWaiting(semaphore, file, previewWidth)) {
 | |
| 			++waiting;
 | |
| 		}
 | |
| 	}
 | |
| 	if (waiting > 0) {
 | |
| 		semaphore.acquire(waiting);
 | |
| 		if (result.albumIsPossible) {
 | |
| 			const auto badIt = ranges::find(
 | |
| 				result.files,
 | |
| 				PreparedFile::AlbumType::None,
 | |
| 				[](const PreparedFile &file) { return file.type; });
 | |
| 			result.albumIsPossible = (badIt == result.files.end());
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| bool ValidateThumbDimensions(int width, int height) {
 | |
| 	return (width > 0)
 | |
| 		&& (height > 0)
 | |
| 		&& (width < 20 * height)
 | |
| 		&& (height < 20 * width);
 | |
| }
 | |
| 
 | |
| PreparedFile::PreparedFile(const QString &path) : path(path) {
 | |
| }
 | |
| 
 | |
| PreparedFile::PreparedFile(PreparedFile &&other) = default;
 | |
| 
 | |
| PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default;
 | |
| 
 | |
| PreparedFile::~PreparedFile() = default;
 | |
| 
 | |
| MimeDataState ComputeMimeDataState(const QMimeData *data) {
 | |
| 	if (!data || data->hasFormat(qsl("application/x-td-forward"))) {
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	const auto imageExtensions = cImgExtensions();
 | |
| 	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();
 | |
| 		if (filesize > App::kFileSizeLimit) {
 | |
| 			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());
 | |
| 	const auto extensionsToCompress = cExtensionsForCompress();
 | |
| 	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
 | |
| 			};
 | |
| 		} else if (filesize > App::kFileSizeLimit) {
 | |
| 			return {
 | |
| 				PreparedList::Error::TooLargeFile,
 | |
| 				file
 | |
| 			};
 | |
| 		}
 | |
| 		const auto toCompress = HasExtensionFrom(file, extensionsToCompress);
 | |
| 		if (filesize > App::kImageSizeLimit || !toCompress) {
 | |
| 			result.allFilesForCompress = false;
 | |
| 		}
 | |
| 		result.files.emplace_back(file);
 | |
| 	}
 | |
| 	PrepareAlbum(result, previewWidth);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| PreparedList PrepareMediaFromImage(
 | |
| 		QImage &&image,
 | |
| 		QByteArray &&content,
 | |
| 		int previewWidth) {
 | |
| 	auto result = Storage::PreparedList();
 | |
| 	result.allFilesForCompress = ValidateThumbDimensions(
 | |
| 		image.width(),
 | |
| 		image.height());
 | |
| 	auto file = PreparedFile(QString());
 | |
| 	file.content = content;
 | |
| 	if (file.content.isEmpty()) {
 | |
| 		file.information = std::make_unique<FileMediaInformation>();
 | |
| 		const auto animated = false;
 | |
| 		FileLoadTask::FillImageInformation(
 | |
| 			std::move(image),
 | |
| 			animated,
 | |
| 			file.information);
 | |
| 	}
 | |
| 	result.files.push_back(std::move(file));
 | |
| 	PrepareAlbum(result, previewWidth);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| PreparedList PreparedList::Reordered(
 | |
| 		PreparedList &&list,
 | |
| 		std::vector<int> order) {
 | |
| 	Expects(list.error == PreparedList::Error::None);
 | |
| 	Expects(list.files.size() == order.size());
 | |
| 
 | |
| 	auto result = PreparedList(list.error, list.errorData);
 | |
| 	result.albumIsPossible = list.albumIsPossible;
 | |
| 	result.allFilesForCompress = list.allFilesForCompress;
 | |
| 	result.files.reserve(list.files.size());
 | |
| 	for (auto index : order) {
 | |
| 		result.files.push_back(std::move(list.files[index]));
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void PreparedList::mergeToEnd(PreparedList &&other) {
 | |
| 	if (error != Error::None) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (other.error != Error::None) {
 | |
| 		error = other.error;
 | |
| 		errorData = other.errorData;
 | |
| 		return;
 | |
| 	}
 | |
| 	allFilesForCompress = allFilesForCompress && other.allFilesForCompress;
 | |
| 	files.reserve(files.size() + other.files.size());
 | |
| 	for (auto &file : other.files) {
 | |
| 		files.push_back(std::move(file));
 | |
| 	}
 | |
| 	if (files.size() > 1 && files.size() <= kMaxAlbumCount) {
 | |
| 		const auto badIt = ranges::find(
 | |
| 			files,
 | |
| 			PreparedFile::AlbumType::None,
 | |
| 			[](const PreparedFile &file) { return file.type; });
 | |
| 		albumIsPossible = (badIt == files.end());
 | |
| 	} else {
 | |
| 		albumIsPossible = false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int MaxAlbumItems() {
 | |
| 	return kMaxAlbumCount;
 | |
| }
 | |
| 
 | |
| } // namespace Storage
 | |
| 
 | 
