From: AsamK Date: Fri, 31 Dec 2021 17:17:32 +0000 (+0100) Subject: Implement multi account commands for dbus client X-Git-Tag: v0.10.1~18 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/805f976d9e3de22e4842df1622941584c7f7c041 Implement multi account commands for dbus client --- diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 80bc0de0..dce3d82e 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -371,6 +371,10 @@ "allDeclaredMethods":true, "allDeclaredClasses":true} , +{ + "name":"org.asamk.SignalControl$Error$Failure", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} +, { "name":"org.asamk.signal.commands.FinishLinkCommand$FinishLinkParams", "allDeclaredFields":true, diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index 5401d832..e16270b1 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -118,6 +118,9 @@ { "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MY\\E" }, + { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NG\\E" + }, { "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NL\\E" }, diff --git a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java index 903e145c..32b03d3a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java @@ -20,8 +20,6 @@ public interface MultiAccountManager extends AutoCloseable { ProvisioningManager getProvisioningManagerFor(URI deviceLinkUri); - ProvisioningManager getNewProvisioningManager(); - RegistrationManager getNewRegistrationManager(String account) throws IOException; @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java index 1a5ea266..dc72aeeb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java @@ -111,8 +111,7 @@ public class MultiAccountManagerImpl implements MultiAccountManager { return provisioningManagers.remove(deviceLinkUri); } - @Override - public ProvisioningManager getNewProvisioningManager() { + private ProvisioningManager getNewProvisioningManager() { return ProvisioningManager.init(dataPath, serviceEnvironment, userAgent, this::addManager); } diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java index ddb884cd..81007d79 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java @@ -243,7 +243,7 @@ public class RegistrationManagerImpl implements RegistrationManager { } @Override - public void close() throws IOException { + public void close() { if (account != null) { account.close(); account = null; diff --git a/src/main/java/org/asamk/SignalControl.java b/src/main/java/org/asamk/SignalControl.java index 610ca103..fe51a7f7 100644 --- a/src/main/java/org/asamk/SignalControl.java +++ b/src/main/java/org/asamk/SignalControl.java @@ -26,10 +26,16 @@ public interface SignalControl extends DBusInterface { String link(String newDeviceName) throws Error.Failure; + String startLink() throws Error.Failure; + + String finishLink(String deviceLinkUri, String newDeviceName) throws Error.Failure; + String version(); List listAccounts(); + DBusPath getAccount(String number); + interface Error { class Failure extends DBusExecutionException { diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 98a1a0bc..995da8d4 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -18,6 +18,9 @@ import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.dbus.DbusManagerImpl; +import org.asamk.signal.dbus.DbusMultiAccountManagerImpl; +import org.asamk.signal.dbus.DbusProvisioningManagerImpl; +import org.asamk.signal.dbus.DbusRegistrationManagerImpl; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManagerImpl; import org.asamk.signal.manager.NotRegisteredException; @@ -220,6 +223,22 @@ public class App { command.handleCommand(ns, pm, outputWriter); } + private void handleProvisioningCommand( + final ProvisioningCommand c, final DBusConnection dBusConn, final OutputWriter outputWriter + ) throws CommandException, DBusException { + final var signalControl = dBusConn.getRemoteObject(DbusConfig.getBusname(), + DbusConfig.getObjectPath(), + SignalControl.class); + final var provisioningManager = new DbusProvisioningManagerImpl(signalControl, dBusConn); + try { + c.handleCommand(ns, provisioningManager, outputWriter); + } catch (UnsupportedOperationException e) { + throw new UserErrorException("Command is not yet implemented via dbus", e); + } catch (DBusExecutionException e) { + throw new UnexpectedErrorException(e.getMessage(), e); + } + } + private void handleRegistrationCommand( final RegistrationCommand command, final String account, @@ -243,6 +262,21 @@ public class App { } } + private void handleRegistrationCommand( + final RegistrationCommand c, String account, final DBusConnection dBusConn, final OutputWriter outputWriter + ) throws CommandException, DBusException { + final var signalControl = dBusConn.getRemoteObject(DbusConfig.getBusname(), + DbusConfig.getObjectPath(), + SignalControl.class); + try (final var registrationManager = new DbusRegistrationManagerImpl(account, signalControl, dBusConn)) { + c.handleCommand(ns, registrationManager); + } catch (UnsupportedOperationException e) { + throw new UserErrorException("Command is not yet implemented via dbus", e); + } catch (DBusExecutionException e) { + throw new UnexpectedErrorException(e.getMessage(), e); + } + } + private void handleLocalCommand( final LocalCommand command, final String account, @@ -258,6 +292,22 @@ public class App { } } + private void handleLocalCommand( + final LocalCommand c, + String accountObjectPath, + final DBusConnection dBusConn, + final OutputWriter outputWriter + ) throws CommandException, DBusException { + var signal = dBusConn.getRemoteObject(DbusConfig.getBusname(), accountObjectPath, Signal.class); + try (final var m = new DbusManagerImpl(signal, dBusConn)) { + c.handleCommand(ns, m, outputWriter); + } catch (UnsupportedOperationException e) { + throw new UserErrorException("Command is not yet implemented via dbus", e); + } catch (DBusExecutionException e) { + throw new UnexpectedErrorException(e.getMessage(), e); + } + } + private void handleMultiLocalCommand( final MultiLocalCommand command, final File dataPath, @@ -283,6 +333,19 @@ public class App { } } + private void handleMultiLocalCommand( + final MultiLocalCommand c, final DBusConnection dBusConn, final OutputWriter outputWriter + ) throws CommandException, DBusException { + final var signalControl = dBusConn.getRemoteObject(DbusConfig.getBusname(), + DbusConfig.getObjectPath(), + SignalControl.class); + try (final var multiAccountManager = new DbusMultiAccountManagerImpl(signalControl, dBusConn)) { + c.handleCommand(ns, multiAccountManager, outputWriter); + } catch (UnsupportedOperationException e) { + throw new UserErrorException("Command is not yet implemented via dbus", e); + } + } + private Manager loadManager( final String account, final File dataPath, @@ -331,13 +394,32 @@ public class App { busType = DBusConnection.DBusBusType.SESSION; } try (var dBusConn = DBusConnection.getConnection(busType)) { + if (command instanceof ProvisioningCommand c) { + if (account != null) { + throw new UserErrorException("You cannot specify a account (phone number) when linking"); + } + + handleProvisioningCommand(c, dBusConn, outputWriter); + return; + } + + if (account == null && command instanceof MultiLocalCommand c) { + handleMultiLocalCommand(c, dBusConn, outputWriter); + return; + } + if (account != null && command instanceof RegistrationCommand c) { + handleRegistrationCommand(c, account, dBusConn, outputWriter); + return; + } + if (!(command instanceof LocalCommand localCommand)) { + throw new UserErrorException("Command only works in multi-account mode"); + } + var accountObjectPath = account == null ? tryGetSingleAccountObjectPath(dBusConn) : null; if (accountObjectPath == null) { accountObjectPath = DbusConfig.getObjectPath(account); } - var ts = dBusConn.getRemoteObject(DbusConfig.getBusname(), accountObjectPath, Signal.class); - - handleCommand(command, ts, dBusConn, outputWriter); + handleLocalCommand(localCommand, accountObjectPath, dBusConn, outputWriter); } } catch (ServiceUnknown e) { throw new UserErrorException("signal-cli DBus daemon not running on " @@ -369,22 +451,6 @@ public class App { } } - private void handleCommand( - Command command, Signal ts, DBusConnection dBusConn, OutputWriter outputWriter - ) throws CommandException { - if (command instanceof LocalCommand localCommand) { - try (final var m = new DbusManagerImpl(ts, dBusConn)) { - localCommand.handleCommand(ns, m, outputWriter); - } catch (UnsupportedOperationException e) { - throw new UserErrorException("Command is not yet implemented via dbus", e); - } catch (IOException | DBusExecutionException e) { - throw new UnexpectedErrorException(e.getMessage(), e); - } - } else { - throw new UserErrorException("Command is not yet implemented via dbus"); - } - } - /** * @return the default data directory to be used by signal-cli. */ diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 54e90761..7faf87e8 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -614,7 +614,7 @@ public class DbusManagerImpl implements Manager { } @Override - public void close() throws IOException { + public void close() { synchronized (this) { this.notify(); } @@ -665,9 +665,9 @@ public class DbusManagerImpl implements Manager { return string == null ? "" : string; } - private T getRemoteObject(final DBusPath devicePath, final Class type) { + private T getRemoteObject(final DBusPath path, final Class type) { try { - return connection.getRemoteObject(DbusConfig.getBusname(), devicePath.getPath(), type); + return connection.getRemoteObject(DbusConfig.getBusname(), path.getPath(), type); } catch (DBusException e) { throw new AssertionError(e); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java new file mode 100644 index 00000000..9659fea5 --- /dev/null +++ b/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java @@ -0,0 +1,98 @@ +package org.asamk.signal.dbus; + +import org.asamk.Signal; +import org.asamk.SignalControl; +import org.asamk.signal.DbusConfig; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.MultiAccountManager; +import org.asamk.signal.manager.ProvisioningManager; +import org.asamk.signal.manager.RegistrationManager; +import org.freedesktop.dbus.DBusPath; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.interfaces.DBusInterface; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +/** + * This class implements the MultiAccountManager interface using the DBus Signal interface, where possible. + * It's used for the signal-cli dbus client mode (--dbus, --dbus-system) + */ +public class DbusMultiAccountManagerImpl implements MultiAccountManager { + + private final SignalControl signalControl; + private final DBusConnection connection; + // TODO add listeners for added/removed accounts + private final Set> onManagerAddedHandlers = new HashSet<>(); + private final Set> onManagerRemovedHandlers = new HashSet<>(); + + public DbusMultiAccountManagerImpl(final SignalControl signalControl, DBusConnection connection) { + this.signalControl = signalControl; + this.connection = connection; + } + + @Override + public List getAccountNumbers() { + return signalControl.listAccounts() + .stream() + .map(a -> getRemoteObject(a, Signal.class).getSelfNumber()) + .toList(); + } + + @Override + public void addOnManagerAddedHandler(final Consumer handler) { + synchronized (onManagerAddedHandlers) { + onManagerAddedHandlers.add(handler); + } + } + + @Override + public void addOnManagerRemovedHandler(final Consumer handler) { + synchronized (onManagerRemovedHandlers) { + onManagerRemovedHandlers.add(handler); + } + } + + @Override + public Manager getManager(final String phoneNumber) { + return new DbusManagerImpl(getRemoteObject(signalControl.getAccount(phoneNumber), Signal.class), connection); + } + + @Override + public URI getNewProvisioningDeviceLinkUri() throws TimeoutException, IOException { + try { + return new URI(signalControl.startLink()); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public ProvisioningManager getProvisioningManagerFor(final URI deviceLinkUri) { + return new DbusProvisioningManagerImpl(signalControl, connection, deviceLinkUri); + } + + @Override + public RegistrationManager getNewRegistrationManager(final String account) throws IOException { + return new DbusRegistrationManagerImpl(account, signalControl, connection); + } + + @Override + public void close() { + } + + private T getRemoteObject(final DBusPath path, final Class type) { + try { + return connection.getRemoteObject(DbusConfig.getBusname(), path.getPath(), type); + } catch (DBusException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java new file mode 100644 index 00000000..cba2b5e4 --- /dev/null +++ b/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java @@ -0,0 +1,53 @@ +package org.asamk.signal.dbus; + +import org.asamk.SignalControl; +import org.asamk.signal.manager.ProvisioningManager; +import org.asamk.signal.manager.UserAlreadyExists; +import org.freedesktop.dbus.connections.impl.DBusConnection; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.TimeoutException; + +/** + * This class implements the ProvisioningManager interface using the DBus Signal interface, where possible. + * It's used for the signal-cli dbus client mode (--dbus, --dbus-system) + */ +public class DbusProvisioningManagerImpl implements ProvisioningManager { + + private final SignalControl signalControl; + private final DBusConnection connection; + + private URI deviceLinkUri; + + public DbusProvisioningManagerImpl(final SignalControl signalControl, DBusConnection connection) { + this.signalControl = signalControl; + this.connection = connection; + } + + public DbusProvisioningManagerImpl( + final SignalControl signalControl, + DBusConnection connection, + URI deviceLinkUri + ) { + this.signalControl = signalControl; + this.connection = connection; + this.deviceLinkUri = deviceLinkUri; + } + + @Override + public URI getDeviceLinkUri() throws TimeoutException, IOException { + try { + deviceLinkUri = new URI(signalControl.startLink()); + return deviceLinkUri; + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public String finishDeviceLink(final String deviceName) throws IOException, TimeoutException, UserAlreadyExists { + return signalControl.finishLink(deviceLinkUri.toString(), deviceName); + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java new file mode 100644 index 00000000..aa27fc27 --- /dev/null +++ b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java @@ -0,0 +1,53 @@ +package org.asamk.signal.dbus; + +import org.asamk.SignalControl; +import org.asamk.signal.manager.RegistrationManager; +import org.asamk.signal.manager.api.CaptchaRequiredException; +import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.PinLockedException; +import org.freedesktop.dbus.connections.impl.DBusConnection; + +import java.io.IOException; + +/** + * This class implements the RegistrationManager interface using the DBus Signal interface, where possible. + * It's used for the signal-cli dbus client mode (--dbus, --dbus-system) + */ +public class DbusRegistrationManagerImpl implements RegistrationManager { + + private final String number; + private final SignalControl signalControl; + private final DBusConnection connection; + + public DbusRegistrationManagerImpl(String number, final SignalControl signalControl, DBusConnection connection) { + this.number = number; + this.signalControl = signalControl; + this.connection = connection; + } + + @Override + public void register( + final boolean voiceVerification, final String captcha + ) throws IOException, CaptchaRequiredException { + if (captcha == null) { + signalControl.register(number, voiceVerification); + } else { + signalControl.registerWithCaptcha(number, voiceVerification, captcha); + } + } + + @Override + public void verifyAccount( + final String verificationCode, final String pin + ) throws IOException, PinLockedException, IncorrectPinException { + if (pin == null) { + signalControl.verify(number, verificationCode); + } else { + signalControl.verifyWithPin(number, verificationCode, pin); + } + } + + @Override + public void close() { + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index cfd17610..aa357444 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -15,6 +15,7 @@ import org.freedesktop.dbus.DBusPath; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.nio.channels.OverlappingFileLockException; import java.util.List; import java.util.concurrent.TimeoutException; @@ -88,9 +89,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { @Override public String link(final String newDeviceName) throws Error.Failure { try { - final ProvisioningManager provisioningManager = c.getNewProvisioningManager(); - final URI deviceLinkUri = provisioningManager.getDeviceLinkUri(); + final URI deviceLinkUri = c.getNewProvisioningDeviceLinkUri(); new Thread(() -> { + final ProvisioningManager provisioningManager = c.getProvisioningManagerFor(deviceLinkUri); try { provisioningManager.finishDeviceLink(newDeviceName); } catch (IOException | TimeoutException | UserAlreadyExists e) { @@ -103,6 +104,26 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { } } + @Override + public String startLink() throws Error.Failure { + try { + final URI deviceLinkUri = c.getNewProvisioningDeviceLinkUri(); + return deviceLinkUri.toString(); + } catch (TimeoutException | IOException e) { + throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); + } + } + + @Override + public String finishLink(String deviceLinkUri, final String newDeviceName) throws Error.Failure { + try { + final var provisioningManager = c.getProvisioningManagerFor(new URI(deviceLinkUri)); + return provisioningManager.finishDeviceLink(newDeviceName); + } catch (TimeoutException | IOException | UserAlreadyExists | URISyntaxException e) { + throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); + } + } + @Override public String version() { return BaseConfig.PROJECT_VERSION; @@ -112,4 +133,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { public List listAccounts() { return c.getAccountNumbers().stream().map(u -> new DBusPath(DbusConfig.getObjectPath(u))).toList(); } + + @Override + public DBusPath getAccount(final String number) { + return new DBusPath(DbusConfig.getObjectPath(number)); + } }