/* * SK's Minecraft Launcher * Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors * Please see LICENSE.txt for license information. */ package com.skcraft.launcher.buildtools; import com.beust.jcommander.JCommander; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter.Lf2SpacesIndenter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.skcraft.concurrency.ObservableFuture; import com.skcraft.launcher.Instance; import com.skcraft.launcher.InstanceList; import com.skcraft.launcher.Launcher; import com.skcraft.launcher.auth.OfflineSession; import com.skcraft.launcher.auth.Session; import com.skcraft.launcher.builder.BuilderConfig; import com.skcraft.launcher.builder.BuilderOptions; import com.skcraft.launcher.builder.FnPatternList; import com.skcraft.launcher.buildtools.build.*; import com.skcraft.launcher.buildtools.build.BuildDialog.BuildOptions; import com.skcraft.launcher.buildtools.project.BuilderConfigDialog; import com.skcraft.launcher.buildtools.http.LocalHttpServerBuilder; import com.skcraft.launcher.buildtools.build.DeployServerDialog.DeployOptions; import com.skcraft.launcher.dialog.ConfigurationDialog; import com.skcraft.launcher.dialog.ConsoleFrame; import com.skcraft.launcher.dialog.ProgressDialog; import com.skcraft.launcher.launch.LaunchOptions; import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy; import com.skcraft.launcher.model.modpack.LaunchModifier; import com.skcraft.launcher.persistence.Persistence; import com.skcraft.launcher.swing.SwingHelper; import lombok.Getter; import lombok.extern.java.Log; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.regex.Pattern; @Log public class BuildTools { private static final DateFormat VERSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); private static final Pattern FILENAME_SANITIZE = Pattern.compile("[^a-z0-9_\\-\\.]+"); private static final DefaultPrettyPrinter lf2ListPrettyPrinter; private final Launcher launcher; @Getter private int port; @Getter private final File inputDir; @Getter private final File srcDir; @Getter private final File wwwDir; @Getter private final File distDir; static { lf2ListPrettyPrinter = new DefaultPrettyPrinter(); lf2ListPrettyPrinter.indentArraysWith(Lf2SpacesIndenter.instance); } @SuppressWarnings("ResultOfMethodCallIgnored") public BuildTools(File baseDir, int port) throws IOException { File launcherDir = new File(baseDir, "staging/launcher"); inputDir = baseDir; srcDir = new File(baseDir, BuilderOptions.DEFAULT_SRC_DIRNAME); wwwDir = new File(baseDir, "staging/www"); distDir = new File(baseDir, "upload"); this.port = port; srcDir.mkdirs(); new File(baseDir, BuilderOptions.DEFAULT_LOADERS_DIRNAME).mkdir(); launcherDir.mkdirs(); wwwDir.mkdirs(); launcher = new Launcher(launcherDir); setPort(port); } private void setPort(int port) { this.port = port; launcher.getProperties().setProperty("newsUrl", "http://localhost:" + port + "/news.html"); launcher.getProperties().setProperty("packageListUrl", "http://localhost:" + port + "/packages.json"); launcher.getProperties().setProperty("selfUpdateUrl", "http://localhost:" + port + "/latest.json"); } public File getConfigFile() { return new File(inputDir, BuilderOptions.DEFAULT_CONFIG_FILENAME); } public String generateManifestName() { File file = getConfigFile(); if (file.exists()) { BuilderConfig config = Persistence.read(file, BuilderConfig.class, true); if (config != null) { String name = Strings.nullToEmpty(config.getName()); name = name.toLowerCase(); name = FILENAME_SANITIZE.matcher(name).replaceAll("-"); name = name.trim(); if (!name.isEmpty()) { return name + ".json"; } } } return "my_modpack.json"; } public String getCurrentModpackName() { File file = getConfigFile(); if (file.exists()) { BuilderConfig config = Persistence.read(file, BuilderConfig.class, true); if (config != null) { return config.getName(); } } return null; } public Instance findCurrentInstance(List<Instance> instances) { String expected = getCurrentModpackName(); for (Instance instance : instances) { if (instance.getName().equals(expected)) { return instance; } } return null; } private void addDefaultConfig(BuilderConfig config) { config.setName("My Modpack"); config.setTitle("My Modpack"); config.setGameVersion("1.8"); LaunchModifier launchModifier = new LaunchModifier(); launchModifier.setFlags(ImmutableList.of("-Dfml.ignoreInvalidMinecraftCertificates=true")); config.setLaunchModifier(launchModifier); FnPatternList userFiles = new FnPatternList(); userFiles.setInclude(Lists.newArrayList("options.txt", "optionsshaders.txt", "mods/VoxelMods/*")); userFiles.setExclude(Lists.<String>newArrayList()); config.setUserFiles(userFiles); } public Server startHttpServer() throws Exception { LocalHttpServerBuilder builder = new LocalHttpServerBuilder(); builder.setBaseDir(wwwDir); builder.setPort(port); Server server = builder.build(); server.start(); setPort(((ServerConnector) server.getConnectors()[0]).getLocalPort()); return server; } private void showMainWindow() { final ToolsFrame frame = new ToolsFrame(); frame.getEditConfigButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { File file = getConfigFile(); boolean existed = file.exists(); BuilderConfig config = Persistence.read(file, BuilderConfig.class); if (!existed) { addDefaultConfig(config); } if (BuilderConfigDialog.showEditor(frame, config)) { try { Persistence.write(file, config, lf2ListPrettyPrinter); } catch (IOException e) { SwingHelper.showErrorDialog(frame, "Couldn't write modpack.json to disk due to an error", "Write Error", e); } } } }); frame.getOpenFolderButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { SwingHelper.browseDir(getInputDir(), frame); } }); frame.getCheckProblemsButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ProblemChecker runnable = new ProblemChecker(BuildTools.this); ObservableFuture<List<Problem>> future = new ObservableFuture<List<Problem>>(launcher.getExecutor().submit(runnable), runnable); ProgressDialog.showProgress(frame, future, "Checking for problems...", "Checking for problems..."); Futures.addCallback(future, new FutureCallback<List<Problem>>() { @Override public void onSuccess(List<Problem> problems) { if (problems.isEmpty()) { SwingHelper.showMessageDialog(frame, "No potential problems found!", "Success", null, JOptionPane.INFORMATION_MESSAGE); } else { ProblemViewer viewer = new ProblemViewer(frame, problems); viewer.setVisible(true); } } @Override public void onFailure(Throwable t) { } }); SwingHelper.addErrorDialogCallback(frame, future); } }); frame.getBuildButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final BuildOptions options = BuildDialog.showBuildDialog(frame, generateVersionFromDate(), generateManifestName(), distDir); if (options != null) { ConsoleFrame.showMessages(); options.getDestDir().mkdirs(); ModpackBuilder runnable = new ModpackBuilder(inputDir, options.getDestDir(), options.getVersion(), options.getManifestFilename(), options.isClean()); ObservableFuture<ModpackBuilder> future = new ObservableFuture<ModpackBuilder>(launcher.getExecutor().submit(runnable), runnable); ProgressDialog.showProgress(frame, future, "Building modpack...", "Building modpack for release..."); Futures.addCallback(future, new FutureCallback<ModpackBuilder>() { @Override public void onSuccess(ModpackBuilder result) { SwingHelper.browseDir(options.getDestDir(), frame); } @Override public void onFailure(Throwable t) { } }); SwingHelper.addErrorDialogCallback(frame, future); } } }); frame.getDeployServerButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final DeployOptions options = DeployServerDialog.showDeployDialog(frame); if (options != null) { ConsoleFrame.showMessages(); distDir.mkdirs(); ServerDeployer runnable = new ServerDeployer(srcDir, options); ObservableFuture<ServerDeployer> future = new ObservableFuture<ServerDeployer>(launcher.getExecutor().submit(runnable), runnable); ProgressDialog.showProgress(frame, future, "Deploying files...", "Deploying server files..."); Futures.addCallback(future, new FutureCallback<ServerDeployer>() { @Override public void onSuccess(ServerDeployer result) { SwingHelper.showMessageDialog(frame, "Server deployment complete!", "Success", null, JOptionPane.INFORMATION_MESSAGE); } @Override public void onFailure(Throwable t) { } }); SwingHelper.addErrorDialogCallback(frame, future); } } }); frame.getTestButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ConsoleFrame.showMessages(); ModpackBuilder runnable = new ModpackBuilder(inputDir, wwwDir, generateVersionFromDate(), "staging.json", false); ObservableFuture<ModpackBuilder> future = new ObservableFuture<ModpackBuilder>(launcher.getExecutor().submit(runnable), runnable); ProgressDialog.showProgress(frame, future, "Preparing files...", "Preparing files for launch..."); SwingHelper.addErrorDialogCallback(frame, future); Futures.addCallback(future, new FutureCallback<ModpackBuilder>() { @Override public void onSuccess(ModpackBuilder result) { launchInstance(frame); ConsoleFrame.hideMessages(); } @Override public void onFailure(Throwable t) { } }); } }); frame.getOptionsButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ConfigurationDialog configDialog = new ConfigurationDialog(frame, launcher); configDialog.setVisible(true); } }); frame.getClearInstanceButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DirectoryRemover remover = new DirectoryRemover(launcher.getInstancesDir()); ObservableFuture<DirectoryRemover> future = new ObservableFuture<DirectoryRemover>(launcher.getExecutor().submit(remover), remover); ProgressDialog.showProgress(frame, future, "Removing files...", "Removing files..."); SwingHelper.addErrorDialogCallback(frame, future); } }); frame.getClearWebRootButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DirectoryRemover remover = new DirectoryRemover(wwwDir); ObservableFuture<DirectoryRemover> future = new ObservableFuture<DirectoryRemover>(launcher.getExecutor().submit(remover), remover); ProgressDialog.showProgress(frame, future, "Removing files...", "Removing files..."); SwingHelper.addErrorDialogCallback(frame, future); } }); frame.getOpenConsoleButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ConsoleFrame.showMessages(); } }); frame.getDocsButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", frame); } }); frame.getQuitButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { frame.dispose(); System.exit(0); } }); frame.setVisible(true); } private void launchInstance(final Window window) { final InstanceList instanceList = launcher.getInstances(); InstanceList.Enumerator loader = instanceList.createEnumerator(); ObservableFuture<InstanceList> future = new ObservableFuture<InstanceList>(launcher.getExecutor().submit(loader), loader); ProgressDialog.showProgress(window, future, "Loading modpacks...", "Loading modpacks..."); SwingHelper.addErrorDialogCallback(window, future); Futures.addCallback(future, new FutureCallback<InstanceList>() { @Override public void onSuccess(InstanceList result) { Session session = new OfflineSession("Player"); Instance instance = findCurrentInstance(instanceList.getInstances()); if (instance != null) { LaunchOptions options = new LaunchOptions.Builder() .setInstance(instance) .setUpdatePolicy(UpdatePolicy.ALWAYS_UPDATE) .setWindow(window) .setSession(session) .build(); launcher.getLaunchSupervisor().launch(options); } else { SwingHelper.showErrorDialog(window, "After generating the necessary files, it appears the modpack can't be found in the " + "launcher. Did you change modpack.json while the launcher was launching?", "Launch Error"); } } @Override public void onFailure(Throwable t) { } }); } public static void main(String[] args) throws Exception { Launcher.setupLogger(); System.setProperty("skcraftLauncher.killWithoutConfirm", "true"); ToolArguments options = new ToolArguments(); new JCommander(options, args); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { UIManager.getDefaults().put("SplitPane.border", BorderFactory.createEmptyBorder()); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignored) { } } }); File inputDir = new ProjectDirectoryChooser(options.getDir()).choose(); if (inputDir == null) { System.exit(100); return; } final BuildTools main = new BuildTools(inputDir, options.getPort()); try { main.startHttpServer(); } catch (Throwable t) { log.log(Level.WARNING, "Web server start failure", t); SwingHelper.showErrorDialog(null, "Couldn't start the local web server on a free TCP port! " + "The web server is required to temporarily host the modpack files for the launcher.", "Build Tools Error", t); System.exit(1); return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { main.showMainWindow(); } catch (Throwable t) { log.log(Level.WARNING, "Load failure", t); SwingHelper.showErrorDialog(null, "Failed to launch build tools!", "Build Tools Error", t); } } }); } public static String generateVersionFromDate() { Date today = Calendar.getInstance().getTime(); return VERSION_DATE_FORMAT.format(today); } }