Implement --delete flag for quitGroup

Closes #638
This commit is contained in:
AsamK 2021-06-12 11:33:19 +02:00
parent d1e760f447
commit 87406e2cdb
6 changed files with 82 additions and 32 deletions

View file

@ -6,6 +6,7 @@
### Added
- Added new parameters to `updateGroup` for group v2 features:
`--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
- Added new `--delete` parameter to `quitGroup`, to delete the local group data
### Fixed
- Prevent last admin of a group from leaving the group

View file

@ -49,6 +49,10 @@ public class AvatarStore {
deleteAvatar(getProfileAvatarFile(address));
}
public void deleteGroupAvatar(GroupId groupId) throws IOException {
deleteAvatar(getGroupAvatarFile(groupId));
}
private StreamDetails retrieveAvatar(final File avatarFile) throws IOException {
if (!avatarFile.exists()) {
return null;

View file

@ -779,35 +779,55 @@ public class Manager implements Closeable {
public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
GroupId groupId, Set<String> groupAdmins
) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
SignalServiceDataMessage.Builder messageBuilder;
var group = getGroupForUpdating(groupId);
if (group instanceof GroupInfoV1) {
return quitGroupV1((GroupInfoV1) group);
}
final var g = getGroupForUpdating(groupId);
if (g instanceof GroupInfoV1) {
var groupInfoV1 = (GroupInfoV1) g;
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT).withId(groupId.serialize()).build();
messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
final var newAdmins = getSignalServiceAddresses(groupAdmins);
try {
return quitGroupV2((GroupInfoV2) group, newAdmins);
} catch (ConflictException e) {
// Detected conflicting update, refreshing group and trying again
group = getGroup(groupId, true);
return quitGroupV2((GroupInfoV2) group, newAdmins);
}
}
private Pair<Long, List<SendMessageResult>> quitGroupV1(final GroupInfoV1 groupInfoV1) throws IOException {
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
.withId(groupInfoV1.getGroupId().serialize())
.build();
var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
groupInfoV1.removeMember(account.getSelfRecipientId());
account.getGroupStore().updateGroup(groupInfoV1);
} else {
final var groupInfoV2 = (GroupInfoV2) g;
final var currentAdmins = g.getAdminMembers();
final var newAdmins = getSignalServiceAddresses(groupAdmins);
return sendMessage(messageBuilder, groupInfoV1.getMembersWithout(account.getSelfRecipientId()));
}
private Pair<Long, List<SendMessageResult>> quitGroupV2(
final GroupInfoV2 groupInfoV2, final Set<RecipientId> newAdmins
) throws LastGroupAdminException, IOException {
final var currentAdmins = groupInfoV2.getAdminMembers();
newAdmins.removeAll(currentAdmins);
newAdmins.retainAll(g.getMembers());
newAdmins.retainAll(groupInfoV2.getMembers());
if (currentAdmins.contains(getSelfRecipientId())
&& currentAdmins.size() == 1
&& g.getMembers().size() > 1
&& groupInfoV2.getMembers().size() > 1
&& newAdmins.size() == 0) {
// Last admin can't leave the group, unless she's also the last member
throw new LastGroupAdminException(g.getGroupId(), g.getTitle());
throw new LastGroupAdminException(groupInfoV2.getGroupId(), groupInfoV2.getTitle());
}
final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
account.getGroupStore().updateGroup(groupInfoV2);
return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId()));
}
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
public void deleteGroup(GroupId groupId) throws IOException {
account.getGroupStore().deleteGroup(groupId);
avatarStore.deleteGroupAvatar(groupId);
}
public Pair<GroupId, List<SendMessageResult>> createGroup(
@ -2013,6 +2033,7 @@ public class Manager implements Closeable {
Exception exception = null;
final CachedMessage[] cachedMessage = {null};
account.setLastReceiveTimestamp(System.currentTimeMillis());
logger.debug("Checking for new message from server");
try {
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
final var recipientId = envelope1.hasSource()
@ -2021,6 +2042,7 @@ public class Manager implements Closeable {
// store message on disk, before acknowledging receipt to the server
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
});
logger.debug("New message received from server");
if (result.isPresent()) {
envelope = result.get();
} else {

View file

@ -133,7 +133,11 @@ public class GroupStore {
saver.save(storage);
}
public void deleteGroupV1(GroupIdV1 groupId) {
public void deleteGroupV1(GroupIdV1 groupIdV1) {
deleteGroup(groupIdV1);
}
public void deleteGroup(GroupId groupId) {
final Storage storage;
synchronized (groups) {
groups.remove(groupId);

View file

@ -285,6 +285,9 @@ If the user is a pending member, this command will decline the group invitation.
*-g* GROUP, *--group* GROUP::
Specify the recipient group ID in base64 encoding.
*--delete*::
Delete local group data completely after quitting group.
=== listGroups
Show a list of known groups and related information.

View file

@ -1,5 +1,6 @@
package org.asamk.signal.commands;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
@ -14,6 +15,8 @@ import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
@ -24,10 +27,15 @@ import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResu
public class QuitGroupCommand implements LocalCommand {
private final static Logger logger = LoggerFactory.getLogger(QuitGroupCommand.class);
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.help("Send a quit group message to all group members and remove self from member list.");
subparser.addArgument("-g", "--group").required(true).help("Specify the recipient group ID.");
subparser.addArgument("--delete")
.action(Arguments.storeTrue())
.help("Delete local group data completely after quitting group.");
subparser.addArgument("--admin")
.nargs("*")
.help("Specify one or more members to make a group admin, required if you're currently the only admin.");
@ -46,13 +54,21 @@ public class QuitGroupCommand implements LocalCommand {
var groupAdmins = ns.<String>getList("admin");
try {
try {
final var results = m.sendQuitGroupMessage(groupId,
groupAdmins == null ? Set.of() : new HashSet<>(groupAdmins));
handleTimestampAndSendMessageResults(writer, results.first(), results.second());
} catch (NotAGroupMemberException e) {
logger.info("User is not a group member");
}
if (ns.getBoolean("delete")) {
logger.debug("Deleting group {}", groupId);
m.deleteGroup(groupId);
}
} catch (IOException e) {
throw new IOErrorException("Failed to send message: " + e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
} catch (GroupNotFoundException e) {
throw new UserErrorException("Failed to send to group: " + e.getMessage());
} catch (InvalidNumberException e) {
throw new UserErrorException("Failed to parse admin number: " + e.getMessage());