462 lines
20 KiB
Java
462 lines
20 KiB
Java
|
package com.discord.stores;
|
||
|
|
||
|
import androidx.annotation.NonNull;
|
||
|
import androidx.annotation.Nullable;
|
||
|
import c.a.u.a;
|
||
|
import c.a.u.b;
|
||
|
import com.discord.api.message.reaction.MessageReaction;
|
||
|
import com.discord.api.message.reaction.MessageReactionEmoji;
|
||
|
import com.discord.api.message.reaction.MessageReactionUpdate;
|
||
|
import com.discord.models.message.Message;
|
||
|
import com.discord.stores.StoreMessagesLoader;
|
||
|
import com.discord.utilities.message.LocalMessageCreatorsKt;
|
||
|
import com.discord.utilities.message.MessageUtils;
|
||
|
import com.discord.utilities.persister.Persister;
|
||
|
import com.discord.utilities.time.ClockFactory;
|
||
|
import j0.l.e.j;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Collections;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.Iterator;
|
||
|
import java.util.LinkedHashMap;
|
||
|
import java.util.List;
|
||
|
import java.util.ListIterator;
|
||
|
import java.util.Map;
|
||
|
import java.util.Set;
|
||
|
import java.util.TreeMap;
|
||
|
import java.util.concurrent.TimeUnit;
|
||
|
import rx.Observable;
|
||
|
import rx.Subscription;
|
||
|
import rx.subjects.BehaviorSubject;
|
||
|
import rx.subjects.SerializedSubject;
|
||
|
import rx.subjects.Subject;
|
||
|
public class StoreMessagesHolder {
|
||
|
private static final int CACHE_MAX_CHANNELS = 8;
|
||
|
private static final int CACHE_MAX_MESSAGES = 10;
|
||
|
private static final int CACHE_PERSIST_INTERVAL = 60000;
|
||
|
private static final int MAX_MESSAGES_PER_CHANNEL = 200;
|
||
|
private static final int MAX_MESSAGES_PER_CHANNEL_TRIM = 100;
|
||
|
private final Object $lock = new Object[0];
|
||
|
private final Set<Long> activeChannels = new HashSet();
|
||
|
private final Persister<Map<Long, List<Message>>> cache = new Persister<>("STORE_MESSAGES_CACHE_V36", new HashMap());
|
||
|
private boolean cacheEnabled;
|
||
|
private Subscription cachePersistSubscription;
|
||
|
private long cachePersistedAt = ClockFactory.get().currentTimeMillis();
|
||
|
private Map<Long, List<Message>> cacheSnapshot = Collections.emptyMap();
|
||
|
private final Set<Long> detachedChannels;
|
||
|
private Subject<Set<Long>, Set<Long>> detachedChannelsSubject;
|
||
|
private final Map<String, Long> messageNonceIds = new HashMap();
|
||
|
private final LinkedHashMap<Long, TreeMap<Long, Message>> messages = new LinkedHashMap<>();
|
||
|
private final Subject<Map<Long, List<Message>>, Map<Long, List<Message>>> messagesPublisher = new SerializedSubject(BehaviorSubject.k0());
|
||
|
private Map<Long, List<Message>> messagesSnapshot = Collections.emptyMap();
|
||
|
private long myUserId;
|
||
|
private long selectedChannelId;
|
||
|
private final Set<Long> staleMessages = new HashSet();
|
||
|
private final Set<Long> updatedChannels = new HashSet();
|
||
|
|
||
|
public StoreMessagesHolder() {
|
||
|
HashSet hashSet = new HashSet();
|
||
|
this.detachedChannels = hashSet;
|
||
|
this.detachedChannelsSubject = new SerializedSubject(BehaviorSubject.l0(new HashSet(hashSet)));
|
||
|
}
|
||
|
|
||
|
private static Message addReaction(Message message, MessageReactionEmoji messageReactionEmoji, boolean z2) {
|
||
|
MessageReaction messageReaction;
|
||
|
Map<String, MessageReaction> reactionsMap = message.getReactionsMap();
|
||
|
String c2 = messageReactionEmoji.c();
|
||
|
if (z2 && reactionsMap.containsKey(c2) && reactionsMap.get(c2).c()) {
|
||
|
return message;
|
||
|
}
|
||
|
LinkedHashMap linkedHashMap = new LinkedHashMap(reactionsMap);
|
||
|
boolean z3 = true;
|
||
|
if (reactionsMap.containsKey(c2)) {
|
||
|
MessageReaction messageReaction2 = (MessageReaction) linkedHashMap.get(messageReactionEmoji.c());
|
||
|
int a = messageReaction2.a() + 1;
|
||
|
MessageReactionEmoji b = messageReaction2.b();
|
||
|
if (!messageReaction2.c() && !z2) {
|
||
|
z3 = false;
|
||
|
}
|
||
|
messageReaction = new MessageReaction(a, b, z3);
|
||
|
} else {
|
||
|
messageReaction = new MessageReaction(1, messageReactionEmoji, z2);
|
||
|
}
|
||
|
linkedHashMap.put(c2, messageReaction);
|
||
|
return LocalMessageCreatorsKt.createWithReactions(message, linkedHashMap);
|
||
|
}
|
||
|
|
||
|
private Map<Long, List<Message>> computeMessagesCache() {
|
||
|
HashMap hashMap = new HashMap();
|
||
|
ListIterator listIterator = new ArrayList(this.messages.entrySet()).listIterator(this.messages.size());
|
||
|
int i = 8;
|
||
|
while (listIterator.hasPrevious() && i > 0) {
|
||
|
Map.Entry entry = (Map.Entry) listIterator.previous();
|
||
|
long longValue = ((Long) entry.getKey()).longValue();
|
||
|
Map<?, Message> map = (Map) entry.getValue();
|
||
|
if (map.size() > 0) {
|
||
|
hashMap.put(Long.valueOf(longValue), computeMessagesCacheSubList(longValue, map));
|
||
|
i--;
|
||
|
}
|
||
|
}
|
||
|
return hashMap;
|
||
|
}
|
||
|
|
||
|
private List<Message> computeMessagesCacheSubList(long j, @NonNull Map<?, Message> map) {
|
||
|
return new ArrayList(new ArrayList(map.values()).subList(Math.max(0, map.size() - (j == this.selectedChannelId ? 20 : 10)), map.size()));
|
||
|
}
|
||
|
|
||
|
private boolean isChannelActive(long j) {
|
||
|
return this.activeChannels.contains(Long.valueOf(j));
|
||
|
}
|
||
|
|
||
|
private boolean isChannelDetached(long j) {
|
||
|
return this.detachedChannels.contains(Long.valueOf(j));
|
||
|
}
|
||
|
|
||
|
private void messageCacheTryPersist() {
|
||
|
synchronized (this.$lock) {
|
||
|
if (this.cacheEnabled) {
|
||
|
long currentTimeMillis = ClockFactory.get().currentTimeMillis();
|
||
|
long j = this.cachePersistedAt + 60000;
|
||
|
if (j < currentTimeMillis) {
|
||
|
this.cachePersistedAt = currentTimeMillis;
|
||
|
Map<Long, List<Message>> computeMessagesCache = computeMessagesCache();
|
||
|
if (!this.cacheSnapshot.equals(computeMessagesCache)) {
|
||
|
this.cacheSnapshot = computeMessagesCache;
|
||
|
this.cache.set(computeMessagesCache);
|
||
|
}
|
||
|
} else {
|
||
|
long j2 = (j - currentTimeMillis) + 1000;
|
||
|
Subscription subscription = this.cachePersistSubscription;
|
||
|
if (subscription != null) {
|
||
|
subscription.unsubscribe();
|
||
|
}
|
||
|
this.cachePersistSubscription = new j(null).q(j2, TimeUnit.MILLISECONDS).W(new b(this), a.i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void publishIfUpdated() {
|
||
|
publishIfUpdated(false);
|
||
|
}
|
||
|
|
||
|
private void publishIfUpdated(boolean z2) {
|
||
|
if (!this.updatedChannels.isEmpty() || z2) {
|
||
|
HashMap hashMap = new HashMap();
|
||
|
for (Long l : this.updatedChannels) {
|
||
|
long longValue = l.longValue();
|
||
|
hashMap.put(Long.valueOf(longValue), new ArrayList(this.messages.get(Long.valueOf(longValue)).values()));
|
||
|
}
|
||
|
for (Map.Entry<Long, List<Message>> entry : this.messagesSnapshot.entrySet()) {
|
||
|
long longValue2 = entry.getKey().longValue();
|
||
|
if (!this.updatedChannels.contains(Long.valueOf(longValue2))) {
|
||
|
hashMap.put(Long.valueOf(longValue2), entry.getValue());
|
||
|
}
|
||
|
}
|
||
|
this.updatedChannels.clear();
|
||
|
this.messagesSnapshot = hashMap;
|
||
|
this.messagesPublisher.onNext(hashMap);
|
||
|
messageCacheTryPersist();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static Message removeReaction(Message message, MessageReactionEmoji messageReactionEmoji, boolean z2) {
|
||
|
Map<String, MessageReaction> reactionsMap = message.getReactionsMap();
|
||
|
String c2 = messageReactionEmoji.c();
|
||
|
if (!reactionsMap.containsKey(c2)) {
|
||
|
return message;
|
||
|
}
|
||
|
if (z2 && !reactionsMap.get(c2).c()) {
|
||
|
return message;
|
||
|
}
|
||
|
LinkedHashMap linkedHashMap = new LinkedHashMap(reactionsMap);
|
||
|
MessageReaction messageReaction = (MessageReaction) linkedHashMap.get(c2);
|
||
|
boolean z3 = true;
|
||
|
if (messageReaction.a() == 1) {
|
||
|
linkedHashMap.remove(c2);
|
||
|
} else {
|
||
|
int a = messageReaction.a() - 1;
|
||
|
MessageReactionEmoji b = messageReaction.b();
|
||
|
if (!messageReaction.c() || z2) {
|
||
|
z3 = false;
|
||
|
}
|
||
|
linkedHashMap.put(c2, new MessageReaction(a, b, z3));
|
||
|
}
|
||
|
if (linkedHashMap.isEmpty()) {
|
||
|
linkedHashMap = null;
|
||
|
}
|
||
|
return LocalMessageCreatorsKt.createWithReactions(message, linkedHashMap);
|
||
|
}
|
||
|
|
||
|
private boolean updateDetachedState(long j, Map<Long, Message> map, boolean z2, boolean z3, boolean z4) {
|
||
|
int size = map.size();
|
||
|
boolean z5 = true;
|
||
|
boolean z6 = size >= 200;
|
||
|
if (z6) {
|
||
|
int i = z2 ? 100 : size - 100;
|
||
|
Iterator<Map.Entry<Long, Message>> it = map.entrySet().iterator();
|
||
|
int i2 = 0;
|
||
|
while (it.hasNext()) {
|
||
|
it.next();
|
||
|
if ((z2 && i2 >= i) || (!z2 && i2 < i)) {
|
||
|
it.remove();
|
||
|
}
|
||
|
i2++;
|
||
|
}
|
||
|
}
|
||
|
boolean isChannelDetached = isChannelDetached(j);
|
||
|
if (!z6 || !z2 || z3) {
|
||
|
z5 = false;
|
||
|
}
|
||
|
if (!isChannelDetached && (z5 || z4)) {
|
||
|
this.detachedChannels.add(Long.valueOf(j));
|
||
|
this.detachedChannelsSubject.onNext(new HashSet(this.detachedChannels));
|
||
|
} else if (isChannelDetached && z3) {
|
||
|
this.detachedChannels.remove(Long.valueOf(j));
|
||
|
this.detachedChannelsSubject.onNext(new HashSet(this.detachedChannels));
|
||
|
}
|
||
|
return z6;
|
||
|
}
|
||
|
|
||
|
public /* synthetic */ void a(Object obj) {
|
||
|
messageCacheTryPersist();
|
||
|
}
|
||
|
|
||
|
public void addMessages(@NonNull List<Message> list) {
|
||
|
boolean z2;
|
||
|
synchronized (this.$lock) {
|
||
|
for (Message message : list) {
|
||
|
long channelId = message.getChannelId();
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(channelId));
|
||
|
if (isChannelActive(channelId) && !isChannelDetached(channelId)) {
|
||
|
String nonce = message.getNonce();
|
||
|
if (message.isLocal()) {
|
||
|
this.messageNonceIds.put(nonce, Long.valueOf(message.getId()));
|
||
|
} else {
|
||
|
Long l = this.messageNonceIds.get(nonce);
|
||
|
if (l != null) {
|
||
|
this.messageNonceIds.remove(nonce);
|
||
|
if (treeMap.containsKey(l)) {
|
||
|
treeMap.remove(l);
|
||
|
z2 = true;
|
||
|
treeMap.put(Long.valueOf(message.getId()), message);
|
||
|
if (!updateDetachedState(channelId, treeMap, false, true, false) || !z2) {
|
||
|
this.updatedChannels.add(Long.valueOf(channelId));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
z2 = false;
|
||
|
treeMap.put(Long.valueOf(message.getId()), message);
|
||
|
if (!updateDetachedState(channelId, treeMap, false, true, false)) {
|
||
|
}
|
||
|
this.updatedChannels.add(Long.valueOf(channelId));
|
||
|
}
|
||
|
}
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void deleteMessages(long j, @Nullable List<Long> list) {
|
||
|
synchronized (this.$lock) {
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(j));
|
||
|
if (isChannelActive(j) && list != null) {
|
||
|
if (!list.isEmpty()) {
|
||
|
for (Long l : list) {
|
||
|
long longValue = l.longValue();
|
||
|
if (treeMap.containsKey(Long.valueOf(longValue))) {
|
||
|
treeMap.remove(Long.valueOf(longValue));
|
||
|
this.updatedChannels.add(Long.valueOf(j));
|
||
|
}
|
||
|
}
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Observable<Set<Long>> getDetachedChannelSubject() {
|
||
|
return this.detachedChannelsSubject;
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
public TreeMap<Long, Message> getMessagesForChannel(Long l) {
|
||
|
TreeMap<Long, Message> treeMap;
|
||
|
synchronized (this.$lock) {
|
||
|
treeMap = this.messages.get(l);
|
||
|
}
|
||
|
return treeMap;
|
||
|
}
|
||
|
|
||
|
public Observable<Map<Long, List<Message>>> getMessagesPublisher() {
|
||
|
return this.messagesPublisher;
|
||
|
}
|
||
|
|
||
|
public void init(boolean z2) {
|
||
|
synchronized (this.$lock) {
|
||
|
if (z2) {
|
||
|
for (Map.Entry<Long, List<Message>> entry : this.cache.get().entrySet()) {
|
||
|
if (entry != null) {
|
||
|
if (entry.getKey() != null) {
|
||
|
long longValue = entry.getKey().longValue();
|
||
|
this.messages.put(Long.valueOf(longValue), new TreeMap<>(MessageUtils.getSORT_BY_IDS_COMPARATOR()));
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(longValue));
|
||
|
for (Message message : entry.getValue()) {
|
||
|
this.staleMessages.add(Long.valueOf(message.getId()));
|
||
|
treeMap.put(Long.valueOf(message.getId()), message);
|
||
|
}
|
||
|
this.updatedChannels.add(Long.valueOf(longValue));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.cacheEnabled = z2;
|
||
|
publishIfUpdated(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void invalidate() {
|
||
|
synchronized (this.$lock) {
|
||
|
for (Map.Entry<Long, TreeMap<Long, Message>> entry : this.messages.entrySet()) {
|
||
|
for (Map.Entry<Long, Message> entry2 : entry.getValue().entrySet()) {
|
||
|
this.staleMessages.add(Long.valueOf(entry2.getValue().getId()));
|
||
|
}
|
||
|
}
|
||
|
this.activeChannels.clear();
|
||
|
this.activeChannels.add(Long.valueOf(this.selectedChannelId));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void loadMessageChunks(@NonNull List<StoreMessagesLoader.ChannelChunk> list) {
|
||
|
synchronized (this.$lock) {
|
||
|
for (StoreMessagesLoader.ChannelChunk channelChunk : list) {
|
||
|
List<Message> messages = channelChunk.getMessages();
|
||
|
long channelId = channelChunk.getChannelId();
|
||
|
if (this.activeChannels.contains(Long.valueOf(channelId))) {
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(channelId));
|
||
|
boolean isChannelDetached = isChannelDetached(channelId);
|
||
|
boolean isJump = channelChunk.isJump();
|
||
|
boolean isInitial = channelChunk.isInitial();
|
||
|
boolean isPresent = channelChunk.isPresent();
|
||
|
if (isInitial || isJump) {
|
||
|
Iterator<Map.Entry<Long, Message>> it = treeMap.entrySet().iterator();
|
||
|
while (it.hasNext()) {
|
||
|
Long valueOf = Long.valueOf(it.next().getValue().getId());
|
||
|
if (this.staleMessages.contains(valueOf) || isChannelDetached || isJump) {
|
||
|
it.remove();
|
||
|
this.staleMessages.remove(valueOf);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (Message message : messages) {
|
||
|
treeMap.put(Long.valueOf(message.getId()), message);
|
||
|
}
|
||
|
updateDetachedState(channelId, treeMap, channelChunk.isAppendingTop(), isPresent, isJump);
|
||
|
this.updatedChannels.add(Long.valueOf(channelId));
|
||
|
}
|
||
|
}
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void removeAllReactions(@NonNull MessageReactionUpdate messageReactionUpdate) {
|
||
|
synchronized (this.$lock) {
|
||
|
long a = messageReactionUpdate.a();
|
||
|
if (isChannelActive(a)) {
|
||
|
long c2 = messageReactionUpdate.c();
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(a));
|
||
|
Message message = treeMap.get(Long.valueOf(c2));
|
||
|
if (message != null) {
|
||
|
treeMap.put(Long.valueOf(c2), LocalMessageCreatorsKt.createWithReactions(message, null));
|
||
|
this.updatedChannels.add(Long.valueOf(a));
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void removeEmojiReactions(@NonNull MessageReactionUpdate messageReactionUpdate) {
|
||
|
synchronized (this.$lock) {
|
||
|
long a = messageReactionUpdate.a();
|
||
|
if (isChannelActive(a)) {
|
||
|
long c2 = messageReactionUpdate.c();
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(a));
|
||
|
Message message = treeMap.get(Long.valueOf(c2));
|
||
|
if (message != null) {
|
||
|
String c3 = messageReactionUpdate.b().c();
|
||
|
Map<String, MessageReaction> reactionsMap = message.getReactionsMap();
|
||
|
if (reactionsMap.containsKey(c3)) {
|
||
|
LinkedHashMap linkedHashMap = new LinkedHashMap();
|
||
|
for (Map.Entry<String, MessageReaction> entry : reactionsMap.entrySet()) {
|
||
|
String key = entry.getKey();
|
||
|
if (!key.equals(c3)) {
|
||
|
linkedHashMap.put(key, entry.getValue());
|
||
|
}
|
||
|
}
|
||
|
treeMap.put(Long.valueOf(c2), LocalMessageCreatorsKt.createWithReactions(message, linkedHashMap));
|
||
|
this.updatedChannels.add(Long.valueOf(a));
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void setMyUserId(long j) {
|
||
|
this.myUserId = j;
|
||
|
}
|
||
|
|
||
|
public void setSelectedChannelId(long j) {
|
||
|
synchronized (this.$lock) {
|
||
|
this.selectedChannelId = j;
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(j));
|
||
|
if (treeMap != null) {
|
||
|
this.messages.remove(Long.valueOf(j));
|
||
|
this.messages.put(Long.valueOf(j), treeMap);
|
||
|
} else {
|
||
|
this.messages.put(Long.valueOf(j), new TreeMap<>(MessageUtils.getSORT_BY_IDS_COMPARATOR()));
|
||
|
}
|
||
|
this.activeChannels.add(Long.valueOf(j));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void updateMessages(@NonNull com.discord.api.message.Message message) {
|
||
|
synchronized (this.$lock) {
|
||
|
long g = message.g();
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(g));
|
||
|
if (isChannelActive(g)) {
|
||
|
Message message2 = treeMap.get(Long.valueOf(message.o()));
|
||
|
if (!isChannelDetached(g)) {
|
||
|
if (message2 != null) {
|
||
|
treeMap.put(Long.valueOf(message.o()), message2.merge(message));
|
||
|
this.updatedChannels.add(Long.valueOf(g));
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void updateReactions(@NonNull List<MessageReactionUpdate> list, boolean z2) {
|
||
|
synchronized (this.$lock) {
|
||
|
for (MessageReactionUpdate messageReactionUpdate : list) {
|
||
|
long a = messageReactionUpdate.a();
|
||
|
if (isChannelActive(a)) {
|
||
|
long c2 = messageReactionUpdate.c();
|
||
|
MessageReactionEmoji b = messageReactionUpdate.b();
|
||
|
boolean z3 = messageReactionUpdate.d() == this.myUserId;
|
||
|
TreeMap<Long, Message> treeMap = this.messages.get(Long.valueOf(a));
|
||
|
Message message = treeMap.get(Long.valueOf(c2));
|
||
|
if (message != null) {
|
||
|
treeMap.put(Long.valueOf(c2), z2 ? addReaction(message, b, z3) : removeReaction(message, b, z3));
|
||
|
this.updatedChannels.add(Long.valueOf(a));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
publishIfUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|