diff --git a/Telegram/BUILD b/Telegram/BUILD index 279220ba6d..3e619c87cc 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1527,6 +1527,7 @@ plist_fragment( {telegram_bundle_id}.refresh {telegram_bundle_id}.cleanup + {telegram_bundle_id}.upload.* CFBundleAllowMixedLocalizations diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 3719c1f735..ee663bb5aa 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -917,15 +917,47 @@ public final class AccountAuxiliaryMethods { } } -public struct AccountRunningImportantTasks: OptionSet { - public var rawValue: Int32 - - public init(rawValue: Int32) { - self.rawValue = rawValue +public struct AccountRunningImportantTasks: Equatable { + public struct TaskTypes: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let other = TaskTypes(rawValue: 1 << 0) } - public static let other = AccountRunningImportantTasks(rawValue: 1 << 0) - public static let pendingMessages = AccountRunningImportantTasks(rawValue: 1 << 1) + public var taskTypes: TaskTypes + public var pendingMessageCount: Int + public var pendingStoryCount: Int + + public init(taskTypes: TaskTypes, pendingMessageCount: Int, pendingStoryCount: Int) { + self.taskTypes = taskTypes + self.pendingMessageCount = pendingMessageCount + self.pendingStoryCount = pendingStoryCount + } + + public func combine(with other: AccountRunningImportantTasks) -> AccountRunningImportantTasks { + return AccountRunningImportantTasks( + taskTypes: self.taskTypes.union(other.taskTypes), + pendingMessageCount: self.pendingMessageCount + other.pendingMessageCount, + pendingStoryCount: self.pendingStoryCount + other.pendingStoryCount + ) + } + + public var isEmpty: Bool { + if !self.taskTypes.isEmpty { + return false + } + if self.pendingMessageCount != 0 { + return false + } + if self.pendingStoryCount != 0 { + return false + } + return true + } } public struct MasterNotificationKey: Codable { @@ -1189,7 +1221,7 @@ public class Account { return self._loggedOut.get() } - private let _importantTasksRunning = ValuePromise([], ignoreRepeated: true) + private let _importantTasksRunning = ValuePromise(AccountRunningImportantTasks(taskTypes: [], pendingMessageCount: 0, pendingStoryCount: 0), ignoreRepeated: true) public var importantTasksRunning: Signal { return self._importantTasksRunning.get() } @@ -1393,51 +1425,55 @@ public class Account { let extractedExpr1: [Signal] = [ managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { inputStates in - if inputStates { - //print("inputStates: true") - } - return inputStates ? AccountRunningImportantTasks.other : [] + return AccountRunningImportantTasks( + taskTypes: inputStates ? .other : [], + pendingMessageCount: 0, + pendingStoryCount: 0 + ) }, - self.pendingMessageManager.hasPendingMessages |> map { hasPendingMessages in - if !hasPendingMessages.isEmpty { - //print("hasPendingMessages: true") - } - return !hasPendingMessages.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] + self.pendingMessageManager.pendingMessageCount |> map { pendingMessageCount in + return AccountRunningImportantTasks( + taskTypes: [], + pendingMessageCount: pendingMessageCount.values.reduce(into: 0, { $0 += $1 }), + pendingStoryCount: 0 + ) }, (self.pendingStoryManager?.hasPending ?? .single(false)) |> map { hasPending in - if hasPending { - //print("hasPending: true") - } - return hasPending ? AccountRunningImportantTasks.pendingMessages : [] + return AccountRunningImportantTasks( + taskTypes: [], + pendingMessageCount: 0, + pendingStoryCount: hasPending ? 1 : 0 + ) }, self.pendingUpdateMessageManager.updatingMessageMedia |> map { updatingMessageMedia in - if !updatingMessageMedia.isEmpty { - //print("updatingMessageMedia: true") - } - return !updatingMessageMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] + return AccountRunningImportantTasks( + taskTypes: [], + pendingMessageCount: updatingMessageMedia.count, + pendingStoryCount: 0 + ) }, self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { uploadingPeerMedia in - if !uploadingPeerMedia.isEmpty { - //print("uploadingPeerMedia: true") - } - return !uploadingPeerMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] + return AccountRunningImportantTasks( + taskTypes: [], + pendingMessageCount: uploadingPeerMedia.count, + pendingStoryCount: 0 + ) }, self.accountPresenceManager.isPerformingUpdate() |> map { presenceUpdate in - if presenceUpdate { - //print("accountPresenceManager isPerformingUpdate: true") - //return [] - } - return presenceUpdate ? AccountRunningImportantTasks.other : [] - }, - //self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] } + return AccountRunningImportantTasks( + taskTypes: presenceUpdate ? .other : [], + pendingMessageCount: 0, + pendingStoryCount: 0 + ) + } ] let extractedExpr: [Signal] = extractedExpr1 let importantBackgroundOperations: [Signal] = extractedExpr let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations) |> map { values -> AccountRunningImportantTasks in - var result: AccountRunningImportantTasks = [] + var result: AccountRunningImportantTasks = AccountRunningImportantTasks(taskTypes: [], pendingMessageCount: 0, pendingStoryCount: 0) for value in values { - result.formUnion(value) + result = result.combine(with: value) } return result } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index ff485b317b..df76f5ddd8 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -219,9 +219,9 @@ public final class PendingMessageManager { private let queue = Queue() - private let _hasPendingMessages = ValuePromise>(Set(), ignoreRepeated: true) - public var hasPendingMessages: Signal, NoError> { - return self._hasPendingMessages.get() + private let _pendingMessageCount = ValuePromise<[PeerId: Int]>([:], ignoreRepeated: true) + public var pendingMessageCount: Signal<[PeerId: Int], NoError> { + return self._pendingMessageCount.get() } private var messageContexts: [MessageId: PendingMessageContext] = [:] @@ -344,14 +344,18 @@ public final class PendingMessageManager { }) } - var peersWithPendingMessages = Set() + var pendingMessageCount: [PeerId: Int] = [:] for id in self.pendingMessageIds { - peersWithPendingMessages.insert(id.peerId) + if let current = pendingMessageCount[id.peerId] { + pendingMessageCount[id.peerId] = current + 1 + } else { + pendingMessageCount[id.peerId] = 1 + } } Logger.shared.log("PendingMessageManager", "pending messages: \(self.pendingMessageIds)") - self._hasPendingMessages.set(peersWithPendingMessages) + self._pendingMessageCount.set(pendingMessageCount) } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 0f72902014..d35f1b6a7b 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1521,31 +1521,6 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) - |> deliverOnMainQueue).start(next: { sharedApplicationContext in - - sharedApplicationContext.wakeupManager.replaceCurrentExtensionWithExternalTime(completion: { - task.setTaskCompleted(success: true) - }, timeout: 29.0) - let _ = (self.context.get() - |> take(1) - |> deliverOnMainQueue).start(next: { context in - guard let context = context else { - return - } - sharedApplicationContext.notificationManager.beginPollingState(account: context.context.account) - }) - }) - }) - }*/ - self.maybeCheckForUpdates() #if canImport(AppCenter) @@ -1557,9 +1532,9 @@ private func extractAccountManagerState(records: AccountRecordsView mapToSignal { peerIds -> Signal in - let value = peerIds.contains(chatLocationPeerId) - if value { + hasPendingMessages = context.account.pendingMessageManager.pendingMessageCount + |> mapToSignal { pendingMessageCount -> Signal in + if let value = pendingMessageCount[chatLocationPeerId], value != 0 { return .single(true) } else { return .single(false) diff --git a/submodules/TelegramUI/Sources/SharedWakeupManager.swift b/submodules/TelegramUI/Sources/SharedWakeupManager.swift index 3f857c200d..e6551d82fc 100644 --- a/submodules/TelegramUI/Sources/SharedWakeupManager.swift +++ b/submodules/TelegramUI/Sources/SharedWakeupManager.swift @@ -1,4 +1,5 @@ import Foundation +import BackgroundTasks import AVFAudio import UIKit import SwiftSignalKit @@ -75,6 +76,10 @@ public final class SharedWakeupManager { private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] + private var pendingMessageTotalCount: Int? + private var nextBackgroundProcessingTaskId: Int = 0 + private var backgroundProcessingTaskId: String? + public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, acquireIdleExtension: @escaping () -> Disposable?, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal, watchTasks: Signal, inForeground: Signal, hasActiveAudioSession: Signal, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal) { assert(Queue.mainQueue().isCurrent()) @@ -312,15 +317,12 @@ public final class SharedWakeupManager { var hasTasksForBackgroundExtension = false var hasActiveCalls = false - var hasPendingMessages = false + var pendingMessageCount = 0 for (_, _, tasks) in self.accountsAndTasks { if tasks.activeCalls { hasActiveCalls = true - break - } - if tasks.importantTasks.contains(.pendingMessages) { - hasPendingMessages = true } + pendingMessageCount += tasks.importantTasks.pendingMessageCount } var endTaskAfterTransactionsComplete: UIBackgroundTaskIdentifier? @@ -417,20 +419,135 @@ public final class SharedWakeupManager { self.isInBackgroundExtension = false } } - self.updateAccounts(hasTasks: hasTasksForBackgroundExtension, endTaskAfterTransactionsComplete: endTaskAfterTransactionsComplete) - if hasPendingMessages { + let baseAppBundleId = Bundle.main.bundleIdentifier! + + if pendingMessageCount != 0 && !self.inForeground { if self.keepIdleDisposable == nil { self.keepIdleDisposable = self.acquireIdleExtension() } + if #available(iOS 26.0, *), self.backgroundProcessingTaskId == nil { + let uploadTaskId = "\(baseAppBundleId).upload.\(self.nextBackgroundProcessingTaskId)" + self.nextBackgroundProcessingTaskId += 1 + if let current = self.backgroundProcessingTaskId { + BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: current) + } + self.backgroundProcessingTaskId = uploadTaskId + + BGTaskScheduler.shared.register(forTaskWithIdentifier: uploadTaskId, using: nil, launchHandler: { [weak self] task in + guard let task = task as? BGContinuedProcessingTask else { + return + } + guard let self else { + task.setTaskCompleted(success: true) + return + } + + var wasExpired = false + + task.expirationHandler = { [weak self] in + wasExpired = true + + Queue.mainQueue().async { + guard let self else { + return + } + if self.backgroundProcessingTaskId == task.identifier { + self.backgroundProcessingTaskId = nil + self.checkTasks() + } + } + } + + Task { @MainActor [weak self] in + guard let self else { + task.setTaskCompleted(success: true) + return + } + + while true { + if wasExpired { + break + } + var pendingMessageCount = 0 + for (_, _, tasks) in self.accountsAndTasks { + pendingMessageCount += tasks.importantTasks.pendingMessageCount + } + if pendingMessageCount == 0 { + self.pendingMessageTotalCount = nil + task.setTaskCompleted(success: true) + if self.backgroundProcessingTaskId == task.identifier { + self.backgroundProcessingTaskId = nil + self.checkTasks() + } + return + } + + let totalCount: Int + if let value = self.pendingMessageTotalCount { + let maxValue = max(value, pendingMessageCount) + totalCount = maxValue + } else { + totalCount = pendingMessageCount + } + self.pendingMessageTotalCount = totalCount + //task.progress.totalUnitCount = Int64(totalCount) + //task.progress.completedUnitCount = Int64(max(0, totalCount - pendingMessageCount)) + + task.progress.totalUnitCount = 200 + task.progress.completedUnitCount += 1 + + let title: String + if totalCount == 1 { + title = "Sending 1 Message" + } else { + title = "Sending \(totalCount) Messages" + } + if task.title != title { + task.updateTitle(title, subtitle: "Running...") + } + + try await Task.sleep(for: .seconds(1.0)) + } + } + }) + + let title: String + if pendingMessageCount == 1 { + title = "Sending 1 Message" + } else { + title = "Sending \(pendingMessageCount) Messages" + } + + let request = BGContinuedProcessingTaskRequest( + identifier: uploadTaskId, + title: title, + subtitle: "Running..." + ) + request.strategy = .fail + + do { + try BGTaskScheduler.shared.submit(request) + } catch let e { + Logger.shared.log("Wakeup", "BGTaskScheduler submit error: \(e)") + } + } } else { if let keepIdleDisposable = self.keepIdleDisposable { self.keepIdleDisposable = nil keepIdleDisposable.dispose() } + if let backgroundProcessingTaskId = self.backgroundProcessingTaskId { + self.backgroundProcessingTaskId = nil + + self.pendingMessageTotalCount = nil + BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: backgroundProcessingTaskId) + } } - if !self.inForeground && hasPendingMessages && !self.hasActiveAudioSession { + self.updateAccounts(hasTasks: hasTasksForBackgroundExtension, endTaskAfterTransactionsComplete: endTaskAfterTransactionsComplete) + + /*if !self.inForeground && pendingMessageCount != 0 && !self.hasActiveAudioSession { if self.silenceAudioRenderer == nil { let audioSession = AVAudioSession() let _ = try? audioSession.setCategory(.ambient) @@ -460,11 +577,11 @@ public final class SharedWakeupManager { } else if let silenceAudioRenderer = self.silenceAudioRenderer { self.silenceAudioRenderer = nil silenceAudioRenderer.stop() - } + }*/ } private func updateAccounts(hasTasks: Bool, endTaskAfterTransactionsComplete: UIBackgroundTaskIdentifier?) { - if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || (hasTasks && self.currentExternalCompletion != nil) || self.activeExplicitExtensionTimer != nil || self.silenceAudioRenderer != nil { + if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || self.backgroundProcessingTaskId != nil || (hasTasks && self.currentExternalCompletion != nil) || self.activeExplicitExtensionTimer != nil || self.silenceAudioRenderer != nil { Logger.shared.log("Wakeup", "enableBeginTransactions: true (active)") for (account, primary, tasks) in self.accountsAndTasks { @@ -475,7 +592,7 @@ public final class SharedWakeupManager { } else { account.shouldBeServiceTaskMaster.set(.single(.never)) } - account.shouldExplicitelyKeepWorkerConnections.set(.single(tasks.backgroundAudio)) + account.shouldExplicitelyKeepWorkerConnections.set(.single(tasks.backgroundAudio || tasks.importantTasks.pendingStoryCount != 0 || tasks.importantTasks.pendingMessageCount != 0)) account.shouldKeepOnlinePresence.set(.single(primary && self.inForeground)) account.shouldKeepBackgroundDownloadConnections.set(.single(tasks.backgroundDownloads)) }