// // APIService+Block.swift // Mastodon // // Created by MainasuK Cirno on 2021-4-2. // import UIKit import Combine import CoreData import CoreDataStack import CommonOSLog import MastodonSDK extension APIService { func toggleBlock( for mastodonUser: MastodonUser, activeMastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() return blockUpdateLocal( mastodonUserObjectID: mastodonUser.objectID, mastodonAuthenticationBox: activeMastodonAuthenticationBox ) .receive(on: DispatchQueue.main) .handleEvents { _ in impactFeedbackGenerator.prepare() } receiveOutput: { _ in impactFeedbackGenerator.impactOccurred() } receiveCompletion: { completion in switch completion { case .failure(let error): // TODO: handle error os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] local relationship update fail", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) assertionFailure(error.localizedDescription) case .finished: os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] local relationship update success", ((#file as NSString).lastPathComponent), #line, #function) break } } .flatMap { blockQueryType, mastodonUserID -> AnyPublisher, Error> in return self.blockUpdateRemote( blockQueryType: blockQueryType, mastodonUserID: mastodonUserID, mastodonAuthenticationBox: activeMastodonAuthenticationBox ) } .receive(on: DispatchQueue.main) .handleEvents(receiveCompletion: { [weak self] completion in guard let self = self else { return } switch completion { case .failure(let error): os_log("%{public}s[%{public}ld], %{public}s: [Relationship] remote friendship update fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) // TODO: handle error // rollback self.blockUpdateLocal( mastodonUserObjectID: mastodonUser.objectID, mastodonAuthenticationBox: activeMastodonAuthenticationBox ) .sink { completion in os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Friendship] rollback finish", ((#file as NSString).lastPathComponent), #line, #function) } receiveValue: { _ in // do nothing notificationFeedbackGenerator.prepare() notificationFeedbackGenerator.notificationOccurred(.error) } .store(in: &self.disposeBag) case .finished: notificationFeedbackGenerator.notificationOccurred(.success) os_log("%{public}s[%{public}ld], %{public}s: [Friendship] remote friendship update success", ((#file as NSString).lastPathComponent), #line, #function) } }) .eraseToAnyPublisher() } } extension APIService { // update database local and return block query update type for remote request func blockUpdateLocal( mastodonUserObjectID: NSManagedObjectID, mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher<(Mastodon.API.Account.BlockQueryType, MastodonUser.ID), Error> { let domain = mastodonAuthenticationBox.domain let requestMastodonUserID = mastodonAuthenticationBox.userID var _targetMastodonUserID: MastodonUser.ID? var _queryType: Mastodon.API.Account.BlockQueryType? let managedObjectContext = backgroundManagedObjectContext return managedObjectContext.performChanges { let request = MastodonUser.sortedFetchRequest request.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID) request.fetchLimit = 1 request.returnsObjectsAsFaults = false guard let _requestMastodonUser = managedObjectContext.safeFetch(request).first else { assertionFailure() return } let mastodonUser = managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser _targetMastodonUserID = mastodonUser.id let isBlocking = (mastodonUser.blockingBy ?? Set()).contains(_requestMastodonUser) _queryType = isBlocking ? .unblock : .block mastodonUser.update(isBlocking: !isBlocking, by: _requestMastodonUser) } .tryMap { result in switch result { case .success: guard let targetMastodonUserID = _targetMastodonUserID, let queryType = _queryType else { throw APIError.implicit(.badRequest) } return (queryType, targetMastodonUserID) case .failure(let error): assertionFailure(error.localizedDescription) throw error } } .eraseToAnyPublisher() } func blockUpdateRemote( blockQueryType: Mastodon.API.Account.BlockQueryType, mastodonUserID: MastodonUser.ID, mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization let requestMastodonUserID = mastodonAuthenticationBox.userID return Mastodon.API.Account.block( session: session, domain: domain, accountID: mastodonUserID, blockQueryType: blockQueryType, authorization: authorization ) .flatMap { response -> AnyPublisher, Error> in let managedObjectContext = self.backgroundManagedObjectContext return managedObjectContext.performChanges { let requestMastodonUserRequest = MastodonUser.sortedFetchRequest requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID) requestMastodonUserRequest.fetchLimit = 1 guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return } let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID) lookUpMastodonUserRequest.fetchLimit = 1 let lookUpMastodonUser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first if let lookUpMastodonUser = lookUpMastodonUser { let entity = response.value APIService.CoreData.update(user: lookUpMastodonUser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate) } } .tryMap { result -> Mastodon.Response.Content in switch result { case .success: return response case .failure(let error): throw error } } .eraseToAnyPublisher() } .handleEvents(receiveCompletion: { [weak self] completion in guard let _ = self else { return } switch completion { case .failure(let error): // TODO: handle error in banner os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] block update fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) case .finished: // TODO: update relationship switch blockQueryType { case .block: break case .unblock: break } } }) .eraseToAnyPublisher() } }