]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusHandler.java
b6799de741900e42a0bf3abbf26d8998992f3b40
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusHandler.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.signal.DbusConfig;
4 import org.asamk.signal.commands.exceptions.CommandException;
5 import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
6 import org.asamk.signal.commands.exceptions.UserErrorException;
7 import org.asamk.signal.manager.Manager;
8 import org.asamk.signal.manager.MultiAccountManager;
9 import org.freedesktop.dbus.connections.impl.DBusConnection;
10 import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
11 import org.freedesktop.dbus.exceptions.DBusException;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14
15 import java.util.ArrayList;
16 import java.util.List;
17
18 public class DbusHandler implements AutoCloseable {
19
20 private static final Logger logger = LoggerFactory.getLogger(DbusHandler.class);
21
22 private final boolean isDbusSystem;
23 private DBusConnection dBusConnection;
24 private final String busname;
25
26 private final List<AutoCloseable> closeables = new ArrayList<>();
27 private final DbusRunner dbusRunner;
28 private final boolean noReceiveOnStart;
29
30 public DbusHandler(
31 final boolean isDbusSystem,
32 final String busname,
33 final Manager m,
34 final boolean noReceiveOnStart
35 ) {
36 this.isDbusSystem = isDbusSystem;
37 this.dbusRunner = (connection) -> {
38 try {
39 exportDbusObject(connection, DbusConfig.getObjectPath(), m).join();
40 } catch (InterruptedException ignored) {
41 }
42 };
43 this.noReceiveOnStart = noReceiveOnStart;
44 this.busname = busname;
45 }
46
47 public DbusHandler(
48 final boolean isDbusSystem,
49 final String busname,
50 final MultiAccountManager c,
51 final boolean noReceiveOnStart
52 ) {
53 this.isDbusSystem = isDbusSystem;
54 this.dbusRunner = (connection) -> {
55 final var signalControl = new DbusSignalControlImpl(c, DbusConfig.getObjectPath());
56 connection.exportObject(signalControl);
57
58 c.addOnManagerAddedHandler(m -> {
59 final var thread = exportManager(connection, m);
60 try {
61 thread.join();
62 } catch (InterruptedException ignored) {
63 }
64 });
65 c.addOnManagerRemovedHandler(m -> {
66 final var path = DbusConfig.getObjectPath(m.getSelfNumber());
67 try {
68 final var object = connection.getExportedObject(null, path);
69 if (object instanceof DbusSignalImpl dbusSignal) {
70 dbusSignal.close();
71 closeables.remove(dbusSignal);
72 }
73 } catch (DBusException ignored) {
74 }
75 });
76
77 final var initThreads = c.getManagers().stream().map(m -> exportManager(connection, m)).toList();
78
79 for (var t : initThreads) {
80 try {
81 t.join();
82 } catch (InterruptedException ignored) {
83 }
84 }
85 };
86 this.noReceiveOnStart = noReceiveOnStart;
87 this.busname = busname;
88 }
89
90 public void init() throws CommandException {
91 if (dBusConnection != null) {
92 throw new AssertionError("DbusHandler already initialized");
93 }
94 final var busType = isDbusSystem ? DBusConnection.DBusBusType.SYSTEM : DBusConnection.DBusBusType.SESSION;
95 logger.debug("Starting DBus server on {} bus: {}", busType, busname);
96 try {
97 dBusConnection = DBusConnectionBuilder.forType(busType).build();
98 dbusRunner.run(dBusConnection);
99 } catch (DBusException e) {
100 throw new UnexpectedErrorException("Dbus command failed: " + e.getMessage(), e);
101 } catch (UnsupportedOperationException e) {
102 throw new UserErrorException("Failed to connect to Dbus: " + e.getMessage(), e);
103 }
104
105 try {
106 dBusConnection.requestBusName(busname);
107 } catch (DBusException e) {
108 throw new UnexpectedErrorException("Dbus command failed, maybe signal-cli dbus daemon is already running: "
109 + e.getMessage(), e);
110 }
111
112 logger.info("Started DBus server on {} bus: {}", busType, busname);
113 }
114
115 @Override
116 public void close() throws Exception {
117 if (dBusConnection == null) {
118 return;
119 }
120 dBusConnection.close();
121 for (final var c : new ArrayList<>(closeables)) {
122 c.close();
123 }
124 closeables.clear();
125 dBusConnection = null;
126 }
127
128 private Thread exportDbusObject(final DBusConnection conn, final String objectPath, final Manager m) {
129 final var signal = new DbusSignalImpl(m, conn, objectPath, noReceiveOnStart);
130 closeables.add(signal);
131
132 return Thread.ofPlatform().name("dbus-init-" + m.getSelfNumber()).start(signal::initObjects);
133 }
134
135 private Thread exportManager(final DBusConnection conn, final Manager m) {
136 final var objectPath = DbusConfig.getObjectPath(m.getSelfNumber());
137 return exportDbusObject(conn, objectPath, m);
138 }
139
140 private interface DbusRunner {
141
142 void run(DBusConnection connection) throws DBusException;
143 }
144 }