Rewrite *everything* to use Okio

This commit is contained in:
comp500 2019-06-21 16:14:25 +01:00
parent 81c1ebaa15
commit 442fb93ca8
No known key found for this signature in database
GPG Key ID: 214C822FFEC586B5
16 changed files with 226 additions and 325 deletions

View File

@ -4,5 +4,6 @@
"**/.project": true, "**/.project": true,
"**/.settings": true, "**/.settings": true,
"**/.factorypath": true "**/.factorypath": true
} },
"java.configuration.updateBuildConfiguration": "interactive"
} }

View File

@ -11,6 +11,7 @@ dependencies {
// TODO: Implement tests // TODO: Implement tests
//testImplementation 'junit:junit:4.12' //testImplementation 'junit:junit:4.12'
implementation 'com.google.code.gson:gson:2.8.1' implementation 'com.google.code.gson:gson:2.8.1'
implementation 'com.squareup.okio:okio:2.2.2'
} }
repositories { repositories {

View File

@ -29,10 +29,14 @@ import com.moandjiezana.toml.Toml;
import link.infra.packwiz.installer.metadata.IndexFile; import link.infra.packwiz.installer.metadata.IndexFile;
import link.infra.packwiz.installer.metadata.ManifestFile; import link.infra.packwiz.installer.metadata.ManifestFile;
import link.infra.packwiz.installer.metadata.PackFile; 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.request.HandlerManager;
import link.infra.packwiz.installer.ui.IUserInterface; import link.infra.packwiz.installer.ui.IUserInterface;
import link.infra.packwiz.installer.ui.InstallProgress; import link.infra.packwiz.installer.ui.InstallProgress;
import okio.Buffer;
import okio.Okio;
import okio.Source;
public class UpdateManager { public class UpdateManager {
@ -118,13 +122,10 @@ public class UpdateManager {
} }
ui.submitProgress(new InstallProgress("Loading pack file...")); ui.submitProgress(new InstallProgress("Loading pack file..."));
Hash.HashInputStream packFileStream; GeneralHashingSource packFileSource;
try { try {
InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI); Source src = HandlerManager.getFileSource(opts.downloadURI);
if (stream == null) { packFileSource = HashUtils.getHasher("sha256").getHashingSource(src);
throw new Exception("Pack file URI is invalid, is it supported?");
}
packFileStream = new Hash.HashInputStream(stream, "sha256");
} catch (Exception e) { } catch (Exception e) {
// TODO: still launch the game if updating doesn't work? // 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 // 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; PackFile pf;
try { try {
pf = new Toml().read(packFileStream).to(PackFile.class); pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
ui.handleExceptionAndExit(e); ui.handleExceptionAndExit(e);
return; return;
} }
Hash packFileHash = packFileStream.get(); if (packFileSource.hashIsEqual(manifest.packFileHash)) {
if (packFileHash.equals(manifest.packFileHash)) {
System.out.println("Hash already up to date!"); System.out.println("Hash already up to date!");
// WOOO it's already up to date // WOOO it's already up to date
// todo: --force? // todo: --force?
@ -148,11 +148,15 @@ public class UpdateManager {
System.out.println(pf.name); System.out.println(pf.name);
processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file), try {
new Hash(pf.index.hash, pf.index.hashFormat), manifest); 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 // When successfully updated
manifest.packFileHash = packFileHash; manifest.packFileHash = packFileSource.getHash();
// update other hashes // update other hashes
// TODO: don't do this on failure? // TODO: don't do this on failure?
try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) { try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) {
@ -168,14 +172,11 @@ public class UpdateManager {
// TODO: implement // TODO: implement
} }
protected void processIndex(URI indexUri, Hash indexHash, ManifestFile manifest) { protected void processIndex(URI indexUri, Object indexHash, ManifestFile manifest) {
Hash.HashInputStream indexFileStream; GeneralHashingSource indexFileSource;
try { try {
InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI); Source src = HandlerManager.getFileSource(opts.downloadURI);
if (stream == null) { indexFileSource = HashUtils.getHasher("sha256").getHashingSource(src);
throw new Exception("Index file URI is invalid, is it supported?");
}
indexFileStream = new Hash.HashInputStream(stream, indexHash);
} catch (Exception e) { } catch (Exception e) {
// TODO: still launch the game if updating doesn't work? // 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 // 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; IndexFile indexFile;
try { try {
indexFile = new Toml().read(indexFileStream).to(IndexFile.class); indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
ui.handleExceptionAndExit(e); ui.handleExceptionAndExit(e);
return; return;
} }
if (!indexFileStream.hashIsEqual()) { if (!indexFileSource.hashIsEqual(indexHash)) {
System.out.println("Hash problems!!!!!!!"); System.out.println("Hash problems!!!!!!!");
// TODO: throw exception // TODO: throw exception
} }
@ -199,7 +200,13 @@ public class UpdateManager {
ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>(); ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>();
List<IndexFile.File> newFiles = indexFile.files.stream().filter(f -> { List<IndexFile.File> newFiles = indexFile.files.stream().filter(f -> {
ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file); 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); return cachedFile == null || newHash.equals(cachedFile.hash);
}).parallel().map(f -> { }).parallel().map(f -> {
try { try {
@ -248,15 +255,22 @@ public class UpdateManager {
} }
try { try {
InputStream stream = f.getInputStream(indexUri); Source src = f.getSource(indexUri);
if (stream == null) { GeneralHashingSource fileSource = HashUtils.getHasher(f.hashFormat).getHashingSource(src);
throw new Exception("Failed to open download stream"); 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; return dc;
} catch (Exception e) { } catch (Exception e) {

View File

@ -1,6 +1,5 @@
package link.infra.packwiz.installer.metadata; package link.infra.packwiz.installer.metadata;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
@ -8,8 +7,11 @@ import java.util.List;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.moandjiezana.toml.Toml; 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 link.infra.packwiz.installer.request.HandlerManager;
import okio.Okio;
import okio.Source;
public class IndexFile { public class IndexFile {
@SerializedName("hash-format") @SerializedName("hash-format")
@ -38,43 +40,40 @@ public class IndexFile {
if (hashFormat == null || hashFormat.length() == 0) { if (hashFormat == null || hashFormat.length() == 0) {
hashFormat = parentIndexFile.hashFormat; hashFormat = parentIndexFile.hashFormat;
} }
Hash fileHash = new Hash(hash, hashFormat); Object fileHash = HashUtils.getHash(hashFormat, hash);
linkedFileURI = HandlerManager.getNewLoc(indexUri, file); linkedFileURI = HandlerManager.getNewLoc(indexUri, file);
InputStream stream = HandlerManager.getFileInputStream(linkedFileURI); Source src = HandlerManager.getFileSource(linkedFileURI);
if (stream == null) { GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src);
throw new Exception("Index file URI is invalid, is it supported?");
}
Hash.HashInputStream fileStream = new Hash.HashInputStream(stream, fileHash);
linkedFile = new Toml().read(fileStream).to(ModFile.class); linkedFile = new Toml().read(Okio.buffer(fileStream).inputStream()).to(ModFile.class);
if (!fileStream.hashIsEqual()) { if (!fileStream.hashIsEqual(fileHash)) {
throw new Exception("Invalid mod file hash"); throw new Exception("Invalid mod file hash");
} }
} }
public InputStream getInputStream(URI indexUri) throws Exception { public Source getSource(URI indexUri) throws Exception {
if (metafile) { if (metafile) {
if (linkedFile == null) { if (linkedFile == null) {
throw new Exception("Linked file doesn't exist!"); throw new Exception("Linked file doesn't exist!");
} }
return linkedFile.getInputStream(linkedFileURI); return linkedFile.getSource(linkedFileURI);
} else { } else {
URI newLoc = HandlerManager.getNewLoc(indexUri, file); URI newLoc = HandlerManager.getNewLoc(indexUri, file);
if (newLoc == null) { if (newLoc == null) {
throw new Exception("Index file URI is invalid"); 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) { if (hash == null) {
throw new Exception("Index file doesn't have a hash"); throw new Exception("Index file doesn't have a hash");
} }
if (hashFormat == null) { if (hashFormat == null) {
throw new Exception("Index file doesn't have a hash format"); throw new Exception("Index file doesn't have a hash format");
} }
return new Hash(hash, hashFormat); return HashUtils.getHash(hashFormat, hash);
} }
public String getName() { public String getName() {

View File

@ -3,18 +3,16 @@ package link.infra.packwiz.installer.metadata;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import link.infra.packwiz.installer.metadata.hash.Hash;
public class ManifestFile { public class ManifestFile {
public Hash packFileHash = null; public Object packFileHash = null;
public Hash indexFileHash = null; public Object indexFileHash = null;
public Map<URI, File> cachedFiles; public Map<URI, File> cachedFiles;
public static class File { public static class File {
public Hash hash = null; public Object hash = null;
public boolean isOptional = false; public boolean isOptional = false;
public boolean optionValue = true; public boolean optionValue = true;
public Hash linkedFileHash = null; public Object linkedFileHash = null;
} }
} }

View File

@ -1,14 +1,14 @@
package link.infra.packwiz.installer.metadata; package link.infra.packwiz.installer.metadata;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import link.infra.packwiz.installer.UpdateManager.Options.Side; 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 link.infra.packwiz.installer.request.HandlerManager;
import okio.Source;
public class ModFile { public class ModFile {
public String name; public String name;
@ -33,7 +33,7 @@ public class ModFile {
public boolean defaultValue; public boolean defaultValue;
} }
public InputStream getInputStream(URI baseLoc) throws Exception { public Source getSource(URI baseLoc) throws Exception {
if (download == null) { if (download == null) {
throw new Exception("Metadata file doesn't have download"); throw new Exception("Metadata file doesn't have download");
} }
@ -45,10 +45,10 @@ public class ModFile {
throw new Exception("Metadata file URI is invalid"); 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) { if (download == null) {
throw new Exception("Metadata file doesn't have download"); throw new Exception("Metadata file doesn't have download");
} }
@ -58,7 +58,7 @@ public class ModFile {
if (download.hashFormat == null) { if (download.hashFormat == null) {
throw new Exception("Metadata file doesn't have a hash format"); 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() { public boolean isOptional() {

View File

@ -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());
}
}

View File

@ -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<String, IHasher> hashTypeConversion = new HashMap<String, IHasher>();
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);
}
}
}

View File

@ -1,46 +1,27 @@
package link.infra.packwiz.installer.metadata.hash; package link.infra.packwiz.installer.metadata.hash;
import java.util.HashMap;
import java.util.Map;
public class HashUtils { public class HashUtils {
private HashUtils() {} private static final Map<String, IHasher> hashTypeConversion = new HashMap<String, IHasher>();
static {
// Why did Java remove this in 1.9????! hashTypeConversion.put("sha256", new HasherHashingSource("sha256"));
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 ) { public static IHasher getHasher(String type) throws Exception {
if( '0'<=ch && ch<='9' ) return ch-'0'; IHasher hasher = hashTypeConversion.get(type);
if( 'A'<=ch && ch<='F' ) return ch-'A'+10; if (hasher == null) {
if( 'a'<=ch && ch<='f' ) return ch-'a'+10; throw new Exception("Hash type not supported!");
return -1; }
return hasher;
} }
private static final char[] hexCode = "0123456789abcdef".toCharArray(); public static Object getHash(String type, String value) throws Exception {
if (hashTypeConversion.containsKey(type)) {
public static String printHexBinary(byte[] data) { return hashTypeConversion.get(type).getHash(value);
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(); throw new Exception("Hash type not supported!");
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,17 +1,8 @@
package link.infra.packwiz.installer.metadata.hash; package link.infra.packwiz.installer.metadata.hash;
import okio.Source;
public interface IHasher { public interface IHasher {
public void update(byte[] data); public GeneralHashingSource getHashingSource(Source delegate);
public void update(byte[] data, int offset, int length); public Object getHash(String value);
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);
}
} }

View File

@ -1,12 +1,12 @@
package link.infra.packwiz.installer.request; package link.infra.packwiz.installer.request;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub; import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub;
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP; import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP;
import okio.Source;
public abstract class HandlerManager { 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 // Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
// Caching system? Copy from already downloaded files? // 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) { for (IRequestHandler handler : handlers) {
if (handler.matchesHandler(loc)) { if (handler.matchesHandler(loc)) {
InputStream stream = handler.getFileInputStream(loc); Source src = handler.getFileSource(loc);
if (stream == null) { if (src == null) {
throw new Exception("Couldn't find URI: " + loc.toString()); throw new Exception("Couldn't find URI: " + loc.toString());
} else { } else {
return stream; return src;
} }
} }
} }
@ -50,25 +50,6 @@ public abstract class HandlerManager {
throw new Exception("No handler available for URI: " + loc.toString()); throw new Exception("No handler available for URI: " + loc.toString());
} }
// To enqueue stuff:
// private ExecutorService threadPool = Executors.newFixedThreadPool(10);
// CompletionService<InputStream> completionService = new ExecutorCompletionService<InputStream>(threadPool);
//
// public Future<InputStream> enqueue(URI loc) {
// for (IRequestHandler handler : handlers) {
// if (handler.matchesHandler(loc)) {
// return completionService.submit(new Callable<InputStream>() {
// public InputStream call() {
// return handler.getFileInputStream(loc);
// }
// });
// }
// }
// // TODO: throw error??
// return null;
// }
// Use completionService.take() to get (waits until available) a Future<InputStream>, where you can call .get() and handle exceptions etc
// github toml resolution // github toml resolution
// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml // e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml // https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml

View File

@ -1,8 +1,9 @@
package link.infra.packwiz.installer.request; package link.infra.packwiz.installer.request;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import okio.Source;
/** /**
* IRequestHandler handles requests for locations specified in modpack metadata. * 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. * It is assumed that each location is read only once for the duration of an IRequestHandler.
* @param loc The location to be read * @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 * @throws Exception
*/ */
public InputStream getFileInputStream(URI loc) throws Exception; public Source getFileSource(URI loc) throws Exception;
} }

View File

@ -1,10 +1,11 @@
package link.infra.packwiz.installer.request.handlers; package link.infra.packwiz.installer.request.handlers;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URLConnection; import java.net.URLConnection;
import link.infra.packwiz.installer.request.IRequestHandler; import link.infra.packwiz.installer.request.IRequestHandler;
import okio.Okio;
import okio.Source;
public class RequestHandlerHTTP implements IRequestHandler { public class RequestHandlerHTTP implements IRequestHandler {
@ -15,14 +16,14 @@ public class RequestHandlerHTTP implements IRequestHandler {
} }
@Override @Override
public InputStream getFileInputStream(URI loc) throws Exception { public Source getFileSource(URI loc) throws Exception {
URLConnection conn = loc.toURL().openConnection(); URLConnection conn = loc.toURL().openConnection();
// TODO: when do we send specific headers??? should there be a way to signal this? // TODO: when do we send specific headers??? should there be a way to signal this?
// github *sometimes* requires it, sometimes not! // github *sometimes* requires it, sometimes not!
//conn.addRequestProperty("Accept", "application/octet-stream"); //conn.addRequestProperty("Accept", "application/octet-stream");
// 30 second read timeout // 30 second read timeout
conn.setReadTimeout(30 * 1000); conn.setReadTimeout(30 * 1000);
return conn.getInputStream(); return Okio.source(conn.getInputStream());
} }
} }

View File

@ -1,9 +1,6 @@
package link.infra.packwiz.installer.request.handlers; package link.infra.packwiz.installer.request.handlers;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
@ -14,6 +11,11 @@ import java.util.function.Predicate;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import okio.Buffer;
import okio.BufferedSource;
import okio.Okio;
import okio.Source;
public abstract class RequestHandlerZip extends RequestHandlerHTTP { public abstract class RequestHandlerZip extends RequestHandlerHTTP {
protected final boolean modeHasFolder; protected final boolean modeHasFolder;
@ -33,31 +35,33 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
private class ZipReader { private class ZipReader {
private final ZipInputStream zis; private final ZipInputStream zis;
private final Map<URI, byte[]> readFiles = new HashMap<URI, byte[]>(); private final Map<URI, Buffer> readFiles = new HashMap<URI, Buffer>();
// Write lock implies access to ZipInputStream - only 1 thread must read at a time! // Write lock implies access to ZipInputStream - only 1 thread must read at a time!
final ReentrantLock filesLock = new ReentrantLock(); final ReentrantLock filesLock = new ReentrantLock();
private ZipEntry entry; private ZipEntry entry;
public ZipReader(InputStream zip) { private final BufferedSource zipSource;
zis = new ZipInputStream(zip);
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 // File lock must be obtained before calling this function
private byte[] readCurrFile() throws IOException { private Buffer readCurrFile() throws IOException {
byte[] bytes = new byte[(int) entry.getSize()]; Buffer fileBuffer = new Buffer();
DataInputStream dis = new DataInputStream(zis); zipSource.readFully(fileBuffer, entry.getSize());
dis.readFully(bytes); return fileBuffer;
return bytes;
} }
// File lock must be obtained before calling this function // 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) { while (true) {
entry = zis.getNextEntry(); entry = zis.getNextEntry();
if (entry == null) { if (entry == null) {
return null; return null;
} }
byte[] data = readCurrFile(); Buffer data = readCurrFile();
URI fileLoc = new URI(removeFolder(entry.getName())); URI fileLoc = new URI(removeFolder(entry.getName()));
if (loc.equals(fileLoc)) { if (loc.equals(fileLoc)) {
return data; 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(); filesLock.lock();
// Assume files are only read once, allow GC by removing // Assume files are only read once, allow GC by removing
byte[] file = readFiles.remove(loc); Buffer file = readFiles.remove(loc);
if (file != null) { if (file != null) {
filesLock.unlock(); filesLock.unlock();
return new ByteArrayInputStream(file); return file;
} }
file = findFile(loc); file = findFile(loc);
filesLock.unlock(); filesLock.unlock();
if (file != null) { if (file != null) {
return new ByteArrayInputStream(file); return file;
} }
return null; return null;
} }
@ -99,7 +103,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
filesLock.unlock(); filesLock.unlock();
return null; return null;
} }
byte[] data = readCurrFile(); Buffer data = readCurrFile();
URI fileLoc = new URI(removeFolder(entry.getName())); URI fileLoc = new URI(removeFolder(entry.getName()));
readFiles.put(fileLoc, data); readFiles.put(fileLoc, data);
if (matches.test(fileLoc)) { if (matches.test(fileLoc)) {
@ -122,7 +126,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
public abstract boolean matchesHandler(URI loc); public abstract boolean matchesHandler(URI loc);
@Override @Override
public InputStream getFileInputStream(URI loc) throws Exception { public Source getFileSource(URI loc) throws Exception {
URI zipUri = getZipUri(loc); URI zipUri = getZipUri(loc);
cacheLock.readLock().lock(); cacheLock.readLock().lock();
ZipReader zr = cache.get(zipUri); 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 // Recheck, because unlocking read lock allows another thread to modify it
zr = cache.get(zipUri); zr = cache.get(zipUri);
if (zr == null) { if (zr == null) {
InputStream str = super.getFileInputStream(zipUri); Source src = super.getFileSource(zipUri);
if (str == null) { if (src == null) {
cacheLock.writeLock().unlock(); cacheLock.writeLock().unlock();
return null; return null;
} }
zr = new ZipReader(str); zr = new ZipReader(src);
cache.put(zipUri, zr); cache.put(zipUri, zr);
} }
cacheLock.writeLock().unlock(); cacheLock.writeLock().unlock();
} }
return zr.getFileInputStream(getLocationInZip(loc)); return zr.getFileSource(getLocationInZip(loc));
} }
protected URI findInZip(URI loc, Predicate<URI> matches) throws Exception { protected URI findInZip(URI loc, Predicate<URI> matches) throws Exception {
@ -156,7 +160,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
// Recheck, because unlocking read lock allows another thread to modify it // Recheck, because unlocking read lock allows another thread to modify it
zr = cache.get(zipUri); zr = cache.get(zipUri);
if (zr == null) { if (zr == null) {
zr = new ZipReader(super.getFileInputStream(zipUri)); zr = new ZipReader(super.getFileSource(zipUri));
cache.put(zipUri, zr); cache.put(zipUri, zr);
} }
cacheLock.writeLock().unlock(); cacheLock.writeLock().unlock();