]> nmode's Git Repositories - signal-cli/commitdiff
Implement --delete flag for quitGroup
authorAsamK <asamk@gmx.de>
Sat, 12 Jun 2021 09:33:19 +0000 (11:33 +0200)
committerAsamK <asamk@gmx.de>
Sat, 12 Jun 2021 09:33:19 +0000 (11:33 +0200)
Closes #638

CHANGELOG.md
lib/src/main/java/org/asamk/signal/manager/AvatarStore.java
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/commands/QuitGroupCommand.java

index 26b0cedbf6081a2deac97d147ef30bec485e66ad..8dae42af9200486fff96a8bcacf77d7bdb8b249e 100644 (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
 - 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
 
 ### Fixed
 - Prevent last admin of a group from leaving the group
index 3ed9919484781a3a36f6e746fdf0656b09411ea4..8a1e61729872e9befc26f7f488f569c57382a30e 100644 (file)
@@ -49,6 +49,10 @@ public class AvatarStore {
         deleteAvatar(getProfileAvatarFile(address));
     }
 
         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;
     private StreamDetails retrieveAvatar(final File avatarFile) throws IOException {
         if (!avatarFile.exists()) {
             return null;
index 08789d9581589c5a7ada04be362e8fc17f395361..55b57828eda9581904655ce4ea34532f48b706da 100644 (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 {
     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);
-            groupInfoV1.removeMember(account.getSelfRecipientId());
-            account.getGroupStore().updateGroup(groupInfoV1);
-        } else {
-            final var groupInfoV2 = (GroupInfoV2) g;
-            final var currentAdmins = g.getAdminMembers();
-            final var newAdmins = getSignalServiceAddresses(groupAdmins);
-            newAdmins.removeAll(currentAdmins);
-            newAdmins.retainAll(g.getMembers());
-            if (currentAdmins.contains(getSelfRecipientId())
-                    && currentAdmins.size() == 1
-                    && g.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());
-            }
-            final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
-            groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
-            messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
-            account.getGroupStore().updateGroup(groupInfoV2);
+        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);
         }
         }
+    }
 
 
-        return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
+    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);
+        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(groupInfoV2.getMembers());
+        if (currentAdmins.contains(getSelfRecipientId())
+                && currentAdmins.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(groupInfoV2.getGroupId(), groupInfoV2.getTitle());
+        }
+        final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
+        groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
+        var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
+        account.getGroupStore().updateGroup(groupInfoV2);
+        return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId()));
+    }
+
+    public void deleteGroup(GroupId groupId) throws IOException {
+        account.getGroupStore().deleteGroup(groupId);
+        avatarStore.deleteGroupAvatar(groupId);
     }
 
     public Pair<GroupId, List<SendMessageResult>> createGroup(
     }
 
     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());
             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()
             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);
                 });
                     // 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 {
                 if (result.isPresent()) {
                     envelope = result.get();
                 } else {
index 19fc7564614fe450948a27ecc47f67b6b84d1e1b..dbbebdf05f5028d4f395521b2eee862c8cff1b5c 100644 (file)
@@ -133,7 +133,11 @@ public class GroupStore {
         saver.save(storage);
     }
 
         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);
         final Storage storage;
         synchronized (groups) {
             groups.remove(groupId);
index e73e7cdc21b8e13625f05448c08e3dd3e7d1789e..8e011c901b411bb033f01ca889d459dd3b81c284 100644 (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.
 
 *-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.
 === listGroups
 
 Show a list of known groups and related information.
index 488c4100effe92522ed199ac4bee7d4ea218c25d..0a53c638d297ebe352642eae683c4e67dcb4e1a0 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal.commands;
 
 package org.asamk.signal.commands;
 
+import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 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.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;
 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 {
 
 
 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.");
     @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.");
         subparser.addArgument("--admin")
                 .nargs("*")
                 .help("Specify one or more members to make a group admin, required if you're currently the only admin.");
@@ -47,12 +55,20 @@ public class QuitGroupCommand implements LocalCommand {
         var groupAdmins = ns.<String>getList("admin");
 
         try {
         var groupAdmins = ns.<String>getList("admin");
 
         try {
-            final var results = m.sendQuitGroupMessage(groupId,
-                    groupAdmins == null ? Set.of() : new HashSet<>(groupAdmins));
-            handleTimestampAndSendMessageResults(writer, results.first(), results.second());
+            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 (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());
             throw new UserErrorException("Failed to send to group: " + e.getMessage());
         } catch (InvalidNumberException e) {
             throw new UserErrorException("Failed to parse admin number: " + e.getMessage());