Skip to content

Instantly share code, notes, and snippets.

@palexdev
Created March 16, 2025 01:43
Show Gist options
  • Save palexdev/775f3824efd9a20a9ed35cdc80386fb4 to your computer and use it in GitHub Desktop.
Save palexdev/775f3824efd9a20a9ed35cdc80386fb4 to your computer and use it in GitHub Desktop.
A very simple API to check for updates
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GitHubUpdateChecker implements UpdateChecker {
private final String currentVersion;
private final String url;
public GitHubUpdateChecker(String currentVersion, String owner, String repo) {
this.currentVersion = currentVersion;
this.url = "https://api.github.com/repos/%s/%s/releases/latest"
.formatted(owner, repo);
}
@Override
public String getCurrentVersion() {
return currentVersion;
}
@Override
public String getLatestVersion() throws UpdateCheckException {
try {
HttpsURLConnection connection = (HttpsURLConnection) URI.create(url).toURL().openConnection();
connection.setRequestProperty("Accept", "application/vnd.github+json");
connection.setRequestProperty("X-GitHub-Api-Version", "2022-11-28");
connection.setRequestMethod("GET");
String response = read(connection);
if (response.isBlank()) return null;
Pattern tagPattern = Pattern.compile("\"tag_name\"\\s*:\\s*\"([^,]*)\",");
Matcher tagMatcher = tagPattern.matcher(response);
if (tagMatcher.find()) return tagMatcher.group(1);
} catch (Exception ex) {
throw new UpdateCheckException(ex);
}
return null;
}
@Override
public int compare(String current, String latest) {
if (latest == null) return -2;
long currL = Long.parseLong(current.replace(".", ""));
long latestL = Long.parseLong(latest.replace(".", ""));
return Long.compare(latestL, currL);
}
protected String read(HttpsURLConnection connection) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream())
)) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
}
import java.util.Comparator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public interface UpdateChecker {
String getCurrentVersion();
String getLatestVersion() throws UpdateCheckException;
/// Similar to [Comparator#compare(Object,Object)] but inverted:
///
/// - negative number: current version is newer than latest
/// - positive number: current version is older than latest
/// - zero: same version
int compare(String current, String latest);
default boolean isBeta(String version) {
return false;
}
default boolean allowBeta() {
return false;
}
default boolean isUpdateAvailable() {
try {
String curr = getCurrentVersion();
String latest = getLatestVersion();
return compare(curr, latest) > 0
&& (!isBeta(latest) || allowBeta());
} catch (UpdateCheckException ex) {
/* TODO add logging here */
return false;
}
}
default void isUpdateAvailableAsync(Consumer<Boolean> callback) {
CompletableFuture.supplyAsync(this::isUpdateAvailable, Helper.executor)
.thenAccept(callback);
}
/* Helper */
class Helper {
protected static final Executor executor = Executors.newVirtualThreadPerTaskExecutor();
}
}
public class UpdateCheckException extends RuntimeException {
public UpdateCheckException(String message) {
super(message);
}
public UpdateCheckException(String message, Throwable cause) {
super(message, cause);
}
public UpdateCheckException(Throwable cause) {
super(cause);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment