diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index 8526e49dd6..63704c88dc 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -105,7 +105,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true) self.accountManager = accountManager let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) @@ -820,6 +820,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag return INObjectSection(title: accountTitle, items: items) }) + |> `catch` { _ -> Signal, NoError> in + return .single(INObjectSection(title: nil, items: [])) + } |> castError(Error.self)) } @@ -895,7 +898,7 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling { let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true) self.accountManager = accountManager let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) @@ -1043,6 +1046,9 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling { return INObjectSection(title: accountTitle, items: items) }) + |> `catch` { _ -> Signal, NoError> in + return .single(INObjectSection(title: nil, items: [])) + } |> castError(Error.self)) } @@ -1103,6 +1109,9 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling { return items }) + |> `catch` { _ -> Signal<[Friend], NoError> in + return .single([]) + } |> castError(Error.self)) } diff --git a/Telegram/WidgetKitWidget/TodayViewController.swift b/Telegram/WidgetKitWidget/TodayViewController.swift index cfdfd19836..efb0c51dac 100644 --- a/Telegram/WidgetKitWidget/TodayViewController.swift +++ b/Telegram/WidgetKitWidget/TodayViewController.swift @@ -112,13 +112,6 @@ struct Provider: IntentTimelineProvider { let rootPath = rootPathForBasePath(appGroupUrl.path) - let dataPath = rootPath + "/widget-data" - - guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else { - completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd)) - return - } - TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64()) let logsPath = rootPath + "/widget-logs" @@ -213,7 +206,10 @@ struct Provider: IntentTimelineProvider { } return result - })) + }) + |> `catch` { _ -> Signal<[ParsedPeer], NoError> in + return .single([]) + }) } let _ = combineLatest(friendsByAccount).start(next: { allPeers in @@ -269,13 +265,6 @@ struct AvatarsProvider: IntentTimelineProvider { let rootPath = rootPathForBasePath(appGroupUrl.path) - let dataPath = rootPath + "/widget-data" - - guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else { - completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd)) - return - } - TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64()) let logsPath = rootPath + "/widget-logs" @@ -370,7 +359,10 @@ struct AvatarsProvider: IntentTimelineProvider { } return result - })) + }) + |> `catch` { _ -> Signal<[ParsedPeer], NoError> in + return .single([]) + }) } let _ = combineLatest(friendsByAccount).start(next: { allPeers in @@ -1006,39 +998,7 @@ private let presentationData: WidgetPresentationData = { func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData { switch contents { case .recent: - let appBundleIdentifier = Bundle.main.bundleIdentifier! - guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { - return .empty - } - let baseAppBundleId = String(appBundleIdentifier[..)>() private var accessChallengeDataViews = Bag<(MutableAccessChallengeDataView, ValuePipe)>() - fileprivate init(queue: Queue, basePath: String, temporarySessionId: Int64) { + fileprivate init?(queue: Queue, basePath: String, isTemporary: Bool, temporarySessionId: Int64) { let startTime = CFAbsoluteTimeGetCurrent() self.queue = queue @@ -61,8 +61,14 @@ final class AccountManagerImpl { self.atomicStatePath = "\(basePath)/atomic-state" self.temporarySessionId = temporarySessionId let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) - self.guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, encryptionParameters: nil, upgradeProgress: { _ in }) - self.valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: nil, upgradeProgress: { _ in }) + guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, encryptionParameters: nil, upgradeProgress: { _ in }) else { + return nil + } + self.guardValueBox = guardValueBox + guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: nil, upgradeProgress: { _ in }) else { + return nil + } + self.valueBox = valueBox self.legacyMetadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0)) self.legacyRecordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1)) @@ -451,7 +457,7 @@ public final class AccountManager { private let impl: QueueLocalObject public let temporarySessionId: Int64 - public init(basePath: String) { + public init(basePath: String, isTemporary: Bool) { self.queue = sharedQueue self.basePath = basePath var temporarySessionId: Int64 = 0 @@ -459,7 +465,11 @@ public final class AccountManager { self.temporarySessionId = temporarySessionId let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return AccountManagerImpl(queue: queue, basePath: basePath, temporarySessionId: temporarySessionId) + if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, temporarySessionId: temporarySessionId) { + return value + } else { + preconditionFailure() + } }) self.mediaBox = MediaBox(basePath: basePath + "/media") } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index dd6c9087e3..0ef34eb3b5 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1096,6 +1096,7 @@ public final class Transaction { public enum PostboxResult { case upgrading(Float) case postbox(Postbox) + case error } func debugSaveState(basePath:String, name: String) { @@ -1137,9 +1138,12 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, let startTime = CFAbsoluteTimeGetCurrent() - var valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard var valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) - }) + }) else { + subscriber.putNext(.error) + return + } loop: while true { let metadataTable = MetadataTable(valueBox: valueBox, table: MetadataTable.tableSpec(0)) @@ -1150,16 +1154,20 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, if let userVersion = userVersion { if userVersion != currentUserVersion { if isTemporary { - subscriber.putNext(.upgrading(0.0)) + subscriber.putNext(.error) return } else { if userVersion > currentUserVersion { postboxLog("Version \(userVersion) is newer than supported") assertionFailure("Version \(userVersion) is newer than supported") valueBox.drop() - valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) - }) + }) else { + subscriber.putNext(.error) + return + } + valueBox = updatedValueBox } else { if let operation = registeredUpgrades()[userVersion] { switch operation { @@ -1177,9 +1185,13 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, valueBox.internalClose() let _ = try? FileManager.default.removeItem(atPath: basePath + "/db") let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: basePath + "/db") - valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) - }) + }) else { + subscriber.putNext(.error) + return + } + valueBox = updatedValueBox } } continue loop @@ -1187,9 +1199,13 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, assertionFailure("Couldn't find any upgrade for \(userVersion)") postboxLog("Couldn't find any upgrade for \(userVersion)") valueBox.drop() - valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) - }) + }) else { + subscriber.putNext(.error) + return + } + valueBox = updatedValueBox } } } diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 95b4c6a601..053b1aa7ab 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -155,6 +155,7 @@ public final class SqliteValueBox: ValueBox { private let lock = NSRecursiveLock() fileprivate let basePath: String + private let isTemporary: Bool private let inMemory: Bool private let encryptionParameters: ValueBoxEncryptionParameters? private let databasePath: String @@ -193,13 +194,18 @@ public final class SqliteValueBox: ValueBox { private let queue: Queue - public init(basePath: String, queue: Queue, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) { + public init?(basePath: String, queue: Queue, isTemporary: Bool, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) { self.basePath = basePath + self.isTemporary = isTemporary self.inMemory = inMemory self.encryptionParameters = encryptionParameters self.databasePath = basePath + "/db_sqlite" self.queue = queue - self.database = self.openDatabase(encryptionParameters: encryptionParameters, upgradeProgress: upgradeProgress) + if let database = self.openDatabase(encryptionParameters: encryptionParameters, isTemporary: isTemporary, upgradeProgress: upgradeProgress) { + self.database = database + } else { + return nil + } } deinit { @@ -212,7 +218,7 @@ public final class SqliteValueBox: ValueBox { self.database = nil } - private func openDatabase(encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void) -> Database { + private func openDatabase(encryptionParameters: ValueBoxEncryptionParameters?, isTemporary: Bool, upgradeProgress: (Float) -> Void) -> Database? { precondition(self.queue.isCurrent()) checkpoints.set(nil) @@ -297,6 +303,9 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) if self.isEncrypted(database) { + if isTemporary { + return nil + } postboxLog("Encryption key is invalid") for fileName in dabaseFileNames { @@ -2060,7 +2069,7 @@ public final class SqliteValueBox: ValueBox { let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(fileName)") } - self.database = self.openDatabase(encryptionParameters: self.encryptionParameters, upgradeProgress: { _ in }) + self.database = self.openDatabase(encryptionParameters: self.encryptionParameters, isTemporary: self.isTemporary, upgradeProgress: { _ in }) tables.removeAll() } diff --git a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift index 08d7d25b29..fcd6797d88 100644 --- a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift +++ b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift @@ -69,19 +69,26 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { }, additionalChatListIndexNamespace: Namespaces.Message.Cloud, messageNamespacesRequiringGroupStatsValidation: [Namespaces.Message.Cloud], defaultMessageNamespaceReadStates: [Namespaces.Message.Local: .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false)], chatMessagesNamespaces: Set([Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]), globalNotificationSettingsPreferencesKey: PreferencesKeys.globalNotifications, defaultGlobalNotificationSettings: GlobalNotificationSettings.defaultSettings) }() -public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { +public enum AccountTransactionError { + case couldNotOpen +} + +public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true) return postbox - |> mapToSignal { value -> Signal in + |> castError(AccountTransactionError.self) + |> mapToSignal { value -> Signal in switch value { - case let .postbox(postbox): - return postbox.transaction { t in - transaction(postbox, t) - } - default: - return .complete() + case let .postbox(postbox): + return postbox.transaction { t in + transaction(postbox, t) + } + |> castError(AccountTransactionError.self) + case .error: + return .fail(.couldNotOpen) + default: + return .complete() } } } - diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index 2f043eb239..d32d372346 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -176,6 +176,8 @@ public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys } return .result(path, result) } + case .error: + return .single(.progress(0.0)) } } } @@ -197,6 +199,8 @@ public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encrypti return postbox.transaction { transaction -> AccountNoticeEntriesResult in return .result(path, transaction.getAllNoticeEntries()) } + case .error: + return .single(.progress(0.0)) } } } @@ -218,6 +222,8 @@ public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecord return postbox.transaction { transaction -> LegacyAccessChallengeDataResult in return .result(transaction.legacyGetAccessChallengeData()) } + case .error: + return .single(.progress(0.0)) } } } @@ -232,6 +238,8 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw switch result { case let .upgrading(progress): return .single(.upgrading(progress)) + case .error: + return .single(.upgrading(0.0)) case let .postbox(postbox): return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return (transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings, transaction.getSharedData(SharedDataKeys.proxySettings) as? ProxySettings) diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index d89f4757b9..732fc76040 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -219,6 +219,8 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti switch result { case .upgrading: return .complete() + case .error: + return .complete() case let .postbox(postbox): return .single(TemporaryAccount(id: id, basePath: path, postbox: postbox)) } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index d15537ae06..2fd693b326 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -665,7 +665,7 @@ final class SharedApplicationContext { }) let accountManagerSignal = Signal { subscriber in - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false) return (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters) |> deliverOnMainQueue).start(next: { progress in if self.dataImportSplash == nil { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4cd52a65e2..66b257c7d5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4998,6 +4998,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return interfaceState.withUpdatedEffectiveInputState(updatedState) }.updatedInputMode({ _ in updatedMode }) }) + + if !strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty { + strongSelf.silentPostTooltipController?.dismiss() + } } }, updateInputModeAndDismissedButtonKeyboardMessageId: { [weak self] f in if let strongSelf = self { @@ -5626,6 +5630,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let intervalText = timeIntervalString(strings: strongSelf.presentationData.strings, value: currentAutoremoveTimeout) let text: String = "Messages in this chat are automatically\ndeleted \(intervalText) after they have been sent." + strongSelf.mediaRecordingModeTooltipController?.dismiss() + if let tooltipController = strongSelf.silentPostTooltipController { tooltipController.updateContent(.text(text), animated: true, extendTimer: true) } else { @@ -11476,6 +11482,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G text = self.presentationData.strings.Conversation_HoldForVideo } + self.silentPostTooltipController?.dismiss() + if let tooltipController = self.mediaRecordingModeTooltipController { tooltipController.updateContent(.text(text), animated: true, extendTimer: true) } else if let rect = rect { @@ -12033,7 +12041,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) }) var isOn: Bool = true - var title: String? var text: String? if let myValue = value.value { text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0 @@ -12042,7 +12049,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G text = "Auto-Delete is now off." } if let text = text { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: title, text: text), elevatedLayout: false, action: { _ in return false }), in: .current) + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, action: { _ in return false }), in: .current) } } }) @@ -12122,3 +12129,29 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent func animatedIn() { } } + +extension Peer { + func canSetupAutoremoveTimeout(accountPeerId: PeerId) -> Bool { + if let _ = self as? TelegramSecretChat { + return false + } else if let group = self as? TelegramGroup { + if case .creator = group.role { + return true + } else if case let .admin(rights, _) = group.role { + if rights.flags.contains(.canDeleteMessages) { + return true + } + } + } else if let user = self as? TelegramUser { + if user.id != accountPeerId && user.botInfo == nil { + return true + } + } else if let channel = self as? TelegramChannel { + if channel.hasPermission(.deleteAllMessages) { + return true + } + } + + return true + } +} diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index b7d020c69e..710e92d4d3 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -21,6 +21,7 @@ import JoinLinkPreviewUI import LanguageLinkPreviewUI import PeerInfoUI import InviteLinksUI +import UndoUI private final class ChatRecentActionsListOpaqueState { let entries: [ChatRecentActionsEntry] @@ -160,6 +161,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { strongSelf.pushController(controller) return true } + case .changeHistoryTTL: + if strongSelf.peer.canSetupAutoremoveTimeout(accountPeerId: strongSelf.context.account.peerId) { + strongSelf.presentAutoremoveSetup() + return true + } default: break } @@ -876,4 +882,29 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } })) } + + private func presentAutoremoveSetup() { + let peer = self.peer + + let controller = peerAutoremoveSetupScreen(context: self.context, peerId: peer.id, completion: { [weak self] updatedValue in + if case let .updated(value) = updatedValue { + guard let strongSelf = self else { + return + } + + var isOn: Bool = true + var text: String? + if let myValue = value.value { + text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0 + } else { + isOn = false + text = "Auto-Delete is now off." + } + if let text = text { + strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, action: { _ in return false }), nil) + } + } + }) + self.pushController(controller) + } } diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 5c77c61151..175a7574aa 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -113,7 +113,7 @@ public final class NotificationViewControllerImpl { if sharedAccountContext == nil { initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true) var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? let semaphore = DispatchSemaphore(value: 0) diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index ef9e18c7b0..573d81fb55 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -202,7 +202,7 @@ public class ShareRootControllerImpl { let internalContext: InternalContext - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true) if let globalInternalContext = globalInternalContext { internalContext = globalInternalContext diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index f7a94ef648..3d3c0b2c23 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -740,9 +740,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.updateNotificationTokensRegistration() if applicationBindings.isMainApp { - self.widgetDataContext = WidgetDataContext(basePath: self.basePath, activeAccount: self.activeAccounts - |> map { primary, _, _ in - return primary + self.widgetDataContext = WidgetDataContext(basePath: self.basePath, inForeground: self.applicationBindings.applicationInForeground, activeAccounts: self.activeAccounts + |> map { _, accounts, _ in + return accounts.map { $0.1 } }, presentationData: self.presentationData, appLockContext: self.appLockContext as! AppLockContextImpl) let enableSpotlight = accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.intentsSettings])) diff --git a/submodules/TelegramUI/Sources/UpgradedAccounts.swift b/submodules/TelegramUI/Sources/UpgradedAccounts.swift index 616c65da58..4497306741 100644 --- a/submodules/TelegramUI/Sources/UpgradedAccounts.swift +++ b/submodules/TelegramUI/Sources/UpgradedAccounts.swift @@ -298,6 +298,9 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e }) }) |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } importSignal = importSignal |> then(importInfoAccounttSignal) } return importSignal diff --git a/submodules/TelegramUI/Sources/WidgetDataContext.swift b/submodules/TelegramUI/Sources/WidgetDataContext.swift index 83b81dfe5f..e668e9dd96 100644 --- a/submodules/TelegramUI/Sources/WidgetDataContext.swift +++ b/submodules/TelegramUI/Sources/WidgetDataContext.swift @@ -29,251 +29,209 @@ private extension SelectFriendsIntent { } } -final class WidgetDataContext { - private var currentAccount: Account? - private var currentAccountDisposable: Disposable? - private var widgetPresentationDataDisposable: Disposable? - private var notificationPresentationDataDisposable: Disposable? +private final class WidgetReloadManager { + private var inForeground = false + private var inForegroundDisposable: Disposable? - init(basePath: String, activeAccount: Signal, presentationData: Signal, appLockContext: AppLockContextImpl) { - self.currentAccountDisposable = (activeAccount - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs === rhs + private var isReloadRequested = false + private var lastBackgroundReload: Double? + + init(inForeground: Signal) { + self.inForegroundDisposable = (inForeground + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + if strongSelf.inForeground != value { + strongSelf.inForeground = value + if value { + strongSelf.performReloadIfNeeded() + } + } }) - |> mapToSignal { account -> Signal in - guard let account = account else { - return .single(WidgetData(accountId: 0, content: .empty, unlockedForLockId: nil)) - } - - enum CombinedRecentPeers { - struct Unread { - var count: Int32 - var isMuted: Bool + } + + deinit { + self.inForegroundDisposable?.dispose() + } + + func requestReload() { + self.isReloadRequested = true + + if self.inForeground { + self.performReloadIfNeeded() + } else { + let timestamp = CFAbsoluteTimeGetCurrent() + if let lastBackgroundReloadValue = self.lastBackgroundReload { + if abs(lastBackgroundReloadValue - timestamp) > 25.0 * 60.0 { + self.lastBackgroundReload = timestamp + performReloadIfNeeded() } - - case disabled - case peers(peers: [Peer], unread: [PeerId: Unread], messages: [PeerId: WidgetDataPeer.Message]) - } - - let updatedAdditionalPeerIds: Signal<(Set, Set), NoError> = Signal { subscriber in - if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { - #if arch(arm64) || arch(i386) || arch(x86_64) - WidgetCenter.shared.getCurrentConfigurations({ result in - var peerIds = Set() - var configurationHashes = Set() - if case let .success(infos) = result { - for info in infos { - if let configuration = info.configuration as? SelectFriendsIntent { - if let items = configuration.friends { - for item in items { - guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else { - continue - } - peerIds.insert(PeerId(peerIdValue)) - } - } - configurationHashes.insert(configuration.configurationHash) - } - } - } - - subscriber.putNext((peerIds, configurationHashes)) - subscriber.putCompletion() - }) - #else - subscriber.putNext((Set(), Set())) - subscriber.putCompletion() - #endif - } else { - subscriber.putNext((Set(), Set())) - subscriber.putCompletion() - } - - return EmptyDisposable - } - |> runOn(.mainQueue()) - - let unlockedForLockId: Signal = .single(nil) - - let sourcePeers: Signal = recentPeers(account: account) - - let recent: Signal = sourcePeers - |> mapToSignal { recent -> Signal in - switch recent { - case .disabled: - return .single(.disabled) - case let .peers(peers): - return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)}) - |> mapToSignal { peerViews -> Signal in - let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map { - $0.peerId - }) - return combineLatest(queue: .mainQueue(), - account.postbox.unreadMessageCountsView(items: peerViews.map { - .peer($0.peerId) - }), - account.postbox.combinedView(keys: [topMessagesKey]) - ) - |> map { values, combinedView -> CombinedRecentPeers in - var peers: [Peer] = [] - var unread: [PeerId: CombinedRecentPeers.Unread] = [:] - var messages: [PeerId: WidgetDataPeer.Message] = [:] - - let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView - - for peerView in peerViews { - if let peer = peerViewMainPeer(peerView) { - var isMuted: Bool = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - switch notificationSettings.muteState { - case .muted: - isMuted = true - default: - break - } - } - - let unreadCount = values.count(for: .peer(peerView.peerId)) - if let unreadCount = unreadCount, unreadCount > 0 { - unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted) - } - - if let message = topMessages.messages[peerView.peerId] { - messages[peerView.peerId] = WidgetDataPeer.Message(message: message) - } - - peers.append(peer) - } - } - return .peers(peers: peers, unread: unread, messages: messages) - } - } - } - } - - let processedRecent = recent - |> map { _ -> WidgetData in - return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: [], updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil) - } - |> distinctUntilChanged - - let additionalPeerIds = Signal<(Set, Set), NoError>.complete() |> then(updatedAdditionalPeerIds) - let processedCustom: Signal = additionalPeerIds - |> distinctUntilChanged(isEqual: { lhs, rhs in - if lhs.0 != rhs.0 { - return false - } - if lhs.1 != rhs.1 { - return false - } - return true - }) - |> mapToSignal { additionalPeerIds, _ -> Signal in - return combineLatest(queue: .mainQueue(), additionalPeerIds.map { account.postbox.peerView(id: $0) }) - |> mapToSignal { peerViews -> Signal in - let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map { - $0.peerId - }) - return combineLatest(queue: .mainQueue(), - account.postbox.unreadMessageCountsView(items: peerViews.map { - .peer($0.peerId) - }), - account.postbox.combinedView(keys: [topMessagesKey]) - ) - |> map { values, combinedView -> CombinedRecentPeers in - var peers: [Peer] = [] - var unread: [PeerId: CombinedRecentPeers.Unread] = [:] - var messages: [PeerId: WidgetDataPeer.Message] = [:] - - let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView - - for peerView in peerViews { - if let peer = peerViewMainPeer(peerView) { - var isMuted: Bool = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - switch notificationSettings.muteState { - case .muted: - isMuted = true - default: - break - } - } - - let unreadCount = values.count(for: .peer(peerView.peerId)) - if let unreadCount = unreadCount, unreadCount > 0 { - unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted) - } - - if let message = topMessages.messages[peerView.peerId] { - messages[peerView.peerId] = WidgetDataPeer.Message(message: message) - } - - peers.append(peer) - } - } - return .peers(peers: peers, unread: unread, messages: messages) - } - } - } - |> map { result -> WidgetData in - switch result { - case .disabled: - return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil) - case let .peers(peers, unread, messages): - return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in - var name: String = "" - var lastName: String? - - if let user = peer as? TelegramUser { - if let firstName = user.firstName { - name = firstName - lastName = user.lastName - } else if let lastName = user.lastName { - name = lastName - } else if let phone = user.phone, !phone.isEmpty { - name = phone - } - } else { - name = peer.debugDisplayTitle - } - - var badge: WidgetDataPeer.Badge? - if let unreadValue = unread[peer.id], unreadValue.count > 0 { - badge = WidgetDataPeer.Badge( - count: Int(unreadValue.count), - isMuted: unreadValue.isMuted - ) - } - - let message = messages[peer.id] - - return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in - return account.postbox.mediaBox.resourcePath(representation.resource) - }, badge: badge, message: message) - }, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil) - } - } - |> distinctUntilChanged - - return combineLatest(processedRecent, processedCustom, unlockedForLockId) - |> map { processedRecent, _, unlockedForLockId -> WidgetData in - var processedRecent = processedRecent - processedRecent.unlockedForLockId = unlockedForLockId - return processedRecent - } - }).start(next: { widgetData in - let path = basePath + "/widget-data" - if let data = try? JSONEncoder().encode(widgetData) { - let _ = try? data.write(to: URL(fileURLWithPath: path), options: [.atomic]) } else { - let _ = try? FileManager.default.removeItem(atPath: path) + self.lastBackgroundReload = timestamp + performReloadIfNeeded() } - + } + } + + private func performReloadIfNeeded() { + if !self.isReloadRequested { + return + } + self.isReloadRequested = false + + DispatchQueue.global(qos: .background).async { if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { #if arch(arm64) || arch(i386) || arch(x86_64) WidgetCenter.shared.reloadAllTimelines() #endif } + } + } +} + +final class WidgetDataContext { + private let reloadManager: WidgetReloadManager + + private var disposable: Disposable? + private var widgetPresentationDataDisposable: Disposable? + private var notificationPresentationDataDisposable: Disposable? + + init(basePath: String, inForeground: Signal, activeAccounts: Signal<[Account], NoError>, presentationData: Signal, appLockContext: AppLockContextImpl) { + self.reloadManager = WidgetReloadManager(inForeground: inForeground) + + let queue = Queue() + let updatedAdditionalPeerIds: Signal<[AccountRecordId: Set], NoError> = Signal { subscriber in + if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { + #if arch(arm64) || arch(i386) || arch(x86_64) + WidgetCenter.shared.getCurrentConfigurations { result in + var peerIds: [AccountRecordId: Set] = [:] + + func processFriend(_ item: Friend) { + guard let identifier = item.identifier else { + return + } + guard let index = identifier.firstIndex(of: ":") else { + return + } + guard let accountIdValue = Int64(identifier[identifier.startIndex ..< index]) else { + return + } + guard let peerIdValue = Int64(identifier[identifier.index(after: index)...]) else { + return + } + let accountId = AccountRecordId(rawValue: accountIdValue) + let peerId = PeerId(peerIdValue) + if peerIds[accountId] == nil { + peerIds[accountId] = Set() + } + peerIds[accountId]?.insert(peerId) + } + + if case let .success(infos) = result { + for info in infos { + if let configuration = info.configuration as? SelectFriendsIntent { + if let items = configuration.friends { + for item in items { + processFriend(item) + } + } + } else if let configuration = info.configuration as? SelectAvatarFriendsIntent { + if let items = configuration.friends { + for item in items { + processFriend(item) + } + } + } + } + } + + subscriber.putNext(peerIds) + subscriber.putCompletion() + } + #else + subscriber.putNext([:]) + subscriber.putCompletion() + #endif + } else { + subscriber.putNext([:]) + subscriber.putCompletion() + } + + return EmptyDisposable + } + |> runOn(queue) + |> then( + Signal<[AccountRecordId: Set], NoError>.complete() + |> delay(10.0, queue: queue) + ) + |> restart + + self.disposable = (combineLatest(queue: queue, + updatedAdditionalPeerIds |> distinctUntilChanged, + activeAccounts |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.count != rhs.count { + return false + } + for i in 0 ..< lhs.count { + if lhs[i] !== rhs[i] { + return false + } + } + return true + }) + ) + |> mapToSignal { peerIdsByAccount, accounts -> Signal<[WidgetDataPeer], NoError> in + var accountSignals: [Signal<[WidgetDataPeer], NoError>] = [] + + for (accountId, peerIds) in peerIdsByAccount { + var accountValue: Account? + for value in accounts { + if value.id == accountId { + accountValue = value + break + } + } + guard let account = accountValue else { + continue + } + if peerIds.isEmpty { + continue + } + let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: Array(peerIds)) + + accountSignals.append(account.postbox.combinedView(keys: [topMessagesKey]) + |> map { combinedView -> [WidgetDataPeer] in + guard let topMessages = combinedView.views[topMessagesKey] as? TopChatMessageView else { + return [] + } + var result: [WidgetDataPeer] = [] + for (peerId, message) in topMessages.messages { + result.append(WidgetDataPeer(id: peerId.toInt64(), name: "", lastName: "", letters: [], avatarPath: nil, badge: nil, message: WidgetDataPeer.Message(message: message))) + } + result.sort(by: { lhs, rhs in + return lhs.id < rhs.id + }) + return result + }) + } + + return combineLatest(queue: queue, accountSignals) + |> map { lists -> [WidgetDataPeer] in + var result: [WidgetDataPeer] = [] + for list in lists { + result.append(contentsOf: list) + } + result.sort(by: { lhs, rhs in + return lhs.id < rhs.id + }) + return result + } + } + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] _ in + self?.reloadManager.requestReload() }) self.widgetPresentationDataDisposable = (presentationData @@ -310,6 +268,8 @@ final class WidgetDataContext { } deinit { - self.currentAccountDisposable?.dispose() + self.disposable?.dispose() + self.widgetPresentationDataDisposable?.dispose() + self.notificationPresentationDataDisposable?.dispose() } }