mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-04-19 21:16:30 +02:00
Rewrite *everything* to use Okio
This commit is contained in:
parent
81c1ebaa15
commit
442fb93ca8
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -4,5 +4,6 @@
|
||||
"**/.project": true,
|
||||
"**/.settings": true,
|
||||
"**/.factorypath": true
|
||||
}
|
||||
},
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
@ -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 {
|
||||
|
@ -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<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>();
|
||||
List<IndexFile.File> 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) {
|
||||
|
@ -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() {
|
||||
|
@ -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<URI, File> 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;
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<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;
|
||||
public class HashUtils {
|
||||
private static final Map<String, IHasher> hashTypeConversion = new HashMap<String, IHasher>();
|
||||
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!");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<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
|
||||
// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
|
||||
// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<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!
|
||||
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<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
|
||||
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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user