From e532a24cf8cba6aede416b1b21aa72e95519c383 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 26 Aug 2021 10:56:30 +0200 Subject: [PATCH] Move more profile functionality to ProfileHelper --- .../asamk/signal/manager/HandleAction.java | 2 +- .../org/asamk/signal/manager/Manager.java | 247 ++-------------- .../signal/manager/helper/ProfileHelper.java | 265 +++++++++++++++++- 3 files changed, 282 insertions(+), 232 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/HandleAction.java b/lib/src/main/java/org/asamk/signal/manager/HandleAction.java index 2396df06..8639806f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/HandleAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/HandleAction.java @@ -170,7 +170,7 @@ class RetrieveProfileAction implements HandleAction { @Override public void execute(Manager m) throws Throwable { - m.getRecipientProfile(recipientId, true); + m.refreshRecipientProfile(recipientId); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 5de65f3e..ac7b571f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -58,14 +58,12 @@ import org.asamk.signal.manager.storage.stickers.StickerPackId; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.KeyUtils; -import org.asamk.signal.manager.util.ProfileUtils; import org.asamk.signal.manager.util.StickerUtils; import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.metadata.ProtocolInvalidMessageException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; -import org.signal.zkgroup.profiles.ProfileKeyCredential; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.IdentityKey; @@ -106,8 +104,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; -import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; @@ -137,7 +133,6 @@ import java.nio.file.Files; import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -205,26 +200,29 @@ public class Manager implements Closeable { account.getSignalProtocolStore(), executor, sessionLock); - this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); + this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); + this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); + this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); + this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, account.getProfileStore()::getProfileKey, this::getRecipientProfile, this::getSenderCertificate); - this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey, + this.profileHelper = new ProfileHelper(account, + dependencies, + avatarStore, + account.getProfileStore()::getProfileKey, unidentifiedAccessHelper::getAccessFor, dependencies::getProfileService, dependencies::getMessageReceiver, this::resolveSignalServiceAddress); - final GroupV2Helper groupV2Helper = new GroupV2Helper(this::getRecipientProfileKeyCredential, + final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper::getRecipientProfileKeyCredential, this::getRecipientProfile, account::getSelfRecipientId, dependencies.getGroupsV2Operations(), dependencies.getGroupsV2Api(), this::resolveSignalServiceAddress); - this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); - this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); - this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); this.sendHelper = new SendHelper(account, dependencies, unidentifiedAccessHelper, @@ -246,7 +244,7 @@ public class Manager implements Closeable { return account.getUsername(); } - public SignalServiceAddress getSelfAddress() { + private SignalServiceAddress getSelfAddress() { return account.getSelfAddress(); } @@ -377,45 +375,12 @@ public class Manager implements Closeable { public void setProfile( String givenName, final String familyName, String about, String aboutEmoji, Optional avatar ) throws IOException { - var profile = getRecipientProfile(account.getSelfRecipientId()); - var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile); - if (givenName != null) { - builder.withGivenName(givenName); - } - if (familyName != null) { - builder.withFamilyName(familyName); - } - if (about != null) { - builder.withAbout(about); - } - if (aboutEmoji != null) { - builder.withAboutEmoji(aboutEmoji); - } - var newProfile = builder.build(); - - try (final var streamDetails = avatar == null - ? avatarStore.retrieveProfileAvatar(getSelfAddress()) - : avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) { - dependencies.getAccountManager() - .setVersionedProfile(account.getUuid(), - account.getProfileKey(), - newProfile.getInternalServiceName(), - newProfile.getAbout() == null ? "" : newProfile.getAbout(), - newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(), - Optional.absent(), - streamDetails); - } + profileHelper.setProfile(givenName, familyName, about, aboutEmoji, avatar); - if (avatar != null) { - if (avatar.isPresent()) { - avatarStore.storeProfileAvatar(getSelfAddress(), - outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream)); - } else { - avatarStore.deleteProfileAvatar(getSelfAddress()); - } - } - account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile); + sendSyncFetchProfileMessage(); + } + private void sendSyncFetchProfileMessage() throws IOException { sendHelper.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); } @@ -522,134 +487,12 @@ public class Manager implements Closeable { return record; } - public Profile getRecipientProfile( - RecipientId recipientId - ) { - return getRecipientProfile(recipientId, false); - } - - private final Set pendingProfileRequest = new HashSet<>(); - - Profile getRecipientProfile( - RecipientId recipientId, boolean force - ) { - var profile = account.getProfileStore().getProfile(recipientId); - - var now = System.currentTimeMillis(); - // Profiles are cached for 24h before retrieving them again, unless forced - if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) { - return profile; - } - - synchronized (pendingProfileRequest) { - if (pendingProfileRequest.contains(recipientId)) { - return profile; - } - pendingProfileRequest.add(recipientId); - } - final SignalServiceProfile encryptedProfile; - try { - encryptedProfile = retrieveEncryptedProfile(recipientId); - } finally { - synchronized (pendingProfileRequest) { - pendingProfileRequest.remove(recipientId); - } - } - if (encryptedProfile == null) { - return null; - } - - profile = decryptProfileIfKeyKnown(recipientId, encryptedProfile); - account.getProfileStore().storeProfile(recipientId, profile); - - return profile; - } - - private Profile decryptProfileIfKeyKnown( - final RecipientId recipientId, final SignalServiceProfile encryptedProfile - ) { - var profileKey = account.getProfileStore().getProfileKey(recipientId); - if (profileKey == null) { - return new Profile(System.currentTimeMillis(), - null, - null, - null, - null, - ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null), - ProfileUtils.getCapabilities(encryptedProfile)); - } - - return decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile); - } - - private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) { - try { - return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile(); - } catch (IOException e) { - logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage()); - return null; - } - } - - private ProfileAndCredential retrieveProfileAndCredential( - final RecipientId recipientId, final SignalServiceProfile.RequestType requestType - ) throws IOException { - final var profileAndCredential = profileHelper.retrieveProfileSync(recipientId, requestType); - final var profile = profileAndCredential.getProfile(); - - try { - var newIdentity = account.getIdentityKeyStore() - .saveIdentity(recipientId, - new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())), - new Date()); - - if (newIdentity) { - account.getSessionStore().archiveSessions(recipientId); - } - } catch (InvalidKeyException ignored) { - logger.warn("Got invalid identity key in profile for {}", - resolveSignalServiceAddress(recipientId).getIdentifier()); - } - return profileAndCredential; + public Profile getRecipientProfile(RecipientId recipientId) { + return profileHelper.getRecipientProfile(recipientId); } - private ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) { - var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId); - if (profileKeyCredential != null) { - return profileKeyCredential; - } - - ProfileAndCredential profileAndCredential; - try { - profileAndCredential = retrieveProfileAndCredential(recipientId, - SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL); - } catch (IOException e) { - logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage()); - return null; - } - - profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull(); - account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential); - - var profileKey = account.getProfileStore().getProfileKey(recipientId); - if (profileKey != null) { - final var profile = decryptProfileAndDownloadAvatar(recipientId, - profileKey, - profileAndCredential.getProfile()); - account.getProfileStore().storeProfile(recipientId, profile); - } - - return profileKeyCredential; - } - - private Profile decryptProfileAndDownloadAvatar( - final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile - ) { - if (encryptedProfile.getAvatar() != null) { - downloadProfileAvatar(resolveSignalServiceAddress(recipientId), encryptedProfile.getAvatar(), profileKey); - } - - return ProfileUtils.decryptProfile(profileKey, encryptedProfile); + public void refreshRecipientProfile(RecipientId recipientId) { + profileHelper.refreshRecipientProfile(recipientId); } private Optional createContactAvatarAttachment(SignalServiceAddress address) throws IOException { @@ -1784,7 +1627,7 @@ public class Manager implements Closeable { if (syncMessage.getFetchType().isPresent()) { switch (syncMessage.getFetchType().get()) { case LOCAL_PROFILE: - getRecipientProfile(account.getSelfRecipientId(), true); + actions.add(new RetrieveProfileAction(account.getSelfRecipientId())); case STORAGE_MANIFEST: // TODO } @@ -1820,20 +1663,6 @@ public class Manager implements Closeable { } } - private void downloadProfileAvatar( - SignalServiceAddress address, String avatarPath, ProfileKey profileKey - ) { - try { - avatarStore.storeProfileAvatar(address, - outputStream -> retrieveProfileAvatar(avatarPath, profileKey, outputStream)); - } catch (Throwable e) { - if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - logger.warn("Failed to download profile avatar, ignoring: {}", e.getMessage()); - } - } - public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { return attachmentStore.getAttachmentFile(attachmentId); } @@ -1862,28 +1691,6 @@ public class Manager implements Closeable { } } - private void retrieveProfileAvatar( - String avatarPath, ProfileKey profileKey, OutputStream outputStream - ) throws IOException { - var tmpFile = IOUtils.createTempFile(); - try (var input = dependencies.getMessageReceiver() - .retrieveProfileAvatar(avatarPath, - tmpFile, - profileKey, - ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) { - // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ... - IOUtils.copyStream(input, outputStream, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); - } finally { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - } - private void retrieveAttachment( final SignalServiceAttachment attachment, final OutputStream outputStream ) throws IOException { @@ -2069,17 +1876,15 @@ public class Manager implements Closeable { public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) { final var recipientId = resolveRecipient(recipientIdentifier); - final var recipient = account.getRecipientStore().getRecipient(recipientId); - if (recipient == null) { - return null; - } - if (recipient.getContact() != null && !Util.isEmpty(recipient.getContact().getName())) { - return recipient.getContact().getName(); + final var contact = account.getRecipientStore().getContact(recipientId); + if (contact != null && !Util.isEmpty(contact.getName())) { + return contact.getName(); } - if (recipient.getProfile() != null && recipient.getProfile() != null) { - return recipient.getProfile().getDisplayName(); + final var profile = getRecipientProfile(recipientId); + if (profile != null) { + return profile.getDisplayName(); } return null; @@ -2188,7 +1993,7 @@ public class Manager implements Closeable { } } else { // Retrieve profile to get the current identity key from the server - retrieveEncryptedProfile(recipientId); + refreshRecipientProfile(recipientId); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index c3c74b0b..ac75a573 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -1,7 +1,20 @@ package org.asamk.signal.manager.helper; +import org.asamk.signal.manager.AvatarStore; +import org.asamk.signal.manager.SignalDependencies; +import org.asamk.signal.manager.config.ServiceConfig; +import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.util.IOUtils; +import org.asamk.signal.manager.util.ProfileUtils; +import org.asamk.signal.manager.util.Utils; import org.signal.zkgroup.profiles.ProfileKey; +import org.signal.zkgroup.profiles.ProfileKeyCredential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; @@ -12,29 +25,43 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.internal.ServiceResponse; +import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; import io.reactivex.rxjava3.core.Single; public final class ProfileHelper { - private final ProfileKeyProvider profileKeyProvider; + private final static Logger logger = LoggerFactory.getLogger(ProfileHelper.class); + private final SignalAccount account; + private final SignalDependencies dependencies; + private final AvatarStore avatarStore; + private final ProfileKeyProvider profileKeyProvider; private final UnidentifiedAccessProvider unidentifiedAccessProvider; - private final ProfileServiceProvider profileServiceProvider; - private final MessageReceiverProvider messageReceiverProvider; - private final SignalServiceAddressResolver addressResolver; public ProfileHelper( + final SignalAccount account, + final SignalDependencies dependencies, + final AvatarStore avatarStore, final ProfileKeyProvider profileKeyProvider, final UnidentifiedAccessProvider unidentifiedAccessProvider, final ProfileServiceProvider profileServiceProvider, final MessageReceiverProvider messageReceiverProvider, final SignalServiceAddressResolver addressResolver ) { + this.account = account; + this.dependencies = dependencies; + this.avatarStore = avatarStore; this.profileKeyProvider = profileKeyProvider; this.unidentifiedAccessProvider = unidentifiedAccessProvider; this.profileServiceProvider = profileServiceProvider; @@ -42,7 +69,193 @@ public final class ProfileHelper { this.addressResolver = addressResolver; } - public ProfileAndCredential retrieveProfileSync( + public Profile getRecipientProfile(RecipientId recipientId) { + return getRecipientProfile(recipientId, false); + } + + public void refreshRecipientProfile(RecipientId recipientId) { + getRecipientProfile(recipientId, true); + } + + public ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) { + var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId); + if (profileKeyCredential != null) { + return profileKeyCredential; + } + + ProfileAndCredential profileAndCredential; + try { + profileAndCredential = retrieveProfileAndCredential(recipientId, + SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL); + } catch (IOException e) { + logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage()); + return null; + } + + profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull(); + account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential); + + var profileKey = account.getProfileStore().getProfileKey(recipientId); + if (profileKey != null) { + final var profile = decryptProfileAndDownloadAvatar(recipientId, + profileKey, + profileAndCredential.getProfile()); + account.getProfileStore().storeProfile(recipientId, profile); + } + + return profileKeyCredential; + } + + /** + * @param givenName if null, the previous givenName will be kept + * @param familyName if null, the previous familyName will be kept + * @param about if null, the previous about text will be kept + * @param aboutEmoji if null, the previous about emoji will be kept + * @param avatar if avatar is null the image from the local avatar store is used (if present), + */ + public void setProfile( + String givenName, final String familyName, String about, String aboutEmoji, Optional avatar + ) throws IOException { + var profile = getRecipientProfile(account.getSelfRecipientId()); + var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile); + if (givenName != null) { + builder.withGivenName(givenName); + } + if (familyName != null) { + builder.withFamilyName(familyName); + } + if (about != null) { + builder.withAbout(about); + } + if (aboutEmoji != null) { + builder.withAboutEmoji(aboutEmoji); + } + var newProfile = builder.build(); + + try (final var streamDetails = avatar == null + ? avatarStore.retrieveProfileAvatar(account.getSelfAddress()) + : avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) { + dependencies.getAccountManager() + .setVersionedProfile(account.getUuid(), + account.getProfileKey(), + newProfile.getInternalServiceName(), + newProfile.getAbout() == null ? "" : newProfile.getAbout(), + newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(), + Optional.absent(), + streamDetails); + } + + if (avatar != null) { + if (avatar.isPresent()) { + avatarStore.storeProfileAvatar(account.getSelfAddress(), + outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream)); + } else { + avatarStore.deleteProfileAvatar(account.getSelfAddress()); + } + } + account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile); + } + + private final Set pendingProfileRequest = new HashSet<>(); + + private Profile getRecipientProfile(RecipientId recipientId, boolean force) { + var profile = account.getProfileStore().getProfile(recipientId); + + var now = System.currentTimeMillis(); + // Profiles are cached for 24h before retrieving them again, unless forced + if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) { + return profile; + } + + synchronized (pendingProfileRequest) { + if (pendingProfileRequest.contains(recipientId)) { + return profile; + } + pendingProfileRequest.add(recipientId); + } + final SignalServiceProfile encryptedProfile; + try { + encryptedProfile = retrieveEncryptedProfile(recipientId); + } finally { + synchronized (pendingProfileRequest) { + pendingProfileRequest.remove(recipientId); + } + } + if (encryptedProfile == null) { + return null; + } + + profile = decryptProfileIfKeyKnown(recipientId, encryptedProfile); + account.getProfileStore().storeProfile(recipientId, profile); + + return profile; + } + + private Profile decryptProfileIfKeyKnown( + final RecipientId recipientId, final SignalServiceProfile encryptedProfile + ) { + var profileKey = account.getProfileStore().getProfileKey(recipientId); + if (profileKey == null) { + return new Profile(System.currentTimeMillis(), + null, + null, + null, + null, + ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null), + ProfileUtils.getCapabilities(encryptedProfile)); + } + + return decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile); + } + + private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) { + try { + return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile(); + } catch (IOException e) { + logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage()); + return null; + } + } + + private SignalServiceProfile retrieveProfileSync(String username) throws IOException { + return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent()); + } + + private ProfileAndCredential retrieveProfileAndCredential( + final RecipientId recipientId, final SignalServiceProfile.RequestType requestType + ) throws IOException { + final var profileAndCredential = retrieveProfileSync(recipientId, requestType); + final var profile = profileAndCredential.getProfile(); + + try { + var newIdentity = account.getIdentityKeyStore() + .saveIdentity(recipientId, + new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())), + new Date()); + + if (newIdentity) { + account.getSessionStore().archiveSessions(recipientId); + } + } catch (InvalidKeyException ignored) { + logger.warn("Got invalid identity key in profile for {}", + addressResolver.resolveSignalServiceAddress(recipientId).getIdentifier()); + } + return profileAndCredential; + } + + private Profile decryptProfileAndDownloadAvatar( + final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile + ) { + if (encryptedProfile.getAvatar() != null) { + downloadProfileAvatar(addressResolver.resolveSignalServiceAddress(recipientId), + encryptedProfile.getAvatar(), + profileKey); + } + + return ProfileUtils.decryptProfile(profileKey, encryptedProfile); + } + + private ProfileAndCredential retrieveProfileSync( RecipientId recipientId, SignalServiceProfile.RequestType requestType ) throws IOException { try { @@ -58,11 +271,7 @@ public final class ProfileHelper { } } - public SignalServiceProfile retrieveProfileSync(String username) throws IOException { - return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent()); - } - - public Single retrieveProfile( + private Single retrieveProfile( RecipientId recipientId, SignalServiceProfile.RequestType requestType ) throws IOException { var unidentifiedAccess = getUnidentifiedAccess(recipientId); @@ -106,6 +315,42 @@ public final class ProfileHelper { }); } + private void downloadProfileAvatar( + SignalServiceAddress address, String avatarPath, ProfileKey profileKey + ) { + try { + avatarStore.storeProfileAvatar(address, + outputStream -> retrieveProfileAvatar(avatarPath, profileKey, outputStream)); + } catch (Throwable e) { + if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + logger.warn("Failed to download profile avatar, ignoring: {}", e.getMessage()); + } + } + + private void retrieveProfileAvatar( + String avatarPath, ProfileKey profileKey, OutputStream outputStream + ) throws IOException { + var tmpFile = IOUtils.createTempFile(); + try (var input = dependencies.getMessageReceiver() + .retrieveProfileAvatar(avatarPath, + tmpFile, + profileKey, + ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) { + // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ... + IOUtils.copyStream(input, outputStream, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); + } finally { + try { + Files.delete(tmpFile.toPath()); + } catch (IOException e) { + logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); + } + } + } + private Optional getUnidentifiedAccess(RecipientId recipientId) { var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId); -- 2.50.1