]> nmode's Git Repositories - signal-cli/commitdiff
Add CommandException to abstract cli return codes for errors
authorAsamK <asamk@gmx.de>
Mon, 22 Feb 2021 19:43:08 +0000 (20:43 +0100)
committerAsamK <asamk@gmx.de>
Tue, 23 Feb 2021 20:42:42 +0000 (21:42 +0100)
47 files changed:
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java
src/main/java/org/asamk/signal/JsonWriter.java
src/main/java/org/asamk/signal/Main.java
src/main/java/org/asamk/signal/PlainTextWriter.java
src/main/java/org/asamk/signal/PlainTextWriterImpl.java
src/main/java/org/asamk/signal/ReceiveMessageHandler.java
src/main/java/org/asamk/signal/commands/AddDeviceCommand.java
src/main/java/org/asamk/signal/commands/BlockCommand.java
src/main/java/org/asamk/signal/commands/DaemonCommand.java
src/main/java/org/asamk/signal/commands/DbusCommand.java
src/main/java/org/asamk/signal/commands/ExtendedDbusCommand.java
src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java
src/main/java/org/asamk/signal/commands/JoinGroupCommand.java
src/main/java/org/asamk/signal/commands/LinkCommand.java
src/main/java/org/asamk/signal/commands/ListContactsCommand.java
src/main/java/org/asamk/signal/commands/ListDevicesCommand.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java
src/main/java/org/asamk/signal/commands/LocalCommand.java
src/main/java/org/asamk/signal/commands/MultiLocalCommand.java
src/main/java/org/asamk/signal/commands/ProvisioningCommand.java
src/main/java/org/asamk/signal/commands/QuitGroupCommand.java
src/main/java/org/asamk/signal/commands/ReceiveCommand.java
src/main/java/org/asamk/signal/commands/RegisterCommand.java
src/main/java/org/asamk/signal/commands/RegistrationCommand.java
src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java
src/main/java/org/asamk/signal/commands/RemovePinCommand.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/commands/SendContactsCommand.java
src/main/java/org/asamk/signal/commands/SendReactionCommand.java
src/main/java/org/asamk/signal/commands/SetPinCommand.java
src/main/java/org/asamk/signal/commands/TrustCommand.java
src/main/java/org/asamk/signal/commands/UnblockCommand.java
src/main/java/org/asamk/signal/commands/UnregisterCommand.java
src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java
src/main/java/org/asamk/signal/commands/UpdateContactCommand.java
src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java
src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java
src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java
src/main/java/org/asamk/signal/commands/VerifyCommand.java
src/main/java/org/asamk/signal/commands/exceptions/CommandException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/exceptions/IOErrorException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/exceptions/UnexpectedErrorException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/exceptions/UntrustedKeyErrorException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/util/ErrorUtils.java

index 5f0f1cdfb40aa9735d9a434c4652f523dc737cdc..5b2c91c6a2faca1d56478f8bca6313b73c3ea746 100644 (file)
@@ -14,6 +14,9 @@ import org.asamk.signal.commands.LocalCommand;
 import org.asamk.signal.commands.MultiLocalCommand;
 import org.asamk.signal.commands.ProvisioningCommand;
 import org.asamk.signal.commands.RegistrationCommand;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotRegisteredException;
 import org.asamk.signal.manager.ProvisioningManager;
@@ -29,9 +32,8 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
 
 public class App {
 
@@ -79,18 +81,16 @@ public class App {
         this.ns = ns;
     }
 
-    public int init() {
+    public void init() throws CommandException {
         var commandKey = ns.getString("command");
         var command = Commands.getCommand(commandKey);
         if (command == null) {
-            logger.error("Command not implemented!");
-            return 1;
+            throw new UserErrorException("Command not implemented!");
         }
 
         OutputType outputType = ns.get("output");
         if (!command.getSupportedOutputTypes().contains(outputType)) {
-            logger.error("Command doesn't support output type {}", outputType.toString());
-            return 1;
+            throw new UserErrorException("Command doesn't support output type " + outputType.toString());
         }
 
         var username = ns.getString("username");
@@ -99,7 +99,8 @@ public class App {
         final boolean useDbusSystem = ns.getBoolean("dbus_system");
         if (useDbus || useDbusSystem) {
             // If username is null, it will connect to the default object path
-            return initDbusClient(command, username, useDbusSystem);
+            initDbusClient(command, username, useDbusSystem);
+            return;
         }
 
         final File dataPath;
@@ -118,111 +119,102 @@ public class App {
         }
 
         if (!ServiceConfig.isSignalClientAvailable()) {
-            logger.error("Missing required native library dependency: libsignal-client");
-            return 1;
+            throw new UserErrorException("Missing required native library dependency: libsignal-client");
         }
 
         if (command instanceof ProvisioningCommand) {
             if (username != null) {
-                System.err.println("You cannot specify a username (phone number) when linking");
-                return 1;
+                throw new UserErrorException("You cannot specify a username (phone number) when linking");
             }
 
-            return handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceEnvironment);
+            handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceEnvironment);
+            return;
         }
 
         if (username == null) {
             var usernames = Manager.getAllLocalUsernames(dataPath);
             if (usernames.size() == 0) {
-                System.err.println("No local users found, you first need to register or link an account");
-                return 1;
+                throw new UserErrorException("No local users found, you first need to register or link an account");
             }
 
             if (command instanceof MultiLocalCommand) {
-                return handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceEnvironment, usernames);
+                handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceEnvironment, usernames);
+                return;
             }
 
             if (usernames.size() > 1) {
-                System.err.println("Multiple users found, you need to specify a username (phone number) with -u");
-                return 1;
+                throw new UserErrorException(
+                        "Multiple users found, you need to specify a username (phone number) with -u");
             }
 
             username = usernames.get(0);
         } else if (!PhoneNumberFormatter.isValidNumber(username, null)) {
-            System.err.println("Invalid username (phone number), make sure you include the country code.");
-            return 1;
+            throw new UserErrorException("Invalid username (phone number), make sure you include the country code.");
         }
 
         if (command instanceof RegistrationCommand) {
-            return handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceEnvironment);
+            handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceEnvironment);
+            return;
         }
 
         if (!(command instanceof LocalCommand)) {
-            System.err.println("Command only works via dbus");
-            return 1;
+            throw new UserErrorException("Command only works via dbus");
         }
 
-        return handleLocalCommand((LocalCommand) command, username, dataPath, serviceEnvironment);
+        handleLocalCommand((LocalCommand) command, username, dataPath, serviceEnvironment);
     }
 
-    private int handleProvisioningCommand(
+    private void handleProvisioningCommand(
             final ProvisioningCommand command, final File dataPath, final ServiceEnvironment serviceEnvironment
-    ) {
+    ) throws CommandException {
         var pm = ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
-        return command.handleCommand(ns, pm);
+        command.handleCommand(ns, pm);
     }
 
-    private int handleRegistrationCommand(
+    private void handleRegistrationCommand(
             final RegistrationCommand command,
             final String username,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment
-    ) {
+    ) throws CommandException {
         final RegistrationManager manager;
         try {
             manager = RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
         } catch (Throwable e) {
-            logger.error("Error loading or creating state file: {}", e.getMessage());
-            return 2;
+            throw new UnexpectedErrorException("Error loading or creating state file: " + e.getMessage());
         }
         try (var m = manager) {
-            return command.handleCommand(ns, m);
+            command.handleCommand(ns, m);
         } catch (IOException e) {
-            logger.error("Cleanup failed", e);
-            return 2;
+            logger.warn("Cleanup failed", e);
         }
     }
 
-    private int handleLocalCommand(
+    private void handleLocalCommand(
             final LocalCommand command,
             final String username,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment
-    ) {
+    ) throws CommandException {
         try (var m = loadManager(username, dataPath, serviceEnvironment)) {
-            if (m == null) {
-                return 2;
-            }
-
-            return command.handleCommand(ns, m);
+            command.handleCommand(ns, m);
         } catch (IOException e) {
-            logger.error("Cleanup failed", e);
-            return 2;
+            logger.warn("Cleanup failed", e);
         }
     }
 
-    private int handleMultiLocalCommand(
+    private void handleMultiLocalCommand(
             final MultiLocalCommand command,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment,
             final List<String> usernames
-    ) {
-        final var managers = usernames.stream()
-                .map(u -> loadManager(u, dataPath, serviceEnvironment))
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
+    ) throws CommandException {
+        final var managers = new ArrayList<Manager>();
+        for (String u : usernames) {
+            managers.add(loadManager(u, dataPath, serviceEnvironment));
+        }
 
-        var result = command.handleCommand(ns, managers);
+        command.handleCommand(ns, managers);
 
         for (var m : managers) {
             try {
@@ -231,34 +223,32 @@ public class App {
                 logger.warn("Cleanup failed", e);
             }
         }
-        return result;
     }
 
     private Manager loadManager(
             final String username, final File dataPath, final ServiceEnvironment serviceEnvironment
-    ) {
+    ) throws CommandException {
         Manager manager;
         try {
             manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
         } catch (NotRegisteredException e) {
-            logger.error("User " + username + " is not registered.");
-            return null;
+            throw new UserErrorException("User " + username + " is not registered.");
         } catch (Throwable e) {
-            logger.error("Error loading state file for user " + username + ": {}", e.getMessage());
-            return null;
+            throw new UnexpectedErrorException("Error loading state file for user " + username + ": " + e.getMessage());
         }
 
         try {
             manager.checkAccountState();
         } catch (IOException e) {
-            logger.error("Error while checking account " + username + ": {}", e.getMessage());
-            return null;
+            throw new UnexpectedErrorException("Error while checking account " + username + ": " + e.getMessage());
         }
 
         return manager;
     }
 
-    private int initDbusClient(final Command command, final String username, final boolean systemBus) {
+    private void initDbusClient(
+            final Command command, final String username, final boolean systemBus
+    ) throws CommandException {
         try {
             DBusConnection.DBusBusType busType;
             if (systemBus) {
@@ -271,22 +261,21 @@ public class App {
                         DbusConfig.getObjectPath(username),
                         Signal.class);
 
-                return handleCommand(command, ts, dBusConn);
+                handleCommand(command, ts, dBusConn);
             }
         } catch (DBusException | IOException e) {
             logger.error("Dbus client failed", e);
-            return 2;
+            throw new UnexpectedErrorException("Dbus client failed");
         }
     }
 
-    private int handleCommand(Command command, Signal ts, DBusConnection dBusConn) {
+    private void handleCommand(Command command, Signal ts, DBusConnection dBusConn) throws CommandException {
         if (command instanceof ExtendedDbusCommand) {
-            return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
+            ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
         } else if (command instanceof DbusCommand) {
-            return ((DbusCommand) command).handleCommand(ns, ts);
+            ((DbusCommand) command).handleCommand(ns, ts);
         } else {
-            System.err.println("Command is not yet implemented via dbus");
-            return 1;
+            throw new UserErrorException("Command is not yet implemented via dbus");
         }
     }
 
index 38657f5e618f71b40e08f1717926c30898a67aae..c8e1c24fe9bf5f5c38ecd660558590915efab45e 100644 (file)
@@ -8,7 +8,6 @@ import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 
-import java.io.IOException;
 import java.util.HashMap;
 
 public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler {
@@ -32,10 +31,7 @@ public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler
         if (envelope != null) {
             object.put("envelope", new JsonMessageEnvelope(envelope, content, m));
         }
-        try {
-            jsonWriter.write(object);
-        } catch (IOException e) {
-            logger.error("Failed to write json object: {}", e.getMessage());
-        }
+
+        jsonWriter.write(object);
     }
 }
index 8aed44875cdc2d0f76498da7012849989aa3ae86..e7549adf8bcb4d1c13231adb7a4a201c7e317bf6 100644 (file)
@@ -26,14 +26,18 @@ public class JsonWriter {
         objectMapper.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
     }
 
-    public void write(final Object object) throws IOException {
+    public void write(final Object object) {
         try {
-            objectMapper.writeValue(writer, object);
-        } catch (JsonProcessingException e) {
-            // Some issue with json serialization, probably caused by a bug
+            try {
+                objectMapper.writeValue(writer, object);
+            } catch (JsonProcessingException e) {
+                // Some issue with json serialization, probably caused by a bug
+                throw new AssertionError(e);
+            }
+            writer.write(System.lineSeparator());
+            writer.flush();
+        } catch (IOException e) {
             throw new AssertionError(e);
         }
-        writer.write(System.lineSeparator());
-        writer.flush();
     }
 }
index 88ae06caaa621d4dfdbc940984c128d1c86770ae..775b5223e94ad1a7788e4bf4aa7d4fcaae4d2621 100644 (file)
@@ -21,6 +21,11 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
 import net.sourceforge.argparse4j.inf.Namespace;
 
+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.UntrustedKeyErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.LibSignalLogger;
 import org.asamk.signal.util.SecurityProvider;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -39,8 +44,14 @@ public class Main {
 
         var ns = parser.parseArgsOrFail(args);
 
-        var res = new App(ns).init();
-        System.exit(res);
+        int status = 0;
+        try {
+            new App(ns).init();
+        } catch (CommandException e) {
+            System.err.println(e.getMessage());
+            status = getStatusForError(e);
+        }
+        System.exit(status);
     }
 
     private static void installSecurityProviderWorkaround() {
@@ -78,4 +89,18 @@ public class Main {
             System.setProperty("org.slf4j.simpleLogger.showDateTime", "false");
         }
     }
+
+    private static int getStatusForError(final CommandException e) {
+        if (e instanceof UserErrorException) {
+            return 1;
+        } else if (e instanceof UnexpectedErrorException) {
+            return 2;
+        } else if (e instanceof IOErrorException) {
+            return 3;
+        } else if (e instanceof UntrustedKeyErrorException) {
+            return 4;
+        } else {
+            return 2;
+        }
+    }
 }
index 91a4dbba42573db47dba26b4ca0a9f60e1e51333..de738de966bbe1209d94d9b81216a0b32e36a241 100644 (file)
@@ -1,23 +1,21 @@
 package org.asamk.signal;
 
-import java.io.IOException;
-
 public interface PlainTextWriter {
 
-    void println(String format, Object... args) throws IOException;
+    void println(String format, Object... args);
 
     PlainTextWriter indentedWriter();
 
-    default void println() throws IOException {
+    default void println() {
         println("");
     }
 
-    default void indent(final WriterConsumer subWriter) throws IOException {
+    default void indent(final WriterConsumer subWriter) {
         subWriter.consume(indentedWriter());
     }
 
     interface WriterConsumer {
 
-        void consume(PlainTextWriter writer) throws IOException;
+        void consume(PlainTextWriter writer);
     }
 }
index d6536481551d0506a731b3add0c6f749d84cf7af..bb18b7f38f7266b231f7f2fd029dcf58f9ea4cea 100644 (file)
@@ -19,12 +19,16 @@ public final class PlainTextWriterImpl implements PlainTextWriter {
     }
 
     @Override
-    public void println(String format, Object... args) throws IOException {
+    public void println(String format, Object... args) {
         final var message = MessageFormatter.arrayFormat(format, args).getMessage();
 
-        writer.write(message);
-        writer.write(System.lineSeparator());
-        writer.flush();
+        try {
+            writer.write(message);
+            writer.write(System.lineSeparator());
+            writer.flush();
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
     }
 
     @Override
@@ -51,8 +55,12 @@ public final class PlainTextWriterImpl implements PlainTextWriter {
         }
 
         @Override
-        public void println(final String format, final Object... args) throws IOException {
-            writer.write(spaces);
+        public void println(final String format, final Object... args) {
+            try {
+                writer.write(spaces);
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
             plainTextWriter.println(format, args);
         }
 
index 8c75fcbacc985229a304480dcaab584b84e2d17a..6df283f367679077c32e0618a178213d1ad64840 100644 (file)
@@ -630,7 +630,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         writer.println("- {}: {} (length: {})", formatContact(address), mention.getStart(), mention.getLength());
     }
 
-    private void printAttachment(PlainTextWriter writer, SignalServiceAttachment attachment) throws IOException {
+    private void printAttachment(PlainTextWriter writer, SignalServiceAttachment attachment) {
         writer.println("Content-Type: {}", attachment.getContentType());
         writer.println("Type: {}", attachment.isPointer() ? "Pointer" : attachment.isStream() ? "Stream" : "<unknown>");
         if (attachment.isPointer()) {
index c5d18ab196b29b5c92de54d1a539211277c2e4f2..cf993e6de4b89d7064d790df40210043374200f1 100644 (file)
@@ -3,7 +3,13 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+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.manager.Manager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.libsignal.InvalidKeyException;
 
 import java.io.IOException;
@@ -14,6 +20,8 @@ import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
 
 public class AddDeviceCommand implements LocalCommand {
 
+    private final static Logger logger = LoggerFactory.getLogger(AddDeviceCommand.class);
+
     @Override
     public void attachToSubparser(final Subparser subparser) {
         subparser.addArgument("--uri")
@@ -22,19 +30,20 @@ public class AddDeviceCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             m.addDeviceLink(new URI(ns.getString("uri")));
-            return 0;
         } catch (IOException e) {
-            e.printStackTrace();
-            return 3;
-        } catch (InvalidKeyException | URISyntaxException e) {
-            e.printStackTrace();
-            return 2;
+            logger.error("Add device link failed", e);
+            throw new IOErrorException("Add device link failed");
+        } catch (URISyntaxException e) {
+            throw new UserErrorException("Device link uri has invalid format: {}" + e.getMessage());
+        } catch (InvalidKeyException e) {
+            logger.error("Add device link failed", e);
+            throw new UnexpectedErrorException("Add device link failed.");
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         }
     }
 }
index 9dff25c6d979ef7477e87260bc26b189d08d5ff6..98fce6676d018e504928ce817e572a390d6f98d5 100644 (file)
@@ -7,10 +7,14 @@ import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.util.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
 public class BlockCommand implements LocalCommand {
 
+    private final static Logger logger = LoggerFactory.getLogger(BlockCommand.class);
+
     @Override
     public void attachToSubparser(final Subparser subparser) {
         subparser.addArgument("contact").help("Contact number").nargs("*");
@@ -19,12 +23,12 @@ public class BlockCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) {
         for (var contact_number : ns.<String>getList("contact")) {
             try {
                 m.setContactBlocked(contact_number, true);
             } catch (InvalidNumberException e) {
-                System.err.println(e.getMessage());
+                logger.warn("Invalid number {}: {}", contact_number, e.getMessage());
             }
         }
 
@@ -34,11 +38,9 @@ public class BlockCommand implements LocalCommand {
                     var groupId = Util.decodeGroupId(groupIdString);
                     m.setGroupBlocked(groupId, true);
                 } catch (GroupIdFormatException | GroupNotFoundException e) {
-                    System.err.println(e.getMessage());
+                    logger.warn("Invalid group id {}: {}", groupIdString, e.getMessage());
                 }
             }
         }
-
-        return 0;
     }
 }
index 6ee4f316cef9fd0b2b6efbb3a7d3fa1130f1b2bd..8d26e452c984658c3dbedbc383e9b1a69547f7ec 100644 (file)
@@ -8,6 +8,8 @@ import org.asamk.signal.DbusConfig;
 import org.asamk.signal.DbusReceiveMessageHandler;
 import org.asamk.signal.JsonDbusReceiveMessageHandler;
 import org.asamk.signal.OutputType;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
 import org.asamk.signal.dbus.DbusSignalImpl;
 import org.asamk.signal.manager.Manager;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
@@ -44,7 +46,7 @@ public class DaemonCommand implements MultiLocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
 
         // TODO delete later when "json" variable is removed
@@ -71,15 +73,14 @@ public class DaemonCommand implements MultiLocalCommand {
                 t.join();
             } catch (InterruptedException ignored) {
             }
-            return 0;
         } catch (DBusException | IOException e) {
             logger.error("Dbus command failed", e);
-            return 2;
+            throw new UnexpectedErrorException("Dbus command failed");
         }
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final List<Manager> managers) {
+    public void handleCommand(final Namespace ns, final List<Manager> managers) throws CommandException {
         var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
 
         // TODO delete later when "json" variable is removed
@@ -112,10 +113,9 @@ public class DaemonCommand implements MultiLocalCommand {
                 } catch (InterruptedException ignored) {
                 }
             }
-            return 0;
         } catch (DBusException | IOException e) {
             logger.error("Dbus command failed", e);
-            return 2;
+            throw new UnexpectedErrorException("Dbus command failed");
         }
     }
 
index 1b3a0268652675349536deea247cb5b6ee863c1a..e4c78c840d9a32481521ea5503075f54db6339e0 100644 (file)
@@ -3,14 +3,15 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 
 import org.asamk.Signal;
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.dbus.DbusSignalImpl;
 import org.asamk.signal.manager.Manager;
 
 public interface DbusCommand extends LocalCommand {
 
-    int handleCommand(Namespace ns, Signal signal);
+    void handleCommand(Namespace ns, Signal signal) throws CommandException;
 
-    default int handleCommand(final Namespace ns, final Manager m) {
-        return handleCommand(ns, new DbusSignalImpl(m));
+    default void handleCommand(final Namespace ns, final Manager m) throws CommandException {
+        handleCommand(ns, new DbusSignalImpl(m));
     }
 }
index f9cd9de85c1817196f3145f652071b10a5b9b0af..1d454f4de9ad74be3cd61234d1148711a649e697 100644 (file)
@@ -3,9 +3,10 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 
 import org.asamk.Signal;
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
 
 public interface ExtendedDbusCommand extends Command {
 
-    int handleCommand(Namespace ns, Signal signal, DBusConnection dbusconnection);
+    void handleCommand(Namespace ns, Signal signal, DBusConnection dbusconnection) throws CommandException;
 }
index 07de8321759d601b30ef7c7370a28b26f01b0b84..69140b239d3d90b0bbcb52d0efb4b747dce1bbed 100644 (file)
@@ -7,6 +7,8 @@ import net.sourceforge.argparse4j.inf.Subparser;
 import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputType;
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,7 +38,7 @@ public class GetUserStatusCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         // Setup the json object mapper
         var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
 
@@ -50,8 +52,8 @@ public class GetUserStatusCommand implements LocalCommand {
         try {
             registered = m.areUsersRegistered(new HashSet<>(ns.getList("number")));
         } catch (IOException e) {
-            System.err.println("Unable to check if users are registered");
-            return 3;
+            logger.debug("Failed to check registered users", e);
+            throw new IOErrorException("Unable to check if users are registered");
         }
 
         // Output
@@ -63,26 +65,14 @@ public class GetUserStatusCommand implements LocalCommand {
                     .map(entry -> new JsonUserStatus(entry.getKey(), entry.getValue()))
                     .collect(Collectors.toList());
 
-            try {
-                jsonWriter.write(jsonUserStatuses);
-            } catch (IOException e) {
-                logger.error("Failed to write json object: {}", e.getMessage());
-                return 3;
-            }
+            jsonWriter.write(jsonUserStatuses);
         } else {
             final var writer = new PlainTextWriterImpl(System.out);
 
-            try {
-                for (var entry : registered.entrySet()) {
-                    writer.println("{}: {}", entry.getKey(), entry.getValue());
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-                return 3;
+            for (var entry : registered.entrySet()) {
+                writer.println("{}: {}", entry.getKey(), entry.getValue());
             }
         }
-
-        return 0;
     }
 
     private static final class JsonUserStatus {
index 7f1cb0b0460fcbe315c544072bf108d5de8f0364..e59ecec644e6262f589bf68ce97c9177da5cfcd8 100644 (file)
@@ -4,46 +4,42 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+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.manager.Manager;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
 import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
 
 import java.io.IOException;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-import static org.asamk.signal.util.ErrorUtils.handleIOException;
 import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
 
 public class JoinGroupCommand implements LocalCommand {
 
-    private final static Logger logger = LoggerFactory.getLogger(JoinGroupCommand.class);
-
     @Override
     public void attachToSubparser(final Subparser subparser) {
         subparser.addArgument("--uri").required(true).help("Specify the uri with the group invitation link.");
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         final GroupInviteLinkUrl linkUrl;
         var uri = ns.getString("uri");
         try {
             linkUrl = GroupInviteLinkUrl.fromUri(uri);
         } catch (GroupInviteLinkUrl.InvalidGroupLinkException e) {
-            System.err.println("Group link is invalid: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Group link is invalid: " + e.getMessage());
         } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
-            System.err.println("Group link was created with an incompatible version: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Group link was created with an incompatible version: " + e.getMessage());
         }
 
         if (linkUrl == null) {
-            System.err.println("Link is not a signal group invitation link");
-            return 1;
+            throw new UserErrorException("Link is not a signal group invitation link");
         }
 
         try {
@@ -56,23 +52,18 @@ public class JoinGroupCommand implements LocalCommand {
             } else {
                 writer.println("Joined group \"{}\"", newGroupId.toBase64());
             }
-            return handleTimestampAndSendMessageResults(writer, 0, results.second());
+            handleTimestampAndSendMessageResults(writer, 0, results.second());
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (GroupPatchNotAcceptedException e) {
-            System.err.println("Failed to join group, maybe already a member");
-            return 1;
+            throw new UserErrorException("Failed to join group, maybe already a member");
         } catch (IOException e) {
-            e.printStackTrace();
-            handleIOException(e);
-            return 3;
+            throw new IOErrorException("Failed to send message: " + e.getMessage());
         } catch (DBusExecutionException e) {
-            System.err.println("Failed to send message: " + e.getMessage());
-            return 2;
+            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
         } catch (GroupLinkNotActiveException e) {
-            System.err.println("Group link is not valid: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Group link is not valid: " + e.getMessage());
         }
     }
 }
index fe580916a1e9691c74fb9f618fb33368f5454712..34775773069b3b79069830374969c4c7aeaacce5 100644 (file)
@@ -4,6 +4,10 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+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.manager.ProvisioningManager;
 import org.asamk.signal.manager.UserAlreadyExists;
 import org.slf4j.Logger;
@@ -25,7 +29,7 @@ public class LinkCommand implements ProvisioningCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final ProvisioningManager m) {
+    public void handleCommand(final Namespace ns, final ProvisioningManager m) throws CommandException {
         final var writer = new PlainTextWriterImpl(System.out);
 
         var deviceName = ns.getString("name");
@@ -37,25 +41,21 @@ public class LinkCommand implements ProvisioningCommand {
             var username = m.finishDeviceLink(deviceName);
             writer.println("Associated with: {}", username);
         } catch (TimeoutException e) {
-            System.err.println("Link request timed out, please try again.");
-            return 3;
+            throw new UserErrorException("Link request timed out, please try again.");
         } catch (IOException e) {
-            System.err.println("Link request error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Link request error: " + e.getMessage());
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (InvalidKeyException e) {
-            e.printStackTrace();
-            return 2;
+            logger.debug("Finish device link failed", e);
+            throw new UnexpectedErrorException("Invalid key: " + e.getMessage());
         } catch (UserAlreadyExists e) {
-            System.err.println("The user "
+            throw new UserErrorException("The user "
                     + e.getUsername()
                     + " already exists\nDelete \""
                     + e.getFileName()
                     + "\" before trying again.");
-            return 1;
         }
-        return 0;
     }
 }
index 6609ec6027f6118f9553f37f7a79376195034f41..4b27e17d3558d0b41aeaac779457043c9829e000 100644 (file)
@@ -6,8 +6,6 @@ import net.sourceforge.argparse4j.inf.Subparser;
 import org.asamk.signal.PlainTextWriterImpl;
 import org.asamk.signal.manager.Manager;
 
-import java.io.IOException;
-
 public class ListContactsCommand implements LocalCommand {
 
     @Override
@@ -15,18 +13,12 @@ public class ListContactsCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) {
         final var writer = new PlainTextWriterImpl(System.out);
 
         var contacts = m.getContacts();
-        try {
-            for (var c : contacts) {
-                writer.println("Number: {} Name: {} Blocked: {}", c.number, c.name, c.blocked);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-            return 3;
+        for (var c : contacts) {
+            writer.println("Number: {} Name: {} Blocked: {}", c.number, c.name, c.blocked);
         }
-        return 0;
     }
 }
index f2037239c4d00c6d6e2dc0880721ac3b88a18303..7165d07c4666a3ce933813bbc2ed5d0d9320bc74 100644 (file)
@@ -4,34 +4,44 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.util.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
 
 import java.io.IOException;
+import java.util.List;
 
 public class ListDevicesCommand implements LocalCommand {
 
+    private final static Logger logger = LoggerFactory.getLogger(ListDevicesCommand.class);
+
     @Override
     public void attachToSubparser(final Subparser subparser) {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         final var writer = new PlainTextWriterImpl(System.out);
+
+        List<DeviceInfo> devices;
         try {
-            var devices = m.getLinkedDevices();
-            for (var d : devices) {
-                writer.println("- Device {}{}:", d.getId(), (d.getId() == m.getDeviceId() ? " (this device)" : ""));
-                writer.indent(w -> {
-                    w.println("Name: {}", d.getName());
-                    w.println("Created: {}", DateUtils.formatTimestamp(d.getCreated()));
-                    w.println("Last seen: {}", DateUtils.formatTimestamp(d.getLastSeen()));
-                });
-            }
-            return 0;
+            devices = m.getLinkedDevices();
         } catch (IOException e) {
-            e.printStackTrace();
-            return 3;
+            logger.debug("Failed to get linked devices", e);
+            throw new IOErrorException("Failed to get linked devices: " + e.getMessage());
+        }
+
+        for (var d : devices) {
+            writer.println("- Device {}{}:", d.getId(), (d.getId() == m.getDeviceId() ? " (this device)" : ""));
+            writer.indent(w -> {
+                w.println("Name: {}", d.getName());
+                w.println("Created: {}", DateUtils.formatTimestamp(d.getCreated()));
+                w.println("Last seen: {}", DateUtils.formatTimestamp(d.getLastSeen()));
+            });
         }
     }
 }
index 477d56a13824d69c32388c7c522240e3e632d872..a547cc15588bf9f54d9ead385419ddbde730ecec 100644 (file)
@@ -8,13 +8,13 @@ import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputType;
 import org.asamk.signal.PlainTextWriter;
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.storage.groups.GroupInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -32,7 +32,7 @@ public class ListGroupsCommand implements LocalCommand {
 
     private static void printGroupPlainText(
             PlainTextWriter writer, Manager m, GroupInfo group, boolean detailed
-    ) throws IOException {
+    ) {
         if (detailed) {
             final var groupInviteLink = group.getGroupInviteLink();
 
@@ -70,7 +70,7 @@ public class ListGroupsCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         if (ns.get("output") == OutputType.JSON) {
             final var jsonWriter = new JsonWriter(System.out);
 
@@ -88,28 +88,14 @@ public class ListGroupsCommand implements LocalCommand {
                         groupInviteLink == null ? null : groupInviteLink.getUrl()));
             }
 
-            try {
-                jsonWriter.write(jsonGroups);
-            } catch (IOException e) {
-                logger.error("Failed to write json object: {}", e.getMessage());
-                return 3;
-            }
-
-            return 0;
+            jsonWriter.write(jsonGroups);
         } else {
             final var writer = new PlainTextWriterImpl(System.out);
             boolean detailed = ns.getBoolean("detailed");
-            try {
-                for (var group : m.getGroups()) {
-                    printGroupPlainText(writer, m, group, detailed);
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-                return 3;
+            for (var group : m.getGroups()) {
+                printGroupPlainText(writer, m, group, detailed);
             }
         }
-
-        return 0;
     }
 
     private static final class JsonGroup {
index 1f1b6258413401ececc34b28371df5d70019b9ac..dc2d92fb58db3ffa4ef34b5dda595b45f2f99d39 100644 (file)
@@ -5,6 +5,8 @@ import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriter;
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.storage.protocol.IdentityInfo;
 import org.asamk.signal.util.Hex;
@@ -13,7 +15,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
-import java.io.IOException;
+import java.util.List;
 
 public class ListIdentitiesCommand implements LocalCommand {
 
@@ -21,16 +23,12 @@ public class ListIdentitiesCommand implements LocalCommand {
 
     private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) {
         var digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey()));
-        try {
-            writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
-                    theirId.getAddress().getNumber().orNull(),
-                    theirId.getTrustLevel(),
-                    theirId.getDateAdded(),
-                    Hex.toString(theirId.getFingerprint()),
-                    digits);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+        writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
+                theirId.getAddress().getNumber().orNull(),
+                theirId.getTrustLevel(),
+                theirId.getDateAdded(),
+                Hex.toString(theirId.getFingerprint()),
+                digits);
     }
 
     @Override
@@ -39,24 +37,27 @@ public class ListIdentitiesCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         final var writer = new PlainTextWriterImpl(System.out);
 
-        if (ns.get("number") == null) {
+        var number = ns.getString("number");
+
+        if (number == null) {
             for (var identity : m.getIdentities()) {
                 printIdentityFingerprint(writer, m, identity);
             }
-        } else {
-            var number = ns.getString("number");
-            try {
-                var identities = m.getIdentities(number);
-                for (var id : identities) {
-                    printIdentityFingerprint(writer, m, id);
-                }
-            } catch (InvalidNumberException e) {
-                System.err.println("Invalid number: " + e.getMessage());
-            }
+            return;
+        }
+
+        List<IdentityInfo> identities;
+        try {
+            identities = m.getIdentities(number);
+        } catch (InvalidNumberException e) {
+            throw new UserErrorException("Invalid number: " + e.getMessage());
+        }
+
+        for (var id : identities) {
+            printIdentityFingerprint(writer, m, id);
         }
-        return 0;
     }
 }
index 7bcb1a4b21c952aaa8259a245b74b645aea6e7bb..a7c64dc131af7da3f96134e52bc98942a281b737 100644 (file)
@@ -2,9 +2,10 @@ package org.asamk.signal.commands;
 
 import net.sourceforge.argparse4j.inf.Namespace;
 
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.Manager;
 
 public interface LocalCommand extends Command {
 
-    int handleCommand(Namespace ns, Manager m);
+    void handleCommand(Namespace ns, Manager m) throws CommandException;
 }
index e8ee8e1db5853dd3c16eac068673005471b3e33d..2a8457bd00978ec2182b13585ddb5014cab6248a 100644 (file)
@@ -2,16 +2,17 @@ package org.asamk.signal.commands;
 
 import net.sourceforge.argparse4j.inf.Namespace;
 
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.Manager;
 
 import java.util.List;
 
 public interface MultiLocalCommand extends LocalCommand {
 
-    int handleCommand(Namespace ns, List<Manager> m);
+    void handleCommand(Namespace ns, List<Manager> m) throws CommandException;
 
     @Override
-    default int handleCommand(final Namespace ns, final Manager m) {
-        return handleCommand(ns, List.of(m));
+    default void handleCommand(final Namespace ns, final Manager m) throws CommandException {
+        handleCommand(ns, List.of(m));
     }
 }
index 12a612ffaf66ebf823d0af4b08bad2659ff39490..354e4af38eadb693ae36d848dc3c39d728519707 100644 (file)
@@ -2,9 +2,10 @@ package org.asamk.signal.commands;
 
 import net.sourceforge.argparse4j.inf.Namespace;
 
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.ProvisioningManager;
 
 public interface ProvisioningCommand extends Command {
 
-    int handleCommand(Namespace ns, ProvisioningManager m);
+    void handleCommand(Namespace ns, ProvisioningManager m) throws CommandException;
 }
index cdbccf19a7fc4c962e39787eefd7708110e2d2a3..d8a865854e3110b3cd3f3d39d3a2ca8741a55dd8 100644 (file)
@@ -4,7 +4,11 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
@@ -13,10 +17,6 @@ import org.asamk.signal.util.Util;
 import java.io.IOException;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
-import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
-import static org.asamk.signal.util.ErrorUtils.handleIOException;
-import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
 import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
 
 public class QuitGroupCommand implements LocalCommand {
@@ -27,28 +27,28 @@ public class QuitGroupCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
+        final var writer = new PlainTextWriterImpl(System.out);
+
+        final GroupId groupId;
         try {
-            final var writer = new PlainTextWriterImpl(System.out);
+            groupId = Util.decodeGroupId(ns.getString("group"));
+        } catch (GroupIdFormatException e) {
+            throw new UserErrorException("Invalid group id:" + e.getMessage());
+        }
 
-            final var groupId = Util.decodeGroupId(ns.getString("group"));
+        try {
             final var results = m.sendQuitGroupMessage(groupId);
-            return handleTimestampAndSendMessageResults(writer, results.first(), results.second());
+            handleTimestampAndSendMessageResults(writer, results.first(), results.second());
         } catch (IOException e) {
-            handleIOException(e);
-            return 3;
+            throw new IOErrorException("Failed to send message: " + e.getMessage());
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (GroupNotFoundException e) {
-            handleGroupNotFoundException(e);
-            return 1;
+            throw new UserErrorException("Failed to send to group: " + e.getMessage());
         } catch (NotAGroupMemberException e) {
-            handleNotAGroupMemberException(e);
-            return 1;
-        } catch (GroupIdFormatException e) {
-            handleGroupIdFormatException(e);
-            return 1;
+            throw new UserErrorException("Failed to send to group: " + e.getMessage());
         }
     }
 }
index 834bc5ebce833fdbb58807e2ce66394b9719a989..8612a71b631d27a6d1632ef07b6cde58857e736e 100644 (file)
@@ -10,6 +10,9 @@ import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputType;
 import org.asamk.signal.PlainTextWriterImpl;
 import org.asamk.signal.ReceiveMessageHandler;
+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.json.JsonMessageEnvelope;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.util.DateUtils;
@@ -48,7 +51,9 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
         return Set.of(OutputType.PLAIN_TEXT, OutputType.JSON);
     }
 
-    public int handleCommand(final Namespace ns, final Signal signal, DBusConnection dbusconnection) {
+    public void handleCommand(
+            final Namespace ns, final Signal signal, DBusConnection dbusconnection
+    ) throws CommandException {
         var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
 
         // TODO delete later when "json" variable is removed
@@ -63,106 +68,81 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
                 dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> {
                     var envelope = new JsonMessageEnvelope(messageReceived);
                     final var object = Map.of("envelope", envelope);
-                    try {
-                        jsonWriter.write(object);
-                    } catch (IOException e) {
-                        logger.error("Failed to write json object: {}", e.getMessage());
-                    }
+                    jsonWriter.write(object);
                 });
 
                 dbusconnection.addSigHandler(Signal.ReceiptReceived.class, receiptReceived -> {
                     var envelope = new JsonMessageEnvelope(receiptReceived);
                     final var object = Map.of("envelope", envelope);
-                    try {
-                        jsonWriter.write(object);
-                    } catch (IOException e) {
-                        logger.error("Failed to write json object: {}", e.getMessage());
-                    }
+                    jsonWriter.write(object);
                 });
 
                 dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> {
                     var envelope = new JsonMessageEnvelope(syncReceived);
                     final var object = Map.of("envelope", envelope);
-                    try {
-                        jsonWriter.write(object);
-                    } catch (IOException e) {
-                        logger.error("Failed to write json object: {}", e.getMessage());
-                    }
+                    jsonWriter.write(object);
                 });
             } else {
                 final var writer = new PlainTextWriterImpl(System.out);
 
                 dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> {
-                    try {
-                        writer.println("Envelope from: {}", messageReceived.getSender());
-                        writer.println("Timestamp: {}", DateUtils.formatTimestamp(messageReceived.getTimestamp()));
-                        writer.println("Body: {}", messageReceived.getMessage());
-                        if (messageReceived.getGroupId().length > 0) {
-                            writer.println("Group info:");
-                            writer.indentedWriter()
-                                    .println("Id: {}",
-                                            Base64.getEncoder().encodeToString(messageReceived.getGroupId()));
-                        }
-                        if (messageReceived.getAttachments().size() > 0) {
-                            writer.println("Attachments:");
-                            for (var attachment : messageReceived.getAttachments()) {
-                                writer.println("- Stored plaintext in: {}", attachment);
-                            }
+                    writer.println("Envelope from: {}", messageReceived.getSender());
+                    writer.println("Timestamp: {}", DateUtils.formatTimestamp(messageReceived.getTimestamp()));
+                    writer.println("Body: {}", messageReceived.getMessage());
+                    if (messageReceived.getGroupId().length > 0) {
+                        writer.println("Group info:");
+                        writer.indentedWriter()
+                                .println("Id: {}", Base64.getEncoder().encodeToString(messageReceived.getGroupId()));
+                    }
+                    if (messageReceived.getAttachments().size() > 0) {
+                        writer.println("Attachments:");
+                        for (var attachment : messageReceived.getAttachments()) {
+                            writer.println("- Stored plaintext in: {}", attachment);
                         }
-                        writer.println();
-                    } catch (IOException e) {
-                        e.printStackTrace();
                     }
+                    writer.println();
                 });
 
                 dbusconnection.addSigHandler(Signal.ReceiptReceived.class, receiptReceived -> {
-                    try {
-                        writer.println("Receipt from: {}", receiptReceived.getSender());
-                        writer.println("Timestamp: {}", DateUtils.formatTimestamp(receiptReceived.getTimestamp()));
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
+                    writer.println("Receipt from: {}", receiptReceived.getSender());
+                    writer.println("Timestamp: {}", DateUtils.formatTimestamp(receiptReceived.getTimestamp()));
                 });
 
                 dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> {
-                    try {
-                        writer.println("Sync Envelope from: {} to: {}",
-                                syncReceived.getSource(),
-                                syncReceived.getDestination());
-                        writer.println("Timestamp: {}", DateUtils.formatTimestamp(syncReceived.getTimestamp()));
-                        writer.println("Body: {}", syncReceived.getMessage());
-                        if (syncReceived.getGroupId().length > 0) {
-                            writer.println("Group info:");
-                            writer.indentedWriter()
-                                    .println("Id: {}", Base64.getEncoder().encodeToString(syncReceived.getGroupId()));
-                        }
-                        if (syncReceived.getAttachments().size() > 0) {
-                            writer.println("Attachments:");
-                            for (var attachment : syncReceived.getAttachments()) {
-                                writer.println("- Stored plaintext in: {}", attachment);
-                            }
+                    writer.println("Sync Envelope from: {} to: {}",
+                            syncReceived.getSource(),
+                            syncReceived.getDestination());
+                    writer.println("Timestamp: {}", DateUtils.formatTimestamp(syncReceived.getTimestamp()));
+                    writer.println("Body: {}", syncReceived.getMessage());
+                    if (syncReceived.getGroupId().length > 0) {
+                        writer.println("Group info:");
+                        writer.indentedWriter()
+                                .println("Id: {}", Base64.getEncoder().encodeToString(syncReceived.getGroupId()));
+                    }
+                    if (syncReceived.getAttachments().size() > 0) {
+                        writer.println("Attachments:");
+                        for (var attachment : syncReceived.getAttachments()) {
+                            writer.println("- Stored plaintext in: {}", attachment);
                         }
-                        writer.println();
-                    } catch (IOException e) {
-                        e.printStackTrace();
                     }
+                    writer.println();
                 });
             }
         } catch (DBusException e) {
-            e.printStackTrace();
-            return 2;
+            logger.error("Dbus client failed", e);
+            throw new UnexpectedErrorException("Dbus client failed");
         }
         while (true) {
             try {
                 Thread.sleep(10000);
-            } catch (InterruptedException e) {
-                return 0;
+            } catch (InterruptedException ignored) {
+                return;
             }
         }
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
 
         // TODO delete later when "json" variable is removed
@@ -187,13 +167,11 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
                     returnOnTimeout,
                     ignoreAttachments,
                     handler);
-            return 0;
         } catch (IOException e) {
-            System.err.println("Error while receiving messages: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Error while receiving messages: " + e.getMessage());
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         }
     }
 }
index d656fe0ec97f0cc786c404309d3fa422e33f041a..a4c613afc09a0e78c525eebf8665aeb21203220f 100644 (file)
@@ -4,6 +4,9 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.RegistrationManager;
 import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
 
@@ -21,26 +24,25 @@ public class RegisterCommand implements RegistrationCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final RegistrationManager m) {
+    public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException {
         final boolean voiceVerification = ns.getBoolean("voice");
         final var captcha = ns.getString("captcha");
 
         try {
             m.register(voiceVerification, captcha);
-            return 0;
         } catch (CaptchaRequiredException e) {
+            String message;
             if (captcha == null) {
-                System.err.println("Captcha required for verification, use --captcha CAPTCHA");
-                System.err.println("To get the token, go to https://signalcaptchas.org/registration/generate.html");
-                System.err.println("Check the developer tools (F12) console for a failed redirect to signalcaptcha://");
-                System.err.println("Everything after signalcaptcha:// is the captcha token.");
+                message = "Captcha required for verification, use --captcha CAPTCHA\n"
+                        + "To get the token, go to https://signalcaptchas.org/registration/generate.html\n"
+                        + "Check the developer tools (F12) console for a failed redirect to signalcaptcha://\n"
+                        + "Everything after signalcaptcha:// is the captcha token.";
             } else {
-                System.err.println("Invalid captcha given.");
+                message = "Invalid captcha given.";
             }
-            return 1;
+            throw new UserErrorException(message);
         } catch (IOException e) {
-            System.err.println("Request verify error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Request verify error: " + e.getMessage());
         }
     }
 }
index 8683570f4bd2395bcb88280955de619c40c1662f..425ac71d2feac71676deaa43e22ede4403464d48 100644 (file)
@@ -2,9 +2,10 @@ package org.asamk.signal.commands;
 
 import net.sourceforge.argparse4j.inf.Namespace;
 
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.RegistrationManager;
 
 public interface RegistrationCommand extends Command {
 
-    int handleCommand(Namespace ns, RegistrationManager m);
+    void handleCommand(Namespace ns, RegistrationManager m) throws CommandException;
 }
index 78d14bbdeb1e295b8f87a70d4506c90e52f63a6b..c9be92e8628c46a64baf005bfa7c788d588f8860 100644 (file)
@@ -3,6 +3,8 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 
 import java.io.IOException;
@@ -18,14 +20,12 @@ public class RemoveDeviceCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             int deviceId = ns.getInt("deviceId");
             m.removeLinkedDevices(deviceId);
-            return 0;
         } catch (IOException e) {
-            e.printStackTrace();
-            return 3;
+            throw new IOErrorException("Error while removing device: " + e.getMessage());
         }
     }
 }
index 52b111eac1aab9687d0e8944d09e1e8ff162ddbc..03d8d7cba9fd689d89c42ad8cc6e016ed6484a29 100644 (file)
@@ -3,6 +3,9 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+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.manager.Manager;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
@@ -16,16 +19,13 @@ public class RemovePinCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             m.setRegistrationLockPin(Optional.absent());
-            return 0;
         } catch (UnauthenticatedResponseException e) {
-            System.err.println("Remove pin error: " + e.getMessage());
-            return 2;
+            throw new UnexpectedErrorException("Remove pin failed with unauthenticated response: " + e.getMessage());
         } catch (IOException e) {
-            System.err.println("Remove pin error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Remove pin error: " + e.getMessage());
         }
     }
 }
index 1976fb60339d3c15956495564b34b616a6f69bc2..23d741656f6cfc723147c43f1d954855896df573 100644 (file)
@@ -6,6 +6,10 @@ import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.Signal;
 import org.asamk.signal.PlainTextWriterImpl;
+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.groups.GroupIdFormatException;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
@@ -19,7 +23,6 @@ import java.nio.charset.Charset;
 import java.util.List;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
 
 public class SendCommand implements DbusCommand {
 
@@ -42,7 +45,7 @@ public class SendCommand implements DbusCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Signal signal) {
+    public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
         final List<String> recipients = ns.getList("recipient");
         final var isEndSession = ns.getBoolean("endsession");
         final var groupIdString = ns.getString("group");
@@ -50,32 +53,27 @@ public class SendCommand implements DbusCommand {
 
         final var noRecipients = recipients == null || recipients.isEmpty();
         if ((noRecipients && isEndSession) || (noRecipients && groupIdString == null && !isNoteToSelf)) {
-            System.err.println("No recipients given");
-            System.err.println("Aborting sending.");
-            return 1;
+            throw new UserErrorException("No recipients given");
         }
         if (!noRecipients && groupIdString != null) {
-            System.err.println("You cannot specify recipients by phone number and groups at the same time");
-            return 1;
+            throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
         }
         if (!noRecipients && isNoteToSelf) {
-            System.err.println("You cannot specify recipients by phone number and not to self at the same time");
-            return 1;
+            throw new UserErrorException(
+                    "You cannot specify recipients by phone number and not to self at the same time");
         }
 
         if (isEndSession) {
             try {
                 signal.sendEndSessionMessage(recipients);
-                return 0;
+                return;
             } catch (AssertionError e) {
                 handleAssertionError(e);
-                return 1;
+                throw e;
             } catch (Signal.Error.UntrustedIdentity e) {
-                System.err.println("Failed to send message: " + e.getMessage());
-                return 4;
+                throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
             } catch (DBusExecutionException e) {
-                System.err.println("Failed to send message: " + e.getMessage());
-                return 2;
+                throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
             }
         }
 
@@ -84,9 +82,7 @@ public class SendCommand implements DbusCommand {
             try {
                 messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
             } catch (IOException e) {
-                System.err.println("Failed to read message from stdin: " + e.getMessage());
-                System.err.println("Aborting sending.");
-                return 1;
+                throw new UserErrorException("Failed to read message from stdin: " + e.getMessage());
             }
         }
 
@@ -102,23 +98,18 @@ public class SendCommand implements DbusCommand {
             try {
                 groupId = Util.decodeGroupId(groupIdString).serialize();
             } catch (GroupIdFormatException e) {
-                handleGroupIdFormatException(e);
-                return 1;
+                throw new UserErrorException("Invalid group id:" + e.getMessage());
             }
 
             try {
                 var timestamp = signal.sendGroupMessage(messageText, attachments, groupId);
                 writer.println("{}", timestamp);
-                return 0;
+                return;
             } catch (AssertionError e) {
                 handleAssertionError(e);
-                return 1;
+                throw e;
             } catch (DBusExecutionException e) {
-                System.err.println("Failed to send group message: " + e.getMessage());
-                return 2;
-            } catch (IOException e) {
-                e.printStackTrace();
-                return 3;
+                throw new UnexpectedErrorException("Failed to send group message: " + e.getMessage());
             }
         }
 
@@ -126,41 +117,29 @@ public class SendCommand implements DbusCommand {
             try {
                 var timestamp = signal.sendNoteToSelfMessage(messageText, attachments);
                 writer.println("{}", timestamp);
-                return 0;
+                return;
             } catch (AssertionError e) {
                 handleAssertionError(e);
-                return 1;
+                throw e;
             } catch (Signal.Error.UntrustedIdentity e) {
-                System.err.println("Failed to send message: " + e.getMessage());
-                return 4;
+                throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
             } catch (DBusExecutionException e) {
-                System.err.println("Failed to send note to self message: " + e.getMessage());
-                return 2;
-            } catch (IOException e) {
-                e.printStackTrace();
-                return 3;
+                throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage());
             }
         }
 
         try {
             var timestamp = signal.sendMessage(messageText, attachments, recipients);
             writer.println("{}", timestamp);
-            return 0;
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (UnknownObject e) {
-            System.err.println("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
         } catch (Signal.Error.UntrustedIdentity e) {
-            System.err.println("Failed to send message: " + e.getMessage());
-            return 4;
+            throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
         } catch (DBusExecutionException e) {
-            System.err.println("Failed to send message: " + e.getMessage());
-            return 2;
-        } catch (IOException e) {
-            e.printStackTrace();
-            return 3;
+            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
         }
     }
 }
index f5eacf8129f51d772850ab9bd70713e2714b0b2d..176f1bb9c21c0e2d3f04e522b4ce825d0447ea90 100644 (file)
@@ -3,6 +3,9 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
 import org.asamk.signal.manager.Manager;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
 
@@ -16,16 +19,13 @@ public class SendContactsCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             m.sendContacts();
-            return 0;
         } catch (UntrustedIdentityException e) {
-            System.err.println("SendContacts error: " + e.getMessage());
-            return 2;
+            throw new UntrustedKeyErrorException("SendContacts error: " + e.getMessage());
         } catch (IOException e) {
-            System.err.println("SendContacts error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("SendContacts error: " + e.getMessage());
         }
     }
 }
index bcae9d41d4291176def5818c393b8bbc2fda25ef..b9cbc0473adb2f2d8ed8391c774091c2fbf1b6f2 100644 (file)
@@ -5,7 +5,11 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
@@ -18,11 +22,6 @@ import java.io.IOException;
 import java.util.List;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
-import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
-import static org.asamk.signal.util.ErrorUtils.handleIOException;
-import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
-import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
 import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
 
 public class SendReactionCommand implements LocalCommand {
@@ -46,19 +45,16 @@ public class SendReactionCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         final List<String> recipients = ns.getList("recipient");
         final var groupIdString = ns.getString("group");
 
         final var noRecipients = recipients == null || recipients.isEmpty();
         if (noRecipients && groupIdString == null) {
-            System.err.println("No recipients given");
-            System.err.println("Aborting sending.");
-            return 1;
+            throw new UserErrorException("No recipients given");
         }
         if (!noRecipients && groupIdString != null) {
-            System.err.println("You cannot specify recipients by phone number and groups at the same time");
-            return 1;
+            throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
         }
 
         final var emoji = ns.getString("emoji");
@@ -66,35 +62,37 @@ public class SendReactionCommand implements LocalCommand {
         final var targetAuthor = ns.getString("target_author");
         final long targetTimestamp = ns.getLong("target_timestamp");
 
-        try {
-            final var writer = new PlainTextWriterImpl(System.out);
+        final var writer = new PlainTextWriterImpl(System.out);
+
+        final Pair<Long, List<SendMessageResult>> results;
+
+        GroupId groupId = null;
+        if (groupId != null) {
+            try {
+                groupId = Util.decodeGroupId(groupIdString);
+            } catch (GroupIdFormatException e) {
+                throw new UserErrorException("Invalid group id:" + e.getMessage());
+            }
+        }
 
-            final Pair<Long, List<SendMessageResult>> results;
-            if (groupIdString != null) {
-                var groupId = Util.decodeGroupId(groupIdString);
+        try {
+            if (groupId != null) {
                 results = m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId);
             } else {
                 results = m.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, recipients);
             }
-            return handleTimestampAndSendMessageResults(writer, results.first(), results.second());
+            handleTimestampAndSendMessageResults(writer, results.first(), results.second());
         } catch (IOException e) {
-            handleIOException(e);
-            return 3;
+            throw new IOErrorException("Failed to send message: " + e.getMessage());
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (GroupNotFoundException e) {
-            handleGroupNotFoundException(e);
-            return 1;
+            throw new UserErrorException("Failed to send to group: " + e.getMessage());
         } catch (NotAGroupMemberException e) {
-            handleNotAGroupMemberException(e);
-            return 1;
-        } catch (GroupIdFormatException e) {
-            handleGroupIdFormatException(e);
-            return 1;
+            throw new UserErrorException("Failed to send to group: " + e.getMessage());
         } catch (InvalidNumberException e) {
-            handleInvalidNumberException(e);
-            return 1;
+            throw new UserErrorException("Invalid number: " + e.getMessage());
         }
     }
 }
index fff105be24faa115ba93d64f1ac64ed95f8e72a4..56b4b8a4d61ca3a99dd734d931351f1aa405144c 100644 (file)
@@ -3,6 +3,9 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+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.manager.Manager;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
@@ -18,17 +21,14 @@ public class SetPinCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             var registrationLockPin = ns.getString("registrationLockPin");
             m.setRegistrationLockPin(Optional.of(registrationLockPin));
-            return 0;
         } catch (UnauthenticatedResponseException e) {
-            System.err.println("Set pin error: " + e.getMessage());
-            return 2;
+            throw new UnexpectedErrorException("Set pin error failed with unauthenticated response: " + e.getMessage());
         } catch (IOException e) {
-            System.err.println("Set pin error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Set pin error: " + e.getMessage());
         }
     }
 }
index 277291eb06b4f9a50e5add82fda242f33f15a8ea..08fe6a41e30b4dd0bebbbb1c17cb91fc4722286b 100644 (file)
@@ -4,8 +4,9 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.util.ErrorUtils;
 import org.asamk.signal.util.Hex;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
@@ -25,13 +26,12 @@ public class TrustCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var number = ns.getString("number");
         if (ns.getBoolean("trust_all_known_keys")) {
             var res = m.trustIdentityAllKeys(number);
             if (!res) {
-                System.err.println("Failed to set the trust for this number, make sure the number is correct.");
-                return 1;
+                throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct.");
             }
         } else {
             var safetyNumber = ns.getString("verified_safety_number");
@@ -42,46 +42,38 @@ public class TrustCommand implements LocalCommand {
                     try {
                         fingerprintBytes = Hex.toByteArray(safetyNumber.toLowerCase(Locale.ROOT));
                     } catch (Exception e) {
-                        System.err.println(
+                        throw new UserErrorException(
                                 "Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
-                        return 1;
                     }
                     boolean res;
                     try {
                         res = m.trustIdentityVerified(number, fingerprintBytes);
                     } catch (InvalidNumberException e) {
-                        ErrorUtils.handleInvalidNumberException(e);
-                        return 1;
+                        throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
                     }
                     if (!res) {
-                        System.err.println(
+                        throw new UserErrorException(
                                 "Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
-                        return 1;
                     }
                 } else if (safetyNumber.length() == 60) {
                     boolean res;
                     try {
                         res = m.trustIdentityVerifiedSafetyNumber(number, safetyNumber);
                     } catch (InvalidNumberException e) {
-                        ErrorUtils.handleInvalidNumberException(e);
-                        return 1;
+                        throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
                     }
                     if (!res) {
-                        System.err.println(
+                        throw new UserErrorException(
                                 "Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
-                        return 1;
                     }
                 } else {
-                    System.err.println(
+                    throw new UserErrorException(
                             "Safety number has invalid format, either specify the old hex fingerprint or the new safety number");
-                    return 1;
                 }
             } else {
-                System.err.println(
+                throw new UserErrorException(
                         "You need to specify the fingerprint/safety number you have verified with -v SAFETY_NUMBER");
-                return 1;
             }
         }
-        return 0;
     }
 }
index b9d6e849cd7ace2c95ef858a3c6b1debcc706084..6e067ee5399852c719703bc5ca0613bc7d9a5d59 100644 (file)
@@ -3,14 +3,19 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.util.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
 public class UnblockCommand implements LocalCommand {
 
+    private final static Logger logger = LoggerFactory.getLogger(UnblockCommand.class);
+
     @Override
     public void attachToSubparser(final Subparser subparser) {
         subparser.addArgument("contact").help("Contact number").nargs("*");
@@ -19,12 +24,12 @@ public class UnblockCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
-        for (var contact_number : ns.<String>getList("contact")) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
+        for (var contactNumber : ns.<String>getList("contact")) {
             try {
-                m.setContactBlocked(contact_number, false);
+                m.setContactBlocked(contactNumber, false);
             } catch (InvalidNumberException e) {
-                System.err.println(e.getMessage());
+                logger.warn("Invalid number: {}", contactNumber);
             }
         }
 
@@ -33,12 +38,12 @@ public class UnblockCommand implements LocalCommand {
                 try {
                     var groupId = Util.decodeGroupId(groupIdString);
                     m.setGroupBlocked(groupId, false);
-                } catch (GroupIdFormatException | GroupNotFoundException e) {
-                    System.err.println(e.getMessage());
+                } catch (GroupIdFormatException e) {
+                    logger.warn("Invalid group id: {}", groupIdString);
+                } catch (GroupNotFoundException e) {
+                    logger.warn("Unknown group id: {}", groupIdString);
                 }
             }
         }
-
-        return 0;
     }
 }
index 079070e6f8548ab84ff457cb3204aa911f7cca69..1846eba17b980104986b5b5349a95461c7e6c41b 100644 (file)
@@ -3,6 +3,8 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 
 import java.io.IOException;
@@ -15,13 +17,11 @@ public class UnregisterCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             m.unregister();
-            return 0;
         } catch (IOException e) {
-            System.err.println("Unregister error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Unregister error: " + e.getMessage());
         }
     }
 }
index 8211e190e5ee03bc364ba740de8c7ebd764843f9..13723f09b671c7414dabe9873340ccf707970ba5 100644 (file)
@@ -3,6 +3,8 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 
 import java.io.IOException;
@@ -15,13 +17,11 @@ public class UpdateAccountCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         try {
             m.updateAccountAttributes();
-            return 0;
         } catch (IOException e) {
-            System.err.println("UpdateAccount error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("UpdateAccount error: " + e.getMessage());
         }
     }
 }
index c2b994d63d535d15f2fc31144fc6a8aff70dde0d..c8ad613b9ba1fceecfaeed24416fa22916414b00 100644 (file)
@@ -3,6 +3,9 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
@@ -22,7 +25,7 @@ public class UpdateContactCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var number = ns.getString("number");
         var name = ns.getString("name");
 
@@ -34,13 +37,9 @@ public class UpdateContactCommand implements LocalCommand {
                 m.setExpirationTimer(number, expiration);
             }
         } catch (InvalidNumberException e) {
-            System.err.println("Invalid contact number: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Invalid contact number: " + e.getMessage());
         } catch (IOException e) {
-            System.err.println("Update contact error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Update contact error: " + e.getMessage());
         }
-
-        return 0;
     }
 }
index fdb23ea13bad6775297913a4e9a360c2491bfda3..204dcfe2af812e5afe06e0f3ba5fa94d018c7916 100644 (file)
@@ -5,19 +5,20 @@ import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.Signal;
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.util.Util;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.List;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
 
 public class UpdateGroupCommand implements DbusCommand {
 
@@ -32,15 +33,14 @@ public class UpdateGroupCommand implements DbusCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Signal signal) {
+    public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
         final var writer = new PlainTextWriterImpl(System.out);
         byte[] groupId = null;
         if (ns.getString("group") != null) {
             try {
                 groupId = Util.decodeGroupId(ns.getString("group")).serialize();
             } catch (GroupIdFormatException e) {
-                handleGroupIdFormatException(e);
-                return 1;
+                throw new UserErrorException("Invalid group id:" + e.getMessage());
             }
         }
         if (groupId == null) {
@@ -65,23 +65,15 @@ public class UpdateGroupCommand implements DbusCommand {
         try {
             var newGroupId = signal.updateGroup(groupId, groupName, groupMembers, groupAvatar);
             if (groupId.length != newGroupId.length) {
-                try {
-                    writer.println("Creating new group \"{}\" …", Base64.getEncoder().encodeToString(newGroupId));
-                } catch (IOException e) {
-                    e.printStackTrace();
-                    return 3;
-                }
+                writer.println("Created new group: \"{}\"", Base64.getEncoder().encodeToString(newGroupId));
             }
-            return 0;
         } catch (AssertionError e) {
             handleAssertionError(e);
-            return 1;
+            throw e;
         } catch (Signal.Error.AttachmentInvalid e) {
-            System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Failed to add avatar attachment for group\": " + e.getMessage());
         } catch (DBusExecutionException e) {
-            System.err.println("Failed to send message: " + e.getMessage());
-            return 2;
+            throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
         }
     }
 }
index b8c7b08c77b5730754649b6e384e0573f595488a..c3fc2e8893b7de8221a059367793940bd13922bb 100644 (file)
@@ -4,6 +4,8 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.manager.Manager;
 import org.whispersystems.libsignal.util.guava.Optional;
 
@@ -26,23 +28,21 @@ public class UpdateProfileCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var name = ns.getString("name");
         var about = ns.getString("about");
         var aboutEmoji = ns.getString("about_emoji");
         var avatarPath = ns.getString("avatar");
         boolean removeAvatar = ns.getBoolean("remove_avatar");
 
+        Optional<File> avatarFile = removeAvatar
+                ? Optional.absent()
+                : avatarPath == null ? null : Optional.of(new File(avatarPath));
+
         try {
-            Optional<File> avatarFile = removeAvatar
-                    ? Optional.absent()
-                    : avatarPath == null ? null : Optional.of(new File(avatarPath));
             m.setProfile(name, about, aboutEmoji, avatarFile);
         } catch (IOException e) {
-            System.err.println("Update profile error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Update profile error: " + e.getMessage());
         }
-
-        return 0;
     }
 }
index 9d72a6c52ff67d78738578477058066523e429c3..1ed14e213e1d0eb383874d1778931842d950e105 100644 (file)
@@ -4,6 +4,9 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.PlainTextWriterImpl;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.StickerPackInvalidException;
 import org.slf4j.Logger;
@@ -23,19 +26,17 @@ public class UploadStickerPackCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         final var writer = new PlainTextWriterImpl(System.out);
+        var path = new File(ns.getString("path"));
+
         try {
-            var path = new File(ns.getString("path"));
             var url = m.uploadStickerPack(path);
             writer.println("{}", url);
-            return 0;
         } catch (IOException e) {
-            System.err.println("Upload error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Upload error: " + e.getMessage());
         } catch (StickerPackInvalidException e) {
-            System.err.println("Invalid sticker pack: " + e.getMessage());
-            return 1;
+            throw new UserErrorException("Invalid sticker pack: " + e.getMessage());
         }
     }
 }
index a244581dab8f445aa0aea2ec79ba76b2c92db590..c26c1d3a0701a949b865f1407ff7b2fcadd31b4d 100644 (file)
@@ -3,6 +3,10 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+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.manager.RegistrationManager;
 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
 import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
@@ -19,26 +23,23 @@ public class VerifyCommand implements RegistrationCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final RegistrationManager m) {
+    public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException {
+        var verificationCode = ns.getString("verificationCode");
+        var pin = ns.getString("pin");
+
         try {
-            var verificationCode = ns.getString("verificationCode");
-            var pin = ns.getString("pin");
             m.verifyAccount(verificationCode, pin);
-            return 0;
         } catch (LockedException e) {
-            System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: "
-                    + (e.getTimeRemaining() / 1000 / 60 / 60));
-            System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN");
-            return 1;
+            throw new UserErrorException(
+                    "Verification failed! This number is locked with a pin. Hours remaining until reset: "
+                            + (e.getTimeRemaining() / 1000 / 60 / 60)
+                            + "\nUse '--pin PIN_CODE' to specify the registration lock PIN");
         } catch (KeyBackupServicePinException e) {
-            System.err.println("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining());
-            return 1;
+            throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining());
         } catch (KeyBackupSystemNoDataException e) {
-            System.err.println("Verification failed! No KBS data.");
-            return 3;
+            throw new UnexpectedErrorException("Verification failed! No KBS data.");
         } catch (IOException e) {
-            System.err.println("Verify error: " + e.getMessage());
-            return 3;
+            throw new IOErrorException("Verify error: " + e.getMessage());
         }
     }
 }
diff --git a/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java b/src/main/java/org/asamk/signal/commands/exceptions/CommandException.java
new file mode 100644 (file)
index 0000000..c82ef54
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.commands.exceptions;
+
+public class CommandException extends Exception {
+
+    public CommandException(final String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/commands/exceptions/IOErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/IOErrorException.java
new file mode 100644 (file)
index 0000000..e405600
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.commands.exceptions;
+
+public final class IOErrorException extends CommandException {
+
+    public IOErrorException(final String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/commands/exceptions/UnexpectedErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/UnexpectedErrorException.java
new file mode 100644 (file)
index 0000000..b6f231d
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.commands.exceptions;
+
+public final class UnexpectedErrorException extends CommandException {
+
+    public UnexpectedErrorException(final String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/commands/exceptions/UntrustedKeyErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/UntrustedKeyErrorException.java
new file mode 100644 (file)
index 0000000..c215f41
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.commands.exceptions;
+
+public final class UntrustedKeyErrorException extends CommandException {
+
+    public UntrustedKeyErrorException(final String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java
new file mode 100644 (file)
index 0000000..84e957c
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.commands.exceptions;
+
+public final class UserErrorException extends CommandException {
+
+    public UserErrorException(final String message) {
+        super(message);
+    }
+}
index 149b16c0df467d7b6427bbc1dce2a7fb226fa0d5..595509a6fd62827befc8753ccc8ddf1ab6180497 100644 (file)
@@ -1,38 +1,34 @@
 package org.asamk.signal.util;
 
 import org.asamk.signal.PlainTextWriter;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
-import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
 public class ErrorUtils {
 
+    private final static Logger logger = LoggerFactory.getLogger(ErrorUtils.class);
+
     private ErrorUtils() {
     }
 
     public static void handleAssertionError(AssertionError e) {
-        System.err.println("Failed to send/receive message (Assertion): " + e.getMessage());
-        e.printStackTrace();
-        System.err.println(
-                "If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
+        logger.warn("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
     }
 
-    public static int handleTimestampAndSendMessageResults(
-            PlainTextWriter writer,
-            long timestamp,
-            List<SendMessageResult> results
-    ) throws IOException {
+    public static void handleTimestampAndSendMessageResults(
+            PlainTextWriter writer, long timestamp, List<SendMessageResult> results
+    ) throws CommandException {
         if (timestamp != 0) {
             writer.println("{}", timestamp);
         }
         var errors = getErrorMessagesFromSendMessageResults(results);
-        return handleSendMessageResultErrors(errors);
+        handleSendMessageResultErrors(errors);
     }
 
     public static List<String> getErrorMessagesFromSendMessageResults(List<SendMessageResult> results) {
@@ -58,39 +54,15 @@ public class ErrorUtils {
         return null;
     }
 
-    private static int handleSendMessageResultErrors(List<String> errors) {
+    private static void handleSendMessageResultErrors(List<String> errors) throws CommandException {
         if (errors.size() == 0) {
-            return 0;
+            return;
         }
-        System.err.println("Failed to send (some) messages:");
+        var message = new StringBuilder();
+        message.append("Failed to send (some) messages:\n");
         for (var error : errors) {
-            System.err.println(error);
+            message.append(error).append("\n");
         }
-        return 3;
-    }
-
-    public static void handleIOException(IOException e) {
-        System.err.println("Failed to send message: " + e.getMessage());
-    }
-
-    public static void handleGroupNotFoundException(GroupNotFoundException e) {
-        System.err.println("Failed to send to group: " + e.getMessage());
-        System.err.println("Aborting sending.");
-    }
-
-    public static void handleNotAGroupMemberException(NotAGroupMemberException e) {
-        System.err.println("Failed to send to group: " + e.getMessage());
-        System.err.println("Update the group on another device to readd the user to this group.");
-        System.err.println("Aborting sending.");
-    }
-
-    public static void handleGroupIdFormatException(GroupIdFormatException e) {
-        System.err.println(e.getMessage());
-        System.err.println("Aborting sending.");
-    }
-
-    public static void handleInvalidNumberException(InvalidNumberException e) {
-        System.err.println("Failed to parse recipient: " + e.getMessage());
-        System.err.println("Aborting sending.");
+        throw new IOErrorException(message.toString());
     }
 }