File -> Path and refactoring

This commit is contained in:
Minecon724 2025-05-05 09:47:06 +02:00
commit 7c8fb07b5d
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
11 changed files with 120 additions and 116 deletions

View file

@ -1,22 +1,22 @@
package eu.m724.jarupdater.download;
import java.io.File;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
public interface Downloader {
/**
* downloads a file and verifies it
* @param url the file url to download from
* @param sha256hex the sha256 hash to verify
* @return a future which can throw ioexception or signatureexception
* Downloads a file and verifies its checksum
* @param url The file URL
* @param sha256hex The expected SHA-256 hash (in hex)
* @return A future which returns the saved file. It can throw {@link java.io.IOException} and {@link java.security.SignatureException}
*/
CompletableFuture<File> downloadAndVerify(String url, String sha256hex);
CompletableFuture<Path> downloadAndVerify(String url, String sha256hex);
/**
* moves source into destination
* @param source source file
* @param destination destination file (path)
* @return a future which can throw ioexception
* Moves source into destination
* @param source The source file
* @param destination The destination file (not folder)
* @return A future which can throw {@link java.io.IOException}
*/
CompletableFuture<Void> install(File source, File destination);
CompletableFuture<Void> install(Path source, Path destination);
}

View file

@ -1,9 +1,8 @@
package eu.m724.jarupdater.download;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
@ -12,6 +11,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -21,14 +21,14 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class SimpleDownloader implements Downloader {
private String branding;
private final String branding;
public SimpleDownloader(String branding) {
this.branding = branding;
}
@Override
public CompletableFuture<File> downloadAndVerify(String url, String sha256hex) { // TODO progress?
public CompletableFuture<Path> downloadAndVerify(String url, String sha256hex) { // TODO progress?
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
@ -40,44 +40,41 @@ public class SimpleDownloader implements Downloader {
.followRedirects(Redirect.NORMAL)
.proxy(ProxySelector.getDefault()).build().
sendAsync(request, BodyHandlers.ofInputStream());
CompletableFuture<File> fileFuture =
responseFuture.thenApply(response -> {
try (InputStream bodyStream = response.body()) {
File downloadedFile = Files.createTempFile(branding, null).toFile();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
try (FileOutputStream fileStream = new FileOutputStream(downloadedFile)) {
while (bodyStream.available() > 0) {
byte[] bytes = bodyStream.readAllBytes();
messageDigest.update(bytes);
fileStream.write(bytes);
}
}
if (!Arrays.equals(messageDigest.digest(), hexStringToByteArray(sha256hex)))
throw new SignatureException(); // This is not outside try because impact is too small to justify more code
return downloadedFile;
} catch (IOException | NoSuchAlgorithmException | SignatureException e) {
throw new CompletionException(e);
}
});
return fileFuture;
return responseFuture.thenApply(response -> {
try (InputStream bodyStream = response.body()) {
Path tempFile = Files.createTempFile(branding, null);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
try (OutputStream tempFileOutputStream = Files.newOutputStream(tempFile)) {
while (bodyStream.available() > 0) {
byte[] bytes = bodyStream.readAllBytes();
messageDigest.update(bytes);
tempFileOutputStream.write(bytes);
}
}
if (!Arrays.equals(messageDigest.digest(), hexStringToByteArray(sha256hex)))
throw new SignatureException(); // This is not outside try because impact is too small to justify more code
return tempFile;
} catch (IOException | NoSuchAlgorithmException | SignatureException e) {
throw new CompletionException(e);
}
});
}
@Override
public CompletableFuture<Void> install(File source, File destination) {
public CompletableFuture<Void> install(Path source, Path destination) {
return CompletableFuture.runAsync(() -> {
// TODO what if we changed File to Path and every ref in this file
try {
Files.move(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new CompletionException(e);
}

View file

@ -3,7 +3,21 @@ package eu.m724.jarupdater.environment;
import java.nio.file.Path;
public interface Environment {
/**
* Get the version of running software.
* @return The running software version
*/
String getRunningVersion();
/**
* Get the configured update channel.
* @return The configured update channel
*/
String getChannel();
/**
* Get the path to the running JAR file.
* @return The path to the running JAR file.
*/
Path getRunningJarFilePath();
}

View file

@ -41,7 +41,7 @@ public class HttpMetadataDAO implements MetadataDAO {
return makeRequest(url).thenApply(response -> {
if (response.statusCode() == 404) {
throw new CompletionException(new NoSuchVersionException(versionLabel));
throw new CompletionException(new NoSuchVersionException(channel, versionLabel));
}
if (response.statusCode() != 200) {

View file

@ -2,9 +2,12 @@ package eu.m724.jarupdater.object;
import java.io.IOException;
/**
* Thrown when trying to retrieve metadata of a non-existent version.
*/
public class NoSuchVersionException extends IOException {
public NoSuchVersionException(String version) {
super(version);
public NoSuchVersionException(String channel, String version) {
super(String.format("Version %s not found in channel %s", version, channel));
}
}

View file

@ -34,36 +34,35 @@ public class Version {
}
/**
* release time of a version
* The release time of this version in Unix seconds
*/
public long getTimestamp() {
return timestamp;
}
/**
* label aka version string<br>
* example: 1.0.0
* The label / name of this version, like 1.0.0
*/
public String getLabel() {
return label;
}
/**
* url of the downloadable jar file
* URL of this version's downloadable JAR file
*/
public String getFileUrl() {
return fileUrl;
}
/**
* url of changelog file
* The URL to this version's changelog
*/
public String getChangelogUrl() {
return changelogUrl;
}
/**
* sha256 hash of that file
* SHA-256 hash of this version's JAR file
*/
public String getSha256() {
return sha256;

View file

@ -7,20 +7,20 @@ import eu.m724.jarupdater.object.Version;
import eu.m724.jarupdater.verify.VerificationException;
import eu.m724.jarupdater.verify.Verifier;
import java.io.File;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class Updater {
protected Environment environment;
protected MetadataFacade metadataProvider;
protected Downloader downloader;
protected Verifier verifier;
protected final Environment environment;
protected final MetadataFacade metadataProvider;
protected final Downloader downloader;
protected final Verifier verifier;
protected CompletableFuture<File> downloaded;
protected CompletableFuture<Path> downloadFuture;
public Updater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier) {
this.environment = environment;
@ -28,10 +28,6 @@ public class Updater {
this.downloader = downloader;
this.verifier = verifier;
}
public Environment getEnvironment() {
return environment;
}
/**
* Get all channels
@ -71,18 +67,17 @@ public class Updater {
* Download the latest available version
* @return a future which returns the downloaded file
*/
public CompletableFuture<File> downloadLatestVersion() {
public CompletableFuture<Path> downloadLatestVersion() {
CompletableFuture<Version> latestVersionFuture = metadataProvider.getLatestVersionMetadata();
downloaded = latestVersionFuture.thenApply(latestVersion -> {
downloadFuture = latestVersionFuture.thenComposeAsync(latestVersion -> {
String url = latestVersion.getFileUrl();
String hash = latestVersion.getSha256();
// TODO better way of catching exception?
return downloader.downloadAndVerify(url, hash).join();
return downloader.downloadAndVerify(url, hash);
});
return downloaded;
return downloadFuture;
}
/**
@ -92,16 +87,16 @@ public class Updater {
* @throws NoSuchFileException if you didn't download it first
*/
public CompletableFuture<Void> installLatestVersion() throws NoSuchFileException {
if (downloaded == null)
if (downloadFuture == null)
throw new NoSuchFileException("Download it first");
return downloaded.thenCompose(file -> {
return downloadFuture.thenCompose(file -> {
try {
verifier.verify(file.getAbsolutePath());
verifier.verify(file.toString());
} catch (VerificationException e) {
throw new CompletionException(e);
}
return downloader.install(file, environment.getRunningJarFilePath().toFile());
return downloader.install(file, environment.getRunningJarFilePath());
});
}

View file

@ -1,23 +1,22 @@
package jarupdater;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import eu.m724.jarupdater.download.Downloader;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.security.SignatureException;
import java.util.concurrent.CompletionException;
import org.junit.Test;
import eu.m724.jarupdater.download.Downloader;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class DownloaderTest {
@Test
public void testDownloader() {
Downloader downloader = new MockDownloader();
File file = downloader.downloadAndVerify("good", "a9194ba3e955ba7482c6552894d4ca41b9bbafd86f4d90f3564c02fcb9e917c2").join();
Path file = downloader.downloadAndVerify("good", "a9194ba3e955ba7482c6552894d4ca41b9bbafd86f4d90f3564c02fcb9e917c2").join();
assertNotNull(file);
try {
@ -36,7 +35,7 @@ public class DownloaderTest {
downloader.install(file, file).join();
try {
downloader.install(new File("ialsoexistfortesting"), file).join();
downloader.install(Path.of("ialsoexistfortesting"), file).join();
fail("this should have thrown");
} catch (CompletionException e) {
assert e.getCause() instanceof IOException;

View file

@ -1,18 +1,17 @@
package jarupdater;
import static org.junit.Assert.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CompletionException;
import org.junit.Test;
import eu.m724.jarupdater.environment.ConstantEnvironment;
import eu.m724.jarupdater.environment.Environment;
import eu.m724.jarupdater.live.MetadataDAO;
import eu.m724.jarupdater.live.MetadataFacade;
import eu.m724.jarupdater.object.Version;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CompletionException;
import static org.junit.Assert.*;
public class MetadataTest {
@Test
@ -29,10 +28,10 @@ public class MetadataTest {
assert cur != null;
assert lat != null;
assertFalse(cur.equals(lat));
assertTrue(cur.getLabel().equals(environment.getRunningVersion()));
assertTrue(lat.getLabel().equals("1.1"));
assertNotEquals(cur, lat);
assertEquals(cur.getLabel(), environment.getRunningVersion());
assertEquals("1.1", lat.getLabel());
try {
facade.getVersionMetadata("invalidversion").join();

View file

@ -1,22 +1,22 @@
package jarupdater;
import java.io.File;
import eu.m724.jarupdater.download.Downloader;
import java.io.IOException;
import java.nio.file.Path;
import java.security.SignatureException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import eu.m724.jarupdater.download.Downloader;
public class MockDownloader implements Downloader {
@Override
public CompletableFuture<File> downloadAndVerify(String url, String sha256hex) {
CompletableFuture<File> future = null;
public CompletableFuture<Path> downloadAndVerify(String url, String sha256hex) {
CompletableFuture<Path> future = null;
switch (url) {
case "good":
future = CompletableFuture.completedFuture(new File("ionlyexistfortesting"));
future = CompletableFuture.completedFuture(Path.of("ionlyexistfortesting"));
break;
case "invalid":
future = CompletableFuture.failedFuture(new CompletionException(new IOException()));
@ -30,10 +30,10 @@ public class MockDownloader implements Downloader {
}
@Override
public CompletableFuture<Void> install(File source, File destination) {
public CompletableFuture<Void> install(Path source, Path destination) {
CompletableFuture<Void> future = null;
if (source.getName().equals("ionlyexistfortesting"))
if (source.toString().equals("ionlyexistfortesting"))
future = CompletableFuture.completedFuture(null);
else
future = CompletableFuture.failedFuture(new CompletionException(new IOException()));

View file

@ -1,9 +1,9 @@
package jarupdater;
import eu.m724.jarupdater.live.MetadataDAO;
import eu.m724.jarupdater.object.NoSuchVersionException;
import eu.m724.jarupdater.object.Version;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -20,20 +20,18 @@ public class MockMetadataDAO implements MetadataDAO {
@Override
public CompletableFuture<Version> getMetadata(String channel, String versionLabel) {
Version version = null;
String fileUrl = "http://127.0.0.1:17357/%s/%s.jar".formatted(channel, versionLabel);
String fileUrl = "http://127.0.0.1:17357/" + channel + "/" + versionLabel + ".jar";
switch (versionLabel) {
case "1.0":
version = new Version(1, 100, "1.0", fileUrl, null, "dd3822ed965b2820aa0800f04b86f26d0b656fca3b97ddd264046076f81e3a24");
break;
case "1.1":
version = new Version(2, 200, "1.1", fileUrl, null,"4d59994f607b89987d5bff430a4229edbbb045b3da9eaf1c63fa8e5fb208d824");
break;
case "latest":
version = new Version(2, 200, "1.1", fileUrl, null, "4d59994f607b89987d5bff430a4229edbbb045b3da9eaf1c63fa8e5fb208d824");
break;
default:
return CompletableFuture.failedFuture(new CompletionException(new IOException("no such version")));
case "1.0":
version = new Version(1, 100, "1.0", fileUrl, null, "dd3822ed965b2820aa0800f04b86f26d0b656fca3b97ddd264046076f81e3a24");
break;
case "1.1":
case "latest":
version = new Version(2, 200, "1.1", fileUrl, null,"4d59994f607b89987d5bff430a4229edbbb045b3da9eaf1c63fa8e5fb208d824");
break;
default:
return CompletableFuture.failedFuture(new CompletionException(new NoSuchVersionException(channel, versionLabel)));
}
return CompletableFuture.completedFuture(version);