fix: server-side data is inconsistent with local

This commit is contained in:
ihugo 2021-04-18 02:02:08 +08:00
parent e42af11bf7
commit 8c7149af89
8 changed files with 212 additions and 125 deletions

View File

@ -155,13 +155,14 @@
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/> <relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
<relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag"/> <relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag"/>
</entity> </entity>
<entity name="Setting" representedClassName="Setting" syncable="YES"> <entity name="Setting" representedClassName=".Setting" syncable="YES">
<attribute name="appearance" optional="YES" attributeType="String"/> <attribute name="appearance" optional="YES" attributeType="String"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" optional="YES" attributeType="String"/> <attribute name="domain" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/> <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="triggerBy" optional="YES" attributeType="String"/> <attribute name="triggerBy" optional="YES" attributeType="String"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="userID" optional="YES" attributeType="String"/>
<relationship name="subscription" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Subscription" inverseName="setting" inverseEntity="Subscription"/> <relationship name="subscription" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Subscription" inverseName="setting" inverseEntity="Subscription"/>
</entity> </entity>
<entity name="Status" representedClassName=".Status" syncable="YES"> <entity name="Status" representedClassName=".Status" syncable="YES">
@ -202,7 +203,7 @@
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/> <relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
<relationship name="tags" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Tag" inverseName="statuses" inverseEntity="Tag"/> <relationship name="tags" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Tag" inverseName="statuses" inverseEntity="Tag"/>
</entity> </entity>
<entity name="Subscription" representedClassName="Subscription" syncable="YES"> <entity name="Subscription" representedClassName=".Subscription" syncable="YES">
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="endpoint" optional="YES" attributeType="String"/> <attribute name="endpoint" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="String"/> <attribute name="id" optional="YES" attributeType="String"/>
@ -212,7 +213,7 @@
<relationship name="alert" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SubscriptionAlerts" inverseName="subscription" inverseEntity="SubscriptionAlerts"/> <relationship name="alert" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SubscriptionAlerts" inverseName="subscription" inverseEntity="SubscriptionAlerts"/>
<relationship name="setting" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Setting" inverseName="subscription" inverseEntity="Setting"/> <relationship name="setting" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Setting" inverseName="subscription" inverseEntity="Setting"/>
</entity> </entity>
<entity name="SubscriptionAlerts" representedClassName="SubscriptionAlerts" syncable="YES"> <entity name="SubscriptionAlerts" representedClassName=".SubscriptionAlerts" syncable="YES">
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="favourite" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="favourite" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="follow" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="follow" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
@ -244,10 +245,10 @@
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/> <element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/> <element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/> <element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
<element name="Setting" positionX="72" positionY="162" width="128" height="149"/>
<element name="Status" positionX="0" positionY="0" width="128" height="569"/> <element name="Status" positionX="0" positionY="0" width="128" height="569"/>
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
<element name="Setting" positionX="72" positionY="162" width="128" height="134"/>
<element name="Subscription" positionX="81" positionY="171" width="128" height="149"/> <element name="Subscription" positionX="81" positionY="171" width="128" height="149"/>
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="149"/> <element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="149"/>
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
</elements> </elements>
</model> </model>

View File

@ -8,11 +8,11 @@
import CoreData import CoreData
import Foundation import Foundation
@objc(Setting)
public final class Setting: NSManagedObject { public final class Setting: NSManagedObject {
@NSManaged public var appearance: String? @NSManaged public var appearance: String?
@NSManaged public var triggerBy: String? @NSManaged public var triggerBy: String?
@NSManaged public var domain: String? @NSManaged public var domain: String?
@NSManaged public var userID: String?
@NSManaged public private(set) var createdAt: Date @NSManaged public private(set) var createdAt: Date
@NSManaged public private(set) var updatedAt: Date @NSManaged public private(set) var updatedAt: Date
@ -40,6 +40,7 @@ public extension Setting {
setting.appearance = property.appearance setting.appearance = property.appearance
setting.triggerBy = property.triggerBy setting.triggerBy = property.triggerBy
setting.domain = property.domain setting.domain = property.domain
setting.userID = property.userID
return setting return setting
} }
@ -61,11 +62,13 @@ public extension Setting {
public let appearance: String public let appearance: String
public let triggerBy: String public let triggerBy: String
public let domain: String public let domain: String
public let userID: String
public init(appearance: String, triggerBy: String, domain: String) { public init(appearance: String, triggerBy: String, domain: String, userID: String) {
self.appearance = appearance self.appearance = appearance
self.triggerBy = triggerBy self.triggerBy = triggerBy
self.domain = domain self.domain = domain
self.userID = userID
} }
} }
} }
@ -77,8 +80,11 @@ extension Setting: Managed {
} }
extension Setting { extension Setting {
public static func predicate(domain: String) -> NSPredicate { public static func predicate(domain: String, userID: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Setting.domain), domain) return NSPredicate(format: "%K == %@ AND %K == %@",
#keyPath(Setting.domain), domain,
#keyPath(Setting.userID), userID
)
} }
} }

View File

@ -9,7 +9,6 @@
import Foundation import Foundation
import CoreData import CoreData
@objc(Subscription)
public final class Subscription: NSManagedObject { public final class Subscription: NSManagedObject {
@NSManaged public var id: String @NSManaged public var id: String
@NSManaged public var endpoint: String @NSManaged public var endpoint: String
@ -95,8 +94,8 @@ extension Subscription: Managed {
extension Subscription { extension Subscription {
public static func predicate(id: String) -> NSPredicate { public static func predicate(type: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Subscription.id), id) return NSPredicate(format: "%K == %@", #keyPath(Subscription.type), type)
} }
} }

View File

@ -20,7 +20,7 @@ public final class SubscriptionAlerts: NSManagedObject {
@NSManaged public private(set) var updatedAt: Date @NSManaged public private(set) var updatedAt: Date
// MARK: - relationships // MARK: - relationships
@NSManaged public var pushSubscription: Subscription? @NSManaged public var subscription: Subscription?
} }
public extension SubscriptionAlerts { public extension SubscriptionAlerts {

View File

@ -46,7 +46,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
UIAction(title: noOne, image: UIImage(systemName: "nosign"), attributes: []) { [weak self] action in UIAction(title: noOne, image: UIImage(systemName: "nosign"), attributes: []) { [weak self] action in
self?.updateTrigger(by: noOne) self?.updateTrigger(by: noOne)
}, },
].reversed() ]
) )
return menu return menu
} }
@ -344,10 +344,15 @@ extension SettingsViewController: UITableViewDelegate {
// Update setting into core data // Update setting into core data
extension SettingsViewController { extension SettingsViewController {
func updateTrigger(by who: String) { func updateTrigger(by who: String) {
guard self.viewModel.triggerBy != who else { return }
guard let setting = self.viewModel.setting.value else { return } guard let setting = self.viewModel.setting.value else { return }
_ = context.managedObjectContext.performChanges { setting.update(triggerBy: who)
setting.update(triggerBy: who) // trigger to call `subscription` API with POST method
// confirm the local data is correct even if request failed
// The asynchronous execution is to solve the problem of dropped frames for animations.
DispatchQueue.main.async { [weak self] in
self?.viewModel.setting.value = setting
} }
} }
@ -356,34 +361,35 @@ extension SettingsViewController {
guard let settings = self.viewModel.setting.value else { return } guard let settings = self.viewModel.setting.value else { return }
guard let triggerBy = settings.triggerBy else { return } guard let triggerBy = settings.triggerBy else { return }
guard let alerts = settings.subscription?.first(where: { (s) -> Bool in if let alerts = settings.subscription?.first(where: { (s) -> Bool in
return s.type == settings.triggerBy return s.type == settings.triggerBy
})?.alert else { })?.alert {
return var alertValues = [Bool?]()
alertValues.append(alerts.favourite?.boolValue)
alertValues.append(alerts.follow?.boolValue)
alertValues.append(alerts.reblog?.boolValue)
alertValues.append(alerts.mention?.boolValue)
// need to update `alerts` to make update API with correct parameter
switch title {
case L10n.Scene.Settings.Section.Notifications.favorites:
alertValues[0] = isOn
alerts.favourite = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.follows:
alertValues[1] = isOn
alerts.follow = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.boosts:
alertValues[2] = isOn
alerts.reblog = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.mentions:
alertValues[3] = isOn
alerts.mention = NSNumber(booleanLiteral: isOn)
default: break
}
self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
} else if let alertValues = self.viewModel.notificationDefaultValue[triggerBy] {
self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
} }
var alertValues = [Bool?]()
alertValues.append(alerts.favourite?.boolValue)
alertValues.append(alerts.follow?.boolValue)
alertValues.append(alerts.reblog?.boolValue)
alertValues.append(alerts.mention?.boolValue)
// need to update `alerts` to make update API with correct parameter
switch title {
case L10n.Scene.Settings.Section.Notifications.favorites:
alertValues[0] = isOn
alerts.favourite = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.follows:
alertValues[1] = isOn
alerts.follow = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.boosts:
alertValues[2] = isOn
alerts.reblog = NSNumber(booleanLiteral: isOn)
case L10n.Scene.Settings.Section.Notifications.mentions:
alertValues[3] = isOn
alerts.mention = NSNumber(booleanLiteral: isOn)
default: break
}
self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
} }
} }
@ -435,7 +441,7 @@ extension SettingsViewController {
guard let setting: Setting? = { guard let setting: Setting? = {
let domain = box.domain let domain = box.domain
let request = Setting.sortedFetchRequest let request = Setting.sortedFetchRequest
request.predicate = Setting.predicate(domain: domain) request.predicate = Setting.predicate(domain: domain, userID: box.userID)
request.fetchLimit = 1 request.fetchLimit = 1
request.returnsObjectsAsFaults = false request.returnsObjectsAsFaults = false
do { do {

View File

@ -29,7 +29,7 @@ class SettingsViewModel: NSObject, NeedsDependency {
if let box = if let box =
self.context.authenticationService.activeMastodonAuthenticationBox.value { self.context.authenticationService.activeMastodonAuthenticationBox.value {
let domain = box.domain let domain = box.domain
fetchRequest.predicate = Setting.predicate(domain: domain) fetchRequest.predicate = Setting.predicate(domain: domain, userID: box.userID)
} }
fetchRequest.fetchLimit = 1 fetchRequest.fetchLimit = 1
@ -78,6 +78,9 @@ class SettingsViewModel: NSObject, NeedsDependency {
return Mastodon.API.privacyURL(domain: box.domain) return Mastodon.API.privacyURL(domain: box.domain)
}() }()
/// to store who trigger the notification.
var triggerBy: String?
struct Input { struct Input {
} }
@ -121,12 +124,14 @@ class SettingsViewModel: NSObject, NeedsDependency {
follow: values[1], follow: values[1],
reblog: values[2], reblog: values[2],
mention: values[3], mention: values[3],
poll: nil) poll: nil
)
self.context.apiService.changeSubscription( self.context.apiService.changeSubscription(
domain: domain, domain: domain,
mastodonAuthenticationBox: activeMastodonAuthenticationBox, mastodonAuthenticationBox: activeMastodonAuthenticationBox,
query: query, query: query,
triggerBy: triggerBy triggerBy: triggerBy,
userID: activeMastodonAuthenticationBox.userID
) )
.sink { (_) in .sink { (_) in
} receiveValue: { (_) in } receiveValue: { (_) in
@ -164,7 +169,8 @@ class SettingsViewModel: NSObject, NeedsDependency {
domain: domain, domain: domain,
mastodonAuthenticationBox: activeMastodonAuthenticationBox, mastodonAuthenticationBox: activeMastodonAuthenticationBox,
query: query, query: query,
triggerBy: triggerBy triggerBy: triggerBy,
userID: activeMastodonAuthenticationBox.userID
) )
.sink { (_) in .sink { (_) in
} receiveValue: { (_) in } receiveValue: { (_) in
@ -178,13 +184,6 @@ class SettingsViewModel: NSObject, NeedsDependency {
// request subsription data for updating or initialization // request subsription data for updating or initialization
requestSubscription() requestSubscription()
do {
try fetchResultsController.performFetch()
setting.value = fetchResultsController.fetchedObjects?.first
} catch {
assertionFailure(error.localizedDescription)
}
return nil return nil
} }
@ -213,12 +212,12 @@ class SettingsViewModel: NSObject, NeedsDependency {
} else if let triggerBy = settings?.triggerBy, } else if let triggerBy = settings?.triggerBy,
let values = self.notificationDefaultValue[triggerBy] { let values = self.notificationDefaultValue[triggerBy] {
switches = values switches = values
self.createSubscriptionSubject.send((triggerBy: triggerBy, values: values))
} else { } else {
// fallback a default value // fallback a default value
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
switches = self.notificationDefaultValue[anyone] switches = self.notificationDefaultValue[anyone]
} }
let notifications = [L10n.Scene.Settings.Section.Notifications.favorites, let notifications = [L10n.Scene.Settings.Section.Notifications.favorites,
L10n.Scene.Settings.Section.Notifications.follows, L10n.Scene.Settings.Section.Notifications.follows,
L10n.Scene.Settings.Section.Notifications.boosts, L10n.Scene.Settings.Section.Notifications.boosts,
@ -273,31 +272,61 @@ class SettingsViewModel: NSObject, NeedsDependency {
} }
private func requestSubscription() { private func requestSubscription() {
// request subscription of notifications setting.sink { [weak self] (settings) in
typealias SubscriptionResponse = Mastodon.Response.Content<Mastodon.Entity.Subscription> guard let self = self else { return }
viewDidLoad.flatMap { [weak self] (_) -> AnyPublisher<SubscriptionResponse, Error> in guard settings != nil else { return }
guard let self = self, guard self.triggerBy != settings?.triggerBy else { return }
let activeMastodonAuthenticationBox = self.triggerBy = settings?.triggerBy
self.context.authenticationService.activeMastodonAuthenticationBox.value else {
return Empty<SubscriptionResponse, Error>().eraseToAnyPublisher() var switches: [Bool?]?
var who: String?
if let alerts = settings?.subscription?.first(where: { (s) -> Bool in
return s.type == settings?.triggerBy
})?.alert {
var items = [Bool?]()
items.append(alerts.favourite?.boolValue)
items.append(alerts.follow?.boolValue)
items.append(alerts.reblog?.boolValue)
items.append(alerts.mention?.boolValue)
switches = items
who = settings?.triggerBy
} else if let triggerBy = settings?.triggerBy,
let values = self.notificationDefaultValue[triggerBy] {
switches = values
who = triggerBy
} else {
// fallback a default value
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
switches = self.notificationDefaultValue[anyone]
who = anyone
} }
let domain = activeMastodonAuthenticationBox.domain // should create a subscription whenever change trigger
return self.context.apiService.subscription( if let values = switches, let triggerBy = who {
domain: domain, self.createSubscriptionSubject.send((triggerBy: triggerBy, values: values))
mastodonAuthenticationBox: activeMastodonAuthenticationBox)
}
.sink { [weak self] competion in
if case .failure(_) = competion {
// create a subscription when doesn't has one
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
if let values = self?.notificationDefaultValue[anyone] {
self?.createSubscriptionSubject.send((triggerBy: anyone, values: values))
}
} }
} receiveValue: { (subscription) in
} }
.store(in: &disposeBag) .store(in: &disposeBag)
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
let domain = activeMastodonAuthenticationBox.domain
let userId = activeMastodonAuthenticationBox.userID
do {
try fetchResultsController.performFetch()
if nil == fetchResultsController.fetchedObjects?.first {
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
setting.value = self.context.apiService.createSettingIfNeed(domain: domain,
userId: userId,
triggerBy: anyone)
} else {
setting.value = fetchResultsController.fetchedObjects?.first
}
} catch {
assertionFailure(error.localizedDescription)
}
} }
deinit { deinit {

View File

@ -5,44 +5,71 @@
// Created by ihugo on 2021/4/9. // Created by ihugo on 2021/4/9.
// //
import Combine
import CoreData
import CoreDataStack
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import Combine
extension APIService { extension APIService {
func subscription( func subscription(
domain: String, domain: String,
userID: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization let authorization = mastodonAuthenticationBox.userAuthorization
let findSettings: Setting? = {
let request = Setting.sortedFetchRequest
request.predicate = Setting.predicate(domain: domain, userID: userID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try self.backgroundManagedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
let triggerBy = findSettings?.triggerBy ?? "anyone"
let setting = self.createSettingIfNeed(
domain: domain,
userId: userID,
triggerBy: triggerBy
)
return Mastodon.API.Subscriptions.subscription( return Mastodon.API.Subscriptions.subscription(
session: session, session: session,
domain: domain, domain: domain,
authorization: authorization) authorization: authorization
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> in )
return self.backgroundManagedObjectContext.performChanges { .flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> in
_ = APIService.CoreData.createOrMergeSubscription( return self.backgroundManagedObjectContext.performChanges {
into: self.backgroundManagedObjectContext, _ = APIService.CoreData.createOrMergeSubscription(
entity: response.value, into: self.backgroundManagedObjectContext,
domain: domain) entity: response.value,
} domain: domain,
.setFailureType(to: Error.self) triggerBy: triggerBy,
.map { _ in return response } setting: setting)
.eraseToAnyPublisher() }
}.eraseToAnyPublisher() .setFailureType(to: Error.self)
.map { _ in return response }
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
} }
func changeSubscription( func changeSubscription(
domain: String, domain: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox,
query: Mastodon.API.Subscriptions.CreateSubscriptionQuery, query: Mastodon.API.Subscriptions.CreateSubscriptionQuery,
triggerBy: String triggerBy: String,
userID: String
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization let authorization = mastodonAuthenticationBox.userAuthorization
let setting = self.createSettingIfNeed(domain: domain,
userId: userID,
triggerBy: triggerBy)
return Mastodon.API.Subscriptions.createSubscription( return Mastodon.API.Subscriptions.createSubscription(
session: session, session: session,
domain: domain, domain: domain,
@ -55,7 +82,9 @@ extension APIService {
into: self.backgroundManagedObjectContext, into: self.backgroundManagedObjectContext,
entity: response.value, entity: response.value,
domain: domain, domain: domain,
triggerBy: triggerBy) triggerBy: triggerBy,
setting: setting
)
} }
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.map { _ in return response } .map { _ in return response }
@ -67,10 +96,15 @@ extension APIService {
domain: String, domain: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox,
query: Mastodon.API.Subscriptions.UpdateSubscriptionQuery, query: Mastodon.API.Subscriptions.UpdateSubscriptionQuery,
triggerBy: String triggerBy: String,
userID: String
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Subscription>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization let authorization = mastodonAuthenticationBox.userAuthorization
let setting = self.createSettingIfNeed(domain: domain,
userId: userID,
triggerBy: triggerBy)
return Mastodon.API.Subscriptions.updateSubscription( return Mastodon.API.Subscriptions.updateSubscription(
session: session, session: session,
domain: domain, domain: domain,
@ -83,12 +117,47 @@ extension APIService {
into: self.backgroundManagedObjectContext, into: self.backgroundManagedObjectContext,
entity: response.value, entity: response.value,
domain: domain, domain: domain,
triggerBy: triggerBy) triggerBy: triggerBy,
setting: setting
)
} }
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.map { _ in return response } .map { _ in return response }
.eraseToAnyPublisher() .eraseToAnyPublisher()
}.eraseToAnyPublisher() }.eraseToAnyPublisher()
} }
func createSettingIfNeed(domain: String, userId: String, triggerBy: String) -> Setting {
// create setting entity if possible
let oldSetting: Setting? = {
let request = Setting.sortedFetchRequest
request.predicate = Setting.predicate(domain: domain, userID: userId)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try backgroundManagedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
var setting: Setting!
if let oldSetting = oldSetting {
setting = oldSetting
} else {
let property = Setting.Property(
appearance: "automatic",
triggerBy: triggerBy,
domain: domain,
userID: userId)
(setting, _) = APIService.CoreData.createOrMergeSetting(
into: backgroundManagedObjectContext,
domain: domain,
userID: userId,
property: property
)
}
return setting
}
} }

View File

@ -16,11 +16,12 @@ extension APIService.CoreData {
static func createOrMergeSetting( static func createOrMergeSetting(
into managedObjectContext: NSManagedObjectContext, into managedObjectContext: NSManagedObjectContext,
domain: String, domain: String,
userID: String,
property: Setting.Property property: Setting.Property
) -> (Subscription: Setting, isCreated: Bool) { ) -> (Subscription: Setting, isCreated: Bool) {
let oldSetting: Setting? = { let oldSetting: Setting? = {
let request = Setting.sortedFetchRequest let request = Setting.sortedFetchRequest
request.predicate = Setting.predicate(domain: property.domain) request.predicate = Setting.predicate(domain: property.domain, userID: userID)
request.fetchLimit = 1 request.fetchLimit = 1
request.returnsObjectsAsFaults = false request.returnsObjectsAsFaults = false
do { do {
@ -45,38 +46,12 @@ extension APIService.CoreData {
into managedObjectContext: NSManagedObjectContext, into managedObjectContext: NSManagedObjectContext,
entity: Mastodon.Entity.Subscription, entity: Mastodon.Entity.Subscription,
domain: String, domain: String,
triggerBy: String? = nil triggerBy: String,
setting: Setting
) -> (Subscription: Subscription, isCreated: Bool) { ) -> (Subscription: Subscription, isCreated: Bool) {
// create setting entity if possible
let oldSetting: Setting? = {
let request = Setting.sortedFetchRequest
request.predicate = Setting.predicate(domain: domain)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
var setting: Setting!
if let oldSetting = oldSetting {
setting = oldSetting
} else {
let property = Setting.Property(
appearance: "automatic",
triggerBy: "anyone",
domain: domain)
(setting, _) = createOrMergeSetting(
into: managedObjectContext,
domain: domain,
property: property)
}
let oldSubscription: Subscription? = { let oldSubscription: Subscription? = {
let request = Subscription.sortedFetchRequest let request = Subscription.sortedFetchRequest
request.predicate = Subscription.predicate(id: entity.id) request.predicate = Subscription.predicate(type: triggerBy)
request.fetchLimit = 1 request.fetchLimit = 1
request.returnsObjectsAsFaults = false request.returnsObjectsAsFaults = false
do { do {
@ -91,7 +66,8 @@ extension APIService.CoreData {
endpoint: entity.endpoint, endpoint: entity.endpoint,
id: entity.id, id: entity.id,
serverKey: entity.serverKey, serverKey: entity.serverKey,
type: triggerBy ?? setting.triggerBy ?? "") type: triggerBy
)
let alertEntity = entity.alerts let alertEntity = entity.alerts
let alert = SubscriptionAlerts.Property( let alert = SubscriptionAlerts.Property(
favourite: alertEntity.favouriteNumber, favourite: alertEntity.favouriteNumber,
@ -105,7 +81,8 @@ extension APIService.CoreData {
if nil == oldSubscription.alert { if nil == oldSubscription.alert {
oldSubscription.alert = SubscriptionAlerts.insert( oldSubscription.alert = SubscriptionAlerts.insert(
into: managedObjectContext, into: managedObjectContext,
property: alert) property: alert
)
} else { } else {
oldSubscription.alert?.updateIfNeed(property: alert) oldSubscription.alert?.updateIfNeed(property: alert)
} }