import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.StickerPackInvalidException;
-import org.asamk.signal.manager.UntrustedIdentityException;
import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Group;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* This class implements the Manager interface using the DBus Signal interface, where possible.
private final Signal signal;
private final DBusConnection connection;
+ private final Set<ReceiveMessageHandler> weakHandlers = new HashSet<>();
private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
+ private final List<Runnable> closedListeners = new ArrayList<>();
private DBusSigHandler<Signal.MessageReceivedV2> dbusMsgHandler;
private DBusSigHandler<Signal.ReceiptReceivedV2> dbusRcptHandler;
private DBusSigHandler<Signal.SyncMessageReceivedV2> dbusSyncHandler;
@Override
public Configuration getConfiguration() {
- throw new UnsupportedOperationException();
+ final var configuration = getRemoteObject(new DBusPath(signal.getObjectPath() + "/Configuration"),
+ Signal.Configuration.class).GetAll("org.asamk.Signal.Configuration");
+ return new Configuration(Optional.of((Boolean) configuration.get("ReadReceipts").getValue()),
+ Optional.of((Boolean) configuration.get("UnidentifiedDeliveryIndicators").getValue()),
+ Optional.of((Boolean) configuration.get("TypingIndicators").getValue()),
+ Optional.of((Boolean) configuration.get("LinkPreviews").getValue()));
}
@Override
- public void updateConfiguration(Configuration configuration) throws IOException {
- throw new UnsupportedOperationException();
+ public void updateConfiguration(Configuration newConfiguration) throws IOException {
+ final var configuration = getRemoteObject(new DBusPath(signal.getObjectPath() + "/Configuration"),
+ Signal.Configuration.class);
+ newConfiguration.readReceipts()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "ReadReceipts", v));
+ newConfiguration.unidentifiedDeliveryIndicators()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration",
+ "UnidentifiedDeliveryIndicators",
+ v));
+ newConfiguration.typingIndicators()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "TypingIndicators", v));
+ newConfiguration.linkPreviews()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "LinkPreviews", v));
}
@Override
emptyIfNull(about),
emptyIfNull(aboutEmoji),
avatar == null ? "" : avatar.map(File::getPath).orElse(""),
- avatar != null && !avatar.isPresent());
+ avatar != null && avatar.isEmpty());
}
@Override
public void unregister() throws IOException {
- throw new UnsupportedOperationException();
+ signal.unregister();
}
@Override
public void deleteAccount() throws IOException {
- throw new UnsupportedOperationException();
+ signal.deleteAccount();
}
@Override
(long) device.get("Created").getValue(),
(long) device.get("LastSeen").getValue(),
thisDevice.equals(d.getObjectPath()));
- }).collect(Collectors.toList());
+ }).toList();
}
@Override
@Override
public List<Group> getGroups() {
final var groups = signal.listGroups();
- return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).collect(Collectors.toList());
+ return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).toList();
}
@Override
@Override
public void deleteGroup(final GroupId groupId) throws IOException {
- throw new UnsupportedOperationException();
+ final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
+ group.deleteGroup();
}
@Override
final String name, final Set<RecipientIdentifier.Single> members, final File avatarFile
) throws IOException, AttachmentInvalidException {
final var newGroupId = signal.createGroup(emptyIfNull(name),
- members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
+ members.stream().map(RecipientIdentifier.Single::getIdentifier).toList(),
avatarFile == null ? "" : avatarFile.getPath());
return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
}
: GroupPermission.EVERY_MEMBER.name());
}
if (updateGroup.getMembers() != null) {
- group.addMembers(updateGroup.getMembers()
- .stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ group.addMembers(updateGroup.getMembers().stream().map(RecipientIdentifier.Single::getIdentifier).toList());
}
if (updateGroup.getRemoveMembers() != null) {
group.removeMembers(updateGroup.getRemoveMembers()
.stream()
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ .toList());
}
if (updateGroup.getAdmins() != null) {
- group.addAdmins(updateGroup.getAdmins()
- .stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ group.addAdmins(updateGroup.getAdmins().stream().map(RecipientIdentifier.Single::getIdentifier).toList());
}
if (updateGroup.getRemoveAdmins() != null) {
group.removeAdmins(updateGroup.getRemoveAdmins()
.stream()
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ .toList());
}
if (updateGroup.isResetGroupLink()) {
group.resetLink();
}
@Override
- public void sendTypingMessage(
+ public SendMessageResults sendTypingMessage(
final TypingAction action, final Set<RecipientIdentifier> recipients
- ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
- for (final var recipient : recipients) {
- if (recipient instanceof RecipientIdentifier.Single) {
- signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(),
- action == TypingAction.STOP);
- } else if (recipient instanceof RecipientIdentifier.Group) {
- throw new UnsupportedOperationException();
- }
- }
+ ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+ return handleMessage(recipients, numbers -> {
+ numbers.forEach(n -> signal.sendTyping(n, action == TypingAction.STOP));
+ return 0L;
+ }, () -> {
+ signal.sendTyping(signal.getSelfNumber(), action == TypingAction.STOP);
+ return 0L;
+ }, groupId -> {
+ signal.sendGroupTyping(groupId, action == TypingAction.STOP);
+ return 0L;
+ });
}
@Override
- public void sendReadReceipt(
+ public SendMessageResults sendReadReceipt(
final RecipientIdentifier.Single sender, final List<Long> messageIds
- ) throws IOException, UntrustedIdentityException {
+ ) {
signal.sendReadReceipt(sender.getIdentifier(), messageIds);
+ return new SendMessageResults(0, Map.of());
}
@Override
- public void sendViewedReceipt(
+ public SendMessageResults sendViewedReceipt(
final RecipientIdentifier.Single sender, final List<Long> messageIds
- ) throws IOException, UntrustedIdentityException {
+ ) {
signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
+ return new SendMessageResults(0, Map.of());
}
@Override
@Override
public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
- signal.sendEndSessionMessage(recipients.stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ signal.sendEndSessionMessage(recipients.stream().map(RecipientIdentifier.Single::getIdentifier).toList());
return new SendMessageResults(0, Map.of());
}
+ @Override
+ public void deleteRecipient(final RecipientIdentifier.Single recipient) throws IOException {
+ signal.deleteRecipient(recipient.getIdentifier());
+ }
+
+ @Override
+ public void deleteContact(final RecipientIdentifier.Single recipient) throws IOException {
+ signal.deleteContact(recipient.getIdentifier());
+ }
+
@Override
public void setContactName(
final RecipientIdentifier.Single recipient, final String name
}
@Override
- public void addReceiveHandler(final ReceiveMessageHandler handler) {
+ public void addReceiveHandler(final ReceiveMessageHandler handler, final boolean isWeakListener) {
synchronized (messageHandlers) {
- if (messageHandlers.size() == 0) {
- installMessageHandlers();
+ if (isWeakListener) {
+ weakHandlers.add(handler);
+ } else {
+ if (messageHandlers.size() == 0) {
+ installMessageHandlers();
+ }
+ messageHandlers.add(handler);
}
- messageHandlers.add(handler);
}
}
@Override
public void removeReceiveHandler(final ReceiveMessageHandler handler) {
synchronized (messageHandlers) {
+ weakHandlers.remove(handler);
messageHandlers.remove(handler);
if (messageHandlers.size() == 0) {
uninstallMessageHandlers();
throw new UnsupportedOperationException();
}
+ @Override
+ public void addClosedListener(final Runnable listener) {
+ synchronized (closedListeners) {
+ closedListeners.add(listener);
+ }
+ }
+
@Override
public void close() throws IOException {
synchronized (this) {
this.notify();
}
synchronized (messageHandlers) {
+ if (messageHandlers.size() > 0) {
+ uninstallMessageHandlers();
+ }
+ weakHandlers.clear();
messageHandlers.clear();
- uninstallMessageHandlers();
+ }
+ synchronized (closedListeners) {
+ closedListeners.forEach(Runnable::run);
+ closedListeners.clear();
}
}
.filter(r -> r instanceof RecipientIdentifier.Single)
.map(RecipientIdentifier.Single.class::cast)
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList());
+ .toList();
if (singleRecipients.size() > 0) {
timestamp = recipientsHandler.apply(singleRecipients);
}
.filter(r -> r instanceof RecipientIdentifier.Group)
.map(RecipientIdentifier.Group.class::cast)
.map(RecipientIdentifier.Group::groupId)
- .collect(Collectors.toList());
+ .toList();
for (final var groupId : groupRecipients) {
timestamp = groupHandler.apply(groupId.serialize());
}
false,
Optional.empty(),
Optional.empty(),
+ Optional.empty(),
getAttachments(extras),
Optional.empty(),
Optional.empty(),
List.of())),
Optional.empty(),
Optional.empty());
- synchronized (messageHandlers) {
- for (final var messageHandler : messageHandlers) {
- messageHandler.handleMessage(envelope, null);
- }
- }
+ notifyMessageHandlers(envelope);
};
connection.addSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
Optional.empty(),
Optional.empty(),
Optional.empty());
- synchronized (messageHandlers) {
- for (final var messageHandler : messageHandlers) {
- messageHandler.handleMessage(envelope, null);
- }
- }
+ notifyMessageHandlers(envelope);
};
connection.addSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
false,
Optional.empty(),
Optional.empty(),
+ Optional.empty(),
getAttachments(extras),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty())),
Optional.empty());
- synchronized (messageHandlers) {
- for (final var messageHandler : messageHandlers) {
- messageHandler.handleMessage(envelope, null);
- }
- }
+ notifyMessageHandlers(envelope);
};
connection.addSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
} catch (DBusException e) {
e.printStackTrace();
}
+ signal.subscribeReceive();
+ }
+
+ private void notifyMessageHandlers(final MessageEnvelope envelope) {
+ synchronized (messageHandlers) {
+ Stream.concat(messageHandlers.stream(), weakHandlers.stream())
+ .forEach(h -> h.handleMessage(envelope, null));
+ }
}
private void uninstallMessageHandlers() {
try {
+ signal.unsubscribeReceive();
connection.removeSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
connection.removeSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
connection.removeSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
getValue(a, "isVoiceNote"),
getValue(a, "isGif"),
getValue(a, "isBorderless"));
- }).collect(Collectors.toList());
+ }).toList();
}
@SuppressWarnings("unchecked")