-
Notifications
You must be signed in to change notification settings - Fork 529
/
GitHubTask.java
153 lines (126 loc) · 5.72 KB
/
GitHubTask.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package io.github.thebusybiscuit.slimefun4.core.services.github;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import io.github.bakedlibs.dough.skins.PlayerSkin;
import io.github.bakedlibs.dough.skins.UUIDLookup;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
/**
* This {@link GitHubTask} represents a {@link Runnable} that is run every X minutes.
* It retrieves every {@link Contributor} of this project from GitHub.
*
* @author TheBusyBiscuit
*
* @see GitHubService
* @see Contributor
*
*/
class GitHubTask implements Runnable {
private static final int MAX_REQUESTS_PER_MINUTE = 16;
private final GitHubService gitHubService;
GitHubTask(@Nonnull GitHubService github) {
gitHubService = github;
}
@Override
public void run() {
if (Bukkit.isPrimaryThread()) {
Slimefun.logger().log(Level.SEVERE, "The contributors task may never run on the main Thread!");
return;
}
connectAndCache();
grabTextures();
}
private void connectAndCache() {
gitHubService.getConnectors().forEach(GitHubConnector::download);
}
/**
* This method will pull the skin textures for every {@link Contributor} and store
* the {@link UUID} and received skin inside a local cache {@link File}.
*/
private void grabTextures() {
/**
* Store all queried usernames to prevent 429 responses for pinging
* the same URL twice in one run.
*/
Map<String, String> skins = new HashMap<>();
int requests = 0;
for (Contributor contributor : gitHubService.getContributors().values()) {
int newRequests = requestTexture(contributor, skins);
requests += newRequests;
if (newRequests < 0 || requests >= MAX_REQUESTS_PER_MINUTE) {
break;
}
}
if (requests >= MAX_REQUESTS_PER_MINUTE && Slimefun.instance() != null && Slimefun.instance().isEnabled()) {
// Slow down API requests and wait a minute after more than x requests were made
Bukkit.getScheduler().runTaskLaterAsynchronously(Slimefun.instance(), this::grabTextures, 2 * 60 * 20L);
}
for (GitHubConnector connector : gitHubService.getConnectors()) {
if (connector instanceof ContributionsConnector contributionsConnector && !contributionsConnector.hasFinished()) {
return;
}
}
/**
* We only wanna save this if all Connectors finished already.
* This will run multiple times but thats okay, this way we get as much
* data as possible stored.
*/
gitHubService.saveCache();
}
private int requestTexture(@Nonnull Contributor contributor, @Nonnull Map<String, String> skins) {
if (!contributor.hasTexture()) {
try {
if (skins.containsKey(contributor.getMinecraftName())) {
contributor.setTexture(skins.get(contributor.getMinecraftName()));
} else {
contributor.setTexture(pullTexture(contributor, skins));
return contributor.getUniqueId().isPresent() ? 1 : 2;
}
} catch (IllegalArgumentException x) {
// There cannot be a texture found because it is not a valid MC username
contributor.setTexture(null);
} catch (InterruptedException x) {
Slimefun.logger().log(Level.WARNING, "The contributors thread was interrupted!");
Thread.currentThread().interrupt();
} catch (Exception x) {
// Too many requests
Slimefun.logger().log(Level.WARNING, "Attempted to refresh skin cache, got this response: {0}: {1}", new Object[] { x.getClass().getSimpleName(), x.getMessage() });
Slimefun.logger().log(Level.WARNING, "This usually means mojang.com is temporarily down or started to rate-limit this connection, nothing to worry about!");
String msg = x.getMessage();
// Retry after 5 minutes if it was just rate-limiting
if (msg != null && msg.contains("429")) {
Bukkit.getScheduler().runTaskLaterAsynchronously(Slimefun.instance(), this::grabTextures, 5 * 60 * 20L);
}
return -1;
}
}
return 0;
}
private @Nullable String pullTexture(@Nonnull Contributor contributor, @Nonnull Map<String, String> skins) throws InterruptedException, ExecutionException, TimeoutException {
Optional<UUID> uuid = contributor.getUniqueId();
if (!uuid.isPresent()) {
CompletableFuture<UUID> future = UUIDLookup.getUuidFromUsername(Slimefun.instance(), contributor.getMinecraftName());
// Fixes #3241 - Do not wait for more than 30 seconds
uuid = Optional.ofNullable(future.get(30, TimeUnit.SECONDS));
uuid.ifPresent(contributor::setUniqueId);
}
if (uuid.isPresent()) {
CompletableFuture<PlayerSkin> future = PlayerSkin.fromPlayerUUID(Slimefun.instance(), uuid.get());
Optional<String> skin = Optional.of(future.get().getProfile().getBase64Texture());
skins.put(contributor.getMinecraftName(), skin.orElse(""));
return skin.orElse(null);
} else {
return null;
}
}
}