]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/cli/Manager.java
Store group info in json
[signal-cli] / src / main / java / cli / Manager.java
index 49043832268d56f562007ce6e195280c94ad3aa7..2258c99d9f1b3a443a5f2aaeb028251aefde362d 100644 (file)
  */
 package cli;
 
-import org.apache.commons.io.IOUtils;
-import org.json.JSONObject;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+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 com.fasterxml.jackson.databind.node.ObjectNode;
 import org.whispersystems.libaxolotl.*;
 import org.whispersystems.libaxolotl.ecc.Curve;
 import org.whispersystems.libaxolotl.ecc.ECKeyPair;
@@ -31,10 +36,7 @@ import org.whispersystems.textsecure.api.TextSecureMessagePipe;
 import org.whispersystems.textsecure.api.TextSecureMessageReceiver;
 import org.whispersystems.textsecure.api.TextSecureMessageSender;
 import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
-import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
-import org.whispersystems.textsecure.api.messages.TextSecureContent;
-import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
-import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
+import org.whispersystems.textsecure.api.messages.*;
 import org.whispersystems.textsecure.api.push.TextSecureAddress;
 import org.whispersystems.textsecure.api.push.TrustStore;
 import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
@@ -47,14 +49,19 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-public class Manager {
+class Manager {
     private final static String URL = "https://textsecure-service.whispersystems.org";
     private final static TrustStore TRUST_STORE = new WhisperTrustStore();
 
+    public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle();
+    public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion();
+    private final static String USER_AGENT = PROJECT_NAME + " " + PROJECT_VERSION;
+
     private final static String settingsPath = System.getProperty("user.home") + "/.config/textsecure";
     private final static String dataPath = settingsPath + "/data";
     private final static String attachmentsPath = settingsPath + "/attachments";
 
+    private final ObjectMapper jsonProcessot = new ObjectMapper();
     private String username;
     private String password;
     private String signalingKey;
@@ -64,10 +71,15 @@ public class Manager {
     private boolean registered = false;
 
     private JsonAxolotlStore axolotlStore;
-    TextSecureAccountManager accountManager;
+    private TextSecureAccountManager accountManager;
+    private JsonGroupStore groupStore;
 
     public Manager(String username) {
         this.username = username;
+        jsonProcessot.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
+        jsonProcessot.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it.
+        jsonProcessot.enable(SerializationFeature.WRITE_NULL_MAP_VALUES);
+        jsonProcessot.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
     }
 
     public String getFileName() {
@@ -77,54 +89,64 @@ public class Manager {
 
     public boolean userExists() {
         File f = new File(getFileName());
-        if (!f.exists() || f.isDirectory()) {
-            return false;
-        }
-        return true;
+        return !(!f.exists() || f.isDirectory());
     }
 
     public boolean userHasKeys() {
         return axolotlStore != null;
     }
 
+    private JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
+        JsonNode node = parent.get(name);
+        if (node == null) {
+            throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ", name));
+        }
+
+        return node;
+    }
+
     public void load() throws IOException, InvalidKeyException {
-        JSONObject in = new JSONObject(IOUtils.toString(new FileInputStream(getFileName())));
-        username = in.getString("username");
-        password = in.getString("password");
-        if (in.has("signalingKey")) {
-            signalingKey = in.getString("signalingKey");
+        JsonNode rootNode = jsonProcessot.readTree(new File(getFileName()));
+
+        username = getNotNullNode(rootNode, "username").asText();
+        password = getNotNullNode(rootNode, "password").asText();
+        if (rootNode.has("signalingKey")) {
+            signalingKey = getNotNullNode(rootNode, "signalingKey").asText();
         }
-        if (in.has("preKeyIdOffset")) {
-            preKeyIdOffset = in.getInt("preKeyIdOffset");
+        if (rootNode.has("preKeyIdOffset")) {
+            preKeyIdOffset = getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
         } else {
             preKeyIdOffset = 0;
         }
-        if (in.has("nextSignedPreKeyId")) {
-            nextSignedPreKeyId = in.getInt("nextSignedPreKeyId");
+        if (rootNode.has("nextSignedPreKeyId")) {
+            nextSignedPreKeyId = getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
         } else {
             nextSignedPreKeyId = 0;
         }
-        axolotlStore = new JsonAxolotlStore(in.getJSONObject("axolotlStore"));
-        registered = in.getBoolean("registered");
-        accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password);
+        axolotlStore = jsonProcessot.convertValue(getNotNullNode(rootNode, "axolotlStore"), JsonAxolotlStore.class); //new JsonAxolotlStore(in.getJSONObject("axolotlStore"));
+        registered = getNotNullNode(rootNode, "registered").asBoolean();
+        JsonNode groupStoreNode = rootNode.get("groupStore");
+        if (groupStoreNode != null) {
+            groupStore = jsonProcessot.convertValue(groupStoreNode, JsonGroupStore.class);
+        }
+        accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password, USER_AGENT);
     }
 
     public void save() {
-        String out = new JSONObject().put("username", username)
+        ObjectNode rootNode = jsonProcessot.createObjectNode();
+        rootNode.put("username", username)
                 .put("password", password)
                 .put("signalingKey", signalingKey)
                 .put("preKeyIdOffset", preKeyIdOffset)
                 .put("nextSignedPreKeyId", nextSignedPreKeyId)
-                .put("axolotlStore", axolotlStore.getJson())
-                .put("registered", registered).toString();
+                .put("registered", registered)
+                .putPOJO("axolotlStore", axolotlStore)
+                .putPOJO("groupStore", groupStore)
+        ;
         try {
-            OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(getFileName()));
-            writer.write(out);
-            writer.flush();
-            writer.close();
+            jsonProcessot.writeValue(new File(getFileName()), rootNode);
         } catch (Exception e) {
-            System.out.println("Saving file error: " + e.getMessage());
-            return;
+            System.err.println(String.format("Error saving file: %s", e.getMessage()));
         }
     }
 
@@ -132,6 +154,7 @@ public class Manager {
         IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
         int registrationId = KeyHelper.generateRegistrationId(false);
         axolotlStore = new JsonAxolotlStore(identityKey, registrationId);
+        groupStore = new JsonGroupStore();
         registered = false;
     }
 
@@ -142,7 +165,7 @@ public class Manager {
     public void register(boolean voiceVerication) throws IOException {
         password = Util.getSecret(18);
 
-        accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password);
+        accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password, USER_AGENT);
 
         if (voiceVerication)
             accountManager.requestVoiceVerificationCode();
@@ -205,7 +228,7 @@ public class Manager {
     public void verifyAccount(String verificationCode) throws IOException {
         verificationCode = verificationCode.replace("-", "");
         signalingKey = Util.getSecret(52);
-        accountManager.verifyAccount(verificationCode, signalingKey, false, axolotlStore.getLocalRegistrationId());
+        accountManager.verifyAccountWithCode(verificationCode, signalingKey, axolotlStore.getLocalRegistrationId(), false);
 
         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
         registered = true;
@@ -222,7 +245,7 @@ public class Manager {
     public void sendMessage(List<TextSecureAddress> recipients, TextSecureDataMessage message)
             throws IOException, EncapsulatedExceptions {
         TextSecureMessageSender messageSender = new TextSecureMessageSender(URL, TRUST_STORE, username, password,
-                axolotlStore, Optional.<TextSecureMessageSender.EventListener>absent());
+                axolotlStore, USER_AGENT, Optional.<TextSecureMessageSender.EventListener>absent());
         messageSender.sendMessage(recipients, message);
     }
 
@@ -242,11 +265,11 @@ public class Manager {
     }
 
     public interface ReceiveMessageHandler {
-        void handleMessage(TextSecureEnvelope envelope);
+        void handleMessage(TextSecureEnvelope envelope, TextSecureContent decryptedContent, GroupInfo group);
     }
 
     public void receiveMessages(int timeoutSeconds, boolean returnOnTimeout, ReceiveMessageHandler handler) throws IOException {
-        final TextSecureMessageReceiver messageReceiver = new TextSecureMessageReceiver(URL, TRUST_STORE, username, password, signalingKey);
+        final TextSecureMessageReceiver messageReceiver = new TextSecureMessageReceiver(URL, TRUST_STORE, username, password, signalingKey, USER_AGENT);
         TextSecureMessagePipe messagePipe = null;
 
         try {
@@ -254,14 +277,42 @@ public class Manager {
 
             while (true) {
                 TextSecureEnvelope envelope;
+                TextSecureContent content = null;
+                GroupInfo group = null;
                 try {
                     envelope = messagePipe.read(timeoutSeconds, TimeUnit.SECONDS);
-                    handler.handleMessage(envelope);
+                    if (!envelope.isReceipt()) {
+                        content = decryptMessage(envelope);
+                        if (content != null) {
+                            if (content.getDataMessage().isPresent()) {
+                                TextSecureDataMessage message = content.getDataMessage().get();
+                                if (message.getGroupInfo().isPresent()) {
+                                    TextSecureGroup groupInfo = message.getGroupInfo().get();
+                                    switch (groupInfo.getType()) {
+                                        case UPDATE:
+                                            group = new GroupInfo(groupInfo.getGroupId(), groupInfo.getName().get(), groupInfo.getMembers().get(), groupInfo.getAvatar().get().asPointer().getId());
+                                            groupStore.updateGroup(group);
+                                            break;
+                                        case DELIVER:
+                                            group = groupStore.getGroup(groupInfo.getGroupId());
+                                            break;
+                                        case QUIT:
+                                            group = groupStore.getGroup(groupInfo.getGroupId());
+                                            if (group != null) {
+                                                group.members.remove(envelope.getSource());
+                                            }
+                                            break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    handler.handleMessage(envelope, content, group);
                 } catch (TimeoutException e) {
                     if (returnOnTimeout)
                         return;
                 } catch (InvalidVersionException e) {
-                    System.out.println("Ignoring error: " + e.getMessage());
+                    System.err.println("Ignoring error: " + e.getMessage());
                 }
                 save();
             }
@@ -272,7 +323,7 @@ public class Manager {
     }
 
     public File retrieveAttachment(TextSecureAttachmentPointer pointer) throws IOException, InvalidMessageException {
-        final TextSecureMessageReceiver messageReceiver = new TextSecureMessageReceiver(URL, TRUST_STORE, username, password, signalingKey);
+        final TextSecureMessageReceiver messageReceiver = new TextSecureMessageReceiver(URL, TRUST_STORE, username, password, signalingKey, USER_AGENT);
 
         File tmpFile = File.createTempFile("ts_attach_" + pointer.getId(), ".tmp");
         InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile);
@@ -294,18 +345,36 @@ public class Manager {
         } finally {
             if (output != null) {
                 output.close();
+                output = null;
+            }
+            if (!tmpFile.delete()) {
+                System.err.println("Failed to delete temp file: " + tmpFile);
+            }
+        }
+        if (pointer.getPreview().isPresent()) {
+            File previewFile = new File(outputFile + ".preview");
+            try {
+                output = new FileOutputStream(previewFile);
+                byte[] preview = pointer.getPreview().get();
+                output.write(preview, 0, preview.length);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+                return null;
+            } finally {
+                if (output != null) {
+                    output.close();
+                }
             }
-            tmpFile.delete();
         }
         return outputFile;
     }
 
-    public String canonicalizeNumber(String number) throws InvalidNumberException {
+    private String canonicalizeNumber(String number) throws InvalidNumberException {
         String localNumber = username;
         return PhoneNumberFormatter.formatNumber(number, localNumber);
     }
 
-    protected TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
+    TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
         String e164number = canonicalizeNumber(number);
         return new TextSecureAddress(e164number);
     }