mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-10-16 16:04:32 +02:00
Compare commits
5 Commits
v0.0.4-pre
...
v0.0.8-pre
Author | SHA1 | Date | |
---|---|---|---|
|
a5ff63c587 | ||
|
34a86ffb7d | ||
|
780efe2c9f | ||
|
d1647764c4 | ||
|
12bf090895 |
@@ -7,10 +7,14 @@ import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -21,6 +25,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
@@ -30,6 +35,7 @@ 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.GeneralHashingSource;
|
||||
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.ui.IUserInterface;
|
||||
@@ -109,7 +115,7 @@ public class UpdateManager {
|
||||
this.checkOptions();
|
||||
|
||||
ui.submitProgress(new InstallProgress("Loading manifest file..."));
|
||||
Gson gson = new Gson();
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(Hash.class, new Hash.TypeHandler()).create();
|
||||
ManifestFile manifest;
|
||||
try {
|
||||
manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
|
||||
@@ -140,25 +146,39 @@ public class UpdateManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (manifest.packFileHash != null && packFileSource.hashIsEqual(manifest.packFileHash)) {
|
||||
System.out.println("Hash already up to date!");
|
||||
// WOOO it's already up to date
|
||||
// todo: --force?
|
||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||
|
||||
List<URI> invalidatedUris = new ArrayList<>();
|
||||
Iterator<Map.Entry<URI, ManifestFile.File>> it = manifest.cachedFiles.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<URI, ManifestFile.File> entry = it.next();
|
||||
if (entry.getValue().cachedLocation != null) {
|
||||
if (!Files.exists(Paths.get(opts.packFolder, entry.getValue().cachedLocation))) {
|
||||
URI fileUri = entry.getKey();
|
||||
System.out.println("File " + fileUri.toString() + " invalidated, marked for redownloading");
|
||||
invalidatedUris.add(fileUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(pf.name);
|
||||
if (manifest.packFileHash != null && packFileSource.hashIsEqual(manifest.packFileHash) && invalidatedUris.size() == 0) {
|
||||
System.out.println("Modpack is already up to date!");
|
||||
// todo: --force?
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Modpack name: " + pf.name);
|
||||
|
||||
try {
|
||||
processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file),
|
||||
HashUtils.getHash(pf.index.hashFormat, pf.index.hash), pf.index.hashFormat, manifest);
|
||||
HashUtils.getHash(pf.index.hashFormat, pf.index.hash), pf.index.hashFormat, manifest, invalidatedUris);
|
||||
} catch (Exception e1) {
|
||||
ui.handleExceptionAndExit(e1);
|
||||
}
|
||||
|
||||
// When successfully updated
|
||||
// TODO: update MMC params, java args etc
|
||||
|
||||
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())) {
|
||||
gson.toJson(manifest, writer);
|
||||
} catch (IOException e) {
|
||||
@@ -172,7 +192,13 @@ public class UpdateManager {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
protected void processIndex(URI indexUri, Object indexHash, String hashFormat, ManifestFile manifest) {
|
||||
protected void processIndex(URI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<URI> invalidatedUris) {
|
||||
if (manifest.indexFileHash != null && manifest.indexFileHash.equals(indexHash) && invalidatedUris.size() == 0) {
|
||||
System.out.println("Modpack files are already up to date!");
|
||||
return;
|
||||
}
|
||||
manifest.indexFileHash = indexHash;
|
||||
|
||||
GeneralHashingSource indexFileSource;
|
||||
try {
|
||||
Source src = HandlerManager.getFileSource(indexUri);
|
||||
@@ -192,16 +218,33 @@ public class UpdateManager {
|
||||
}
|
||||
|
||||
if (!indexFileSource.hashIsEqual(indexHash)) {
|
||||
System.out.println("Hash problems!!!!!!!");
|
||||
System.out.println(indexHash);
|
||||
System.out.println(indexFileSource.getHash());
|
||||
// TODO: throw exception
|
||||
return;
|
||||
}
|
||||
|
||||
if (manifest.cachedFiles == null) {
|
||||
manifest.cachedFiles = new HashMap<URI, ManifestFile.File>();
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||
Iterator<Map.Entry<URI, ManifestFile.File>> it = manifest.cachedFiles.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<URI, ManifestFile.File> entry = it.next();
|
||||
if (entry.getValue().cachedLocation != null) {
|
||||
if (!indexFile.files.stream().anyMatch(f -> f.file.equals(entry.getKey()))) {
|
||||
// File has been removed from the index
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().cachedLocation));
|
||||
} catch (IOException e) {
|
||||
// TODO: should this be shown to the user in some way?
|
||||
e.printStackTrace();
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.submitProgress(new InstallProgress("Comparing new files..."));
|
||||
|
||||
// TODO: progress bar
|
||||
ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>();
|
||||
List<IndexFile.File> newFiles = indexFile.files.stream().map(f -> {
|
||||
@@ -210,8 +253,11 @@ public class UpdateManager {
|
||||
}
|
||||
return f;
|
||||
}).filter(f -> {
|
||||
if (invalidatedUris.contains(f.file)) {
|
||||
return true;
|
||||
}
|
||||
ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file);
|
||||
Object newHash;
|
||||
Hash newHash;
|
||||
try {
|
||||
newHash = HashUtils.getHash(f.hashFormat, f.hash);
|
||||
} catch (Exception e) {
|
||||
@@ -265,8 +311,17 @@ public class UpdateManager {
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
Path destPath = Paths.get(opts.packFolder, f.getDestURI().toString());
|
||||
|
||||
// Don't update files marked with preserve if they already exist on disk
|
||||
if (f.preserve) {
|
||||
if (Files.exists(destPath)) {
|
||||
return dc;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Object hash;
|
||||
Hash hash;
|
||||
String fileHashFormat;
|
||||
if (f.linkedFile != null) {
|
||||
hash = f.linkedFile.getHash();
|
||||
@@ -282,14 +337,19 @@ public class UpdateManager {
|
||||
Okio.buffer(fileSource).readAll(data);
|
||||
|
||||
if (fileSource.hashIsEqual(hash)) {
|
||||
Files.createDirectories(Paths.get(opts.packFolder, f.getDestURI().toString()).getParent());
|
||||
Files.copy(data.inputStream(), Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.createDirectories(destPath.getParent());
|
||||
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
System.out.println("Invalid hash for " + f.getDestURI().toString());
|
||||
System.out.println("Calculated: " + fileSource.getHash());
|
||||
System.out.println("Expected: " + hash);
|
||||
dc.err = new Exception("Hash invalid!");
|
||||
}
|
||||
|
||||
if (cachedFile != null && !destPath.equals(Paths.get(opts.packFolder, cachedFile.cachedLocation))) {
|
||||
// Delete old file if location changes
|
||||
Files.delete(Paths.get(opts.packFolder, cachedFile.cachedLocation));
|
||||
}
|
||||
|
||||
return dc;
|
||||
} catch (Exception e) {
|
||||
@@ -331,6 +391,7 @@ public class UpdateManager {
|
||||
ret.err = e;
|
||||
}
|
||||
}
|
||||
newCachedFile.cachedLocation = ret.file.getDestURI().toString();
|
||||
manifest.cachedFiles.put(ret.file.file, newCachedFile);
|
||||
}
|
||||
// TODO: show errors properly?
|
||||
|
@@ -9,6 +9,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
|
||||
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource;
|
||||
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.Okio;
|
||||
@@ -25,10 +26,8 @@ public class IndexFile {
|
||||
@SerializedName("hash-format")
|
||||
public String hashFormat;
|
||||
public String hash;
|
||||
// TODO: implement
|
||||
public String alias;
|
||||
public URI alias;
|
||||
public boolean metafile;
|
||||
// TODO: implement
|
||||
public boolean preserve;
|
||||
|
||||
public transient ModFile linkedFile;
|
||||
@@ -42,7 +41,7 @@ public class IndexFile {
|
||||
if (hashFormat == null || hashFormat.length() == 0) {
|
||||
hashFormat = parentIndexFile.hashFormat;
|
||||
}
|
||||
Object fileHash = HashUtils.getHash(hashFormat, hash);
|
||||
Hash fileHash = HashUtils.getHash(hashFormat, hash);
|
||||
linkedFileURI = HandlerManager.getNewLoc(indexUri, file);
|
||||
Source src = HandlerManager.getFileSource(linkedFileURI);
|
||||
GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src);
|
||||
@@ -68,7 +67,7 @@ public class IndexFile {
|
||||
}
|
||||
}
|
||||
|
||||
public Object getHash() throws Exception {
|
||||
public Hash getHash() throws Exception {
|
||||
if (hash == null) {
|
||||
throw new Exception("Index file doesn't have a hash");
|
||||
}
|
||||
@@ -95,6 +94,9 @@ public class IndexFile {
|
||||
}
|
||||
|
||||
public URI getDestURI() {
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
if (metafile && linkedFile != null) {
|
||||
// TODO: URIs are bad
|
||||
return file.resolve(linkedFile.filename.replace(" ", "%20"));
|
||||
|
@@ -3,16 +3,19 @@ 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 Object packFileHash = null;
|
||||
public Object indexFileHash = null;
|
||||
public Hash packFileHash = null;
|
||||
public Hash indexFileHash = null;
|
||||
public Map<URI, File> cachedFiles;
|
||||
|
||||
public static class File {
|
||||
public Object hash = null;
|
||||
public Hash hash = null;
|
||||
public boolean isOptional = false;
|
||||
public boolean optionValue = true;
|
||||
public Object linkedFileHash = null;
|
||||
public Hash linkedFileHash = null;
|
||||
public String cachedLocation = null;
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ import com.google.gson.annotations.JsonAdapter;
|
||||
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;
|
||||
@@ -50,7 +51,7 @@ public class ModFile {
|
||||
return HandlerManager.getFileSource(newLoc);
|
||||
}
|
||||
|
||||
public Object getHash() throws Exception {
|
||||
public Hash getHash() throws Exception {
|
||||
if (download == null) {
|
||||
throw new Exception("Metadata file doesn't have download");
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ public abstract class GeneralHashingSource extends ForwardingSource {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
public abstract Object getHash();
|
||||
public abstract Hash getHash();
|
||||
|
||||
public boolean hashIsEqual(Object compareTo) {
|
||||
return compareTo.equals(getHash());
|
||||
|
@@ -0,0 +1,48 @@
|
||||
package link.infra.packwiz.installer.metadata.hash;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
public abstract class Hash {
|
||||
protected abstract String getStringValue();
|
||||
|
||||
protected abstract String getType();
|
||||
|
||||
public static class TypeHandler implements JsonDeserializer<Hash>, JsonSerializer<Hash> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Hash src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject out = new JsonObject();
|
||||
out.add("type", new JsonPrimitive(src.getType()));
|
||||
out.add("value", new JsonPrimitive(src.getStringValue()));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hash deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
JsonObject obj = json.getAsJsonObject();
|
||||
String type, value;
|
||||
try {
|
||||
type = obj.get("type").getAsString();
|
||||
value = obj.get("value").getAsString();
|
||||
} catch (NullPointerException e) {
|
||||
throw new JsonParseException("Invalid hash JSON data");
|
||||
}
|
||||
try {
|
||||
return HashUtils.getHash(type, value);
|
||||
} catch (Exception e) {
|
||||
throw new JsonParseException("Failed to create hash object", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ public class HashUtils {
|
||||
return hasher;
|
||||
}
|
||||
|
||||
public static Object getHash(String type, String value) throws Exception {
|
||||
public static Hash getHash(String type, String value) throws Exception {
|
||||
if (hashTypeConversion.containsKey(type)) {
|
||||
return hashTypeConversion.get(type).getHash(value);
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ public class HashingSourceHasher implements IHasher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHash() {
|
||||
public Hash getHash() {
|
||||
if (value == null) {
|
||||
value = new HashingSourceHash(delegateHashing.hash().hex());
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class HashingSourceHasher implements IHasher {
|
||||
// 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 {
|
||||
private class HashingSourceHash extends Hash {
|
||||
String value;
|
||||
private HashingSourceHash(String value) {
|
||||
this.value = value;
|
||||
@@ -56,6 +56,16 @@ public class HashingSourceHasher implements IHasher {
|
||||
public String toString() {
|
||||
return type + ": " + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStringValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +79,7 @@ public class HashingSourceHasher implements IHasher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHash(String value) {
|
||||
public Hash getHash(String value) {
|
||||
return new HashingSourceHash(value);
|
||||
}
|
||||
|
||||
|
@@ -4,5 +4,5 @@ import okio.Source;
|
||||
|
||||
public interface IHasher {
|
||||
public GeneralHashingSource getHashingSource(Source delegate);
|
||||
public Object getHash(String value);
|
||||
public Hash getHash(String value);
|
||||
}
|
@@ -28,7 +28,7 @@ public class Murmur2Hasher implements IHasher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHash() {
|
||||
public Hash getHash() {
|
||||
if (value == null) {
|
||||
byte[] data = computeNormalizedArray(internalBuffer.readByteArray());
|
||||
value = new Murmur2Hash(Murmur2Lib.hash32(data, data.length, 1));
|
||||
@@ -54,7 +54,7 @@ public class Murmur2Hasher implements IHasher {
|
||||
|
||||
}
|
||||
|
||||
private class Murmur2Hash {
|
||||
private class Murmur2Hash extends Hash {
|
||||
int value;
|
||||
private Murmur2Hash(String value) {
|
||||
// Parsing as long then casting to int converts values gt int max value but lt uint max value
|
||||
@@ -79,6 +79,16 @@ public class Murmur2Hasher implements IHasher {
|
||||
public String toString() {
|
||||
return "murmur2: " + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStringValue() {
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getType() {
|
||||
return "murmur2";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +97,7 @@ public class Murmur2Hasher implements IHasher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHash(String value) {
|
||||
public Hash getHash(String value) {
|
||||
return new Murmur2Hash(value);
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ public class CLIHandler implements IUserInterface {
|
||||
@Override
|
||||
public void executeManager(Runnable task) {
|
||||
task.run();
|
||||
System.out.println("Finished successfully!");
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user