mastodon-app-ufficiale-ipho.../MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follow.swift

126 lines
5.6 KiB
Swift
Raw Normal View History

//
// 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
}
}