diff --git a/build.gradle b/build.gradle index 54d564d..a825be4 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,9 @@ plugins { dependencies { implementation 'commons-cli:commons-cli:1.4' implementation 'com.moandjiezana.toml:toml4j:0.7.2' + // TODO: Implement tests //testImplementation 'junit:junit:4.12' - // TODO: add GSON, as toml4j depends on it anyway + implementation 'com.google.code.gson:gson:2.8.1' } repositories { diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java index fa4617b..2e616e3 100644 --- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java +++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java @@ -1,7 +1,20 @@ package link.infra.packwiz.installer; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.net.URI; +import java.nio.file.Paths; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +import link.infra.packwiz.installer.metadata.HashInputStream; +import link.infra.packwiz.installer.metadata.HashTypeAdapter; +import link.infra.packwiz.installer.metadata.ManifestFile; +import link.infra.packwiz.installer.request.HandlerManager; import link.infra.packwiz.installer.ui.IUserInterface; import link.infra.packwiz.installer.ui.InstallProgress; @@ -71,7 +84,42 @@ public class UpdateManager { protected void start() { this.checkOptions(); + + ui.submitProgress(new InstallProgress("Loading manifest file...")); + Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(byte[].class, new HashTypeAdapter()).create(); + ManifestFile manifest; + try { + manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()), + ManifestFile.class); + } catch (FileNotFoundException e) { + manifest = new ManifestFile(); + } catch (JsonSyntaxException | JsonIOException e) { + ui.handleExceptionAndExit(e); + return; + } + ui.submitProgress(new InstallProgress("Loading pack file...")); + HashInputStream packFileStream; + try { + packFileStream = new HashInputStream(HandlerManager.getFileInputStream(opts.downloadURI)); + } catch (Exception e) { + ui.handleExceptionAndExit(e); + return; + } + // TODO: read file + try { + packFileStream.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + byte[] packFileHash = packFileStream.getHashValue(); + if (packFileHash.equals(manifest.packFileHash)) { + // WOOO it's already up to date + // todo: --force? + } + + // TODO: save manifest file } protected void checkOptions() { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java b/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java new file mode 100644 index 0000000..5579d30 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java @@ -0,0 +1,60 @@ +package link.infra.packwiz.installer.metadata; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class HashInputStream extends FilterInputStream { + + private MessageDigest md; + private byte[] output; + + public HashInputStream(InputStream in) throws NoSuchAlgorithmException { + super(in); + md = MessageDigest.getInstance("SHA-256"); + } + + @Override + public int read() throws IOException { + int value = super.read(); + if (value == -1) { + return value; + } + md.update((byte) value); + return value; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int bytesRead = super.read(b, off, len); + if (bytesRead > 0) { + md.update(b, off, len); + } + return bytesRead; + } + + @Override + public void reset() throws IOException { + throw new IOException("HashInputStream doesn't support reset()"); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void mark(int readlimit) { + // Do nothing + } + + public byte[] getHashValue() { + if (output == null) { + output = md.digest(); + } + return output; + } + +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java b/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java new file mode 100644 index 0000000..40baa21 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java @@ -0,0 +1,66 @@ +package link.infra.packwiz.installer.metadata; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class HashTypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> { + + @Override + public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(printHexBinary(src)); + } + + @Override + public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return parseHexBinary(json.getAsString()); + } + + // Why did Java remove this in 1.9????! + public static byte[] parseHexBinary(String s) { + final int len = s.length(); + + // "111" is not a valid hex encoding. + if( len%2 != 0 ) + throw new IllegalArgumentException("hexBinary needs to be even-length: "+s); + + byte[] out = new byte[len/2]; + + for( int i=0; i<len; i+=2 ) { + int h = hexToBin(s.charAt(i )); + int l = hexToBin(s.charAt(i+1)); + if( h==-1 || l==-1 ) + throw new IllegalArgumentException("contains illegal character for hexBinary: "+s); + + out[i/2] = (byte)(h*16+l); + } + + return out; + } + + private static int hexToBin( char ch ) { + if( '0'<=ch && ch<='9' ) return ch-'0'; + if( 'A'<=ch && ch<='F' ) return ch-'A'+10; + if( 'a'<=ch && ch<='f' ) return ch-'a'+10; + return -1; + } + + private static final char[] hexCode = "0123456789ABCDEF".toCharArray(); + + public static String printHexBinary(byte[] data) { + StringBuilder r = new StringBuilder(data.length*2); + for ( byte b : data) { + r.append(hexCode[(b >> 4) & 0xF]); + r.append(hexCode[(b & 0xF)]); + } + return r.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java new file mode 100644 index 0000000..e0b3637 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java @@ -0,0 +1,5 @@ +package link.infra.packwiz.installer.metadata; + +public class ManifestFile { + public byte[] packFileHash = null; +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java b/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java new file mode 100644 index 0000000..d13ae09 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java @@ -0,0 +1,5 @@ +package link.infra.packwiz.installer.metadata; + +public class PackFile { + +} \ No newline at end of file