Merge pull request #1144 from kiwix/1107-liveactivities-backgroundprocess

Handle background downloads to update live activities
This commit is contained in:
Kelson 2025-03-22 21:22:20 +01:00 committed by GitHub
commit 4d45603da0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 3 deletions

View File

@ -17,6 +17,8 @@
import SwiftUI
import Combine
import UserNotifications
import BackgroundTasks
import os
@main
struct Kiwix: App {
@ -33,7 +35,7 @@ struct Kiwix: App {
// MARK: - live activities
switch AppType.current {
case .kiwix:
activityService = ActivityService()
activityService = ActivityService.shared()
case .custom:
activityService = nil
}
@ -114,12 +116,69 @@ struct Kiwix: App {
}
private class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
/// Storing background download completion handler sent to application delegate
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
DownloadService.shared.backgroundCompletionHandler = completionHandler
}
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
registerBackgroundTask()
return true
}
// Background download task
func applicationDidFinishLaunching(_ application: UIApplication) {
registerBackgroundTask()
}
func applicationDidEnterBackground(_ application: UIApplication) {
registerBackgroundTask()
}
private func registerBackgroundTask() {
guard case .kiwix = AppType.current else { return }
let isRegistered = BGTaskScheduler.shared.register(
forTaskWithIdentifier: BackgroundDownloads.identifier,
using: .main) { [self] _ in
// update the live activities, if any
ActivityService.shared().start()
// reschedule
reScheduleBackgroundDownloadTask()
}
if isRegistered {
os_log("BackgroundDownloads registered", log: Log.DownloadService, type: .debug)
} else {
os_log("BackgroundDownloads registering failed: %s", log: Log.DownloadService, type: .error)
}
}
private func reScheduleBackgroundDownloadTask() {
do {
let date = BackgroundDownloads.nextDate()
let request = BGAppRefreshTaskRequest(identifier: BackgroundDownloads.identifier)
request.earliestBeginDate = date
os_log(
"BackgroundDownloads task re-scheduled for: %s",
log: Log.DownloadService,
type: .debug,
date.formatted()
)
try BGTaskScheduler.shared.submit(request)
} catch {
os_log(
"BackgroundDownloads re-schedule failed: %s",
log: Log.DownloadService,
type: .error,
error.localizedDescription
)
}
}
/// Handling file download complete notification
func userNotificationCenter(_ center: UNUserNotificationCenter,

View File

@ -0,0 +1,28 @@
// This file is part of Kiwix for iOS & macOS.
//
// Kiwix is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// any later version.
//
// Kiwix is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
#if os(iOS)
import Foundation
import BackgroundTasks
enum BackgroundDownloads {
static let identifier = "org.kiwix.downloads_to_liveactivity"
static func nextDate() -> Date {
.now + 2 // after 2 seconds
}
}
#endif

View File

@ -6,7 +6,7 @@
<string>id997079563</string>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>org.kiwix.library_refresh</string>
<string>org.kiwix.downloads_to_liveactivity</string>
</array>
<key>CFBundleDocumentTypes</key>
<array>

View File

@ -22,6 +22,7 @@ import QuartzCore
@MainActor
final class ActivityService {
private static var instance: ActivityService?
private var cancellables = Set<AnyCancellable>()
private var activity: Activity<DownloadActivityAttributes>?
private var lastUpdate = CACurrentMediaTime()
@ -31,12 +32,30 @@ final class ActivityService {
private var isStarted: Bool = false
private var downloadTimes: [UUID: DownloadTime] = [:]
init(
public static func shared(
publisher: @MainActor @escaping () -> CurrentValueSubject<[UUID: DownloadState], Never> = {
DownloadService.shared.progress.publisher
},
updateFrequency: Double = 2,
averageDownloadSpeedFromLastSeconds: Double = 30
) -> ActivityService {
if let instance = Self.instance {
return instance
} else {
let instance = ActivityService(
publisher: publisher,
updateFrequency: updateFrequency,
averageDownloadSpeedFromLastSeconds: averageDownloadSpeedFromLastSeconds
)
Self.instance = instance
return instance
}
}
private init(
publisher: @MainActor @escaping () -> CurrentValueSubject<[UUID: DownloadState], Never>,
updateFrequency: Double,
averageDownloadSpeedFromLastSeconds: Double
) {
assert(updateFrequency > 0)
assert(averageDownloadSpeedFromLastSeconds > 0)