Skip to content

Commit 06833a3

Browse files
committed
port update checker to CompletableFuture
1 parent 454e62c commit 06833a3

File tree

5 files changed

+143
-136
lines changed

5 files changed

+143
-136
lines changed

src/main/java/com/falsepattern/lib/internal/Internet.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import java.io.*;
77
import java.net.HttpURLConnection;
88
import java.net.URL;
9+
import java.util.concurrent.CompletableFuture;
10+
import java.util.concurrent.CompletionException;
11+
import java.util.concurrent.atomic.AtomicReference;
912
import java.util.function.Consumer;
1013

1114
public class Internet {
@@ -27,6 +30,21 @@ public static void connect(URL URL, Consumer<Exception> onError, Consumer<InputS
2730
}
2831
}
2932

33+
public static CompletableFuture<byte[]> download(URL URL) {
34+
return CompletableFuture.supplyAsync(() -> {
35+
val result = new ByteArrayOutputStream();
36+
AtomicReference<Exception> caught = new AtomicReference<>();
37+
connect(URL, caught::set, (input) -> {
38+
try {
39+
transferAndClose(input, result);
40+
} catch (IOException e) {
41+
throw new CompletionException(e);
42+
}
43+
});
44+
return result.toByteArray();
45+
});
46+
}
47+
3048

3149
public static void transferAndClose(InputStream is, OutputStream target) throws IOException {
3250
var bytesRead = 0;

src/main/java/com/falsepattern/lib/internal/proxy/ClientProxy.java

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.falsepattern.lib.internal.FalsePatternLib;
44
import com.falsepattern.lib.internal.Tags;
55
import com.falsepattern.lib.updates.UpdateChecker;
6-
import com.falsepattern.lib.util.AsyncUtil;
6+
import cpw.mods.fml.client.IModGuiFactory;
77
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
88
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
99
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -15,14 +15,13 @@
1515
import net.minecraftforge.common.MinecraftForge;
1616
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
1717

18+
import java.util.Collections;
1819
import java.util.List;
19-
import java.util.concurrent.Callable;
20-
import java.util.concurrent.Future;
21-
import java.util.concurrent.TimeUnit;
20+
import java.util.concurrent.CompletableFuture;
2221

2322
@SideOnly(Side.CLIENT)
2423
public class ClientProxy extends CommonProxy {
25-
private Future<List<IChatComponent>> chatFuture;
24+
private CompletableFuture<List<IChatComponent>> chatFuture;
2625

2726
@Override
2827
public void preInit(FMLPreInitializationEvent e) {
@@ -33,21 +32,11 @@ public void preInit(FMLPreInitializationEvent e) {
3332
@Override
3433
public void postInit(FMLPostInitializationEvent e) {
3534
super.postInit(e);
36-
chatFuture = AsyncUtil.asyncWorker.submit(new Callable<List<IChatComponent>>() {
37-
@Override
38-
public List<IChatComponent> call() throws Exception {
39-
//Deadlock avoidance
40-
if (updatesFuture == null || updatesFuture.isCancelled()) {
41-
chatFuture = null;
42-
return null;
43-
}
44-
if (!updatesFuture.isDone()) {
45-
chatFuture = AsyncUtil.asyncWorker.submit(this);
46-
return null;
47-
}
48-
val updates = updatesFuture.get();
49-
return UpdateChecker.updateListToChatMessages(Tags.MODNAME, updates);
35+
chatFuture = updatesFuture.handleAsync((updates, exception) -> {
36+
if (exception != null || updates.isEmpty()) {
37+
return Collections.emptyList();
5038
}
39+
return UpdateChecker.updateListToChatMessages(Tags.MODNAME, updates);
5140
});
5241
}
5342

@@ -57,7 +46,7 @@ public void onSinglePlayer(EntityJoinWorldEvent e) {
5746
!(e.entity instanceof EntityPlayerSP)) return;
5847
val player = (EntityPlayerSP) e.entity;
5948
try {
60-
for (val line: chatFuture.get(1, TimeUnit.SECONDS)) {
49+
for (val line: chatFuture.get()) {
6150
player.addChatMessage(line);
6251
}
6352
chatFuture = null;

src/main/java/com/falsepattern/lib/internal/proxy/CommonProxy.java

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,17 @@
66
import com.falsepattern.lib.internal.LibraryConfig;
77
import com.falsepattern.lib.updates.ModUpdateInfo;
88
import com.falsepattern.lib.updates.UpdateChecker;
9-
import com.falsepattern.lib.util.AsyncUtil;
10-
import cpw.mods.fml.common.event.FMLConstructionEvent;
119
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
1210
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
1311
import lombok.val;
14-
import lombok.var;
1512

13+
import java.util.Collections;
1614
import java.util.List;
17-
import java.util.concurrent.Callable;
18-
import java.util.concurrent.ExecutionException;
19-
import java.util.concurrent.Future;
15+
import java.util.concurrent.CompletableFuture;
2016

2117
public class CommonProxy {
2218

23-
protected Future<List<ModUpdateInfo>> updatesFuture;
19+
protected CompletableFuture<List<ModUpdateInfo>> updatesFuture;
2420

2521
public void preInit(FMLPreInitializationEvent e) {
2622
ConfigurationManager.registerBus();
@@ -31,37 +27,22 @@ public void preInit(FMLPreInitializationEvent e) {
3127
}
3228
if (LibraryConfig.ENABLE_UPDATE_CHECKER) {
3329
FalsePatternLib.getLog().info("Launching asynchronous update check.");
34-
val updateCheckFuture = UpdateChecker.fetchUpdatesAsync(FalsePatternLib.UPDATE_URL);
35-
updatesFuture = AsyncUtil.asyncWorker.submit(new Callable<List<ModUpdateInfo>>() {
36-
@Override
37-
public List<ModUpdateInfo> call() {
38-
//Deadlock avoidance
39-
if (updateCheckFuture.isCancelled()) {
40-
updatesFuture = null;
41-
return null;
42-
}
43-
if (!updateCheckFuture.isDone()) {
44-
updatesFuture = AsyncUtil.asyncWorker.submit(this);
45-
return null;
46-
}
47-
try {
48-
var updates = updateCheckFuture.get();
49-
if (updates != null && updates.size() > 0) {
50-
for (val update : updates) {
51-
update.log(FalsePatternLib.getLog());
52-
}
53-
} else if (updates == null) {
54-
FalsePatternLib.getLog().warn("Unknown error while checking updates.");
55-
} else {
56-
FalsePatternLib.getLog().info("All checked mods up to date!");
57-
updates = null;
58-
}
59-
return updates;
60-
} catch (InterruptedException | ExecutionException ex) {
61-
FalsePatternLib.getLog().warn("Error while checking updates", ex);
30+
updatesFuture = UpdateChecker.fetchUpdatesAsync(FalsePatternLib.UPDATE_URL).thenApplyAsync(updates -> {
31+
if (updates == null) {
32+
updates = Collections.emptyList();
33+
}
34+
if (updates.isEmpty()) {
35+
FalsePatternLib.getLog().info("No updates found.");
36+
} else {
37+
FalsePatternLib.getLog().info("Found {} updates.", updates.size());
38+
for (val update : updates) {
39+
update.log(FalsePatternLib.getLog());
6240
}
63-
return null;
6441
}
42+
return updates;
43+
}).exceptionally(ex -> {
44+
FalsePatternLib.getLog().error("Failed to check for updates!", ex);
45+
return Collections.emptyList();
6546
});
6647
}
6748
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.falsepattern.lib.updates;
2+
3+
import com.falsepattern.lib.StableAPI;
4+
5+
@StableAPI(since = "0.8.3")
6+
public class UpdateCheckException extends Exception {
7+
public UpdateCheckException(String message) {
8+
super(message);
9+
}
10+
11+
public UpdateCheckException(String message, Throwable cause) {
12+
super(message, cause);
13+
}
14+
15+
public UpdateCheckException(Throwable cause) {
16+
super(cause);
17+
}
18+
19+
protected UpdateCheckException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20+
super(message, cause, enableSuppression, writableStackTrace);
21+
}
22+
}

src/main/java/com/falsepattern/lib/updates/UpdateChecker.java

Lines changed: 77 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,29 @@
44
import com.falsepattern.lib.StableAPI;
55
import com.falsepattern.lib.dependencies.DependencyLoader;
66
import com.falsepattern.lib.dependencies.SemanticVersion;
7-
import com.falsepattern.lib.internal.FalsePatternLib;
87
import com.falsepattern.lib.internal.Internet;
98
import com.falsepattern.lib.internal.LibraryConfig;
109
import com.falsepattern.lib.internal.Tags;
1110
import com.falsepattern.lib.text.FormattedText;
12-
import com.falsepattern.lib.util.AsyncUtil;
1311
import cpw.mods.fml.common.Loader;
1412
import lombok.val;
1513
import net.minecraft.client.resources.I18n;
1614
import net.minecraft.event.ClickEvent;
1715
import net.minecraft.util.IChatComponent;
1816

19-
import java.io.ByteArrayOutputStream;
20-
import java.io.IOException;
2117
import java.net.MalformedURLException;
2218
import java.net.URL;
2319
import java.util.ArrayList;
2420
import java.util.Collections;
2521
import java.util.List;
26-
import java.util.concurrent.Future;
27-
import java.util.concurrent.atomic.AtomicInteger;
28-
import java.util.concurrent.atomic.AtomicReference;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.CompletionException;
24+
import java.util.concurrent.atomic.AtomicBoolean;
2925

3026
@StableAPI(since = "0.8.0")
3127
public class UpdateChecker {
32-
private static final AtomicInteger jsonLibraryLoaded = new AtomicInteger(0);
33-
/**
34-
* Same this as {@link #fetchUpdates(String)}, but defers the check to a different thread. Useful for asynchronous
35-
* update checks, if you don't want to block loading.
36-
* @param url The URL to check
37-
* @return A future that will contain the update info about mods that were both available on the URL and installed
38-
*/
39-
public static Future<List<ModUpdateInfo>> fetchUpdatesAsync(String url) {
40-
return AsyncUtil.asyncWorker.submit(() -> fetchUpdates(url));
41-
}
28+
private static final AtomicBoolean jsonLibraryLoaded = new AtomicBoolean(false);
29+
4230

4331
/**
4432
* Checks for updates. The URL should be a JSON file that contains a list of mods, each with a mod ID, one or more
@@ -62,21 +50,20 @@ public static Future<List<ModUpdateInfo>> fetchUpdatesAsync(String url) {
6250
* @param url The URL to check
6351
* @return A list of mods that were both available on the URL and installed
6452
*/
65-
public static List<ModUpdateInfo> fetchUpdates(String url) {
66-
if (!LibraryConfig.ENABLE_UPDATE_CHECKER) {
67-
return null;
68-
}
69-
URL URL;
70-
try {
71-
URL = new URL(url);
72-
} catch (MalformedURLException e) {
73-
FalsePatternLib.getLog().error("Invalid URL: {}", url, e);
74-
return null;
75-
}
76-
switch (jsonLibraryLoaded.get()) {
77-
case 0:
78-
DependencyLoader.addMavenRepo("https://maven.falsepattern.com/");
53+
public static CompletableFuture<List<ModUpdateInfo>> fetchUpdatesAsync(String url) {
54+
return CompletableFuture.supplyAsync(() -> {
55+
if (!LibraryConfig.ENABLE_UPDATE_CHECKER) {
56+
throw new CompletionException(new UpdateCheckException("Update checker is disabled in config!"));
57+
}
58+
URL URL;
59+
try {
60+
URL = new URL(url);
61+
} catch (MalformedURLException e) {
62+
throw new CompletionException(new UpdateCheckException("Invalid URL: " + url, e));
63+
}
64+
if (!jsonLibraryLoaded.get()) {
7965
try {
66+
DependencyLoader.addMavenRepo("https://maven.falsepattern.com/");
8067
DependencyLoader.builder()
8168
.loadingModId(Tags.MODID)
8269
.groupId("com.falsepattern")
@@ -86,61 +73,71 @@ public static List<ModUpdateInfo> fetchUpdates(String url) {
8673
.preferredVersion(new SemanticVersion(0, 4, 1))
8774
.build();
8875
} catch (Exception e) {
89-
FalsePatternLib.getLog().error("Failed to load json library for update checker!", e);
90-
jsonLibraryLoaded.set(-1);
91-
return null;
76+
throw new CompletionException(new UpdateCheckException("Failed to load json library for update checker!", e));
9277
}
93-
jsonLibraryLoaded.set(1);
94-
break;
95-
case -1:
96-
return null;
97-
}
98-
AtomicReference<String> loadedData = new AtomicReference<>(null);
99-
Internet.connect(URL, (ex) -> FalsePatternLib.getLog().warn("Failed to check for updates from URL {}", url, ex), (input) -> {
100-
val data = new ByteArrayOutputStream();
78+
jsonLibraryLoaded.set(true);
79+
}
80+
val result = new ArrayList<ModUpdateInfo>();
81+
JsonNode parsed;
10182
try {
102-
Internet.transferAndClose(input, data);
103-
} catch (IOException e) {
104-
throw new RuntimeException(e);
83+
parsed = JsonNode.parse(Internet.download(URL).thenApply(String::new).join());
84+
} catch (CompletionException e) {
85+
throw new CompletionException(new UpdateCheckException("Failed to download update checker JSON file!", e.getCause() == null ? e : e.getCause()));
10586
}
106-
loadedData.set(data.toString());
107-
});
108-
if (loadedData.get() == null) return null;
109-
val result = new ArrayList<ModUpdateInfo>();
110-
val parsed = JsonNode.parse(loadedData.get());
111-
List<JsonNode> modList;
112-
if (parsed.isList()) {
113-
modList = parsed.getJavaList();
114-
} else {
115-
modList = Collections.singletonList(parsed);
116-
}
117-
val installedMods = Loader.instance().getIndexedModList();
118-
for (val node: modList) {
119-
if (!node.isObject()) continue;
120-
if (!node.containsKey("modid")) continue;
121-
if (!node.containsKey("latestVersion")) continue;
122-
val modid = node.getString("modid");
123-
if (!installedMods.containsKey(modid)) continue;
124-
val mod = installedMods.get(modid);
125-
val latestVersionsNode = node.get("latestVersion");
126-
List<String> latestVersions;
127-
if (latestVersionsNode.isString()) {
128-
latestVersions = Collections.singletonList(latestVersionsNode.stringValue());
129-
} else if (latestVersionsNode.isList()) {
130-
latestVersions = new ArrayList<>();
131-
for (val version: latestVersionsNode.getJavaList()) {
132-
if (!version.isString()) continue;
133-
latestVersions.add(version.stringValue());
134-
}
87+
List<JsonNode> modList;
88+
if (parsed.isList()) {
89+
modList = parsed.getJavaList();
13590
} else {
136-
continue;
91+
modList = Collections.singletonList(parsed);
92+
}
93+
val installedMods = Loader.instance().getIndexedModList();
94+
for (val node: modList) {
95+
if (!node.isObject()) continue;
96+
if (!node.containsKey("modid")) continue;
97+
if (!node.containsKey("latestVersion")) continue;
98+
val modid = node.getString("modid");
99+
if (!installedMods.containsKey(modid)) continue;
100+
val mod = installedMods.get(modid);
101+
val latestVersionsNode = node.get("latestVersion");
102+
List<String> latestVersions;
103+
if (latestVersionsNode.isString()) {
104+
latestVersions = Collections.singletonList(latestVersionsNode.stringValue());
105+
} else if (latestVersionsNode.isList()) {
106+
latestVersions = new ArrayList<>();
107+
for (val version: latestVersionsNode.getJavaList()) {
108+
if (!version.isString()) continue;
109+
latestVersions.add(version.stringValue());
110+
}
111+
} else {
112+
continue;
113+
}
114+
val currentVersion = mod.getVersion();
115+
if (latestVersions.contains(currentVersion)) continue;
116+
val updateURL = node.containsKey("updateURL") && node.get("updateURL").isString() ? node.getString("updateURL") : "";
117+
result.add(new ModUpdateInfo(modid, currentVersion, latestVersions.get(0), updateURL));
118+
}
119+
return result;
120+
});
121+
}
122+
123+
/**
124+
* Same this as {@link #fetchUpdatesAsync(String)}, but returns the result in a blocking fashion.
125+
* @param url The URL to check
126+
* @return A future that will contain the update info about mods that were both available on the URL and installed
127+
* @throws UpdateCheckException If the update checker is disabled in config, the URL is invalid, or
128+
*/
129+
public static List<ModUpdateInfo> fetchUpdates(String url) throws UpdateCheckException {
130+
try {
131+
return fetchUpdatesAsync(url).join();
132+
} catch (CompletionException e) {
133+
try {
134+
throw e.getCause();
135+
} catch (UpdateCheckException e1) {
136+
throw e1;
137+
} catch (Throwable e1) {
138+
throw new UpdateCheckException("Failed to check for updates!", e1);
137139
}
138-
val currentVersion = mod.getVersion();
139-
if (latestVersions.contains(currentVersion)) continue;
140-
val updateURL = node.containsKey("updateURL") && node.get("updateURL").isString() ? node.getString("updateURL") : "";
141-
result.add(new ModUpdateInfo(modid, currentVersion, latestVersions.get(0), updateURL));
142140
}
143-
return result;
144141
}
145142

146143
/**

0 commit comments

Comments
 (0)