This commit is contained in:
Isaac 2026-02-24 10:56:22 +01:00
parent 4d4fcdd17f
commit 4f517ed01f
6 changed files with 220 additions and 88 deletions

View file

@ -1527,6 +1527,7 @@ plist_fragment(
<array>
<string>{telegram_bundle_id}.refresh</string>
<string>{telegram_bundle_id}.cleanup</string>
<string>{telegram_bundle_id}.upload.*</string>
</array>
<key>CFBundleAllowMixedLocalizations</key>
<true/>

View file

@ -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<AccountRunningImportantTasks>([], ignoreRepeated: true)
private let _importantTasksRunning = ValuePromise<AccountRunningImportantTasks>(AccountRunningImportantTasks(taskTypes: [], pendingMessageCount: 0, pendingStoryCount: 0), ignoreRepeated: true)
public var importantTasksRunning: Signal<AccountRunningImportantTasks, NoError> {
return self._importantTasksRunning.get()
}
@ -1393,51 +1425,55 @@ public class Account {
let extractedExpr1: [Signal<AccountRunningImportantTasks, NoError>] = [
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<AccountRunningImportantTasks, NoError>] = extractedExpr1
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = 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
}

View file

@ -219,9 +219,9 @@ public final class PendingMessageManager {
private let queue = Queue()
private let _hasPendingMessages = ValuePromise<Set<PeerId>>(Set(), ignoreRepeated: true)
public var hasPendingMessages: Signal<Set<PeerId>, 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<PeerId>()
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)
}
}

View file

@ -1521,31 +1521,6 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}
/*if UIApplication.shared.isStatusBarHidden {
UIApplication.shared.internalSetStatusBarHidden(false, animation: .none)
}*/
/*if #available(iOS 13.0, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: baseAppBundleId + ".refresh", using: nil, launchHandler: { task in
let _ = (self.sharedContextPromise.get()
|> 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<TelegramAcco
#endif
if #available(iOS 13.0, *) {
let taskId = "\(baseAppBundleId).cleanup"
let cleanupTaskId = "\(baseAppBundleId).cleanup"
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskId, using: DispatchQueue.main) { task in
BGTaskScheduler.shared.register(forTaskWithIdentifier: cleanupTaskId, using: DispatchQueue.main) { task in
Logger.shared.log("App \(self.episodeId)", "Executing cleanup task")
let disposable = self.runCacheReindexTasks(lowImpact: true, completion: {
@ -1575,11 +1550,11 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
BGTaskScheduler.shared.getPendingTaskRequests(completionHandler: { tasks in
if tasks.contains(where: { $0.identifier == taskId }) {
if tasks.contains(where: { $0.identifier == cleanupTaskId }) {
Logger.shared.log("App \(self.episodeId)", "Already have a cleanup task pending")
return
}
let request = BGProcessingTaskRequest(identifier: taskId)
let request = BGProcessingTaskRequest(identifier: cleanupTaskId)
request.requiresExternalPower = true
request.requiresNetworkConnectivity = false

View file

@ -2186,10 +2186,9 @@ extension ChatControllerImpl {
let chatLocationPeerId = chatLocation.peerId
if let chatLocationPeerId = chatLocationPeerId {
hasPendingMessages = context.account.pendingMessageManager.hasPendingMessages
|> mapToSignal { peerIds -> Signal<Bool, NoError> in
let value = peerIds.contains(chatLocationPeerId)
if value {
hasPendingMessages = context.account.pendingMessageManager.pendingMessageCount
|> mapToSignal { pendingMessageCount -> Signal<Bool, NoError> in
if let value = pendingMessageCount[chatLocationPeerId], value != 0 {
return .single(true)
} else {
return .single(false)

View file

@ -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<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
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))
}