DBus listen(number<s>) and daemon \-\-number

implement listen method for DBus
listen(number<s>) -> <>::

add \-\-number option to daemon subcommand
- permits starting daemon in anonymous mode with zero or more numbers
- numbers may be added to daemon with listen() and removed with unlisten()

change misleading "dataPath" to `settingsPath` in App.java
- settingsPath=~/.local/share/signal-cli, while dataPath=~/.local/share/signal-cli/data

only use FileLock when necessary in App.java, and unlock when appropriate

update documentation
This commit is contained in:
John Freed 2021-10-05 13:17:30 +02:00
parent 26594dd0ee
commit 690636b83d
12 changed files with 317 additions and 64 deletions

View file

@ -94,6 +94,8 @@ public interface Manager extends Closeable {
void checkAccountState() throws IOException;
SignalAccount getAccount();
Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException;
void updateAccountAttributes(String deviceName) throws IOException;

View file

@ -247,6 +247,11 @@ public class ManagerImpl implements Manager {
return account.getUsername();
}
@Override
public SignalAccount getAccount() {
return account;
}
@Override
public void checkAccountState() throws IOException {
if (account.getLastReceiveTimestamp() == 0) {

View file

@ -116,8 +116,9 @@ public class RegistrationManager implements Closeable {
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
public void register(boolean voiceVerification, String captcha) throws IOException {
public void register(boolean voiceVerification, String captchaString) throws IOException {
final ServiceResponse<RequestVerificationCodeResponse> response;
final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", "");
if (voiceVerification) {
response = accountManager.requestVoiceVerificationCode(getDefaultLocale(),
Optional.fromNullable(captcha),

View file

@ -67,6 +67,14 @@ listAccounts() -> accountList<as>::
Exceptions: None
listen(number<s>) -> <>::
* number : Phone number
Starting checking the Signal servers on behalf of this number, and export a DBus object path for it.
Fails if user is not already registered.
Exceptions: Failure
register(number<s>, voiceVerification<b>) -> <>::
* number : Phone number
* voiceVerification : true = use voice verification; false = use SMS verification
@ -78,6 +86,8 @@ registerWithCaptcha(number<s>, voiceVerification<b>, captcha<s>) -> <>::
* voiceVerification : true = use voice verification; false = use SMS verification
* captcha : Captcha string
Captcha strings may be obtained from `https://signalcaptchas.org/registration/generate.html`
Exceptions: Failure, InvalidNumber, RequiresCaptcha
verify(number<s>, verificationCode<s>) -> <>::

View file

@ -494,13 +494,16 @@ The path of the manifest.json or a zip file containing the sticker pack you wish
=== daemon
signal-cli can run in daemon mode and provides an experimental dbus interface.
If no `-u` username is given, all local users will be exported as separate dbus
objects under the same bus name.
If no `-u` username is given, zero or more local users as specified by the
`--number` option will be exported as separate dbus objects under the same bus name.
If `--number` is omitted, all local users will be exported.
*--system*::
Use DBus system bus instead of user bus.
*--ignore-attachments*::
Dont download attachments of received messages.
*--number* [NUMBER [NUMBER ...]]::
List of zero or more numbers for anonymous daemon to listen to (default=all)
== Examples

View file

@ -28,6 +28,8 @@ public interface SignalControl extends DBusInterface {
public String version();
void listen(String number) throws Error.Failure;
List<DBusPath> listAccounts();
interface Error {

View file

@ -36,6 +36,7 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.File;
import java.io.IOException;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList;
import java.util.List;
@ -125,12 +126,12 @@ public class App {
return;
}
final File dataPath;
final File settingsPath;
var config = ns.getString("config");
if (config != null) {
dataPath = new File(config);
settingsPath = new File(config);
} else {
dataPath = getDefaultDataPath();
settingsPath = getDefaultSettingsPath();
}
if (!ServiceConfig.getCapabilities().isGv2()) {
@ -157,22 +158,24 @@ public class App {
throw new UserErrorException("You cannot specify a username (phone number) when linking");
}
handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceEnvironment, outputWriter);
handleProvisioningCommand((ProvisioningCommand) command, settingsPath, serviceEnvironment, outputWriter);
return;
}
if (command instanceof MultiLocalCommand) {
List<String> usernames = new ArrayList<>();
if (username == null) {
//anonymous mode
handleMultiLocalCommand((MultiLocalCommand) command, settingsPath, serviceEnvironment, usernames, outputWriter, trustNewIdentity);
} else {
//single-user mode
handleMultiLocalCommand((MultiLocalCommand) command, settingsPath, serviceEnvironment, username, outputWriter, trustNewIdentity);
}
return;
}
if (username == null) {
var usernames = Manager.getAllLocalNumbers(dataPath);
if (command instanceof MultiLocalCommand) {
handleMultiLocalCommand((MultiLocalCommand) command,
dataPath,
serviceEnvironment,
usernames,
outputWriter,
trustNewIdentity);
return;
}
var usernames = Manager.getAllLocalNumbers(settingsPath);
if (usernames.size() == 0) {
throw new UserErrorException("No local users found, you first need to register or link an account");
@ -187,7 +190,7 @@ public class App {
}
if (command instanceof RegistrationCommand) {
handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceEnvironment);
handleRegistrationCommand((RegistrationCommand) command, username, settingsPath, serviceEnvironment);
return;
}
@ -197,7 +200,7 @@ public class App {
handleLocalCommand((LocalCommand) command,
username,
dataPath,
settingsPath,
serviceEnvironment,
outputWriter,
trustNewIdentity);
@ -205,23 +208,23 @@ public class App {
private void handleProvisioningCommand(
final ProvisioningCommand command,
final File dataPath,
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final OutputWriter outputWriter
) throws CommandException {
var pm = ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
var pm = ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
command.handleCommand(ns, pm, outputWriter);
}
private void handleRegistrationCommand(
final RegistrationCommand command,
final String username,
final File dataPath,
final File settingsPath,
final ServiceEnvironment serviceEnvironment
) throws CommandException {
final RegistrationManager manager;
try {
manager = RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
manager = RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
} catch (Throwable e) {
throw new UnexpectedErrorException("Error loading or creating state file: "
+ e.getMessage()
@ -231,6 +234,7 @@ public class App {
}
try (var m = manager) {
command.handleCommand(ns, m);
m.close();
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
@ -239,13 +243,14 @@ public class App {
private void handleLocalCommand(
final LocalCommand command,
final String username,
final File dataPath,
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final OutputWriter outputWriter,
final TrustNewIdentity trustNewIdentity
) throws CommandException {
try (var m = loadManager(username, dataPath, serviceEnvironment, trustNewIdentity)) {
try (var m = loadManager(username, settingsPath, serviceEnvironment, trustNewIdentity)) {
command.handleCommand(ns, m, outputWriter);
m.close();
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
@ -253,32 +258,35 @@ public class App {
private void handleMultiLocalCommand(
final MultiLocalCommand command,
final File dataPath,
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final List<String> usernames,
final OutputWriter outputWriter,
final TrustNewIdentity trustNewIdentity
) throws CommandException {
final var managers = new ArrayList<Manager>();
for (String u : usernames) {
try {
managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity));
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", u, e.getMessage());
SignalCreator c = new SignalCreator() {
@Override
public File getSettingsPath() {
return settingsPath;
}
}
command.handleCommand(ns, managers, new SignalCreator() {
@Override
public ServiceEnvironment getServiceEnvironment() {
return serviceEnvironment;
}
@Override
public ProvisioningManager getNewProvisioningManager() {
return ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
return ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
@Override
public RegistrationManager getNewRegistrationManager(String username) throws IOException {
return RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
return RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
}, outputWriter);
};
final var managers = new ArrayList<Manager>();
command.handleCommand(ns, managers, c, outputWriter, trustNewIdentity);
for (var m : managers) {
try {
@ -289,17 +297,67 @@ public class App {
}
}
private Manager loadManager(
private void handleMultiLocalCommand(
final MultiLocalCommand command,
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final String username,
final File dataPath,
final OutputWriter outputWriter,
final TrustNewIdentity trustNewIdentity
) throws CommandException {
SignalCreator c = new SignalCreator() {
@Override
public File getSettingsPath() {
return settingsPath;
}
@Override
public ServiceEnvironment getServiceEnvironment() {
return serviceEnvironment;
}
@Override
public ProvisioningManager getNewProvisioningManager() {
return ProvisioningManager.init(settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
@Override
public RegistrationManager getNewRegistrationManager(String username) throws IOException {
return RegistrationManager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
};
Manager manager = null;
try {
manager = loadManager(username, settingsPath, serviceEnvironment, trustNewIdentity);
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", username, e.getMessage());
}
command.handleCommand(ns, manager, c, outputWriter, trustNewIdentity);
try {
manager.close();
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
}
public static Manager loadManager(
final String username,
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final TrustNewIdentity trustNewIdentity
) throws CommandException {
Manager manager;
try {
manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
manager = Manager.init(username, settingsPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
} catch (NotRegisteredException e) {
throw new UserErrorException("User " + username + " is not registered.");
} catch (OverlappingFileLockException e) {
throw new UserErrorException("User " + username + " is already listening.");
} catch (Throwable e) {
throw new UnexpectedErrorException("Error loading state file for user "
+ username
@ -313,6 +371,13 @@ public class App {
try {
manager.checkAccountState();
} catch (IOException e) {
/* In case account isn't registered on Signal servers, close it locally,
* thus removing the FileLock so another daemon can get it.
*/
try {
manager.getAccount().close();
} catch (IOException ignore) {
}
throw new IOErrorException("Error while checking account " + username + ": " + e.getMessage(), e);
}
@ -361,9 +426,9 @@ public class App {
}
/**
* @return the default data directory to be used by signal-cli.
* @return the default settings directory to be used by signal-cli.
*/
private static File getDefaultDataPath() {
private static File getDefaultSettingsPath() {
return new File(IOUtils.getDataHomeDir(), "signal-cli");
}
}

View file

@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.App;
import org.asamk.signal.DbusConfig;
import org.asamk.signal.DbusReceiveMessageHandler;
import org.asamk.signal.JsonDbusReceiveMessageHandler;
@ -13,14 +14,19 @@ import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusSignalControlImpl;
import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -28,6 +34,9 @@ import java.util.concurrent.TimeUnit;
public class DaemonCommand implements MultiLocalCommand {
private final static Logger logger = LoggerFactory.getLogger(DaemonCommand.class);
public static DBusConnection.DBusBusType dBusType;
public static TrustNewIdentity trustNewIdentity;
public static OutputWriter outputWriter;
@Override
public String getName() {
@ -43,6 +52,8 @@ public class DaemonCommand implements MultiLocalCommand {
subparser.addArgument("--ignore-attachments")
.help("Dont download attachments of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--number", "--numbers").help("Phone numbers").nargs("*")
.help("List of zero or more numbers for anonymous daemon to listen to (default=all)");
}
@Override
@ -54,6 +65,12 @@ public class DaemonCommand implements MultiLocalCommand {
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
handleCommand(ns, m, null, outputWriter, null);
}
@Override
public void handleCommand(final Namespace ns, final Manager m, final SignalCreator c, final OutputWriter outputWriter, final TrustNewIdentity trustNewIdentity) throws CommandException {
//single-user mode
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
DBusConnection.DBusBusType busType;
@ -63,6 +80,12 @@ public class DaemonCommand implements MultiLocalCommand {
busType = DBusConnection.DBusBusType.SESSION;
}
this.dBusType = busType;
this.trustNewIdentity = trustNewIdentity;
this.outputWriter = outputWriter;
checkMacOS();
try (var conn = DBusConnection.getConnection(busType)) {
var objectPath = DbusConfig.getObjectPath();
var t = run(conn, objectPath, m, outputWriter, ignoreAttachments);
@ -73,7 +96,10 @@ public class DaemonCommand implements MultiLocalCommand {
t.join();
} catch (InterruptedException ignored) {
}
} catch (DBusException | IOException e) {
} catch (DBusException e) {
logger.error("Dbus command failed", e);
throw new UserErrorException("Dbus command failed, daemon already started on this bus.");
} catch (IOException e) {
logger.error("Dbus command failed", e);
throw new UnexpectedErrorException("Dbus command failed", e);
}
@ -81,8 +107,9 @@ public class DaemonCommand implements MultiLocalCommand {
@Override
public void handleCommand(
final Namespace ns, final List<Manager> managers, final SignalCreator c, final OutputWriter outputWriter
final Namespace ns, final List<Manager> managers, final SignalCreator c, final OutputWriter outputWriter, TrustNewIdentity trustNewIdentity
) throws CommandException {
//anonymous mode
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
DBusConnection.DBusBusType busType;
@ -92,6 +119,12 @@ public class DaemonCommand implements MultiLocalCommand {
busType = DBusConnection.DBusBusType.SESSION;
}
this.dBusType = busType;
this.trustNewIdentity = trustNewIdentity;
this.outputWriter = outputWriter;
checkMacOS();
try (var conn = DBusConnection.getConnection(busType)) {
final var signalControl = new DbusSignalControlImpl(c, m -> {
try {
@ -104,19 +137,59 @@ public class DaemonCommand implements MultiLocalCommand {
}, DbusConfig.getObjectPath());
conn.exportObject(signalControl);
List<String> daemonUsernames = ns.<String>getList("number");
File settingsPath = c.getSettingsPath();
ServiceEnvironment serviceEnvironment = c.getServiceEnvironment();
if (daemonUsernames == null) {
//--numbers option was not given, so add all local usernames
daemonUsernames = Manager.getAllLocalNumbers(settingsPath);
if (daemonUsernames.size() == 0) {
logger.error("No users are registered yet.");
throw new UserErrorException("No users are registered yetTry again with signal-cli daemon --numbers");
}
}
for (String u : daemonUsernames) {
try {
managers.add(App.loadManager(u, settingsPath, serviceEnvironment, trustNewIdentity));
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", u, e.getMessage());
}
}
for (var m : managers) {
signalControl.addManager(m);
}
conn.requestBusName(DbusConfig.getBusname());
logger.info("Starting daemon.");
signalControl.run();
} catch (DBusException | IOException e) {
} catch (DBusException e) {
logger.error("Dbus command failed", e);
throw new UserErrorException("Dbus command failed, daemon alreadytarted on this bus.");
} catch (IOException e ) {
logger.error("Dbus command failed", e);
throw new UnexpectedErrorException("Dbus command failed", e);
}
}
private void checkMacOS() throws UserErrorException {
if (System.getProperty("os.name").toLowerCase().startsWith("mac ")) {
String dBusVar = System.getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET");
if (dBusVar == null || dBusVar.isBlank()) {
String message = "\n\n" +
"*************************************" +
"\n\nDBUS_LAUNCHD_SESSION_BUS_SOCKET is not set. Issue the command:\n\n" +
"export DBUS_LAUNCHD_SESSION_BUS_SOCKET=$(launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET)\n" +
"\nand then try again.\n\n" +
"*************************************";
throw new UserErrorException(message);
}
}
}
private Thread run(
DBusConnection conn, String objectPath, Manager m, OutputWriter outputWriter, boolean ignoreAttachments
) throws DBusException {

View file

@ -5,19 +5,12 @@ import net.sourceforge.argparse4j.inf.Namespace;
import org.asamk.signal.OutputWriter;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import java.util.List;
public interface MultiLocalCommand extends LocalCommand {
void handleCommand(Namespace ns, List<Manager> m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException;
void handleCommand(Namespace ns, Manager m, SignalCreator c, OutputWriter outputWriter, TrustNewIdentity trustNewIdentity) throws CommandException;
void handleCommand(
Namespace ns, List<Manager> m, SignalCreator c, OutputWriter outputWriter
) throws CommandException;
@Override
default void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
handleCommand(ns, List.of(m), null, outputWriter);
}
}

View file

@ -2,11 +2,17 @@ package org.asamk.signal.commands;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.config.ServiceEnvironment;
import java.io.File;
import java.io.IOException;
public interface SignalCreator {
File getSettingsPath();
ServiceEnvironment getServiceEnvironment();
ProvisioningManager getNewProvisioningManager();
RegistrationManager getNewRegistrationManager(String username) throws IOException;

View file

@ -23,6 +23,7 @@ import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
@ -80,6 +81,11 @@ public class DbusManagerImpl implements Manager {
throw new UnsupportedOperationException();
}
@Override
public SignalAccount getAccount() {
throw new UnsupportedOperationException();
}
@Override
public Map<String, Pair<String, UUID>> areUsersRegistered(final Set<String> numbers) throws IOException {
final var numbersList = new ArrayList<>(numbers);

View file

@ -1,36 +1,65 @@
package org.asamk.signal.dbus;
import org.asamk.SignalControl;
import org.asamk.SignalControl.Error;
import org.asamk.signal.App;
import org.asamk.signal.BaseConfig;
import org.asamk.signal.DbusConfig;
import org.asamk.signal.DbusReceiveMessageHandler;
import org.asamk.signal.JsonDbusReceiveMessageHandler;
import org.asamk.signal.JsonWriter;
import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.DaemonCommand;
import org.asamk.signal.commands.SignalCreator;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.UserAlreadyExists;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class DbusSignalControlImpl implements org.asamk.SignalControl {
private final SignalCreator c;
private final Function<Manager, Thread> newManagerRunner;
private static SignalCreator c;
private static Function<Manager, Thread> newManagerRunner;
private final List<Pair<Manager, Thread>> receiveThreads = new ArrayList<>();
private final Object stopTrigger = new Object();
private final String objectPath;
private static List<Pair<Manager, Thread>> receiveThreads = new ArrayList<>();
private static Object stopTrigger = new Object();
private static String objectPath;
private static DBusConnection.DBusBusType busType;
public static RegistrationManager registrationManager;
public static ProvisioningManager provisioningManager;
private final static Logger logger = LoggerFactory.getLogger(DbusSignalControlImpl.class);
public DbusSignalControlImpl(
final SignalCreator c, final Function<Manager, Thread> newManagerRunner, final String objectPath
@ -38,9 +67,10 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
this.c = c;
this.newManagerRunner = newManagerRunner;
this.objectPath = objectPath;
this.busType = busType;
}
public void addManager(Manager m) {
public static void addManager(Manager m) {
var thread = newManagerRunner.apply(m);
if (thread == null) {
return;
@ -125,7 +155,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
) throws Error.Failure, Error.InvalidNumber {
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
final Manager manager = registrationManager.verifyAccount(verificationCode, pin);
logger.info("Registration of " + number + " verified");
addManager(manager);
registrationManager.close();
} catch (IOException | KeyBackupSystemNoDataException | KeyBackupServicePinException e) {
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
}
@ -139,9 +171,19 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
new Thread(() -> {
try {
final Manager manager = provisioningManager.finishDeviceLink(newDeviceName);
logger.info("Linking of " + newDeviceName + " successful");
addManager(manager);
} catch (IOException | TimeoutException | UserAlreadyExists e) {
e.printStackTrace();
//no need to close provisioningManager; it cleaned up during finishDeviceLink
} catch (TimeoutException e) {
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": Link request timed out, please try again.");
} catch (IOException e) {
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": Link request error: " + e.getMessage());
} catch (UserAlreadyExists e) {
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + ": The user "
+ e.getNumber()
+ " already exists\nDelete \""
+ e.getFileName()
+ "\" before trying again.");
}
}).start();
return deviceLinkUri.toString();
@ -155,6 +197,51 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
return BaseConfig.PROJECT_VERSION;
}
@Override
public void listen(String number) {
try {
File settingsPath = c.getSettingsPath();
List<String> usernames = Manager.getAllLocalNumbers(settingsPath);
if (!usernames.contains(number)) {
throw new Error.Failure("Listen: " + number + " is not registered.");
}
String objectPath = DbusConfig.getObjectPath(number);
DBusConnection.DBusBusType busType = DaemonCommand.dBusType;
ServiceEnvironment serviceEnvironment = c.getServiceEnvironment();
TrustNewIdentity trustNewIdentity = DaemonCommand.trustNewIdentity;
//create new manager for this number
final Manager m = App.loadManager(number, settingsPath, serviceEnvironment, trustNewIdentity);
addManager(m);
final var thread = new Thread(() -> {
try {
OutputWriter outputWriter = DaemonCommand.outputWriter;
boolean ignoreAttachments = false;
DBusConnection conn = DBusConnection.getConnection(busType);
while (!Thread.interrupted()) {
try {
final var receiveMessageHandler = outputWriter instanceof JsonWriter
? new JsonDbusReceiveMessageHandler(m, (JsonWriter) outputWriter, conn, objectPath)
: new DbusReceiveMessageHandler(m, (PlainTextWriter) outputWriter, conn, objectPath);
m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, receiveMessageHandler);
break;
} catch (IOException e) {
logger.warn("Receiving messages failed, retrying", e);
}
}
} catch (DBusException e) {
throw new Error.Failure(e.getClass().getSimpleName() + " Listen error: " + e.getMessage());
}
});
} catch (OverlappingFileLockException e) {
logger.warn("Ignoring {}: {}", number, e.getMessage());
throw new Error.Failure(e.getClass().getSimpleName() + " Already listening: " + e.getMessage());
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", number, e.getMessage());
throw new Error.Failure(e.getClass().getSimpleName() + " Listen error: " + e.getMessage());
}
}
@Override
public List<DBusPath> listAccounts() {
synchronized (receiveThreads) {