mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Refactor sticker store
This commit is contained in:
parent
4e123a2dc3
commit
624fa4fda4
8 changed files with 214 additions and 171 deletions
|
@ -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.Profile;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.stickers.Sticker;
|
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.AttachmentUtils;
|
||||||
import org.asamk.signal.manager.util.IOUtils;
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
import org.asamk.signal.manager.util.KeyUtils;
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
|
@ -1170,7 +1171,7 @@ public class Manager implements Closeable {
|
||||||
var packKey = KeyUtils.createStickerUploadKey();
|
var packKey = KeyUtils.createStickerUploadKey();
|
||||||
var packId = messageSender.uploadStickerManifest(manifest, packKey);
|
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.getStickerStore().updateSticker(sticker);
|
||||||
account.save();
|
account.save();
|
||||||
|
|
||||||
|
@ -1591,9 +1592,10 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
if (message.getSticker().isPresent()) {
|
if (message.getSticker().isPresent()) {
|
||||||
final var messageSticker = message.getSticker().get();
|
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) {
|
if (sticker == null) {
|
||||||
sticker = new Sticker(messageSticker.getPackId(), messageSticker.getPackKey());
|
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
|
||||||
account.getStickerStore().updateSticker(sticker);
|
account.getStickerStore().updateSticker(sticker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2086,12 +2088,13 @@ public class Manager implements Closeable {
|
||||||
if (!m.getPackId().isPresent()) {
|
if (!m.getPackId().isPresent()) {
|
||||||
continue;
|
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 (sticker == null) {
|
||||||
if (!m.getPackKey().isPresent()) {
|
if (!m.getPackKey().isPresent()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sticker = new Sticker(m.getPackId().get(), m.getPackKey().get());
|
sticker = new Sticker(stickerPackId, m.getPackKey().get());
|
||||||
}
|
}
|
||||||
sticker.setInstalled(!m.getType().isPresent()
|
sticker.setInstalled(!m.getType().isPresent()
|
||||||
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL);
|
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL);
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
package org.asamk.signal.manager.storage;
|
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.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
|
||||||
|
|
||||||
import org.asamk.signal.manager.groups.GroupId;
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
import org.asamk.signal.manager.storage.contacts.ContactsStore;
|
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.storage.threads.LegacyJsonThreadStore;
|
||||||
import org.asamk.signal.manager.util.IOUtils;
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
import org.asamk.signal.manager.util.KeyUtils;
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
import org.asamk.signal.manager.util.Utils;
|
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -69,9 +62,11 @@ public class SignalAccount implements Closeable {
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
|
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 FileChannel fileChannel;
|
||||||
private final FileLock lock;
|
private final FileLock lock;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
|
@ -94,17 +89,13 @@ public class SignalAccount implements Closeable {
|
||||||
private JsonGroupStore groupStore;
|
private JsonGroupStore groupStore;
|
||||||
private RecipientStore recipientStore;
|
private RecipientStore recipientStore;
|
||||||
private StickerStore stickerStore;
|
private StickerStore stickerStore;
|
||||||
|
private StickerStore.Storage stickerStoreStorage;
|
||||||
|
|
||||||
private MessageCache messageCache;
|
private MessageCache messageCache;
|
||||||
|
|
||||||
private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
|
private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
|
||||||
this.fileChannel = fileChannel;
|
this.fileChannel = fileChannel;
|
||||||
this.lock = lock;
|
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 {
|
public static SignalAccount load(File dataPath, String username) throws IOException {
|
||||||
|
@ -137,24 +128,10 @@ public class SignalAccount implements Closeable {
|
||||||
|
|
||||||
account.username = username;
|
account.username = username;
|
||||||
account.profileKey = profileKey;
|
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;
|
account.registered = false;
|
||||||
|
|
||||||
|
@ -163,6 +140,23 @@ public class SignalAccount implements Closeable {
|
||||||
return account;
|
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(
|
public static SignalAccount createLinkedAccount(
|
||||||
File dataPath,
|
File dataPath,
|
||||||
String username,
|
String username,
|
||||||
|
@ -187,29 +181,15 @@ public class SignalAccount implements Closeable {
|
||||||
account.password = password;
|
account.password = password;
|
||||||
account.profileKey = profileKey;
|
account.profileKey = profileKey;
|
||||||
account.deviceId = deviceId;
|
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.registered = true;
|
||||||
account.isMultiDevice = true;
|
account.isMultiDevice = true;
|
||||||
|
|
||||||
|
account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
|
||||||
account.migrateLegacyConfigs();
|
account.migrateLegacyConfigs();
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
|
@ -347,20 +327,10 @@ public class SignalAccount implements Closeable {
|
||||||
registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
|
registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
|
||||||
}
|
}
|
||||||
|
|
||||||
recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
|
initStores(dataPath, identityKeyPair, registrationId);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
loadLegacyStores(rootNode, legacySignalProtocolStore);
|
loadLegacyStores(rootNode, legacySignalProtocolStore);
|
||||||
|
|
||||||
signalProtocolStore = new SignalProtocolStore(preKeyStore, signedPreKeyStore, sessionStore, identityKeyStore);
|
|
||||||
|
|
||||||
var groupStoreNode = rootNode.get("groupStore");
|
var groupStoreNode = rootNode.get("groupStore");
|
||||||
if (groupStoreNode != null) {
|
if (groupStoreNode != null) {
|
||||||
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
|
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
|
||||||
|
@ -370,15 +340,12 @@ public class SignalAccount implements Closeable {
|
||||||
groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
|
groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
var stickerStoreNode = rootNode.get("stickerStore");
|
if (rootNode.hasNonNull("stickerStore")) {
|
||||||
if (stickerStoreNode != null) {
|
stickerStoreStorage = jsonProcessor.convertValue(rootNode.get("stickerStore"), StickerStore.Storage.class);
|
||||||
stickerStore = jsonProcessor.convertValue(stickerStoreNode, StickerStore.class);
|
stickerStore = StickerStore.fromStorage(stickerStoreStorage, this::saveStickerStore);
|
||||||
|
} else {
|
||||||
|
stickerStore = new StickerStore(this::saveStickerStore);
|
||||||
}
|
}
|
||||||
if (stickerStore == null) {
|
|
||||||
stickerStore = new StickerStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
messageCache = new MessageCache(getMessageCachePath(dataPath, username));
|
|
||||||
|
|
||||||
loadLegacyThreadStore(rootNode);
|
loadLegacyThreadStore(rootNode);
|
||||||
}
|
}
|
||||||
|
@ -538,48 +505,50 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveStickerStore(StickerStore.Storage storage) {
|
||||||
|
this.stickerStoreStorage = storage;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
if (fileChannel == null) {
|
synchronized (fileChannel) {
|
||||||
return;
|
var rootNode = jsonProcessor.createObjectNode();
|
||||||
}
|
rootNode.put("username", username)
|
||||||
var rootNode = jsonProcessor.createObjectNode();
|
.put("uuid", uuid == null ? null : uuid.toString())
|
||||||
rootNode.put("username", username)
|
.put("deviceId", deviceId)
|
||||||
.put("uuid", uuid == null ? null : uuid.toString())
|
.put("isMultiDevice", isMultiDevice)
|
||||||
.put("deviceId", deviceId)
|
.put("password", password)
|
||||||
.put("isMultiDevice", isMultiDevice)
|
.put("registrationId", identityKeyStore.getLocalRegistrationId())
|
||||||
.put("password", password)
|
.put("identityPrivateKey",
|
||||||
.put("registrationId", identityKeyStore.getLocalRegistrationId())
|
Base64.getEncoder()
|
||||||
.put("identityPrivateKey",
|
.encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
|
||||||
Base64.getEncoder()
|
.put("identityKey",
|
||||||
.encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
|
Base64.getEncoder()
|
||||||
.put("identityKey",
|
.encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
|
||||||
Base64.getEncoder()
|
.put("registrationLockPin", registrationLockPin)
|
||||||
.encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
|
.put("pinMasterKey",
|
||||||
.put("registrationLockPin", registrationLockPin)
|
pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
|
||||||
.put("pinMasterKey",
|
.put("storageKey",
|
||||||
pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
|
storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
|
||||||
.put("storageKey",
|
.put("preKeyIdOffset", preKeyIdOffset)
|
||||||
storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
|
.put("nextSignedPreKeyId", nextSignedPreKeyId)
|
||||||
.put("preKeyIdOffset", preKeyIdOffset)
|
.put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
|
||||||
.put("nextSignedPreKeyId", nextSignedPreKeyId)
|
.put("registered", registered)
|
||||||
.put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
|
.putPOJO("groupStore", groupStore)
|
||||||
.put("registered", registered)
|
.putPOJO("stickerStore", stickerStoreStorage);
|
||||||
.putPOJO("groupStore", groupStore)
|
try {
|
||||||
.putPOJO("stickerStore", stickerStore);
|
try (var output = new ByteArrayOutputStream()) {
|
||||||
try {
|
// Write to memory first to prevent corrupting the file in case of serialization errors
|
||||||
try (var output = new ByteArrayOutputStream()) {
|
jsonProcessor.writeValue(output, rootNode);
|
||||||
// Write to memory first to prevent corrupting the file in case of serialization errors
|
var input = new ByteArrayInputStream(output.toByteArray());
|
||||||
jsonProcessor.writeValue(output, rootNode);
|
|
||||||
var input = new ByteArrayInputStream(output.toByteArray());
|
|
||||||
synchronized (fileChannel) {
|
|
||||||
fileChannel.position(0);
|
fileChannel.position(0);
|
||||||
input.transferTo(Channels.newOutputStream(fileChannel));
|
input.transferTo(Channels.newOutputStream(fileChannel));
|
||||||
fileChannel.truncate(fileChannel.position());
|
fileChannel.truncate(fileChannel.position());
|
||||||
fileChannel.force(false);
|
fileChannel.force(false);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error saving file: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error saving file: {}", e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,12 @@ import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
|
||||||
|
import java.io.InvalidObjectException;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
private Utils() {
|
private Utils() {
|
||||||
|
@ -24,4 +27,14 @@ public class Utils {
|
||||||
|
|
||||||
return jsonProcessor;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.asamk.signal.manager.storage.groups;
|
package org.asamk.signal.manager.storage.groups;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
@ -39,6 +40,7 @@ public class JsonGroupStore {
|
||||||
private final static Logger logger = LoggerFactory.getLogger(JsonGroupStore.class);
|
private final static Logger logger = LoggerFactory.getLogger(JsonGroupStore.class);
|
||||||
|
|
||||||
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
||||||
|
@JsonIgnore
|
||||||
public File groupCachePath;
|
public File groupCachePath;
|
||||||
|
|
||||||
@JsonProperty("groups")
|
@JsonProperty("groups")
|
||||||
|
@ -137,6 +139,7 @@ public class JsonGroupStore {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
public List<GroupInfo> getGroups() {
|
public List<GroupInfo> getGroups() {
|
||||||
final var groups = this.groups.values();
|
final var groups = this.groups.values();
|
||||||
for (var group : groups) {
|
for (var group : groups) {
|
||||||
|
|
|
@ -2,22 +2,22 @@ package org.asamk.signal.manager.storage.stickers;
|
||||||
|
|
||||||
public class Sticker {
|
public class Sticker {
|
||||||
|
|
||||||
private final byte[] packId;
|
private final StickerPackId packId;
|
||||||
private final byte[] packKey;
|
private final byte[] packKey;
|
||||||
private boolean installed;
|
private boolean installed;
|
||||||
|
|
||||||
public Sticker(final byte[] packId, final byte[] packKey) {
|
public Sticker(final StickerPackId packId, final byte[] packKey) {
|
||||||
this.packId = packId;
|
this.packId = packId;
|
||||||
this.packKey = packKey;
|
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.packId = packId;
|
||||||
this.packKey = packKey;
|
this.packKey = packKey;
|
||||||
this.installed = installed;
|
this.installed = installed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getPackId() {
|
public StickerPackId getPackId() {
|
||||||
return packId;
|
return packId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +1,102 @@
|
||||||
package org.asamk.signal.manager.storage.stickers;
|
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.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class StickerStore {
|
public class StickerStore {
|
||||||
|
|
||||||
@JsonSerialize(using = StickersSerializer.class)
|
private final Map<StickerPackId, Sticker> stickers;
|
||||||
@JsonDeserialize(using = StickersDeserializer.class)
|
|
||||||
private final Map<byte[], Sticker> stickers = new HashMap<>();
|
|
||||||
|
|
||||||
public Sticker getSticker(byte[] packId) {
|
private final Saver saver;
|
||||||
return stickers.get(packId);
|
|
||||||
|
public StickerStore(final Saver saver) {
|
||||||
|
this.saver = saver;
|
||||||
|
stickers = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StickerStore(final Map<StickerPackId, Sticker> stickers, final Saver saver) {
|
||||||
|
this.stickers = stickers;
|
||||||
|
this.saver = saver;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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) {
|
public void updateSticker(Sticker sticker) {
|
||||||
stickers.put(sticker.getPackId(), sticker);
|
Storage storage;
|
||||||
|
synchronized (stickers) {
|
||||||
|
stickers.put(sticker.getPackId(), sticker);
|
||||||
|
storage = toStorageLocked();
|
||||||
|
}
|
||||||
|
saver.save(storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StickersSerializer extends JsonSerializer<Map<byte[], Sticker>> {
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public static class Storage {
|
||||||
public void serialize(
|
|
||||||
final Map<byte[], Sticker> value, final JsonGenerator jgen, final SerializerProvider provider
|
public List<Storage.Sticker> stickers;
|
||||||
) throws IOException {
|
|
||||||
final var stickers = value.values();
|
// For deserialization
|
||||||
jgen.writeStartArray(stickers.size());
|
private Storage() {
|
||||||
for (var sticker : stickers) {
|
}
|
||||||
jgen.writeStartObject();
|
|
||||||
jgen.writeStringField("packId", Base64.getEncoder().encodeToString(sticker.getPackId()));
|
public Storage(final List<Sticker> stickers) {
|
||||||
jgen.writeStringField("packKey", Base64.getEncoder().encodeToString(sticker.getPackKey()));
|
this.stickers = stickers;
|
||||||
jgen.writeBooleanField("installed", sticker.isInstalled());
|
}
|
||||||
jgen.writeEndObject();
|
|
||||||
|
private static class Sticker {
|
||||||
|
|
||||||
|
public String packId;
|
||||||
|
public String packKey;
|
||||||
|
public boolean installed;
|
||||||
|
|
||||||
|
// For deserialization
|
||||||
|
private Sticker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sticker(final String packId, final String packKey, final boolean installed) {
|
||||||
|
this.packId = packId;
|
||||||
|
this.packKey = packKey;
|
||||||
|
this.installed = installed;
|
||||||
}
|
}
|
||||||
jgen.writeEndArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StickersDeserializer extends JsonDeserializer<Map<byte[], Sticker>> {
|
public interface Saver {
|
||||||
|
|
||||||
@Override
|
void save(Storage storage);
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.asamk.signal.manager.util;
|
package org.asamk.signal.manager.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
|
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
|
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
@ -13,7 +11,6 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InvalidObjectException;
|
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
@ -80,14 +77,4 @@ public class Utils {
|
||||||
return new SignalServiceAddress(null, identifier);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue