126 lines
5.6 KiB
Swift
126 lines
5.6 KiB
Swift
//
|
||
// APIService+Follow.swift
|
||
// Mastodon
|
||
//
|
||
// Created by MainasuK Cirno on 2021-4-2.
|
||
//
|
||
|
||
import UIKit
|
||
import Combine
|
||
import CoreData
|
||
import CoreDataStack
|
||
import CommonOSLog
|
||
import MastodonSDK
|
||
|
||
extension APIService {
|
||
|
||
private struct MastodonFollowContext {
|
||
let sourceUserID: MastodonUser.ID
|
||
let targetUserID: MastodonUser.ID
|
||
let isFollowing: Bool
|
||
let isPending: Bool
|
||
let needsUnfollow: Bool
|
||
}
|
||
|
||
/// Toggle friendship between target MastodonUser and current MastodonUser
|
||
///
|
||
/// Following / Following pending <-> Unfollow
|
||
///
|
||
/// - Parameters:
|
||
/// - mastodonUser: target MastodonUser
|
||
/// - activeMastodonAuthenticationBox: `AuthenticationService.MastodonAuthenticationBox`
|
||
/// - Returns: publisher for `Relationship`
|
||
func toggleFollow(
|
||
user: ManagedObjectRecord<MastodonUser>,
|
||
authenticationBox: MastodonAuthenticationBox
|
||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
|
||
let logger = Logger(subsystem: "APIService", category: "Follow")
|
||
|
||
let managedObjectContext = backgroundManagedObjectContext
|
||
let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges {
|
||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return nil }
|
||
guard let user = user.object(in: managedObjectContext) else { return nil }
|
||
|
||
let isFollowing = user.followingBy.contains(me)
|
||
let isPending = user.followRequestedBy.contains(me)
|
||
let needsUnfollow = isFollowing || isPending
|
||
|
||
if needsUnfollow {
|
||
// unfollow
|
||
user.update(isFollowing: false, by: me)
|
||
user.update(isFollowRequested: false, by: me)
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Local] update user friendship: undo follow")
|
||
} else {
|
||
// follow
|
||
if user.locked {
|
||
user.update(isFollowing: false, by: me)
|
||
user.update(isFollowRequested: true, by: me)
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Local] update user friendship: pending follow")
|
||
} else {
|
||
user.update(isFollowing: true, by: me)
|
||
user.update(isFollowRequested: false, by: me)
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Local] update user friendship: following")
|
||
}
|
||
}
|
||
let context = MastodonFollowContext(
|
||
sourceUserID: me.id,
|
||
targetUserID: user.id,
|
||
isFollowing: isFollowing,
|
||
isPending: isPending,
|
||
needsUnfollow: needsUnfollow
|
||
)
|
||
return context
|
||
}
|
||
|
||
guard let followContext = _followContext else {
|
||
throw APIError.implicit(.badRequest)
|
||
}
|
||
|
||
// request follow or unfollow
|
||
let result: Result<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>
|
||
do {
|
||
let response = try await Mastodon.API.Account.follow(
|
||
session: session,
|
||
domain: authenticationBox.domain,
|
||
accountID: followContext.targetUserID,
|
||
followQueryType: followContext.needsUnfollow ? .unfollow : .follow(query: .init()),
|
||
authorization: authenticationBox.userAuthorization
|
||
).singleOutput()
|
||
result = .success(response)
|
||
} catch {
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Remote] update friendship failure: \(error.localizedDescription)")
|
||
result = .failure(error)
|
||
}
|
||
|
||
// update friendship state
|
||
try await managedObjectContext.performChanges {
|
||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user,
|
||
let user = user.object(in: managedObjectContext)
|
||
else { return }
|
||
|
||
switch result {
|
||
case .success(let response):
|
||
Persistence.MastodonUser.update(
|
||
mastodonUser: user,
|
||
context: Persistence.MastodonUser.RelationshipContext(
|
||
entity: response.value,
|
||
me: me,
|
||
networkDate: response.networkDate
|
||
)
|
||
)
|
||
let following = response.value.following
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Remote] update user friendship: following \(following)")
|
||
case .failure:
|
||
// rollback
|
||
user.update(isFollowing: followContext.isFollowing, by: me)
|
||
user.update(isFollowRequested: followContext.isPending, by: me)
|
||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Remote] rollback user friendship")
|
||
}
|
||
}
|
||
|
||
let response = try result.get()
|
||
return response
|
||
}
|
||
|
||
}
|