diff --git a/ApplicationLibrary/Service/ProfileUpdateTask.swift b/ApplicationLibrary/Service/ProfileUpdateTask.swift index f6a2dd2..b0fc30c 100644 --- a/ApplicationLibrary/Service/ProfileUpdateTask.swift +++ b/ApplicationLibrary/Service/ProfileUpdateTask.swift @@ -2,44 +2,59 @@ import Foundation import Library public enum ProfileUpdateTask { + static let minUpdateInterval: TimeInterval = 15 * 60 + static let defaultUpdateInterval: TimeInterval = 60 * 60 + private static var timer: Timer? - public static func setup() throws { - var earliestBeginDate: Date? - if let updatedAt = try oldestUpdated() { - if updatedAt > Date(timeIntervalSinceNow: -taskInterval) { - earliestBeginDate = updatedAt.addingTimeInterval(taskInterval) - } + public static func configure() async throws { + timer?.invalidate() + timer = nil + let profiles = try await ProfileManager.listAutoUpdateEnabled() + if profiles.isEmpty { + return } - timer = Timer(fire: earliestBeginDate ?? Date.now, interval: taskInterval, repeats: true, block: { _ in - do { - _ = try updateProfiles() - NSLog("profile update task succeed") - } catch { - NSLog("profile update task failed: \(error.localizedDescription)") + var updateInterval = profiles.map { it in + it.autoUpdateIntervalOrDefault + }.min()! + if updateInterval < minUpdateInterval { + updateInterval = minUpdateInterval + } + timer = Timer(fire: calculateEarliestBeginDate(profiles), interval: updateInterval, repeats: true, block: { _ in + Task { + await getAndupdateProfiles() } }) } - static let taskInterval: TimeInterval = 15 * 60 - - static func oldestUpdated() throws -> Date? { - let profiles = try ProfileManager.listAutoUpdateEnabled() - return profiles.map { profile in - profile.lastUpdated! + static func calculateEarliestBeginDate(_ profiles: [Profile]) -> Date { + let nowTime = Date.now + var earliestBeginDate = profiles.map { it in + it.lastUpdated!.addingTimeInterval(it.autoUpdateIntervalOrDefault) + }.min()! + if earliestBeginDate <= nowTime { + earliestBeginDate = nowTime } - .min() + return earliestBeginDate } - static func updateProfiles() throws -> Bool { - let profiles = try ProfileManager.listAutoUpdateEnabled() + private nonisolated static func getAndupdateProfiles() async { + do { + _ = try await updateProfiles(ProfileManager.listAutoUpdateEnabled()) + NSLog("profile update task succeed") + } catch { + NSLog("profile update task failed: \(error.localizedDescription)") + } + } + + static func updateProfiles(_ profiles: [Profile]) async throws -> Bool { var success = true for profile in profiles { - if profile.lastUpdated! > Date(timeIntervalSinceNow: -taskInterval) { + if profile.lastUpdated! > Date(timeIntervalSinceNow: -profile.autoUpdateIntervalOrDefault) { continue } do { - try profile.updateRemoteProfile() + try await profile.updateRemoteProfile() } catch { NSLog("Update profile \(profile.name) failed: \(error.localizedDescription)") success = false @@ -48,3 +63,13 @@ public enum ProfileUpdateTask { return success } } + +extension Profile { + var autoUpdateIntervalOrDefault: TimeInterval { + if autoUpdateInterval > 0 { + return TimeInterval(autoUpdateInterval * 60) + } else { + return ProfileUpdateTask.defaultUpdateInterval + } + } +} diff --git a/ApplicationLibrary/Service/UIProfileUpdateTask.swift b/ApplicationLibrary/Service/UIProfileUpdateTask.swift index f6fc608..1ec272c 100644 --- a/ApplicationLibrary/Service/UIProfileUpdateTask.swift +++ b/ApplicationLibrary/Service/UIProfileUpdateTask.swift @@ -4,37 +4,49 @@ import Library #if os(iOS) || os(tvOS) public class UIProfileUpdateTask: BGAppRefreshTask { - public static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles" + private static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles" - public static func setup() async throws { + public static func configure() async throws { let success = BGTaskScheduler.shared.register(forTaskWithIdentifier: taskSchedulerPermittedIdentifier, using: nil) { task in NSLog("profile update task started") - do { - let success = try ProfileUpdateTask.updateProfiles() - try? scheduleUpdate(Date(timeIntervalSinceNow: ProfileUpdateTask.taskInterval)) - task.setTaskCompleted(success: success) - NSLog("profile update task succeed") - } catch { - try? scheduleUpdate(nil) - task.setTaskCompleted(success: false) - NSLog("profile update task failed: \(error.localizedDescription)") - } - task.expirationHandler = { - try? scheduleUpdate(nil) - NSLog("profile update task expired") + Task { + await getAndupdateProfiles(task) } } if !success { throw NSError(domain: "register failed", code: 0) } - if await BGTaskScheduler.shared.pendingTaskRequests().isEmpty { - var earliestBeginDate: Date? = nil - if let updatedAt = try ProfileUpdateTask.oldestUpdated() { - if updatedAt > Date(timeIntervalSinceNow: -ProfileUpdateTask.taskInterval) { - earliestBeginDate = updatedAt.addingTimeInterval(ProfileUpdateTask.taskInterval) - } - } - try scheduleUpdate(earliestBeginDate) + BGTaskScheduler.shared.cancelAllTaskRequests() + let profiles = try await ProfileManager.listAutoUpdateEnabled() + if profiles.isEmpty { + return + } + try scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles)) + } + + private nonisolated static func getAndupdateProfiles(_ task: BGTask) async { + let profiles: [Profile] + do { + profiles = try await ProfileManager.listAutoUpdateEnabled() + } catch { + return + } + if profiles.isEmpty { + return + } + do { + let success = try await ProfileUpdateTask.updateProfiles(profiles) + try? scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles)) + task.setTaskCompleted(success: success) + NSLog("profile update task succeed") + } catch { + try? scheduleUpdate(nil) + task.setTaskCompleted(success: false) + NSLog("profile update task failed: \(error.localizedDescription)") + } + task.expirationHandler = { + try? scheduleUpdate(nil) + NSLog("profile update task expired") } } diff --git a/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift b/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift index f3d782d..ea6e18f 100644 --- a/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift +++ b/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift @@ -9,3 +9,21 @@ public extension Binding { }) } } + +public extension Binding where Value == Int32 { + func stringBinding(defaultValue: Int32) -> Binding { + return Binding { + var intValue = wrappedValue + if intValue == 0 { + intValue = defaultValue + } + return String(intValue) + } set: { newValue in + var newIntValue = Int32(newValue) ?? defaultValue + if newIntValue == 0 { + newIntValue = defaultValue + } + wrappedValue = newIntValue + } + } +} diff --git a/ApplicationLibrary/Views/Abstract/Formtem.swift b/ApplicationLibrary/Views/Abstract/Formtem.swift index e20f053..00a34bb 100644 --- a/ApplicationLibrary/Views/Abstract/Formtem.swift +++ b/ApplicationLibrary/Views/Abstract/Formtem.swift @@ -27,6 +27,8 @@ public func FormItem(_ title: String, @ViewBuilder content: () -> some View) -> #if os(iOS) || os(tvOS) HStack { Text(title) + .lineLimit(1) + .layoutPriority(1) Spacer() Spacer() content() diff --git a/ApplicationLibrary/Views/Abstract/ShareButton.swift b/ApplicationLibrary/Views/Abstract/ShareButton.swift index 69f71cf..6a2ce35 100644 --- a/ApplicationLibrary/Views/Abstract/ShareButton.swift +++ b/ApplicationLibrary/Views/Abstract/ShareButton.swift @@ -7,6 +7,7 @@ import SwiftUI import AppKit #endif +@MainActor public struct ProfileShareButton