ReadKeep/readeck/Data/Repository/OfflineSyncManager.swift
Ilyas Hallak a227c275f3 feat: Implement extended font system and offline sync improvements
- Add 10 new fonts (Literata, Merriweather, Source Serif, Lato, Montserrat, Source Sans)
- Support Apple system fonts and Google Fonts (OFL 1.1 licensed)
- Extend FontFamily enum with new fonts and categories
- Update FontSettingsViewModel and WebView with font support
- Force WebView reload when font settings change
- Refactor OfflineSyncManager with protocol and improved error handling
- Add test mocks and OfflineSyncManagerTests with 9 test cases
- Add OpenSourceLicensesView and FontDebugView
- Bump build number
- Update localization strings
2025-12-10 21:25:39 +01:00

132 lines
4.0 KiB
Swift

import Foundation
import CoreData
import SwiftUI
protocol POfflineSyncManager {
func syncOfflineBookmarks() async
func getOfflineBookmarks() -> [ArticleURLEntity]
func deleteOfflineBookmark(_ entity: ArticleURLEntity)
}
open class OfflineSyncManager: ObservableObject, @unchecked Sendable {
static let shared = OfflineSyncManager()
@Published var isSyncing = false
@Published var syncStatus: String?
private let coreDataManager = CoreDataManager.shared
private let api: PAPI
init(api: PAPI = API()) {
self.api = api
}
// MARK: - Sync Methods
func syncOfflineBookmarks() async {
await MainActor.run {
isSyncing = true
syncStatus = "Syncing bookmarks with server..."
}
let offlineBookmarks = getOfflineBookmarks()
guard !offlineBookmarks.isEmpty else {
await MainActor.run {
isSyncing = false
syncStatus = "No bookmarks to sync"
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.syncStatus = nil
}
return
}
var successCount = 0
var failedCount = 0
for bookmark in offlineBookmarks {
guard let url = bookmark.url else {
failedCount += 1
continue
}
let tags = bookmark.tags?.components(separatedBy: ",").filter { !$0.isEmpty } ?? []
let title = bookmark.title ?? ""
do {
let dto = CreateBookmarkRequestDto(url: url, title: title, labels: tags.isEmpty ? nil : tags)
_ = try await api.createBookmark(createRequest: dto)
deleteOfflineBookmark(bookmark)
successCount += 1
await MainActor.run {
syncStatus = "Synced \(successCount) bookmarks..."
}
} catch {
print("Failed to sync bookmark: \(url) - \(error)")
failedCount += 1
// If first sync attempt fails, server is likely unreachable - abort
if successCount == 0 && failedCount == 1 {
await MainActor.run {
isSyncing = false
syncStatus = "Server not reachable. Cannot sync."
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.syncStatus = nil
}
return
}
}
}
await MainActor.run {
isSyncing = false
if successCount > 0 {
if failedCount == 0 {
syncStatus = "✅ Successfully synced \(successCount) bookmarks"
} else {
syncStatus = "⚠️ Synced \(successCount), failed \(failedCount) bookmarks"
}
} else if failedCount > 0 {
syncStatus = "❌ Sync failed - check your connection"
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.syncStatus = nil
}
}
func getOfflineBookmarksCount() -> Int {
return getOfflineBookmarks().count
}
open func getOfflineBookmarks() -> [ArticleURLEntity] {
do {
let fetchRequest: NSFetchRequest<ArticleURLEntity> = ArticleURLEntity.fetchRequest()
return try coreDataManager.context.safeFetch(fetchRequest)
} catch {
print("Failed to fetch offline bookmarks: \(error)")
return []
}
}
open func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
do {
try coreDataManager.context.safePerform { [weak self] in
guard let self = self else { return }
self.coreDataManager.context.delete(entity)
self.coreDataManager.save()
}
} catch {
print("Failed to delete offline bookmark: \(error)")
}
}
}