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.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;
private JsonGroupStore groupStore;
private JsonContactsStore contactStore;
private RecipientStore recipientStore;
+ private ProfileStore profileStore;
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);
final Pair<FileChannel, FileLock> 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();
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.registered = false;
return account;
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.registered = true;
account.isMultiDevice = true;
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;
return !(!f.exists() || f.isDirectory());
}
- private void load() throws IOException {
+ private void load(String dataPath) throws IOException {
JsonNode rootNode;
synchronized (fileChannel) {
fileChannel.position(0);
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");
}
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()) {
}
}
+ JsonNode profileStoreNode = rootNode.get("profileStore");
+ if (profileStoreNode != null) {
+ profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class);
+ }
+ if (profileStore == null) {
+ profileStore = new ProfileStore();
+ }
+
JsonNode threadStoreNode = rootNode.get("threadStore");
if (threadStoreNode != null) {
LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
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);
}
}
.putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore)
.putPOJO("recipientStore", recipientStore)
+ .putPOJO("profileStore", profileStore)
;
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()));
return recipientStore;
}
+ public ProfileStore getProfileStore() {
+ return profileStore;
+ }
+
public String getUsername() {
return username;
}