/* 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_auto_download.h" #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_document.h" #include namespace Data { namespace AutoDownload { namespace { constexpr auto kDefaultMaxSize = 8 * int64(1024 * 1024); constexpr auto kDefaultAutoPlaySize = 50 * int64(1024 * 1024); constexpr auto kVersion1 = char(1); constexpr auto kVersion2 = char(2); constexpr auto kVersion = char(3); template auto enums_view(int from, int till) { using namespace ranges::views; return ints(from, till) | transform([](int index) { return static_cast(index); }); } template auto enums_view(int till) { return enums_view(0, till); } void SetDefaultsForSource(Full &data, Source source) { data.setBytesLimit(source, Type::Photo, kDefaultMaxSize); data.setBytesLimit(source, Type::VoiceMessage, kDefaultMaxSize); data.setBytesLimit( source, Type::AutoPlayVideoMessage, kDefaultAutoPlaySize); data.setBytesLimit(source, Type::AutoPlayGIF, kDefaultAutoPlaySize); const auto channelsFileLimit = (source == Source::Channel) ? 0 : kDefaultMaxSize; data.setBytesLimit(source, Type::File, channelsFileLimit); data.setBytesLimit(source, Type::AutoPlayVideo, kDefaultAutoPlaySize); data.setBytesLimit(source, Type::Music, channelsFileLimit); } const Full &Defaults() { static auto Result = [] { auto result = Full::FullDisabled(); for (const auto source : enums_view(kSourcesCount)) { SetDefaultsForSource(result, source); } return result; }(); return Result; } Source SourceFromPeer(not_null peer) { if (peer->isUser()) { return Source::User; } else if (peer->isChat() || peer->isMegagroup()) { return Source::Group; } else { return Source::Channel; } } Type AutoPlayTypeFromDocument(not_null document) { return document->isVideoFile() ? Type::AutoPlayVideo : document->isVideoMessage() ? Type::AutoPlayVideoMessage : Type::AutoPlayGIF; } [[nodiscard]] int64 ForceAllowLimit( const Full &data, Source source, Type type) { const auto user = data.bytesLimit(source, type); return (user > 0) ? user : Defaults().bytesLimit(source, type); } [[nodiscard]] bool ForceAllowed( const Full &data, Source source, Type type, int64 fileSize) { if (ranges::find(kStreamedTypes, type) != end(kStreamedTypes)) { return false; } const auto limit = ForceAllowLimit(data, source, type); return (limit > 0) && (fileSize <= limit); } } // namespace void Single::setBytesLimit(int64 bytesLimit) { Expects(bytesLimit >= 0 && bytesLimit <= kMaxBytesLimit); _limit = int32(uint32(bytesLimit)); Ensures(hasValue()); } bool Single::hasValue() const { return (_limit != -1); } bool Single::shouldDownload(int64 fileSize) const { Expects(hasValue()); const auto realLimit = bytesLimit(); return (realLimit > 0) && (fileSize <= realLimit); } int64 Single::bytesLimit() const { Expects(hasValue()); return uint32(_limit); } qint32 Single::serialize() const { return _limit; } bool Single::setFromSerialized(qint32 serialized) { auto realLimit = quint32(serialized); if (serialized != -1 && int64(realLimit) > kMaxBytesLimit) { return false; } _limit = serialized; return true; } const Single &Set::single(Type type) const { Expects(static_cast(type) >= 0 && static_cast(type) < kTypesCount); return _data[static_cast(type)]; } Single &Set::single(Type type) { return const_cast(static_cast(this)->single(type)); } void Set::setBytesLimit(Type type, int64 bytesLimit) { single(type).setBytesLimit(bytesLimit); } bool Set::hasValue(Type type) const { return single(type).hasValue(); } bool Set::shouldDownload(Type type, int64 fileSize) const { return single(type).shouldDownload(fileSize); } int64 Set::bytesLimit(Type type) const { return single(type).bytesLimit(); } qint32 Set::serialize(Type type) const { return single(type).serialize(); } bool Set::setFromSerialized(Type type, qint32 serialized) { if (static_cast(type) < 0 || static_cast(type) >= kTypesCount) { return false; } return single(type).setFromSerialized(serialized); } const Set &Full::set(Source source) const { Expects(static_cast(source) >= 0 && static_cast(source) < kSourcesCount); return _data[static_cast(source)]; } Set &Full::set(Source source) { return const_cast(static_cast(this)->set(source)); } const Set &Full::setOrDefault(Source source, Type type) const { const auto &my = set(source); const auto &result = my.hasValue(type) ? my : Defaults().set(source); Ensures(result.hasValue(type)); return result; } void Full::setBytesLimit(Source source, Type type, int64 bytesLimit) { set(source).setBytesLimit(type, bytesLimit); } bool Full::shouldDownload(Source source, Type type, int64 fileSize) const { if (ranges::find(kStreamedTypes, type) != end(kStreamedTypes)) { // With streaming we disable autodownload and hide them in Settings. return false; } return setOrDefault(source, type).shouldDownload(type, fileSize); } int64 Full::bytesLimit(Source source, Type type) const { return setOrDefault(source, type).bytesLimit(type); } void Full::setPeerOverride(PeerId peerId, Override value) { if (value == Override::Default) { _peerOverrides.remove(peerId); } else { _peerOverrides[peerId] = value; } } Override Full::peerOverride(PeerId peerId) const { const auto i = _peerOverrides.find(peerId); return (i != end(_peerOverrides)) ? i->second : Override::Default; } void Full::enumeratePeerOverrides( Fn callback) const { for (const auto &[peerId, value] : _peerOverrides) { callback(peerId, value); } } QByteArray Full::serialize() const { auto result = QByteArray(); auto size = sizeof(qint8); size += kSourcesCount * kTypesCount * sizeof(qint32); size += sizeof(qint32); size += _peerOverrides.size() * (sizeof(quint64) + sizeof(qint8)); result.reserve(size); { auto buffer = QBuffer(&result); buffer.open(QIODevice::WriteOnly); auto stream = QDataStream(&buffer); stream << qint8(kVersion); for (const auto source : enums_view(kSourcesCount)) { for (const auto type : enums_view(kTypesCount)) { stream << set(source).serialize(type); } } stream << qint32(_peerOverrides.size()); for (const auto &[peerId, override] : _peerOverrides) { stream << SerializePeerId(peerId) << qint8(static_cast(override)); } } return result; } bool Full::setFromSerialized(const QByteArray &serialized) { if (serialized.isEmpty()) { return false; } auto stream = QDataStream(serialized); auto version = qint8(); stream >> version; if (stream.status() != QDataStream::Ok) { return false; } else if (version != kVersion && version != kVersion2 && version != kVersion1) { return false; } auto temp = Full(); for (const auto source : enums_view(kSourcesCount)) { for (const auto type : enums_view(kTypesCount)) { auto value = qint32(); stream >> value; if (!temp.set(source).setFromSerialized(type, value)) { return false; } } } if (version == kVersion1) { for (const auto source : enums_view(kSourcesCount)) { for (const auto type : kAutoPlayTypes) { temp.setBytesLimit(source, type, std::max( temp.bytesLimit(source, type), kDefaultAutoPlaySize)); } } } if (version >= kVersion && !stream.atEnd()) { auto count = qint32(); stream >> count; if (stream.status() != QDataStream::Ok || count < 0) { return false; } for (auto i = 0; i != count; ++i) { auto serializedPeerId = quint64(); auto rawOverride = qint8(); stream >> serializedPeerId >> rawOverride; if (stream.status() != QDataStream::Ok) { return false; } const auto value = (rawOverride == qint8(Override::ForceAllow)) ? Override::ForceAllow : (rawOverride == qint8(Override::ForceDeny)) ? Override::ForceDeny : Override::Default; if (value != Override::Default) { temp.setPeerOverride( DeserializePeerId(serializedPeerId), value); } } } _data = temp._data; _peerOverrides = std::move(temp._peerOverrides); return true; } Full Full::FullDisabled() { auto result = Full(); for (const auto source : enums_view(kSourcesCount)) { for (const auto type : enums_view(kTypesCount)) { result.setBytesLimit(source, type, 0); } } return result; } bool Should( const Full &data, Source source, not_null document) { if (document->sticker() || document->isGifv()) { return true; } else if (document->isVoiceMessage() || document->isVideoMessage() || document->isSong() || document->isVideoFile()) { return false; } return data.shouldDownload(source, Type::File, document->size); } bool Should( const Full &data, not_null peer, not_null document) { if (document->sticker()) { return true; } const auto override = data.peerOverride(peer->id); if (override == Override::ForceDeny) { return false; } else if (document->isGifv()) { return true; } else if (override == Override::ForceAllow) { if (document->isVoiceMessage() || document->isVideoMessage() || document->isSong() || document->isVideoFile()) { return false; } return ForceAllowed( data, SourceFromPeer(peer), Type::File, document->size); } return Should(data, SourceFromPeer(peer), document); } bool Should( const Full &data, not_null document) { if (document->sticker()) { return true; } return Should(data, Source::User, document) || Should(data, Source::Group, document) || Should(data, Source::Channel, document); } bool Should( const Full &data, not_null peer, not_null photo) { const auto override = data.peerOverride(peer->id); if (override == Override::ForceDeny) { return false; } else if (override == Override::ForceAllow) { return ForceAllowed( data, SourceFromPeer(peer), Type::Photo, photo->imageByteSize(PhotoSize::Large)); } return data.shouldDownload( SourceFromPeer(peer), Type::Photo, photo->imageByteSize(PhotoSize::Large)); } bool ShouldAutoPlay( const Full &data, not_null peer, not_null document) { if (document->sticker()) { return true; } const auto override = data.peerOverride(peer->id); if (override == Override::ForceDeny) { return false; } else if (override == Override::ForceAllow) { return ForceAllowed( data, SourceFromPeer(peer), AutoPlayTypeFromDocument(document), document->size); } return data.shouldDownload( SourceFromPeer(peer), AutoPlayTypeFromDocument(document), document->size); } bool ShouldAutoPlay( const Full &data, not_null peer, not_null photo) { if (!photo->hasVideo()) { return false; } const auto override = data.peerOverride(peer->id); if (override == Override::ForceDeny) { return false; } const auto source = SourceFromPeer(peer); const auto size = photo->videoByteSize(PhotoSize::Large); if (override == Override::ForceAllow) { return ForceAllowed(data, source, Type::AutoPlayGIF, size) || ForceAllowed(data, source, Type::AutoPlayVideo, size) || ForceAllowed(data, source, Type::AutoPlayVideoMessage, size); } return data.shouldDownload(source, Type::AutoPlayGIF, size) || data.shouldDownload(source, Type::AutoPlayVideo, size) || data.shouldDownload(source, Type::AutoPlayVideoMessage, size); } Full WithDisabledAutoPlay(const Full &data) { auto result = data; for (const auto source : enums_view(kSourcesCount)) { for (const auto type : kAutoPlayTypes) { result.setBytesLimit(source, type, 0); } } auto toClear = std::vector(); data.enumeratePeerOverrides([&](PeerId id, Override value) { if (value == Override::ForceAllow) { toClear.push_back(id); } }); for (const auto id : toClear) { result.setPeerOverride(id, Override::Default); } return result; } } // namespace AutoDownload } // namespace Data