diff --git a/.vscode/settings.json b/.vscode/settings.json index 330bb72..da7c4f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "**/.project": true, "**/.settings": true, "**/.factorypath": true - } + }, + "java.configuration.updateBuildConfiguration": "interactive" } \ No newline at end of file diff --git a/build.gradle b/build.gradle index a825be4..6b0727b 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ dependencies { // TODO: Implement tests //testImplementation 'junit:junit:4.12' implementation 'com.google.code.gson:gson:2.8.1' + implementation 'com.squareup.okio:okio:2.2.2' } repositories { diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java index 37568c8..0abc023 100644 --- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java +++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java @@ -29,10 +29,14 @@ import com.moandjiezana.toml.Toml; import link.infra.packwiz.installer.metadata.IndexFile; import link.infra.packwiz.installer.metadata.ManifestFile; import link.infra.packwiz.installer.metadata.PackFile; -import link.infra.packwiz.installer.metadata.hash.Hash; +import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; +import link.infra.packwiz.installer.metadata.hash.HashUtils; import link.infra.packwiz.installer.request.HandlerManager; import link.infra.packwiz.installer.ui.IUserInterface; import link.infra.packwiz.installer.ui.InstallProgress; +import okio.Buffer; +import okio.Okio; +import okio.Source; public class UpdateManager { @@ -118,13 +122,10 @@ public class UpdateManager { } ui.submitProgress(new InstallProgress("Loading pack file...")); - Hash.HashInputStream packFileStream; + GeneralHashingSource packFileSource; try { - InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI); - if (stream == null) { - throw new Exception("Pack file URI is invalid, is it supported?"); - } - packFileStream = new Hash.HashInputStream(stream, "sha256"); + Source src = HandlerManager.getFileSource(opts.downloadURI); + packFileSource = HashUtils.getHasher("sha256").getHashingSource(src); } catch (Exception e) { // TODO: still launch the game if updating doesn't work? // TODO: ask user if they want to launch the game, exit(1) if they don't @@ -133,14 +134,13 @@ public class UpdateManager { } PackFile pf; try { - pf = new Toml().read(packFileStream).to(PackFile.class); + pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class); } catch (IllegalStateException e) { ui.handleExceptionAndExit(e); return; } - Hash packFileHash = packFileStream.get(); - if (packFileHash.equals(manifest.packFileHash)) { + if (packFileSource.hashIsEqual(manifest.packFileHash)) { System.out.println("Hash already up to date!"); // WOOO it's already up to date // todo: --force? @@ -148,11 +148,15 @@ public class UpdateManager { System.out.println(pf.name); - processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file), - new Hash(pf.index.hash, pf.index.hashFormat), manifest); + try { + processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file), + HashUtils.getHash(pf.index.hash, pf.index.hashFormat), manifest); + } catch (Exception e1) { + ui.handleExceptionAndExit(e1); + } // When successfully updated - manifest.packFileHash = packFileHash; + manifest.packFileHash = packFileSource.getHash(); // update other hashes // TODO: don't do this on failure? try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) { @@ -168,14 +172,11 @@ public class UpdateManager { // TODO: implement } - protected void processIndex(URI indexUri, Hash indexHash, ManifestFile manifest) { - Hash.HashInputStream indexFileStream; + protected void processIndex(URI indexUri, Object indexHash, ManifestFile manifest) { + GeneralHashingSource indexFileSource; try { - InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI); - if (stream == null) { - throw new Exception("Index file URI is invalid, is it supported?"); - } - indexFileStream = new Hash.HashInputStream(stream, indexHash); + Source src = HandlerManager.getFileSource(opts.downloadURI); + indexFileSource = HashUtils.getHasher("sha256").getHashingSource(src); } catch (Exception e) { // TODO: still launch the game if updating doesn't work? // TODO: ask user if they want to launch the game, exit(1) if they don't @@ -184,13 +185,13 @@ public class UpdateManager { } IndexFile indexFile; try { - indexFile = new Toml().read(indexFileStream).to(IndexFile.class); + indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class); } catch (IllegalStateException e) { ui.handleExceptionAndExit(e); return; } - if (!indexFileStream.hashIsEqual()) { + if (!indexFileSource.hashIsEqual(indexHash)) { System.out.println("Hash problems!!!!!!!"); // TODO: throw exception } @@ -199,7 +200,13 @@ public class UpdateManager { ConcurrentLinkedQueue exceptionQueue = new ConcurrentLinkedQueue(); List newFiles = indexFile.files.stream().filter(f -> { ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file); - Hash newHash = new Hash(f.hash, f.hashFormat); + Object newHash; + try { + newHash = HashUtils.getHash(f.hashFormat, f.hash); + } catch (Exception e) { + exceptionQueue.add(e); + return false; + } return cachedFile == null || newHash.equals(cachedFile.hash); }).parallel().map(f -> { try { @@ -248,15 +255,22 @@ public class UpdateManager { } try { - InputStream stream = f.getInputStream(indexUri); - if (stream == null) { - throw new Exception("Failed to open download stream"); + Source src = f.getSource(indexUri); + GeneralHashingSource fileSource = HashUtils.getHasher(f.hashFormat).getHashingSource(src); + Buffer data = new Buffer(); + Okio.buffer(fileSource).readAll(data); + + Object hash; + if (f.linkedFile != null) { + hash = f.linkedFile.getHash(); + } else { + hash = f.getHash(); + } + if (fileSource.hashIsEqual(hash)) { + Files.copy(data.inputStream(), Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING); + } else { + dc.err = new Exception("Hash invalid!"); } - Hash.HashInputStream fileStream = new Hash.HashInputStream(stream, f.getHash()); - // UGHHHHHH if you're reading this point in history - // this is the point where i change EVERYTHING to okio because it's 1000000000000 times nicer for this - byte[] data = fileStream.readAllBytes(); - Files.copy(fileStream, Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING); return dc; } catch (Exception e) { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java index 29de8c7..c7ac666 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java @@ -1,6 +1,5 @@ package link.infra.packwiz.installer.metadata; -import java.io.InputStream; import java.net.URI; import java.nio.file.Paths; import java.util.List; @@ -8,8 +7,11 @@ import java.util.List; import com.google.gson.annotations.SerializedName; import com.moandjiezana.toml.Toml; -import link.infra.packwiz.installer.metadata.hash.Hash; +import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; +import link.infra.packwiz.installer.metadata.hash.HashUtils; import link.infra.packwiz.installer.request.HandlerManager; +import okio.Okio; +import okio.Source; public class IndexFile { @SerializedName("hash-format") @@ -38,43 +40,40 @@ public class IndexFile { if (hashFormat == null || hashFormat.length() == 0) { hashFormat = parentIndexFile.hashFormat; } - Hash fileHash = new Hash(hash, hashFormat); + Object fileHash = HashUtils.getHash(hashFormat, hash); linkedFileURI = HandlerManager.getNewLoc(indexUri, file); - InputStream stream = HandlerManager.getFileInputStream(linkedFileURI); - if (stream == null) { - throw new Exception("Index file URI is invalid, is it supported?"); - } - Hash.HashInputStream fileStream = new Hash.HashInputStream(stream, fileHash); + Source src = HandlerManager.getFileSource(linkedFileURI); + GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src); - linkedFile = new Toml().read(fileStream).to(ModFile.class); - if (!fileStream.hashIsEqual()) { + linkedFile = new Toml().read(Okio.buffer(fileStream).inputStream()).to(ModFile.class); + if (!fileStream.hashIsEqual(fileHash)) { throw new Exception("Invalid mod file hash"); } } - public InputStream getInputStream(URI indexUri) throws Exception { + public Source getSource(URI indexUri) throws Exception { if (metafile) { if (linkedFile == null) { throw new Exception("Linked file doesn't exist!"); } - return linkedFile.getInputStream(linkedFileURI); + return linkedFile.getSource(linkedFileURI); } else { URI newLoc = HandlerManager.getNewLoc(indexUri, file); if (newLoc == null) { throw new Exception("Index file URI is invalid"); } - return HandlerManager.getFileInputStream(newLoc); + return HandlerManager.getFileSource(newLoc); } } - public Hash getHash() throws Exception { + public Object getHash() throws Exception { if (hash == null) { throw new Exception("Index file doesn't have a hash"); } if (hashFormat == null) { throw new Exception("Index file doesn't have a hash format"); } - return new Hash(hash, hashFormat); + return HashUtils.getHash(hashFormat, hash); } public String getName() { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java index ecf5c65..09f2eb6 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java @@ -3,18 +3,16 @@ package link.infra.packwiz.installer.metadata; import java.net.URI; import java.util.Map; -import link.infra.packwiz.installer.metadata.hash.Hash; - public class ManifestFile { - public Hash packFileHash = null; - public Hash indexFileHash = null; + public Object packFileHash = null; + public Object indexFileHash = null; public Map cachedFiles; public static class File { - public Hash hash = null; + public Object hash = null; public boolean isOptional = false; public boolean optionValue = true; - public Hash linkedFileHash = null; + public Object linkedFileHash = null; } } \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java index 1f536da..6b5c10c 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java @@ -1,14 +1,14 @@ package link.infra.packwiz.installer.metadata; -import java.io.InputStream; import java.net.URI; import java.util.Map; import com.google.gson.annotations.SerializedName; import link.infra.packwiz.installer.UpdateManager.Options.Side; -import link.infra.packwiz.installer.metadata.hash.Hash; +import link.infra.packwiz.installer.metadata.hash.HashUtils; import link.infra.packwiz.installer.request.HandlerManager; +import okio.Source; public class ModFile { public String name; @@ -33,7 +33,7 @@ public class ModFile { public boolean defaultValue; } - public InputStream getInputStream(URI baseLoc) throws Exception { + public Source getSource(URI baseLoc) throws Exception { if (download == null) { throw new Exception("Metadata file doesn't have download"); } @@ -45,10 +45,10 @@ public class ModFile { throw new Exception("Metadata file URI is invalid"); } - return HandlerManager.getFileInputStream(newLoc); + return HandlerManager.getFileSource(newLoc); } - public Hash getHash() throws Exception { + public Object getHash() throws Exception { if (download == null) { throw new Exception("Metadata file doesn't have download"); } @@ -58,7 +58,7 @@ public class ModFile { if (download.hashFormat == null) { throw new Exception("Metadata file doesn't have a hash format"); } - return new Hash(download.hash, download.hashFormat); + return HashUtils.getHash(download.hash, download.hashFormat); } public boolean isOptional() { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java new file mode 100644 index 0000000..1aaba53 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java @@ -0,0 +1,18 @@ +package link.infra.packwiz.installer.metadata.hash; + +import okio.ForwardingSource; +import okio.Source; + +public abstract class GeneralHashingSource extends ForwardingSource { + + public GeneralHashingSource(Source delegate) { + super(delegate); + } + + public abstract Object getHash(); + + public boolean hashIsEqual(Object compareTo) { + return compareTo.equals(getHash()); + } + +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java deleted file mode 100644 index fd2a009..0000000 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java +++ /dev/null @@ -1,115 +0,0 @@ -package link.infra.packwiz.installer.metadata.hash; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; - -public class Hash { - public final String value; - public final String type; - - public Hash(String value, String type) { - this.value = value; - this.type = type; - } - - private static final Map hashTypeConversion = new HashMap(); - static { - try { - hashTypeConversion.put("sha256", new HasherMessageDigest("SHA-256")); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - } - - public IHasher getHasher() { - return hashTypeConversion.get(type); - } - - public static IHasher getHasher(String type) { - return hashTypeConversion.get(type); - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof Hash)) { - return false; - } - Hash hash = (Hash)obj; - return type.equals(hash.type) && getHasher().equalValues(value, hash.value); - } - - public static class HashInputStream extends FilterInputStream { - - private IHasher md; - private Hash output; - private final String hashType; - private Hash compare = null; - - public HashInputStream(InputStream in, String hashType) throws NoSuchAlgorithmException { - super(in); - this.hashType = hashType; - md = hashTypeConversion.get(hashType); - } - - public HashInputStream(InputStream in, Hash compare) throws NoSuchAlgorithmException { - this(in, compare.type); - this.compare = compare; - } - - @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 Hash get() { - if (output == null) { - String value = md.get(); - if (value != null) { - output = new Hash(value, hashType); - } - } - return output; - } - - public boolean hashIsEqual() { - if (output == null) { - get(); - } - return !output.equals(compare); - } - - } -} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java index 922ffb6..ecd95bc 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java @@ -1,46 +1,27 @@ package link.infra.packwiz.installer.metadata.hash; -public class HashUtils { - private HashUtils() {} +import java.util.HashMap; +import java.util.Map; - // 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 hashTypeConversion = new HashMap(); + static { + hashTypeConversion.put("sha256", new HasherHashingSource("sha256")); } - - 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)]); + + public static IHasher getHasher(String type) throws Exception { + IHasher hasher = hashTypeConversion.get(type); + if (hasher == null) { + throw new Exception("Hash type not supported!"); } - return r.toString(); + return hasher; + } + + public static Object getHash(String type, String value) throws Exception { + if (hashTypeConversion.containsKey(type)) { + return hashTypeConversion.get(type).getHash(value); + } + throw new Exception("Hash type not supported!"); } } \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java new file mode 100644 index 0000000..456f2c4 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java @@ -0,0 +1,71 @@ +package link.infra.packwiz.installer.metadata.hash; + +import okio.HashingSource; +import okio.Source; + +public class HasherHashingSource implements IHasher { + String type; + + public HasherHashingSource(String type) { + this.type = type; + } + + // i love naming things + private class HashingSourceGeneralHashingSource extends GeneralHashingSource { + HashingSource delegateHashing; + HashingSourceHash value; + + public HashingSourceGeneralHashingSource(HashingSource delegate) { + super(delegate); + delegateHashing = delegate; + } + + @Override + public Object getHash() { + if (value == null) { + value = new HashingSourceHash(delegateHashing.hash().hex()); + } + return value; + } + + } + + // this some funky inner class stuff + // each of these classes is specific to the instance of the HasherHashingSource + // therefore HashingSourceHashes from different parent instances will be not instanceof each other + private class HashingSourceHash { + String value; + private HashingSourceHash(String value) { + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof HashingSourceHash)) { + return false; + } + HashingSourceHash objHash = (HashingSourceHash) obj; + if (value != null) { + return value.equals(objHash.value); + } else { + return objHash.value == null ? true : false; + } + } + } + + @Override + public GeneralHashingSource getHashingSource(Source delegate) { + switch (type) { + case "sha256": + return new HashingSourceGeneralHashingSource(HashingSource.sha256(delegate)); + // TODO: support other hash types + } + throw new RuntimeException("Invalid hash type provided"); + } + + @Override + public Object getHash(String value) { + return new HashingSourceHash(value); + } + +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java deleted file mode 100644 index 04aba5e..0000000 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java +++ /dev/null @@ -1,45 +0,0 @@ -package link.infra.packwiz.installer.metadata.hash; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class HasherMessageDigest implements IHasher { - MessageDigest md; - - public HasherMessageDigest(String hashType) throws NoSuchAlgorithmException { - md = MessageDigest.getInstance(hashType); - } - - @Override - public void update(byte[] data) { - md.update(data); - } - - @Override - public void update(byte[] data, int offset, int length) { - md.update(data, offset, length); - } - - @Override - public void update(byte data) { - md.update(data); - } - - @Override - public String get() { - return HashUtils.printHexBinary(md.digest()); - } - - // Enforce case insensitivity - @Override - public boolean equalValues(String a, String b) { - if (a == null) { - if (b == null) { - return true; - } - return false; - } - return a.equalsIgnoreCase(b); - } - -} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java index 95a76a5..f037cc8 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java @@ -1,17 +1,8 @@ package link.infra.packwiz.installer.metadata.hash; +import okio.Source; + public interface IHasher { - public void update(byte[] data); - public void update(byte[] data, int offset, int length); - public void update(byte data); - public String get(); - public default boolean equalValues(String a, String b) { - if (a == null) { - if (b == null) { - return true; - } - return false; - } - return a.equals(b); - } + public GeneralHashingSource getHashingSource(Source delegate); + public Object getHash(String value); } \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java b/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java index a44ea62..ebc9231 100644 --- a/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java +++ b/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java @@ -1,12 +1,12 @@ package link.infra.packwiz.installer.request; -import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.List; import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub; import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP; +import okio.Source; public abstract class HandlerManager { @@ -35,14 +35,14 @@ public abstract class HandlerManager { // Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads // Caching system? Copy from already downloaded files? - public static InputStream getFileInputStream(URI loc) throws Exception { + public static Source getFileSource(URI loc) throws Exception { for (IRequestHandler handler : handlers) { if (handler.matchesHandler(loc)) { - InputStream stream = handler.getFileInputStream(loc); - if (stream == null) { + Source src = handler.getFileSource(loc); + if (src == null) { throw new Exception("Couldn't find URI: " + loc.toString()); } else { - return stream; + return src; } } } @@ -50,25 +50,6 @@ public abstract class HandlerManager { throw new Exception("No handler available for URI: " + loc.toString()); } - // To enqueue stuff: -// private ExecutorService threadPool = Executors.newFixedThreadPool(10); -// CompletionService completionService = new ExecutorCompletionService(threadPool); -// -// public Future enqueue(URI loc) { -// for (IRequestHandler handler : handlers) { -// if (handler.matchesHandler(loc)) { -// return completionService.submit(new Callable() { -// public InputStream call() { -// return handler.getFileInputStream(loc); -// } -// }); -// } -// } -// // TODO: throw error?? -// return null; -// } - // Use completionService.take() to get (waits until available) a Future, where you can call .get() and handle exceptions etc - // github toml resolution // e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml // https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml diff --git a/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java b/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java index aaf48b5..30d5cdc 100644 --- a/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java +++ b/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java @@ -1,8 +1,9 @@ package link.infra.packwiz.installer.request; -import java.io.InputStream; import java.net.URI; +import okio.Source; + /** * IRequestHandler handles requests for locations specified in modpack metadata. */ @@ -15,12 +16,12 @@ public interface IRequestHandler { } /** - * Gets the InputStream for a location. Must be threadsafe. + * Gets the Source for a location. Must be threadsafe. * It is assumed that each location is read only once for the duration of an IRequestHandler. * @param loc The location to be read - * @return The InputStream containing the data of the file + * @return The Source containing the data of the file * @throws Exception */ - public InputStream getFileInputStream(URI loc) throws Exception; + public Source getFileSource(URI loc) throws Exception; } diff --git a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java index 3a27486..114ef6b 100644 --- a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java +++ b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java @@ -1,10 +1,11 @@ package link.infra.packwiz.installer.request.handlers; -import java.io.InputStream; import java.net.URI; import java.net.URLConnection; import link.infra.packwiz.installer.request.IRequestHandler; +import okio.Okio; +import okio.Source; public class RequestHandlerHTTP implements IRequestHandler { @@ -15,14 +16,14 @@ public class RequestHandlerHTTP implements IRequestHandler { } @Override - public InputStream getFileInputStream(URI loc) throws Exception { + public Source getFileSource(URI loc) throws Exception { URLConnection conn = loc.toURL().openConnection(); // TODO: when do we send specific headers??? should there be a way to signal this? // github *sometimes* requires it, sometimes not! //conn.addRequestProperty("Accept", "application/octet-stream"); // 30 second read timeout conn.setReadTimeout(30 * 1000); - return conn.getInputStream(); + return Okio.source(conn.getInputStream()); } } diff --git a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java index b32276b..0217ce7 100644 --- a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java +++ b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java @@ -1,9 +1,6 @@ package link.infra.packwiz.installer.request.handlers; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; @@ -14,6 +11,11 @@ import java.util.function.Predicate; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import okio.Buffer; +import okio.BufferedSource; +import okio.Okio; +import okio.Source; + public abstract class RequestHandlerZip extends RequestHandlerHTTP { protected final boolean modeHasFolder; @@ -33,31 +35,33 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { private class ZipReader { private final ZipInputStream zis; - private final Map readFiles = new HashMap(); + private final Map readFiles = new HashMap(); // Write lock implies access to ZipInputStream - only 1 thread must read at a time! final ReentrantLock filesLock = new ReentrantLock(); private ZipEntry entry; - public ZipReader(InputStream zip) { - zis = new ZipInputStream(zip); + private final BufferedSource zipSource; + + public ZipReader(Source zip) { + zis = new ZipInputStream(Okio.buffer(zip).inputStream()); + zipSource = Okio.buffer(Okio.source(zis)); } // File lock must be obtained before calling this function - private byte[] readCurrFile() throws IOException { - byte[] bytes = new byte[(int) entry.getSize()]; - DataInputStream dis = new DataInputStream(zis); - dis.readFully(bytes); - return bytes; + private Buffer readCurrFile() throws IOException { + Buffer fileBuffer = new Buffer(); + zipSource.readFully(fileBuffer, entry.getSize()); + return fileBuffer; } // File lock must be obtained before calling this function - private byte[] findFile(URI loc) throws IOException, URISyntaxException { + private Buffer findFile(URI loc) throws IOException, URISyntaxException { while (true) { entry = zis.getNextEntry(); if (entry == null) { return null; } - byte[] data = readCurrFile(); + Buffer data = readCurrFile(); URI fileLoc = new URI(removeFolder(entry.getName())); if (loc.equals(fileLoc)) { return data; @@ -67,19 +71,19 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { } } - public InputStream getFileInputStream(URI loc) throws Exception { + public Source getFileSource(URI loc) throws Exception { filesLock.lock(); // Assume files are only read once, allow GC by removing - byte[] file = readFiles.remove(loc); + Buffer file = readFiles.remove(loc); if (file != null) { filesLock.unlock(); - return new ByteArrayInputStream(file); + return file; } file = findFile(loc); filesLock.unlock(); if (file != null) { - return new ByteArrayInputStream(file); + return file; } return null; } @@ -99,7 +103,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { filesLock.unlock(); return null; } - byte[] data = readCurrFile(); + Buffer data = readCurrFile(); URI fileLoc = new URI(removeFolder(entry.getName())); readFiles.put(fileLoc, data); if (matches.test(fileLoc)) { @@ -122,7 +126,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { public abstract boolean matchesHandler(URI loc); @Override - public InputStream getFileInputStream(URI loc) throws Exception { + public Source getFileSource(URI loc) throws Exception { URI zipUri = getZipUri(loc); cacheLock.readLock().lock(); ZipReader zr = cache.get(zipUri); @@ -132,18 +136,18 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { // Recheck, because unlocking read lock allows another thread to modify it zr = cache.get(zipUri); if (zr == null) { - InputStream str = super.getFileInputStream(zipUri); - if (str == null) { + Source src = super.getFileSource(zipUri); + if (src == null) { cacheLock.writeLock().unlock(); return null; } - zr = new ZipReader(str); + zr = new ZipReader(src); cache.put(zipUri, zr); } cacheLock.writeLock().unlock(); } - return zr.getFileInputStream(getLocationInZip(loc)); + return zr.getFileSource(getLocationInZip(loc)); } protected URI findInZip(URI loc, Predicate matches) throws Exception { @@ -156,7 +160,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP { // Recheck, because unlocking read lock allows another thread to modify it zr = cache.get(zipUri); if (zr == null) { - zr = new ZipReader(super.getFileInputStream(zipUri)); + zr = new ZipReader(super.getFileSource(zipUri)); cache.put(zipUri, zr); } cacheLock.writeLock().unlock();