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