112 lines
4.1 KiB
Swift
112 lines
4.1 KiB
Swift
//
|
|
// SyncController.swift
|
|
// MastoSearchCore
|
|
//
|
|
// Created by Shadowfacts on 7/3/22.
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import OSLog
|
|
|
|
public class SyncController {
|
|
private init() {}
|
|
|
|
public static let shared = SyncController()
|
|
|
|
public let onSync = PassthroughSubject<Void, Never>()
|
|
|
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Sync")
|
|
private var syncTotal = 0
|
|
|
|
private let dateFormatter = {
|
|
let f = ISO8601DateFormatter()
|
|
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
return f
|
|
}()
|
|
|
|
public func syncStatuses(errorHandler: @escaping (APIController.Error) -> Void) {
|
|
DatabaseController.shared.getNewestAndOldestStatuses { results in
|
|
if let results {
|
|
self.logger.log("Starting sync...")
|
|
self.syncTotal = 0
|
|
self.syncStatuses(direction: .newer, range: .after(results.0.id), errorHandler: errorHandler)
|
|
self.syncStatuses(direction: .older, range: .before(results.1.id), errorHandler: errorHandler)
|
|
} else {
|
|
self.logger.log("No newest, starting backwards sync...")
|
|
self.syncTotal = 0
|
|
self.syncStatuses(direction: .older, range: .default, errorHandler: errorHandler)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func syncStatuses(direction: Direction, range: APIController.RequestRange, errorHandler: @escaping (APIController.Error) -> Void) {
|
|
APIController.shared.getStatuses(range: range) { response in
|
|
switch response {
|
|
case .failure(let error):
|
|
self.logger.error("Error syncing statuses: \(String(describing: error), privacy: .public)")
|
|
if case .unexpectedStatusCode(_, let resp) = error,
|
|
resp.value(forHTTPHeaderField: "x-ratelimit-remaining") == "0",
|
|
let reset = resp.value(forHTTPHeaderField: "x-ratelimit-reset"),
|
|
let date = self.dateFormatter.date(from: reset) {
|
|
self.logger.info("Rate limited, continuing at \(date)")
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(Int(ceil(date.timeIntervalSinceNow)))) {
|
|
self.syncStatuses(direction: direction, range: range, errorHandler: errorHandler)
|
|
}
|
|
} else {
|
|
DispatchQueue.main.async {
|
|
errorHandler(error)
|
|
}
|
|
}
|
|
|
|
case .success(let statuses):
|
|
guard statuses.count > 0 else {
|
|
DispatchQueue.main.async {
|
|
self.logger.log("Finished sync of \(self.syncTotal, privacy: .public) \(direction.name) statuses")
|
|
self.onSync.send()
|
|
}
|
|
return
|
|
}
|
|
|
|
DatabaseController.shared.addStatuses(statuses.compactMap {
|
|
if $0.hasReblog {
|
|
return nil
|
|
} else {
|
|
return Status(id: $0.id, url: $0.url, summary: $0.spoiler_text, content: $0.content, published: $0.created_at)
|
|
}
|
|
})
|
|
DispatchQueue.main.async {
|
|
self.onSync.send()
|
|
}
|
|
|
|
self.syncTotal += statuses.count
|
|
|
|
self.syncStatuses(direction: direction, range: direction.nextRange(statuses: statuses), errorHandler: errorHandler)
|
|
}
|
|
}
|
|
}
|
|
|
|
enum Direction {
|
|
case newer, older
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .newer:
|
|
return "newer"
|
|
case .older:
|
|
return "older"
|
|
}
|
|
}
|
|
|
|
func nextRange(statuses: [APIController.Status]) -> APIController.RequestRange {
|
|
switch self {
|
|
case .newer:
|
|
return .after(statuses.first!.id)
|
|
case .older:
|
|
return .before(statuses.last!.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|