1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2025-02-26 03:21:51 +01:00

Basic plugin loading framework

This commit is contained in:
Henry Le Grys 2021-01-25 23:57:33 +00:00
parent 8612b951e1
commit be299164de
11 changed files with 251 additions and 15 deletions

View File

@ -14,27 +14,42 @@ import com.skcraft.launcher.creator.dialog.WelcomeDialog;
import com.skcraft.launcher.creator.model.creator.CreatorConfig;
import com.skcraft.launcher.creator.model.creator.RecentEntry;
import com.skcraft.launcher.creator.model.creator.Workspace;
import com.skcraft.launcher.creator.plugin.CreatorPluginLoader;
import com.skcraft.launcher.creator.plugin.CreatorToolsPlugin;
import com.skcraft.launcher.persistence.Persistence;
import com.skcraft.launcher.swing.SwingHelper;
import lombok.Getter;
import lombok.extern.java.Log;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
@Log
public class Creator {
@Getter private final File dataDir;
@Getter private final CreatorConfig config;
@Getter private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
@Getter private final List<CreatorToolsPlugin> plugins;
public Creator() {
this.dataDir = getAppDataDir();
this.config = Persistence.load(new File(dataDir, "config.json"), CreatorConfig.class);
// Load plugins
CreatorPluginLoader pluginLoader = new CreatorPluginLoader();
try {
pluginLoader.walk(new File(dataDir, "plugins"));
} catch (IOException e) {
log.severe("Could not walk plugin directory, plugins have not been loaded");
}
this.plugins = pluginLoader.loadAll();
// Remove deleted workspaces
List<RecentEntry> recentEntries = config.getRecentEntries();
Iterator<RecentEntry> it = recentEntries.iterator();

View File

@ -791,7 +791,8 @@ public class PackManagerController {
String version = generateVersionFromDate();
PackBuilder builder = new PackBuilder(pack, webRoot, version, "staging.json", false, false);
PackBuilder builder = new PackBuilder(creator, pack, webRoot, version, "staging.json",
false, false);
InstanceList.Enumerator enumerator = launcher.getInstances().createEnumerator();
TestLauncher instanceLauncher = new TestLauncher(launcher, frame, pack.getCachedConfig().getName(), session);
@ -816,7 +817,8 @@ public class PackManagerController {
if (options != null) {
ConsoleFrame.showMessages();
PackBuilder builder = new PackBuilder(pack, options.getDestDir(), options.getVersion(), options.getManifestFilename(), false, true);
PackBuilder builder = new PackBuilder(creator, pack, options.getDestDir(), options.getVersion(),
options.getManifestFilename(), false, true);
Deferred<?> deferred = Deferreds.makeDeferred(executor.submit(builder), executor)
.handleAsync(result -> {
ConsoleFrame.hideMessages();

View File

@ -6,11 +6,15 @@
package com.skcraft.launcher.creator.controller.task;
import com.beust.jcommander.internal.Lists;
import com.skcraft.concurrency.ProgressObservable;
import com.skcraft.launcher.LauncherException;
import com.skcraft.launcher.LauncherUtils;
import com.skcraft.launcher.builder.PackageBuilder;
import com.skcraft.launcher.creator.Creator;
import com.skcraft.launcher.creator.model.creator.Pack;
import com.skcraft.launcher.creator.plugin.CreatorToolsPlugin;
import lombok.RequiredArgsConstructor;
import java.io.File;
import java.io.IOException;
@ -18,8 +22,10 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
@RequiredArgsConstructor
public class PackBuilder implements Callable<PackBuilder>, ProgressObservable {
private final Creator creator;
private final Pack pack;
private final File outputDir;
private final String version;
@ -27,15 +33,6 @@ public class PackBuilder implements Callable<PackBuilder>, ProgressObservable {
private final boolean clean;
private final boolean downloadUrls;
public PackBuilder(Pack pack, File outputDir, String version, String manifestFilename, boolean clean, boolean downloadUrls) {
this.pack = pack;
this.outputDir = outputDir;
this.version = version;
this.manifestFilename = manifestFilename;
this.clean = clean;
this.downloadUrls = downloadUrls;
}
@Override
public PackBuilder call() throws Exception {
if (clean) {
@ -57,13 +54,21 @@ public class PackBuilder implements Callable<PackBuilder>, ProgressObservable {
outputDir.mkdirs();
System.setProperty("com.skcraft.builder.ignoreURLOverrides", downloadUrls ? "false" : "true");
String[] args = {
List<String> args = Lists.newArrayList(
"--version", version,
"--manifest-dest", new File(outputDir, manifestFilename).getAbsolutePath(),
"-i", pack.getDirectory().getAbsolutePath(),
"-o", outputDir.getAbsolutePath()
};
PackageBuilder.main(args);
);
for (CreatorToolsPlugin plugin : creator.getPlugins()) {
if (plugin.getBuilderPlugin() != null) {
args.add("--plugin");
args.add(plugin.getBuilderPlugin().getCanonicalName());
}
}
PackageBuilder.main(args.toArray(new String[0]));
return this;
}

View File

@ -0,0 +1,11 @@
package com.skcraft.launcher.creator.plugin;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CreatorPluginInfo {
private String id;
private String pluginClass;
}

View File

@ -0,0 +1,86 @@
package com.skcraft.launcher.creator.plugin;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.skcraft.launcher.builder.BuilderUtils;
import com.skcraft.launcher.builder.DirectoryWalker;
import lombok.Data;
import lombok.extern.java.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
@Log
public class CreatorPluginLoader extends DirectoryWalker {
private static final ObjectMapper mapper = new ObjectMapper();
private List<PluginCandidate> candidates = new ArrayList<>();
@Override
protected void onFile(File file, String relPath) throws IOException {
JarFile jarFile = new JarFile(file);
ZipEntry metaEntry = jarFile.getEntry("skcraftcreator.plugin.json");
if (metaEntry != null) {
InputStreamReader reader = new InputStreamReader(jarFile.getInputStream(metaEntry));
CreatorPluginInfo pluginInfo = mapper.readValue(BuilderUtils.readStringFromStream(reader),
CreatorPluginInfo.class);
log.info("Found plugin " + pluginInfo.getId());
candidates.add(new PluginCandidate(pluginInfo, file.toURI().toURL()));
}
}
public List<CreatorToolsPlugin> loadAll() {
URLClassLoader pluginClassLoader = new URLClassLoader(
candidates.stream().map(PluginCandidate::getJarUrl).toArray(URL[]::new),
this.getClass().getClassLoader()
);
return candidates.stream()
.map(candidate -> loadPlugin(pluginClassLoader, candidate))
.collect(Collectors.toList());
}
private <T extends CreatorToolsPlugin> CreatorToolsPlugin loadPlugin(URLClassLoader classLoader, PluginCandidate candidate) {
try {
Class<T> pluginClass = (Class<T>) classLoader.loadClass(candidate.getInfo().getPluginClass());
return pluginClass.getConstructor().newInstance();
} catch (ClassNotFoundException e) {
log.warning(candidate.format("Could not find plugin class %s for plugin %s"));
} catch (ClassCastException e) {
log.warning(candidate.format("Plugin main class %s (from plugin '%s') does not extend CreatorToolsPlugin!"));
} catch (NoSuchMethodException e) {
log.warning(candidate.format("Could not find constructor for class %s (of plugin '%s')!"));
} catch (InstantiationException e) {
log.warning(candidate.format("Could not instantiate class %s (from plugin '%s')!"));
e.printStackTrace();
} catch (InvocationTargetException e) {
log.warning(candidate.format("Error while initializing main class %s (from plugin '%s')!"));
e.printStackTrace();
} catch (IllegalAccessException e) {
log.warning(candidate.format("Could not access constructor for class %s (of plugin '%s')!"));
e.printStackTrace();
}
return null;
}
@Data
static class PluginCandidate {
private final CreatorPluginInfo info;
private final URL jarUrl;
public String format(String format) {
return String.format(format, info.getPluginClass(), info.getId());
}
}
}

View File

@ -0,0 +1,9 @@
package com.skcraft.launcher.creator.plugin;
import com.skcraft.launcher.builder.plugin.BuilderPlugin;
public abstract class CreatorToolsPlugin {
public Class<? extends BuilderPlugin> getBuilderPlugin() {
return null;
}
}

View File

@ -11,6 +11,7 @@ import com.beust.jcommander.ParameterException;
import lombok.Data;
import java.io.File;
import java.util.List;
@Data
public class BuilderOptions {
@ -67,6 +68,10 @@ public class BuilderOptions {
@Parameter(names = "--pretty-print")
private boolean prettyPrinting;
// Plugins
@Parameter(names = "--plugin-class")
private List<String> pluginClasses;
public void choosePaths() throws ParameterException {
if (configPath == null) {
requireInputPath("--config");

View File

@ -20,6 +20,8 @@ import com.google.common.io.Files;
import com.skcraft.launcher.Launcher;
import com.skcraft.launcher.LauncherUtils;
import com.skcraft.launcher.builder.loaders.*;
import com.skcraft.launcher.builder.plugin.Builder;
import com.skcraft.launcher.builder.plugin.BuilderPluginLoader;
import com.skcraft.launcher.model.loader.BasicInstallProfile;
import com.skcraft.launcher.model.minecraft.Library;
import com.skcraft.launcher.model.minecraft.ReleaseList;
@ -51,10 +53,11 @@ import static com.skcraft.launcher.util.HttpRequest.url;
* Builds packages for the launcher.
*/
@Log
public class PackageBuilder {
public class PackageBuilder implements Builder {
private final Properties properties;
private final ObjectMapper mapper;
private ObjectWriter writer;
@Getter
private final Manifest manifest;
private final PropertiesApplicator applicator;
@Getter
@ -409,6 +412,11 @@ public class PackageBuilder {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
// Load plugins
BuilderPluginLoader pluginLoader = new BuilderPluginLoader();
pluginLoader.loadClasses(options.getPluginClasses());
pluginLoader.dispatchAcceptOptions(args);
Manifest manifest = new Manifest();
manifest.setMinimumVersion(Manifest.MIN_PROTOCOL_VERSION);
PackageBuilder builder = new PackageBuilder(mapper, manifest);
@ -427,10 +435,15 @@ public class PackageBuilder {
manifest.setLibrariesLocation(options.getLibrariesLocation());
manifest.setObjectsLocation(options.getObjectsLocation());
pluginLoader.dispatchManifestCreated(manifest);
builder.scan(options.getFilesDir());
builder.addFiles(options.getFilesDir(), options.getObjectsDir());
builder.addLoaders(options.getLoadersDir(), options.getLibrariesDir());
builder.downloadLibraries(options.getLibrariesDir());
pluginLoader.dispatchBuilderFinished(builder);
builder.writeManifest(options.getManifestPath());
logSection("Done");

View File

@ -0,0 +1,10 @@
package com.skcraft.launcher.builder.plugin;
import com.skcraft.launcher.model.modpack.Manifest;
/**
* Builder passed to plugins to allow restricted access
*/
public interface Builder {
Manifest getManifest();
}

View File

@ -0,0 +1,12 @@
package com.skcraft.launcher.builder.plugin;
import com.skcraft.launcher.model.modpack.Manifest;
/**
* Class that all builder plugins should extend.
*/
public abstract class BuilderPlugin {
public void acceptOptions(String[] args) {}
public void onManifestCreated(Manifest manifest) {}
public void onBuilderFinished(Builder builder) {}
}

View File

@ -0,0 +1,68 @@
package com.skcraft.launcher.builder.plugin;
import com.google.common.collect.Lists;
import com.skcraft.launcher.model.modpack.Manifest;
import lombok.extern.java.Log;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
@Log
public class BuilderPluginLoader {
private List<BuilderPlugin> loadedPlugins = Lists.newArrayList();
public void loadClasses(List<String> classNames) {
for (String pluginClassName : classNames) {
try {
BuilderPlugin plugin = loadClass(pluginClassName);
if (plugin != null) {
loadedPlugins.add(plugin);
}
} catch (ClassNotFoundException e) {
log.severe("Failed to load plugin " + pluginClassName + ", is it on the classpath?");
}
}
}
private <T extends BuilderPlugin> T loadClass(String pluginClassName) throws ClassNotFoundException {
try {
Class<T> pluginClass = (Class<T>) getClass().getClassLoader().loadClass(pluginClassName);
return pluginClass.getConstructor().newInstance();
} catch (InstantiationException e) {
log.warning("Plugin class " + pluginClassName + " could not be created!");
e.printStackTrace();
} catch (IllegalAccessException e) {
log.warning("Plugin class " + pluginClassName + " could not be accessed!");
e.printStackTrace();
} catch (InvocationTargetException e) {
log.warning("Plugin class " + pluginClassName + " threw an error while initializing!");
e.printStackTrace();
} catch (NoSuchMethodException e) {
log.warning("Plugin class " + pluginClassName + " is missing a primary constructor!");
} catch (ClassCastException e) {
log.warning("Plugin class" + pluginClassName + " does not extend BuilderPlugin!");
}
return null;
}
public void dispatchAcceptOptions(String[] args) {
for (BuilderPlugin plugin : loadedPlugins) {
plugin.acceptOptions(args);
}
}
public void dispatchManifestCreated(Manifest manifest) {
for (BuilderPlugin plugin : loadedPlugins) {
plugin.onManifestCreated(manifest);
}
}
public void dispatchBuilderFinished(Builder builder) {
for (BuilderPlugin plugin : loadedPlugins) {
plugin.onBuilderFinished(builder);
}
}
}