import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
public class StorageHelper {
return;
}
- account.setStorageManifestVersion(manifest.get().getVersion());
+ logger.trace("Remote storage manifest has {} records", manifest.get().getStorageIds().size());
+ final var storageIds = manifest.get()
+ .getStorageIds()
+ .stream()
+ .filter(id -> !id.isUnknown())
+ .collect(Collectors.toSet());
- final var storageIds = manifest.get().getStorageIds().stream().filter(id -> !id.isUnknown()).toList();
+ Optional<SignalStorageManifest> localManifest = account.getStorageManifest();
+ localManifest.ifPresent(m -> m.getStorageIds().forEach(storageIds::remove));
+ logger.trace("Reading {} new records", manifest.get().getStorageIds().size());
for (final var record : getSignalStorageRecords(storageIds)) {
+ logger.debug("Reading record of type {}", record.getType());
if (record.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE) {
readAccountRecord(record);
} else if (record.getType() == ManifestRecord.Identifier.Type.GROUPV2_VALUE) {
readContactRecord(record);
}
}
+ account.setStorageManifestVersion(manifest.get().getVersion());
+ account.setStorageManifest(manifest.get());
logger.debug("Done reading data from remote storage");
}
final var contact = account.getContactStore().getContact(recipientId);
final var blocked = contact != null && contact.isBlocked();
final var profileShared = contact != null && contact.isProfileSharingEnabled();
- if (contactRecord.getGivenName().isPresent()
- || contactRecord.getFamilyName().isPresent()
- || blocked != contactRecord.isBlocked()
- || profileShared != contactRecord.isProfileSharingEnabled()) {
+ final var givenName = contact == null ? null : contact.getGivenName();
+ final var familyName = contact == null ? null : contact.getFamilyName();
+ if ((contactRecord.getGivenName().isPresent() && !contactRecord.getGivenName().get().equals(givenName)) || (
+ contactRecord.getFamilyName().isPresent() && !contactRecord.getFamilyName().get().equals(familyName)
+ ) || blocked != contactRecord.isBlocked() || profileShared != contactRecord.isProfileSharingEnabled()) {
+ logger.debug("Storing new or updated contact {}", recipientId);
final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
final var newContact = contactBuilder.withBlocked(contactRecord.isBlocked())
.withGivenName(contactRecord.getGivenName().orElse(null))
if (contactRecord.getProfileKey().isPresent()) {
try {
+ logger.trace("Storing profile key {}", recipientId);
final var profileKey = new ProfileKey(contactRecord.getProfileKey().get());
account.getProfileStore().storeProfileKey(recipientId, profileKey);
} catch (InvalidInputException e) {
}
if (contactRecord.getIdentityKey().isPresent()) {
try {
+ logger.trace("Storing identity key {}", recipientId);
final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
return records.size() > 0 ? records.get(0) : null;
}
- private List<SignalStorageRecord> getSignalStorageRecords(final List<StorageId> storageIds) throws IOException {
+ private List<SignalStorageRecord> getSignalStorageRecords(final Collection<StorageId> storageIds) throws IOException {
List<SignalStorageRecord> records;
try {
- records = dependencies.getAccountManager().readStorageRecords(account.getStorageKey(), storageIds);
+ records = dependencies.getAccountManager()
+ .readStorageRecords(account.getStorageKey(), new ArrayList<>(storageIds));
} catch (InvalidKeyException e) {
logger.warn("Failed to read storage records, ignoring.");
return List.of();
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.StorageKey;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.function.Supplier;
public class SignalAccount implements Closeable {
return new File(getUserPath(dataPath, account), "recipients-store");
}
+ private static File getStorageManifestFile(File dataPath, String account) {
+ return new File(getUserPath(dataPath, account), "storage-manifest");
+ }
+
private static File getDatabaseFile(File dataPath, String account) {
return new File(getUserPath(dataPath, account), "account.db");
}
save();
}
+ public Optional<SignalStorageManifest> getStorageManifest() {
+ final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
+ if (!storageManifestFile.exists()) {
+ return Optional.empty();
+ }
+ try (var inputStream = new FileInputStream(storageManifestFile)) {
+ return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
+ } catch (IOException e) {
+ logger.warn("Failed to read local storage manifest.", e);
+ return Optional.empty();
+ }
+ }
+
+ public void setStorageManifest(SignalStorageManifest manifest) {
+ final var manifestBytes = manifest.serialize();
+
+ final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
+ try (var outputStream = new FileOutputStream(storageManifestFile)) {
+ outputStream.write(manifestBytes);
+ } catch (IOException e) {
+ logger.error("Failed to store local storage manifest.", e);
+ }
+ }
+
public ProfileKey getProfileKey() {
return profileKey;
}