@@ -31,25 +31,38 @@ public final class WebFilterListUpdateScheduler {
3131 private let log = Logger ( subsystem: " MaruWeb " , category: " filter-list-scheduler " )
3232 private let downloader : WebFilterListDownloader
3333 private let storage : WebFilterListStorage
34+ private let submitRequest : ( BGTaskRequest ) throws -> Void
3435 private var didRegister = false
3536 private var inFlight : Task < Void , Never > ?
3637
37- public init (
38+ public convenience init (
3839 downloader: WebFilterListDownloader = . shared,
3940 storage: WebFilterListStorage = . shared
41+ ) {
42+ self . init (
43+ downloader: downloader,
44+ storage: storage,
45+ submitRequest: { try BGTaskScheduler . shared. submit ( $0) }
46+ )
47+ }
48+
49+ init (
50+ downloader: WebFilterListDownloader ,
51+ storage: WebFilterListStorage ,
52+ submitRequest: @escaping ( BGTaskRequest ) throws -> Void
4053 ) {
4154 self . downloader = downloader
4255 self . storage = storage
56+ self . submitRequest = submitRequest
4357 }
4458
4559 /// Registers the BGAppRefreshTask handler. Must be called from the host app's `init`
4660 /// (or before scene activation), exactly once per process.
4761 public func registerBackgroundTask( ) {
4862 guard !didRegister else { return }
49- didRegister = true
50- BGTaskScheduler . shared. register (
63+ didRegister = BGTaskScheduler . shared. register (
5164 forTaskWithIdentifier: WebContentBlocker . backgroundRefreshTaskIdentifier,
52- using: nil
65+ using: DispatchQueue . main
5366 ) { [ weak self] task in
5467 guard let task = task as? BGAppRefreshTask else {
5568 task. setTaskCompleted ( success: false )
@@ -59,6 +72,9 @@ public final class WebFilterListUpdateScheduler {
5972 await self ? . handle ( backgroundTask: task)
6073 }
6174 }
75+ if !didRegister {
76+ log. error ( " Failed to register background refresh task " )
77+ }
6278 }
6379
6480 /// Triggers a refresh if the most recent successful fetch across enabled lists is
@@ -70,15 +86,22 @@ public final class WebFilterListUpdateScheduler {
7086
7187 /// Manually triggers a refresh. UI calls this from the "Update Now" button.
7288 public func refreshNow( ) {
73- if inFlight != nil {
74- return
89+ _ = startRefresh ( )
90+ }
91+
92+ @discardableResult
93+ func startRefresh( ) -> Task < Void , Never > {
94+ if let inFlight {
95+ return inFlight
7596 }
76- inFlight = Task { @MainActor [ weak self] in
97+ let task = Task { @MainActor [ weak self] in
7798 guard let self else { return }
99+ defer { self . inFlight = nil }
78100 await self . downloader. refreshAll ( force: false )
79101 self . scheduleNextRefresh ( )
80- self . inFlight = nil
81102 }
103+ inFlight = task
104+ return task
82105 }
83106
84107 /// Submits the next BGAppRefreshTask request. Safe to call repeatedly.
@@ -88,7 +111,7 @@ public final class WebFilterListUpdateScheduler {
88111 )
89112 request. earliestBeginDate = nextEligibleDate ( now: now)
90113 do {
91- try BGTaskScheduler . shared . submit ( request)
114+ try submitRequest ( request)
92115 } catch {
93116 log. error ( " Failed to schedule background refresh: \( String ( describing: error) , privacy: . public) " )
94117 }
@@ -99,10 +122,10 @@ public final class WebFilterListUpdateScheduler {
99122 private func handle( backgroundTask: BGAppRefreshTask ) async {
100123 // Always reschedule first so we keep ticking even if this run is curtailed.
101124 scheduleNextRefresh ( )
125+ storage. start ( )
126+ storage. seedDefaultsIfNeeded ( )
102127
103- let task = Task { @MainActor [ weak self] in
104- _ = await self ? . downloader. refreshAll ( force: false )
105- }
128+ let task = startRefresh ( )
106129 backgroundTask. expirationHandler = {
107130 task. cancel ( )
108131 }
0 commit comments