Remove CoreData.Instance Entity Classes (IOS-271)
This commit is contained in:
parent
3db058800b
commit
972426fa50
|
@ -1,80 +0,0 @@
|
|||
//
|
||||
// Instance.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-10-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@available(*, deprecated, message: "Please use `MastodonAuthentication.InstanceConfiguration` instead.")
|
||||
public final class Instance: NSManagedObject {
|
||||
@NSManaged public var domain: String
|
||||
@NSManaged public var version: String?
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
@NSManaged public private(set) var configurationRaw: Data?
|
||||
@NSManaged public private(set) var configurationV2Raw: Data?
|
||||
|
||||
// MARK: one-to-many relationships
|
||||
@NSManaged public var authentications: Set<MastodonAuthenticationLegacy>
|
||||
}
|
||||
|
||||
extension Instance {
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
let now = Date()
|
||||
setPrimitiveValue(now, forKey: #keyPath(Instance.createdAt))
|
||||
setPrimitiveValue(now, forKey: #keyPath(Instance.updatedAt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Instance {
|
||||
let instance: Instance = context.insertObject()
|
||||
instance.domain = property.domain
|
||||
instance.version = property.version
|
||||
return instance
|
||||
}
|
||||
|
||||
public func update(configurationRaw: Data?) {
|
||||
self.configurationRaw = configurationRaw
|
||||
}
|
||||
|
||||
public func update(configurationV2Raw: Data?) {
|
||||
self.configurationV2Raw = configurationV2Raw
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
}
|
||||
|
||||
extension Instance {
|
||||
public struct Property {
|
||||
public let domain: String
|
||||
public let version: String?
|
||||
|
||||
public init(domain: String, version: String?) {
|
||||
self.domain = domain
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Instance: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Instance.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Instance {
|
||||
public static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Instance.domain), domain)
|
||||
}
|
||||
}
|
|
@ -29,11 +29,7 @@ final public class MastodonAuthenticationLegacy: NSManagedObject {
|
|||
@NSManaged public private(set) var activedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var user: MastodonUser
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var instance: Instance?
|
||||
|
||||
@NSManaged public private(set) var user: MastodonUser
|
||||
}
|
||||
|
||||
extension MastodonAuthenticationLegacy {
|
||||
|
@ -101,12 +97,6 @@ extension MastodonAuthenticationLegacy {
|
|||
}
|
||||
}
|
||||
|
||||
public func update(instance: Instance) {
|
||||
if self.instance != instance {
|
||||
self.instance = instance
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ public class AuthenticationServiceProvider: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@discardableResult
|
||||
func updating(instanceV1 instance: Mastodon.Entity.Instance, for domain: String) -> Self {
|
||||
authentications = authentications.map { authentication in
|
||||
|
@ -32,6 +33,7 @@ public class AuthenticationServiceProvider: ObservableObject {
|
|||
return self
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@discardableResult
|
||||
func updating(instanceV2 instance: Mastodon.Entity.V2.Instance, for domain: String) -> Self {
|
||||
authentications = authentications.map { authentication in
|
||||
|
@ -41,6 +43,7 @@ public class AuthenticationServiceProvider: ObservableObject {
|
|||
return self
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@discardableResult
|
||||
func updating(translationLanguages: TranslationLanguages, for domain: String) -> Self {
|
||||
authentications = authentications.map { authentication in
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
//
|
||||
// Instance.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-10-9.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension Instance {
|
||||
public var configuration: Mastodon.Entity.Instance.Configuration? {
|
||||
guard let configurationRaw = configurationRaw else { return nil }
|
||||
guard let configuration = try? JSONDecoder().decode(Mastodon.Entity.Instance.Configuration.self, from: configurationRaw) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
static func encode(configuration: Mastodon.Entity.Instance.Configuration) -> Data? {
|
||||
return try? JSONEncoder().encode(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
extension Instance {
|
||||
public var configurationV2: Mastodon.Entity.V2.Instance.Configuration? {
|
||||
guard
|
||||
let configurationRaw = configurationV2Raw,
|
||||
let configuration = try? JSONDecoder().decode(
|
||||
Mastodon.Entity.V2.Instance.Configuration.self,
|
||||
from: configurationRaw
|
||||
)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
static func encodeV2(configuration: Mastodon.Entity.V2.Instance.Configuration) -> Data? {
|
||||
return try? JSONEncoder().encode(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
public func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool {
|
||||
guard
|
||||
let majorVersionString = split(separator: ".").first,
|
||||
let majorVersionInt = Int(majorVersionString)
|
||||
else { return false }
|
||||
|
||||
return majorVersionInt >= comparedVersion
|
||||
}
|
||||
}
|
|
@ -133,10 +133,12 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier {
|
|||
MastodonUserIdentifier(domain: domain, userID: userID)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updating(instanceV1 instance: Mastodon.Entity.Instance) -> Self {
|
||||
return copy(instanceConfiguration: .v1(instance))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updating(instanceV2 instance: Mastodon.Entity.V2.Instance) -> Self {
|
||||
guard
|
||||
let instanceConfiguration = self.instanceConfiguration,
|
||||
|
@ -147,6 +149,7 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier {
|
|||
return copy(instanceConfiguration: .v2(instance, translationLanguages))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updating(translationLanguages: TranslationLanguages) -> Self {
|
||||
switch self.instanceConfiguration {
|
||||
case .v1(let instance):
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
//
|
||||
// APIService+CoreData+Instance.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-10-9.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService.CoreData {
|
||||
|
||||
static func createOrMergeInstance(
|
||||
into managedObjectContext: NSManagedObjectContext,
|
||||
domain: String,
|
||||
entity: Mastodon.Entity.Instance,
|
||||
networkDate: Date
|
||||
) -> (instance: Instance, isCreated: Bool) {
|
||||
// fetch old mastodon user
|
||||
let old: Instance? = {
|
||||
let request = Instance.sortedFetchRequest
|
||||
request.predicate = Instance.predicate(domain: domain)
|
||||
request.fetchLimit = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
if let old = old {
|
||||
// merge old
|
||||
APIService.CoreData.merge(
|
||||
instance: old,
|
||||
entity: entity,
|
||||
domain: domain,
|
||||
networkDate: networkDate
|
||||
)
|
||||
return (old, false)
|
||||
} else {
|
||||
let instance = Instance.insert(
|
||||
into: managedObjectContext,
|
||||
property: Instance.Property(domain: domain, version: entity.version)
|
||||
)
|
||||
let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
||||
instance.update(configurationRaw: configurationRaw)
|
||||
|
||||
return (instance, true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension APIService.CoreData {
|
||||
|
||||
static func merge(
|
||||
instance: Instance,
|
||||
entity: Mastodon.Entity.Instance,
|
||||
domain: String,
|
||||
networkDate: Date
|
||||
) {
|
||||
guard networkDate > instance.updatedAt else { return }
|
||||
|
||||
let configurationRaw = entity.configuration.flatMap { Instance.encode(configuration: $0) }
|
||||
instance.update(configurationRaw: configurationRaw)
|
||||
instance.version = entity.version
|
||||
|
||||
instance.didUpdate(at: networkDate)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService.CoreData {
|
||||
|
||||
public struct PersistContext {
|
||||
public let domain: String
|
||||
public let entity: Mastodon.Entity.V2.Instance
|
||||
public let networkDate: Date
|
||||
|
||||
public init(
|
||||
domain: String,
|
||||
entity: Mastodon.Entity.V2.Instance,
|
||||
networkDate: Date
|
||||
) {
|
||||
self.domain = domain
|
||||
self.entity = entity
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
}
|
||||
|
||||
static func createOrMergeInstance(
|
||||
in managedObjectContext: NSManagedObjectContext,
|
||||
context: PersistContext
|
||||
) -> (instance: Instance, isCreated: Bool) {
|
||||
// fetch old mastodon user
|
||||
let old: Instance? = {
|
||||
let request = Instance.sortedFetchRequest
|
||||
request.predicate = Instance.predicate(domain: context.domain)
|
||||
request.fetchLimit = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
if let old = old {
|
||||
APIService.CoreData.merge(
|
||||
instance: old,
|
||||
context: context
|
||||
)
|
||||
return (old, false)
|
||||
} else {
|
||||
let instance = Instance.insert(
|
||||
into: managedObjectContext,
|
||||
property: Instance.Property(domain: context.domain, version: context.entity.version)
|
||||
)
|
||||
let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
||||
instance.update(configurationV2Raw: configurationRaw)
|
||||
|
||||
return (instance, true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension APIService.CoreData {
|
||||
|
||||
static func merge(
|
||||
instance: Instance,
|
||||
context: PersistContext
|
||||
) {
|
||||
guard context.networkDate > instance.updatedAt else { return }
|
||||
|
||||
let configurationRaw = context.entity.configuration.flatMap { Instance.encodeV2(configuration: $0) }
|
||||
instance.update(configurationV2Raw: configurationRaw)
|
||||
instance.version = context.entity.version
|
||||
|
||||
instance.didUpdate(at: context.networkDate)
|
||||
}
|
||||
|
||||
}
|
|
@ -34,9 +34,9 @@ public final class InstanceService {
|
|||
.receive(on: DispatchQueue.main)
|
||||
.compactMap { $0.first?.domain }
|
||||
.removeDuplicates() // prevent infinity loop
|
||||
.sink { [weak self] domain in
|
||||
guard let self = self else { return }
|
||||
self.updateInstance(domain: domain)
|
||||
.asyncMap { [weak self] in await self?.updateInstance(domain: $0) }
|
||||
.sink { _ in
|
||||
// no-op
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
@ -44,102 +44,54 @@ public final class InstanceService {
|
|||
}
|
||||
|
||||
extension InstanceService {
|
||||
func updateInstance(domain: String) {
|
||||
|
||||
@MainActor
|
||||
func updateInstance(domain: String) async {
|
||||
guard let apiService else { return }
|
||||
apiService.instance(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.flatMap { [unowned self] response -> AnyPublisher<Void, Error> in
|
||||
if response.value.version?.majorServerVersion(greaterThanOrEquals: 4) == true {
|
||||
return apiService.instanceV2(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.flatMap { return self.updateInstanceV2(domain: domain, response: $0) }
|
||||
.eraseToAnyPublisher()
|
||||
} else {
|
||||
return self.updateInstance(domain: domain, response: response)
|
||||
}
|
||||
|
||||
let response = try? await apiService.instance(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.singleOutput()
|
||||
|
||||
if response?.value.version?.majorServerVersion(greaterThanOrEquals: 4) == true {
|
||||
guard let instanceV2 = try? await apiService.instanceV2(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first).singleOutput() else {
|
||||
return
|
||||
}
|
||||
.sink { [weak self] completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
self?.updateTranslationLanguages(domain: domain)
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let _ = self else { return }
|
||||
// do nothing
|
||||
|
||||
self.updateInstanceV2(domain: domain, response: instanceV2)
|
||||
if let translationResponse = try? await apiService.translationLanguages(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first).singleOutput() {
|
||||
updateTranslationLanguages(domain: domain, response: translationResponse)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
} else if let response {
|
||||
self.updateInstance(domain: domain, response: response)
|
||||
}
|
||||
}
|
||||
|
||||
func updateTranslationLanguages(domain: String) {
|
||||
apiService?.translationLanguages(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.sink(receiveCompletion: { completion in
|
||||
// no-op
|
||||
}, receiveValue: { [weak self] response in
|
||||
self?.updateTranslationLanguages(domain: domain, response: response)
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
|
||||
@MainActor
|
||||
private func updateTranslationLanguages(domain: String, response: Mastodon.Response.Content<TranslationLanguages>) {
|
||||
AuthenticationServiceProvider.shared
|
||||
.updating(translationLanguages: response.value, for: domain)
|
||||
}
|
||||
|
||||
private func updateInstance(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.Instance>) -> AnyPublisher<Void, Error> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
let instanceEntity = response.value
|
||||
return managedObjectContext.performChanges {
|
||||
// get instance
|
||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
||||
into: managedObjectContext,
|
||||
domain: domain,
|
||||
entity: instanceEntity,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
|
||||
// update instance
|
||||
AuthenticationServiceProvider.shared
|
||||
.updating(instanceV1: instanceEntity, for: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
@MainActor
|
||||
private func updateInstance(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.Instance>) {
|
||||
AuthenticationServiceProvider.shared
|
||||
.updating(instanceV1: response.value, for: domain)
|
||||
}
|
||||
|
||||
private func updateInstanceV2(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.V2.Instance>) -> AnyPublisher<Void, Error> {
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
let instanceEntity = response.value
|
||||
return managedObjectContext.performChanges {
|
||||
// get instance
|
||||
let (instance, _) = APIService.CoreData.createOrMergeInstance(
|
||||
in: managedObjectContext,
|
||||
context: .init(
|
||||
domain: domain,
|
||||
entity: instanceEntity,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
|
||||
// update instance
|
||||
@MainActor
|
||||
private func updateInstanceV2(domain: String, response: Mastodon.Response.Content<Mastodon.Entity.V2.Instance>) {
|
||||
AuthenticationServiceProvider.shared
|
||||
.updating(instanceV2: instanceEntity, for: domain)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
.updating(instanceV2: response.value, for: domain)
|
||||
}
|
||||
}
|
||||
|
||||
public extension String {
|
||||
func majorServerVersion(greaterThanOrEquals comparedVersion: Int) -> Bool {
|
||||
guard
|
||||
let majorVersionString = split(separator: ".").first,
|
||||
let majorVersionInt = Int(majorVersionString)
|
||||
else { return false }
|
||||
|
||||
return majorVersionInt >= comparedVersion
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue