From f43e0ad143ba013690223b427248aef9b42a36bf Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 21 Dec 2017 16:45:01 +0400 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 150 +++++ .../xcschemes/xcschememanagement.plist | 4 +- TelegramCore/Account.swift | 5 +- TelegramCore/AccountIntermediateState.swift | 26 +- .../AccountStateManagementUtils.swift | 82 ++- TelegramCore/AccountStateManager.swift | 2 +- TelegramCore/AccountStateReset.swift | 29 +- TelegramCore/AccountViewTracker.swift | 56 +- TelegramCore/ApiGroupOrChannel.swift | 10 +- TelegramCore/Authorization.swift | 8 +- TelegramCore/ChatHistoryPreloadManager.swift | 2 +- TelegramCore/ContactManagement.swift | 2 +- TelegramCore/EnqueueMessage.swift | 11 +- TelegramCore/ExportMessageLink.swift | 4 +- TelegramCore/FetchChatList.swift | 308 ++++++++++ TelegramCore/Holes.swift | 573 ++++++++---------- TelegramCore/Localization.swift | 1 + TelegramCore/Log.swift | 3 + TelegramCore/LoggingSettings.swift | 4 + TelegramCore/ManagedChatListHoles.swift | 10 +- ...gedCloudChatRemoveMessagesOperations.swift | 8 +- ...anagedConsumePersonalMessagesActions.swift | 5 +- ...agedGroupFeedReadStateSyncOperations.swift | 219 +++++++ TelegramCore/ManagedMessageHistoryHoles.swift | 17 +- TelegramCore/ManagedServiceViews.swift | 5 +- ...gedSynchronizeGroupedPeersOperations.swift | 212 +++++++ ...agedSynchronizePinnedChatsOperations.swift | 54 +- TelegramCore/Namespaces.swift | 5 + TelegramCore/Network.swift | 8 + TelegramCore/RecentPeers.swift | 6 +- TelegramCore/SearchMessages.swift | 205 ++++--- TelegramCore/SearchPeers.swift | 50 +- TelegramCore/StoreMessage_Telegram.swift | 4 +- .../SynchronizeGroupedPeersOperation.swift | 79 +++ TelegramCore/SynchronizePeerReadState.swift | 10 + .../SynchronizePinnedChatsOperation.swift | 47 +- TelegramCore/TelegramChannel.swift | 24 +- TelegramCore/TogglePeerChatPinned.swift | 30 +- TelegramCore/UpdatePeers.swift | 6 + TelegramCore/UpdatesApiUtils.swift | 4 +- 40 files changed, 1710 insertions(+), 578 deletions(-) create mode 100644 TelegramCore/FetchChatList.swift create mode 100644 TelegramCore/ManagedGroupFeedReadStateSyncOperations.swift create mode 100644 TelegramCore/ManagedSynchronizeGroupedPeersOperations.swift create mode 100644 TelegramCore/SynchronizeGroupedPeersOperation.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 3fd3485461..bb08e10207 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -499,6 +499,14 @@ D0C0B58B1ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */; }; D0C0B58D1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */; }; D0C0B58E1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */; }; + D0C26D661FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */; }; + D0C26D671FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */; }; + D0C26D691FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */; }; + D0C26D6A1FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */; }; + D0C26D6C1FE286C3004ABF18 /* FetchChatList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */; }; + D0C26D6D1FE286C3004ABF18 /* FetchChatList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */; }; + D0C26D7B1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D7A1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift */; }; + D0C26D7C1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D7A1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift */; }; D0C27B3F1F4B51D000A4E170 /* CachedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */; }; D0C27B401F4B51D000A4E170 /* CachedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */; }; D0C27B421F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B411F4B58C000A4E170 /* PeerSpecificStickerPack.swift */; }; @@ -889,6 +897,10 @@ D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalizationUpdatesOperations.swift; sourceTree = ""; }; D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeLocalizationUpdatesOperation.swift; sourceTree = ""; }; + D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeGroupedPeersOperation.swift; sourceTree = ""; }; + D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeGroupedPeersOperations.swift; sourceTree = ""; }; + D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchChatList.swift; sourceTree = ""; }; + D0C26D7A1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedGroupFeedReadStateSyncOperations.swift; sourceTree = ""; }; D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedStickerPack.swift; sourceTree = ""; }; D0C27B411F4B58C000A4E170 /* PeerSpecificStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerSpecificStickerPack.swift; sourceTree = ""; }; D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchGroupMembers.swift; sourceTree = ""; }; @@ -1235,7 +1247,10 @@ D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */, D07047B91F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift */, D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */, + D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */, + D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */, D0AF32211FAC95C20097362B /* StandaloneUploadedMedia.swift */, + D0C26D7A1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift */, ); name = State; sourceTree = ""; @@ -1507,6 +1522,7 @@ D0DF0C881D819C5F008AEB01 /* Peers */ = { isa = PBXGroup; children = ( + D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */, D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */, D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */, D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */, @@ -1854,6 +1870,7 @@ D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */, D02ABC7E1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */, D0448CA51E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, + D0C26D661FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */, D03C53771DAFF20F004C17B3 /* MultipartUpload.swift in Sources */, D00C7CE01E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */, C2E0646D1ECF171D00387BB8 /* TelegramMediaWebDocument.swift in Sources */, @@ -1903,6 +1920,7 @@ C2FD33E41E687BF1008D13D4 /* PeerPhotoUpdater.swift in Sources */, D01C06B71FBBA269001561AB /* CanSendMessagesToPeer.swift in Sources */, D0B843B51DA7FF30005F29E1 /* NBMetadataCore.m in Sources */, + D0C26D691FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */, D03B0CD61D62245300955575 /* TelegramUser.swift in Sources */, D02395D61F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift in Sources */, D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */, @@ -1945,6 +1963,7 @@ D0F02CE51E9926C40065DEE2 /* ManagedConfigurationUpdates.swift in Sources */, D0528E6A1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */, D0BEAF5D1E54941B00BD963D /* Authorization.swift in Sources */, + D0C26D6C1FE286C3004ABF18 /* FetchChatList.swift in Sources */, D0B843831DA6EDB8005F29E1 /* CachedGroupData.swift in Sources */, D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, C239BE9C1E630CA700C2C453 /* UpdatePinnedMessage.swift in Sources */, @@ -1959,6 +1978,7 @@ D03B0CE81D6224AD00955575 /* ViewCountMessageAttribute.swift in Sources */, D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */, D03B0D0C1D62255C00955575 /* AccountStateManagementUtils.swift in Sources */, + D0C26D7B1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift in Sources */, D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, @@ -2062,6 +2082,7 @@ D01C7ED41EF5DF83008305F1 /* LimitsConfiguration.swift in Sources */, C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */, D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */, + D0C26D6A1FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */, D01C7F051EFC1C49008305F1 /* DeviceContact.swift in Sources */, D050F26A1E4A5B6D00988324 /* ManagedGlobalNotificationSettings.swift in Sources */, D050F26B1E4A5B6D00988324 /* ApplyMaxReadIndexInteractively.swift in Sources */, @@ -2116,6 +2137,7 @@ C2E064691ECEEF0B00387BB8 /* TelegramMediaInvoice.swift in Sources */, D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */, D0B844351DAB91E0005F29E1 /* NBPhoneNumberDesc.m in Sources */, + D0C26D7C1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift in Sources */, C205FEA91EB3B75900455808 /* ExportMessageLink.swift in Sources */, D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */, D0B8442F1DAB91E0005F29E1 /* NBMetadataHelper.m in Sources */, @@ -2176,6 +2198,7 @@ D0B8442A1DAB91E0005F29E1 /* NBAsYouTypeFormatter.m in Sources */, D07047B51F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift in Sources */, D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */, + D0C26D6D1FE286C3004ABF18 /* FetchChatList.swift in Sources */, D073CE6E1DCBCF17007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D05A32E21E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */, D0613FD01E60520700202CDB /* ChannelMembers.swift in Sources */, @@ -2262,6 +2285,7 @@ C2FD33E21E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */, D0B8440E1DAB91CD005F29E1 /* MessageUtils.swift in Sources */, D0FA8BAB1E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift in Sources */, + D0C26D671FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */, D0B418BA1D7E05BB004562A4 /* NetworkLogging.m in Sources */, D03C536B1DAD5CA9004C17B3 /* TelegramGroup.swift in Sources */, D0B418941D7E0580004562A4 /* TelegramMediaAction.swift in Sources */, @@ -2555,6 +2579,128 @@ }; name = "Release AppStore"; }; + D0924FE81FE52C12003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FE91FE52C12003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + COPY_PHASE_STRIP = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/third-party/FFmpeg-iOS/lib", + "$(PROJECT_DIR)/third-party/libwebp/lib", + ); + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/TelegramCore/module.private.modulemap"; + OTHER_LDFLAGS = "-Wl,-dead_strip"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FEA1FE52C12003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + INFOPLIST_FILE = TelegramCoreTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FEB1FE52C12003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TelegramCoreMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/TelegramCore/module.private-mac.modulemap"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCoreMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + }; + name = "Release Hockeyapp Internal"; + }; D09D8C131D4FAB1D0081DBEC /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; @@ -2849,6 +2995,7 @@ D09D8C131D4FAB1D0081DBEC /* Debug Hockeyapp */, D09D8C141D4FAB1D0081DBEC /* Debug AppStore */, C22069BE1E8EB4A200E82730 /* Release Hockeyapp */, + D0924FE81FE52C12003F693F /* Release Hockeyapp Internal */, D06706551D51162400DED3E3 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -2860,6 +3007,7 @@ D09D8C161D4FAB1D0081DBEC /* Debug Hockeyapp */, D09D8C171D4FAB1D0081DBEC /* Debug AppStore */, C22069BF1E8EB4A200E82730 /* Release Hockeyapp */, + D0924FE91FE52C12003F693F /* Release Hockeyapp Internal */, D06706561D51162400DED3E3 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -2871,6 +3019,7 @@ D09D8C191D4FAB1D0081DBEC /* Debug Hockeyapp */, D09D8C1A1D4FAB1D0081DBEC /* Debug AppStore */, C22069C01E8EB4A200E82730 /* Release Hockeyapp */, + D0924FEA1FE52C12003F693F /* Release Hockeyapp Internal */, D06706571D51162400DED3E3 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -2882,6 +3031,7 @@ D0B4186D1D7E03D5004562A4 /* Debug Hockeyapp */, D0B4186E1D7E03D5004562A4 /* Debug AppStore */, C22069C11E8EB4A200E82730 /* Release Hockeyapp */, + D0924FEB1FE52C12003F693F /* Release Hockeyapp Internal */, D0B4186F1D7E03D5004562A4 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; diff --git a/TelegramCore.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/TelegramCore.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index e4def498f4..0b14839407 100644 --- a/TelegramCore.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/TelegramCore.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ TelegramCore.xcscheme orderHint - 3 + 4 TelegramCoreMac.xcscheme orderHint - 5 + 6 SuppressBuildableAutocreation diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 7bcd7469bb..04db69f7e2 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -250,6 +250,7 @@ private var declaredEncodables: Void = { declareEncodable(CachedStickerPack.self, f: { CachedStickerPack(decoder: $0) }) declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) }) declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) }) + declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) }) return }() @@ -644,7 +645,7 @@ public class Account { |> mapToSignal { [weak self] value -> Signal in if let strongSelf = self, value { Logger.shared.log("Account", "Became master") - return managedServiceViews(network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager) + return managedServiceViews(accountPeerId: peerId, network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager) } else { Logger.shared.log("Account", "Resigned master") return .never() @@ -662,6 +663,8 @@ public class Account { self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(postbox: self.postbox).start()) self.managedOperationsDisposable.add(managedGlobalNotificationSettings(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizePinnedChatsOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) + + self.managedOperationsDisposable.add(managedSynchronizeGroupedPeersOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .stickers).start()) self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .masks).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkFeaturedStickerPacksAsSeenOperations(postbox: self.postbox, network: self.network).start()) diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index 848746e012..213d818e73 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -32,10 +32,10 @@ final class AccountInitialState { } } -enum AccountStateUpdatePinnerPeerIdsOperation { - case pin(PeerId) - case unpin(PeerId) - case reorder([PeerId]) +enum AccountStateUpdatePinnedItemIdsOperation { + case pin(PinnedItemId) + case unpin(PinnedItemId) + case reorder([PinnedItemId]) case sync } @@ -64,6 +64,7 @@ enum AccountStateMutationOperation { case ReadOutbox(MessageId) case ResetReadState(PeerId, MessageId.Namespace, MessageId.Id, MessageId.Id, MessageId.Id, Int32) case ResetMessageTagSummary(PeerId, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange) + case ReadGroupFeedInbox(PeerGroupId, MessageIndex) case UpdateState(AuthorizedAccountState.State) case UpdateChannelState(PeerId, ChannelState) case UpdateNotificationSettings(AccountStateNotificationSettingsSubject, PeerNotificationSettings) @@ -78,10 +79,11 @@ enum AccountStateMutationOperation { case AddSecretMessages([Api.EncryptedMessage]) case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case AddPeerInputActivity(chatPeerId: PeerId, peerId: PeerId?, activity: PeerInputActivity?) - case UpdatePinnedPeerIds(AccountStateUpdatePinnerPeerIdsOperation) + case UpdatePinnedItemIds(AccountStateUpdatePinnedItemIdsOperation) case ReadMessageContents((PeerId?, [Int32])) case UpdateMessageImpressionCount(MessageId, Int32) case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation) + case UpdateRecentGifs case UpdateChatInputState(PeerId, SynchronizeableChatInputState?) case UpdateCall(Api.PhoneCall) case UpdateLangPack(Api.LangPackDifference?) @@ -178,6 +180,10 @@ struct AccountMutableState { self.addOperation(.ReadOutbox(messageId)) } + mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { + self.addOperation(.ReadGroupFeedInbox(groupId, index)) + } + mutating func resetReadState(_ peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32) { self.addOperation(.ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count)) } @@ -266,8 +272,8 @@ struct AccountMutableState { self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity)) } - mutating func addUpdatePinnedPeerIds(_ operation: AccountStateUpdatePinnerPeerIdsOperation) { - self.addOperation(.UpdatePinnedPeerIds(operation)) + mutating func addUpdatePinnedItemIds(_ operation: AccountStateUpdatePinnedItemIdsOperation) { + self.addOperation(.UpdatePinnedItemIds(operation)) } mutating func addReadMessagesContents(_ peerIdsAndMessageIds: (PeerId?, [Int32])) { @@ -282,6 +288,10 @@ struct AccountMutableState { self.addOperation(.UpdateInstalledStickerPacks(operation)) } + mutating func addUpdateRecentGifs() { + self.addOperation(.UpdateRecentGifs) + } + mutating func addUpdateChatInputState(peerId: PeerId, state: SynchronizeableChatInputState?) { self.addOperation(.UpdateChatInputState(peerId, state)) } @@ -292,7 +302,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage: + case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage: break case let .AddMessages(messages, _): for message in messages { diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 75a2be362c..007d1374b0 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -825,6 +825,12 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.readInbox(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: maxId)) case let .updateReadHistoryOutbox(peer, maxId, _, _): updatedState.readOutbox(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: maxId)) + /*%FEED case let .updateReadFeed(feedId, maxPosition): + switch maxPosition { + case let .feedPosition(date, peer, id): + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date) + updatedState.readGroupFeedInbox(groupId: PeerGroupId(rawValue: feedId), index: index) + }*/ case let .updateWebPage(apiWebpage, _, _): switch apiWebpage { case let .webPageEmpty(id): @@ -993,15 +999,15 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.addPeerInputActivity(chatPeerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), peerId: nil, activity: .typingText) case let .updateDialogPinned(flags, peer): if (flags & (1 << 0)) != 0 { - updatedState.addUpdatePinnedPeerIds(.pin(peer.peerId)) + updatedState.addUpdatePinnedItemIds(.pin(.peer(peer.peerId))) } else { - updatedState.addUpdatePinnedPeerIds(.unpin(peer.peerId)) + updatedState.addUpdatePinnedItemIds(.unpin(.peer(peer.peerId))) } case let .updatePinnedDialogs(_, order): if let order = order { - updatedState.addUpdatePinnedPeerIds(.reorder(order.map { $0.peerId })) + updatedState.addUpdatePinnedItemIds(.reorder(order.map { .peer($0.peerId) })) } else { - updatedState.addUpdatePinnedPeerIds(.sync) + updatedState.addUpdatePinnedItemIds(.sync) } case let .updateReadMessagesContents(messages, _, _): updatedState.addReadMessagesContents((nil, messages)) @@ -1311,7 +1317,7 @@ private func resetChannels(_ account: Account, peers: [Peer], state: AccountMuta dialogsChats.append(contentsOf: chats) dialogsUsers.append(contentsOf: users) - for dialog in dialogs { + loop: for dialog in dialogs { let apiPeer: Api.Peer let apiReadInboxMaxId: Int32 let apiReadOutboxMaxId: Int32 @@ -1330,6 +1336,9 @@ private func resetChannels(_ account: Account, peers: [Peer], state: AccountMuta apiUnreadMentionsCount = unreadMentionsCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts + /*%FEED case .dialogFeed: + assertionFailure() + continue loop*/ } let peerId: PeerId @@ -1661,7 +1670,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedPeerIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage: + case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -1711,6 +1720,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] + var syncRecentGifs = false var langPackDifferences: [Api.LangPackDifference] = [] var pollLangPack = false @@ -1748,6 +1758,8 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif modifier.applyIncomingReadMaxId(messageId) case let .ReadOutbox(messageId): modifier.applyOutgoingReadMaxId(messageId) + case let .ReadGroupFeedInbox(groupId, index): + modifier.applyGroupFeedReadMaxIndex(groupId: groupId, index: index) case let .ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count): modifier.resetIncomingReadStates([peerId: [namespace: .idBased(maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count)]]) case let .ResetMessageTagSummary(peerId, namespace, count, range): @@ -1844,30 +1856,40 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif } else if chatPeerId.namespace == Namespaces.Peer.SecretChat { updatedSecretChatTypingActivities.insert(chatPeerId) } - case let .UpdatePinnedPeerIds(pinnedOperation): + case let .UpdatePinnedItemIds(pinnedOperation): switch pinnedOperation { - case let .pin(peerId): - if modifier.getPeer(peerId) == nil || modifier.getPeerChatListInclusion(peerId) == .notSpecified { - addSynchronizePinnedChatsOperation(modifier: modifier) - } else { - var currentPeerIds = modifier.getPinnedPeerIds() - if !currentPeerIds.contains(peerId) { - currentPeerIds.insert(peerId, at: 0) - modifier.setPinnedPeerIds(currentPeerIds) - } + case let .pin(itemId): + switch itemId { + case let .peer(peerId): + if modifier.getPeer(peerId) == nil || modifier.getPeerChatListInclusion(peerId) == .notSpecified { + addSynchronizePinnedChatsOperation(modifier: modifier) + } else { + var currentItemIds = modifier.getPinnedItemIds() + if !currentItemIds.contains(.peer(peerId)) { + currentItemIds.insert(.peer(peerId), at: 0) + modifier.setPinnedItemIds(currentItemIds) + } + } + case let .group(groupId): + break } - case let .unpin(peerId): - var currentPeerIds = modifier.getPinnedPeerIds() - if let index = currentPeerIds.index(of: peerId) { - currentPeerIds.remove(at: index) - modifier.setPinnedPeerIds(currentPeerIds) - } else { - addSynchronizePinnedChatsOperation(modifier: modifier) + case let .unpin(itemId): + switch itemId { + case let .peer(peerId): + var currentItemIds = modifier.getPinnedItemIds() + if let index = currentItemIds.index(of: .peer(peerId)) { + currentItemIds.remove(at: index) + modifier.setPinnedItemIds(currentItemIds) + } else { + addSynchronizePinnedChatsOperation(modifier: modifier) + } + case let .group(groupId): + break } - case let .reorder(peerIds): - let currentPeerIds = modifier.getPinnedPeerIds() - if Set(peerIds) == Set(currentPeerIds) { - modifier.setPinnedPeerIds(peerIds) + case let .reorder(itemIds): + let currentItemIds = modifier.getPinnedItemIds() + if Set(itemIds) == Set(currentItemIds) { + modifier.setPinnedItemIds(itemIds) } else { addSynchronizePinnedChatsOperation(modifier: modifier) } @@ -1901,6 +1923,8 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif }) case let .UpdateInstalledStickerPacks(operation): stickerPackOperations.append(operation) + case let .UpdateRecentGifs: + syncRecentGifs = true case let .UpdateChatInputState(peerId, inputState): modifier.updatePeerChatInterfaceState(peerId, update: { current in return auxiliaryMethods.updatePeerChatInputState(current, inputState) @@ -2041,6 +2065,10 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif } } + if syncRecentGifs { + addSynchronizeSavedGifsOperation(modifier: modifier, operation: .sync) + } + for chatPeerId in updatedSecretChatTypingActivities { if let peer = modifier.getPeer(chatPeerId) as? TelegramSecretChat { let authorId = peer.regularPeerId diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift index ce458c4614..734fca1c86 100644 --- a/TelegramCore/AccountStateManager.swift +++ b/TelegramCore/AccountStateManager.swift @@ -247,7 +247,7 @@ public final class AccountStateManager { |> take(1) |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in if let authorizedState = state.state { - let request = account.network.request(Api.functions.updates.getDifference(flags: 1 << 0, pts: authorizedState.pts, ptsTotalLimit: 100, date: authorizedState.date, qts: authorizedState.qts)) + let request = account.network.request(Api.functions.updates.getDifference(flags: 1 << 0, pts: authorizedState.pts, ptsTotalLimit: 300, date: authorizedState.date, qts: authorizedState.qts)) |> retryRequest return request |> mapToSignal { difference -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in switch difference { diff --git a/TelegramCore/AccountStateReset.swift b/TelegramCore/AccountStateReset.swift index 542cd3bf36..09065e90d8 100644 --- a/TelegramCore/AccountStateReset.swift +++ b/TelegramCore/AccountStateReset.swift @@ -16,6 +16,7 @@ func accountStateReset(postbox: Postbox, network: Network) -> Signal retryRequest + /*%FEED */ return combineLatest(network.request(Api.functions.messages.getDialogs(flags: 0, offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 100)) |> retryRequest, pinnedChats, state) |> mapToSignal { result, pinnedChats, state -> Signal in @@ -40,7 +41,7 @@ func accountStateReset(postbox: Postbox, network: Network) -> Signal Signal Signal Signal Signal Signal Signal [AdditionalMessageHistoryViewData] { +private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] { var result = additionalData - if peerId.namespace == Namespaces.Peer.CloudChannel { - if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { - result.append(.peerChatState(peerId)) - } + switch chatLocation { + case let .peer(peerId): + if peerId.namespace == Namespaces.Peer.CloudChannel { + if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { + result.append(.peerChatState(peerId)) + } + } + case .group: + break } return result } @@ -534,7 +539,7 @@ public final class AccountViewTracker { } } - func wrappedMessageHistorySignal(peerId: PeerId, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + func wrappedMessageHistorySignal(chatLocation: ChatLocation, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { let history = withState(signal, { [weak self] () -> Int32 in if let strongSelf = self { return OSAtomicIncrement32(&strongSelf.nextViewId) @@ -545,20 +550,25 @@ public final class AccountViewTracker { if let strongSelf = self { let messageIds = pendingWebpages(entries: next.0.entries) strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds) - if peerId.namespace == Namespaces.Peer.CloudChannel { + if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: next.0) } } }, disposed: { [weak self] viewId in if let strongSelf = self { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: []) - if peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: nil) + switch chatLocation { + case let .peer(peerId): + if peerId.namespace == Namespaces.Peer.CloudChannel { + strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: nil) + } + case .group: + break } } }) - if peerId.namespace == Namespaces.Peer.CloudChannel { + if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { return Signal { subscriber in let disposable = history.start(next: { next in subscriber.putNext(next) @@ -578,28 +588,28 @@ public final class AccountViewTracker { } } - public func aroundMessageOfInterestHistoryViewForPeerId(_ peerId: PeerId, count: Int, clipHoles: Bool = true, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundMessageOfInterestHistoryViewForPeerId(peerId, count: count, clipHoles: clipHoles, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) - return wrappedMessageHistorySignal(peerId: peerId, signal: signal) + let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, clipHoles: clipHoles, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal) } else { return .never() } } - public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, clipHoles: Bool = true, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, clipHoles: clipHoles, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) - return wrappedMessageHistorySignal(peerId: peerId, signal: signal) + let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, clipHoles: clipHoles, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal) } else { return .never() } } - public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, anchorIndex: anchorIndex, count: count, clipHoles: clipHoles, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData)) - return wrappedMessageHistorySignal(peerId: peerId, signal: signal) + let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, clipHoles: clipHoles, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal) } else { return .never() } @@ -866,17 +876,17 @@ public final class AccountViewTracker { } } - public func tailChatListView(count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func tailChatListView(groupId: PeerGroupId?, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return account.postbox.tailChatListView(count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud))) + return account.postbox.tailChatListView(groupId: groupId, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud))) } else { return .never() } } - public func aroundChatListView(index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func aroundChatListView(groupId: PeerGroupId?, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return account.postbox.aroundChatListView(index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud))) + return account.postbox.aroundChatListView(groupId: groupId, index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud))) } else { return .never() } diff --git a/TelegramCore/ApiGroupOrChannel.swift b/TelegramCore/ApiGroupOrChannel.swift index b9e5ae505c..c92c9fd648 100644 --- a/TelegramCore/ApiGroupOrChannel.swift +++ b/TelegramCore/ApiGroupOrChannel.swift @@ -50,7 +50,7 @@ public func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], migrationReference: nil, creationDate: 0, version: 0) case let .chatForbidden(id, title): return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], migrationReference: nil, creationDate: 0, version: 0) - case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, _): + case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, _):/*%FEED */ let participationStatus: TelegramChannelParticipationStatus if (flags & Int32(1 << 1)) != 0 { participationStatus = .kicked @@ -90,7 +90,7 @@ public func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { restrictionInfo = nil } - return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: version, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChannelAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChannelBannedRights.init)) + return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: version, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChannelAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChannelBannedRights.init), peerGroupId: nil)/*%FEED */ case let .channelForbidden(flags, id, accessHash, title, untilDate): let info: TelegramChannelInfo if (flags & Int32(1 << 8)) != 0 { @@ -99,7 +99,7 @@ public func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { info = .broadcast(TelegramChannelBroadcastInfo(flags: [])) } - return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChannelBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max)) + return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChannelBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), peerGroupId: nil) } } @@ -107,7 +107,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { switch rhs { case .chat, .chatEmpty, .chatForbidden, .channelForbidden: return parseTelegramGroupOrChannel(chat: rhs) - case let .channel(flags, _, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, _): + case let .channel(flags, _, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, _):/*%FEED */ if let _ = accessHash { return parseTelegramGroupOrChannel(chat: rhs) } else if let lhs = lhs as? TelegramChannel { @@ -128,7 +128,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { } info = .group(TelegramChannelGroupInfo(flags: infoFlags)) } - return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights) + return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, peerGroupId: lhs.peerGroupId) } else { return nil } diff --git a/TelegramCore/Authorization.swift b/TelegramCore/Authorization.swift index 34b5d36633..82104fc774 100644 --- a/TelegramCore/Authorization.swift +++ b/TelegramCore/Authorization.swift @@ -174,9 +174,9 @@ public func authorizeWithCode(account: UnauthorizedAccount, code: String) -> Sig case let .authorization(_, _, user): let user = TelegramUser(user: user) let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) - modifier.updatePeersInternal([user], update: { current, peer -> Peer? in + /*modifier.updatePeersInternal([user], update: { current, peer -> Peer? in return peer - }) + })*/ modifier.setState(state) } } @@ -220,9 +220,9 @@ public func authorizeWithPassword(account: UnauthorizedAccount, password: String case let .authorization(_, _, user): let user = TelegramUser(user: user) let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) - modifier.updatePeersInternal([user], update: { current, peer -> Peer? in + /*modifier.updatePeersInternal([user], update: { current, peer -> Peer? in return peer - }) + })*/ modifier.setState(state) } } diff --git a/TelegramCore/ChatHistoryPreloadManager.swift b/TelegramCore/ChatHistoryPreloadManager.swift index 6c44c83108..83e473497c 100644 --- a/TelegramCore/ChatHistoryPreloadManager.swift +++ b/TelegramCore/ChatHistoryPreloadManager.swift @@ -120,7 +120,7 @@ final class ChatHistoryPreloadManager { self.network = network self.download.set(network.download(datacenterId: network.datacenterId, tag: nil)) - self.automaticChatListDisposable = (postbox.tailChatListView(count: 20, summaryComponents: ChatListEntrySummaryComponents()) |> deliverOnMainQueue).start(next: { [weak self] view in + self.automaticChatListDisposable = (postbox.tailChatListView(groupId: nil, count: 20, summaryComponents: ChatListEntrySummaryComponents()) |> deliverOnMainQueue).start(next: { [weak self] view in if let strongSelf = self { var indices: [(ChatListIndex, Bool, Bool)] = [] for entry in view.0.entries { diff --git a/TelegramCore/ContactManagement.swift b/TelegramCore/ContactManagement.swift index 99e7814030..c74c99f806 100644 --- a/TelegramCore/ContactManagement.swift +++ b/TelegramCore/ContactManagement.swift @@ -55,7 +55,7 @@ private func hashForCountAndIds(count: Int32, ids: [Int32]) -> Int32 { func manageContacts(network: Network, postbox: Postbox) -> Signal { #if os(iOS) && DEBUG - return .never() + //return .never() #endif let initialContactPeerIdsHash = postbox.contactPeerIdsView() |> take(1) diff --git a/TelegramCore/EnqueueMessage.swift b/TelegramCore/EnqueueMessage.swift index c47a5dc8c9..59adab6d26 100644 --- a/TelegramCore/EnqueueMessage.swift +++ b/TelegramCore/EnqueueMessage.swift @@ -305,7 +305,16 @@ func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messa sourceId = peer.id sourceMessageId = sourceMessage.id } - forwardInfo = StoreMessageForwardInfo(authorId: author.id, sourceId: sourceId, sourceMessageId: sourceMessageId, date: sourceMessage.timestamp, authorSignature: nil) + + var authorSignature: String? + for attribute in sourceMessage.attributes { + if let attribute = attribute as? AuthorSignatureMessageAttribute { + authorSignature = attribute.signature + break + } + } + + forwardInfo = StoreMessageForwardInfo(authorId: author.id, sourceId: sourceId, sourceMessageId: sourceMessageId, date: sourceMessage.timestamp, authorSignature: authorSignature) } else { forwardInfo = nil } diff --git a/TelegramCore/ExportMessageLink.swift b/TelegramCore/ExportMessageLink.swift index c2ebb6a79a..becd17d095 100644 --- a/TelegramCore/ExportMessageLink.swift +++ b/TelegramCore/ExportMessageLink.swift @@ -15,8 +15,8 @@ public func exportMessageLink(account:Account, peerId:PeerId, messageId:MessageI if let peer = peer, let input = apiInputChannel(peer) { return account.network.request(Api.functions.channels.exportMessageLink(channel: input, id: messageId.id)) |> mapError {_ in return } |> map { res in switch res { - case .exportedMessageLink(let link): - return link + case let .exportedMessageLink(link): + return link } } |> `catch` { _ -> Signal in return .single(nil) diff --git a/TelegramCore/FetchChatList.swift b/TelegramCore/FetchChatList.swift new file mode 100644 index 0000000000..0a35ca2a64 --- /dev/null +++ b/TelegramCore/FetchChatList.swift @@ -0,0 +1,308 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +enum FetchChatListLocation { + case general + case group(PeerGroupId) +} + +struct ParsedDialogs { + let itemIds: [PinnedItemId] + let peers: [Peer] + let peerPresences: [PeerId: PeerPresence] + + let notificationSettings: [PeerId: PeerNotificationSettings] + let readStates: [PeerId: [MessageId.Namespace: PeerReadState]] + let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] + let chatStates: [PeerId: PeerChatState] + + let storeMessages: [StoreMessage] + + let lowerNonPinnedIndex: MessageIndex? + let referencedFeeds: [PeerGroupId] +} + +private func extractDialogsData(dialogs: Api.messages.Dialogs) -> (apiDialogs: [Api.Dialog], apiMessages: [Api.Message], apiChats: [Api.Chat], apiUsers: [Api.User], apiIsAtLowestBoundary: Bool) { + switch dialogs { + case let .dialogs(dialogs, messages, chats, users): + return (dialogs, messages, chats, users, true) + case let .dialogsSlice(_, dialogs, messages, chats, users): + return (dialogs, messages, chats, users, false) + } +} + +private func extractDialogsData(peerDialogs: Api.messages.PeerDialogs) -> (apiDialogs: [Api.Dialog], apiMessages: [Api.Message], apiChats: [Api.Chat], apiUsers: [Api.User], apiIsAtLowestBoundary: Bool) { + switch peerDialogs { + case let .peerDialogs(dialogs, messages, chats, users, _): + return (dialogs, messages, chats, users, false) + } +} + +private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], apiChats: [Api.Chat], apiUsers: [Api.User], apiIsAtLowestBoundary: Bool) -> ParsedDialogs { + var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] + var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var chatStates: [PeerId: PeerChatState] = [:] + + var storeMessages: [StoreMessage] = [] + var nonPinnedDialogsTopMessageIds = Set() + + var referencedFeeds = Set() + var itemIds: [PinnedItemId] = [] + + for dialog in apiDialogs { + let apiPeer: Api.Peer + let apiReadInboxMaxId: Int32 + let apiReadOutboxMaxId: Int32 + let apiTopMessage: Int32 + let apiUnreadCount: Int32 + let apiUnreadMentionsCount: Int32 + var apiChannelPts: Int32? + let apiNotificationSettings: Api.PeerNotifySettings + switch dialog { + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _): + itemIds.append(.peer(peer.peerId)) + apiPeer = peer + apiTopMessage = topMessage + apiReadInboxMaxId = readInboxMaxId + apiReadOutboxMaxId = readOutboxMaxId + apiUnreadCount = unreadCount + apiUnreadMentionsCount = unreadMentionsCount + apiNotificationSettings = peerNotificationSettings + apiChannelPts = pts + let isPinned = (flags & (1 << 2)) != 0 + if !isPinned { + nonPinnedDialogsTopMessageIds.insert(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage)) + } + let peerId: PeerId + switch apiPeer { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } + + if readStates[peerId] == nil { + readStates[peerId] = [:] + } + readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) + + if apiTopMessage != 0 { + mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) + } + + if let apiChannelPts = apiChannelPts { + chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) + } + + notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) + /*%FEED case let .dialogFeed(_, _, _, feedId, _, _, _, _): + itemIds.append(.group(PeerGroupId(rawValue: feedId))) + referencedFeeds.insert(PeerGroupId(rawValue: feedId))*/ + } + } + + var lowerNonPinnedIndex: MessageIndex? + + for message in apiMessages { + if let storeMessage = StoreMessage(apiMessage: message) { + var updatedStoreMessage = storeMessage + if case let .Id(id) = storeMessage.id { + if let channelState = chatStates[id.peerId] as? ChannelState { + var updatedAttributes = storeMessage.attributes + updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) + updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes) + } + + if !apiIsAtLowestBoundary, nonPinnedDialogsTopMessageIds.contains(id) { + let index = MessageIndex(id: id, timestamp: storeMessage.timestamp) + if lowerNonPinnedIndex == nil || lowerNonPinnedIndex! > index { + lowerNonPinnedIndex = index + } + } + } + storeMessages.append(updatedStoreMessage) + } + } + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in apiChats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in apiUsers { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + return ParsedDialogs( + itemIds: itemIds, + peers: peers, + peerPresences: peerPresences, + + notificationSettings: notificationSettings, + readStates: readStates, + mentionTagSummaries: mentionTagSummaries, + chatStates: chatStates, + + storeMessages: storeMessages, + + lowerNonPinnedIndex: lowerNonPinnedIndex, + referencedFeeds: Array(referencedFeeds) + ) +} + +struct FetchedChatList { + let peers: [Peer] + let peerPresences: [PeerId: PeerPresence] + let notificationSettings: [PeerId: PeerNotificationSettings] + let readStates: [PeerId: [MessageId.Namespace: PeerReadState]] + let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] + let chatStates: [PeerId: PeerChatState] + let storeMessages: [StoreMessage] + + let lowerNonPinnedIndex: MessageIndex? + + let pinnedItemIds: [PinnedItemId]? + let feeds: [(PeerGroupId, MessageIndex?)] +} + +func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLocation, upperBound: MessageIndex) -> Signal { + let offset: Signal<(Int32, Int32, Api.InputPeer), NoError> + if upperBound.id.peerId.namespace == Namespaces.Peer.Empty { + offset = single((0, 0, Api.InputPeer.inputPeerEmpty), NoError.self) + } else { + offset = postbox.loadedPeerWithId(upperBound.id.peerId) + |> take(1) + |> map { peer in + return (upperBound.timestamp, upperBound.id.id + 1, apiInputPeer(peer) ?? .inputPeerEmpty) + } + } + + return offset + |> mapToSignal { (timestamp, id, peer) -> Signal in + let additionalPinnedChats: Signal + if case .general = location, case .inputPeerEmpty = peer, timestamp == 0 { + additionalPinnedChats = network.request(Api.functions.messages.getPinnedDialogs()) + |> retryRequest + |> map { Optional($0) } + } else { + additionalPinnedChats = .single(nil) + } + + var flags: Int32 = 0 + var requestFeedId: Int32? + + switch location { + case .general: + break + case let .group(groupId): + break + /*%FEED requestFeedId = groupId.rawValue + flags |= 1 << 1*/ + } + let requestChats = network.request(Api.functions.messages.getDialogs(flags: flags, offsetDate: timestamp, offsetId: id, offsetPeer: peer, limit: 100)) + |> retryRequest + + return combineLatest(requestChats, additionalPinnedChats) + |> mapToSignal { remoteChats, pinnedChats -> Signal in + let extractedRemoteDialogs = extractDialogsData(dialogs: remoteChats) + let parsedRemoteChats = parseDialogs(apiDialogs: extractedRemoteDialogs.apiDialogs, apiMessages: extractedRemoteDialogs.apiMessages, apiChats: extractedRemoteDialogs.apiChats, apiUsers: extractedRemoteDialogs.apiUsers, apiIsAtLowestBoundary: extractedRemoteDialogs.apiIsAtLowestBoundary) + var parsedPinnedChats: ParsedDialogs? + if let pinnedChats = pinnedChats { + let extractedPinnedChats = extractDialogsData(peerDialogs: pinnedChats) + parsedPinnedChats = parseDialogs(apiDialogs: extractedPinnedChats.apiDialogs, apiMessages: extractedPinnedChats.apiMessages, apiChats: extractedPinnedChats.apiChats, apiUsers: extractedPinnedChats.apiUsers, apiIsAtLowestBoundary: extractedPinnedChats.apiIsAtLowestBoundary) + } + + var combinedReferencedFeeds = Set() + combinedReferencedFeeds.formUnion(parsedRemoteChats.referencedFeeds) + if let parsedPinnedChats = parsedPinnedChats { + combinedReferencedFeeds.formUnion(parsedPinnedChats.referencedFeeds) + } + + var feedSignals: [Signal<(PeerGroupId, ParsedDialogs), NoError>] = [] + if case .general = location { + /*%FEED for groupId in combinedReferencedFeeds { + let flags: Int32 = 1 << 1 + let requestFeed = network.request(Api.functions.messages.getDialogs(flags: flags, feedId: groupId.rawValue, offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 4)) + |> retryRequest + |> map { result -> (PeerGroupId, ParsedDialogs) in + let extractedData = extractDialogsData(dialogs: result) + let parsedChats = parseDialogs(apiDialogs: extractedData.apiDialogs, apiMessages: extractedData.apiMessages, apiChats: extractedData.apiChats, apiUsers: extractedData.apiUsers, apiIsAtLowestBoundary: extractedData.apiIsAtLowestBoundary) + return (groupId, parsedChats) + } + feedSignals.append(requestFeed) + }*/ + } + + return combineLatest(feedSignals) + |> map { feeds -> FetchedChatList in + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] + var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var chatStates: [PeerId: PeerChatState] = [:] + var storeMessages: [StoreMessage] = [] + + peers.append(contentsOf: parsedRemoteChats.peers) + peerPresences.merge(parsedRemoteChats.peerPresences, uniquingKeysWith: { _, updated in updated }) + notificationSettings.merge(parsedRemoteChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + readStates.merge(parsedRemoteChats.readStates, uniquingKeysWith: { _, updated in updated }) + mentionTagSummaries.merge(parsedRemoteChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + chatStates.merge(parsedRemoteChats.chatStates, uniquingKeysWith: { _, updated in updated }) + storeMessages.append(contentsOf: parsedRemoteChats.storeMessages) + + if let parsedPinnedChats = parsedPinnedChats { + peers.append(contentsOf: parsedPinnedChats.peers) + peerPresences.merge(parsedPinnedChats.peerPresences, uniquingKeysWith: { _, updated in updated }) + notificationSettings.merge(parsedPinnedChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + readStates.merge(parsedPinnedChats.readStates, uniquingKeysWith: { _, updated in updated }) + mentionTagSummaries.merge(parsedPinnedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + chatStates.merge(parsedPinnedChats.chatStates, uniquingKeysWith: { _, updated in updated }) + storeMessages.append(contentsOf: parsedPinnedChats.storeMessages) + } + + for (_, feedChats) in feeds { + peers.append(contentsOf: feedChats.peers) + peerPresences.merge(feedChats.peerPresences, uniquingKeysWith: { _, updated in updated }) + notificationSettings.merge(feedChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + readStates.merge(feedChats.readStates, uniquingKeysWith: { _, updated in updated }) + mentionTagSummaries.merge(feedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + chatStates.merge(feedChats.chatStates, uniquingKeysWith: { _, updated in updated }) + storeMessages.append(contentsOf: feedChats.storeMessages) + } + + return FetchedChatList( + peers: peers, + peerPresences: peerPresences, + notificationSettings: notificationSettings, + readStates: readStates, + mentionTagSummaries: mentionTagSummaries, + chatStates: chatStates, + storeMessages: storeMessages, + + lowerNonPinnedIndex: parsedRemoteChats.lowerNonPinnedIndex, + + pinnedItemIds: parsedPinnedChats.flatMap { $0.itemIds }, + feeds: feeds.map { ($0.0, $0.1.lowerNonPinnedIndex) } + ) + } + } + } +} diff --git a/TelegramCore/Holes.swift b/TelegramCore/Holes.swift index e3b43ddb03..1cc052a5da 100644 --- a/TelegramCore/Holes.swift +++ b/TelegramCore/Holes.swift @@ -42,11 +42,9 @@ enum FetchMessageHistoryHoleSource { func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Postbox, hole: MessageHistoryHole, direction: MessageHistoryViewRelativeHoleDirection, tagMask: MessageTags?, limit: Int = 100) -> Signal { return postbox.loadedPeerWithId(hole.maxIndex.id.peerId) |> take(1) - //|> delay(4.0, queue: Queue.concurrentDefaultQueue()) |> mapToSignal { peer in if let inputPeer = apiInputPeer(peer) { - //Logger.shared.log("Holes", "fetchMessageHistoryHole for \(peer.displayTitle)") - print("fetchMessageHistoryHole for \(peer.displayTitle)") + //print("fetchMessageHistoryHole for \(peer.displayTitle)") let request: Signal var maxIndexRequest: Signal = .single(nil) if let tagMask = tagMask { @@ -65,12 +63,17 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos minId = 1 case .LowerToUpper: offsetId = hole.min <= 1 ? 1 : (hole.min - 1) - addOffset = Int32(-limit) + addOffset = Int32(-selectedLimit) maxId = Int32.max minId = hole.min - 1 case let .AroundId(id): offsetId = id.id - addOffset = Int32(-limit / 2) + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + case let .AroundIndex(index): + offsetId = index.id.id + addOffset = Int32(-selectedLimit / 2) maxId = Int32.max minId = 1 } @@ -90,12 +93,17 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos minId = 1 case .LowerToUpper: offsetId = hole.min <= 1 ? 1 : (hole.min - 1) - addOffset = Int32(-limit) + addOffset = Int32(-selectedLimit) maxId = Int32.max minId = hole.min - 1 case let .AroundId(id): offsetId = id.id - addOffset = Int32(-limit / 2) + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + case let .AroundIndex(index): + offsetId = index.id.id + addOffset = Int32(-selectedLimit / 2) maxId = Int32.max minId = 1 } @@ -120,7 +128,7 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos minId = 1 case .LowerToUpper: offsetId = hole.min <= 1 ? 1 : (hole.min - 1) - addOffset = Int32(-limit) + addOffset = Int32(-selectedLimit) maxId = Int32.max minId = hole.min - 1 if hole.maxIndex.timestamp == Int32.max { @@ -131,7 +139,12 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos } case let .AroundId(id): offsetId = id.id - addOffset = Int32(-limit / 2) + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + case let .AroundIndex(index): + offsetId = index.id.id + addOffset = Int32(-selectedLimit / 2) maxId = Int32.max minId = 1 } @@ -196,11 +209,13 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos let fillDirection: HoleFillDirection switch direction { case .UpperToLower: - fillDirection = .UpperToLower + fillDirection = .UpperToLower(updatedMinIndex: nil, clippingMaxIndex: nil) case .LowerToUpper: - fillDirection = .LowerToUpper(updatedMaxIndex: updatedMaxIndex) + fillDirection = .LowerToUpper(updatedMaxIndex: updatedMaxIndex, clippingMinIndex: nil) case let .AroundId(id): fillDirection = .AroundId(id, lowerComplete: false, upperComplete: false) + case let .AroundIndex(index): + fillDirection = .AroundId(index.id, lowerComplete: false, upperComplete: false) } modifier.fillMultipleHoles(hole, fillType: HoleFill(complete: messages.count == 0, direction: fillDirection), tagMask: tagMask, messages: storeMessages) @@ -236,338 +251,246 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos } } -func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) -> Signal { - let offset: Signal<(Int32, Int32, Api.InputPeer), NoError> - if hole.index.id.peerId.namespace == Namespaces.Peer.Empty { - offset = single((0, 0, Api.InputPeer.inputPeerEmpty), NoError.self) - } else { - offset = postbox.loadedPeerWithId(hole.index.id.peerId) - |> take(1) - |> map { peer in - return (hole.index.timestamp, hole.index.id.id + 1, apiInputPeer(peer) ?? .inputPeerEmpty) - } +private func groupBoundaryPeer(_ peerId: PeerId, accountPeerId: PeerId) -> Api.Peer { + switch peerId.namespace { + case Namespaces.Peer.CloudUser: + return Api.Peer.peerUser(userId: peerId.id) + case Namespaces.Peer.CloudGroup: + return Api.Peer.peerChat(chatId: peerId.id) + case Namespaces.Peer.CloudChannel: + return Api.Peer.peerChannel(channelId: peerId.id) + default: + return Api.Peer.peerUser(userId: accountPeerId.id) } - return offset - |> mapToSignal { (timestamp, id, peer) in - let pinnedChats: Signal - if case .inputPeerEmpty = peer, timestamp == 0 { - pinnedChats = network.request(Api.functions.messages.getPinnedDialogs()) - |> retryRequest - |> map { Optional($0) } - } else { - pinnedChats = .single(nil) +} + +func fetchGroupFeedHole(source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, postbox: Postbox, groupId: PeerGroupId, minIndex: MessageIndex, maxIndex: MessageIndex, direction: MessageHistoryViewRelativeHoleDirection, limit: Int = 100) -> Signal { + /*%FEED return postbox.modify { modifier -> (Peer?, Peer?) in + return (modifier.getPeer(minIndex.id.peerId), modifier.getPeer(maxIndex.id.peerId)) + } + |> mapToSignal { lowerPeer, upperPeer in + print("fetchGroupFeedHole for \(groupId)") + + let request: Signal + + let offsetPosition: Api.FeedPosition + let addOffset: Int32 + let selectedLimit = limit + var maxPositionAndClipIndex: (Api.FeedPosition, MessageIndex)? + var minPositionAndClipIndex: (Api.FeedPosition, MessageIndex)? + + let lowerInputPeer: Api.Peer = groupBoundaryPeer(minIndex.id.peerId, accountPeerId: accountPeerId) + let upperInputPeer: Api.Peer = groupBoundaryPeer(maxIndex.id.peerId, accountPeerId: accountPeerId) + + switch direction { + case .UpperToLower: + let upperIndex = maxIndex.successor() + offsetPosition = .feedPosition(date: min(upperIndex.timestamp, Int32.max - 1), peer: upperInputPeer, id: min(upperIndex.id.id, Int32.max - 1)) + addOffset = 0 + maxPositionAndClipIndex = (.feedPosition(date: min(upperIndex.timestamp, Int32.max - 1), peer: upperInputPeer, id: min(upperIndex.id.id, Int32.max - 1)), maxIndex) + //minPosition = .feedPosition(date: 1, peer: lowerInputPeer, id: 1) + case .LowerToUpper: + let lowerIndex = minIndex.predecessor() + offsetPosition = .feedPosition(date: minIndex.timestamp, peer: lowerInputPeer, id: lowerIndex.id.id) + addOffset = Int32(-selectedLimit) + //maxPosition = .feedPosition(date: Int32.max, peer: emptyInputPeer, id: Int32.max) + minPositionAndClipIndex = (.feedPosition(date: minIndex.timestamp, peer: lowerInputPeer, id: max(0, minIndex.id.id - 1)), minIndex) + case .AroundId: + preconditionFailure() + case let .AroundIndex(index): + let upperIndex = maxIndex.successor() + let lowerIndex = minIndex.predecessor() + + offsetPosition = .feedPosition(date: index.timestamp, peer: groupBoundaryPeer(index.id.peerId, accountPeerId: accountPeerId), id: index.id.id) + addOffset = Int32(-selectedLimit / 2) + if maxIndex.timestamp <= Int32.max - 1 { + //maxPositionAndClipIndex = (.feedPosition(date: upperIndex.timestamp, peer: upperInputPeer, id: upperIndex.id.id), maxIndex) + } + if minIndex.timestamp >= 2 { + //minPositionAndClipIndex = (.feedPosition(date: lowerIndex.timestamp, peer: lowerInputPeer, id: lowerIndex.id.id), minIndex) + } } - return combineLatest(network.request(Api.functions.messages.getDialogs(flags: 0, offsetDate: timestamp, offsetId: id, offsetPeer: peer, limit: 100)) - |> retryRequest, pinnedChats) - |> mapToSignal { result, pinnedChats -> Signal in - var dialogsChats: [Api.Chat] = [] - var dialogsUsers: [Api.User] = [] - - var replacementHole: ChatListHole? - var storeMessages: [StoreMessage] = [] - var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] - var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] - var chatStates: [PeerId: PeerChatState] = [:] - var notificationSettings: [PeerId: PeerNotificationSettings] = [:] - - switch result { - case let .dialogs(dialogs, messages, chats, users): - dialogsChats.append(contentsOf: chats) - dialogsUsers.append(contentsOf: users) - - for dialog in dialogs { - let apiPeer: Api.Peer - let apiReadInboxMaxId: Int32 - let apiReadOutboxMaxId: Int32 - let apiTopMessage: Int32 - let apiUnreadCount: Int32 - let apiUnreadMentionsCount: Int32 - var apiChannelPts: Int32? - let apiNotificationSettings: Api.PeerNotifySettings - switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _): - apiPeer = peer - apiTopMessage = topMessage - apiReadInboxMaxId = readInboxMaxId - apiReadOutboxMaxId = readOutboxMaxId - apiUnreadCount = unreadCount - apiUnreadMentionsCount = unreadMentionsCount - apiNotificationSettings = peerNotificationSettings - apiChannelPts = pts - } - - let peerId: PeerId - switch apiPeer { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } - - if readStates[peerId] == nil { - readStates[peerId] = [:] - } - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) - - if apiTopMessage != 0 { - mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) - } - - if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) - } - - notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) - } - - for message in messages { - if let storeMessage = StoreMessage(apiMessage: message) { - var updatedStoreMessage = storeMessage - if case let .Id(id) = storeMessage.id { - if let channelState = chatStates[id.peerId] as? ChannelState { - var updatedAttributes = storeMessage.attributes - updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) - updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes) - } - } - storeMessages.append(updatedStoreMessage) - } - } - case let .dialogsSlice(_, dialogs, messages, chats, users): - var intermediateMessages: [StoreMessage] = [] - for message in messages { - if let storeMessage = StoreMessage(apiMessage: message) { - intermediateMessages.append(storeMessage) - } - } - - dialogsChats.append(contentsOf: chats) - dialogsUsers.append(contentsOf: users) - - for dialog in dialogs { - let apiPeer: Api.Peer - let apiTopMessage: Int32 - let apiReadInboxMaxId: Int32 - let apiReadOutboxMaxId: Int32 - let apiUnreadCount: Int32 - let apiUnreadMentionsCount: Int32 - let apiNotificationSettings: Api.PeerNotifySettings - let isPinned: Bool - switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, _, _): - isPinned = (flags & (1 << 2)) != 0 - apiPeer = peer - apiTopMessage = topMessage - apiReadInboxMaxId = readInboxMaxId - apiReadOutboxMaxId = readOutboxMaxId - apiUnreadCount = unreadCount - apiUnreadMentionsCount = unreadMentionsCount - apiNotificationSettings = peerNotificationSettings - } - - let peerId: PeerId - switch apiPeer { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } - - if readStates[peerId] == nil { - readStates[peerId] = [:] - } - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) - - if apiTopMessage != 0 { - mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) - } - - notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) - - let topMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: apiTopMessage) - - var timestamp: Int32? - for message in intermediateMessages { - if case let .Id(id) = message.id, id == topMessageId { - timestamp = message.timestamp - } - } - - if let timestamp = timestamp { - let index = MessageIndex(id: MessageId(peerId: topMessageId.peerId, namespace: topMessageId.namespace, id: topMessageId.id - 1), timestamp: timestamp) - if !isPinned && (replacementHole == nil || replacementHole!.index > index) { - replacementHole = ChatListHole(index: index) - } - } - } - - for storeMessage in intermediateMessages { - var updatedStoreMessage = storeMessage - if case let .Id(id) = storeMessage.id { - if let channelState = chatStates[id.peerId] as? ChannelState { - var updatedAttributes = storeMessage.attributes - updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) - updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes) - } - } - storeMessages.append(updatedStoreMessage) - } - } - - var replacePinnedPeerIds: [PeerId]? - - if let pinnedChats = pinnedChats { - switch pinnedChats { - case let .peerDialogs(apiDialogs, apiMessages, apiChats, apiUsers, _): - dialogsChats.append(contentsOf: apiChats) - dialogsUsers.append(contentsOf: apiUsers) - - var peerIds: [PeerId] = [] - - for dialog in apiDialogs { - let apiPeer: Api.Peer - let apiReadInboxMaxId: Int32 - let apiReadOutboxMaxId: Int32 - let apiTopMessage: Int32 - let apiUnreadCount: Int32 - let apiUnreadMentionsCount: Int32 - var apiChannelPts: Int32? - let apiNotificationSettings: Api.PeerNotifySettings - switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _): - apiPeer = peer - apiTopMessage = topMessage - apiReadInboxMaxId = readInboxMaxId - apiReadOutboxMaxId = readOutboxMaxId - apiUnreadCount = unreadCount - apiUnreadMentionsCount = unreadMentionsCount - apiNotificationSettings = peerNotificationSettings - apiChannelPts = pts - } - - let peerId: PeerId - switch apiPeer { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } - - peerIds.append(peerId) - - if readStates[peerId] == nil { - readStates[peerId] = [:] - } - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) - - if apiTopMessage != 0 { - mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) - } - - if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil) - } - - notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) - } - - replacePinnedPeerIds = peerIds - - for message in apiMessages { - if let storeMessage = StoreMessage(apiMessage: message) { - var updatedStoreMessage = storeMessage - if case let .Id(id) = storeMessage.id { - if let channelState = chatStates[id.peerId] as? ChannelState { - var updatedAttributes = storeMessage.attributes - updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts)) - updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes) + var flags: Int32 = 0 + flags |= (1 << 0) + if maxPositionAndClipIndex != nil { + flags |= (1 << 1) + } + if minPositionAndClipIndex != nil { + flags |= (1 << 2) + } + + request = source.request(Api.functions.channels.getFeed(flags: flags, feedId: groupId.rawValue, offsetPosition: offsetPosition, addOffset: addOffset, limit: Int32(selectedLimit), maxPosition: maxPositionAndClipIndex?.0, minPosition: minPositionAndClipIndex?.0, sourcesHash: 0, hash: 0)) + + return request + |> retryRequest + |> mapToSignal { result in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + var updatedMinIndex: MessageIndex? + var updatedMaxIndex: MessageIndex? + switch result { + case let .feedMessages(_, resultMaxPosition, resultMinPosition, _, apiMessages, apiChats, apiUsers): + if let resultMaxPosition = resultMaxPosition { + switch resultMaxPosition { + case let .feedPosition(date, peer, id): + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date).successor() + if index < maxIndex { + updatedMaxIndex = index + } else { + //assertionFailure() + updatedMaxIndex = maxIndex } - } - storeMessages.append(updatedStoreMessage) } } - } - } - - var peers: [Peer] = [] - var peerPresences: [PeerId: PeerPresence] = [:] - for chat in dialogsChats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers.append(groupOrChannel) - } - } - for user in dialogsUsers { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence - } - } - - return postbox.modify { modifier in - updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in - return updated - }) - modifier.updatePeerPresences(peerPresences) - - modifier.updateCurrentPeerNotificationSettings(notificationSettings) - - var allPeersWithMessages = Set() - for message in storeMessages { - if !allPeersWithMessages.contains(message.id.peerId) { - allPeersWithMessages.insert(message.id.peerId) - } - } - let _ = modifier.addMessages(storeMessages, location: .UpperHistoryBlock) - modifier.replaceChatListHole(hole.index, hole: replacementHole) - - modifier.resetIncomingReadStates(readStates) - - for (peerId, chatState) in chatStates { - if let chatState = chatState as? ChannelState { - if let current = modifier.getPeerChatState(peerId) as? ChannelState { - modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) - } else { - modifier.setPeerChatState(peerId, state: chatState) + if let resultMinPosition = resultMinPosition { + switch resultMinPosition { + case let .feedPosition(date, peer, id): + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date).predecessor() + if index > minIndex { + updatedMinIndex = index + } else { + //assertionFailure() + updatedMinIndex = minIndex + } + } + } + messages = apiMessages + chats = apiChats + users = apiUsers + case .feedMessagesNotModified: + messages = [] + chats = [] + users = [] + } + + return postbox.modify { modifier in + var storeMessages: [StoreMessage] = [] + + loop: for message in messages { + if let storeMessage = StoreMessage(apiMessage: message), let messageIndex = storeMessage.index { + if let minClipIndex = minPositionAndClipIndex?.1 { + if messageIndex < minClipIndex { + continue loop + } + } + if let maxClipIndex = maxPositionAndClipIndex?.1 { + if messageIndex > maxClipIndex { + continue loop + } + } + storeMessages.append(storeMessage) } - } else { - modifier.setPeerChatState(peerId, state: chatState) } + + var complete = false + let fillDirection: HoleFillDirection + switch direction { + case .UpperToLower: + if updatedMinIndex == nil { + complete = true + } + fillDirection = .UpperToLower(updatedMinIndex: nil, clippingMaxIndex: updatedMinIndex) + case .LowerToUpper: + if updatedMaxIndex == nil { + complete = true + } + fillDirection = .LowerToUpper(updatedMaxIndex: nil, clippingMinIndex: updatedMaxIndex) + case let .AroundId(id): + fillDirection = .AroundId(id, lowerComplete: false, upperComplete: false) + case let .AroundIndex(index): + fillDirection = .AroundIndex(index, lowerComplete: updatedMinIndex == nil, upperComplete: updatedMaxIndex == nil, clippingMinIndex: updatedMinIndex, clippingMaxIndex: updatedMaxIndex) + } + + modifier.fillMultipleGroupFeedHoles(groupId: groupId, mainHoleMaxIndex: maxIndex, fillType: HoleFill(complete: messages.count == 0 || complete, direction: fillDirection), messages: storeMessages) + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in + return updated + }) + modifier.updatePeerPresences(peerPresences) + + print("fetchGroupFeedHole for \(groupId) done") + + return } - - if let replacePinnedPeerIds = replacePinnedPeerIds { - modifier.setPinnedPeerIds(replacePinnedPeerIds) - } - - for (peerId, summary) in mentionTagSummaries { - modifier.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) - } + } + }*/ + return .complete() +} + +func fetchChatListHole(postbox: Postbox, network: Network, groupId: PeerGroupId?, hole: ChatListHole) -> Signal { + let location: FetchChatListLocation + if let groupId = groupId { + location = .group(groupId) + } else { + location = .general + } + return fetchChatList(postbox: postbox, network: network, location: location, upperBound: hole.index) + |> mapToSignal { fetchedChats -> Signal in + return postbox.modify { modifier in + for peer in fetchedChats.peers { + updatePeers(modifier: modifier, peers: [peer], update: { _, updated -> Peer in + return updated + }) + } + modifier.updatePeerPresences(fetchedChats.peerPresences) + modifier.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings) + let _ = modifier.addMessages(fetchedChats.storeMessages, location: .UpperHistoryBlock) + modifier.resetIncomingReadStates(fetchedChats.readStates) + + modifier.replaceChatListHole(groupId: groupId, index: hole.index, hole: fetchedChats.lowerNonPinnedIndex.flatMap(ChatListHole.init)) + + for (feedGroupId, lowerIndex) in fetchedChats.feeds { + if let hole = postbox.seedConfiguration.initializeChatListWithHoles.first { + modifier.replaceChatListHole(groupId: feedGroupId, index: hole.index, hole: lowerIndex.flatMap(ChatListHole.init)) } } + + for (peerId, chatState) in fetchedChats.chatStates { + if let chatState = chatState as? ChannelState { + if let current = modifier.getPeerChatState(peerId) as? ChannelState { + modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) + } else { + modifier.setPeerChatState(peerId, state: chatState) + } + } else { + modifier.setPeerChatState(peerId, state: chatState) + } + } + + if let replacePinnedItemIds = fetchedChats.pinnedItemIds { + modifier.setPinnedItemIds(replacePinnedItemIds) + } + + for (peerId, summary) in fetchedChats.mentionTagSummaries { + modifier.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) + } } + } } func fetchCallListHole(network: Network, postbox: Postbox, holeIndex: MessageIndex, limit: Int32 = 100) -> Signal { let offset: Signal<(Int32, Int32, Api.InputPeer), NoError> - if holeIndex.id.peerId.namespace == Namespaces.Peer.Empty { - offset = single((0, 0, Api.InputPeer.inputPeerEmpty), NoError.self) - } else { - offset = postbox.loadedPeerWithId(holeIndex.id.peerId) - |> take(1) - |> map { peer in - return (holeIndex.timestamp, holeIndex.id.id + 1, apiInputPeer(peer) ?? .inputPeerEmpty) - } - } + offset = single((holeIndex.timestamp, min(holeIndex.id.id, Int32.max - 1) + 1, Api.InputPeer.inputPeerEmpty), NoError.self) return offset |> mapToSignal { (timestamp, id, peer) -> Signal in - let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: peer, q: "", fromId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0)) + let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: .inputPeerEmpty, q: "", fromId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0)) |> retryRequest |> mapToSignal { result -> Signal in let messages: [Api.Message] diff --git a/TelegramCore/Localization.swift b/TelegramCore/Localization.swift index ff9c05984f..5437f0ed72 100644 --- a/TelegramCore/Localization.swift +++ b/TelegramCore/Localization.swift @@ -193,6 +193,7 @@ public final class Localization: PostboxCoding, Equatable { } public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.version, forKey: "v") encoder.encodeInt32(Int32(self.entries.count), forKey: "c") let buffer = WriteBuffer() diff --git a/TelegramCore/Log.swift b/TelegramCore/Log.swift index aceeeaa887..4501558731 100644 --- a/TelegramCore/Log.swift +++ b/TelegramCore/Log.swift @@ -73,6 +73,9 @@ public final class Logger { public static func setSharedLogger(_ logger: Logger) { sharedLogger = logger + setPostboxLogger({ s in + Logger.shared.log("Postbox", s) + }) } public static var shared: Logger { diff --git a/TelegramCore/LoggingSettings.swift b/TelegramCore/LoggingSettings.swift index 1f43a61950..edec0eb65c 100644 --- a/TelegramCore/LoggingSettings.swift +++ b/TelegramCore/LoggingSettings.swift @@ -13,7 +13,11 @@ public final class LoggingSettings: PreferencesEntry, Equatable { public let logToFile: Bool public let logToConsole: Bool + #if DEBUG + public static var defaultSettings = LoggingSettings(logToFile: true, logToConsole: true) + #else public static var defaultSettings = LoggingSettings(logToFile: false, logToConsole: false) + #endif public init(logToFile: Bool, logToConsole: Bool) { self.logToFile = logToFile diff --git a/TelegramCore/ManagedChatListHoles.swift b/TelegramCore/ManagedChatListHoles.swift index 7569169e0c..ac15567617 100644 --- a/TelegramCore/ManagedChatListHoles.swift +++ b/TelegramCore/ManagedChatListHoles.swift @@ -8,7 +8,7 @@ import Foundation #endif private final class ManagedChatListHolesState { - private var holeDisposables: [ChatListHole: Disposable] = [:] + private var holeDisposables: [ChatListHolesEntry: Disposable] = [:] func clearDisposables() -> [Disposable] { let disposables = Array(self.holeDisposables.values) @@ -16,9 +16,9 @@ private final class ManagedChatListHolesState { return disposables } - func update(entries: Set) -> (removed: [Disposable], added: [ChatListHole: MetaDisposable]) { + func update(entries: Set) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) { var removed: [Disposable] = [] - var added: [ChatListHole: MetaDisposable] = [:] + var added: [ChatListHolesEntry: MetaDisposable] = [:] for (entry, disposable) in self.holeDisposables { if !entries.contains(entry) { @@ -44,7 +44,7 @@ func managedChatListHoles(network: Network, postbox: Postbox) -> Signal (removed: [Disposable], added: [ChatListHole: MetaDisposable]) in + let (removed, added) = state.with { state -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) in return state.update(entries: view.entries) } @@ -53,7 +53,7 @@ func managedChatListHoles(network: Network, postbox: Postbox) -> Signal Signal { if peer.id.namespace == Namespaces.Peer.CloudChannel { if let inputChannel = apiInputChannel(peer) { - let signal: Signal - if let channel = peer as? TelegramChannel, channel.flags.contains(.isCreator) { - signal = network.request(Api.functions.channels.deleteChannel(channel: inputChannel)) - } else { - signal = network.request(Api.functions.channels.leaveChannel(channel: inputChannel)) - } + let signal = network.request(Api.functions.channels.leaveChannel(channel: inputChannel)) + let reportSignal: Signal if let inputPeer = apiInputPeer(peer), operation.reportChatSpam { reportSignal = network.request(Api.functions.messages.reportSpam(peer: inputPeer)) diff --git a/TelegramCore/ManagedConsumePersonalMessagesActions.swift b/TelegramCore/ManagedConsumePersonalMessagesActions.swift index f331f8c6c8..0039722580 100644 --- a/TelegramCore/ManagedConsumePersonalMessagesActions.swift +++ b/TelegramCore/ManagedConsumePersonalMessagesActions.swift @@ -242,7 +242,10 @@ private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Net case let .dialog(_, _, topMessage, _, _, _, unreadMentionsCount, _, _, _): apiTopMessage = topMessage apiUnreadMentionsCount = unreadMentionsCount - } + /*%FEED case .dialogFeed: + assertionFailure() + return .complete()*/ + } return postbox.modify { modifier -> Void in modifier.replaceMessageTagSummary(peerId: entry.key.peerId, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadMentionsCount, maxId: apiTopMessage) diff --git a/TelegramCore/ManagedGroupFeedReadStateSyncOperations.swift b/TelegramCore/ManagedGroupFeedReadStateSyncOperations.swift new file mode 100644 index 0000000000..4d0078fad0 --- /dev/null +++ b/TelegramCore/ManagedGroupFeedReadStateSyncOperations.swift @@ -0,0 +1,219 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedGroupFeedReadStateSyncOperationsHelper { + var operationDisposables: [PeerGroupId: (GroupFeedReadStateSyncOperation, Disposable)] = [:] + + func update(entries: [PeerGroupId: GroupFeedReadStateSyncOperation]) -> (disposeOperations: [Disposable], beginOperations: [(PeerGroupId, GroupFeedReadStateSyncOperation, MetaDisposable)]) { + var disposeOperations: [Disposable] = [] + var beginOperations: [(PeerGroupId, GroupFeedReadStateSyncOperation, MetaDisposable)] = [] + + var validIds = Set() + for (groupId, operation) in entries { + validIds.insert(groupId) + + if let (currentOperation, currentDisposable) = self.operationDisposables[groupId] { + if currentOperation != operation { + disposeOperations.append(currentDisposable) + + let disposable = MetaDisposable() + beginOperations.append((groupId, operation, disposable)) + self.operationDisposables[groupId] = (operation, disposable) + } + } else { + let disposable = MetaDisposable() + beginOperations.append((groupId, operation, disposable)) + self.operationDisposables[groupId] = (operation, disposable) + } + } + + var removeIds: [PeerGroupId] = [] + for (id, operationAndDisposable) in self.operationDisposables { + if !validIds.contains(id) { + removeIds.append(id) + disposeOperations.append(operationAndDisposable.1) + } + } + + for id in removeIds { + self.operationDisposables.removeValue(forKey: id) + } + + return (disposeOperations, beginOperations) + } + + func reset() -> [Disposable] { + let disposables = Array(self.operationDisposables.values).map { $0.1 } + self.operationDisposables.removeAll() + return disposables + } +} + +func managedGroupFeedReadStateSyncOperations(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager) -> Signal { + return Signal { _ in + let helper = Atomic(value: ManagedGroupFeedReadStateSyncOperationsHelper()) + + let disposable = postbox.combinedView(keys: [.groupFeedReadStateSyncOperations]).start(next: { view in + var entries: [PeerGroupId: GroupFeedReadStateSyncOperation] = [:] + if let v = view.views[.groupFeedReadStateSyncOperations] as? GroupFeedReadStateSyncOperationsView { + entries = v.entries + } + + let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerGroupId, GroupFeedReadStateSyncOperation, MetaDisposable)]) in + return helper.update(entries: entries) + } + + for disposable in disposeOperations { + disposable.dispose() + } + + for (groupId, operation, disposable) in beginOperations { + let signal = performSyncOperation(postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, groupId: groupId, operation: operation) + disposable.set(signal.start()) + } + }) + + return ActionDisposable { + let disposables = helper.with { helper -> [Disposable] in + return helper.reset() + } + for disposable in disposables { + disposable.dispose() + } + disposable.dispose() + } + } +} + +private func fetchReadStateNext(network: Network, groupId: PeerGroupId) -> Signal { + /*%FEED return network.request(Api.functions.messages.getPeerDialogs(peers: [.inputPeerFeed(feedId: groupId.rawValue)])) + |> retryRequest + |> map { result -> GroupFeedReadState? in + switch result { + case let .peerDialogs(dialogs, messages, _, _, _): + for dialog in dialogs { + if case let .dialogFeed(_, _, topMessage, feedId, _, resultMaxReadPosition, _, _) = dialog { + assert(feedId == groupId.rawValue) + if let resultMaxReadPosition = resultMaxReadPosition { + switch resultMaxReadPosition { + case let .feedPosition(date, peer, id): + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date).successor() + return GroupFeedReadState(maxReadIndex: index) + } + } else { + for message in messages { + if let storeMessage = StoreMessage(apiMessage: message), let index = storeMessage.index, index.id.id == topMessage { + return GroupFeedReadState(maxReadIndex: index) + } + } + } + break + } + } + return nil + } + }*/ + return .single(nil) +} + +private func fetchReadState(network: Network, groupId: PeerGroupId) -> Signal { + /*%FEED return network.request(Api.functions.channels.getFeed(flags: 0, feedId: groupId.rawValue, offsetPosition: nil, addOffset: 0, limit: 1, maxPosition: nil, minPosition: nil, sourcesHash: 0, hash: 0)) + |> retryRequest + |> map { result -> GroupFeedReadState? in + switch result { + case let .feedMessages(_, _, _, resultMaxReadPosition, messages, _, _): + if let resultMaxReadPosition = resultMaxReadPosition { + switch resultMaxReadPosition { + case let .feedPosition(date, peer, id): + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date).successor() + return GroupFeedReadState(maxReadIndex: index) + } + } else { + var maxIndex: MessageIndex? + for message in messages { + if let storeMessage = StoreMessage(apiMessage: message), let messageIndex = storeMessage.index { + if maxIndex == nil || maxIndex! < messageIndex { + maxIndex = messageIndex + } + } + } + if let maxIndex = maxIndex { + return GroupFeedReadState(maxReadIndex: maxIndex) + } else { + return nil + } + } + case .feedMessagesNotModified: + return nil + } + }*/ + return .single(nil) +} + +private func groupBoundaryPeer(_ peerId: PeerId, accountPeerId: PeerId) -> Api.Peer { + switch peerId.namespace { + case Namespaces.Peer.CloudUser: + return Api.Peer.peerUser(userId: peerId.id) + case Namespaces.Peer.CloudGroup: + return Api.Peer.peerChat(chatId: peerId.id) + case Namespaces.Peer.CloudChannel: + return Api.Peer.peerChannel(channelId: peerId.id) + default: + return Api.Peer.peerUser(userId: accountPeerId.id) + } +} + +private func pushReadState(network: Network, accountPeerId: PeerId, groupId: PeerGroupId, state: GroupFeedReadState) -> Signal { + return .single(nil) + /*%FEED + let position: Api.FeedPosition = .feedPosition(date: state.maxReadIndex.timestamp, peer: groupBoundaryPeer(state.maxReadIndex.id.peerId, accountPeerId: accountPeerId), id: state.maxReadIndex.id.id) + return network.request(Api.functions.channels.readFeed(feedId: groupId.rawValue, maxPosition: position)) + |> retryRequest + |> map(Optional.init)*/ +} + +private func performSyncOperation(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, groupId: PeerGroupId, operation: GroupFeedReadStateSyncOperation) -> Signal { + return postbox.modify { modifier -> GroupFeedReadState? in + return modifier.getGroupFeedReadState(groupId: groupId) + } |> mapToSignal { currentState -> Signal<(GroupFeedReadState?, GroupFeedReadState?), NoError> in + if operation.validate { + return fetchReadState(network: network, groupId: groupId) + |> map { (currentState, $0) } + } else { + return .single((currentState, nil)) + } + } |> mapToSignal { currentState, remoteState -> Signal in + if operation.push, let currentState = currentState { + return pushReadState(network: network, accountPeerId: accountPeerId, groupId: groupId, state: currentState) + |> mapToSignal { updates -> Signal in + return postbox.modify { modifier -> Void in + var resultingState: GroupFeedReadState + if let remoteState = remoteState, remoteState.maxReadIndex > currentState.maxReadIndex { + resultingState = remoteState + } else { + resultingState = currentState + } + modifier.applyGroupFeedReadMaxIndex(groupId: groupId, index: resultingState.maxReadIndex) + + if let updates = updates { + stateManager.addUpdates(updates) + } + } + } + } else if operation.validate, let remoteState = remoteState { + return postbox.modify { modifier -> Void in + modifier.applyGroupFeedReadMaxIndex(groupId: groupId, index: remoteState.maxReadIndex) + } + } else { + return .complete() + } + } +} diff --git a/TelegramCore/ManagedMessageHistoryHoles.swift b/TelegramCore/ManagedMessageHistoryHoles.swift index 77ee5a1a56..7d79d4753b 100644 --- a/TelegramCore/ManagedMessageHistoryHoles.swift +++ b/TelegramCore/ManagedMessageHistoryHoles.swift @@ -39,19 +39,13 @@ private final class ManagedMessageHistoryHolesState { } } -func managedMessageHistoryHoles(network: Network, postbox: Postbox) -> Signal { +func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return Signal { _ in let state = Atomic(value: ManagedMessageHistoryHolesState()) let disposable = postbox.messageHistoryHolesView().start(next: { view in let (removed, added) = state.with { state -> (removed: [Disposable], added: [MessageHistoryHolesViewEntry: MetaDisposable]) in - var entries = Set() - for (_, entrySet) in view.entries { - for entry in entrySet { - entries.insert(entry) - } - } - return state.update(entries: entries) + return state.update(entries: view.entries) } for disposable in removed { @@ -59,7 +53,12 @@ func managedMessageHistoryHoles(network: Network, postbox: Postbox) -> Signal Signal { +func managedServiceViews(accountPeerId: PeerId, network: Network, postbox: Postbox, stateManager: AccountStateManager, pendingMessageManager: PendingMessageManager) -> Signal { return Signal { _ in let disposable = DisposableSet() - disposable.add(managedMessageHistoryHoles(network: network, postbox: postbox).start()) + disposable.add(managedMessageHistoryHoles(accountPeerId: accountPeerId, network: network, postbox: postbox).start()) disposable.add(managedChatListHoles(network: network, postbox: postbox).start()) disposable.add(managedSynchronizePeerReadStates(network: network, postbox: postbox, stateManager: stateManager).start()) + disposable.add(managedGroupFeedReadStateSyncOperations(postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager).start()) return disposable } diff --git a/TelegramCore/ManagedSynchronizeGroupedPeersOperations.swift b/TelegramCore/ManagedSynchronizeGroupedPeersOperations.swift new file mode 100644 index 0000000000..14a6d12bd7 --- /dev/null +++ b/TelegramCore/ManagedSynchronizeGroupedPeersOperations.swift @@ -0,0 +1,212 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeGroupedPeersOperationsHelper { + var operationDisposables: [Int32: Disposable] = [:] + + func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) { + var disposeOperations: [Disposable] = [] + var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = [] + + var hasRunningOperationForPeerId = Set() + var validMergedIndices = Set() + for entry in entries { + if !hasRunningOperationForPeerId.contains(entry.peerId) { + hasRunningOperationForPeerId.insert(entry.peerId) + validMergedIndices.insert(entry.mergedIndex) + + if self.operationDisposables[entry.mergedIndex] == nil { + let disposable = MetaDisposable() + beginOperations.append((entry, disposable)) + self.operationDisposables[entry.mergedIndex] = disposable + } + } + } + + var removeMergedIndices: [Int32] = [] + for (mergedIndex, disposable) in self.operationDisposables { + if !validMergedIndices.contains(mergedIndex) { + removeMergedIndices.append(mergedIndex) + disposeOperations.append(disposable) + } + } + + for mergedIndex in removeMergedIndices { + self.operationDisposables.removeValue(forKey: mergedIndex) + } + + return (disposeOperations, beginOperations) + } + + func reset() -> [Disposable] { + let disposables = Array(self.operationDisposables.values) + self.operationDisposables.removeAll() + return disposables + } +} + +private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Modifier, PeerMergedOperationLogEntry?) -> Signal) -> Signal { + return postbox.modify { modifier -> Signal in + var result: PeerMergedOperationLogEntry? + modifier.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in + if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeGroupedPeersOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(modifier, result) + } |> switchToLatest +} + +func managedSynchronizeGroupedPeersOperations(postbox: Postbox, network: Network) -> Signal { + return Signal { _ in + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeGroupedPeers + + let helper = Atomic(value: ManagedSynchronizeGroupedPeersOperationsHelper()) + + let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in + let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in + return helper.update(view.entries) + } + + for disposable in disposeOperations { + disposable.dispose() + } + + for (entry, disposable) in beginOperations { + let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { modifier, entry -> Signal in + if let entry = entry { + if let operation = entry.contents as? SynchronizeGroupedPeersOperation { + return synchronizeGroupedPeers(modifier: modifier, postbox: postbox, network: network, operation: operation) + } else { + assertionFailure() + } + } + return .complete() + }) + |> then(postbox.modify { modifier -> Void in + let _ = modifier.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) + }) + + disposable.set(signal.start()) + } + }) + + return ActionDisposable { + let disposables = helper.with { helper -> [Disposable] in + return helper.reset() + } + for disposable in disposables { + disposable.dispose() + } + disposable.dispose() + } + } +} + +private func hashForIds(_ ids: [Int32]) -> Int32 { + var acc: UInt32 = 0 + + for id in ids { + let low = UInt32(bitPattern: id) + acc = (acc &* 20261) &+ low + } + return Int32(bitPattern: acc & UInt32(0x7FFFFFFF)) +} + +private func synchronizeGroupedPeers(modifier: Modifier, postbox: Postbox, network: Network, operation: SynchronizeGroupedPeersOperation) -> Signal { + /*%FEED let initialRemotePeerIds = operation.initialPeerIds + let localPeerIds = modifier.getPeerIdsInGroup(operation.groupId) + + return network.request(Api.functions.channels.getFeedSources(flags: 1 << 0, feedId: operation.groupId.rawValue, hash: hashForIds(localPeerIds.map({ $0.id }).sorted()))) + |> retryRequest + |> mapToSignal { sources -> Signal in + switch sources { + case .feedSourcesNotModified: + return .complete() + case let .feedSources(_, newlyJoinedFeed, feeds, chats, users): + var remotePeerIds = Set() + for feedsInfo in feeds { + switch feedsInfo { + case let .feedBroadcasts(feedId, channels): + if feedId == operation.groupId.rawValue { + for id in channels { + remotePeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudChannel, id: id)) + } + } + case .feedBroadcastsUngrouped: + break + } + } + + let remoteAdded = remotePeerIds.subtracting(initialRemotePeerIds) + let remoteRemoved = initialRemotePeerIds.subtracting(remotePeerIds) + var finalPeerIds = localPeerIds + finalPeerIds.formUnion(remoteAdded) + finalPeerIds.subtract(remoteRemoved) + + //channels.setFeedBroadcasts feed_id:int channels:Vector also_newly_joined:Bool = Bool; + return postbox.modify { modifier -> Signal in + var peers: [PeerId: Peer] = [:] + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers[telegramUser.id] = telegramUser + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + var inputChannels: [Api.InputChannel] = [] + for peerId in finalPeerIds { + if let peer = modifier.getPeer(peerId) ?? peers[peerId], let inputChannel = apiInputChannel(peer) { + inputChannels.append(inputChannel) + } else { + assertionFailure() + } + } + + updatePeers(modifier: modifier, peers: Array(peers.values), update: { _, updated -> Peer in + return updated + }) + modifier.updatePeerPresences(peerPresences) + + return network.request(Api.functions.channels.setFeedBroadcasts(feedId: operation.groupId.rawValue, channels: inputChannels, alsoNewlyJoined: .boolFalse)) + |> retryRequest + |> mapToSignal { _ -> Signal in + return postbox.modify { modifier -> Void in + let currentLocalPeerIds = modifier.getPeerIdsInGroup(operation.groupId) + + for peerId in currentLocalPeerIds { + if !finalPeerIds.contains(peerId) { + modifier.updatePeerGroupId(peerId, groupId: nil) + } + } + + for peerId in finalPeerIds { + modifier.updatePeerGroupId(peerId, groupId: operation.groupId) + } + } + } + } |> switchToLatest + } + }*/ + return .complete() +} + diff --git a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift index 826ff6a2fd..88d2543e52 100644 --- a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift +++ b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift @@ -114,13 +114,23 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, } private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, network: Network, stateManager: AccountStateManager, operation: SynchronizePinnedChatsOperation) -> Signal { - let initialRemotePeerIds = operation.previousPeerIds - let initialRemotePeerIdsWithoutSecretChats = initialRemotePeerIds.filter { - $0.namespace != Namespaces.Peer.SecretChat + let initialRemoteItemIds = operation.previousItemIds + let initialRemoteItemIdsWithoutSecretChats = initialRemoteItemIds.filter { item in + switch item { + case let .peer(peerId): + return peerId.namespace != Namespaces.Peer.SecretChat + case .group: + return false + } } - let localPeerIds = modifier.getPinnedPeerIds() - let localPeerIdsWithoutSecretChats = localPeerIds.filter { - $0.namespace != Namespaces.Peer.SecretChat + let localItemIds = modifier.getPinnedItemIds() + let localItemIdsWithoutSecretChats = localItemIds.filter { item in + switch item { + case let .peer(peerId): + return peerId.namespace != Namespaces.Peer.SecretChat + case .group: + return false + } } return network.request(Api.functions.messages.getPinnedDialogs()) @@ -134,14 +144,14 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ var chatStates: [PeerId: PeerChatState] = [:] var notificationSettings: [PeerId: PeerNotificationSettings] = [:] - var remotePeerIds: [PeerId] = [] + var remoteItemIds: [PinnedItemId] = [] switch dialogs { case let .peerDialogs(dialogs, messages, chats, users, _): dialogsChats = chats dialogsUsers = users - for dialog in dialogs { + loop: for dialog in dialogs { let apiPeer: Api.Peer let apiReadInboxMaxId: Int32 let apiReadOutboxMaxId: Int32 @@ -158,6 +168,9 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ apiUnreadCount = unreadCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts + /*%FEED case let .dialogFeed(_, _, _, feedId, _, _, _, _): + remoteItemIds.append(.group(PeerGroupId(rawValue: feedId))) + continue loop*/ } let peerId: PeerId @@ -170,7 +183,7 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } - remotePeerIds.append(peerId) + remoteItemIds.append(.peer(peerId)) if readStates[peerId] == nil { readStates[peerId] = [:] @@ -206,18 +219,18 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ } } - let locallyRemovedFromRemotePeerIds = Set(initialRemotePeerIdsWithoutSecretChats).subtracting(Set(localPeerIdsWithoutSecretChats)) - let remotelyRemovedPeerIds = Set(initialRemotePeerIdsWithoutSecretChats).subtracting(Set(remotePeerIds)) + let locallyRemovedFromRemoteItemIds = Set(initialRemoteItemIdsWithoutSecretChats).subtracting(Set(localItemIdsWithoutSecretChats)) + let remotelyRemovedItemIds = Set(initialRemoteItemIdsWithoutSecretChats).subtracting(Set(remoteItemIds)) - var resultingPeerIds = localPeerIds.filter { !remotelyRemovedPeerIds.contains($0) } - resultingPeerIds.append(contentsOf: remotePeerIds.filter { !locallyRemovedFromRemotePeerIds.contains($0) && !resultingPeerIds.contains($0) }) + var resultingItemIds = localItemIds.filter { !remotelyRemovedItemIds.contains($0) } + resultingItemIds.append(contentsOf: remoteItemIds.filter { !locallyRemovedFromRemoteItemIds.contains($0) && !resultingItemIds.contains($0) }) return postbox.modify { modifier -> Signal in updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in return updated }) - modifier.setPinnedPeerIds(resultingPeerIds) + modifier.setPinnedItemIds(resultingItemIds) modifier.updatePeerPresences(peerPresences) @@ -245,13 +258,18 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ } } - if remotePeerIds == resultingPeerIds { + if remoteItemIds == resultingItemIds { return .complete() } else { var inputPeers: [Api.InputPeer] = [] - for peerId in resultingPeerIds { - if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) { - inputPeers.append(inputPeer) + for itemId in resultingItemIds { + switch itemId { + case let .peer(peerId): + if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) { + inputPeers.append(inputPeer) + } + case .group: + break } } diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index 9a62e84c0a..2d97ea1c73 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -68,6 +68,10 @@ public struct Namespaces { return UnorderedItemListEntryTag(value: key) }() } + + public struct PeerGroup { + public static let feed = PeerGroupId(rawValue: 1) + } } public extension MessageTags { @@ -110,6 +114,7 @@ struct OperationLogTags { static let SynchronizeSavedGifs = PeerOperationLogTag(value: 12) static let SynchronizeLocalizationUpdates = PeerOperationLogTag(value: 13) static let SynchronizeSavedStickers = PeerOperationLogTag(value: 14) + static let SynchronizeGroupedPeers = PeerOperationLogTag(value: 15) } private enum PreferencesKeyValues: Int32 { diff --git a/TelegramCore/Network.swift b/TelegramCore/Network.swift index 44318519a5..2bfeb709cc 100644 --- a/TelegramCore/Network.swift +++ b/TelegramCore/Network.swift @@ -472,6 +472,14 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { public let shouldKeepConnection = Promise(false) private let shouldKeepConnectionDisposable = MetaDisposable() + public var mockConnectionStatus: ConnectionStatus? { + didSet { + if let mockConnectionStatus = self.mockConnectionStatus { + self._connectionStatus.set(.single(mockConnectionStatus)) + } + } + } + var loggedOut: (() -> Void)? fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String) { diff --git a/TelegramCore/RecentPeers.swift b/TelegramCore/RecentPeers.swift index 63f524d9e4..1dfe619d35 100644 --- a/TelegramCore/RecentPeers.swift +++ b/TelegramCore/RecentPeers.swift @@ -45,7 +45,11 @@ public func recentPeers(account: Account) -> Signal<[Peer], NoError> { return .complete() } } - return cachedPeers |> then(updatedRemotePeers |> filter({ !$0.isEmpty })) + return cachedPeers + |> then(updatedRemotePeers |> filter({ !$0.isEmpty })) + |> map { peers -> [Peer] in + return peers.filter { $0.id != account.peerId } + } } public func removeRecentPeer(account: Account, peerId: PeerId) -> Signal { diff --git a/TelegramCore/SearchMessages.swift b/TelegramCore/SearchMessages.swift index 29c86b03ff..40a00364ef 100644 --- a/TelegramCore/SearchMessages.swift +++ b/TelegramCore/SearchMessages.swift @@ -9,37 +9,67 @@ import Foundation import MtProtoKitDynamic #endif -public func searchMessages(account: Account, peerId: PeerId?, query: String, fromId:PeerId? = nil, tagMask: MessageTags? = nil) -> Signal<[Message], NoError> { - if let peerId = peerId, peerId.namespace == Namespaces.Peer.SecretChat { - return account.postbox.modify { modifier -> [Message] in - return modifier.searchMessages(peerId: peerId, query: query, tags: tagMask) +public enum SearchMessagesLocation: Equatable { + case general + case group(PeerGroupId) + case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?) + + public static func ==(lhs: SearchMessagesLocation, rhs: SearchMessagesLocation) -> Bool { + switch lhs { + case .general: + if case .general = rhs { + return true + } else { + return false + } + case let .group(groupId): + if case .group(groupId) = rhs { + return true + } else { + return false + } + case let .peer(lhsPeerId, lhsFromId, lhsTags): + if case let .peer(rhsPeerId, rhsFromId, rhsTags) = rhs, lhsPeerId == rhsPeerId, lhsFromId == rhsFromId, lhsTags == rhsTags { + return true + } else { + return false + } } - } else { - let searchResult: Signal - - let filter: Api.MessagesFilter - - if let tags = tagMask { - if tags.contains(.file) { - filter = .inputMessagesFilterDocument - } else if tags.contains(.music) { - filter = .inputMessagesFilterMusic - } else if tags.contains(.webPage) { - filter = .inputMessagesFilterUrl + } +} + +public func searchMessages(account: Account, location: SearchMessagesLocation, query: String) -> Signal<[Message], NoError> { + let remoteSearchResult: Signal + switch location { + case let .peer(peerId, fromId, tags): + if peerId.namespace == Namespaces.Peer.SecretChat { + return account.postbox.modify { modifier -> [Message] in + return modifier.searchMessages(peerId: peerId, query: query, tags: tags) + } + } + + let filter: Api.MessagesFilter + + if let tags = tags { + if tags.contains(.file) { + filter = .inputMessagesFilterDocument + } else if tags.contains(.music) { + filter = .inputMessagesFilterMusic + } else if tags.contains(.webPage) { + filter = .inputMessagesFilterUrl + } else { + filter = .inputMessagesFilterEmpty + } } else { filter = .inputMessagesFilterEmpty } - } else { - filter = .inputMessagesFilterEmpty - } - if let peerId = peerId { - searchResult = account.postbox.modify { modifier -> (peer:Peer?, from: Peer?) in + remoteSearchResult = account.postbox.modify { modifier -> (peer:Peer?, from: Peer?) in if let fromId = fromId { return (peer: modifier.getPeer(peerId), from: modifier.getPeer(fromId)) } return (peer: modifier.getPeer(peerId), from: nil) - } |> mapToSignal { values -> Signal in + } |> mapToSignal { values -> Signal in if let peer = values.peer, let inputPeer = apiInputPeer(peer) { var fromInputUser:Api.InputUser? = nil var flags:Int32 = 0 @@ -58,72 +88,75 @@ public func searchMessages(account: Account, peerId: PeerId?, query: String, fro return .never() } } - } else { - searchResult = account.network.request(Api.functions.messages.searchGlobal(q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64)) - |> mapError {_ in} |> map {Optional($0)} + case let .group(groupId): + /*%FEED remoteSearchResult = account.network.request(Api.functions.channels.searchFeed(feedId: groupId.rawValue, q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64)) + |> mapError { _ in } |> map(Optional.init)*/ + remoteSearchResult = .single(nil) + case .general: + remoteSearchResult = account.network.request(Api.functions.messages.searchGlobal(q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64)) + |> mapError { _ in } |> map(Optional.init) + } + + let processedSearchResult = remoteSearchResult + |> mapToSignal { result -> Signal<[Message], NoError> in + guard let result = result else { + return .single([]) + } + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + switch result { + case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messages(apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let.messagesSlice(_, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + } + + return account.postbox.modify { modifier -> [Message] in + var peers: [PeerId: Peer] = [:] + + for user in users { + if let user = TelegramUser.merge(modifier.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers[user.id] = user + } + } + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + + var renderedMessages: [Message] = [] + for message in messages { + if let message = StoreMessage(apiMessage: message), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) { + renderedMessages.append(renderedMessage) + } + } + + if case .general = location { + let secretMessages = modifier.searchMessages(peerId: nil, query: query, tags: nil) + renderedMessages.append(contentsOf: secretMessages) + } + + renderedMessages.sort(by: { lhs, rhs in + return MessageIndex(lhs) > MessageIndex(rhs) + }) + + return renderedMessages + } + } - let processedSearchResult = searchResult - |> mapToSignal { result -> Signal<[Message], NoError> in - guard let result = result else { - return .single([]) - } - let messages: [Api.Message] - let chats: [Api.Chat] - let users: [Api.User] - switch result { - case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let .messages(apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let.messagesSlice(_, apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - } - - return account.postbox.modify { modifier -> [Message] in - var peers: [PeerId: Peer] = [:] - - for user in users { - if let user = TelegramUser.merge(modifier.getPeer(user.peerId) as? TelegramUser, rhs: user) { - peers[user.id] = user - } - } - - for chat in chats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers[groupOrChannel.id] = groupOrChannel - } - } - - var renderedMessages: [Message] = [] - for message in messages { - if let message = StoreMessage(apiMessage: message), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) { - renderedMessages.append(renderedMessage) - } - } - - if peerId == nil { - let secretMessages = modifier.searchMessages(peerId: nil, query: query, tags: nil) - renderedMessages.append(contentsOf: secretMessages) - } - - renderedMessages.sort(by: { lhs, rhs in - return MessageIndex(lhs) > MessageIndex(rhs) - }) - - return renderedMessages - } - - } - - return processedSearchResult - } + return processedSearchResult } diff --git a/TelegramCore/SearchPeers.swift b/TelegramCore/SearchPeers.swift index d28abfc99b..53dc6d3f08 100644 --- a/TelegramCore/SearchPeers.swift +++ b/TelegramCore/SearchPeers.swift @@ -23,35 +23,34 @@ public struct FoundPeer: Equatable { } } -public func searchPeers(account: Account, query: String) -> Signal<[FoundPeer], NoError> { - let searchResult = account.network.request(Api.functions.contacts.search(q: query, limit: 10)) +public func searchPeers(account: Account, query: String) -> Signal<([FoundPeer], [FoundPeer]), NoError> { + let searchResult = account.network.request(Api.functions.contacts.search(q: query, limit: 20)) |> map { Optional($0) } |> `catch` { _ in return Signal.single(nil) } let processedSearchResult = searchResult - |> mapToSignal { result -> Signal<[FoundPeer], NoError> in + |> mapToSignal { result -> Signal<([FoundPeer], [FoundPeer]), NoError> in if let result = result { switch result { + /*%FEED */ case let .found(results, chats, users): - return account.postbox.modify { modifier -> [FoundPeer] in + return account.postbox.modify { modifier -> ([FoundPeer], [FoundPeer]) in var peers: [PeerId: Peer] = [:] - var subscribres:[PeerId : Int32] = [:] + var subscribers:[PeerId : Int32] = [:] for user in users { if let user = TelegramUser.merge(modifier.getPeer(user.peerId) as? TelegramUser, rhs: user) { peers[user.id] = user } } - - for chat in chats { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { peers[groupOrChannel.id] = groupOrChannel switch chat { case let .channel(_, _, _, _, _, _, _, _, _, _, _, participantsCount): if let participantsCount = participantsCount { - subscribres[groupOrChannel.id] = participantsCount + subscribers[groupOrChannel.id] = participantsCount } default: break @@ -59,27 +58,44 @@ public func searchPeers(account: Account, query: String) -> Signal<[FoundPeer], } } + var renderedMyPeers: [FoundPeer] = [] + /*%FEED + for result in myResults { + let peerId: PeerId + switch result { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } + if let peer = peers[peerId] { + renderedMyPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + } + }*/ + var renderedPeers: [FoundPeer] = [] for result in results { let peerId: PeerId switch result { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } if let peer = peers[peerId] { - renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribres[peerId])) + renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) } } - return renderedPeers + return (renderedMyPeers, renderedPeers) } } } else { - return .single([]) + return .single(([], [])) } } diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index d0d51e21c1..9661dd0e48 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -425,7 +425,7 @@ extension StoreMessage { if let authorId = authorId { forwardInfo = StoreMessageForwardInfo(authorId: authorId, sourceId: sourceId, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor) } else if let sourceId = sourceId { - forwardInfo = StoreMessageForwardInfo(authorId: sourceId, sourceId: nil, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor) + forwardInfo = StoreMessageForwardInfo(authorId: sourceId, sourceId: sourceId, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor) } } } @@ -523,6 +523,8 @@ extension StoreMessage { let (tags, globalTags) = tagsForStoreMessage(incoming: storeFlags.contains(.Incoming), attributes: attributes, media: medias, textEntities: entitiesAttribute?.entities) + storeFlags.insert(.CanBeGroupedIntoFeed) + assert(!tags.contains(.unseenPersonalMessage) || date > 1493596800) self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), globallyUniqueId: nil, groupingKey: groupingId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) diff --git a/TelegramCore/SynchronizeGroupedPeersOperation.swift b/TelegramCore/SynchronizeGroupedPeersOperation.swift new file mode 100644 index 0000000000..1b742db15f --- /dev/null +++ b/TelegramCore/SynchronizeGroupedPeersOperation.swift @@ -0,0 +1,79 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +final class SynchronizeGroupedPeersOperation: PostboxCoding { + let groupId: PeerGroupId + let initialPeerIds: Set + + init(groupId: PeerGroupId, initialPeerIds: Set) { + self.groupId = groupId + self.initialPeerIds = initialPeerIds + } + + init(decoder: PostboxDecoder) { + self.groupId = PeerGroupId(rawValue: decoder.decodeOptionalInt32ForKey("groupId")!) + self.initialPeerIds = Set(PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("initialPeerIds")!)) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.groupId.rawValue, forKey: "groupId") + let buffer = WriteBuffer() + PeerId.encodeArrayToBuffer(Array(self.initialPeerIds), buffer: buffer) + encoder.encodeBytes(buffer, forKey: "initialPeerIds") + } +} + +public func updatePeerGroupIdInteractively(postbox: Postbox, peerId: PeerId, groupId: PeerGroupId?) -> Signal { + return postbox.modify { modifier -> Void in + let previousGroupId = modifier.getPeerGroupId(peerId) + + if previousGroupId != groupId { + var previousGroupPeerIds = Set() + if let previousGroupId = previousGroupId { + previousGroupPeerIds = modifier.getPeerIdsInGroup(previousGroupId) + } + + var updatedGroupPeerIds = Set() + if let groupId = groupId { + updatedGroupPeerIds = modifier.getPeerIdsInGroup(groupId) + } + + modifier.updatePeerGroupId(peerId, groupId: groupId) + if let previousGroupId = previousGroupId { + addSynchronizeGroupedPeersOperation(modifier: modifier, groupId: previousGroupId, initialPeerIds: previousGroupPeerIds) + } + if let groupId = groupId { + addSynchronizeGroupedPeersOperation(modifier: modifier, groupId: groupId, initialPeerIds: updatedGroupPeerIds) + } + } + } +} + +private func addSynchronizeGroupedPeersOperation(modifier: Modifier, groupId: PeerGroupId, initialPeerIds: Set) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeGroupedPeers + let peerId = PeerId(namespace: 0, id: groupId.rawValue) + + var topLocalIndex: Int32? + var previousInitialPeerIds: Set? + modifier.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in + topLocalIndex = entry.tagLocalIndex + if let operation = entry.contents as? SynchronizeGroupedPeersOperation { + previousInitialPeerIds = operation.initialPeerIds + } + return false + }) + + if let topLocalIndex = topLocalIndex { + let _ = modifier.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex) + } + + let initialPeerIds: Set = previousInitialPeerIds ?? initialPeerIds + + modifier.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeGroupedPeersOperation(groupId: groupId, initialPeerIds: initialPeerIds)) +} diff --git a/TelegramCore/SynchronizePeerReadState.swift b/TelegramCore/SynchronizePeerReadState.swift index 408c7cb40e..ace83e954b 100644 --- a/TelegramCore/SynchronizePeerReadState.swift +++ b/TelegramCore/SynchronizePeerReadState.swift @@ -79,6 +79,9 @@ func fetchPeerCloudReadState(network: Network, postbox: Postbox, peerId: PeerId, apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId apiUnreadCount = unreadCount + /*%FEED case .dialogFeed: + assertionFailure() + return nil*/ } return .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount) @@ -117,6 +120,9 @@ private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) if let pts = pts { apiChannelPts = pts } + /*%FEED case .dialogFeed: + assertionFailure() + return .fail(.Abort)*/ } let marker: PeerReadStateMarker @@ -227,6 +233,10 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana } private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, readState: PeerReadState) -> Signal { + #if (arch(i386) || arch(x86_64)) && os(iOS) + //return .single(readState) + #endif + if peerId.namespace == Namespaces.Peer.SecretChat { return inputSecretChat(postbox: postbox, peerId: peerId) |> mapToSignal { inputPeer -> Signal in diff --git a/TelegramCore/SynchronizePinnedChatsOperation.swift b/TelegramCore/SynchronizePinnedChatsOperation.swift index 058edc0e65..8193d1324a 100644 --- a/TelegramCore/SynchronizePinnedChatsOperation.swift +++ b/TelegramCore/SynchronizePinnedChatsOperation.swift @@ -5,21 +5,50 @@ import Foundation import Postbox #endif -final class SynchronizePinnedChatsOperation: PostboxCoding { - let previousPeerIds: [PeerId] +private struct PreviousPeerItemId: PostboxCoding { + let id: PinnedItemId - init(previousPeerIds: [PeerId]) { - self.previousPeerIds = previousPeerIds + init(_ id: PinnedItemId) { + self.id = id } init(decoder: PostboxDecoder) { - self.previousPeerIds = PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKey("p")!) + switch decoder.decodeInt32ForKey("_t", orElse: 0) { + case 0: + self.id = .peer(PeerId(decoder.decodeInt64ForKey("i", orElse: 0))) + case 1: + self.id = .group(PeerGroupId(rawValue: decoder.decodeInt32ForKey("i", orElse: 0))) + default: + preconditionFailure() + } } func encode(_ encoder: PostboxEncoder) { - let buffer = WriteBuffer() - PeerId.encodeArrayToBuffer(self.previousPeerIds, buffer: buffer) - encoder.encodeBytes(buffer, forKey: "p") + switch self.id { + case let .peer(peerId): + encoder.encodeInt32(0, forKey: "_t") + encoder.encodeInt64(peerId.toInt64(), forKey: "i") + case let .group(groupId): + encoder.encodeInt32(1, forKey: "_t") + encoder.encodeInt32(groupId.rawValue, forKey: "i") + } + } +} + +final class SynchronizePinnedChatsOperation: PostboxCoding { + let previousItemIds: [PinnedItemId] + + init(previousItemIds: [PinnedItemId]) { + self.previousItemIds = previousItemIds + } + + init(decoder: PostboxDecoder) { + let wrappedIds: [PreviousPeerItemId] = decoder.decodeObjectArrayWithDecoderForKey("previousItemIds") + self.previousItemIds = wrappedIds.map { $0.id } + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.previousItemIds.map(PreviousPeerItemId.init), forKey: "previousItemIds") } } @@ -29,7 +58,7 @@ func addSynchronizePinnedChatsOperation(modifier: Modifier) { updateLocalIndex = entry.tagLocalIndex return false }) - let operationContents = SynchronizePinnedChatsOperation(previousPeerIds: modifier.getPinnedPeerIds()) + let operationContents = SynchronizePinnedChatsOperation(previousItemIds: modifier.getPinnedItemIds()) if let updateLocalIndex = updateLocalIndex { let _ = modifier.operationLogRemoveEntry(peerId: PeerId(namespace: 0, id: 0), tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: updateLocalIndex) } diff --git a/TelegramCore/TelegramChannel.swift b/TelegramCore/TelegramChannel.swift index 89749bb5df..f6f1d4e3bb 100644 --- a/TelegramCore/TelegramChannel.swift +++ b/TelegramCore/TelegramChannel.swift @@ -52,6 +52,10 @@ public struct TelegramChannelBroadcastFlags: OptionSet { public struct TelegramChannelBroadcastInfo: Equatable { public let flags: TelegramChannelBroadcastFlags + public init(flags: TelegramChannelBroadcastFlags) { + self.flags = flags + } + public static func ==(lhs: TelegramChannelBroadcastInfo, rhs: TelegramChannelBroadcastInfo) -> Bool { return lhs.flags == rhs.flags } @@ -152,6 +156,7 @@ public final class TelegramChannel: Peer { public let restrictionInfo: PeerAccessRestrictionInfo? public let adminRights: TelegramChannelAdminRights? public let bannedRights: TelegramChannelBannedRights? + public let peerGroupId: PeerGroupId? public var indexName: PeerIndexNameRepresentation { return .title(title: self.title, addressName: self.username) @@ -160,7 +165,7 @@ public final class TelegramChannel: Peer { public let associatedPeerId: PeerId? = nil public let notificationSettingsPeerId: PeerId? = nil - public init(id: PeerId, accessHash: Int64?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChannelAdminRights?, bannedRights: TelegramChannelBannedRights?) { + public init(id: PeerId, accessHash: Int64?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChannelAdminRights?, bannedRights: TelegramChannelBannedRights?, peerGroupId: PeerGroupId?) { self.id = id self.accessHash = accessHash self.title = title @@ -174,6 +179,7 @@ public final class TelegramChannel: Peer { self.restrictionInfo = restrictionInfo self.adminRights = adminRights self.bannedRights = bannedRights + self.peerGroupId = peerGroupId } public init(decoder: PostboxDecoder) { @@ -190,6 +196,11 @@ public final class TelegramChannel: Peer { self.restrictionInfo = decoder.decodeObjectForKey("ri") as? PeerAccessRestrictionInfo self.adminRights = decoder.decodeObjectForKey("ar", decoder: { TelegramChannelAdminRights(decoder: $0) }) as? TelegramChannelAdminRights self.bannedRights = decoder.decodeObjectForKey("br", decoder: { TelegramChannelBannedRights(decoder: $0) }) as? TelegramChannelBannedRights + if let value = decoder.decodeOptionalInt32ForKey("pgi") { + self.peerGroupId = PeerGroupId(rawValue: value) + } else { + self.peerGroupId = nil + } } public func encode(_ encoder: PostboxEncoder) { @@ -226,6 +237,11 @@ public final class TelegramChannel: Peer { } else { encoder.encodeNil(forKey: "br") } + if let peerGroupId = self.peerGroupId { + encoder.encodeInt32(peerGroupId.rawValue, forKey: "pgi") + } else { + encoder.encodeNil(forKey: "pgi") + } } public func isEqual(_ other: Peer) -> Bool { @@ -253,11 +269,15 @@ public final class TelegramChannel: Peer { return false } + if self.peerGroupId != other.peerGroupId { + return false + } + return true } func withUpdatedAddressName(_ addressName: String?) -> TelegramChannel { - return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights) + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, peerGroupId: self.peerGroupId) } } diff --git a/TelegramCore/TogglePeerChatPinned.swift b/TelegramCore/TogglePeerChatPinned.swift index bdf57f1f59..5b76fa182b 100644 --- a/TelegramCore/TogglePeerChatPinned.swift +++ b/TelegramCore/TogglePeerChatPinned.swift @@ -12,20 +12,36 @@ public enum TogglePeerChatPinnedResult { case limitExceeded } -public func togglePeerChatPinned(postbox: Postbox, peerId: PeerId) -> Signal { +public func toggleItemPinned(postbox: Postbox, itemId: PinnedItemId) -> Signal { return postbox.modify { modifier -> TogglePeerChatPinnedResult in - var peerIds = modifier.getPinnedPeerIds() - let sameKind = peerIds.filter { ($0.namespace == Namespaces.Peer.SecretChat) == (peerId.namespace == Namespaces.Peer.SecretChat) && $0 != peerId } + var itemIds = modifier.getPinnedItemIds() + let sameKind = itemIds.filter { item in + switch itemId { + case let .peer(lhsPeerId): + if case let .peer(rhsPeerId) = item { + return (lhsPeerId.namespace == Namespaces.Peer.SecretChat) == (rhsPeerId.namespace == Namespaces.Peer.SecretChat) && lhsPeerId != rhsPeerId + } else { + return false + } + case let .group(lhsGroupId): + if case let .group(rhsGroupId) = item { + return lhsGroupId != rhsGroupId + } else { + return false + } + } + + } if sameKind.count + 1 > 5 { return .limitExceeded } else { - if let index = peerIds.index(of: peerId) { - peerIds.remove(at: index) + if let index = itemIds.index(of: itemId) { + itemIds.remove(at: index) } else { - peerIds.insert(peerId, at: 0) + itemIds.insert(itemId, at: 0) } - modifier.setPinnedPeerIds(peerIds) + modifier.setPinnedItemIds(itemIds) addSynchronizePinnedChatsOperation(modifier: modifier) return .done } diff --git a/TelegramCore/UpdatePeers.swift b/TelegramCore/UpdatePeers.swift index 5e0893967c..799255ac8f 100644 --- a/TelegramCore/UpdatePeers.swift +++ b/TelegramCore/UpdatePeers.swift @@ -103,6 +103,12 @@ public func updatePeers(modifier: Modifier, peers: [Peer], update: (Peer?, Peer) if let updatedInclusion = updatedInclusion { modifier.updatePeerChatListInclusion(peerId, inclusion: updatedInclusion) } + if let channel = updated as? TelegramChannel { + let previousGroupId = (previous as? TelegramChannel)?.peerGroupId + if previousGroupId != channel.peerGroupId { + modifier.updatePeerGroupId(peerId, groupId: channel.peerGroupId) + } + } return update(previous, updated) }) } diff --git a/TelegramCore/UpdatesApiUtils.swift b/TelegramCore/UpdatesApiUtils.swift index 272f42c217..2b3e9b29e4 100644 --- a/TelegramCore/UpdatesApiUtils.swift +++ b/TelegramCore/UpdatesApiUtils.swift @@ -155,10 +155,12 @@ extension Api.Peer { } extension Api.Dialog { - var peerId: PeerId { + var peerId: PeerId? { switch self { case let .dialog(_, peer, _, _, _, _, _, _, _, _): return peer.peerId + /*%FEED case .dialogFeed: + return nil*/ } } }