// // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public import LibSignalClient public enum OptionalChange: Equatable { case noChange case setTo(Wrapped) public func map(_ transform: (Wrapped) -> U) -> OptionalChange { switch self { case .noChange: return .noChange case .setTo(let value): return .setTo(transform(value)) } } public func orExistingValue(_ existingValue: @autoclosure () -> Wrapped) -> Wrapped { switch self { case .setTo(let value): return value case .noChange: return existingValue() } } public func orElseIfNoChange(_ fallbackValue: @autoclosure () -> Self) -> Self { switch self { case .setTo: return self case .noChange: return fallbackValue() } } } public enum OptionalAvatarChange: Equatable { /// There's no change to the avatar. The existing one is fine. case noChange /// There's no user-provided change to the avatar, but the avatar needs to /// be re-uploaded anyways (perhaps we're rotating the profile key or /// perhaps we detected an inconsistency). case noChangeButMustReupload /// There's a change to the avatar. case setTo(Wrapped) public func map(_ transform: (Wrapped) -> U) -> OptionalAvatarChange { switch self { case .noChange: return .noChange case .noChangeButMustReupload: return .noChangeButMustReupload case .setTo(let value): return .setTo(transform(value)) } } private var importanceLevel: Int { switch self { case .noChange: return 0 case .noChangeButMustReupload: return 1 case .setTo: return 2 } } public func isLessImportantThan(_ otherValue: OptionalAvatarChange) -> Bool { return self.importanceLevel < otherValue.importanceLevel } } public protocol ProfileManager: ProfileManagerProtocol { func warmCaches() // MARK: - func fetchLocalUsersProfile(authedAccount: AuthedAccount) async throws -> FetchedProfile func fetchUserProfiles(for addresses: [SignalServiceAddress], tx: DBReadTransaction) -> [OWSUserProfile?] func reuploadLocalProfile( unsavedRotatedProfileKey: Aes256Key?, mustReuploadAvatar: Bool, authedAccount: AuthedAccount, tx: DBWriteTransaction, ) -> Promise func downloadAndDecryptLocalUserAvatarIfNeeded(authedAccount: AuthedAccount) async throws // MARK: - /// Downloads & decrypts the avatar at a particular URL. /// /// While this method de-dupes in-flight requests, it won't de-dupe requests /// once they've finished. If you download an avatar at a particular path, /// wait for it to finish, and then ask to download the same avatar again, /// this method will download it twice. func downloadAndDecryptAvatar( avatarUrlPath: String, profileKey: ProfileKey, ) async throws -> URL func updateProfile( address: OWSUserProfile.InsertableAddress, decryptedProfile: DecryptedProfile?, avatarUrlPath: OptionalChange, avatarFileName: OptionalChange, profileBadges: [OWSUserProfileBadgeInfo], lastFetchDate: Date, userProfileWriter: UserProfileWriter, tx: DBWriteTransaction, ) func updateLocalProfile( profileGivenName: OptionalChange, profileFamilyName: OptionalChange, profileBio: OptionalChange, profileBioEmoji: OptionalChange, profileAvatarData: OptionalAvatarChange, visibleBadgeIds: OptionalChange<[String]>, unsavedRotatedProfileKey: Aes256Key?, userProfileWriter: UserProfileWriter, authedAccount: AuthedAccount, tx: DBWriteTransaction, ) -> Promise func didSendOrReceiveMessage( serviceId: ServiceId, localIdentifiers: LocalIdentifiers, tx: DBWriteTransaction, ) func setProfileKeyData( _ profileKeyData: Data, for serviceId: ServiceId, onlyFillInIfMissing: Bool, shouldFetchProfile: Bool, userProfileWriter: UserProfileWriter, localIdentifiers: LocalIdentifiers, authedAccount: AuthedAccount, tx: DBWriteTransaction, ) func fillInProfileKeys( allProfileKeys: [Aci: Data], authoritativeProfileKeys: [Aci: Data], userProfileWriter: UserProfileWriter, localIdentifiers: LocalIdentifiers, tx: DBWriteTransaction, ) // MARK: - func allWhitelistedAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] func allWhitelistedRegisteredAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] } extension ProfileManager { public func localProfileKey(tx: DBReadTransaction) -> ProfileKey? { return localUserProfile(tx: tx)?.profileKey.map(ProfileKey.init(_:)) } }