Files
RSSuper/native-route/ios/RSSuper/SyncScheduler.swift
Michael Freno 14efe072fa feat: implement cross-platform features and UI integration
- iOS: Add BackgroundSyncService, SyncScheduler, SyncWorker, BookmarkViewModel, FeedViewModel
- iOS: Add BackgroundSyncService, SyncScheduler, SyncWorker services
- Linux: Add settings-store.vala, State.vala signals, view widgets (FeedList, FeedDetail, AddFeed, Search, Settings, Bookmark)
- Linux: Add bookmark-store.vala, bookmark vala model, search-service.vala
- Android: Add NotificationService, NotificationManager, NotificationPreferencesStore
- Android: Add BookmarkDao, BookmarkRepository, SettingsStore
- Add unit tests for iOS, Android, Linux
- Add integration tests
- Add performance benchmarks
- Update tasks and documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 23:06:12 -04:00

194 lines
6.6 KiB
Swift

import Foundation
import BackgroundTasks
/// Manages background sync scheduling
/// Handles intelligent scheduling based on user behavior and system conditions
final class SyncScheduler {
// MARK: - Properties
/// Default sync interval (in seconds)
static let defaultSyncInterval: TimeInterval = 6 * 3600 // 6 hours
/// Minimum sync interval (in seconds)
static let minimumSyncInterval: TimeInterval = 15 * 60 // 15 minutes
/// Maximum sync interval (in seconds)
static let maximumSyncInterval: TimeInterval = 24 * 3600 // 24 hours
/// Key for storing last sync date in UserDefaults
private static let lastSyncDateKey = "RSSuperLastSyncDate"
/// Key for storing preferred sync interval
private static let preferredSyncIntervalKey = "RSSuperPreferredSyncInterval"
/// UserDefaults for persisting sync state
private let userDefaults = UserDefaults.standard
// MARK: - Computed Properties
/// Last sync date from UserDefaults
var lastSyncDate: Date? {
get { userDefaults.object(forKey: Self.lastSyncDateKey) as? Date }
set { userDefaults.set(newValue, forKey: Self.lastSyncDateKey) }
}
/// Preferred sync interval from UserDefaults
var preferredSyncInterval: TimeInterval {
get {
return userDefaults.double(forKey: Self.preferredSyncIntervalKey)
?? Self.defaultSyncInterval
}
set {
let clamped = max(Self.minimumSyncInterval, min(newValue, Self.maximumSyncInterval))
userDefaults.set(clamped, forKey: Self.preferredSyncIntervalKey)
}
}
/// Time since last sync
var timeSinceLastSync: TimeInterval {
guard let lastSync = lastSyncDate else {
return .greatestFiniteMagnitude
}
return Date().timeIntervalSince(lastSync)
}
/// Whether a sync is due
var isSyncDue: Bool {
return timeSinceLastSync >= preferredSyncInterval
}
// MARK: - Public Methods
/// Schedule the next sync based on current conditions
func scheduleNextSync() -> Bool {
// Check if we should sync immediately
if isSyncDue && timeSinceLastSync >= preferredSyncInterval * 2 {
print("📱 Sync is significantly overdue, scheduling immediate sync")
return scheduleImmediateSync()
}
// Calculate next sync time
let nextSyncTime = calculateNextSyncTime()
print("📅 Next sync scheduled for: \(nextSyncTime) (in \(nextSyncTime.timeIntervalSinceNow)/3600)h)")
return scheduleSync(at: nextSyncTime)
}
/// Update preferred sync interval based on user behavior
func updateSyncInterval(for numberOfFeeds: Int, userActivityLevel: UserActivityLevel) {
var baseInterval: TimeInterval
// Adjust base interval based on number of feeds
switch numberOfFeeds {
case 0..<10:
baseInterval = 4 * 3600 // 4 hours for small feed lists
case 10..<50:
baseInterval = 6 * 3600 // 6 hours for medium feed lists
case 50..<200:
baseInterval = 12 * 3600 // 12 hours for large feed lists
default:
baseInterval = 24 * 3600 // 24 hours for very large feed lists
}
// Adjust based on user activity
switch userActivityLevel {
case .high:
preferredSyncInterval = baseInterval * 0.5 // Sync more frequently
case .medium:
preferredSyncInterval = baseInterval
case .low:
preferredSyncInterval = baseInterval * 2.0 // Sync less frequently
}
print("⚙️ Sync interval updated to: \(preferredSyncInterval/3600)h (feeds: \(numberOfFeeds), activity: \(userActivityLevel))")
}
/// Get recommended sync interval based on current conditions
func recommendedSyncInterval() -> TimeInterval {
// This could be enhanced with machine learning based on user patterns
return preferredSyncInterval
}
/// Reset sync schedule
func resetSyncSchedule() {
lastSyncDate = nil
preferredSyncInterval = Self.defaultSyncInterval
print("🔄 Sync schedule reset")
}
// MARK: - Private Methods
/// Schedule immediate sync
private func scheduleImmediateSync() -> Bool {
let taskRequest = BGAppRefreshTaskRequest(identifier: BackgroundSyncService.backgroundRefreshIdentifier)
taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: 60) // 1 minute
do {
try BGTaskScheduler.shared.submit(taskRequest)
print("✓ Immediate sync scheduled")
return true
} catch {
print("❌ Failed to schedule immediate sync: \(error)")
return false
}
}
/// Schedule sync at specific time
private func scheduleSync(at date: Date) -> Bool {
let taskRequest = BGAppRefreshTaskRequest(identifier: BackgroundSyncService.backgroundRefreshIdentifier)
taskRequest.earliestBeginDate = date
do {
try BGTaskScheduler.shared.submit(taskRequest)
print("✓ Sync scheduled for \(date)")
return true
} catch {
print("❌ Failed to schedule sync: \(error)")
return false
}
}
/// Calculate next sync time
private func calculateNextSyncTime() -> Date {
let baseTime = lastSyncDate ?? Date()
return baseTime.addingTimeInterval(preferredSyncInterval)
}
}
// MARK: - UserActivityLevel
/// User activity level for adaptive sync scheduling
enum UserActivityLevel: String, Codable {
case high // User actively reading, sync more frequently
case medium // Normal usage
case low // Inactive user, sync less frequently
/// Calculate activity level based on app usage
static func calculate(from dailyOpenCount: Int, lastOpenedAgo: TimeInterval) -> UserActivityLevel {
// High activity: opened 5+ times today OR opened within last hour
if dailyOpenCount >= 5 || lastOpenedAgo < 3600 {
return .high
}
// Medium activity: opened 2+ times today OR opened within last day
if dailyOpenCount >= 2 || lastOpenedAgo < 86400 {
return .medium
}
// Low activity: otherwise
return .low
}
}
extension SyncScheduler {
static var lastSyncDate: Date? {
get {
return UserDefaults.standard.object(forKey: Self.lastSyncDateKey) as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: Self.lastSyncDateKey)
}
}
}