94 lines
4.2 KiB
Swift
94 lines
4.2 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Wraps a value that might need to be resolved asynchronously.
|
|
///
|
|
/// For example, when displaying a notification, the name of the sender (and
|
|
/// thus the title for the notification) might not be available until after
|
|
/// we finish fetching their profile. We represent the title of a
|
|
/// notification as a `ResolvableValue<String>` to indicate that the caller
|
|
/// might need to fetch it before displaying it.
|
|
public struct ResolvableValue<Element> {
|
|
private let resolver: (_ timeout: TimeInterval) async -> Element
|
|
|
|
init(resolvedValue: Element) {
|
|
self.resolver = { _ in resolvedValue }
|
|
}
|
|
|
|
/// The `resolver` MUST return within ~`timeout` seconds; if it can't
|
|
/// resolve the best value before `timeout` seconds have elapsed, it MUST
|
|
/// return a fallback value.
|
|
init(_ resolver: @escaping (_ timeout: TimeInterval) async -> Element) {
|
|
self.resolver = resolver
|
|
}
|
|
|
|
/// Waits for the value to be resolved; returns a fallback after `timeout`.
|
|
func resolve(timeout: TimeInterval = .infinity) async -> Element {
|
|
return await self.resolver(timeout)
|
|
}
|
|
}
|
|
|
|
/// Builds a `ResolvableValue<Element>` that waits for profile fetches.
|
|
public struct ResolvableDisplayNameBuilder<Element> {
|
|
private let address: SignalServiceAddress
|
|
private let transform: (DisplayName, DBReadTransaction) -> Element
|
|
private let contactManager: any ContactManager
|
|
|
|
/// An intermediate builder that can construct a `ResolvableValue<Element>`
|
|
/// that waits for unknown profile names to be fetched.
|
|
///
|
|
/// - Parameters:
|
|
/// - address: The address whose `DisplayName` is relevant. This
|
|
/// `DisplayName` will be loaded from disk, and `resolve(...)` will wait
|
|
/// for already-started profile fetches to complete if it's unknown.
|
|
///
|
|
/// - transform: A block that transforms the `DisplayName` for `address`
|
|
/// into a resolved `Element`. This block may be invoked synchronously in
|
|
/// `resolvableValue` if the name is known, or it may be invoked
|
|
/// asynchronously in the `resolver` block after fetching an unknown name.
|
|
public init(
|
|
displayNameForAddress address: SignalServiceAddress,
|
|
transformedBy transform: @escaping (DisplayName, DBReadTransaction) -> Element,
|
|
contactManager: any ContactManager,
|
|
) {
|
|
self.address = address
|
|
self.transform = transform
|
|
self.contactManager = contactManager
|
|
}
|
|
|
|
/// Converts this builder into a resolvable value.
|
|
public func resolvableValue(db: any DB, profileFetcher: any ProfileFetcher, tx: DBReadTransaction) -> ResolvableValue<Element> {
|
|
let initialDisplayName = self.contactManager.displayName(for: self.address, tx: tx)
|
|
// If we don't yet have a profile name (or nickname or address book name)...
|
|
if !initialDisplayName.hasProfileNameOrBetter, let serviceId = self.address.serviceId {
|
|
return ResolvableValue { timeout in
|
|
// ...wait for up to `timeout` seconds for any in progress fetches.
|
|
do {
|
|
try await withCooperativeTimeout(seconds: timeout) {
|
|
try await profileFetcher.waitForPendingFetches(for: serviceId)
|
|
}
|
|
} catch {
|
|
Logger.warn("Falling back. Couldn't resolve better name for \(serviceId): \(error)")
|
|
}
|
|
// and then show the fetched name (or "Unknown" if it's still not known).
|
|
return db.read { tx in
|
|
let displayName = self.contactManager.displayName(for: self.address, tx: tx)
|
|
return self.transform(displayName, tx)
|
|
}
|
|
}
|
|
} else {
|
|
// ...but if we do have a name already, process it synchronously without waiting.
|
|
return ResolvableValue(resolvedValue: self.transform(initialDisplayName, tx))
|
|
}
|
|
}
|
|
|
|
/// Fetches & transforms the value without waiting to resolve it.
|
|
public func value(tx: DBReadTransaction) -> Element {
|
|
return self.transform(self.contactManager.displayName(for: self.address, tx: tx), tx)
|
|
}
|
|
}
|