]> nmode's Git Repositories - signal-cli/commitdiff
Refactor sticker store
authorAsamK <asamk@gmx.de>
Sun, 2 May 2021 10:08:47 +0000 (12:08 +0200)
committerAsamK <asamk@gmx.de>
Mon, 3 May 2021 16:43:45 +0000 (18:43 +0200)
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/Utils.java
lib/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java
lib/src/main/java/org/asamk/signal/manager/storage/stickers/Sticker.java
lib/src/main/java/org/asamk/signal/manager/storage/stickers/StickerPackId.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/storage/stickers/StickerStore.java
lib/src/main/java/org/asamk/signal/manager/util/Utils.java

index f62af7ad42a1645a0f384d8831fc8dfa5284456d..7be2c2d37d73a7c14904021e1e507dfd8c4e0de4 100644 (file)
@@ -39,6 +39,7 @@ import org.asamk.signal.manager.storage.recipients.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.stickers.Sticker;
+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;
@@ -1170,7 +1171,7 @@ public class Manager implements Closeable {
         var packKey = KeyUtils.createStickerUploadKey();
         var packId = messageSender.uploadStickerManifest(manifest, packKey);
 
-        var sticker = new Sticker(Hex.fromStringCondensed(packId), packKey);
+        var sticker = new Sticker(StickerPackId.deserialize(Hex.fromStringCondensed(packId)), packKey);
         account.getStickerStore().updateSticker(sticker);
         account.save();
 
@@ -1591,9 +1592,10 @@ public class Manager implements Closeable {
         }
         if (message.getSticker().isPresent()) {
             final var messageSticker = message.getSticker().get();
-            var sticker = account.getStickerStore().getSticker(messageSticker.getPackId());
+            final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
+            var sticker = account.getStickerStore().getSticker(stickerPackId);
             if (sticker == null) {
-                sticker = new Sticker(messageSticker.getPackId(), messageSticker.getPackKey());
+                sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
                 account.getStickerStore().updateSticker(sticker);
             }
         }
@@ -2086,12 +2088,13 @@ public class Manager implements Closeable {
                         if (!m.getPackId().isPresent()) {
                             continue;
                         }
-                        var sticker = account.getStickerStore().getSticker(m.getPackId().get());
+                        final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
+                        var sticker = account.getStickerStore().getSticker(stickerPackId);
                         if (sticker == null) {
                             if (!m.getPackKey().isPresent()) {
                                 continue;
                             }
-                            sticker = new Sticker(m.getPackId().get(), m.getPackKey().get());
+                            sticker = new Sticker(stickerPackId, m.getPackKey().get());
                         }
                         sticker.setInstalled(!m.getType().isPresent()
                                 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL);
index ea3b5ea85400491f83af7dbd9a0db94cd9b61585..0c5290b954e15dc703174b9e5f820838220b1591 100644 (file)
@@ -1,13 +1,7 @@
 package org.asamk.signal.manager.storage;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
 
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.storage.contacts.ContactsStore;
@@ -32,7 +26,6 @@ import org.asamk.signal.manager.storage.stickers.StickerStore;
 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
 import org.asamk.signal.manager.util.IOUtils;
 import org.asamk.signal.manager.util.KeyUtils;
-import org.asamk.signal.manager.util.Utils;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.profiles.ProfileKey;
 import org.slf4j.Logger;
@@ -69,9 +62,11 @@ public class SignalAccount implements Closeable {
 
     private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
 
-    private final ObjectMapper jsonProcessor = new ObjectMapper();
+    private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
+
     private final FileChannel fileChannel;
     private final FileLock lock;
+
     private String username;
     private UUID uuid;
     private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
@@ -94,17 +89,13 @@ public class SignalAccount implements Closeable {
     private JsonGroupStore groupStore;
     private RecipientStore recipientStore;
     private StickerStore stickerStore;
+    private StickerStore.Storage stickerStoreStorage;
 
     private MessageCache messageCache;
 
     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
-        jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
-        jsonProcessor.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
-        jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
     }
 
     public static SignalAccount load(File dataPath, String username) throws IOException {
@@ -137,24 +128,10 @@ public class SignalAccount implements Closeable {
 
         account.username = username;
         account.profileKey = profileKey;
-        account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
-        account.recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username),
-                account::mergeRecipients);
-        account.preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
-        account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
-        account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
-                account.recipientStore::resolveRecipient);
-        account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
-                account.recipientStore::resolveRecipient,
-                identityKey,
-                registrationId);
-        account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
-                account.signedPreKeyStore,
-                account.sessionStore,
-                account.identityKeyStore);
-        account.stickerStore = new StickerStore();
 
-        account.messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+        account.initStores(dataPath, identityKey, registrationId);
+        account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
+        account.stickerStore = new StickerStore(account::saveStickerStore);
 
         account.registered = false;
 
@@ -163,6 +140,23 @@ public class SignalAccount implements Closeable {
         return account;
     }
 
+    private void initStores(
+            final File dataPath, final IdentityKeyPair identityKey, final int registrationId
+    ) throws IOException {
+        recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
+
+        preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
+        signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
+        sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient);
+        identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
+                recipientStore::resolveRecipient,
+                identityKey,
+                registrationId);
+        signalProtocolStore = new SignalProtocolStore(preKeyStore, signedPreKeyStore, sessionStore, identityKeyStore);
+
+        messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+    }
+
     public static SignalAccount createLinkedAccount(
             File dataPath,
             String username,
@@ -187,29 +181,15 @@ public class SignalAccount implements Closeable {
         account.password = password;
         account.profileKey = profileKey;
         account.deviceId = deviceId;
-        account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
-        account.recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username),
-                account::mergeRecipients);
-        account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
-        account.preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
-        account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
-        account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
-                account.recipientStore::resolveRecipient);
-        account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
-                account.recipientStore::resolveRecipient,
-                identityKey,
-                registrationId);
-        account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
-                account.signedPreKeyStore,
-                account.sessionStore,
-                account.identityKeyStore);
-        account.stickerStore = new StickerStore();
 
-        account.messageCache = new MessageCache(getMessageCachePath(dataPath, username));
+        account.initStores(dataPath, identityKey, registrationId);
+        account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
+        account.stickerStore = new StickerStore(account::saveStickerStore);
 
         account.registered = true;
         account.isMultiDevice = true;
 
+        account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
         account.migrateLegacyConfigs();
 
         return account;
@@ -347,20 +327,10 @@ public class SignalAccount implements Closeable {
             registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
         }
 
-        recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
-
-        preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
-        signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
-        sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient);
-        identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
-                recipientStore::resolveRecipient,
-                identityKeyPair,
-                registrationId);
+        initStores(dataPath, identityKeyPair, registrationId);
 
         loadLegacyStores(rootNode, legacySignalProtocolStore);
 
-        signalProtocolStore = new SignalProtocolStore(preKeyStore, signedPreKeyStore, sessionStore, identityKeyStore);
-
         var groupStoreNode = rootNode.get("groupStore");
         if (groupStoreNode != null) {
             groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
@@ -370,16 +340,13 @@ public class SignalAccount implements Closeable {
             groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
         }
 
-        var stickerStoreNode = rootNode.get("stickerStore");
-        if (stickerStoreNode != null) {
-            stickerStore = jsonProcessor.convertValue(stickerStoreNode, StickerStore.class);
-        }
-        if (stickerStore == null) {
-            stickerStore = new StickerStore();
+        if (rootNode.hasNonNull("stickerStore")) {
+            stickerStoreStorage = jsonProcessor.convertValue(rootNode.get("stickerStore"), StickerStore.Storage.class);
+            stickerStore = StickerStore.fromStorage(stickerStoreStorage, this::saveStickerStore);
+        } else {
+            stickerStore = new StickerStore(this::saveStickerStore);
         }
 
-        messageCache = new MessageCache(getMessageCachePath(dataPath, username));
-
         loadLegacyThreadStore(rootNode);
     }
 
@@ -538,48 +505,50 @@ public class SignalAccount implements Closeable {
         }
     }
 
+    private void saveStickerStore(StickerStore.Storage storage) {
+        this.stickerStoreStorage = storage;
+        save();
+    }
+
     public void save() {
-        if (fileChannel == null) {
-            return;
-        }
-        var rootNode = jsonProcessor.createObjectNode();
-        rootNode.put("username", username)
-                .put("uuid", uuid == null ? null : uuid.toString())
-                .put("deviceId", deviceId)
-                .put("isMultiDevice", isMultiDevice)
-                .put("password", password)
-                .put("registrationId", identityKeyStore.getLocalRegistrationId())
-                .put("identityPrivateKey",
-                        Base64.getEncoder()
-                                .encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
-                .put("identityKey",
-                        Base64.getEncoder()
-                                .encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
-                .put("registrationLockPin", registrationLockPin)
-                .put("pinMasterKey",
-                        pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
-                .put("storageKey",
-                        storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
-                .put("preKeyIdOffset", preKeyIdOffset)
-                .put("nextSignedPreKeyId", nextSignedPreKeyId)
-                .put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
-                .put("registered", registered)
-                .putPOJO("groupStore", groupStore)
-                .putPOJO("stickerStore", stickerStore);
-        try {
-            try (var output = new ByteArrayOutputStream()) {
-                // Write to memory first to prevent corrupting the file in case of serialization errors
-                jsonProcessor.writeValue(output, rootNode);
-                var input = new ByteArrayInputStream(output.toByteArray());
-                synchronized (fileChannel) {
+        synchronized (fileChannel) {
+            var rootNode = jsonProcessor.createObjectNode();
+            rootNode.put("username", username)
+                    .put("uuid", uuid == null ? null : uuid.toString())
+                    .put("deviceId", deviceId)
+                    .put("isMultiDevice", isMultiDevice)
+                    .put("password", password)
+                    .put("registrationId", identityKeyStore.getLocalRegistrationId())
+                    .put("identityPrivateKey",
+                            Base64.getEncoder()
+                                    .encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
+                    .put("identityKey",
+                            Base64.getEncoder()
+                                    .encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
+                    .put("registrationLockPin", registrationLockPin)
+                    .put("pinMasterKey",
+                            pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
+                    .put("storageKey",
+                            storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
+                    .put("preKeyIdOffset", preKeyIdOffset)
+                    .put("nextSignedPreKeyId", nextSignedPreKeyId)
+                    .put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
+                    .put("registered", registered)
+                    .putPOJO("groupStore", groupStore)
+                    .putPOJO("stickerStore", stickerStoreStorage);
+            try {
+                try (var output = new ByteArrayOutputStream()) {
+                    // Write to memory first to prevent corrupting the file in case of serialization errors
+                    jsonProcessor.writeValue(output, rootNode);
+                    var input = new ByteArrayInputStream(output.toByteArray());
                     fileChannel.position(0);
                     input.transferTo(Channels.newOutputStream(fileChannel));
                     fileChannel.truncate(fileChannel.position());
                     fileChannel.force(false);
                 }
+            } catch (Exception e) {
+                logger.error("Error saving file: {}", e.getMessage());
             }
-        } catch (Exception e) {
-            logger.error("Error saving file: {}", e.getMessage());
         }
     }
 
index 6c87d5dc3baeca5fb716cdd9dea4faa5564e176a..51bc2fdf50ec95962534d28363172b838831f9f2 100644 (file)
@@ -5,9 +5,12 @@ import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 
+import java.io.InvalidObjectException;
+
 public class Utils {
 
     private Utils() {
@@ -24,4 +27,14 @@ public class Utils {
 
         return jsonProcessor;
     }
+
+    public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
+        var node = parent.get(name);
+        if (node == null || node.isNull()) {
+            throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
+                    name));
+        }
+
+        return node;
+    }
 }
index 8e37895a679fb4cefe5c1b5e36f656e68f482de7..4bd19ea123a44788507771fd551546f3fa301bca 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal.manager.storage.groups;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
@@ -39,6 +40,7 @@ public class JsonGroupStore {
     private final static Logger logger = LoggerFactory.getLogger(JsonGroupStore.class);
 
     private static final ObjectMapper jsonProcessor = new ObjectMapper();
+    @JsonIgnore
     public File groupCachePath;
 
     @JsonProperty("groups")
@@ -137,6 +139,7 @@ public class JsonGroupStore {
         return null;
     }
 
+    @JsonIgnore
     public List<GroupInfo> getGroups() {
         final var groups = this.groups.values();
         for (var group : groups) {
index 54e95d0a9d4e31e56b56a5dc2bc42cf79e9e36c1..495da8d624a09be2a8af786c0a0880fae29e4b59 100644 (file)
@@ -2,22 +2,22 @@ package org.asamk.signal.manager.storage.stickers;
 
 public class Sticker {
 
-    private final byte[] packId;
+    private final StickerPackId packId;
     private final byte[] packKey;
     private boolean installed;
 
-    public Sticker(final byte[] packId, final byte[] packKey) {
+    public Sticker(final StickerPackId packId, final byte[] packKey) {
         this.packId = packId;
         this.packKey = packKey;
     }
 
-    public Sticker(final byte[] packId, final byte[] packKey, final boolean installed) {
+    public Sticker(final StickerPackId packId, final byte[] packKey, final boolean installed) {
         this.packId = packId;
         this.packKey = packKey;
         this.installed = installed;
     }
 
-    public byte[] getPackId() {
+    public StickerPackId getPackId() {
         return packId;
     }
 
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/stickers/StickerPackId.java b/lib/src/main/java/org/asamk/signal/manager/storage/stickers/StickerPackId.java
new file mode 100644 (file)
index 0000000..6d992d1
--- /dev/null
@@ -0,0 +1,35 @@
+package org.asamk.signal.manager.storage.stickers;
+
+import java.util.Arrays;
+
+public class StickerPackId {
+
+    private final byte[] id;
+
+    private StickerPackId(final byte[] id) {
+        this.id = id;
+    }
+
+    public static StickerPackId deserialize(byte[] packId) {
+        return new StickerPackId(packId);
+    }
+
+    public byte[] serialize() {
+        return id;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final StickerPackId that = (StickerPackId) o;
+
+        return Arrays.equals(id, that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(id);
+    }
+}
index 8d2275752d74551c4b57718952d236b3bd97ed41..19803bdc64ec0f36255f807d36440fac8047bb57 100644 (file)
 package org.asamk.signal.manager.storage.stickers;
 
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
-import java.io.IOException;
 import java.util.Base64;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 public class StickerStore {
 
-    @JsonSerialize(using = StickersSerializer.class)
-    @JsonDeserialize(using = StickersDeserializer.class)
-    private final Map<byte[], Sticker> stickers = new HashMap<>();
+    private final Map<StickerPackId, Sticker> stickers;
 
-    public Sticker getSticker(byte[] packId) {
-        return stickers.get(packId);
+    private final Saver saver;
+
+    public StickerStore(final Saver saver) {
+        this.saver = saver;
+        stickers = new HashMap<>();
     }
 
-    public void updateSticker(Sticker sticker) {
-        stickers.put(sticker.getPackId(), sticker);
+    public StickerStore(final Map<StickerPackId, Sticker> stickers, final Saver saver) {
+        this.stickers = stickers;
+        this.saver = saver;
     }
 
-    private static class StickersSerializer extends JsonSerializer<Map<byte[], Sticker>> {
-
-        @Override
-        public void serialize(
-                final Map<byte[], Sticker> value, final JsonGenerator jgen, final SerializerProvider provider
-        ) throws IOException {
-            final var stickers = value.values();
-            jgen.writeStartArray(stickers.size());
-            for (var sticker : stickers) {
-                jgen.writeStartObject();
-                jgen.writeStringField("packId", Base64.getEncoder().encodeToString(sticker.getPackId()));
-                jgen.writeStringField("packKey", Base64.getEncoder().encodeToString(sticker.getPackKey()));
-                jgen.writeBooleanField("installed", sticker.isInstalled());
-                jgen.writeEndObject();
+    public static StickerStore fromStorage(Storage storage, Saver saver) {
+        final var packIds = new HashSet<StickerPackId>();
+        final var stickers = storage.stickers.stream().map(s -> {
+            var packId = StickerPackId.deserialize(Base64.getDecoder().decode(s.packId));
+            if (packIds.contains(packId)) {
+                // Remove legacy duplicate packIds ...
+                return null;
             }
-            jgen.writeEndArray();
+            packIds.add(packId);
+            var packKey = Base64.getDecoder().decode(s.packKey);
+            var installed = s.installed;
+            return new Sticker(packId, packKey, installed);
+        }).filter(Objects::nonNull).collect(Collectors.toMap(Sticker::getPackId, s -> s));
+
+        return new StickerStore(stickers, saver);
+    }
+
+    public Sticker getSticker(StickerPackId packId) {
+        synchronized (stickers) {
+            return stickers.get(packId);
+        }
+    }
+
+    public void updateSticker(Sticker sticker) {
+        Storage storage;
+        synchronized (stickers) {
+            stickers.put(sticker.getPackId(), sticker);
+            storage = toStorageLocked();
         }
+        saver.save(storage);
     }
 
-    private static class StickersDeserializer extends JsonDeserializer<Map<byte[], Sticker>> {
-
-        @Override
-        public Map<byte[], Sticker> deserialize(
-                JsonParser jsonParser, DeserializationContext deserializationContext
-        ) throws IOException {
-            var stickers = new HashMap<byte[], Sticker>();
-            JsonNode node = jsonParser.getCodec().readTree(jsonParser);
-            for (var n : node) {
-                var packId = Base64.getDecoder().decode(n.get("packId").asText());
-                var packKey = Base64.getDecoder().decode(n.get("packKey").asText());
-                var installed = n.get("installed").asBoolean(false);
-                stickers.put(packId, new Sticker(packId, packKey, installed));
+    private Storage toStorageLocked() {
+        return new Storage(stickers.values()
+                .stream()
+                .map(s -> new Storage.Sticker(Base64.getEncoder().encodeToString(s.getPackId().serialize()),
+                        Base64.getEncoder().encodeToString(s.getPackKey()),
+                        s.isInstalled()))
+                .collect(Collectors.toList()));
+    }
+
+    public static class Storage {
+
+        public List<Storage.Sticker> stickers;
+
+        // For deserialization
+        private Storage() {
+        }
+
+        public Storage(final List<Sticker> stickers) {
+            this.stickers = stickers;
+        }
+
+        private static class Sticker {
+
+            public String packId;
+            public String packKey;
+            public boolean installed;
+
+            // For deserialization
+            private Sticker() {
             }
 
-            return stickers;
+            public Sticker(final String packId, final String packKey, final boolean installed) {
+                this.packId = packId;
+                this.packKey = packKey;
+                this.installed = installed;
+            }
         }
     }
+
+    public interface Saver {
+
+        void save(Storage storage);
+    }
 }
index 2963a9966b7c770d2086795134ac2c194fd1922c..b0a9bbb184ba5c35a3065de47c5749b6217c7694 100644 (file)
@@ -1,7 +1,5 @@
 package org.asamk.signal.manager.util;
 
-import com.fasterxml.jackson.databind.JsonNode;
-
 import org.whispersystems.libsignal.IdentityKey;
 import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -13,7 +11,6 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InvalidObjectException;
 import java.net.URLConnection;
 import java.nio.file.Files;
 
@@ -80,14 +77,4 @@ public class Utils {
             return new SignalServiceAddress(null, identifier);
         }
     }
-
-    public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
-        var node = parent.get(name);
-        if (node == null || node.isNull()) {
-            throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
-                    name));
-        }
-
-        return node;
-    }
 }