GroupsV2 state mapping.

This commit is contained in:
Alan Evans
2020-04-09 18:09:47 -03:00
committed by Greyson Parrelli
parent 4e0279200f
commit 7bf090fdab
6 changed files with 361 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
package org.thoughtcrime.securesms.groups.v2.processing;
import androidx.annotation.NonNull;
import java.util.Collection;
/**
* Pair of log entries applied and a new {@link GlobalGroupState}.
*/
final class AdvanceGroupStateResult {
@NonNull private final Collection<GroupLogEntry> processedLogEntries;
@NonNull private final GlobalGroupState newGlobalGroupState;
AdvanceGroupStateResult(@NonNull Collection<GroupLogEntry> processedLogEntries,
@NonNull GlobalGroupState newGlobalGroupState)
{
this.processedLogEntries = processedLogEntries;
this.newGlobalGroupState = newGlobalGroupState;
}
@NonNull Collection<GroupLogEntry> getProcessedLogEntries() {
return processedLogEntries;
}
@NonNull GlobalGroupState getNewGlobalGroupState() {
return newGlobalGroupState;
}
}

View File

@@ -0,0 +1,43 @@
package org.thoughtcrime.securesms.groups.v2.processing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import java.util.Collection;
import java.util.List;
/**
* Combination of Local and Server group state.
*/
final class GlobalGroupState {
@Nullable private final DecryptedGroup localState;
@NonNull private final List<GroupLogEntry> history;
GlobalGroupState(@Nullable DecryptedGroup localState,
@NonNull List<GroupLogEntry> serverStates)
{
this.localState = localState;
this.history = serverStates;
}
@Nullable DecryptedGroup getLocalState() {
return localState;
}
@NonNull Collection<GroupLogEntry> getHistory() {
return history;
}
int getLatestVersionNumber() {
if (history.isEmpty()) {
if (localState == null) {
throw new AssertionError();
}
return localState.getVersion();
}
return history.get(history.size() - 1).getGroup().getVersion();
}
}

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.groups.v2.processing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
/**
* Pair of a group state and optionally the corresponding change.
* <p>
* Changes are typically not available for pending members.
*/
final class GroupLogEntry {
@NonNull private final DecryptedGroup group;
@Nullable private final DecryptedGroupChange change;
GroupLogEntry(@NonNull DecryptedGroup group, @Nullable DecryptedGroupChange change) {
if (change != null && group.getVersion() != change.getVersion()) {
throw new AssertionError();
}
this.group = group;
this.change = change;
}
@NonNull DecryptedGroup getGroup() {
return group;
}
@Nullable DecryptedGroupChange getChange() {
return change;
}
}

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.groups.v2.processing;
import androidx.annotation.NonNull;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
final class GroupStateMapper {
static final int LATEST = Integer.MAX_VALUE;
private static final Comparator<GroupLogEntry> BY_VERSION = (o1, o2) -> Integer.compare(o1.getGroup().getVersion(), o2.getGroup().getVersion());
private GroupStateMapper() {
}
/**
* Given an input {@link GlobalGroupState} and a {@param maximumVersionToApply}, returns a result
* containing what the new local group state should be, and any remaining version history to apply.
* <p>
* Function is pure.
* @param maximumVersionToApply Use {@link #LATEST} to apply the very latest.
*/
static @NonNull AdvanceGroupStateResult partiallyAdvanceGroupState(@NonNull GlobalGroupState inputState,
int maximumVersionToApply)
{
final ArrayList<GroupLogEntry> statesToApplyNow = new ArrayList<>(inputState.getHistory().size());
final ArrayList<GroupLogEntry> statesToApplyLater = new ArrayList<>(inputState.getHistory().size());
final DecryptedGroup newLocalState;
final GlobalGroupState newGlobalGroupState;
for (GroupLogEntry entry : inputState.getHistory()) {
if (inputState.getLocalState() != null &&
inputState.getLocalState().getVersion() >= entry.getGroup().getVersion())
{
continue;
}
if (entry.getGroup().getVersion() > maximumVersionToApply) {
statesToApplyLater.add(entry);
} else {
statesToApplyNow.add(entry);
}
}
Collections.sort(statesToApplyNow, BY_VERSION);
Collections.sort(statesToApplyLater, BY_VERSION);
if (statesToApplyNow.size() > 0) {
newLocalState = statesToApplyNow.get(statesToApplyNow.size() - 1)
.getGroup();
} else {
newLocalState = inputState.getLocalState();
}
newGlobalGroupState = new GlobalGroupState(newLocalState, statesToApplyLater);
return new AdvanceGroupStateResult(statesToApplyNow, newGlobalGroupState);
}
}