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))
}