From d570d3ef0959f8c3158277314dd5b70ca738bc20 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Thu, 25 May 2023 16:26:17 +0200 Subject: [PATCH] Replace MastodonAuthentication to not be backed by CoreData --- Mastodon.xcodeproj/project.pbxproj | 4 - Mastodon/Coordinator/SceneCoordinator.swift | 9 +- .../Diffable/Discovery/DiscoverySection.swift | 2 +- .../Search/SearchHistorySection.swift | 2 +- .../Diffable/Search/SearchResultSection.swift | 2 +- Mastodon/Diffable/User/UserSection.swift | 2 +- .../Extension/AppContext+NextAccount.swift | 10 +- .../DataSourceFacade+SearchHistory.swift | 8 +- .../Scene/Account/AccountListViewModel.swift | 54 ++------- .../Scene/Account/AccountViewController.swift | 6 +- .../Share/AuthenticationViewModel.swift | 41 +++---- .../FollowedTags/FollowedTagsViewModel.swift | 2 +- .../Scene/Profile/MeProfileViewModel.swift | 2 +- .../Scene/Profile/ProfileViewController.swift | 7 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 2 +- .../ReportResult/ReportResultViewModel.swift | 2 +- .../Root/MainTab/MainTabBarController.swift | 6 +- .../Scene/Root/Sidebar/SidebarViewModel.swift | 4 +- Mastodon/Supporting Files/SceneDelegate.swift | 7 +- .../Handler/SendPostIntentHandler.swift | 9 +- MastodonIntent/Model/Account+Fetch.swift | 10 +- .../Model/MastodonAuthentication+Fetch.swift | 20 ---- .../Entity/Mastodon/Instance.swift | 2 +- .../Mastodon/MastodonAuthentication.swift | 47 ++++---- .../Entity/Mastodon/MastodonUser.swift | 2 +- .../Sources/MastodonCore/AuthContext.swift | 26 ----- .../MastodonAuthenticationBox.swift | 10 +- .../AuthenticationServiceProvider.swift | 49 +++++++++ .../MastodonCore/MastodonAuthentication.swift | 103 ++++++++++++++++++ .../Service/API/APIService+Account.swift | 2 +- .../Service/API/APIService+Block.swift | 18 ++- .../Service/API/APIService+Bookmark.swift | 23 ++-- .../Service/API/APIService+Favorite.swift | 20 ++-- .../Service/API/APIService+Follow.swift | 9 +- .../API/APIService+FollowRequest.swift | 2 +- .../Service/API/APIService+Follower.swift | 2 +- .../Service/API/APIService+Following.swift | 4 +- .../API/APIService+HashtagTimeline.swift | 4 +- .../Service/API/APIService+HomeTimeline.swift | 2 +- .../Service/API/APIService+Mute.swift | 11 +- .../Service/API/APIService+Notification.swift | 4 +- .../Service/API/APIService+Poll.swift | 4 +- .../API/APIService+PublicTimeline.swift | 2 +- .../Service/API/APIService+Reblog.swift | 17 ++- .../Service/API/APIService+Relationship.swift | 2 +- .../Service/API/APIService+Search.swift | 2 +- .../API/APIService+Status+History.swift | 2 +- .../API/APIService+Status+Publish.swift | 2 +- .../Service/API/APIService+Status.swift | 2 +- .../Service/API/APIService+Tags.swift | 2 +- .../Service/API/APIService+Thread.swift | 2 +- .../Service/API/APIService+UserTimeline.swift | 2 +- ...vice+CoreData+MastodonAuthentication.swift | 76 ------------- .../Service/AuthenticationService.swift | 72 ++---------- .../Service/InstanceService.swift | 28 +---- .../Notification/NotificationService.swift | 40 +++---- .../ComposeContentViewModel.swift | 15 ++- .../Content/NotificationView+ViewModel.swift | 5 +- .../View/Content/StatusView+ViewModel.swift | 5 +- .../Scene/ShareViewController.swift | 4 +- .../FollowersCount/FollowersCountWidget.swift | 4 +- .../MultiFollowersCountWidget.swift | 4 +- 62 files changed, 369 insertions(+), 474 deletions(-) delete mode 100644 MastodonIntent/Model/MastodonAuthentication+Fetch.swift create mode 100644 MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift create mode 100644 MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift delete mode 100644 MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+MastodonAuthentication.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 7d3c4f611..91a227d5e 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -309,7 +309,6 @@ DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F776279A9A2A00455B82 /* NotificationView+Configuration.swift */; }; DB63F779279ABF9C00455B82 /* DataSourceFacade+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */; }; DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */; }; - DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */; }; DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */; }; DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB65C63627A2AF6C008BAC2E /* ReportItem.swift */; }; DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */; }; @@ -997,7 +996,6 @@ DB63F776279A9A2A00455B82 /* NotificationView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationView+Configuration.swift"; sourceTree = ""; }; DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Reblog.swift"; sourceTree = ""; }; DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Favorite.swift"; sourceTree = ""; }; - DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAuthentication+Fetch.swift"; sourceTree = ""; }; DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = ""; }; DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = ""; }; DB6746EA278ED8B0008A6B94 /* PollOptionView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PollOptionView+Configuration.swift"; sourceTree = ""; }; @@ -2302,7 +2300,6 @@ DB64BA462851F23300ADF1B7 /* Model */ = { isa = PBXGroup; children = ( - DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */, DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */, ); path = Model; @@ -3949,7 +3946,6 @@ DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */, 2AB5011D299243FB00346092 /* WidgetExtension.intentdefinition in Sources */, 2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */, - DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */, DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */, D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */, 2A86A14629892944007F1062 /* MultiFollowersCountIntentHandler.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 4908a533a..496808d1f 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -54,12 +54,8 @@ final public class SceneCoordinator { return } else { // switch to notification's account - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken) - request.returnsObjectsAsFaults = false - request.fetchLimit = 1 do { - guard let authentication = try appContext.managedObjectContext.fetch(request).first else { + guard let authentication = AuthenticationServiceProvider.shared.authentications.first(where: { $0.userAccessToken == accessToken }) else { return } let domain = authentication.domain @@ -221,8 +217,7 @@ extension SceneCoordinator { let rootViewController: UIViewController do { - let request = MastodonAuthentication.activeSortedFetchRequest // use active order - let _authentication = try appContext.managedObjectContext.fetch(request).first + let _authentication = AuthenticationServiceProvider.shared.authentications.first let _authContext = _authentication.flatMap { AuthContext(authentication: $0) } self.authContext = _authContext diff --git a/Mastodon/Diffable/Discovery/DiscoverySection.swift b/Mastodon/Diffable/Discovery/DiscoverySection.swift index 225b6f46a..53726292b 100644 --- a/Mastodon/Diffable/Discovery/DiscoverySection.swift +++ b/Mastodon/Diffable/Discovery/DiscoverySection.swift @@ -77,7 +77,7 @@ extension DiscoverySection { cell.profileCardView.viewModel.familiarFollowers = nil } // bind me - cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user + cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) } return cell case .bottomLoader: diff --git a/Mastodon/Diffable/Search/SearchHistorySection.swift b/Mastodon/Diffable/Search/SearchHistorySection.swift index 813c5b59a..1cd17da78 100644 --- a/Mastodon/Diffable/Search/SearchHistorySection.swift +++ b/Mastodon/Diffable/Search/SearchHistorySection.swift @@ -31,7 +31,7 @@ extension SearchHistorySection { context.managedObjectContext.performAndWait { guard let user = item.object(in: context.managedObjectContext) else { return } cell.configure( - me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user, + me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext), viewModel: SearchHistoryUserCollectionViewCell.ViewModel( value: user, followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), diff --git a/Mastodon/Diffable/Search/SearchResultSection.swift b/Mastodon/Diffable/Search/SearchResultSection.swift index 90560150e..06ea1b7fe 100644 --- a/Mastodon/Diffable/Search/SearchResultSection.swift +++ b/Mastodon/Diffable/Search/SearchResultSection.swift @@ -129,7 +129,7 @@ extension SearchResultSection { configuration: Configuration ) { cell.configure( - me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user, + me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext), tableView: tableView, viewModel: viewModel, delegate: configuration.userTableViewCellDelegate diff --git a/Mastodon/Diffable/User/UserSection.swift b/Mastodon/Diffable/User/UserSection.swift index cbad1ff72..8a0563db4 100644 --- a/Mastodon/Diffable/User/UserSection.swift +++ b/Mastodon/Diffable/User/UserSection.swift @@ -83,7 +83,7 @@ extension UserSection { configuration: Configuration ) { cell.configure( - me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user, + me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext), tableView: tableView, viewModel: viewModel, delegate: configuration.userTableViewCellDelegate diff --git a/Mastodon/Extension/AppContext+NextAccount.swift b/Mastodon/Extension/AppContext+NextAccount.swift index a8eae1e13..db3df4194 100644 --- a/Mastodon/Extension/AppContext+NextAccount.swift +++ b/Mastodon/Extension/AppContext+NextAccount.swift @@ -12,17 +12,13 @@ import MastodonSDK extension AppContext { func nextAccount(in authContext: AuthContext) -> MastodonAuthentication? { - let request = MastodonAuthentication.sortedFetchRequest - guard - let accounts = try? managedObjectContext.fetch(request), - accounts.count > 1 - else { return nil } + let accounts = AuthenticationServiceProvider.shared.authentications + guard accounts.count > 1 else { return nil } let nextSelectedAccountIndex: Int? = { for (index, account) in accounts.enumerated() { guard account == authContext.mastodonAuthenticationBox - .authenticationRecord - .object(in: managedObjectContext) + .authentication else { continue } let nextAccountIndex = index + 1 diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift index 18d238c02..446b0a27d 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift @@ -23,7 +23,7 @@ extension DataSourceFacade { let managedObjectContext = provider.context.backgroundManagedObjectContext try? await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } guard let user = record.object(in: managedObjectContext) else { return } _ = Persistence.SearchHistory.createOrMerge( in: managedObjectContext, @@ -41,7 +41,7 @@ extension DataSourceFacade { switch tag { case .entity(let entity): try? await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } let now = Date() @@ -67,7 +67,7 @@ extension DataSourceFacade { case .record(let record): try? await managedObjectContext.performChanges { let authenticationBox = provider.authContext.mastodonAuthenticationBox - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } guard let tag = record.object(in: managedObjectContext) else { return } let now = Date() @@ -98,7 +98,7 @@ extension DataSourceFacade { let managedObjectContext = provider.context.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let _ = authenticationBox.authentication.user(in: managedObjectContext) else { return } let request = SearchHistory.sortedFetchRequest request.predicate = SearchHistory.predicate( domain: authenticationBox.domain, diff --git a/Mastodon/Scene/Account/AccountListViewModel.swift b/Mastodon/Scene/Account/AccountListViewModel.swift index 2b8c75b8d..16a5055c6 100644 --- a/Mastodon/Scene/Account/AccountListViewModel.swift +++ b/Mastodon/Scene/Account/AccountListViewModel.swift @@ -22,10 +22,8 @@ final class AccountListViewModel: NSObject { // input let context: AppContext let authContext: AuthContext - let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController // output - @Published var authentications: [ManagedObjectRecord] = [] @Published var items: [Item] = [] let dataSourceDidUpdate = PassthroughSubject() @@ -34,30 +32,11 @@ final class AccountListViewModel: NSObject { init(context: AppContext, authContext: AuthContext) { self.context = context self.authContext = authContext - self.mastodonAuthenticationFetchedResultsController = { - let fetchRequest = MastodonAuthentication.sortedFetchRequest - fetchRequest.returnsObjectsAsFaults = false - fetchRequest.fetchBatchSize = 20 - let controller = NSFetchedResultsController( - fetchRequest: fetchRequest, - managedObjectContext: context.managedObjectContext, - sectionNameKeyPath: nil, - cacheName: nil - ) - return controller - }() + super.init() // end init - - mastodonAuthenticationFetchedResultsController.delegate = self - do { - try mastodonAuthenticationFetchedResultsController.performFetch() - authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? [] - } catch { - assertionFailure(error.localizedDescription) - } - $authentications + AuthenticationServiceProvider.shared.$authentications .receive(on: DispatchQueue.main) .sink { [weak self] authentications in guard let self = self else { return } @@ -86,7 +65,7 @@ extension AccountListViewModel { } enum Item: Hashable { - case authentication(record: ManagedObjectRecord) + case authentication(record: MastodonAuthentication) case addAccount } @@ -98,12 +77,12 @@ extension AccountListViewModel { switch item { case .authentication(let record): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell - if let authentication = record.object(in: managedObjectContext), - let activeAuthentication = self.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext) + if let activeAuthentication = AuthenticationServiceProvider.shared.authentications.first { AccountListViewModel.configure( + in: managedObjectContext, cell: cell, - authentication: authentication, + authentication: record, activeAuthentication: activeAuthentication ) } @@ -120,11 +99,12 @@ extension AccountListViewModel { } static func configure( + in context: NSManagedObjectContext, cell: AccountListTableViewCell, authentication: MastodonAuthentication, activeAuthentication: MastodonAuthentication ) { - let user = authentication.user + guard let user = authentication.user(in: context) else { return } // avatar cell.avatarButton.avatarImageView.configure( @@ -169,21 +149,3 @@ extension AccountListViewModel { .joined(separator: ", ") } } - -// MARK: - NSFetchedResultsControllerDelegate -extension AccountListViewModel: NSFetchedResultsControllerDelegate { - - public func controllerWillChangeContent(_ controller: NSFetchedResultsController) { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - } - - public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - guard controller === mastodonAuthenticationFetchedResultsController else { - assertionFailure() - return - } - - authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecord } ?? [] - } - -} diff --git a/Mastodon/Scene/Account/AccountViewController.swift b/Mastodon/Scene/Account/AccountViewController.swift index 75ea91fd6..4eab3565c 100644 --- a/Mastodon/Scene/Account/AccountViewController.swift +++ b/Mastodon/Scene/Account/AccountViewController.swift @@ -69,8 +69,7 @@ extension AccountListViewController: PanModalPresentable { return .contentHeight(CGFloat(height)) } - let request = MastodonAuthentication.sortedFetchRequest - let authenticationCount = (try? context.managedObjectContext.count(for: request)) ?? 0 + let authenticationCount = AuthenticationServiceProvider.shared.authentications.count let count = authenticationCount + 1 let height = calculateHeight(of: count) @@ -177,9 +176,8 @@ extension AccountListViewController: UITableViewDelegate { switch item { case .authentication(let record): assert(Thread.isMainThread) - guard let authentication = record.object(in: context.managedObjectContext) else { return } Task { @MainActor in - let isActive = try await context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID) + let isActive = try await context.authenticationService.activeMastodonUser(domain: record.domain, userID: record.userID) guard isActive else { return } self.coordinator.setup() } // end Task diff --git a/Mastodon/Scene/Onboarding/Share/AuthenticationViewModel.swift b/Mastodon/Scene/Onboarding/Share/AuthenticationViewModel.swift index 920164bce..868b14641 100644 --- a/Mastodon/Scene/Onboarding/Share/AuthenticationViewModel.swift +++ b/Mastodon/Scene/Onboarding/Share/AuthenticationViewModel.swift @@ -199,41 +199,26 @@ extension AuthenticationViewModel { domain: info.domain, authorization: authorization ) - .flatMap { response -> AnyPublisher, Error> in + .tryMap { response -> Mastodon.Response.Content in let account = response.value let mastodonUserRequest = MastodonUser.sortedFetchRequest mastodonUserRequest.predicate = MastodonUser.predicate(domain: info.domain, id: account.id) mastodonUserRequest.fetchLimit = 1 guard let mastodonUser = try? managedObjectContext.fetch(mastodonUserRequest).first else { - return Fail(error: AuthenticationError.badCredentials).eraseToAnyPublisher() + throw AuthenticationError.badCredentials } + + AuthenticationServiceProvider.shared + .authentications + .insert(MastodonAuthentication.createFrom(domain: info.domain, + userID: mastodonUser.id, + username: mastodonUser.username, + appAccessToken: userToken.accessToken, // TODO: swap app token + userAccessToken: userToken.accessToken, + clientID: info.clientID, + clientSecret: info.clientSecret), at: 0) - let property = MastodonAuthentication.Property( - domain: info.domain, - userID: mastodonUser.id, - username: mastodonUser.username, - appAccessToken: userToken.accessToken, // TODO: swap app token - userAccessToken: userToken.accessToken, - clientID: info.clientID, - clientSecret: info.clientSecret - ) - return managedObjectContext.performChanges { - _ = APIService.CoreData.createOrMergeMastodonAuthentication( - into: managedObjectContext, - for: mastodonUser, - in: info.domain, - property: property, - networkDate: response.networkDate - ) - } - .setFailureType(to: Error.self) - .tryMap { result in - switch result { - case .failure(let error): throw error - case .success: return response - } - } - .eraseToAnyPublisher() + return response } .eraseToAnyPublisher() } diff --git a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift index dbcf4d756..91e6c36ca 100644 --- a/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift +++ b/Mastodon/Scene/Profile/FollowedTags/FollowedTagsViewModel.swift @@ -34,7 +34,7 @@ final class FollowedTagsViewModel: NSObject { self.fetchedResultsController = FollowedTagsFetchedResultController( managedObjectContext: context.managedObjectContext, domain: authContext.mastodonAuthenticationBox.domain, - user: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)!.user + user: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)! // fixme: ) super.init() diff --git a/Mastodon/Scene/Profile/MeProfileViewModel.swift b/Mastodon/Scene/Profile/MeProfileViewModel.swift index 995e32002..46424ab5e 100644 --- a/Mastodon/Scene/Profile/MeProfileViewModel.swift +++ b/Mastodon/Scene/Profile/MeProfileViewModel.swift @@ -16,7 +16,7 @@ import MastodonSDK final class MeProfileViewModel: ProfileViewModel { init(context: AppContext, authContext: AuthContext) { - let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user + let user = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) super.init( context: context, authContext: authContext, diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 238f076f0..8e0d61ee4 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -963,11 +963,6 @@ extension ProfileViewController: PagerTabStripNavigateable { private extension ProfileViewController { var currentInstance: Instance? { - guard let authenticationRecord = authContext.mastodonAuthenticationBox - .authenticationRecord - .object(in: context.managedObjectContext) - else { return nil } - - return authenticationRecord.instance + authContext.mastodonAuthenticationBox.authentication.instance(in: context.managedObjectContext) } } diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 5c81c7920..de32b5607 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -85,7 +85,7 @@ class ProfileViewModel: NSObject { super.init() // bind me - self.me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user + self.me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) $me .assign(to: \.me, on: relationshipViewModel) .store(in: &disposeBag) diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift index 8123a8773..9caaeeb59 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift @@ -60,7 +60,7 @@ class ReportResultViewModel: ObservableObject { Task { @MainActor in guard let user = user.object(in: context.managedObjectContext) else { return } - guard let me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user else { return } + guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return } self.relationshipViewModel.user = user self.relationshipViewModel.me = me diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 13ae74103..3867e8cbe 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -292,7 +292,7 @@ extension MainTabBarController { } .store(in: &disposeBag) - if let user = authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user { + if let user = authContext?.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) { self.avatarURLObserver = user.publisher(for: \.avatar) .sink { [weak self, weak user] _ in guard let self = self else { return } @@ -486,9 +486,9 @@ extension MainTabBarController { authenticationBox: authContext.mastodonAuthenticationBox ) - if let user = authContext.mastodonAuthenticationBox.authenticationRecord.object( + if let user = authContext.mastodonAuthenticationBox.authentication.user( in: context.managedObjectContext - )?.user { + ) { user.update( property: .init( entity: profileResponse.value, diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 6362cfe95..fd3e3e45e 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -75,7 +75,7 @@ extension SidebarViewModel { let imageURL: URL? = { switch item { case .me: - let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user + let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) return user?.avatarImageURL() default: return nil @@ -132,7 +132,7 @@ extension SidebarViewModel { } .store(in: &cell.disposeBag) case .me: - guard let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return } + guard let user = self.authContext?.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return } let currentUserDisplayName = user.displayNameWithFallback cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) default: diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index a5c3a9caa..1e023227c 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -214,11 +214,8 @@ extension SceneDelegate { assertionFailure() return false } - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken) - request.fetchLimit = 1 - - guard let authentication = try? coordinator.appContext.managedObjectContext.fetch(request).first else { + + guard let authentication = AuthenticationServiceProvider.shared.getAuthentication(matching: accessToken) else { assertionFailure() return false } diff --git a/MastodonIntent/Handler/SendPostIntentHandler.swift b/MastodonIntent/Handler/SendPostIntentHandler.swift index 54fcefe39..b457b1c91 100644 --- a/MastodonIntent/Handler/SendPostIntentHandler.swift +++ b/MastodonIntent/Handler/SendPostIntentHandler.swift @@ -50,9 +50,8 @@ extension SendPostIntentHandler: SendPostIntentHandling { let mastodonAuthentications: [MastodonAuthentication] let accounts = intent.accounts ?? [] if accounts.isEmpty { - let request = MastodonAuthentication.sortedFetchRequest - let authentications = try managedObjectContext.fetch(request) - let _authentication = authentications.sorted(by: { $0.activedAt > $1.activedAt }).first + // fixme: refactor this and implemented method on AuthenticationServiceProvider + let _authentication = AuthenticationServiceProvider.shared.authentications.sorted(by: { $0.activedAt > $1.activedAt }).first guard let authentication = _authentication else { let failureReason = APIService.APIError.implicit(.authenticationMissing).errorDescription ?? "Fail to Send Post" @@ -65,12 +64,12 @@ extension SendPostIntentHandler: SendPostIntentHandling { let authenticationBoxes = mastodonAuthentications.map { authentication in MastodonAuthenticationBox( - authenticationRecord: .init(objectID: authentication.objectID), + authentication: authentication, domain: authentication.domain, userID: authentication.userID, appAuthorization: .init(accessToken: authentication.appAccessToken), userAuthorization: .init(accessToken: authentication.userAccessToken), - inMemoryCache: .sharedCache(for: authentication.objectID.description) + inMemoryCache: .sharedCache(for: authentication.userAccessToken) ) } diff --git a/MastodonIntent/Model/Account+Fetch.swift b/MastodonIntent/Model/Account+Fetch.swift index fd8c81769..f3d8ee344 100644 --- a/MastodonIntent/Model/Account+Fetch.swift +++ b/MastodonIntent/Model/Account+Fetch.swift @@ -17,9 +17,11 @@ extension Account { static func fetch(in managedObjectContext: NSManagedObjectContext) async throws -> [Account] { // get accounts let accounts: [Account] = try await managedObjectContext.perform { - let results = try MastodonAuthentication.fetch(in: managedObjectContext) + let results = AuthenticationServiceProvider.shared.authentications let accounts = results.compactMap { mastodonAuthentication -> Account? in - let user = mastodonAuthentication.user + guard let user = mastodonAuthentication.user(in: managedObjectContext) else { + return nil + } let account = Account( identifier: mastodonAuthentication.identifier.uuidString, display: user.displayNameWithFallback, @@ -43,9 +45,7 @@ extension Array where Element == Account { let identifiers = self .compactMap { $0.identifier } .compactMap { UUID(uuidString: $0) } - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(identifiers: identifiers) - let results = try managedObjectContext.fetch(request) + let results = AuthenticationServiceProvider.shared.authentications.filter({ identifiers.contains($0.identifier) }) return results } diff --git a/MastodonIntent/Model/MastodonAuthentication+Fetch.swift b/MastodonIntent/Model/MastodonAuthentication+Fetch.swift deleted file mode 100644 index 9d1201000..000000000 --- a/MastodonIntent/Model/MastodonAuthentication+Fetch.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// MastodonAuthentication.swift -// MastodonIntent -// -// Created by MainasuK on 2022-6-9. -// - -import Foundation -import CoreData -import CoreDataStack - -extension MastodonAuthentication { - - static func fetch(in managedObjectContext: NSManagedObjectContext) throws -> [MastodonAuthentication] { - let request = MastodonAuthentication.sortedFetchRequest - let results = try managedObjectContext.fetch(request) - return results - } - -} diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift index c11a92b76..d7b32d5d2 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Instance.swift @@ -19,7 +19,7 @@ public final class Instance: NSManagedObject { @NSManaged public private(set) var configurationV2Raw: Data? // MARK: one-to-many relationships - @NSManaged public var authentications: Set + @NSManaged public var authentications: Set } extension Instance { diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift index 2d8a97fad..dfdc0f045 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift @@ -8,7 +8,8 @@ import Foundation import CoreData -final public class MastodonAuthentication: NSManagedObject { +@objc(MastodonAuthentication) +final public class MastodonAuthenticationLegacy: NSManagedObject { public typealias ID = UUID @@ -35,16 +36,16 @@ final public class MastodonAuthentication: NSManagedObject { } -extension MastodonAuthentication { +extension MastodonAuthenticationLegacy { public override func awakeFromInsert() { super.awakeFromInsert() - setPrimitiveValue(UUID(), forKey: #keyPath(MastodonAuthentication.identifier)) + setPrimitiveValue(UUID(), forKey: #keyPath(MastodonAuthenticationLegacy.identifier)) let now = Date() - setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.createdAt)) - setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.updatedAt)) - setPrimitiveValue(now, forKey: #keyPath(MastodonAuthentication.activedAt)) + setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.createdAt)) + setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.updatedAt)) + setPrimitiveValue(now, forKey: #keyPath(MastodonAuthenticationLegacy.activedAt)) } @discardableResult @@ -52,8 +53,8 @@ extension MastodonAuthentication { into context: NSManagedObjectContext, property: Property, user: MastodonUser - ) -> MastodonAuthentication { - let authentication: MastodonAuthentication = context.insertObject() + ) -> MastodonAuthenticationLegacy { + let authentication: MastodonAuthenticationLegacy = context.insertObject() authentication.domain = property.domain authentication.userID = property.userID @@ -112,7 +113,7 @@ extension MastodonAuthentication { } -extension MastodonAuthentication { +extension MastodonAuthenticationLegacy { public struct Property { public let domain: String @@ -144,51 +145,51 @@ extension MastodonAuthentication { } } -extension MastodonAuthentication: Managed { +extension MastodonAuthenticationLegacy: Managed { public static var defaultSortDescriptors: [NSSortDescriptor] { - return [NSSortDescriptor(keyPath: \MastodonAuthentication.createdAt, ascending: false)] + return [NSSortDescriptor(keyPath: \MastodonAuthenticationLegacy.createdAt, ascending: false)] } public static var activeSortDescriptors: [NSSortDescriptor] { - return [NSSortDescriptor(keyPath: \MastodonAuthentication.activedAt, ascending: false)] + return [NSSortDescriptor(keyPath: \MastodonAuthenticationLegacy.activedAt, ascending: false)] } } -extension MastodonAuthentication { - public static var activeSortedFetchRequest: NSFetchRequest { - let request = NSFetchRequest(entityName: entityName) +extension MastodonAuthenticationLegacy { + public static var activeSortedFetchRequest: NSFetchRequest { + let request = NSFetchRequest(entityName: entityName) request.sortDescriptors = activeSortDescriptors return request } } -extension MastodonAuthentication { +extension MastodonAuthenticationLegacy { public static func predicate(domain: String) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.domain), domain) + return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.domain), domain) } static func predicate(userID: String) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.userID), userID) + return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.userID), userID) } public static func predicate(domain: String, userID: String) -> NSPredicate { return NSCompoundPredicate(andPredicateWithSubpredicates: [ - MastodonAuthentication.predicate(domain: domain), - MastodonAuthentication.predicate(userID: userID) + MastodonAuthenticationLegacy.predicate(domain: domain), + MastodonAuthenticationLegacy.predicate(userID: userID) ]) } public static func predicate(userAccessToken: String) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.userAccessToken), userAccessToken) + return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.userAccessToken), userAccessToken) } public static func predicate(identifier: UUID) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.identifier), identifier as NSUUID) + return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthenticationLegacy.identifier), identifier as NSUUID) } public static func predicate(identifiers: [UUID]) -> NSPredicate { - return NSPredicate(format: "%K IN %@", #keyPath(MastodonAuthentication.identifier), identifiers as [NSUUID]) + return NSPredicate(format: "%K IN %@", #keyPath(MastodonAuthenticationLegacy.identifier), identifiers as [NSUUID]) } } diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift index fe668ccb9..cb2548b6a 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift @@ -62,7 +62,7 @@ final public class MastodonUser: NSManagedObject { // one-to-one relationship @NSManaged public private(set) var pinnedStatus: Status? - @NSManaged public private(set) var mastodonAuthentication: MastodonAuthentication? + @NSManaged public private(set) var mastodonAuthentication: MastodonAuthenticationLegacy? // one-to-many relationship @NSManaged public private(set) var statuses: Set diff --git a/MastodonSDK/Sources/MastodonCore/AuthContext.swift b/MastodonSDK/Sources/MastodonCore/AuthContext.swift index b93a2e03a..ff936c3c0 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthContext.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthContext.swift @@ -27,38 +27,12 @@ public class AuthContext { private init(mastodonAuthenticationBox: MastodonAuthenticationBox) { self.mastodonAuthenticationBox = mastodonAuthenticationBox } - } extension AuthContext { public convenience init?(authentication: MastodonAuthentication) { self.init(mastodonAuthenticationBox: MastodonAuthenticationBox(authentication: authentication)) - - ManagedObjectObserver.observe(object: authentication) - .receive(on: DispatchQueue.main) - .sink { [weak self] completion in - guard let self = self else { return } - switch completion { - case .failure(let error): - self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(error.localizedDescription)") - case .finished: - self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): observer finished") - } - } receiveValue: { [weak self] change in - guard let self = self else { return } - switch change.changeType { - case .update(let object): - guard let authentication = object as? MastodonAuthentication else { - assertionFailure() - return - } - self.mastodonAuthenticationBox = .init(authentication: authentication) - default: - break - } - } - .store(in: &disposeBag) } } diff --git a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift index e35264ed1..8db326dfe 100644 --- a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift +++ b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift @@ -10,7 +10,7 @@ import CoreDataStack import MastodonSDK public struct MastodonAuthenticationBox: UserIdentifier { - public let authenticationRecord: ManagedObjectRecord + public let authentication: MastodonAuthentication public let domain: String public let userID: MastodonUser.ID public let appAuthorization: Mastodon.API.OAuth.Authorization @@ -18,14 +18,14 @@ public struct MastodonAuthenticationBox: UserIdentifier { public let inMemoryCache: MastodonAccountInMemoryCache public init( - authenticationRecord: ManagedObjectRecord, + authentication: MastodonAuthentication, domain: String, userID: MastodonUser.ID, appAuthorization: Mastodon.API.OAuth.Authorization, userAuthorization: Mastodon.API.OAuth.Authorization, inMemoryCache: MastodonAccountInMemoryCache ) { - self.authenticationRecord = authenticationRecord + self.authentication = authentication self.domain = domain self.userID = userID self.appAuthorization = appAuthorization @@ -38,12 +38,12 @@ extension MastodonAuthenticationBox { init(authentication: MastodonAuthentication) { self = MastodonAuthenticationBox( - authenticationRecord: .init(objectID: authentication.objectID), + authentication: authentication, domain: authentication.domain, userID: authentication.userID, appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken), - inMemoryCache: .sharedCache(for: authentication.objectID.description) + inMemoryCache: .sharedCache(for: authentication.userID) // todo: make sure this is really unique ) } diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift new file mode 100644 index 000000000..a7c3decc1 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -0,0 +1,49 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import Combine +import CoreDataStack +import MastodonSDK + +public class AuthenticationServiceProvider: ObservableObject { + public static let shared = AuthenticationServiceProvider() + + private init() {} + + @Published public var authentications: [MastodonAuthentication] = [] + + func update(instance: Instance, where domain: String) { + authentications = authentications.map { authentication in + guard authentication.domain == domain else { return authentication } + return authentication.updating(instance: instance) + } + } + + func delete(authentication: MastodonAuthentication) { + authentications.removeAll(where: { $0 == authentication }) + } + + func activateAuthentication(in domain: String, for userID: String) { + authentications = authentications.map { authentication in + guard authentication.domain == domain, authentication.userID == userID else { + return authentication + } + return authentication.updating(activatedAt: Date()) + } + } + + func getAuthentication(in domain: String, for userID: String) -> MastodonAuthentication? { + authentications.first(where: { $0.domain == domain && $0.userID == userID }) + } +} + +// MARK: - Public +public extension AuthenticationServiceProvider { + func getAuthentication(matching userAccessToken: String) -> MastodonAuthentication? { + authentications.first(where: { $0.userAccessToken == userAccessToken }) + } + + func sortByActivation() { // fixme: why do we need this? + authentications = authentications.sorted(by: { $0.activedAt > $1.activedAt }) + } +} diff --git a/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift new file mode 100644 index 000000000..7b0660502 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift @@ -0,0 +1,103 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import CoreDataStack +import MastodonSDK + +public struct MastodonAuthentication: Codable, Hashable { + public typealias ID = UUID + + public private(set) var identifier: ID + public private(set) var domain: String + public private(set) var username: String + + public private(set) var appAccessToken: String + public private(set) var userAccessToken: String + public private(set) var clientID: String + public private(set) var clientSecret: String + + public private(set) var createdAt: Date + public private(set) var updatedAt: Date + public private(set) var activedAt: Date + + public private(set) var userID: String + public private(set) var instanceObjectIdURI: URL? + + public static func createFrom( + domain: String, + userID: String, + username: String, + appAccessToken: String, + userAccessToken: String, + clientID: String, + clientSecret: String + ) -> Self { + let now = Date() + return MastodonAuthentication( + identifier: .init(), // fixme + domain: domain, + username: username, + appAccessToken: appAccessToken, + userAccessToken: userAccessToken, + clientID: clientID, + clientSecret: clientSecret, + createdAt: now, + updatedAt: now, + activedAt: now, + userID: userID, + instanceObjectIdURI: nil + ) + } + + func copy( + identifier: ID? = nil, + domain: String? = nil, + username: String? = nil, + appAccessToken: String? = nil, + userAccessToken: String? = nil, + clientID: String? = nil, + clientSecret: String? = nil, + createdAt: Date? = nil, + updatedAt: Date? = nil, + activedAt: Date? = nil, + userID: String? = nil, + instanceObjectIdURI: URL? = nil + ) -> Self { + MastodonAuthentication( + identifier: identifier ?? self.identifier, + domain: domain ?? self.domain, + username: username ?? self.username, + appAccessToken: appAccessToken ?? self.appAccessToken, + userAccessToken: userAccessToken ?? self.userAccessToken, + clientID: clientID ?? self.clientID, + clientSecret: clientSecret ?? self.clientSecret, + createdAt: createdAt ?? self.createdAt, + updatedAt: updatedAt ?? self.updatedAt, + activedAt: activedAt ?? self.activedAt, + userID: userID ?? self.userID, + instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI + ) + } + + public func instance(in context: NSManagedObjectContext) -> Instance? { + guard + let instanceObjectIdURI = instanceObjectIdURI, + let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI) + else { return nil } + + return try? context.existingObject(with: objectID) as? Instance + } + + public func user(in context: NSManagedObjectContext) -> MastodonUser? { + let userPredicate = MastodonUser.predicate(domain: domain, id: userID) + return MastodonUser.findOrFetch(in: context, matching: userPredicate) + } + + func updating(instance: Instance) -> Self { + copy(instanceObjectIdURI: instance.objectID.uriRepresentation()) + } + + func updating(activatedAt: Date) -> Self { + copy(activedAt: activatedAt) + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift index d68984587..ad8320fe9 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Account.swift @@ -182,7 +182,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) for entity in response.value { _ = Persistence.Tag.createOrMerge( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift index 19c5ff437..5580245ce 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift @@ -69,12 +69,15 @@ extension APIService { let managedObjectContext = backgroundManagedObjectContext let blockContext: MastodonBlockContext = try await managedObjectContext.performChanges { - guard let user = user.object(in: managedObjectContext), - let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let user = user.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { throw APIError.implicit(.badRequest) } - let me = authentication.user + let isBlocking = user.blockingBy.contains(me) let isFollowing = user.followingBy.contains(me) // toggle block state @@ -119,10 +122,13 @@ extension APIService { } try await managedObjectContext.performChanges { - guard let user = user.object(in: managedObjectContext), - let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let user = user.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { return } - let me = authentication.user + switch result { case .success(let response): diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Bookmark.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Bookmark.swift index 0d8c243f8..9020524ea 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Bookmark.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Bookmark.swift @@ -29,12 +29,15 @@ extension APIService { // update bookmark state and retrieve bookmark context let bookmarkContext: MastodonBookmarkContext = try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let _status = record.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { throw APIError.implicit(.badRequest) } - let me = authentication.user + let status = _status.reblog ?? _status let isBookmarked = status.bookmarkedBy.contains(me) status.update(bookmarked: !isBookmarked, by: me) @@ -64,10 +67,13 @@ extension APIService { // update bookmark state try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let _status = record.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { return } - let me = authentication.user + let status = _status.reblog ?? _status switch result { @@ -114,7 +120,10 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { + + guard + let me = authenticationBox.authentication.user(in: managedObjectContext) + else { assertionFailure() return } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Favorite.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Favorite.swift index 4abe9ba5f..166118c8a 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Favorite.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Favorite.swift @@ -31,12 +31,15 @@ extension APIService { // update like state and retrieve like context let favoriteContext: MastodonFavoriteContext = try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let _status = record.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { throw APIError.implicit(.badRequest) } - let me = authentication.user + let status = _status.reblog ?? _status let isFavorited = status.favouritedBy.contains(me) let favoritedCount = status.favouritesCount @@ -70,10 +73,13 @@ extension APIService { // update like state try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let _status = record.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { return } - let me = authentication.user + let status = _status.reblog ?? _status switch result { @@ -124,7 +130,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { assertionFailure() return } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follow.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follow.swift index 442d293ce..0cd71cb2e 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follow.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follow.swift @@ -38,7 +38,7 @@ extension APIService { let managedObjectContext = backgroundManagedObjectContext let _followContext: MastodonFollowContext? = try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return nil } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return nil } guard let user = user.object(in: managedObjectContext) else { return nil } let isFollowing = user.followingBy.contains(me) @@ -94,7 +94,7 @@ extension APIService { // update friendship state try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user, + guard let me = authenticationBox.authentication.user(in: managedObjectContext), let user = user.object(in: managedObjectContext) else { return } @@ -129,10 +129,9 @@ extension APIService { let managedObjectContext = backgroundManagedObjectContext guard let user = user.object(in: managedObjectContext), - let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) + let me = authenticationBox.authentication.user(in: managedObjectContext) else { throw APIError.implicit(.badRequest) } - let me = authentication.user let result: Result, Error> let oldShowReblogs = me.showingReblogsBy.contains(user) @@ -153,7 +152,7 @@ extension APIService { } try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } switch result { case .success(let response): diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift index 74650131d..fb0e52ddb 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift @@ -36,7 +36,7 @@ extension APIService { ) request.fetchLimit = 1 guard let user = managedObjectContext.safeFetch(request).first else { return } - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } Persistence.MastodonUser.update( mastodonUser: user, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follower.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follower.swift index 0f0ea1a59..9c5801449 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follower.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Follower.swift @@ -36,7 +36,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) for entity in response.value { let result = Persistence.MastodonUser.createOrMerge( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Following.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Following.swift index 313d715ed..e2a2c6142 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Following.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Following.swift @@ -37,8 +37,8 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user - + let me = authenticationBox.authentication.user(in: managedObjectContext) + for entity in response.value { let result = Persistence.MastodonUser.createOrMerge( in: managedObjectContext, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HashtagTimeline.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HashtagTimeline.swift index d2fbe844a..86ee7faed 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HashtagTimeline.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HashtagTimeline.swift @@ -45,8 +45,8 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user - + let me = authenticationBox.authentication.user(in: managedObjectContext) + for entity in response.value { _ = Persistence.Status.createOrMerge( in: managedObjectContext, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HomeTimeline.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HomeTimeline.swift index dad844d37..7fddc8344 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HomeTimeline.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+HomeTimeline.swift @@ -40,7 +40,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { assertionFailure() return } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift index cc46872f4..15cd009e5 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Mute.swift @@ -68,13 +68,15 @@ extension APIService { let managedObjectContext = backgroundManagedObjectContext let muteContext: MastodonMuteContext = try await managedObjectContext.performChanges { - guard let user = user.object(in: managedObjectContext), - let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let user = user.object(in: managedObjectContext), + let me = authentication.user(in: managedObjectContext) else { throw APIError.implicit(.badRequest) } - let me = authentication.user let isMuting = user.mutingBy.contains(me) // toggle mute state @@ -116,9 +118,8 @@ extension APIService { try await managedObjectContext.performChanges { guard let user = user.object(in: managedObjectContext), - let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) + let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } - let me = authentication.user switch result { case .success(let response): diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index c4d82df41..296a43d2b 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -88,7 +88,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { assertionFailure() return } @@ -176,7 +176,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return } + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } _ = Persistence.Notification.createOrMerge( in: managedObjectContext, context: Persistence.Notification.PersistContext( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Poll.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Poll.swift index 5a4b38b29..dba4527a4 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Poll.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Poll.swift @@ -36,7 +36,7 @@ extension APIService { ).singleOutput() try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) _ = Persistence.Poll.createOrMerge( in: managedObjectContext, context: Persistence.Poll.PersistContext( @@ -79,7 +79,7 @@ extension APIService { ).singleOutput() try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) _ = Persistence.Poll.createOrMerge( in: managedObjectContext, context: Persistence.Poll.PersistContext( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+PublicTimeline.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+PublicTimeline.swift index 21f198299..6d6726db9 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+PublicTimeline.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+PublicTimeline.swift @@ -30,7 +30,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) for entity in response.value { _ = Persistence.Status.createOrMerge( in: managedObjectContext, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Reblog.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Reblog.swift index 0dc1a40e5..717f2715d 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Reblog.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Reblog.swift @@ -29,11 +29,13 @@ extension APIService { // update repost state and retrieve repost context let _reblogContext: MastodonReblogContext? = try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let me = authentication.user(in: managedObjectContext), + let _status = record.object(in: managedObjectContext) else { return nil } - let me = authentication.user let status = _status.reblog ?? _status let isReblogged = status.rebloggedBy.contains(me) let rebloggedCount = status.reblogsCount @@ -70,10 +72,13 @@ extension APIService { // update repost state try await managedObjectContext.performChanges { - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext), - let _status = record.object(in: managedObjectContext) + let authentication = authenticationBox.authentication + + guard + let me = authentication.user(in: managedObjectContext), + let _status = record.object(in: managedObjectContext) else { return } - let me = authentication.user + let status = _status.reblog ?? _status switch result { diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Relationship.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Relationship.swift index f5c108725..c2c88cfb1 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Relationship.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Relationship.swift @@ -42,7 +42,7 @@ extension APIService { ).singleOutput() try await managedObjectContext.performChanges { - guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { + guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { // assertionFailure() return } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Search.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Search.swift index 5e6217bcf..7f63b2419 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Search.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Search.swift @@ -28,7 +28,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) // user for entity in response.value.accounts { diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift index 88cf91f9e..482dd0019 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift @@ -76,7 +76,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) let status = Persistence.Status.createOrMerge( in: managedObjectContext, context: Persistence.Status.PersistContext( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Publish.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Publish.swift index d2cbd3f5c..ee70db964 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Publish.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Publish.swift @@ -34,7 +34,7 @@ extension APIService { #if !APP_EXTENSION let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) _ = Persistence.Status.createOrMerge( in: managedObjectContext, context: Persistence.Status.PersistContext( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status.swift index 52b169ee7..d652f0ad4 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status.swift @@ -30,7 +30,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) _ = Persistence.Status.createOrMerge( in: managedObjectContext, context: Persistence.Status.PersistContext( diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift index 0b0320078..b5b526b8a 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Tags.swift @@ -74,7 +74,7 @@ fileprivate extension APIService { ) async throws -> Mastodon.Response.Content { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) _ = Persistence.Tag.createOrMerge( in: managedObjectContext, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Thread.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Thread.swift index f1b94376e..ecc7a0bc3 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Thread.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Thread.swift @@ -30,7 +30,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) let value = response.value.ancestors + response.value.descendants for entity in value { diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+UserTimeline.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+UserTimeline.swift index fdf90a2aa..402bd9f61 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+UserTimeline.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+UserTimeline.swift @@ -46,7 +46,7 @@ extension APIService { let managedObjectContext = self.backgroundManagedObjectContext try await managedObjectContext.performChanges { - let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user + let me = authenticationBox.authentication.user(in: managedObjectContext) for entity in response.value { _ = Persistence.Status.createOrMerge( in: managedObjectContext, diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+MastodonAuthentication.swift b/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+MastodonAuthentication.swift deleted file mode 100644 index 1acb52a77..000000000 --- a/MastodonSDK/Sources/MastodonCore/Service/API/CoreData/APIService+CoreData+MastodonAuthentication.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// APIService+CoreData+MastodonAuthentication.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021/2/3. -// - -import os.log -import Foundation -import CoreData -import CoreDataStack -import MastodonSDK - -extension APIService.CoreData { - - public static func createOrMergeMastodonAuthentication( - into managedObjectContext: NSManagedObjectContext, - for authenticateMastodonUser: MastodonUser, - in domain: String, - property: MastodonAuthentication.Property, - networkDate: Date - ) -> (mastodonAuthentication: MastodonAuthentication, isCreated: Bool) { - // fetch old mastodon authentication - let oldMastodonAuthentication: MastodonAuthentication? = { - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(domain: domain, userID: property.userID) - request.fetchLimit = 1 - request.returnsObjectsAsFaults = false - do { - return try managedObjectContext.fetch(request).first - } catch { - assertionFailure(error.localizedDescription) - return nil - } - }() - - if let oldMastodonAuthentication = oldMastodonAuthentication { - // merge old mastodon authentication - APIService.CoreData.mergeMastodonAuthentication( - for: authenticateMastodonUser, - old: oldMastodonAuthentication, - in: domain, - property: property, - networkDate: networkDate - ) - return (oldMastodonAuthentication, false) - } else { - let mastodonAuthentication = MastodonAuthentication.insert( - into: managedObjectContext, - property: property, - user: authenticateMastodonUser - ) - return (mastodonAuthentication, true) - } - } - - static func mergeMastodonAuthentication( - for authenticateMastodonUser: MastodonUser, - old authentication: MastodonAuthentication, - in domain: String, - property: MastodonAuthentication.Property, - networkDate: Date - ) { - guard networkDate > authentication.updatedAt else { return } - - - authentication.update(username: property.username) - authentication.update(appAccessToken: property.appAccessToken) - authentication.update(userAccessToken: property.userAccessToken) - authentication.update(clientID: property.clientID) - authentication.update(clientSecret: property.clientSecret) - - authentication.didUpdate(at: networkDate) - } - -} diff --git a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift index 8faac74d9..b3ad468cc 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift @@ -22,10 +22,9 @@ public final class AuthenticationService: NSObject { weak var apiService: APIService? let managedObjectContext: NSManagedObjectContext // read-only let backgroundManagedObjectContext: NSManagedObjectContext - let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController + let authenticationServiceProvider = AuthenticationServiceProvider.shared // output - @Published public var mastodonAuthentications: [ManagedObjectRecord] = [] @Published public var mastodonAuthenticationBoxes: [MastodonAuthenticationBox] = [] private func fetchFollowedBlockedUserIds( @@ -93,21 +92,8 @@ public final class AuthenticationService: NSObject { self.managedObjectContext = managedObjectContext self.backgroundManagedObjectContext = backgroundManagedObjectContext self.apiService = apiService - self.mastodonAuthenticationFetchedResultsController = { - let fetchRequest = MastodonAuthentication.sortedFetchRequest - fetchRequest.returnsObjectsAsFaults = false - fetchRequest.fetchBatchSize = 20 - let controller = NSFetchedResultsController( - fetchRequest: fetchRequest, - managedObjectContext: managedObjectContext, - sectionNameKeyPath: nil, - cacheName: nil - ) - return controller - }() - super.init() - mastodonAuthenticationFetchedResultsController.delegate = self + super.init() $mastodonAuthenticationBoxes .sink { [weak self] boxes in @@ -123,10 +109,9 @@ public final class AuthenticationService: NSObject { // TODO: verify credentials for active authentication - $mastodonAuthentications + authenticationServiceProvider.$authentications .map { authentications -> [MastodonAuthenticationBox] in return authentications - .compactMap { $0.object(in: managedObjectContext) } .sorted(by: { $0.activedAt > $1.activedAt }) .compactMap { authentication -> MastodonAuthenticationBox? in return MastodonAuthenticationBox(authentication: authentication) @@ -134,14 +119,7 @@ public final class AuthenticationService: NSObject { } .assign(to: &$mastodonAuthenticationBoxes) - do { - try mastodonAuthenticationFetchedResultsController.performFetch() - mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? - .sorted(by: { $0.activedAt > $1.activedAt }) - .compactMap { $0.asRecord } ?? [] - } catch { - assertionFailure(error.localizedDescription) - } + AuthenticationServiceProvider.shared.sortByActivation() } } @@ -151,18 +129,9 @@ extension AuthenticationService { public func activeMastodonUser(domain: String, userID: MastodonUser.ID) async throws -> Bool { var isActive = false - let managedObjectContext = backgroundManagedObjectContext - - try await managedObjectContext.performChanges { - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(domain: domain, userID: userID) - request.fetchLimit = 1 - guard let mastodonAuthentication = try? managedObjectContext.fetch(request).first else { - return - } - mastodonAuthentication.update(activedAt: Date()) - isActive = true - } + AuthenticationServiceProvider.shared.activateAuthentication(in: domain, for: userID) + + isActive = true return isActive } @@ -183,12 +152,7 @@ extension AuthenticationService { managedObjectContext.delete(feed) } - guard let authentication = authenticationBox.authenticationRecord.object(in: managedObjectContext) else { - assertionFailure() - throw APIService.APIError.implicit(.authenticationMissing) - } - - managedObjectContext.delete(authentication) + AuthenticationServiceProvider.shared.delete(authentication: authenticationBox.authentication) } // cancel push notification subscription @@ -203,23 +167,3 @@ extension AuthenticationService { } } - -// MARK: - NSFetchedResultsControllerDelegate -extension AuthenticationService: NSFetchedResultsControllerDelegate { - - public func controllerWillChangeContent(_ controller: NSFetchedResultsController) { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - } - - public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - guard controller === mastodonAuthenticationFetchedResultsController else { - assertionFailure() - return - } - - mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? - .sorted(by: { $0.activedAt > $1.activedAt }) - .compactMap { $0.asRecord } ?? [] - } - -} diff --git a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift index 0745e2f37..1c3c05d4b 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift @@ -89,18 +89,8 @@ extension InstanceService { log: Logger(subsystem: "Update", category: "InstanceService") ) - // update relationship - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(domain: domain) - request.returnsObjectsAsFaults = false - do { - let authentications = try managedObjectContext.fetch(request) - for authentication in authentications { - authentication.update(instance: instance) - } - } catch { - assertionFailure(error.localizedDescription) - } + // update instance + AuthenticationServiceProvider.shared.update(instance: instance, where: domain) } .setFailureType(to: Error.self) .tryMap { result in @@ -128,18 +118,8 @@ extension InstanceService { ) ) - // update relationship - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(domain: domain) - request.returnsObjectsAsFaults = false - do { - let authentications = try managedObjectContext.fetch(request) - for authentication in authentications { - authentication.update(instance: instance) - } - } catch { - assertionFailure(error.localizedDescription) - } + // update instance + AuthenticationServiceProvider.shared.update(instance: instance, where: domain) } .setFailureType(to: Error.self) .tryMap { result in diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index adabd8962..229def922 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -42,7 +42,7 @@ public final class NotificationService { self.apiService = apiService self.authenticationService = authenticationService - authenticationService.$mastodonAuthentications + AuthenticationServiceProvider.shared.$authentications .sink(receiveValue: { [weak self] mastodonAuthentications in guard let self = self else { return } @@ -113,13 +113,13 @@ extension NotificationService { let managedObjectContext = authenticationService.managedObjectContext return try await managedObjectContext.perform { var items: [UIApplicationShortcutItem] = [] - for object in authenticationService.mastodonAuthentications { - guard let authentication = managedObjectContext.object(with: object.objectID) as? MastodonAuthentication else { continue } + for authentication in AuthenticationServiceProvider.shared.authentications { + guard let user = authentication.user(in: managedObjectContext) else { continue } let accessToken = authentication.userAccessToken let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken) guard count > 0 else { continue } - let title = "@\(authentication.user.acctWithDomain)" + let title = "@\(user.acctWithDomain)" let subtitle = L10n.A11y.Plural.Count.Unread.notification(count) let item = UIApplicationShortcutItem( @@ -214,9 +214,8 @@ extension NotificationService { let needsCancelSubscription: Bool = try await managedObjectContext.perform { // check authentication exists - let authenticationRequest = MastodonAuthentication.sortedFetchRequest - authenticationRequest.predicate = MastodonAuthentication.predicate(userAccessToken: userAccessToken) - return managedObjectContext.safeFetch(authenticationRequest).first == nil + let results = AuthenticationServiceProvider.shared.authentications.filter { $0.userAccessToken == userAccessToken } + return results.first == nil } guard needsCancelSubscription else { @@ -255,22 +254,17 @@ extension NotificationService { private func authenticationBox(for pushNotification: MastodonPushNotification) async throws -> MastodonAuthenticationBox? { guard let authenticationService = self.authenticationService else { return nil } - let managedObjectContext = authenticationService.managedObjectContext - return try await managedObjectContext.perform { - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(userAccessToken: pushNotification.accessToken) - request.fetchLimit = 1 - guard let authentication = managedObjectContext.safeFetch(request).first else { return nil } - - return MastodonAuthenticationBox( - authenticationRecord: .init(objectID: authentication.objectID), - domain: authentication.domain, - userID: authentication.userID, - appAuthorization: .init(accessToken: authentication.appAccessToken), - userAuthorization: .init(accessToken: authentication.userAccessToken), - inMemoryCache: .sharedCache(for: authentication.objectID.description) - ) - } + let results = AuthenticationServiceProvider.shared.authentications.filter { $0.userAccessToken == pushNotification.accessToken } + guard let authentication = results.first else { return nil } + + return MastodonAuthenticationBox( + authentication: authentication, + domain: authentication.domain, + userID: authentication.userID, + appAuthorization: .init(accessToken: authentication.appAccessToken), + userAuthorization: .init(accessToken: authentication.userAccessToken), + inMemoryCache: .sharedCache(for: authentication.userAccessToken) + ) } } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index 4e5c217ba..dac67b607 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -159,7 +159,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { self.visibility = { // default private when user locked var visibility: Mastodon.Entity.Status.Visibility = { - guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user else { + guard let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return .public } return author.locked ? .private : .public @@ -227,7 +227,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { assertionFailure() return } - let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user + let author = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) var mentionAccts: [String] = [] if author?.id != status.author.id { @@ -262,9 +262,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { let _configuration: Mastodon.Entity.Instance.Configuration? = { var configuration: Mastodon.Entity.Instance.Configuration? = nil context.managedObjectContext.performAndWait { - guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) - else { return } - configuration = authentication.instance?.configuration + let authentication = authContext.mastodonAuthenticationBox.authentication + configuration = authentication.instance(in: context.managedObjectContext)?.configuration } return configuration }() @@ -325,7 +324,7 @@ extension ComposeContentViewModel { $authContext .sink { [weak self] authContext in guard let self = self else { return } - guard let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return } + guard let user = authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else { return } self.avatarURL = user.avatarImageURL() self.name = user.nameMetaContent ?? PlaintextMetaContent(string: user.displayNameWithFallback) self.username = user.acctWithDomain @@ -564,7 +563,7 @@ extension ComposeContentViewModel { let managedObjectContext = self.context.managedObjectContext var _author: ManagedObjectRecord? managedObjectContext.performAndWait { - _author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecord + _author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord } guard let author = _author else { throw AppError.badAuthentication @@ -620,7 +619,7 @@ extension ComposeContentViewModel { let managedObjectContext = self.context.managedObjectContext var _author: ManagedObjectRecord? managedObjectContext.performAndWait { - _author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user.asRecord + _author = authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext)?.asRecord } guard let author = _author else { throw AppError.badAuthentication diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index ed038f47f..e887e540f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -230,9 +230,8 @@ extension NotificationView.ViewModel { var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil context.managedObjectContext.performAndWait { - guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) - else { return } - configuration = authentication.instance?.configurationV2 + let authentication = authContext.mastodonAuthenticationBox.authentication + configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2 } return configuration }() diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f466fd819..c3b0caf7a 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -691,9 +691,8 @@ extension StatusView.ViewModel { var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil context.managedObjectContext.performAndWait { - guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) - else { return } - configuration = authentication.instance?.configurationV2 + let authentication = authContext.mastodonAuthenticationBox.authentication + configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2 } return configuration }() diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index 18383a053..8ed018c99 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -179,8 +179,8 @@ extension ShareViewController { extension ShareViewController { private func setupAuthContext() throws -> AuthContext? { - let request = MastodonAuthentication.activeSortedFetchRequest // use active order - let _authentication = try context.managedObjectContext.fetch(request).first + AuthenticationServiceProvider.shared.sortByActivation() + let _authentication = AuthenticationServiceProvider.shared.authentications.first let _authContext = _authentication.flatMap { AuthContext(authentication: $0) } return _authContext } diff --git a/WidgetExtension/Variants/FollowersCount/FollowersCountWidget.swift b/WidgetExtension/Variants/FollowersCount/FollowersCountWidget.swift index c8e53fb1c..bef484e39 100644 --- a/WidgetExtension/Variants/FollowersCount/FollowersCountWidget.swift +++ b/WidgetExtension/Variants/FollowersCount/FollowersCountWidget.swift @@ -86,9 +86,9 @@ private extension FollowersCountWidgetProvider { } guard - let desiredAccount = configuration.account ?? authBox.authenticationRecord.object( + let desiredAccount = configuration.account ?? authBox.authentication.user( in: WidgetExtension.appContext.managedObjectContext - )?.user.acctWithDomain + )?.acctWithDomain else { return completion(.unconfigured) } diff --git a/WidgetExtension/Variants/MultiFollowersCount/MultiFollowersCountWidget.swift b/WidgetExtension/Variants/MultiFollowersCount/MultiFollowersCountWidget.swift index 9049a5a79..15c1c4d14 100644 --- a/WidgetExtension/Variants/MultiFollowersCount/MultiFollowersCountWidget.swift +++ b/WidgetExtension/Variants/MultiFollowersCount/MultiFollowersCountWidget.swift @@ -86,9 +86,9 @@ private extension MultiFollowersCountWidgetProvider { if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) { desiredAccounts = configuredAccounts - } else if let currentlyLoggedInAccount = authBox.authenticationRecord.object( + } else if let currentlyLoggedInAccount = authBox.authentication.user( in: WidgetExtension.appContext.managedObjectContext - )?.user.acctWithDomain { + )?.acctWithDomain { desiredAccounts = [currentlyLoggedInAccount] } else { return completion(.unconfigured)