import org.asamk.signal.storage.contacts.JsonContactsStore;
import org.asamk.signal.storage.groups.GroupInfo;
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.threads.LegacyJsonThreadStore;
import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.Medium;
+import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.util.Base64;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
+import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Collection;
import java.util.UUID;
+import java.util.stream.Collectors;
-public class SignalAccount {
+public class SignalAccount implements Closeable {
private final ObjectMapper jsonProcessor = new ObjectMapper();
- private FileChannel fileChannel;
- private FileLock lock;
+ private final FileChannel fileChannel;
+ private final FileLock lock;
private String username;
private UUID uuid;
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
private JsonSignalProtocolStore signalProtocolStore;
private JsonGroupStore groupStore;
private JsonContactsStore contactStore;
+ private RecipientStore recipientStore;
+ private ProfileStore profileStore;
- private SignalAccount() {
+ 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);
}
public static SignalAccount load(String dataPath, String username) throws IOException {
- SignalAccount account = new SignalAccount();
- IOUtils.createPrivateDirectories(dataPath);
- account.openFileChannel(getFileName(dataPath, username));
- account.load();
- return account;
+ final String fileName = getFileName(dataPath, username);
+ final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
+ try {
+ SignalAccount account = new SignalAccount(pair.first(), pair.second());
+ account.load();
+ return account;
+ } catch (Throwable e) {
+ pair.second().close();
+ pair.first().close();
+ throw e;
+ }
}
public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
+ String fileName = getFileName(dataPath, username);
+ if (!new File(fileName).exists()) {
+ IOUtils.createPrivateFile(fileName);
+ }
- SignalAccount account = new SignalAccount();
- account.openFileChannel(getFileName(dataPath, username));
+ final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
+ SignalAccount account = new SignalAccount(pair.first(), pair.second());
account.username = username;
account.profileKey = profileKey;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.contactStore = new JsonContactsStore();
+ account.recipientStore = new RecipientStore();
+ account.profileStore = new ProfileStore();
account.registered = false;
return account;
public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
+ String fileName = getFileName(dataPath, username);
+ if (!new File(fileName).exists()) {
+ IOUtils.createPrivateFile(fileName);
+ }
- SignalAccount account = new SignalAccount();
- account.openFileChannel(getFileName(dataPath, username));
+ final Pair<FileChannel, FileLock> pair = openFileChannel(fileName);
+ SignalAccount account = new SignalAccount(pair.first(), pair.second());
account.username = username;
account.uuid = uuid;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.contactStore = new JsonContactsStore();
+ account.recipientStore = new RecipientStore();
+ account.profileStore = new ProfileStore();
account.registered = true;
account.isMultiDevice = true;
return account;
}
- public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) {
- SignalAccount account = new SignalAccount();
-
- account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
- account.registered = false;
-
- return account;
- }
-
public static String getFileName(String dataPath, String username) {
return dataPath + "/" + username;
}
if (contactStore == null) {
contactStore = new JsonContactsStore();
}
+
+ JsonNode recipientStoreNode = rootNode.get("recipientStore");
+ if (recipientStoreNode != null) {
+ recipientStore = jsonProcessor.convertValue(recipientStoreNode, RecipientStore.class);
+ }
+ if (recipientStore == null) {
+ recipientStore = new RecipientStore();
+
+ recipientStore.resolveServiceAddress(getSelfAddress());
+
+ for (ContactInfo contact : contactStore.getContacts()) {
+ recipientStore.resolveServiceAddress(contact.getAddress());
+ }
+
+ for (GroupInfo group : groupStore.getGroups()) {
+ group.members = group.members.stream()
+ .map(m -> recipientStore.resolveServiceAddress(m))
+ .collect(Collectors.toSet());
+ }
+
+ for (SessionInfo session : signalProtocolStore.getSessions()) {
+ session.address = recipientStore.resolveServiceAddress(session.address);
+ }
+
+ for (JsonIdentityKeyStore.Identity identity : signalProtocolStore.getIdentities()) {
+ identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress()));
+ }
+ }
+
+ 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);
// Migrate thread info to group and contact store
for (ThreadInfo thread : threadStore.getThreads()) {
+ if (thread.id == null || thread.id.isEmpty()) {
+ continue;
+ }
try {
ContactInfo contactInfo = contactStore.getContact(new SignalServiceAddress(null, thread.id));
if (contactInfo != null) {
.putPOJO("axolotlStore", signalProtocolStore)
.putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore)
+ .putPOJO("recipientStore", recipientStore)
+ .putPOJO("profileStore", profileStore)
;
try {
synchronized (fileChannel) {
}
}
- private void openFileChannel(String fileName) throws IOException {
- if (fileChannel != null) {
- return;
- }
-
- if (!new File(fileName).exists()) {
- IOUtils.createPrivateFile(fileName);
- }
- fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel();
- lock = fileChannel.tryLock();
+ private static Pair<FileChannel, FileLock> openFileChannel(String fileName) throws IOException {
+ FileChannel fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel();
+ FileLock lock = fileChannel.tryLock();
if (lock == null) {
System.err.println("Config file is in use by another instance, waiting…");
lock = fileChannel.lock();
System.err.println("Config file lock acquired.");
}
+ return new Pair<>(fileChannel, lock);
+ }
+
+ public void setResolver(final SignalServiceAddressResolver resolver) {
+ signalProtocolStore.setResolver(resolver);
}
public void addPreKeys(Collection<PreKeyRecord> records) {
return contactStore;
}
+ public RecipientStore getRecipientStore() {
+ return recipientStore;
+ }
+
+ public ProfileStore getProfileStore() {
+ return profileStore;
+ }
+
public String getUsername() {
return username;
}
public void setMultiDevice(final boolean multiDevice) {
isMultiDevice = multiDevice;
}
+
+ @Override
+ public void close() throws IOException {
+ synchronized (fileChannel) {
+ try {
+ lock.close();
+ } catch (ClosedChannelException ignored) {
+ }
+ fileChannel.close();
+ }
+ }
}