no message

This commit is contained in:
Peter
2017-02-19 23:20:10 +03:00
parent 00ad214144
commit b076cf4dd1
29 changed files with 1110 additions and 257 deletions

View File

@@ -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 = "<group>"; };
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 = "<group>"; };
D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutAccountAttribute.swift; sourceTree = "<group>"; };
D041E3F41E535464008C24B4 /* AddPeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddPeerMember.swift; sourceTree = "<group>"; };
D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePeerMember.swift; sourceTree = "<group>"; };
D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessSecretChatIncomingDecryptedOperations.swift; sourceTree = "<group>"; };
D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatEncryption.swift; sourceTree = "<group>"; };
D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer46.swift; sourceTree = "<group>"; };
@@ -477,6 +494,8 @@
D049EAF41E44DF3300A2CD3A /* AccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountState.swift; sourceTree = "<group>"; };
D050F20F1E48AB0600988324 /* InteractivePhoneFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractivePhoneFormatter.swift; sourceTree = "<group>"; };
D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = "<group>"; };
D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = "<group>"; };
D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
@@ -556,6 +575,8 @@
D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizePinnedChatsOperations.swift; sourceTree = "<group>"; };
D0BC38781E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePinnedChatsOperation.swift; sourceTree = "<group>"; };
D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TogglePeerChatPinned.swift; sourceTree = "<group>"; };
D0BEAF5C1E54941B00BD963D /* Authorization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; };
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
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 = "<group>"; };
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestChatContextResults.swift; sourceTree = "<group>"; };
D0DC354F1DE36900000195EB /* ChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatContextResult.swift; sourceTree = "<group>"; };
@@ -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 = "<group>";
@@ -904,6 +928,14 @@
name = "Supporting Files";
sourceTree = "<group>";
};
D03E5E0A1E55E0220029569A /* Accounts */ = {
isa = PBXGroup;
children = (
D03E5E0B1E55E02D0029569A /* LoggedOutAccountAttribute.swift */,
);
name = Accounts;
sourceTree = "<group>";
};
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 = "<group>";
@@ -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 */,

View File

@@ -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<Either<UnauthorizedAccount, Account>, NoError> {
public func accountWithId(_ id: AccountRecordId, appGroupPath: String, testingEnvironment: Bool) -> Signal<Either<UnauthorizedAccount, Account>, 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<UnauthorizedAccount, Account> 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<UnauthorizedAccount, Account> 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<UnauthorizedAccount, Account> 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<Void, NoError> 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()
}
}

View File

@@ -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
}

View File

@@ -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<Either<UnauthorizedAccount, Account>?, NoError> {
return manager.allocatedCurrentAccountId()
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs == rhs
})
|> mapToSignal { id -> Signal<Either<UnauthorizedAccount, Account>?, NoError> in
if let id = id {
let reload = ValuePromise<Bool>(true, ignoreRepeated: false)
return reload.get() |> mapToSignal { _ -> Signal<Either<UnauthorizedAccount, Account>?, NoError> in
return accountWithId(id, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment)
|> mapToSignal { account -> Signal<Either<UnauthorizedAccount, Account>?, 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<Void, NoError> {
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<Void, NoError> {
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<Void, NoError> {
return accountWithId(id, appGroupPath: appGroupPath, testingEnvironment: false)
|> mapToSignal { account -> Signal<Void, NoError> 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<Api.Bool?, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
account.shouldBeServiceTaskMaster.set(.single(.never))
return accountManager.modify { modifier -> Void in
modifier.updateRecord(id, { _ in
return nil
})
}
}
}
}
}

View File

@@ -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

View File

@@ -14,7 +14,7 @@ private enum AccountStateManagerOperation {
case collectUpdateGroups([UpdateGroup], Double)
case processUpdateGroups([UpdateGroup])
case custom(Int32, Signal<Void, NoError>)
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,46 +409,12 @@ 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 {
let (message, notify) = messageForNotification(modifier: modifier, id: id, alwaysReturnMessage: false)
if let message = message, notify {
messages.append(message)
}
} else {
trace("AccountStateManager", what: "notification message doesn't exist")
}
}
}
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] {
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, subscribers)
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<Void, NoError> {
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)
}
}

View File

@@ -322,7 +322,6 @@ public final class AccountViewTracker {
}
public func updatedCachedChannelParticipants(_ peerId: PeerId, forceImmediateUpdate: Bool = false) -> Signal<Void, NoError> {
if let account = self.account {
let queue = self.queue
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
@@ -362,9 +361,5 @@ public final class AccountViewTracker {
}
return disposable
}
return .never()
} else {
return .never()
}
}
}

View File

@@ -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<Void, AddPeerMemberError> {
return account.postbox.modify { modifier -> Signal<Void, AddPeerMemberError> 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<Void, AddPeerMemberError> 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<Void, AddPeerMemberError> 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
}

View File

@@ -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<UnauthorizedAccount, AuthorizationCodeRequestError> {
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<UnauthorizedAccount, AuthorizationCodeRequestError> 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<Void, AuthorizationCodeVerificationError> {
return account.postbox.modify { modifier -> Signal<Void, AuthorizationCodeVerificationError> 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<AuthorizationCodeResult, AuthorizationCodeVerificationError> 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<AuthorizationCodeResult, AuthorizationCodeVerificationError> 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<Void, AuthorizationCodeVerificationError> 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<Void, AuthorizationPasswordVerificationError> {
return verifyPassword(account, password: password)
|> `catch` { error -> Signal<Api.auth.Authorization, AuthorizationPasswordVerificationError> 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<Void, AuthorizationPasswordVerificationError> 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
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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<Void, NoError> {
return Signal<Void, MTRpcError> { subscriber in
let request = MTRequest()

View File

@@ -53,16 +53,17 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt
}
}
public func enqueueMessages(account: Account, peerId: PeerId, messages: [EnqueueMessage]) -> Signal<Void, NoError> {
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 []
}
}

View File

@@ -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,9 +168,11 @@ public final class Logger {
createNew = true
}
}
}
if createNew {
let path = self.basePath + "/log-\(Date()).txt"
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 {
@@ -135,12 +180,15 @@ public final class Logger {
self.file = (handle, 0)
}
}
}
if let fd = fd {
if let data = content.data(using: .utf8) {
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> 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 {

View File

@@ -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
}
}

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -1,6 +1,6 @@
import Foundation
func arc4random64() -> Int64 {
public func arc4random64() -> Int64 {
var value: Int64 = 0
arc4random_buf(&value, 8)
return value

View File

@@ -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<Void, NoError> {
return account.postbox.modify { modifier -> Signal<Void, NoError> 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<Api.Updates, NoError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, NoError> 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<Api.Updates, NoError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, NoError> 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
}

View File

@@ -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<Void, NoError> in
return .complete()
}
}
}

View File

@@ -1,4 +1,3 @@
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac

View File

@@ -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)
}
}

View File

@@ -119,5 +119,4 @@ func fetchAndUpdateCachedPeerData(peerId: PeerId, network: Network, postbox: Pos
return .complete()
}
}
return .never()
}

View File

@@ -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<Void, UpdatePeerTitleError> {
return account.postbox.modify { modifier -> Signal<Void, UpdatePeerTitleError> 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<Void, UpdatePeerTitleError> 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<Void, UpdatePeerTitleError> 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<Void, UpdatePeerDescriptionError> {
return account.postbox.modify { modifier -> Signal<Void, UpdatePeerDescriptionError> 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<Void, UpdatePeerDescriptionError> 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
}

View File

@@ -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<UsernameAvailabilityState,Void> {
public func addressNameAvailability(account: Account, domain: AddressNameAvailabilityDomain, def: String?, current: String) -> Signal<UsernameAvailabilityState, NoError> {
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<Bool,Void> {
public func updateAddressName(account: Account, username: String) -> Signal<Bool,Void> {
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<Bool,Void
}
public enum UpdatePeerAddressNameError {
case generic
}
public func updatePeerAddressName(account: Account, peerId: PeerId, username: String?) -> Signal<Void, UpdatePeerAddressNameError> {
return account.postbox.modify { modifier -> Signal<Void, UpdatePeerAddressNameError> 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<Void, UpdatePeerAddressNameError> 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
}
}