import Foundation import Postbox import SwiftSignalKit import TelegramApi import MtProtoKit private enum AccountKind { case authorized case unauthorized } public struct AccountSupportUserInfo: Codable, Equatable { public init() { } } public enum TelegramAccountRecordAttribute: AccountRecordAttribute, Equatable { enum CodingKeys: String, CodingKey { case backupData case environment case sortOrder case loggedOut case supportUserInfo case legacyRootObject = "_" } case backupData(AccountBackupDataAttribute) case environment(AccountEnvironmentAttribute) case sortOrder(AccountSortOrderAttribute) case loggedOut(LoggedOutAccountAttribute) case supportUserInfo(AccountSupportUserInfo) public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let backupData = try? container.decodeIfPresent(AccountBackupDataAttribute.self, forKey: .backupData) { self = .backupData(backupData) } else if let environment = try? container.decodeIfPresent(AccountEnvironmentAttribute.self, forKey: .environment) { self = .environment(environment) } else if let sortOrder = try? container.decodeIfPresent(AccountSortOrderAttribute.self, forKey: .sortOrder) { self = .sortOrder(sortOrder) } else if let loggedOut = try? container.decodeIfPresent(LoggedOutAccountAttribute.self, forKey: .loggedOut) { self = .loggedOut(loggedOut) } else if let supportUserInfo = try? container.decodeIfPresent(AccountSupportUserInfo.self, forKey: .supportUserInfo) { self = .supportUserInfo(supportUserInfo) } else { let legacyRootObjectData = try! container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .legacyRootObject) if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountBackupDataAttribute.self) { self = .backupData(try! AdaptedPostboxDecoder().decode(AccountBackupDataAttribute.self, from: legacyRootObjectData.data)) } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountEnvironmentAttribute.self) { self = .environment(try! AdaptedPostboxDecoder().decode(AccountEnvironmentAttribute.self, from: legacyRootObjectData.data)) } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountSortOrderAttribute.self) { self = .sortOrder(try! AdaptedPostboxDecoder().decode(AccountSortOrderAttribute.self, from: legacyRootObjectData.data)) } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(LoggedOutAccountAttribute.self) { self = .loggedOut(try! AdaptedPostboxDecoder().decode(LoggedOutAccountAttribute.self, from: legacyRootObjectData.data)) } else { preconditionFailure() } } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case let .backupData(backupData): try container.encode(backupData, forKey: .backupData) case let .environment(environment): try container.encode(environment, forKey: .environment) case let .sortOrder(sortOrder): try container.encode(sortOrder, forKey: .sortOrder) case let .loggedOut(loggedOut): try container.encode(loggedOut, forKey: .loggedOut) case let .supportUserInfo(supportUserInfo): try container.encode(supportUserInfo, forKey: .supportUserInfo) } } public func isEqual(to: AccountRecordAttribute) -> Bool { return self == to as? TelegramAccountRecordAttribute } } public final class TelegramAccountManagerTypes: AccountManagerTypes { public typealias Attribute = TelegramAccountRecordAttribute } private var declaredEncodables: Void = { declareEncodable(UnauthorizedAccountState.self, f: { UnauthorizedAccountState(decoder: $0) }) declareEncodable(AuthorizedAccountState.self, f: { AuthorizedAccountState(decoder: $0) }) declareEncodable(TelegramUser.self, f: { TelegramUser(decoder: $0) }) declareEncodable(TelegramGroup.self, f: { TelegramGroup(decoder: $0) }) declareEncodable(TelegramChannel.self, f: { TelegramChannel(decoder: $0) }) declareEncodable(TelegramMediaImage.self, f: { TelegramMediaImage(decoder: $0) }) declareEncodable(TelegramMediaImageRepresentation.self, f: { TelegramMediaImageRepresentation(decoder: $0) }) declareEncodable(TelegramMediaContact.self, f: { TelegramMediaContact(decoder: $0) }) declareEncodable(TelegramMediaMap.self, f: { TelegramMediaMap(decoder: $0) }) declareEncodable(TelegramMediaFile.self, f: { TelegramMediaFile(decoder: $0) }) declareEncodable(TelegramMediaFileAttribute.self, f: { TelegramMediaFileAttribute(decoder: $0) }) declareEncodable(CloudFileMediaResource.self, f: { CloudFileMediaResource(decoder: $0) }) declareEncodable(ChannelState.self, f: { ChannelState(decoder: $0) }) declareEncodable(RegularChatState.self, f: { RegularChatState(decoder: $0) }) declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) }) declareEncodable(InlineBusinessBotMessageAttribute.self, f: { InlineBusinessBotMessageAttribute(decoder: $0) }) declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) }) declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) }) declareEncodable(QuotedReplyMessageAttribute.self, f: { QuotedReplyMessageAttribute(decoder: $0) }) declareEncodable(ReplyStoryAttribute.self, f: { ReplyStoryAttribute(decoder: $0) }) declareEncodable(ReplyThreadMessageAttribute.self, f: { ReplyThreadMessageAttribute(decoder: $0) }) declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) }) declareEncodable(PendingReactionsMessageAttribute.self, f: { PendingReactionsMessageAttribute(decoder: $0) }) declareEncodable(PendingStarsReactionsMessageAttribute.self, f: { PendingStarsReactionsMessageAttribute(decoder: $0) }) declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) }) declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) }) declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) }) declareEncodable(ForwardCountMessageAttribute.self, f: { ForwardCountMessageAttribute(decoder: $0) }) declareEncodable(BoostCountMessageAttribute.self, f: { BoostCountMessageAttribute(decoder: $0) }) declareEncodable(NotificationInfoMessageAttribute.self, f: { NotificationInfoMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaAction.self, f: { TelegramMediaAction(decoder: $0) }) declareEncodable(TelegramPeerNotificationSettings.self, f: { TelegramPeerNotificationSettings(decoder: $0) }) declareEncodable(CachedUserData.self, f: { CachedUserData(decoder: $0) }) declareEncodable(BotInfo.self, f: { BotInfo(decoder: $0) }) declareEncodable(CachedGroupData.self, f: { CachedGroupData(decoder: $0) }) declareEncodable(CachedChannelData.self, f: { CachedChannelData(decoder: $0) }) declareEncodable(TelegramUserPresence.self, f: { TelegramUserPresence(decoder: $0) }) declareEncodable(LocalFileMediaResource.self, f: { LocalFileMediaResource(decoder: $0) }) declareEncodable(StickerPackCollectionInfo.self, f: { StickerPackCollectionInfo(decoder: $0) }) declareEncodable(StickerPackItem.self, f: { StickerPackItem(decoder: $0) }) declareEncodable(LocalFileReferenceMediaResource.self, f: { LocalFileReferenceMediaResource(decoder: $0) }) declareEncodable(OutgoingMessageInfoAttribute.self, f: { OutgoingMessageInfoAttribute(decoder: $0) }) declareEncodable(ForwardSourceInfoAttribute.self, f: { ForwardSourceInfoAttribute(decoder: $0) }) declareEncodable(SourceReferenceMessageAttribute.self, f: { SourceReferenceMessageAttribute(decoder: $0) }) declareEncodable(SourceAuthorInfoMessageAttribute.self, f: { SourceAuthorInfoMessageAttribute(decoder: $0) }) declareEncodable(EditedMessageAttribute.self, f: { EditedMessageAttribute(decoder: $0) }) declareEncodable(ReplyMarkupMessageAttribute.self, f: { ReplyMarkupMessageAttribute(decoder: $0) }) declareEncodable(OutgoingChatContextResultMessageAttribute.self, f: { OutgoingChatContextResultMessageAttribute(decoder: $0) }) declareEncodable(HttpReferenceMediaResource.self, f: { HttpReferenceMediaResource(decoder: $0) }) declareEncodable(WebFileReferenceMediaResource.self, f: { WebFileReferenceMediaResource(decoder: $0) }) declareEncodable(EmptyMediaResource.self, f: { EmptyMediaResource(decoder: $0) }) declareEncodable(TelegramSecretChat.self, f: { TelegramSecretChat(decoder: $0) }) declareEncodable(SecretChatState.self, f: { SecretChatState(decoder: $0) }) declareEncodable(SecretChatIncomingEncryptedOperation.self, f: { SecretChatIncomingEncryptedOperation(decoder: $0) }) declareEncodable(SecretChatIncomingDecryptedOperation.self, f: { SecretChatIncomingDecryptedOperation(decoder: $0) }) declareEncodable(SecretChatOutgoingOperation.self, f: { SecretChatOutgoingOperation(decoder: $0) }) declareEncodable(SecretFileMediaResource.self, f: { SecretFileMediaResource(decoder: $0) }) declareEncodable(CloudChatRemoveMessagesOperation.self, f: { CloudChatRemoveMessagesOperation(decoder: $0) }) declareEncodable(AutoremoveTimeoutMessageAttribute.self, f: { AutoremoveTimeoutMessageAttribute(decoder: $0) }) declareEncodable(AutoclearTimeoutMessageAttribute.self, f: { AutoclearTimeoutMessageAttribute(decoder: $0) }) declareEncodable(CloudChatRemoveChatOperation.self, f: { CloudChatRemoveChatOperation(decoder: $0) }) declareEncodable(SynchronizePinnedChatsOperation.self, f: { SynchronizePinnedChatsOperation(decoder: $0) }) declareEncodable(SynchronizeConsumeMessageContentsOperation.self, f: { SynchronizeConsumeMessageContentsOperation(decoder: $0) }) declareEncodable(CloudChatClearHistoryOperation.self, f: { CloudChatClearHistoryOperation(decoder: $0) }) declareEncodable(OutgoingContentInfoMessageAttribute.self, f: { OutgoingContentInfoMessageAttribute(decoder: $0) }) declareEncodable(ConsumableContentMessageAttribute.self, f: { ConsumableContentMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaGame.self, f: { TelegramMediaGame(decoder: $0) }) declareEncodable(TelegramMediaInvoice.self, f: { TelegramMediaInvoice(decoder: $0) }) declareEncodable(TelegramMediaWebFile.self, f: { TelegramMediaWebFile(decoder: $0) }) declareEncodable(SynchronizeInstalledStickerPacksOperation.self, f: { SynchronizeInstalledStickerPacksOperation(decoder: $0) }) declareEncodable(SynchronizeMarkFeaturedStickerPacksAsSeenOperation.self, f: { SynchronizeMarkFeaturedStickerPacksAsSeenOperation(decoder: $0) }) declareEncodable(SynchronizeChatInputStateOperation.self, f: { SynchronizeChatInputStateOperation(decoder: $0) }) declareEncodable(SynchronizeSavedGifsOperation.self, f: { SynchronizeSavedGifsOperation(decoder: $0) }) declareEncodable(SynchronizeSavedStickersOperation.self, f: { SynchronizeSavedStickersOperation(decoder: $0) }) declareEncodable(SynchronizeRecentlyUsedMediaOperation.self, f: { SynchronizeRecentlyUsedMediaOperation(decoder: $0) }) declareEncodable(SynchronizeLocalizationUpdatesOperation.self, f: { SynchronizeLocalizationUpdatesOperation(decoder: $0) }) declareEncodable(ChannelMessageStateVersionAttribute.self, f: { ChannelMessageStateVersionAttribute(decoder: $0) }) declareEncodable(PeerGroupMessageStateVersionAttribute.self, f: { PeerGroupMessageStateVersionAttribute(decoder: $0) }) declareEncodable(CachedSecretChatData.self, f: { CachedSecretChatData(decoder: $0) }) declareEncodable(AuthorSignatureMessageAttribute.self, f: { AuthorSignatureMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaExpiredContent.self, f: { TelegramMediaExpiredContent(decoder: $0) }) declareEncodable(ConsumablePersonalMentionMessageAttribute.self, f: { ConsumablePersonalMentionMessageAttribute(decoder: $0) }) declareEncodable(ConsumePersonalMessageAction.self, f: { ConsumePersonalMessageAction(decoder: $0) }) declareEncodable(ReadReactionAction.self, f: { ReadReactionAction(decoder: $0) }) declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) }) declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) }) declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) }) declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) }) declareEncodable(SynchronizeMarkAllUnseenReactionsOperation.self, f: { SynchronizeMarkAllUnseenReactionsOperation(decoder: $0) }) declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) }) declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) }) declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) }) declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) }) declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) }) declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) }) declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) }) declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) }) declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) }) declareEncodable(CloudStickerPackThumbnailMediaResource.self, f: { CloudStickerPackThumbnailMediaResource(decoder: $0) }) declareEncodable(ContentRequiresValidationMessageAttribute.self, f: { ContentRequiresValidationMessageAttribute(decoder: $0) }) declareEncodable(PendingProcessingMessageAttribute.self, f: { PendingProcessingMessageAttribute(decoder: $0) }) declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) }) declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) }) declareEncodable(SendStarsReactionsAction.self, f: { SendStarsReactionsAction(decoder: $0) }) declareEncodable(PostponeSendPaidMessageAction.self, f: { PostponeSendPaidMessageAction(decoder: $0) }) declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) }) declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) }) declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaWebpageAttribute.self, f: { TelegramMediaWebpageAttribute(decoder: $0) }) declareEncodable(TelegramMediaDice.self, f: { TelegramMediaDice(decoder: $0) }) declareEncodable(SynchronizeChatListFiltersOperation.self, f: { SynchronizeChatListFiltersOperation(decoder: $0) }) declareEncodable(PromoChatListItem.self, f: { PromoChatListItem(decoder: $0) }) declareEncodable(TelegramMediaFile.VideoThumbnail.self, f: { TelegramMediaFile.VideoThumbnail(decoder: $0) }) declareEncodable(PeerAccessRestrictionInfo.self, f: { PeerAccessRestrictionInfo(decoder: $0) }) declareEncodable(TelegramMediaImage.VideoRepresentation.self, f: { TelegramMediaImage.VideoRepresentation(decoder: $0) }) declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) }) declareEncodable(EmojiSearchQueryMessageAttribute.self, f: { EmojiSearchQueryMessageAttribute(decoder: $0) }) declareEncodable(WallpaperDataResource.self, f: { WallpaperDataResource(decoder: $0) }) declareEncodable(ForwardOptionsMessageAttribute.self, f: { ForwardOptionsMessageAttribute(decoder: $0) }) declareEncodable(SendAsMessageAttribute.self, f: { SendAsMessageAttribute(decoder: $0) }) declareEncodable(ForwardVideoTimestampAttribute.self, f: { ForwardVideoTimestampAttribute(decoder: $0) }) declareEncodable(AudioTranscriptionMessageAttribute.self, f: { AudioTranscriptionMessageAttribute(decoder: $0) }) declareEncodable(NonPremiumMessageAttribute.self, f: { NonPremiumMessageAttribute(decoder: $0) }) declareEncodable(TelegramExtendedMedia.self, f: { TelegramExtendedMedia(decoder: $0) }) declareEncodable(TelegramPeerUsername.self, f: { TelegramPeerUsername(decoder: $0) }) declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) }) declareEncodable(AuthSessionInfoAttribute.self, f: { AuthSessionInfoAttribute(decoder: $0) }) declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) }) declareEncodable(TranslationMessageAttribute.Additional.self, f: { TranslationMessageAttribute.Additional(decoder: $0) }) declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) }) declareEncodable(TelegramMediaStory.self, f: { TelegramMediaStory(decoder: $0) }) declareEncodable(SynchronizeViewStoriesOperation.self, f: { SynchronizeViewStoriesOperation(decoder: $0) }) declareEncodable(SynchronizePeerStoriesOperation.self, f: { SynchronizePeerStoriesOperation(decoder: $0) }) declareEncodable(MapVenue.self, f: { MapVenue(decoder: $0) }) declareEncodable(MapGeoAddress.self, f: { MapGeoAddress(decoder: $0) }) declareEncodable(TelegramMediaGiveaway.self, f: { TelegramMediaGiveaway(decoder: $0) }) declareEncodable(TelegramMediaGiveawayResults.self, f: { TelegramMediaGiveawayResults(decoder: $0) }) declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) }) declareEncodable(InvertMediaMessageAttribute.self, f: { InvertMediaMessageAttribute(decoder: $0) }) declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) }) declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) }) declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) }) declareEncodable(OutgoingSuggestedPostMessageAttribute.self, f: { OutgoingSuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(EffectMessageAttribute.self, f: { EffectMessageAttribute(decoder: $0) }) declareEncodable(FactCheckMessageAttribute.self, f: { FactCheckMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) }) declareEncodable(ReportDeliveryMessageAttribute.self, f: { ReportDeliveryMessageAttribute(decoder: $0) }) declareEncodable(PaidStarsMessageAttribute.self, f: { PaidStarsMessageAttribute(decoder: $0) }) return }() public func initializeAccountManagement() { let _ = declaredEncodables } public func rootPathForBasePath(_ appGroupPath: String) -> String { return appGroupPath + "/telegram-data" } public func performAppGroupUpgrades(appGroupPath: String, rootPath: String) { DispatchQueue.global(qos: .default).async { let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: rootPath), withIntermediateDirectories: true, attributes: nil) if let items = FileManager.default.enumerator(at: URL(fileURLWithPath: appGroupPath), includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { let allowedDirectories: [String] = [ "telegram-data", "Library" ] for url in items { guard let url = url as? URL else { continue } if let isDirectory = try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory, isDirectory { if !allowedDirectories.contains(url.lastPathComponent) { let _ = try? FileManager.default.removeItem(at: url) } } } } } do { var resourceValues = URLResourceValues() resourceValues.isExcludedFromBackup = true var mutableUrl = URL(fileURLWithPath: rootPath) try mutableUrl.setResourceValues(resourceValues) } catch let e { print("\(e)") } } public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkInitializationArguments, supplementary: Bool, manager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { return manager.currentAccountRecord(allocateIfNotExists: allocateIfNotExists) |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs?.0 == rhs?.0 }) |> mapToSignal { record -> Signal in if let record = record { let reload = ValuePromise(true, ignoreRepeated: false) return reload.get() |> mapToSignal { _ -> Signal in let beginWithTestingEnvironment = record.1.contains(where: { attribute in if case let .environment(environment) = attribute, case .test = environment.environment { return true } else { return false } }) let isSupportUser = record.1.contains(where: { attribute in if case .supportUserInfo = attribute { return true } else { return false } }) return accountWithId(accountManager: manager, networkArguments: networkArguments, id: record.0, encryptionParameters: encryptionParameters, supplementary: supplementary, isSupportUser: isSupportUser, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { accountResult -> Signal in let postbox: Postbox let initialKind: AccountKind switch accountResult { case .upgrading: return .complete() case let .unauthorized(account): postbox = account.postbox initialKind = .unauthorized case let .authorized(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(accountResult) return updatedKind.start(next: { value in if value { reload.set(true) } }) } } } } else { return .single(nil) } } } public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManager, alreadyLoggedOutRemotely: Bool) -> Signal { Logger.shared.log("AccountManager", "logoutFromAccount \(id)") return accountManager.transaction { transaction -> Void in transaction.updateRecord(id, { current in if alreadyLoggedOutRemotely { return nil } else if let current = current { var found = false for attribute in current.attributes { if case .loggedOut = attribute { found = true break } } if found { return current } else { return AccountRecord(id: current.id, attributes: current.attributes + [.loggedOut(LoggedOutAccountAttribute())], temporarySessionId: nil) } } else { return nil } }) } } public func managedCleanupAccounts(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let currentTemporarySessionId = accountManager.temporarySessionId return Signal { subscriber in let loggedOutAccounts = Atomic<[AccountRecordId: MetaDisposable]>(value: [:]) let _ = (accountManager.transaction { transaction -> Void in for record in transaction.getRecords() { if let temporarySessionId = record.temporarySessionId, temporarySessionId != currentTemporarySessionId { transaction.updateRecord(record.id, { _ in return nil }) } } }).start() let disposable = accountManager.accountRecords().start(next: { view in var disposeList: [(AccountRecordId, MetaDisposable)] = [] var beginList: [(AccountRecordId, [TelegramAccountManagerTypes.Attribute], MetaDisposable)] = [] let _ = loggedOutAccounts.modify { disposables in var validIds: [AccountRecordId: [TelegramAccountManagerTypes.Attribute]] = [:] outer: for record in view.records { for attribute in record.attributes { if case .loggedOut = attribute { validIds[record.id] = record.attributes continue outer } } } var disposables = disposables for id in disposables.keys { if validIds[id] == nil { disposeList.append((id, disposables[id]!)) } } for (id, _) in disposeList { disposables.removeValue(forKey: id) } for (id, attributes) in validIds { if disposables[id] == nil { let disposable = MetaDisposable() beginList.append((id, attributes, disposable)) disposables[id] = disposable } } return disposables } for (_, disposable) in disposeList { disposable.dispose() } for (id, attributes, disposable) in beginList { Logger.shared.log("managedCleanupAccounts", "cleanup \(id), current is \(String(describing: view.currentRecord?.id))") disposable.set(cleanupAccount(networkArguments: networkArguments, accountManager: accountManager, id: id, encryptionParameters: encryptionParameters, attributes: attributes, rootPath: rootPath, auxiliaryMethods: auxiliaryMethods).start()) } var validPaths = Set() for record in view.records { if let temporarySessionId = record.temporarySessionId, temporarySessionId != currentTemporarySessionId { continue } validPaths.insert("\(accountRecordIdPathName(record.id))") } if let record = view.currentAuthAccount { validPaths.insert("\(accountRecordIdPathName(record.id))") } DispatchQueue.global(qos: .utility).async { if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath), includingPropertiesForKeys: [], options: []) { for url in files { if url.lastPathComponent.hasPrefix("account-") { if !validPaths.contains(url.lastPathComponent) { try? FileManager.default.removeItem(at: url) } } } } } }) return ActionDisposable { disposable.dispose() } } } public typealias AccountManagerPreferencesEntry = PreferencesEntry private func cleanupAccount(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, attributes: [TelegramAccountManagerTypes.Attribute], rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { let beginWithTestingEnvironment = attributes.contains(where: { attribute in if case let .environment(accountEnvironment) = attribute, case .test = accountEnvironment.environment { return true } else { return false } }) let isSupportUser = attributes.contains(where: { attribute in if case .supportUserInfo = attribute { return true } else { return false } }) return accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: true, isSupportUser: isSupportUser, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods) |> mapToSignal { account -> Signal in switch account { case .upgrading: return .complete() case .unauthorized: return .complete() case let .authorized(account): account.shouldBeServiceTaskMaster.set(.single(.always)) return account.network.request(Api.functions.auth.logOut()) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> mapToSignal { result -> Signal in switch result { case let .loggedOut(_, futureAuthToken): if let futureAuthToken = futureAuthToken { storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData()) } default: break } account.shouldBeServiceTaskMaster.set(.single(.never)) return accountManager.transaction { transaction -> Void in transaction.updateRecord(id, { _ in return nil }) } } } } }