diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 10b317bee8..a583776fc4 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -258,6 +258,10 @@ D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448CA41E29215A005A61A7 /* MediaResourceApiUtils.swift */; }; D0458C881E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */; }; D0458C891E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */; }; + D0467D0B20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */; }; + D0467D0C20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */; }; + D0467D1520D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */; }; + D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */; }; D048B4AC20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */; }; D048B4AD20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */; }; D049EAD51E43D98500A2CD3A /* RecentMediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAD41E43D98500A2CD3A /* RecentMediaItem.swift */; }; @@ -875,6 +879,8 @@ D0448CA11E291B14005A61A7 /* FetchSecretFileResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchSecretFileResource.swift; sourceTree = ""; }; D0448CA41E29215A005A61A7 /* MediaResourceApiUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResourceApiUtils.swift; sourceTree = ""; }; D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingContentInfoMessageAttribute.swift; sourceTree = ""; }; + D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeMarkAllUnseenPersonalMessagesOperation.swift; sourceTree = ""; }; + D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift; sourceTree = ""; }; D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedProxyInfoUpdates.swift; sourceTree = ""; }; D049EAD41E43D98500A2CD3A /* RecentMediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentMediaItem.swift; sourceTree = ""; }; D049EAD71E43DAD200A2CD3A /* ManagedRecentStickers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedRecentStickers.swift; sourceTree = ""; }; @@ -1419,6 +1425,8 @@ D0C26D7A1FE31DAC004ABF18 /* ManagedGroupFeedReadStateSyncOperations.swift */, D0380DB9204EF306000414AB /* MessageMediaPreuploadManager.swift */, D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */, + D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */, + D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */, ); name = State; sourceTree = ""; @@ -2089,6 +2097,7 @@ D03B0D5F1D631A6900955575 /* Serialization.swift in Sources */, D093D7F920641AA500BC3599 /* SecureIdEmailValue.swift in Sources */, D0C44B611FC616E200227BE0 /* SearchGroupMembers.swift in Sources */, + D0467D1520D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */, D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */, D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */, D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */, @@ -2267,6 +2276,7 @@ D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */, D0E412DC206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift in Sources */, + D0467D0B20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */, D0613FCA1E60440600202CDB /* InvitationLinks.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, @@ -2468,6 +2478,7 @@ D03C536A1DAD5CA9004C17B3 /* TelegramUser.swift in Sources */, D001F3EA1E128A1C007A8C60 /* TelegramPeerNotificationSettings.swift in Sources */, D0E412F5206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift in Sources */, + D0467D0C20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */, D0FA8BA81E1FA6DF001E855B /* TelegramSecretChat.swift in Sources */, C23BC3881E9BE3CB00D79F92 /* ImportContact.swift in Sources */, D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */, @@ -2689,6 +2700,7 @@ D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */, D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */, + D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */, D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */, D0B844361DAB91E0005F29E1 /* NBPhoneNumberUtil.m in Sources */, D0E305A81E5B5CBE00D7A3A2 /* PeerAdmins.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index cfd270842c..498cee13f3 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -260,6 +260,8 @@ private var declaredEncodables: Void = { declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) }) declareEncodable(CachedStickerQueryResult.self, f: { CachedStickerQueryResult(decoder: $0) }) declareEncodable(TelegramWallpaper.self, f: { TelegramWallpaper(decoder: $0) }) + declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) }) + declareEncodable(CachedRecentPeers.self, f: { CachedRecentPeers(decoder: $0) }) return }() @@ -780,6 +782,8 @@ public class Account { self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) + self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) + let importantBackgroundOperations: [Signal] = [ managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] }, self.pendingMessageManager.hasPendingMessages |> map { $0 ? AccountRunningImportantTasks.pendingMessages : [] } diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 5f62212b76..d1ea7fc81f 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -749,7 +749,13 @@ private func finalStateWithUpdatesAndServerTime(account: Account, state: Account if let message = StoreMessage(apiMessage: apiMessage) { if let previousState = updatedState.chatStates[message.id.peerId] as? ChannelState { if previousState.pts >= pts { - Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) skip old message \(message.id) (\(message.text))") + let messageText: String + if Logger.shared.redactSensitiveData { + messageText = "[[redacted]]" + } else { + messageText = message.text + } + Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) skip old message \(message.id) (\(messageText))") } else if previousState.pts + ptsCount == pts { if let preCachedResources = apiMessage.preCachedResources { for (resource, data) in preCachedResources { diff --git a/TelegramCore/AccountViewTracker.swift b/TelegramCore/AccountViewTracker.swift index ee7f2b0431..2b1c6c2f46 100644 --- a/TelegramCore/AccountViewTracker.swift +++ b/TelegramCore/AccountViewTracker.swift @@ -457,6 +457,26 @@ public final class AccountViewTracker { } } + public func updateMarkAllMentionsSeen(peerId: PeerId) { + self.queue.async { + guard let account = self.account else { + return + } + let _ = (account.postbox.transaction { transaction -> Set in + let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, tag: .unseenPersonalMessage).map({ $0.id })) + if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), summary.count > 0 { + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: 0, maxId: summary.range.maxId) + addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: transaction, peerId: peerId, maxId: summary.range.maxId) + } + + return ids + } + |> deliverOn(self.queue)).start(next: { [weak self] messageIds in + //self?.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) + }) + } + } + public func updateMarkMentionsSeenForMessageIds(messageIds: Set) { self.queue.async { var addedMessageIds: [MessageId] = [] diff --git a/TelegramCore/Api0.swift b/TelegramCore/Api0.swift index 856d278135..dd7927bd79 100644 --- a/TelegramCore/Api0.swift +++ b/TelegramCore/Api0.swift @@ -643,6 +643,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-74070332] = { return Api.InputPhoto.parse_inputPhoto($0) } dict[-567906571] = { return Api.contacts.TopPeers.parse_topPeersNotModified($0) } dict[1891070632] = { return Api.contacts.TopPeers.parse_topPeers($0) } + dict[-1255369827] = { return Api.contacts.TopPeers.parse_topPeersDisabled($0) } dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[-1073693790] = { return Api.auth.SentCodeType.parse_sentCodeTypeSms($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } diff --git a/TelegramCore/Api2.swift b/TelegramCore/Api2.swift index 321296c54c..20451921d8 100644 --- a/TelegramCore/Api2.swift +++ b/TelegramCore/Api2.swift @@ -1257,6 +1257,7 @@ struct contacts { enum TopPeers: TypeConstructorDescription { case topPeersNotModified case topPeers(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) + case topPeersDisabled func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1285,6 +1286,12 @@ struct contacts { for item in users { item.serialize(buffer, true) } + break + case .topPeersDisabled: + if boxed { + buffer.appendInt32(-1255369827) + } + break } } @@ -1295,6 +1302,8 @@ struct contacts { return ("topPeersNotModified", []) case .topPeers(let categories, let chats, let users): return ("topPeers", [("categories", categories), ("chats", chats), ("users", users)]) + case .topPeersDisabled: + return ("topPeersDisabled", []) } } @@ -1324,6 +1333,9 @@ struct contacts { return nil } } + static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersDisabled + } } } diff --git a/TelegramCore/Api3.swift b/TelegramCore/Api3.swift index 1ebacaeb69..75fa31f373 100644 --- a/TelegramCore/Api3.swift +++ b/TelegramCore/Api3.swift @@ -3892,6 +3892,20 @@ extension Api { return result }) } + + static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2062238246) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "contacts.toggleTopPeers", parameters: [("enabled", enabled)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } } struct help { static func getConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/TelegramCore/ApplyMaxReadIndexInteractively.swift b/TelegramCore/ApplyMaxReadIndexInteractively.swift index 6cee25e7d1..998a1e4881 100644 --- a/TelegramCore/ApplyMaxReadIndexInteractively.swift +++ b/TelegramCore/ApplyMaxReadIndexInteractively.swift @@ -114,7 +114,7 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message } } -public func togglePeerUnreadMarkInteractively(postbox: Postbox, peerId: PeerId) -> Signal { +public func togglePeerUnreadMarkInteractively(postbox: Postbox, viewTracker: AccountViewTracker, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in let namespace: MessageId.Namespace if peerId.namespace == Namespaces.Peer.SecretChat { @@ -129,6 +129,7 @@ public func togglePeerUnreadMarkInteractively(postbox: Postbox, peerId: PeerId) if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: namespace) { let _ = transaction.applyInteractiveReadMaxIndex(index) } + viewTracker.updateMarkAllMentionsSeen(peerId: peerId) } else { transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: true, interactive: true) } diff --git a/TelegramCore/Log.swift b/TelegramCore/Log.swift index 5d06266757..e85d5a232a 100644 --- a/TelegramCore/Log.swift +++ b/TelegramCore/Log.swift @@ -70,6 +70,7 @@ public final class Logger { public var logToFile: Bool = true public var logToConsole: Bool = true + public var redactSensitiveData: Bool = true public static func setSharedLogger(_ logger: Logger) { sharedLogger = logger diff --git a/TelegramCore/LoggingSettings.swift b/TelegramCore/LoggingSettings.swift index 0bde852c3b..b9d7676454 100644 --- a/TelegramCore/LoggingSettings.swift +++ b/TelegramCore/LoggingSettings.swift @@ -12,42 +12,42 @@ import Foundation public final class LoggingSettings: PreferencesEntry, Equatable { public let logToFile: Bool public let logToConsole: Bool + public let redactSensitiveData: Bool #if DEBUG - public static var defaultSettings = LoggingSettings(logToFile: true, logToConsole: true) + public static var defaultSettings = LoggingSettings(logToFile: true, logToConsole: true, redactSensitiveData: true) #else - public static var defaultSettings = LoggingSettings(logToFile: false, logToConsole: false) + public static var defaultSettings = LoggingSettings(logToFile: false, logToConsole: false, redactSensitiveData: true) #endif - public init(logToFile: Bool, logToConsole: Bool) { + public init(logToFile: Bool, logToConsole: Bool, redactSensitiveData: Bool) { self.logToFile = logToFile self.logToConsole = logToConsole + self.redactSensitiveData = redactSensitiveData } public init(decoder: PostboxDecoder) { self.logToFile = decoder.decodeInt32ForKey("logToFile", orElse: 0) != 0 self.logToConsole = decoder.decodeInt32ForKey("logToConsole", orElse: 0) != 0 + self.redactSensitiveData = decoder.decodeInt32ForKey("redactSensitiveData", orElse: 1) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.logToFile ? 1 : 0, forKey: "logToFile") encoder.encodeInt32(self.logToConsole ? 1 : 0, forKey: "logToConsole") + encoder.encodeInt32(self.redactSensitiveData ? 1 : 0, forKey: "redactSensitiveData") } public func withUpdatedLogToFile(_ logToFile: Bool) -> LoggingSettings { - return LoggingSettings(logToFile: logToFile, logToConsole: self.logToConsole) + return LoggingSettings(logToFile: logToFile, logToConsole: self.logToConsole, redactSensitiveData: self.redactSensitiveData) } public func withUpdatedLogToConsole(_ logToConsole: Bool) -> LoggingSettings { - return LoggingSettings(logToFile: self.logToFile, logToConsole: logToConsole) + return LoggingSettings(logToFile: self.logToFile, logToConsole: logToConsole, redactSensitiveData: self.redactSensitiveData) } - public func isEqual(to: PreferencesEntry) -> Bool { - guard let to = to as? LoggingSettings else { - return false - } - - return self == to + public func withUpdatedRedactSensitiveData(_ redactSensitiveData: Bool) -> LoggingSettings { + return LoggingSettings(logToFile: self.logToFile, logToConsole: self.logToConsole, redactSensitiveData: redactSensitiveData) } public static func ==(lhs: LoggingSettings, rhs: LoggingSettings) -> Bool { @@ -57,8 +57,19 @@ public final class LoggingSettings: PreferencesEntry, Equatable { if lhs.logToConsole != rhs.logToConsole { return false } + if lhs.redactSensitiveData != rhs.redactSensitiveData { + return false + } return true } + + public func isEqual(to: PreferencesEntry) -> Bool { + guard let to = to as? LoggingSettings else { + return false + } + + return self == to + } } public func updateLoggingSettings(postbox: Postbox, _ f: @escaping (LoggingSettings) -> LoggingSettings) -> Signal { @@ -77,6 +88,7 @@ public func updateLoggingSettings(postbox: Postbox, _ f: @escaping (LoggingSetti if let updated = updated { Logger.shared.logToFile = updated.logToFile Logger.shared.logToConsole = updated.logToConsole + Logger.shared.redactSensitiveData = updated.redactSensitiveData } } } diff --git a/TelegramCore/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/TelegramCore/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift new file mode 100644 index 0000000000..11ddb3ca8e --- /dev/null +++ b/TelegramCore/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -0,0 +1,186 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +import MtProtoKitMac +#else +import Postbox +import SwiftSignalKit +import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper { + var operationDisposables: [Int32: Disposable] = [:] + + func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) { + var disposeOperations: [Disposable] = [] + var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = [] + + var hasRunningOperationForPeerId = Set() + var validMergedIndices = Set() + for entry in entries { + if !hasRunningOperationForPeerId.contains(entry.peerId) { + hasRunningOperationForPeerId.insert(entry.peerId) + validMergedIndices.insert(entry.mergedIndex) + + if self.operationDisposables[entry.mergedIndex] == nil { + let disposable = MetaDisposable() + beginOperations.append((entry, disposable)) + self.operationDisposables[entry.mergedIndex] = disposable + } + } + } + + var removeMergedIndices: [Int32] = [] + for (mergedIndex, disposable) in self.operationDisposables { + if !validMergedIndices.contains(mergedIndex) { + removeMergedIndices.append(mergedIndex) + disposeOperations.append(disposable) + } + } + + for mergedIndex in removeMergedIndices { + self.operationDisposables.removeValue(forKey: mergedIndex) + } + + return (disposeOperations, beginOperations) + } + + func reset() -> [Disposable] { + let disposables = Array(self.operationDisposables.values) + self.operationDisposables.removeAll() + return disposables + } +} + +private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal) -> Signal { + return postbox.transaction { transaction -> Signal in + var result: PeerMergedOperationLogEntry? + transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in + if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeMarkAllUnseenPersonalMessagesOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(transaction, result) + } |> switchToLatest +} + +func managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal { + return Signal { _ in + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenPersonalMessages + + let helper = Atomic(value: ManagedSynchronizeMarkAllUnseenPersonalMessagesOperationsHelper()) + + let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in + let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in + return helper.update(view.entries) + } + + for disposable in disposeOperations { + disposable.dispose() + } + + for (entry, disposable) in beginOperations { + let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in + if let entry = entry { + if let operation = entry.contents as? SynchronizeMarkAllUnseenPersonalMessagesOperation { + return synchronizeMarkAllUnseen(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, peerId: entry.peerId, operation: operation) + } else { + assertionFailure() + } + } + return .complete() + }) + |> then(postbox.transaction { transaction -> Void in + let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) + }) + + disposable.set(signal.start()) + } + }) + + return ActionDisposable { + let disposables = helper.with { helper -> [Disposable] in + return helper.reset() + } + for disposable in disposables { + disposable.dispose() + } + disposable.dispose() + } + } +} + +private enum GetUnseenIdsError { + case done + case error(MTRpcError) +} + +private func synchronizeMarkAllUnseen(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, operation: SynchronizeMarkAllUnseenPersonalMessagesOperation) -> Signal { + guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else { + return .complete() + } + let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) + let oneOperation: Signal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: 0, addOffset: 0, limit: 100, maxId: 0, minId: 0)) + |> mapToSignal { result -> Signal<[MessageId], MTRpcError> in + switch result { + case let .messages(messages, _, _): + return .single(messages.compactMap({ $0.id })) + case let .channelMessages(channelMessages): + return .single(channelMessages.messages.compactMap({ $0.id })) + case .messagesNotModified: + return .single([]) + case let .messagesSlice(messagesSlice): + return .single(messagesSlice.messages.compactMap({ $0.id })) + } + } + |> mapToSignal { ids -> Signal in + if peerId.namespace == Namespaces.Peer.CloudChannel { + guard let inputChannel = inputChannel else { + return .single(true) + } + return network.request(Api.functions.channels.readMessageContents(channel: inputChannel, id: ids.map { $0.id })) + |> mapToSignal { result -> Signal in + return .single(true) + } + } else { + return network.request(Api.functions.messages.readMessageContents(id: ids.map { $0.id })) + |> mapToSignal { result -> Signal in + switch result { + case let .affectedMessages(pts, ptsCount): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + } + return .single(true) + } + } + } + let loopOperations: Signal = ( + (oneOperation + |> `catch` { error -> Signal in + return .fail(.error(error)) + } + ) + |> mapToSignal { result -> Signal in + if result { + return .fail(.done) + } else { + return .complete() + } + } + |> `catch` { error -> Signal in + switch error { + case .done, .error: + return .fail(error) + } + } + |> restart + ) + return loopOperations + |> `catch` { _ -> Signal in + return .complete() + } +} diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index 1b99c480f6..a5c325a27c 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -123,6 +123,7 @@ struct OperationLogTags { static let SynchronizeLocalizationUpdates = PeerOperationLogTag(value: 13) static let SynchronizeSavedStickers = PeerOperationLogTag(value: 14) static let SynchronizeGroupedPeers = PeerOperationLogTag(value: 15) + static let SynchronizeMarkAllUnseenPersonalMessages = PeerOperationLogTag(value: 16) } private enum PreferencesKeyValues: Int32 { diff --git a/TelegramCore/RecentPeers.swift b/TelegramCore/RecentPeers.swift index 9c605222b6..ae39f7f335 100644 --- a/TelegramCore/RecentPeers.swift +++ b/TelegramCore/RecentPeers.swift @@ -7,13 +7,86 @@ import Foundation import SwiftSignalKit #endif -public func recentPeers(account: Account) -> Signal<[Peer], NoError> { - let cachedPeers = account.postbox.recentPeers() - |> take(1) +public enum RecentPeers { + case peers([Peer]) + case disabled +} + +final class CachedRecentPeers: PostboxCoding { + let enabled: Bool + let ids: [PeerId] - let remotePeers = account.network.request(Api.functions.contacts.getTopPeers(flags: 1 << 0, offset: 0, limit: 16, hash: 0)) - |> retryRequest - |> map { result -> ([Peer], [PeerId: PeerPresence])? in + init(enabled: Bool, ids: [PeerId]) { + self.enabled = enabled + self.ids = ids + } + + init(decoder: PostboxDecoder) { + self.enabled = decoder.decodeInt32ForKey("enabled", orElse: 0) != 0 + self.ids = decoder.decodeInt64ArrayForKey("ids").map(PeerId.init) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.enabled ? 1 : 0, forKey: "enabled") + encoder.encodeInt64Array(self.ids.map({ $0.toInt64() }), forKey: "ids") + } + + static func cacheKey() -> ValueBoxKey { + let key = ValueBoxKey(length: 0) + return key + } +} + +private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 1, highWaterItemCount: 1) + +private func cachedRecentPeersEntryId() -> ItemCacheEntryId { + return ItemCacheEntryId(collectionId: 101, key: CachedRecentPeers.cacheKey()) +} + +public func recentPeers(account: Account) -> Signal { + let key = PostboxViewKey.cachedItem(cachedRecentPeersEntryId()) + return account.postbox.combinedView(keys: [key]) + |> mapToSignal { views -> Signal in + if let value = (views.views[key] as? CachedItemView)?.value as? CachedRecentPeers { + if value.enabled { + return account.postbox.multiplePeersView(value.ids) + |> map { view -> RecentPeers in + var peers: [Peer] = [] + for id in value.ids { + if let peer = view.peers[id], id != account.peerId { + peers.append(peer) + } + } + return .peers(peers) + } + } else { + return .single(.disabled) + } + } else { + return .single(.peers([])) + } + } +} + +public func managedUpdatedRecentPeers(postbox: Postbox, network: Network) -> Signal { + let key = PostboxViewKey.cachedItem(cachedRecentPeersEntryId()) + let peersEnabled = postbox.combinedView(keys: [key]) + |> map { views -> Bool in + if let value = (views.views[key] as? CachedItemView)?.value as? CachedRecentPeers { + return value.enabled + } else { + return true + } + } + |> distinctUntilChanged + + let updateOnce = + network.request(Api.functions.contacts.getTopPeers(flags: 1 << 0, offset: 0, limit: 16, hash: 0)) + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in switch result { case let .topPeers(_, _, users): var peers: [Peer] = [] @@ -25,39 +98,33 @@ public func recentPeers(account: Account) -> Signal<[Peer], NoError> { peerPresences[telegramUser.id] = presence } } - return (peers, peerPresences) - case .topPeersNotModified: - break - } - return ([], [:]) - } - - let updatedRemotePeers = remotePeers - |> mapToSignal { peersAndPresences -> Signal<[Peer], NoError> in - if let (peers, peerPresences) = peersAndPresences { - return account.postbox.transaction { transaction -> [Peer] in updatePeers(transaction: transaction, peers: peers, update: { return $1 }) transaction.updatePeerPresences(peerPresences) - transaction.replaceRecentPeerIds(peers.map({ $0.id })) - return peers - } - } else { - return .complete() + + transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: CachedRecentPeers(enabled: true, ids: peers.map { $0.id }), collectionSpec: collectionSpec) + case .topPeersNotModified: + break + case .topPeersDisabled: + transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: CachedRecentPeers(enabled: false, ids: []), collectionSpec: collectionSpec) } } - return cachedPeers - |> then(updatedRemotePeers |> filter({ !$0.isEmpty })) - |> map { peers -> [Peer] in - return peers.filter { $0.id != account.peerId } - } + } + + return peersEnabled |> mapToSignal { _ -> Signal in + return updateOnce + } } public func removeRecentPeer(account: Account, peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in - var peerIds = transaction.getRecentPeerIds() - if let index = peerIds.index(of: peerId) { - peerIds.remove(at: index) - transaction.replaceRecentPeerIds(peerIds) + guard let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId()) as? CachedRecentPeers else { + return .complete() + } + + if let index = entry.ids.index(of: peerId) { + var updatedIds = entry.ids + updatedIds.remove(at: index) + transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: CachedRecentPeers(enabled: entry.enabled, ids: updatedIds), collectionSpec: collectionSpec) } if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) { return account.network.request(Api.functions.contacts.resetTopPeerRating(category: .topPeerCategoryCorrespondents, peer: apiPeer)) @@ -73,11 +140,39 @@ public func removeRecentPeer(account: Account, peerId: PeerId) -> Signal switchToLatest } +public func updateRecentPeersEnabled(postbox: Postbox, network: Network, enabled: Bool) -> Signal { + return postbox.transaction { transaction -> Signal in + var currentValue = true + if let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId()) as? CachedRecentPeers { + currentValue = entry.enabled + if !enabled { + transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: CachedRecentPeers(enabled: false, ids: []), collectionSpec: collectionSpec) + } else { + transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: CachedRecentPeers(enabled: true, ids: entry.ids), collectionSpec: collectionSpec) + } + } + + if currentValue == enabled { + return .complete() + } + + return network.request(Api.functions.contacts.toggleTopPeers(enabled: currentValue ? .boolTrue : .boolFalse)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } |> switchToLatest +} + public func managedRecentlyUsedInlineBots(postbox: Postbox, network: Network) -> Signal { let remotePeers = network.request(Api.functions.contacts.getTopPeers(flags: 1 << 2, offset: 0, limit: 16, hash: 0)) |> retryRequest |> map { result -> ([Peer], [PeerId: PeerPresence], [(PeerId, Double)])? in switch result { + case .topPeersDisabled: + break case let .topPeers(categories, _, users): var peers: [Peer] = [] var peerPresences: [PeerId: PeerPresence] = [:] diff --git a/TelegramCore/Serialization.swift b/TelegramCore/Serialization.swift index eec8699ca8..c4cfb15755 100644 --- a/TelegramCore/Serialization.swift +++ b/TelegramCore/Serialization.swift @@ -48,10 +48,10 @@ func apiFunctionDescription(of desc: FunctionDescription) -> String { redactParam = redactParams.contains(param.0) } - if redactParam { + if redactParam, Logger.shared.redactSensitiveData { result.append("[[redacted]]") } else { - result.append(recursiveDescription(redact: true, of: param.1)) + result.append(recursiveDescription(redact: Logger.shared.redactSensitiveData, of: param.1)) } } result.append(")") @@ -195,13 +195,7 @@ public class BoxedMessage: NSObject { override public var description: String { get { - let redact: Bool - #if DEBUG - redact = true - #else - redact = true - #endif - return recursiveDescription(redact: redact, of: self.body) + return recursiveDescription(redact: Logger.shared.redactSensitiveData, of: self.body) } } } diff --git a/TelegramCore/SynchronizeMarkAllUnseenPersonalMessagesOperation.swift b/TelegramCore/SynchronizeMarkAllUnseenPersonalMessagesOperation.swift new file mode 100644 index 0000000000..1fa91c7a00 --- /dev/null +++ b/TelegramCore/SynchronizeMarkAllUnseenPersonalMessagesOperation.swift @@ -0,0 +1,47 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +#else +import Postbox +import SwiftSignalKit +#endif + +final class SynchronizeMarkAllUnseenPersonalMessagesOperation: PostboxCoding { + let maxId: MessageId.Id + + init(maxId: MessageId.Id) { + self.maxId = maxId + } + + init(decoder: PostboxDecoder) { + self.maxId = decoder.decodeInt32ForKey("maxId", orElse: Int32.min + 1) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.maxId, forKey: "maxId") + } +} + +func addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: Transaction, peerId: PeerId, maxId: MessageId.Id) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeMarkAllUnseenPersonalMessages + + var topLocalIndex: Int32? + var currentMaxId: MessageId.Id? + transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in + topLocalIndex = entry.tagLocalIndex + if let operation = entry.contents as? SynchronizeMarkAllUnseenPersonalMessagesOperation { + currentMaxId = operation.maxId + } + return false + }) + + if let topLocalIndex = topLocalIndex { + if let currentMaxId = currentMaxId, currentMaxId >= maxId { + return + } + let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex) + } + + transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeMarkAllUnseenPersonalMessagesOperation(maxId: maxId)) +}