]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java
Refactor Manager interface
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusSignalControlImpl.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.SignalControl;
4 import org.asamk.signal.BaseConfig;
5 import org.asamk.signal.DbusConfig;
6 import org.asamk.signal.commands.SignalCreator;
7 import org.asamk.signal.manager.Manager;
8 import org.asamk.signal.manager.ProvisioningManager;
9 import org.asamk.signal.manager.RegistrationManager;
10 import org.asamk.signal.manager.UserAlreadyExists;
11 import org.freedesktop.dbus.DBusPath;
12 import org.whispersystems.libsignal.util.Pair;
13 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
14 import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
15 import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
16 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
17
18 import java.io.IOException;
19 import java.net.URI;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.concurrent.TimeoutException;
23 import java.util.function.Function;
24 import java.util.stream.Collectors;
25
26 public class DbusSignalControlImpl implements org.asamk.SignalControl {
27
28 private final SignalCreator c;
29 private final Function<Manager, Thread> newManagerRunner;
30
31 private final List<Pair<Manager, Thread>> receiveThreads = new ArrayList<>();
32 private final Object stopTrigger = new Object();
33 private final String objectPath;
34
35 public DbusSignalControlImpl(
36 final SignalCreator c, final Function<Manager, Thread> newManagerRunner, final String objectPath
37 ) {
38 this.c = c;
39 this.newManagerRunner = newManagerRunner;
40 this.objectPath = objectPath;
41 }
42
43 public void addManager(Manager m) {
44 var thread = newManagerRunner.apply(m);
45 if (thread == null) {
46 return;
47 }
48 synchronized (receiveThreads) {
49 receiveThreads.add(new Pair<>(m, thread));
50 }
51 }
52
53 public void run() {
54 synchronized (stopTrigger) {
55 try {
56 stopTrigger.wait();
57 } catch (InterruptedException ignored) {
58 }
59 }
60
61 synchronized (receiveThreads) {
62 for (var t : receiveThreads) {
63 t.second().interrupt();
64 }
65 }
66 while (true) {
67 final Thread thread;
68 synchronized (receiveThreads) {
69 if (receiveThreads.size() == 0) {
70 break;
71 }
72 var pair = receiveThreads.remove(0);
73 thread = pair.second();
74 }
75 try {
76 thread.join();
77 } catch (InterruptedException ignored) {
78 }
79 }
80 }
81
82 @Override
83 public boolean isRemote() {
84 return false;
85 }
86
87 @Override
88 public String getObjectPath() {
89 return objectPath;
90 }
91
92 @Override
93 public void register(
94 final String number, final boolean voiceVerification
95 ) throws Error.Failure, Error.InvalidNumber {
96 registerWithCaptcha(number, voiceVerification, null);
97 }
98
99 @Override
100 public void registerWithCaptcha(
101 final String number, final boolean voiceVerification, final String captcha
102 ) throws Error.Failure, Error.InvalidNumber {
103 if (!PhoneNumberFormatter.isValidNumber(number, null)) {
104 throw new SignalControl.Error.InvalidNumber(
105 "Invalid username (phone number), make sure you include the country code.");
106 }
107 try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
108 registrationManager.register(voiceVerification, captcha);
109 } catch (CaptchaRequiredException e) {
110 String message = captcha == null ? "Captcha required for verification." : "Invalid captcha given.";
111 throw new SignalControl.Error.RequiresCaptcha(message);
112 } catch (IOException e) {
113 throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
114 }
115 }
116
117 @Override
118 public void verify(final String number, final String verificationCode) throws Error.Failure, Error.InvalidNumber {
119 verifyWithPin(number, verificationCode, null);
120 }
121
122 @Override
123 public void verifyWithPin(
124 final String number, final String verificationCode, final String pin
125 ) throws Error.Failure, Error.InvalidNumber {
126 try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
127 final Manager manager = registrationManager.verifyAccount(verificationCode, pin);
128 addManager(manager);
129 } catch (IOException | KeyBackupSystemNoDataException | KeyBackupServicePinException e) {
130 throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
131 }
132 }
133
134 @Override
135 public String link(final String newDeviceName) throws Error.Failure {
136 try {
137 final ProvisioningManager provisioningManager = c.getNewProvisioningManager();
138 final URI deviceLinkUri = provisioningManager.getDeviceLinkUri();
139 new Thread(() -> {
140 try {
141 final Manager manager = provisioningManager.finishDeviceLink(newDeviceName);
142 addManager(manager);
143 } catch (IOException | TimeoutException | UserAlreadyExists e) {
144 e.printStackTrace();
145 }
146 }).start();
147 return deviceLinkUri.toString();
148 } catch (TimeoutException | IOException e) {
149 throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
150 }
151 }
152
153 @Override
154 public String version() {
155 return BaseConfig.PROJECT_VERSION;
156 }
157
158 @Override
159 public List<DBusPath> listAccounts() {
160 synchronized (receiveThreads) {
161 return receiveThreads.stream()
162 .map(Pair::first)
163 .map(Manager::getSelfNumber)
164 .map(u -> new DBusPath(DbusConfig.getObjectPath(u)))
165 .collect(Collectors.toList());
166 }
167 }
168 }