Skip to content

Commit

Permalink
Migrate background update to use BGTaskScheduler (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
sitomani committed Oct 16, 2023
1 parent cc2649d commit 7d49b2c
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 57 deletions.
4 changes: 4 additions & 0 deletions 4champ.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
7397C1E729091D9D002D5CDB /* uade_ios.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7397C1E129091BC3002D5CDB /* uade_ios.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
739B4BF42720850200C2D69F /* RadioSessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739B4BF32720850200C2D69F /* RadioSessionCell.swift */; };
73CE92E52AAF03D1000BFC1C /* PlaylistPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73CE92E42AAF03D1000BFC1C /* PlaylistPresenter.swift */; };
73E4FBF02ADC4D01009A985A /* RefreshLatestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73E4FBEF2ADC4D01009A985A /* RefreshLatestOperation.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -228,6 +229,7 @@
7397C1E129091BC3002D5CDB /* uade_ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = uade_ios.xcframework; path = "../uade-ios/uade_ios.xcframework"; sourceTree = "<group>"; };
739B4BF32720850200C2D69F /* RadioSessionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSessionCell.swift; sourceTree = "<group>"; };
73CE92E42AAF03D1000BFC1C /* PlaylistPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaylistPresenter.swift; sourceTree = "<group>"; };
73E4FBEF2ADC4D01009A985A /* RefreshLatestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshLatestOperation.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -511,6 +513,7 @@
45D61F5A20E1780D00869814 /* Appearance.swift */,
45855FCC2444AC6B0079C767 /* ModuleSharing.swift */,
45B5FC25255D997000282550 /* ReviewActions.swift */,
73E4FBEF2ADC4D01009A985A /* RefreshLatestOperation.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -726,6 +729,7 @@
45AA910620FF36C200794147 /* MPTReplayer.m in Sources */,
451B6AA72204BBBB001848FA /* ModuleStorage.swift in Sources */,
4543983620D0344F00991ADC /* AboutInteractor.swift in Sources */,
73E4FBF02ADC4D01009A985A /* RefreshLatestOperation.swift in Sources */,
451B6AA32204B6CC001848FA /* LocalModels.swift in Sources */,
45AA7AED2101BDB10096D7E7 /* ModulePlayer.swift in Sources */,
456E6F2E240A5FAF0034BDA2 /* PlaylistView.swift in Sources */,
Expand Down
108 changes: 51 additions & 57 deletions 4champ/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AVFoundation
import Alamofire
import UserNotifications
import SwiftUI
import BackgroundTasks

// Global
let modulePlayer = ModulePlayer()
Expand All @@ -22,11 +23,9 @@ let shareUtil = ShareUtility()
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

private var _bgFetchCallback: ((UIBackgroundFetchResult) -> Void)?

private var sharedMod: MMD?

private lazy var dlController: DownloadController = DownloadController()
private var bgQueue = OperationQueue()

var window: UIWindow?

Expand All @@ -35,22 +34,35 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Appearance.setup()
setupLogging()
setupAVSession()
setupBackgroundTask()
cleanupFiles()

// UNCOMMENT BELOW TWO LINES TO TEST LOCAL NOTIFICATIONS
// settings.prevCollectionSize = 0
// settings.newestPlayed = 152890

updateLatest()
// Refresh the latest
bgQueue.maxConcurrentOperationCount = 1
bgQueue.addOperation(RefershLatestOperation())
UIApplication.shared.beginReceivingRemoteControlEvents()

#if DEBUG
#if DEBUG
ReviewActions.reset()
#endif

application.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
#endif
return true
}

func setupBackgroundTask() {
let scheduled = BGTaskScheduler.shared.register(forTaskWithIdentifier: "fourchamp.latest.refresh", using: nil) { task in
log.debug("BG Task triggered")
self.handleBackgroundTask(task: task)
}
if scheduled {
log.debug("Task scheduled")
scheduleAppRefresh()
}
}

func setupAVSession() {
let sess = AVAudioSession.sharedInstance()
do {
Expand All @@ -77,20 +89,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
modulePlayer.cleanup()
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
log.debug("performFetch")
_bgFetchCallback = completionHandler
updateLatest()
}

func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}

if components.path == "/mod", let idString = components.queryItems?.first?.value, let modId = Int(idString) {
Expand All @@ -116,6 +122,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

func scheduleAppRefresh() {
let taskRequest = BGAppRefreshTaskRequest(identifier: "fourchamp.latest.refresh")
taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: 5*60)
do {
try BGTaskScheduler.shared.submit(taskRequest)
} catch {
print("Unable to schedule app refresh task: \(error)")
}
}

func handleBackgroundTask(task: BGTask) {
log.debug("")
scheduleAppRefresh()

let operation = RefershLatestOperation()
bgQueue.addOperation(operation)

task.expirationHandler = {
operation.cancel()
}

operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
}

/// Set up SwiftyBeaver logging
func setupLogging() {
let console = ConsoleDestination() // log to Xcode Console
Expand All @@ -129,9 +161,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
log.addDestination(console)

console.minLevel = .warning
#if DEBUG
#if DEBUG
console.minLevel = .debug
#endif
#endif
log.info("Logger initialized")
}

Expand All @@ -148,42 +180,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
log.error("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
}

func updateLatest() {
log.debug("")
let req = RESTRoutes.latestId
AF.request(req).validate().responseString { resp in
switch resp.result {
case .failure:
self._bgFetchCallback?(.noData)
self._bgFetchCallback = nil
case .success(let str):
guard let collectionSize = Int(str) else { return }
log.info("Collection Size: \(collectionSize)")
self.updateCollectionSize(size: collectionSize)
}
}
}

func updateCollectionSize(size: Int) {
log.debug("")
settings.collectionSize = size
let prevSize = settings.prevCollectionSize

// Only fire the request once per a given collectionSize/diff
if prevSize < size && settings.badgeCount < Constants.maxBadgeValue {
let fmt = "Radio_Notification".l13n()
let content = UNMutableNotificationContent()
content.body = String.init(format: fmt, "\(settings.badgeCount)")
content.categoryIdentifier = "newmodules"
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 0.1, repeats: false)
let req = UNNotificationRequest.init(identifier: "newmodules-usernotif", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
settings.prevCollectionSize = settings.collectionSize
_bgFetchCallback?(.newData)
} else {
_bgFetchCallback?(.noData)
}
_bgFetchCallback = nil
}
}
4 changes: 4 additions & 0 deletions 4champ/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http:https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>fourchamp.latest.refresh</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand Down
91 changes: 91 additions & 0 deletions 4champ/Utils/RefreshLatestOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// BackgroundFetchOp.swift
// ampplayer
//
// Created by Aleksi Sitomaniemi on 15.10.2023.
// Copyright © 2023 Aleksi Sitomaniemi. All rights reserved.
//

import Foundation
import NotificationCenter
import Alamofire

class RefershLatestOperation: Operation {
private var _executing = false
private var _finished = false

override private(set) var isExecuting: Bool {
get {
return _executing
}
set {
willChangeValue(forKey: "isExecuting")
_executing = newValue
didChangeValue(forKey: "isExecuting")
}
}

override private(set) var isFinished: Bool {
get {
return _finished
}
set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}

override func start() {
if isCancelled {
isFinished = true
return
}

isExecuting = true
main() // Call your main method to perform the task
}

override func main() {
// Send a REST request to refresh app contents
log.debug("")
let req = RESTRoutes.latestId
AF.request(req).validate().responseString { resp in
switch resp.result {
case .failure:
self.cancel()
case .success(let str):
guard let collectionSize = Int(str) else {
self.completeOperation()
return
}
log.info("Collection Size: \(collectionSize)")
self.updateCollectionSize(size: collectionSize)
self.completeOperation()
}
}
}

private func completeOperation() {
isExecuting = false
isFinished = true
}

func updateCollectionSize(size: Int) {
log.debug("")
settings.collectionSize = size
let prevSize = settings.prevCollectionSize

// Only fire the request once per a given collectionSize/diff
if prevSize < size && settings.badgeCount < Constants.maxBadgeValue {
let fmt = "Radio_Notification".l13n()
let content = UNMutableNotificationContent()
content.body = String.init(format: fmt, "\(settings.badgeCount)")
content.categoryIdentifier = "newmodules"
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 0.1, repeats: false)
let req = UNNotificationRequest.init(identifier: "newmodules-usernotif", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
settings.prevCollectionSize = settings.collectionSize
}
}
}

0 comments on commit 7d49b2c

Please sign in to comment.