diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 37156c372c..bfe6cb5905 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -129,6 +129,12 @@ D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843841DA6EDC4005F29E1 /* CachedChannelData.swift */; }; D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; }; D03C53771DAFF20F004C17B3 /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */; }; + D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */; }; + D03E5E0D1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */; }; + D041E3F51E535464008C24B4 /* AddPeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F41E535464008C24B4 /* AddPeerMember.swift */; }; + D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F41E535464008C24B4 /* AddPeerMember.swift */; }; + D041E3F81E535A88008C24B4 /* RemovePeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */; }; + D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */; }; D0448C8E1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */; }; D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */; }; D0448C911E251F96005A61A7 /* SecretChatEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */; }; @@ -178,6 +184,10 @@ D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */; }; D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */; }; D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38741E40A7F70044D6FE /* RemovePeerChat.swift */; }; + D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; }; + D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; }; + D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; }; + D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; }; D067066C1D512ADB00DED3E3 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D06706671D512ADB00DED3E3 /* Postbox.framework */; }; D067066D1D512ADB00DED3E3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D06706681D512ADB00DED3E3 /* SwiftSignalKit.framework */; }; D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */; }; @@ -310,6 +320,10 @@ D0BC38791E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38781E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift */; }; D0BC387B1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */; }; D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */; }; + D0BEAF5D1E54941B00BD963D /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5C1E54941B00BD963D /* Authorization.swift */; }; + D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5C1E54941B00BD963D /* Authorization.swift */; }; + D0BEAF601E54ACF900BD963D /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */; }; + D0BEAF611E54ACF900BD963D /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */; }; D0CAF2EA1D75EC600011F558 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */; }; D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */; }; D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354F1DE36900000195EB /* ChatContextResult.swift */; }; @@ -464,6 +478,9 @@ D03B0E691D63283000955575 /* libwebp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebp.a; path = "third-party/libwebp/lib/libwebp.a"; sourceTree = ""; }; D03B0E6B1D63283C00955575 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartUpload.swift; sourceTree = ""; }; + D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutAccountAttribute.swift; sourceTree = ""; }; + D041E3F41E535464008C24B4 /* AddPeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddPeerMember.swift; sourceTree = ""; }; + D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePeerMember.swift; sourceTree = ""; }; D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessSecretChatIncomingDecryptedOperations.swift; sourceTree = ""; }; D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatEncryption.swift; sourceTree = ""; }; D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer46.swift; sourceTree = ""; }; @@ -477,6 +494,8 @@ D049EAF41E44DF3300A2CD3A /* AccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountState.swift; sourceTree = ""; }; D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractivePhoneFormatter.swift; sourceTree = ""; }; D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = ""; }; + D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = ""; }; + D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = ""; }; D06706641D512ADB00DED3E3 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/AsyncDisplayKit.framework"; sourceTree = ""; }; D06706651D512ADB00DED3E3 /* Display.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Display.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/Display.framework"; sourceTree = ""; }; D06706671D512ADB00DED3E3 /* Postbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Postbox.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/Postbox.framework"; sourceTree = ""; }; @@ -556,6 +575,8 @@ D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizePinnedChatsOperations.swift; sourceTree = ""; }; D0BC38781E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePinnedChatsOperation.swift; sourceTree = ""; }; D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TogglePeerChatPinned.swift; sourceTree = ""; }; + D0BEAF5C1E54941B00BD963D /* Authorization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = ""; }; + D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MtProtoKitDynamic.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/MtProtoKitDynamic.framework"; sourceTree = ""; }; D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestChatContextResults.swift; sourceTree = ""; }; D0DC354F1DE36900000195EB /* ChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatContextResult.swift; sourceTree = ""; }; @@ -708,6 +729,7 @@ isa = PBXGroup; children = ( D03B0CD21D62244300955575 /* Namespaces.swift */, + D03E5E0A1E55E0220029569A /* Accounts */, D03B0CD01D62242C00955575 /* Peers */, D03B0CD11D62242F00955575 /* Messages */, D0FA8B961E1E952D001E855B /* Secret Chats */, @@ -864,6 +886,8 @@ D00C7CCE1E3628180080C3D5 /* UpdateCachedChannelParticipants.swift */, D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */, D08774FB1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift */, + D0BEAF5C1E54941B00BD963D /* Authorization.swift */, + D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */, ); name = Account; sourceTree = ""; @@ -904,6 +928,14 @@ name = "Supporting Files"; sourceTree = ""; }; + D03E5E0A1E55E0220029569A /* Accounts */ = { + isa = PBXGroup; + children = ( + D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */, + ); + name = Accounts; + sourceTree = ""; + }; D06706631D512ADA00DED3E3 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1009,6 +1041,10 @@ C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */, C2366C851E4F403C0097CCFF /* UsernameAvailability.swift */, C2366C881E4F40480097CCFF /* SupportPeerId.swift */, + D041E3F41E535464008C24B4 /* AddPeerMember.swift */, + D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */, + D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */, + D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */, ); name = Peers; sourceTree = ""; @@ -1267,6 +1303,7 @@ D03B0CDB1D62245F00955575 /* ApiUtils.swift in Sources */, D0B843C91DA7FF30005F29E1 /* NBPhoneNumberDesc.m in Sources */, D03B0CE61D6224A700955575 /* ReplyMessageAttribute.swift in Sources */, + D0BEAF601E54ACF900BD963D /* AccountManager.swift in Sources */, D02ABC7E1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */, D0448CA51E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, D03C53771DAFF20F004C17B3 /* MultipartUpload.swift in Sources */, @@ -1306,6 +1343,7 @@ D03B0CD31D62244300955575 /* Namespaces.swift in Sources */, D01D6BF91E42A713006151C6 /* SearchStickers.swift in Sources */, D0FA8BB91E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */, + D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */, D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, D0B843971DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift in Sources */, @@ -1320,6 +1358,7 @@ D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */, D0BC38771E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift in Sources */, D0B843851DA6EDC4005F29E1 /* CachedChannelData.swift in Sources */, + D0BEAF5D1E54941B00BD963D /* Authorization.swift in Sources */, D0B843831DA6EDB8005F29E1 /* CachedGroupData.swift in Sources */, D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */, @@ -1331,6 +1370,7 @@ D03B0D0C1D62255C00955575 /* AccountStateManagementUtils.swift in Sources */, D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, + D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, D00D97CA1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */, @@ -1346,6 +1386,7 @@ D03B0D5D1D631A6900955575 /* MultipartFetch.swift in Sources */, D0BC38751E40A7F70044D6FE /* RemovePeerChat.swift in Sources */, D0AB0B961D662F0B002C78E7 /* ManagedChatListHoles.swift in Sources */, + D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */, D02ABC841E32183300CAE539 /* ManagedSynchronizePinnedCloudChatsOperations.swift in Sources */, D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */, D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */, @@ -1369,7 +1410,9 @@ D0AB0B941D662ECE002C78E7 /* ManagedMessageHistoryHoles.swift in Sources */, D08774FC1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift in Sources */, D03B0CF41D62250800955575 /* TelegramMediaAction.swift in Sources */, + D041E3F81E535A88008C24B4 /* RemovePeerMember.swift in Sources */, D0B417C11D7DCEEF004562A4 /* ApiGroupOrChannel.swift in Sources */, + D041E3F51E535464008C24B4 /* AddPeerMember.swift in Sources */, D0B843BF1DA7FF30005F29E1 /* NBNumberFormat.m in Sources */, D0B843C51DA7FF30005F29E1 /* NBPhoneNumber.m in Sources */, D03B0D0D1D62255C00955575 /* SynchronizePeerReadState.swift in Sources */, @@ -1441,6 +1484,7 @@ D049EAEC1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift in Sources */, D0FA8B991E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */, D001F3F01E128A1C007A8C60 /* AccountStateManagementUtils.swift in Sources */, + D0BEAF611E54ACF900BD963D /* AccountManager.swift in Sources */, D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */, D0B8442B1DAB91E0005F29E1 /* NBMetadataCore.m in Sources */, D00C7CD01E3628180080C3D5 /* UpdateCachedChannelParticipants.swift in Sources */, @@ -1480,6 +1524,7 @@ D02ABC851E32183300CAE539 /* ManagedSynchronizePinnedCloudChatsOperations.swift in Sources */, D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */, D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */, + D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, D0DC35521DE36908000195EB /* ChatContextResult.swift in Sources */, D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, D073CE6D1DCBCF17007511FD /* InlineBotMessageAttribute.swift in Sources */, @@ -1494,6 +1539,7 @@ D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */, D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */, D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */, + D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */, D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */, D03C53701DAD5CA9004C17B3 /* ExportedInvitation.swift in Sources */, D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */, @@ -1505,6 +1551,7 @@ D03C53681DAD5CA9004C17B3 /* PeerUtils.swift in Sources */, D050F2621E4A5AE700988324 /* GlobalNotificationSettings.swift in Sources */, D0B418991D7E0580004562A4 /* TelegramMediaMap.swift in Sources */, + D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */, D03C53691DAD5CA9004C17B3 /* PeerAccessRestrictionInfo.swift in Sources */, D0B8440E1DAB91CD005F29E1 /* MessageUtils.swift in Sources */, @@ -1520,6 +1567,7 @@ D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */, D0B418861D7E056D004562A4 /* Namespaces.swift in Sources */, D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */, + D03E5E0D1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */, D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */, D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */, D0FA8BBA1E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */, @@ -1543,7 +1591,9 @@ D0B844301DAB91E0005F29E1 /* NBNumberFormat.m in Sources */, D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */, D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, + D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */, D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */, + D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */, D0B844361DAB91E0005F29E1 /* NBPhoneNumberUtil.m in Sources */, D073CE6F1DCBCF17007511FD /* OutgoingMessageInfoAttribute.swift in Sources */, D0B844431DAB91FD005F29E1 /* Account.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 727082533c..10af0959fd 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -10,22 +10,6 @@ import Foundation #endif import TelegramCorePrivateModule -public struct AccountId: Comparable, Hashable { - let stringValue: String - - public static func ==(lhs: AccountId, rhs: AccountId) -> Bool { - return lhs.stringValue == rhs.stringValue - } - - public static func <(lhs: AccountId, rhs: AccountId) -> Bool { - return lhs.stringValue < rhs.stringValue - } - - public var hashValue: Int { - return self.stringValue.hash - } -} - public class AccountState: Coding, Equatable { public required init(decoder: Decoder) { } @@ -129,47 +113,25 @@ public func ==(lhs: AuthorizedAccountState.State, rhs: AuthorizedAccountState.St lhs.seq == rhs.seq } -public func currentAccountId(appGroupPath: String, testingEnvironment: Bool) -> AccountId { - let filePath: String - if testingEnvironment { - filePath = "\(appGroupPath)/currentAccountId_test" - } else { - filePath = "\(appGroupPath)/currentAccountId" - } - if let id = try? String(contentsOfFile: filePath) { - return AccountId(stringValue: id) - } else { - let id = generateAccountId() - let _ = try? id.stringValue.write(toFile: filePath, atomically: true, encoding: .utf8) - return id - } -} - -public func generateAccountId() -> AccountId { - return AccountId(stringValue: NSUUID().uuidString) -} - public class UnauthorizedAccount { - public let id: AccountId + public let id: AccountRecordId public let appGroupPath: String public let basePath: String public let testingEnvironment: Bool public let postbox: Postbox public let network: Network - public let logger: Logger public var masterDatacenterId: Int32 { return Int32(self.network.mtProto.datacenterId) } - init(id: AccountId, appGroupPath: String, basePath: String, logger: Logger, testingEnvironment: Bool, postbox: Postbox, network: Network) { + init(id: AccountRecordId, appGroupPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network) { self.id = id self.appGroupPath = appGroupPath self.basePath = basePath self.testingEnvironment = testingEnvironment self.postbox = postbox self.network = network - self.logger = logger network.shouldKeepConnection.set(.single(true)) } @@ -188,7 +150,7 @@ public class UnauthorizedAccount { return initializedNetwork(datacenterId: Int(masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: self.basePath), testingEnvironment: self.testingEnvironment) |> map { network in - return UnauthorizedAccount(id: self.id, appGroupPath: self.appGroupPath, basePath: self.basePath, logger: self.logger, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) + return UnauthorizedAccount(id: self.id, appGroupPath: self.appGroupPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) } } } @@ -247,6 +209,7 @@ private var declaredEncodables: Void = { declareEncodable(SynchronizePinnedChatsOperation.self, f: { SynchronizePinnedChatsOperation(decoder: $0) }) declareEncodable(RecentMediaItem.self, f: { RecentMediaItem(decoder: $0) }) declareEncodable(RecentPeerItem.self, f: { RecentPeerItem(decoder: $0) }) + declareEncodable(LoggedOutAccountAttribute.self, f: { LoggedOutAccountAttribute(decoder: $0) }) return }() @@ -255,16 +218,15 @@ func accountNetworkUsageInfoPath(basePath: String) -> String { return basePath + "/network-usage" } -public enum AccountLogger { - case named(String) - case instance(Logger) +private func accountRecordIdPathName(_ id: AccountRecordId) -> String { + return "account-\(UInt64(bitPattern: id.int64))" } -public func accountWithId(_ id: AccountId, appGroupPath: String, logger: AccountLogger, testingEnvironment: Bool) -> Signal, NoError> { +public func accountWithId(_ id: AccountRecordId, appGroupPath: String, testingEnvironment: Bool) -> Signal, NoError> { return Signal<(String, Postbox, Coding?), NoError> { subscriber in let _ = declaredEncodables - let path = "\(appGroupPath)/account\(id.stringValue)" + let path = "\(appGroupPath)/\(accountRecordIdPathName(id))" var initializeMessageNamespacesWithHoles: [(PeerId.Namespace, MessageId.Namespace)] = [] for peerNamespace in peerIdNamespacesWithInitialCloudMessageHoles { @@ -274,6 +236,7 @@ public func accountWithId(_ id: AccountId, appGroupPath: String, logger: Account let seedConfiguration = SeedConfiguration(initializeChatListWithHoles: [ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: 1))], initializeMessageNamespacesWithHoles: initializeMessageNamespacesWithHoles, existingMessageTags: allMessageTags) let postbox = Postbox(basePath: path + "/postbox", globalMessageIdsNamespace: Namespaces.Message.Cloud, seedConfiguration: seedConfiguration) + return (postbox.stateView() |> take(1) |> map { view -> (String, Postbox, Coding?) in let accountState = view.state return (path, postbox, accountState) @@ -290,25 +253,17 @@ public func accountWithId(_ id: AccountId, appGroupPath: String, logger: Account postbox.removeKeychainEntryForKey(key) }) - let concreteLogger: Logger - switch logger { - case let .named(name): - concreteLogger = Logger(basePath: basePath + "/" + name) - case let .instance(instance): - concreteLogger = instance - } - if let accountState = accountState { switch accountState { case let unauthorizedState as UnauthorizedAccountState: return initializedNetwork(datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: basePath), testingEnvironment: testingEnvironment) |> map { network -> Either in - .left(value: UnauthorizedAccount(id: id, appGroupPath: appGroupPath, basePath: basePath, logger: concreteLogger, testingEnvironment: testingEnvironment, postbox: postbox, network: network)) + .left(value: UnauthorizedAccount(id: id, appGroupPath: appGroupPath, basePath: basePath, testingEnvironment: testingEnvironment, postbox: postbox, network: network)) } case let authorizedState as AuthorizedAccountState: return initializedNetwork(datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: basePath), testingEnvironment: testingEnvironment) |> map { network -> Either in - return .right(value: Account(id: id, basePath: basePath, logger: concreteLogger, testingEnvironment: testingEnvironment, postbox: postbox, network: network, peerId: authorizedState.peerId)) + return .right(value: Account(id: id, basePath: basePath, testingEnvironment: testingEnvironment, postbox: postbox, network: network, peerId: authorizedState.peerId)) } case _: assertionFailure("Unexpected accountState \(accountState)") @@ -317,7 +272,7 @@ public func accountWithId(_ id: AccountId, appGroupPath: String, logger: Account return initializedNetwork(datacenterId: 2, keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: basePath), testingEnvironment: testingEnvironment) |> map { network -> Either in - return .left(value: UnauthorizedAccount(id: id, appGroupPath: appGroupPath, basePath: basePath, logger: concreteLogger, testingEnvironment: testingEnvironment, postbox: postbox, network: network)) + return .left(value: UnauthorizedAccount(id: id, appGroupPath: appGroupPath, basePath: basePath, testingEnvironment: testingEnvironment, postbox: postbox, network: network)) } } } @@ -380,15 +335,13 @@ public enum AccountNetworkState { } public class Account { - public let id: AccountId + public let id: AccountRecordId public let basePath: String public let testingEnvironment: Bool public let postbox: Postbox public let network: Network public let peerId: PeerId - public let logger: Logger - public private(set) var stateManager: AccountStateManager! public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! @@ -422,14 +375,13 @@ public class Account { return self.networkStateValue.get() } - public init(id: AccountId, basePath: String, logger: Logger, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId) { + public init(id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, peerId: PeerId) { self.id = id self.basePath = basePath self.testingEnvironment = testingEnvironment self.postbox = postbox self.network = network self.peerId = peerId - self.logger = logger self.peerInputActivityManager = PeerInputActivityManager() self.stateManager = AccountStateManager(account: self, peerInputActivityManager: self.peerInputActivityManager) @@ -546,10 +498,10 @@ public class Account { |> deliverOn(Queue.concurrentDefaultQueue()) |> mapToSignal { [weak self] value -> Signal in if let strongSelf = self, value { - trace("Account", what: "Became master") + Logger.shared.log("Account", "Became master") return managedServiceViews(network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager) } else { - trace("Account", what: "Resigned master") + Logger.shared.log("Account", "Resigned master") return .never() } } diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index 39748113bf..bc67cd601e 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -43,7 +43,7 @@ enum AccountStateMutationOperation { case UpdatePeerNotificationSettings(PeerId, PeerNotificationSettings) case AddHole(MessageId) case MergeApiChats([Api.Chat]) - case UpdatePeer(PeerId, (Peer) -> Peer) + case UpdatePeer(PeerId, (Peer?) -> Peer?) case MergeApiUsers([Api.User]) case MergePeerPresences([PeerId: PeerPresence]) case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) @@ -166,7 +166,7 @@ struct AccountMutableState { self.addOperation(.MergeApiChats(chats)) } - mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer) -> Peer) { + mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer?) -> Peer?) { self.addOperation(.UpdatePeer(id, f)) } @@ -241,8 +241,8 @@ struct AccountMutableState { } } case let .UpdatePeer(id, f): - if let peer = self.peers[id] { - let updatedPeer = f(peer) + let peer = self.peers[id] + if let updatedPeer = f(peer) { peers[id] = updatedPeer insertedPeers[id] = updatedPeer } diff --git a/TelegramCore/AccountManager.swift b/TelegramCore/AccountManager.swift new file mode 100644 index 0000000000..e844a1e045 --- /dev/null +++ b/TelegramCore/AccountManager.swift @@ -0,0 +1,176 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private enum AccountKind { + case authorized + case unauthorized +} + +public func currentAccount(manager: AccountManager, appGroupPath: String, testingEnvironment: Bool) -> Signal?, NoError> { + return manager.allocatedCurrentAccountId() + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs == rhs + }) + |> mapToSignal { id -> Signal?, NoError> in + if let id = id { + let reload = ValuePromise(true, ignoreRepeated: false) + return reload.get() |> mapToSignal { _ -> Signal?, NoError> in + return accountWithId(id, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment) + |> mapToSignal { account -> Signal?, NoError> in + let postbox: Postbox + let initialKind: AccountKind + switch account { + case let .left(value: account): + postbox = account.postbox + initialKind = .unauthorized + case let.right(value: account): + postbox = account.postbox + initialKind = .authorized + } + let updatedKind = postbox.stateView() + |> map { view -> Bool in + let kind: AccountKind + if view.state is AuthorizedAccountState { + kind = .authorized + } else { + kind = .unauthorized + } + if kind != initialKind { + return true + } else { + return false + } + } + |> distinctUntilChanged + + return Signal { subscriber in + subscriber.putNext(account) + + return updatedKind.start(next: { value in + if value { + reload.set(true) + } + }) + } + } + } + } else { + return .single(nil) + } + } +} + +public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManager) -> Signal { + return accountManager.modify { modifier -> Void in + let currentId = modifier.getCurrentId() + if let currentId = currentId { + modifier.updateRecord(currentId, { current in + if let current = current { + var found = false + for attribute in current.attributes { + if attribute is LoggedOutAccountAttribute { + found = true + break + } + } + if found { + return current + } else { + return AccountRecord(id: current.id, attributes: current.attributes + [LoggedOutAccountAttribute()]) + } + } else { + return nil + } + }) + let id = modifier.createRecord([]) + modifier.setCurrentId(id) + } + } +} + +public func managedCleanupAccounts(accountManager: AccountManager, appGroupPath: String) -> Signal { + return Signal { subscriber in + let loggedOutAccounts = Atomic<[AccountRecordId: MetaDisposable]>(value: [:]) + let disposable = accountManager.accountRecords().start(next: { view in + var disposeList: [(AccountRecordId, MetaDisposable)] = [] + var beginList: [(AccountRecordId, MetaDisposable)] = [] + let _ = loggedOutAccounts.modify { disposables in + let validIds = Set(view.records.filter { + for attribute in $0.attributes { + if attribute is LoggedOutAccountAttribute { + return true + } + } + return false + }.map { $0.id }) + + + var disposables = disposables + + for id in disposables.keys { + if !validIds.contains(id) { + disposeList.append((id, disposables[id]!)) + } + } + + for (id, _) in disposeList { + disposables.removeValue(forKey: id) + } + + for id in validIds { + if disposables[id] == nil { + let disposable = MetaDisposable() + beginList.append((id, disposable)) + disposables[id] = disposable + } + } + + return disposables + } + for (_, disposable) in disposeList { + disposable.dispose() + } + for (id, disposable) in beginList { + disposable.set(cleanupAccount(accountManager: accountManager, id: id, appGroupPath: appGroupPath).start()) + } + }) + + return ActionDisposable { + disposable.dispose() + } + } +} + + +private func cleanupAccount(accountManager: AccountManager, id: AccountRecordId, appGroupPath: String) -> Signal { + return accountWithId(id, appGroupPath: appGroupPath, testingEnvironment: false) + |> mapToSignal { account -> Signal in + switch account { + case .left: + return .complete() + case let .right(account): + account.shouldBeServiceTaskMaster.set(.single(.always)) + return account.network.request(Api.functions.auth.logOut()) + |> map { Optional($0) } + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + account.shouldBeServiceTaskMaster.set(.single(.never)) + return accountManager.modify { modifier -> Void in + modifier.updateRecord(id, { _ in + return nil + }) + } + } + } + } +} diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 78f54071e2..8db9a2dc20 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -376,7 +376,7 @@ func finalStateWithUpdateGroups(_ account: Account, state: AccountMutableState, updatedState.updateState(AuthorizedAccountState.State(pts: update.ptsRange.0, qts: updatedState.state.qts, date: updatedState.state.date, seq: updatedState.state.seq)) } else { if ptsUpdatesAfterHole.count == 0 { - trace("State", what: "update pts hole: \(update.ptsRange.0) != \(updatedState.state.pts) + \(update.ptsRange.1)") + Logger.shared.log("State", "update pts hole: \(update.ptsRange.0) != \(updatedState.state.pts) + \(update.ptsRange.1)") } ptsUpdatesAfterHole.append(update) } @@ -396,7 +396,7 @@ func finalStateWithUpdateGroups(_ account: Account, state: AccountMutableState, updatedState.updateState(AuthorizedAccountState.State(pts: updatedState.state.pts, qts: update.qtsRange.0, date: updatedState.state.date, seq: updatedState.state.seq)) } else { if qtsUpdatesAfterHole.count == 0 { - trace("State", what: "update qts hole: \(update.qtsRange.0) != \(updatedState.state.qts) + \(update.qtsRange.1)") + Logger.shared.log("State", "update qts hole: \(update.qtsRange.0) != \(updatedState.state.qts) + \(update.qtsRange.1)") } qtsUpdatesAfterHole.append(update) } @@ -414,7 +414,7 @@ func finalStateWithUpdateGroups(_ account: Account, state: AccountMutableState, updatedState.updateState(AuthorizedAccountState.State(pts: updatedState.state.pts, qts: updatedState.state.qts, date: group.date, seq: group.seqRange.0)) } else { if seqGroupsAfterHole.count == 0 { - print("update seq hole: \(group.seqRange.0) != \(updatedState.state.seq) + \(group.seqRange.1)") + Logger.shared.log("State", "update seq hole: \(group.seqRange.0) != \(updatedState.state.seq) + \(group.seqRange.1)") } seqGroupsAfterHole.append(group) } @@ -616,27 +616,27 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) if !channelsToPoll.contains(peerId) { - //trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateChannelTooLong") + //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateChannelTooLong") channelsToPoll.insert(peerId) } case let .updateDeleteChannelMessages(channelId, messages, pts: pts, ptsCount): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) if let previousState = updatedState.channelStates[peerId] { if previousState.pts >= pts { - //trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update") + //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update") } else if previousState.pts + ptsCount == pts { updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })) updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) } else { if !channelsToPoll.contains(peerId) { - trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole") + Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole") channelsToPoll.insert(peerId) //updatedMissingUpdates = true } } } else { if !channelsToPoll.contains(peerId) { - //trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") + //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") channelsToPoll.insert(peerId) } } @@ -645,7 +645,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, let peerId = messageId.peerId if let previousState = updatedState.channelStates[peerId] { if previousState.pts >= pts { - //trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update") + //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old edit update") } else if previousState.pts + ptsCount == pts { if let preCachedResources = apiMessage.preCachedResources { for (resource, data) in preCachedResources { @@ -656,19 +656,19 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) } else { if !channelsToPoll.contains(peerId) { - trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) edit message pts hole") + Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) edit message pts hole") channelsToPoll.insert(peerId) //updatedMissingUpdates = true } } } else { if !channelsToPoll.contains(peerId) { - //trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") + //Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") channelsToPoll.insert(peerId) } } } else { - trace("State", what: "Invalid updateEditChannelMessage") + Logger.shared.log("State", "Invalid updateEditChannelMessage") } case let .updateChannelWebPage(channelId, apiWebpage, pts, ptsCount): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) @@ -687,7 +687,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.updateChannelState(peerId, state: previousState.setPts(pts)) } else { if !channelsToPoll.contains(peerId) { - trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateWebPage pts hole") + Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateWebPage pts hole") channelsToPoll.insert(peerId) } } @@ -711,7 +711,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, if let message = StoreMessage(apiMessage: apiMessage) { if let previousState = updatedState.channelStates[message.id.peerId] { if previousState.pts >= pts { - //trace("State", what: "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) skip old message \(message.id) (\(message.text))") + //Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) skip old message \(message.id) (\(message.text))") } else if previousState.pts + ptsCount == pts { if let preCachedResources = apiMessage.preCachedResources { for (resource, data) in preCachedResources { @@ -722,7 +722,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, updatedState.updateChannelState(message.id.peerId, state: previousState.setPts(pts)) } else { if !channelsToPoll.contains(message.id.peerId) { - trace("State", what: "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) message pts hole") + Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) message pts hole") ; channelsToPoll.insert(message.id.peerId) //updatedMissingUpdates = true @@ -730,7 +730,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, } } else { if !channelsToPoll.contains(message.id.peerId) { - trace("State", what: "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") + Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) state unknown") channelsToPoll.insert(message.id.peerId) } } @@ -769,7 +769,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, } if alreadyStored { - trace("State", what: "skipping message at \(date) for \(peerId): already stored") + Logger.shared.log("State", "skipping message at \(date) for \(peerId): already stored") } else { var attributes: [MessageAttribute] = [] if !entities.isEmpty { @@ -864,7 +864,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState, if let peer = updatedState.peers[peerId] { pollChannelSignals.append(pollChannel(account, peer: peer, state: updatedState.branch())) } else { - trace("State", what: "can't poll channel \(peerId): no peer found") + Logger.shared.log("State", "can't poll channel \(peerId): no peer found") } } @@ -972,7 +972,7 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Acc if let peer = state.peers[peerId], let inputPeer = apiInputPeer(peer) { missingPeers[peerId] = inputPeer } else { - trace("State", what: "can't fetch notification settings for peer \(peerId): can't create inputPeer") + Logger.shared.log("State", "can't fetch notification settings for peer \(peerId): can't create inputPeer") } } } @@ -980,7 +980,7 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Acc if missingPeers.isEmpty { return .single(state) } else { - trace("State", what: "will fetch notification settings for \(missingPeers.count) peers") + Logger.shared.log("State", "will fetch notification settings for \(missingPeers.count) peers") var signals: [Signal<(PeerId, PeerNotificationSettings)?, NoError>] = [] for (peerId, peer) in missingPeers { let fetchSettings = account.network.request(Api.functions.account.getNotifySettings(peer: .inputNotifyPeer(peer: peer))) @@ -1088,7 +1088,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt return updatedState } } else { - trace("State", what: "can't poll channel \(peer.id): can't create inputChannel") + Logger.shared.log("State", "can't poll channel \(peer.id): can't create inputChannel") return single(state, NoError.self) } } @@ -1105,7 +1105,7 @@ private func verifyTransaction(_ modifier: Modifier, finalState: AccountMutableS } if !missingPeerIds.isEmpty { - trace("State", what: "missing peers \(missingPeerIds)") + Logger.shared.log("State", "missing peers \(missingPeerIds)") return false } @@ -1133,7 +1133,7 @@ private func verifyTransaction(_ modifier: Modifier, finalState: AccountMutableS } if !previousStateMatches { - trace("State", what: ".UpdateState previous state \(previousState) doesn't match current state \(String(describing: currentState))") + Logger.shared.log("State", ".UpdateState previous state \(previousState) doesn't match current state \(String(describing: currentState))") failed = true } } @@ -1150,7 +1150,7 @@ private func verifyTransaction(_ modifier: Modifier, finalState: AccountMutableS previousStateMatches = true } if !previousStateMatches { - trace("State", what: ".UpdateChannelState for \(peerId), previous state \(previousState) doesn't match current state \(String(describing: currentState))") + Logger.shared.log("State", ".UpdateChannelState for \(peerId), previous state \(previousState) doesn't match current state \(String(describing: currentState))") failed = true } } @@ -1268,10 +1268,8 @@ func replayFinalState(mediaBox: MediaBox, modifier: Modifier, finalState: Accoun case let .UpdateState(state): let currentState = modifier.getState() as! AuthorizedAccountState modifier.setState(currentState.changedState(state)) - //trace("State", what: "setting state \(state)") case let .UpdateChannelState(peerId, channelState): modifier.setPeerChatState(peerId, state: channelState) - //trace("State", what: "setting channel \(peerId) \(finalState.state.peers[peerId]?.displayTitle ?? "nil") state \(channelState)") case let .UpdatePeerNotificationSettings(peerId, notificationSettings): modifier.updatePeerNotificationSettings([peerId: notificationSettings]) case let .AddHole(messageId): @@ -1297,8 +1295,8 @@ func replayFinalState(mediaBox: MediaBox, modifier: Modifier, finalState: Accoun return updated }) case let .UpdatePeer(id, f): - if let peer = modifier.getPeer(id) { - updatePeers(modifier: modifier, peers: [f(peer)], update: { _, updated in + if let peer = f(modifier.getPeer(id)) { + updatePeers(modifier: modifier, peers: [peer], update: { _, updated in return updated }) } @@ -1319,7 +1317,7 @@ func replayFinalState(mediaBox: MediaBox, modifier: Modifier, finalState: Accoun modifier.setPeerChatState(peer.id, state: state) modifier.operationLogRemoveAllEntries(peerId: peer.id, tag: OperationLogTags.SecretOutgoing) } else { - trace("State", what: "got encryptedChatDiscarded, but peer doesn't exist") + Logger.shared.log("State", "got encryptedChatDiscarded, but peer doesn't exist") } case .encryptedChatEmpty(_): break @@ -1343,7 +1341,7 @@ func replayFinalState(mediaBox: MediaBox, modifier: Modifier, finalState: Accoun assertionFailure() } } else { - trace("State", what: "got encryptedChatRequested, but peer already exists") + Logger.shared.log("State", "got encryptedChatRequested, but peer already exists") } case let .encryptedChatWaiting(_, accessHash, date, adminId, participantId): break diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift index f278923013..e4619af265 100644 --- a/TelegramCore/AccountStateManager.swift +++ b/TelegramCore/AccountStateManager.swift @@ -14,7 +14,7 @@ private enum AccountStateManagerOperation { case collectUpdateGroups([UpdateGroup], Double) case processUpdateGroups([UpdateGroup]) case custom(Int32, Signal) - case pollCompletion(Int32, [(Int32, () -> Void)]) + case pollCompletion(Int32, [MessageId], [(Int32, ([MessageId]) -> Void)]) case processEvents(Int32, AccountFinalStateEvents) } @@ -161,11 +161,13 @@ public final class AccountStateManager { } private func replaceOperations(with operation: AccountStateManagerOperation) { - var collectedPollCompletionSubscribers: [(Int32, () -> Void)] = [] + var collectedMessageIds: [MessageId] = [] + var collectedPollCompletionSubscribers: [(Int32, ([MessageId]) -> Void)] = [] if !self.operations.isEmpty { for operation in self.operations { - if case let .pollCompletion(_, subscribers) = operation { + if case let .pollCompletion(_, messageIds, subscribers) = operation { + collectedMessageIds.append(contentsOf: messageIds) collectedPollCompletionSubscribers.append(contentsOf: subscribers) } } @@ -173,8 +175,9 @@ public final class AccountStateManager { self.operations.removeAll() self.operations.append(operation) - for (id, f) in collectedPollCompletionSubscribers { - let _ = self.addPollCompletion(f, id: id) + + if !collectedPollCompletionSubscribers.isEmpty || !collectedMessageIds.isEmpty { + self.operations.append(.pollCompletion(self.getNextId(), collectedMessageIds, collectedPollCompletionSubscribers)) } } @@ -215,7 +218,7 @@ public final class AccountStateManager { return initialStateWithDifference(account, difference: difference) |> mapToSignal { state -> Signal<(Api.updates.Difference?, AccountReplayedFinalState?), NoError> in if state.initialState.state != authorizedState { - trace("State", what: "pollDifference initial state \(authorizedState) != current state \(state.initialState.state)") + Logger.shared.log("State", "pollDifference initial state \(authorizedState) != current state \(state.initialState.state)") return .single((nil, nil)) } else { return finalStateWithDifference(account: account, state: state, difference: difference) @@ -286,7 +289,7 @@ public final class AccountStateManager { } }, error: { _ in assertionFailure() - trace("AccountStateManager", what: "processUpdateGroups signal completed with error") + Logger.shared.log("AccountStateManager", "processUpdateGroups signal completed with error") }) case let .collectUpdateGroups(_, timeout): self.operationTimer?.invalidate() @@ -296,7 +299,7 @@ public final class AccountStateManager { if timeout.isEqual(to: 0.0) { strongSelf.operations[0] = .processUpdateGroups(groups) } else { - trace("AccountStateManager", what: "timeout while waiting for updates") + Logger.shared.log("AccountStateManager", "timeout while waiting for updates") strongSelf.replaceOperations(with: .pollDifference(AccountFinalStateEvents())) } strongSelf.startFirstOperation() @@ -350,7 +353,7 @@ public final class AccountStateManager { } }, error: { _ in assertionFailure() - trace("AccountStateManager", what: "processUpdateGroups signal completed with error") + Logger.shared.log("AccountStateManager", "processUpdateGroups signal completed with error") }) case let .custom(operationId, signal): self.operationTimer?.invalidate() @@ -388,6 +391,16 @@ public final class AccountStateManager { } } strongSelf.operations.removeFirst() + var pollCount = 0 + for i in 0 ..< strongSelf.operations.count { + if case let .pollCompletion(pollId, messageIds, subscribers) = strongSelf.operations[i] { + pollCount += 1 + var updatedMessageIds = messageIds + updatedMessageIds.append(contentsOf: events.addedIncomingMessageIds) + strongSelf.operations[i] = .pollCompletion(pollId, updatedMessageIds, subscribers) + } + } + assert(pollCount <= 1) strongSelf.startFirstOperation() } else { assertionFailure() @@ -396,45 +409,11 @@ public final class AccountStateManager { } let signal = self.account.postbox.modify { modifier -> [Message] in - let timestamp = Int32(self.account.network.context.globalTime()) var messages: [Message] = [] for id in events.addedIncomingMessageIds { - var notify = true - - if let notificationSettings = modifier.getPeerNotificationSettings(id.peerId) as? TelegramPeerNotificationSettings { - switch notificationSettings.muteState { - case let .muted(until): - if until >= timestamp { - notify = false - } - case .unmuted: - break - } - } else { - trace("AccountStateManager", what: "notification settings for \(id.peerId) are undefined") - } - - if notify { - if let message = modifier.getMessage(id) { - var foundReadState = false - var isUnread = true - if let readState = modifier.getCombinedPeerReadState(id.peerId) { - if readState.isIncomingMessageIndexRead(MessageIndex(message)) { - isUnread = false - } - foundReadState = true - } - - if !foundReadState { - trace("AccountStateManager", what: "read state for \(id.peerId) is undefined") - } - - if isUnread { - messages.append(message) - } - } else { - trace("AccountStateManager", what: "notification message doesn't exist") - } + let (message, notify) = messageForNotification(modifier: modifier, id: id, alwaysReturnMessage: false) + if let message = message, notify { + messages.append(message) } } return messages @@ -443,7 +422,7 @@ public final class AccountStateManager { let _ = (signal |> deliverOn(self.queue)).start(next: { [weak self] messages in if let strongSelf = self { for message in messages { - print("notify: \(String(describing: messageMainPeer(message)?.displayTitle)): \(message.id)") + Logger.shared.log("State" , "notify: \(String(describing: messageMainPeer(message)?.displayTitle)): \(message.id)") } strongSelf.notificationMessagesPipe.putNext(messages) @@ -453,12 +432,10 @@ public final class AccountStateManager { }, completed: { completed() }) - case let .pollCompletion(pollId, preSubscribers): + case let .pollCompletion(pollId, preMessageIds, preSubscribers): if self.operations.count > 1 { self.operations.removeFirst() - for (id, f) in preSubscribers { - let _ = self.addPollCompletion(f, id: id) - } + self.postponePollCompletionOperation(messageIds: preMessageIds, subscribers: preSubscribers) self.startFirstOperation() } else { self.operationTimer?.invalidate() @@ -466,18 +443,16 @@ public final class AccountStateManager { |> deliverOn(self.queue) let completed: () -> Void = { [weak self] in if let strongSelf = self { - if let topOperation = strongSelf.operations.first, case let .pollCompletion(topPollId, subscribers) = topOperation { + if let topOperation = strongSelf.operations.first, case let .pollCompletion(topPollId, messageIds, subscribers) = topOperation { assert(topPollId == pollId) strongSelf.operations.removeFirst() if strongSelf.operations.isEmpty { for (_, f) in subscribers { - f() + f(messageIds) } } else { - for (id, f) in subscribers { - let _ = strongSelf.addPollCompletion(f, id: id) - } + strongSelf.postponePollCompletionOperation(messageIds: messageIds, subscribers: subscribers) } strongSelf.startFirstOperation() } else { @@ -506,29 +481,34 @@ public final class AccountStateManager { } } - private func addPollCompletion(_ f: @escaping () -> Void, id: Int32?) -> Int32 { + private func postponePollCompletionOperation(messageIds: [MessageId], subscribers: [(Int32, ([MessageId]) -> Void)]) { + self.operations.append(.pollCompletion(self.getNextId(), messageIds, subscribers)) + + for i in 0 ..< self.operations.count { + if case .pollCompletion = self.operations[i] { + if i != self.operations.count - 1 { + assertionFailure() + } + } + } + } + + private func addPollCompletion(_ f: @escaping ([MessageId]) -> Void) -> Int32 { assert(self.queue.isCurrent()) - let updatedId: Int32 - if let id = id { - updatedId = id - } else { - updatedId = self.getNextId() - } + let updatedId: Int32 = self.getNextId() - if !self.operations.isEmpty { - for i in 1 ..< self.operations.count { - if case let .pollCompletion(pollId, subscribers) = self.operations[i] { - var subscribers = subscribers - subscribers.append((updatedId, f)) - self.operations[i] = .pollCompletion(pollId, subscribers) - return updatedId - } + for i in 0 ..< self.operations.count { + if case let .pollCompletion(pollId, messageIds, subscribers) = self.operations[i] { + var subscribers = subscribers + subscribers.append((updatedId, f)) + self.operations[i] = .pollCompletion(pollId, messageIds, subscribers) + return updatedId } } let beginFirst = self.operations.isEmpty - self.operations.append(.pollCompletion(self.getNextId(), [(updatedId, f)])) + self.operations.append(.pollCompletion(self.getNextId(), [], [(updatedId, f)])) if beginFirst { self.startFirstOperation() } @@ -538,12 +518,12 @@ public final class AccountStateManager { private func removePollCompletion(_ id: Int32) { for i in 0 ..< self.operations.count { - if case let .pollCompletion(pollId, subscribers) = self.operations[i] { + if case let .pollCompletion(pollId, messages, subscribers) = self.operations[i] { for j in 0 ..< subscribers.count { if subscribers[j].0 == id { var subscribers = subscribers subscribers.remove(at: j) - self.operations[i] = .pollCompletion(pollId, subscribers) + self.operations[i] = .pollCompletion(pollId, messages, subscribers) break } } @@ -551,14 +531,15 @@ public final class AccountStateManager { } } - public func wakeup() -> Signal { + public func pollStateUpdateCompletion() -> Signal<[MessageId], NoError> { return Signal { [weak self] subscriber in let disposable = MetaDisposable() if let strongSelf = self { strongSelf.queue.async { - let id = strongSelf.addPollCompletion({ + let id = strongSelf.addPollCompletion({ messageIds in + subscriber.putNext(messageIds) subscriber.putCompletion() - }, id: nil) + }) disposable.set(ActionDisposable { if let strongSelf = self { @@ -573,3 +554,52 @@ public final class AccountStateManager { } } } + +public func messageForNotification(modifier: Modifier, id: MessageId, alwaysReturnMessage: Bool) -> (message: Message?, notify: Bool) { + var notify = true + + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + if let notificationSettings = modifier.getPeerNotificationSettings(id.peerId) as? TelegramPeerNotificationSettings { + switch notificationSettings.muteState { + case let .muted(until): + if until >= timestamp { + notify = false + } + case .unmuted: + break + } + } else { + Logger.shared.log("AccountStateManager", "notification settings for \(id.peerId) are undefined") + } + + + if notify { + let message = modifier.getMessage(id) + if let message = message { + var foundReadState = false + var isUnread = true + if let readState = modifier.getCombinedPeerReadState(id.peerId) { + if readState.isIncomingMessageIndexRead(MessageIndex(message)) { + isUnread = false + } + foundReadState = true + } + + if !foundReadState { + Logger.shared.log("AccountStateManager", "read state for \(id.peerId) is undefined") + } + + return (message, isUnread) + } else { + Logger.shared.log("AccountStateManager", "notification message doesn't exist") + return (nil, false) + } + } else { + var message: Message? + if alwaysReturnMessage { + message = modifier.getMessage(id) + } + return (message, false) + } +} diff --git a/TelegramCore/AccountViewTracker.swift b/TelegramCore/AccountViewTracker.swift index ec040dc87b..a72323b3d6 100644 --- a/TelegramCore/AccountViewTracker.swift +++ b/TelegramCore/AccountViewTracker.swift @@ -322,49 +322,44 @@ public final class AccountViewTracker { } public func updatedCachedChannelParticipants(_ peerId: PeerId, forceImmediateUpdate: Bool = false) -> Signal { - if let account = self.account { - let queue = self.queue - return Signal { [weak self] subscriber in - let disposable = MetaDisposable() - queue.async { - if let strongSelf = self { - let context: CachedChannelParticipantsContext - if let currentContext = strongSelf.cachedChannelParticipantsContexts[peerId] { - context = currentContext - } else { - context = CachedChannelParticipantsContext() - strongSelf.cachedChannelParticipantsContexts[peerId] = context + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let context: CachedChannelParticipantsContext + if let currentContext = strongSelf.cachedChannelParticipantsContexts[peerId] { + context = currentContext + } else { + context = CachedChannelParticipantsContext() + strongSelf.cachedChannelParticipantsContexts[peerId] = context + } + + let viewId = OSAtomicIncrement32(&strongSelf.nextViewId) + let begin = forceImmediateUpdate || context.subscribers.isEmpty + let index = context.subscribers.add(viewId) + + if begin { + if let account = strongSelf.account { + let signal = (fetchAndUpdateCachedParticipants(peerId: peerId, network: account.network, postbox: account.postbox) |> then(Signal.complete() |> delay(10 * 60, queue: Queue.concurrentDefaultQueue()))) |> restart + context.disposable.set(signal.start()) } - - let viewId = OSAtomicIncrement32(&strongSelf.nextViewId) - let begin = forceImmediateUpdate || context.subscribers.isEmpty - let index = context.subscribers.add(viewId) - - if begin { - if let account = strongSelf.account { - let signal = (fetchAndUpdateCachedParticipants(peerId: peerId, network: account.network, postbox: account.postbox) |> then(Signal.complete() |> delay(10 * 60, queue: Queue.concurrentDefaultQueue()))) |> restart - context.disposable.set(signal.start()) - } - } - - disposable.set(ActionDisposable { - if let strongSelf = self { - if let currentContext = strongSelf.cachedChannelParticipantsContexts[peerId] { - currentContext.subscribers.remove(index) - currentContext.disposable.dispose() - if currentContext.subscribers.isEmpty { - strongSelf.cachedChannelParticipantsContexts.removeValue(forKey: peerId) - } + } + + disposable.set(ActionDisposable { + if let strongSelf = self { + if let currentContext = strongSelf.cachedChannelParticipantsContexts[peerId] { + currentContext.subscribers.remove(index) + currentContext.disposable.dispose() + if currentContext.subscribers.isEmpty { + strongSelf.cachedChannelParticipantsContexts.removeValue(forKey: peerId) } } - }) - } + } + }) } - return disposable } - return .never() - } else { - return .never() + return disposable } } } diff --git a/TelegramCore/AddPeerMember.swift b/TelegramCore/AddPeerMember.swift new file mode 100644 index 0000000000..904b6e9307 --- /dev/null +++ b/TelegramCore/AddPeerMember.swift @@ -0,0 +1,85 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public enum AddPeerMemberError { + case generic +} + +public func addPeerMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal { + return account.postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId), let memberPeer = modifier.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { + if let group = peer as? TelegramGroup { + return account.network.request(Api.functions.messages.addChatUser(chatId: group.id.id, userId: inputUser, fwdLimit: 100)) + |> mapError { error -> AddPeerMemberError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + return account.postbox.modify { modifier -> Void in + if let message = result.messages.first, let timestamp = message.timestamp { + modifier.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants { + var updatedParticipants = participants.participants + var found = false + for participant in participants.participants { + if participant.peerId == memberId { + found = true + break + } + } + if !found { + updatedParticipants.append(.member(id: memberId, invitedBy: account.peerId, invitedAt: timestamp)) + } + return CachedGroupData(participants: CachedGroupParticipants(participants: updatedParticipants, version: participants.version), exportedInvitation: cachedData.exportedInvitation, botInfos: cachedData.botInfos) + } else { + return cachedData + } + }) + } + } |> mapError { _ -> AddPeerMemberError in return .generic } + } + } else if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) { + return account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: [inputUser])) + |> mapError { error -> AddPeerMemberError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + return account.postbox.modify { modifier -> Void in + modifier.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedChannelData, let participants = cachedData.topParticipants { + var updatedParticipants = participants.participants + var found = false + for participant in participants.participants { + if participant.peerId == memberId { + found = true + break + } + } + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if !found { + updatedParticipants.insert(.member(id: memberId, invitedAt: timestamp), at: 0) + } + return cachedData.withUpdatedTopParticipants(CachedChannelParticipants(participants: updatedParticipants)) + } else { + return cachedData + } + }) + } |> mapError { _ -> AddPeerMemberError in return .generic } + } + } else { + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } |> mapError { _ -> AddPeerMemberError in return .generic } |> switchToLatest +} diff --git a/TelegramCore/Authorization.swift b/TelegramCore/Authorization.swift new file mode 100644 index 0000000000..5c759a3b7a --- /dev/null +++ b/TelegramCore/Authorization.swift @@ -0,0 +1,174 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public enum AuthorizationCodeRequestError { + case invalidPhoneNumber + case limitExceeded + case generic +} + +public func sendAuthorizationCode(account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String) -> Signal { + let sendCode = Api.functions.auth.sendCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil, apiId: apiId, apiHash: apiHash) + + let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false) + |> map { result in + return (result, account) + } |> `catch` { error -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in + switch error.errorDescription { + case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"): + let range = error.errorDescription.range(of: "MIGRATE_")! + let updatedMasterDatacenterId = Int32(error.errorDescription.substring(from: range.upperBound))! + let updatedAccount = account.changedMasterDatacenterId(updatedMasterDatacenterId) + return updatedAccount + |> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in + return updatedAccount.network.request(sendCode, automaticFloodWait: false) + |> map { sentCode in + return (sentCode, updatedAccount) + } + } + case _: + return .fail(error) + } + } + |> mapError { error -> AuthorizationCodeRequestError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "PHONE_NUMBER_INVALID" { + return .invalidPhoneNumber + } else { + return .generic + } + } + + return codeAndAccount + |> mapToSignal { (sentCode, account) -> Signal in + return account.postbox.modify { modifier -> UnauthorizedAccount in + switch sentCode { + case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + var parsedNextType: AuthorizationCodeNextType? + if let nextType = nextType { + parsedNextType = AuthorizationCodeNextType(apiType: nextType) + } + modifier.setState(UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))) + } + return account + } |> mapError { _ -> AuthorizationCodeRequestError in return .generic } + } +} + +public enum AuthorizationCodeVerificationError { + case invalidCode + case limitExceeded + case generic +} + +private enum AuthorizationCodeResult { + case Authorization(Api.auth.Authorization) + case Password(String) +} + +public func authorizeWithCode(account: UnauthorizedAccount, code: String) -> Signal { + return account.postbox.modify { modifier -> Signal in + if let state = modifier.getState() as? UnauthorizedAccountState { + switch state.contents { + case let .confirmationCodeEntry(number, _, hash, _, _): + return account.network.request(Api.functions.auth.signIn(phoneNumber: number, phoneCodeHash: hash, phoneCode: code), automaticFloodWait: false) |> map { authorization in + return AuthorizationCodeResult.Authorization(authorization) + } |> `catch` { error -> Signal in + switch (error.errorCode, error.errorDescription) { + case (401, "SESSION_PASSWORD_NEEDED"): + return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false) + |> mapError { error -> AuthorizationCodeVerificationError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + switch result { + case .noPassword: + return .fail(.generic) + case let .password(_, _, hint, _, _): + return .single(.Password(hint)) + } + } + case let (_, errorDescription): + if errorDescription.hasPrefix("FLOOD_WAIT") { + return .fail(.limitExceeded) + } else if errorDescription == "PHONE_CODE_INVALID" { + return .fail(.invalidCode) + } else { + return .fail(.generic) + } + } + } + |> mapToSignal { result -> Signal in + return account.postbox.modify { modifier -> Void in + switch result { + case let .Password(hint): + modifier.setState(UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint))) + case let .Authorization(authorization): + switch authorization { + case let .authorization(_, _, user): + let user = TelegramUser(user: user) + let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) + modifier.setState(state) + } + } + } |> mapError { _ -> AuthorizationCodeVerificationError in + return .generic + } + } + default: + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } + |> mapError { _ -> AuthorizationCodeVerificationError in + return .generic + } + |> switchToLatest +} + +public enum AuthorizationPasswordVerificationError { + case limitExceeded + case invalidPassword + case generic +} + +public func authorizeWithPassword(account: UnauthorizedAccount, password: String) -> Signal { + return verifyPassword(account, password: password) + |> `catch` { error -> Signal in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .fail(.limitExceeded) + } else if error.errorDescription == "PASSWORD_HASH_INVALID" { + return .fail(.invalidPassword) + } else { + return .fail(.generic) + } + } + |> mapToSignal { result -> Signal in + return account.postbox.modify { modifier -> Void in + switch result { + case let .authorization(_, _, user): + let user = TelegramUser(user: user) + let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil) + modifier.setState(state) + } + } + |> mapError { _ -> AuthorizationPasswordVerificationError in + return .generic + } + } +} diff --git a/TelegramCore/CachedChannelData.swift b/TelegramCore/CachedChannelData.swift index ff774920a8..13f0f2e461 100644 --- a/TelegramCore/CachedChannelData.swift +++ b/TelegramCore/CachedChannelData.swift @@ -106,6 +106,10 @@ public final class CachedChannelData: CachedPeerData { return CachedChannelData(flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, topParticipants: topParticipants) } + func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData { + return CachedChannelData(flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, topParticipants: self.topParticipants) + } + public init(decoder: Decoder) { self.flags = CachedChannelFlags(rawValue: decoder.decodeInt32ForKey("f")) self.about = decoder.decodeStringForKey("a") @@ -178,6 +182,10 @@ public final class CachedChannelData: CachedPeerData { return true } + + func withUpdatedAbout(_ about: String?) -> CachedChannelData { + return CachedChannelData(flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, topParticipants: self.topParticipants) + } } extension CachedChannelData { diff --git a/TelegramCore/CachedGroupParticipants.swift b/TelegramCore/CachedGroupParticipants.swift index 29f1394530..ef04a59129 100644 --- a/TelegramCore/CachedGroupParticipants.swift +++ b/TelegramCore/CachedGroupParticipants.swift @@ -74,6 +74,17 @@ public enum GroupParticipant: Coding, Equatable { } } } + + public var invitedBy: PeerId { + switch self { + case let .admin(_, invitedBy, _): + return invitedBy + case let .member(_, invitedBy, _): + return invitedBy + case let .creator(id): + return id + } + } } public final class CachedGroupParticipants: Coding, Equatable { diff --git a/TelegramCore/ChannelAdmins.swift b/TelegramCore/ChannelAdmins.swift new file mode 100644 index 0000000000..9e1bc27592 --- /dev/null +++ b/TelegramCore/ChannelAdmins.swift @@ -0,0 +1,53 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public struct RenderedChannelParticipant: Equatable { + public let participant: ChannelParticipant + public let peer: Peer + + public init(participant: ChannelParticipant, peer: Peer) { + self.participant = participant + self.peer = peer + } + + public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool { + return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer) + } +} + +public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedChannelParticipant], NoError> { + return account.postbox.modify { modifier -> Signal<[RenderedChannelParticipant], NoError> in + if let peer = modifier.getPeer(peerId), let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100)) + |> retryRequest + |> map { result -> [RenderedChannelParticipant] in + var items: [RenderedChannelParticipant] = [] + switch result { + case let .channelParticipants(_, participants, users): + var peers: [PeerId: Peer] = [:] + for user in users { + let peer = TelegramUser(user: user) + peers[peer.id] = peer + } + + for participant in CachedChannelParticipants(apiParticipants: participants).participants { + if let peer = peers[participant.peerId] { + items.append(RenderedChannelParticipant(participant: participant, peer: peer)) + } + } + } + return items + } + } else { + return .single([]) + } + } |> switchToLatest +} diff --git a/TelegramCore/Download.swift b/TelegramCore/Download.swift index 5eec0cc4a7..c4a7a7e8ba 100644 --- a/TelegramCore/Download.swift +++ b/TelegramCore/Download.swift @@ -9,7 +9,7 @@ import Foundation import SwiftSignalKit #endif -class Download { +class Download: NSObject, MTRequestMessageServiceDelegate { let datacenterId: Int let context: MTContext let mtProto: MTProto @@ -25,6 +25,10 @@ class Download { self.mtProto.requiredAuthToken = Int(datacenterId) as NSNumber } self.requestService = MTRequestMessageService(context: self.context) + + super.init() + + self.requestService.delegate = self self.mtProto.add(self.requestService) } @@ -33,6 +37,11 @@ class Download { self.mtProto.stop() } + func requestMessageServiceAuthorizationRequired(_ requestMessageService: MTRequestMessageService!) { + self.context.updateAuthTokenForDatacenter(withId: self.datacenterId, authToken: nil) + self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) + } + func uploadPart(fileId: Int64, index: Int, data: Data) -> Signal { return Signal { subscriber in let request = MTRequest() diff --git a/TelegramCore/EnqueueMessage.swift b/TelegramCore/EnqueueMessage.swift index 5373c31ce3..52e84d666c 100644 --- a/TelegramCore/EnqueueMessage.swift +++ b/TelegramCore/EnqueueMessage.swift @@ -53,16 +53,17 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt } } -public func enqueueMessages(account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> Signal { - return account.postbox.modify { modifier -> Void in - enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: messages) +public func enqueueMessages(account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> Signal<[MessageId?], NoError> { + return account.postbox.modify { modifier -> [MessageId?] in + return enqueueMessages(modifier: modifier, account: account, peerId: peerId, messages: messages) } } -func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messages: [EnqueueMessage]) { +func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> [MessageId?] { if let peer = modifier.getPeer(peerId) { var storeMessages: [StoreMessage] = [] let timestamp = Int32(account.network.context.globalTime()) + var globallyUniqueIds: [Int64] = [] for message in messages { var attributes: [MessageAttribute] = [] var flags = StoreMessageFlags() @@ -71,6 +72,7 @@ func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messa var randomId: Int64 = 0 arc4random_buf(&randomId, 8) attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId)) + globallyUniqueIds.append(randomId) switch message { case let .message(text, requestedAttributes, media, replyToMessageId): @@ -122,8 +124,15 @@ func enqueueMessages(modifier: Modifier, account: Account, peerId: PeerId, messa } } } + var messageIds: [MessageId?] = [] if !storeMessages.isEmpty { - modifier.addMessages(storeMessages, location: .Random) + let globallyUniqueIdToMessageId = modifier.addMessages(storeMessages, location: .Random) + for globallyUniqueId in globallyUniqueIds { + messageIds.append(globallyUniqueIdToMessageId[globallyUniqueId]) + } } + return messageIds + } else { + return [] } } diff --git a/TelegramCore/Log.swift b/TelegramCore/Log.swift index 8221332800..110731e24a 100644 --- a/TelegramCore/Log.swift +++ b/TelegramCore/Log.swift @@ -1,9 +1,10 @@ import Foundation import TelegramCorePrivateModule +import SwiftSignalKit private let queue = DispatchQueue(label: "org.telegram.Telegram.trace", qos: .utility) -public func trace(_ what: @autoclosure() -> String) { +public func trace2(_ what: @autoclosure() -> String) { let string = what() var rawTime = time_t() time(&rawTime) @@ -20,7 +21,7 @@ public func trace(_ what: @autoclosure() -> String) { //} } -public func trace(_ domain: String, what: @autoclosure() -> String) { +public func trace1(_ domain: String, what: @autoclosure() -> String) { let string = what() var rawTime = time_t() time(&rawTime) @@ -43,27 +44,57 @@ public func registerLoggingFunctions() { setBridgingTraceFunction({ domain, what in if let what = what { if let domain = domain { - trace(domain, what: what as String) + Logger.shared.log(domain, what as String) } else { - trace("", what: what as String) + Logger.shared.log("", what as String) } } }) } +private var sharedLogger: Logger? + public final class Logger { private let queue = DispatchQueue(label: "org.telegram.Telegram.log", qos: .utility) - private let maxLength: Int = 512 * 1024 + private let maxLength: Int = 2 * 1024 * 1024 + //private let maxLength: Int = 4 * 1024 private let maxFiles: Int = 20 private let basePath: String private var file: (Int32, Int)? - init(basePath: String) { + public static func setSharedLogger(_ logger: Logger) { + sharedLogger = logger + } + + public static var shared: Logger { + return sharedLogger! + } + + public init(basePath: String) { self.basePath = basePath } - func log(_ tag: String, _ what: @autoclosure () -> String) { + public func collectLogs() -> Signal<[(String, String)], NoError> { + return Signal { subscriber in + self.queue.async { + var result: [(String, String)] = [] + if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) { + for url in files { + if url.lastPathComponent.hasPrefix("log-") { + result.append((url.lastPathComponent, url.path)) + } + } + } + subscriber.putNext(result) + subscriber.putCompletion() + } + + return EmptyDisposable + } + } + + public func log(_ tag: String, _ what: @autoclosure () -> String) { let string = what() var rawTime = time_t() @@ -73,23 +104,35 @@ public final class Logger { var curTime = timeval() gettimeofday(&curTime, nil) - let seconds = curTime.tv_sec let milliseconds = curTime.tv_usec / 1000 - let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_yday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(seconds), Int(milliseconds), string]) + #if TARGET_IPHONE_SIMULATOR || DEBUG + let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_yday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string]) + + print(content) + #endif self.queue.async { + #if !(TARGET_IPHONE_SIMULATOR || DEBUG) + let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_yday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string]) + #endif + var fd: Int32? - var createNew = false + var openNew = false if let (file, length) = self.file { - if length < self.maxLength { + if length >= self.maxLength { close(file) - createNew = true + openNew = true } else { fd = file } } else { + openNew = true + } + if openNew { let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil) + + var createNew = false if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) { var minCreationDate: (Date, URL)? var maxCreationDate: (Date, URL)? @@ -125,14 +168,17 @@ public final class Logger { createNew = true } } - } - if createNew { - let path = self.basePath + "/log-\(Date()).txt" - let handle = open(path, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) - if handle >= 0 { - fd = handle - self.file = (handle, 0) + if createNew { + let fileName = String(format: "log-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_yday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)]) + + let path = self.basePath + "/" + fileName + + let handle = open(path, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) + if handle >= 0 { + fd = handle + self.file = (handle, 0) + } } } @@ -141,6 +187,8 @@ public final class Logger { data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in write(fd, bytes, data.count) } + var newline: UInt8 = 0x0a + write(fd, &newline, 1) if let file = self.file { self.file = (file.0, file.1 + data.count) } else { diff --git a/TelegramCore/LoggedOutAccountAttribute.swift b/TelegramCore/LoggedOutAccountAttribute.swift new file mode 100644 index 0000000000..c5a56a956d --- /dev/null +++ b/TelegramCore/LoggedOutAccountAttribute.swift @@ -0,0 +1,21 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public final class LoggedOutAccountAttribute: AccountRecordAttribute { + public init() { + } + + public init(decoder: Decoder) { + } + + public func encode(_ encoder: Encoder) { + } + + public func isEqual(to: AccountRecordAttribute) -> Bool { + return to is LoggedOutAccountAttribute + } +} diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index e31184b9c1..2743d41101 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -665,12 +665,12 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: } let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex) maybeKey = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) - print("sending message with index \(canonicalOperationIndex) key \(maybeKey?.fingerprint)") + Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(maybeKey?.fingerprint)") sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex) } guard let key = maybeKey else { - trace("SecretChat", what: "no valid key found") + Logger.shared.log("SecretChat", "no valid key found") return .single(nil) } diff --git a/TelegramCore/Network.swift b/TelegramCore/Network.swift index 4bafbb3578..52ba87aa6d 100644 --- a/TelegramCore/Network.swift +++ b/TelegramCore/Network.swift @@ -194,10 +194,10 @@ public class Network { self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in if let strongSelf = self { if value { - trace("Network", what: "Resume network connection") + Logger.shared.log("Network", "Resume network connection") strongSelf.mtProto.resume() } else { - trace("Network", what: "Pause network connection") + Logger.shared.log("Network", "Pause network connection") strongSelf.mtProto.pause() } } diff --git a/TelegramCore/NetworkLogging.m b/TelegramCore/NetworkLogging.m index 9af8076025..4405ba893d 100644 --- a/TelegramCore/NetworkLogging.m +++ b/TelegramCore/NetworkLogging.m @@ -16,7 +16,7 @@ void setBridgingTraceFunction(void (*f)(NSString *, NSString *)) { #if TARGET_IPHONE_SIMULATOR static bool loggingEnabled = true; #elif defined(DEBUG) -static bool loggingEnabled = false; +static bool loggingEnabled = true; #else static bool loggingEnabled = false; #endif diff --git a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift index 4cf7ccee8d..32d4828571 100644 --- a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift @@ -249,7 +249,7 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier: if let error = error as? MessageParsingError { switch error { case .contentParsingError: - print("Couldn't parse secret message payload") + Logger.shared.log("SecretChat", "Couldn't parse secret message payload") removeTagLocalIndices.append(entry.tagLocalIndex) return true case .unsupportedLayer: @@ -261,10 +261,10 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier: removeTagLocalIndices.append(entry.tagLocalIndex) return true case .holesInSequenceBasedLayer: - print("Found holes in incoming operation sequence") + Logger.shared.log("SecretChat", "Found holes in incoming operation sequence") return false case .secretChatCorruption: - print("Secret chat corrupted") + Logger.shared.log("SecretChat", "Secret chat corrupted") return false } } else { @@ -285,7 +285,7 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier: switch updatedState.embeddedState { case let .sequenceBasedLayer(sequenceState): let tagLocalIndex = max(0, sequenceState.outgoingOperationIndexFromCanonicalOperationIndex(maxAcknowledgedCanonicalOperationIndex) - 1) - //trace("SecretChat", what: "peer \(peerId) dropping acknowledged operations <= \(tagLocalIndex)") + //Logger.shared.log("SecretChat", "peer \(peerId) dropping acknowledged operations <= \(tagLocalIndex)") modifier.operationLogRemoveEntries(peerId: peerId, tag: OperationLogTags.SecretOutgoing, withTagLocalIndicesEqualToOrLowerThan: tagLocalIndex) default: break diff --git a/TelegramCore/ProcessSecretChatIncomingEncryptedOperations.swift b/TelegramCore/ProcessSecretChatIncomingEncryptedOperations.swift index a424aaaf31..5eb96425d9 100644 --- a/TelegramCore/ProcessSecretChatIncomingEncryptedOperations.swift +++ b/TelegramCore/ProcessSecretChatIncomingEncryptedOperations.swift @@ -112,18 +112,18 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe break } } - trace("SecretChat", what: "peerId \(peerId) malformed data after decryption") + Logger.shared.log("SecretChat", "peerId \(peerId) malformed data after decryption") } removeTagLocalIndices.append(entry.tagLocalIndex) }) } else { - trace("SecretChat", what: "peerId \(peerId) couldn't decrypt message content") + Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content") removeTagLocalIndices.append(entry.tagLocalIndex) } }) } else { - trace("SecretChat", what: "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist") + Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist") } } else { assertionFailure() diff --git a/TelegramCore/Random.swift b/TelegramCore/Random.swift index 05a9eb634f..20bf912990 100644 --- a/TelegramCore/Random.swift +++ b/TelegramCore/Random.swift @@ -1,6 +1,6 @@ import Foundation -func arc4random64() -> Int64 { +public func arc4random64() -> Int64 { var value: Int64 = 0 arc4random_buf(&value, 8) return value diff --git a/TelegramCore/RemovePeerMember.swift b/TelegramCore/RemovePeerMember.swift new file mode 100644 index 0000000000..232156655c --- /dev/null +++ b/TelegramCore/RemovePeerMember.swift @@ -0,0 +1,78 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public func removePeerMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal { + return account.postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId), let memberPeer = modifier.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { + if let group = peer as? TelegramGroup { + return account.network.request(Api.functions.messages.deleteChatUser(chatId: group.id.id, userId: inputUser)) + |> mapError { error -> Void in + return Void() + } + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.modify { modifier -> Void in + modifier.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants { + var updatedParticipants = participants.participants + for i in 0 ..< participants.participants.count { + if participants.participants[i].peerId == memberId { + updatedParticipants.remove(at: i) + break + } + } + return CachedGroupData(participants: CachedGroupParticipants(participants: updatedParticipants, version: participants.version), exportedInvitation: cachedData.exportedInvitation, botInfos: cachedData.botInfos) + } else { + return cachedData + } + }) + } + } + } else if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) { + return account.network.request(Api.functions.channels.kickFromChannel(channel: inputChannel, userId: inputUser, kicked: .boolTrue)) + |> mapError { error -> Void in + return Void() + } + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.modify { modifier -> Void in + modifier.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedChannelData, let participants = cachedData.topParticipants { + var updatedParticipants = participants.participants + for i in 0 ..< participants.participants.count { + if participants.participants[i].peerId == memberId { + updatedParticipants.remove(at: i) + break + } + } + return cachedData.withUpdatedTopParticipants(CachedChannelParticipants(participants: updatedParticipants)) + } else { + return cachedData + } + }) + } + } + } else { + return .complete() + } + } else { + return .complete() + } + } |> switchToLatest +} diff --git a/TelegramCore/RequestStartBot.swift b/TelegramCore/RequestStartBot.swift index 8ea56a3bde..50a31969d7 100644 --- a/TelegramCore/RequestStartBot.swift +++ b/TelegramCore/RequestStartBot.swift @@ -31,6 +31,8 @@ public func requestStartBot(account: Account, botPeerId: PeerId, payload: String } } } else { - return enqueueMessages(account: account, peerId: botPeerId, messages: [.message(text: "/start", attributes: [], media: nil, replyToMessageId: nil)]) + return enqueueMessages(account: account, peerId: botPeerId, messages: [.message(text: "/start", attributes: [], media: nil, replyToMessageId: nil)]) |> mapToSignal { _ -> Signal in + return .complete() + } } } diff --git a/TelegramCore/SupportPeerId.swift b/TelegramCore/SupportPeerId.swift index c495df4d3b..15bc7670c0 100644 --- a/TelegramCore/SupportPeerId.swift +++ b/TelegramCore/SupportPeerId.swift @@ -1,4 +1,3 @@ - #if os(macOS) import PostboxMac import SwiftSignalKitMac diff --git a/TelegramCore/TelegramChannel.swift b/TelegramCore/TelegramChannel.swift index a52ce36ee8..6a44b73504 100644 --- a/TelegramCore/TelegramChannel.swift +++ b/TelegramCore/TelegramChannel.swift @@ -269,4 +269,8 @@ public final class TelegramChannel: Peer { 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, role: self.role, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo) + } } diff --git a/TelegramCore/UpdateCachedPeerData.swift b/TelegramCore/UpdateCachedPeerData.swift index 1332a58ff1..ec1db43794 100644 --- a/TelegramCore/UpdateCachedPeerData.swift +++ b/TelegramCore/UpdateCachedPeerData.swift @@ -119,5 +119,4 @@ func fetchAndUpdateCachedPeerData(peerId: PeerId, network: Network, postbox: Pos return .complete() } } - return .never() } diff --git a/TelegramCore/UpdatePeerInfo.swift b/TelegramCore/UpdatePeerInfo.swift new file mode 100644 index 0000000000..d60cfef847 --- /dev/null +++ b/TelegramCore/UpdatePeerInfo.swift @@ -0,0 +1,92 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public enum UpdatePeerTitleError { + case generic +} + +public func updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal { + return account.postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId) { + if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.editTitle(channel: inputChannel, title: title)) + |> mapError { _ -> UpdatePeerTitleError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.modify { modifier -> Void in + if let apiChat = result.groups.first, let updatedPeer = parseTelegramGroupOrChannel(chat: apiChat) { + updatePeers(modifier: modifier, peers: [updatedPeer], update: { _, updated in + return updated + }) + } + } |> mapError { _ -> UpdatePeerTitleError in return .generic } + } + } else if let peer = peer as? TelegramGroup { + return account.network.request(Api.functions.messages.editChatTitle(chatId: peer.id.id, title: title)) + |> mapError { _ -> UpdatePeerTitleError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.modify { modifier -> Void in + if let apiChat = result.groups.first, let updatedPeer = parseTelegramGroupOrChannel(chat: apiChat) { + updatePeers(modifier: modifier, peers: [updatedPeer], update: { _, updated in + return updated + }) + } + } |> mapError { _ -> UpdatePeerTitleError in return .generic } + } + } else { + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } |> mapError { _ -> UpdatePeerTitleError in return .generic } |> switchToLatest +} + +public enum UpdatePeerDescriptionError { + case generic +} + +public func updatePeerDescription(account: Account, peerId: PeerId, description: String?) -> Signal { + return account.postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId) { + if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.editAbout(channel: inputChannel, about: description ?? "")) + |> mapError { _ -> UpdatePeerDescriptionError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.modify { modifier -> Void in + if case .boolTrue = result { + modifier.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedChannelData { + return current.withUpdatedAbout(description) + } else { + return current + } + }) + } + } |> mapError { _ -> UpdatePeerDescriptionError in return .generic } + } + } else { + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } |> mapError { _ -> UpdatePeerDescriptionError in return .generic } |> switchToLatest +} diff --git a/TelegramCore/UsernameAvailability.swift b/TelegramCore/UsernameAvailability.swift index ddb452fa48..30c08eb849 100644 --- a/TelegramCore/UsernameAvailability.swift +++ b/TelegramCore/UsernameAvailability.swift @@ -66,11 +66,14 @@ public func ==(lhs:UsernameAvailabilityState, rhs:UsernameAvailabilityState) -> } } +public enum AddressNameAvailabilityDomain { + case account + case peer(PeerId) +} -public func usernameAvailability(account:Account, def:String?, current:String) -> Signal { +public func addressNameAvailability(account: Account, domain: AddressNameAvailabilityDomain, def: String?, current: String) -> Signal { return Signal { subscriber in - let none = { () -> Disposable in subscriber.putNext(.none(username: current)) subscriber.putCompletion() @@ -117,11 +120,17 @@ public func usernameAvailability(account:Account, def:String?, current:String) - return fail(.short) } - subscriber.putNext(.progress(username: current)) let disposable:Disposable + switch domain { + case .account: + break + case let .peer(peerId): + break + } + let req = account.network.request(Api.functions.account.checkUsername(username: current)) |> delay(0.3, queue: Queue.concurrentDefaultQueue()) |> map {result in switch result { case .boolFalse: @@ -150,7 +159,7 @@ public func usernameAvailability(account:Account, def:String?, current:String) - } } -public func updateUsername(account:Account, username:String) -> Signal { +public func updateAddressName(account: Account, username: String) -> Signal { return account.network.request(Api.functions.account.updateUsername(username: username)) |> map { result in return TelegramUser(user: result) @@ -177,3 +186,54 @@ public func updateUsername(account:Account, username:String) -> Signal Signal { + return account.postbox.modify { modifier -> Signal in + if let peer = modifier.getPeer(peerId) as? TelegramChannel, let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.updateUsername(channel: inputChannel, username: username ?? "")) + |> mapError { _ -> UpdatePeerAddressNameError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.modify { modifier -> Void in + if case .boolTrue = result { + if let peer = modifier.getPeer(peerId) as? TelegramChannel { + updatePeers(modifier: modifier, peers: [peer.withUpdatedAddressName(username)], update: { _, updated in + return updated + }) + } + } + } |> mapError { _ -> UpdatePeerAddressNameError in return .generic } + } + } else { + return .fail(.generic) + } + } |> mapError { _ -> UpdatePeerAddressNameError in return .generic } |> switchToLatest +} + +public func adminedPublicChannels(account: Account) -> Signal<[Peer], NoError> { + return account.network.request(Api.functions.channels.getAdminedPublicChannels()) + |> retryRequest + |> map { result -> [Peer] in + var peers: [Peer] = [] + switch result { + case let .chats(apiChats): + for chat in apiChats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) + } + } + case let .chatsSlice(_, apiChats): + for chat in apiChats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) + } + } + } + return peers + } +}