]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/cli/Manager.java
Always call save() after modifying something
[signal-cli] / src / main / java / cli / Manager.java
index a1d58f036e10b7d33cd523eddfd41e0c426aea90..86850ed46ffa53c7a8fc55043dd749b7177b0c9d 100644 (file)
@@ -44,13 +44,13 @@ import org.whispersystems.textsecure.api.util.InvalidNumberException;
 import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
 
 import java.io.*;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-class Manager {
+class Manager implements TextSecure {
     private final static String URL = "https://textsecure-service.whispersystems.org";
     private final static TrustStore TRUST_STORE = new WhisperTrustStore();
 
@@ -130,10 +130,13 @@ class Manager {
         if (groupStoreNode != null) {
             groupStore = jsonProcessot.convertValue(groupStoreNode, JsonGroupStore.class);
         }
+        if (groupStore == null) {
+            groupStore = new JsonGroupStore();
+        }
         accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password, USER_AGENT);
     }
 
-    public void save() {
+    private void save() {
         ObjectNode rootNode = jsonProcessot.createObjectNode();
         rootNode.put("username", username)
                 .put("password", password)
@@ -157,6 +160,7 @@ class Manager {
         axolotlStore = new JsonAxolotlStore(identityKey, registrationId);
         groupStore = new JsonGroupStore();
         registered = false;
+        save();
     }
 
     public boolean isRegistered() {
@@ -174,6 +178,7 @@ class Manager {
             accountManager.requestSmsVerificationCode();
 
         registered = false;
+        save();
     }
 
     private static final int BATCH_SIZE = 100;
@@ -191,6 +196,8 @@ class Manager {
         }
 
         preKeyIdOffset = (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE;
+        save();
+
         return records;
     }
 
@@ -207,6 +214,7 @@ class Manager {
         PreKeyRecord record = new PreKeyRecord(Medium.MAX_VALUE, keyPair);
 
         axolotlStore.storePreKey(Medium.MAX_VALUE, record);
+        save();
 
         return record;
     }
@@ -219,6 +227,7 @@ class Manager {
 
             axolotlStore.storeSignedPreKey(nextSignedPreKeyId, record);
             nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
+            save();
 
             return record;
         } catch (InvalidKeyException e) {
@@ -241,31 +250,168 @@ class Manager {
         SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(axolotlStore.getIdentityKeyPair());
 
         accountManager.setPreKeys(axolotlStore.getIdentityKeyPair().getPublicKey(), lastResortKey, signedPreKeyRecord, oneTimePreKeys);
+        save();
+    }
+
+
+    private static List<TextSecureAttachment> getTextSecureAttachments(List<String> attachments) throws AttachmentInvalidException {
+        List<TextSecureAttachment> textSecureAttachments = null;
+        if (attachments != null) {
+            textSecureAttachments = new ArrayList<>(attachments.size());
+            for (String attachment : attachments) {
+                try {
+                    textSecureAttachments.add(createAttachment(attachment));
+                } catch (IOException e) {
+                    throw new AttachmentInvalidException(attachment, e);
+                }
+            }
+        }
+        return textSecureAttachments;
+    }
+
+    private static TextSecureAttachmentStream createAttachment(String attachment) throws IOException {
+        File attachmentFile = new File(attachment);
+        InputStream attachmentStream = new FileInputStream(attachmentFile);
+        final long attachmentSize = attachmentFile.length();
+        String mime = Files.probeContentType(Paths.get(attachment));
+        return new TextSecureAttachmentStream(attachmentStream, mime, attachmentSize, null);
+    }
+
+    @Override
+    public void sendGroupMessage(String messageText, List<String> attachments,
+                                 byte[] groupId)
+            throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
+        final TextSecureDataMessage.Builder messageBuilder = TextSecureDataMessage.newBuilder().withBody(messageText);
+        if (attachments != null) {
+            messageBuilder.withAttachments(getTextSecureAttachments(attachments));
+        }
+        if (groupId != null) {
+            TextSecureGroup group = TextSecureGroup.newBuilder(TextSecureGroup.Type.DELIVER)
+                    .withId(groupId)
+                    .build();
+            messageBuilder.asGroupMessage(group);
+        }
+        TextSecureDataMessage message = messageBuilder.build();
+
+        sendMessage(message, getGroupInfo(groupId).members);
+    }
+
+    public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
+        TextSecureGroup group = TextSecureGroup.newBuilder(TextSecureGroup.Type.QUIT)
+                .withId(groupId)
+                .build();
+
+        TextSecureDataMessage message = TextSecureDataMessage.newBuilder()
+                .asGroupMessage(group)
+                .build();
+
+        sendMessage(message, getGroupInfo(groupId).members);
+    }
+
+    public byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
+        GroupInfo g;
+        if (groupId == null) {
+            // Create new group
+            g = new GroupInfo(Util.getSecretBytes(16));
+            g.members.add(username);
+        } else {
+            g = getGroupInfo(groupId);
+        }
+
+        if (name != null) {
+            g.name = name;
+        }
+
+        if (members != null) {
+            for (String member : members) {
+                try {
+                    g.members.add(canonicalizeNumber(member));
+                } catch (InvalidNumberException e) {
+                    System.err.println("Failed to add member \"" + member + "\" to group: " + e.getMessage());
+                    System.err.println("Aborting…");
+                    System.exit(1);
+                }
+            }
+        }
+
+        TextSecureGroup.Builder group = TextSecureGroup.newBuilder(TextSecureGroup.Type.UPDATE)
+                .withId(g.groupId)
+                .withName(g.name)
+                .withMembers(new ArrayList<>(g.members));
+
+        if (avatarFile != null) {
+            try {
+                group.withAvatar(createAttachment(avatarFile));
+                // TODO
+                g.avatarId = 0;
+            } catch (IOException e) {
+                throw new AttachmentInvalidException(avatarFile, e);
+            }
+        }
+
+        setGroupInfo(g);
+
+        TextSecureDataMessage message = TextSecureDataMessage.newBuilder()
+                .asGroupMessage(group.build())
+                .build();
+
+        sendMessage(message, g.members);
+        return g.groupId;
+    }
+
+    @Override
+    public void sendMessage(String message, List<String> attachments, String recipient)
+            throws EncapsulatedExceptions, AttachmentInvalidException, IOException {
+        List<String> recipients = new ArrayList<>(1);
+        recipients.add(recipient);
+        sendMessage(message, attachments, recipients);
+    }
+
+    public void sendMessage(String messageText, List<String> attachments,
+                            Collection<String> recipients)
+            throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
+        final TextSecureDataMessage.Builder messageBuilder = TextSecureDataMessage.newBuilder().withBody(messageText);
+        if (attachments != null) {
+            messageBuilder.withAttachments(getTextSecureAttachments(attachments));
+        }
+        TextSecureDataMessage message = messageBuilder.build();
+
+        sendMessage(message, recipients);
+    }
+
+    public void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions {
+        TextSecureDataMessage message = TextSecureDataMessage.newBuilder()
+                .asEndSessionMessage()
+                .build();
+
+        sendMessage(message, recipients);
     }
 
-    public void sendMessage(List<String> recipients, TextSecureDataMessage message)
+    private void sendMessage(TextSecureDataMessage message, Collection<String> recipients)
             throws IOException, EncapsulatedExceptions {
         TextSecureMessageSender messageSender = new TextSecureMessageSender(URL, TRUST_STORE, username, password,
                 axolotlStore, USER_AGENT, Optional.<TextSecureMessageSender.EventListener>absent());
 
-        List<TextSecureAddress> recipientsTS = new ArrayList<>(recipients.size());
+        Set<TextSecureAddress> recipientsTS = new HashSet<>(recipients.size());
         for (String recipient : recipients) {
             try {
                 recipientsTS.add(getPushAddress(recipient));
             } catch (InvalidNumberException e) {
                 System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
                 System.err.println("Aborting sending.");
+                save();
                 return;
             }
         }
 
-        messageSender.sendMessage(recipientsTS, message);
+        messageSender.sendMessage(new ArrayList<>(recipientsTS), message);
 
         if (message.isEndSession()) {
             for (TextSecureAddress recipient : recipientsTS) {
                 handleEndSession(recipient.getNumber());
             }
         }
+        save();
     }
 
     private TextSecureContent decryptMessage(TextSecureEnvelope envelope) {
@@ -309,29 +455,46 @@ class Manager {
                                     TextSecureGroup groupInfo = message.getGroupInfo().get();
                                     switch (groupInfo.getType()) {
                                         case UPDATE:
-                                            long avatarId = 0;
+                                            try {
+                                                group = groupStore.getGroup(groupInfo.getGroupId());
+                                            } catch (GroupNotFoundException e) {
+                                                group = new GroupInfo(groupInfo.getGroupId());
+                                            }
+
                                             if (groupInfo.getAvatar().isPresent()) {
                                                 TextSecureAttachment avatar = groupInfo.getAvatar().get();
                                                 if (avatar.isPointer()) {
-                                                    avatarId = avatar.asPointer().getId();
+                                                    long avatarId = avatar.asPointer().getId();
                                                     try {
                                                         retrieveAttachment(avatar.asPointer());
+                                                        group.avatarId = avatarId;
                                                     } catch (IOException | InvalidMessageException e) {
                                                         System.err.println("Failed to retrieve group avatar (" + avatarId + "): " + e.getMessage());
                                                     }
                                                 }
                                             }
 
-                                            group = new GroupInfo(groupInfo.getGroupId(), groupInfo.getName().get(), groupInfo.getMembers().get(), avatarId);
+                                            if (groupInfo.getName().isPresent()) {
+                                                group.name = groupInfo.getName().get();
+                                            }
+
+                                            if (groupInfo.getMembers().isPresent()) {
+                                                group.members.addAll(groupInfo.getMembers().get());
+                                            }
+
                                             groupStore.updateGroup(group);
                                             break;
                                         case DELIVER:
-                                            group = groupStore.getGroup(groupInfo.getGroupId());
+                                            try {
+                                                group = groupStore.getGroup(groupInfo.getGroupId());
+                                            } catch (GroupNotFoundException e) {
+                                            }
                                             break;
                                         case QUIT:
-                                            group = groupStore.getGroup(groupInfo.getGroupId());
-                                            if (group != null) {
+                                            try {
+                                                group = groupStore.getGroup(groupInfo.getGroupId());
                                                 group.members.remove(envelope.getSource());
+                                            } catch (GroupNotFoundException e) {
                                             }
                                             break;
                                     }
@@ -353,6 +516,7 @@ class Manager {
                             }
                         }
                     }
+                    save();
                     handler.handleMessage(envelope, content, group);
                 } catch (TimeoutException e) {
                     if (returnOnTimeout)
@@ -360,7 +524,6 @@ class Manager {
                 } catch (InvalidVersionException e) {
                     System.err.println("Ignoring error: " + e.getMessage());
                 }
-                save();
             }
         } finally {
             if (messagePipe != null)
@@ -429,7 +592,16 @@ class Manager {
         return new TextSecureAddress(e164number);
     }
 
-    public GroupInfo getGroupInfo(byte[] groupId) {
+    private GroupInfo getGroupInfo(byte[] groupId) throws GroupNotFoundException {
         return groupStore.getGroup(groupId);
     }
+
+    private void setGroupInfo(GroupInfo group) {
+        groupStore.updateGroup(group);
+    }
+
+    @Override
+    public boolean isRemote() {
+        return false;
+    }
 }