mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-04-19 21:16:30 +02:00
Port metadata code to Kotlin
This commit is contained in:
parent
ecaab219c2
commit
0770029dc6
@ -2,6 +2,7 @@ package link.infra.packwiz.installer;
|
|||||||
|
|
||||||
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.ModFile;
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
||||||
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.Hash;
|
||||||
@ -19,6 +20,7 @@ import java.nio.file.Paths;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
class DownloadTask implements IOptionDetails, IExceptionDetails {
|
class DownloadTask implements IOptionDetails, IExceptionDetails {
|
||||||
final IndexFile.File metadata;
|
final IndexFile.File metadata;
|
||||||
@ -33,8 +35,8 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
|
|
||||||
private DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
private DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
if (metadata.hashFormat == null || metadata.hashFormat.length() == 0) {
|
if (metadata.getHashFormat() == null || metadata.getHashFormat().length() == 0) {
|
||||||
metadata.hashFormat = defaultFormat;
|
metadata.setHashFormat(defaultFormat);
|
||||||
}
|
}
|
||||||
this.downloadSide = downloadSide;
|
this.downloadSide = downloadSide;
|
||||||
}
|
}
|
||||||
@ -56,18 +58,18 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
if (!invalidated) {
|
if (!invalidated) {
|
||||||
Hash currHash;
|
Hash currHash;
|
||||||
try {
|
try {
|
||||||
currHash = HashUtils.getHash(metadata.hashFormat, metadata.hash);
|
currHash = HashUtils.getHash(Objects.requireNonNull(metadata.getHashFormat()), Objects.requireNonNull(metadata.getHash()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failure = e;
|
failure = e;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currHash != null && currHash.equals(cachedFile.hash)) {
|
if (currHash.equals(cachedFile.getHash())) {
|
||||||
// Already up to date
|
// Already up to date
|
||||||
alreadyUpToDate = true;
|
alreadyUpToDate = true;
|
||||||
metadataRequired = false;
|
metadataRequired = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cachedFile.isOptional) {
|
if (cachedFile.isOptional()) {
|
||||||
// Because option selection dialog might set this task to true/false, metadata is always needed to download
|
// Because option selection dialog might set this task to true/false, metadata is always needed to download
|
||||||
// the file, and to show the description and name
|
// the file, and to show the description and name
|
||||||
metadataRequired = true;
|
metadataRequired = true;
|
||||||
@ -83,21 +85,23 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
failure = e;
|
failure = e;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (metadata.linkedFile != null) {
|
ModFile linkedFile = metadata.getLinkedFile();
|
||||||
if (metadata.linkedFile.option != null) {
|
if (linkedFile != null) {
|
||||||
if (metadata.linkedFile.option.optional) {
|
ModFile.Option option = linkedFile.getOption();
|
||||||
if (cachedFile.isOptional) {
|
if (option != null) {
|
||||||
|
if (option.getOptional()) {
|
||||||
|
if (cachedFile.isOptional()) {
|
||||||
// isOptional didn't change
|
// isOptional didn't change
|
||||||
newOptional = false;
|
newOptional = false;
|
||||||
} else {
|
} else {
|
||||||
// isOptional false -> true, set option to it's default value
|
// isOptional false -> true, set option to it's default value
|
||||||
// TODO: preserve previous option value, somehow??
|
// TODO: preserve previous option value, somehow??
|
||||||
cachedFile.optionValue = this.metadata.linkedFile.option.defaultValue;
|
cachedFile.setOptionValue(option.getDefaultValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cachedFile.isOptional = isOptional();
|
cachedFile.setOptional(isOptional());
|
||||||
cachedFile.onlyOtherSide = !correctSide();
|
cachedFile.setOnlyOtherSide(!correctSide());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,24 +110,24 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
if (failure != null) return;
|
if (failure != null) return;
|
||||||
|
|
||||||
// Ensure it is removed
|
// Ensure it is removed
|
||||||
if (!cachedFile.optionValue || !correctSide()) {
|
if (!cachedFile.getOptionValue() || !correctSide()) {
|
||||||
if (cachedFile.cachedLocation == null) return;
|
if (cachedFile.getCachedLocation() == null) return;
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(packFolder, cachedFile.cachedLocation));
|
Files.deleteIfExists(Paths.get(packFolder, cachedFile.getCachedLocation()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO: how much of a problem is this? use log4j/other log library to show warning?
|
// TODO: how much of a problem is this? use log4j/other log library to show warning?
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
cachedFile.cachedLocation = null;
|
cachedFile.setCachedLocation(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alreadyUpToDate) return;
|
if (alreadyUpToDate) return;
|
||||||
|
|
||||||
Path destPath = Paths.get(packFolder, metadata.getDestURI().toString());
|
Path destPath = Paths.get(packFolder, Objects.requireNonNull(metadata.getDestURI()).toString());
|
||||||
|
|
||||||
// Don't update files marked with preserve if they already exist on disk
|
// Don't update files marked with preserve if they already exist on disk
|
||||||
if (metadata.preserve) {
|
if (metadata.getPreserve()) {
|
||||||
if (destPath.toFile().exists()) {
|
if (destPath.toFile().exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -132,15 +136,17 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
try {
|
try {
|
||||||
Hash hash;
|
Hash hash;
|
||||||
String fileHashFormat;
|
String fileHashFormat;
|
||||||
if (metadata.linkedFile != null) {
|
ModFile linkedFile = metadata.getLinkedFile();
|
||||||
hash = metadata.linkedFile.getHash();
|
if (linkedFile != null) {
|
||||||
fileHashFormat = metadata.linkedFile.download.hashFormat;
|
hash = linkedFile.getHash();
|
||||||
|
fileHashFormat = Objects.requireNonNull(linkedFile.getDownload()).getHashFormat();
|
||||||
} else {
|
} else {
|
||||||
hash = metadata.getHash();
|
hash = metadata.getHashObj();
|
||||||
fileHashFormat = metadata.hashFormat;
|
fileHashFormat = metadata.getHashFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
Source src = metadata.getSource(indexUri);
|
Source src = metadata.getSource(indexUri);
|
||||||
|
assert fileHashFormat != null;
|
||||||
GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src);
|
GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src);
|
||||||
Buffer data = new Buffer();
|
Buffer data = new Buffer();
|
||||||
Okio.buffer(fileSource).readAll(data);
|
Okio.buffer(fileSource).readAll(data);
|
||||||
@ -156,9 +162,9 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
failure = new Exception("Hash invalid!");
|
failure = new Exception("Hash invalid!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cachedFile.cachedLocation != null && !destPath.equals(Paths.get(packFolder, cachedFile.cachedLocation))) {
|
if (cachedFile.getCachedLocation() != null && !destPath.equals(Paths.get(packFolder, cachedFile.getCachedLocation()))) {
|
||||||
// Delete old file if location changes
|
// Delete old file if location changes
|
||||||
Files.delete(Paths.get(packFolder, cachedFile.cachedLocation));
|
Files.delete(Paths.get(packFolder, cachedFile.getCachedLocation()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failure = e;
|
failure = e;
|
||||||
@ -169,16 +175,16 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
}
|
}
|
||||||
// Update the manifest file
|
// Update the manifest file
|
||||||
try {
|
try {
|
||||||
cachedFile.hash = metadata.getHash();
|
cachedFile.setHash(metadata.getHashObj());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failure = e;
|
failure = e;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cachedFile.isOptional = isOptional();
|
cachedFile.setOptional(isOptional());
|
||||||
cachedFile.cachedLocation = metadata.getDestURI().toString();
|
cachedFile.setCachedLocation(metadata.getDestURI().toString());
|
||||||
if (metadata.linkedFile != null) {
|
if (metadata.getLinkedFile() != null) {
|
||||||
try {
|
try {
|
||||||
cachedFile.linkedFileHash = metadata.linkedFile.getHash();
|
cachedFile.setLinkedFileHash(metadata.getLinkedFile().getHash());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failure = e;
|
failure = e;
|
||||||
}
|
}
|
||||||
@ -191,8 +197,8 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isOptional() {
|
boolean isOptional() {
|
||||||
if (metadata.linkedFile != null) {
|
if (metadata.getLinkedFile() != null) {
|
||||||
return metadata.linkedFile.isOptional();
|
return metadata.getLinkedFile().isOptional();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -202,8 +208,8 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean correctSide() {
|
boolean correctSide() {
|
||||||
if (metadata.linkedFile != null) {
|
if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getSide() != null) {
|
||||||
return metadata.linkedFile.side.hasSide(downloadSide);
|
return metadata.getLinkedFile().getSide().hasSide(downloadSide);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -214,28 +220,28 @@ class DownloadTask implements IOptionDetails, IExceptionDetails {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getOptionValue() {
|
public boolean getOptionValue() {
|
||||||
return cachedFile.optionValue;
|
return cachedFile.getOptionValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getOptionDescription() {
|
public String getOptionDescription() {
|
||||||
if (metadata.linkedFile != null) {
|
if (metadata.getLinkedFile() != null && metadata.getLinkedFile().getOption() != null) {
|
||||||
return metadata.linkedFile.option.description;
|
return metadata.getLinkedFile().getOption().getDescription();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOptionValue(boolean value) {
|
public void setOptionValue(boolean value) {
|
||||||
if (value && !cachedFile.optionValue) {
|
if (value && !cachedFile.getOptionValue()) {
|
||||||
// Ensure that an update is done if it changes from false to true, or from true to false
|
// Ensure that an update is done if it changes from false to true, or from true to false
|
||||||
alreadyUpToDate = false;
|
alreadyUpToDate = false;
|
||||||
}
|
}
|
||||||
cachedFile.optionValue = value;
|
cachedFile.setOptionValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<DownloadTask> createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
static List<DownloadTask> createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) {
|
||||||
ArrayList<DownloadTask> tasks = new ArrayList<>();
|
ArrayList<DownloadTask> tasks = new ArrayList<>();
|
||||||
for (IndexFile.File file : index.files) {
|
for (IndexFile.File file : Objects.requireNonNull(index.getFiles())) {
|
||||||
tasks.add(new DownloadTask(file, defaultFormat, downloadSide));
|
tasks.add(new DownloadTask(file, defaultFormat, downloadSide));
|
||||||
}
|
}
|
||||||
return tasks;
|
return tasks;
|
||||||
|
@ -151,17 +151,17 @@ public class UpdateManager {
|
|||||||
|
|
||||||
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
|
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
|
||||||
List<SpaceSafeURI> invalidatedUris = new ArrayList<>();
|
List<SpaceSafeURI> invalidatedUris = new ArrayList<>();
|
||||||
if (manifest.cachedFiles != null) {
|
if (manifest.getCachedFiles() != null) {
|
||||||
for (Map.Entry<SpaceSafeURI, ManifestFile.File> entry : manifest.cachedFiles.entrySet()) {
|
for (Map.Entry<SpaceSafeURI, ManifestFile.File> entry : manifest.getCachedFiles().entrySet()) {
|
||||||
// ignore onlyOtherSide files
|
// ignore onlyOtherSide files
|
||||||
if (entry.getValue().onlyOtherSide) {
|
if (entry.getValue().getOnlyOtherSide()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean invalid = false;
|
boolean invalid = false;
|
||||||
// if isn't optional, or is optional but optionValue == true
|
// if isn't optional, or is optional but optionValue == true
|
||||||
if (!entry.getValue().isOptional || entry.getValue().optionValue) {
|
if (!entry.getValue().isOptional() || entry.getValue().getOptionValue()) {
|
||||||
if (entry.getValue().cachedLocation != null) {
|
if (entry.getValue().getCachedLocation() != null) {
|
||||||
if (!Paths.get(opts.packFolder, entry.getValue().cachedLocation).toFile().exists()) {
|
if (!Paths.get(opts.packFolder, entry.getValue().getCachedLocation()).toFile().exists()) {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -177,7 +177,7 @@ public class UpdateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manifest.packFileHash != null && packFileSource.hashIsEqual(manifest.packFileHash) && invalidatedUris.isEmpty()) {
|
if (manifest.getPackFileHash() != null && packFileSource.hashIsEqual(manifest.getPackFileHash()) && invalidatedUris.isEmpty()) {
|
||||||
System.out.println("Modpack is already up to date!");
|
System.out.println("Modpack is already up to date!");
|
||||||
// todo: --force?
|
// todo: --force?
|
||||||
if (!stateHandler.getOptionsButton()) {
|
if (!stateHandler.getOptionsButton()) {
|
||||||
@ -185,7 +185,7 @@ public class UpdateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("Modpack name: " + pf.name);
|
System.out.println("Modpack name: " + pf.getName());
|
||||||
|
|
||||||
if (stateHandler.getCancelButton()) {
|
if (stateHandler.getCancelButton()) {
|
||||||
showCancellationDialog();
|
showCancellationDialog();
|
||||||
@ -194,8 +194,8 @@ public class UpdateManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// This is badly written, I'll probably heavily refactor it at some point
|
// This is badly written, I'll probably heavily refactor it at some point
|
||||||
processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file),
|
processIndex(HandlerManager.getNewLoc(opts.downloadURI, Objects.requireNonNull(pf.getIndex()).getFile()),
|
||||||
HashUtils.getHash(pf.index.hashFormat, pf.index.hash), pf.index.hashFormat, manifest, invalidatedUris);
|
HashUtils.getHash(Objects.requireNonNull(pf.getIndex().getHashFormat()), Objects.requireNonNull(pf.getIndex().getHash())), pf.getIndex().getHashFormat(), manifest, invalidatedUris);
|
||||||
} catch (Exception e1) {
|
} catch (Exception e1) {
|
||||||
ui.handleExceptionAndExit(e1);
|
ui.handleExceptionAndExit(e1);
|
||||||
}
|
}
|
||||||
@ -206,13 +206,13 @@ public class UpdateManager {
|
|||||||
|
|
||||||
// If there were errors, don't write the manifest/index hashes, to ensure they are rechecked later
|
// If there were errors, don't write the manifest/index hashes, to ensure they are rechecked later
|
||||||
if (errorsOccurred) {
|
if (errorsOccurred) {
|
||||||
manifest.indexFileHash = null;
|
manifest.setIndexFileHash(null);
|
||||||
manifest.packFileHash = null;
|
manifest.setPackFileHash(null);
|
||||||
} else {
|
} else {
|
||||||
manifest.packFileHash = packFileSource.getHash();
|
manifest.setPackFileHash(packFileSource.getHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest.cachedSide = opts.side;
|
manifest.setCachedSide(opts.side);
|
||||||
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) {
|
||||||
@ -227,13 +227,13 @@ public class UpdateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<SpaceSafeURI> invalidatedUris) {
|
private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<SpaceSafeURI> invalidatedUris) {
|
||||||
if (manifest.indexFileHash != null && manifest.indexFileHash.equals(indexHash) && invalidatedUris.isEmpty()) {
|
if (manifest.getIndexFileHash() != null && manifest.getIndexFileHash().equals(indexHash) && invalidatedUris.isEmpty()) {
|
||||||
System.out.println("Modpack files are already up to date!");
|
System.out.println("Modpack files are already up to date!");
|
||||||
if (!stateHandler.getOptionsButton()) {
|
if (!stateHandler.getOptionsButton()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
manifest.indexFileHash = indexHash;
|
manifest.setIndexFileHash(indexHash);
|
||||||
|
|
||||||
GeneralHashingSource indexFileSource;
|
GeneralHashingSource indexFileSource;
|
||||||
try {
|
try {
|
||||||
@ -264,33 +264,29 @@ public class UpdateManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manifest.cachedFiles == null) {
|
|
||||||
manifest.cachedFiles = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||||
Iterator<Map.Entry<SpaceSafeURI, ManifestFile.File>> it = manifest.cachedFiles.entrySet().iterator();
|
Iterator<Map.Entry<SpaceSafeURI, ManifestFile.File>> it = manifest.getCachedFiles().entrySet().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Map.Entry<SpaceSafeURI, ManifestFile.File> entry = it.next();
|
Map.Entry<SpaceSafeURI, ManifestFile.File> entry = it.next();
|
||||||
if (entry.getValue().cachedLocation != null) {
|
if (entry.getValue().getCachedLocation() != null) {
|
||||||
boolean alreadyDeleted = false;
|
boolean alreadyDeleted = false;
|
||||||
// Delete if option value has been set to false
|
// Delete if option value has been set to false
|
||||||
if (entry.getValue().isOptional && !entry.getValue().optionValue) {
|
if (entry.getValue().isOptional() && !entry.getValue().getOptionValue()) {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().cachedLocation));
|
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO: should this be shown to the user in some way?
|
// TODO: should this be shown to the user in some way?
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
// Set to null, as it doesn't exist anymore
|
// Set to null, as it doesn't exist anymore
|
||||||
entry.getValue().cachedLocation = null;
|
entry.getValue().setCachedLocation(null);
|
||||||
alreadyDeleted = true;
|
alreadyDeleted = true;
|
||||||
}
|
}
|
||||||
if (indexFile.files.stream().noneMatch(f -> f.file.equals(entry.getKey()))) {
|
if (indexFile.getFiles().stream().noneMatch(f -> Objects.equals(f.getFile(), entry.getKey()))) {
|
||||||
// File has been removed from the index
|
// File has been removed from the index
|
||||||
if (!alreadyDeleted) {
|
if (!alreadyDeleted) {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().cachedLocation));
|
Files.deleteIfExists(Paths.get(opts.packFolder, entry.getValue().getCachedLocation()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO: should this be shown to the user in some way?
|
// TODO: should this be shown to the user in some way?
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -308,14 +304,13 @@ public class UpdateManager {
|
|||||||
ui.submitProgress(new InstallProgress("Comparing new files..."));
|
ui.submitProgress(new InstallProgress("Comparing new files..."));
|
||||||
|
|
||||||
// TODO: progress bar?
|
// TODO: progress bar?
|
||||||
if (indexFile.files == null || indexFile.files.size() == 0) {
|
if (indexFile.getFiles().isEmpty()) {
|
||||||
System.out.println("Warning: Index is empty!");
|
System.out.println("Warning: Index is empty!");
|
||||||
indexFile.files = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
List<DownloadTask> tasks = DownloadTask.createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side);
|
List<DownloadTask> tasks = DownloadTask.createTasksFromIndex(indexFile, indexFile.getHashFormat(), opts.side);
|
||||||
// If the side changes, invalidate EVERYTHING just in case
|
// If the side changes, invalidate EVERYTHING just in case
|
||||||
// Might not be needed, but done just to be safe
|
// Might not be needed, but done just to be safe
|
||||||
boolean invalidateAll = !opts.side.equals(manifest.cachedSide);
|
boolean invalidateAll = !opts.side.equals(manifest.getCachedSide());
|
||||||
if (invalidateAll) {
|
if (invalidateAll) {
|
||||||
System.out.println("Side changed, invalidating all mods");
|
System.out.println("Side changed, invalidating all mods");
|
||||||
}
|
}
|
||||||
@ -323,10 +318,10 @@ public class UpdateManager {
|
|||||||
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
||||||
if (invalidateAll) {
|
if (invalidateAll) {
|
||||||
f.invalidate();
|
f.invalidate();
|
||||||
} else if (invalidatedUris.contains(f.metadata.file)) {
|
} else if (invalidatedUris.contains(f.metadata.getFile())) {
|
||||||
f.invalidate();
|
f.invalidate();
|
||||||
}
|
}
|
||||||
ManifestFile.File file = manifest.cachedFiles.get(f.metadata.file);
|
ManifestFile.File file = manifest.getCachedFiles().get(f.metadata.getFile());
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
// Ensure the file can be reverted later if necessary - the DownloadTask modifies the file so if it fails we need the old version back
|
// Ensure the file can be reverted later if necessary - the DownloadTask modifies the file so if it fails we need the old version back
|
||||||
file.backup();
|
file.backup();
|
||||||
@ -412,11 +407,11 @@ public class UpdateManager {
|
|||||||
if (task.getException() != null) {
|
if (task.getException() != null) {
|
||||||
ManifestFile.File file = task.cachedFile.getRevert();
|
ManifestFile.File file = task.cachedFile.getRevert();
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
manifest.cachedFiles.putIfAbsent(task.metadata.file, file);
|
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), file);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// idiot, if it wasn't there in the first place it won't magically appear there
|
// idiot, if it wasn't there in the first place it won't magically appear there
|
||||||
manifest.cachedFiles.putIfAbsent(task.metadata.file, task.cachedFile);
|
manifest.getCachedFiles().putIfAbsent(task.metadata.getFile(), task.cachedFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
import com.google.gson.TypeAdapter;
|
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.google.gson.stream.JsonToken;
|
|
||||||
import com.google.gson.stream.JsonWriter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class EfficientBooleanAdapter extends TypeAdapter<Boolean> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(JsonWriter out, Boolean value) throws IOException {
|
|
||||||
if (value == null || !value) {
|
|
||||||
out.nullValue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.value(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean read(JsonReader in) throws IOException {
|
|
||||||
if (in.peek() == JsonToken.NULL) {
|
|
||||||
in.nextNull();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return in.nextBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import okio.Source;
|
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class IndexFile {
|
|
||||||
@SerializedName("hash-format")
|
|
||||||
public String hashFormat;
|
|
||||||
public List<File> files;
|
|
||||||
|
|
||||||
public static class File {
|
|
||||||
public SpaceSafeURI file;
|
|
||||||
@SerializedName("hash-format")
|
|
||||||
public String hashFormat;
|
|
||||||
public String hash;
|
|
||||||
public SpaceSafeURI alias;
|
|
||||||
public boolean metafile;
|
|
||||||
public boolean preserve;
|
|
||||||
|
|
||||||
public transient ModFile linkedFile;
|
|
||||||
public transient SpaceSafeURI linkedFileURI;
|
|
||||||
|
|
||||||
public void downloadMeta(IndexFile parentIndexFile, SpaceSafeURI indexUri) throws Exception {
|
|
||||||
if (!metafile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hashFormat == null || hashFormat.length() == 0) {
|
|
||||||
hashFormat = parentIndexFile.hashFormat;
|
|
||||||
}
|
|
||||||
Hash fileHash = HashUtils.getHash(hashFormat, hash);
|
|
||||||
linkedFileURI = HandlerManager.getNewLoc(indexUri, file);
|
|
||||||
Source src = HandlerManager.getFileSource(linkedFileURI);
|
|
||||||
GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src);
|
|
||||||
|
|
||||||
linkedFile = new Toml().read(Okio.buffer(fileStream).inputStream()).to(ModFile.class);
|
|
||||||
if (!fileStream.hashIsEqual(fileHash)) {
|
|
||||||
throw new Exception("Invalid mod file hash");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Source getSource(SpaceSafeURI indexUri) throws Exception {
|
|
||||||
if (metafile) {
|
|
||||||
if (linkedFile == null) {
|
|
||||||
throw new Exception("Linked file doesn't exist!");
|
|
||||||
}
|
|
||||||
return linkedFile.getSource(linkedFileURI);
|
|
||||||
} else {
|
|
||||||
SpaceSafeURI newLoc = HandlerManager.getNewLoc(indexUri, file);
|
|
||||||
if (newLoc == null) {
|
|
||||||
throw new Exception("Index file URI is invalid");
|
|
||||||
}
|
|
||||||
return HandlerManager.getFileSource(newLoc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Hash getHash() throws Exception {
|
|
||||||
if (hash == null) {
|
|
||||||
// TODO: should these be more specific exceptions (e.g. IndexFileException?!)
|
|
||||||
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 HashUtils.getHash(hashFormat, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
if (metafile) {
|
|
||||||
if (linkedFile != null) {
|
|
||||||
if (linkedFile.name != null) {
|
|
||||||
return linkedFile.name;
|
|
||||||
} else if (linkedFile.filename != null) {
|
|
||||||
return linkedFile.filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file != null) {
|
|
||||||
return Paths.get(file.getPath()).getFileName().toString();
|
|
||||||
}
|
|
||||||
// TODO: throw some kind of exception?
|
|
||||||
return "Invalid file";
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpaceSafeURI getDestURI() {
|
|
||||||
if (alias != null) {
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
if (metafile && linkedFile != null) {
|
|
||||||
// TODO: URIs are bad
|
|
||||||
return file.resolve(linkedFile.filename);
|
|
||||||
} else {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.JsonAdapter;
|
|
||||||
import link.infra.packwiz.installer.UpdateManager;
|
|
||||||
import link.infra.packwiz.installer.metadata.hash.Hash;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ManifestFile {
|
|
||||||
public Hash packFileHash = null;
|
|
||||||
public Hash indexFileHash = null;
|
|
||||||
public Map<SpaceSafeURI, File> cachedFiles;
|
|
||||||
// If the side changes, EVERYTHING invalidates. FUN!!!
|
|
||||||
public UpdateManager.Options.Side cachedSide = UpdateManager.Options.Side.CLIENT;
|
|
||||||
|
|
||||||
public static class File {
|
|
||||||
private transient File revert;
|
|
||||||
|
|
||||||
public Hash hash = null;
|
|
||||||
public Hash linkedFileHash = null;
|
|
||||||
public String cachedLocation = null;
|
|
||||||
|
|
||||||
@JsonAdapter(EfficientBooleanAdapter.class)
|
|
||||||
public boolean isOptional = false;
|
|
||||||
public boolean optionValue = true;
|
|
||||||
|
|
||||||
@JsonAdapter(EfficientBooleanAdapter.class)
|
|
||||||
public boolean onlyOtherSide = false;
|
|
||||||
|
|
||||||
// When an error occurs, the state needs to be reverted. To do this, I have a crude revert system.
|
|
||||||
public void backup() {
|
|
||||||
revert = new File();
|
|
||||||
revert.hash = hash;
|
|
||||||
revert.linkedFileHash = linkedFileHash;
|
|
||||||
revert.cachedLocation = cachedLocation;
|
|
||||||
revert.isOptional = isOptional;
|
|
||||||
revert.optionValue = optionValue;
|
|
||||||
revert.onlyOtherSide = onlyOtherSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getRevert() {
|
|
||||||
return revert;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ModFile {
|
|
||||||
public String name;
|
|
||||||
public String filename;
|
|
||||||
public Side side;
|
|
||||||
|
|
||||||
public Download download;
|
|
||||||
public static class Download {
|
|
||||||
public SpaceSafeURI url;
|
|
||||||
@SerializedName("hash-format")
|
|
||||||
public String hashFormat;
|
|
||||||
public String hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> update;
|
|
||||||
|
|
||||||
public Option option;
|
|
||||||
public static class Option {
|
|
||||||
public boolean optional;
|
|
||||||
public String description;
|
|
||||||
@SerializedName("default")
|
|
||||||
public boolean defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Source getSource(SpaceSafeURI baseLoc) throws Exception {
|
|
||||||
if (download == null) {
|
|
||||||
throw new Exception("Metadata file doesn't have download");
|
|
||||||
}
|
|
||||||
if (download.url == null) {
|
|
||||||
throw new Exception("Metadata file doesn't have a download URI");
|
|
||||||
}
|
|
||||||
SpaceSafeURI newLoc = HandlerManager.getNewLoc(baseLoc, download.url);
|
|
||||||
if (newLoc == null) {
|
|
||||||
throw new Exception("Metadata file URI is invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return HandlerManager.getFileSource(newLoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Hash getHash() throws Exception {
|
|
||||||
if (download == null) {
|
|
||||||
throw new Exception("Metadata file doesn't have download");
|
|
||||||
}
|
|
||||||
if (download.hash == null) {
|
|
||||||
throw new Exception("Metadata file doesn't have a hash");
|
|
||||||
}
|
|
||||||
if (download.hashFormat == null) {
|
|
||||||
throw new Exception("Metadata file doesn't have a hash format");
|
|
||||||
}
|
|
||||||
return HashUtils.getHash(download.hashFormat, download.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOptional() {
|
|
||||||
if (option != null) {
|
|
||||||
return option.optional;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class PackFile {
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
public IndexFileLoc index;
|
|
||||||
public static class IndexFileLoc {
|
|
||||||
public SpaceSafeURI file;
|
|
||||||
@SerializedName("hash-format")
|
|
||||||
public String hashFormat;
|
|
||||||
public String hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> versions;
|
|
||||||
public Map<String, Object> client;
|
|
||||||
public Map<String, Object> server;
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.JsonAdapter;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
// The world's worst URI wrapper
|
|
||||||
@JsonAdapter(SpaceSafeURIParser.class)
|
|
||||||
public class SpaceSafeURI implements Comparable<SpaceSafeURI>, Serializable {
|
|
||||||
private final URI u;
|
|
||||||
|
|
||||||
public SpaceSafeURI(String str) throws URISyntaxException {
|
|
||||||
u = new URI(str.replace(" ", "%20"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpaceSafeURI(URI uri) {
|
|
||||||
this.u = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpaceSafeURI(String scheme, String authority, String path, String query, String fragment) throws URISyntaxException {
|
|
||||||
// TODO: do all components need to be replaced?
|
|
||||||
scheme = scheme.replace(" ", "%20");
|
|
||||||
authority = authority.replace(" ", "%20");
|
|
||||||
path = path.replace(" ", "%20");
|
|
||||||
query = query.replace(" ", "%20");
|
|
||||||
fragment = fragment.replace(" ", "%20");
|
|
||||||
u = new URI(scheme, authority, path, query, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath() {
|
|
||||||
return u.getPath().replace("%20", " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return u.toString().replace("%20", " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public SpaceSafeURI resolve(String path) {
|
|
||||||
return new SpaceSafeURI(u.resolve(path.replace(" ", "%20")));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpaceSafeURI resolve(SpaceSafeURI loc) {
|
|
||||||
return new SpaceSafeURI(u.resolve(loc.u));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpaceSafeURI relativize(SpaceSafeURI loc) {
|
|
||||||
return new SpaceSafeURI(u.relativize(loc.u));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj instanceof SpaceSafeURI) {
|
|
||||||
return u.equals(((SpaceSafeURI) obj).u);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return u.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(SpaceSafeURI uri) {
|
|
||||||
return u.compareTo(uri.u);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getScheme() {
|
|
||||||
return u.getScheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthority() {
|
|
||||||
return u.getAuthority();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return u.getHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
public URL toURL() throws MalformedURLException {
|
|
||||||
return u.toURL();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.metadata;
|
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class encodes spaces before parsing the URI, so the URI can actually be
|
|
||||||
* parsed.
|
|
||||||
*/
|
|
||||||
class SpaceSafeURIParser implements JsonDeserializer<SpaceSafeURI> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpaceSafeURI deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
|
||||||
throws JsonParseException {
|
|
||||||
try {
|
|
||||||
return new SpaceSafeURI(json.getAsString());
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new JsonParseException("Failed to parse URI", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace this with a better solution?
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.request;
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
|
||||||
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub;
|
|
||||||
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP;
|
|
||||||
import okio.Source;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class HandlerManager {
|
|
||||||
|
|
||||||
private static List<IRequestHandler> handlers = new ArrayList<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
handlers.add(new RequestHandlerGithub());
|
|
||||||
handlers.add(new RequestHandlerHTTP());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SpaceSafeURI getNewLoc(SpaceSafeURI base, SpaceSafeURI loc) {
|
|
||||||
if (loc == null) return null;
|
|
||||||
if (base != null) {
|
|
||||||
loc = base.resolve(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (IRequestHandler handler : handlers) {
|
|
||||||
if (handler.matchesHandler(loc)) {
|
|
||||||
return handler.getNewLoc(loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: What if files are read multiple times??
|
|
||||||
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
|
|
||||||
// Caching system? Copy from already downloaded files?
|
|
||||||
|
|
||||||
public static Source getFileSource(SpaceSafeURI loc) throws Exception {
|
|
||||||
for (IRequestHandler handler : handlers) {
|
|
||||||
if (handler.matchesHandler(loc)) {
|
|
||||||
Source src = handler.getFileSource(loc);
|
|
||||||
if (src == null) {
|
|
||||||
throw new Exception("Couldn't find URI: " + loc.toString());
|
|
||||||
} else {
|
|
||||||
return src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: specialised exception classes??
|
|
||||||
throw new Exception("No handler available for URI: " + loc.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// github toml resolution
|
|
||||||
// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
|
|
||||||
// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml
|
|
||||||
|
|
||||||
// To handle "progress", just count tasks, rather than individual progress
|
|
||||||
// It'll look bad, especially for zip-based things, but it should work fine
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class EfficientBooleanAdapter : TypeAdapter<Boolean?>() {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun write(out: JsonWriter, value: Boolean?) {
|
||||||
|
if (value == null || !value) {
|
||||||
|
out.nullValue()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.value(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun read(reader: JsonReader): Boolean? {
|
||||||
|
if (reader.peek() == JsonToken.NULL) {
|
||||||
|
reader.nextNull()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return reader.nextBoolean()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
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.HashUtils.getHash
|
||||||
|
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHasher
|
||||||
|
import link.infra.packwiz.installer.request.HandlerManager.getFileSource
|
||||||
|
import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
|
||||||
|
import okio.Source
|
||||||
|
import okio.buffer
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class IndexFile {
|
||||||
|
@SerializedName("hash-format")
|
||||||
|
var hashFormat: String = "sha-256"
|
||||||
|
var files: MutableList<File> = ArrayList()
|
||||||
|
|
||||||
|
class File {
|
||||||
|
var file: SpaceSafeURI? = null
|
||||||
|
@SerializedName("hash-format")
|
||||||
|
var hashFormat: String? = null
|
||||||
|
var hash: String? = null
|
||||||
|
var alias: SpaceSafeURI? = null
|
||||||
|
var metafile = false
|
||||||
|
var preserve = false
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
var linkedFile: ModFile? = null
|
||||||
|
@Transient
|
||||||
|
var linkedFileURI: SpaceSafeURI? = null
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun downloadMeta(parentIndexFile: IndexFile, indexUri: SpaceSafeURI?) {
|
||||||
|
if (!metafile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hashFormat?.length ?: 0 == 0) {
|
||||||
|
hashFormat = parentIndexFile.hashFormat
|
||||||
|
}
|
||||||
|
// TODO: throw a proper exception instead of allowing NPE?
|
||||||
|
val fileHash = getHash(hashFormat!!, hash!!)
|
||||||
|
linkedFileURI = getNewLoc(indexUri, file)
|
||||||
|
val src = getFileSource(linkedFileURI!!)
|
||||||
|
val fileStream = getHasher(hashFormat!!).getHashingSource(src)
|
||||||
|
linkedFile = Toml().read(fileStream.buffer().inputStream()).to(ModFile::class.java)
|
||||||
|
if (!fileStream.hashIsEqual(fileHash)) {
|
||||||
|
throw Exception("Invalid mod file hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun getSource(indexUri: SpaceSafeURI?): Source {
|
||||||
|
return if (metafile) {
|
||||||
|
if (linkedFile == null) {
|
||||||
|
throw Exception("Linked file doesn't exist!")
|
||||||
|
}
|
||||||
|
linkedFile!!.getSource(linkedFileURI)
|
||||||
|
} else {
|
||||||
|
val newLoc = getNewLoc(indexUri, file) ?: throw Exception("Index file URI is invalid")
|
||||||
|
getFileSource(newLoc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun getHashObj(): Hash {
|
||||||
|
if (hash == null) { // TODO: should these be more specific exceptions (e.g. IndexFileException?!)
|
||||||
|
throw Exception("Index file doesn't have a hash")
|
||||||
|
}
|
||||||
|
if (hashFormat == null) {
|
||||||
|
throw Exception("Index file doesn't have a hash format")
|
||||||
|
}
|
||||||
|
return getHash(hashFormat!!, hash!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: throw some kind of exception?
|
||||||
|
val name: String?
|
||||||
|
get() {
|
||||||
|
if (metafile) {
|
||||||
|
return linkedFile?.name ?: linkedFile?.filename
|
||||||
|
}
|
||||||
|
return file?.run { Paths.get(path).fileName.toString() } ?: "Invalid file"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: URIs are bad
|
||||||
|
val destURI: SpaceSafeURI?
|
||||||
|
get() {
|
||||||
|
if (alias != null) {
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
return if (metafile && linkedFile != null) {
|
||||||
|
linkedFile?.filename?.let { file?.resolve(it) }
|
||||||
|
} else {
|
||||||
|
file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import link.infra.packwiz.installer.UpdateManager
|
||||||
|
import link.infra.packwiz.installer.metadata.hash.Hash
|
||||||
|
|
||||||
|
class ManifestFile {
|
||||||
|
var packFileHash: Hash? = null
|
||||||
|
var indexFileHash: Hash? = null
|
||||||
|
var cachedFiles: MutableMap<SpaceSafeURI, File> = HashMap()
|
||||||
|
// If the side changes, EVERYTHING invalidates. FUN!!!
|
||||||
|
var cachedSide = UpdateManager.Options.Side.CLIENT
|
||||||
|
|
||||||
|
// TODO: switch to Kotlin-friendly JSON/TOML libs?
|
||||||
|
class File {
|
||||||
|
@Transient
|
||||||
|
var revert: File? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var hash: Hash? = null
|
||||||
|
var linkedFileHash: Hash? = null
|
||||||
|
var cachedLocation: String? = null
|
||||||
|
|
||||||
|
@JsonAdapter(EfficientBooleanAdapter::class)
|
||||||
|
var isOptional = false
|
||||||
|
var optionValue = true
|
||||||
|
|
||||||
|
@JsonAdapter(EfficientBooleanAdapter::class)
|
||||||
|
var onlyOtherSide = false
|
||||||
|
|
||||||
|
// When an error occurs, the state needs to be reverted. To do this, I have a crude revert system.
|
||||||
|
fun backup() {
|
||||||
|
revert = File().also {
|
||||||
|
it.hash = hash
|
||||||
|
it.linkedFileHash = linkedFileHash
|
||||||
|
it.cachedLocation = cachedLocation
|
||||||
|
it.isOptional = isOptional
|
||||||
|
it.optionValue = optionValue
|
||||||
|
it.onlyOtherSide = onlyOtherSide
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import link.infra.packwiz.installer.UpdateManager
|
||||||
|
import link.infra.packwiz.installer.metadata.hash.Hash
|
||||||
|
import link.infra.packwiz.installer.metadata.hash.HashUtils.getHash
|
||||||
|
import link.infra.packwiz.installer.request.HandlerManager.getFileSource
|
||||||
|
import link.infra.packwiz.installer.request.HandlerManager.getNewLoc
|
||||||
|
import okio.Source
|
||||||
|
|
||||||
|
class ModFile {
|
||||||
|
var name: String? = null
|
||||||
|
var filename: String? = null
|
||||||
|
var side: UpdateManager.Options.Side? = null
|
||||||
|
var download: Download? = null
|
||||||
|
|
||||||
|
class Download {
|
||||||
|
var url: SpaceSafeURI? = null
|
||||||
|
@SerializedName("hash-format")
|
||||||
|
var hashFormat: String? = null
|
||||||
|
var hash: String? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
var update: Map<String, Any>? = null
|
||||||
|
var option: Option? = null
|
||||||
|
|
||||||
|
class Option {
|
||||||
|
var optional = false
|
||||||
|
var description: String? = null
|
||||||
|
@SerializedName("default")
|
||||||
|
var defaultValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun getSource(baseLoc: SpaceSafeURI?): Source {
|
||||||
|
download?.let {
|
||||||
|
if (it.url == null) {
|
||||||
|
throw Exception("Metadata file doesn't have a download URI")
|
||||||
|
}
|
||||||
|
val newLoc = getNewLoc(baseLoc, it.url) ?: throw Exception("Metadata file URI is invalid")
|
||||||
|
return getFileSource(newLoc)
|
||||||
|
} ?: throw Exception("Metadata file doesn't have download")
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Throws(Exception::class)
|
||||||
|
val hash: Hash
|
||||||
|
get() {
|
||||||
|
download?.let {
|
||||||
|
return getHash(
|
||||||
|
it.hashFormat ?: throw Exception("Metadata file doesn't have a hash format"),
|
||||||
|
it.hash ?: throw Exception("Metadata file doesn't have a hash")
|
||||||
|
)
|
||||||
|
} ?: throw Exception("Metadata file doesn't have download")
|
||||||
|
}
|
||||||
|
|
||||||
|
val isOptional: Boolean get() = option?.optional ?: false
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class PackFile {
|
||||||
|
var name: String? = null
|
||||||
|
var index: IndexFileLoc? = null
|
||||||
|
|
||||||
|
class IndexFileLoc {
|
||||||
|
var file: SpaceSafeURI? = null
|
||||||
|
@SerializedName("hash-format")
|
||||||
|
var hashFormat: String? = null
|
||||||
|
var hash: String? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
var versions: Map<String, String>? = null
|
||||||
|
var client: Map<String, Any>? = null
|
||||||
|
var server: Map<String, Any>? = null
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
// The world's worst URI wrapper
|
||||||
|
@JsonAdapter(SpaceSafeURIParser::class)
|
||||||
|
class SpaceSafeURI : Comparable<SpaceSafeURI>, Serializable {
|
||||||
|
private val u: URI
|
||||||
|
|
||||||
|
@Throws(URISyntaxException::class)
|
||||||
|
constructor(str: String) {
|
||||||
|
u = URI(str.replace(" ", "%20"))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(uri: URI) {
|
||||||
|
u = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(URISyntaxException::class)
|
||||||
|
constructor(scheme: String?, authority: String?, path: String?, query: String?, fragment: String?) { // TODO: do all components need to be replaced?
|
||||||
|
u = URI(
|
||||||
|
scheme?.replace(" ", "%20"),
|
||||||
|
authority?.replace(" ", "%20"),
|
||||||
|
path?.replace(" ", "%20"),
|
||||||
|
query?.replace(" ", "%20"),
|
||||||
|
fragment?.replace(" ", "%20")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val path: String get() = u.path.replace("%20", " ")
|
||||||
|
|
||||||
|
override fun toString(): String = u.toString().replace("%20", " ")
|
||||||
|
|
||||||
|
fun resolve(path: String): SpaceSafeURI = SpaceSafeURI(u.resolve(path.replace(" ", "%20")))
|
||||||
|
|
||||||
|
fun resolve(loc: SpaceSafeURI): SpaceSafeURI = SpaceSafeURI(u.resolve(loc.u))
|
||||||
|
|
||||||
|
fun relativize(loc: SpaceSafeURI): SpaceSafeURI = SpaceSafeURI(u.relativize(loc.u))
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return if (other is SpaceSafeURI) {
|
||||||
|
u == other.u
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode() = u.hashCode()
|
||||||
|
|
||||||
|
override fun compareTo(other: SpaceSafeURI): Int = u.compareTo(other.u)
|
||||||
|
|
||||||
|
val scheme: String get() = u.scheme
|
||||||
|
val authority: String get() = u.authority
|
||||||
|
val host: String get() = u.host
|
||||||
|
|
||||||
|
@Throws(MalformedURLException::class)
|
||||||
|
fun toURL(): URL = u.toURL()
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package link.infra.packwiz.installer.metadata
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonParseException
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class encodes spaces before parsing the URI, so the URI can actually be
|
||||||
|
* parsed.
|
||||||
|
*/
|
||||||
|
internal class SpaceSafeURIParser : JsonDeserializer<SpaceSafeURI> {
|
||||||
|
@Throws(JsonParseException::class)
|
||||||
|
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): SpaceSafeURI {
|
||||||
|
return try {
|
||||||
|
SpaceSafeURI(json.asString)
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
throw JsonParseException("Failed to parse URI", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace this with a better solution?
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package link.infra.packwiz.installer.request
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
|
import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub
|
||||||
|
import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP
|
||||||
|
import okio.Source
|
||||||
|
|
||||||
|
object HandlerManager {
|
||||||
|
|
||||||
|
private val handlers: List<IRequestHandler> = listOf(
|
||||||
|
RequestHandlerGithub(),
|
||||||
|
RequestHandlerHTTP()
|
||||||
|
)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getNewLoc(base: SpaceSafeURI?, loc: SpaceSafeURI?): SpaceSafeURI? {
|
||||||
|
if (loc == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val dest = base?.run { resolve(loc) } ?: loc
|
||||||
|
for (handler in handlers) with (handler) {
|
||||||
|
if (matchesHandler(dest)) {
|
||||||
|
return getNewLoc(dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: What if files are read multiple times??
|
||||||
|
// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
|
||||||
|
// Caching system? Copy from already downloaded files?
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun getFileSource(loc: SpaceSafeURI): Source {
|
||||||
|
for (handler in handlers) {
|
||||||
|
if (handler.matchesHandler(loc)) {
|
||||||
|
return handler.getFileSource(loc) ?: throw Exception("Couldn't find URI: $loc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception("No handler available for URI: $loc")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: github toml resolution?
|
||||||
|
// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
|
||||||
|
// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user