243 lines
6.8 KiB
Java

package link.infra.packwiz.installer;
import link.infra.packwiz.installer.metadata.IndexFile;
import link.infra.packwiz.installer.metadata.ManifestFile;
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.ui.IOptionDetails;
import okio.Buffer;
import okio.Okio;
import okio.Source;
import java.io.IOException;
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.List;
class DownloadTask implements IOptionDetails {
final IndexFile.File metadata;
ManifestFile.File cachedFile = null;
private Exception failure = null;
private boolean alreadyUpToDate = false;
private boolean metadataRequired = true;
private boolean invalidated = false;
// If file is new or isOptional changed to true, the option needs to be presented again
private boolean newOptional = true;
private final UpdateManager.Options.Side downloadSide;
public DownloadTask(IndexFile.File metadata, String defaultFormat, UpdateManager.Options.Side downloadSide) {
this.metadata = metadata;
if (metadata.hashFormat == null || metadata.hashFormat.length() == 0) {
metadata.hashFormat = defaultFormat;
}
this.downloadSide = downloadSide;
}
public void invalidate() {
invalidated = true;
alreadyUpToDate = false;
}
public void updateFromCache(ManifestFile.File cachedFile) {
if (failure != null) return;
if (cachedFile == null) {
this.cachedFile = new ManifestFile.File();
return;
}
this.cachedFile = cachedFile;
if (!invalidated) {
Hash currHash = null;
try {
currHash = HashUtils.getHash(metadata.hashFormat, metadata.hash);
} catch (Exception e) {
failure = e;
return;
}
if (currHash != null && currHash.equals(cachedFile.hash)) {
// Already up to date
alreadyUpToDate = true;
metadataRequired = false;
}
}
if (cachedFile.isOptional) {
// 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
metadataRequired = true;
}
}
public void downloadMetadata(IndexFile parentIndexFile, URI indexUri) {
if (failure != null) return;
if (metadataRequired) {
try {
metadata.downloadMeta(parentIndexFile, indexUri);
} catch (Exception e) {
failure = e;
return;
}
if (metadata.linkedFile != null) {
if (metadata.linkedFile.option != null) {
if (metadata.linkedFile.option.optional) {
if (cachedFile.isOptional) {
// isOptional didn't change
newOptional = false;
} else {
// isOptional false -> true, set option to it's default value
// TODO: preserve previous option value, somehow??
cachedFile.optionValue = this.metadata.linkedFile.option.defaultValue;
}
}
}
cachedFile.isOptional = isOptional();
}
}
}
public void download(String packFolder, URI indexUri) {
if (failure != null) return;
// Ensure it is removed
if (!cachedFile.optionValue || !correctSide()) {
if (cachedFile.cachedLocation == null) return;
try {
Files.deleteIfExists(Paths.get(packFolder, cachedFile.cachedLocation));
} catch (IOException e) {
// TODO: how much of a problem is this? use log4j/other log library to show warning?
e.printStackTrace();
}
cachedFile.cachedLocation = null;
return;
}
if (alreadyUpToDate) return;
Path destPath = Paths.get(packFolder, metadata.getDestURI().toString());
// Don't update files marked with preserve if they already exist on disk
if (metadata.preserve) {
if (Files.exists(destPath)) {
return;
}
}
try {
Hash hash;
String fileHashFormat;
if (metadata.linkedFile != null) {
hash = metadata.linkedFile.getHash();
fileHashFormat = metadata.linkedFile.download.hashFormat;
} else {
hash = metadata.getHash();
fileHashFormat = metadata.hashFormat;
}
Source src = metadata.getSource(indexUri);
GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src);
Buffer data = new Buffer();
Okio.buffer(fileSource).readAll(data);
if (fileSource.hashIsEqual(hash)) {
Files.createDirectories(destPath.getParent());
Files.copy(data.inputStream(), destPath, StandardCopyOption.REPLACE_EXISTING);
} else {
// TODO: no more SYSOUT!!!!!!!!!
System.out.println("Invalid hash for " + metadata.getDestURI().toString());
System.out.println("Calculated: " + fileSource.getHash());
System.out.println("Expected: " + hash);
failure = new Exception("Hash invalid!");
}
if (cachedFile.cachedLocation != null && !destPath.equals(Paths.get(packFolder, cachedFile.cachedLocation))) {
// Delete old file if location changes
Files.delete(Paths.get(packFolder, cachedFile.cachedLocation));
}
} catch (Exception e) {
failure = e;
}
if (failure == null) {
if (cachedFile == null) {
cachedFile = new ManifestFile.File();
}
// Update the manifest file
try {
cachedFile.hash = metadata.getHash();
} catch (Exception e) {
failure = e;
return;
}
cachedFile.isOptional = isOptional();
cachedFile.cachedLocation = metadata.getDestURI().toString();
if (metadata.linkedFile != null) {
try {
cachedFile.linkedFileHash = metadata.linkedFile.getHash();
} catch (Exception e) {
failure = e;
}
}
}
}
public Exception getException() {
return failure;
}
public boolean isOptional() {
if (metadata.linkedFile != null) {
return metadata.linkedFile.isOptional();
}
return false;
}
public boolean isNewOptional() {
return isOptional() && this.newOptional;
}
public boolean correctSide() {
if (metadata.linkedFile != null) {
return metadata.linkedFile.side.hasSide(downloadSide);
}
return true;
}
public String getName() {
return metadata.getName();
}
@Override
public boolean getOptionValue() {
return cachedFile.optionValue;
}
@Override
public String getOptionDescription() {
if (metadata.linkedFile != null) {
return metadata.linkedFile.option.description;
}
return null;
}
public void setOptionValue(boolean value) {
if (value && !cachedFile.optionValue) {
// Ensure that an update is done if it changes from false to true, or from true to false
alreadyUpToDate = false;
}
cachedFile.optionValue = value;
}
public static List<DownloadTask> createTasksFromIndex(IndexFile index, String defaultFormat, UpdateManager.Options.Side downloadSide) {
ArrayList<DownloadTask> tasks = new ArrayList<>();
for (IndexFile.File file : index.files) {
tasks.add(new DownloadTask(file, defaultFormat, downloadSide));
}
return tasks;
}
}