Compare commits

..

6 Commits

Author SHA1 Message Date
comp500
794b817eff Check for cachedFiles existing 2019-07-04 01:08:24 +01:00
comp500
a5ff63c587 Correctly handle file invalidation 2019-07-03 23:12:11 +01:00
comp500
34a86ffb7d Handle renamed files correctly, log invalidation 2019-07-03 22:49:08 +01:00
comp500
780efe2c9f Remove files correctly, redownload when deleted 2019-07-02 23:32:00 +01:00
comp500
d1647764c4 Implement preserve and alias in index 2019-06-24 17:47:17 +01:00
comp500
12bf090895 Fix hash serialisation in JSON, check index hash 2019-06-24 17:27:28 +01:00
11 changed files with 175 additions and 37 deletions

View File

@@ -7,10 +7,14 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService; import java.util.concurrent.CompletionService;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
@@ -21,6 +25,7 @@ import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException; import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName; 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.ManifestFile;
import link.infra.packwiz.installer.metadata.PackFile; import link.infra.packwiz.installer.metadata.PackFile;
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; 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.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;
@@ -109,7 +115,7 @@ public class UpdateManager {
this.checkOptions(); this.checkOptions();
ui.submitProgress(new InstallProgress("Loading manifest file...")); ui.submitProgress(new InstallProgress("Loading manifest file..."));
Gson gson = new Gson(); Gson gson = new GsonBuilder().registerTypeAdapter(Hash.class, new Hash.TypeHandler()).create();
ManifestFile manifest; ManifestFile manifest;
try { try {
manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()), manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
@@ -140,25 +146,41 @@ public class UpdateManager {
return; return;
} }
if (manifest.packFileHash != null && packFileSource.hashIsEqual(manifest.packFileHash)) { ui.submitProgress(new InstallProgress("Checking local files..."));
System.out.println("Hash already up to date!");
// WOOO it's already up to date List<URI> invalidatedUris = new ArrayList<>();
// todo: --force? if (manifest.cachedFiles != null) {
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 { try {
processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file), 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) { } catch (Exception e1) {
ui.handleExceptionAndExit(e1); ui.handleExceptionAndExit(e1);
} }
// When successfully updated // TODO: update MMC params, java args etc
manifest.packFileHash = packFileSource.getHash(); 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())) { try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) {
gson.toJson(manifest, writer); gson.toJson(manifest, writer);
} catch (IOException e) { } catch (IOException e) {
@@ -172,7 +194,13 @@ public class UpdateManager {
// TODO: implement // 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; GeneralHashingSource indexFileSource;
try { try {
Source src = HandlerManager.getFileSource(indexUri); Source src = HandlerManager.getFileSource(indexUri);
@@ -192,16 +220,33 @@ public class UpdateManager {
} }
if (!indexFileSource.hashIsEqual(indexHash)) { if (!indexFileSource.hashIsEqual(indexHash)) {
System.out.println("Hash problems!!!!!!!");
System.out.println(indexHash);
System.out.println(indexFileSource.getHash());
// TODO: throw exception // TODO: throw exception
return;
} }
if (manifest.cachedFiles == null) { if (manifest.cachedFiles == null) {
manifest.cachedFiles = new HashMap<URI, ManifestFile.File>(); 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 // TODO: progress bar
ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>(); ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>();
List<IndexFile.File> newFiles = indexFile.files.stream().map(f -> { List<IndexFile.File> newFiles = indexFile.files.stream().map(f -> {
@@ -210,8 +255,11 @@ public class UpdateManager {
} }
return f; return f;
}).filter(f -> { }).filter(f -> {
if (invalidatedUris.contains(f.file)) {
return true;
}
ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file); ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file);
Object newHash; Hash newHash;
try { try {
newHash = HashUtils.getHash(f.hashFormat, f.hash); newHash = HashUtils.getHash(f.hashFormat, f.hash);
} catch (Exception e) { } catch (Exception e) {
@@ -265,8 +313,17 @@ public class UpdateManager {
} catch (Exception e) {} } 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 { try {
Object hash; Hash hash;
String fileHashFormat; String fileHashFormat;
if (f.linkedFile != null) { if (f.linkedFile != null) {
hash = f.linkedFile.getHash(); hash = f.linkedFile.getHash();
@@ -282,14 +339,19 @@ public class UpdateManager {
Okio.buffer(fileSource).readAll(data); Okio.buffer(fileSource).readAll(data);
if (fileSource.hashIsEqual(hash)) { if (fileSource.hashIsEqual(hash)) {
Files.createDirectories(Paths.get(opts.packFolder, f.getDestURI().toString()).getParent()); Files.createDirectories(destPath.getParent());
Files.copy(data.inputStream(), Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING); Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING);
} else { } else {
System.out.println("Invalid hash for " + f.getDestURI().toString()); System.out.println("Invalid hash for " + f.getDestURI().toString());
System.out.println("Calculated: " + fileSource.getHash()); System.out.println("Calculated: " + fileSource.getHash());
System.out.println("Expected: " + hash); System.out.println("Expected: " + hash);
dc.err = new Exception("Hash invalid!"); 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; return dc;
} catch (Exception e) { } catch (Exception e) {
@@ -331,6 +393,7 @@ public class UpdateManager {
ret.err = e; ret.err = e;
} }
} }
newCachedFile.cachedLocation = ret.file.getDestURI().toString();
manifest.cachedFiles.put(ret.file.file, newCachedFile); manifest.cachedFiles.put(ret.file.file, newCachedFile);
} }
// TODO: show errors properly? // TODO: show errors properly?

View File

@@ -9,6 +9,7 @@ import com.google.gson.annotations.SerializedName;
import com.moandjiezana.toml.Toml; import com.moandjiezana.toml.Toml;
import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource; 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.metadata.hash.HashUtils;
import link.infra.packwiz.installer.request.HandlerManager; import link.infra.packwiz.installer.request.HandlerManager;
import okio.Okio; import okio.Okio;
@@ -25,10 +26,8 @@ public class IndexFile {
@SerializedName("hash-format") @SerializedName("hash-format")
public String hashFormat; public String hashFormat;
public String hash; public String hash;
// TODO: implement public URI alias;
public String alias;
public boolean metafile; public boolean metafile;
// TODO: implement
public boolean preserve; public boolean preserve;
public transient ModFile linkedFile; public transient ModFile linkedFile;
@@ -42,7 +41,7 @@ public class IndexFile {
if (hashFormat == null || hashFormat.length() == 0) { if (hashFormat == null || hashFormat.length() == 0) {
hashFormat = parentIndexFile.hashFormat; hashFormat = parentIndexFile.hashFormat;
} }
Object fileHash = HashUtils.getHash(hashFormat, hash); Hash fileHash = HashUtils.getHash(hashFormat, hash);
linkedFileURI = HandlerManager.getNewLoc(indexUri, file); linkedFileURI = HandlerManager.getNewLoc(indexUri, file);
Source src = HandlerManager.getFileSource(linkedFileURI); Source src = HandlerManager.getFileSource(linkedFileURI);
GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src); 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) { if (hash == null) {
throw new Exception("Index file doesn't have a hash"); throw new Exception("Index file doesn't have a hash");
} }
@@ -95,6 +94,9 @@ public class IndexFile {
} }
public URI getDestURI() { public URI getDestURI() {
if (alias != null) {
return alias;
}
if (metafile && linkedFile != null) { if (metafile && linkedFile != null) {
// TODO: URIs are bad // TODO: URIs are bad
return file.resolve(linkedFile.filename.replace(" ", "%20")); return file.resolve(linkedFile.filename.replace(" ", "%20"));

View File

@@ -3,16 +3,19 @@ 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 Object packFileHash = null; public Hash packFileHash = null;
public Object indexFileHash = null; public Hash indexFileHash = null;
public Map<URI, File> cachedFiles; public Map<URI, File> cachedFiles;
public static class File { public static class File {
public Object hash = null; public Hash hash = null;
public boolean isOptional = false; public boolean isOptional = false;
public boolean optionValue = true; public boolean optionValue = true;
public Object linkedFileHash = null; public Hash linkedFileHash = null;
public String cachedLocation = null;
} }
} }

View File

@@ -7,6 +7,7 @@ import com.google.gson.annotations.JsonAdapter;
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.metadata.hash.HashUtils;
import link.infra.packwiz.installer.request.HandlerManager; import link.infra.packwiz.installer.request.HandlerManager;
import okio.Source; import okio.Source;
@@ -50,7 +51,7 @@ public class ModFile {
return HandlerManager.getFileSource(newLoc); return HandlerManager.getFileSource(newLoc);
} }
public Object getHash() throws Exception { public Hash 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");
} }

View File

@@ -9,7 +9,7 @@ public abstract class GeneralHashingSource extends ForwardingSource {
super(delegate); super(delegate);
} }
public abstract Object getHash(); public abstract Hash getHash();
public boolean hashIsEqual(Object compareTo) { public boolean hashIsEqual(Object compareTo) {
return compareTo.equals(getHash()); return compareTo.equals(getHash());

View File

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

View File

@@ -18,7 +18,7 @@ public class HashUtils {
return hasher; 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)) { if (hashTypeConversion.containsKey(type)) {
return hashTypeConversion.get(type).getHash(value); return hashTypeConversion.get(type).getHash(value);
} }

View File

@@ -21,7 +21,7 @@ public class HashingSourceHasher implements IHasher {
} }
@Override @Override
public Object getHash() { public Hash getHash() {
if (value == null) { if (value == null) {
value = new HashingSourceHash(delegateHashing.hash().hex()); value = new HashingSourceHash(delegateHashing.hash().hex());
} }
@@ -33,7 +33,7 @@ public class HashingSourceHasher implements IHasher {
// this some funky inner class stuff // this some funky inner class stuff
// each of these classes is specific to the instance of the HasherHashingSource // each of these classes is specific to the instance of the HasherHashingSource
// therefore HashingSourceHashes from different parent instances will be not instanceof each other // therefore HashingSourceHashes from different parent instances will be not instanceof each other
private class HashingSourceHash { private class HashingSourceHash extends Hash {
String value; String value;
private HashingSourceHash(String value) { private HashingSourceHash(String value) {
this.value = value; this.value = value;
@@ -56,6 +56,16 @@ public class HashingSourceHasher implements IHasher {
public String toString() { public String toString() {
return type + ": " + value; return type + ": " + value;
} }
@Override
protected String getStringValue() {
return value;
}
@Override
protected String getType() {
return type;
}
} }
@Override @Override
@@ -69,7 +79,7 @@ public class HashingSourceHasher implements IHasher {
} }
@Override @Override
public Object getHash(String value) { public Hash getHash(String value) {
return new HashingSourceHash(value); return new HashingSourceHash(value);
} }

View File

@@ -4,5 +4,5 @@ import okio.Source;
public interface IHasher { public interface IHasher {
public GeneralHashingSource getHashingSource(Source delegate); public GeneralHashingSource getHashingSource(Source delegate);
public Object getHash(String value); public Hash getHash(String value);
} }

View File

@@ -28,7 +28,7 @@ public class Murmur2Hasher implements IHasher {
} }
@Override @Override
public Object getHash() { public Hash getHash() {
if (value == null) { if (value == null) {
byte[] data = computeNormalizedArray(internalBuffer.readByteArray()); byte[] data = computeNormalizedArray(internalBuffer.readByteArray());
value = new Murmur2Hash(Murmur2Lib.hash32(data, data.length, 1)); 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; int value;
private Murmur2Hash(String value) { private Murmur2Hash(String value) {
// Parsing as long then casting to int converts values gt int max value but lt uint max 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() { public String toString() {
return "murmur2: " + value; return "murmur2: " + value;
} }
@Override
protected String getStringValue() {
return Integer.toString(value);
}
@Override
protected String getType() {
return "murmur2";
}
} }
@Override @Override
@@ -87,7 +97,7 @@ public class Murmur2Hasher implements IHasher {
} }
@Override @Override
public Object getHash(String value) { public Hash getHash(String value) {
return new Murmur2Hash(value); return new Murmur2Hash(value);
} }

View File

@@ -27,6 +27,7 @@ public class CLIHandler implements IUserInterface {
@Override @Override
public void executeManager(Runnable task) { public void executeManager(Runnable task) {
task.run(); task.run();
System.out.println("Finished successfully!");
} }
} }