From be828e8b012ea22e4729be609977ab87dd70f262 Mon Sep 17 00:00:00 2001 From: JoshiCodes <55353244+JoshiCodes@users.noreply.github.com> Date: Sun, 11 Jun 2023 18:42:57 +0200 Subject: [PATCH] Reworked the RestAction Implementation - Reworked the RestAction class and added SimpleRestAction - Changed RJA#channelCache and RJA#serverCache to CacheMap --- src/main/java/de/joshicodes/rja/RJA.java | 184 ++++-------------- .../event/message/MessageReceivedEvent.java | 6 +- .../rja/event/message/MessageUpdateEvent.java | 8 +- .../rja/event/server/ServerDeleteEvent.java | 5 +- .../rja/event/server/ServerUpdateEvent.java | 16 +- .../rja/exception/RatelimitException.java | 25 +++ .../rja/object/channel/DirectChannel.java | 4 +- .../rja/object/channel/GenericChannel.java | 11 +- .../rja/object/channel/GroupChannel.java | 4 +- .../rja/object/channel/TextChannel.java | 4 +- .../rja/object/message/Message.java | 65 +++---- .../rja/object/message/MessageReceiver.java | 4 +- .../joshicodes/rja/object/server/Server.java | 10 +- .../rja/requests/RequestHandler.java | 29 ++- .../rja/requests/rest/RestResponse.java | 13 ++ .../rest/message/MessageSendRequest.java | 6 +- .../rja/rest/EditSelfRestAction.java | 10 +- .../de/joshicodes/rja/rest/RestAction.java | 149 +++++++------- .../joshicodes/rja/rest/RestActionImpl.java | 25 --- .../joshicodes/rja/rest/SimpleRestAction.java | 23 +++ .../rja/rest/message/MessageEditAction.java | 13 +- .../rja/rest/message/MessageSendAction.java | 10 +- .../de/joshicodes/rja/cache/CacheTest.java | 34 ---- 23 files changed, 295 insertions(+), 363 deletions(-) create mode 100644 src/main/java/de/joshicodes/rja/exception/RatelimitException.java create mode 100644 src/main/java/de/joshicodes/rja/requests/rest/RestResponse.java delete mode 100644 src/main/java/de/joshicodes/rja/rest/RestActionImpl.java create mode 100644 src/main/java/de/joshicodes/rja/rest/SimpleRestAction.java delete mode 100644 src/test/java/de/joshicodes/rja/cache/CacheTest.java diff --git a/src/main/java/de/joshicodes/rja/RJA.java b/src/main/java/de/joshicodes/rja/RJA.java index 7aea89b..6c36eb7 100644 --- a/src/main/java/de/joshicodes/rja/RJA.java +++ b/src/main/java/de/joshicodes/rja/RJA.java @@ -1,7 +1,6 @@ package de.joshicodes.rja; import com.google.gson.JsonObject; -import de.joshicodes.rja.cache.Cache; import de.joshicodes.rja.cache.CacheMap; import de.joshicodes.rja.exception.InvalidChannelTypeException; import de.joshicodes.rja.exception.RJAPingException; @@ -29,6 +28,7 @@ import de.joshicodes.rja.requests.rest.user.self.FetchSelfRequest; import de.joshicodes.rja.rest.EditSelfRestAction; import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import java.io.File; import java.io.FileNotFoundException; @@ -55,8 +55,8 @@ public abstract class RJA { private final CacheMap memberCache; private final CacheMap messageCache; private final CacheMap emojiCache; - private final Cache channelCache; - private final Cache serverCache; + private final CacheMap channelCache; + private final CacheMap serverCache; private final FileHandler fileHandler; @@ -79,8 +79,8 @@ public abstract class RJA { else emojiCache = null; if(cachingPolicies.contains(CachingPolicy.SERVER)) { - channelCache = new Cache<>(); - serverCache = new Cache<>(); + channelCache = new CacheMap<>(); + serverCache = new CacheMap<>(); } else { channelCache = null; serverCache = null; @@ -115,23 +115,20 @@ public RestAction getPing() { * @return The RestAction containing the ping in milliseconds. Use {@link RestAction#complete()} or {@link RestAction#queue} to execute the ping request and receive the ping. */ public RestAction getPing(final int timeout) { - return new RestAction<>(this) { - @Override - protected Long execute() { - long start = System.currentTimeMillis(); - try { - InetAddress[] addresses = InetAddress.getAllByName(getApiUrl().replaceAll("https://", "").replaceAll("http://", "")); - for (InetAddress inetAddress : addresses) { - if (inetAddress.isReachable(timeout)) { - return System.currentTimeMillis() - start; - } + return new SimpleRestAction<>(this, () -> { + long start = System.currentTimeMillis(); + try { + InetAddress[] addresses = InetAddress.getAllByName(getApiUrl().replaceAll("https://", "").replaceAll("http://", "")); + for (InetAddress inetAddress : addresses) { + if (inetAddress.isReachable(timeout)) { + return System.currentTimeMillis() - start; } - } catch (IOException e) { - throw new RJAPingException("Cannot Ping Revolt REST-API", e); } - throw new RJAPingException("Cannot Ping Revolt REST-API"); + } catch (IOException e) { + throw new RJAPingException("Cannot Ping Revolt REST-API", e); } - }; + throw new RJAPingException("Cannot Ping Revolt REST-API"); + }); } public Attachment uploadFile(File file) { @@ -165,21 +162,7 @@ public void shutdownNow() { * */ public RestAction retrieveUser(String id) { - final RJA rja = this; - return new RestAction<>(this) { - @Override - public User execute() { - if(userCache != null) { - // Caching for User is enabled - if(userCache.containsKey(id)) { - return userCache.get(id); - } - } - // Caching is disabled or user is not found in cache - FetchUserRequest request = new FetchUserRequest(id); - return getRequestHandler().sendRequest(rja, request); - } - }; + return new RestAction<>(this, () -> new FetchUserRequest(id)); } public RestAction retrieveMember(final Server server, final User user) { @@ -187,21 +170,7 @@ public RestAction retrieveMember(final Server server, final User user) { } public RestAction retrieveMember(final Server server, final String id) { - final RJA rja = this; - return new RestAction<>(this) { - @Override - public Member execute() { - if(memberCache != null) { - // Caching for Member is enabled - if(memberCache.containsKey(id)) { - return memberCache.get(id); - } - } - // Caching is disabled or member is not found in cache - FetchMemberRequest request = new FetchMemberRequest(server.getId(), id); - return getRequestHandler().sendRequest(rja, request); - } - }; + return new RestAction<>(this, () -> new FetchMemberRequest(server.getId(), id)); } /** @@ -217,60 +186,15 @@ public RestAction retrieveMessage(String channel, String id) { } public RestAction retrieveMessage(String channel, String id, boolean forceFetch) { - return new RestAction<>(this) { - @Override - public Message execute() { - if(messageCache != null && !forceFetch) { - // Caching for Message is enabled - if(messageCache.containsKey(id)) { - return messageCache.get(id); - } - } - // Message is not in cache or caching is disabled - FetchMessageRequest request = new FetchMessageRequest(channel, id); - Message message = getRequestHandler().sendRequest(RJA.this, request); - if(messageCache != null) messageCache.put(id, message); // Cache the message - return message; - } - }; + return new RestAction<>(this, () -> new FetchMessageRequest(channel, id)); } public RestAction retrieveEmoji(String id) { - return new RestAction<>(this) { - @Override - public Emoji execute() { - if(emojiCache != null) { - // Caching for Emoji is enabled - if(emojiCache.containsKey(id)) { - Emoji emoji = emojiCache.get(id); - if(emoji != null) return emoji; - } - } - // Emoji is not in cache or caching is disabled - FetchEmojiRequest request = new FetchEmojiRequest(id); - return getRequestHandler().sendRequest(RJA.this, request); - } - }; + return new RestAction<>(this, () -> new FetchEmojiRequest(id)); } public RestAction retrieveDirectChannel(String id) { - final RJA rja = this; - return new RestAction<>(this) { - @Override - public DirectChannel execute() { - if(channelCache != null) { - // Caching for Channel is enabled - GenericChannel c = channelCache.stream().filter(channel -> channel.getId().equals(id)).findFirst().orElse(null); - if(c != null) { - if(c instanceof DirectChannel dc) return dc; - else throw new InvalidChannelTypeException(id, ChannelType.DIRECT_MESSAGE, c.getType()); - } - } - // Caching is disabled or channel is not found in cache - OpenDirectMessageRequest request = new OpenDirectMessageRequest(id); - return getRequestHandler().sendRequest(rja, request); - } - }; + return new RestAction<>(this, () -> new OpenDirectMessageRequest(id)); } /** @@ -280,48 +204,18 @@ public DirectChannel execute() { * @return The RestAction containing the channel. Use {@link RestAction#complete()} or {@link RestAction#queue} to get the channel. Channel can be null. */ public RestAction retrieveChannel(String id) { - final RJA rja = this; - return new RestAction<>(this) { - @Override - public GenericChannel execute() { - if(channelCache != null) { - // Caching for Channel is enabled - GenericChannel c = channelCache.stream().filter(channel -> channel.getId().equals(id)).findFirst().orElse(null); - if(c != null) return c; - } - // Caching is disabled or channel is not found in cache - FetchChannelRequest request = new FetchChannelRequest(id); - return getRequestHandler().sendRequest(rja, request); - } - }; + return new RestAction(this, () -> new FetchChannelRequest(id)); } - public RestAction retrieveTextChannel(String id) { - return new RestAction<>(this) { - @Override - public TextChannel execute() { - GenericChannel c = retrieveChannel(id).complete(); - if(c instanceof TextChannel tc) return tc; - return null; - } - }; + public TextChannel retrieveTextChannel(String id) { + GenericChannel c = retrieveChannel(id).complete(); + if(c instanceof TextChannel tc) return tc; + else throw new InvalidChannelTypeException(id, ChannelType.TEXT_CHANNEL, c.getType()); } public RestAction retrieveServer(String serverId) { final RJA rja = this; - return new RestAction<>(this) { - @Override - public Server execute() { - if(serverCache != null) { - // Caching for Server is enabled - Server s = serverCache.stream().filter(server -> server.getId().equals(serverId)).findFirst().orElse(null); - if(s != null) return s; - } - // Caching is disabled or server is not found in cache - FetchServerRequest request = new FetchServerRequest(serverId); - return getRequestHandler().sendRequest(rja, request); - } - }; + return new RestAction<>(this, () -> new FetchServerRequest(serverId)); } public void cacheMessage(Message message) { @@ -365,10 +259,8 @@ public GenericChannel cacheChannel(JsonObject channel) { public GenericChannel cacheChannel(GenericChannel channel) { if(channelCache == null) return channel; // Caching is disabled if(channel != null) { - if(channelCache.stream().anyMatch(ch -> ch.getId().equals(channel.getId()))) { - channelCache.stream().filter(ch -> ch.getId().equals(channel.getId())).findFirst().ifPresent(channelCache::remove); // Channel is cached, remove old one - } - channelCache.add(channel); + channelCache.remove(channel.getId()); + channelCache.put(channel.getId(), channel); //getLogger().info("Loaded channel " + c.getName()); // DEBUG } else { getLogger().warning("Failed to load channel!"); @@ -379,10 +271,8 @@ public GenericChannel cacheChannel(GenericChannel channel) { public Server cacheServer(Server cachedServer) { if(serverCache == null) return null; // Caching is disabled if(cachedServer != null) { - if(serverCache.stream().anyMatch(server -> server.getId().equals(cachedServer.getId()))) { - serverCache.stream().filter(server -> server.getId().equals(cachedServer.getId())).findFirst().ifPresent(serverCache::remove); // Server is cached, remove old one - } - serverCache.add(cachedServer); + serverCache.remove(cachedServer.getId()); + serverCache.put(cachedServer.getId(), cachedServer); //getLogger().info("Loaded server " + s.getName()); // DEBUG } else { getLogger().warning("Failed to load server!"); @@ -418,22 +308,16 @@ public CacheMap getEmojiCache() { * Retrieves the Channel cache. * @return The channel cache or null if the caching policy for {@link CachingPolicy#SERVER} is disabled. */ - public Cache getChannelCache() { + public CacheMap getChannelCache() { return channelCache; } - public Cache getServerCache() { + public CacheMap getServerCache() { return serverCache; } public RestAction retrieveSelfUser() { - return new RestAction<>(this) { - @Override - public User execute() { - FetchSelfRequest request = new FetchSelfRequest(); - return getRequestHandler().sendRequest(RJA.this, request); - } - }; + return new RestAction<>(this, FetchSelfRequest::new); } public EditSelfRestAction editSelfUser() { diff --git a/src/main/java/de/joshicodes/rja/event/message/MessageReceivedEvent.java b/src/main/java/de/joshicodes/rja/event/message/MessageReceivedEvent.java index f9cbe44..24eef8e 100644 --- a/src/main/java/de/joshicodes/rja/event/message/MessageReceivedEvent.java +++ b/src/main/java/de/joshicodes/rja/event/message/MessageReceivedEvent.java @@ -73,11 +73,11 @@ public MessageReceivedEvent handle(RJA rja, JsonObject object) { if(message.getChannel().complete() instanceof TextChannel tc) { textChannel = tc; } - rja.getChannelCache().stream().filter(c -> c.getId().equals(message.getChannelId())).findFirst().ifPresent(c -> { - if(c instanceof TextChannel tc) { + if(rja.getChannelCache().containsKey(message.getChannelId())) { + if(rja.getChannelCache().get(message.getChannelId()) instanceof TextChannel tc) { tc.getCachedHistory().add(message.getId()); } - }); + } return new MessageReceivedEvent(rja, author, message, textChannel); } diff --git a/src/main/java/de/joshicodes/rja/event/message/MessageUpdateEvent.java b/src/main/java/de/joshicodes/rja/event/message/MessageUpdateEvent.java index 12b3f18..6e25996 100644 --- a/src/main/java/de/joshicodes/rja/event/message/MessageUpdateEvent.java +++ b/src/main/java/de/joshicodes/rja/event/message/MessageUpdateEvent.java @@ -7,6 +7,7 @@ import de.joshicodes.rja.object.channel.ServerChannel; import de.joshicodes.rja.object.message.Message; import de.joshicodes.rja.object.server.Server; +import de.joshicodes.rja.requests.rest.RestResponse; import de.joshicodes.rja.requests.rest.message.FetchMessageRequest; import javax.annotation.Nullable; @@ -55,11 +56,14 @@ public IncomingEvent handle(RJA rja, JsonObject object) { Message message; if(!inCache) { // Message not in cache, cannot update with partial data -> fetch full message - message = rja.getRequestHandler().sendRequest(rja, new FetchMessageRequest(channel, id)); + RestResponse response = rja.getRequestHandler().fetchRequest(rja, new FetchMessageRequest(channel, id)); + if(response.isOk()) { + message = response.object(); + } else return null; } else message = rja.getMessageCache().getIf(m -> m.equals(id)); Message updated = Message.from(rja, object.get("data").getAsJsonObject(), message); rja.cacheMessage(updated); - return new MessageUpdateEvent(rja, rja.getChannelCache().getIf(c -> c.getId().equals(channel)), updated); + return new MessageUpdateEvent(rja, rja.getChannelCache().get(channel), updated); } @Override diff --git a/src/main/java/de/joshicodes/rja/event/server/ServerDeleteEvent.java b/src/main/java/de/joshicodes/rja/event/server/ServerDeleteEvent.java index f79b9e3..ed46365 100644 --- a/src/main/java/de/joshicodes/rja/event/server/ServerDeleteEvent.java +++ b/src/main/java/de/joshicodes/rja/event/server/ServerDeleteEvent.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import de.joshicodes.rja.RJA; import de.joshicodes.rja.cache.Cache; +import de.joshicodes.rja.cache.CacheMap; import de.joshicodes.rja.event.IncomingEvent; import de.joshicodes.rja.object.server.Server; @@ -26,9 +27,9 @@ public String getId() { @Override public IncomingEvent handle(RJA rja, JsonObject object) { String id = object.get("id").getAsString(); - Cache cache = rja.getServerCache(); + CacheMap cache = rja.getServerCache(); if(cache != null) - cache.stream().filter(server -> server.getId().equals(id)).forEach(cache::remove); + cache.remove(id); return new ServerDeleteEvent(rja, id); } diff --git a/src/main/java/de/joshicodes/rja/event/server/ServerUpdateEvent.java b/src/main/java/de/joshicodes/rja/event/server/ServerUpdateEvent.java index 82ddf67..1804e74 100644 --- a/src/main/java/de/joshicodes/rja/event/server/ServerUpdateEvent.java +++ b/src/main/java/de/joshicodes/rja/event/server/ServerUpdateEvent.java @@ -4,6 +4,7 @@ import de.joshicodes.rja.RJA; import de.joshicodes.rja.event.IncomingEvent; import de.joshicodes.rja.object.server.Server; +import de.joshicodes.rja.requests.rest.RestResponse; import de.joshicodes.rja.requests.rest.server.FetchServerRequest; public class ServerUpdateEvent extends IncomingEvent { @@ -20,16 +21,21 @@ public ServerUpdateEvent(RJA rja, Server server) { public IncomingEvent handle(RJA rja, JsonObject object) { String id = object.get("id").getAsString(); - boolean inCache = rja.getServerCache().containsIf(s -> s.getId().equals(id)); + boolean inCache = rja.getServerCache().containsKey(id); if(!inCache) { // Server not in cache, cannot update with partial data -> fetch full server FetchServerRequest request = new FetchServerRequest(id); - Server server = rja.getRequestHandler().sendRequest(rja, request); - return new ServerUpdateEvent(rja, server); + RestResponse response = rja.getRequestHandler().fetchRequest(rja, request); + if(response.isOk()) { + Server server = response.object(); + return new ServerUpdateEvent(rja, server); + } } - Server server = rja.getServerCache().getIf(s -> s.getId().equals(id)); - server.update(object); + Server server = rja.getServerCache().get(id); + if(server != null) { + server.update(object); + } return new ServerUpdateEvent(rja, rja.cacheServer(server)); } diff --git a/src/main/java/de/joshicodes/rja/exception/RatelimitException.java b/src/main/java/de/joshicodes/rja/exception/RatelimitException.java new file mode 100644 index 0000000..087adb6 --- /dev/null +++ b/src/main/java/de/joshicodes/rja/exception/RatelimitException.java @@ -0,0 +1,25 @@ +package de.joshicodes.rja.exception; + +import de.joshicodes.rja.requests.rest.RestRequest; +import de.joshicodes.rja.rest.RestAction; + +public class RatelimitException extends RuntimeException { + + private final RestAction action; + private final RestRequest request; + + public RatelimitException(RestAction action, RestRequest request) { + super("Ratelimit reached for " + request.getEndpoint() + " (" + request.getMethod() + ")"); + this.action = action; + this.request = request; + } + + public RestAction getAction() { + return action; + } + + public RestRequest getRequest() { + return request; + } + +} diff --git a/src/main/java/de/joshicodes/rja/object/channel/DirectChannel.java b/src/main/java/de/joshicodes/rja/object/channel/DirectChannel.java index dd36771..8863c1a 100644 --- a/src/main/java/de/joshicodes/rja/object/channel/DirectChannel.java +++ b/src/main/java/de/joshicodes/rja/object/channel/DirectChannel.java @@ -3,7 +3,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import de.joshicodes.rja.RJA; -import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.util.JsonUtil; import javax.annotation.Nullable; @@ -60,7 +60,7 @@ public String getLastMessageId() { abstract public String getLastMessageId(); @Override - public RestAction close() { + public SimpleRestAction close() { // TODO return null; } diff --git a/src/main/java/de/joshicodes/rja/object/channel/GenericChannel.java b/src/main/java/de/joshicodes/rja/object/channel/GenericChannel.java index 98458a1..0a14924 100644 --- a/src/main/java/de/joshicodes/rja/object/channel/GenericChannel.java +++ b/src/main/java/de/joshicodes/rja/object/channel/GenericChannel.java @@ -6,6 +6,7 @@ import de.joshicodes.rja.object.message.MessageReceiver; import de.joshicodes.rja.requests.rest.channel.info.FetchChannelRequest; import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.util.JsonUtil; public abstract class GenericChannel extends MessageReceiver implements IMentionable { @@ -35,18 +36,12 @@ public static GenericChannel from(final RJA rja, final JsonObject object) { abstract public RJA getRJA(); abstract public String getId(); - abstract public RestAction close(); + abstract public SimpleRestAction close(); abstract public ChannelType getType(); public RestAction fetch() { - return new RestAction(getRJA()) { - @Override - protected GenericChannel execute() { - FetchChannelRequest request = new FetchChannelRequest(getId()); - return getRJA().getRequestHandler().sendRequest(getRJA(), request); - } - }; + return new RestAction(getRJA(), () -> new FetchChannelRequest(getId())); } @Override diff --git a/src/main/java/de/joshicodes/rja/object/channel/GroupChannel.java b/src/main/java/de/joshicodes/rja/object/channel/GroupChannel.java index 2c4a825..090bb8c 100644 --- a/src/main/java/de/joshicodes/rja/object/channel/GroupChannel.java +++ b/src/main/java/de/joshicodes/rja/object/channel/GroupChannel.java @@ -3,7 +3,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import de.joshicodes.rja.RJA; -import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.util.JsonUtil; public abstract class GroupChannel extends GenericChannel { @@ -82,7 +82,7 @@ public String getId() { abstract public boolean isNsfw(); @Override - public RestAction close() { + public SimpleRestAction close() { return null; } diff --git a/src/main/java/de/joshicodes/rja/object/channel/TextChannel.java b/src/main/java/de/joshicodes/rja/object/channel/TextChannel.java index 7daa7cf..bbe0f58 100644 --- a/src/main/java/de/joshicodes/rja/object/channel/TextChannel.java +++ b/src/main/java/de/joshicodes/rja/object/channel/TextChannel.java @@ -2,7 +2,7 @@ import com.google.gson.JsonObject; import de.joshicodes.rja.RJA; -import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.util.JsonUtil; import javax.annotation.Nullable; @@ -95,7 +95,7 @@ public List getCachedHistory() { //abstract public Permission getRolePermissions(); // TODO @Override - public RestAction close() { + public SimpleRestAction close() { // TODO return null; } diff --git a/src/main/java/de/joshicodes/rja/object/message/Message.java b/src/main/java/de/joshicodes/rja/object/message/Message.java index 7f6ea39..3a58916 100644 --- a/src/main/java/de/joshicodes/rja/object/message/Message.java +++ b/src/main/java/de/joshicodes/rja/object/message/Message.java @@ -11,6 +11,7 @@ import de.joshicodes.rja.requests.rest.interaction.AddReactionRequest; import de.joshicodes.rja.requests.rest.interaction.RemoveReactionRequest; import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.rest.message.MessageEditAction; import de.joshicodes.rja.rest.message.MessageSendAction; import de.joshicodes.rja.util.JsonUtil; @@ -335,35 +336,32 @@ RestAction removeReaction(Emoji emoji, String user, boolean rem } RestAction removeReaction(String emoji, @Nullable String user, boolean removeAll) { - return new RestAction<>(getRJA()) { - @Override - protected MessageReaction execute() { - RemoveReactionRequest request = new RemoveReactionRequest(getChannelId(), getId(), emoji, user, removeAll); - getRJA().getRequestHandler().sendRequest(getRJA(), request); - if(getReaction(emoji) != null) { - if(removeAll) { - getReaction(emoji).removeReaction(user); + return new SimpleRestAction<>(getRJA(), () -> { + RemoveReactionRequest request = new RemoveReactionRequest(getChannelId(), getId(), emoji, user, removeAll); + getRJA().getRequestHandler().fetchRequest(getRJA(), request); + if(getReaction(emoji) != null) { + if(removeAll) { + getReaction(emoji).removeReaction(user); + } else { + if(user == null) { + // We do not know which user got removed, so we have to fetch the message again + // Cannot retrieve reactions from a message alone, so we have to remove it from the cache and retrieve it again + getRJA().getMessageCache().remove(getId()); + Message updated = getRJA().retrieveMessage(getChannelId(), getId()).complete(); // Retrieve the message again (this will cache it if enabled) + + // This Message instance is now outdated, so we have to update it + getReactions().stream().filter(r -> r.getEmojiId().equals(emoji)).findFirst().ifPresent(r -> { + getReactions().remove(r); + MessageReaction updatedReaction = updated.getReaction(emoji); + if(updatedReaction != null) getReactions().add(updatedReaction); + }); } else { - if(user == null) { - // We do not know which user got removed, so we have to fetch the message again - // Cannot retrieve reactions from a message alone, so we have to remove it from the cache and retrieve it again - getRJA().getMessageCache().remove(getId()); - Message updated = getRJA().retrieveMessage(getChannelId(), getId()).complete(); // Retrieve the message again (this will cache it if enabled) - - // This Message instance is now outdated, so we have to update it - getReactions().stream().filter(r -> r.getEmojiId().equals(emoji)).findFirst().ifPresent(r -> { - getReactions().remove(r); - MessageReaction updatedReaction = updated.getReaction(emoji); - if(updatedReaction != null) getReactions().add(updatedReaction); - }); - } else { - getReaction(emoji).removeReaction(user); - } + getReaction(emoji).removeReaction(user); } } - return getReaction(emoji); } - }; + return getReaction(emoji); + }); } public RestAction react(Emoji emoji) { @@ -371,16 +369,13 @@ public RestAction react(Emoji emoji) { } public RestAction react(String emoji) { - return new RestAction<>(getRJA()) { - @Override - protected MessageReaction execute() { - AddReactionRequest request = new AddReactionRequest(getChannelId(), getId(), emoji); - getRJA().getRequestHandler().sendRequest(getRJA(), request); - MessageReaction reaction = getReaction(emoji); - reaction.addReaction(getRJA().retrieveSelfUser().complete().getId()); - return reaction; - } - }; + return new SimpleRestAction<>(getRJA(), () -> { + AddReactionRequest request = new AddReactionRequest(getChannelId(), getId(), emoji); + getRJA().getRequestHandler().fetchRequest(getRJA(), request); + MessageReaction reaction = getReaction(emoji); + reaction.addReaction(getRJA().retrieveSelfUser().complete().getId()); + return reaction; + }); } } diff --git a/src/main/java/de/joshicodes/rja/object/message/MessageReceiver.java b/src/main/java/de/joshicodes/rja/object/message/MessageReceiver.java index 3f5f937..94db860 100644 --- a/src/main/java/de/joshicodes/rja/object/message/MessageReceiver.java +++ b/src/main/java/de/joshicodes/rja/object/message/MessageReceiver.java @@ -5,7 +5,7 @@ import de.joshicodes.rja.object.message.embed.MessageEmbed; import de.joshicodes.rja.requests.packet.BeginTypingRequest; import de.joshicodes.rja.rest.RestAction; -import de.joshicodes.rja.rest.RestActionImpl; +import de.joshicodes.rja.rest.SimpleRestAction; import de.joshicodes.rja.rest.message.MessageSendAction; import java.util.List; @@ -16,7 +16,7 @@ public abstract class MessageReceiver { abstract public String getId(); public RestAction sendTyping() { - return new RestActionImpl<>(getRJA(), (Void) -> { + return new SimpleRestAction<>(getRJA(), () -> { getRJA().getRequestHandler().sendRequest(new BeginTypingRequest(getId())); return null; }); diff --git a/src/main/java/de/joshicodes/rja/object/server/Server.java b/src/main/java/de/joshicodes/rja/object/server/Server.java index 66c49c7..d79c367 100644 --- a/src/main/java/de/joshicodes/rja/object/server/Server.java +++ b/src/main/java/de/joshicodes/rja/object/server/Server.java @@ -83,14 +83,8 @@ public RestAction retrieveMember(User user) { * @param excludeOffline Whether to exclude offline members. * @return A {@link RestAction} that retrieves a {@link HashMap} of {@link User}s and {@link Member}s. */ - public RestAction> retrieveAllMembers(boolean excludeOffline) { - return new RestAction<>(rja) { - @Override - protected HashMap execute() { - FetchAllMembersRequest request = new FetchAllMembersRequest(id, excludeOffline); - return rja.getRequestHandler().sendRequest(rja, request); - } - }; + public RestAction> retrieveAllMembers(final boolean excludeOffline) { + return new RestAction<>(rja, () -> new FetchAllMembersRequest(id, excludeOffline)); } public RJA getRJA() { diff --git a/src/main/java/de/joshicodes/rja/requests/RequestHandler.java b/src/main/java/de/joshicodes/rja/requests/RequestHandler.java index b5d1a69..b78d814 100644 --- a/src/main/java/de/joshicodes/rja/requests/RequestHandler.java +++ b/src/main/java/de/joshicodes/rja/requests/RequestHandler.java @@ -11,7 +11,7 @@ import de.joshicodes.rja.requests.packet.PacketRequest; import de.joshicodes.rja.requests.packet.PingRequest; import de.joshicodes.rja.requests.rest.RestRequest; -import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.requests.rest.RestResponse; import de.joshicodes.rja.util.Pair; import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.framing.CloseFrame; @@ -60,12 +60,15 @@ public void run() { /** * Sends a request to the API and returns the result. - * This method can block the current thread and should be called in a {@link RestAction}. + * This method can block the current thread and should be called in a {@link de.joshicodes.rja.rest.RestAction}. * @param rja The RJA instance * @param request The request to send * @return The result of the request * @param The type of the result + * + * @deprecated Use {@link #fetchRequest(RJA, RestRequest)} instead */ + @Deprecated(forRemoval = true) public T sendRequest(final RJA rja, RestRequest request) { final RJABuilder builder = this.rja; Pair multi = builder.makeRequest(request); @@ -77,6 +80,28 @@ public T sendRequest(final RJA rja, RestRequest request) { return request.fetch(rja, multi.getFirst(), e); } + public RestResponse fetchRequest(final RJA rja, RestRequest request) { + final RJABuilder builder = this.rja; + Pair multi = builder.makeRequest(request); + JsonElement e = multi.getSecond(); + int code = multi.getFirst(); + if(e == null) { + return null; + } + boolean isRatelimit = code == 429; + if(isRatelimit) { + if(e.isJsonObject()) { + JsonObject o = e.getAsJsonObject(); + if(o.has("retry_after")) { + long retryAfter = o.get("retry_after").getAsLong(); + return new RestResponse<>(null, code, retryAfter); // Invalid Response, but ratelimited + } + } + return new RestResponse<>(null, code, 0); // Invalid Response, but the ratelimit is unknown + } + return new RestResponse<>(request.fetch(rja, multi.getFirst(), e), code, -1); // Valid response, no ratelimit + } + public void sendRequest(PacketRequest request) { JsonObject jsonObject = new JsonObject(); HashMap data = request.getData(); diff --git a/src/main/java/de/joshicodes/rja/requests/rest/RestResponse.java b/src/main/java/de/joshicodes/rja/requests/rest/RestResponse.java new file mode 100644 index 0000000..377208f --- /dev/null +++ b/src/main/java/de/joshicodes/rja/requests/rest/RestResponse.java @@ -0,0 +1,13 @@ +package de.joshicodes.rja.requests.rest; + +public record RestResponse(T object, int code, long retryAfter) { + + public boolean isOk() { + return code >= 200 && code < 300 && !isRatelimited() && object != null; + } + + public boolean isRatelimited() { + return retryAfter != -1; + } + +} diff --git a/src/main/java/de/joshicodes/rja/requests/rest/message/MessageSendRequest.java b/src/main/java/de/joshicodes/rja/requests/rest/message/MessageSendRequest.java index fe1a2f5..ee3e7ff 100644 --- a/src/main/java/de/joshicodes/rja/requests/rest/message/MessageSendRequest.java +++ b/src/main/java/de/joshicodes/rja/requests/rest/message/MessageSendRequest.java @@ -18,11 +18,11 @@ public Message fetch(RJA rja, int responseCode, JsonElement data) { return null; Message m = Message.from(rja, data.getAsJsonObject(), null); rja.cacheMessage(m); - rja.getChannelCache().stream().filter(c -> c.getId().equals(m.getChannelId())).findFirst().ifPresent(c -> { - if(c instanceof TextChannel tc) { + if(rja.getChannelCache().containsKey(m.getChannelId())) { + if(rja.getChannelCache().get(m.getChannelId()) instanceof TextChannel tc) { tc.getCachedHistory().add(m.getId()); } - }); + } return m; } diff --git a/src/main/java/de/joshicodes/rja/rest/EditSelfRestAction.java b/src/main/java/de/joshicodes/rja/rest/EditSelfRestAction.java index 7004477..d6e6038 100644 --- a/src/main/java/de/joshicodes/rja/rest/EditSelfRestAction.java +++ b/src/main/java/de/joshicodes/rja/rest/EditSelfRestAction.java @@ -5,13 +5,14 @@ import de.joshicodes.rja.object.user.User; import de.joshicodes.rja.object.user.UserStatus; import de.joshicodes.rja.requests.rest.user.self.EditSelfUserRequest; +import de.joshicodes.rja.util.Pair; -public class EditSelfRestAction extends RestAction { +public class EditSelfRestAction extends SimpleRestAction { private final EditSelfUserRequest request; public EditSelfRestAction(RJA rja) { - super(rja); + super(rja, () -> null); request = new EditSelfUserRequest(rja); } @@ -40,8 +41,9 @@ public Presence presence() { } @Override - protected User execute() { - return getRJA().getRequestHandler().sendRequest(getRJA(), request); + protected Pair execute() throws Exception { + super.request = () -> request; + return super.execute(); } } diff --git a/src/main/java/de/joshicodes/rja/rest/RestAction.java b/src/main/java/de/joshicodes/rja/rest/RestAction.java index fc25aba..4cef3cd 100644 --- a/src/main/java/de/joshicodes/rja/rest/RestAction.java +++ b/src/main/java/de/joshicodes/rja/rest/RestAction.java @@ -1,99 +1,118 @@ package de.joshicodes.rja.rest; import de.joshicodes.rja.RJA; +import de.joshicodes.rja.exception.RatelimitException; +import de.joshicodes.rja.requests.RequestHandler; +import de.joshicodes.rja.requests.rest.RestRequest; +import de.joshicodes.rja.requests.rest.RestResponse; +import de.joshicodes.rja.util.Pair; import javax.annotation.Nullable; -import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; -public abstract class RestAction { +public class RestAction { - private final boolean canMultiple; - private boolean executed = false; + public static final int MAX_ATTEMPTS = 4; private final RJA rja; + protected Supplier> request; - public RestAction(RJA rja) { - this(rja, false); - } - - public RestAction(RJA rja, boolean canMultiple) { + public RestAction(RJA rja, Supplier> request) { this.rja = rja; - this.canMultiple = canMultiple; + this.request = request; } - public RJA getRJA() { - return rja; + protected Pair execute() throws Exception { + RequestHandler handler = rja.getRequestHandler(); + RestResponse response = handler.fetchRequest(rja, request.get()); + if(response == null) { + return null; + } + return new Pair<>(response.retryAfter(), response.object()); } - protected abstract T execute(); - - /** - * Executes the action and returns the result. - * This Method can block your current thread. - * @return The result of the action. - */ - public T complete() { - if (!canMultiple && executed) { - throw new IllegalStateException("This action can only be executed once."); + public R complete() { + int attempts = 0; + long retryAfter = 0; + while (attempts < MAX_ATTEMPTS) { + try { + Pair result = execute(); + if(result == null) { + // Failed to get a result, throw an exception + throw new RatelimitException(this, request.get()); + } + retryAfter = result.getFirst(); + if(retryAfter == -1) { + // Successfully got a result, call the success consumer and return + return result.getSecond(); + } + // Got ratelimited, wait and try again + try { + Thread.sleep(retryAfter); + } catch (InterruptedException e) { + e.printStackTrace(); + } + attempts++; + } catch (Exception e) { + // Failed to get a result + e.printStackTrace(); + continue; + } } - this.executed = true; - return execute(); + throw new RatelimitException(this, request.get()); } - /** - * Queues the action to be executed in a new thread. - * If you need to handle the result or exception, use {@link #queue(Consumer)} or {@link #queue(Consumer, Consumer)}. - * - * @see #queue(Consumer) - * @see #queue(Consumer, Consumer) - * @see #complete() - */ public void queue() { - queue(null); + queue(null, null); } - /** - * Queues the action to be executed in a new thread. - * If finished, the success consumer will be called. - * @param success The success consumer, can be null. If you do not want to handle the result, use {@link #queue()}. - * - * @see #queue() - * @see #queue(Consumer, Consumer) - * @see #complete() - */ - public void queue(@Nullable Consumer success) { + public void queue(Consumer success) { queue(success, null); } - /** - * Queues the action to be executed in a new thread. - * If finished, the success consumer will be called. - * If an exception occurs, the failure consumer will be called. - * @param success The success consumer, can be null. If you do not want to handle the result, use {@link #queue()}. - * @param failure The failure consumer, can be null. If you do not want to handle the exception, use {@link #queue()} or {@link #queue(Consumer)}. - * - * @see #queue() - * @see #queue(Consumer) - * @see #complete() - */ - public void queue(@Nullable Consumer success, @Nullable Consumer failure) { + public void queue(Consumer success, Consumer failure) { + final RestAction action = this; new Thread(() -> { - try { - T t = complete(); - if (success != null) { - success.accept(t); - } - } catch (Exception exception) { - if (failure != null) { - failure.accept(exception); + int attempts = 0; + long retryAfter = 0; + while (attempts < MAX_ATTEMPTS) { + try { + Pair result = execute(); + if(result == null) { + // Failed to get a result, throw an exception + throw new RatelimitException(action, request.get()); + } + retryAfter = result.getFirst(); + if(retryAfter == -1) { + // Successfully got a result, call the success consumer and return + if(success != null) { + success.accept(result.getSecond()); + } + return; + } + // Got ratelimited, wait and try again + try { + Thread.sleep(retryAfter + 100); // Add 100ms to the ratelimit to make sure it's over + } catch (InterruptedException e) { + e.printStackTrace(); + } + attempts++; + } catch (Exception e) { + // Failed to get a result + e.printStackTrace(); + continue; } } + if(failure != null) { + failure.accept(new RatelimitException(action, request.get())); + } }).start(); } - public CompletableFuture submit() { - return CompletableFuture.supplyAsync(this::execute); + public RJA getRJA() { + return rja; } } diff --git a/src/main/java/de/joshicodes/rja/rest/RestActionImpl.java b/src/main/java/de/joshicodes/rja/rest/RestActionImpl.java deleted file mode 100644 index 2e06ac6..0000000 --- a/src/main/java/de/joshicodes/rja/rest/RestActionImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.joshicodes.rja.rest; - -import de.joshicodes.rja.RJA; - -import java.util.function.Function; - -public class RestActionImpl extends RestAction { - - private Function function; - - public RestActionImpl(RJA rja, boolean async, Function function) { - super(rja, async); - this.function = function; - } - - public RestActionImpl(RJA rja, Function function) { - this(rja, false, function); - } - - @Override - protected T execute() { - return function.apply(null); - } - -} diff --git a/src/main/java/de/joshicodes/rja/rest/SimpleRestAction.java b/src/main/java/de/joshicodes/rja/rest/SimpleRestAction.java new file mode 100644 index 0000000..ad732d8 --- /dev/null +++ b/src/main/java/de/joshicodes/rja/rest/SimpleRestAction.java @@ -0,0 +1,23 @@ +package de.joshicodes.rja.rest; + +import de.joshicodes.rja.RJA; +import de.joshicodes.rja.requests.rest.RestRequest; +import de.joshicodes.rja.util.Pair; + +import java.util.function.Supplier; + +public class SimpleRestAction extends RestAction { + + private final Supplier run; + + public SimpleRestAction(RJA rja, Supplier run) { + super(rja, null); + this.run = run; + } + + @Override + protected Pair execute() throws Exception { + return new Pair<>(-1L, run.get()); + } + +} diff --git a/src/main/java/de/joshicodes/rja/rest/message/MessageEditAction.java b/src/main/java/de/joshicodes/rja/rest/message/MessageEditAction.java index cf9cce9..eb028f2 100644 --- a/src/main/java/de/joshicodes/rja/rest/message/MessageEditAction.java +++ b/src/main/java/de/joshicodes/rja/rest/message/MessageEditAction.java @@ -6,6 +6,7 @@ import de.joshicodes.rja.object.message.embed.MessageEmbed; import de.joshicodes.rja.requests.rest.message.EditMessageRequest; import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.util.Pair; import java.util.ArrayList; import java.util.List; @@ -20,7 +21,7 @@ public class MessageEditAction extends RestAction { private List attachments; public MessageEditAction(final RJA rja, final Message message) { - super(rja); + super(rja, () -> null); this.message = message; } @@ -62,7 +63,7 @@ public MessageEditAction addAttachment(Attachment attachment) { } @Override - protected Message execute() { + protected Pair execute() throws Exception { if(!message.getAuthorId().equals(getRJA().retrieveSelfUser().complete().getId())) { throw new UnsupportedOperationException("Cannot edit a message that was not sent by the current user!"); @@ -70,9 +71,11 @@ protected Message execute() { EditMessageRequest request = new EditMessageRequest(message, content, embeds, attachments); if(!request.hasData() || (!request.hasData("content") && !request.hasData("embeds"))) { - return message; // Nothing to edit, return original message + return new Pair<>(-1L, message); // Nothing to edit, return original message } - return getRJA().getRequestHandler().sendRequest(getRJA(), request); - } + super.request = () -> request; + return super.execute(); + + } } diff --git a/src/main/java/de/joshicodes/rja/rest/message/MessageSendAction.java b/src/main/java/de/joshicodes/rja/rest/message/MessageSendAction.java index 3c8994d..accf404 100644 --- a/src/main/java/de/joshicodes/rja/rest/message/MessageSendAction.java +++ b/src/main/java/de/joshicodes/rja/rest/message/MessageSendAction.java @@ -10,6 +10,7 @@ import de.joshicodes.rja.object.user.Masquerade; import de.joshicodes.rja.requests.rest.message.MessageSendRequest; import de.joshicodes.rja.rest.RestAction; +import de.joshicodes.rja.util.Pair; import java.util.HashMap; import java.util.List; @@ -27,7 +28,7 @@ public class MessageSendAction extends RestAction { // TODO: Interactions public MessageSendAction(final RJA rja, final String receiver) { - super(rja); + super(rja, () -> null); this.receiver = receiver; } @@ -64,7 +65,7 @@ public MessageSendAction setMasquerade(Masquerade masquerade) { } @Override - protected Message execute() { + protected Pair execute() throws Exception { MessageSendRequest request = new MessageSendRequest(receiver); if(content != null) { @@ -110,7 +111,8 @@ protected Message execute() { headers.put("Idempotency-Key", nonce); request.setHeaders(headers); - return getRJA().getRequestHandler().sendRequest(getRJA(), request); - } + super.request = () -> request; + return super.execute(); + } } diff --git a/src/test/java/de/joshicodes/rja/cache/CacheTest.java b/src/test/java/de/joshicodes/rja/cache/CacheTest.java deleted file mode 100644 index fb81bfe..0000000 --- a/src/test/java/de/joshicodes/rja/cache/CacheTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.joshicodes.rja.cache; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class CacheTest { - - @Test - public void testCache() throws InterruptedException { - Cache cache = new Cache<>(); - - cache.add("test"); - - assertTrue(cache.contains("test")); - - cache.add("test2", 1000); // put Object with lifetime of 1 second - assertTrue(cache.contains("test2")); // Make sure it's in cache - Thread.sleep(1005); // Wait 1,005 seconds - assertFalse(cache.contains("test2")); // Make sure lifetime works - assertFalse(cache.stream().anyMatch(s -> s.equals("test2"))); // Make sure not in stream/list - - assertTrue(cache.contains("test")); // Make sure it's still in cache - assertTrue(cache.stream().anyMatch(s -> s.equals("test"))); // Make sure it's still in stream/list - - cache.remove("test"); // Remove Object - - assertFalse(cache.contains("test")); // Make sure it's not in cache - assertFalse(cache.stream().anyMatch(s -> s.equals("test"))); // Make sure it's not in stream/list - - } - -}