- 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>
194 lines
6.6 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|