X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/4177deccf1e91483f54c5fcfacffce0ce525ad39..4acc9a96e34995c86c076d9020a40251c726c64c:/src/main/java/org/asamk/signal/storage/SignalAccount.java diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index d0638e41..dbb0ac04 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -13,12 +13,15 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.storage.groups.GroupInfoV1; import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.profiles.ProfileStore; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.protocol.RecipientStore; import org.asamk.signal.storage.protocol.SessionInfo; import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; +import org.asamk.signal.storage.stickers.StickerStore; import org.asamk.signal.storage.threads.LegacyJsonThreadStore; import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; @@ -33,6 +36,8 @@ import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.util.Base64; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -67,13 +72,14 @@ public class SignalAccount implements Closeable { private JsonGroupStore groupStore; private JsonContactsStore contactStore; private RecipientStore recipientStore; + private ProfileStore profileStore; + private StickerStore stickerStore; private SignalAccount(final FileChannel fileChannel, final FileLock lock) { this.fileChannel = fileChannel; this.lock = lock; jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect jsonProcessor.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it. - jsonProcessor.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); jsonProcessor.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); @@ -84,7 +90,7 @@ public class SignalAccount implements Closeable { final Pair pair = openFileChannel(fileName); try { SignalAccount account = new SignalAccount(pair.first(), pair.second()); - account.load(); + account.load(dataPath); return account; } catch (Throwable e) { pair.second().close(); @@ -106,9 +112,11 @@ public class SignalAccount implements Closeable { account.username = username; account.profileKey = profileKey; account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); - account.groupStore = new JsonGroupStore(); + account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username)); account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); + account.stickerStore = new StickerStore(); account.registered = false; return account; @@ -131,9 +139,11 @@ public class SignalAccount implements Closeable { account.deviceId = deviceId; account.signalingKey = signalingKey; account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); - account.groupStore = new JsonGroupStore(); + account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username)); account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); + account.stickerStore = new StickerStore(); account.registered = true; account.isMultiDevice = true; @@ -144,6 +154,10 @@ public class SignalAccount implements Closeable { return dataPath + "/" + username; } + private static File getGroupCachePath(String dataPath, String username) { + return new File(new File(dataPath, username + ".d"), "group-cache"); + } + public static boolean userExists(String dataPath, String username) { if (username == null) { return false; @@ -152,7 +166,7 @@ public class SignalAccount implements Closeable { return !(!f.exists() || f.isDirectory()); } - private void load() throws IOException { + private void load(String dataPath) throws IOException { JsonNode rootNode; synchronized (fileChannel) { fileChannel.position(0); @@ -204,9 +218,10 @@ public class SignalAccount implements Closeable { JsonNode groupStoreNode = rootNode.get("groupStore"); if (groupStoreNode != null) { groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class); + groupStore.groupCachePath = getGroupCachePath(dataPath, username); } if (groupStore == null) { - groupStore = new JsonGroupStore(); + groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username)); } JsonNode contactStoreNode = rootNode.get("contactStore"); @@ -231,9 +246,12 @@ public class SignalAccount implements Closeable { } for (GroupInfo group : groupStore.getGroups()) { - group.members = group.members.stream() - .map(m -> recipientStore.resolveServiceAddress(m)) - .collect(Collectors.toSet()); + if (group instanceof GroupInfoV1) { + GroupInfoV1 groupInfoV1 = (GroupInfoV1) group; + groupInfoV1.members = groupInfoV1.members.stream() + .map(m -> recipientStore.resolveServiceAddress(m)) + .collect(Collectors.toSet()); + } } for (SessionInfo session : signalProtocolStore.getSessions()) { @@ -245,6 +263,22 @@ public class SignalAccount implements Closeable { } } + JsonNode profileStoreNode = rootNode.get("profileStore"); + if (profileStoreNode != null) { + profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class); + } + if (profileStore == null) { + profileStore = new ProfileStore(); + } + + JsonNode stickerStoreNode = rootNode.get("stickerStore"); + if (stickerStoreNode != null) { + stickerStore = jsonProcessor.convertValue(stickerStoreNode, StickerStore.class); + } + if (stickerStore == null) { + stickerStore = new StickerStore(); + } + JsonNode threadStoreNode = rootNode.get("threadStore"); if (threadStoreNode != null) { LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class); @@ -260,8 +294,8 @@ public class SignalAccount implements Closeable { contactStore.updateContact(contactInfo); } else { GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id)); - if (groupInfo != null) { - groupInfo.messageExpirationTime = thread.messageExpirationTime; + if (groupInfo instanceof GroupInfoV1) { + ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime; groupStore.updateGroup(groupInfo); } } @@ -291,13 +325,20 @@ public class SignalAccount implements Closeable { .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) .putPOJO("recipientStore", recipientStore) + .putPOJO("profileStore", profileStore) + .putPOJO("stickerStore", stickerStore) ; try { - synchronized (fileChannel) { - fileChannel.position(0); - jsonProcessor.writeValue(Channels.newOutputStream(fileChannel), rootNode); - fileChannel.truncate(fileChannel.position()); - fileChannel.force(false); + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + // Write to memory first to prevent corrupting the file in case of serialization errors + jsonProcessor.writeValue(output, rootNode); + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + synchronized (fileChannel) { + fileChannel.position(0); + input.transferTo(Channels.newOutputStream(fileChannel)); + fileChannel.truncate(fileChannel.position()); + fileChannel.force(false); + } } } catch (Exception e) { System.err.println(String.format("Error saving file: %s", e.getMessage())); @@ -347,6 +388,14 @@ public class SignalAccount implements Closeable { return recipientStore; } + public ProfileStore getProfileStore() { + return profileStore; + } + + public StickerStore getStickerStore() { + return stickerStore; + } + public String getUsername() { return username; }