mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Begin moving filesystem cache responsibility into PersistenceManager
contributes to iOS-319
This commit is contained in:
parent
11c0deff2d
commit
9ed9c79f25
@ -66,7 +66,7 @@ final public class SceneCoordinator {
|
||||
}
|
||||
let domain = authentication.domain
|
||||
let userID = authentication.userID
|
||||
let isSuccess = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: userID)
|
||||
let isSuccess = AuthenticationServiceProvider.shared.activateUser(userID, inDomain: domain)
|
||||
guard isSuccess else { return }
|
||||
|
||||
self.setup()
|
||||
@ -94,7 +94,7 @@ final public class SceneCoordinator {
|
||||
// show notification related content
|
||||
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
||||
guard let authenticationBox = self.authenticationBox else { return }
|
||||
guard let me = authenticationBox.authentication.account() else { return }
|
||||
guard let me = authenticationBox.cachedAccount else { return }
|
||||
let notificationID = String(pushNotification.notificationID)
|
||||
|
||||
switch type {
|
||||
|
@ -37,7 +37,7 @@ extension UserSection {
|
||||
case .account(let account, let relationship):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
||||
|
||||
guard let me = authenticationBox.authentication.account() else { return cell }
|
||||
guard let me = authenticationBox.cachedAccount else { return cell }
|
||||
|
||||
cell.userView.setButtonState(.loading)
|
||||
cell.configure(
|
||||
|
@ -83,10 +83,8 @@ extension DataSourceFacade {
|
||||
|
||||
do {
|
||||
let account = try await APIService.shared.accountInfo(
|
||||
domain: domain,
|
||||
userID: accountID,
|
||||
authorization: provider.authenticationBox.userAuthorization
|
||||
).value
|
||||
provider.authenticationBox
|
||||
)
|
||||
|
||||
provider.coordinator.hideLoading()
|
||||
|
||||
@ -103,7 +101,7 @@ extension DataSourceFacade {
|
||||
) async {
|
||||
provider.coordinator.showLoading()
|
||||
|
||||
guard let me = provider.authenticationBox.authentication.account(),
|
||||
guard let me = provider.authenticationBox.cachedAccount,
|
||||
let relationship = try? await APIService.shared.relationship(forAccounts: [account], authenticationBox: provider.authenticationBox).value.first else {
|
||||
return provider.coordinator.hideLoading()
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ extension AccountListViewModel {
|
||||
authentication: MastodonAuthentication,
|
||||
activeAuthentication: MastodonAuthentication
|
||||
) {
|
||||
guard let account = authentication.account() else { return }
|
||||
guard let account = authentication.cachedAccount() else { return }
|
||||
|
||||
// avatar
|
||||
cell.avatarButton.avatarImageView.configure(with: account.avatarImageURL())
|
||||
|
@ -144,7 +144,7 @@ extension AccountListViewController: UITableViewDelegate {
|
||||
case .authentication(let record):
|
||||
assert(Thread.isMainThread)
|
||||
Task { @MainActor in
|
||||
let isActive = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: record.domain, userID: record.userID)
|
||||
let isActive = AuthenticationServiceProvider.shared.activateUser(record.userID, inDomain: record.domain)
|
||||
guard isActive else { return }
|
||||
self.coordinator.setup()
|
||||
} // end Task
|
||||
|
@ -355,7 +355,7 @@ extension HomeTimelineViewController {
|
||||
|
||||
let userDoesntFollowPeople: Bool
|
||||
if let authenticationBox = self?.authenticationBox,
|
||||
let me = authenticationBox.authentication.account() {
|
||||
let me = authenticationBox.cachedAccount {
|
||||
userDoesntFollowPeople = me.followersCount == 0
|
||||
} else {
|
||||
userDoesntFollowPeople = true
|
||||
|
@ -96,7 +96,7 @@ final class HomeTimelineViewModel: NSObject {
|
||||
self.authenticationBox = authenticationBox
|
||||
self.dataController = FeedDataController(context: context, authenticationBox: authenticationBox)
|
||||
super.init()
|
||||
self.dataController.records = (try? FileManager.default.cachedHomeTimeline(for: authenticationBox).map {
|
||||
self.dataController.records = (try? PersistenceManager.shared.cachedTimeline(.homeTimeline(authenticationBox)).map {
|
||||
MastodonFeed.fromStatus($0, kind: .home)
|
||||
}) ?? []
|
||||
|
||||
|
@ -189,7 +189,7 @@ extension NotificationView {
|
||||
notificationTypeIndicatorLabel.reset()
|
||||
}
|
||||
|
||||
if let me = authenticationBox.authentication.account() {
|
||||
if let me = authenticationBox.cachedAccount {
|
||||
let isMyself = (author == me)
|
||||
let isMuting: Bool
|
||||
let isBlocking: Bool
|
||||
|
@ -125,8 +125,7 @@ class MastodonLoginViewController: UIViewController, NeedsDependency {
|
||||
.authenticated.sink { (domain, account) in
|
||||
Task { @MainActor in
|
||||
do {
|
||||
_ = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: account.id)
|
||||
FileManager.default.store(account: account, forUserID: MastodonUserIdentifier(domain: domain, userID: account.id))
|
||||
AuthenticationServiceProvider.shared.activateUser(account.id, inDomain: domain)
|
||||
|
||||
self.coordinator.setup()
|
||||
} catch {
|
||||
|
@ -151,8 +151,8 @@ extension MastodonPickServerViewController {
|
||||
.authenticated
|
||||
.asyncMap { domain, user -> Result<Bool, Error> in
|
||||
do {
|
||||
let result = try await AuthenticationServiceProvider.shared.activeMastodonUser(domain: domain, userID: user.id)
|
||||
return .success(result)
|
||||
let activated = AuthenticationServiceProvider.shared.activateUser(user.id, inDomain: domain)
|
||||
return .success(activated)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
@ -209,42 +209,9 @@ extension AuthenticationViewModel {
|
||||
.authentications
|
||||
.insert(authentication, at: 0)
|
||||
|
||||
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
|
||||
|
||||
return response
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
static func verifyAndSaveAuthentication(
|
||||
context: AppContext,
|
||||
domain: String,
|
||||
clientID: String,
|
||||
clientSecret: String,
|
||||
userToken: String
|
||||
) async throws -> Mastodon.Entity.Account {
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: userToken)
|
||||
|
||||
let account = try await APIService.shared.accountVerifyCredentials(
|
||||
domain: domain,
|
||||
authorization: authorization
|
||||
)
|
||||
|
||||
let authentication = MastodonAuthentication.createFrom(domain: domain,
|
||||
userID: account.id,
|
||||
username: account.username,
|
||||
appAccessToken: userToken, // TODO: swap app token
|
||||
userAccessToken: userToken,
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
accountCreatedAt: account.createdAt)
|
||||
|
||||
AuthenticationServiceProvider.shared
|
||||
.authentications
|
||||
.insert(authentication, at: 0)
|
||||
|
||||
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
|
||||
|
||||
return account
|
||||
}
|
||||
}
|
||||
|
@ -653,9 +653,8 @@ extension ProfileViewController {
|
||||
viewModel.profileAboutViewModel.fields = updatedAccount.mastodonFields
|
||||
}
|
||||
|
||||
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox).value {
|
||||
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox) {
|
||||
viewModel.me = updatedMe
|
||||
FileManager.default.store(account: updatedMe, forUserID: viewModel.authenticationBox.authentication.userIdentifier())
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
@ -1097,10 +1096,9 @@ extension ProfileViewController {
|
||||
} else if viewModel.account == viewModel.me {
|
||||
// update my profile
|
||||
Task {
|
||||
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox).value {
|
||||
if let updatedMe = try? await APIService.shared.authenticatedUserInfo(authenticationBox: viewModel.authenticationBox) {
|
||||
viewModel.me = updatedMe
|
||||
viewModel.account = updatedMe
|
||||
FileManager.default.store(account: updatedMe, forUserID: viewModel.authenticationBox.authentication.userIdentifier())
|
||||
}
|
||||
|
||||
viewModel.isUpdating = false
|
||||
|
@ -181,7 +181,6 @@ class ProfileViewModel: NSObject {
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
|
||||
return APIService.shared.accountVerifyCredentials(domain: domain, authorization: authorization)
|
||||
.tryMap { response in
|
||||
FileManager.default.store(account: response.value, forUserID: mastodonAuthentication.userIdentifier())
|
||||
return response
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
@ -233,8 +232,6 @@ extension ProfileViewModel {
|
||||
query: query,
|
||||
authorization: authorization
|
||||
)
|
||||
|
||||
FileManager.default.store(account: response.value, forUserID: authenticationBox.authentication.userIdentifier())
|
||||
NotificationCenter.default.post(name: .userFetched, object: nil)
|
||||
|
||||
return response
|
||||
|
@ -86,7 +86,7 @@ class MainTabBarController: UITabBarController {
|
||||
homeTimelineViewController.viewModel = HomeTimelineViewModel(context: context, authenticationBox: authenticationBox)
|
||||
searchViewController.viewModel = SearchViewModel(context: context, authenticationBox: authenticationBox)
|
||||
|
||||
if let account = authenticationBox.authentication.account() {
|
||||
if let account = authenticationBox.cachedAccount {
|
||||
meProfileViewController.viewModel = ProfileViewModel(context: context, authenticationBox: authenticationBox, account: account, relationship: nil, me: account)
|
||||
}
|
||||
}
|
||||
@ -112,7 +112,7 @@ extension MainTabBarController {
|
||||
willSet {
|
||||
if let profileView = (newValue as? UINavigationController)?.topViewController as? ProfileViewController{
|
||||
guard let authenticationBox,
|
||||
let account = authenticationBox.authentication.account() else { return }
|
||||
let account = authenticationBox.cachedAccount else { return }
|
||||
profileView.viewModel = ProfileViewModel(context: self.context, authenticationBox: authenticationBox, account: account, relationship: nil, me: account)
|
||||
}
|
||||
}
|
||||
@ -204,7 +204,7 @@ extension MainTabBarController {
|
||||
.sink { [weak self] _ in
|
||||
guard let self,
|
||||
let authenticationBox,
|
||||
let account = authenticationBox.authentication.account() else { return }
|
||||
let account = authenticationBox.cachedAccount else { return }
|
||||
|
||||
self.avatarURL = account.avatarImageURL()
|
||||
|
||||
@ -381,7 +381,6 @@ extension MainTabBarController {
|
||||
|
||||
Task { @MainActor in
|
||||
let profileResponse = try await APIService.shared.authenticatedUserInfo(authenticationBox: authenticationBox)
|
||||
FileManager.default.store(account: profileResponse.value, forUserID: authenticationBox.authentication.userIdentifier())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ extension SidebarViewModel {
|
||||
let imageURL: URL?
|
||||
switch item {
|
||||
case .me:
|
||||
let account = self.authenticationBox?.authentication.account()
|
||||
let account = self.authenticationBox?.authentication.cachedAccount()
|
||||
imageURL = account?.avatarImageURL()
|
||||
case .home, .search, .compose, .notifications:
|
||||
// no custom avatar for other tabs
|
||||
@ -134,7 +134,7 @@ extension SidebarViewModel {
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
case .me:
|
||||
guard let account = self.authenticationBox?.authentication.account() else { return }
|
||||
guard let account = self.authenticationBox?.authentication.cachedAccount() else { return }
|
||||
|
||||
let currentUserDisplayName = account.displayNameWithFallback
|
||||
cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName)
|
||||
|
@ -44,7 +44,7 @@ extension SearchResultSection {
|
||||
case .account(let account, let relationship):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as! UserTableViewCell
|
||||
|
||||
guard let me = authenticationBox.authentication.account() else { return cell }
|
||||
guard let me = authenticationBox.cachedAccount else { return cell }
|
||||
|
||||
cell.userView.setButtonState(.loading)
|
||||
cell.configure(
|
||||
|
@ -139,7 +139,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
switch (profile, statusID) {
|
||||
case (profile, nil):
|
||||
Task {
|
||||
guard let me = authenticationBox.authentication.account() else { return }
|
||||
guard let me = authenticationBox.cachedAccount else { return }
|
||||
|
||||
guard let account = try await APIService.shared.search(
|
||||
query: .init(q: incomingURL.absoluteString, type: .accounts, resolve: true),
|
||||
@ -208,9 +208,8 @@ extension SceneDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
let _isActive = try? await AuthenticationServiceProvider.shared.activeMastodonUser(
|
||||
domain: authentication.domain,
|
||||
userID: authentication.userID
|
||||
let _isActive = AuthenticationServiceProvider.shared.activateUser(authentication.userID,
|
||||
inDomain: authentication.domain
|
||||
)
|
||||
|
||||
guard _isActive == true else {
|
||||
@ -283,7 +282,7 @@ extension SceneDelegate {
|
||||
|
||||
Task {
|
||||
do {
|
||||
guard let me = authenticationBox.authentication.account() else { return }
|
||||
guard let me = authenticationBox.cachedAccount else { return }
|
||||
|
||||
guard let account = try await APIService.shared.search(
|
||||
query: .init(q: components[1], type: .accounts, resolve: true),
|
||||
|
@ -16,7 +16,7 @@ extension Account {
|
||||
@MainActor
|
||||
static func fetch() async throws -> [Account] {
|
||||
let accounts = AuthenticationServiceProvider.shared.authentications.compactMap { mastodonAuthentication -> Account? in
|
||||
guard let authenticatedAccount = mastodonAuthentication.account() else {
|
||||
guard let authenticatedAccount = mastodonAuthentication.cachedAccount() else {
|
||||
return nil
|
||||
}
|
||||
let account = Account(
|
||||
|
@ -30,6 +30,11 @@ public struct MastodonAuthenticationBox: UserIdentifier {
|
||||
public init(authentication: MastodonAuthentication) {
|
||||
self.authentication = authentication
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public var cachedAccount: Mastodon.Entity.Account? {
|
||||
return authentication.cachedAccount()
|
||||
}
|
||||
}
|
||||
|
||||
public class MastodonAccountInMemoryCache {
|
||||
|
@ -101,13 +101,16 @@ public class AuthenticationServiceProvider: ObservableObject {
|
||||
authentications.removeAll(where: { $0 == authentication })
|
||||
}
|
||||
|
||||
func activateAuthentication(in domain: String, for userID: String) {
|
||||
public func activateUser(_ userID: String, inDomain domain: String) -> Bool {
|
||||
var found = false
|
||||
authentications = authentications.map { authentication in
|
||||
guard authentication.domain == domain, authentication.userID == userID else {
|
||||
return authentication
|
||||
}
|
||||
found = true
|
||||
return authentication.updating(activatedAt: Date())
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func getAuthentication(in domain: String, for userID: String) -> MastodonAuthentication? {
|
||||
@ -141,16 +144,6 @@ public extension AuthenticationServiceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
func activeMastodonUser(domain: String, userID: String) async throws -> Bool {
|
||||
var isActive = false
|
||||
|
||||
AuthenticationServiceProvider.shared.activateAuthentication(in: domain, for: userID)
|
||||
|
||||
isActive = true
|
||||
|
||||
return isActive
|
||||
}
|
||||
|
||||
func signOutMastodonUser(authentication: MastodonAuthentication) async throws {
|
||||
try await AuthenticationServiceProvider.shared.delete(authentication: authentication)
|
||||
_ = try await APIService.shared.cancelSubscription(domain: authentication.domain, authorization: authentication.authorization)
|
||||
@ -230,11 +223,7 @@ public extension AuthenticationServiceProvider {
|
||||
// it when we need it to display on the home timeline.
|
||||
// We need this (also) for the Account-list, but it might be the wrong place. App Startup might be more appropriate
|
||||
for authentication in authentications {
|
||||
guard let account = try? await APIService.shared.accountInfo(domain: authentication.domain,
|
||||
userID: authentication.userID,
|
||||
authorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)).value else { continue }
|
||||
|
||||
FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
|
||||
guard let account = try? await APIService.shared.accountInfo(MastodonAuthenticationBox(authentication: authentication)) else { continue }
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .userFetched, object: nil)
|
||||
|
@ -136,14 +136,9 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier {
|
||||
)
|
||||
}
|
||||
|
||||
public func account() -> Mastodon.Entity.Account? {
|
||||
|
||||
let account = FileManager
|
||||
.default
|
||||
.accounts(for: self.userIdentifier())
|
||||
.first(where: { $0.id == userID })
|
||||
|
||||
return account
|
||||
@MainActor
|
||||
public func cachedAccount() -> Mastodon.Entity.Account? {
|
||||
return PersistenceManager.shared.cachedAccount(for: self)
|
||||
}
|
||||
|
||||
public func userIdentifier() -> MastodonUserIdentifier {
|
||||
|
@ -1,57 +0,0 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
public extension FileManager {
|
||||
func store(account: Mastodon.Entity.Account, forUserID userID: UserIdentifier) {
|
||||
var accounts = accounts(for: userID)
|
||||
|
||||
if let index = accounts.firstIndex(of: account) {
|
||||
accounts.remove(at: index)
|
||||
}
|
||||
|
||||
accounts.append(account)
|
||||
|
||||
storeJSON(accounts, userID: userID)
|
||||
}
|
||||
|
||||
func accounts(for userId: UserIdentifier) -> [Mastodon.Entity.Account] {
|
||||
guard let sharedDirectory else { assert(false); return [] }
|
||||
|
||||
let accountPath = Persistence.accounts(userId).filepath(baseURL: sharedDirectory)
|
||||
|
||||
guard let data = try? Data(contentsOf: accountPath) else { return [] }
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.dateDecodingStrategy = .iso8601
|
||||
|
||||
do {
|
||||
let accounts = try jsonDecoder.decode([Mastodon.Entity.Account].self, from: data)
|
||||
assert(accounts.count > 0)
|
||||
return accounts
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private extension FileManager {
|
||||
private func storeJSON(_ encodable: Encodable, userID: UserIdentifier) {
|
||||
guard let sharedDirectory else { return }
|
||||
|
||||
let jsonEncoder = JSONEncoder()
|
||||
jsonEncoder.dateEncodingStrategy = .iso8601
|
||||
do {
|
||||
let data = try jsonEncoder.encode(encodable)
|
||||
|
||||
let accountsPath = Persistence.accounts( userID).filepath(baseURL: sharedDirectory)
|
||||
try data.write(to: accountsPath)
|
||||
} catch {
|
||||
debugPrint(error.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
@MainActor
|
||||
public class PersistenceManager {
|
||||
@ -37,7 +38,125 @@ public class PersistenceManager {
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
func newTaskContext() -> NSManagedObjectContext {
|
||||
public func newTaskContext() -> NSManagedObjectContext {
|
||||
return coreDataStack.newTaskContext()
|
||||
}
|
||||
|
||||
public func cachedTimeline(_ timeline: Persistence) throws -> [MastodonStatus] {
|
||||
return try FileManager.default.cached(timeline: timeline).map(MastodonStatus.fromEntity)
|
||||
}
|
||||
|
||||
public func cachedAccount(for authentication: MastodonAuthentication) -> Mastodon.Entity.Account? {
|
||||
let account = FileManager
|
||||
.default
|
||||
.accounts(for: authentication.userIdentifier())
|
||||
.first(where: { $0.id == authentication.userID })
|
||||
return account
|
||||
}
|
||||
|
||||
public func cacheAccount(_ account: Mastodon.Entity.Account, for authenticationBox: MastodonAuthenticationBox) {
|
||||
FileManager.default.store(account: account, forUserID: authenticationBox.authentication.userIdentifier())
|
||||
}
|
||||
}
|
||||
|
||||
private extension FileManager {
|
||||
static let cacheItemsLimit: Int = 100 // max number of items to cache
|
||||
|
||||
func cached<T: Decodable>(timeline: Persistence) throws -> [T] {
|
||||
guard let cachesDirectory else { return [] }
|
||||
|
||||
let filePath = timeline.filepath(baseURL: cachesDirectory)
|
||||
|
||||
guard let data = try? Data(contentsOf: filePath) else { return [] }
|
||||
|
||||
do {
|
||||
let items = try JSONDecoder().decode([T].self, from: data)
|
||||
|
||||
return items
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cache<T: Encodable>(_ items: [T], timeline: Persistence) {
|
||||
guard let cachesDirectory else { return }
|
||||
|
||||
let processableItems: [T]
|
||||
if items.count > Self.cacheItemsLimit {
|
||||
processableItems = items.dropLast(items.count - Self.cacheItemsLimit)
|
||||
} else {
|
||||
processableItems = items
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try JSONEncoder().encode(processableItems)
|
||||
|
||||
let filePath = timeline.filepath(baseURL: cachesDirectory)
|
||||
try data.write(to: filePath)
|
||||
} catch {
|
||||
debugPrint(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidate(timeline: Persistence) {
|
||||
guard let cachesDirectory else { return }
|
||||
|
||||
let filePath = timeline.filepath(baseURL: cachesDirectory)
|
||||
|
||||
try? removeItem(at: filePath)
|
||||
}
|
||||
}
|
||||
|
||||
private extension FileManager {
|
||||
func store(account: Mastodon.Entity.Account, forUserID userID: UserIdentifier) {
|
||||
var accounts = accounts(for: userID)
|
||||
|
||||
if let index = accounts.firstIndex(of: account) {
|
||||
accounts.remove(at: index)
|
||||
}
|
||||
|
||||
accounts.append(account)
|
||||
|
||||
storeJSON(accounts, userID: userID)
|
||||
}
|
||||
|
||||
func accounts(for userId: UserIdentifier) -> [Mastodon.Entity.Account] {
|
||||
guard let sharedDirectory else { assert(false); return [] }
|
||||
|
||||
let accountPath = Persistence.accounts(userId).filepath(baseURL: sharedDirectory)
|
||||
|
||||
guard let data = try? Data(contentsOf: accountPath) else { return [] }
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.dateDecodingStrategy = .iso8601
|
||||
|
||||
do {
|
||||
let accounts = try jsonDecoder.decode([Mastodon.Entity.Account].self, from: data)
|
||||
assert(accounts.count > 0)
|
||||
return accounts
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private extension FileManager {
|
||||
private func storeJSON(_ encodable: Encodable, userID: UserIdentifier) {
|
||||
guard let sharedDirectory else { return }
|
||||
|
||||
let jsonEncoder = JSONEncoder()
|
||||
jsonEncoder.dateEncodingStrategy = .iso8601
|
||||
do {
|
||||
let data = try jsonEncoder.encode(encodable)
|
||||
|
||||
let accountsPath = Persistence.accounts( userID).filepath(baseURL: sharedDirectory)
|
||||
try data.write(to: accountsPath)
|
||||
} catch {
|
||||
debugPrint(error.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,27 +14,23 @@ import MastodonSDK
|
||||
extension APIService {
|
||||
public func authenticatedUserInfo(
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
||||
try await accountInfo(
|
||||
) async throws -> Mastodon.Entity.Account {
|
||||
let authenticated = try await accountInfo(authenticationBox)
|
||||
return authenticated
|
||||
}
|
||||
|
||||
public func accountInfo(_ authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Entity.Account {
|
||||
let account = try await Mastodon.API.Account.accountInfo(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
userID: authenticationBox.userID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
)
|
||||
}
|
||||
|
||||
public func accountInfo(
|
||||
domain: String,
|
||||
userID: Mastodon.Entity.Account.ID,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
||||
let response = try await Mastodon.API.Account.accountInfo(
|
||||
session: session,
|
||||
domain: domain,
|
||||
userID: userID,
|
||||
authorization: authorization
|
||||
).singleOutput()
|
||||
).singleOutput().value
|
||||
|
||||
return response
|
||||
PersistenceManager.shared.cacheAccount(account, for: authenticationBox)
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ extension NotificationService {
|
||||
|
||||
var items: [UIApplicationShortcutItem] = []
|
||||
for authentication in AuthenticationServiceProvider.shared.authentications {
|
||||
guard let account = authentication.account() else { continue }
|
||||
guard let account = authentication.cachedAccount() else { continue }
|
||||
let accessToken = authentication.userAccessToken
|
||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken)
|
||||
guard count > 0 else { continue }
|
||||
|
@ -156,7 +156,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
self.visibility = {
|
||||
// default private when user locked
|
||||
var visibility: Mastodon.Entity.Status.Visibility = {
|
||||
guard let author = authenticationBox.authentication.account() else {
|
||||
guard let author = authenticationBox.cachedAccount else {
|
||||
return .public
|
||||
}
|
||||
return author.locked ? .private : .public
|
||||
@ -195,7 +195,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
|
||||
switch destination {
|
||||
case .reply(let record):
|
||||
let status = record.entity
|
||||
let author = authenticationBox.authentication.account()
|
||||
let author = authenticationBox.cachedAccount
|
||||
|
||||
var mentionAccts: [String] = []
|
||||
if author?.id != status.account.id {
|
||||
@ -306,7 +306,7 @@ extension ComposeContentViewModel {
|
||||
// bind author
|
||||
$authenticationBox
|
||||
.sink { [weak self] authenticationBox in
|
||||
guard let self, let account = authenticationBox.authentication.account() else { return }
|
||||
guard let self, let account = authenticationBox.cachedAccount else { return }
|
||||
|
||||
self.avatarURL = account.avatarImageURL()
|
||||
|
||||
@ -575,7 +575,7 @@ extension ComposeContentViewModel {
|
||||
|
||||
public func statusPublisher() throws -> StatusPublisher {
|
||||
|
||||
guard authenticationBox.authentication.account() != nil else {
|
||||
guard authenticationBox.cachedAccount != nil else {
|
||||
throw AppError.badAuthentication
|
||||
}
|
||||
|
||||
@ -624,7 +624,7 @@ extension ComposeContentViewModel {
|
||||
guard case let .editStatus(status, _) = composeContext else { return nil }
|
||||
|
||||
// author
|
||||
guard let author = authenticationBox.authentication.account() else {
|
||||
guard let author = authenticationBox.cachedAccount else {
|
||||
throw AppError.badAuthentication
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ extension StatusView {
|
||||
private func configureHeader(status: MastodonStatus) {
|
||||
if status.entity.reblogged == true,
|
||||
let authenticationBox = viewModel.authenticationBox,
|
||||
let account = authenticationBox.authentication.account() {
|
||||
let account = authenticationBox.cachedAccount {
|
||||
|
||||
let name = account.displayNameWithFallback
|
||||
let emojis = account.emojis
|
||||
|
@ -71,12 +71,12 @@ struct FollowersCountWidget: Widget {
|
||||
|
||||
private extension FollowersCountWidgetProvider {
|
||||
func loadCurrentEntry(for configuration: FollowersCountIntent, in context: Context, completion: @escaping (FollowersCountEntry) -> Void) {
|
||||
Task {
|
||||
Task { @MainActor in
|
||||
|
||||
await AuthenticationServiceProvider.shared.prepareForUse()
|
||||
AuthenticationServiceProvider.shared.prepareForUse()
|
||||
|
||||
guard
|
||||
let authBox = await AuthenticationServiceProvider.shared.activeAuthentication
|
||||
let authBox = AuthenticationServiceProvider.shared.activeAuthentication
|
||||
else {
|
||||
guard !context.isPreview else {
|
||||
return completion(.placeholder)
|
||||
@ -85,7 +85,7 @@ private extension FollowersCountWidgetProvider {
|
||||
}
|
||||
|
||||
guard
|
||||
let desiredAccount = configuration.account ?? authBox.authentication.account()?.acctWithDomain
|
||||
let desiredAccount = configuration.account ?? authBox.cachedAccount?.acctWithDomain
|
||||
else {
|
||||
return completion(.unconfigured)
|
||||
}
|
||||
|
@ -71,12 +71,12 @@ struct MultiFollowersCountWidget: Widget {
|
||||
|
||||
private extension MultiFollowersCountWidgetProvider {
|
||||
func loadCurrentEntry(for configuration: MultiFollowersCountIntent, in context: Context, completion: @escaping (MultiFollowersCountEntry) -> Void) {
|
||||
Task {
|
||||
Task { @MainActor in
|
||||
|
||||
await AuthenticationServiceProvider.shared.prepareForUse()
|
||||
AuthenticationServiceProvider.shared.prepareForUse()
|
||||
|
||||
guard
|
||||
let authBox = await AuthenticationServiceProvider.shared.activeAuthentication
|
||||
let authBox = AuthenticationServiceProvider.shared.activeAuthentication
|
||||
else {
|
||||
guard !context.isPreview else {
|
||||
return completion(.placeholder)
|
||||
@ -88,7 +88,7 @@ private extension MultiFollowersCountWidgetProvider {
|
||||
|
||||
if let configuredAccounts = configuration.accounts?.compactMap({ $0 }) {
|
||||
desiredAccounts = configuredAccounts
|
||||
} else if let currentlyLoggedInAccount = authBox.authentication.account()?.acctWithDomain {
|
||||
} else if let currentlyLoggedInAccount = authBox.cachedAccount?.acctWithDomain {
|
||||
desiredAccounts = [currentlyLoggedInAccount]
|
||||
} else {
|
||||
return completion(.unconfigured)
|
||||
|
Loading…
x
Reference in New Issue
Block a user