268 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			7.5 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 "data/data_document_resolver.h"
 | |
| 
 | |
| #include "base/options.h"
 | |
| #include "base/platform/base_platform_info.h"
 | |
| #include "boxes/abstract_box.h" // Ui::show().
 | |
| #include "chat_helpers/ttl_media_layer_widget.h"
 | |
| #include "core/application.h"
 | |
| #include "core/core_settings.h"
 | |
| #include "core/mime_type.h"
 | |
| #include "data/data_document.h"
 | |
| #include "data/data_document_media.h"
 | |
| #include "data/data_file_click_handler.h"
 | |
| #include "data/data_session.h"
 | |
| #include "history/history.h"
 | |
| #include "history/history_item.h"
 | |
| #include "history/view/media/history_view_gif.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "media/player/media_player_instance.h"
 | |
| #include "platform/platform_file_utilities.h"
 | |
| #include "ui/boxes/confirm_box.h"
 | |
| #include "ui/chat/chat_theme.h"
 | |
| #include "ui/text/text_utilities.h"
 | |
| #include "ui/widgets/checkbox.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "styles/style_layers.h"
 | |
| 
 | |
| #include <QtCore/QBuffer>
 | |
| #include <QtCore/QMimeType>
 | |
| #include <QtCore/QMimeDatabase>
 | |
| 
 | |
| namespace Data {
 | |
| namespace {
 | |
| 
 | |
| base::options::toggle OptionExternalVideoPlayer({
 | |
| 	.id = kOptionExternalVideoPlayer,
 | |
| 	.name = "External video player",
 | |
| });
 | |
| 
 | |
| void ConfirmDontWarnBox(
 | |
| 		not_null<Ui::GenericBox*> box,
 | |
| 		rpl::producer<TextWithEntities> &&text,
 | |
| 		rpl::producer<QString> &&check,
 | |
| 		rpl::producer<QString> &&confirm,
 | |
| 		Fn<void(bool)> callback) {
 | |
| 	auto checkbox = object_ptr<Ui::Checkbox>(
 | |
| 		box.get(),
 | |
| 		std::move(check),
 | |
| 		false,
 | |
| 		st::defaultBoxCheckbox);
 | |
| 	const auto weak = Ui::MakeWeak(checkbox.data());
 | |
| 	auto confirmed = crl::guard(weak, [=, callback = std::move(callback)] {
 | |
| 		const auto checked = weak->checked();
 | |
| 		box->closeBox();
 | |
| 		callback(checked);
 | |
| 	});
 | |
| 	Ui::ConfirmBox(box, {
 | |
| 		.text = std::move(text),
 | |
| 		.confirmed = std::move(confirmed),
 | |
| 		.confirmText = std::move(confirm),
 | |
| 	});
 | |
| 	auto padding = st::boxPadding;
 | |
| 	padding.setTop(padding.bottom());
 | |
| 	box->addRow(std::move(checkbox), std::move(padding));
 | |
| 	box->addRow(object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
 | |
| 		box,
 | |
| 		object_ptr<Ui::FlatLabel>(
 | |
| 			box,
 | |
| 			tr::lng_launch_dont_ask_settings(),
 | |
| 			st::boxLabel)
 | |
| 	))->toggleOn(weak->checkedValue());
 | |
| }
 | |
| 
 | |
| void LaunchWithWarning(
 | |
| 		// not_null<Window::Controller*> controller,
 | |
| 		const QString &name,
 | |
| 		HistoryItem *item) {
 | |
| 	const auto nameType = Core::DetectNameType(name);
 | |
| 	const auto isIpReveal = (nameType != Core::NameType::Executable)
 | |
| 		&& Core::IsIpRevealingPath(name);
 | |
| 	const auto extension = Core::FileExtension(name).toLower();
 | |
| 
 | |
| 	auto &app = Core::App();
 | |
| 	auto &settings = app.settings();
 | |
| 	const auto warn = [&] {
 | |
| 		if (item && item->history()->peer->isVerified()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		return (isIpReveal && settings.ipRevealWarning())
 | |
| 			|| ((nameType == Core::NameType::Executable
 | |
| 				|| nameType == Core::NameType::Unknown)
 | |
| 				&& !settings.noWarningExtensions().contains(extension));
 | |
| 	}();
 | |
| 	if (extension.isEmpty()) {
 | |
| 		// If you launch a file without extension, like "test", in case
 | |
| 		// there is an executable file with the same name in this folder,
 | |
| 		// like "test.bat", the executable file will be launched.
 | |
| 		//
 | |
| 		// Now we always force an Open With dialog box for such files.
 | |
| 		//
 | |
| 		// Let's force it for all platforms for files without extension.
 | |
| 		crl::on_main([=] {
 | |
| 			Platform::File::UnsafeShowOpenWith(name);
 | |
| 		});
 | |
| 		return;
 | |
| 	} else if (!warn) {
 | |
| 		File::Launch(name);
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto callback = [=, &app, &settings](bool checked) {
 | |
| 		if (checked) {
 | |
| 			if (isIpReveal) {
 | |
| 				settings.setIpRevealWarning(false);
 | |
| 			} else {
 | |
| 				auto copy = settings.noWarningExtensions();
 | |
| 				copy.emplace(extension);
 | |
| 				settings.setNoWarningExtensions(std::move(copy));
 | |
| 			}
 | |
| 			app.saveSettingsDelayed();
 | |
| 		}
 | |
| 		File::Launch(name);
 | |
| 	};
 | |
| 	auto text = isIpReveal
 | |
| 		? tr::lng_launch_svg_warning(Ui::Text::WithEntities)
 | |
| 		: ((nameType == Core::NameType::Executable)
 | |
| 			? tr::lng_launch_exe_warning
 | |
| 			: tr::lng_launch_other_warning)(
 | |
| 				lt_extension,
 | |
| 				rpl::single(Ui::Text::Bold('.' + extension)),
 | |
| 				Ui::Text::WithEntities);
 | |
| 	auto check = (isIpReveal
 | |
| 		? tr::lng_launch_exe_dont_ask
 | |
| 		: tr::lng_launch_dont_ask)();
 | |
| 	auto confirm = ((nameType == Core::NameType::Executable)
 | |
| 		? tr::lng_launch_exe_sure
 | |
| 		: tr::lng_launch_other_sure)();
 | |
| 	Ui::show(Box(
 | |
| 		ConfirmDontWarnBox,
 | |
| 		std::move(text),
 | |
| 		std::move(check),
 | |
| 		std::move(confirm),
 | |
| 		callback));
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| const char kOptionExternalVideoPlayer[] = "external-video-player";
 | |
| 
 | |
| base::binary_guard ReadBackgroundImageAsync(
 | |
| 		not_null<Data::DocumentMedia*> media,
 | |
| 		FnMut<QImage(QImage)> postprocess,
 | |
| 		FnMut<void(QImage&&)> done) {
 | |
| 	auto result = base::binary_guard();
 | |
| 	const auto gzipSvg = media->owner()->isPatternWallPaperSVG();
 | |
| 	crl::async([
 | |
| 		gzipSvg,
 | |
| 		bytes = media->bytes(),
 | |
| 		path = media->owner()->filepath(),
 | |
| 		postprocess = std::move(postprocess),
 | |
| 		guard = result.make_guard(),
 | |
| 		callback = std::move(done)
 | |
| 	]() mutable {
 | |
| 		auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
 | |
| 		if (postprocess) {
 | |
| 			image = postprocess(std::move(image));
 | |
| 		}
 | |
| 		crl::on_main(std::move(guard), [
 | |
| 			image = std::move(image),
 | |
| 			callback = std::move(callback)
 | |
| 		]() mutable {
 | |
| 			callback(std::move(image));
 | |
| 		});
 | |
| 	});
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void ResolveDocument(
 | |
| 		Window::SessionController *controller,
 | |
| 		not_null<DocumentData*> document,
 | |
| 		HistoryItem *item,
 | |
| 		MsgId topicRootId) {
 | |
| 	if (document->isNull()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto msgId = item ? item->fullId() : FullMsgId();
 | |
| 
 | |
| 	const auto showDocument = [&] {
 | |
| 		if (OptionExternalVideoPlayer.value()
 | |
| 			&& document->isVideoFile()
 | |
| 			&& !document->filepath().isEmpty()) {
 | |
| 			File::Launch(document->location(false).fname);
 | |
| 		} else if (controller) {
 | |
| 			controller->openDocument(
 | |
| 				document,
 | |
| 				true,
 | |
| 				{ msgId, topicRootId });
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	const auto media = document->createMediaView();
 | |
| 	const auto openImageInApp = [&] {
 | |
| 		if (document->size >= Images::kReadBytesLimit) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		const auto &location = document->location(true);
 | |
| 		const auto mime = u"image/"_q;
 | |
| 		if (!location.isEmpty() && location.accessEnable()) {
 | |
| 			const auto guard = gsl::finally([&] {
 | |
| 				location.accessDisable();
 | |
| 			});
 | |
| 			const auto path = location.name();
 | |
| 			if (Core::MimeTypeForFile(QFileInfo(path)).name().startsWith(mime)
 | |
| 				&& QImageReader(path).canRead()) {
 | |
| 				showDocument();
 | |
| 				return true;
 | |
| 			}
 | |
| 		} else if (document->mimeString().startsWith(mime)
 | |
| 			&& !media->bytes().isEmpty()) {
 | |
| 			auto bytes = media->bytes();
 | |
| 			auto buffer = QBuffer(&bytes);
 | |
| 			if (QImageReader(&buffer).canRead()) {
 | |
| 				showDocument();
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 		return false;
 | |
| 	};
 | |
| 	const auto &location = document->location(true);
 | |
| 	if (document->isTheme() && media->loaded(true)) {
 | |
| 		showDocument();
 | |
| 		location.accessDisable();
 | |
| 	} else if (media->canBePlayed(item)) {
 | |
| 		if (document->isAudioFile()
 | |
| 			|| document->isVoiceMessage()
 | |
| 			|| document->isVideoMessage()) {
 | |
| 			::Media::Player::instance()->playPause({ document, msgId });
 | |
| 			if (controller
 | |
| 				&& item
 | |
| 				&& item->media()
 | |
| 				&& item->media()->ttlSeconds()) {
 | |
| 				ChatHelpers::ShowTTLMediaLayerWidget(controller, item);
 | |
| 			}
 | |
| 		} else {
 | |
| 			showDocument();
 | |
| 		}
 | |
| 	} else {
 | |
| 		document->saveFromDataSilent();
 | |
| 		if (!openImageInApp()) {
 | |
| 			if (!document->filepath(true).isEmpty()) {
 | |
| 				LaunchWithWarning(location.name(), item);
 | |
| 			} else if (document->status == FileReady
 | |
| 				|| document->status == FileDownloadFailed) {
 | |
| 				DocumentSaveClickHandler::Save(
 | |
| 					item ? item->fullId() : Data::FileOrigin(),
 | |
| 					document);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace Data
 | 
