From 7f8587913f6105162ad526036b029bfc08c43052 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Fri, 21 Jun 2024 12:09:49 +0200 Subject: [PATCH] Implement TranslationLanguages fetching (IOS-271) --- .../AuthenticationServiceProvider.swift | 31 ++++++++++++- .../MastodonCore/MastodonAuthentication.swift | 46 +++++++++++++++++-- .../Service/API/APIService+Instance.swift | 7 +++ .../Service/InstanceService.swift | 40 ++++++++++++---- ...on+API+Instance+TranslationLanguages.swift | 30 ++++++++++++ .../API/Mastodon+API+Instance.swift | 2 + .../Entity/Mastodon+Entity+Instance.swift | 10 ++++ .../Entity/Mastodon+Entity+InstanceV2.swift | 10 ++++ 8 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance+TranslationLanguages.swift diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 544d3836d..380c18a70 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -23,11 +23,40 @@ public class AuthenticationServiceProvider: ObservableObject { } } - func update(instance: Instance, where domain: String) { + @discardableResult + func updating(instance: Instance, where domain: String) -> Self { authentications = authentications.map { authentication in guard authentication.domain == domain else { return authentication } return authentication.updating(instance: instance) } + return self + } + + @discardableResult + func updating(instanceV1 instance: Mastodon.Entity.Instance, for domain: String) -> Self { + authentications = authentications.map { authentication in + guard authentication.domain == domain else { return authentication } + return authentication.updating(instanceV1: instance) + } + return self + } + + @discardableResult + func updating(instanceV2 instance: Mastodon.Entity.V2.Instance, for domain: String) -> Self { + authentications = authentications.map { authentication in + guard authentication.domain == domain else { return authentication } + return authentication.updating(instanceV2: instance) + } + return self + } + + @discardableResult + func updating(translationLanguages: TranslationLanguages, for domain: String) -> Self { + authentications = authentications.map { authentication in + guard authentication.domain == domain else { return authentication } + return authentication.updating(translationLanguages: translationLanguages) + } + return self } func delete(authentication: MastodonAuthentication) throws { diff --git a/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift index f8c4410b7..02416c8d0 100644 --- a/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift +++ b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift @@ -5,6 +5,11 @@ import CoreDataStack import MastodonSDK public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { + public enum InstanceConfiguration: Codable, Hashable { + case v1(Mastodon.Entity.Instance, TranslationLanguages) + case v2(Mastodon.Entity.V2.Instance, TranslationLanguages) + } + public typealias ID = UUID public private(set) var identifier: ID @@ -22,6 +27,7 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { public private(set) var userID: String public private(set) var instanceObjectIdURI: URL? + public private(set) var instanceConfiguration: InstanceConfiguration? public var persistenceIdentifier: String { "\(username)@\(domain)" @@ -49,7 +55,8 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { updatedAt: now, activedAt: now, userID: userID, - instanceObjectIdURI: nil + instanceObjectIdURI: nil, + instanceConfiguration: nil ) } @@ -65,7 +72,8 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { updatedAt: Date? = nil, activedAt: Date? = nil, userID: String? = nil, - instanceObjectIdURI: URL? = nil + instanceObjectIdURI: URL? = nil, + instanceConfiguration: InstanceConfiguration? = nil ) -> Self { MastodonAuthentication( identifier: identifier ?? self.identifier, @@ -79,7 +87,8 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { updatedAt: updatedAt ?? self.updatedAt, activedAt: activedAt ?? self.activedAt, userID: userID ?? self.userID, - instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI + instanceObjectIdURI: instanceObjectIdURI ?? self.instanceObjectIdURI, + instanceConfiguration: instanceConfiguration ?? self.instanceConfiguration ) } @@ -117,6 +126,37 @@ public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { copy(instanceObjectIdURI: instance.objectID.uriRepresentation()) } + func updating(instanceV1 instance: Mastodon.Entity.Instance) -> Self { + guard + let instanceConfiguration = self.instanceConfiguration, + case let InstanceConfiguration.v1(_, translationLanguages) = instanceConfiguration + else { + return copy(instanceConfiguration: .v1(instance, [:])) + } + return copy(instanceConfiguration: .v1(instance, translationLanguages)) + } + + func updating(instanceV2 instance: Mastodon.Entity.V2.Instance) -> Self { + guard + let instanceConfiguration = self.instanceConfiguration, + case let InstanceConfiguration.v2(_, translationLanguages) = instanceConfiguration + else { + return copy(instanceConfiguration: .v2(instance, [:])) + } + return copy(instanceConfiguration: .v2(instance, translationLanguages)) + } + + func updating(translationLanguages: TranslationLanguages) -> Self { + switch self.instanceConfiguration { + case .v1(let instance, _): + return copy(instanceConfiguration: .v1(instance, translationLanguages)) + case .v2(let instance, _): + return copy(instanceConfiguration: .v2(instance, translationLanguages)) + case .none: + return self + } + } + func updating(activatedAt: Date) -> Self { copy(activedAt: activatedAt) } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift index 140c37157..9464c1c42 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Instance.swift @@ -33,4 +33,11 @@ extension APIService { ) -> AnyPublisher, Error> { return Mastodon.API.Instance.extendedDescription(session: session, authorization: authenticationBox?.userAuthorization, domain: domain) } + + public func translationLanguages( + domain: String, + authenticationBox: MastodonAuthenticationBox? + ) -> AnyPublisher, Error> { + return Mastodon.API.Instance.translationLanguages(session: session, authorization: authenticationBox?.userAuthorization, domain: domain) + } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift index 5088d8124..8d1bc41b5 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift @@ -56,10 +56,13 @@ extension InstanceService { return self.updateInstance(domain: domain, response: response) } } -// .flatMap { [unowned self] response -> AnyPublisher in -// return -// } - .sink { _ in + .sink { [weak self] completion in + switch completion { + case .finished: + self?.updateTranslationLanguages(domain: domain) + case .failure: + break + } } receiveValue: { [weak self] response in guard let _ = self else { return } // do nothing @@ -67,19 +70,37 @@ extension InstanceService { .store(in: &disposeBag) } + func updateTranslationLanguages(domain: String) { + apiService?.translationLanguages(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first) + .sink(receiveCompletion: { completion in + // no-op + }, receiveValue: { [weak self] response in + self?.updateTranslationLanguages(domain: domain, response: response) + }) + .store(in: &disposeBag) + } + + private func updateTranslationLanguages(domain: String, response: Mastodon.Response.Content) { + AuthenticationServiceProvider.shared + .updating(translationLanguages: response.value, for: domain) + } + private func updateInstance(domain: String, response: Mastodon.Response.Content) -> AnyPublisher { let managedObjectContext = self.backgroundManagedObjectContext + let instanceEntity = response.value return managedObjectContext.performChanges { // get instance let (instance, _) = APIService.CoreData.createOrMergeInstance( into: managedObjectContext, domain: domain, - entity: response.value, + entity: instanceEntity, networkDate: response.networkDate ) // update instance - AuthenticationServiceProvider.shared.update(instance: instance, where: domain) + AuthenticationServiceProvider.shared + .updating(instance: instance, where: domain) + .updating(instanceV1: instanceEntity, for: domain) } .setFailureType(to: Error.self) .tryMap { result in @@ -95,19 +116,22 @@ extension InstanceService { private func updateInstanceV2(domain: String, response: Mastodon.Response.Content) -> AnyPublisher { let managedObjectContext = self.backgroundManagedObjectContext + let instanceEntity = response.value return managedObjectContext.performChanges { // get instance let (instance, _) = APIService.CoreData.createOrMergeInstance( in: managedObjectContext, context: .init( domain: domain, - entity: response.value, + entity: instanceEntity, networkDate: response.networkDate ) ) // update instance - AuthenticationServiceProvider.shared.update(instance: instance, where: domain) + AuthenticationServiceProvider.shared + .updating(instance: instance, where: domain) + .updating(instanceV2: instanceEntity, for: domain) } .setFailureType(to: Error.self) .tryMap { result in diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance+TranslationLanguages.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance+TranslationLanguages.swift new file mode 100644 index 000000000..fded25ebf --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance+TranslationLanguages.swift @@ -0,0 +1,30 @@ +// +// File.swift +// +// +// Created by Marcus Kida on 20.06.24. +// + +import Foundation +import Combine + +extension Mastodon.API.Instance { + + static func translationLanguagesEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("instance/translation_languages") + } + + public static func translationLanguages( + session: URLSession, + authorization: Mastodon.API.OAuth.Authorization?, + domain: String + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get(url: translationLanguagesEndpointURL(domain: domain), authorization: authorization) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: TranslationLanguages.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance.swift index 8cf9430cd..627a8672d 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Instance.swift @@ -8,6 +8,8 @@ import Foundation import Combine +public typealias TranslationLanguages = [String: [String]] + extension Mastodon.API.Instance { static func instanceEndpointURL(domain: String) -> URL { diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift index bbe37cce9..fc34d841a 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift @@ -172,3 +172,13 @@ extension Mastodon.Entity.Instance.Configuration { } } } + +extension Mastodon.Entity.Instance: Hashable { + public static func == (lhs: Mastodon.Entity.Instance, rhs: Mastodon.Entity.Instance) -> Bool { + lhs.uri == rhs.uri + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(uri) + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift index 95f6e1706..9923f73ac 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+InstanceV2.swift @@ -110,3 +110,13 @@ extension Mastodon.Entity.V2.Instance { public let account: Mastodon.Entity.Account? } } + +extension Mastodon.Entity.V2.Instance: Hashable { + public static func == (lhs: Mastodon.Entity.V2.Instance, rhs: Mastodon.Entity.V2.Instance) -> Bool { + lhs.domain == rhs.domain + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(domain) + } +}