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;
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;
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;
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,
return account.getUsername();
}
- public SignalServiceAddress getSelfAddress() {
+ private SignalServiceAddress getSelfAddress() {
return account.getSelfAddress();
}
public void setProfile(
String givenName, final String familyName, String about, String aboutEmoji, Optional<File> 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));
}
return record;
}
- public Profile getRecipientProfile(
- RecipientId recipientId
- ) {
- return getRecipientProfile(recipientId, false);
- }
-
- private final Set<RecipientId> 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<SignalServiceAttachmentStream> createContactAvatarAttachment(SignalServiceAddress address) throws IOException {
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
}
}
}
- 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);
}
}
}
- 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 {
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;
}
} else {
// Retrieve profile to get the current identity key from the server
- retrieveEncryptedProfile(recipientId);
+ refreshRecipientProfile(recipientId);
}
}
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;
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;
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<File> 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<RecipientId> 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 {
}
}
- public SignalServiceProfile retrieveProfileSync(String username) throws IOException {
- return messageReceiverProvider.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent());
- }
-
- public Single<ProfileAndCredential> retrieveProfile(
+ private Single<ProfileAndCredential> retrieveProfile(
RecipientId recipientId, SignalServiceProfile.RequestType requestType
) throws IOException {
var unidentifiedAccess = getUnidentifiedAccess(recipientId);
});
}
+ 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<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId);