]> nmode's Git Repositories - signal-cli/commitdiff
Refactor dbus client mode to improve maintainability
authorAsamK <asamk@gmx.de>
Tue, 28 Sep 2021 16:51:44 +0000 (18:51 +0200)
committerAsamK <asamk@gmx.de>
Tue, 28 Sep 2021 20:33:35 +0000 (22:33 +0200)
src/main/java/org/asamk/Signal.java
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/commands/DbusCommand.java [deleted file]
src/main/java/org/asamk/signal/commands/RemoteDeleteCommand.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/commands/SendReactionCommand.java
src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java
src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java [new file with mode: 0644]
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java

index 2105ca76f330e5daaad111845cd8e3839f41bf2f..cc521f6d1eefbf763589ca6ac354edef18dde04b 100644 (file)
@@ -13,7 +13,7 @@ import java.util.List;
  */
 public interface Signal extends DBusInterface {
 
-    String getNumber();
+    String getSelfNumber();
 
     long sendMessage(
             String message, List<String> attachments, String recipient
index e81b7018cbb910031710c3ca6efb5830ebd5506e..bffbded51128d28893e57bb41c308ceb5b107a74 100644 (file)
@@ -8,7 +8,6 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import org.asamk.Signal;
 import org.asamk.signal.commands.Command;
 import org.asamk.signal.commands.Commands;
-import org.asamk.signal.commands.DbusCommand;
 import org.asamk.signal.commands.ExtendedDbusCommand;
 import org.asamk.signal.commands.LocalCommand;
 import org.asamk.signal.commands.MultiLocalCommand;
@@ -19,6 +18,7 @@ import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.dbus.DbusManagerImpl;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotRegisteredException;
 import org.asamk.signal.manager.ProvisioningManager;
@@ -29,6 +29,7 @@ import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.util.IOUtils;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
 import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
@@ -346,8 +347,14 @@ public class App {
     ) throws CommandException {
         if (command instanceof ExtendedDbusCommand) {
             ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn, outputWriter);
-        } else if (command instanceof DbusCommand) {
-            ((DbusCommand) command).handleCommand(ns, ts, outputWriter);
+        } else if (command instanceof LocalCommand) {
+            try {
+                ((LocalCommand) command).handleCommand(ns, new DbusManagerImpl(ts), outputWriter);
+            } catch (UnsupportedOperationException e) {
+                throw new UserErrorException("Command is not yet implemented via dbus", e);
+            } catch (DBusExecutionException e) {
+                throw new UnexpectedErrorException(e.getMessage(), e);
+            }
         } else {
             throw new UserErrorException("Command is not yet implemented via dbus");
         }
diff --git a/src/main/java/org/asamk/signal/commands/DbusCommand.java b/src/main/java/org/asamk/signal/commands/DbusCommand.java
deleted file mode 100644 (file)
index 9f676a3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.asamk.signal.commands;
-
-import net.sourceforge.argparse4j.inf.Namespace;
-
-import org.asamk.Signal;
-import org.asamk.signal.OutputWriter;
-import org.asamk.signal.commands.exceptions.CommandException;
-import org.asamk.signal.dbus.DbusSignalImpl;
-import org.asamk.signal.manager.Manager;
-
-public interface DbusCommand extends LocalCommand {
-
-    void handleCommand(Namespace ns, Signal signal, OutputWriter outputWriter) throws CommandException;
-
-    default void handleCommand(
-            final Namespace ns, final Manager m, final OutputWriter outputWriter
-    ) throws CommandException {
-        handleCommand(ns, new DbusSignalImpl(m, null), outputWriter);
-    }
-}
index 7d7067c4397607123020443442d6493d821b2cfb..e515defefa4d05ab037faad4ac91bb06de8acc94 100644 (file)
@@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
-import org.asamk.Signal;
 import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
 import org.asamk.signal.PlainTextWriter;
@@ -17,13 +16,11 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.util.CommandUtil;
 import org.asamk.signal.util.ErrorUtils;
-import org.freedesktop.dbus.errors.UnknownObject;
-import org.freedesktop.dbus.exceptions.DBusExecutionException;
 
 import java.io.IOException;
 import java.util.Map;
 
-public class RemoteDeleteCommand implements DbusCommand, JsonRpcLocalCommand {
+public class RemoteDeleteCommand implements JsonRpcLocalCommand {
 
     @Override
     public String getName() {
@@ -69,47 +66,6 @@ public class RemoteDeleteCommand implements DbusCommand, JsonRpcLocalCommand {
         }
     }
 
-    @Override
-    public void handleCommand(
-            final Namespace ns, final Signal signal, final OutputWriter outputWriter
-    ) throws CommandException {
-        final var recipients = ns.<String>getList("recipient");
-        final var groupIdStrings = ns.<String>getList("group-id");
-
-        final var noRecipients = recipients == null || recipients.isEmpty();
-        final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
-        if (noRecipients && noGroups) {
-            throw new UserErrorException("No recipients given");
-        }
-        if (!noRecipients && !noGroups) {
-            throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
-        }
-
-        final long targetTimestamp = ns.getLong("target-timestamp");
-
-        try {
-            long timestamp = 0;
-            if (!noGroups) {
-                final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
-                for (final var groupId : groupIds) {
-                    timestamp = signal.sendGroupRemoteDeleteMessage(targetTimestamp, groupId.serialize());
-                }
-            } else {
-                timestamp = signal.sendRemoteDeleteMessage(targetTimestamp, recipients);
-            }
-            outputResult(outputWriter, timestamp);
-        } catch (UnknownObject e) {
-            throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
-        } catch (Signal.Error.InvalidNumber e) {
-            throw new UserErrorException("Invalid number: " + e.getMessage());
-        } catch (Signal.Error.GroupNotFound e) {
-            throw new UserErrorException("Failed to send to group: " + e.getMessage());
-        } catch (DBusExecutionException e) {
-            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                    .getSimpleName() + ")", e);
-        }
-    }
-
     private void outputResult(final OutputWriter outputWriter, final long timestamp) {
         if (outputWriter instanceof PlainTextWriter) {
             final var writer = (PlainTextWriter) outputWriter;
index 1973b1a106ca31a40de20574ec41b7b5c1444b17..1cd2e674ba8e5395089982bab13bdf58bfc48a01 100644 (file)
@@ -4,13 +4,11 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
-import org.asamk.Signal;
 import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
 import org.asamk.signal.PlainTextWriter;
 import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
-import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.AttachmentInvalidException;
 import org.asamk.signal.manager.Manager;
@@ -22,8 +20,6 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.util.CommandUtil;
 import org.asamk.signal.util.ErrorUtils;
 import org.asamk.signal.util.IOUtils;
-import org.freedesktop.dbus.errors.UnknownObject;
-import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,7 +29,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-public class SendCommand implements DbusCommand, JsonRpcLocalCommand {
+public class SendCommand implements JsonRpcLocalCommand {
 
     private final static Logger logger = LoggerFactory.getLogger(SendCommand.class);
 
@@ -116,97 +112,6 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand {
         }
     }
 
-    @Override
-    public void handleCommand(
-            final Namespace ns, final Signal signal, final OutputWriter outputWriter
-    ) throws CommandException {
-        final var recipients = ns.<String>getList("recipient");
-        final var isEndSession = ns.getBoolean("end-session");
-        final var groupIdStrings = ns.<String>getList("group-id");
-        final var isNoteToSelf = ns.getBoolean("note-to-self");
-
-        final var noRecipients = recipients == null || recipients.isEmpty();
-        final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
-        if ((noRecipients && isEndSession) || (noRecipients && noGroups && !isNoteToSelf)) {
-            throw new UserErrorException("No recipients given");
-        }
-        if (!noRecipients && !noGroups) {
-            throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
-        }
-        if (!noRecipients && isNoteToSelf) {
-            throw new UserErrorException(
-                    "You cannot specify recipients by phone number and note to self at the same time");
-        }
-
-        if (isEndSession) {
-            try {
-                signal.sendEndSessionMessage(recipients);
-                return;
-            } catch (Signal.Error.UntrustedIdentity e) {
-                throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                        .getSimpleName() + ")");
-            } catch (DBusExecutionException e) {
-                throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                        .getSimpleName() + ")", e);
-            }
-        }
-
-        var messageText = ns.getString("message");
-        if (messageText == null) {
-            try {
-                messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
-            } catch (IOException e) {
-                throw new UserErrorException("Failed to read message from stdin: " + e.getMessage());
-            }
-        }
-
-        List<String> attachments = ns.getList("attachment");
-        if (attachments == null) {
-            attachments = List.of();
-        }
-
-        if (!noGroups) {
-            final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
-
-            try {
-                long timestamp = 0;
-                for (final var groupId : groupIds) {
-                    timestamp = signal.sendGroupMessage(messageText, attachments, groupId.serialize());
-                }
-                outputResult(outputWriter, timestamp);
-                return;
-            } catch (DBusExecutionException e) {
-                throw new UnexpectedErrorException("Failed to send group message: " + e.getMessage(), e);
-            }
-        }
-
-        if (isNoteToSelf) {
-            try {
-                var timestamp = signal.sendNoteToSelfMessage(messageText, attachments);
-                outputResult(outputWriter, timestamp);
-                return;
-            } catch (Signal.Error.UntrustedIdentity e) {
-                throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                        .getSimpleName() + ")");
-            } catch (DBusExecutionException e) {
-                throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage(), e);
-            }
-        }
-
-        try {
-            var timestamp = signal.sendMessage(messageText, attachments, recipients);
-            outputResult(outputWriter, timestamp);
-        } catch (UnknownObject e) {
-            throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
-        } catch (Signal.Error.UntrustedIdentity e) {
-            throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                    .getSimpleName() + ")");
-        } catch (DBusExecutionException e) {
-            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                    .getSimpleName() + ")", e);
-        }
-    }
-
     private void outputResult(final OutputWriter outputWriter, final long timestamp) {
         if (outputWriter instanceof PlainTextWriter) {
             final var writer = (PlainTextWriter) outputWriter;
index f8c3c358183f819a124cb56d78ced8ce82aca18e..a1c6c31989c12ab1db1c2cafa27929f57622a112 100644 (file)
@@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
-import org.asamk.Signal;
 import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
 import org.asamk.signal.PlainTextWriter;
@@ -17,13 +16,11 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.util.CommandUtil;
 import org.asamk.signal.util.ErrorUtils;
-import org.freedesktop.dbus.errors.UnknownObject;
-import org.freedesktop.dbus.exceptions.DBusExecutionException;
 
 import java.io.IOException;
 import java.util.Map;
 
-public class SendReactionCommand implements DbusCommand, JsonRpcLocalCommand {
+public class SendReactionCommand implements JsonRpcLocalCommand {
 
     @Override
     public String getName() {
@@ -85,54 +82,6 @@ public class SendReactionCommand implements DbusCommand, JsonRpcLocalCommand {
         }
     }
 
-    @Override
-    public void handleCommand(
-            final Namespace ns, final Signal signal, final OutputWriter outputWriter
-    ) throws CommandException {
-        final var recipients = ns.<String>getList("recipient");
-        final var groupIdStrings = ns.<String>getList("group-id");
-
-        final var noRecipients = recipients == null || recipients.isEmpty();
-        final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
-        if (noRecipients && noGroups) {
-            throw new UserErrorException("No recipients given");
-        }
-        if (!noRecipients && !noGroups) {
-            throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
-        }
-
-        final var emoji = ns.getString("emoji");
-        final var isRemove = ns.getBoolean("remove");
-        final var targetAuthor = ns.getString("target-author");
-        final var targetTimestamp = ns.getLong("target-timestamp");
-
-        try {
-            long timestamp = 0;
-            if (!noGroups) {
-                final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
-                for (final var groupId : groupIds) {
-                    timestamp = signal.sendGroupMessageReaction(emoji,
-                            isRemove,
-                            targetAuthor,
-                            targetTimestamp,
-                            groupId.serialize());
-                }
-            } else {
-                timestamp = signal.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, recipients);
-            }
-            outputResult(outputWriter, timestamp);
-        } catch (UnknownObject e) {
-            throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
-        } catch (Signal.Error.InvalidNumber e) {
-            throw new UserErrorException("Invalid number: " + e.getMessage());
-        } catch (Signal.Error.GroupNotFound e) {
-            throw new UserErrorException("Failed to send to group: " + e.getMessage());
-        } catch (DBusExecutionException e) {
-            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                    .getSimpleName() + ")", e);
-        }
-    }
-
     private void outputResult(final OutputWriter outputWriter, final long timestamp) {
         if (outputWriter instanceof PlainTextWriter) {
             final var writer = (PlainTextWriter) outputWriter;
index 49cd4719be596856735b6c6ebef98854d01e95e2..4bbaa992b4e7938c7a470925454f4d44301167e6 100644 (file)
@@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
-import org.asamk.Signal;
 import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
 import org.asamk.signal.PlainTextWriter;
@@ -21,17 +20,14 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.util.CommandUtil;
 import org.asamk.signal.util.ErrorUtils;
-import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 
-public class UpdateGroupCommand implements DbusCommand, JsonRpcLocalCommand {
+public class UpdateGroupCommand implements JsonRpcLocalCommand {
 
     private final static Logger logger = LoggerFactory.getLogger(UpdateGroupCommand.class);
 
@@ -179,43 +175,6 @@ public class UpdateGroupCommand implements DbusCommand, JsonRpcLocalCommand {
         }
     }
 
-    @Override
-    public void handleCommand(
-            final Namespace ns, final Signal signal, final OutputWriter outputWriter
-    ) throws CommandException {
-        var groupId = CommandUtil.getGroupId(ns.getString("group-id"));
-
-        var groupName = ns.getString("name");
-        if (groupName == null) {
-            groupName = "";
-        }
-
-        List<String> groupMembers = ns.getList("member");
-        if (groupMembers == null) {
-            groupMembers = new ArrayList<>();
-        }
-
-        var groupAvatar = ns.getString("avatar");
-        if (groupAvatar == null) {
-            groupAvatar = "";
-        }
-
-        try {
-            var newGroupId = signal.updateGroup(groupId == null ? new byte[0] : groupId.serialize(),
-                    groupName,
-                    groupMembers,
-                    groupAvatar);
-            if (groupId == null) {
-                outputResult(outputWriter, null, GroupId.unknownVersion(newGroupId));
-            }
-        } catch (Signal.Error.AttachmentInvalid e) {
-            throw new UserErrorException("Failed to add avatar attachment for group\": " + e.getMessage());
-        } catch (DBusExecutionException e) {
-            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
-                    .getSimpleName() + ")", e);
-        }
-    }
-
     private void outputResult(final OutputWriter outputWriter, final Long timestamp, final GroupId groupId) {
         if (outputWriter instanceof PlainTextWriter) {
             final var writer = (PlainTextWriter) outputWriter;
index 84e957cc93c51644d063f2e2b20b512f9f4a8d88..819ce495aeb2d0f9d3d76cea52803283e6725851 100644 (file)
@@ -5,4 +5,8 @@ public final class UserErrorException extends CommandException {
     public UserErrorException(final String message) {
         super(message);
     }
+
+    public UserErrorException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
new file mode 100644 (file)
index 0000000..b9f5ae1
--- /dev/null
@@ -0,0 +1,487 @@
+package org.asamk.signal.dbus;
+
+import org.asamk.Signal;
+import org.asamk.signal.manager.AttachmentInvalidException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.NotMasterDeviceException;
+import org.asamk.signal.manager.StickerPackInvalidException;
+import org.asamk.signal.manager.UntrustedIdentityException;
+import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.api.Identity;
+import org.asamk.signal.manager.api.Message;
+import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.api.SendGroupMessageResults;
+import org.asamk.signal.manager.api.SendMessageResults;
+import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupLinkState;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.GroupPermission;
+import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
+import org.asamk.signal.manager.groups.LastGroupAdminException;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.manager.storage.recipients.Contact;
+import org.asamk.signal.manager.storage.recipients.Profile;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.util.Pair;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * This class implements the Manager interface using the DBus Signal interface, where possible.
+ * It's used for the signal-cli dbus client mode (--dbus, --dbus-system)
+ */
+public class DbusManagerImpl implements Manager {
+
+    private final Signal signal;
+
+    public DbusManagerImpl(final Signal signal) {
+        this.signal = signal;
+    }
+
+    @Override
+    public String getSelfNumber() {
+        return signal.getSelfNumber();
+    }
+
+    @Override
+    public void checkAccountState() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Pair<String, UUID>> areUsersRegistered(final Set<String> numbers) throws IOException {
+        final var numbersList = new ArrayList<>(numbers);
+        final var registered = signal.isRegistered(numbersList);
+
+        final var result = new HashMap<String, Pair<String, UUID>>();
+        for (var i = 0; i < numbersList.size(); i++) {
+            result.put(numbersList.get(i),
+                    new Pair<>(numbersList.get(i), registered.get(i) ? UuidUtil.UNKNOWN_UUID : null));
+        }
+        return result;
+    }
+
+    @Override
+    public void updateAccountAttributes(final String deviceName) throws IOException {
+        if (deviceName != null) {
+            signal.updateDeviceName(deviceName);
+        }
+    }
+
+    @Override
+    public void setProfile(
+            final String givenName,
+            final String familyName,
+            final String about,
+            final String aboutEmoji,
+            final Optional<File> avatar
+    ) throws IOException {
+        signal.updateProfile(emptyIfNull(givenName),
+                emptyIfNull(familyName),
+                emptyIfNull(about),
+                emptyIfNull(aboutEmoji),
+                avatar == null ? "" : avatar.transform(File::getPath).or(""),
+                avatar != null && !avatar.isPresent());
+    }
+
+    @Override
+    public void unregister() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void deleteAccount() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void submitRateLimitRecaptchaChallenge(final String challenge, final String captcha) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Device> getLinkedDevices() throws IOException {
+        return signal.listDevices()
+                .stream()
+                .map(name -> new Device(-1, name, 0, 0, false))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void removeLinkedDevices(final int deviceId) throws IOException {
+        signal.removeDevice(deviceId);
+    }
+
+    @Override
+    public void addDeviceLink(final URI linkUri) throws IOException, InvalidKeyException {
+        signal.addDevice(linkUri.toString());
+    }
+
+    @Override
+    public void setRegistrationLockPin(final Optional<String> pin) throws IOException, UnauthenticatedResponseException {
+        if (pin.isPresent()) {
+            signal.setPin(pin.get());
+        } else {
+            signal.removePin();
+        }
+    }
+
+    @Override
+    public Profile getRecipientProfile(final RecipientIdentifier.Single recipient) throws UnregisteredUserException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Group> getGroups() {
+        final var groupIds = signal.getGroupIds();
+        return groupIds.stream().map(id -> getGroup(GroupId.unknownVersion(id))).collect(Collectors.toList());
+    }
+
+    @Override
+    public SendGroupMessageResults quitGroup(
+            final GroupId groupId, final Set<RecipientIdentifier.Single> groupAdmins
+    ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException {
+        if (groupAdmins.size() > 0) {
+            throw new UnsupportedOperationException();
+        }
+        signal.quitGroup(groupId.serialize());
+        return new SendGroupMessageResults(0, List.of());
+    }
+
+    @Override
+    public void deleteGroup(final GroupId groupId) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Pair<GroupId, SendGroupMessageResults> createGroup(
+            final String name, final Set<RecipientIdentifier.Single> members, final File avatarFile
+    ) throws IOException, AttachmentInvalidException {
+        final var newGroupId = signal.updateGroup(new byte[0],
+                emptyIfNull(name),
+                members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
+                avatarFile == null ? "" : avatarFile.getPath());
+        return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
+    }
+
+    @Override
+    public SendGroupMessageResults updateGroup(
+            final GroupId groupId,
+            final String name,
+            final String description,
+            final Set<RecipientIdentifier.Single> members,
+            final Set<RecipientIdentifier.Single> removeMembers,
+            final Set<RecipientIdentifier.Single> admins,
+            final Set<RecipientIdentifier.Single> removeAdmins,
+            final boolean resetGroupLink,
+            final GroupLinkState groupLinkState,
+            final GroupPermission addMemberPermission,
+            final GroupPermission editDetailsPermission,
+            final File avatarFile,
+            final Integer expirationTimer,
+            final Boolean isAnnouncementGroup
+    ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
+        signal.updateGroup(groupId.serialize(),
+                emptyIfNull(name),
+                members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
+                avatarFile == null ? "" : avatarFile.getPath());
+        return new SendGroupMessageResults(0, List.of());
+    }
+
+    @Override
+    public Pair<GroupId, SendGroupMessageResults> joinGroup(final GroupInviteLinkUrl inviteLinkUrl) throws IOException, GroupLinkNotActiveException {
+        final var newGroupId = signal.joinGroup(inviteLinkUrl.getUrl());
+        return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
+    }
+
+    @Override
+    public void sendTypingMessage(
+            final TypingAction action, final Set<RecipientIdentifier> recipients
+    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        for (final var recipient : recipients) {
+            if (recipient instanceof RecipientIdentifier.Single) {
+                signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(),
+                        action == TypingAction.STOP);
+            } else if (recipient instanceof RecipientIdentifier.Group) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    @Override
+    public void sendReadReceipt(
+            final RecipientIdentifier.Single sender, final List<Long> messageIds
+    ) throws IOException, UntrustedIdentityException {
+        signal.sendReadReceipt(sender.getIdentifier(), messageIds);
+    }
+
+    @Override
+    public void sendViewedReceipt(
+            final RecipientIdentifier.Single sender, final List<Long> messageIds
+    ) throws IOException, UntrustedIdentityException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SendMessageResults sendMessage(
+            final Message message, final Set<RecipientIdentifier> recipients
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return handleMessage(recipients,
+                numbers -> signal.sendMessage(message.getMessageText(), message.getAttachments(), numbers),
+                () -> signal.sendNoteToSelfMessage(message.getMessageText(), message.getAttachments()),
+                groupId -> signal.sendGroupMessage(message.getMessageText(), message.getAttachments(), groupId));
+    }
+
+    @Override
+    public SendMessageResults sendRemoteDeleteMessage(
+            final long targetSentTimestamp, final Set<RecipientIdentifier> recipients
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return handleMessage(recipients,
+                numbers -> signal.sendRemoteDeleteMessage(targetSentTimestamp, numbers),
+                () -> signal.sendRemoteDeleteMessage(targetSentTimestamp, signal.getSelfNumber()),
+                groupId -> signal.sendGroupRemoteDeleteMessage(targetSentTimestamp, groupId));
+    }
+
+    @Override
+    public SendMessageResults sendMessageReaction(
+            final String emoji,
+            final boolean remove,
+            final RecipientIdentifier.Single targetAuthor,
+            final long targetSentTimestamp,
+            final Set<RecipientIdentifier> recipients
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return handleMessage(recipients,
+                numbers -> signal.sendMessageReaction(emoji,
+                        remove,
+                        targetAuthor.getIdentifier(),
+                        targetSentTimestamp,
+                        numbers),
+                () -> signal.sendMessageReaction(emoji,
+                        remove,
+                        targetAuthor.getIdentifier(),
+                        targetSentTimestamp,
+                        signal.getSelfNumber()),
+                groupId -> signal.sendGroupMessageReaction(emoji,
+                        remove,
+                        targetAuthor.getIdentifier(),
+                        targetSentTimestamp,
+                        groupId));
+    }
+
+    @Override
+    public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
+        signal.sendEndSessionMessage(recipients.stream()
+                .map(RecipientIdentifier.Single::getIdentifier)
+                .collect(Collectors.toList()));
+        return new SendMessageResults(0, Map.of());
+    }
+
+    @Override
+    public void setContactName(
+            final RecipientIdentifier.Single recipient, final String name
+    ) throws NotMasterDeviceException, UnregisteredUserException {
+        signal.setContactName(recipient.getIdentifier(), name);
+    }
+
+    @Override
+    public void setContactBlocked(
+            final RecipientIdentifier.Single recipient, final boolean blocked
+    ) throws NotMasterDeviceException, IOException {
+        signal.setContactBlocked(recipient.getIdentifier(), blocked);
+    }
+
+    @Override
+    public void setGroupBlocked(
+            final GroupId groupId, final boolean blocked
+    ) throws GroupNotFoundException, IOException {
+        signal.setGroupBlocked(groupId.serialize(), blocked);
+    }
+
+    @Override
+    public void setExpirationTimer(
+            final RecipientIdentifier.Single recipient, final int messageExpirationTimer
+    ) throws IOException {
+        signal.setExpirationTimer(recipient.getIdentifier(), messageExpirationTimer);
+    }
+
+    @Override
+    public URI uploadStickerPack(final File path) throws IOException, StickerPackInvalidException {
+        try {
+            return new URI(signal.uploadStickerPack(path.getPath()));
+        } catch (URISyntaxException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    @Override
+    public void requestAllSyncData() throws IOException {
+        signal.sendSyncRequest();
+    }
+
+    @Override
+    public void receiveMessages(
+            final long timeout,
+            final TimeUnit unit,
+            final boolean returnOnTimeout,
+            final boolean ignoreAttachments,
+            final ReceiveMessageHandler handler
+    ) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasCaughtUpWithOldMessages() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
+        return signal.isContactBlocked(recipient.getIdentifier());
+    }
+
+    @Override
+    public File getAttachmentFile(final SignalServiceAttachmentRemoteId attachmentId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void sendContacts() throws IOException {
+        signal.sendContacts();
+    }
+
+    @Override
+    public List<Pair<RecipientAddress, Contact>> getContacts() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContactOrProfileName(final RecipientIdentifier.Single recipient) {
+        return signal.getContactName(recipient.getIdentifier());
+    }
+
+    @Override
+    public Group getGroup(final GroupId groupId) {
+        final var id = groupId.serialize();
+        return new Group(groupId,
+                signal.getGroupName(id),
+                null,
+                null,
+                signal.getGroupMembers(id).stream().map(m -> new RecipientAddress(null, m)).collect(Collectors.toSet()),
+                Set.of(),
+                Set.of(),
+                Set.of(),
+                signal.isGroupBlocked(id),
+                0,
+                false,
+                signal.isMember(id));
+    }
+
+    @Override
+    public List<Identity> getIdentities() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Identity> getIdentities(final RecipientIdentifier.Single recipient) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean trustIdentityVerified(final RecipientIdentifier.Single recipient, final byte[] fingerprint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean trustIdentityVerifiedSafetyNumber(
+            final RecipientIdentifier.Single recipient, final String safetyNumber
+    ) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean trustIdentityVerifiedSafetyNumber(
+            final RecipientIdentifier.Single recipient, final byte[] safetyNumber
+    ) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean trustIdentityAllKeys(final RecipientIdentifier.Single recipient) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String computeSafetyNumber(
+            final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
+    ) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SignalServiceAddress resolveSignalServiceAddress(final SignalServiceAddress address) {
+        return address;
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    private SendMessageResults handleMessage(
+            Set<RecipientIdentifier> recipients,
+            Function<List<String>, Long> recipientsHandler,
+            Supplier<Long> noteToSelfHandler,
+            Function<byte[], Long> groupHandler
+    ) {
+        long timestamp = 0;
+        final var singleRecipients = recipients.stream()
+                .filter(r -> r instanceof RecipientIdentifier.Single)
+                .map(RecipientIdentifier.Single.class::cast)
+                .map(RecipientIdentifier.Single::getIdentifier)
+                .collect(Collectors.toList());
+        if (singleRecipients.size() > 0) {
+            timestamp = recipientsHandler.apply(singleRecipients);
+        }
+
+        if (recipients.contains(RecipientIdentifier.NoteToSelf.INSTANCE)) {
+            timestamp = noteToSelfHandler.get();
+        }
+        final var groupRecipients = recipients.stream()
+                .filter(r -> r instanceof RecipientIdentifier.Group)
+                .map(RecipientIdentifier.Group.class::cast)
+                .map(g -> g.groupId)
+                .collect(Collectors.toList());
+        for (final var groupId : groupRecipients) {
+            timestamp = groupHandler.apply(groupId.serialize());
+        }
+        return new SendMessageResults(timestamp, Map.of());
+    }
+
+    private String emptyIfNull(final String string) {
+        return string == null ? "" : string;
+    }
+}
index c8208774c70d1d5193b4bf319a0658e091576325..1a4fdc10240fb9394aa1f5c15582cd5fc9cb74ba 100644 (file)
@@ -67,7 +67,7 @@ public class DbusSignalImpl implements Signal {
     }
 
     @Override
-    public String getNumber() {
+    public String getSelfNumber() {
         return m.getSelfNumber();
     }
 
@@ -96,8 +96,6 @@ public class DbusSignalImpl implements Signal {
     @Override
     public List<String> listDevices() {
         List<Device> devices;
-        List<String> results = new ArrayList<String>();
-
         try {
             devices = m.getLinkedDevices();
         } catch (IOException | Error.Failure e) {
@@ -345,7 +343,8 @@ public class DbusSignalImpl implements Signal {
     // the profile name
     @Override
     public String getContactName(final String number) {
-        return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber()));
+        final var name = m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber()));
+        return name == null ? "" : name;
     }
 
     @Override
@@ -403,7 +402,7 @@ public class DbusSignalImpl implements Signal {
     @Override
     public String getGroupName(final byte[] groupId) {
         var group = m.getGroup(getGroupId(groupId));
-        if (group == null) {
+        if (group == null || group.getTitle() == null) {
             return "";
         } else {
             return group.getTitle();
@@ -423,15 +422,9 @@ public class DbusSignalImpl implements Signal {
     @Override
     public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
         try {
-            if (groupId.length == 0) {
-                groupId = null;
-            }
-            if (name.isEmpty()) {
-                name = null;
-            }
-            if (avatar.isEmpty()) {
-                avatar = null;
-            }
+            groupId = nullIfEmpty(groupId);
+            name = nullIfEmpty(name);
+            avatar = nullIfEmpty(avatar);
             final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber());
             if (groupId == null) {
                 final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar));
@@ -499,17 +492,19 @@ public class DbusSignalImpl implements Signal {
 
     @Override
     public void updateProfile(
-            final String givenName,
-            final String familyName,
-            final String about,
-            final String aboutEmoji,
+            String givenName,
+            String familyName,
+            String about,
+            String aboutEmoji,
             String avatarPath,
             final boolean removeAvatar
     ) {
         try {
-            if (avatarPath.isEmpty()) {
-                avatarPath = null;
-            }
+            givenName = nullIfEmpty(givenName);
+            familyName = nullIfEmpty(familyName);
+            about = nullIfEmpty(about);
+            aboutEmoji = nullIfEmpty(aboutEmoji);
+            avatarPath = nullIfEmpty(avatarPath);
             Optional<File> avatarFile = removeAvatar
                     ? Optional.absent()
                     : avatarPath == null ? null : Optional.of(new File(avatarPath));
@@ -527,17 +522,7 @@ public class DbusSignalImpl implements Signal {
             String avatarPath,
             final boolean removeAvatar
     ) {
-        try {
-            if (avatarPath.isEmpty()) {
-                avatarPath = null;
-            }
-            Optional<File> avatarFile = removeAvatar
-                    ? Optional.absent()
-                    : avatarPath == null ? null : Optional.of(new File(avatarPath));
-            m.setProfile(name, null, about, aboutEmoji, avatarFile);
-        } catch (IOException e) {
-            throw new Error.Failure(e.getMessage());
-        }
+        updateProfile(name, "", about, aboutEmoji, avatarPath, removeAvatar);
     }
 
     @Override
@@ -766,4 +751,12 @@ public class DbusSignalImpl implements Signal {
             throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage());
         }
     }
+
+    private byte[] nullIfEmpty(final byte[] array) {
+        return array.length == 0 ? null : array;
+    }
+
+    private String nullIfEmpty(final String name) {
+        return name.isEmpty() ? null : name;
+    }
 }