Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Use CompletableFutures for async update checking #1

Merged
merged 6 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Simply instantiate a new `UpdateChecker` object with your GitHub username, repos

Then call `.check()` or `.checkAsync()` on the instance to check for updates.

You can then log the update message with `logUpdateMessage()`.
You can then log the update message with `logUpdateMessage()` or `logUpdateMessageAsync()`.

```java
UpdateChecker updateChecker = new UpdateChecker("TechnicJelle", "UpdateCheckerJava", "2.0");
Expand All @@ -18,7 +18,3 @@ updateChecker.logUpdateMessage(getLogger());
```

Full javadoc API reference: [technicjelle.com/UpdateCheckerJava](https://technicjelle.com/UpdateCheckerJava/com/technicjelle/UpdateChecker.html)

When using the async method, you, or your program's users, can override it to be synchronous anyway,
by passing `-Dtechnicjelle.updatechecker.noasync` as a JVM argument.\
Example: `java -Dtechnicjelle.updatechecker.noasync -jar server.jar`
107 changes: 61 additions & 46 deletions src/main/java/com/technicjelle/UpdateChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.logging.Logger;

/**
* Checks for updates on a GitHub repository
*/
public class UpdateChecker {
private final String repoName;

private final String currentVersion;
private final URL url;

private boolean updateAvailable = false;
private String latestVersion = null;
private transient CompletableFuture<String> latestVersionFuture = null;

/**
* @param author GitHub Username
* @param repoName GitHub Repository Name
* @param currentVersion Current version of the program. This must be in the same format as the version tags on GitHub
*/
public UpdateChecker(@NotNull String author, @NotNull String repoName, @NotNull String currentVersion) {
this.repoName = repoName;
this.currentVersion = removePrefix(currentVersion);
try {
this.url = new URL("https://github.com/" + author + "/" + repoName + "/releases/latest");
Expand All @@ -39,71 +39,86 @@ public UpdateChecker(@NotNull String author, @NotNull String repoName, @NotNull
* Checks for updates from a GitHub repository's releases<br>
* <i>This method blocks the thread it is called from</i>
*
* @throws IOException If an IO exception occurs
* @see #checkAsync()
*/
public void check() throws IOException {
// Connect to GitHub website
HttpURLConnection con;
con = (HttpURLConnection) url.openConnection();
con.setInstanceFollowRedirects(false);

// Check if the response is a redirect
String newUrl = con.getHeaderField("Location");

if (newUrl == null) {
throw new IOException("Did not get a redirect");
}

// Get the latest version tag from the redirect url
String[] split = newUrl.split("/");
latestVersion = removePrefix(split[split.length - 1]);

// Check if the latest version is not the current version
if (!latestVersion.equals(currentVersion)) updateAvailable = true;
public void check() {
checkAsync();
latestVersionFuture.join();
}

/**
* Checks for updates from a GitHub repository's releases<br>
* <i>This method does not block the thread it is called from</i><br>
* <br>Start your program with <code>-Dtechnicjelle.updatechecker.noasync</code> to disable async, and just always check synchronously
* <i>This method does not block the thread it is called from</i>
*
* @see #check()
*/
public void checkAsync() {
String s = System.getProperty("technicjelle.updatechecker.noasync");
latestVersionFuture = CompletableFuture.supplyAsync(this::fetchLatestVersion);
}

/**
* Checks if necessary and returns the latest available version
*
* @return the latest available version
*/
public synchronized String getLatestVersion() {
if (latestVersionFuture == null) checkAsync();
return latestVersionFuture.join();
}

if (s != null) {
try {
check();
} catch (IOException e) {
throw new RuntimeException(e);
private String fetchLatestVersion() {
try {
// Connect to GitHub website
HttpURLConnection con;
con = (HttpURLConnection) url.openConnection();
con.setInstanceFollowRedirects(false);

// Check if the response is a redirect
String newUrl = con.getHeaderField("Location");

if (newUrl == null) {
throw new IOException("Did not get a redirect");
}
return;

// Get the latest version tag from the redirect url
String[] split = newUrl.split("/");
return removePrefix(split[split.length - 1]);
} catch (IOException ex) {
throw new CompletionException("Exception trying to fetch the latest version", ex);
}
}

new Thread(() -> {
try {
check();
} catch (IOException e) {
throw new RuntimeException(e);
}
}, repoName + "-Update-Checker").start();
/**
* Checks if necessary and returns whether an update is available or not
*
* @return <code>true</code> if there is an update available or <code>false</code> otherwise.
*/
public boolean isUpdateAvailable() {
return !getLatestVersion().equals(currentVersion);
}

/**
* This method logs a message to the console if an update is available<br>
*
* @param logger Logger to log a potential update notification to
* @throws IllegalStateException If {@link #check()} has not been called
*/
public void logUpdateMessage(@NotNull Logger logger) throws IllegalStateException {
if (latestVersion == null) throw new IllegalStateException("check() has not been called");
if (updateAvailable) {
logger.warning("New version available: v" + latestVersion + " (current: v" + currentVersion + ")");
public void logUpdateMessage(@NotNull Logger logger) {
if (isUpdateAvailable()) {
logger.warning("New version available: v" + getLatestVersion() + " (current: v" + currentVersion + ")");
logger.warning("Download it at " + url);
}
}

/**
* This method logs a message to the console if an update is available, asynchronously<br>
*
* @param logger Logger to log a potential update notification to
*/
public synchronized void logUpdateMessageAsync(@NotNull Logger logger) {
if (latestVersionFuture == null) checkAsync();
latestVersionFuture.thenRun(() -> logUpdateMessage(logger));
}

/**
* Removes a potential <code>v</code> prefix from a version
*
Expand Down