mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-04-19 21:16:30 +02:00
Start porting to Kotlin
This commit is contained in:
parent
c0c318772b
commit
b45a2983e7
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
72
build.gradle
72
build.gradle
@ -1,72 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'application'
|
|
||||||
id 'com.github.johnrengelman.shadow' version '5.0.0'
|
|
||||||
id 'com.palantir.git-version' version '0.11.0'
|
|
||||||
id 'com.github.breadmoirai.github-release' version '2.2.9'
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'commons-cli:commons-cli:1.4'
|
|
||||||
implementation 'com.moandjiezana.toml:toml4j:0.7.2'
|
|
||||||
// TODO: Implement tests
|
|
||||||
//testImplementation 'junit:junit:4.12'
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.1'
|
|
||||||
implementation 'com.squareup.okio:okio:2.2.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
mainClassName = 'link.infra.packwiz.installer.RequiresBootstrap'
|
|
||||||
version gitVersion()
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes(
|
|
||||||
'Main-Class': 'link.infra.packwiz.installer.RequiresBootstrap',
|
|
||||||
'Implementation-Version': project.version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commons CLI and Minimal JSON are already included in packwiz-installer-bootstrap
|
|
||||||
shadowJar {
|
|
||||||
dependencies {
|
|
||||||
exclude(dependency('commons-cli:commons-cli:1.4'))
|
|
||||||
exclude(dependency('com.eclipsesource.minimal-json:minimal-json:0.9.5'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used for vscode launch.json
|
|
||||||
task copyJar(type: Copy) {
|
|
||||||
from shadowJar
|
|
||||||
rename "packwiz-installer-(.*)\\.jar", "packwiz-installer.jar"
|
|
||||||
into "build/libs/"
|
|
||||||
}
|
|
||||||
|
|
||||||
build.dependsOn copyJar
|
|
||||||
|
|
||||||
if (project.hasProperty("github.token")) {
|
|
||||||
githubRelease {
|
|
||||||
// IntelliJ u ok?
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
owner "comp500"
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
repo "packwiz-installer"
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
tagName "${project.version}"
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
releaseName "Release ${project.version}"
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
draft true
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
token findProperty("github.token") ?: ""
|
|
||||||
releaseAssets = [jar.destinationDirectory.file("packwiz-installer.jar").get()]
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.githubRelease.dependsOn(build)
|
|
||||||
}
|
|
86
build.gradle.kts
Normal file
86
build.gradle.kts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
plugins {
|
||||||
|
java
|
||||||
|
application
|
||||||
|
id("com.github.johnrengelman.shadow") version "5.0.0"
|
||||||
|
id("com.palantir.git-version") version "0.11.0"
|
||||||
|
id("com.github.breadmoirai.github-release") version "2.2.9"
|
||||||
|
kotlin("jvm") version "1.3.61"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("commons-cli:commons-cli:1.4")
|
||||||
|
implementation("com.moandjiezana.toml:toml4j:0.7.2")
|
||||||
|
// TODO: Implement tests
|
||||||
|
//testImplementation "junit:junit:4.12"
|
||||||
|
implementation("com.google.code.gson:gson:2.8.1")
|
||||||
|
implementation("com.squareup.okio:okio:2.2.2")
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClassName = "link.infra.packwiz.installer.RequiresBootstrap"
|
||||||
|
}
|
||||||
|
|
||||||
|
val gitVersion: groovy.lang.Closure<*> by extra
|
||||||
|
version = gitVersion()
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
manifest {
|
||||||
|
attributes["Main-Class"] = "link.infra.packwiz.installer.RequiresBootstrap"
|
||||||
|
attributes["Implementation-Version"] = project.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commons CLI and Minimal JSON are already included in packwiz-installer-bootstrap
|
||||||
|
tasks.shadowJar {
|
||||||
|
dependencies {
|
||||||
|
exclude(dependency("commons-cli:commons-cli:1.4"))
|
||||||
|
exclude(dependency("com.eclipsesource.minimal-json:minimal-json:0.9.5"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for vscode launch.json
|
||||||
|
tasks.register<Copy>("copyJar") {
|
||||||
|
from(tasks.shadowJar)
|
||||||
|
rename("packwiz-installer-(.*)\\.jar", "packwiz-installer.jar")
|
||||||
|
into("build/libs/")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.build {
|
||||||
|
dependsOn("copyJar")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("github.token")) {
|
||||||
|
githubRelease {
|
||||||
|
owner("comp500")
|
||||||
|
repo("packwiz-installer")
|
||||||
|
tagName("${project.version}")
|
||||||
|
releaseName("Release ${project.version}")
|
||||||
|
draft(true)
|
||||||
|
token(findProperty("github.token") as String? ?: "")
|
||||||
|
releaseAssets(tasks.jar.get().destinationDirectory.file("packwiz-installer.jar").get())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.githubRelease {
|
||||||
|
dependsOn(tasks.build)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.compileKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.compileTestKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
@ -1,148 +0,0 @@
|
|||||||
package link.infra.packwiz.installer;
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
|
||||||
import link.infra.packwiz.installer.ui.CLIHandler;
|
|
||||||
import link.infra.packwiz.installer.ui.IUserInterface;
|
|
||||||
import link.infra.packwiz.installer.ui.InputStateHandler;
|
|
||||||
import link.infra.packwiz.installer.ui.InstallWindow;
|
|
||||||
import org.apache.commons.cli.*;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class Main {
|
|
||||||
|
|
||||||
// Actual main() is in RequiresBootstrap!
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Main(String[] args) {
|
|
||||||
// Big overarching try/catch just in case everything breaks
|
|
||||||
try {
|
|
||||||
this.startup(args);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
EventQueue.invokeLater(() -> {
|
|
||||||
JOptionPane.showMessageDialog(null,
|
|
||||||
"A fatal error occurred: \n" + e.getClass().getCanonicalName() + ": " + e.getMessage(),
|
|
||||||
"packwiz-installer", JOptionPane.ERROR_MESSAGE);
|
|
||||||
System.exit(1);
|
|
||||||
});
|
|
||||||
// In case the eventqueue is broken, exit after 1 minute
|
|
||||||
try {
|
|
||||||
Thread.sleep(60 * 1000);
|
|
||||||
} catch (InterruptedException e1) {
|
|
||||||
// Good, it was already called?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startup(String[] args) {
|
|
||||||
Options options = new Options();
|
|
||||||
addNonBootstrapOptions(options);
|
|
||||||
addBootstrapOptions(options);
|
|
||||||
|
|
||||||
CommandLineParser parser = new DefaultParser();
|
|
||||||
CommandLine cmd = null;
|
|
||||||
try {
|
|
||||||
cmd = parser.parse(options, args);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
try {
|
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
||||||
} catch (Exception e1) {
|
|
||||||
// Ignore the exceptions, just continue using the ugly L&F
|
|
||||||
}
|
|
||||||
JOptionPane.showMessageDialog(null, e.getMessage(), "packwiz-installer", JOptionPane.ERROR_MESSAGE);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
IUserInterface ui;
|
|
||||||
// if "headless", GUI creation will fail anyway!
|
|
||||||
if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) {
|
|
||||||
ui = new CLIHandler();
|
|
||||||
} else {
|
|
||||||
ui = new InstallWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] unparsedArgs = cmd.getArgs();
|
|
||||||
if (unparsedArgs.length > 1) {
|
|
||||||
ui.handleExceptionAndExit(new RuntimeException("Too many arguments specified!"));
|
|
||||||
return;
|
|
||||||
} else if (unparsedArgs.length < 1) {
|
|
||||||
ui.handleExceptionAndExit(new RuntimeException("URI to install from must be specified!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String title = cmd.getOptionValue("title");
|
|
||||||
if (title != null) {
|
|
||||||
ui.setTitle(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStateHandler inputStateHandler = new InputStateHandler();
|
|
||||||
ui.show(inputStateHandler);
|
|
||||||
|
|
||||||
UpdateManager.Options uOptions = new UpdateManager.Options();
|
|
||||||
|
|
||||||
String side = cmd.getOptionValue("side");
|
|
||||||
if (side != null) {
|
|
||||||
uOptions.side = UpdateManager.Options.Side.from(side);
|
|
||||||
}
|
|
||||||
|
|
||||||
String packFolder = cmd.getOptionValue("pack-folder");
|
|
||||||
if (packFolder != null) {
|
|
||||||
uOptions.packFolder = packFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
String metaFile = cmd.getOptionValue("meta-file");
|
|
||||||
if (metaFile != null) {
|
|
||||||
uOptions.manifestFile = metaFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
uOptions.downloadURI = new SpaceSafeURI(unparsedArgs[0]);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
// TODO: better error message?
|
|
||||||
ui.handleExceptionAndExit(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start update process!
|
|
||||||
// TODO: start in SwingWorker?
|
|
||||||
try {
|
|
||||||
ui.executeManager(() -> {
|
|
||||||
try {
|
|
||||||
new UpdateManager(uOptions, ui, inputStateHandler);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO: better error message?
|
|
||||||
ui.handleExceptionAndExit(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO: better error message?
|
|
||||||
ui.handleExceptionAndExit(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by packwiz-installer-bootstrap to set up the help command
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public static void addNonBootstrapOptions(Options options) {
|
|
||||||
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)");
|
|
||||||
options.addOption(null, "title", true, "Title of the installer window");
|
|
||||||
options.addOption(null, "pack-folder", true, "Folder to install the pack to (defaults to the JAR directory)");
|
|
||||||
options.addOption(null, "meta-file", true, "JSON file to store pack metadata, relative to the pack folder (defaults to packwiz.json)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: link these somehow so they're only defined once?
|
|
||||||
private static void addBootstrapOptions(Options options) {
|
|
||||||
options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates");
|
|
||||||
options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories");
|
|
||||||
options.addOption(null, "bootstrap-no-update", false, "Don't update packwiz-installer");
|
|
||||||
options.addOption(null, "bootstrap-main-jar", true, "Location of the packwiz-installer JAR file");
|
|
||||||
options.addOption("g", "no-gui", false, "Don't display a GUI to show update progress");
|
|
||||||
options.addOption("h", "help", false, "Display this message"); // Implemented in packwiz-installer-bootstrap!
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.request.handlers;
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class RequestHandlerGithub extends RequestHandlerZip {
|
|
||||||
|
|
||||||
public RequestHandlerGithub() {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpaceSafeURI getNewLoc(SpaceSafeURI loc) {
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is caching really needed, if HTTPURLConnection follows redirects correctly?
|
|
||||||
private Map<String, SpaceSafeURI> zipUriMap = new HashMap<>();
|
|
||||||
private final ReentrantReadWriteLock zipUriLock = new ReentrantReadWriteLock();
|
|
||||||
private static Pattern repoMatcherPattern = Pattern.compile("/([\\w.-]+/[\\w.-]+).*");
|
|
||||||
|
|
||||||
private String getRepoName(SpaceSafeURI loc) {
|
|
||||||
Matcher matcher = repoMatcherPattern.matcher(loc.getPath());
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpaceSafeURI getZipUri(SpaceSafeURI loc) throws Exception {
|
|
||||||
String repoName = getRepoName(loc);
|
|
||||||
String branchName = getBranch(loc);
|
|
||||||
zipUriLock.readLock().lock();
|
|
||||||
SpaceSafeURI zipUri = zipUriMap.get(repoName + "/" + branchName);
|
|
||||||
zipUriLock.readLock().unlock();
|
|
||||||
if (zipUri != null) {
|
|
||||||
return zipUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
zipUri = new SpaceSafeURI("https://api.github.com/repos/" + repoName + "/zipball/" + branchName);
|
|
||||||
|
|
||||||
zipUriLock.writeLock().lock();
|
|
||||||
// If another thread sets the value concurrently, use the value of the
|
|
||||||
// thread that first acquired the lock.
|
|
||||||
SpaceSafeURI zipUriInserted = zipUriMap.putIfAbsent(repoName + "/" + branchName, zipUri);
|
|
||||||
if (zipUriInserted != null) {
|
|
||||||
zipUri = zipUriInserted;
|
|
||||||
}
|
|
||||||
zipUriLock.writeLock().unlock();
|
|
||||||
return zipUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pattern branchMatcherPattern = Pattern.compile("/[\\w.-]+/[\\w.-]+/blob/([\\w.-]+).*");
|
|
||||||
|
|
||||||
private String getBranch(SpaceSafeURI loc) {
|
|
||||||
Matcher matcher = branchMatcherPattern.matcher(loc.getPath());
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpaceSafeURI getLocationInZip(SpaceSafeURI loc) throws Exception {
|
|
||||||
String path = "/" + getRepoName(loc) + "/blob/" + getBranch(loc);
|
|
||||||
return new SpaceSafeURI(loc.getScheme(), loc.getAuthority(), path, null, null).relativize(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matchesHandler(SpaceSafeURI loc) {
|
|
||||||
String scheme = loc.getScheme();
|
|
||||||
if (!("http".equals(scheme) || "https".equals(scheme))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!"github.com".equals(loc.getHost())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// TODO: sanity checks, support for more github urls
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.request.handlers;
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
|
||||||
import link.infra.packwiz.installer.request.IRequestHandler;
|
|
||||||
import okio.Okio;
|
|
||||||
import okio.Source;
|
|
||||||
|
|
||||||
import java.net.URLConnection;
|
|
||||||
|
|
||||||
public class RequestHandlerHTTP implements IRequestHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matchesHandler(SpaceSafeURI loc) {
|
|
||||||
String scheme = loc.getScheme();
|
|
||||||
return "http".equals(scheme) || "https".equals(scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Source getFileSource(SpaceSafeURI loc) throws Exception {
|
|
||||||
URLConnection conn = loc.toURL().openConnection();
|
|
||||||
// TODO: when do we send specific headers??? should there be a way to signal this?
|
|
||||||
// github *sometimes* requires it, sometimes not!
|
|
||||||
//conn.addRequestProperty("Accept", "application/octet-stream");
|
|
||||||
// 30 second read timeout
|
|
||||||
conn.setReadTimeout(30 * 1000);
|
|
||||||
return Okio.source(conn.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
package link.infra.packwiz.installer.request.handlers;
|
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
|
||||||
import okio.Buffer;
|
|
||||||
import okio.BufferedSource;
|
|
||||||
import okio.Okio;
|
|
||||||
import okio.Source;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
public abstract class RequestHandlerZip extends RequestHandlerHTTP {
|
|
||||||
|
|
||||||
private final boolean modeHasFolder;
|
|
||||||
|
|
||||||
public RequestHandlerZip(boolean modeHasFolder) {
|
|
||||||
this.modeHasFolder = modeHasFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String removeFolder(String name) {
|
|
||||||
if (modeHasFolder) {
|
|
||||||
return name.substring(name.indexOf("/")+1);
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ZipReader {
|
|
||||||
|
|
||||||
private final ZipInputStream zis;
|
|
||||||
private final Map<SpaceSafeURI, Buffer> readFiles = new HashMap<>();
|
|
||||||
// Write lock implies access to ZipInputStream - only 1 thread must read at a time!
|
|
||||||
final ReentrantLock filesLock = new ReentrantLock();
|
|
||||||
private ZipEntry entry;
|
|
||||||
|
|
||||||
private final BufferedSource zipSource;
|
|
||||||
|
|
||||||
ZipReader(Source zip) {
|
|
||||||
zis = new ZipInputStream(Okio.buffer(zip).inputStream());
|
|
||||||
zipSource = Okio.buffer(Okio.source(zis));
|
|
||||||
}
|
|
||||||
|
|
||||||
// File lock must be obtained before calling this function
|
|
||||||
private Buffer readCurrFile() throws IOException {
|
|
||||||
Buffer fileBuffer = new Buffer();
|
|
||||||
zipSource.readFully(fileBuffer, entry.getSize());
|
|
||||||
return fileBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// File lock must be obtained before calling this function
|
|
||||||
private Buffer findFile(SpaceSafeURI loc) throws IOException, URISyntaxException {
|
|
||||||
while (true) {
|
|
||||||
entry = zis.getNextEntry();
|
|
||||||
if (entry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Buffer data = readCurrFile();
|
|
||||||
SpaceSafeURI fileLoc = new SpaceSafeURI(removeFolder(entry.getName()));
|
|
||||||
if (loc.equals(fileLoc)) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
readFiles.put(fileLoc, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Source getFileSource(SpaceSafeURI loc) throws Exception {
|
|
||||||
filesLock.lock();
|
|
||||||
// Assume files are only read once, allow GC by removing
|
|
||||||
Buffer file = readFiles.remove(loc);
|
|
||||||
if (file != null) {
|
|
||||||
filesLock.unlock();
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
file = findFile(loc);
|
|
||||||
filesLock.unlock();
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpaceSafeURI findInZip(Predicate<SpaceSafeURI> matches) throws Exception {
|
|
||||||
filesLock.lock();
|
|
||||||
for (SpaceSafeURI file : readFiles.keySet()) {
|
|
||||||
if (matches.test(file)) {
|
|
||||||
filesLock.unlock();
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
entry = zis.getNextEntry();
|
|
||||||
if (entry == null) {
|
|
||||||
filesLock.unlock();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Buffer data = readCurrFile();
|
|
||||||
SpaceSafeURI fileLoc = new SpaceSafeURI(removeFolder(entry.getName()));
|
|
||||||
readFiles.put(fileLoc, data);
|
|
||||||
if (matches.test(fileLoc)) {
|
|
||||||
filesLock.unlock();
|
|
||||||
return fileLoc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<SpaceSafeURI, ZipReader> cache = new HashMap<>();
|
|
||||||
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
protected abstract SpaceSafeURI getZipUri(SpaceSafeURI loc) throws Exception;
|
|
||||||
|
|
||||||
protected abstract SpaceSafeURI getLocationInZip(SpaceSafeURI loc) throws Exception;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract boolean matchesHandler(SpaceSafeURI loc);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Source getFileSource(SpaceSafeURI loc) throws Exception {
|
|
||||||
SpaceSafeURI zipUri = getZipUri(loc);
|
|
||||||
cacheLock.readLock().lock();
|
|
||||||
ZipReader zr = cache.get(zipUri);
|
|
||||||
cacheLock.readLock().unlock();
|
|
||||||
if (zr == null) {
|
|
||||||
cacheLock.writeLock().lock();
|
|
||||||
// Recheck, because unlocking read lock allows another thread to modify it
|
|
||||||
zr = cache.get(zipUri);
|
|
||||||
if (zr == null) {
|
|
||||||
Source src = super.getFileSource(zipUri);
|
|
||||||
if (src == null) {
|
|
||||||
cacheLock.writeLock().unlock();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
zr = new ZipReader(src);
|
|
||||||
cache.put(zipUri, zr);
|
|
||||||
}
|
|
||||||
cacheLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return zr.getFileSource(getLocationInZip(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SpaceSafeURI findInZip(SpaceSafeURI loc, Predicate<SpaceSafeURI> matches) throws Exception {
|
|
||||||
SpaceSafeURI zipUri = getZipUri(loc);
|
|
||||||
cacheLock.readLock().lock();
|
|
||||||
ZipReader zr = cache.get(zipUri);
|
|
||||||
cacheLock.readLock().unlock();
|
|
||||||
if (zr == null) {
|
|
||||||
cacheLock.writeLock().lock();
|
|
||||||
// Recheck, because unlocking read lock allows another thread to modify it
|
|
||||||
zr = cache.get(zipUri);
|
|
||||||
if (zr == null) {
|
|
||||||
zr = new ZipReader(super.getFileSource(zipUri));
|
|
||||||
cache.put(zipUri, zr);
|
|
||||||
}
|
|
||||||
cacheLock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return zr.findInZip(matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
123
src/main/kotlin/link/infra/packwiz/installer/Main.kt
Normal file
123
src/main/kotlin/link/infra/packwiz/installer/Main.kt
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package link.infra.packwiz.installer
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
|
import link.infra.packwiz.installer.ui.CLIHandler
|
||||||
|
import link.infra.packwiz.installer.ui.InputStateHandler
|
||||||
|
import link.infra.packwiz.installer.ui.InstallWindow
|
||||||
|
import org.apache.commons.cli.DefaultParser
|
||||||
|
import org.apache.commons.cli.Options
|
||||||
|
import org.apache.commons.cli.ParseException
|
||||||
|
import java.awt.EventQueue
|
||||||
|
import java.awt.GraphicsEnvironment
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
import javax.swing.UIManager
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class Main(args: Array<String>) {
|
||||||
|
private fun startup(args: Array<String>) {
|
||||||
|
val options = Options()
|
||||||
|
addNonBootstrapOptions(options)
|
||||||
|
addBootstrapOptions(options)
|
||||||
|
|
||||||
|
val parser = DefaultParser()
|
||||||
|
val cmd = try {
|
||||||
|
parser.parse(options, args)
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
||||||
|
} catch (e1: Exception) {
|
||||||
|
// Ignore the exceptions, just continue using the ugly L&F
|
||||||
|
}
|
||||||
|
JOptionPane.showMessageDialog(null, e.message, "packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if "headless", GUI creation will fail anyway!
|
||||||
|
val ui = if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) {
|
||||||
|
CLIHandler()
|
||||||
|
} else InstallWindow()
|
||||||
|
|
||||||
|
val unparsedArgs = cmd.args
|
||||||
|
if (unparsedArgs.size > 1) {
|
||||||
|
ui.handleExceptionAndExit(RuntimeException("Too many arguments specified!"))
|
||||||
|
} else if (unparsedArgs.isEmpty()) {
|
||||||
|
ui.handleExceptionAndExit(RuntimeException("URI to install from must be specified!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.getOptionValue("title")?.also {
|
||||||
|
ui.setTitle(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val inputStateHandler = InputStateHandler()
|
||||||
|
ui.show(inputStateHandler)
|
||||||
|
|
||||||
|
val uOptions = UpdateManager.Options().apply {
|
||||||
|
side = cmd.getOptionValue("side")?.let { UpdateManager.Options.Side.from(it) } ?: side
|
||||||
|
packFolder = cmd.getOptionValue("pack-folder") ?: packFolder
|
||||||
|
manifestFile = cmd.getOptionValue("meta-file") ?: manifestFile
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uOptions.downloadURI = SpaceSafeURI(unparsedArgs[0])
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
// TODO: better error message?
|
||||||
|
ui.handleExceptionAndExit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start update process!
|
||||||
|
// TODO: start in SwingWorker?
|
||||||
|
try {
|
||||||
|
ui.executeManager {
|
||||||
|
try {
|
||||||
|
UpdateManager(uOptions, ui, inputStateHandler)
|
||||||
|
} catch (e: Exception) { // TODO: better error message?
|
||||||
|
ui.handleExceptionAndExit(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { // TODO: better error message?
|
||||||
|
ui.handleExceptionAndExit(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Called by packwiz-installer-bootstrap to set up the help command
|
||||||
|
fun addNonBootstrapOptions(options: Options) {
|
||||||
|
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)")
|
||||||
|
options.addOption(null, "title", true, "Title of the installer window")
|
||||||
|
options.addOption(null, "pack-folder", true, "Folder to install the pack to (defaults to the JAR directory)")
|
||||||
|
options.addOption(null, "meta-file", true, "JSON file to store pack metadata, relative to the pack folder (defaults to packwiz.json)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: link these somehow so they're only defined once?
|
||||||
|
private fun addBootstrapOptions(options: Options) {
|
||||||
|
options.addOption(null, "bootstrap-update-url", true, "Github API URL for checking for updates")
|
||||||
|
options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories")
|
||||||
|
options.addOption(null, "bootstrap-no-update", false, "Don't update packwiz-installer")
|
||||||
|
options.addOption(null, "bootstrap-main-jar", true, "Location of the packwiz-installer JAR file")
|
||||||
|
options.addOption("g", "no-gui", false, "Don't display a GUI to show update progress")
|
||||||
|
options.addOption("h", "help", false, "Display this message") // Implemented in packwiz-installer-bootstrap!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual main() is in RequiresBootstrap!
|
||||||
|
init {
|
||||||
|
// Big overarching try/catch just in case everything breaks
|
||||||
|
try {
|
||||||
|
startup(args)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
EventQueue.invokeLater {
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"A fatal error occurred: \n" + e.javaClass.canonicalName + ": " + e.message,
|
||||||
|
"packwiz-installer", JOptionPane.ERROR_MESSAGE)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
// In case the EventQueue is broken, exit after 1 minute
|
||||||
|
Thread.sleep(60 * 1000.toLong())
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,16 @@
|
|||||||
package link.infra.packwiz.installer.request;
|
package link.infra.packwiz.installer.request
|
||||||
|
|
||||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
import okio.Source;
|
import okio.Source
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IRequestHandler handles requests for locations specified in modpack metadata.
|
* IRequestHandler handles requests for locations specified in modpack metadata.
|
||||||
*/
|
*/
|
||||||
public interface IRequestHandler {
|
interface IRequestHandler {
|
||||||
|
fun matchesHandler(loc: SpaceSafeURI): Boolean
|
||||||
|
|
||||||
boolean matchesHandler(SpaceSafeURI loc);
|
fun getNewLoc(loc: SpaceSafeURI): SpaceSafeURI {
|
||||||
|
return loc
|
||||||
default SpaceSafeURI getNewLoc(SpaceSafeURI loc) {
|
|
||||||
return loc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,6 +20,5 @@ public interface IRequestHandler {
|
|||||||
* @return The Source containing the data of the file
|
* @return The Source containing the data of the file
|
||||||
* @throws Exception Exception if it failed to download a file!!!
|
* @throws Exception Exception if it failed to download a file!!!
|
||||||
*/
|
*/
|
||||||
Source getFileSource(SpaceSafeURI loc) throws Exception;
|
fun getFileSource(loc: SpaceSafeURI): Source?
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package link.infra.packwiz.installer.request.handlers
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.concurrent.read
|
||||||
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
|
class RequestHandlerGithub : RequestHandlerZip(true) {
|
||||||
|
override fun getNewLoc(loc: SpaceSafeURI): SpaceSafeURI {
|
||||||
|
return loc
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val repoMatcherPattern = Pattern.compile("/([\\w.-]+/[\\w.-]+).*")
|
||||||
|
private val branchMatcherPattern = Pattern.compile("/[\\w.-]+/[\\w.-]+/blob/([\\w.-]+).*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is caching really needed, if HTTPURLConnection follows redirects correctly?
|
||||||
|
private val zipUriMap: MutableMap<String, SpaceSafeURI> = HashMap()
|
||||||
|
private val zipUriLock = ReentrantReadWriteLock()
|
||||||
|
private fun getRepoName(loc: SpaceSafeURI): String? {
|
||||||
|
val matcher = repoMatcherPattern.matcher(loc.path)
|
||||||
|
return if (matcher.matches()) {
|
||||||
|
matcher.group(1)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getZipUri(loc: SpaceSafeURI): SpaceSafeURI {
|
||||||
|
val repoName = getRepoName(loc)
|
||||||
|
val branchName = getBranch(loc)
|
||||||
|
|
||||||
|
zipUriLock.read {
|
||||||
|
zipUriMap["$repoName/$branchName"]
|
||||||
|
}?.let { return it }
|
||||||
|
|
||||||
|
var zipUri = SpaceSafeURI("https://api.github.com/repos/$repoName/zipball/$branchName")
|
||||||
|
zipUriLock.write {
|
||||||
|
// If another thread sets the value concurrently, use the existing value from the
|
||||||
|
// thread that first acquired the lock.
|
||||||
|
zipUri = zipUriMap.putIfAbsent("$repoName/$branchName", zipUri) ?: zipUri
|
||||||
|
}
|
||||||
|
return zipUri
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBranch(loc: SpaceSafeURI): String? {
|
||||||
|
val matcher = branchMatcherPattern.matcher(loc.path)
|
||||||
|
return if (matcher.matches()) {
|
||||||
|
matcher.group(1)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLocationInZip(loc: SpaceSafeURI): SpaceSafeURI {
|
||||||
|
val path = "/" + getRepoName(loc) + "/blob/" + getBranch(loc)
|
||||||
|
return SpaceSafeURI(loc.scheme, loc.authority, path, null, null).relativize(loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchesHandler(loc: SpaceSafeURI): Boolean {
|
||||||
|
val scheme = loc.scheme
|
||||||
|
if (!("http" == scheme || "https" == scheme)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return "github.com" == loc.host
|
||||||
|
// TODO: sanity checks, support for more github urls
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package link.infra.packwiz.installer.request.handlers
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
|
import link.infra.packwiz.installer.request.IRequestHandler
|
||||||
|
import okio.Source
|
||||||
|
import okio.source
|
||||||
|
|
||||||
|
open class RequestHandlerHTTP : IRequestHandler {
|
||||||
|
override fun matchesHandler(loc: SpaceSafeURI): Boolean {
|
||||||
|
val scheme = loc.scheme
|
||||||
|
return "http" == scheme || "https" == scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFileSource(loc: SpaceSafeURI): Source? {
|
||||||
|
val conn = loc.toURL().openConnection()
|
||||||
|
// TODO: when do we send specific headers??? should there be a way to signal this?
|
||||||
|
// github *sometimes* requires it, sometimes not!
|
||||||
|
//conn.addRequestProperty("Accept", "application/octet-stream");
|
||||||
|
conn.apply {
|
||||||
|
// 30 second read timeout
|
||||||
|
readTimeout = 30 * 1000
|
||||||
|
}
|
||||||
|
return conn.getInputStream().source()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package link.infra.packwiz.installer.request.handlers
|
||||||
|
|
||||||
|
import link.infra.packwiz.installer.metadata.SpaceSafeURI
|
||||||
|
import okio.Buffer
|
||||||
|
import okio.Source
|
||||||
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import java.util.function.Predicate
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
import kotlin.concurrent.read
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
|
abstract class RequestHandlerZip(private val modeHasFolder: Boolean) : RequestHandlerHTTP() {
|
||||||
|
private fun removeFolder(name: String): String {
|
||||||
|
return if (modeHasFolder) {
|
||||||
|
// TODO: replace with proper path checks once switched to Path??
|
||||||
|
name.substring(name.indexOf("/") + 1)
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ZipReader internal constructor(zip: Source) {
|
||||||
|
private val zis = ZipInputStream(zip.buffer().inputStream())
|
||||||
|
private val readFiles: MutableMap<SpaceSafeURI, Buffer> = HashMap()
|
||||||
|
// Write lock implies access to ZipInputStream - only 1 thread must read at a time!
|
||||||
|
val filesLock = ReentrantLock()
|
||||||
|
private var entry: ZipEntry? = null
|
||||||
|
|
||||||
|
private val zipSource = zis.source().buffer()
|
||||||
|
|
||||||
|
// File lock must be obtained before calling this function
|
||||||
|
private fun readCurrFile(): Buffer {
|
||||||
|
val fileBuffer = Buffer()
|
||||||
|
zipSource.readFully(fileBuffer, entry!!.size)
|
||||||
|
return fileBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// File lock must be obtained before calling this function
|
||||||
|
private fun findFile(loc: SpaceSafeURI): Buffer? {
|
||||||
|
while (true) {
|
||||||
|
entry = zis.nextEntry
|
||||||
|
entry?.also {
|
||||||
|
val data = readCurrFile()
|
||||||
|
val fileLoc = SpaceSafeURI(removeFolder(it.name))
|
||||||
|
if (loc == fileLoc) {
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
readFiles[fileLoc] = data
|
||||||
|
}
|
||||||
|
} ?: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileSource(loc: SpaceSafeURI): Source? {
|
||||||
|
filesLock.withLock {
|
||||||
|
// Assume files are only read once, allow GC by removing
|
||||||
|
readFiles.remove(loc)?.also { return it }
|
||||||
|
return findFile(loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findInZip(matches: Predicate<SpaceSafeURI>): SpaceSafeURI? {
|
||||||
|
filesLock.withLock {
|
||||||
|
readFiles.keys.find { matches.test(it) }?.let { return it }
|
||||||
|
|
||||||
|
do {
|
||||||
|
val entry = zis.nextEntry?.also {
|
||||||
|
val data = readCurrFile()
|
||||||
|
val fileLoc = SpaceSafeURI(removeFolder(it.name))
|
||||||
|
readFiles[fileLoc] = data
|
||||||
|
if (matches.test(fileLoc)) {
|
||||||
|
return fileLoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (entry != null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cache: MutableMap<SpaceSafeURI, ZipReader> = HashMap()
|
||||||
|
private val cacheLock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
|
protected abstract fun getZipUri(loc: SpaceSafeURI): SpaceSafeURI
|
||||||
|
protected abstract fun getLocationInZip(loc: SpaceSafeURI): SpaceSafeURI
|
||||||
|
abstract override fun matchesHandler(loc: SpaceSafeURI): Boolean
|
||||||
|
|
||||||
|
override fun getFileSource(loc: SpaceSafeURI): Source? {
|
||||||
|
val zipUri = getZipUri(loc)
|
||||||
|
var zr = cacheLock.read { cache[zipUri] }
|
||||||
|
if (zr == null) {
|
||||||
|
cacheLock.write {
|
||||||
|
// Recheck, because unlocking read lock allows another thread to modify it
|
||||||
|
zr = cache[zipUri]
|
||||||
|
|
||||||
|
if (zr == null) {
|
||||||
|
val src = super.getFileSource(zipUri) ?: return null
|
||||||
|
zr = ZipReader(src).also { cache[zipUri] = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zr?.getFileSource(getLocationInZip(loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun findInZip(loc: SpaceSafeURI, matches: Predicate<SpaceSafeURI>): SpaceSafeURI? {
|
||||||
|
val zipUri = getZipUri(loc)
|
||||||
|
return (cacheLock.read { cache[zipUri] } ?: cacheLock.write {
|
||||||
|
// Recheck, because unlocking read lock allows another thread to modify it
|
||||||
|
cache[zipUri] ?: run {
|
||||||
|
// Create the ZipReader if it doesn't exist, return null if getFileSource returns null
|
||||||
|
super.getFileSource(zipUri)?.let { ZipReader(it) }
|
||||||
|
?.also { cache[zipUri] = it }
|
||||||
|
}
|
||||||
|
})?.findInZip(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user